├── tests
├── .gitkeep
├── TestCase.php
├── Api
│ ├── Endpoint
│ │ ├── TransfermarketEndpointTest.php
│ │ └── EndpointTestCase.php
│ ├── CoreTest.php
│ └── PinTest.php
├── Config
│ └── ConfigTest.php
└── Authentication
│ └── CredentialsTest.php
├── .gitignore
├── .env.dist
├── src
├── Api
│ ├── Core.php
│ ├── Pin.php
│ └── CoreInterface.php
├── bootstrap.php
├── Resources
│ └── locales
│ │ ├── en_US.yaml
│ │ └── de_DE.yaml
├── Http
│ ├── CookieJarBuilderInterface.php
│ ├── ClientFactoryInterface.php
│ ├── Plugin
│ │ └── ClientCallPlugin.php
│ ├── ClientCall.php
│ ├── CookieJarBuilder.php
│ └── ClientFactory.php
├── Exception
│ ├── ServerDownException.php
│ ├── AuthFailedException.php
│ ├── CaptchaException.php
│ ├── PermissionDeniedException.php
│ ├── UserExpiredException.php
│ ├── MaxSessionsException.php
│ ├── NoPersonaException.php
│ ├── SessionExpiredException.php
│ ├── ToManyRequestsException.php
│ ├── PinErrorException.php
│ ├── ProvideSecurityCodeException.php
│ ├── TemporaryBanException.php
│ ├── IncorrectCredentialsException.php
│ ├── NoSessionException.php
│ ├── IncorrectSecurityCodeException.php
│ ├── TransferMarketDisabledException.php
│ ├── FutFailedException.php
│ ├── FutException.php
│ └── FutResponseException.php
├── Items
│ ├── CurrencyValue.php
│ ├── Kit.php
│ ├── DuplicateItem.php
│ ├── Health.php
│ ├── ItemInterface.php
│ ├── Contract.php
│ ├── TradeItemInterface.php
│ ├── SuperBase.php
│ ├── Item.php
│ ├── TradeItem.php
│ └── Player.php
├── Model
│ ├── ProxyInterface.php
│ └── Proxy.php
├── Authentication
│ ├── AccountInterface.php
│ ├── SessionInterface.php
│ ├── CredentialsInterface.php
│ ├── Account.php
│ ├── Credentials.php
│ └── Session.php
├── Response
│ ├── AbstractResponse.php
│ ├── WatchlistResponse.php
│ ├── UnassignedResponse.php
│ ├── TradepileResponse.php
│ ├── MarketSearchResponse.php
│ ├── BidResponse.php
│ └── TradeStatusResponse.php
├── Config
│ ├── ConfigInterface.php
│ └── Config.php
├── Locale
│ └── Locale.php
├── Util
│ ├── FutUtil.php
│ └── EAHasher.php
└── Mapper
│ └── Mapper.php
├── data
└── fixtures
│ ├── weekendleage_status.respsonse.json
│ ├── trade_status_lite.response.json
│ ├── club_stats_stuff.respsonse.json
│ ├── unassigned.response.json
│ ├── accountinfo.response.json
│ ├── sets_challenges.response.json
│ ├── bid.response.json
│ ├── watchlist.reponse.json
│ ├── tradepile.response.json
│ ├── settings.response.json
│ └── sets_challenge.response.json
├── phpstan.neon.dist
├── composer-require-checker.json
├── .github
├── workflows
│ └── php.yml
└── FUNDING.yml
├── phpunit.xml
├── phpcs.xml
├── composer.json
└── README.md
/tests/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | composer.phar
2 | vendor/
3 | .phpunit.result.cache
4 | .env
--------------------------------------------------------------------------------
/.env.dist:
--------------------------------------------------------------------------------
1 | EMAIL=email@example.com
2 | PASSWORD=YOUR_ACCOUNT_PASSWORD
3 | PLATFORM=ps4
--------------------------------------------------------------------------------
/src/Api/Core.php:
--------------------------------------------------------------------------------
1 | load(__DIR__ . '/../.env');
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/composer-require-checker.json:
--------------------------------------------------------------------------------
1 | {
2 | "symbol-whitelist": [
3 | "null",
4 | "true",
5 | "false",
6 | "static",
7 | "self",
8 | "parent",
9 | "array",
10 | "string",
11 | "int",
12 | "float",
13 | "bool",
14 | "iterable",
15 | "callable",
16 | "void",
17 | "object"
18 | ]
19 | }
20 |
--------------------------------------------------------------------------------
/src/Exception/ServerDownException.php:
--------------------------------------------------------------------------------
1 | get('name');
12 | }
13 |
14 | public function getFunds() : int
15 | {
16 | return $this->get('funds');
17 | }
18 |
19 | public function getFinalFunds() : int
20 | {
21 | return $this->get('finalFunds');
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/Model/ProxyInterface.php:
--------------------------------------------------------------------------------
1 | rawBody = $rawBody;
18 | }
19 |
20 | /**
21 | * @return mixed[]
22 | */
23 | public function getRawBody() : array
24 | {
25 | return $this->rawBody;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Items/Kit.php:
--------------------------------------------------------------------------------
1 | get('assetId');
12 | }
13 |
14 | public function getRating() : ?int
15 | {
16 | return $this->get('rating');
17 | }
18 |
19 | public function getCategory() : ?string
20 | {
21 | return $this->get('category');
22 | }
23 |
24 | public function getName() : ?string
25 | {
26 | return $this->get('name');
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/.github/workflows/php.yml:
--------------------------------------------------------------------------------
1 | name: PHP Composer
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | pull_request:
8 | branches:
9 | - master
10 |
11 | jobs:
12 | build:
13 |
14 | runs-on: ubuntu-latest
15 |
16 | steps:
17 | - uses: actions/checkout@v1
18 |
19 | - name: Validate composer.json and composer.lock
20 | run: composer validate
21 |
22 | - name: Install dependencies
23 | run: composer install --prefer-dist --no-progress --no-suggest
24 |
25 | - name: Copy env
26 | run: cp .env.dist .env
27 |
28 | - name: Run Check
29 | run: composer check-build
30 |
--------------------------------------------------------------------------------
/src/Authentication/SessionInterface.php:
--------------------------------------------------------------------------------
1 | itemId = $itemId;
18 | $this->duplicateItemId = $duplicateItemId;
19 | }
20 |
21 | public function getItemId() : int
22 | {
23 | return $this->itemId;
24 | }
25 |
26 | public function getDuplicateItemId() : int
27 | {
28 | return $this->duplicateItemId;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/Items/Health.php:
--------------------------------------------------------------------------------
1 | get('assetId');
12 | }
13 |
14 | public function getRating() : ?int
15 | {
16 | return $this->get('rating');
17 | }
18 |
19 | public function getCardAssetId() : ?int
20 | {
21 | return $this->get('cardassetid');
22 | }
23 |
24 | public function getWeightRare() : ?int
25 | {
26 | return $this->get('weightrare');
27 | }
28 |
29 | public function getAmount() : ?int
30 | {
31 | return $this->get('amount');
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Exception/FutFailedException.php:
--------------------------------------------------------------------------------
1 | getErrorMessage(), $response, $this->getErrorReason(), $options, $previous);
18 | }
19 |
20 | abstract protected function getErrorMessage() : string;
21 |
22 | abstract protected function getErrorReason() : string;
23 | }
24 |
--------------------------------------------------------------------------------
/src/Items/ItemInterface.php:
--------------------------------------------------------------------------------
1 | createClientFactoryMock('transfermarekt.response.json');
14 | $core = $this->createCore($factory);
15 |
16 | $result = $core->search();
17 |
18 | self::assertCount(21, $result->getAuctions());
19 | self::assertCount(0, $result->getBidTokens());
20 | self::assertNotEmpty($result->getRawBody());
21 | self::assertContainsOnlyInstancesOf(TradeItem::class, $result->getAuctions());
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
13 | tests
14 |
15 |
16 |
17 |
18 |
19 | src
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/Exception/FutException.php:
--------------------------------------------------------------------------------
1 | options = $options;
23 | }
24 |
25 | /**
26 | * @return mixed[]
27 | */
28 | public function getOptions() : array
29 | {
30 | return $this->options;
31 | }
32 |
33 | /**
34 | * @return mixed|null
35 | */
36 | public function getOption(string $name)
37 | {
38 | return $this->options[$name] ?? null;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/Items/Contract.php:
--------------------------------------------------------------------------------
1 | get('rating');
12 | }
13 |
14 | public function getAssetId() : ?int
15 | {
16 | return $this->get('assetId');
17 | }
18 |
19 | public function getCardAssetId() : ?int
20 | {
21 | return $this->get('cardassetid');
22 | }
23 |
24 | public function getWeightRare() : ?int
25 | {
26 | return $this->get('weightrare');
27 | }
28 |
29 | public function getBronze() : ?int
30 | {
31 | return $this->get('bronze');
32 | }
33 |
34 | public function getSilver() : ?int
35 | {
36 | return $this->get('silver');
37 | }
38 |
39 | public function getGold() : ?int
40 | {
41 | return $this->get('gold');
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/Locale/Locale.php:
--------------------------------------------------------------------------------
1 | translator = new Translator($locale);
21 |
22 | $path = __DIR__ . '/../Resources/locales/';
23 | $file = $path . $locale . '.yaml';
24 |
25 | $this->translator->addLoader('yaml', new YamlFileLoader());
26 | $this->translator->addResource('yaml', $file, $locale);
27 | }
28 |
29 | /**
30 | * @param mixed $value
31 | */
32 | public function get($value) : string
33 | {
34 | return $this->translator->trans($value);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/Authentication/CredentialsInterface.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | src/
10 | tests/
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/Response/WatchlistResponse.php:
--------------------------------------------------------------------------------
1 | credits = $credits;
26 | $this->auctions = $auctions;
27 | $this->total = $total;
28 | }
29 |
30 | public function getCredits() : ?int
31 | {
32 | return $this->credits;
33 | }
34 |
35 | /**
36 | * @return TradeItem[]
37 | */
38 | public function getAuctions() : array
39 | {
40 | return $this->auctions;
41 | }
42 |
43 | public function getTotal() : ?int
44 | {
45 | return $this->total;
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/Response/UnassignedResponse.php:
--------------------------------------------------------------------------------
1 | items = $items;
25 | $this->duplicateItemIdList = $duplicateItemIdList;
26 | }
27 |
28 | /**
29 | * @return ItemInterface[]
30 | */
31 | public function getItems() : array
32 | {
33 | return $this->items;
34 | }
35 |
36 | /**
37 | * @return DuplicateItem[]
38 | */
39 | public function getDuplicateItemIdList() : array
40 | {
41 | return $this->duplicateItemIdList;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/Items/TradeItemInterface.php:
--------------------------------------------------------------------------------
1 | credits = $credits;
27 | $this->auctions = $auctions;
28 | $this->bidTokens = $bidTokens;
29 | }
30 |
31 | public function getCredits() : ?int
32 | {
33 | return $this->credits;
34 | }
35 |
36 | /**
37 | * @return TradeItem[]
38 | */
39 | public function getAuctions() : array
40 | {
41 | return $this->auctions;
42 | }
43 |
44 | /**
45 | * @return mixed[]
46 | */
47 | public function getBidTokens() : array
48 | {
49 | return $this->bidTokens;
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/Http/Plugin/ClientCallPlugin.php:
--------------------------------------------------------------------------------
1 | call = $call;
22 | }
23 |
24 | public function handleRequest(RequestInterface $request, callable $next, callable $first) : Promise
25 | {
26 | $this->call->setRequest($request);
27 |
28 | return $next($request)->then(function (ResponseInterface $response) {
29 | $this->call->setResponse($response);
30 |
31 | return $response;
32 | }, function (Exception $exception) : void {
33 | if ($exception instanceof Exception\HttpException) {
34 | $this->call->setResponse($exception->getResponse());
35 | }
36 |
37 | throw $exception;
38 | });
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/data/fixtures/trade_status_lite.response.json:
--------------------------------------------------------------------------------
1 | {
2 | "auctionInfo": [
3 | {
4 | "tradeId": 261540141928,
5 | "itemData": {
6 | "id": 368363499429,
7 | "timestamp": 0,
8 | "untradeable": false,
9 | "resourceId": 0,
10 | "owners": 0,
11 | "cardsubtypeid": 0,
12 | "lastSalePrice": 0,
13 | "morale": 0,
14 | "fitness": 0,
15 | "injuryGames": 0,
16 | "statsList": [],
17 | "lifetimeStats": [],
18 | "training": 0,
19 | "contract": 0,
20 | "suspension": 0,
21 | "attributeList": [],
22 | "pile": 0,
23 | "nation": 0,
24 | "resourceGameYear": 2020
25 | },
26 | "tradeState": "active",
27 | "buyNowPrice": 1784000,
28 | "currentBid": 0,
29 | "watched": false,
30 | "bidState": "none",
31 | "startingBid": 1744000,
32 | "expires": 60,
33 | "tradeIdStr": "261540141928",
34 | "tradeOwner": false
35 | }
36 | ]
37 | }
--------------------------------------------------------------------------------
/src/Http/ClientCall.php:
--------------------------------------------------------------------------------
1 | request;
24 | }
25 |
26 | public function setRequest(RequestInterface $request) : void
27 | {
28 | $this->request = $request;
29 | }
30 |
31 | public function getResponse() : ResponseInterface
32 | {
33 | return $this->response;
34 | }
35 |
36 | public function setResponse(ResponseInterface $response) : void
37 | {
38 | $this->response = $response;
39 | }
40 |
41 | /**
42 | * @return mixed
43 | */
44 | public function getContent()
45 | {
46 | if ($this->contents === null) {
47 | $this->contents = $this->getResponse()->getBody()->getContents();
48 | }
49 |
50 | return $this->contents;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/Exception/FutResponseException.php:
--------------------------------------------------------------------------------
1 | getStatusCode();
30 | } else {
31 | $code = 0;
32 | }
33 |
34 | parent::__construct($message, $options, $code, $previous);
35 |
36 | $this->response = $response;
37 | $this->reason = $reason;
38 | }
39 |
40 | public function getReason() : ?string
41 | {
42 | return $this->reason;
43 | }
44 |
45 | public function getResponse() : ?ResponseInterface
46 | {
47 | return $this->response;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/tests/Api/CoreTest.php:
--------------------------------------------------------------------------------
1 | expectException(ProvideSecurityCodeException::class),
31 | $this->expectException(IncorrectCredentialsException::class)
32 | );
33 |
34 | self::logicalXor(
35 | $this->expectExceptionMessage('You must provide a backup code'),
36 | $this->expectExceptionMessage('Your email or password is incorrect.')
37 | );
38 |
39 | $core->login();
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/Http/CookieJarBuilder.php:
--------------------------------------------------------------------------------
1 | getCredentials()->getEmail();
22 |
23 | if (array_key_exists($email, $this->jars) === false) {
24 | $this->jars[$email] = $this->createFileCookieJarByTemp($account->getCredentials()->getEmail());
25 | }
26 |
27 | return $this->jars[$email];
28 | }
29 |
30 | private function createFileCookieJarByTemp(string $email) : CookieJarInterface
31 | {
32 | $filename = sys_get_temp_dir() . '/' . sha1($email);
33 |
34 | return $this->createFileCookieJarByFilename($filename);
35 | }
36 |
37 | private function createFileCookieJarByFilename(string $filename) : CookieJarInterface
38 | {
39 | return new FileCookieJar($filename, true);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/Response/MarketSearchResponse.php:
--------------------------------------------------------------------------------
1 | auctions = $auctions;
34 | $this->bidTokens = $bidTokens;
35 | }
36 |
37 | /**
38 | * @return TradeItem[]
39 | */
40 | public function getAuctions() : array
41 | {
42 | return $this->auctions;
43 | }
44 |
45 | public function hasAuctions() : bool
46 | {
47 | return count($this->auctions) > 0;
48 | }
49 |
50 | /**
51 | * @return mixed[]
52 | */
53 | public function getBidTokens() : array
54 | {
55 | return $this->bidTokens;
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/Response/BidResponse.php:
--------------------------------------------------------------------------------
1 | auctions = $auctions;
25 | $this->credits = $credits;
26 | }
27 |
28 | /**
29 | * @return TradeItem[]
30 | */
31 | public function getAuctions() : array
32 | {
33 | return $this->auctions;
34 | }
35 |
36 | public function hasAuctions() : bool
37 | {
38 | return count($this->auctions) > 0;
39 | }
40 |
41 | public function hasAuction(int $index) : bool
42 | {
43 | return isset($this->auctions[$index]);
44 | }
45 |
46 | public function getAuction(int $index) : ?TradeItemInterface
47 | {
48 | if (! $this->hasAuction($index)) {
49 | return null;
50 | }
51 |
52 | return $this->auctions[$index];
53 | }
54 |
55 | public function getCredits() : ?int
56 | {
57 | return $this->credits;
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/data/fixtures/club_stats_stuff.respsonse.json:
--------------------------------------------------------------------------------
1 | {
2 | "bonus": [
3 | {
4 | "type": "defending",
5 | "value": 15
6 | },
7 | {
8 | "type": "dribbling",
9 | "value": 10
10 | },
11 | {
12 | "type": "pace",
13 | "value": 20
14 | },
15 | {
16 | "type": "heading",
17 | "value": 5
18 | },
19 | {
20 | "type": "passing",
21 | "value": 10
22 | },
23 | {
24 | "type": "fitness",
25 | "value": 18
26 | },
27 | {
28 | "type": "gkReflexes",
29 | "value": 5
30 | },
31 | {
32 | "type": "gkHandling",
33 | "value": 5
34 | },
35 | {
36 | "type": "gkOneOnOne",
37 | "value": 5
38 | },
39 | {
40 | "type": "gkPositioning",
41 | "value": 10
42 | },
43 | {
44 | "type": "contract",
45 | "value": 7
46 | },
47 | {
48 | "type": "managerTalk",
49 | "value": 0
50 | },
51 | {
52 | "type": "physioLeg",
53 | "value": 25
54 | },
55 | {
56 | "type": "physioBack",
57 | "value": 15
58 | },
59 | {
60 | "type": "physioHip",
61 | "value": 5
62 | }
63 | ]
64 | }
--------------------------------------------------------------------------------
/src/Authentication/Account.php:
--------------------------------------------------------------------------------
1 | credentials = $credentials;
30 | $this->proxy = $proxy;
31 | $this->session = $session;
32 | }
33 |
34 | public function getCredentials() : CredentialsInterface
35 | {
36 | return $this->credentials;
37 | }
38 |
39 | public function getSession() : ?SessionInterface
40 | {
41 | return $this->session;
42 | }
43 |
44 | public function setSession(SessionInterface $session) : void
45 | {
46 | $this->session = $session;
47 | }
48 |
49 | public function resetSession() : void
50 | {
51 | $this->session = null;
52 | }
53 |
54 | public function getProxy() : ?ProxyInterface
55 | {
56 | return $this->proxy;
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/Util/FutUtil.php:
--------------------------------------------------------------------------------
1 | 'FFA20PCC',
13 | 'xbox' => 'FFA20XBO',
14 | 'xbox360' => 'FFA20XBX',
15 | 'ps3' => 'FFA20PS3',
16 | 'ps4' => 'FFA20PS4',
17 | ];
18 |
19 | private function __construct()
20 | {
21 | // object not allowed
22 | }
23 |
24 | public static function getBaseId(int $assetId) : int
25 | {
26 | $version = 0;
27 | $assetId += 0xC4000000;
28 | while ($assetId > 0x01000000) {
29 | $version++;
30 | if ($version === 1) {
31 | //the constant applied to all items
32 | $assetId -= 1342177280;
33 | } elseif ($version === 2) {
34 | //the value added to the first updated version
35 | $assetId -= 50331648;
36 | } else {
37 | //the value added on all subsequent versions
38 | $assetId -= 16777216;
39 | }
40 | }
41 |
42 | return $assetId;
43 | }
44 |
45 | public static function getGameSku(string $platform) : string
46 | {
47 | if (! isset(self::GAME_SKU[$platform])) {
48 | throw new FutException('Wrong platform. (Valid ones are pc/xbox/xbox360/ps3/ps4)');
49 | }
50 |
51 | return self::GAME_SKU[$platform];
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/tests/Config/ConfigTest.php:
--------------------------------------------------------------------------------
1 | getOptions();
17 |
18 | $keys = [
19 | 'requestProtocol',
20 | 'authURL',
21 | 'eadpConnectHost',
22 | 'eadpPortalHost',
23 | 'eadpProxyHost',
24 | 'eadpClientId',
25 | 'eadpClientSecret',
26 | 'pinURL',
27 | 'releaseType',
28 | 'showOffURL',
29 | 'resourceRoot',
30 | 'resourceBase',
31 | 'changelist',
32 | 'requestTimeout',
33 | 'localStorageVersion',
34 | 'maxConsecutive500Errors',
35 | 'settingsRefreshInterval',
36 | 'verboseLogging',
37 | 'staticResponseData',
38 | 'originCss',
39 | 'originJS',
40 | 'originHost',
41 | 'originProfile',
42 | 'originMasterTitle',
43 | 'funCaptchaPublicKey',
44 | 'userAgent',
45 | 'delay',
46 | 'delayMinTime',
47 | 'delayMaxTime',
48 | ];
49 |
50 | self::assertCount(29, $options);
51 |
52 | foreach ($keys as $key) {
53 | self::assertArrayHasKey($key, $options);
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/Items/SuperBase.php:
--------------------------------------------------------------------------------
1 | data = $data;
21 | }
22 |
23 | /**
24 | * @return mixed[]
25 | */
26 | public function getArray() : array
27 | {
28 | return $this->data;
29 | }
30 |
31 | /**
32 | * @param mixed $key
33 | *
34 | * @return mixed
35 | */
36 | public function get($key)
37 | {
38 | return $this->data[$key] ?? null;
39 | }
40 |
41 | /**
42 | * @param mixed $offset
43 | */
44 | public function offsetExists($offset) : bool
45 | {
46 | return isset($this->data[$offset]) || array_key_exists($offset, $this->data);
47 | }
48 |
49 | /**
50 | * @param mixed $offset
51 | *
52 | * @return mixed
53 | */
54 | public function offsetGet($offset)
55 | {
56 | return $this->get($offset);
57 | }
58 |
59 | /**
60 | * @param mixed $offset
61 | * @param mixed $value
62 | */
63 | public function offsetSet($offset, $value) : void
64 | {
65 | $this->data[$offset] = $value;
66 | }
67 |
68 | /**
69 | * @param mixed $offset
70 | */
71 | public function offsetUnset($offset) : void
72 | {
73 | if (! $this->offsetExists($offset)) {
74 | return;
75 | }
76 |
77 | unset($this->data[$offset]);
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/tests/Authentication/CredentialsTest.php:
--------------------------------------------------------------------------------
1 | expectNotToPerformAssertions();
24 | }
25 |
26 | public function testCredentialFailure() : void
27 | {
28 | $this->expectException(FutException::class);
29 | $this->expectExceptionMessage('Wrong platform. (Valid ones are pc/xbox/xbox360/ps3/ps4)');
30 |
31 | new Credentials(
32 | 'test@example.com',
33 | 'password',
34 | 'wrong_platform'
35 | );
36 | }
37 |
38 | public function testCredentialEmail() : void
39 | {
40 | $this->expectException(InvalidArgumentException::class);
41 | $this->expectExceptionMessage('Expected a value to be a valid e-mail address. Got "wrong_email"');
42 |
43 | new Credentials(
44 | 'wrong_email',
45 | 'password',
46 | CredentialsInterface::PLATFORM_PS4
47 | );
48 | }
49 |
50 | public function testCredentialPassword() : void
51 | {
52 | $this->expectException(InvalidArgumentException::class);
53 | $this->expectExceptionMessage('Expected a non-empty value. Got: ""');
54 |
55 | new Credentials(
56 | 'test@example.com',
57 | '',
58 | CredentialsInterface::PLATFORM_PS4
59 | );
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/data/fixtures/unassigned.response.json:
--------------------------------------------------------------------------------
1 | {
2 | "itemData": [
3 | {
4 | "id": 369798564985,
5 | "timestamp": 1572616187,
6 | "formation": "f5212",
7 | "untradeable": false,
8 | "assetId": 244791,
9 | "rating": 77,
10 | "itemType": "player",
11 | "resourceId": 244791,
12 | "owners": 3,
13 | "discardValue": 308,
14 | "itemState": "free",
15 | "cardsubtypeid": 2,
16 | "lastSalePrice": 400,
17 | "fitness": 99,
18 | "injuryType": "none",
19 | "injuryGames": 0,
20 | "preferredPosition": "CM",
21 | "training": 0,
22 | "contract": 7,
23 | "teamid": 673,
24 | "rareflag": 0,
25 | "playStyle": 250,
26 | "leagueId": 4,
27 | "assists": 0,
28 | "lifetimeAssists": 0,
29 | "loyaltyBonus": 0,
30 | "pile": 6,
31 | "nation": 43,
32 | "marketDataMinPrice": 350,
33 | "marketDataMaxPrice": 10000,
34 | "resourceGameYear": 2020,
35 | "attributeArray": [
36 | 71,
37 | 68,
38 | 76,
39 | 76,
40 | 70,
41 | 68
42 | ],
43 | "statsArray": [
44 | 0,
45 | 0,
46 | 0,
47 | 0,
48 | 0
49 | ],
50 | "lifetimeStatsArray": [
51 | 0,
52 | 0,
53 | 0,
54 | 0,
55 | 0
56 | ],
57 | "skillmoves": 2,
58 | "weakfootabilitytypecode": 3,
59 | "attackingworkrate": 2,
60 | "defensiveworkrate": 2,
61 | "trait1": 3072,
62 | "trait2": 0,
63 | "preferredfoot": 1
64 | }
65 | ]
66 | }
--------------------------------------------------------------------------------
/src/Items/Item.php:
--------------------------------------------------------------------------------
1 | get('id');
14 | }
15 |
16 | public function getResourceId() : int
17 | {
18 | return $this->get('resourceId');
19 | }
20 |
21 | public function isUntradeable() : bool
22 | {
23 | return $this->get('untradeable');
24 | }
25 |
26 | public function getOwners() : int
27 | {
28 | return $this->get('owners');
29 | }
30 |
31 | public function getDiscardValue() : int
32 | {
33 | return $this->get('discardValue');
34 | }
35 |
36 | public function getLastSalePrice() : ?int
37 | {
38 | return $this->get('lastSalePrice');
39 | }
40 |
41 | public function getItemState() : ?string
42 | {
43 | return $this->get('itemState');
44 | }
45 |
46 | public function getCardSubTypeId() : ?int
47 | {
48 | return $this->get('cardsubtypeid');
49 | }
50 |
51 | public function getPile() : ?string
52 | {
53 | return $this->get('pile');
54 | }
55 |
56 | public function getMarketDataMinPrice() : ?int
57 | {
58 | return $this->get('marketDataMinPrice');
59 | }
60 |
61 | public function getMarketDataMaxPrice() : ?int
62 | {
63 | return $this->get('marketDataMaxPrice');
64 | }
65 |
66 | public function getTimestamp() : ?int
67 | {
68 | return $this->get('timestamp');
69 | }
70 |
71 | public function getItemType() : string
72 | {
73 | return $this->get('itemType');
74 | }
75 |
76 | public function getResourceGameYear() : ?int
77 | {
78 | return $this->get('resourceGameYear');
79 | }
80 |
81 | public function getDateTime() : DateTime
82 | {
83 | $date = new DateTime();
84 |
85 | if ($this->getTimestamp() !== null) {
86 | $date->setTimestamp($this->getTimestamp());
87 | }
88 |
89 | return $date;
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/src/Response/TradeStatusResponse.php:
--------------------------------------------------------------------------------
1 | credits = $credits;
34 | $this->auctions = $auctions;
35 | $this->bidTokens = $bidTokens;
36 | $this->currencies = $currencies;
37 | }
38 |
39 | public function getCredits() : ?int
40 | {
41 | return $this->credits;
42 | }
43 |
44 | /**
45 | * @return TradeItemInterface[]
46 | */
47 | public function getAuctions() : array
48 | {
49 | return $this->auctions;
50 | }
51 |
52 | public function hasAuctions() : bool
53 | {
54 | return count($this->auctions) > 0;
55 | }
56 |
57 | public function getAuction(int $index) : ?TradeItemInterface
58 | {
59 | if (! $this->hasAuction($index)) {
60 | return null;
61 | }
62 |
63 | return $this->auctions[$index];
64 | }
65 |
66 | public function hasAuction(int $index) : bool
67 | {
68 | return array_key_exists($index, $this->auctions);
69 | }
70 |
71 | /**
72 | * @return mixed[]
73 | */
74 | public function getBidTokens() : array
75 | {
76 | return $this->bidTokens;
77 | }
78 |
79 | /**
80 | * @return CurrencyValue[]
81 | */
82 | public function getCurrencies() : array
83 | {
84 | return $this->currencies;
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/data/fixtures/accountinfo.response.json:
--------------------------------------------------------------------------------
1 | {
2 | "userAccountInfo": {
3 | "personas": [
4 | {
5 | "personaId": 6540058052,
6 | "personaName": "PersonaName",
7 | "returningUser": 1,
8 | "onlineAccess": true,
9 | "trial": false,
10 | "userState": null,
11 | "userClubList": [
12 | {
13 | "year": "2019",
14 | "assetId": 160,
15 | "teamId": 160,
16 | "lastAccessTime": 1569537415,
17 | "platform": "ps3",
18 | "clubName": "clubName1",
19 | "clubAbbr": "clubAbbr1",
20 | "established": 1546179266,
21 | "divisionOnline": 1,
22 | "badgeId": 6000160,
23 | "skuAccessList": {
24 | "FFA19PS4": 1569537654
25 | }
26 | },
27 | {
28 | "year": "2020",
29 | "assetId": 114029,
30 | "teamId": 114029,
31 | "lastAccessTime": 1572615098,
32 | "platform": "ps3",
33 | "clubName": "clubName2",
34 | "clubAbbr": "clubAbbr2",
35 | "established": 1546179266,
36 | "divisionOnline": 1,
37 | "badgeId": 6114029,
38 | "skuAccessList": {
39 | "FFA20PS4": 1572615654
40 | }
41 | },
42 | {
43 | "year": "2019",
44 | "assetId": 160,
45 | "teamId": 160,
46 | "lastAccessTime": 1566051349,
47 | "platform": "ps3",
48 | "clubName": "clubName3",
49 | "clubAbbr": "clubAbbr2",
50 | "established": 1546179654,
51 | "divisionOnline": 0,
52 | "badgeId": 6000160,
53 | "skuAccessList": null
54 | }
55 | ],
56 | "trialFree": false
57 | }
58 | ]
59 | }
60 | }
--------------------------------------------------------------------------------
/src/Authentication/Credentials.php:
--------------------------------------------------------------------------------
1 | email = $email;
46 | $this->password = $password;
47 | $this->locale = $locale;
48 | $this->country = $country;
49 | $this->emulate = $emulate;
50 |
51 | $this->setPlatform($platform);
52 | }
53 |
54 | public function getEmail() : string
55 | {
56 | return $this->email;
57 | }
58 |
59 | public function getPassword() : string
60 | {
61 | return $this->password;
62 | }
63 |
64 | public function getPlatform() : string
65 | {
66 | return $this->platform;
67 | }
68 |
69 | public function getEmulate() : string
70 | {
71 | return $this->emulate;
72 | }
73 |
74 | public function getLocale() : string
75 | {
76 | return $this->locale;
77 | }
78 |
79 | public function getCountry() : string
80 | {
81 | return $this->country;
82 | }
83 |
84 | protected function setPlatform(string $platform) : void
85 | {
86 | if (! in_array($platform, self::VALID_PLATFORMS, true)) {
87 | throw new FutException('Wrong platform. (Valid ones are pc/xbox/xbox360/ps3/ps4)', [
88 | 'reason' => 'invalid_platform',
89 | ]);
90 | }
91 |
92 | $this->platform = $platform;
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/src/Model/Proxy.php:
--------------------------------------------------------------------------------
1 | protocol = $protocol;
32 | $this->ip = $ip;
33 | $this->port = $port;
34 | $this->username = $username;
35 | $this->password = $password;
36 | }
37 |
38 | public function getProtocol() : string
39 | {
40 | return $this->protocol;
41 | }
42 |
43 | public function getIp() : string
44 | {
45 | return $this->ip;
46 | }
47 |
48 | public function setIp(string $ip) : void
49 | {
50 | $this->ip = $ip;
51 | }
52 |
53 | public function getPort() : string
54 | {
55 | return $this->port;
56 | }
57 |
58 | public function setPort(string $port) : void
59 | {
60 | $this->port = $port;
61 | }
62 |
63 | public function getUsername() : ?string
64 | {
65 | return $this->username;
66 | }
67 |
68 | public function setUsername(?string $username) : void
69 | {
70 | $this->username = $username;
71 | }
72 |
73 | public function getPassword() : ?string
74 | {
75 | return $this->password;
76 | }
77 |
78 | public function setPassword(?string $password) : void
79 | {
80 | $this->password = $password;
81 | }
82 |
83 | public function getProxyProtocol() : string
84 | {
85 | $auth = '';
86 |
87 | if ($this->getUsername() !== null) {
88 | $auth = $this->getUsername();
89 |
90 | if ($this->getPassword() !== null) {
91 | $auth .= ':' . $this->getPassword();
92 | }
93 |
94 | $auth .= '@';
95 | }
96 |
97 | return $this->getProtocol() . '://' . $auth . $this->getIp() . ':' . $this->getPort();
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/tests/Api/PinTest.php:
--------------------------------------------------------------------------------
1 | createMock(Session::class);
22 | $session->method('getDob')->willReturn('2019-02-15');
23 | $session->method('getPersona')->willReturn('persona_stuff');
24 | $session->method('getNucleus')->willReturn('nucleus_stuff');
25 | $session->method('getSession')->willReturn('session_stuff');
26 |
27 | $account = $this->createMock(Account::class);
28 | $account->method('getSession')->willReturn($session);
29 |
30 | $factory = $this->createMock(ClientFactory::class);
31 |
32 | $pin = new Pin($account, $factory);
33 |
34 | $eventLogin = $pin->event('login', 'Page');
35 | $eventError = $pin->event('error');
36 |
37 | self::assertEquals([
38 | 'pgid' => 'Page',
39 | 'type' => 'utas',
40 | 'userid' => 'persona_stuff',
41 | 'core' => [
42 | 'dob' => '2019-02-15',
43 | 'en' => 'login',
44 | 'pid' => 'persona_stuff',
45 | 'pidm' => [
46 | 'nucleus' => 'nucleus_stuff',
47 | ],
48 | 'pidt' => 'persona',
49 | 's' => 2,
50 | 'ts_event' => $date->format('Y-m-d\TH:i:s.v\Z'),
51 | ],
52 | ], $eventLogin);
53 |
54 | self::assertEquals([
55 | 'type' => 'disconnect',
56 | 'core' => [
57 | 'dob' => '2019-02-15',
58 | 'en' => 'error',
59 | 'pid' => 'persona_stuff',
60 | 'pidm' => [
61 | 'nucleus' => 'nucleus_stuff',
62 | ],
63 | 'pidt' => 'persona',
64 | 's' => 3,
65 | 'ts_event' => $date->format('Y-m-d\TH:i:s.v\Z'),
66 | ],
67 | 'server_type' => 'utas',
68 | 'errid' => 'server_error',
69 | 'sid' => 'session_stuff',
70 | ], $eventError);
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/Items/TradeItem.php:
--------------------------------------------------------------------------------
1 | item = $item;
23 | }
24 |
25 | public function getTradeId() : int
26 | {
27 | return $this->get('tradeId');
28 | }
29 |
30 | public function getItem() : ItemInterface
31 | {
32 | return $this->item;
33 | }
34 |
35 | public function getTradeState() : ?string
36 | {
37 | return $this->get('tradeState');
38 | }
39 |
40 | public function getBuyNowPrice() : int
41 | {
42 | return $this->get('buyNowPrice');
43 | }
44 |
45 | public function getBidValue() : int
46 | {
47 | return $this->getCurrentBid() > 200 ? $this->getCurrentBid() : $this->getStartingBid();
48 | }
49 |
50 | public function getCurrentBid() : int
51 | {
52 | return $this->get('currentBid');
53 | }
54 |
55 | public function getOffers() : int
56 | {
57 | return $this->get('offers');
58 | }
59 |
60 | public function isWatched() : bool
61 | {
62 | return $this->get('watched');
63 | }
64 |
65 | public function getBidState() : ?string
66 | {
67 | return $this->get('bidState');
68 | }
69 |
70 | public function getStartingBid() : int
71 | {
72 | return $this->get('startingBid');
73 | }
74 |
75 | public function getConfidenceValue() : int
76 | {
77 | return $this->get('confidenceValue');
78 | }
79 |
80 | public function getExpires() : int
81 | {
82 | return $this->get('expires');
83 | }
84 |
85 | public function getExpireDate() : ?DateTime
86 | {
87 | if ($this->getExpires() > 0) {
88 | return new Carbon('+' . $this->getExpires() . ' seconds');
89 | }
90 |
91 | return null;
92 | }
93 |
94 | public function getSellerName() : ?string
95 | {
96 | return $this->get('sellerName');
97 | }
98 |
99 | public function getSellerEstablished() : int
100 | {
101 | return $this->get('sellerEstablished');
102 | }
103 |
104 | public function getSellerId() : int
105 | {
106 | return $this->get('sellerId');
107 | }
108 |
109 | public function isTradeOwner() : ?bool
110 | {
111 | return $this->get('tradeOwner');
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/data/fixtures/sets_challenges.response.json:
--------------------------------------------------------------------------------
1 | {
2 | "challenges": [
3 | {
4 | "name": "Hexenkessel",
5 | "priority": 0,
6 | "status": "NOT_STARTED",
7 | "setId": 100,
8 | "description": "Wähle die richtigen Zutaten, um die Challenge abzuschließen!",
9 | "challengeId": 212,
10 | "endTime": 15376,
11 | "repeatable": false,
12 | "formation": "f4411",
13 | "timesCompleted": 0,
14 | "elgReq": [
15 | {
16 | "type": "SAME_NATION_COUNT",
17 | "eligibilitySlot": 1,
18 | "eligibilityKey": 4,
19 | "eligibilityValue": 3
20 | },
21 | {
22 | "type": "SCOPE",
23 | "eligibilitySlot": 1,
24 | "eligibilityKey": 13,
25 | "eligibilityValue": 1
26 | },
27 | {
28 | "type": "SAME_LEAGUE_COUNT",
29 | "eligibilitySlot": 2,
30 | "eligibilityKey": 5,
31 | "eligibilityValue": 4
32 | },
33 | {
34 | "type": "SCOPE",
35 | "eligibilitySlot": 2,
36 | "eligibilityKey": 13,
37 | "eligibilityValue": 1
38 | },
39 | {
40 | "type": "PLAYER_COUNT",
41 | "eligibilitySlot": 3,
42 | "eligibilityKey": 2,
43 | "eligibilityValue": 3
44 | },
45 | {
46 | "type": "SCOPE",
47 | "eligibilitySlot": 3,
48 | "eligibilityKey": 13,
49 | "eligibilityValue": 2
50 | },
51 | {
52 | "type": "PLAYER_LEVEL",
53 | "eligibilitySlot": 3,
54 | "eligibilityKey": 17,
55 | "eligibilityValue": 3
56 | },
57 | {
58 | "type": "TEAM_CHEMISTRY",
59 | "eligibilitySlot": 4,
60 | "eligibilityKey": 1,
61 | "eligibilityValue": 60
62 | },
63 | {
64 | "type": "SCOPE",
65 | "eligibilitySlot": 4,
66 | "eligibilityKey": 13,
67 | "eligibilityValue": 0
68 | }
69 | ],
70 | "elgOperation": "AND",
71 | "tutorial": 0,
72 | "type": "BRICK_CHALLENGE",
73 | "challengeImageId": "100006-2dcb22ad-3800"
74 | }
75 | ]
76 | }
--------------------------------------------------------------------------------
/src/Authentication/Session.php:
--------------------------------------------------------------------------------
1 | persona = $persona;
46 | $this->nucleus = $nucleus;
47 | $this->phishing = $phishing;
48 | $this->session = $session;
49 | $this->dob = $dob;
50 | $this->accessToken = $accessToken;
51 | $this->tokenType = $tokenType;
52 | $this->expiresAt = $expiresAt;
53 | }
54 |
55 | public static function create(
56 | string $persona,
57 | string $nucleus,
58 | string $phishing,
59 | string $session,
60 | string $dob,
61 | string $accessToken,
62 | ?string $tokenType = null,
63 | ?DateTime $expiresAt = null
64 | ) : Session {
65 | return new self($persona, $nucleus, $phishing, $session, $dob, $accessToken, $tokenType, $expiresAt);
66 | }
67 |
68 | public function getPersona() : string
69 | {
70 | return $this->persona;
71 | }
72 |
73 | public function getNucleus() : string
74 | {
75 | return $this->nucleus;
76 | }
77 |
78 | public function getPhishing() : string
79 | {
80 | return $this->phishing;
81 | }
82 |
83 | public function getSession() : string
84 | {
85 | return $this->session;
86 | }
87 |
88 | public function getDob() : string
89 | {
90 | return $this->dob;
91 | }
92 |
93 | public function getAccessToken() : string
94 | {
95 | return $this->accessToken;
96 | }
97 |
98 | public function getTokenType() : ?string
99 | {
100 | return $this->tokenType;
101 | }
102 |
103 | public function getExpiresAt() : ?DateTime
104 | {
105 | return $this->expiresAt;
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/tests/Api/Endpoint/EndpointTestCase.php:
--------------------------------------------------------------------------------
1 | createMock(Session::class);
30 | $session->method('getDob')->willReturn('2019-02-15');
31 | $session->method('getPersona')->willReturn('persona_stuff');
32 | $session->method('getNucleus')->willReturn('nucleus_stuff');
33 | $session->method('getSession')->willReturn('session_stuff');
34 |
35 | $credentials = $this->createMock(Credentials::class);
36 | $credentials->method('getPlatform')->willReturn('ps4');
37 |
38 | $account = $this->createMock(Account::class);
39 | $account->method('getSession')->willReturn($session);
40 | $account->method('getCredentials')->willReturn($credentials);
41 |
42 | $pin = $this->createMock(Pin::class);
43 |
44 | $config = new Config();
45 |
46 | $core = new Core($account, $config, $factory);
47 |
48 | $reflectionClass = new ReflectionClass($core);
49 |
50 | $reflectionProperty = $reflectionClass->getProperty('pin');
51 | $reflectionProperty->setAccessible(true);
52 | $reflectionProperty->setValue($core, $pin);
53 |
54 | return $core;
55 | }
56 |
57 | protected function createClientFactoryMock(string $filename) : ClientFactory
58 | {
59 | $jsonData = file_get_contents(__DIR__ . '/../../../data/fixtures/' . $filename);
60 |
61 | $body = $this->createMock(StreamInterface::class);
62 | $body->method('getContents')->willReturn($jsonData);
63 |
64 | $response = $this->createMock(ResponseInterface::class);
65 | $response->method('getBody')->willReturn($body);
66 | $response->method('getStatusCode')->willReturn(200);
67 |
68 | $clientCall = $this->createMock(ClientCall::class);
69 | $clientCall->method('getResponse')->willReturn($response);
70 | $clientCall->method('getContent')->willReturn($jsonData);
71 |
72 | $factory = $this->createMock(ClientFactory::class);
73 | $factory->method('request')->willReturn($clientCall);
74 |
75 | return $factory;
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/Config/Config.php:
--------------------------------------------------------------------------------
1 | futConfigUrl = $futConfigUrl;
37 | $this->resolveOptions($options);
38 | }
39 |
40 | public function isDelay() : bool
41 | {
42 | return $this->getOption('delay');
43 | }
44 |
45 | public function getDelayMinTime() : int
46 | {
47 | return $this->getOption('delayMinTime');
48 | }
49 |
50 | public function getDelayMaxTime() : int
51 | {
52 | return $this->getOption('delayMaxTime');
53 | }
54 |
55 | public function getRandomDelayTime(?int $min = null, ?int $max = null) : int
56 | {
57 | if ($min === null) {
58 | $min = $this->getDelayMinTime();
59 | }
60 |
61 | if ($max === null) {
62 | $max = $this->getDelayMaxTime();
63 | }
64 |
65 | $delayMS = random_int($min, $max);
66 |
67 | return $delayMS * 1000;
68 | }
69 |
70 | public function getUserAgent() : string
71 | {
72 | return $this->getOption('userAgent');
73 | }
74 |
75 | /**
76 | * @inheritDoc
77 | */
78 | public function getOptions() : array
79 | {
80 | return $this->options;
81 | }
82 |
83 | /**
84 | * @inheritDoc
85 | */
86 | public function getOption(string $name)
87 | {
88 | return $this->options[$name];
89 | }
90 |
91 | /**
92 | * @inheritDoc
93 | */
94 | public function setOption(string $name, $value) : void
95 | {
96 | $this->options[$name] = $value;
97 | }
98 |
99 | protected function getResolver() : OptionsResolver
100 | {
101 | $resolver = new OptionsResolver();
102 |
103 | $url = $this->futConfigUrl;
104 | $content = file_get_contents($url);
105 |
106 | $futConfig = $content !== false? json_decode($content, true, 512, JSON_THROW_ON_ERROR) :null;
107 |
108 | if (! is_array($futConfig)) {
109 | $futConfig = [];
110 | }
111 |
112 | $defaults = array_merge($futConfig, [
113 | 'userAgent' => self::USER_AGENT,
114 | 'delay' => true,
115 | 'delayMinTime' => 1000,
116 | 'delayMaxTime' => 1500,
117 | ]);
118 |
119 | $resolver->setDefaults($defaults);
120 |
121 | return $resolver;
122 | }
123 |
124 | /**
125 | * @param mixed[] $options
126 | */
127 | protected function resolveOptions(array $options) : void
128 | {
129 | $this->options = $this->getResolver()->resolve($options);
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "shapecode/fut-api",
3 | "description": "FIFA WebApp API",
4 | "keywords": [
5 | "fut",
6 | "fifa",
7 | "api",
8 | "fifa20"
9 | ],
10 | "type": "library",
11 | "license": "GPL-3.0-or-later",
12 | "homepage": "https://github.com/shapecode/fut-api",
13 | "support": {
14 | "email": "git@shapecode.de",
15 | "issues": "https://github.com/shapecode/fut-api/issues",
16 | "source": "https://github.com/shapecode/fut-api/releases",
17 | "wiki": "https://github.com/shapecode/fut-api/wiki"
18 | },
19 | "authors": [
20 | {
21 | "name": "Shapecode",
22 | "homepage": "https://shapecode.de",
23 | "email": "git@shapecode.de"
24 | }
25 | ],
26 | "require": {
27 | "php": "~7.3",
28 | "ext-json": "*",
29 | "ext-mbstring": "*",
30 |
31 | "symfony/yaml": "^3.4|^4.0",
32 | "symfony/translation": "^3.4|^4.0",
33 | "symfony/stopwatch": "^3.4|^4.0",
34 | "symfony/options-resolver": "^3.4|^4.0",
35 |
36 | "webmozart/assert": "^1.5",
37 | "nesbot/carbon": "^2.25",
38 | "psr/http-factory": "^1.0",
39 | "psr/http-message": "^1.0",
40 | "psr/log": "^1.0",
41 | "php-http/logger-plugin": "^1.0",
42 | "php-http/stopwatch-plugin": "^1.0",
43 | "php-http/discovery": "^1.4",
44 | "php-http/client-common": "^2.0",
45 | "php-http/message": "^1.7",
46 | "php-http/httplug": "^2.0",
47 | "php-http/promise": "^1.0",
48 | "php-http/message-factory": "^1.0",
49 | "php-http/guzzle6-adapter": "^2.0",
50 | "guzzlehttp/psr7": "^1.6",
51 | "guzzlehttp/promises": "^1.3",
52 | "guzzlehttp/guzzle": "^6.4",
53 | "nyholm/psr7": "^1.2"
54 | },
55 | "require-dev": {
56 | "doctrine/coding-standard": "^6.0",
57 | "roave/security-advisories": "dev-master",
58 | "squizlabs/php_codesniffer": "^3.4",
59 | "phpstan/phpstan": "^0.11.16",
60 | "phpstan/phpstan-deprecation-rules": "^0.11.2",
61 | "phpstan/phpstan-phpunit": "^0.11.2",
62 | "phpstan/phpstan-strict-rules": "^0.11.1",
63 | "maglnet/composer-require-checker": "^2.0",
64 | "phpunit/phpunit": "^8.4",
65 | "symfony/var-dumper": "^4.3",
66 | "symfony/dotenv": "^4.3",
67 | "icanhazstring/composer-unused": "^0.6.2"
68 | },
69 | "autoload": {
70 | "psr-4": {
71 | "Shapecode\\FUT\\Client\\": "src/"
72 | },
73 | "files": [
74 | "src/bootstrap.php"
75 | ]
76 | },
77 | "autoload-dev": {
78 | "psr-4": {
79 | "Shapecode\\FUT\\Client\\Tests\\": "tests/"
80 | }
81 | },
82 | "scripts": {
83 | "check": [
84 | "@crc",
85 | "@cs-fix",
86 | "@cs-check",
87 | "@phpstan",
88 | "@phpunit"
89 | ],
90 | "check-build": [
91 | "@crc",
92 | "@cs-check",
93 | "@phpstan",
94 | "@phpunit"
95 | ],
96 | "phpstan": "./vendor/bin/phpstan analyse ./src",
97 | "crc": "./vendor/bin/composer-require-checker --config-file=./composer-require-checker.json",
98 | "phpunit": "./vendor/bin/phpunit",
99 | "cs-fix": "./vendor/bin/phpcbf",
100 | "cs-check": "./vendor/bin/phpcs -s"
101 | },
102 | "extra": {
103 | "branch-alias": {
104 | "dev-master": "20.0-dev"
105 | },
106 | "unused": [
107 | "nyholm/psr7"
108 | ]
109 | },
110 | "minimum-stability": "dev",
111 | "prefer-stable": true
112 | }
113 |
--------------------------------------------------------------------------------
/src/Items/Player.php:
--------------------------------------------------------------------------------
1 | get('formation');
12 | }
13 |
14 | public function getAssetId() : ?int
15 | {
16 | return $this->get('assetId');
17 | }
18 |
19 | public function getRating() : ?int
20 | {
21 | return $this->get('rating');
22 | }
23 |
24 | public function getMorale() : ?int
25 | {
26 | return $this->get('morale');
27 | }
28 |
29 | public function getFitness() : ?int
30 | {
31 | return $this->get('fitness');
32 | }
33 |
34 | public function getInjuryType() : ?string
35 | {
36 | return $this->get('injuryType');
37 | }
38 |
39 | public function getInjuryGames() : ?int
40 | {
41 | return $this->get('injuryGames');
42 | }
43 |
44 | public function getPreferredPosition() : ?string
45 | {
46 | return $this->get('preferredPosition');
47 | }
48 |
49 | /**
50 | * @return mixed[]
51 | */
52 | public function getStatsList() : array
53 | {
54 | return $this->get('statsList') ?? [];
55 | }
56 |
57 | /**
58 | * @return mixed[]
59 | */
60 | public function getLifetimeStats() : array
61 | {
62 | return $this->get('lifetimeStats') ?? [];
63 | }
64 |
65 | public function getTraining() : ?int
66 | {
67 | return $this->get('training');
68 | }
69 |
70 | public function getContract() : ?int
71 | {
72 | return $this->get('contract');
73 | }
74 |
75 | public function getSuspension() : ?int
76 | {
77 | return $this->get('suspension');
78 | }
79 |
80 | /**
81 | * @return mixed[]
82 | */
83 | public function getAttributeList() : array
84 | {
85 | return $this->get('attributeList') ?? [];
86 | }
87 |
88 | public function getTeamid() : ?int
89 | {
90 | return $this->get('teamid');
91 | }
92 |
93 | public function getRareflag() : ?bool
94 | {
95 | return $this->get('rareflag');
96 | }
97 |
98 | public function getPlayStyle() : ?int
99 | {
100 | return $this->get('playStyle');
101 | }
102 |
103 | public function getLeagueId() : ?int
104 | {
105 | return $this->get('leagueId');
106 | }
107 |
108 | public function getAssists() : ?int
109 | {
110 | return $this->get('assists');
111 | }
112 |
113 | public function getLifetimeAssists() : ?int
114 | {
115 | return $this->get('lifetimeAssists');
116 | }
117 |
118 | public function getLoans() : ?int
119 | {
120 | return $this->get('loans');
121 | }
122 |
123 | public function getLoyaltyBonus() : ?bool
124 | {
125 | return (bool) $this->get('loyaltyBonus');
126 | }
127 |
128 | public function getNation() : ?int
129 | {
130 | return $this->get('nation');
131 | }
132 |
133 | public function getSkillMoves() : ?int
134 | {
135 | return $this->get('skillmoves');
136 | }
137 |
138 | public function getWeakFootAbilityTypeCode() : ?int
139 | {
140 | return $this->get('weakfootabilitytypecode');
141 | }
142 |
143 | public function getAttackingWorkRate() : ?int
144 | {
145 | return $this->get('attackingworkrate');
146 | }
147 |
148 | public function getDefensiveWorkRate() : ?int
149 | {
150 | return $this->get('defensiveworkrate');
151 | }
152 |
153 | public function getPreferredFoot() : ?int
154 | {
155 | return $this->get('preferredfoot');
156 | }
157 |
158 | public function getItemType() : string
159 | {
160 | return 'player';
161 | }
162 | }
163 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
5 |
6 | # FIFA 20 WebApp API
7 |
8 | Manage your FIFA 20 Ultimate Team using this FIFA 20 Ultimate Team API.
9 | Written solely in PHP
10 |
11 | [](http://paypal.me/nloges)
12 |
13 | [](https://packagist.org/packages/shapecode/fut-api)
14 | [](https://packagist.org/packages/shapecode/fut-api)
15 | [](https://packagist.org/packages/shapecode/fut-api)
16 | [](https://packagist.org/packages/shapecode/fut-api)
17 | [](https://packagist.org/packages/shapecode/fut-api)
18 | [](https://packagist.org/packages/shapecode/fut-api)
19 | [](https://packagist.org/packages/shapecode/fut-api)
20 |
21 |
22 | ## Installing
23 |
24 | The recommended way to install FIFA 20 WebApp API is through
25 | [Composer](http://getcomposer.org).
26 |
27 | ```bash
28 | composer require shapecode/fut-api "~20.0@dev"
29 | ```
30 |
31 | ## Contribute
32 |
33 | Don't be shy. Feel free to contribute and create pull-requests. There's a lot to do.
34 |
35 | ## Usage
36 |
37 | ### Login
38 |
39 | Login parameters:
40 |
41 | - email: [string] email used for logging into the FIFA 20 WebApp
42 | - password: [string] password used for logging into the FIFA 20 WebApp
43 | - platform: [string] pc/ps4/ps4/xbox/xbox360
44 | - code: [string] email code for two-step verification (make sure to use string if your code starts with 0).
45 |
46 | ```php
47 | use FUTApi\Api\Core;
48 | use FUTApi\Exception\FutError;
49 | use FUTApi\Api\Authentication\Account;
50 | use FUTApi\Api\Authentication\Credentials;
51 | use FUTApi\Api\Authentication\Session;
52 |
53 | $credentials = new Credentials($email, $password, $platform);
54 |
55 | // if you already have a valid session
56 | $session = new Session($persona, $nucleus, $phishing, $session, $dob, $accessToken, $tokenType);
57 |
58 | // otherwise
59 | $session = null;
60 |
61 | $account = new Account($credentials, $session);
62 | $fut = new Core($account);
63 |
64 | try {
65 | $login = $fut->login($code);
66 | $session = $account->getSession();
67 | } catch(FutError $e) {
68 | $reason = $e->getReason();
69 | die("We have an error logging in: ".$reason);
70 | }
71 | ```
72 |
73 | After you have initiated your first session, you can then use the API wthout logging in again using the session info.
74 |
75 |
76 | ### Search
77 |
78 | Optional parameters:
79 |
80 | - min_price: [int] Minimal price.
81 | - max_price: [int] Maximum price.
82 | - min_buy: [int] Minimal buy now price.
83 | - max_buy: [int] Maximum buy now price.
84 | - level: ['bronze'/'silver'/gold'] Card level.
85 | - start: [int] Start page number.
86 | - category: ['fitness'/'?'] Card category.
87 | - assetId: [int] assetId.
88 | - defId: [int] defId.
89 | - league: [int] League id.
90 | - club: [int] Club id.
91 | - position: [int?/str?] Position.
92 | - zone: ['attacker'/'?'] zone.
93 | - nationality: [int] Nation id.
94 | - rare: [boolean] True for searching special cards.
95 | - playStyle: [str?] playStyle.
96 | - page_size: [int] Amount of cards on single page (changing this might be risky).
97 |
98 | ```php
99 | $options = [];
100 | $items = $fut->search($options);
101 | ```
102 |
103 | ### Logout
104 |
105 | Replicates clicking the Logout button.
106 |
107 | ```php
108 | $fut->logout();
109 | ```
110 |
111 |
112 | ## License
113 |
114 | GNU GPLv3
115 |
116 | ##### Forked
117 |
118 | https://github.com/InkedCurtis/FUT-API
119 |
--------------------------------------------------------------------------------
/data/fixtures/bid.response.json:
--------------------------------------------------------------------------------
1 | {
2 | "credits": 22194,
3 | "auctionInfo": [
4 | {
5 | "tradeId": 261547766667,
6 | "itemData": {
7 | "id": 369465564985,
8 | "timestamp": 1572616187,
9 | "formation": "f5212",
10 | "untradeable": false,
11 | "assetId": 244791,
12 | "rating": 77,
13 | "itemType": "player",
14 | "resourceId": 244791,
15 | "owners": 3,
16 | "discardValue": 308,
17 | "itemState": "free",
18 | "cardsubtypeid": 2,
19 | "lastSalePrice": 400,
20 | "fitness": 99,
21 | "injuryType": "none",
22 | "injuryGames": 0,
23 | "preferredPosition": "CM",
24 | "training": 0,
25 | "contract": 7,
26 | "teamid": 673,
27 | "rareflag": 0,
28 | "playStyle": 250,
29 | "leagueId": 4,
30 | "assists": 0,
31 | "lifetimeAssists": 0,
32 | "loyaltyBonus": 0,
33 | "pile": 6,
34 | "nation": 43,
35 | "resourceGameYear": 2020,
36 | "attributeArray": [
37 | 71,
38 | 68,
39 | 76,
40 | 76,
41 | 70,
42 | 68
43 | ],
44 | "statsArray": [
45 | 0,
46 | 0,
47 | 0,
48 | 0,
49 | 0
50 | ],
51 | "lifetimeStatsArray": [
52 | 0,
53 | 0,
54 | 0,
55 | 0,
56 | 0
57 | ],
58 | "skillmoves": 2,
59 | "weakfootabilitytypecode": 3,
60 | "attackingworkrate": 2,
61 | "defensiveworkrate": 2,
62 | "trait1": 3072,
63 | "trait2": 0,
64 | "preferredfoot": 1
65 | },
66 | "tradeState": "closed",
67 | "buyNowPrice": 400,
68 | "currentBid": 400,
69 | "offers": 0,
70 | "watched": false,
71 | "bidState": "buyNow",
72 | "startingBid": 350,
73 | "confidenceValue": 100,
74 | "expires": -1,
75 | "sellerName": "FIFA UT",
76 | "sellerEstablished": 0,
77 | "sellerId": 0,
78 | "tradeOwner": false,
79 | "coinsProcessed": 1,
80 | "tradeIdStr": "261547766667"
81 | }
82 | ],
83 | "bidTokens": {},
84 | "currencies": [
85 | {
86 | "name": "COINS",
87 | "funds": 22194,
88 | "finalFunds": 22194
89 | },
90 | {
91 | "name": "POINTS",
92 | "funds": 0,
93 | "finalFunds": 0
94 | },
95 | {
96 | "name": "DRAFT_TOKEN",
97 | "funds": 0,
98 | "finalFunds": 0
99 | }
100 | ],
101 | "dynamicObjectivesUpdates": {
102 | "needsGroupsRefresh": true,
103 | "scmpCategoryProgressList": [
104 | {
105 | "categoryId": 1,
106 | "scmpGroupProgressList": [
107 | {
108 | "groupId": 136,
109 | "state": 1,
110 | "objectiveProgressList": [
111 | {
112 | "objectiveId": 22041,
113 | "state": 1,
114 | "progressCount": 29
115 | }
116 | ]
117 | },
118 | {
119 | "groupId": 235,
120 | "state": 1,
121 | "objectiveProgressList": [
122 | {
123 | "objectiveId": 22557,
124 | "state": 3,
125 | "progressCount": 1
126 | }
127 | ]
128 | }
129 | ]
130 | },
131 | {
132 | "categoryId": 1,
133 | "scmpGroupProgressList": [
134 | {
135 | "groupId": 192,
136 | "state": 1,
137 | "objectiveProgressList": [
138 | {
139 | "objectiveId": 22385,
140 | "state": 3,
141 | "progressCount": 1
142 | }
143 | ]
144 | }
145 | ]
146 | }
147 | ],
148 | "autoRedeemedXp": 450,
149 | "needsAutoClaim": false
150 | }
151 | }
--------------------------------------------------------------------------------
/data/fixtures/watchlist.reponse.json:
--------------------------------------------------------------------------------
1 | {
2 | "total": 2,
3 | "credits": 22594,
4 | "auctionInfo": [
5 | {
6 | "tradeId": 261547235571,
7 | "itemData": {
8 | "id": 366188751336,
9 | "timestamp": 1571143759,
10 | "formation": "f343",
11 | "untradeable": false,
12 | "assetId": 158023,
13 | "rating": 94,
14 | "itemType": "player",
15 | "resourceId": 158023,
16 | "owners": 1,
17 | "discardValue": 752,
18 | "itemState": "forSale",
19 | "cardsubtypeid": 3,
20 | "lastSalePrice": 0,
21 | "fitness": 99,
22 | "injuryType": "none",
23 | "injuryGames": 0,
24 | "preferredPosition": "RW",
25 | "training": 0,
26 | "contract": 18,
27 | "teamid": 241,
28 | "rareflag": 1,
29 | "playStyle": 250,
30 | "leagueId": 53,
31 | "assists": 34,
32 | "lifetimeAssists": 34,
33 | "loyaltyBonus": 1,
34 | "pile": 5,
35 | "nation": 52,
36 | "resourceGameYear": 2020,
37 | "attributeArray": [
38 | 87,
39 | 92,
40 | 92,
41 | 96,
42 | 39,
43 | 66
44 | ],
45 | "statsArray": [
46 | 111,
47 | 90,
48 | 1,
49 | 0,
50 | 0
51 | ],
52 | "lifetimeStatsArray": [
53 | 111,
54 | 90,
55 | 1,
56 | 0,
57 | 0
58 | ],
59 | "skillmoves": 3,
60 | "weakfootabilitytypecode": 4,
61 | "attackingworkrate": 0,
62 | "defensiveworkrate": 1,
63 | "trait1": 58865920,
64 | "trait2": 0,
65 | "preferredfoot": 2
66 | },
67 | "tradeState": "active",
68 | "buyNowPrice": 1799000,
69 | "currentBid": 0,
70 | "offers": 0,
71 | "watched": true,
72 | "bidState": "none",
73 | "startingBid": 1750000,
74 | "confidenceValue": 100,
75 | "expires": 3004,
76 | "sellerName": "FIFA UT",
77 | "sellerEstablished": 0,
78 | "sellerId": 0,
79 | "tradeOwner": false,
80 | "tradeIdStr": "261547235571"
81 | },
82 | {
83 | "tradeId": 261372538143,
84 | "itemData": {
85 | "id": 368508640970,
86 | "timestamp": 1571954707,
87 | "formation": "f5221",
88 | "untradeable": false,
89 | "assetId": 202857,
90 | "rating": 82,
91 | "itemType": "player",
92 | "resourceId": 202857,
93 | "owners": 1,
94 | "discardValue": 656,
95 | "itemState": "forSale",
96 | "cardsubtypeid": 3,
97 | "lastSalePrice": 0,
98 | "fitness": 99,
99 | "injuryType": "none",
100 | "injuryGames": 0,
101 | "preferredPosition": "RW",
102 | "training": 0,
103 | "contract": 7,
104 | "teamid": 32,
105 | "rareflag": 1,
106 | "playStyle": 250,
107 | "leagueId": 19,
108 | "assists": 0,
109 | "lifetimeAssists": 0,
110 | "loyaltyBonus": 1,
111 | "pile": 5,
112 | "nation": 21,
113 | "resourceGameYear": 2020,
114 | "attributeArray": [
115 | 92,
116 | 76,
117 | 74,
118 | 84,
119 | 34,
120 | 71
121 | ],
122 | "statsArray": [
123 | 0,
124 | 0,
125 | 0,
126 | 0,
127 | 0
128 | ],
129 | "lifetimeStatsArray": [
130 | 0,
131 | 0,
132 | 0,
133 | 0,
134 | 0
135 | ],
136 | "skillmoves": 3,
137 | "weakfootabilitytypecode": 3,
138 | "attackingworkrate": 2,
139 | "defensiveworkrate": 0,
140 | "trait1": 133632,
141 | "trait2": 0,
142 | "preferredfoot": 1
143 | },
144 | "tradeState": "active",
145 | "buyNowPrice": 7200,
146 | "currentBid": 750,
147 | "offers": 0,
148 | "watched": true,
149 | "bidState": "highest",
150 | "startingBid": 750,
151 | "confidenceValue": 100,
152 | "expires": 163084,
153 | "sellerName": "FIFA UT",
154 | "sellerEstablished": 0,
155 | "sellerId": 0,
156 | "tradeOwner": false,
157 | "tradeIdStr": "261372538143"
158 | }
159 | ]
160 | }
--------------------------------------------------------------------------------
/data/fixtures/tradepile.response.json:
--------------------------------------------------------------------------------
1 | {
2 | "credits": 22594,
3 | "auctionInfo": [
4 | {
5 | "tradeId": 0,
6 | "itemData": {
7 | "id": 363965480067,
8 | "timestamp": 1570300781,
9 | "formation": "f451",
10 | "untradeable": false,
11 | "assetId": 241497,
12 | "rating": 72,
13 | "itemType": "player",
14 | "resourceId": 241497,
15 | "owners": 1,
16 | "discardValue": 252,
17 | "itemState": "free",
18 | "cardsubtypeid": 1,
19 | "lastSalePrice": 0,
20 | "fitness": 99,
21 | "injuryType": "none",
22 | "injuryGames": 0,
23 | "preferredPosition": "RB",
24 | "training": 0,
25 | "contract": 7,
26 | "teamid": 73,
27 | "rareflag": 1,
28 | "playStyle": 250,
29 | "leagueId": 16,
30 | "assists": 0,
31 | "lifetimeAssists": 0,
32 | "loyaltyBonus": 1,
33 | "pile": 5,
34 | "nation": 18,
35 | "marketDataMinPrice": 300,
36 | "marketDataMaxPrice": 10000,
37 | "resourceGameYear": 2020,
38 | "attributeArray": [
39 | 83,
40 | 50,
41 | 68,
42 | 77,
43 | 68,
44 | 57
45 | ],
46 | "statsArray": [
47 | 0,
48 | 0,
49 | 0,
50 | 0,
51 | 0
52 | ],
53 | "lifetimeStatsArray": [
54 | 0,
55 | 0,
56 | 0,
57 | 0,
58 | 0
59 | ],
60 | "skillmoves": 2,
61 | "weakfootabilitytypecode": 2,
62 | "attackingworkrate": 2,
63 | "defensiveworkrate": 0,
64 | "trait1": 0,
65 | "trait2": 0,
66 | "preferredfoot": 1
67 | },
68 | "tradeState": null,
69 | "buyNowPrice": 0,
70 | "currentBid": 0,
71 | "offers": 0,
72 | "watched": false,
73 | "bidState": null,
74 | "startingBid": 0,
75 | "confidenceValue": 100,
76 | "expires": 0,
77 | "sellerName": null,
78 | "sellerEstablished": 0,
79 | "sellerId": 0,
80 | "tradeOwner": null,
81 | "tradeIdStr": "0"
82 | },
83 | {
84 | "tradeId": 0,
85 | "itemData": {
86 | "id": 363256459876,
87 | "timestamp": 1570105892,
88 | "formation": "f5212",
89 | "untradeable": false,
90 | "assetId": 197681,
91 | "rating": 75,
92 | "itemType": "player",
93 | "resourceId": 50529329,
94 | "owners": 1,
95 | "discardValue": 300,
96 | "itemState": "free",
97 | "cardsubtypeid": 2,
98 | "lastSalePrice": 0,
99 | "fitness": 99,
100 | "injuryType": "none",
101 | "injuryGames": 0,
102 | "preferredPosition": "CM",
103 | "training": 0,
104 | "contract": 7,
105 | "teamid": 347,
106 | "rareflag": 0,
107 | "playStyle": 250,
108 | "leagueId": 31,
109 | "assists": 0,
110 | "lifetimeAssists": 0,
111 | "loyaltyBonus": 1,
112 | "pile": 5,
113 | "nation": 18,
114 | "marketDataMinPrice": 300,
115 | "marketDataMaxPrice": 10000,
116 | "resourceGameYear": 2020,
117 | "attributeArray": [
118 | 68,
119 | 60,
120 | 71,
121 | 78,
122 | 60,
123 | 75
124 | ],
125 | "statsArray": [
126 | 0,
127 | 0,
128 | 0,
129 | 0,
130 | 0
131 | ],
132 | "lifetimeStatsArray": [
133 | 0,
134 | 0,
135 | 0,
136 | 0,
137 | 0
138 | ],
139 | "skillmoves": 3,
140 | "weakfootabilitytypecode": 2,
141 | "attackingworkrate": 0,
142 | "defensiveworkrate": 1,
143 | "trait1": 32,
144 | "trait2": 0,
145 | "preferredfoot": 2
146 | },
147 | "tradeState": null,
148 | "buyNowPrice": 0,
149 | "currentBid": 0,
150 | "offers": 0,
151 | "watched": false,
152 | "bidState": null,
153 | "startingBid": 0,
154 | "confidenceValue": 100,
155 | "expires": 0,
156 | "sellerName": null,
157 | "sellerEstablished": 0,
158 | "sellerId": 0,
159 | "tradeOwner": null,
160 | "tradeIdStr": "0"
161 | }
162 | ],
163 | "bidTokens": {}
164 | }
--------------------------------------------------------------------------------
/src/Mapper/Mapper.php:
--------------------------------------------------------------------------------
1 | createTradeItem($a);
37 | }
38 |
39 | return new MarketSearchResponse(
40 | $data,
41 | $auctions,
42 | $bidTokens
43 | );
44 | }
45 |
46 | /**
47 | * @param mixed[] $data
48 | */
49 | public function createBidResult(array $data) : BidResponse
50 | {
51 | $as = $data['auctionInfo'] ?? [];
52 | $credits = $data['credits'] ?? null;
53 |
54 | $auctions = [];
55 |
56 | foreach ($as as $a) {
57 | $auctions[] = $this->createTradeItem($a);
58 | }
59 |
60 | return new BidResponse($auctions, $credits);
61 | }
62 |
63 | /**
64 | * @param mixed[] $data
65 | */
66 | public function createUnassignedResponse(array $data) : UnassignedResponse
67 | {
68 | $itemData = $data['itemData'] ?? [];
69 | $duplicateItemIdList = $data['duplicateItemIdList'] ?? [];
70 |
71 | $items = [];
72 | $duplicates = [];
73 |
74 | foreach ($itemData as $a) {
75 | $items[] = $this->createItem($a);
76 | }
77 |
78 | foreach ($duplicateItemIdList as $a) {
79 | $duplicates[] = $this->createDuplicateItem($a);
80 | }
81 |
82 | return new UnassignedResponse($items, $duplicates);
83 | }
84 |
85 | /**
86 | * @param mixed[] $data
87 | */
88 | public function createWatchlistResponse(array $data) : WatchlistResponse
89 | {
90 | $credits = $data['credits'] ?? null;
91 | $total = $data['total'] ?? null;
92 | $as = $data['auctionInfo'] ?? [];
93 |
94 | $auctions = [];
95 |
96 | foreach ($as as $a) {
97 | $auctions[] = $this->createTradeItem($a);
98 | }
99 |
100 | return new WatchlistResponse($total, $credits, $auctions);
101 | }
102 |
103 | /**
104 | * @param mixed[] $data
105 | */
106 | public function createTradepileResponse(array $data) : TradepileResponse
107 | {
108 | $credits = $data['credits'] ?? null;
109 | $as = $data['auctionInfo'] ?? [];
110 | $bs = $search['bidTokens'] ?? [];
111 |
112 | $auctions = [];
113 | $bidTokens = [];
114 |
115 | foreach ($as as $a) {
116 | $auctions[] = $this->createTradeItem($a);
117 | }
118 |
119 | return new TradepileResponse($credits, $auctions, $bidTokens);
120 | }
121 |
122 | /**
123 | * @param mixed[] $data
124 | */
125 | public function createTradeStatusResponse(array $data) : TradeStatusResponse
126 | {
127 | $credits = $data['credits'] ?? null;
128 | $as = $data['auctionInfo'] ?? [];
129 | $bs = $data['bidTokens'] ?? [];
130 | $cs = $data['currencies'] ?? [];
131 |
132 | $auctions = [];
133 | $bidTokens = [];
134 | $currencies = [];
135 |
136 | foreach ($as as $a) {
137 | $auctions[] = $this->createTradeItem($a);
138 | }
139 |
140 | foreach ($cs as $a) {
141 | $currencies[] = $this->createCurrencyValue($a);
142 | }
143 |
144 | return new TradeStatusResponse($credits, $auctions, $bidTokens, $currencies);
145 | }
146 |
147 | /**
148 | * @param mixed[] $data
149 | */
150 | public function createTradeItem(array $data) : TradeItem
151 | {
152 | $item = $this->createItem($data['itemData']);
153 |
154 | return new TradeItem($data, $item);
155 | }
156 |
157 | /**
158 | * @param mixed[] $data
159 | */
160 | public function createCurrencyValue(array $data) : CurrencyValue
161 | {
162 | return new CurrencyValue($data);
163 | }
164 |
165 | /**
166 | * @param mixed[] $data
167 | */
168 | public function createItem(array $data) : Item
169 | {
170 | $itemType = $data['itemType'] ?? null;
171 |
172 | if ($itemType === 'player') {
173 | return new Player($data);
174 | }
175 |
176 | if ($itemType === 'health') {
177 | return new Health($data);
178 | }
179 |
180 | if ($itemType === 'contract') {
181 | return new Contract($data);
182 | }
183 |
184 | if ($itemType === 'kit') {
185 | return new Kit($data);
186 | }
187 |
188 | return new Item($data);
189 | }
190 |
191 | /**
192 | * @param mixed[] $data
193 | */
194 | public function createDuplicateItem(array $data) : DuplicateItem
195 | {
196 | return new DuplicateItem($data['itemId'], $data['duplicateItemId']);
197 | }
198 | }
199 |
--------------------------------------------------------------------------------
/src/Api/Pin.php:
--------------------------------------------------------------------------------
1 | account = $account;
35 | $this->clientFactory = $clientFactory;
36 | }
37 |
38 | public function sendEvent(
39 | string $en,
40 | ?string $pgid = null,
41 | ?string $status = null,
42 | ?string $source = null,
43 | ?string $end_reason = null
44 | ) : void {
45 | $event = $this->event($en, $pgid, $status, $source, $end_reason);
46 | $this->send([$event]);
47 | }
48 |
49 | /**
50 | * @return mixed[]
51 | */
52 | public function event(
53 | string $en,
54 | ?string $pgid = null,
55 | ?string $status = null,
56 | ?string $source = null,
57 | ?string $end_reason = null
58 | ) : array {
59 | $account = $this->account;
60 | $session = $account->getSession();
61 |
62 | if ($session === null) {
63 | throw new RuntimeException('session has to be set');
64 | }
65 |
66 | $data = [
67 | 'core' => [
68 | 'dob' => $session->getDob(),
69 | 'en' => $en,
70 | 'pid' => $session->getPersona(),
71 | 'pidm' => [
72 | 'nucleus' => $session->getNucleus(),
73 | ],
74 | 'pidt' => 'persona',
75 | 's' => $this->s,
76 | // 'didm' => [
77 | // 'uuid' => '0',
78 | // ],
79 | 'ts_event' => $this->timestamp(),
80 | ],
81 | ];
82 |
83 | if ($pgid !== null) {
84 | $data['pgid'] = $pgid;
85 | }
86 |
87 | if ($status !== null) {
88 | $data['status'] = $status;
89 | }
90 |
91 | if ($source !== null) {
92 | $data['source'] = $source;
93 | }
94 |
95 | if ($end_reason !== null) {
96 | $data['end_reason'] = $end_reason;
97 | }
98 |
99 | switch ($en) {
100 | case 'login':
101 | $data['type'] = 'utas';
102 | $data['userid'] = $session->getPersona();
103 | break;
104 | case 'page_view':
105 | $data['type'] = 'menu';
106 | break;
107 | case 'error':
108 | $data['server_type'] = 'utas';
109 | $data['errid'] = 'server_error';
110 | $data['type'] = 'disconnect';
111 | $data['sid'] = $session->getSession();
112 | break;
113 | }
114 |
115 | $this->s++;
116 |
117 | return $data;
118 | }
119 |
120 | /**
121 | * @param mixed[] $events
122 | */
123 | public function send(array $events) : bool
124 | {
125 | $account = $this->account;
126 | $session = $account->getSession();
127 | $platform = $account->getCredentials()->getPlatform();
128 |
129 | if ($session === null) {
130 | throw new RuntimeException('session has to be set');
131 | }
132 |
133 | $body = json_encode([
134 | 'custom' => [
135 | 'networkAccess' => 'G',
136 | 'service_plat' => substr($platform, 0, 3),
137 | ],
138 | 'et' => 'client',
139 | 'events' => $events,
140 | 'gid' => 0,
141 | 'is_sess' => true,
142 | 'loc' => 'en_US',
143 | 'plat' => 'web',
144 | 'rel' => 'prod',
145 | 'sid' => $session->getSession(),
146 | 'taxv' => '1.1',
147 | 'tid' => 'FUT20WEB',
148 | 'tidt' => 'easku',
149 | 'ts_post' => $this->timestamp(),
150 | 'v' => '20.1.0',
151 | ], JSON_THROW_ON_ERROR);
152 |
153 | $headers = [
154 | 'Accept' => '*/*',
155 | 'Accept-Encoding' => 'gzip, deflate, br',
156 | 'Accept-Language' => 'en-US,de;q=0.9,en-US;q=0.8,en;q=0.7,lb;q=0.6',
157 | 'Cache-Control' => 'no-cache',
158 | 'Connection' => 'kkeep-alive',
159 | 'Content-Length' => mb_strlen($body),
160 | 'Content-Type' => 'application/json',
161 | 'DNT' => '1',
162 | 'Host' => 'pin-river.data.ea.com',
163 | 'Origin' => 'https://www.easports.com',
164 | 'Pragma' => 'no-cache',
165 | 'Referer' => 'https://www.easports.com/fifa/ultimate-team/web-app/',
166 | 'Sec-Fetch-Mode' => 'cors',
167 | 'Sec-Fetch-Site' => 'cross-site',
168 | 'x-ea-game-id' => 'FUT20WEB',
169 | 'x-ea-game-id-type' => 'easku',
170 | 'x-ea-taxv' => '1.1',
171 | ];
172 |
173 | $call = $this->clientFactory->request($account, 'POST', self::PIN_URL, [
174 | 'body' => $body,
175 | 'headers' => $headers,
176 | ]);
177 |
178 | $content = json_decode($call->getContent(), true, 512, JSON_THROW_ON_ERROR);
179 |
180 | if ($content['status'] !== 'ok') {
181 | throw new PinErrorException($call->getResponse());
182 | }
183 |
184 | return true;
185 | }
186 |
187 | private function timestamp() : string
188 | {
189 | return Carbon::now()->format(self::DATETIME_FORMAT);
190 | }
191 | }
192 |
--------------------------------------------------------------------------------
/src/Http/ClientFactory.php:
--------------------------------------------------------------------------------
1 | config = $config;
69 | $this->cookieJarBuilder = $cookieJarBuilder ?: new CookieJarBuilder();
70 | $this->logger = $logger ?: new NullLogger();
71 |
72 | $this->requestFactory = $requestFactory ?: Psr17FactoryDiscovery::findRequestFactory();
73 | $this->streamFactory = $streamFactory ?: Psr17FactoryDiscovery::findStreamFactory();
74 | $this->urlFactory = $urlFactory ?: Psr17FactoryDiscovery::findUrlFactory();
75 | }
76 |
77 | /**
78 | * @inheritdoc
79 | */
80 | public function request(
81 | AccountInterface $account,
82 | string $method,
83 | string $url,
84 | array $options = [],
85 | array $plugins = []
86 | ) : ClientCall {
87 | $headers = [];
88 |
89 | if (isset($options['headers'])) {
90 | /** @var mixed[] $headers */
91 | $headers = $options['headers'];
92 | unset($options['headers']);
93 | }
94 |
95 | $call = new ClientCall();
96 |
97 | $plugins[] = new HeaderSetPlugin(CoreInterface::REQUEST_HEADERS);
98 | $plugins[] = new HeaderSetPlugin([
99 | 'User-Agent' => $this->getConfig()->getUserAgent(),
100 | ]);
101 |
102 | if (count($headers) > 0) {
103 | $plugins[] = new HeaderSetPlugin($headers);
104 | }
105 |
106 | $plugins[] = new ContentLengthPlugin();
107 | $plugins[] = new LoggerPlugin($this->logger);
108 | $stopwatch = new Stopwatch();
109 | $plugins[] = new StopwatchPlugin($stopwatch);
110 | $plugins[] = new ClientCallPlugin($call);
111 | $plugins[] = new RedirectPlugin();
112 |
113 | $guzzle = $this->createAccountClient($account, $options);
114 | $client = $this->createPluginClient($guzzle, $plugins);
115 |
116 | $request = $this->createRequest($method, $url);
117 |
118 | $client->sendRequest($request);
119 |
120 | return $call;
121 | }
122 |
123 | /**
124 | * @inheritdoc
125 | */
126 | protected function createPluginClient(HttpClient $client, array $plugins = []) : PluginClient
127 | {
128 | return new PluginClient($client, $plugins);
129 | }
130 |
131 | /**
132 | * @inheritdoc
133 | */
134 | protected function createAccountClient(
135 | AccountInterface $account,
136 | array $options = []
137 | ) : GuzzleAdapter {
138 | $options['http_errors'] = false;
139 | $options['allow_redirects'] = true;
140 |
141 | if ($account->getProxy() !== null) {
142 | $options['proxy'] = $account->getProxy()->getProxyProtocol();
143 | }
144 |
145 | $options['cookies'] = $this->cookieJarBuilder->createCookieJar($account);
146 |
147 | $stack = HandlerStack::create(new CurlHandler());
148 | $stack->push(Middleware::retry($this->createRetryHandler()));
149 |
150 | $options['stack'] = $stack;
151 | $options['timeout'] = 5;
152 |
153 | $guzzle = new Client($options);
154 |
155 | return new GuzzleAdapter($guzzle);
156 | }
157 |
158 | /**
159 | * @inheritdoc
160 | */
161 | protected function createRequest(
162 | string $method,
163 | string $uri,
164 | ?string $body = null,
165 | array $headers = []
166 | ) : RequestInterface {
167 | $url = $this->urlFactory->createUri($uri);
168 | $request = $this->requestFactory->createRequest($method, $url);
169 |
170 | if ($body !== null) {
171 | $stream = $this->streamFactory->createStream($body);
172 | $request = $request->withBody($stream);
173 | }
174 |
175 | if (count($headers) > 0) {
176 | foreach ($headers as $name => $header) {
177 | $request = $request->withHeader($name, $header);
178 | }
179 | }
180 |
181 | return $request;
182 | }
183 |
184 | protected function getConfig() : ConfigInterface
185 | {
186 | return $this->config;
187 | }
188 |
189 | protected function createRetryHandler() : Closure
190 | {
191 | return static function (
192 | $retries,
193 | Psr7Request $request,
194 | ?Psr7Response $response = null,
195 | ?RequestException $exception = null
196 | ) {
197 | return $retries < self::MAX_RETRIES;
198 | };
199 | }
200 |
201 | protected function isServerError(?Psr7Response $response = null) : bool
202 | {
203 | return $response !== null && $response->getStatusCode() >= 500;
204 | }
205 |
206 | protected function isConnectError(?RequestException $exception = null) : bool
207 | {
208 | return $exception instanceof ConnectException;
209 | }
210 | }
211 |
--------------------------------------------------------------------------------
/src/Api/CoreInterface.php:
--------------------------------------------------------------------------------
1 | 'utas.external.s2.fut.ea.com:443',
16 | 'ps3' => 'utas.external.s2.fut.ea.com:443',
17 | 'ps4' => 'utas.external.s2.fut.ea.com:443',
18 | 'xbox' => 'utas.external.s3.fut.ea.com:443',
19 | ];
20 | public const REQUEST_HEADERS = [
21 | 'Accept' => '*/*',
22 | 'Accept-Encoding' => 'gzip, deflate, br',
23 | 'Accept-Language' => 'en-US,de;q=0.9,en-US;q=0.8,en;q=0.7,lb;q=0.6',
24 | 'Cache-Control' => 'no-cache',
25 | 'Connection' => 'keep-alive',
26 | 'DNT' => '1',
27 | 'Origin' => 'https://www.easports.com',
28 | 'Pragma' => 'no-cache',
29 | 'Referer' => 'https://www.easports.com/fifa/ultimate-team/web-app/',
30 | 'Sec-Fetch-Mode' => 'cors',
31 | 'Sec-Fetch-Site' => 'cross-site',
32 | ];
33 | public const AUTH_URL = 'utas.mob.v4.fut.ea.com:443';
34 | public const CLIENT_ID = 'FIFA-20-WEBCLIENT';
35 | public const SKU = 'FUT20WEB';
36 |
37 | /**
38 | * @return mixed[]
39 | */
40 | public function login(?string $code = null) : array;
41 |
42 | public function logout() : void;
43 |
44 | /**
45 | * @return mixed
46 | */
47 | public function searchDefinition(int $assetId, int $start = 0, int $count = 20);
48 |
49 | /**
50 | * @param mixed[] $params
51 | *
52 | * lev: level
53 | * cat: category
54 | * definitionId:
55 | * micr: min bid price
56 | * macr: max bid price
57 | * minb: min bin price
58 | * maxb: max bin price
59 | * leag: league
60 | * team: club
61 | * pos: position
62 | * zone:
63 | * nation:
64 | * rare: rare type
65 | * playStyle: player style
66 | *
67 | * @return mixed
68 | */
69 | public function search(array $params = [], int $pageSize = 20, int $start = 0);
70 |
71 | /**
72 | * @param mixed $tradeId
73 | * @param mixed $price
74 | *
75 | * @return mixed
76 | */
77 | public function bid($tradeId, $price);
78 |
79 | /**
80 | * @return mixed
81 | */
82 | public function usermassInfo();
83 |
84 | /**
85 | * @return mixed
86 | */
87 | public function credits();
88 |
89 | /**
90 | * @param mixed[] $params
91 | *
92 | * @return mixed
93 | *
94 | * level
95 | * defId
96 | * start
97 | * count
98 | * sort
99 | */
100 | public function club(array $params = []);
101 |
102 | /**
103 | * @param mixed[] $params
104 | *
105 | * @return mixed[]
106 | */
107 | public function players(array $params = []) : array;
108 |
109 | /**
110 | * @param mixed[] $params
111 | *
112 | * @return mixed[]
113 | */
114 | public function stadiums(array $params = []) : array;
115 |
116 | /**
117 | * @param mixed[] $params
118 | *
119 | * @return mixed[]
120 | */
121 | public function kits(array $params = []) : array;
122 |
123 | /**
124 | * @param mixed[] $params
125 | *
126 | * @return mixed[]
127 | */
128 | public function staffs(array $params = []) : array;
129 |
130 | /**
131 | * @param mixed[] $params
132 | *
133 | * @return mixed[]
134 | */
135 | public function badges(array $params = []) : array;
136 |
137 | /**
138 | * @param mixed[] $params
139 | *
140 | * @return mixed[]
141 | */
142 | public function balls(array $params = []) : array;
143 |
144 | /**
145 | * @return mixed
146 | */
147 | public function clubStaff();
148 |
149 | /**
150 | * @return mixed
151 | */
152 | public function clubConsumables();
153 |
154 | /**
155 | * @return mixed
156 | */
157 | public function squad(int $squadId = 0);
158 |
159 | /**
160 | * @param mixed $tradeId
161 | *
162 | * @return mixed
163 | */
164 | public function tradeStatus($tradeId);
165 |
166 | /**
167 | * @return mixed
168 | */
169 | public function tradepile();
170 |
171 | /**
172 | * @return mixed
173 | */
174 | public function watchlist();
175 |
176 | /**
177 | * @param mixed $tradeId
178 | *
179 | * @return mixed
180 | */
181 | public function watchlistDelete($tradeId);
182 |
183 | /**
184 | * @return mixed
185 | */
186 | public function unassigned();
187 |
188 | /**
189 | * @param mixed $id
190 | * @param mixed $bid
191 | * @param mixed $bin
192 | *
193 | * @return mixed
194 | */
195 | public function sell($id, $bid, $bin, int $duration = 3600);
196 |
197 | /**
198 | * @param mixed $itemId
199 | *
200 | * @return mixed
201 | */
202 | public function quickSell($itemId);
203 |
204 | /**
205 | * @param mixed $tradeId
206 | *
207 | * @return mixed
208 | */
209 | public function removeSold($tradeId);
210 |
211 | /**
212 | * @param mixed $itemId
213 | *
214 | * @return mixed
215 | */
216 | public function sendToTradepile($itemId);
217 |
218 | /**
219 | * @param mixed $itemId
220 | *
221 | * @return mixed
222 | */
223 | public function sendToClub($itemId);
224 |
225 | /**
226 | * @param mixed $tradeId
227 | *
228 | * @return mixed
229 | */
230 | public function sendToWatchList($tradeId);
231 |
232 | /**
233 | * @param mixed $definitionId
234 | *
235 | * @return mixed
236 | */
237 | public function priceRange($definitionId);
238 |
239 | /**
240 | * @return mixed
241 | */
242 | public function relist();
243 |
244 | /**
245 | * @param mixed $itemId
246 | * @param mixed $resourceId
247 | *
248 | * @return mixed
249 | */
250 | public function applyConsumable($itemId, $resourceId);
251 |
252 | /**
253 | * @return mixed
254 | */
255 | public function keepalive();
256 |
257 | /**
258 | * @return mixed
259 | */
260 | public function pileSize();
261 |
262 | /**
263 | * @param mixed $packId
264 | *
265 | * @return mixed
266 | */
267 | public function buyPack($packId, string $currency = 'COINS');
268 |
269 | /**
270 | * @param mixed $packId
271 | *
272 | * @return mixed
273 | */
274 | public function openPack($packId);
275 |
276 | /**
277 | * @return mixed
278 | */
279 | public function squadBuildingSets();
280 |
281 | /**
282 | * @param mixed $setId
283 | *
284 | * @return mixed
285 | */
286 | public function squadBuildingChallenges($setId);
287 |
288 | /**
289 | * @return mixed
290 | */
291 | public function objectives(string $scope = 'all');
292 |
293 | /**
294 | * @return mixed|string
295 | */
296 | public function getCaptchaData();
297 |
298 | public function validateCaptcha(string $token) : ClientCall;
299 |
300 | public function phishingQuestion() : ClientCall;
301 |
302 | /**
303 | * @param mixed $answer
304 | */
305 | public function phishingValidate($answer) : ClientCall;
306 | }
307 |
--------------------------------------------------------------------------------
/src/Util/EAHasher.php:
--------------------------------------------------------------------------------
1 | hash($string);
45 | }
46 |
47 | private function int32(int $val) : int
48 | {
49 | return $val & 0xFFFFFFFF;
50 | }
51 |
52 | private function ff(int $a, int $b, int $c, int $d, int $x, int $s, int $t) : int
53 | {
54 | $a = $this->int32($a);
55 | $b = $this->int32($b);
56 | $c = $this->int32($c);
57 | $d = $this->int32($d);
58 | $x = $this->int32($x);
59 | $s = $this->int32($s);
60 | $t = $this->int32($t);
61 |
62 | return $this->cmn(($b & $c) | ((~$b) & $d), $a, $b, $x, $s, $t);
63 | }
64 |
65 | private function gg(int $a, int $b, int $c, int $d, int $x, int $s, int $t) : int
66 | {
67 | $a = $this->int32($a);
68 | $b = $this->int32($b);
69 | $c = $this->int32($c);
70 | $d = $this->int32($d);
71 | $x = $this->int32($x);
72 | $s = $this->int32($s);
73 | $t = $this->int32($t);
74 |
75 | return $this->cmn(($b & $d) | ($c & (~$d)), $a, $b, $x, $s, $t);
76 | }
77 |
78 | private function hh(int $a, int $b, int $c, int $d, int $x, int $s, int $t) : int
79 | {
80 | $a = $this->int32($a);
81 | $b = $this->int32($b);
82 | $c = $this->int32($c);
83 | $d = $this->int32($d);
84 | $x = $this->int32($x);
85 | $s = $this->int32($s);
86 | $t = $this->int32($t);
87 |
88 | return $this->cmn($b ^ $c ^ $d, $a, $b, $x, $s, $t);
89 | }
90 |
91 | private function ii(int $a, int $b, int $c, int $d, int $x, int $s, int $t) : int
92 | {
93 | $a = $this->int32($a);
94 | $b = $this->int32($b);
95 | $c = $this->int32($c);
96 | $d = $this->int32($d);
97 | $x = $this->int32($x);
98 | $s = $this->int32($s);
99 | $t = $this->int32($t);
100 |
101 | return $this->cmn($c ^ ($b | (~$d)), $a, $b, $x, $s, $t);
102 | }
103 |
104 | private function cmn(int $q, int $a, int $b, int $x, int $s, int $t) : int
105 | {
106 | $q = $this->int32($q);
107 | $b = $this->int32($b);
108 | $x = $this->int32($x);
109 | $s = $this->int32($s);
110 | $t = $this->int32($t);
111 |
112 | return $this->add($this->bitwiseRotate($this->add($this->add($a, $q), $this->add($x, $t)), $s), $b);
113 | }
114 |
115 | private function add(int $x, int $y) : int
116 | {
117 | $x = $this->int32($x);
118 | $y = $this->int32($y);
119 | $lsw = ($x & 0xFFFF) + ($y & 0xFFFF);
120 | $msw = ($x >> 16) + ($y >> 16) + ($lsw >> 16);
121 |
122 | return ($msw << 16) | ($lsw & 0xFFFF);
123 | }
124 |
125 | private function bitwiseRotate(int $x, int $c) : int
126 | {
127 | $x = $this->int32($x);
128 |
129 | return ($x << $c) | $this->uRShift($x, 32 - $c);
130 | }
131 |
132 | private function uRShift(int $number, int $shiftBits) : int
133 | {
134 | $number = $this->int32($number);
135 | $z = hexdec('80000000');
136 | // if ($z !== null & $number !== null) {
137 | $number = ($number >> 1);
138 | $number &= (~$z);
139 | $number |= 0x40000000;
140 | $number >>= ($shiftBits - 1);
141 | // } else {
142 | // $number = ($number >> $shiftBits);
143 | // }
144 |
145 | return $number;
146 | }
147 |
148 | private function numberToHex(int $number) : string
149 | {
150 | $result = '';
151 | for ($j = 0; $j <= 3; $j++) {
152 | $result .= static::$HexCharacters[($number >> ($j * 8 + 4)) & 0x0F];
153 | $result .= static::$HexCharacters[($number >> ($j * 8)) & 0x0F];
154 | }
155 |
156 | return $result;
157 | }
158 |
159 | /**
160 | * @return int[]
161 | */
162 | private function chunkInput(string $input) : array
163 | {
164 | $inputLength = strlen($input);
165 | $numberOfBlocks = $inputLength + 8 >> 6 + 1;
166 | $blocks = [];
167 | for ($i = 0; $i < $numberOfBlocks * 16; $i++) {
168 | $blocks[$i] = 0;
169 | }
170 | for ($i = 0; $i < $inputLength; $i++) {
171 | $blocks[$i >> 2] |= ord($input[$i]) << (($i % 4) * 8);
172 | }
173 | $blocks[$inputLength >> 2] |= 0x80 << (($inputLength % 4) * 8);
174 | $blocks[$numberOfBlocks * 16 - 2] = $inputLength * 8;
175 |
176 | return $blocks;
177 | }
178 |
179 | private function hash(string $string) : string
180 | {
181 | $chunks = $this->chunkInput($string);
182 | $count = count($chunks);
183 | $a = 1732584193;
184 | $b = -271733879;
185 | $c = -1732584194;
186 | $d = 271733878;
187 | for ($i = 0; $i < $count; $i += 16) {
188 | $tempA = $a;
189 | $tempB = $b;
190 | $tempC = $c;
191 | $tempD = $d;
192 | $a = $this->ff($a, $b, $c, $d, $chunks[$i + 0], static::$R1Shifts[0], -680876936);
193 | $d = $this->ff($d, $a, $b, $c, $chunks[$i + 1], static::$R1Shifts[1], -389564586);
194 | $c = $this->ff($c, $d, $a, $b, $chunks[$i + 2], static::$R1Shifts[2], 606105819);
195 | $b = $this->ff($b, $c, $d, $a, $chunks[$i + 3], static::$R1Shifts[3], -1044525330);
196 | $a = $this->ff($a, $b, $c, $d, $chunks[$i + 4], static::$R1Shifts[4], -176418897);
197 | $d = $this->ff($d, $a, $b, $c, $chunks[$i + 5], static::$R1Shifts[5], 1200080426);
198 | $c = $this->ff($c, $d, $a, $b, $chunks[$i + 6], static::$R1Shifts[6], -1473231341);
199 | $b = $this->ff($b, $c, $d, $a, $chunks[$i + 7], static::$R1Shifts[7], -45705983);
200 | $a = $this->ff($a, $b, $c, $d, $chunks[$i + 8], static::$R1Shifts[8], 1770035416);
201 | $d = $this->ff($d, $a, $b, $c, $chunks[$i + 9], static::$R1Shifts[9], -1958414417);
202 | $c = $this->ff($c, $d, $a, $b, $chunks[$i + 10], static::$R1Shifts[10], -42063);
203 | $b = $this->ff($b, $c, $d, $a, $chunks[$i + 11], static::$R1Shifts[11], -1990404162);
204 | $a = $this->ff($a, $b, $c, $d, $chunks[$i + 12], static::$R1Shifts[12], 1804603682);
205 | $d = $this->ff($d, $a, $b, $c, $chunks[$i + 13], static::$R1Shifts[13], -40341101);
206 | $c = $this->ff($c, $d, $a, $b, $chunks[$i + 14], static::$R1Shifts[14], -1502002290);
207 | $b = $this->ff($b, $c, $d, $a, $chunks[$i + 15], static::$R1Shifts[15], 1236535329);
208 | $a = $this->gg($a, $b, $c, $d, $chunks[$i + 1], static::$R2Shifts[0], -165796510);
209 | $d = $this->gg($d, $a, $b, $c, $chunks[$i + 6], static::$R2Shifts[1], -1069501632);
210 | $c = $this->gg($c, $d, $a, $b, $chunks[$i + 11], static::$R2Shifts[2], 643717713);
211 | $b = $this->gg($b, $c, $d, $a, $chunks[$i + 0], static::$R2Shifts[3], -373897302);
212 | $a = $this->gg($a, $b, $c, $d, $chunks[$i + 5], static::$R2Shifts[4], -701558691);
213 | $d = $this->gg($d, $a, $b, $c, $chunks[$i + 10], static::$R2Shifts[5], 38016083);
214 | $c = $this->gg($c, $d, $a, $b, $chunks[$i + 15], static::$R2Shifts[6], -660478335);
215 | $b = $this->gg($b, $c, $d, $a, $chunks[$i + 4], static::$R2Shifts[7], -405537848);
216 | $a = $this->gg($a, $b, $c, $d, $chunks[$i + 9], static::$R2Shifts[8], 568446438);
217 | $d = $this->gg($d, $a, $b, $c, $chunks[$i + 14], static::$R2Shifts[9], -1019803690);
218 | $c = $this->gg($c, $d, $a, $b, $chunks[$i + 3], static::$R2Shifts[10], -187363961);
219 | $b = $this->gg($b, $c, $d, $a, $chunks[$i + 8], static::$R2Shifts[11], 1163531501);
220 | $a = $this->gg($a, $b, $c, $d, $chunks[$i + 13], static::$R2Shifts[12], -1444681467);
221 | $d = $this->gg($d, $a, $b, $c, $chunks[$i + 2], static::$R2Shifts[13], -51403784);
222 | $c = $this->gg($c, $d, $a, $b, $chunks[$i + 7], static::$R2Shifts[14], 1735328473);
223 | $b = $this->gg($b, $c, $d, $a, $chunks[$i + 12], static::$R2Shifts[15], -1926607734);
224 | $a = $this->hh($a, $b, $c, $d, $chunks[$i + 5], static::$R3Shifts[0], -378558);
225 | $d = $this->hh($d, $a, $b, $c, $chunks[$i + 8], static::$R3Shifts[1], -2022574463);
226 | //line below uses _r2Shifts[2] where as MD5 would use _r3Shifts[2]
227 | $c = $this->hh($c, $d, $a, $b, $chunks[$i + 11], static::$R2Shifts[2], 1839030562);
228 | $b = $this->hh($b, $c, $d, $a, $chunks[$i + 14], static::$R3Shifts[3], -35309556);
229 | $a = $this->hh($a, $b, $c, $d, $chunks[$i + 1], static::$R3Shifts[4], -1530992060);
230 | $d = $this->hh($d, $a, $b, $c, $chunks[$i + 4], static::$R3Shifts[5], 1272893353);
231 | $c = $this->hh($c, $d, $a, $b, $chunks[$i + 7], static::$R3Shifts[6], -155497632);
232 | $b = $this->hh($b, $c, $d, $a, $chunks[$i + 10], static::$R3Shifts[7], -1094730640);
233 | $a = $this->hh($a, $b, $c, $d, $chunks[$i + 13], static::$R3Shifts[8], 681279174);
234 | $d = $this->hh($d, $a, $b, $c, $chunks[$i + 0], static::$R3Shifts[9], -358537222);
235 | $c = $this->hh($c, $d, $a, $b, $chunks[$i + 3], static::$R3Shifts[10], -722521979);
236 | $b = $this->hh($b, $c, $d, $a, $chunks[$i + 6], static::$R3Shifts[11], 76029189);
237 | $a = $this->hh($a, $b, $c, $d, $chunks[$i + 9], static::$R3Shifts[12], -640364487);
238 | $d = $this->hh($d, $a, $b, $c, $chunks[$i + 12], static::$R3Shifts[13], -421815835);
239 | $c = $this->hh($c, $d, $a, $b, $chunks[$i + 15], static::$R3Shifts[14], 530742520);
240 | $b = $this->hh($b, $c, $d, $a, $chunks[$i + 2], static::$R3Shifts[15], -995338651);
241 | $a = $this->ii($a, $b, $c, $d, $chunks[$i + 0], static::$R4Shifts[0], -198630844);
242 | $d = $this->ii($d, $a, $b, $c, $chunks[$i + 7], static::$R4Shifts[1], 1126891415);
243 | $c = $this->ii($c, $d, $a, $b, $chunks[$i + 14], static::$R4Shifts[2], -1416354905);
244 | $b = $this->ii($b, $c, $d, $a, $chunks[$i + 5], static::$R4Shifts[3], -57434055);
245 | $a = $this->ii($a, $b, $c, $d, $chunks[$i + 12], static::$R4Shifts[4], 1700485571);
246 | $d = $this->ii($d, $a, $b, $c, $chunks[$i + 3], static::$R4Shifts[5], -1894986606);
247 | $c = $this->ii($c, $d, $a, $b, $chunks[$i + 10], static::$R4Shifts[6], -1051523);
248 | $b = $this->ii($b, $c, $d, $a, $chunks[$i + 1], static::$R4Shifts[7], -2054922799);
249 | $a = $this->ii($a, $b, $c, $d, $chunks[$i + 8], static::$R4Shifts[8], 1873313359);
250 | $d = $this->ii($d, $a, $b, $c, $chunks[$i + 15], static::$R4Shifts[9], -30611744);
251 | $c = $this->ii($c, $d, $a, $b, $chunks[$i + 6], static::$R4Shifts[10], -1560198380);
252 | $b = $this->ii($b, $c, $d, $a, $chunks[$i + 13], static::$R4Shifts[11], 1309151649);
253 | $a = $this->ii($a, $b, $c, $d, $chunks[$i + 4], static::$R4Shifts[12], -145523070);
254 | $d = $this->ii($d, $a, $b, $c, $chunks[$i + 11], static::$R4Shifts[13], -1120210379);
255 | $c = $this->ii($c, $d, $a, $b, $chunks[$i + 2], static::$R4Shifts[14], 718787259);
256 | $b = $this->ii($b, $c, $d, $a, $chunks[$i + 9], static::$R4Shifts[15], -343485551);
257 | //This line is doubled for some reason, line below is not in the MD5 version
258 | $b = $this->ii($b, $c, $d, $a, $chunks[$i + 9], static::$R4Shifts[15], -343485551);
259 | $a = $this->add($a, $tempA);
260 | $b = $this->add($b, $tempB);
261 | $c = $this->add($c, $tempC);
262 | $d = $this->add($d, $tempD);
263 | }
264 |
265 | return $this->numberToHex($a) . $this->numberToHex($b) . $this->numberToHex($c) . $this->numberToHex($d);
266 | }
267 | }
268 |
--------------------------------------------------------------------------------
/data/fixtures/settings.response.json:
--------------------------------------------------------------------------------
1 | {
2 | "configs": [
3 | {
4 | "value": 1,
5 | "type": "enableSeasonalCampaigns"
6 | },
7 | {
8 | "value": 0,
9 | "type": "tokenRedemptionEnabled"
10 | },
11 | {
12 | "value": 1,
13 | "type": "tradingEnabled"
14 | },
15 | {
16 | "value": 1,
17 | "type": "enableEntitlementsVerificationInPromoMessages"
18 | },
19 | {
20 | "value": 1,
21 | "type": "newChemistry"
22 | },
23 | {
24 | "value": 1,
25 | "type": "enableSpectatorMode"
26 | },
27 | {
28 | "value": 1,
29 | "type": "coinEnabled_JP"
30 | },
31 | {
32 | "value": 20200101,
33 | "type": "transferFifaPointsFromLastYearEndDate"
34 | },
35 | {
36 | "value": 5,
37 | "type": "kitSelectCountdownTimerLengthInSeconds"
38 | },
39 | {
40 | "value": 1,
41 | "type": "coinEnabled"
42 | },
43 | {
44 | "value": 10,
45 | "type": "onlineEndSeasonDivision"
46 | },
47 | {
48 | "value": 15,
49 | "type": "deleteOperationTimeoutSec"
50 | },
51 | {
52 | "value": 20190101,
53 | "type": "transferFifaPointsFromLastYearStartDate"
54 | },
55 | {
56 | "value": 0,
57 | "type": "friendlySeasonsEnabled"
58 | },
59 | {
60 | "value": 1,
61 | "type": "storeEnabled_JP"
62 | },
63 | {
64 | "value": 1,
65 | "type": "enableSinglePlayerDraftMode"
66 | },
67 | {
68 | "value": 3,
69 | "type": "numEndMatchRetriesAllowed"
70 | },
71 | {
72 | "value": 0,
73 | "type": "enableBatchedTrades"
74 | },
75 | {
76 | "value": 1,
77 | "type": "checkServerDbVersion"
78 | },
79 | {
80 | "value": 1,
81 | "type": "allowGracePeriodForSquadBuildingSets"
82 | },
83 | {
84 | "value": 5,
85 | "type": "clubCreateThreshold"
86 | },
87 | {
88 | "value": 1,
89 | "type": "draftEntryPackStoreEnabled"
90 | },
91 | {
92 | "value": 1,
93 | "type": "immediateRecoveryAttempt"
94 | },
95 | {
96 | "value": 0,
97 | "type": "userProgressionDWLoggingEnabled"
98 | },
99 | {
100 | "value": 1,
101 | "type": "enableLiveMessaging"
102 | },
103 | {
104 | "value": 1,
105 | "type": "mtxEnabled"
106 | },
107 | {
108 | "value": 300,
109 | "type": "tFAResendIntervalSecs"
110 | },
111 | {
112 | "value": 1,
113 | "type": "packOddsLowPercentageLocalizationThreshold"
114 | },
115 | {
116 | "value": 0,
117 | "type": "enableLegacyYearInfoInItemResourceId"
118 | },
119 | {
120 | "value": 1,
121 | "type": "enablePlayerPicks"
122 | },
123 | {
124 | "value": 0,
125 | "type": "enableAbuseReporting"
126 | },
127 | {
128 | "value": 5000,
129 | "type": "deactivateUsersTimeoutMS"
130 | },
131 | {
132 | "value": 100,
133 | "type": "transferListSize"
134 | },
135 | {
136 | "value": 0,
137 | "type": "deviceTemporalScoresEnabled"
138 | },
139 | {
140 | "value": 2,
141 | "type": "immediateRecoveryAttemptDelay"
142 | },
143 | {
144 | "value": 1,
145 | "type": "processingStateEnabled"
146 | },
147 | {
148 | "value": 0,
149 | "type": "enableSavingRegionOnSession"
150 | },
151 | {
152 | "value": 0,
153 | "type": "tFAEnabled"
154 | },
155 | {
156 | "value": 1,
157 | "type": "enableGrantLoaner"
158 | },
159 | {
160 | "value": 1,
161 | "type": "pointsPackStoreEnabled"
162 | },
163 | {
164 | "value": 1,
165 | "type": "cardPackStoreEnabled"
166 | },
167 | {
168 | "value": 1,
169 | "type": "enableHouseRules"
170 | },
171 | {
172 | "value": 1,
173 | "type": "maxPTPSOptimization"
174 | },
175 | {
176 | "value": 840,
177 | "type": "rivalsInitialSkillPointsInPlacement"
178 | },
179 | {
180 | "value": 0,
181 | "type": "enableSCMPAutoClaimOnFUTHub"
182 | },
183 | {
184 | "value": 0,
185 | "type": "allowRemoveFifaPointsOnDeactivateUser"
186 | },
187 | {
188 | "value": 15,
189 | "type": "houseRulesFriendsListPageSize"
190 | },
191 | {
192 | "value": 1,
193 | "type": "enableLegendPlayers"
194 | },
195 | {
196 | "value": 50,
197 | "type": "transferTargetListSize"
198 | },
199 | {
200 | "value": 7200,
201 | "type": "extendGameSessionTimerSec"
202 | },
203 | {
204 | "value": 1140,
205 | "type": "clientKeepAliveResetTimeoutSec"
206 | },
207 | {
208 | "value": 0,
209 | "type": "enableEntitlementDraftEntry"
210 | },
211 | {
212 | "value": 1,
213 | "type": "fifaPointsEnabled"
214 | },
215 | {
216 | "value": 1,
217 | "type": "tradeProcessConfidenceValueEnabled"
218 | },
219 | {
220 | "value": 15,
221 | "type": "postOperationTimeoutSec"
222 | },
223 | {
224 | "value": 1,
225 | "type": "enableDraftTokenAccountCreation"
226 | },
227 | {
228 | "value": 15,
229 | "type": "houseRulesCouchPlayMaxMiniSquadCategoriesRetrieved"
230 | },
231 | {
232 | "value": 1,
233 | "type": "totwHistoryEnabled"
234 | },
235 | {
236 | "value": 50,
237 | "type": "tradeProcessConfidenceValue"
238 | },
239 | {
240 | "value": 0,
241 | "type": "enablePackOddsCalculatingDueToManualAction"
242 | },
243 | {
244 | "value": 0,
245 | "type": "enableTifoInGame"
246 | },
247 | {
248 | "value": 1000,
249 | "type": "championsGlickoRatingMultiplier"
250 | },
251 | {
252 | "value": 1,
253 | "type": "returningUserRewardsScreenEnabled"
254 | },
255 | {
256 | "value": 1,
257 | "type": "enableSquadBuildingSetsFeature"
258 | },
259 | {
260 | "value": 1,
261 | "type": "enableDraftMode"
262 | },
263 | {
264 | "value": 1,
265 | "type": "TS2Enabled"
266 | },
267 | {
268 | "value": 0,
269 | "type": "ps3ConsumableTxnB2B"
270 | },
271 | {
272 | "value": 10,
273 | "type": "auctionSearchResultsCacheSize"
274 | },
275 | {
276 | "value": 1,
277 | "type": "enableRivals"
278 | },
279 | {
280 | "value": 217874930400755712,
281 | "type": "auctionExpiryData"
282 | },
283 | {
284 | "value": 0,
285 | "type": "enableSimMatch"
286 | },
287 | {
288 | "value": 1,
289 | "type": "dreamSquadEnabled"
290 | },
291 | {
292 | "value": 0,
293 | "type": "spectatorModeChampionsEventIdOverride"
294 | },
295 | {
296 | "value": 20160,
297 | "type": "championsScheduleViewPeriodInMinutes"
298 | },
299 | {
300 | "value": 1,
301 | "type": "enableItemDataRarityGroupsInfo"
302 | },
303 | {
304 | "value": 20000,
305 | "type": "ps3B2BAuthCodeTimeout"
306 | },
307 | {
308 | "value": 1,
309 | "type": "seasonTicketStoreEnabled"
310 | },
311 | {
312 | "value": 0,
313 | "type": "charityPlayerDbID"
314 | },
315 | {
316 | "value": 1,
317 | "type": "mtxEnabled_JP"
318 | },
319 | {
320 | "value": 1800,
321 | "type": "rivalsEventEndTimeInSec"
322 | },
323 | {
324 | "value": 15,
325 | "type": "getOperationTimeoutSec"
326 | },
327 | {
328 | "value": 1,
329 | "type": "enableFloatPointSquadRating"
330 | },
331 | {
332 | "value": 240,
333 | "type": "rivalsPlacementRFactor"
334 | },
335 | {
336 | "value": 0,
337 | "type": "enableBannerInGame"
338 | },
339 | {
340 | "value": 24,
341 | "type": "gracePeriodWeekendLeagueInHours"
342 | },
343 | {
344 | "value": 0,
345 | "type": "enableV4CompanionCommerce"
346 | },
347 | {
348 | "value": 1,
349 | "type": "allowBrokeringSkuToRequestTargetSku"
350 | },
351 | {
352 | "value": 0,
353 | "type": "enablePackOddsCalculatingDueToCacheflush"
354 | },
355 | {
356 | "value": 1,
357 | "type": "enableMatchHistoryLogging"
358 | },
359 | {
360 | "value": 4,
361 | "type": "firstPartyCommerceFlowVersion"
362 | },
363 | {
364 | "value": 0,
365 | "type": "rewardProviderEnabled"
366 | },
367 | {
368 | "value": 1,
369 | "type": "enableLeaderboards"
370 | },
371 | {
372 | "value": 10,
373 | "type": "offlineEndSeasonDivision"
374 | },
375 | {
376 | "value": 1,
377 | "type": "allowUntradeableForSquadBuildingSets"
378 | },
379 | {
380 | "value": 1,
381 | "type": "TS2ForBrokeringEnabled"
382 | },
383 | {
384 | "value": 1,
385 | "type": "pickPackRulesFixEnabled"
386 | },
387 | {
388 | "value": 0,
389 | "type": "enableRegionalKillSwitchFramework"
390 | },
391 | {
392 | "value": 1,
393 | "type": "userTemporalScoresEnabled"
394 | },
395 | {
396 | "value": 0,
397 | "type": "enableUntradeableQueryParameterForSearchClubApi"
398 | },
399 | {
400 | "value": 1,
401 | "type": "sendSimEvents"
402 | },
403 | {
404 | "value": 10,
405 | "type": "houseRulesCouchPlayMaxSquadsRetrieved"
406 | },
407 | {
408 | "value": 0,
409 | "type": "enableLoyaltyBonusForConceptPlayers"
410 | },
411 | {
412 | "value": 1,
413 | "type": "tournamentQuitEnabled"
414 | },
415 | {
416 | "value": 1,
417 | "type": "enableShowPackOdds"
418 | },
419 | {
420 | "value": 0,
421 | "type": "tradeOffersEnabled"
422 | },
423 | {
424 | "value": 15,
425 | "type": "putOperationTimeoutSec"
426 | },
427 | {
428 | "value": 0,
429 | "type": "rivalsSkillPointsCapValue"
430 | },
431 | {
432 | "value": 1,
433 | "type": "loanPlayerPurchaseFeatureEnable"
434 | },
435 | {
436 | "value": 1209600,
437 | "type": "SecurityScoreResetExternalScoreApplyTillSecs"
438 | },
439 | {
440 | "value": 0,
441 | "type": "enableSpecificErrorForBannedUserLogin"
442 | },
443 | {
444 | "value": 1,
445 | "type": "tokenPackStoreEnabled"
446 | },
447 | {
448 | "value": 30,
449 | "type": "squadSlots"
450 | },
451 | {
452 | "value": 5,
453 | "type": "scmpMaxNumberOfRequestedGroups"
454 | },
455 | {
456 | "value": 1,
457 | "type": "enableDefIdSearch"
458 | },
459 | {
460 | "value": 1,
461 | "type": "fifaPointsEnabled_JP"
462 | },
463 | {
464 | "value": 1,
465 | "type": "enableFUTChampions"
466 | },
467 | {
468 | "value": 1,
469 | "type": "tournamentEnabled"
470 | },
471 | {
472 | "value": 1,
473 | "type": "cardPackStoreEnabled_JP"
474 | },
475 | {
476 | "value": 0,
477 | "type": "phishingEnabled"
478 | },
479 | {
480 | "value": 0,
481 | "type": "enableCelebrationInGame"
482 | },
483 | {
484 | "value": 1,
485 | "type": "enableDynamicObjectives"
486 | },
487 | {
488 | "value": 60,
489 | "type": "squadBuildingSetsGracePeriodMinutes"
490 | },
491 | {
492 | "value": 1,
493 | "type": "enablePointsDraftEntry"
494 | },
495 | {
496 | "value": 1,
497 | "type": "storeEnabled"
498 | }
499 | ]
500 | }
--------------------------------------------------------------------------------
/data/fixtures/sets_challenge.response.json:
--------------------------------------------------------------------------------
1 | {
2 | "challengeId": 212,
3 | "playerRequirements": [
4 | {
5 | "index": 0,
6 | "playerType": "DEFAULT"
7 | },
8 | {
9 | "index": 1,
10 | "playerType": "BRICK"
11 | },
12 | {
13 | "index": 2,
14 | "playerType": "DEFAULT"
15 | },
16 | {
17 | "index": 3,
18 | "playerType": "DEFAULT"
19 | },
20 | {
21 | "index": 4,
22 | "playerType": "BRICK"
23 | },
24 | {
25 | "index": 5,
26 | "playerType": "BRICK"
27 | },
28 | {
29 | "index": 6,
30 | "playerType": "DEFAULT"
31 | },
32 | {
33 | "index": 7,
34 | "playerType": "DEFAULT"
35 | },
36 | {
37 | "index": 8,
38 | "playerType": "BRICK"
39 | },
40 | {
41 | "index": 9,
42 | "playerType": "DEFAULT"
43 | },
44 | {
45 | "index": 10,
46 | "playerType": "DEFAULT"
47 | }
48 | ],
49 | "squad": {
50 | "id": 1,
51 | "formation": "f4411",
52 | "rating": 0,
53 | "chemistry": 0,
54 | "manager": [
55 | {
56 | "id": 0,
57 | "timestamp": 0,
58 | "formation": "any",
59 | "untradeable": false,
60 | "assetId": 0,
61 | "rating": 0,
62 | "itemType": "manager",
63 | "resourceId": 0,
64 | "owners": 0,
65 | "discardValue": 0,
66 | "itemState": "invalid",
67 | "cardsubtypeid": 0,
68 | "lastSalePrice": 0,
69 | "morale": 0,
70 | "fitness": 0,
71 | "injuryGames": 0,
72 | "preferredPosition": "any",
73 | "statsList": [],
74 | "lifetimeStats": [],
75 | "training": 0,
76 | "contract": 0,
77 | "suspension": 0,
78 | "attributeList": [],
79 | "teamid": 0,
80 | "rareflag": 0,
81 | "pile": 0,
82 | "nation": 0,
83 | "resourceGameYear": 2020
84 | }
85 | ],
86 | "players": [
87 | {
88 | "index": 0,
89 | "itemData": {
90 | "id": 0,
91 | "timestamp": 0,
92 | "formation": "any",
93 | "untradeable": false,
94 | "assetId": 0,
95 | "rating": 0,
96 | "itemType": "player",
97 | "resourceId": 0,
98 | "owners": 0,
99 | "discardValue": 0,
100 | "itemState": "invalid",
101 | "cardsubtypeid": 0,
102 | "lastSalePrice": 0,
103 | "morale": 0,
104 | "fitness": 0,
105 | "injuryGames": 0,
106 | "preferredPosition": "any",
107 | "statsList": [],
108 | "lifetimeStats": [],
109 | "training": 0,
110 | "contract": 0,
111 | "suspension": 0,
112 | "attributeList": [],
113 | "teamid": 0,
114 | "rareflag": 0,
115 | "loyaltyBonus": 1,
116 | "pile": 0,
117 | "nation": 0,
118 | "resourceGameYear": 2020
119 | }
120 | },
121 | {
122 | "index": 1,
123 | "itemData": {
124 | "id": 0,
125 | "timestamp": 0,
126 | "formation": "any",
127 | "untradeable": false,
128 | "assetId": 0,
129 | "rating": 0,
130 | "itemType": "player",
131 | "resourceId": 0,
132 | "owners": 0,
133 | "discardValue": 0,
134 | "itemState": "invalid",
135 | "cardsubtypeid": 0,
136 | "lastSalePrice": 0,
137 | "morale": 0,
138 | "fitness": 0,
139 | "injuryGames": 0,
140 | "preferredPosition": "any",
141 | "statsList": [],
142 | "lifetimeStats": [],
143 | "training": 0,
144 | "contract": 0,
145 | "suspension": 0,
146 | "attributeList": [],
147 | "teamid": 0,
148 | "rareflag": 0,
149 | "loyaltyBonus": 1,
150 | "pile": 0,
151 | "nation": 0,
152 | "resourceGameYear": 2020
153 | }
154 | },
155 | {
156 | "index": 2,
157 | "itemData": {
158 | "id": 0,
159 | "timestamp": 0,
160 | "formation": "any",
161 | "untradeable": false,
162 | "assetId": 0,
163 | "rating": 0,
164 | "itemType": "player",
165 | "resourceId": 0,
166 | "owners": 0,
167 | "discardValue": 0,
168 | "itemState": "invalid",
169 | "cardsubtypeid": 0,
170 | "lastSalePrice": 0,
171 | "morale": 0,
172 | "fitness": 0,
173 | "injuryGames": 0,
174 | "preferredPosition": "any",
175 | "statsList": [],
176 | "lifetimeStats": [],
177 | "training": 0,
178 | "contract": 0,
179 | "suspension": 0,
180 | "attributeList": [],
181 | "teamid": 0,
182 | "rareflag": 0,
183 | "loyaltyBonus": 1,
184 | "pile": 0,
185 | "nation": 0,
186 | "resourceGameYear": 2020
187 | }
188 | },
189 | {
190 | "index": 3,
191 | "itemData": {
192 | "id": 0,
193 | "timestamp": 0,
194 | "formation": "any",
195 | "untradeable": false,
196 | "assetId": 0,
197 | "rating": 0,
198 | "itemType": "player",
199 | "resourceId": 0,
200 | "owners": 0,
201 | "discardValue": 0,
202 | "itemState": "invalid",
203 | "cardsubtypeid": 0,
204 | "lastSalePrice": 0,
205 | "morale": 0,
206 | "fitness": 0,
207 | "injuryGames": 0,
208 | "preferredPosition": "any",
209 | "statsList": [],
210 | "lifetimeStats": [],
211 | "training": 0,
212 | "contract": 0,
213 | "suspension": 0,
214 | "attributeList": [],
215 | "teamid": 0,
216 | "rareflag": 0,
217 | "loyaltyBonus": 1,
218 | "pile": 0,
219 | "nation": 0,
220 | "resourceGameYear": 2020
221 | }
222 | },
223 | {
224 | "index": 4,
225 | "itemData": {
226 | "id": 0,
227 | "timestamp": 0,
228 | "formation": "any",
229 | "untradeable": false,
230 | "assetId": 0,
231 | "rating": 0,
232 | "itemType": "player",
233 | "resourceId": 0,
234 | "owners": 0,
235 | "discardValue": 0,
236 | "itemState": "invalid",
237 | "cardsubtypeid": 0,
238 | "lastSalePrice": 0,
239 | "morale": 0,
240 | "fitness": 0,
241 | "injuryGames": 0,
242 | "preferredPosition": "any",
243 | "statsList": [],
244 | "lifetimeStats": [],
245 | "training": 0,
246 | "contract": 0,
247 | "suspension": 0,
248 | "attributeList": [],
249 | "teamid": 0,
250 | "rareflag": 0,
251 | "loyaltyBonus": 1,
252 | "pile": 0,
253 | "nation": 0,
254 | "resourceGameYear": 2020
255 | }
256 | },
257 | {
258 | "index": 5,
259 | "itemData": {
260 | "id": 0,
261 | "timestamp": 0,
262 | "formation": "any",
263 | "untradeable": false,
264 | "assetId": 0,
265 | "rating": 0,
266 | "itemType": "player",
267 | "resourceId": 0,
268 | "owners": 0,
269 | "discardValue": 0,
270 | "itemState": "invalid",
271 | "cardsubtypeid": 0,
272 | "lastSalePrice": 0,
273 | "morale": 0,
274 | "fitness": 0,
275 | "injuryGames": 0,
276 | "preferredPosition": "any",
277 | "statsList": [],
278 | "lifetimeStats": [],
279 | "training": 0,
280 | "contract": 0,
281 | "suspension": 0,
282 | "attributeList": [],
283 | "teamid": 0,
284 | "rareflag": 0,
285 | "loyaltyBonus": 1,
286 | "pile": 0,
287 | "nation": 0,
288 | "resourceGameYear": 2020
289 | }
290 | },
291 | {
292 | "index": 6,
293 | "itemData": {
294 | "id": 0,
295 | "timestamp": 0,
296 | "formation": "any",
297 | "untradeable": false,
298 | "assetId": 0,
299 | "rating": 0,
300 | "itemType": "player",
301 | "resourceId": 0,
302 | "owners": 0,
303 | "discardValue": 0,
304 | "itemState": "invalid",
305 | "cardsubtypeid": 0,
306 | "lastSalePrice": 0,
307 | "morale": 0,
308 | "fitness": 0,
309 | "injuryGames": 0,
310 | "preferredPosition": "any",
311 | "statsList": [],
312 | "lifetimeStats": [],
313 | "training": 0,
314 | "contract": 0,
315 | "suspension": 0,
316 | "attributeList": [],
317 | "teamid": 0,
318 | "rareflag": 0,
319 | "loyaltyBonus": 1,
320 | "pile": 0,
321 | "nation": 0,
322 | "resourceGameYear": 2020
323 | }
324 | },
325 | {
326 | "index": 7,
327 | "itemData": {
328 | "id": 0,
329 | "timestamp": 0,
330 | "formation": "any",
331 | "untradeable": false,
332 | "assetId": 0,
333 | "rating": 0,
334 | "itemType": "player",
335 | "resourceId": 0,
336 | "owners": 0,
337 | "discardValue": 0,
338 | "itemState": "invalid",
339 | "cardsubtypeid": 0,
340 | "lastSalePrice": 0,
341 | "morale": 0,
342 | "fitness": 0,
343 | "injuryGames": 0,
344 | "preferredPosition": "any",
345 | "statsList": [],
346 | "lifetimeStats": [],
347 | "training": 0,
348 | "contract": 0,
349 | "suspension": 0,
350 | "attributeList": [],
351 | "teamid": 0,
352 | "rareflag": 0,
353 | "loyaltyBonus": 1,
354 | "pile": 0,
355 | "nation": 0,
356 | "resourceGameYear": 2020
357 | }
358 | },
359 | {
360 | "index": 8,
361 | "itemData": {
362 | "id": 0,
363 | "timestamp": 0,
364 | "formation": "any",
365 | "untradeable": false,
366 | "assetId": 0,
367 | "rating": 0,
368 | "itemType": "player",
369 | "resourceId": 0,
370 | "owners": 0,
371 | "discardValue": 0,
372 | "itemState": "invalid",
373 | "cardsubtypeid": 0,
374 | "lastSalePrice": 0,
375 | "morale": 0,
376 | "fitness": 0,
377 | "injuryGames": 0,
378 | "preferredPosition": "any",
379 | "statsList": [],
380 | "lifetimeStats": [],
381 | "training": 0,
382 | "contract": 0,
383 | "suspension": 0,
384 | "attributeList": [],
385 | "teamid": 0,
386 | "rareflag": 0,
387 | "loyaltyBonus": 1,
388 | "pile": 0,
389 | "nation": 0,
390 | "resourceGameYear": 2020
391 | }
392 | },
393 | {
394 | "index": 9,
395 | "itemData": {
396 | "id": 0,
397 | "timestamp": 0,
398 | "formation": "any",
399 | "untradeable": false,
400 | "assetId": 0,
401 | "rating": 0,
402 | "itemType": "player",
403 | "resourceId": 0,
404 | "owners": 0,
405 | "discardValue": 0,
406 | "itemState": "invalid",
407 | "cardsubtypeid": 0,
408 | "lastSalePrice": 0,
409 | "morale": 0,
410 | "fitness": 0,
411 | "injuryGames": 0,
412 | "preferredPosition": "any",
413 | "statsList": [],
414 | "lifetimeStats": [],
415 | "training": 0,
416 | "contract": 0,
417 | "suspension": 0,
418 | "attributeList": [],
419 | "teamid": 0,
420 | "rareflag": 0,
421 | "loyaltyBonus": 1,
422 | "pile": 0,
423 | "nation": 0,
424 | "resourceGameYear": 2020
425 | }
426 | },
427 | {
428 | "index": 10,
429 | "itemData": {
430 | "id": 0,
431 | "timestamp": 0,
432 | "formation": "any",
433 | "untradeable": false,
434 | "assetId": 0,
435 | "rating": 0,
436 | "itemType": "player",
437 | "resourceId": 0,
438 | "owners": 0,
439 | "discardValue": 0,
440 | "itemState": "invalid",
441 | "cardsubtypeid": 0,
442 | "lastSalePrice": 0,
443 | "morale": 0,
444 | "fitness": 0,
445 | "injuryGames": 0,
446 | "preferredPosition": "any",
447 | "statsList": [],
448 | "lifetimeStats": [],
449 | "training": 0,
450 | "contract": 0,
451 | "suspension": 0,
452 | "attributeList": [],
453 | "teamid": 0,
454 | "rareflag": 0,
455 | "loyaltyBonus": 1,
456 | "pile": 0,
457 | "nation": 0,
458 | "resourceGameYear": 2020
459 | }
460 | },
461 | {
462 | "index": 11,
463 | "itemData": {
464 | "id": 0,
465 | "timestamp": 0,
466 | "formation": "any",
467 | "untradeable": false,
468 | "assetId": 0,
469 | "rating": 0,
470 | "itemType": "player",
471 | "resourceId": 0,
472 | "owners": 0,
473 | "discardValue": 0,
474 | "itemState": "invalid",
475 | "cardsubtypeid": 0,
476 | "lastSalePrice": 0,
477 | "morale": 0,
478 | "fitness": 0,
479 | "injuryGames": 0,
480 | "preferredPosition": "any",
481 | "statsList": [],
482 | "lifetimeStats": [],
483 | "training": 0,
484 | "contract": 0,
485 | "suspension": 0,
486 | "attributeList": [],
487 | "teamid": 0,
488 | "rareflag": 0,
489 | "loyaltyBonus": 1,
490 | "pile": 0,
491 | "nation": 0,
492 | "resourceGameYear": 2020
493 | }
494 | },
495 | {
496 | "index": 12,
497 | "itemData": {
498 | "id": 0,
499 | "timestamp": 0,
500 | "formation": "any",
501 | "untradeable": false,
502 | "assetId": 0,
503 | "rating": 0,
504 | "itemType": "player",
505 | "resourceId": 0,
506 | "owners": 0,
507 | "discardValue": 0,
508 | "itemState": "invalid",
509 | "cardsubtypeid": 0,
510 | "lastSalePrice": 0,
511 | "morale": 0,
512 | "fitness": 0,
513 | "injuryGames": 0,
514 | "preferredPosition": "any",
515 | "statsList": [],
516 | "lifetimeStats": [],
517 | "training": 0,
518 | "contract": 0,
519 | "suspension": 0,
520 | "attributeList": [],
521 | "teamid": 0,
522 | "rareflag": 0,
523 | "loyaltyBonus": 1,
524 | "pile": 0,
525 | "nation": 0,
526 | "resourceGameYear": 2020
527 | }
528 | },
529 | {
530 | "index": 13,
531 | "itemData": {
532 | "id": 0,
533 | "timestamp": 0,
534 | "formation": "any",
535 | "untradeable": false,
536 | "assetId": 0,
537 | "rating": 0,
538 | "itemType": "player",
539 | "resourceId": 0,
540 | "owners": 0,
541 | "discardValue": 0,
542 | "itemState": "invalid",
543 | "cardsubtypeid": 0,
544 | "lastSalePrice": 0,
545 | "morale": 0,
546 | "fitness": 0,
547 | "injuryGames": 0,
548 | "preferredPosition": "any",
549 | "statsList": [],
550 | "lifetimeStats": [],
551 | "training": 0,
552 | "contract": 0,
553 | "suspension": 0,
554 | "attributeList": [],
555 | "teamid": 0,
556 | "rareflag": 0,
557 | "loyaltyBonus": 1,
558 | "pile": 0,
559 | "nation": 0,
560 | "resourceGameYear": 2020
561 | }
562 | },
563 | {
564 | "index": 14,
565 | "itemData": {
566 | "id": 0,
567 | "timestamp": 0,
568 | "formation": "any",
569 | "untradeable": false,
570 | "assetId": 0,
571 | "rating": 0,
572 | "itemType": "player",
573 | "resourceId": 0,
574 | "owners": 0,
575 | "discardValue": 0,
576 | "itemState": "invalid",
577 | "cardsubtypeid": 0,
578 | "lastSalePrice": 0,
579 | "morale": 0,
580 | "fitness": 0,
581 | "injuryGames": 0,
582 | "preferredPosition": "any",
583 | "statsList": [],
584 | "lifetimeStats": [],
585 | "training": 0,
586 | "contract": 0,
587 | "suspension": 0,
588 | "attributeList": [],
589 | "teamid": 0,
590 | "rareflag": 0,
591 | "loyaltyBonus": 1,
592 | "pile": 0,
593 | "nation": 0,
594 | "resourceGameYear": 2020
595 | }
596 | },
597 | {
598 | "index": 15,
599 | "itemData": {
600 | "id": 0,
601 | "timestamp": 0,
602 | "formation": "any",
603 | "untradeable": false,
604 | "assetId": 0,
605 | "rating": 0,
606 | "itemType": "player",
607 | "resourceId": 0,
608 | "owners": 0,
609 | "discardValue": 0,
610 | "itemState": "invalid",
611 | "cardsubtypeid": 0,
612 | "lastSalePrice": 0,
613 | "morale": 0,
614 | "fitness": 0,
615 | "injuryGames": 0,
616 | "preferredPosition": "any",
617 | "statsList": [],
618 | "lifetimeStats": [],
619 | "training": 0,
620 | "contract": 0,
621 | "suspension": 0,
622 | "attributeList": [],
623 | "teamid": 0,
624 | "rareflag": 0,
625 | "loyaltyBonus": 1,
626 | "pile": 0,
627 | "nation": 0,
628 | "resourceGameYear": 2020
629 | }
630 | },
631 | {
632 | "index": 16,
633 | "itemData": {
634 | "id": 0,
635 | "timestamp": 0,
636 | "formation": "any",
637 | "untradeable": false,
638 | "assetId": 0,
639 | "rating": 0,
640 | "itemType": "player",
641 | "resourceId": 0,
642 | "owners": 0,
643 | "discardValue": 0,
644 | "itemState": "invalid",
645 | "cardsubtypeid": 0,
646 | "lastSalePrice": 0,
647 | "morale": 0,
648 | "fitness": 0,
649 | "injuryGames": 0,
650 | "preferredPosition": "any",
651 | "statsList": [],
652 | "lifetimeStats": [],
653 | "training": 0,
654 | "contract": 0,
655 | "suspension": 0,
656 | "attributeList": [],
657 | "teamid": 0,
658 | "rareflag": 0,
659 | "loyaltyBonus": 1,
660 | "pile": 0,
661 | "nation": 0,
662 | "resourceGameYear": 2020
663 | }
664 | },
665 | {
666 | "index": 17,
667 | "itemData": {
668 | "id": 0,
669 | "timestamp": 0,
670 | "formation": "any",
671 | "untradeable": false,
672 | "assetId": 0,
673 | "rating": 0,
674 | "itemType": "player",
675 | "resourceId": 0,
676 | "owners": 0,
677 | "discardValue": 0,
678 | "itemState": "invalid",
679 | "cardsubtypeid": 0,
680 | "lastSalePrice": 0,
681 | "morale": 0,
682 | "fitness": 0,
683 | "injuryGames": 0,
684 | "preferredPosition": "any",
685 | "statsList": [],
686 | "lifetimeStats": [],
687 | "training": 0,
688 | "contract": 0,
689 | "suspension": 0,
690 | "attributeList": [],
691 | "teamid": 0,
692 | "rareflag": 0,
693 | "loyaltyBonus": 1,
694 | "pile": 0,
695 | "nation": 0,
696 | "resourceGameYear": 2020
697 | }
698 | },
699 | {
700 | "index": 18,
701 | "itemData": {
702 | "id": 0,
703 | "timestamp": 0,
704 | "formation": "any",
705 | "untradeable": false,
706 | "assetId": 0,
707 | "rating": 0,
708 | "itemType": "player",
709 | "resourceId": 0,
710 | "owners": 0,
711 | "discardValue": 0,
712 | "itemState": "invalid",
713 | "cardsubtypeid": 0,
714 | "lastSalePrice": 0,
715 | "morale": 0,
716 | "fitness": 0,
717 | "injuryGames": 0,
718 | "preferredPosition": "any",
719 | "statsList": [],
720 | "lifetimeStats": [],
721 | "training": 0,
722 | "contract": 0,
723 | "suspension": 0,
724 | "attributeList": [],
725 | "teamid": 0,
726 | "rareflag": 0,
727 | "loyaltyBonus": 1,
728 | "pile": 0,
729 | "nation": 0,
730 | "resourceGameYear": 2020
731 | }
732 | },
733 | {
734 | "index": 19,
735 | "itemData": {
736 | "id": 0,
737 | "timestamp": 0,
738 | "formation": "any",
739 | "untradeable": false,
740 | "assetId": 0,
741 | "rating": 0,
742 | "itemType": "player",
743 | "resourceId": 0,
744 | "owners": 0,
745 | "discardValue": 0,
746 | "itemState": "invalid",
747 | "cardsubtypeid": 0,
748 | "lastSalePrice": 0,
749 | "morale": 0,
750 | "fitness": 0,
751 | "injuryGames": 0,
752 | "preferredPosition": "any",
753 | "statsList": [],
754 | "lifetimeStats": [],
755 | "training": 0,
756 | "contract": 0,
757 | "suspension": 0,
758 | "attributeList": [],
759 | "teamid": 0,
760 | "rareflag": 0,
761 | "loyaltyBonus": 1,
762 | "pile": 0,
763 | "nation": 0,
764 | "resourceGameYear": 2020
765 | }
766 | },
767 | {
768 | "index": 20,
769 | "itemData": {
770 | "id": 0,
771 | "timestamp": 0,
772 | "formation": "any",
773 | "untradeable": false,
774 | "assetId": 0,
775 | "rating": 0,
776 | "itemType": "player",
777 | "resourceId": 0,
778 | "owners": 0,
779 | "discardValue": 0,
780 | "itemState": "invalid",
781 | "cardsubtypeid": 0,
782 | "lastSalePrice": 0,
783 | "morale": 0,
784 | "fitness": 0,
785 | "injuryGames": 0,
786 | "preferredPosition": "any",
787 | "statsList": [],
788 | "lifetimeStats": [],
789 | "training": 0,
790 | "contract": 0,
791 | "suspension": 0,
792 | "attributeList": [],
793 | "teamid": 0,
794 | "rareflag": 0,
795 | "loyaltyBonus": 1,
796 | "pile": 0,
797 | "nation": 0,
798 | "resourceGameYear": 2020
799 | }
800 | },
801 | {
802 | "index": 21,
803 | "itemData": {
804 | "id": 0,
805 | "timestamp": 0,
806 | "formation": "any",
807 | "untradeable": false,
808 | "assetId": 0,
809 | "rating": 0,
810 | "itemType": "player",
811 | "resourceId": 0,
812 | "owners": 0,
813 | "discardValue": 0,
814 | "itemState": "invalid",
815 | "cardsubtypeid": 0,
816 | "lastSalePrice": 0,
817 | "morale": 0,
818 | "fitness": 0,
819 | "injuryGames": 0,
820 | "preferredPosition": "any",
821 | "statsList": [],
822 | "lifetimeStats": [],
823 | "training": 0,
824 | "contract": 0,
825 | "suspension": 0,
826 | "attributeList": [],
827 | "teamid": 0,
828 | "rareflag": 0,
829 | "loyaltyBonus": 1,
830 | "pile": 0,
831 | "nation": 0,
832 | "resourceGameYear": 2020
833 | }
834 | },
835 | {
836 | "index": 22,
837 | "itemData": {
838 | "id": 0,
839 | "timestamp": 0,
840 | "formation": "any",
841 | "untradeable": false,
842 | "assetId": 0,
843 | "rating": 0,
844 | "itemType": "player",
845 | "resourceId": 0,
846 | "owners": 0,
847 | "discardValue": 0,
848 | "itemState": "invalid",
849 | "cardsubtypeid": 0,
850 | "lastSalePrice": 0,
851 | "morale": 0,
852 | "fitness": 0,
853 | "injuryGames": 0,
854 | "preferredPosition": "any",
855 | "statsList": [],
856 | "lifetimeStats": [],
857 | "training": 0,
858 | "contract": 0,
859 | "suspension": 0,
860 | "attributeList": [],
861 | "teamid": 0,
862 | "rareflag": 0,
863 | "loyaltyBonus": 1,
864 | "pile": 0,
865 | "nation": 0,
866 | "resourceGameYear": 2020
867 | }
868 | }
869 | ]
870 | }
871 | }
--------------------------------------------------------------------------------