From bfec3048d36b996477c2ecbbee73148992c127c1 Mon Sep 17 00:00:00 2001 From: Anton Vakhrushev Date: Fri, 20 Oct 2023 15:11:42 +0300 Subject: [PATCH] Implement automatic cards exchange --- .gitignore | 1 + Taskfile.yml | 17 +- composer.json | 7 +- composer.lock | 662 ++++++++++++++++++++++++++++++++++- src/Card.php | 31 ++ src/ExchangeCardsCommand.php | 191 +++++++++- 6 files changed, 898 insertions(+), 11 deletions(-) create mode 100644 src/Card.php diff --git a/.gitignore b/.gitignore index 0970f44..1ccb417 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /vendor/ +.env .php-cs-fixer.cache \ No newline at end of file diff --git a/Taskfile.yml b/Taskfile.yml index 90576e4..d53c21e 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -4,23 +4,26 @@ version: '3' env: {} +dotenv: + - '.env' + vars: DOCKER_IMAGE: 'the-tale-ca-php' + DOCKER_RUN: 'docker run -it --rm -u 1000:1000 -v ${PWD}:/app -w /app {{.DOCKER_IMAGE}}' tasks: - default: - cmds: - - echo "{{.GREETING}}" - silent: true - build-docker-image: cmds: - docker build -t "{{.DOCKER_IMAGE}}" . run-shell: cmds: - - docker run -it --rm -u 1000:1000 -v ${PWD}:/app -w /app {{.DOCKER_IMAGE}} bash + - '{{.DOCKER_RUN}} bash' format: cmds: - - docker run -it --rm -u 1000:1000 -v ${PWD}:/app -w /app {{.DOCKER_IMAGE}} php-cs-fixer fix \ No newline at end of file + - '{{.DOCKER_RUN}} php-cs-fixer fix' + + exchange: + cmds: + - '{{.DOCKER_RUN}} php console.php cards:exchange' \ No newline at end of file diff --git a/composer.json b/composer.json index e9a332c..5666e05 100644 --- a/composer.json +++ b/composer.json @@ -4,7 +4,12 @@ "require": { "php": "^8.2", "guzzlehttp/guzzle": "^7.8", - "symfony/console": "^6.3" + "symfony/console": "^6.3", + "symfony/var-dumper": "^6.3", + "zakirullin/mess": "^0.8.4", + "webmozart/assert": "^1.11", + "lstrojny/functional-php": "^1.17", + "vlucas/phpdotenv": "^5.5" }, "require-dev": { "roave/security-advisories": "dev-latest" diff --git a/composer.lock b/composer.lock index 93eed82..1a6d225 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,70 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "9f83c44ba9881f7ae4f216d730b5731e", + "content-hash": "ab3e79c130e558d40e02b1d848a78b7e", "packages": [ + { + "name": "graham-campbell/result-type", + "version": "v1.1.1", + "source": { + "type": "git", + "url": "https://github.com/GrahamCampbell/Result-Type.git", + "reference": "672eff8cf1d6fe1ef09ca0f89c4b287d6a3eb831" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/672eff8cf1d6fe1ef09ca0f89c4b287d6a3eb831", + "reference": "672eff8cf1d6fe1ef09ca0f89c4b287d6a3eb831", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "phpoption/phpoption": "^1.9.1" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.32 || ^9.6.3 || ^10.0.12" + }, + "type": "library", + "autoload": { + "psr-4": { + "GrahamCampbell\\ResultType\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + } + ], + "description": "An Implementation Of The Result Type", + "keywords": [ + "Graham Campbell", + "GrahamCampbell", + "Result Type", + "Result-Type", + "result" + ], + "support": { + "issues": "https://github.com/GrahamCampbell/Result-Type/issues", + "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.1" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/graham-campbell/result-type", + "type": "tidelift" + } + ], + "time": "2023-02-25T20:23:15+00:00" + }, { "name": "guzzlehttp/guzzle", "version": "7.8.0", @@ -331,6 +393,235 @@ ], "time": "2023-08-27T10:13:57+00:00" }, + { + "name": "lstrojny/functional-php", + "version": "1.17.0", + "source": { + "type": "git", + "url": "https://github.com/lstrojny/functional-php.git", + "reference": "e459d5cb307bc6e10e9e992c4e96bb71a0262506" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/lstrojny/functional-php/zipball/e459d5cb307bc6e10e9e992c4e96bb71a0262506", + "reference": "e459d5cb307bc6e10e9e992c4e96bb71a0262506", + "shasum": "" + }, + "require": { + "php": "^7.1|~8" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.17", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.5", + "squizlabs/php_codesniffer": "~3.0" + }, + "type": "library", + "autoload": { + "files": [ + "src/Functional/Ary.php", + "src/Functional/Average.php", + "src/Functional/ButLast.php", + "src/Functional/Capture.php", + "src/Functional/ConstFunction.php", + "src/Functional/CompareOn.php", + "src/Functional/CompareObjectHashOn.php", + "src/Functional/Compose.php", + "src/Functional/Concat.php", + "src/Functional/Contains.php", + "src/Functional/Converge.php", + "src/Functional/Curry.php", + "src/Functional/CurryN.php", + "src/Functional/Difference.php", + "src/Functional/DropFirst.php", + "src/Functional/DropLast.php", + "src/Functional/Each.php", + "src/Functional/Equal.php", + "src/Functional/ErrorToException.php", + "src/Functional/Every.php", + "src/Functional/False.php", + "src/Functional/Falsy.php", + "src/Functional/Filter.php", + "src/Functional/First.php", + "src/Functional/FirstIndexOf.php", + "src/Functional/FlatMap.php", + "src/Functional/Flatten.php", + "src/Functional/Flip.php", + "src/Functional/GreaterThan.php", + "src/Functional/GreaterThanOrEqual.php", + "src/Functional/Group.php", + "src/Functional/Head.php", + "src/Functional/Id.php", + "src/Functional/IfElse.php", + "src/Functional/Identical.php", + "src/Functional/IndexesOf.php", + "src/Functional/Intersperse.php", + "src/Functional/Invoke.php", + "src/Functional/InvokeFirst.php", + "src/Functional/InvokeIf.php", + "src/Functional/InvokeLast.php", + "src/Functional/Invoker.php", + "src/Functional/Last.php", + "src/Functional/LastIndexOf.php", + "src/Functional/LessThan.php", + "src/Functional/LessThanOrEqual.php", + "src/Functional/LexicographicCompare.php", + "src/Functional/Map.php", + "src/Functional/Matching.php", + "src/Functional/Maximum.php", + "src/Functional/Memoize.php", + "src/Functional/Minimum.php", + "src/Functional/None.php", + "src/Functional/Noop.php", + "src/Functional/Not.php", + "src/Functional/OmitKeys.php", + "src/Functional/PartialAny.php", + "src/Functional/PartialLeft.php", + "src/Functional/PartialMethod.php", + "src/Functional/PartialRight.php", + "src/Functional/Partition.php", + "src/Functional/Pick.php", + "src/Functional/Pluck.php", + "src/Functional/Poll.php", + "src/Functional/Product.php", + "src/Functional/Ratio.php", + "src/Functional/ReduceLeft.php", + "src/Functional/ReduceRight.php", + "src/Functional/Reindex.php", + "src/Functional/Reject.php", + "src/Functional/Repeat.php", + "src/Functional/Retry.php", + "src/Functional/Select.php", + "src/Functional/SelectKeys.php", + "src/Functional/SequenceConstant.php", + "src/Functional/SequenceExponential.php", + "src/Functional/SequenceLinear.php", + "src/Functional/Some.php", + "src/Functional/Sort.php", + "src/Functional/Sum.php", + "src/Functional/SuppressError.php", + "src/Functional/Tap.php", + "src/Functional/Tail.php", + "src/Functional/TailRecursion.php", + "src/Functional/TakeLeft.php", + "src/Functional/TakeRight.php", + "src/Functional/True.php", + "src/Functional/Truthy.php", + "src/Functional/Unique.php", + "src/Functional/ValueToKey.php", + "src/Functional/With.php", + "src/Functional/Zip.php", + "src/Functional/ZipAll.php" + ], + "psr-4": { + "Functional\\": "src/Functional" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Lars Strojny", + "email": "lstrojny@php.net", + "homepage": "https://usrportage.de" + }, + { + "name": "Max Beutel", + "email": "nash12@gmail.com" + } + ], + "description": "Functional primitives for PHP", + "keywords": [ + "functional" + ], + "support": { + "issues": "https://github.com/lstrojny/functional-php/issues", + "source": "https://github.com/lstrojny/functional-php/tree/1.17.0" + }, + "funding": [ + { + "url": "https://github.com/lstrojny", + "type": "github" + } + ], + "time": "2021-03-07T00:25:34+00:00" + }, + { + "name": "phpoption/phpoption", + "version": "1.9.1", + "source": { + "type": "git", + "url": "https://github.com/schmittjoh/php-option.git", + "reference": "dd3a383e599f49777d8b628dadbb90cae435b87e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/dd3a383e599f49777d8b628dadbb90cae435b87e", + "reference": "dd3a383e599f49777d8b628dadbb90cae435b87e", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.32 || ^9.6.3 || ^10.0.12" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": true + }, + "branch-alias": { + "dev-master": "1.9-dev" + } + }, + "autoload": { + "psr-4": { + "PhpOption\\": "src/PhpOption/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Johannes M. Schmitt", + "email": "schmittjoh@gmail.com", + "homepage": "https://github.com/schmittjoh" + }, + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + } + ], + "description": "Option Type for PHP", + "keywords": [ + "language", + "option", + "php", + "type" + ], + "support": { + "issues": "https://github.com/schmittjoh/php-option/issues", + "source": "https://github.com/schmittjoh/php-option/tree/1.9.1" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpoption/phpoption", + "type": "tidelift" + } + ], + "time": "2023-02-25T19:38:58+00:00" + }, { "name": "psr/container", "version": "2.0.2", @@ -1075,6 +1366,89 @@ ], "time": "2023-07-28T09:04:16+00:00" }, + { + "name": "symfony/polyfill-php80", + "version": "v1.28.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/6caa57379c4aec19c0a12a38b59b26487dcfe4b5", + "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5", + "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\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/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.3.0", @@ -1242,6 +1616,292 @@ } ], "time": "2023-09-18T10:38:32+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v6.3.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "3d9999376be5fea8de47752837a3e1d1c5f69ef5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/3d9999376be5fea8de47752837a3e1d1c5f69ef5", + "reference": "3d9999376be5fea8de47752837a3e1d1c5f69ef5", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/console": "<5.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^5.4|^6.0", + "symfony/http-kernel": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/uid": "^5.4|^6.0", + "twig/twig": "^2.13|^3.0.4" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "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 mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v6.3.5" + }, + "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-09-12T10:11:35+00:00" + }, + { + "name": "vlucas/phpdotenv", + "version": "v5.5.0", + "source": { + "type": "git", + "url": "https://github.com/vlucas/phpdotenv.git", + "reference": "1a7ea2afc49c3ee6d87061f5a233e3a035d0eae7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/1a7ea2afc49c3ee6d87061f5a233e3a035d0eae7", + "reference": "1a7ea2afc49c3ee6d87061f5a233e3a035d0eae7", + "shasum": "" + }, + "require": { + "ext-pcre": "*", + "graham-campbell/result-type": "^1.0.2", + "php": "^7.1.3 || ^8.0", + "phpoption/phpoption": "^1.8", + "symfony/polyfill-ctype": "^1.23", + "symfony/polyfill-mbstring": "^1.23.1", + "symfony/polyfill-php80": "^1.23.1" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.4.1", + "ext-filter": "*", + "phpunit/phpunit": "^7.5.20 || ^8.5.30 || ^9.5.25" + }, + "suggest": { + "ext-filter": "Required to use the boolean validator." + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": true + }, + "branch-alias": { + "dev-master": "5.5-dev" + } + }, + "autoload": { + "psr-4": { + "Dotenv\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Vance Lucas", + "email": "vance@vancelucas.com", + "homepage": "https://github.com/vlucas" + } + ], + "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.", + "keywords": [ + "dotenv", + "env", + "environment" + ], + "support": { + "issues": "https://github.com/vlucas/phpdotenv/issues", + "source": "https://github.com/vlucas/phpdotenv/tree/v5.5.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/vlucas/phpdotenv", + "type": "tidelift" + } + ], + "time": "2022-10-16T01:01:54+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.11.0", + "source": { + "type": "git", + "url": "https://github.com/webmozarts/assert.git", + "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991", + "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "php": "^7.2 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<0.12.20", + "vimeo/psalm": "<4.6.1 || 4.6.2" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.13" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.10-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "support": { + "issues": "https://github.com/webmozarts/assert/issues", + "source": "https://github.com/webmozarts/assert/tree/1.11.0" + }, + "time": "2022-06-03T18:03:27+00:00" + }, + { + "name": "zakirullin/mess", + "version": "0.8.4", + "source": { + "type": "git", + "url": "https://github.com/zakirullin/mess.git", + "reference": "71c66962b96b31071d8c05dee073d4bf6d38f6d1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zakirullin/mess/zipball/71c66962b96b31071d8c05dee073d4bf6d38f6d1", + "reference": "71c66962b96b31071d8c05dee073d4bf6d38f6d1", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "require-dev": { + "infection/infection": "^0.15|^0.20", + "phpunit/phpunit": "^8.0", + "vimeo/psalm": "4.16.1" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Zakirullin\\Mess\\": "src/", + "Zakirullin\\TypedAccessor\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Artem Zakirullin", + "email": "artemzr@gmail.com" + } + ], + "description": "Convenient array-related routine & better type casting", + "homepage": "https://github.com/zakirullin/mess", + "keywords": [ + "array", + "assert", + "cast", + "json", + "language", + "mess", + "php", + "type" + ], + "support": { + "issues": "https://github.com/zakirullin/mess/issues", + "source": "https://github.com/zakirullin/mess/tree/0.8.4" + }, + "time": "2022-04-05T14:52:52+00:00" } ], "packages-dev": [ diff --git a/src/Card.php b/src/Card.php new file mode 100644 index 0000000..436dc7c --- /dev/null +++ b/src/Card.php @@ -0,0 +1,31 @@ +getString(), + type: $mess['type']->getInt(), + fullType: $mess['full_type']->getString(), + rarity: $mess['rarity']->getInt(), + uid: $mess['uid']->getString(), + ); + } +} diff --git a/src/ExchangeCardsCommand.php b/src/ExchangeCardsCommand.php index 8fb5d4f..c966f04 100644 --- a/src/ExchangeCardsCommand.php +++ b/src/ExchangeCardsCommand.php @@ -4,10 +4,18 @@ declare(strict_types=1); namespace App; +use Dotenv\Dotenv; +use GuzzleHttp\Client; +use GuzzleHttp\Cookie\CookieJar; +use GuzzleHttp\Cookie\SetCookie; +use GuzzleHttp\RequestOptions; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; +use Webmozart\Assert\Assert; +use function Functional\map; +use function Functional\partition; #[AsCommand( name: 'cards:exchange', @@ -15,8 +23,187 @@ use Symfony\Component\Console\Output\OutputInterface; )] class ExchangeCardsCommand extends Command { - protected function execute(InputInterface $input, OutputInterface $output) - { + private const CARDS = [ + 144, // Удачный поворот: +4 карты + 145, // Колесо Фортуны: +12 карт + 146, // Белая полоса: +42 карты + 147, // Прядь Кайроса: +150 карт + ]; + private readonly string $csrfToken; + private readonly CookieJar $cookies; + private readonly Client $client; + + public function __construct(string $name = null) + { + parent::__construct($name); + + $dotenv = Dotenv::createImmutable(__DIR__ . '/../'); + $dotenv->load(); + + $this->csrfToken = bin2hex(random_bytes(32)); + + $this->cookies = new CookieJar(); + $this->cookies->setCookie(new SetCookie([ + 'Name' => 'csrftoken', + 'Value' => $this->csrfToken, + 'Domain' => 'the-tale.org', + ])); + + $this->client = new Client(); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $this->login(); + + $output->writeln('Receive new cards'); + $this->receiveNewCards(); + + do { + $output->writeln('Fetch cards'); + $cards = $this->getCards(); + $output->writeln('Find special cards'); + $specialCards = array_filter($cards, static fn (Card $c) => in_array($c->type, self::CARDS)); + if (count($specialCards) > 0) { + foreach ($specialCards as $card) { + $output->writeln('Use card ' . $card->name); + $this->useCard($card->uid); + } + } + + $output->writeln('Wait for cards applies'); + sleep(3); + + $output->writeln('Receive new cards'); + $this->receiveNewCards(); + $cards = $this->getCards(); + + $output->writeln('Combine cards'); + $combined = 0; + foreach ([0, 1, 2, 3] as $rarity) { + $selectedCards = array_filter($cards, static fn (Card $c) => $c->rarity === $rarity && !in_array($c->type, self::CARDS)); + while (true) { + [$next, $rest] = partition(array_values($selectedCards), fn ($_, $index) => $index < 3); + if (count($next) < 3) { + break; + } + $this->combileCards(map($next, fn (Card $c) => $c->uid)); + $selectedCards = $rest; + $combined++; + } + } + $output->writeln('Combined cards: ' . $combined); + } while ($combined !== 0); + + return self::SUCCESS; + } + + private function login() + { + $response = $this->client->request('POST', 'https://the-tale.org/accounts/auth/api/login', [ + RequestOptions::HEADERS => [ + 'Referer' => 'https://the-tale.org/', + 'X-CSRFToken' => $this->csrfToken, + ], + RequestOptions::QUERY => [ + 'api_version' => '1.0', + 'api_client' => 'anwinged-1', + ], + RequestOptions::FORM_PARAMS => [ + 'email' => (string) $_ENV['TALE_EMAIL'], + 'password' => (string) $_ENV['TALE_PASS'], + ], + RequestOptions::COOKIES => $this->cookies, + ]); + + Assert::eq($response->getStatusCode(), 200); + } + + /** + * @return Card[] + * @throws \GuzzleHttp\Exception\GuzzleException + */ + private function getCards(): array + { + $response = $this->client->request('GET', 'https://the-tale.org/game/cards/api/get-cards', [ + RequestOptions::HEADERS => [ + 'Referer' => 'https://the-tale.org/', + ], + RequestOptions::QUERY => [ + 'api_version' => '2.0', + 'api_client' => 'anwinged-1', + ], + RequestOptions::COOKIES => $this->cookies, + ]); + + Assert::eq($response->getStatusCode(), 200); + + $data = json_decode((string) $response->getBody(), associative: true); + + $cardsArray = $data['data']['cards']; + Assert::isArray($cardsArray); + + return array_map(Card::fromArray(...), $cardsArray); + } + + private function receiveNewCards() + { + $response = $this->client->request('POST', 'https://the-tale.org/game/cards/api/receive', [ + RequestOptions::HEADERS => [ + 'Referer' => 'https://the-tale.org/', + 'X-CSRFToken' => $this->cookies->getCookieByName('csrftoken')?->getValue(), + ], + RequestOptions::QUERY => [ + 'api_version' => '1.0', + 'api_client' => 'anwinged-1', + ], + RequestOptions::COOKIES => $this->cookies, + ]); + + Assert::eq($response->getStatusCode(), 200); + } + + private function useCard(string $uid) + { + $response = $this->client->request('POST', 'https://the-tale.org/game/cards/api/use', [ + RequestOptions::HEADERS => [ + 'Referer' => 'https://the-tale.org/', + 'X-CSRFToken' => $this->cookies->getCookieByName('csrftoken')?->getValue(), + ], + RequestOptions::QUERY => [ + 'api_version' => '2.0', + 'api_client' => 'anwinged-1', + 'card' => $uid, + ], + RequestOptions::COOKIES => $this->cookies, + ]); + + Assert::eq($response->getStatusCode(), 200); + + dump((string) $response->getBody()); + } + + private function combileCards(array $uids) + { + $cardsData = implode('&', map($uids, fn ($uid) => 'card=' . $uid)); + + $response = $this->client->request('POST', 'https://the-tale.org/game/cards/api/combine', [ + RequestOptions::HEADERS => [ + 'Referer' => 'https://the-tale.org/', + 'X-CSRFToken' => $this->cookies->getCookieByName('csrftoken')?->getValue(), + 'Content-Type' => 'application/x-www-form-urlencoded', + ], + RequestOptions::QUERY => [ + 'api_version' => '3.0', + 'api_client' => 'anwinged-1', + ], + RequestOptions::BODY => $cardsData, + RequestOptions::COOKIES => $this->cookies, + ]); + + Assert::eq($response->getStatusCode(), 200); + + dump((string) $response->getBody()); } }