diff --git a/server/composer.json b/server/composer.json
new file mode 100644
index 0000000..1844528
--- /dev/null
+++ b/server/composer.json
@@ -0,0 +1,6 @@
+{
+ "require": {
+ "fabpot/goutte": "^4.0",
+ "clio/clio": "^0.1.8"
+ }
+}
diff --git a/server/composer.lock b/server/composer.lock
new file mode 100644
index 0000000..e5654c1
--- /dev/null
+++ b/server/composer.lock
@@ -0,0 +1,1307 @@
+{
+ "_readme": [
+ "This file locks the dependencies of your project to a known state",
+ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
+ "This file is @generated automatically"
+ ],
+ "content-hash": "60aa2585bcce885bae7ab3a59a70ba3f",
+ "packages": [
+ {
+ "name": "clio/clio",
+ "version": "0.1.8",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/nramenta/clio.git",
+ "reference": "6a9f600b73fa213e817d5c1e67a4641aecd156ef"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/nramenta/clio/zipball/6a9f600b73fa213e817d5c1e67a4641aecd156ef",
+ "reference": "6a9f600b73fa213e817d5c1e67a4641aecd156ef",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.2"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-0": {
+ "Clio": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nofriandi Ramenta",
+ "email": "nramenta@gmail.com"
+ }
+ ],
+ "description": "Lightweight utility and helper classes for CLI applications",
+ "keywords": [
+ "cli",
+ "console",
+ "daemon"
+ ],
+ "support": {
+ "issues": "https://github.com/nramenta/clio/issues",
+ "source": "https://github.com/nramenta/clio/tree/0.1.8"
+ },
+ "time": "2014-06-22T14:49:41+00:00"
+ },
+ {
+ "name": "fabpot/goutte",
+ "version": "v4.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/FriendsOfPHP/Goutte.git",
+ "reference": "e3f28671c87a48a0f13ada1baea0d95acc2138c3"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/FriendsOfPHP/Goutte/zipball/e3f28671c87a48a0f13ada1baea0d95acc2138c3",
+ "reference": "e3f28671c87a48a0f13ada1baea0d95acc2138c3",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1.3",
+ "symfony/browser-kit": "^4.4|^5.0|^6.0",
+ "symfony/css-selector": "^4.4|^5.0|^6.0",
+ "symfony/deprecation-contracts": "^2.1|^3",
+ "symfony/dom-crawler": "^4.4|^5.0|^6.0",
+ "symfony/http-client": "^4.4|^5.0|^6.0",
+ "symfony/mime": "^4.4|^5.0|^6.0"
+ },
+ "require-dev": {
+ "symfony/phpunit-bridge": "^6.0"
+ },
+ "type": "application",
+ "autoload": {
+ "psr-4": {
+ "Goutte\\": "Goutte"
+ },
+ "exclude-from-classmap": [
+ "Goutte/Tests"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ }
+ ],
+ "description": "A simple PHP Web Scraper",
+ "homepage": "https://github.com/FriendsOfPHP/Goutte",
+ "keywords": [
+ "scraper"
+ ],
+ "support": {
+ "issues": "https://github.com/FriendsOfPHP/Goutte/issues",
+ "source": "https://github.com/FriendsOfPHP/Goutte/tree/v4.0.3"
+ },
+ "abandoned": "symfony/browser-kit",
+ "time": "2023-04-01T09:05:33+00:00"
+ },
+ {
+ "name": "masterminds/html5",
+ "version": "2.8.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Masterminds/html5-php.git",
+ "reference": "f47dcf3c70c584de14f21143c55d9939631bc6cf"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/f47dcf3c70c584de14f21143c55d9939631bc6cf",
+ "reference": "f47dcf3c70c584de14f21143c55d9939631bc6cf",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "php": ">=5.3.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^8"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.7-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Masterminds\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Matt Butcher",
+ "email": "technosophos@gmail.com"
+ },
+ {
+ "name": "Matt Farina",
+ "email": "matt@mattfarina.com"
+ },
+ {
+ "name": "Asmir Mustafic",
+ "email": "goetas@gmail.com"
+ }
+ ],
+ "description": "An HTML5 parser and serializer.",
+ "homepage": "http://masterminds.github.io/html5-php",
+ "keywords": [
+ "HTML5",
+ "dom",
+ "html",
+ "parser",
+ "querypath",
+ "serializer",
+ "xml"
+ ],
+ "support": {
+ "issues": "https://github.com/Masterminds/html5-php/issues",
+ "source": "https://github.com/Masterminds/html5-php/tree/2.8.1"
+ },
+ "time": "2023-05-10T11:58:31+00:00"
+ },
+ {
+ "name": "psr/container",
+ "version": "2.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/container.git",
+ "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963",
+ "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.4.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Container\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "https://www.php-fig.org/"
+ }
+ ],
+ "description": "Common Container Interface (PHP FIG PSR-11)",
+ "homepage": "https://github.com/php-fig/container",
+ "keywords": [
+ "PSR-11",
+ "container",
+ "container-interface",
+ "container-interop",
+ "psr"
+ ],
+ "support": {
+ "issues": "https://github.com/php-fig/container/issues",
+ "source": "https://github.com/php-fig/container/tree/2.0.2"
+ },
+ "time": "2021-11-05T16:47:00+00:00"
+ },
+ {
+ "name": "psr/log",
+ "version": "3.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/log.git",
+ "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001",
+ "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.0.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Log\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "https://www.php-fig.org/"
+ }
+ ],
+ "description": "Common interface for logging libraries",
+ "homepage": "https://github.com/php-fig/log",
+ "keywords": [
+ "log",
+ "psr",
+ "psr-3"
+ ],
+ "support": {
+ "source": "https://github.com/php-fig/log/tree/3.0.0"
+ },
+ "time": "2021-07-14T16:46:02+00:00"
+ },
+ {
+ "name": "symfony/browser-kit",
+ "version": "v6.4.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/browser-kit.git",
+ "reference": "a3bb210e001580ec75e1d02b27fae3452e6bf502"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/browser-kit/zipball/a3bb210e001580ec75e1d02b27fae3452e6bf502",
+ "reference": "a3bb210e001580ec75e1d02b27fae3452e6bf502",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1",
+ "symfony/dom-crawler": "^5.4|^6.0|^7.0"
+ },
+ "require-dev": {
+ "symfony/css-selector": "^5.4|^6.0|^7.0",
+ "symfony/http-client": "^5.4|^6.0|^7.0",
+ "symfony/mime": "^5.4|^6.0|^7.0",
+ "symfony/process": "^5.4|^6.0|^7.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\BrowserKit\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Simulates the behavior of a web browser, allowing you to make requests, click on links and submit forms programmatically",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/browser-kit/tree/v6.4.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2023-10-31T08:18:17+00:00"
+ },
+ {
+ "name": "symfony/css-selector",
+ "version": "v6.4.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/css-selector.git",
+ "reference": "d036c6c0d0b09e24a14a35f8292146a658f986e4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/css-selector/zipball/d036c6c0d0b09e24a14a35f8292146a658f986e4",
+ "reference": "d036c6c0d0b09e24a14a35f8292146a658f986e4",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\CssSelector\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Jean-François Simon",
+ "email": "jeanfrancois.simon@sensiolabs.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Converts CSS selectors to XPath expressions",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/css-selector/tree/v6.4.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2023-10-31T08:40:20+00:00"
+ },
+ {
+ "name": "symfony/deprecation-contracts",
+ "version": "v3.4.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/deprecation-contracts.git",
+ "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/7c3aff79d10325257a001fcf92d991f24fc967cf",
+ "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "3.4-dev"
+ },
+ "thanks": {
+ "name": "symfony/contracts",
+ "url": "https://github.com/symfony/contracts"
+ }
+ },
+ "autoload": {
+ "files": [
+ "function.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "A generic function and convention to trigger deprecation notices",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/deprecation-contracts/tree/v3.4.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2023-05-23T14:45:45+00:00"
+ },
+ {
+ "name": "symfony/dom-crawler",
+ "version": "v6.4.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/dom-crawler.git",
+ "reference": "14ff4fd2a5c8969d6158dbe7ef5b17d6a9c6ba33"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/14ff4fd2a5c8969d6158dbe7ef5b17d6a9c6ba33",
+ "reference": "14ff4fd2a5c8969d6158dbe7ef5b17d6a9c6ba33",
+ "shasum": ""
+ },
+ "require": {
+ "masterminds/html5": "^2.6",
+ "php": ">=8.1",
+ "symfony/polyfill-ctype": "~1.8",
+ "symfony/polyfill-mbstring": "~1.0"
+ },
+ "require-dev": {
+ "symfony/css-selector": "^5.4|^6.0|^7.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\DomCrawler\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Eases DOM navigation for HTML and XML documents",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/dom-crawler/tree/v6.4.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2023-11-20T16:41:16+00:00"
+ },
+ {
+ "name": "symfony/http-client",
+ "version": "v6.4.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/http-client.git",
+ "reference": "fc0944665bd932cf32a7b8a1d009466afc16528f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/http-client/zipball/fc0944665bd932cf32a7b8a1d009466afc16528f",
+ "reference": "fc0944665bd932cf32a7b8a1d009466afc16528f",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1",
+ "psr/log": "^1|^2|^3",
+ "symfony/deprecation-contracts": "^2.5|^3",
+ "symfony/http-client-contracts": "^3",
+ "symfony/service-contracts": "^2.5|^3"
+ },
+ "conflict": {
+ "php-http/discovery": "<1.15",
+ "symfony/http-foundation": "<6.3"
+ },
+ "provide": {
+ "php-http/async-client-implementation": "*",
+ "php-http/client-implementation": "*",
+ "psr/http-client-implementation": "1.0",
+ "symfony/http-client-implementation": "3.0"
+ },
+ "require-dev": {
+ "amphp/amp": "^2.5",
+ "amphp/http-client": "^4.2.1",
+ "amphp/http-tunnel": "^1.0",
+ "amphp/socket": "^1.1",
+ "guzzlehttp/promises": "^1.4",
+ "nyholm/psr7": "^1.0",
+ "php-http/httplug": "^1.0|^2.0",
+ "psr/http-client": "^1.0",
+ "symfony/dependency-injection": "^5.4|^6.0|^7.0",
+ "symfony/http-kernel": "^5.4|^6.0|^7.0",
+ "symfony/messenger": "^5.4|^6.0|^7.0",
+ "symfony/process": "^5.4|^6.0|^7.0",
+ "symfony/stopwatch": "^5.4|^6.0|^7.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\HttpClient\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Provides powerful methods to fetch HTTP resources synchronously or asynchronously",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "http"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/http-client/tree/v6.4.2"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2023-12-02T12:49:56+00:00"
+ },
+ {
+ "name": "symfony/http-client-contracts",
+ "version": "v3.4.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/http-client-contracts.git",
+ "reference": "1ee70e699b41909c209a0c930f11034b93578654"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/1ee70e699b41909c209a0c930f11034b93578654",
+ "reference": "1ee70e699b41909c209a0c930f11034b93578654",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "3.4-dev"
+ },
+ "thanks": {
+ "name": "symfony/contracts",
+ "url": "https://github.com/symfony/contracts"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Contracts\\HttpClient\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Test/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Generic abstractions related to HTTP clients",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "abstractions",
+ "contracts",
+ "decoupling",
+ "interfaces",
+ "interoperability",
+ "standards"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/http-client-contracts/tree/v3.4.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2023-07-30T20:28:31+00:00"
+ },
+ {
+ "name": "symfony/mime",
+ "version": "v6.4.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/mime.git",
+ "reference": "ca4f58b2ef4baa8f6cecbeca2573f88cd577d205"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/mime/zipball/ca4f58b2ef4baa8f6cecbeca2573f88cd577d205",
+ "reference": "ca4f58b2ef4baa8f6cecbeca2573f88cd577d205",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1",
+ "symfony/deprecation-contracts": "^2.5|^3",
+ "symfony/polyfill-intl-idn": "^1.10",
+ "symfony/polyfill-mbstring": "^1.0"
+ },
+ "conflict": {
+ "egulias/email-validator": "~3.0.0",
+ "phpdocumentor/reflection-docblock": "<3.2.2",
+ "phpdocumentor/type-resolver": "<1.4.0",
+ "symfony/mailer": "<5.4",
+ "symfony/serializer": "<6.3.2"
+ },
+ "require-dev": {
+ "egulias/email-validator": "^2.1.10|^3.1|^4",
+ "league/html-to-markdown": "^5.0",
+ "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0",
+ "symfony/dependency-injection": "^5.4|^6.0|^7.0",
+ "symfony/property-access": "^5.4|^6.0|^7.0",
+ "symfony/property-info": "^5.4|^6.0|^7.0",
+ "symfony/serializer": "^6.3.2|^7.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Mime\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Allows manipulating MIME messages",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "mime",
+ "mime-type"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/mime/tree/v6.4.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2023-10-17T11:49:05+00:00"
+ },
+ {
+ "name": "symfony/polyfill-ctype",
+ "version": "v1.28.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-ctype.git",
+ "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb",
+ "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1"
+ },
+ "provide": {
+ "ext-ctype": "*"
+ },
+ "suggest": {
+ "ext-ctype": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "1.28-dev"
+ },
+ "thanks": {
+ "name": "symfony/polyfill",
+ "url": "https://github.com/symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Ctype\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Gert de Pagter",
+ "email": "BackEndTea@gmail.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill for ctype functions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "ctype",
+ "polyfill",
+ "portable"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-ctype/tree/v1.28.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2023-01-26T09:26:14+00:00"
+ },
+ {
+ "name": "symfony/polyfill-intl-idn",
+ "version": "v1.28.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-intl-idn.git",
+ "reference": "ecaafce9f77234a6a449d29e49267ba10499116d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/ecaafce9f77234a6a449d29e49267ba10499116d",
+ "reference": "ecaafce9f77234a6a449d29e49267ba10499116d",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1",
+ "symfony/polyfill-intl-normalizer": "^1.10",
+ "symfony/polyfill-php72": "^1.10"
+ },
+ "suggest": {
+ "ext-intl": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "1.28-dev"
+ },
+ "thanks": {
+ "name": "symfony/polyfill",
+ "url": "https://github.com/symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Intl\\Idn\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Laurent Bassin",
+ "email": "laurent@bassin.info"
+ },
+ {
+ "name": "Trevor Rowbotham",
+ "email": "trevor.rowbotham@pm.me"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "idn",
+ "intl",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.28.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2023-01-26T09:30:37+00:00"
+ },
+ {
+ "name": "symfony/polyfill-intl-normalizer",
+ "version": "v1.28.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-intl-normalizer.git",
+ "reference": "8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92",
+ "reference": "8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1"
+ },
+ "suggest": {
+ "ext-intl": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "1.28-dev"
+ },
+ "thanks": {
+ "name": "symfony/polyfill",
+ "url": "https://github.com/symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Intl\\Normalizer\\": ""
+ },
+ "classmap": [
+ "Resources/stubs"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill for intl's Normalizer class and related functions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "intl",
+ "normalizer",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.28.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2023-01-26T09:26:14+00:00"
+ },
+ {
+ "name": "symfony/polyfill-mbstring",
+ "version": "v1.28.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-mbstring.git",
+ "reference": "42292d99c55abe617799667f454222c54c60e229"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/42292d99c55abe617799667f454222c54c60e229",
+ "reference": "42292d99c55abe617799667f454222c54c60e229",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1"
+ },
+ "provide": {
+ "ext-mbstring": "*"
+ },
+ "suggest": {
+ "ext-mbstring": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "1.28-dev"
+ },
+ "thanks": {
+ "name": "symfony/polyfill",
+ "url": "https://github.com/symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Mbstring\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill for the Mbstring extension",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "mbstring",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.28.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2023-07-28T09:04:16+00:00"
+ },
+ {
+ "name": "symfony/polyfill-php72",
+ "version": "v1.28.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-php72.git",
+ "reference": "70f4aebd92afca2f865444d30a4d2151c13c3179"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/70f4aebd92afca2f865444d30a4d2151c13c3179",
+ "reference": "70f4aebd92afca2f865444d30a4d2151c13c3179",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "1.28-dev"
+ },
+ "thanks": {
+ "name": "symfony/polyfill",
+ "url": "https://github.com/symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Php72\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-php72/tree/v1.28.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2023-01-26T09:26:14+00:00"
+ },
+ {
+ "name": "symfony/service-contracts",
+ "version": "v3.4.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/service-contracts.git",
+ "reference": "fe07cbc8d837f60caf7018068e350cc5163681a0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/service-contracts/zipball/fe07cbc8d837f60caf7018068e350cc5163681a0",
+ "reference": "fe07cbc8d837f60caf7018068e350cc5163681a0",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1",
+ "psr/container": "^1.1|^2.0"
+ },
+ "conflict": {
+ "ext-psr": "<1.1|>=2"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "3.4-dev"
+ },
+ "thanks": {
+ "name": "symfony/contracts",
+ "url": "https://github.com/symfony/contracts"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Contracts\\Service\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Test/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Generic abstractions related to writing services",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "abstractions",
+ "contracts",
+ "decoupling",
+ "interfaces",
+ "interoperability",
+ "standards"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/service-contracts/tree/v3.4.1"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2023-12-26T14:02:43+00:00"
+ }
+ ],
+ "packages-dev": [],
+ "aliases": [],
+ "minimum-stability": "stable",
+ "stability-flags": [],
+ "prefer-stable": false,
+ "prefer-lowest": false,
+ "platform": [],
+ "platform-dev": [],
+ "plugin-api-version": "2.6.0"
+}
diff --git a/server/fetch.php b/server/fetch.php
new file mode 100644
index 0000000..fba990b
--- /dev/null
+++ b/server/fetch.php
@@ -0,0 +1,235 @@
+ "dnn\$ctr994\$SOLLogin\$btnODeslat",
+ "__EVENTARGUMENT" => "",
+ "__VIEWSTATE" => "",
+ "__VIEWSTATEGENERATOR" => "",
+ "__VIEWSTATEENCRYPTED" => "",
+ "__PREVIOUSPAGE" => "",
+ "__EVENTVALIDATION" => "",
+ "dnn\$dnnSearch\$txtSearch" => "",
+ "JmenoUzivatele" => $username, # username
+ "HesloUzivatele" => $password, # password
+ "ScrollTop" => "",
+ "__dnnVariable" => "",
+ "__RequestVerificationToken" => ""
+ ];
+
+ $client->request("POST", $url, $headers=$headers);
+
+ if ($client->getResponse()->getStatusCode() != 200) {
+ return false;
+ } else {
+ return true;
+ }
+}
+
+function parse_teacher($onmouseover) {
+ $explode_on_teacher = explode("Učitel:", $onmouseover); // [0] => something, [1] => teacher and something, 0 can be ignored
+ if ($explode_on_teacher == null) {
+ $explode_on_teacher = "~ ~";
+ }
+ $explode_on_first_tilda = explode("~", $explode_on_teacher[1]);
+ $explode_on_second_tilda = explode("~", $explode_on_first_tilda[1]);
+ $teacher = $explode_on_second_tilda[0];
+ if ($teacher == " ") {
+ $teacher = "";
+ }
+
+ if (strstr($teacher, ">")) {
+ $teacher = explode("<", explode(">", $teacher)[1])[0];
+ }
+
+ return $teacher;
+}
+
+function get_time($times, $index) {
+ foreach ($times as $cell) {
+ $current_hour = explode(".", $cell->filter(".KuvHeaderNadpis")->text())[0];
+ if ($index == $current_hour) {
+ return explode(" - ", $cell->filter(".KuvHeaderText")->text());
+ }
+ }
+}
+
+function parse_substitute($substitutes) {
+ if ($substitutes->count() == 0) {
+ return 0;
+ } else {
+ return 1;
+ }
+}
+
+function parse_cell($cell, $cell_counter, $times) {
+ $is_empty = false;
+ if ($cell->filter(".KuvBunkaRozvrhNadpis")->count() == 0) {
+ $is_empty = true;
+ }
+
+ if (!$is_empty && $cell->filter("table")->filter(".KuvSuplovanaHodina")->count() == 0) {
+ $name = $cell->filter(".KuvBunkaRozvrhNadpis")->first()->text();
+ $place = explode("
", $cell->filter(".KuvBunkaRozvrhText")->first()->html())[1];
+ if ($place == null) {
+ $place = "";
+ }
+ $teacher = parse_teacher($cell->filter("table")->filter("td")->first()->attr("onmouseover"));
+ $is_substitute = parse_substitute($cell->filter(".KuvSuplujiciHodina"));
+ $number = $cell_counter;
+ $start = get_time($times, $cell_counter)[0]; // gives [start, end]
+ $end = get_time($times, $cell_counter)[1];
+
+ return [
+ "name" => $name,
+ "place" => $place,
+ "teacher" => $teacher,
+ "is_substitute" => $is_substitute,
+ "number" => $number,
+ "start" => $start,
+ "end" => $end
+ ];
+ } else {
+ $number = $cell_counter;
+ $start = get_time($times, $cell_counter)[0];
+ $end = get_time($times, $cell_counter)[1];
+
+ return [
+ "name" => "Volno",
+ "place" => "Volno",
+ "teacher" => "Volno",
+ "is_substitute" => "Volno",
+ "number" => $number,
+ "start" => $start,
+ "end" => $end
+ ];
+ }
+}
+
+function parse_cal_week($crawler) {
+ $week = [];
+
+ $table = $crawler->filter("#CCADynamicCalendarTable");
+
+ $rows_ = $table->filter("tr");
+ $times_ = $rows_->first()->filter("th");
+ $times = [];
+ $rows = [];
+
+ $rows_->each(function (Crawler $node, $i) use (&$rows) {
+ $rows[] = $node;
+ });
+ array_shift($rows);
+
+ $times_->each(function (Crawler $node, $i) use (&$times) {
+ $times[] = $node;
+ });
+ array_shift($times);
+
+ foreach ($rows as $row) { // for every day do:
+ $filtered_names = $row->filter(".KuvHeaderNadpis");
+ $filtered_dates = $row->filter(".KuvHeaderText");
+
+ if ($filtered_names->count() > 0 && $filtered_dates->count() > 0) {
+ $name = $filtered_names->first()->text();
+ $date = $filtered_dates->first()->text();
+ $subjects = [];
+ $cells_ = $row->filter(".DctCell");
+ $cells = [];
+ $cells_->each(function (Crawler $node, $i) use (&$cells) {
+ $cells[] = $node;
+ });
+
+ $cell_counter = 1; // start from 1
+ foreach ($cells as $cell) {
+ $subject = parse_cell($cell, $cell_counter, $times);
+ $colspan = $cell->attr("colspan");
+ $cell_counter += 1;
+ if ($colspan != null) {
+ array_push($subjects, $subject);
+ for ($i = 1; $i < $colspan; $i++) {
+ $subject = parse_cell($cell, $cell_counter, $times);
+ $cell_counter += 1;
+ array_push($subjects, $subject);
+ }
+ } else {
+ array_push($subjects, $subject);
+ }
+ }
+
+ // get rid of trash lectures from the end
+ while ($subjects[count($subjects)-1]["name"] == "Volno") {
+ array_pop($subjects);
+ }
+
+ $day = [
+ "name" => $name,
+ "date" => $date,
+ "subjects" => $subjects
+ ];
+ array_push($week, $day);
+ }
+ }
+
+ return $week;
+}
+
+function get_cal_week() {
+ global $client;
+
+ $url = "https://aplikace.skolaonline.cz/SOL/App/Kalendar/KZK001_KalendarTyden.aspx";
+
+ $crawler = $client->request("GET", $url);
+
+ if ($client->getResponse()->getStatusCode() != 200) {
+ return "Could not fetch weelky calendar data";
+ } else {
+ return parse_cal_week($crawler);
+ }
+}
+
+function create_json_file($body, $status_code) {
+ $json = ["body" => $body, "status_code" => $status_code];
+
+ return json_encode($json);
+}
+
+if (!isset($_POST["username"]) || !isset($_POST["password"])) {
+ echo create_json_file(json_encode("Credentials not supplied"), 1); // status 1 => failure
+} else {
+ if (!make_login_request($_POST["username"], $_POST["password"])) {
+ echo create_json_file("Invalid credentials", 1);
+ }
+
+ if ($_POST["cal_week"]) {
+ echo create_json_file(get_cal_week(), 0);
+ }
+
+ if ($_POST["cal_tdy_tmr"]) {
+ echo create_json_file(get_cal_tdy_tmr(), 0);
+ }
+
+ if ($_POST["grades"]) {
+ echo create_json_file(get_grades(), 0);
+ }
+}
+
+?>
+
diff --git a/server/vendor/autoload.php b/server/vendor/autoload.php
new file mode 100644
index 0000000..dc45db6
--- /dev/null
+++ b/server/vendor/autoload.php
@@ -0,0 +1,25 @@
+ 'localhost']);
+```
+
+If you provide your own validator callable, you can pass a custom error message
+to the second parameter:
+
+```php
+ true,
+ 'validator' => function($input, &$error = null) {
+ if (is_readable($input)) {
+ return true;
+ } else {
+ $error = 'Path given is not a readable file';
+ return false;
+ }
+ }
+]);
+```
+
+Note that for this to work, the second parameter must be declared a reference.
+
+### Console::confirm($text)
+
+Asks the user for a simple y/n answer. The answer can be `'y'`, `'n'`, `'Y'`, or
+`'N'`. Returns either `true` or `false`.
+
+```php
+ 'yes', 'n' => 'no', 'a' => 'all']
+);
+```
+
+### Console::work(callable $callable)
+
+Forks another process to run `$callable` in the background while showing status
+updates to the standard output. By default the status update is a simple spinner
+which will stop once the `$callable` returns. By providing `$callable` with a
+`$socket` parameter, the status update is whatever is sent from the background
+process to the foreground process using the `socket_write()` function:
+
+```php
+ '/path/to/process.pid', // required
+ 'stdin' => '/dev/null', // defaults to /dev/null
+ 'stdout' => '/path/to/stdout.txt', // defaults to /dev/null
+ 'stderr' => '/path/to/stderr.txt', // defaults to php://stdout
+ ),
+ function($stdin, $stdout, $stderr) { // these parameters are optional
+ while (true) {
+ // do whatever it is daemons do
+ sleep(1); // sleep is good for you
+ }
+ }
+ );
+ echo "daemon is now running.\n";
+}
+```
+
+The PID file is an ordinary text file with the process ID as its only content.
+It will be created by the library automatically if it doesn't exist. It is
+highly recommended to put a call to `sleep` to ease the system load.
+
+### Daemon::kill($pid, $delete = false)
+
+Kill a daemonized process:
+
+```php
+=5.3.2"
+ },
+ "autoload": {
+ "psr-0": {"Clio": "src/"}
+ }
+}
diff --git a/server/vendor/clio/clio/src/Clio/Console.php b/server/vendor/clio/clio/src/Clio/Console.php
new file mode 100644
index 0000000..292c88d
--- /dev/null
+++ b/server/vendor/clio/clio/src/Clio/Console.php
@@ -0,0 +1,476 @@
+ 30,
+ 'red' => 31,
+ 'green' => 32,
+ 'brown' => 33,
+ 'blue' => 34,
+ 'purple' => 35,
+ 'cyan' => 36,
+ 'grey' => 37,
+ 'yellow' => 33,
+ );
+
+ /**
+ * Text styling.
+ */
+ protected static $STYLE = array(
+ 'normal' => 0,
+ 'bold' => 1,
+ 'light' => 1,
+ 'underscore' => 4,
+ 'underline' => 4,
+ 'blink' => 5,
+ 'inverse' => 6,
+ 'hidden' => 8,
+ 'concealed' => 8,
+ );
+
+ /**
+ * Text background color.
+ */
+ protected static $BGCOLOR = array(
+ 'black' => 40,
+ 'red' => 41,
+ 'green' => 42,
+ 'brown' => 43,
+ 'yellow' => 43,
+ 'blue' => 44,
+ 'purple' => 45,
+ 'cyan' => 46,
+ 'grey' => 47,
+ );
+
+ /**
+ * Color specifier conversion table. Taken from PEAR's Console_Color.
+ */
+ protected static $CONVERSIONS = array(
+ '%y' => array('yellow', null, null),
+ '%g' => array('green', null, null),
+ '%b' => array('blue', null, null),
+ '%r' => array('red', null, null),
+ '%p' => array('purple', null, null),
+ '%m' => array('purple', null, null),
+ '%c' => array('cyan', null, null),
+ '%w' => array('grey', null, null),
+ '%k' => array('black', null, null),
+ '%n' => array('reset', null, null),
+ '%Y' => array('yellow', 'light', null),
+ '%G' => array('green', 'light', null),
+ '%B' => array('blue', 'light', null),
+ '%R' => array('red', 'light', null),
+ '%P' => array('purple', 'light', null),
+ '%M' => array('purple', 'light', null),
+ '%C' => array('cyan', 'light', null),
+ '%W' => array('grey', 'light', null),
+ '%K' => array('black', 'light', null),
+ '%N' => array('reset', 'light', null),
+ '%3' => array(null, null, 'yellow'),
+ '%2' => array(null, null, 'green'),
+ '%4' => array(null, null, 'blue'),
+ '%1' => array(null, null, 'red'),
+ '%5' => array(null, null, 'purple'),
+ '%6' => array(null, null, 'cyan'),
+ '%7' => array(null, null, 'grey'),
+ '%0' => array(null, null, 'black'),
+ // Don't use this, I can't stand flashing text
+ '%F' => array(null, 'blink', null),
+ '%U' => array(null, 'underline', null),
+ '%8' => array(null, 'inverse', null),
+ '%9' => array(null, 'bold', null),
+ '%_' => array(null, 'bold', null),
+ );
+
+ /**
+ * Create ANSI-control codes for text foreground and background colors, and
+ * styling.
+ *
+ * @param string $fgcolor Text foreground color
+ * @param string $style Text style
+ * @param string $bgcolor Text background color
+ *
+ * @return string ANSI-control code
+ */
+ public static function color($fgcolor, $style, $bgcolor)
+ {
+ $code = array();
+ if ($fgcolor == 'reset') {
+ return "\033[0m";
+ }
+ if (isset(static::$FGCOLOR[$fgcolor])) {
+ $code[] = static::$FGCOLOR[$fgcolor];
+ }
+ if (isset(static::$STYLE[$style])) {
+ $code[] = static::$STYLE[$style];
+ }
+ if (isset(static::$BGCOLOR[$bgcolor])) {
+ $code[] = static::$BGCOLOR[$bgcolor];
+ }
+ if (empty($code)) {
+ $code[] = 0;
+ }
+ return "\033[" . implode(';', $code) . 'm';
+ }
+
+ /**
+ * Taken from PEAR's Console_Color:
+ *
+ * Converts colorcodes in the format %y (for yellow) into ansi-control
+ * codes. The conversion table is: ('bold' meaning 'light' on some
+ * terminals). It's almost the same conversion table irssi uses.
+ *
+ * text text background + * ------------------------------------------------ + * %k %K %0 black dark grey black + * %r %R %1 red bold red red + * %g %G %2 green bold green green + * %y %Y %3 yellow bold yellow yellow + * %b %B %4 blue bold blue blue + * %m %M %5 magenta bold magenta magenta + * %p %P magenta (think: purple) + * %c %C %6 cyan bold cyan cyan + * %w %W %7 white bold white white + * + * %F Blinking, Flashing + * %U Underline + * %8 Reverse + * %_,%9 Bold + * + * %n Resets the color + * %% A single % + *+ * First param is the string to convert, second is an optional flag if + * colors should be used. It defaults to true, if set to false, the + * colorcodes will just be removed (And %% will be transformed into %) + * + * @param string $text String to color + * + * @return string + */ + public static function colorize($text, $color = true) + { + $text = str_replace('%%', '% ', $text); + foreach (static::$CONVERSIONS as $key => $value) { + list($fgcolor, $style, $bgcolor) = $value; + $text = str_replace( + $key, + $color ? static::color($fgcolor, $style, $bgcolor) : '', + $text + ); + } + return str_replace('% ', '%', $text); + } + + /** + * Strips a string from color specifiers. + * + * @param string $text String to strip + * + * @return string + */ + public static function decolorize($text) + { + return static::colorize($text, false); + } + + /** + * Strips a string of ansi-control codes. + * + * @param string $text String to strip + * + * @return string + */ + public static function strip($text) + { + return preg_replace('/\033\[(\d+)(;\d+)*m/', '', $text); + } + + /** + * Gets input from STDIN and returns a string right-trimmed for EOLs. + * + * @param bool $raw If set to true, returns the raw string without trimming + * + * @return string + */ + public static function stdin($raw = false) + { + return $raw ? fgets(STDIN) : rtrim(fgets(STDIN), PHP_EOL); + } + + /** + * Asks the user for input. Ends when the user types a PHP_EOL. Optionally + * provide a prompt. + * + * @param string $prompt String prompt (optional) + * @param bool $raw If set to true, returns the raw string without trimming + * + * @return string User input + */ + public static function input($prompt = null, $raw = false) + { + if (isset($prompt)) { + static::stdout($prompt); + } + return static::stdin($raw); + } + + /** + * Prints text to STDOUT. + * + * @param string $text String to write to STDOUT + * @param bool $raw Write string as-is; defaults to false + * + * @return int|false Number of bytes printed or false on error + */ + public static function stdout($text, $raw = false) + { + if ($raw) { + return fwrite(STDOUT, $text); + } elseif (extension_loaded('posix') && posix_isatty(STDOUT)) { + return fwrite(STDOUT, static::colorize($text)); + } else { + return fwrite(STDOUT, static::decolorize($text)); + } + } + + /** + * Prints text to STDOUT appended with a PHP_EOL. + * + * @param string $text String to write to STDOUT + * @param bool $raw Write string as-is; defaults to false + * + * @return int|false Number of bytes printed or false on error + */ + public static function output($text = null, $raw = false) + { + return static::stdout($text . PHP_EOL, $raw); + } + + /** + * Prints text to STDERR. + * + * @param string $text String to write to STDERR + * @param bool $raw Write string as-is; defaults to false + * + * @return int|false Number of bytes printed or false on error + */ + public static function stderr($text, $raw = false) + { + if ($raw) { + return fwrite(STDERR, $text); + } elseif (extension_loaded('posix') && posix_isatty(STDERR)) { + return fwrite(STDERR, static::colorize($text)); + } else { + return fwrite(STDERR, static::decolorize($text)); + } + } + + /** + * Prints text to STDERR appended with a PHP_EOL. + * + * @param string $text String to write to STDERR + * @param bool $raw Write string as-is; defaults to false + * + * @return int|false Number of bytes printed or false on error + */ + public static function error($text = null, $raw = false) + { + return static::stderr($text . PHP_EOL, $raw); + } + + /** + * Prompts the user for input + * + * @param string $text Prompt string + * @param array $options Set of options + * + * @return string + */ + public static function prompt($text, $options = array()) + { + $options = $options + array( + 'required' => false, + 'default' => null, + 'pattern' => null, + 'validator' => null, + 'error' => 'Input unacceptable.', + ); + + top: + if ($options['default']) { + $input = static::input("$text [" . $options['default'] . ']: '); + } else { + $input = static::input("$text: "); + } + + if (!strlen($input)) { + if (isset($options['default'])) { + $input = $options['default']; + } elseif ($options['required']) { + static::output($options['error']); + goto top; + } + } elseif ($options['pattern'] && !preg_match($options['pattern'], $input)) { + static::output($options['error']); + goto top; + } elseif ($options['validator'] && + !call_user_func_array($options['validator'], array($input, &$error))) { + static::output(isset($error) ? $error : $options['error']); + goto top; + } + + return $input; + } + + /** + * Asks the user for a simple yes/no confirmation. + * + * @param string $text Prompt string + * + * @return bool Either true or false + */ + public static function confirm($text) + { + top: + $input = strtolower(static::input("$text [y/n]: ")); + if (!in_array($input, array('y', 'n'))) goto top; + return $input === 'y' ? true : false; + } + + /** + * Gives the user an option to choose from. Giving '?' as an input will show + * a list of options to choose from and their explanations. + * + * @param string $text Prompt string + * @param array $options Key-value array of options to choose from + * + * @return string An option character the user chose + */ + public static function select($text, $options = array()) + { + top: + static::stdout("$text [" . implode(',', array_keys($options)) . ",?]: "); + $input = static::stdin(); + if ($input === '?') { + foreach ($options as $key => $value) { + echo " $key - $value\n"; + } + echo " ? - Show help\n"; + goto top; + } elseif (!in_array($input, array_keys($options))) goto top; + return $input; + } + + /** + * Execute a Closure as another process in the background while showing a + * status update. The status update can be an indefinite spinner or a string + * periodically sent from the background process, depending on whether the + * provided Closure object has a $socket parameter or not. Messaging to the + * main process is done by socket_* functions. The return value is either + * the return value of the background process, or false if the process fork + * failed. + * + * @throws \Exception + * + * @param callable $callable Closure object + * + * @return int|false Process exit status + */ + public static function work(callable $callable) + { + if (!extension_loaded('pcntl')) { + throw new \Exception('pcntl extension required'); + } + + if (!extension_loaded('sockets')) { + throw new \Exception('sockets extension required'); + } + + $spinner = array('|', '/', '-', '\\'); + $i = 0; $l = count($spinner); + $delay = 100000; + + $func = new \ReflectionFunction($callable); + + $socket = (bool)$func->getNumberOfParameters(); + + if ($socket) { + $sockets = array(); + if (socket_create_pair(AF_UNIX, SOCK_STREAM, 0, $sockets) === false) { + return false; + } + } + + $pid = pcntl_fork(); + + if ($pid > 0) { + $done = false; + $retval = 0; + pcntl_signal(SIGCHLD, function() use ($pid, &$done, &$retval) { + $child_pid = pcntl_waitpid($pid, $status); + if (pcntl_wifexited($status)) { + $retval = pcntl_wexitstatus($status); + } + $done = true; + }); + + if ($socket) { + $text = ''; + while (!$done) { + $r = array($sockets[1]); + $w = null; + $e = null; + if ($status = socket_select($r, $w, $e, 0)) { + $data = socket_read($sockets[1], 4096, PHP_NORMAL_READ); + if ($data === false) { + throw new \Exception( + sprintf( + 'socket write error %s', + socket_strerror(socket_last_error($sockets[1])) + ) + ); + } + echo str_repeat(chr(8), strlen($text)); + $text = rtrim($data, "\n"); + Console::stdout($text); + } else { + pcntl_signal_dispatch(); + } + usleep($delay); + } + echo str_repeat(chr(8), strlen($text)); + socket_close($sockets[0]); + socket_close($sockets[1]); + } else { + while (!$done) { + pcntl_signal_dispatch(); + echo $spinner[$i]; + usleep($delay); + echo chr(8); + $i = $i === $l - 1 ? 0 : $i + 1; + } + } + + return $retval; + } elseif ($pid === 0) { + if ($socket) { + call_user_func($callable, $sockets[0]); + } else { + call_user_func($callable); + } + exit; + } else { + // Unable to fork process. + return false; + } + } +} + diff --git a/server/vendor/clio/clio/src/Clio/Daemon.php b/server/vendor/clio/clio/src/Clio/Daemon.php new file mode 100644 index 0000000..cb665de --- /dev/null +++ b/server/vendor/clio/clio/src/Clio/Daemon.php @@ -0,0 +1,156 @@ + '/dev/null', + 'stdout' => '/dev/null', + 'stderr' => 'php://stdout', + ); + + if (($lock = @fopen($options['pid'], 'c+')) === false) { + throw new \Exception('unable to open pid file ' . $options['pid']); + } + + if (!flock($lock, LOCK_EX | LOCK_NB)) { + throw new \Exception('could not acquire lock for ' . $options['pid']); + } + + switch ($pid = pcntl_fork()) { + case -1: + throw new \Exception('unable to fork'); + case 0: + break; + default: + fseek($lock, 0); + ftruncate($lock, 0); + fwrite($lock ,$pid); + fflush($lock); + return; + } + + if (posix_setsid() === -1) { + throw new \Exception('failed to setsid'); + } + + fclose(STDIN); + fclose(STDOUT); + fclose(STDERR); + + if (!($stdin = fopen($options['stdin'], 'r'))) { + throw new \Exception('failed to open STDIN ' . $options['stdin']); + } + + if (!($stdout = fopen($options['stdout'], 'w'))) { + throw new \Exception('failed to open STDOUT ' . $options['stdout']); + } + + if (!($stderr = fopen($options['stderr'], 'w'))) { + throw new \Exception('failed to open STDERR ' . $options['stderr']); + } + + pcntl_signal(SIGTSTP, SIG_IGN); + pcntl_signal(SIGTTOU, SIG_IGN); + pcntl_signal(SIGTTIN, SIG_IGN); + pcntl_signal(SIGHUP, SIG_IGN); + + call_user_func($callable, $stdin, $stdout, $stderr); + } + + /** + * Checks whether a daemon process specified by its PID file is running. + * + * @throws \Exception + * + * @param string $file Daemon PID file + * + * @return bool True if the daemon is still running, false otherwise + */ + public static function isRunning($file) + { + if (!extension_loaded('posix')) { + throw new \Exception('posix extension required'); + } + + if (!is_readable($file)) { + return false; + } + + if (($lock = @fopen($file, 'c+')) === false) { + throw new \Exception('unable to open pid file ' . $file); + } + + if (flock($lock, LOCK_EX | LOCK_NB)) { + return false; + } else { + flock($lock, LOCK_UN); + return true; + } + } + + /** + * Kills a daemon process specified by its PID file. + * + * @throws \Exception + * + * @param string $file Daemon PID file + * @param bool $delete Flag to delete PID file after killing + * + * @return bool True on success, false otherwise + */ + public static function kill($file, $delete = false) + { + if (!extension_loaded('posix')) { + throw new \Exception('posix extension required'); + } + + if (!is_readable($file)) { + throw new \Exception('unreadable pid file ' . $file); + } + + if (($lock = @fopen($file, 'c+')) === false) { + throw new \Exception('unable to open pid file ' . $file); + } + + if (flock($lock, LOCK_EX | LOCK_NB)) { + flock($lock, LOCK_UN); + throw new \Exception('process not running'); + } + + $pid = fgets($lock); + + if (posix_kill($pid, SIGTERM)) { + if ($delete) unlink($file); + return true; + } else { + return false; + } + } +} + diff --git a/server/vendor/composer/ClassLoader.php b/server/vendor/composer/ClassLoader.php new file mode 100644 index 0000000..7824d8f --- /dev/null +++ b/server/vendor/composer/ClassLoader.php @@ -0,0 +1,579 @@ + + * Jordi Boggiano
This is a test of the HTML5 parser.
+ +