diff --git a/src/AnimeClient/API/Kitsu/KitsuRequestBuilder.php b/src/AnimeClient/API/Kitsu/KitsuRequestBuilder.php index aa105062..4e6b7c21 100644 --- a/src/AnimeClient/API/Kitsu/KitsuRequestBuilder.php +++ b/src/AnimeClient/API/Kitsu/KitsuRequestBuilder.php @@ -16,10 +16,26 @@ namespace Aviat\AnimeClient\API\Kitsu; +use const Aviat\AnimeClient\SESSION_SEGMENT; use const Aviat\AnimeClient\USER_AGENT; + +use function Amp\Promise\wait; +use function Aviat\AnimeClient\getResponse; + +use Amp\Http\Client\Request; +use Amp\Http\Client\Response; use Aviat\AnimeClient\API\APIRequestBuilder; +use Aviat\AnimeClient\API\FailedResponseException; +use Aviat\AnimeClient\API\Kitsu as K; +use Aviat\AnimeClient\Enum\EventType; +use Aviat\Ion\Di\ContainerAware; +use Aviat\Ion\Di\ContainerInterface; +use Aviat\Ion\Event; +use Aviat\Ion\Json; +use Aviat\Ion\JsonException; final class KitsuRequestBuilder extends APIRequestBuilder { + use ContainerAware; /** * The base url for api requests @@ -39,4 +55,217 @@ final class KitsuRequestBuilder extends APIRequestBuilder { 'CLIENT_ID' => 'dd031b32d2f56c990b1425efe6c42ad847e7fe3ab46bf1299f05ecd856bdb7dd', 'CLIENT_SECRET' => '54d7307928f63414defd96399fc31ba847961ceaecef3a5fd93144e960c0e151', ]; + + public function __construct(ContainerInterface $container) + { + $this->setContainer($container); + } + + /** + * Create a request object + * + * @param string $type + * @param string $url + * @param array $options + * @return Request + */ + public function setUpRequest(string $type, string $url, array $options = []): Request + { + $request = $this->newRequest($type, $url); + + $sessionSegment = $this->getContainer() + ->get('session') + ->getSegment(SESSION_SEGMENT); + + $cache = $this->getContainer()->get('cache'); + $token = null; + + if ($cache->has(K::AUTH_TOKEN_CACHE_KEY)) + { + $token = $cache->get(K::AUTH_TOKEN_CACHE_KEY); + } + else if ($url !== K::AUTH_URL && $sessionSegment->get('auth_token') !== NULL) + { + $token = $sessionSegment->get('auth_token'); + if ( ! (empty($token) || $cache->has(K::AUTH_TOKEN_CACHE_KEY))) + { + $cache->set(K::AUTH_TOKEN_CACHE_KEY, $token); + } + } + + if ($token !== NULL) + { + $request = $request->setAuth('bearer', $token); + } + + if (array_key_exists('form_params', $options)) + { + $request = $request->setFormFields($options['form_params']); + } + + if (array_key_exists('query', $options)) + { + $request = $request->setQuery($options['query']); + } + + if (array_key_exists('body', $options)) + { + $request = $request->setJsonBody($options['body']); + } + + if (array_key_exists('headers', $options)) + { + $request = $request->setHeaders($options['headers']); + } + + return $request->getFullRequest(); + } + + /** + * Remove some boilerplate for get requests + * + * @param mixed ...$args + * @throws Throwable + * @return array + */ + public function getRequest(...$args): array + { + return $this->request('GET', ...$args); + } + + /** + * Remove some boilerplate for patch requests + * + * @param mixed ...$args + * @throws Throwable + * @return array + */ + public function patchRequest(...$args): array + { + return $this->request('PATCH', ...$args); + } + + /** + * Remove some boilerplate for post requests + * + * @param mixed ...$args + * @throws Throwable + * @return array + */ + public function postRequest(...$args): array + { + $logger = NULL; + if ($this->getContainer()) + { + $logger = $this->container->getLogger('kitsu-request'); + } + + $response = $this->getResponse('POST', ...$args); + $validResponseCodes = [200, 201]; + + if ( ! in_array($response->getStatus(), $validResponseCodes, TRUE) && $logger) + { + $logger->warning('Non 2xx response for POST api call', $response->getBody()); + } + + return JSON::decode(wait($response->getBody()->buffer()), TRUE); + } + + /** + * Remove some boilerplate for delete requests + * + * @param mixed ...$args + * @throws Throwable + * @return bool + */ + public function deleteRequest(...$args): bool + { + $response = $this->getResponse('DELETE', ...$args); + return ($response->getStatus() === 204); + } + + /** + * Make a request + * + * @param string $type + * @param string $url + * @param array $options + * @return Response + * @throws Throwable + */ + public function getResponse(string $type, string $url, array $options = []): Response + { + $logger = NULL; + if ($this->getContainer()) + { + $logger = $this->container->getLogger('kitsu-request'); + } + + $request = $this->setUpRequest($type, $url, $options); + + $response = getResponse($request); + + if ($logger) + { + $logger->debug('Kitsu API Response', [ + 'response_status' => $response->getStatus(), + 'request_headers' => $response->getOriginalRequest()->getHeaders(), + 'response_headers' => $response->getHeaders() + ]); + } + + return $response; + } + + /** + * Make a request + * + * @param string $type + * @param string $url + * @param array $options + * @throws JsonException + * @throws FailedResponseException + * @throws Throwable + * @return array + */ + private function request(string $type, string $url, array $options = []): array + { + $logger = NULL; + if ($this->getContainer()) + { + $logger = $this->container->getLogger('kitsu-request'); + } + + $response = $this->getResponse($type, $url, $options); + $statusCode = $response->getStatus(); + + // Check for requests that are unauthorized + if ($statusCode === 401 || $statusCode === 403) + { + Event::emit(EventType::UNAUTHORIZED); + } + + // Any other type of failed request + if ($statusCode > 299 || $statusCode < 200) + { + if ($logger) + { + $logger->warning('Non 2xx response for api call', (array)$response); + } + + throw new FailedResponseException('Failed to get the proper response from the API'); + } + + try + { + return Json::decode(wait($response->getBody()->buffer())); + } + catch (JsonException $e) + { + print_r($e); + die(); + } + } + + } \ No newline at end of file diff --git a/src/AnimeClient/API/Kitsu/KitsuTrait.php b/src/AnimeClient/API/Kitsu/KitsuTrait.php index 67ce2815..c4d477af 100644 --- a/src/AnimeClient/API/Kitsu/KitsuTrait.php +++ b/src/AnimeClient/API/Kitsu/KitsuTrait.php @@ -16,25 +16,7 @@ namespace Aviat\AnimeClient\API\Kitsu; -use Aviat\AnimeClient\Enum\EventType; -use function in_array; -use const PHP_SAPI; -use const Aviat\AnimeClient\SESSION_SEGMENT; - -use function Amp\Promise\wait; -use function Aviat\AnimeClient\getResponse; - -use Amp\Http\Client\Request; -use Amp\Http\Client\Response; -use Aviat\AnimeClient\API\{FailedResponseException, Kitsu as K}; -use Aviat\Ion\Event; -use Aviat\Ion\Json; -use Aviat\Ion\JsonException; - -use Throwable; - trait KitsuTrait { - /** * The request builder for the Kitsu API * @var KitsuRequestBuilder @@ -52,212 +34,4 @@ trait KitsuTrait { $this->requestBuilder = $requestBuilder; return $this; } - - /** - * Create a request object - * - * @param string $type - * @param string $url - * @param array $options - * @return Request - */ - public function setUpRequest(string $type, string $url, array $options = []): Request - { - $request = $this->requestBuilder->newRequest($type, $url); - - $sessionSegment = $this->getContainer() - ->get('session') - ->getSegment(SESSION_SEGMENT); - - $cache = $this->getContainer()->get('cache'); - $cacheItem = $cache->getItem(K::AUTH_TOKEN_CACHE_KEY); - $token = null; - - if (PHP_SAPI === 'cli' && $cacheItem->isHit()) - { - $token = $cacheItem->get(); - } - else if ($url !== K::AUTH_URL && $sessionSegment->get('auth_token') !== NULL) - { - $token = $sessionSegment->get('auth_token'); - if ( ! (empty($token) || $cacheItem->isHit())) - { - $cacheItem->set($token); - $cacheItem->save(); - } - } - - if ($token !== NULL) - { - $request = $request->setAuth('bearer', $token); - } - - if (array_key_exists('form_params', $options)) - { - $request = $request->setFormFields($options['form_params']); - } - - if (array_key_exists('query', $options)) - { - $request = $request->setQuery($options['query']); - } - - if (array_key_exists('body', $options)) - { - $request = $request->setJsonBody($options['body']); - } - - if (array_key_exists('headers', $options)) - { - $request = $request->setHeaders($options['headers']); - } - - return $request->getFullRequest(); - } - - /** - * Make a request - * - * @param string $type - * @param string $url - * @param array $options - * @return Response - * @throws Throwable - */ - private function getResponse(string $type, string $url, array $options = []): Response - { - $logger = NULL; - if ($this->getContainer()) - { - $logger = $this->container->getLogger('kitsu-request'); - } - - $request = $this->setUpRequest($type, $url, $options); - - $response = getResponse($request); - - if ($logger) - { - $logger->debug('Kitsu API Response', [ - 'response_status' => $response->getStatus(), - 'request_headers' => $response->getOriginalRequest()->getHeaders(), - 'response_headers' => $response->getHeaders() - ]); - } - - return $response; - } - - /** - * Make a request - * - * @param string $type - * @param string $url - * @param array $options - * @throws JsonException - * @throws FailedResponseException - * @throws Throwable - * @return array - */ - private function request(string $type, string $url, array $options = []): array - { - $logger = NULL; - if ($this->getContainer()) - { - $logger = $this->container->getLogger('kitsu-request'); - } - - $response = $this->getResponse($type, $url, $options); - $statusCode = $response->getStatus(); - - // Check for requests that are unauthorized - if ($statusCode === 401 || $statusCode === 403) - { - Event::emit(EventType::UNAUTHORIZED); - } - - // Any other type of failed request - if ($statusCode > 299 || $statusCode < 200) - { - if ($logger) - { - $logger->warning('Non 2xx response for api call', (array)$response); - } - - throw new FailedResponseException('Failed to get the proper response from the API'); - } - - try - { - return Json::decode(wait($response->getBody()->buffer())); - } - catch (JsonException $e) - { - print_r($e); - die(); - } - } - - /** - * Remove some boilerplate for get requests - * - * @param mixed ...$args - * @throws Throwable - * @return array - */ - protected function getRequest(...$args): array - { - return $this->request('GET', ...$args); - } - - /** - * Remove some boilerplate for patch requests - * - * @param mixed ...$args - * @throws Throwable - * @return array - */ - protected function patchRequest(...$args): array - { - return $this->request('PATCH', ...$args); - } - - /** - * Remove some boilerplate for post requests - * - * @param mixed ...$args - * @throws Throwable - * @return array - */ - protected function postRequest(...$args): array - { - $logger = NULL; - if ($this->getContainer()) - { - $logger = $this->container->getLogger('kitsu-request'); - } - - $response = $this->getResponse('POST', ...$args); - $validResponseCodes = [200, 201]; - - if ( ! in_array($response->getStatus(), $validResponseCodes, TRUE) && $logger) - { - $logger->warning('Non 201 response for POST api call', $response->getBody()); - } - - return JSON::decode(wait($response->getBody()->buffer()), TRUE); - } - - /** - * Remove some boilerplate for delete requests - * - * @param mixed ...$args - * @throws Throwable - * @return bool - */ - protected function deleteRequest(...$args): bool - { - $response = $this->getResponse('DELETE', ...$args); - return ($response->getStatus() === 204); - } } \ No newline at end of file diff --git a/src/AnimeClient/API/Kitsu/ListItem.php b/src/AnimeClient/API/Kitsu/ListItem.php index 4b11080e..76fc7fef 100644 --- a/src/AnimeClient/API/Kitsu/ListItem.php +++ b/src/AnimeClient/API/Kitsu/ListItem.php @@ -18,7 +18,6 @@ namespace Aviat\AnimeClient\API\Kitsu; use Aviat\Ion\Di\Exception\ContainerException; use Aviat\Ion\Di\Exception\NotFoundException; -use const Aviat\AnimeClient\SESSION_SEGMENT; use function Amp\Promise\wait; use function Aviat\AnimeClient\getResponse; @@ -78,7 +77,7 @@ final class ListItem extends AbstractListItem { $request = $this->requestBuilder->newRequest('POST', 'library-entries'); - if ($authHeader !== FALSE) + if ($authHeader !== NULL) { $request = $request->setHeader('Authorization', $authHeader); } @@ -97,7 +96,7 @@ final class ListItem extends AbstractListItem { $authHeader = $this->getAuthHeader(); $request = $this->requestBuilder->newRequest('DELETE', "library-entries/{$id}"); - if ($authHeader !== FALSE) + if ($authHeader !== NULL) { $request = $request->setHeader('Authorization', $authHeader); } @@ -119,7 +118,7 @@ final class ListItem extends AbstractListItem { 'include' => 'media,media.categories,media.mappings' ]); - if ($authHeader !== FALSE) + if ($authHeader !== NULL) { $request = $request->setHeader('Authorization', $authHeader); } @@ -159,7 +158,7 @@ final class ListItem extends AbstractListItem { $request = $this->requestBuilder->newRequest('PATCH', "library-entries/{$id}") ->setJsonBody($requestData); - if ($authHeader !== FALSE) + if ($authHeader !== NULL) { $request = $request->setHeader('Authorization', $authHeader); } @@ -172,24 +171,15 @@ final class ListItem extends AbstractListItem { * @throws ContainerException * @throws NotFoundException */ - private function getAuthHeader() + private function getAuthHeader(): ?string { - $cache = $this->getContainer()->get('cache'); - $cacheItem = $cache->getItem('kitsu-auth-token'); - $sessionSegment = $this->getContainer() - ->get('session') - ->getSegment(SESSION_SEGMENT); + $auth = $this->getContainer()->get('auth'); + $token = $auth->getAuthToken(); - if ($sessionSegment->get('auth_token') !== NULL) { - $token = $sessionSegment->get('auth_token'); + if ( ! empty($token)) { return "bearer {$token}"; } - if ($cacheItem->isHit()) { - $token = $cacheItem->get(); - return "bearer {$token}"; - } - - return FALSE; + return NULL; } } \ No newline at end of file diff --git a/src/AnimeClient/API/Kitsu/Model.php b/src/AnimeClient/API/Kitsu/Model.php index 5c5a90e4..929e3ffe 100644 --- a/src/AnimeClient/API/Kitsu/Model.php +++ b/src/AnimeClient/API/Kitsu/Model.php @@ -116,7 +116,7 @@ final class Model { public function authenticate(string $username, string $password) { // K::AUTH_URL - $response = $this->getResponse('POST', K::AUTH_URL, [ + $response = $this->requestBuilder->getResponse('POST', K::AUTH_URL, [ 'headers' => [ 'accept' => NULL, 'Content-type' => 'application/x-www-form-urlencoded', @@ -182,6 +182,7 @@ final class Model { * @param string $username * @return string * @throws InvalidArgumentException + * @throws Throwable */ public function getUserIdByUsername(string $username = NULL): string { @@ -211,14 +212,14 @@ final class Model { */ public function getCharacter(string $slug): array { - return $this->getRequest('characters', [ + return $this->requestBuilder->getRequest('characters', [ 'query' => [ 'filter' => [ 'slug' => $slug, ], 'fields' => [ - 'anime' => 'canonicalTitle,titles,slug,posterImage', - 'manga' => 'canonicalTitle,titles,slug,posterImage' + 'anime' => 'canonicalTitle,abbreviatedTitles,titles,slug,posterImage', + 'manga' => 'canonicalTitle,abbreviatedTitles,titles,slug,posterImage' ], 'include' => 'castings.person,castings.media' ] @@ -234,31 +235,22 @@ final class Model { */ public function getPerson(string $id): array { - $cacheItem = $this->cache->getItem("kitsu-person-{$id}"); - - if ( ! $cacheItem->isHit()) - { - $data = $this->getRequest("people/{$id}", [ - 'query' => [ - 'filter' => [ - 'id' => $id, - ], - 'fields' => [ - 'characters' => 'canonicalName,slug,image', - 'characterVoices' => 'mediaCharacter', - 'anime' => 'canonicalTitle,abbreviatedTitles,titles,slug,posterImage', - 'manga' => 'canonicalTitle,abbreviatedTitles,titles,slug,posterImage', - 'mediaCharacters' => 'role,media,character', - 'mediaStaff' => 'role,media,person', - ], - 'include' => 'voices.mediaCharacter.media,voices.mediaCharacter.character,staff.media', + return $this->getCached("kitsu-person-{$id}", fn () => $this->requestBuilder->getRequest("people/{$id}", [ + 'query' => [ + 'filter' => [ + 'id' => $id, ], - ]); - $cacheItem->set($data); - $cacheItem->save(); - } - - return $cacheItem->get(); + 'fields' => [ + 'characters' => 'canonicalName,slug,image', + 'characterVoices' => 'mediaCharacter', + 'anime' => 'canonicalTitle,abbreviatedTitles,titles,slug,posterImage', + 'manga' => 'canonicalTitle,abbreviatedTitles,titles,slug,posterImage', + 'mediaCharacters' => 'role,media,character', + 'mediaStaff' => 'role,media,person', + ], + 'include' => 'voices.mediaCharacter.media,voices.mediaCharacter.character,staff.media', + ], + ])); } /** @@ -269,7 +261,7 @@ final class Model { */ public function getUserData(string $username): array { - return $this->getRequest('users', [ + return $this->requestBuilder->getRequest('users', [ 'query' => [ 'filter' => [ 'name' => $username, @@ -306,7 +298,7 @@ final class Model { ] ]; - $raw = $this->getRequest($type, $options); + $raw = $this->requestBuilder->getRequest($type, $options); $raw['included'] = JsonAPI::organizeIncluded($raw['included']); foreach ($raw['data'] as &$item) @@ -351,7 +343,7 @@ final class Model { ] ]; - $raw = $this->getRequest('mappings', $options); + $raw = $this->requestBuilder->getRequest('mappings', $options); if ( ! array_key_exists('included', $raw)) { @@ -555,7 +547,7 @@ final class Model { 'include' => 'mappings' ] ]; - $data = $this->getRequest("anime/{$kitsuAnimeId}", $options); + $data = $this->requestBuilder->getRequest("anime/{$kitsuAnimeId}", $options); if ( ! array_key_exists('included', $data)) { @@ -601,7 +593,7 @@ final class Model { ]; $options = array_merge($defaultOptions, $options); - return $this->setUpRequest('GET', 'library-entries', ['query' => $options]); + return $this->requestBuilder->setUpRequest('GET', 'library-entries', ['query' => $options]); } /** @@ -841,7 +833,7 @@ final class Model { ]; $options = array_merge($defaultOptions, $options); - return $this->setUpRequest('GET', 'library-entries', ['query' => $options]); + return $this->requestBuilder->setUpRequest('GET', 'library-entries', ['query' => $options]); } /** @@ -858,7 +850,7 @@ final class Model { 'include' => 'mappings' ] ]; - $data = $this->getRequest("manga/{$kitsuMangaId}", $options); + $data = $this->requestBuilder->getRequest("manga/{$kitsuMangaId}", $options); $mappings = array_column($data['included'], 'attributes'); foreach($mappings as $map) @@ -1016,7 +1008,7 @@ final class Model { */ protected function getRawHistoryPage(string $type, int $offset, int $limit = 20): Request { - return $this->setUpRequest('GET', 'library-events', [ + return $this->requestBuilder->setUpRequest('GET', 'library-events', [ 'query' => [ 'filter' => [ 'kind' => 'progressed,updated', @@ -1078,7 +1070,7 @@ final class Model { ] ]; - $data = $this->getRequest("{$type}/{$id}", $options); + $data = $this->requestBuilder->getRequest("{$type}/{$id}", $options); if (empty($data['data'])) { @@ -1118,7 +1110,7 @@ final class Model { ] ]; - $data = $this->getRequest($type, $options); + $data = $this->requestBuilder->getRequest($type, $options); if (empty($data['data'])) { @@ -1151,7 +1143,7 @@ final class Model { $options['query']['filter']['status'] = $status; } - $response = $this->getRequest('library-entries', $options); + $response = $this->requestBuilder->getRequest('library-entries', $options); return $response['meta']['count']; } @@ -1217,6 +1209,6 @@ final class Model { ]; $options = array_merge($defaultOptions, $options); - return $this->setUpRequest('GET', 'library-entries', ['query' => $options]); + return $this->requestBuilder->setUpRequest('GET', 'library-entries', ['query' => $options]); } } \ No newline at end of file