mirror of
https://github.com/elyby/mojang-api.git
synced 2024-12-22 16:19:48 +05:30
Merge branch 'valentin-pazushko-master'
This commit is contained in:
commit
987afb244b
@ -5,6 +5,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
### Added
|
||||||
|
- [Check if security questions are needed](https://wiki.vg/Mojang_API#Check_if_security_questions_are_needed) endpoint.
|
||||||
|
- [Get list of questions](https://wiki.vg/Mojang_API#Get_list_of_questions) endpoint.
|
||||||
|
- [Send back the answers](https://wiki.vg/Mojang_API#Send_back_the_answers) endpoint.
|
||||||
|
- [Statistics](https://wiki.vg/Mojang_API#Statistics) endpoint.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Changed the threshold value for [Playernames -> UUIDs](https://wiki.vg/Mojang_API#Playernames_-.3E_UUIDs) endpoint
|
||||||
|
from `100` to `10`.
|
||||||
|
|
||||||
## [0.2.0] - 2019-05-07
|
## [0.2.0] - 2019-05-07
|
||||||
### Added
|
### Added
|
||||||
|
87
src/Api.php
87
src/Api.php
@ -6,6 +6,7 @@ namespace Ely\Mojang;
|
|||||||
use DateTime;
|
use DateTime;
|
||||||
use Ely\Mojang\Middleware\ResponseConverterMiddleware;
|
use Ely\Mojang\Middleware\ResponseConverterMiddleware;
|
||||||
use Ely\Mojang\Middleware\RetryMiddleware;
|
use Ely\Mojang\Middleware\RetryMiddleware;
|
||||||
|
use Ely\Mojang\Response\QuestionResponse;
|
||||||
use GuzzleHttp\Client as GuzzleClient;
|
use GuzzleHttp\Client as GuzzleClient;
|
||||||
use GuzzleHttp\ClientInterface;
|
use GuzzleHttp\ClientInterface;
|
||||||
use GuzzleHttp\Exception\GuzzleException;
|
use GuzzleHttp\Exception\GuzzleException;
|
||||||
@ -150,8 +151,8 @@ class Api {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (count($names) > 100) {
|
if (count($names) > 10) {
|
||||||
throw new InvalidArgumentException('You cannot request more than 100 names per request');
|
throw new InvalidArgumentException('You cannot request more than 10 names per request');
|
||||||
}
|
}
|
||||||
|
|
||||||
$response = $this->getClient()->request('POST', 'https://api.mojang.com/profiles/minecraft', [
|
$response = $this->getClient()->request('POST', 'https://api.mojang.com/profiles/minecraft', [
|
||||||
@ -452,6 +453,88 @@ class Api {
|
|||||||
return new Response\ProfileResponse($body['id'], $body['name'], $body['properties']);
|
return new Response\ProfileResponse($body['id'], $body['name'], $body['properties']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $accessToken
|
||||||
|
* @throws GuzzleException
|
||||||
|
*
|
||||||
|
* @url https://wiki.vg/Mojang_API#Check_if_security_questions_are_needed
|
||||||
|
*/
|
||||||
|
public function isSecurityQuestionsNeeded(string $accessToken): void {
|
||||||
|
$request = new Request(
|
||||||
|
'GET',
|
||||||
|
'https://api.mojang.com/user/security/location',
|
||||||
|
['Authorization' => 'Bearer ' . $accessToken]
|
||||||
|
);
|
||||||
|
$response = $this->getClient()->send($request);
|
||||||
|
$rawBody = $response->getBody()->getContents();
|
||||||
|
if (!empty($rawBody)) {
|
||||||
|
$body = $this->decode($rawBody);
|
||||||
|
throw new Exception\OperationException($body['errorMessage'], $request, $response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $accessToken
|
||||||
|
* @return array
|
||||||
|
* @throws GuzzleException
|
||||||
|
*
|
||||||
|
* @url https://wiki.vg/Mojang_API#Get_list_of_questions
|
||||||
|
*/
|
||||||
|
public function questions(string $accessToken): array {
|
||||||
|
$request = new Request(
|
||||||
|
'GET',
|
||||||
|
'https://api.mojang.com/user/security/challenges',
|
||||||
|
['Authorization' => 'Bearer ' . $accessToken]
|
||||||
|
);
|
||||||
|
$response = $this->getClient()->send($request);
|
||||||
|
$result = [];
|
||||||
|
$body = $this->decode($response->getBody()->getContents());
|
||||||
|
foreach ($body as $question) {
|
||||||
|
$result[] = new QuestionResponse($question['question']['id'], $question['question']['question'], $question['answer']['id']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $accessToken
|
||||||
|
* @param array $answers
|
||||||
|
* @throws GuzzleException
|
||||||
|
* @return bool
|
||||||
|
*
|
||||||
|
* @url https://wiki.vg/Mojang_API#Send_back_the_answers
|
||||||
|
*/
|
||||||
|
public function answer(string $accessToken, array $answers): bool {
|
||||||
|
$request = new Request(
|
||||||
|
'POST',
|
||||||
|
'https://api.mojang.com/user/security/location',
|
||||||
|
['Authorization' => 'Bearer ' . $accessToken],
|
||||||
|
json_encode($answers)
|
||||||
|
);
|
||||||
|
$response = $this->getClient()->send($request);
|
||||||
|
$rawBody = $response->getBody()->getContents();
|
||||||
|
|
||||||
|
return empty($rawBody);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $metricKeys
|
||||||
|
* @return Response\StatisticsResponse
|
||||||
|
* @throws GuzzleException
|
||||||
|
*
|
||||||
|
* @url https://wiki.vg/Mojang_API#Statistics
|
||||||
|
*/
|
||||||
|
public function statistics(array $metricKeys) {
|
||||||
|
$response = $this->getClient()->request('POST', 'https://api.mojang.com/orders/statistics', [
|
||||||
|
'json' => [
|
||||||
|
'metricKeys' => $metricKeys,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
$body = $this->decode($response->getBody()->getContents());
|
||||||
|
|
||||||
|
return new Response\StatisticsResponse($body['total'], $body['last24h'], $body['saleVelocityPerSeconds']);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return ClientInterface
|
* @return ClientInterface
|
||||||
*/
|
*/
|
||||||
|
16
src/Exception/OperationException.php
Normal file
16
src/Exception/OperationException.php
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Ely\Mojang\Exception;
|
||||||
|
|
||||||
|
use GuzzleHttp\Exception\ClientException;
|
||||||
|
use Psr\Http\Message\RequestInterface;
|
||||||
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
|
||||||
|
class OperationException extends ClientException implements MojangApiException {
|
||||||
|
|
||||||
|
public function __construct(string $message, RequestInterface $request, ResponseInterface $response) {
|
||||||
|
parent::__construct($message, $request, $response);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
41
src/Response/QuestionResponse.php
Normal file
41
src/Response/QuestionResponse.php
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Ely\Mojang\Response;
|
||||||
|
|
||||||
|
class QuestionResponse {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
private $questionId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $question;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
private $answerId;
|
||||||
|
|
||||||
|
public function __construct(int $questionId, string $question, int $answerId) {
|
||||||
|
$this->questionId = $questionId;
|
||||||
|
$this->question = $question;
|
||||||
|
$this->answerId = $answerId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getQuestionId(): int {
|
||||||
|
return $this->questionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getQuestion(): string {
|
||||||
|
return $this->question;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAnswerId(): int {
|
||||||
|
return $this->answerId;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
41
src/Response/StatisticsResponse.php
Normal file
41
src/Response/StatisticsResponse.php
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Ely\Mojang\Response;
|
||||||
|
|
||||||
|
class StatisticsResponse {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
private $total;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
private $last24h;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var float
|
||||||
|
*/
|
||||||
|
private $saleVelocityPerSeconds;
|
||||||
|
|
||||||
|
public function __construct(int $total, int $last24h, float $saleVelocityPerSeconds) {
|
||||||
|
$this->total = $total;
|
||||||
|
$this->last24h = $last24h;
|
||||||
|
$this->saleVelocityPerSeconds = $saleVelocityPerSeconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTotal(): int {
|
||||||
|
return $this->total;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLast24H(): int {
|
||||||
|
return $this->last24h;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSaleVelocityPerSeconds(): float {
|
||||||
|
return $this->saleVelocityPerSeconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -6,12 +6,14 @@ namespace Ely\Mojang\Test;
|
|||||||
use Ely\Mojang\Api;
|
use Ely\Mojang\Api;
|
||||||
use Ely\Mojang\Exception\ForbiddenException;
|
use Ely\Mojang\Exception\ForbiddenException;
|
||||||
use Ely\Mojang\Exception\NoContentException;
|
use Ely\Mojang\Exception\NoContentException;
|
||||||
|
use Ely\Mojang\Exception\OperationException;
|
||||||
use Ely\Mojang\Middleware\ResponseConverterMiddleware;
|
use Ely\Mojang\Middleware\ResponseConverterMiddleware;
|
||||||
use Ely\Mojang\Middleware\RetryMiddleware;
|
use Ely\Mojang\Middleware\RetryMiddleware;
|
||||||
use Ely\Mojang\Response\ApiStatus;
|
use Ely\Mojang\Response\ApiStatus;
|
||||||
use Ely\Mojang\Response\NameHistoryItem;
|
use Ely\Mojang\Response\NameHistoryItem;
|
||||||
use Ely\Mojang\Response\ProfileInfo;
|
use Ely\Mojang\Response\ProfileInfo;
|
||||||
use Ely\Mojang\Response\Properties\TexturesProperty;
|
use Ely\Mojang\Response\Properties\TexturesProperty;
|
||||||
|
use Ely\Mojang\Response\QuestionResponse;
|
||||||
use GuzzleHttp\Client;
|
use GuzzleHttp\Client;
|
||||||
use GuzzleHttp\ClientInterface;
|
use GuzzleHttp\ClientInterface;
|
||||||
use GuzzleHttp\Handler\MockHandler;
|
use GuzzleHttp\Handler\MockHandler;
|
||||||
@ -309,7 +311,7 @@ class ApiTest extends TestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$this->expectException(InvalidArgumentException::class);
|
$this->expectException(InvalidArgumentException::class);
|
||||||
$this->expectExceptionMessage('You cannot request more than 100 names per request');
|
$this->expectExceptionMessage('You cannot request more than 10 names per request');
|
||||||
$this->api->playernamesToUuids($names);
|
$this->api->playernamesToUuids($names);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -609,6 +611,130 @@ class ApiTest extends TestCase {
|
|||||||
$this->assertInstanceOf(ClientInterface::class, $child->getDefaultClient());
|
$this->assertInstanceOf(ClientInterface::class, $child->getDefaultClient());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testIsSecurityQuestionsNeeded() {
|
||||||
|
$this->mockHandler->append(new Response(204));
|
||||||
|
$this->expectException(NoContentException::class);
|
||||||
|
$this->api->isSecurityQuestionsNeeded('mocked access token');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testIsSecurityQuestionsNeededOperationException() {
|
||||||
|
$this->mockHandler->append($this->createResponse(200, [
|
||||||
|
'error' => 'ForbiddenOperationException',
|
||||||
|
'errorMessage' => 'Current IP is not secured',
|
||||||
|
]));
|
||||||
|
$this->expectException(OperationException::class);
|
||||||
|
$this->api->isSecurityQuestionsNeeded('mocked access token');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testQuestions() {
|
||||||
|
$this->mockHandler->append($this->createResponse(200, [
|
||||||
|
[
|
||||||
|
'answer' => [
|
||||||
|
'id' => 123,
|
||||||
|
],
|
||||||
|
'question' => [
|
||||||
|
'id' => 1,
|
||||||
|
'question' => 'What is your favorite pet\'s name?',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'answer' => [
|
||||||
|
'id' => 456,
|
||||||
|
],
|
||||||
|
'question' => [
|
||||||
|
'id' => 2,
|
||||||
|
'question' => 'What is your favorite movie?',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'answer' => [
|
||||||
|
'id' => 789,
|
||||||
|
],
|
||||||
|
'question' => [
|
||||||
|
'id' => 3,
|
||||||
|
'question' => 'What is your favorite author\'s last name?',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
]));
|
||||||
|
$result = $this->api->questions('mocked access token');
|
||||||
|
/** @var \Psr\Http\Message\RequestInterface $request */
|
||||||
|
$request = $this->history[0]['request'];
|
||||||
|
$this->assertSame('Bearer mocked access token', $request->getHeaderLine('Authorization'));
|
||||||
|
|
||||||
|
foreach ($result as $question) {
|
||||||
|
$this->assertInstanceOf(QuestionResponse::class, $question);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var QuestionResponse $firstQuestion */
|
||||||
|
$firstQuestion = $result[0];
|
||||||
|
$this->assertSame(123, $firstQuestion->getAnswerId());
|
||||||
|
$this->assertSame(1, $firstQuestion->getQuestionId());
|
||||||
|
$this->assertSame('What is your favorite pet\'s name?', $firstQuestion->getQuestion());
|
||||||
|
|
||||||
|
/** @var QuestionResponse $secondQuestion */
|
||||||
|
$secondQuestion = $result[1];
|
||||||
|
$this->assertSame(456, $secondQuestion->getAnswerId());
|
||||||
|
$this->assertSame(2, $secondQuestion->getQuestionId());
|
||||||
|
$this->assertSame('What is your favorite movie?', $secondQuestion->getQuestion());
|
||||||
|
|
||||||
|
/** @var QuestionResponse $thirdQuestion */
|
||||||
|
$thirdQuestion = $result[2];
|
||||||
|
$this->assertSame(789, $thirdQuestion->getAnswerId());
|
||||||
|
$this->assertSame(3, $thirdQuestion->getQuestionId());
|
||||||
|
$this->assertSame('What is your favorite author\'s last name?', $thirdQuestion->getQuestion());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAnswerOperationException() {
|
||||||
|
$this->mockHandler->append($this->createResponse(200, [
|
||||||
|
'error' => 'ForbiddenOperationException',
|
||||||
|
'errorMessage' => 'At least one answer was incorrect',
|
||||||
|
]));
|
||||||
|
$result = $this->api->answer('mocked access token', [
|
||||||
|
[
|
||||||
|
'id' => 123,
|
||||||
|
'answer' => 'foo',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'id' => 456,
|
||||||
|
'answer' => 'bar',
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
$this->assertFalse($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAnswer() {
|
||||||
|
$this->mockHandler->append(new Response(204));
|
||||||
|
$this->expectException(NoContentException::class);
|
||||||
|
$this->api->isSecurityQuestionsNeeded('mocked access token');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testStatistics() {
|
||||||
|
$this->mockHandler->append($this->createResponse(200, [
|
||||||
|
'total' => 145,
|
||||||
|
'last24h' => 13,
|
||||||
|
'saleVelocityPerSeconds' => 1.32,
|
||||||
|
]));
|
||||||
|
$result = $this->api->statistics([
|
||||||
|
'item_sold_minecraft',
|
||||||
|
'prepaid_card_redeemed_minecraft',
|
||||||
|
'item_sold_cobalt',
|
||||||
|
'item_sold_scrolls',
|
||||||
|
]);
|
||||||
|
/** @var \Psr\Http\Message\RequestInterface $request */
|
||||||
|
$request = $this->history[0]['request'];
|
||||||
|
$params = json_decode($request->getBody()->getContents(), true);
|
||||||
|
$this->assertSame([
|
||||||
|
'item_sold_minecraft',
|
||||||
|
'prepaid_card_redeemed_minecraft',
|
||||||
|
'item_sold_cobalt',
|
||||||
|
'item_sold_scrolls',
|
||||||
|
], $params['metricKeys']);
|
||||||
|
|
||||||
|
$this->assertSame(145, $result->getTotal());
|
||||||
|
$this->assertSame(13, $result->getLast24H());
|
||||||
|
$this->assertSame(1.32, $result->getSaleVelocityPerSeconds());
|
||||||
|
}
|
||||||
|
|
||||||
private function createResponse(int $statusCode, array $response): ResponseInterface {
|
private function createResponse(int $statusCode, array $response): ResponseInterface {
|
||||||
return new Response($statusCode, ['content-type' => 'json'], json_encode($response));
|
return new Response($statusCode, ['content-type' => 'json'], json_encode($response));
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user