From 0e9c257ab77738328c37ddaef3b5b3d79b2b790b Mon Sep 17 00:00:00 2001 From: Timothy J Warren Date: Wed, 8 Feb 2017 15:48:20 -0500 Subject: [PATCH] Another ugly progress commit - Eradicated Guzzle from main codebase - All API requests now use Artax - Refactor code to use function and constant imports - And more! --- app/bootstrap.php | 34 ++-- app/config/routes.php | 17 +- composer.json | 6 +- index.php | 15 +- src/API/APIRequestBuilder.php | 45 ++++-- ...Client.php => FailedResponseException.php} | 20 +-- src/API/GuzzleTrait.php | 41 ----- src/API/Kitsu.php | 30 ++-- src/API/Kitsu/Auth.php | 13 +- src/API/Kitsu/KitsuRequestBuilder.php | 18 +-- src/API/Kitsu/KitsuTrait.php | 139 ++++++----------- src/API/Kitsu/ListItem.php | 66 +++++--- src/API/Kitsu/Model.php | 21 ++- src/API/ListItemInterface.php | 8 +- src/API/MAL/ListItem.php | 47 ++++-- src/API/MAL/MALTrait.php | 2 +- src/API/MAL/Model.php | 7 +- src/AnimeClient.php | 63 ++++---- src/Command/BaseCommand.php | 38 ++--- src/Command/SyncKitsuWithMal.php | 147 ++++++++++++------ src/Controller.php | 4 +- src/Dispatcher.php | 42 ++--- src/Model/API.php | 8 +- src/Model/Anime.php | 37 ++++- tests/API/APIRequestBuilderTest.php | 47 +++--- tests/AnimeClient_TestCase.php | 28 +--- 26 files changed, 468 insertions(+), 475 deletions(-) rename src/API/{APIClient.php => FailedResponseException.php} (61%) delete mode 100644 src/API/GuzzleTrait.php diff --git a/app/bootstrap.php b/app/bootstrap.php index 01e06953..599a275b 100644 --- a/app/bootstrap.php +++ b/app/bootstrap.php @@ -19,17 +19,9 @@ namespace Aviat\AnimeClient; use Aura\Html\HelperLocatorFactory; use Aura\Router\RouterContainer; use Aura\Session\SessionFactory; -use Aviat\AnimeClient\API\Kitsu\{ - Auth as KitsuAuth, - ListItem as KitsuListItem, - KitsuRequestBuilder, - Model as KitsuModel -}; -use Aviat\AnimeClient\API\MAL\{ - ListItem as MALListItem, - MALRequestBuilder, - Model as MALModel -}; +use Aviat\AnimeClient\API\{Kitsu, MAL}; +use Aviat\AnimeClient\API\Kitsu\KitsuRequestBuilder; +use Aviat\AnimeClient\API\MAL\MALRequestBuilder; use Aviat\AnimeClient\Model; use Aviat\Banker\Pool; use Aviat\Ion\Config; @@ -119,15 +111,15 @@ return function(array $config_array = []) { $container->set('kitsu-model', function($container) { $requestBuilder = new KitsuRequestBuilder(); $requestBuilder->setLogger($container->getLogger('kitsu-request')); - - $listItem = new KitsuListItem(); + + $listItem = new Kitsu\ListItem(); $listItem->setContainer($container); $listItem->setRequestBuilder($requestBuilder); - - $model = new KitsuModel($listItem); + + $model = new Kitsu\Model($listItem); $model->setContainer($container); $model->setRequestBuilder($requestBuilder); - + $cache = $container->get('cache'); $model->setCache($cache); return $model; @@ -135,12 +127,12 @@ return function(array $config_array = []) { $container->set('mal-model', function($container) { $requestBuilder = new MALRequestBuilder(); $requestBuilder->setLogger($container->getLogger('mal-request')); - - $listItem = new MALListItem(); + + $listItem = new MAL\ListItem(); $listItem->setContainer($container); $listItem->setRequestBuilder($requestBuilder); - - $model = new MALModel($listItem); + + $model = new MAL\Model($listItem); $model->setContainer($container); $model->setRequestBuilder($requestBuilder); return $model; @@ -161,7 +153,7 @@ return function(array $config_array = []) { // Miscellaneous Classes $container->set('auth', function($container) { - return new KitsuAuth($container); + return new Kitsu\Auth($container); }); $container->set('url-generator', function($container) { return new UrlGenerator($container); diff --git a/app/config/routes.php b/app/config/routes.php index df9329ad..4ead95bf 100644 --- a/app/config/routes.php +++ b/app/config/routes.php @@ -14,6 +14,11 @@ * @link https://github.com/timw4mail/HummingBirdAnimeClient */ +use const Aviat\AnimeClient\{ + DEFAULT_CONTROLLER_METHOD, + DEFAULT_CONTROLLER_NAMESPACE +}; + use Aviat\AnimeClient\AnimeClient; return [ @@ -148,25 +153,25 @@ return [ 'cache_purge' => [ 'path' => '/cache_purge', 'action' => 'clearCache', - 'controller' => AnimeClient::DEFAULT_CONTROLLER_NAMESPACE, + 'controller' => DEFAULT_CONTROLLER_NAMESPACE, 'verb' => 'get', ], 'login' => [ 'path' => '/login', 'action' => 'login', - 'controller' => AnimeClient::DEFAULT_CONTROLLER_NAMESPACE, + 'controller' => DEFAULT_CONTROLLER_NAMESPACE, 'verb' => 'get', ], 'login.post' => [ 'path' => '/login', 'action' => 'loginAction', - 'controller' => AnimeClient::DEFAULT_CONTROLLER_NAMESPACE, + 'controller' => DEFAULT_CONTROLLER_NAMESPACE, 'verb' => 'post', ], 'logout' => [ 'path' => '/logout', 'action' => 'logout', - 'controller' => AnimeClient::DEFAULT_CONTROLLER_NAMESPACE, + 'controller' => DEFAULT_CONTROLLER_NAMESPACE, ], 'update' => [ 'path' => '/{controller}/update', @@ -194,7 +199,7 @@ return [ ], 'list' => [ 'path' => '/{controller}/{type}{/view}', - 'action' => AnimeClient::DEFAULT_CONTROLLER_METHOD, + 'action' => DEFAULT_CONTROLLER_METHOD, 'tokens' => [ 'type' => '[a-z_]+', 'view' => '[a-z_]+', @@ -202,7 +207,7 @@ return [ ], 'index_redirect' => [ 'path' => '/', - 'controller' => AnimeClient::DEFAULT_CONTROLLER_NAMESPACE, + 'controller' => DEFAULT_CONTROLLER_NAMESPACE, 'action' => 'redirectToDefaultRoute', ], ], diff --git a/composer.json b/composer.json index c21f88ae..4b91d3b0 100644 --- a/composer.json +++ b/composer.json @@ -3,6 +3,9 @@ "description": "A self-hosted anime/manga client for Kitsu.", "license":"MIT", "autoload": { + "files": [ + "src/AnimeClient.php" + ], "psr-4": { "Aviat\\AnimeClient\\": "src/" } @@ -20,7 +23,6 @@ "aviat/banker": "^1.0.0", "aviat/ion": "1.0.*", "filp/whoops": "^2.1.5", - "guzzlehttp/guzzle": "^6.0", "monolog/monolog": "^1.0", "psr/http-message": "~1.0", "psr/log": "~1.0", @@ -46,4 +48,4 @@ "build:css": "cd public && npm run build && cd ..", "watch:css": "cd public && npm run watch" } -} +} \ No newline at end of file diff --git a/index.php b/index.php index fff3616a..0d8a5615 100644 --- a/index.php +++ b/index.php @@ -15,6 +15,8 @@ */ namespace Aviat\AnimeClient; +use function Aviat\AnimeClient\loadToml; + use Aviat\AnimeClient\AnimeClient; use Whoops\Handler\PrettyPageHandler; use Whoops\Run; @@ -42,7 +44,7 @@ $APP_DIR = _dir(__DIR__, 'app'); $CONF_DIR = _dir($APP_DIR, 'config'); // Load composer autoloader -require _dir(__DIR__, '/vendor/autoload.php'); +require _dir(__DIR__, 'vendor/autoload.php'); // ------------------------------------------------------------------------- // Setup error handling @@ -54,10 +56,7 @@ $defaultHandler = new PrettyPageHandler(); $whoops->pushHandler($defaultHandler); // Register as the error handler -if (array_key_exists('whoops', $_GET)) -{ - $whoops->register(); -} +$whoops->register(); // ----------------------------------------------------------------------------- // Dependency Injection setup @@ -65,7 +64,7 @@ if (array_key_exists('whoops', $_GET)) require _dir($CONF_DIR, 'base_config.php'); // $base_config $di = require _dir($APP_DIR, 'bootstrap.php'); -$config = AnimeClient::loadToml($CONF_DIR); +$config = loadToml($CONF_DIR); $config_array = array_merge($base_config, $config); $container = $di($config_array); @@ -77,6 +76,4 @@ unset($CONF_DIR); // ----------------------------------------------------------------------------- // Dispatch to the current route // ----------------------------------------------------------------------------- -$container->get('dispatcher')->__invoke(); - -// End of index.php \ No newline at end of file +$container->get('dispatcher')->__invoke(); \ No newline at end of file diff --git a/src/API/APIRequestBuilder.php b/src/API/APIRequestBuilder.php index 0ea1f77a..d3d2a037 100644 --- a/src/API/APIRequestBuilder.php +++ b/src/API/APIRequestBuilder.php @@ -16,12 +16,14 @@ namespace Aviat\AnimeClient\API; +use Amp; use Amp\Artax\{ Client, FormBody, Request }; use Aviat\Ion\Di\ContainerAware; +use Aviat\Ion\Json; use InvalidArgumentException; use Psr\Log\LoggerAwareTrait; @@ -67,6 +69,21 @@ class APIRequestBuilder { */ protected $request; + /** + * Set an authorization header + * + * @param string $type The type of authorization, eg, basic, bearer, etc. + * @param string $value The authorization value + * @return self + */ + public function setAuth(string $type, string $value): self + { + $authString = ucfirst($type) . ' ' . $value; + $this->setHeader('Authorization', $authString); + + return $this; + } + /** * Set a basic authentication header * @@ -76,9 +93,7 @@ class APIRequestBuilder { */ public function setBasicAuth(string $username, string $password): self { - $authString = 'Basic ' . base64_encode($username . ':' . $password); - $this->setHeader('Authorization', $authString); - + $this->setAuth('basic', base64_encode($username . ':' . $password)); return $this; } @@ -139,6 +154,18 @@ class APIRequestBuilder { return $this; } + /** + * Set the request body + * + * @param array|FormBody|string $body + * @return self + */ + public function setJsonBody(array $body): self + { + $requestBody = Json::encode($body); + return $this->setBody($requestBody); + } + /** * Append a query string in array format * @@ -159,7 +186,7 @@ class APIRequestBuilder { public function getFullRequest() { $this->buildUri(); - + if ($this->logger) { $this->logger->debug('API Request', [ @@ -168,7 +195,7 @@ class APIRequestBuilder { 'request_body' => $this->request->getBody() ]); } - + return $this->request; } @@ -191,13 +218,13 @@ class APIRequestBuilder { $this->request ->setMethod($type) ->setProtocol('1.1'); - + $this->path = $uri; - + if ( ! empty($this->defaultHeaders)) { $this->setHeaders($this->defaultHeaders); - } + } return $this; } @@ -232,7 +259,7 @@ class APIRequestBuilder { */ private function fixBody(FormBody $formBody): string { - $rawBody = \Amp\wait($formBody->getBody()); + $rawBody = Amp\wait($formBody->getBody()); return html_entity_decode($rawBody, \ENT_HTML5, 'UTF-8'); } diff --git a/src/API/APIClient.php b/src/API/FailedResponseException.php similarity index 61% rename from src/API/APIClient.php rename to src/API/FailedResponseException.php index 05d3b8a9..648bb83d 100644 --- a/src/API/APIClient.php +++ b/src/API/FailedResponseException.php @@ -16,24 +16,8 @@ namespace Aviat\AnimeClient\API; -use Amp; -use Amp\Artax\{ - Client, - Response, - Request -} +use UnexpectedValueException; -class APIClient { +class FailedResponseException extends UnexpectedValueException { - /** - * 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)); - } } \ No newline at end of file diff --git a/src/API/GuzzleTrait.php b/src/API/GuzzleTrait.php deleted file mode 100644 index 6af78cde..00000000 --- a/src/API/GuzzleTrait.php +++ /dev/null @@ -1,41 +0,0 @@ - - * @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; - -/** - * Base trait for api interaction - */ -trait GuzzleTrait { - /** - * The Guzzle http client object - * @var object - */ - protected $client; - - /** - * Cookie jar object for api requests - * @var object - */ - protected $cookieJar; - - /** - * Set up the class properties - * - * @return void - */ - abstract protected function init(); -} \ No newline at end of file diff --git a/src/API/Kitsu.php b/src/API/Kitsu.php index 34907c90..117f7bdd 100644 --- a/src/API/Kitsu.php +++ b/src/API/Kitsu.php @@ -23,6 +23,10 @@ use Aviat\AnimeClient\API\Kitsu\Enum\{ }; use DateTimeImmutable; +const AUTH_URL = 'https://kitsu.io/api/oauth/token'; +const AUTH_USER_ID_KEY = 'kitsu-auth-userid'; +const AUTH_TOKEN_CACHE_KEY = 'kitsu-auth-token'; + /** * Data massaging helpers for the Kitsu API */ @@ -91,7 +95,7 @@ class Kitsu { return AnimeAiringStatus::NOT_YET_AIRED; } } - + /** * Get the name and logo for the streaming service of the current link * @@ -108,22 +112,22 @@ class Kitsu { 'link' => true, 'logo' => '' ]; - + case 'www.funimation.com': return [ 'name' => 'Funimation', 'link' => true, 'logo' => '' ]; - + case 'www.hulu.com': return [ 'name' => 'Hulu', 'link' => true, 'logo' => '' ]; - - // Default to Netflix, because the API links are broken, + + // Default to Netflix, because the API links are broken, // and there's no other real identifier for Netflix default: return [ @@ -133,7 +137,7 @@ class Kitsu { ]; } } - + /** * Reorganize streaming links * @@ -146,13 +150,13 @@ class Kitsu { { return []; } - + $links = []; - + foreach ($included['streamingLinks'] as $streamingLink) { $host = parse_url($streamingLink['url'], \PHP_URL_HOST); - + $links[] = [ 'meta' => static::getServiceMetaData($host), 'link' => $streamingLink['url'], @@ -160,10 +164,10 @@ class Kitsu { 'dubs' => $streamingLink['dubs'] ]; } - + return $links; } - + /** * Reorganize streaming links for the current list item * @@ -192,10 +196,10 @@ class Kitsu { ]; } } - + return $links; } - + return []; } diff --git a/src/API/Kitsu/Auth.php b/src/API/Kitsu/Auth.php index 70691eb1..7e01e592 100644 --- a/src/API/Kitsu/Auth.php +++ b/src/API/Kitsu/Auth.php @@ -16,6 +16,9 @@ namespace Aviat\AnimeClient\API\Kitsu; +use const Aviat\AnimeClient\SESSION_SEGMENT; +use const Aviat\AnimeClient\API\Kitsu\AUTH_TOKEN_CACHE_KEY; + use Aviat\AnimeClient\AnimeClient; use Aviat\AnimeClient\API\{ CacheTrait, @@ -55,7 +58,7 @@ class Auth { $this->setContainer($container); $this->setCache($container->get('cache')); $this->segment = $container->get('session') - ->getSegment(AnimeClient::SESSION_SEGMENT); + ->getSegment(SESSION_SEGMENT); $this->model = $container->get('kitsu-model'); } @@ -70,7 +73,7 @@ class Auth { { $config = $this->container->get('config'); $username = $config->get(['kitsu_username']); - + try { $auth = $this->model->authenticate($username, $password); @@ -79,15 +82,15 @@ class Auth { { return FALSE; } - + if (FALSE !== $auth) { // Set the token in the cache for command line operations - $cacheItem = $this->cache->getItem(K::AUTH_TOKEN_CACHE_KEY); + $cacheItem = $this->cache->getItem(AUTH_TOKEN_CACHE_KEY); $cacheItem->set($auth['access_token']); $cacheItem->save(); - + $this->segment->set('auth_token', $auth['access_token']); return TRUE; } diff --git a/src/API/Kitsu/KitsuRequestBuilder.php b/src/API/Kitsu/KitsuRequestBuilder.php index c155794a..7a3cab02 100644 --- a/src/API/Kitsu/KitsuRequestBuilder.php +++ b/src/API/Kitsu/KitsuRequestBuilder.php @@ -20,8 +20,8 @@ use Aviat\AnimeClient\API\APIRequestBuilder; use Aviat\AnimeClient\API\Kitsu as K; use Aviat\Ion\Json; -class KitsuRequestBuilder extends APIRequestBuilder { - +class KitsuRequestBuilder extends APIRequestBuilder { + /** * The base url for api requests * @var string $base_url @@ -40,16 +40,4 @@ class KitsuRequestBuilder extends APIRequestBuilder { 'client_id' => 'dd031b32d2f56c990b1425efe6c42ad847e7fe3ab46bf1299f05ecd856bdb7dd', 'client_secret' => '54d7307928f63414defd96399fc31ba847961ceaecef3a5fd93144e960c0e151', ]; - - /** - * Set the request body - * - * @param array|FormBody|string $body - * @return self - */ - public function setJsonBody(array $body): self - { - $requestBody = Json::encode($body); - return $this->setBody($requestBody); - } -} +} \ No newline at end of file diff --git a/src/API/Kitsu/KitsuTrait.php b/src/API/Kitsu/KitsuTrait.php index 612ee747..75b9e554 100644 --- a/src/API/Kitsu/KitsuTrait.php +++ b/src/API/Kitsu/KitsuTrait.php @@ -16,13 +16,14 @@ namespace Aviat\AnimeClient\API\Kitsu; +use const Aviat\AnimeClient\SESSION_SEGMENT; + +use function Amp\wait; + +use Amp\Artax\Client; use Aviat\AnimeClient\AnimeClient; -use Aviat\AnimeClient\API\GuzzleTrait; use Aviat\AnimeClient\API\Kitsu as K; use Aviat\Ion\Json; -use GuzzleHttp\Client; -use GuzzleHttp\Cookie\CookieJar; -use GuzzleHttp\Psr7\Response; use InvalidArgumentException; use RuntimeException; @@ -34,37 +35,6 @@ trait KitsuTrait { */ protected $requestBuilder; - /** - * The Guzzle http client object - * @var object - */ - protected $client; - - /** - * Cookie jar object for api requests - * @var object - */ - protected $cookieJar; - - /** - * The base url for api requests - * @var string $base_url - */ - protected $baseUrl = "https://kitsu.io/api/edge/"; - - /** - * HTTP headers to send with every request - * - * @var array - */ - protected $defaultHeaders = [ - 'User-Agent' => "Tim's Anime Client/4.0", - 'Accept-Encoding' => 'application/vnd.api+json', - 'Content-Type' => 'application/vnd.api+json', - 'client_id' => 'dd031b32d2f56c990b1425efe6c42ad847e7fe3ab46bf1299f05ecd856bdb7dd', - 'client_secret' => '54d7307928f63414defd96399fc31ba847961ceaecef3a5fd93144e960c0e151', - ]; - /** * Set the request builder object * @@ -78,30 +48,45 @@ trait KitsuTrait { } /** - * Set up the class properties + * Create a request object * - * @return void + * @param string $type + * @param string $url + * @param array $options + * @return \Amp\Artax\Response */ - protected function init() + public function setUpRequest(string $type, string $url, array $options = []) { - $defaults = [ - 'cookies' => $this->cookieJar, - 'headers' => $this->defaultHeaders, - 'timeout' => 25, - 'connect_timeout' => 25 - ]; + $config = $this->container->get('config'); - $this->cookieJar = new CookieJar(); - $this->client = new Client([ - 'base_uri' => $this->baseUrl, - 'cookies' => TRUE, - 'http_errors' => TRUE, - 'defaults' => $defaults - ]); + $request = $this->requestBuilder->newRequest($type, $url); + + $sessionSegment = $this->getContainer() + ->get('session') + ->getSegment(SESSION_SEGMENT); + + if ($sessionSegment->get('auth_token') !== null && $url !== K::AUTH_URL) + { + $token = $sessionSegment->get('auth_token'); + $request = $request->setAuth('bearer', $token); + // $defaultOptions['headers']['Authorization'] = "bearer {$token}"; + } + + if (array_key_exists('query', $options)) + { + $request->setQuery($options['query']); + } + + if (array_key_exists('body', $options)) + { + $request->setJsonBody($options['body']); + } + + return $request->getFullRequest(); } /** - * Make a request via Guzzle + * Make a request * * @param string $type * @param string $url @@ -110,48 +95,24 @@ trait KitsuTrait { */ private function getResponse(string $type, string $url, array $options = []) { - $logger = null; - $validTypes = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS']; - - if ( ! in_array($type, $validTypes)) - { - throw new InvalidArgumentException('Invalid http request type'); - } - - $defaultOptions = [ - 'headers' => $this->defaultHeaders - ]; - + $request = $this->setUpRequest($type, $url, $options); $logger = $this->container->getLogger('kitsu-request'); - $sessionSegment = $this->getContainer() - ->get('session') - ->getSegment(AnimeClient::SESSION_SEGMENT); - if ($sessionSegment->get('auth_token') !== null && $url !== K::AUTH_URL) - { - $token = $sessionSegment->get('auth_token'); - $defaultOptions['headers']['Authorization'] = "bearer {$token}"; - } + $response = wait((new Client)->request($request)); - $options = array_merge($defaultOptions, $options); - - $response = $this->client->request($type, $url, $options); - - $logger->debug('Kitsu API request', [ - 'requestParams' => [ - 'type' => $type, - 'url' => $url, - ], - 'responseValues' => [ - 'status' => $response->getStatusCode() - ] - ]); + /* $logger->debug('Kitsu api response', [ + 'status' => $response->getStatus(), + 'reason' => $response->getReason(), + 'body' => $response->getBody(), + 'headers' => $response->getAllHeaders(), + 'requestHeaders' => $request->getAllHeaders(), + ]); */ return $response; } /** - * Make a request via Guzzle + * Make a request * * @param string $type * @param string $url @@ -168,7 +129,7 @@ trait KitsuTrait { $response = $this->getResponse($type, $url, $options); - if ((int) $response->getStatusCode() > 299 || (int) $response->getStatusCode() < 200) + if ((int) $response->getStatus() > 299 || (int) $response->getStatus() < 200) { if ($logger) { @@ -218,7 +179,7 @@ trait KitsuTrait { $response = $this->getResponse('POST', ...$args); $validResponseCodes = [200, 201]; - if ( ! in_array((int) $response->getStatusCode(), $validResponseCodes)) + if ( ! in_array((int) $response->getStatus(), $validResponseCodes)) { if ($logger) { @@ -238,6 +199,6 @@ trait KitsuTrait { protected function deleteRequest(...$args): bool { $response = $this->getResponse('DELETE', ...$args); - return ((int) $response->getStatusCode() === 204); + return ((int) $response->getStatus() === 204); } } \ No newline at end of file diff --git a/src/API/Kitsu/ListItem.php b/src/API/Kitsu/ListItem.php index 51121471..c57d6d3e 100644 --- a/src/API/Kitsu/ListItem.php +++ b/src/API/Kitsu/ListItem.php @@ -16,11 +16,12 @@ namespace Aviat\AnimeClient\API\Kitsu; +use const Aviat\AnimeClient\SESSION_SEGMENT; + +use Amp\Artax\Request; use Aviat\AnimeClient\API\AbstractListItem; use Aviat\Ion\Di\ContainerAware; use Aviat\Ion\Json; -use GuzzleHttp\Exception\ClientException; -use GuzzleHttp\Psr7\Response; use RuntimeException; /** @@ -30,12 +31,22 @@ class ListItem extends AbstractListItem { use ContainerAware; use KitsuTrait; - public function __construct() + private function getAuthHeader() { - $this->init(); + $sessionSegment = $this->getContainer() + ->get('session') + ->getSegment(SESSION_SEGMENT); + + if ($sessionSegment->get('auth_token') !== null) + { + $token = $sessionSegment->get('auth_token'); + return "bearer {$token}"; + } + + return FALSE; } - public function create(array $data): bool + public function create(array $data): Request { $body = [ 'data' => [ @@ -60,21 +71,35 @@ class ListItem extends AbstractListItem { ] ] ]; - - $request = $this->requestBuilder->newRequest('POST', 'library-entries') - ->setJsonBody($body) - ->getFullRequest(); - $response = $this->getResponse('POST', 'library-entries', [ - 'body' => Json::encode($body) - ]); - return ($response->getStatusCode() === 201); + $authHeader = $this->getAuthHeader(); + + $request = $this->requestBuilder->newRequest('POST', 'library-entries'); + + if ($authHeader !== FALSE) + { + $request = $request->setHeader('Authorization', $authHeader); + } + + return $request->setJsonBody($body) + ->getFullRequest(); + + // return ($response->getStatus() === 201); } - public function delete(string $id): bool + public function delete(string $id): Request { - $response = $this->getResponse('DELETE', "library-entries/{$id}"); - return ($response->getStatusCode() === 204); + $authHeader = $this->getAuthHeader(); + $request = $this->requestBuilder->newRequest('DELETE', "library-entries/{$id}"); + + if ($authHeader !== FALSE) + { + $request = $request->setHeader('Authorization', $authHeader); + } + + return $request->getFullRequest(); + + // return ($response->getStatus() === 204); } public function get(string $id): array @@ -84,17 +109,12 @@ class ListItem extends AbstractListItem { '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 + public function update(string $id, array $data): Request { $requestData = [ 'data' => [ diff --git a/src/API/Kitsu/Model.php b/src/API/Kitsu/Model.php index 7077f809..1a401b1c 100644 --- a/src/API/Kitsu/Model.php +++ b/src/API/Kitsu/Model.php @@ -16,6 +16,7 @@ namespace Aviat\AnimeClient\API\Kitsu; +use Amp\Artax\Request; use Aviat\AnimeClient\API\CacheTrait; use Aviat\AnimeClient\API\JsonAPI; use Aviat\AnimeClient\API\Kitsu as K; @@ -27,7 +28,6 @@ use Aviat\AnimeClient\API\Kitsu\Transformer\{ }; use Aviat\Ion\Di\ContainerAware; use Aviat\Ion\Json; -use GuzzleHttp\Exception\ClientException; /** * Kitsu API Model @@ -72,9 +72,6 @@ class Model { */ public function __construct(ListItem $listItem) { - // Set up Guzzle trait - $this->init(); - $this->animeTransformer = new AnimeTransformer(); $this->animeListTransformer = new AnimeListTransformer(); $this->listItem = $listItem; @@ -355,9 +352,9 @@ class Model { * Create a list item * * @param array $data - * @return bool + * @return Request */ - public function createListItem(array $data): bool + public function createListItem(array $data): Request { $data['user_id'] = $this->getUserIdByUsername($this->getUsername()); return $this->listItem->create($data); @@ -397,22 +394,22 @@ class Model { * Modify a list item * * @param array $data - * @return array + * @return Request */ - public function updateListItem(array $data) + public function updateListItem(array $data): Request { try { $response = $this->listItem->update($data['id'], $data['data']); return [ - 'statusCode' => $response->getStatusCode(), + 'statusCode' => $response->getStatus(), 'body' => $response->getBody(), ]; } catch(ClientException $e) { return [ - 'statusCode' => $e->getResponse()->getStatusCode(), + 'statusCode' => $e->getResponse()->getStatus(), 'body' => Json::decode((string)$e->getResponse()->getBody()) ]; } @@ -422,9 +419,9 @@ class Model { * Remove a list item * * @param string $id - The id of the list item to remove - * @return bool + * @return Request */ - public function deleteListItem(string $id): bool + public function deleteListItem(string $id): Request { return $this->listItem->delete($id); } diff --git a/src/API/ListItemInterface.php b/src/API/ListItemInterface.php index b0001428..17f0cad8 100644 --- a/src/API/ListItemInterface.php +++ b/src/API/ListItemInterface.php @@ -16,7 +16,7 @@ namespace Aviat\AnimeClient\API; -use GuzzleHttp\Psr7\Response; +use Amp\Artax\Request; /** * Common interface for anime and manga list item CRUD @@ -29,7 +29,7 @@ interface ListItemInterface { * @param array $data - * @return bool */ - public function create(array $data): bool; + public function create(array $data): Request; /** * Retrieve a list item @@ -46,7 +46,7 @@ interface ListItemInterface { * @param array $data - The data with which to update the list item * @return Response */ - public function update(string $id, array $data): Response; + public function update(string $id, array $data): Request; /** * Delete a list item @@ -54,5 +54,5 @@ interface ListItemInterface { * @param string $id - The id of the list item to delete * @return bool */ - public function delete(string $id): bool; + public function delete(string $id): Request; } \ No newline at end of file diff --git a/src/API/MAL/ListItem.php b/src/API/MAL/ListItem.php index 8c87b166..d1d16dfd 100644 --- a/src/API/MAL/ListItem.php +++ b/src/API/MAL/ListItem.php @@ -16,7 +16,7 @@ namespace Aviat\AnimeClient\API\MAL; -use Amp\Artax\FormBody; +use Amp\Artax\Request; use Aviat\AnimeClient\API\{ AbstractListItem, XML @@ -30,7 +30,7 @@ class ListItem { use ContainerAware; use MALTrait; - public function create(array $data) + public function create(array $data): Request { $id = $data['id']; $createData = [ @@ -40,29 +40,36 @@ class ListItem { ]) ]; - // $config = $this->container->get('config'); + $config = $this->container->get('config'); - /*$request = $this->requestBuilder->newRequest('POST', "animelist/add/{$id}.xml") + return $this->requestBuilder->newRequest('POST', "animelist/add/{$id}.xml") ->setFormFields($createData) ->setBasicAuth($config->get(['mal','username']), $config->get(['mal', 'password'])) - ->getFullRequest();*/ + ->getFullRequest(); - $response = $this->getResponse('POST', "animelist/add/{$id}.xml", [ + /* $response = $this->getResponse('POST', "animelist/add/{$id}.xml", [ 'body' => $this->fixBody((new FormBody)->addFields($createData)) ]); - return $response->getBody() === 'Created'; - - // return $request; + return $response->getBody() === 'Created'; */ } - public function delete(string $id): bool + public function delete(string $id): Request { - $response = $this->getResponse('DELETE', "animelist/delete/{$id}.xml", [ + $config = $this->container->get('config'); + + return $this->requestBuilder->newRequest('DELETE', "animelist/delete/{$id}.xml") + ->setFormFields([ + 'id' => $id + ]) + ->setBasicAuth($config->get(['mal','username']), $config->get(['mal', 'password'])) + ->getFullRequest(); + + /*$response = $this->getResponse('DELETE', "animelist/delete/{$id}.xml", [ 'body' => $this->fixBody((new FormBody)->addField('id', $id)) ]); - return $response->getBody() === 'Deleted'; + return $response->getBody() === 'Deleted';*/ } public function get(string $id): array @@ -70,15 +77,25 @@ class ListItem { return []; } - public function update(string $id, array $data) + public function update(string $id, array $data): Request { + $config = $this->container->get('config'); + $xml = XML::toXML(['entry' => $data]); $body = (new FormBody) ->addField('id', $id) ->addField('data', $xml); - return $this->getResponse('POST', "animelist/update/{$id}.xml", [ + return $this->requestBuilder->newRequest('POST', "animelist/update/{$id}.xml") + ->setFormFields([ + 'id' => $id, + 'data' => $xml + ]) + ->setBasicAuth($config->get(['mal','username']), $config->get(['mal', 'password'])) + ->getFullRequest(); + + /* return $this->getResponse('POST', "animelist/update/{$id}.xml", [ 'body' => $this->fixBody($body) - ]); + ]); */ } } \ No newline at end of file diff --git a/src/API/MAL/MALTrait.php b/src/API/MAL/MALTrait.php index 8769f632..ba69da6e 100644 --- a/src/API/MAL/MALTrait.php +++ b/src/API/MAL/MALTrait.php @@ -85,7 +85,7 @@ trait MALTrait { * @param string $type * @param string $url * @param array $options - * @return \Amp\Promise + * @return \Amp\Artax\Response */ public function setUpRequest(string $type, string $url, array $options = []) { diff --git a/src/API/MAL/Model.php b/src/API/MAL/Model.php index a2d0633e..e8458121 100644 --- a/src/API/MAL/Model.php +++ b/src/API/MAL/Model.php @@ -16,6 +16,7 @@ namespace Aviat\AnimeClient\API\MAL; +use Amp\Artax\Request; use Aviat\AnimeClient\API\MAL as M; use Aviat\AnimeClient\API\MAL\ListItem; use Aviat\AnimeClient\API\MAL\Transformer\AnimeListTransformer; @@ -43,7 +44,7 @@ class Model { $this->listItem = $listItem; } - public function createListItem(array $data): bool + public function createListItem(array $data): Request { $createData = [ 'id' => $data['id'], @@ -77,13 +78,13 @@ class Model { return []; } - public function updateListItem(array $data) + public function updateListItem(array $data): Request { $updateData = $this->animeListTransformer->untransform($data); return $this->listItem->update($updateData['id'], $updateData['data']); } - public function deleteListItem(string $id): bool + public function deleteListItem(string $id): Request { return $this->listItem->delete($id); } diff --git a/src/AnimeClient.php b/src/AnimeClient.php index 0ffaca69..b43531bd 100644 --- a/src/AnimeClient.php +++ b/src/AnimeClient.php @@ -20,50 +20,43 @@ use Yosymfony\Toml\Toml; define('SRC_DIR', realpath(__DIR__)); +const SESSION_SEGMENT = 'Aviat\AnimeClient\Auth'; +const DEFAULT_CONTROLLER_NAMESPACE = 'Aviat\AnimeClient\Controller'; +const DEFAULT_CONTROLLER = 'Aviat\AnimeClient\Controller\Anime'; +const DEFAULT_CONTROLLER_METHOD = 'index'; +const NOT_FOUND_METHOD = 'notFound'; +const ERROR_MESSAGE_METHOD = 'errorPage'; +const SRC_DIR = SRC_DIR; + /** - * Application constants + * Load configuration options from .toml files + * + * @param string $path - Path to load config + * @return array */ -class AnimeClient { +function loadToml(string $path): array +{ + $output = []; + $files = glob("{$path}/*.toml"); - const SESSION_SEGMENT = 'Aviat\AnimeClient\Auth'; - const DEFAULT_CONTROLLER_NAMESPACE = 'Aviat\AnimeClient\Controller'; - const DEFAULT_CONTROLLER = 'Aviat\AnimeClient\Controller\Anime'; - const DEFAULT_CONTROLLER_METHOD = 'index'; - const NOT_FOUND_METHOD = 'notFound'; - const ERROR_MESSAGE_METHOD = 'errorPage'; - const SRC_DIR = SRC_DIR; - - /** - * Load configuration options from .toml files - * - * @param string $path - Path to load config - * @return array - */ - public static function loadToml(string $path): array + foreach ($files as $file) { - $output = []; - $files = glob("{$path}/*.toml"); + $key = str_replace('.toml', '', basename($file)); + $toml = file_get_contents($file); + $config = Toml::Parse($toml); - foreach ($files as $file) + if ($key === 'config') { - $key = str_replace('.toml', '', basename($file)); - $toml = file_get_contents($file); - $config = Toml::Parse($toml); - - if ($key === 'config') + foreach($config as $name => $value) { - foreach($config as $name => $value) - { - $output[$name] = $value; - } - - continue; + $output[$name] = $value; } - $output[$key] = $config; + continue; } - return $output; + $output[$key] = $config; } -} -// End of AnimeClient.php \ No newline at end of file + + return $output; +} \ No newline at end of file diff --git a/src/Command/BaseCommand.php b/src/Command/BaseCommand.php index e929578f..63fc16c6 100644 --- a/src/Command/BaseCommand.php +++ b/src/Command/BaseCommand.php @@ -16,21 +16,18 @@ namespace Aviat\AnimeClient\Command; +use function Aviat\AnimeClient\loadToml; + use Aura\Session\SessionFactory; use Aviat\AnimeClient\{ AnimeClient, Model, Util }; -use Aviat\AnimeClient\API\CacheTrait; -use Aviat\AnimeClient\API\Kitsu\{ - Auth as KitsuAuth, - ListItem as KitsuListItem, - Model as KitsuModel -}; -use Aviat\AnimeClient\API\MAL\{ - ListItem as MALListItem, - Model as MALModel +use Aviat\AnimeClient\API\{ + CacheTrait, + Kitsu, + MAL }; use Aviat\Banker\Pool; use Aviat\Ion\Config; @@ -72,23 +69,26 @@ class BaseCommand extends Command { $CONF_DIR = realpath("{$APP_DIR}/config/"); require_once $CONF_DIR . '/base_config.php'; // $base_config - $config = AnimeClient::loadToml($CONF_DIR); + $config = loadToml($CONF_DIR); $config_array = array_merge($base_config, $config); $di = function ($config_array) use ($APP_DIR) { $container = new Container(); - + // ------------------------------------------------------------------------- // Logging // ------------------------------------------------------------------------- $app_logger = new Logger('animeclient'); $app_logger->pushHandler(new NullHandler); - $request_logger = new Logger('request'); - $request_logger->pushHandler(new NullHandler); + $kitsu_request_logger = new Logger('kitsu-request'); + $kitsu_request_logger->pushHandler(new NullHandler); + $mal_request_logger = new Logger('mal-request'); + $mal_request_logger->pushHandler(new NullHandler); $container->setLogger($app_logger, 'default'); - $container->setLogger($request_logger, 'request'); - + $container->setLogger($kitsu_request_logger, 'kitsu-request'); + $container->setLogger($mal_request_logger, 'mal-request'); + // Create Config Object $container->set('config', function() use ($config_array) { return new Config($config_array); @@ -108,18 +108,18 @@ class BaseCommand extends Command { // Models $container->set('kitsu-model', function($container) { - $listItem = new KitsuListItem(); + $listItem = new Kitsu\istItem(); $listItem->setContainer($container); - $model = new KitsuModel($listItem); + $model = new Kitsu\Model($listItem); $model->setContainer($container); $cache = $container->get('cache'); $model->setCache($cache); return $model; }); $container->set('mal-model', function($container) { - $listItem = new MALListItem(); + $listItem = new MAL\ListItem(); $listItem->setContainer($container); - $model = new MALModel($listItem); + $model = new MAL\Model($listItem); $model->setContainer($container); return $model; }); diff --git a/src/Command/SyncKitsuWithMal.php b/src/Command/SyncKitsuWithMal.php index 93a714c2..d7f13daf 100644 --- a/src/Command/SyncKitsuWithMal.php +++ b/src/Command/SyncKitsuWithMal.php @@ -23,49 +23,10 @@ use Aviat\AnimeClient\API\Kitsu; * Clears the API Cache */ class SyncKitsuWithMal extends BaseCommand { - + protected $kitsuModel; - - public function getKitsuAnimeListPageCount() - { - $cacheItem = $this->cache->getItem(Kitsu::AUTH_TOKEN_CACHE_KEY); - - $query = http_build_query([ - 'filter' => [ - 'user_id' => $this->kitsuModel->getUserIdByUsername(), - 'media_type' => 'Anime' - ], - 'include' => 'anime,anime.genres,anime.mappings,anime.streamingLinks', - 'page' => [ - 'limit' => 1 - ], - 'sort' => '-updated_at' - ]); - $request = (new Artax\Request) - ->setUri("https://kitsu.io/api/edge/library-entries?{$query}") - ->setProtocol('1.1') - ->setAllHeaders([ - 'Accept' => 'application/vnd.api+json', - 'Content-Type' => 'application/vnd.api+json', - 'User-Agent' => "Tim's Anime Client/4.0" - ]); - - if ($cacheItem->isHit()) - { - $token = $cacheItem->get(); - $request->setHeader('Authorization', "bearer {$token}"); - } - else - { - $this->echoBox("WARNING: NOT LOGGED IN\nSome data might be missing"); - } - - $response = \Amp\wait((new Artax\Client)->request($request)); - - $body = json_decode($response->getBody(), TRUE); - return $body['meta']['count']; - } - + protected $malModel; + /** * Run the image conversion script * @@ -79,8 +40,102 @@ class SyncKitsuWithMal extends BaseCommand { $this->setContainer($this->setupContainer()); $this->setCache($this->container->get('cache')); $this->kitsuModel = $this->container->get('kitsu-model'); - - $kitsuCount = $this->getKitsuAnimeListPageCount(); - $this->echoBox("List item count: {$kitsuCount}"); + $this->malModel = $this->container->get('mal-model'); + + //$kitsuCount = $this->getKitsuAnimeListPageCount(); + //$this->echoBox("List item count: {$kitsuCount}"); + $this->MALItemCreate(); + + //echo json_encode($this->getMALList(), \JSON_PRETTY_PRINT); } -} + + + public function getMALList() + { + return $this->malModel->getFullList(); + } + + public function getKitsuAnimeListPageCount() + { + $cacheItem = $this->cache->getItem(Kitsu::AUTH_TOKEN_CACHE_KEY); + + $query = http_build_query([ + 'filter' => [ + 'user_id' => $this->kitsuModel->getUserIdByUsername(), + 'media_type' => 'Anime' + ], + // 'include' => 'anime,anime.genres,anime.mappings,anime.streamingLinks', + 'page' => [ + 'limit' => 1 + ], + 'sort' => '-updated_at' + ]); + $request = (new Artax\Request) + ->setUri("https://kitsu.io/api/edge/library-entries?{$query}") + ->setProtocol('1.1') + ->setAllHeaders([ + 'Accept' => 'application/vnd.api+json', + 'Content-Type' => 'application/vnd.api+json', + 'User-Agent' => "Tim's Anime Client/4.0" + ]); + + if ($cacheItem->isHit()) + { + $token = $cacheItem->get(); + $request->setHeader('Authorization', "bearer {$token}"); + } + else + { + $this->echoBox("WARNING: NOT LOGGED IN\nSome data might be missing"); + } + + $response = \Amp\wait((new Artax\Client)->request($request)); + + $body = json_decode($response->getBody(), TRUE); + return $body['meta']['count']; + } + + public function MALItemCreate() + { + $input = json_decode('{ + "watching_status": "current", + "user_rating": "", + "episodes_watched": "4", + "rewatched": "0", + "notes": "", + "id": "15794526", + "mal_id": "33731", + "edit": "true" + }', TRUE); + + $response = $this->malModel->createListItem([ + 'id' => 12255, + 'status' => 'planned', + 'type' => 'anime' + ]); + + //$response = $this->malModel->updateListItem($input); + //print_r($response); + //echo $response->getBody(); + + } + + public function diffLists() + { + // Get libraryEntries with media.mappings from Kitsu + // Organize mappings, and ignore entries without mappings + + // Get MAL list data + + // Compare each list entry + // If a list item exists only on MAL, create it on Kitsu with the existing data from MAL + // If a list item exists only on Kitsu, create it on MAL with the existing data from Kitsu + // If an item already exists on both APIS: + // Compare last updated dates, and use the later one + // Otherwise, use rewatch count, then episode progress as critera for selecting the more up + // to date entry + // Based on the 'newer' entry, update the other api list item + } + + +} \ No newline at end of file diff --git a/src/Controller.php b/src/Controller.php index 91148358..e83d71cd 100644 --- a/src/Controller.php +++ b/src/Controller.php @@ -16,6 +16,8 @@ namespace Aviat\AnimeClient; +use const Aviat\AnimeClient\SESSION_SEGMENT; + use Aviat\Ion\Di\{ContainerAware, ContainerInterface}; use Aviat\Ion\View\{HtmlView, HttpView, JsonView}; use InvalidArgumentException; @@ -102,7 +104,7 @@ class Controller { $this->urlGenerator = $urlGenerator; $session = $container->get('session'); - $this->session = $session->getSegment(AnimeClient::SESSION_SEGMENT); + $this->session = $session->getSegment(SESSION_SEGMENT); // Set a 'previous' flash value for better redirects $server_params = $this->request->getServerParams(); diff --git a/src/Dispatcher.php b/src/Dispatcher.php index 9b5892aa..f06a185b 100644 --- a/src/Dispatcher.php +++ b/src/Dispatcher.php @@ -16,9 +16,16 @@ namespace Aviat\AnimeClient; +use const Aviat\AnimeClient\{ + DEFAULT_CONTROLLER, + DEFAULT_CONTROLLER_NAMESPACE, + ERROR_MESSAGE_METHOD, + NOT_FOUND_METHOD, + SRC_DIR +}; + use Aviat\Ion\Di\ContainerInterface; use Aviat\Ion\Friend; -use GuzzleHttp\Exception\ServerException; /** * Basic routing/ dispatch @@ -125,27 +132,12 @@ class Dispatcher extends RoutingBase { // If not route was matched, return an appropriate http // error message $error_route = $this->getErrorParams(); - $controllerName = AnimeClient::DEFAULT_CONTROLLER; + $controllerName = DEFAULT_CONTROLLER; $actionMethod = $error_route['action_method']; $params = $error_route['params']; } - - // Try to catch API errors in a presentable fashion - try - { - // Actually instantiate the controller - $this->call($controllerName, $actionMethod, $params); - } - catch (ServerException $e) - { - $response = $e->getResponse(); - $this->call(AnimeClient::DEFAULT_CONTROLLER, AnimeClient::ERROR_MESSAGE_METHOD, [ - $response->getStatusCode(), - 'API Error', - 'There was a problem getting data from an external source.', - (string) $response->getBody() - ]); - } + + $this->call($controllerName, $actionMethod, $params); } /** @@ -176,7 +168,7 @@ class Dispatcher extends RoutingBase { $action_method = (array_key_exists('action', $route->attributes)) ? $route->attributes['action'] - : AnimeClient::NOT_FOUND_METHOD; + : NOT_FOUND_METHOD; $params = []; if ( ! empty($route->__get('tokens'))) @@ -229,11 +221,11 @@ class Dispatcher extends RoutingBase { */ public function getControllerList() { - $default_namespace = AnimeClient::DEFAULT_CONTROLLER_NAMESPACE; + $default_namespace = DEFAULT_CONTROLLER_NAMESPACE; $path = str_replace('\\', '/', $default_namespace); $path = str_replace('Aviat/AnimeClient/', '', $path); $path = trim($path, '/'); - $actual_path = realpath(_dir(AnimeClient::SRC_DIR, $path)); + $actual_path = realpath(_dir(SRC_DIR, $path)); $class_files = glob("{$actual_path}/*.php"); $controllers = []; @@ -285,7 +277,7 @@ class Dispatcher extends RoutingBase { $logger->info('Dispatcher - failed route'); $logger->info(print_r($failure, TRUE)); - $action_method = AnimeClient::ERROR_MESSAGE_METHOD; + $action_method = ERROR_MESSAGE_METHOD; $params = []; @@ -308,7 +300,7 @@ class Dispatcher extends RoutingBase { default: // Fall back to a 404 message - $action_method = AnimeClient::NOT_FOUND_METHOD; + $action_method = NOT_FOUND_METHOD; break; } @@ -337,7 +329,7 @@ class Dispatcher extends RoutingBase { $controller_map = $this->getControllerList(); $controller_class = (array_key_exists($route_type, $controller_map)) ? $controller_map[$route_type] - : AnimeClient::DEFAULT_CONTROLLER; + : DEFAULT_CONTROLLER; if (array_key_exists($route_type, $controller_map)) { diff --git a/src/Model/API.php b/src/Model/API.php index d2c423af..24879073 100644 --- a/src/Model/API.php +++ b/src/Model/API.php @@ -38,12 +38,6 @@ class API extends Model { */ protected $cache; - /** - * Default settings for Guzzle - * @var array - */ - protected $connectionDefaults = []; - /** * Constructor * @@ -74,4 +68,4 @@ class API extends Model { array_multisort($sort, SORT_ASC, $array); } -} +} \ No newline at end of file diff --git a/src/Model/Anime.php b/src/Model/Anime.php index 14bc58a1..328c8819 100644 --- a/src/Model/Anime.php +++ b/src/Model/Anime.php @@ -15,6 +15,10 @@ */ namespace Aviat\AnimeClient\Model; + +use function Amp\some; +use function Amp\wait; +use Amp\Artax\Client; use Aviat\AnimeClient\API\Kitsu\Enum\AnimeWatchingStatus; use Aviat\Ion\Di\ContainerInterface; use Aviat\Ion\Json; @@ -91,9 +95,15 @@ class Anime extends API { return $this->kitsuModel->getAnime($slug); } - public function getAnimeById($anime_id) + /** + * Get anime by its kitsu id + * + * @param string $animeId + * @return array + */ + public function getAnimeById($animeId) { - return $this->kitsuModel->getAnimeById($anime_id); + return $this->kitsuModel->getAnimeById($animeId); } /** @@ -104,7 +114,6 @@ class Anime extends API { */ public function search($name) { - // $raw = $this->kitsuModel->search('anime', $name); return $this->kitsuModel->search('anime', $name); } @@ -128,6 +137,8 @@ class Anime extends API { */ public function createLibraryItem(array $data): bool { + $requests = []; + if ($this->useMALAPI) { $malData = $data; @@ -136,11 +147,17 @@ class Anime extends API { if ( ! is_null($malId)) { $malData['id'] = $malId; - $this->malModel->createListItem($malData); + $requests['mal'] = $this->malModel->createListItem($malData); } } - return $this->kitsuModel->createListItem($data); + $requests['kitsu'] = $this->kitsuModel->createListItem($data); + + $promises = (new Client)->requestMulti($requests); + + $results = wait(some($promises)); + + return count($results[1]) > 0; } /** @@ -168,12 +185,18 @@ class Anime extends API { */ public function deleteLibraryItem(string $id, string $malId = null): bool { + $requests = []; + if ($this->useMALAPI && ! is_null($malId)) { - $this->malModel->deleteListItem($malId); + $requests['mal'] = $this->malModel->deleteListItem($malId); } - return $this->kitsuModel->deleteListItem($id); + $requests['kitsu'] = $this->kitsuModel->deleteListItem($id); + + $results = wait(some((new Client)->requestMulti($requests))); + + return count($results[1]) > 0; } } // End of AnimeModel.php \ No newline at end of file diff --git a/tests/API/APIRequestBuilderTest.php b/tests/API/APIRequestBuilderTest.php index 4b3b10aa..f2050e86 100644 --- a/tests/API/APIRequestBuilderTest.php +++ b/tests/API/APIRequestBuilderTest.php @@ -29,13 +29,13 @@ class APIRequestBuilderTest extends TestCase { { $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') @@ -44,26 +44,26 @@ class APIRequestBuilderTest extends TestCase { $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 = [ @@ -75,40 +75,40 @@ class APIRequestBuilderTest extends TestCase { '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']); + + $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 = [ @@ -121,16 +121,15 @@ class APIRequestBuilderTest extends TestCase { ] ] ]; - + $request = $this->builder->newRequest('PUT', 'https://httpbin.org/put') ->setHeader('Content-Type', 'application/json') - ->setBody(Json::encode($data)) + ->setJsonBody($data) ->getFullRequest(); - + $response = Amp\wait((new Client)->request($request)); $body = Json::decode($response->getBody()); - + $this->assertEquals($data, $body['json']); } -} - +} \ No newline at end of file diff --git a/tests/AnimeClient_TestCase.php b/tests/AnimeClient_TestCase.php index 6e8ab7fa..90733e39 100644 --- a/tests/AnimeClient_TestCase.php +++ b/tests/AnimeClient_TestCase.php @@ -1,12 +1,10 @@ $handler]); - - return $client; - } } // End of AnimeClient_TestCase.php \ No newline at end of file