Version 5.1 - All the GraphQL #32
39
src/API/APIClient.php
Normal file
39
src/API/APIClient.php
Normal file
@ -0,0 +1,39 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* Anime List Client
|
||||
*
|
||||
* An API client for Kitsu and MyAnimeList to manage anime and manga watch lists
|
||||
*
|
||||
* PHP version 7
|
||||
*
|
||||
* @package AnimeListClient
|
||||
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||
* @copyright 2015 - 2017 Timothy J. Warren
|
||||
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
||||
* @version 4.0
|
||||
* @link https://github.com/timw4mail/HummingBirdAnimeClient
|
||||
*/
|
||||
|
||||
namespace Aviat\AnimeClient\API;
|
||||
|
||||
use Amp;
|
||||
use Amp\Artax\{
|
||||
Client,
|
||||
Response,
|
||||
Request
|
||||
}
|
||||
|
||||
class APIClient {
|
||||
|
||||
/**
|
||||
* Get a syncronous response for a request
|
||||
*
|
||||
* @param Request $request
|
||||
* @return Response
|
||||
*/
|
||||
static public function syncResponse(Request $request): Response
|
||||
{
|
||||
$client = new Client();
|
||||
return wait($client->request($request));
|
||||
}
|
||||
}
|
@ -68,15 +68,17 @@ class APIRequestBuilder {
|
||||
protected $request;
|
||||
|
||||
/**
|
||||
* Set body as form fields
|
||||
* Set a basic authentication header
|
||||
*
|
||||
* @param array $fields Mapping of field names to values
|
||||
* @param string $username
|
||||
* @param string $password
|
||||
* @return self
|
||||
*/
|
||||
public function setFormFields(array $fields): self
|
||||
public function setBasicAuth(string $username, string $password): self
|
||||
{
|
||||
$body = $this->fixBody((new FormBody)->addFields($createData));
|
||||
$this->setBody($body);
|
||||
$authString = 'Basic ' . base64_encode($username . ':' . $password);
|
||||
$this->setHeader('Authorization', $authString);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@ -92,6 +94,20 @@ class APIRequestBuilder {
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set body as form fields
|
||||
*
|
||||
* @param array $fields Mapping of field names to values
|
||||
* @return self
|
||||
*/
|
||||
public function setFormFields(array $fields): self
|
||||
{
|
||||
$this->setHeader("Content-Type", "application/x-www-form-urlencoded");
|
||||
$body = $this->fixBody((new FormBody)->addFields($fields));
|
||||
$this->setBody($body);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a request header
|
||||
*
|
||||
@ -143,6 +159,16 @@ class APIRequestBuilder {
|
||||
public function getFullRequest()
|
||||
{
|
||||
$this->buildUri();
|
||||
|
||||
if ($this->logger)
|
||||
{
|
||||
$this->logger->debug('API Request', [
|
||||
'request_url' => $this->request->getUri(),
|
||||
'request_headers' => $this->request->getAllHeaders(),
|
||||
'request_body' => $this->request->getBody()
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->request;
|
||||
}
|
||||
|
||||
@ -166,6 +192,13 @@ class APIRequestBuilder {
|
||||
->setMethod($type)
|
||||
->setProtocol('1.1');
|
||||
|
||||
$this->path = $uri;
|
||||
|
||||
if ( ! empty($this->defaultHeaders))
|
||||
{
|
||||
$this->setHeaders($this->defaultHeaders);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@ -178,7 +211,7 @@ class APIRequestBuilder {
|
||||
{
|
||||
$url = (strpos($this->path, '//') !== FALSE)
|
||||
? $this->path
|
||||
: $this->baseUrl . $url;
|
||||
: $this->baseUrl . $this->path;
|
||||
|
||||
if ( ! empty($this->query))
|
||||
{
|
||||
|
@ -37,30 +37,35 @@ class ListItem extends AbstractListItem {
|
||||
|
||||
public function create(array $data): bool
|
||||
{
|
||||
$response = $this->getResponse('POST', 'library-entries', [
|
||||
'body' => Json::encode([
|
||||
'data' => [
|
||||
'type' => 'libraryEntries',
|
||||
'attributes' => [
|
||||
'status' => $data['status'],
|
||||
'progress' => $data['progress'] ?? 0
|
||||
$body = [
|
||||
'data' => [
|
||||
'type' => 'libraryEntries',
|
||||
'attributes' => [
|
||||
'status' => $data['status'],
|
||||
'progress' => $data['progress'] ?? 0
|
||||
],
|
||||
'relationships' => [
|
||||
'user' => [
|
||||
'data' => [
|
||||
'id' => $data['user_id'],
|
||||
'type' => 'users'
|
||||
]
|
||||
],
|
||||
'relationships' => [
|
||||
'user' => [
|
||||
'data' => [
|
||||
'id' => $data['user_id'],
|
||||
'type' => 'users'
|
||||
]
|
||||
],
|
||||
'media' => [
|
||||
'data' => [
|
||||
'id' => $data['id'],
|
||||
'type' => $data['type']
|
||||
]
|
||||
'media' => [
|
||||
'data' => [
|
||||
'id' => $data['id'],
|
||||
'type' => $data['type']
|
||||
]
|
||||
]
|
||||
]
|
||||
])
|
||||
]
|
||||
];
|
||||
|
||||
$request = $this->requestBuilder->newRequest('POST', 'library-entries')
|
||||
->setJsonBody($body)
|
||||
->getFullRequest();
|
||||
$response = $this->getResponse('POST', 'library-entries', [
|
||||
'body' => Json::encode($body)
|
||||
]);
|
||||
|
||||
return ($response->getStatusCode() === 201);
|
||||
@ -74,11 +79,19 @@ class ListItem extends AbstractListItem {
|
||||
|
||||
public function get(string $id): array
|
||||
{
|
||||
return $this->getRequest("library-entries/{$id}", [
|
||||
$request = $this->requestBuilder->newRequest('GET', "library-entries/{$id}")
|
||||
->setQuery([
|
||||
'include' => 'media,media.genres,media.mappings'
|
||||
])
|
||||
->getFullRequest();
|
||||
/*return $this->getRequest("library-entries/{$id}", [
|
||||
'query' => [
|
||||
'include' => 'media,media.genres,media.mappings'
|
||||
]
|
||||
]);
|
||||
]);*/
|
||||
|
||||
$response = \Amp\wait((new \Amp\Artax\Client)->request($request));
|
||||
return Json::decode($response->getBody());
|
||||
}
|
||||
|
||||
public function update(string $id, array $data): Response
|
||||
|
@ -116,7 +116,6 @@ class AnimeListTransformer extends AbstractTransformer {
|
||||
'mal_id' => $item['mal_id'] ?? null,
|
||||
'data' => [
|
||||
'status' => $item['watching_status'],
|
||||
'rating' => $item['user_rating'] / 2,
|
||||
'reconsuming' => $rewatching,
|
||||
'reconsumeCount' => $item['rewatched'],
|
||||
'notes' => $item['notes'],
|
||||
@ -125,9 +124,9 @@ class AnimeListTransformer extends AbstractTransformer {
|
||||
]
|
||||
];
|
||||
|
||||
if ((int) $untransformed['data']['rating'] === 0)
|
||||
if ( ! empty($item['user_rating']))
|
||||
{
|
||||
unset($untransformed['data']['rating']);
|
||||
$untransformed['data']['rating'] = $item['user_rating'] / 2;
|
||||
}
|
||||
|
||||
return $untransformed;
|
||||
|
@ -30,7 +30,7 @@ class ListItem {
|
||||
use ContainerAware;
|
||||
use MALTrait;
|
||||
|
||||
public function create(array $data): bool
|
||||
public function create(array $data)
|
||||
{
|
||||
$id = $data['id'];
|
||||
$createData = [
|
||||
@ -40,11 +40,20 @@ class ListItem {
|
||||
])
|
||||
];
|
||||
|
||||
// $config = $this->container->get('config');
|
||||
|
||||
/*$request = $this->requestBuilder->newRequest('POST', "animelist/add/{$id}.xml")
|
||||
->setFormFields($createData)
|
||||
->setBasicAuth($config->get(['mal','username']), $config->get(['mal', 'password']))
|
||||
->getFullRequest();*/
|
||||
|
||||
$response = $this->getResponse('POST', "animelist/add/{$id}.xml", [
|
||||
'body' => $this->fixBody((new FormBody)->addFields($createData))
|
||||
]);
|
||||
|
||||
return $response->getBody() === 'Created';
|
||||
|
||||
// return $request;
|
||||
}
|
||||
|
||||
public function delete(string $id): bool
|
||||
|
@ -89,47 +89,23 @@ trait MALTrait {
|
||||
*/
|
||||
public function setUpRequest(string $type, string $url, array $options = [])
|
||||
{
|
||||
$this->defaultHeaders['User-Agent'] = $_SERVER['HTTP_USER_AGENT'] ?? $this->defaultHeaders;
|
||||
|
||||
$type = strtoupper($type);
|
||||
$validTypes = ['GET', 'POST', 'DELETE'];
|
||||
|
||||
if ( ! in_array($type, $validTypes))
|
||||
{
|
||||
throw new InvalidArgumentException('Invalid http request type');
|
||||
}
|
||||
|
||||
$config = $this->container->get('config');
|
||||
$logger = $this->container->getLogger('mal-request');
|
||||
|
||||
$headers = array_merge($this->defaultHeaders, $options['headers'] ?? [], [
|
||||
'Authorization' => 'Basic ' .
|
||||
base64_encode($config->get(['mal','username']) . ':' .$config->get(['mal','password']))
|
||||
]);
|
||||
$request = $this->requestBuilder
|
||||
->newRequest($type, $url)
|
||||
->setBasicAuth($config->get(['mal','username']), $config->get(['mal','password']));
|
||||
|
||||
$query = $options['query'] ?? [];
|
||||
|
||||
$url = (strpos($url, '//') !== FALSE)
|
||||
? $url
|
||||
: $this->baseUrl . $url;
|
||||
|
||||
if ( ! empty($query))
|
||||
if (array_key_exists('query', $options))
|
||||
{
|
||||
$url .= '?' . http_build_query($query);
|
||||
$request->setQuery($options['query']);
|
||||
}
|
||||
|
||||
$request = (new Request)
|
||||
->setMethod($type)
|
||||
->setUri($url)
|
||||
->setProtocol('1.1')
|
||||
->setAllHeaders($headers);
|
||||
|
||||
if (array_key_exists('body', $options))
|
||||
{
|
||||
$request->setBody($options['body']);
|
||||
}
|
||||
|
||||
return $request;
|
||||
return $request->getFullRequest();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -151,15 +127,12 @@ trait MALTrait {
|
||||
$request = $this->setUpRequest($type, $url, $options);
|
||||
$response = \Amp\wait((new Client)->request($request));
|
||||
|
||||
$logger->debug('MAL api request', [
|
||||
'url' => $url,
|
||||
$logger->debug('MAL api response', [
|
||||
'status' => $response->getStatus(),
|
||||
'reason' => $response->getReason(),
|
||||
'body' => $response->getBody(),
|
||||
'headers' => $response->getAllHeaders(),
|
||||
'requestHeaders' => $request->getAllHeaders(),
|
||||
'requestBody' => $request->hasBody() ? $request->getBody() : 'No request body',
|
||||
'requestBodyBeforeEncode' => $request->hasBody() ? urldecode($request->getBody()) : '',
|
||||
'body' => $response->getBody()
|
||||
]);
|
||||
|
||||
return $response;
|
||||
|
@ -57,26 +57,32 @@ class AnimeListTransformer extends AbstractTransformer {
|
||||
*/
|
||||
public function untransform(array $item): array
|
||||
{
|
||||
$rewatching = (array_key_exists('reconsuming', $item['data']) && $item['data']['reconsuming']);
|
||||
|
||||
$map = [
|
||||
'id' => $item['mal_id'],
|
||||
'data' => [
|
||||
'episode' => $item['data']['progress'],
|
||||
// 'enable_rewatching' => $rewatching,
|
||||
// 'times_rewatched' => $item['data']['reconsumeCount'],
|
||||
// 'comments' => $item['data']['notes'],
|
||||
'episode' => $item['data']['progress']
|
||||
]
|
||||
];
|
||||
|
||||
if (array_key_exists('rating', $item['data']))
|
||||
switch(TRUE)
|
||||
{
|
||||
$map['data']['score'] = $item['data']['rating'] * 2;
|
||||
}
|
||||
case array_key_exists('notes', $item['data']):
|
||||
$map['data']['comments'] = $item['data']['notes'];
|
||||
|
||||
if (array_key_exists('status', $item['data']))
|
||||
{
|
||||
$map['data']['status'] = self::statusMap[$item['data']['status']];
|
||||
case array_key_exists('rating', $item['data']):
|
||||
$map['data']['score'] = $item['data']['rating'] * 2;
|
||||
|
||||
case array_key_exists('reconsuming', $item['data']):
|
||||
$map['data']['enable_rewatching'] = (bool) $item['data']['reconsuming'];
|
||||
|
||||
case array_key_exists('reconsumeCount', $item['data']):
|
||||
$map['data']['times_rewatched'] = $item['data']['reconsumeCount'];
|
||||
|
||||
case array_key_exists('status', $item['data']):
|
||||
$map['data']['status'] = self::statusMap[$item['data']['status']];
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return $map;
|
||||
|
136
tests/API/APIRequestBuilderTest.php
Normal file
136
tests/API/APIRequestBuilderTest.php
Normal file
@ -0,0 +1,136 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* Anime List Client
|
||||
*
|
||||
* An API client for Kitsu and MyAnimeList to manage anime and manga watch lists
|
||||
*
|
||||
* PHP version 7
|
||||
*
|
||||
* @package AnimeListClient
|
||||
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||
* @copyright 2015 - 2017 Timothy J. Warren
|
||||
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
||||
* @version 4.0
|
||||
* @link https://github.com/timw4mail/HummingBirdAnimeClient
|
||||
*/
|
||||
|
||||
namespace Aviat\AnimeClient\Tests\API;
|
||||
|
||||
use Amp;
|
||||
use Amp\Artax\Client;
|
||||
use Aviat\AnimeClient\API\APIRequestBuilder;
|
||||
use Aviat\Ion\Json;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\Log\NullLogger;
|
||||
|
||||
class APIRequestBuilderTest extends TestCase {
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->builder = new class extends APIRequestBuilder {
|
||||
protected $baseUrl = 'https://httpbin.org/';
|
||||
|
||||
protected $defaultHeaders = ['User-Agent' => "Tim's Anime Client Testsuite / 4.0"];
|
||||
};
|
||||
|
||||
$this->builder->setLogger(new NullLogger);
|
||||
}
|
||||
|
||||
public function testGzipRequest()
|
||||
{
|
||||
$request = $this->builder->newRequest('GET', 'gzip')
|
||||
->getFullRequest();
|
||||
$response = Amp\wait((new Client)->request($request));
|
||||
$body = Json::decode($response->getBody());
|
||||
$this->assertEquals(1, $body['gzipped']);
|
||||
}
|
||||
|
||||
public function testInvalidRequestMethod()
|
||||
{
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
$this->builder->newRequest('FOO', 'gzip')
|
||||
->getFullRequest();
|
||||
}
|
||||
|
||||
public function testRequestWithBasicAuth()
|
||||
{
|
||||
$request = $this->builder->newRequest('GET', 'headers')
|
||||
->setBasicAuth('username', 'password')
|
||||
->getFullRequest();
|
||||
|
||||
$response = Amp\wait((new Client)->request($request));
|
||||
$body = Json::decode($response->getBody());
|
||||
|
||||
$this->assertEquals('Basic dXNlcm5hbWU6cGFzc3dvcmQ=', $body['headers']['Authorization']);
|
||||
}
|
||||
|
||||
public function testRequestWithQueryString()
|
||||
{
|
||||
$query = [
|
||||
'foo' => 'bar',
|
||||
'bar' => [
|
||||
'foo' => 'bar'
|
||||
],
|
||||
'baz' => [
|
||||
'bar' => 'foo'
|
||||
]
|
||||
];
|
||||
|
||||
$expected = [
|
||||
'foo' => 'bar',
|
||||
'bar[foo]' => 'bar',
|
||||
'baz[bar]' => 'foo'
|
||||
];
|
||||
|
||||
$request = $this->builder->newRequest('GET', 'get')
|
||||
->setQuery($query)
|
||||
->getFullRequest();
|
||||
|
||||
$response = Amp\wait((new Client)->request($request));
|
||||
$body = Json::decode($response->getBody());
|
||||
|
||||
$this->assertEquals($expected, $body['args']);
|
||||
}
|
||||
|
||||
public function testFormValueRequest()
|
||||
{
|
||||
$formValues = [
|
||||
'foo' => 'bar',
|
||||
'bar' => 'foo'
|
||||
];
|
||||
|
||||
$request = $this->builder->newRequest('POST', 'post')
|
||||
->setFormFields($formValues)
|
||||
->getFullRequest();
|
||||
|
||||
$response = Amp\wait((new Client)->request($request));
|
||||
$body = Json::decode($response->getBody());
|
||||
|
||||
$this->assertEquals($formValues, $body['form']);
|
||||
}
|
||||
|
||||
public function testFullUrlRequest()
|
||||
{
|
||||
$data = [
|
||||
'foo' => [
|
||||
'bar' => 1,
|
||||
'baz' => [2, 3, 4],
|
||||
'bar' => [
|
||||
'a' => 1,
|
||||
'b' => 2
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
$request = $this->builder->newRequest('PUT', 'https://httpbin.org/put')
|
||||
->setHeader('Content-Type', 'application/json')
|
||||
->setBody(Json::encode($data))
|
||||
->getFullRequest();
|
||||
|
||||
$response = Amp\wait((new Client)->request($request));
|
||||
$body = Json::decode($response->getBody());
|
||||
|
||||
$this->assertEquals($data, $body['json']);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user