mirror of
https://github.com/elyby/mojang-api.git
synced 2024-11-26 16:51:59 +05:30
Init
This commit is contained in:
commit
7211fbc190
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
/vendor/
|
||||||
|
/build/
|
||||||
|
.phpunit.result.cache
|
||||||
|
.php_cs.cache
|
5
.php_cs.dist
Normal file
5
.php_cs.dist
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<?php
|
||||||
|
$finder = \PhpCsFixer\Finder::create()
|
||||||
|
->in(__DIR__);
|
||||||
|
return \Ely\CS\Config::create()
|
||||||
|
->setFinder($finder);
|
34
.travis.yml
Normal file
34
.travis.yml
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
language: php
|
||||||
|
php:
|
||||||
|
- 7.1
|
||||||
|
- 7.2
|
||||||
|
- 7.3
|
||||||
|
|
||||||
|
cache:
|
||||||
|
directories:
|
||||||
|
- vendor
|
||||||
|
- $HOME/.composer
|
||||||
|
|
||||||
|
env:
|
||||||
|
global:
|
||||||
|
- DEFAULT_COMPOSER_FLAGS="--optimize-autoloader --no-progress"
|
||||||
|
- COMPOSER_NO_INTERACTION=1
|
||||||
|
|
||||||
|
before_script:
|
||||||
|
- composer global show hirak/prestissimo -q || travis_retry composer global require $DEFAULT_COMPOSER_FLAGS hirak/prestissimo
|
||||||
|
- travis_retry composer install
|
||||||
|
- travis_retry phpenv rehash
|
||||||
|
|
||||||
|
stages:
|
||||||
|
- Static Code Analysis
|
||||||
|
- Test
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
include:
|
||||||
|
- stage: Static Code Analysis
|
||||||
|
php: 7.3
|
||||||
|
script:
|
||||||
|
- vendor/bin/php-cs-fixer fix -v --dry-run
|
||||||
|
|
||||||
|
script:
|
||||||
|
- vendor/bin/phpunit
|
21
LICENSE.md
Normal file
21
LICENSE.md
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2019 Ely.by <team@ely.by>
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
62
README.md
Normal file
62
README.md
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
# Mojang API
|
||||||
|
|
||||||
|
This package provides easy access to the Minecraft related API of Mojang.
|
||||||
|
The library is built on the top of the [Guzzle HTTP client](https://github.com/guzzle/guzzle),
|
||||||
|
has custom errors handler and automatic retry in case of problems with Mojang.
|
||||||
|
|
||||||
|
> Please note that this is not a complete implementation of all available APIs.
|
||||||
|
If you don't find the method you need, [open Issue](https://github.com/elyby/mojang-api/issues/new)
|
||||||
|
or [submit a PR](https://github.com/elyby/mojang-api/compare) with the implementation.
|
||||||
|
|
||||||
|
[![Latest Version on Packagist][ico-version]][link-packagist]
|
||||||
|
[![Total Downloads][ico-downloads]][link-downloads]
|
||||||
|
[![Software License][ico-license]](LICENSE.md)
|
||||||
|
[![Build Status][ico-build-status]][link-build-status]
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
To install, use composer:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
composer require ely/mojang-api
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
To get the configured `Api` object right away, just use the static `create()` method:
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
$api = \Ely\Mojang\Api::create();
|
||||||
|
$response = $api->usernameToUUID('erickskrauch');
|
||||||
|
echo $response->getId();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ ./vendor/bin/phpunit
|
||||||
|
```
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Please see [CONTRIBUTING](CONTRIBUTING.md) for details.
|
||||||
|
|
||||||
|
## Credits
|
||||||
|
|
||||||
|
This package was designed and developed within the [Ely.by](http://ely.by) project team. We also thank all the
|
||||||
|
[contributors](link-contributors) for their help.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
The MIT License (MIT). Please see [License File](LICENSE.md) for more information.
|
||||||
|
|
||||||
|
[ico-version]: https://img.shields.io/packagist/v/ely/mojang-api.svg?style=flat-square
|
||||||
|
[ico-license]: https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square
|
||||||
|
[ico-downloads]: https://img.shields.io/packagist/dt/ely/mojang-api.svg?style=flat-square
|
||||||
|
[ico-build-status]: https://img.shields.io/travis/elyby/mojang-api/master.svg?style=flat-square
|
||||||
|
|
||||||
|
[link-packagist]: https://packagist.org/packages/ely/mojang-api
|
||||||
|
[link-contributors]: ../../contributors
|
||||||
|
[link-downloads]: https://packagist.org/packages/ely/mojang-api/stats
|
||||||
|
[link-build-status]: https://travis-ci.org/elyby/mojang-api
|
35
composer.json
Normal file
35
composer.json
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"name": "ely/mojang-api",
|
||||||
|
"description": "",
|
||||||
|
"license": "MIT",
|
||||||
|
"type": "library",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "ErickSkrauch",
|
||||||
|
"email": "erickskrauch@yandex.ru"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"require": {
|
||||||
|
"php": ">=7.1.0",
|
||||||
|
"ext-json": "*",
|
||||||
|
"guzzlehttp/guzzle": "^6.0.0",
|
||||||
|
"ramsey/uuid": "^3.0.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"ely/php-code-style": "^0.3.0",
|
||||||
|
"phpunit/phpunit": "^8.0.0"
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"sort-packages": true
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Ely\\Mojang\\": "src"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload-dev": {
|
||||||
|
"psr-4": {
|
||||||
|
"Ely\\Mojang\\Test\\": "tests"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
2987
composer.lock
generated
Normal file
2987
composer.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
32
phpunit.xml
Normal file
32
phpunit.xml
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<phpunit
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/8.0/phpunit.xsd"
|
||||||
|
bootstrap="vendor/autoload.php"
|
||||||
|
colors="true"
|
||||||
|
>
|
||||||
|
<logging>
|
||||||
|
<log
|
||||||
|
type="coverage-html"
|
||||||
|
target="./build/coverage/html"
|
||||||
|
/>
|
||||||
|
<log
|
||||||
|
type="coverage-clover"
|
||||||
|
target="./build/coverage/log/coverage.xml"
|
||||||
|
/>
|
||||||
|
</logging>
|
||||||
|
<testsuites>
|
||||||
|
<testsuite name="Package Test Suite">
|
||||||
|
<directory suffix=".php">./tests/</directory>
|
||||||
|
</testsuite>
|
||||||
|
</testsuites>
|
||||||
|
<filter>
|
||||||
|
<whitelist>
|
||||||
|
<directory suffix=".php">./</directory>
|
||||||
|
<exclude>
|
||||||
|
<directory suffix=".php">./vendor</directory>
|
||||||
|
<directory suffix=".php">./tests</directory>
|
||||||
|
</exclude>
|
||||||
|
</whitelist>
|
||||||
|
</filter>
|
||||||
|
</phpunit>
|
265
src/Api.php
Normal file
265
src/Api.php
Normal file
@ -0,0 +1,265 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Ely\Mojang;
|
||||||
|
|
||||||
|
use Ely\Mojang\Middleware\ResponseConverterMiddleware;
|
||||||
|
use Ely\Mojang\Middleware\RetryMiddleware;
|
||||||
|
use GuzzleHttp\Client as GuzzleClient;
|
||||||
|
use GuzzleHttp\ClientInterface;
|
||||||
|
use GuzzleHttp\Exception\GuzzleException;
|
||||||
|
use GuzzleHttp\HandlerStack;
|
||||||
|
use GuzzleHttp\Psr7\Request;
|
||||||
|
use GuzzleHttp\Psr7\Uri;
|
||||||
|
use Ramsey\Uuid\Uuid;
|
||||||
|
|
||||||
|
class Api {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var ClientInterface
|
||||||
|
*/
|
||||||
|
private $client;
|
||||||
|
|
||||||
|
public function __construct(ClientInterface $client) {
|
||||||
|
$this->client = $client;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param callable $handler HTTP handler function to use with the stack. If no
|
||||||
|
* handler is provided, the best handler for your
|
||||||
|
* system will be utilized.
|
||||||
|
*
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public static function create(callable $handler = null): self {
|
||||||
|
$stack = HandlerStack::create($handler);
|
||||||
|
// use after method because middleware executes in reverse order
|
||||||
|
$stack->after('http_errors', ResponseConverterMiddleware::create(), 'mojang_response_converter');
|
||||||
|
$stack->push(RetryMiddleware::create(), 'retry');
|
||||||
|
|
||||||
|
return new static(new GuzzleClient([
|
||||||
|
'handler' => $stack,
|
||||||
|
'timeout' => 10,
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $username
|
||||||
|
* @param int $atTime
|
||||||
|
*
|
||||||
|
* @return \Ely\Mojang\Response\ProfileInfo
|
||||||
|
*
|
||||||
|
* @throws \Ely\Mojang\Exception\MojangApiException
|
||||||
|
* @throws \GuzzleHttp\Exception\GuzzleException
|
||||||
|
*
|
||||||
|
* @url http://wiki.vg/Mojang_API#Username_-.3E_UUID_at_time
|
||||||
|
*/
|
||||||
|
public function usernameToUUID(string $username, int $atTime = null): Response\ProfileInfo {
|
||||||
|
$query = [];
|
||||||
|
if ($atTime !== null) {
|
||||||
|
$query['atTime'] = $atTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = $this->getClient()->request('GET', "https://api.mojang.com/users/profiles/minecraft/{$username}", [
|
||||||
|
'query' => $query,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$data = $this->decode($response->getBody()->getContents());
|
||||||
|
|
||||||
|
return Response\ProfileInfo::createFromResponse($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $uuid
|
||||||
|
*
|
||||||
|
* @return \Ely\Mojang\Response\ProfileResponse
|
||||||
|
*
|
||||||
|
* @throws \Ely\Mojang\Exception\MojangApiException
|
||||||
|
* @throws \GuzzleHttp\Exception\GuzzleException
|
||||||
|
*
|
||||||
|
* @url http://wiki.vg/Mojang_API#UUID_-.3E_Profile_.2B_Skin.2FCape
|
||||||
|
*/
|
||||||
|
public function uuidToTextures(string $uuid): Response\ProfileResponse {
|
||||||
|
$response = $this->getClient()->request('GET', "https://sessionserver.mojang.com/session/minecraft/profile/{$uuid}", [
|
||||||
|
'query' => [
|
||||||
|
'unsigned' => false,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
$body = $this->decode($response->getBody()->getContents());
|
||||||
|
|
||||||
|
return new Response\ProfileResponse($body['id'], $body['name'], $body['properties']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method to exchange username to the corresponding textures.
|
||||||
|
*
|
||||||
|
* @param string $username
|
||||||
|
*
|
||||||
|
* @return \Ely\Mojang\Response\ProfileResponse
|
||||||
|
*
|
||||||
|
* @throws GuzzleException
|
||||||
|
* @throws \Ely\Mojang\Exception\MojangApiException
|
||||||
|
*/
|
||||||
|
public function usernameToTextures(string $username): Response\ProfileResponse {
|
||||||
|
return $this->uuidToTextures($this->usernameToUUID($username)->getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $login
|
||||||
|
* @param string $password
|
||||||
|
* @param string $clientToken
|
||||||
|
*
|
||||||
|
* @return \Ely\Mojang\Response\AuthenticateResponse
|
||||||
|
*
|
||||||
|
* @throws \GuzzleHttp\Exception\GuzzleException
|
||||||
|
*
|
||||||
|
* @url https://wiki.vg/Authentication#Authenticate
|
||||||
|
*/
|
||||||
|
public function authenticate(
|
||||||
|
string $login,
|
||||||
|
string $password,
|
||||||
|
string $clientToken = null
|
||||||
|
): Response\AuthenticateResponse {
|
||||||
|
if ($clientToken === null) {
|
||||||
|
/** @noinspection PhpUnhandledExceptionInspection */
|
||||||
|
$clientToken = Uuid::uuid4()->toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = $this->getClient()->request('POST', 'https://authserver.mojang.com/authenticate', [
|
||||||
|
'json' => [
|
||||||
|
'username' => $login,
|
||||||
|
'password' => $password,
|
||||||
|
'clientToken' => $clientToken,
|
||||||
|
'requestUser' => true,
|
||||||
|
'agent' => [
|
||||||
|
'name' => 'Minecraft',
|
||||||
|
'version' => 1,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
$body = $this->decode($response->getBody()->getContents());
|
||||||
|
|
||||||
|
return new Response\AuthenticateResponse(
|
||||||
|
$body['accessToken'],
|
||||||
|
$body['clientToken'],
|
||||||
|
$body['availableProfiles'],
|
||||||
|
$body['selectedProfile'],
|
||||||
|
$body['user']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $accessToken
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*
|
||||||
|
* @throws \GuzzleHttp\Exception\GuzzleException
|
||||||
|
*
|
||||||
|
* @url https://wiki.vg/Authentication#Validate
|
||||||
|
*/
|
||||||
|
public function validate(string $accessToken): bool {
|
||||||
|
try {
|
||||||
|
$response = $this->getClient()->request('POST', 'https://authserver.mojang.com/authenticate', [
|
||||||
|
'json' => [
|
||||||
|
'accessToken' => $accessToken,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
if ($response->getStatusCode() === 204) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch (Exception\ForbiddenException $e) {
|
||||||
|
// Suppress exception and let it just exit below
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $accessToken
|
||||||
|
* @param string $accountUuid
|
||||||
|
* @param \Psr\Http\Message\StreamInterface|resource|string $skinContents
|
||||||
|
* @param bool $isSlim
|
||||||
|
*
|
||||||
|
* @throws GuzzleException
|
||||||
|
*
|
||||||
|
* @url https://wiki.vg/Mojang_API#Upload_Skin
|
||||||
|
*/
|
||||||
|
public function uploadSkin(string $accessToken, string $accountUuid, $skinContents, bool $isSlim): void {
|
||||||
|
$this->getClient()->request('PUT', "https://api.mojang.com/user/profile/{$accountUuid}/skin", [
|
||||||
|
'multipart' => [
|
||||||
|
[
|
||||||
|
'name' => 'file',
|
||||||
|
'contents' => $skinContents,
|
||||||
|
'filename' => 'char.png',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => 'model',
|
||||||
|
'contents' => $isSlim ? 'slim' : '',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'headers' => [
|
||||||
|
'Authorization' => 'Bearer ' . $accessToken,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $accessToken
|
||||||
|
* @param string $accountUuid
|
||||||
|
* @param string $serverId
|
||||||
|
*
|
||||||
|
* @throws GuzzleException
|
||||||
|
*
|
||||||
|
* @url https://wiki.vg/Protocol_Encryption#Client
|
||||||
|
*/
|
||||||
|
public function joinServer(string $accessToken, string $accountUuid, string $serverId): void {
|
||||||
|
$this->getClient()->request('POST', 'https://sessionserver.mojang.com/session/minecraft/join', [
|
||||||
|
'json' => [
|
||||||
|
'accessToken' => $accessToken,
|
||||||
|
'selectedProfile' => $accountUuid,
|
||||||
|
'serverId' => $serverId,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $username
|
||||||
|
* @param string $serverId
|
||||||
|
*
|
||||||
|
* @return \Ely\Mojang\Response\ProfileResponse
|
||||||
|
*
|
||||||
|
* @throws \Ely\Mojang\Exception\NoContentException
|
||||||
|
* @throws GuzzleException
|
||||||
|
*
|
||||||
|
* @url https://wiki.vg/Protocol_Encryption#Server
|
||||||
|
*/
|
||||||
|
public function hasJoinedServer(string $username, string $serverId): Response\ProfileResponse {
|
||||||
|
$uri = (new Uri('https://sessionserver.mojang.com/session/minecraft/hasJoined'))
|
||||||
|
->withQuery(http_build_query([
|
||||||
|
'username' => $username,
|
||||||
|
'serverId' => $serverId,
|
||||||
|
], '', '&', PHP_QUERY_RFC3986));
|
||||||
|
$request = new Request('GET', $uri);
|
||||||
|
$response = $this->getClient()->send($request);
|
||||||
|
$rawBody = $response->getBody()->getContents();
|
||||||
|
if (empty($rawBody)) {
|
||||||
|
throw new Exception\NoContentException($request, $response);
|
||||||
|
}
|
||||||
|
|
||||||
|
$body = $this->decode($rawBody);
|
||||||
|
|
||||||
|
return new Response\ProfileResponse($body['id'], $body['name'], $body['properties']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return ClientInterface
|
||||||
|
*/
|
||||||
|
protected function getClient(): ClientInterface {
|
||||||
|
return $this->client;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function decode(string $response): array {
|
||||||
|
return json_decode($response, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
20
src/Exception/ForbiddenException.php
Normal file
20
src/Exception/ForbiddenException.php
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Ely\Mojang\Exception;
|
||||||
|
|
||||||
|
use GuzzleHttp\Exception\ClientException;
|
||||||
|
use Psr\Http\Message\RequestInterface;
|
||||||
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
|
||||||
|
class ForbiddenException extends ClientException implements MojangApiException {
|
||||||
|
|
||||||
|
public function __construct(RequestInterface $request, ResponseInterface $response) {
|
||||||
|
parent::__construct(
|
||||||
|
'The request was executed with a non-existent or expired access token',
|
||||||
|
$request,
|
||||||
|
$response
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
10
src/Exception/MojangApiException.php
Normal file
10
src/Exception/MojangApiException.php
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Ely\Mojang\Exception;
|
||||||
|
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
interface MojangApiException extends Throwable {
|
||||||
|
|
||||||
|
}
|
16
src/Exception/NoContentException.php
Normal file
16
src/Exception/NoContentException.php
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Ely\Mojang\Exception;
|
||||||
|
|
||||||
|
use GuzzleHttp\Exception\RequestException;
|
||||||
|
use Psr\Http\Message\RequestInterface;
|
||||||
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
|
||||||
|
class NoContentException extends RequestException implements MojangApiException {
|
||||||
|
|
||||||
|
public function __construct(RequestInterface $request, ResponseInterface $response) {
|
||||||
|
parent::__construct('No data were received in the response.', $request, $response);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
21
src/Exception/TooManyRequestsException.php
Normal file
21
src/Exception/TooManyRequestsException.php
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Ely\Mojang\Exception;
|
||||||
|
|
||||||
|
use GuzzleHttp\Exception\ClientException;
|
||||||
|
use Psr\Http\Message\RequestInterface;
|
||||||
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
|
||||||
|
class TooManyRequestsException extends ClientException implements MojangApiException {
|
||||||
|
|
||||||
|
public function __construct(RequestInterface $request, ResponseInterface $response) {
|
||||||
|
parent::__construct(
|
||||||
|
'The request limit was exceeded. ' .
|
||||||
|
'Read the documentation for the method requested to find out which RPS is allowed.',
|
||||||
|
$request,
|
||||||
|
$response
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
54
src/Middleware/ResponseConverterMiddleware.php
Normal file
54
src/Middleware/ResponseConverterMiddleware.php
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Ely\Mojang\Middleware;
|
||||||
|
|
||||||
|
use Ely\Mojang\Exception;
|
||||||
|
use GuzzleHttp\Promise\PromiseInterface;
|
||||||
|
use Psr\Http\Message\RequestInterface;
|
||||||
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
|
||||||
|
class ResponseConverterMiddleware {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var callable
|
||||||
|
*/
|
||||||
|
private $nextHandler;
|
||||||
|
|
||||||
|
public function __construct(callable $nextHandler) {
|
||||||
|
$this->nextHandler = $nextHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __invoke(RequestInterface $request, array $options): PromiseInterface {
|
||||||
|
$fn = $this->nextHandler;
|
||||||
|
/** @var PromiseInterface $promise */
|
||||||
|
$promise = $fn($request, $options);
|
||||||
|
|
||||||
|
return $promise->then(static function($response) use ($request) {
|
||||||
|
if ($response instanceof ResponseInterface) {
|
||||||
|
$method = $request->getMethod();
|
||||||
|
$statusCode = $response->getStatusCode();
|
||||||
|
if ($method === 'GET' && $statusCode === 204) {
|
||||||
|
throw new Exception\NoContentException($request, $response);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($statusCode === 403) {
|
||||||
|
throw new Exception\ForbiddenException($request, $response);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($statusCode === 429) {
|
||||||
|
throw new Exception\TooManyRequestsException($request, $response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function create(): callable {
|
||||||
|
return static function(callable $handler): callable {
|
||||||
|
return new static($handler);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
39
src/Middleware/RetryMiddleware.php
Normal file
39
src/Middleware/RetryMiddleware.php
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Ely\Mojang\Middleware;
|
||||||
|
|
||||||
|
use GuzzleHttp\Exception\ConnectException;
|
||||||
|
use GuzzleHttp\Exception\GuzzleException;
|
||||||
|
use GuzzleHttp\Middleware;
|
||||||
|
use Psr\Http\Message\RequestInterface;
|
||||||
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
|
||||||
|
class RetryMiddleware {
|
||||||
|
|
||||||
|
public static function create(): callable {
|
||||||
|
return Middleware::retry([static::class, 'shouldRetry']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function shouldRetry(
|
||||||
|
int $retries,
|
||||||
|
RequestInterface $request,
|
||||||
|
?ResponseInterface $response,
|
||||||
|
?GuzzleException $reason
|
||||||
|
): bool {
|
||||||
|
if ($retries >= 2) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($reason instanceof ConnectException) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($response !== null && (int)floor($response->getStatusCode() / 100) === 5) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
70
src/Response/AuthenticateResponse.php
Normal file
70
src/Response/AuthenticateResponse.php
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Ely\Mojang\Response;
|
||||||
|
|
||||||
|
class AuthenticateResponse {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $accessToken;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $clientToken;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $rawAvailableProfiles;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $rawSelectedProfile;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $rawUser;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
string $accessToken,
|
||||||
|
string $clientToken,
|
||||||
|
array $availableProfiles,
|
||||||
|
array $selectedProfile,
|
||||||
|
array $user
|
||||||
|
) {
|
||||||
|
$this->accessToken = $accessToken;
|
||||||
|
$this->clientToken = $clientToken;
|
||||||
|
$this->rawAvailableProfiles = $availableProfiles;
|
||||||
|
$this->rawSelectedProfile = $selectedProfile;
|
||||||
|
$this->rawUser = $user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAccessToken(): string {
|
||||||
|
return $this->accessToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getClientToken(): string {
|
||||||
|
return $this->clientToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return ProfileInfo[]
|
||||||
|
*/
|
||||||
|
public function getAvailableProfiles(): array {
|
||||||
|
return array_map([ProfileInfo::class, 'createFromResponse'], $this->rawAvailableProfiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSelectedProfile(): ProfileInfo {
|
||||||
|
return ProfileInfo::createFromResponse($this->rawSelectedProfile);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUser(): AuthenticationResponseUserField {
|
||||||
|
return new AuthenticationResponseUserField($this->rawUser['id'], $this->rawUser['properties']);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
30
src/Response/AuthenticationResponseUserField.php
Normal file
30
src/Response/AuthenticationResponseUserField.php
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Ely\Mojang\Response;
|
||||||
|
|
||||||
|
use Ely\Mojang\Response\Properties\Factory;
|
||||||
|
|
||||||
|
class AuthenticationResponseUserField {
|
||||||
|
|
||||||
|
private $id;
|
||||||
|
|
||||||
|
private $rawProperties;
|
||||||
|
|
||||||
|
public function __construct(string $id, array $rawProperties) {
|
||||||
|
$this->id = $id;
|
||||||
|
$this->rawProperties = $rawProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getId(): string {
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return \Ely\Mojang\Response\Properties\Property[]
|
||||||
|
*/
|
||||||
|
public function getProperties(): array {
|
||||||
|
return array_map([Factory::class, 'createFromProp'], $this->rawProperties);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
72
src/Response/ProfileInfo.php
Normal file
72
src/Response/ProfileInfo.php
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Ely\Mojang\Response;
|
||||||
|
|
||||||
|
class ProfileInfo {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
private $isLegacy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
private $isDemo;
|
||||||
|
|
||||||
|
public function __construct(string $id, string $name, bool $isLegacy = false, bool $isDemo = false) {
|
||||||
|
$this->id = $id;
|
||||||
|
$this->name = $name;
|
||||||
|
$this->isLegacy = $isLegacy;
|
||||||
|
$this->isDemo = $isDemo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function createFromResponse(array $response): self {
|
||||||
|
return new static(
|
||||||
|
$response['id'],
|
||||||
|
$response['name'],
|
||||||
|
$response['legacy'] ?? false,
|
||||||
|
$response['demo'] ?? false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string user's uuid without dashes
|
||||||
|
*/
|
||||||
|
public function getId(): string {
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string username at the current time
|
||||||
|
*/
|
||||||
|
public function getName(): string {
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool true means, that account not migrated into Mojang account
|
||||||
|
*/
|
||||||
|
public function isLegacy(): bool {
|
||||||
|
return $this->isLegacy;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool true means, that account now in demo mode (not premium user)
|
||||||
|
*/
|
||||||
|
public function isDemo(): bool {
|
||||||
|
return $this->isDemo;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
46
src/Response/ProfileResponse.php
Normal file
46
src/Response/ProfileResponse.php
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Ely\Mojang\Response;
|
||||||
|
|
||||||
|
use Ely\Mojang\Response\Properties\Factory;
|
||||||
|
|
||||||
|
class ProfileResponse {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $props;
|
||||||
|
|
||||||
|
public function __construct(string $id, string $name, array $rawProps) {
|
||||||
|
$this->id = $id;
|
||||||
|
$this->name = $name;
|
||||||
|
$this->props = $rawProps;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getId(): string {
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getName(): string {
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return \Ely\Mojang\Response\Properties\Property[]
|
||||||
|
*/
|
||||||
|
public function getProps(): array {
|
||||||
|
return array_map([Factory::class, 'createFromProp'], $this->props);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
22
src/Response/properties/Factory.php
Normal file
22
src/Response/properties/Factory.php
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Ely\Mojang\Response\Properties;
|
||||||
|
|
||||||
|
class Factory {
|
||||||
|
|
||||||
|
private static $MAP = [
|
||||||
|
'textures' => TexturesProperty::class,
|
||||||
|
];
|
||||||
|
|
||||||
|
public static function createFromProp(array $prop): Property {
|
||||||
|
$name = $prop['name'];
|
||||||
|
if (isset(static::$MAP[$name])) {
|
||||||
|
$className = static::$MAP[$name];
|
||||||
|
return new $className($prop);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Property($prop);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
31
src/Response/properties/Property.php
Normal file
31
src/Response/properties/Property.php
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Ely\Mojang\Response\Properties;
|
||||||
|
|
||||||
|
class Property {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public $name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public $value;
|
||||||
|
|
||||||
|
public function __construct(array $prop) {
|
||||||
|
$this->name = $prop['name'];
|
||||||
|
$this->value = $prop['value'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getName(): string {
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getValue(): string {
|
||||||
|
return $this->value;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
26
src/Response/properties/TexturesProperty.php
Normal file
26
src/Response/properties/TexturesProperty.php
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Ely\Mojang\Response\Properties;
|
||||||
|
|
||||||
|
class TexturesProperty extends Property {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string|null
|
||||||
|
*/
|
||||||
|
private $signature;
|
||||||
|
|
||||||
|
public function __construct(array $prop) {
|
||||||
|
parent::__construct($prop);
|
||||||
|
$this->signature = $prop['signature'] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTextures(): TexturesPropertyValue {
|
||||||
|
return TexturesPropertyValue::createFromRawTextures($this->value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSignature(): ?string {
|
||||||
|
return $this->signature;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
90
src/Response/properties/TexturesPropertyValue.php
Normal file
90
src/Response/properties/TexturesPropertyValue.php
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Ely\Mojang\Response\Properties;
|
||||||
|
|
||||||
|
class TexturesPropertyValue {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $username;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $textures;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
private $timestamp;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
private $signatureRequired;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
string $profileId,
|
||||||
|
string $profileName,
|
||||||
|
array $textures,
|
||||||
|
int $timestamp,
|
||||||
|
bool $signatureRequired = false
|
||||||
|
) {
|
||||||
|
$this->id = $profileId;
|
||||||
|
$this->username = $profileName;
|
||||||
|
$this->textures = $textures;
|
||||||
|
$this->timestamp = (int)floor($timestamp / 1000);
|
||||||
|
$this->signatureRequired = $signatureRequired;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function createFromRawTextures(string $rawTextures): self {
|
||||||
|
$decoded = json_decode(base64_decode($rawTextures), true);
|
||||||
|
return new static(
|
||||||
|
$decoded['profileId'],
|
||||||
|
$decoded['profileName'],
|
||||||
|
$decoded['textures'],
|
||||||
|
$decoded['timestamp'],
|
||||||
|
$decoded['signatureRequired'] ?? false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getProfileId(): string {
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getProfileName(): string {
|
||||||
|
return $this->username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTimestamp(): int {
|
||||||
|
return $this->timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isSignatureRequired(): bool {
|
||||||
|
return $this->signatureRequired;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSkin(): ?TexturesPropertyValueSkin {
|
||||||
|
if (!isset($this->textures['SKIN'])) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return TexturesPropertyValueSkin::createFromTextures($this->textures['SKIN']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCape(): ?TexturesPropertyValueCape {
|
||||||
|
if (!isset($this->textures['CAPE'])) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new TexturesPropertyValueCape($this->textures['CAPE']['url']);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
21
src/Response/properties/TexturesPropertyValueCape.php
Normal file
21
src/Response/properties/TexturesPropertyValueCape.php
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Ely\Mojang\Response\Properties;
|
||||||
|
|
||||||
|
class TexturesPropertyValueCape {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $url;
|
||||||
|
|
||||||
|
public function __construct(string $skinUrl) {
|
||||||
|
$this->url = $skinUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUrl(): string {
|
||||||
|
return $this->url;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
36
src/Response/properties/TexturesPropertyValueSkin.php
Normal file
36
src/Response/properties/TexturesPropertyValueSkin.php
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Ely\Mojang\Response\Properties;
|
||||||
|
|
||||||
|
class TexturesPropertyValueSkin {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $url;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
private $isSlim;
|
||||||
|
|
||||||
|
public function __construct(string $skinUrl, bool $isSlim = false) {
|
||||||
|
$this->url = $skinUrl;
|
||||||
|
$this->isSlim = $isSlim;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function createFromTextures(array $textures): self {
|
||||||
|
$model = &$textures['metainfo']['model']; // ampersand to avoid notice about unexpected key
|
||||||
|
return new static($textures['url'], $model === 'slim');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUrl(): string {
|
||||||
|
return $this->url;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isSlim(): bool {
|
||||||
|
return $this->isSlim;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
347
tests/ApiTest.php
Normal file
347
tests/ApiTest.php
Normal file
@ -0,0 +1,347 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Ely\Mojang\Test;
|
||||||
|
|
||||||
|
use Ely\Mojang\Api;
|
||||||
|
use Ely\Mojang\Exception\NoContentException;
|
||||||
|
use Ely\Mojang\Middleware\ResponseConverterMiddleware;
|
||||||
|
use Ely\Mojang\Middleware\RetryMiddleware;
|
||||||
|
use Ely\Mojang\Response\Properties\TexturesProperty;
|
||||||
|
use GuzzleHttp\Client;
|
||||||
|
use GuzzleHttp\Handler\MockHandler;
|
||||||
|
use GuzzleHttp\HandlerStack;
|
||||||
|
use GuzzleHttp\Middleware;
|
||||||
|
use GuzzleHttp\Psr7\Response;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
use function GuzzleHttp\Psr7\parse_query;
|
||||||
|
|
||||||
|
class ApiTest extends TestCase {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var Api
|
||||||
|
*/
|
||||||
|
private $api;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \GuzzleHttp\Handler\MockHandler
|
||||||
|
*/
|
||||||
|
private $mockHandler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Psr\Http\Message\RequestInterface[]
|
||||||
|
*/
|
||||||
|
private $history;
|
||||||
|
|
||||||
|
protected function setUp(): void {
|
||||||
|
$this->mockHandler = new MockHandler();
|
||||||
|
$handlerStack = HandlerStack::create($this->mockHandler);
|
||||||
|
$this->history = [];
|
||||||
|
$handlerStack->push(Middleware::history($this->history), 'history');
|
||||||
|
$handlerStack->after('http_errors', ResponseConverterMiddleware::create(), 'mojang_responses');
|
||||||
|
$handlerStack->push(RetryMiddleware::create(), 'retry');
|
||||||
|
$client = new Client(['handler' => $handlerStack]);
|
||||||
|
$this->api = new Api($client);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCreate() {
|
||||||
|
$this->assertInstanceOf(Api::class, Api::create());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUsernameToUuid() {
|
||||||
|
$this->mockHandler->append($this->createResponse(200, [
|
||||||
|
'id' => '86f6e3695b764412a29820cac1d4d0d6',
|
||||||
|
'name' => 'MockUsername',
|
||||||
|
'legacy' => false,
|
||||||
|
]));
|
||||||
|
|
||||||
|
$result = $this->api->usernameToUUID('MockUsername');
|
||||||
|
|
||||||
|
/** @var \Psr\Http\Message\RequestInterface $request */
|
||||||
|
$request = $this->history[0]['request'];
|
||||||
|
$this->assertSame('https://api.mojang.com/users/profiles/minecraft/MockUsername', (string)$request->getUri());
|
||||||
|
|
||||||
|
$this->assertSame('86f6e3695b764412a29820cac1d4d0d6', $result->getId());
|
||||||
|
$this->assertSame('MockUsername', $result->getName());
|
||||||
|
$this->assertFalse($result->isLegacy());
|
||||||
|
$this->assertFalse($result->isDemo());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUsernameToUuidWithAtParam() {
|
||||||
|
$this->mockHandler->append($this->createResponse(200, [
|
||||||
|
'id' => '86f6e3695b764412a29820cac1d4d0d6',
|
||||||
|
'name' => 'MockUsername',
|
||||||
|
'legacy' => false,
|
||||||
|
]));
|
||||||
|
|
||||||
|
$this->api->usernameToUUID('MockUsername', 1553961511);
|
||||||
|
|
||||||
|
/** @var \Psr\Http\Message\RequestInterface $request */
|
||||||
|
$request = $this->history[0]['request'];
|
||||||
|
$this->assertSame(
|
||||||
|
'https://api.mojang.com/users/profiles/minecraft/MockUsername?atTime=1553961511',
|
||||||
|
(string)$request->getUri()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUuidToTextures() {
|
||||||
|
$this->mockHandler->append($this->createResponse(200, [
|
||||||
|
'id' => '86f6e3695b764412a29820cac1d4d0d6',
|
||||||
|
'name' => 'MockUsername',
|
||||||
|
'properties' => [
|
||||||
|
[
|
||||||
|
'name' => 'textures',
|
||||||
|
'value' => base64_encode(json_encode([
|
||||||
|
'timestamp' => 1553961848860,
|
||||||
|
'profileId' => '86f6e3695b764412a29820cac1d4d0d6',
|
||||||
|
'profileName' => 'MockUsername',
|
||||||
|
'signatureRequired' => true,
|
||||||
|
'textures' => [
|
||||||
|
'SKIN' => [
|
||||||
|
'url' => 'http://textures.minecraft.net/texture/292009a4925b58f02c77dadc3ecef07ea4c7472f64e0fdc32ce5522489362680',
|
||||||
|
],
|
||||||
|
'CAPE' => [
|
||||||
|
'url' => 'http://textures.minecraft.net/texture/capePath',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
])),
|
||||||
|
'signature' => 'mocked signature value',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
]));
|
||||||
|
|
||||||
|
$result = $this->api->uuidToTextures('86f6e3695b764412a29820cac1d4d0d6');
|
||||||
|
|
||||||
|
/** @var \Psr\Http\Message\RequestInterface $request */
|
||||||
|
$request = $this->history[0]['request'];
|
||||||
|
$this->assertSame(
|
||||||
|
'https://sessionserver.mojang.com/session/minecraft/profile/86f6e3695b764412a29820cac1d4d0d6?unsigned=0',
|
||||||
|
(string)$request->getUri()
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertSame('86f6e3695b764412a29820cac1d4d0d6', $result->getId());
|
||||||
|
$this->assertSame('MockUsername', $result->getName());
|
||||||
|
$props = $result->getProps();
|
||||||
|
/** @var TexturesProperty $texturesProperty */
|
||||||
|
$texturesProperty = $props[0];
|
||||||
|
$this->assertInstanceOf(TexturesProperty::class, $texturesProperty);
|
||||||
|
$this->assertSame('textures', $texturesProperty->getName());
|
||||||
|
$this->assertSame('mocked signature value', $texturesProperty->getSignature());
|
||||||
|
$textures = $texturesProperty->getTextures();
|
||||||
|
$this->assertSame(1553961848, $textures->getTimestamp());
|
||||||
|
$this->assertSame('86f6e3695b764412a29820cac1d4d0d6', $textures->getProfileId());
|
||||||
|
$this->assertSame('MockUsername', $textures->getProfileName());
|
||||||
|
$this->assertTrue($textures->isSignatureRequired());
|
||||||
|
$this->assertNotNull($textures->getSkin());
|
||||||
|
$this->assertSame(
|
||||||
|
'http://textures.minecraft.net/texture/292009a4925b58f02c77dadc3ecef07ea4c7472f64e0fdc32ce5522489362680',
|
||||||
|
$textures->getSkin()->getUrl()
|
||||||
|
);
|
||||||
|
$this->assertFalse($textures->getSkin()->isSlim());
|
||||||
|
$this->assertSame('http://textures.minecraft.net/texture/capePath', $textures->getCape()->getUrl());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUsernameToTextures() {
|
||||||
|
$this->mockHandler->append($this->createResponse(200, [
|
||||||
|
'id' => '86f6e3695b764412a29820cac1d4d0d6',
|
||||||
|
'name' => 'MockUsername',
|
||||||
|
'legacy' => false,
|
||||||
|
]));
|
||||||
|
$this->mockHandler->append($this->createResponse(200, [
|
||||||
|
'id' => '86f6e3695b764412a29820cac1d4d0d6',
|
||||||
|
'name' => 'MockUsername',
|
||||||
|
'properties' => [],
|
||||||
|
]));
|
||||||
|
|
||||||
|
$this->api->usernameToTextures('MockUsername');
|
||||||
|
/** @var \Psr\Http\Message\RequestInterface $request1 */
|
||||||
|
/** @var \Psr\Http\Message\RequestInterface $request2 */
|
||||||
|
[0 => ['request' => $request1], 1 => ['request' => $request2]] = $this->history;
|
||||||
|
$this->assertSame('https://api.mojang.com/users/profiles/minecraft/MockUsername', (string)$request1->getUri());
|
||||||
|
$this->assertStringStartsWith('https://sessionserver.mojang.com/session/minecraft/profile/86f6e3695b764412a29820cac1d4d0d6', (string)$request2->getUri());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAuthenticate() {
|
||||||
|
$this->mockHandler->append($this->createResponse(200, [
|
||||||
|
'accessToken' => 'access token value',
|
||||||
|
'clientToken' => 'client token value',
|
||||||
|
'availableProfiles' => [
|
||||||
|
[
|
||||||
|
'id' => '86f6e3695b764412a29820cac1d4d0d6',
|
||||||
|
'name' => 'MockUsername',
|
||||||
|
'legacy' => false,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'selectedProfile' => [
|
||||||
|
'id' => '86f6e3695b764412a29820cac1d4d0d6',
|
||||||
|
'name' => 'MockUsername',
|
||||||
|
'legacy' => false,
|
||||||
|
],
|
||||||
|
'user' => [
|
||||||
|
'id' => '86f6e3695b764412a29820cac1d4d0d6',
|
||||||
|
'properties' => [
|
||||||
|
[
|
||||||
|
'name' => 'preferredLanguage',
|
||||||
|
'value' => 'en',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => 'twitch_access_token',
|
||||||
|
'value' => 'twitch oauth token',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
]));
|
||||||
|
|
||||||
|
$result = $this->api->authenticate('MockUsername', 'some password', 'client token value');
|
||||||
|
|
||||||
|
/** @var \Psr\Http\Message\RequestInterface $request */
|
||||||
|
$request = $this->history[0]['request'];
|
||||||
|
$this->assertSame('https://authserver.mojang.com/authenticate', (string)$request->getUri());
|
||||||
|
|
||||||
|
$this->assertSame('access token value', $result->getAccessToken());
|
||||||
|
$this->assertSame('client token value', $result->getClientToken());
|
||||||
|
|
||||||
|
$this->assertSame('86f6e3695b764412a29820cac1d4d0d6', $result->getAvailableProfiles()[0]->getId());
|
||||||
|
$this->assertSame('MockUsername', $result->getAvailableProfiles()[0]->getName());
|
||||||
|
$this->assertFalse($result->getAvailableProfiles()[0]->isLegacy());
|
||||||
|
$this->assertFalse($result->getAvailableProfiles()[0]->isDemo());
|
||||||
|
|
||||||
|
$this->assertSame('86f6e3695b764412a29820cac1d4d0d6', $result->getSelectedProfile()->getId());
|
||||||
|
$this->assertSame('MockUsername', $result->getSelectedProfile()->getName());
|
||||||
|
$this->assertFalse($result->getSelectedProfile()->isLegacy());
|
||||||
|
$this->assertFalse($result->getSelectedProfile()->isDemo());
|
||||||
|
|
||||||
|
$this->assertSame('86f6e3695b764412a29820cac1d4d0d6', $result->getUser()->getId());
|
||||||
|
|
||||||
|
$this->assertSame('preferredLanguage', $result->getUser()->getProperties()[0]->getName());
|
||||||
|
$this->assertSame('en', $result->getUser()->getProperties()[0]->getValue());
|
||||||
|
$this->assertSame('twitch_access_token', $result->getUser()->getProperties()[1]->getName());
|
||||||
|
$this->assertSame('twitch oauth token', $result->getUser()->getProperties()[1]->getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAuthenticateWithNotSpecifiedClientToken() {
|
||||||
|
$this->mockHandler->append($this->createResponse(200, [
|
||||||
|
'accessToken' => 'access token value',
|
||||||
|
'clientToken' => 'client token value',
|
||||||
|
'availableProfiles' => [],
|
||||||
|
'selectedProfile' => [
|
||||||
|
'id' => '86f6e3695b764412a29820cac1d4d0d6',
|
||||||
|
'name' => 'MockUsername',
|
||||||
|
'legacy' => false,
|
||||||
|
],
|
||||||
|
'user' => [
|
||||||
|
'id' => '86f6e3695b764412a29820cac1d4d0d6',
|
||||||
|
'properties' => [],
|
||||||
|
],
|
||||||
|
]));
|
||||||
|
|
||||||
|
$this->api->authenticate('MockUsername', 'some password');
|
||||||
|
|
||||||
|
/** @var \Psr\Http\Message\RequestInterface $request */
|
||||||
|
$request = $this->history[0]['request'];
|
||||||
|
$body = json_decode($request->getBody()->getContents(), true);
|
||||||
|
// https://gist.github.com/johnelliott/cf77003f72f889abbc3f32785fa3df8d
|
||||||
|
$this->assertRegExp('/^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i', $body['clientToken']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testValidateSuccessful() {
|
||||||
|
$this->mockHandler->append(new Response(204));
|
||||||
|
$this->assertTrue($this->api->validate('mocked access token'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testValidateInvalid() {
|
||||||
|
$this->mockHandler->append(new Response(403));
|
||||||
|
$this->assertFalse($this->api->validate('mocked access token'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUploadSkinNotSlim() {
|
||||||
|
$this->mockHandler->append(new Response(200));
|
||||||
|
$this->api->uploadSkin('mocked access token', '86f6e3695b764412a29820cac1d4d0d6', 'skin contents', false);
|
||||||
|
/** @var \Psr\Http\Message\RequestInterface $request */
|
||||||
|
$request = $this->history[0]['request'];
|
||||||
|
$this->assertSame('Bearer mocked access token', $request->getHeaderLine('Authorization'));
|
||||||
|
$this->assertStringNotContainsString('slim', $request->getBody()->getContents());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUploadSkinSlim() {
|
||||||
|
$this->mockHandler->append(new Response(200));
|
||||||
|
$this->api->uploadSkin('mocked access token', '86f6e3695b764412a29820cac1d4d0d6', 'skin contents', true);
|
||||||
|
/** @var \Psr\Http\Message\RequestInterface $request */
|
||||||
|
$request = $this->history[0]['request'];
|
||||||
|
$this->assertStringContainsString('slim', $request->getBody()->getContents());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testJoinServer() {
|
||||||
|
$this->mockHandler->append(new Response(200));
|
||||||
|
$this->api->joinServer('mocked access token', '86f6e3695b764412a29820cac1d4d0d6', 'ad72fe1efe364e6eb78c644a9fba1d30');
|
||||||
|
/** @var \Psr\Http\Message\RequestInterface $request */
|
||||||
|
$request = $this->history[0]['request'];
|
||||||
|
$params = json_decode($request->getBody()->getContents(), true);
|
||||||
|
$this->assertSame('mocked access token', $params['accessToken']);
|
||||||
|
$this->assertSame('86f6e3695b764412a29820cac1d4d0d6', $params['selectedProfile']);
|
||||||
|
$this->assertSame('ad72fe1efe364e6eb78c644a9fba1d30', $params['serverId']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testHasJoinedServer() {
|
||||||
|
$this->mockHandler->append($this->createResponse(200, [
|
||||||
|
'id' => '86f6e3695b764412a29820cac1d4d0d6',
|
||||||
|
'name' => 'MockUsername',
|
||||||
|
'properties' => [
|
||||||
|
[
|
||||||
|
'name' => 'textures',
|
||||||
|
'value' => base64_encode(json_encode([
|
||||||
|
'timestamp' => 1553961848860,
|
||||||
|
'profileId' => '86f6e3695b764412a29820cac1d4d0d6',
|
||||||
|
'profileName' => 'MockUsername',
|
||||||
|
'signatureRequired' => true,
|
||||||
|
'textures' => [
|
||||||
|
'SKIN' => [
|
||||||
|
'url' => 'http://textures.minecraft.net/texture/292009a4925b58f02c77dadc3ecef07ea4c7472f64e0fdc32ce5522489362680',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
])),
|
||||||
|
'signature' => 'mocked signature value',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
]));
|
||||||
|
$result = $this->api->hasJoinedServer('MockedUsername', 'ad72fe1efe364e6eb78c644a9fba1d30');
|
||||||
|
/** @var \Psr\Http\Message\RequestInterface $request */
|
||||||
|
$request = $this->history[0]['request'];
|
||||||
|
$params = parse_query($request->getUri()->getQuery());
|
||||||
|
$this->assertSame('MockedUsername', $params['username']);
|
||||||
|
$this->assertSame('ad72fe1efe364e6eb78c644a9fba1d30', $params['serverId']);
|
||||||
|
|
||||||
|
$this->assertSame('86f6e3695b764412a29820cac1d4d0d6', $result->getId());
|
||||||
|
$this->assertSame('MockUsername', $result->getName());
|
||||||
|
$props = $result->getProps();
|
||||||
|
/** @var TexturesProperty $texturesProperty */
|
||||||
|
$texturesProperty = $props[0];
|
||||||
|
$this->assertInstanceOf(TexturesProperty::class, $texturesProperty);
|
||||||
|
$this->assertSame('textures', $texturesProperty->getName());
|
||||||
|
$this->assertSame('mocked signature value', $texturesProperty->getSignature());
|
||||||
|
$textures = $texturesProperty->getTextures();
|
||||||
|
$this->assertSame(1553961848, $textures->getTimestamp());
|
||||||
|
$this->assertSame('86f6e3695b764412a29820cac1d4d0d6', $textures->getProfileId());
|
||||||
|
$this->assertSame('MockUsername', $textures->getProfileName());
|
||||||
|
$this->assertTrue($textures->isSignatureRequired());
|
||||||
|
$this->assertNotNull($textures->getSkin());
|
||||||
|
$this->assertSame(
|
||||||
|
'http://textures.minecraft.net/texture/292009a4925b58f02c77dadc3ecef07ea4c7472f64e0fdc32ce5522489362680',
|
||||||
|
$textures->getSkin()->getUrl()
|
||||||
|
);
|
||||||
|
$this->assertFalse($textures->getSkin()->isSlim());
|
||||||
|
$this->assertNull($textures->getCape());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testHasJoinedServerEmptyResponse() {
|
||||||
|
$this->mockHandler->append(new Response(200));
|
||||||
|
$this->expectException(NoContentException::class);
|
||||||
|
$this->api->hasJoinedServer('MockedUsername', 'ad72fe1efe364e6eb78c644a9fba1d30');
|
||||||
|
}
|
||||||
|
|
||||||
|
private function createResponse(int $statusCode, array $response): ResponseInterface {
|
||||||
|
return new Response($statusCode, ['content-type' => 'json'], json_encode($response));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
56
tests/Middleware/ResponseConverterMiddlewareTest.php
Normal file
56
tests/Middleware/ResponseConverterMiddlewareTest.php
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Ely\Mojang\Test\Middleware;
|
||||||
|
|
||||||
|
use Ely\Mojang\Exception;
|
||||||
|
use Ely\Mojang\Middleware\ResponseConverterMiddleware;
|
||||||
|
use GuzzleHttp\Handler\MockHandler;
|
||||||
|
use GuzzleHttp\Psr7\Request;
|
||||||
|
use GuzzleHttp\Psr7\Response;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Psr\Http\Message\RequestInterface;
|
||||||
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
|
||||||
|
class ResponseConverterMiddlewareTest extends TestCase {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param ResponseInterface $response
|
||||||
|
* @dataProvider getResponses
|
||||||
|
*/
|
||||||
|
public function testInvoke(RequestInterface $request, ResponseInterface $response, string $expectedException) {
|
||||||
|
$this->expectException($expectedException);
|
||||||
|
$handler = new MockHandler([$response]);
|
||||||
|
$middleware = new ResponseConverterMiddleware($handler);
|
||||||
|
$middleware($request, [])->wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getResponses(): iterable {
|
||||||
|
yield [
|
||||||
|
new Request('GET', 'http://localhost'),
|
||||||
|
new Response(204, [], ''),
|
||||||
|
Exception\NoContentException::class,
|
||||||
|
];
|
||||||
|
|
||||||
|
yield [
|
||||||
|
new Request('GET', 'http://localhost'),
|
||||||
|
new Response(
|
||||||
|
403,
|
||||||
|
['Content-Type' => 'application/json'],
|
||||||
|
'{"error":"ForbiddenOperationException","errorMessage":"Invalid token"}'
|
||||||
|
),
|
||||||
|
Exception\ForbiddenException::class,
|
||||||
|
];
|
||||||
|
|
||||||
|
yield [
|
||||||
|
new Request('GET', 'http://localhost'),
|
||||||
|
new Response(
|
||||||
|
429,
|
||||||
|
['Content-Type' => 'application/json'],
|
||||||
|
'{"error":"TooManyRequestsException","errorMessage":"The client has sent too many requests within a certain amount of time"}'
|
||||||
|
),
|
||||||
|
Exception\TooManyRequestsException::class,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
24
tests/Middleware/RetryMiddlewareTest.php
Normal file
24
tests/Middleware/RetryMiddlewareTest.php
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Ely\Mojang\Test\Middleware;
|
||||||
|
|
||||||
|
use Ely\Mojang\Middleware\RetryMiddleware;
|
||||||
|
use GuzzleHttp\Exception\ConnectException;
|
||||||
|
use GuzzleHttp\Psr7\Request;
|
||||||
|
use GuzzleHttp\Psr7\Response;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
class RetryMiddlewareTest extends TestCase {
|
||||||
|
|
||||||
|
public function testShouldRetry() {
|
||||||
|
$r = new Request('GET', 'http://localhost');
|
||||||
|
$this->assertFalse(RetryMiddleware::shouldRetry(0, $r, new Response(200), null), 'not retry on success response');
|
||||||
|
$this->assertFalse(RetryMiddleware::shouldRetry(0, $r, new Response(403), null), 'not retry on client error');
|
||||||
|
$this->assertTrue(RetryMiddleware::shouldRetry(0, $r, null, new ConnectException('', $r)), 'retry when network error happens');
|
||||||
|
$this->assertTrue(RetryMiddleware::shouldRetry(0, $r, new Response(503), null), 'retry when 50x error 1 time');
|
||||||
|
$this->assertTrue(RetryMiddleware::shouldRetry(1, $r, new Response(503), null), 'retry when 50x error 2 time');
|
||||||
|
$this->assertFalse(RetryMiddleware::shouldRetry(2, $r, new Response(503), null), 'don\'t retry when 50x error 3 time');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
36
tests/Response/Properties/FactoryTest.php
Normal file
36
tests/Response/Properties/FactoryTest.php
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Ely\Mojang\Test\Response\Properties;
|
||||||
|
|
||||||
|
use Ely\Mojang\Response\Properties\Factory;
|
||||||
|
use Ely\Mojang\Response\Properties\Property;
|
||||||
|
use Ely\Mojang\Response\Properties\TexturesProperty;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
class FactoryTest extends TestCase {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $inputProps
|
||||||
|
* @param string $expectedType
|
||||||
|
*
|
||||||
|
* @dataProvider getProps
|
||||||
|
*/
|
||||||
|
public function testCreate(array $inputProps, string $expectedType) {
|
||||||
|
$this->assertInstanceOf($expectedType, Factory::createFromProp($inputProps));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getProps(): iterable {
|
||||||
|
yield [[
|
||||||
|
'name' => 'textures',
|
||||||
|
'value' => 'value',
|
||||||
|
'signature' => '123',
|
||||||
|
], TexturesProperty::class];
|
||||||
|
|
||||||
|
yield [[
|
||||||
|
'name' => 'other',
|
||||||
|
'value' => 'value',
|
||||||
|
], Property::class];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
50
tests/Response/Properties/TexturesPropertyValueTest.php
Normal file
50
tests/Response/Properties/TexturesPropertyValueTest.php
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Ely\Mojang\Test\Response\Properties;
|
||||||
|
|
||||||
|
use Ely\Mojang\Response\Properties\TexturesPropertyValue;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
class TexturesPropertyValueTest extends TestCase {
|
||||||
|
|
||||||
|
public function testGetSkin() {
|
||||||
|
$object = new TexturesPropertyValue('', '', [
|
||||||
|
'SKIN' => [
|
||||||
|
'url' => 'skin url',
|
||||||
|
],
|
||||||
|
], 0);
|
||||||
|
$this->assertNotNull($object->getSkin());
|
||||||
|
$this->assertSame('skin url', $object->getSkin()->getUrl());
|
||||||
|
$this->assertFalse($object->getSkin()->isSlim());
|
||||||
|
|
||||||
|
$object = new TexturesPropertyValue('', '', [
|
||||||
|
'SKIN' => [
|
||||||
|
'url' => 'skin url',
|
||||||
|
'metainfo' => [
|
||||||
|
'model' => 'slim',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
], 0);
|
||||||
|
$this->assertNotNull($object->getSkin());
|
||||||
|
$this->assertSame('skin url', $object->getSkin()->getUrl());
|
||||||
|
$this->assertTrue($object->getSkin()->isSlim());
|
||||||
|
|
||||||
|
$object = new TexturesPropertyValue('', '', [], 0);
|
||||||
|
$this->assertNull($object->getSkin());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetCape() {
|
||||||
|
$object = new TexturesPropertyValue('', '', [
|
||||||
|
'CAPE' => [
|
||||||
|
'url' => 'cape url',
|
||||||
|
],
|
||||||
|
], 0);
|
||||||
|
$this->assertNotNull($object->getCape());
|
||||||
|
$this->assertSame('cape url', $object->getCape()->getUrl());
|
||||||
|
|
||||||
|
$object = new TexturesPropertyValue('', '', [], 0);
|
||||||
|
$this->assertNull($object->getCape());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user