diff --git a/app/appConf/routes.php b/app/appConf/routes.php index 3f7c62ab..3219bda3 100644 --- a/app/appConf/routes.php +++ b/app/appConf/routes.php @@ -175,9 +175,10 @@ $routes = [ ] ], 'person' => [ - 'path' => '/people/{id}', + 'path' => '/people/{id}{/slug}', 'tokens' => [ - 'id' => SLUG_PATTERN + 'id' => SLUG_PATTERN, + 'slug' => SLUG_PATTERN, ] ], 'default_user_info' => [ diff --git a/app/bootstrap.php b/app/bootstrap.php index e7d66d5e..dc3233d6 100644 --- a/app/bootstrap.php +++ b/app/bootstrap.php @@ -130,7 +130,7 @@ return static function (array $configArray = []): Container { return $model; }); $container->set('anilist-model', static function(ContainerInterface $container): Anilist\Model { - $requestBuilder = new AnilistRequestBuilder(); + $requestBuilder = new AnilistRequestBuilder($container); $requestBuilder->setLogger($container->getLogger('anilist-request')); $listItem = new Anilist\ListItem(); diff --git a/app/views/anime/details.php b/app/views/anime/details.php index c915bee4..7a450785 100644 --- a/app/views/anime/details.php +++ b/app/views/anime/details.php @@ -174,7 +174,7 @@ use function Aviat\AnimeClient\getLocalImg;
$person): ?>
- generate('person', ['id' => $person['id']]) ?> + generate('person', ['id' => $person['id'], 'slug' => $person['slug']]) ?>
diff --git a/app/views/manga/details.php b/app/views/manga/details.php index 060999a0..3fb449c7 100644 --- a/app/views/manga/details.php +++ b/app/views/manga/details.php @@ -100,7 +100,7 @@
- generate('person', ['id' => $person['id']]) ?> + generate('person', ['id' => $person['id'], 'slug' => $person['slug']]) ?>
diff --git a/src/AnimeClient/API/Anilist/AnilistRequestBuilder.php b/src/AnimeClient/API/Anilist/AnilistRequestBuilder.php index c567e604..9f0e86cf 100644 --- a/src/AnimeClient/API/Anilist/AnilistRequestBuilder.php +++ b/src/AnimeClient/API/Anilist/AnilistRequestBuilder.php @@ -16,17 +16,28 @@ namespace Aviat\AnimeClient\API\Anilist; +use Amp\Http\Client\Request; +use Amp\Http\Client\Response; +use Aviat\AnimeClient\API\Anilist; +use Aviat\Ion\Di\ContainerAware; +use Aviat\Ion\Di\ContainerInterface; +use Aviat\Ion\Json; + +use Aviat\Ion\JsonException; +use function Amp\Promise\wait; +use function Aviat\AnimeClient\getResponse; use const Aviat\AnimeClient\USER_AGENT; use Aviat\AnimeClient\API\APIRequestBuilder; final class AnilistRequestBuilder extends APIRequestBuilder { + use ContainerAware; /** * The base url for api requests * @var string $base_url */ - protected string $baseUrl = 'https://graphql.anilist.co'; + protected string $baseUrl = Anilist::BASE_URL; /** * Valid HTTP request methods @@ -40,8 +51,226 @@ final class AnilistRequestBuilder extends APIRequestBuilder { * @var array */ protected array $defaultHeaders = [ - 'User-Agent' => USER_AGENT, 'Accept' => 'application/json', - 'Content-Type' => 'application/json', + // 'Accept-Encoding' => 'gzip', + 'Content-type' => 'application/json', + 'User-Agent' => USER_AGENT, ]; + + public function __construct(ContainerInterface $container) + { + $this->setContainer($container); + } + + /** + * Create a request object + * @param string $url + * @param array $options + * @return Request + * @throws Throwable + */ + public function setUpRequest(string $url, array $options = []): Request + { + $config = $this->getContainer()->get('config'); + $anilistConfig = $config->get('anilist'); + + $request = $this->newRequest('POST', $url); + + // You can only authenticate the request if you + // actually have an access_token saved + if ($config->has(['anilist', 'access_token'])) + { + $request = $request->setAuth('bearer', $anilistConfig['access_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(); + } + + /** + * Run a GraphQL API query + * + * @param string $name + * @param array $variables + * @return array + */ + public function runQuery(string $name, array $variables = []): array + { + $file = realpath(__DIR__ . "/Queries/{$name}.graphql"); + if ( ! file_exists($file)) + { + throw new LogicException('GraphQL query file does not exist.'); + } + + $query = file_get_contents($file); + $body = [ + 'query' => $query + ]; + + if ( ! empty($variables)) + { + $body['variables'] = []; + foreach($variables as $key => $val) + { + $body['variables'][$key] = $val; + } + } + + return $this->postRequest([ + 'body' => $body + ]); + } + + /** + * @param string $name + * @param array $variables + * @return Request + * @throws Throwable + */ + public function mutateRequest (string $name, array $variables = []): Request + { + $file = realpath(__DIR__ . "/Mutations/{$name}.graphql"); + if (!file_exists($file)) + { + throw new LogicException('GraphQL mutation file does not exist.'); + } + + $query = file_get_contents($file); + + $body = [ + 'query' => $query + ]; + + if (!empty($variables)) { + $body['variables'] = []; + foreach ($variables as $key => $val) + { + $body['variables'][$key] = $val; + } + } + + return $this->setUpRequest(Anilist::BASE_URL, [ + 'body' => $body, + ]); + } + + /** + * @param string $name + * @param array $variables + * @return array + * @throws Throwable + */ + public function mutate (string $name, array $variables = []): array + { + $request = $this->mutateRequest($name, $variables); + $response = $this->getResponseFromRequest($request); + + return Json::decode(wait($response->getBody()->buffer())); + } + + /** + * Make a request + * + * @param string $url + * @param array $options + * @return Response + * @throws Throwable + */ + private function getResponse(string $url, array $options = []): Response + { + $logger = $this->container->getLogger('anilist-request'); + + $request = $this->setUpRequest($url, $options); + $response = getResponse($request); + + $logger->debug('Anilist response', [ + 'status' => $response->getStatus(), + 'reason' => $response->getReason(), + 'body' => $response->getBody(), + 'headers' => $response->getHeaders(), + 'requestHeaders' => $request->getHeaders(), + ]); + + return $response; + } + + /** + * @param Request $request + * @return Response + * @throws Throwable + */ + private function getResponseFromRequest(Request $request): Response + { + $logger = $this->container->getLogger('anilist-request'); + + $response = getResponse($request); + + $logger->debug('Anilist response', [ + 'status' => $response->getStatus(), + 'reason' => $response->getReason(), + 'body' => $response->getBody(), + 'headers' => $response->getHeaders(), + 'requestHeaders' => $request->getHeaders(), + ]); + + return $response; + } + + /** + * Remove some boilerplate for post requests + * + * @param array $options + * @return array + * @throws Throwable + */ + protected function postRequest(array $options = []): array + { + $response = $this->getResponse(Anilist::BASE_URL, $options); + $validResponseCodes = [200, 201]; + + $logger = $this->container->getLogger('anilist-request'); + $logger->debug('Anilist response', [ + 'status' => $response->getStatus(), + 'reason' => $response->getReason(), + 'body' => $response->getBody(), + 'headers' => $response->getHeaders(), + //'requestHeaders' => $request->getHeaders(), + ]); + + if ( ! \in_array($response->getStatus(), $validResponseCodes, TRUE)) + { + $logger->warning('Non 200 response for POST api call', (array)$response->getBody()); + } + + $rawBody = wait($response->getBody()->buffer()); + try + { + return Json::decode($rawBody); + } + catch (JsonException $e) + { + dump($e); + dump($rawBody); + die(); + } + } } \ No newline at end of file diff --git a/src/AnimeClient/API/Anilist/AnilistTrait.php b/src/AnimeClient/API/Anilist/AnilistTrait.php index bb973970..a5990f0d 100644 --- a/src/AnimeClient/API/Anilist/AnilistTrait.php +++ b/src/AnimeClient/API/Anilist/AnilistTrait.php @@ -16,21 +16,8 @@ namespace Aviat\AnimeClient\API\Anilist; -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\Anilist; -use Aviat\Ion\Json; use Aviat\Ion\Di\ContainerAware; -use LogicException; -use Throwable; - trait AnilistTrait { use ContainerAware; @@ -40,24 +27,6 @@ trait AnilistTrait { */ protected AnilistRequestBuilder $requestBuilder; - /** - * The base url for api requests - * @var string $base_url - */ - protected string $baseUrl = Anilist::BASE_URL; - - /** - * HTTP headers to send with every request - * - * @var array - */ - protected array $defaultHeaders = [ - 'Accept' => 'application/json', - 'Accept-Encoding' => 'gzip', - 'Content-type' => 'application/json', - 'User-Agent' => USER_AGENT, - ]; - /** * Set the request builder object * @@ -69,223 +38,4 @@ trait AnilistTrait { $this->requestBuilder = $requestBuilder; return $this; } - - /** - * Create a request object - * @param string $url - * @param array $options - * @return Request - * @throws Throwable - */ - public function setUpRequest(string $url, array $options = []): Request - { - $config = $this->getContainer()->get('config'); - $anilistConfig = $config->get('anilist'); - - $request = $this->requestBuilder->newRequest('POST', $url); - - // You can only authenticate the request if you - // actually have an access_token saved - if ($config->has(['anilist', 'access_token'])) - { - $request = $request->setAuth('bearer', $anilistConfig['access_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(); - } - - /** - * Run a GraphQL API query - * - * @param string $name - * @param array $variables - * @return array - */ - public function runQuery(string $name, array $variables = []): array - { - $file = realpath(__DIR__ . "/Queries/{$name}.graphql"); - if ( ! file_exists($file)) - { - throw new LogicException('GraphQL query file does not exist.'); - } - - $query = file_get_contents($file); - $body = [ - 'query' => $query - ]; - - if ( ! empty($variables)) - { - $body['variables'] = []; - foreach($variables as $key => $val) - { - $body['variables'][$key] = $val; - } - } - - return $this->postRequest([ - 'body' => $body - ]); - } - - /** - * @param string $name - * @param array $variables - * @return Request - * @throws Throwable - */ - public function mutateRequest (string $name, array $variables = []): Request - { - $file = realpath(__DIR__ . "/Mutations/{$name}.graphql"); - if (!file_exists($file)) - { - throw new LogicException('GraphQL mutation file does not exist.'); - } - - $query = file_get_contents($file); - - $body = [ - 'query' => $query - ]; - - if (!empty($variables)) { - $body['variables'] = []; - foreach ($variables as $key => $val) - { - $body['variables'][$key] = $val; - } - } - - return $this->setUpRequest(Anilist::BASE_URL, [ - 'body' => $body, - ]); - } - - /** - * @param string $name - * @param array $variables - * @return array - * @throws Throwable - */ - public function mutate (string $name, array $variables = []): array - { - $request = $this->mutateRequest($name, $variables); - $response = $this->getResponseFromRequest($request); - - return Json::decode(wait($response->getBody()->buffer())); - } - - /** - * Make a request - * - * @param string $url - * @param array $options - * @return Response - * @throws Throwable - */ - private function getResponse(string $url, array $options = []): Response - { - $logger = NULL; - if ($this->getContainer()) - { - $logger = $this->container->getLogger('anilist-request'); - } - - $request = $this->setUpRequest($url, $options); - $response = getResponse($request); - - $logger->debug('Anilist response', [ - 'status' => $response->getStatus(), - 'reason' => $response->getReason(), - 'body' => $response->getBody(), - 'headers' => $response->getHeaders(), - 'requestHeaders' => $request->getHeaders(), - ]); - - return $response; - } - - /** - * @param Request $request - * @return Response - * @throws Throwable - */ - private function getResponseFromRequest(Request $request): Response - { - $logger = NULL; - if ($this->getContainer()) - { - $logger = $this->container->getLogger('anilist-request'); - } - - $response = getResponse($request); - - $logger->debug('Anilist response', [ - 'status' => $response->getStatus(), - 'reason' => $response->getReason(), - 'body' => $response->getBody(), - 'headers' => $response->getHeaders(), - 'requestHeaders' => $request->getHeaders(), - ]); - - return $response; - } - - /** - * Remove some boilerplate for post requests - * - * @param array $options - * @return array - * @throws Throwable - */ - protected function postRequest(array $options = []): array - { - $response = $this->getResponse(Anilist::BASE_URL, $options); - $validResponseCodes = [200, 201]; - - $logger = NULL; - if ($this->getContainer()) - { - $logger = $this->container->getLogger('anilist-request'); - $logger->debug('Anilist response', [ - 'status' => $response->getStatus(), - 'reason' => $response->getReason(), - 'body' => $response->getBody(), - 'headers' => $response->getHeaders(), - //'requestHeaders' => $request->getHeaders(), - ]); - } - - if ( ! \in_array($response->getStatus(), $validResponseCodes, TRUE)) - { - if ($logger !== NULL) - { - $logger->warning('Non 200 response for POST api call', (array)$response->getBody()); - } - } - - // dump(wait($response->getBody()->buffer())); - - return Json::decode(wait($response->getBody()->buffer())); - } } \ No newline at end of file diff --git a/src/AnimeClient/API/Anilist/ListItem.php b/src/AnimeClient/API/Anilist/ListItem.php index c01a6f6f..d17b897e 100644 --- a/src/AnimeClient/API/Anilist/ListItem.php +++ b/src/AnimeClient/API/Anilist/ListItem.php @@ -38,7 +38,7 @@ final class ListItem extends AbstractListItem { public function create(array $data): Request { $checkedData = Types\MediaListEntry::check($data); - return $this->mutateRequest('CreateMediaListEntry', $checkedData); + return $this->requestBuilder->mutateRequest('CreateMediaListEntry', $checkedData); } /** @@ -50,7 +50,7 @@ final class ListItem extends AbstractListItem { public function createFull(array $data): Request { $checkedData = Types\MediaListEntry::check($data); - return $this->mutateRequest('CreateFullMediaListEntry', $checkedData); + return $this->requestBuilder->mutateRequest('CreateFullMediaListEntry', $checkedData); } /** @@ -62,7 +62,7 @@ final class ListItem extends AbstractListItem { */ public function delete(string $id, string $type = 'anime'): Request { - return $this->mutateRequest('DeleteMediaListEntry', ['id' => $id]); + return $this->requestBuilder->mutateRequest('DeleteMediaListEntry', ['id' => $id]); } /** @@ -73,7 +73,7 @@ final class ListItem extends AbstractListItem { */ public function get(string $id): array { - return $this->runQuery('MediaListItem', ['id' => $id]); + return $this->requestBuilder->runQuery('MediaListItem', ['id' => $id]); } /** @@ -90,7 +90,7 @@ final class ListItem extends AbstractListItem { 'progress' => $data->progress, ]); - return $this->mutateRequest('IncrementMediaListEntry', $checkedData); + return $this->requestBuilder->mutateRequest('IncrementMediaListEntry', $checkedData); } /** @@ -120,6 +120,6 @@ final class ListItem extends AbstractListItem { 'notes' => $notes, ]); - return $this->mutateRequest('UpdateMediaListEntry', $updateData); + return $this->requestBuilder->mutateRequest('UpdateMediaListEntry', $updateData); } } \ No newline at end of file diff --git a/src/AnimeClient/API/Anilist/Model.php b/src/AnimeClient/API/Anilist/Model.php index e0297bfa..9a7ddded 100644 --- a/src/AnimeClient/API/Anilist/Model.php +++ b/src/AnimeClient/API/Anilist/Model.php @@ -89,7 +89,7 @@ final class Model */ public function checkAuth(): array { - return $this->runQuery('CheckLogin'); + return $this->requestBuilder->runQuery('CheckLogin'); } /** @@ -110,7 +110,7 @@ final class Model throw new InvalidArgumentException('Anilist username is not defined in config'); } - return $this->runQuery('SyncUserList', [ + return $this->requestBuilder->runQuery('SyncUserList', [ 'name' => $anilistUser, 'type' => $type, ]); @@ -275,17 +275,25 @@ final class Model * Get the Anilist list item id from the media id from its MAL id * this way is more accurate than getting the list item id * directly from the MAL id + * + * @param string $mediaId + * @return string|null */ - private function getListIdFromMediaId(string $mediaId): string + private function getListIdFromMediaId(string $mediaId): ?string { $config = $this->container->get('config'); $anilistUser = $config->get(['anilist', 'username']); - $info = $this->runQuery('ListItemIdByMediaId', [ + $info = $this->requestBuilder->runQuery('ListItemIdByMediaId', [ 'id' => $mediaId, 'userName' => $anilistUser, ]); + if ( ! empty($info['errors'])) + { + return NULL; + } + return (string)$info['data']['MediaList']['id']; } @@ -303,7 +311,7 @@ final class Model return NULL; } - $info = $this->runQuery('MediaIdByMalId', [ + $info = $this->requestBuilder->runQuery('MediaIdByMalId', [ 'id' => $malId, 'type' => mb_strtoupper($type), ]); diff --git a/src/AnimeClient/API/Kitsu/KitsuRequestBuilder.php b/src/AnimeClient/API/Kitsu/KitsuRequestBuilder.php index 27fdb7a2..65c6507a 100644 --- a/src/AnimeClient/API/Kitsu/KitsuRequestBuilder.php +++ b/src/AnimeClient/API/Kitsu/KitsuRequestBuilder.php @@ -388,6 +388,8 @@ final class KitsuRequestBuilder extends APIRequestBuilder { Event::emit(EventType::UNAUTHORIZED); } + $rawBody = wait($response->getBody()->buffer()); + // Any other type of failed request if ($statusCode > 299 || $statusCode < 200) { diff --git a/src/AnimeClient/API/Kitsu/Queries/AnimeDetails.graphql b/src/AnimeClient/API/Kitsu/Queries/AnimeDetails.graphql index 66750d88..fe2728b7 100644 --- a/src/AnimeClient/API/Kitsu/Queries/AnimeDetails.graphql +++ b/src/AnimeClient/API/Kitsu/Queries/AnimeDetails.graphql @@ -82,7 +82,7 @@ query ($slug: String!) { canonical localized } - #slug + slug } role } diff --git a/src/AnimeClient/API/Kitsu/Queries/AnimeDetailsById.graphql b/src/AnimeClient/API/Kitsu/Queries/AnimeDetailsById.graphql index c8706649..12b74675 100644 --- a/src/AnimeClient/API/Kitsu/Queries/AnimeDetailsById.graphql +++ b/src/AnimeClient/API/Kitsu/Queries/AnimeDetailsById.graphql @@ -82,7 +82,7 @@ query ($id: ID!) { canonical localized } - #slug + slug } role } diff --git a/src/AnimeClient/API/Kitsu/Queries/MangaDetails.graphql b/src/AnimeClient/API/Kitsu/Queries/MangaDetails.graphql index 9a6f5500..5c3dfb09 100644 --- a/src/AnimeClient/API/Kitsu/Queries/MangaDetails.graphql +++ b/src/AnimeClient/API/Kitsu/Queries/MangaDetails.graphql @@ -93,7 +93,7 @@ query ($slug: String!) { canonical localized } - #slug + slug } role } diff --git a/src/AnimeClient/API/Kitsu/Queries/MangaDetailsById.graphql b/src/AnimeClient/API/Kitsu/Queries/MangaDetailsById.graphql index 89e97b6c..a21b4087 100644 --- a/src/AnimeClient/API/Kitsu/Queries/MangaDetailsById.graphql +++ b/src/AnimeClient/API/Kitsu/Queries/MangaDetailsById.graphql @@ -93,7 +93,7 @@ query ($id: ID!) { canonical localized } - #slug + slug } role } diff --git a/src/AnimeClient/API/Kitsu/Transformer/AnimeTransformer.php b/src/AnimeClient/API/Kitsu/Transformer/AnimeTransformer.php index a0e091fe..2f6263e4 100644 --- a/src/AnimeClient/API/Kitsu/Transformer/AnimeTransformer.php +++ b/src/AnimeClient/API/Kitsu/Transformer/AnimeTransformer.php @@ -94,6 +94,7 @@ final class AnimeTransformer extends AbstractTransformer { 'image' => [ 'original' => $person['image']['original']['url'], ], + 'slug' => $person['slug'], ]; usort($staff[$role], fn ($a, $b) => $a['name'] <=> $b['name']); diff --git a/src/AnimeClient/API/Kitsu/Transformer/MangaTransformer.php b/src/AnimeClient/API/Kitsu/Transformer/MangaTransformer.php index afc84d3a..e03bcc1a 100644 --- a/src/AnimeClient/API/Kitsu/Transformer/MangaTransformer.php +++ b/src/AnimeClient/API/Kitsu/Transformer/MangaTransformer.php @@ -87,6 +87,7 @@ final class MangaTransformer extends AbstractTransformer { $staff[$role][$person['id']] = [ 'id' => $person['id'], + 'slug' => $person['slug'], 'name' => $name, 'image' => [ 'original' => $person['image']['original']['url'], diff --git a/src/AnimeClient/Command/BaseCommand.php b/src/AnimeClient/Command/BaseCommand.php index 64f474b5..8887cacb 100644 --- a/src/AnimeClient/Command/BaseCommand.php +++ b/src/AnimeClient/Command/BaseCommand.php @@ -147,13 +147,13 @@ abstract class BaseCommand extends Command { // ------------------------------------------------------------------------- $app_logger = new Logger('animeclient'); - $app_logger->pushHandler(new RotatingFileHandler($APP_DIR . '/logs/app-cli.log', Logger::NOTICE)); + $app_logger->pushHandler(new RotatingFileHandler($APP_DIR . '/logs/app-cli.log', Logger::WARNING)); $kitsu_request_logger = new Logger('kitsu-request'); - $kitsu_request_logger->pushHandler(new RotatingFileHandler($APP_DIR . '/logs/kitsu_request-cli.log', Logger::NOTICE)); + $kitsu_request_logger->pushHandler(new RotatingFileHandler($APP_DIR . '/logs/kitsu_request-cli.log', Logger::WARNING)); $anilistRequestLogger = new Logger('anilist-request'); - $anilistRequestLogger->pushHandler(new RotatingFileHandler($APP_DIR . '/logs/anilist_request-cli.log', Logger::NOTICE)); + $anilistRequestLogger->pushHandler(new RotatingFileHandler($APP_DIR . '/logs/anilist_request-cli.log', Logger::WARNING)); $container->setLogger($app_logger); $container->setLogger($anilistRequestLogger, 'anilist-request'); @@ -203,7 +203,7 @@ abstract class BaseCommand extends Command { return $model; }); $container->set('anilist-model', static function ($container): Anilist\Model { - $requestBuilder = new Anilist\AnilistRequestBuilder(); + $requestBuilder = new Anilist\AnilistRequestBuilder($container); $requestBuilder->setLogger($container->getLogger('anilist-request')); $listItem = new Anilist\ListItem(); diff --git a/src/AnimeClient/Controller/People.php b/src/AnimeClient/Controller/People.php index cffe6f84..b6c7bb41 100644 --- a/src/AnimeClient/Controller/People.php +++ b/src/AnimeClient/Controller/People.php @@ -51,11 +51,14 @@ final class People extends BaseController { * Show information about a person * * @param string $id + * @param string|null $slug * @return void + * @throws ContainerException + * @throws NotFoundException */ - public function index(string $id): void + public function index(string $id, ?string $slug = NULL): void { - $rawData = $this->model->getPerson($id); + $rawData = $this->model->getPerson($id, $slug); $data = (new PersonTransformer())->transform($rawData)->toArray(); if (( ! array_key_exists('data', $rawData)) || empty($rawData['data']))