Get library entry via GraphQL, see #28

This commit is contained in:
Timothy Warren 2020-08-24 13:07:47 -04:00
parent a4cde0b28d
commit 7d6af5ad00
9 changed files with 304 additions and 157 deletions

View File

@ -115,21 +115,9 @@ final class ListItem extends AbstractListItem {
*/
public function get(string $id): array
{
$authHeader = $this->getAuthHeader();
$request = $this->requestBuilder->newRequest('GET', "library-entries/{$id}")
->setQuery([
'include' => 'media,media.categories,media.mappings'
]);
if ($authHeader !== NULL)
{
$request = $request->setHeader('Authorization', $authHeader);
}
$request = $request->getFullRequest();
$response = getResponse($request);
return Json::decode(wait($response->getBody()->buffer()));
return $this->requestBuilder->runQuery('GetLibraryItem', [
'id' => $id,
]);
}
/**

View File

@ -59,7 +59,6 @@ trait MangaTrait {
$baseData = $this->requestBuilder->runQuery('MangaDetails', [
'slug' => $slug
]);
// $baseData = $this->getRawMediaData('manga', $slug);
if (empty($baseData))
{
@ -80,7 +79,6 @@ trait MangaTrait {
$baseData = $this->requestBuilder->runQuery('MangaDetailsById', [
'id' => $mangaId,
]);
// $baseData = $this->getRawMediaDataById('manga', $mangaId);
return $this->mangaTransformer->transform($baseData);
}

View File

@ -28,6 +28,7 @@ use Aviat\AnimeClient\API\{
use Aviat\AnimeClient\API\Kitsu\Transformer\{
AnimeTransformer,
AnimeListTransformer,
LibraryEntryTransformer,
MangaTransformer,
MangaListTransformer
};
@ -331,24 +332,12 @@ final class Model {
public function getListItem(string $listId)
{
$baseData = $this->listItem->get($listId);
$included = JsonAPI::organizeIncludes($baseData['included']);
if (array_key_exists('anime', $included))
if ( ! isset($baseData['data']['findLibraryEntryById']))
{
$included = JsonAPI::inlineIncludedRelationships($included, 'anime');
$baseData['data']['included'] = $included;
return $this->animeListTransformer->transform($baseData['data']);
return [];
}
if (array_key_exists('manga', $included))
{
$included = JsonAPI::inlineIncludedRelationships($included, 'manga');
$baseData['data']['included'] = $included;
$baseData['data']['manga'] = $baseData['included'][0];
return $this->mangaListTransformer->transform($baseData['data']);
}
return $baseData['data'];
return (new LibraryEntryTransformer())->transform($baseData['data']['findLibraryEntryById']);
}
/**
@ -464,76 +453,6 @@ final class Model {
->get(['kitsu_username']);
}
/**
* Get the raw data for the anime/manga id
*
* @param string $type
* @param string $id
* @return array
*/
private function getRawMediaDataById(string $type, string $id): array
{
$options = [
'query' => [
'include' => ($type === 'anime')
? 'categories,mappings,streamingLinks'
: 'categories,mappings',
]
];
$data = $this->requestBuilder->getRequest("{$type}/{$id}", $options);
if (empty($data['data']))
{
return [];
}
$baseData = $data['data']['attributes'];
$baseData['id'] = $id;
$baseData['included'] = $data['included'];
return $baseData;
}
/**
* Get media item by slug
*
* @param string $type
* @param string $slug
* @return array
*/
private function getRawMediaData(string $type, string $slug): array
{
$options = [
'query' => [
'filter' => [
'slug' => $slug
],
'fields' => [
'categories' => 'slug,title',
'characters' => 'slug,name,image',
'mappings' => 'externalSite,externalId',
'animeCharacters' => 'character,role',
'mediaCharacters' => 'character,role',
],
'include' => ($type === 'anime')
? 'staff,staff.person,categories,mappings,streamingLinks,animeCharacters.character,characters.character'
: 'staff,staff.person,categories,mappings,characters.character',
]
];
$data = $this->requestBuilder->getRequest($type, $options);
if (empty($data['data']))
{
return [];
}
$baseData = $data['data'][0]['attributes'];
$baseData['id'] = $data['data'][0]['id'];
$baseData['included'] = $data['included'];
return $baseData;
}
private function getListCount(string $type, string $status = ''): int
{
$options = [

View File

@ -13,17 +13,6 @@ query ($slug: String!) {
canonicalLocale
localized
},
primaryMedia {
posterImage {
original {
url
}
}
titles {
canonical
}
type
},
media {
nodes {
media {

View File

@ -0,0 +1,73 @@
query($id: ID!) {
findLibraryEntryById(id: $id) {
id
updatedAt
notes
nsfw
private
progress
reconsumeCount
reconsuming
status
rating
media {
id
slug
ageRating
categories {
nodes {
title
}
}
mappings {
nodes {
externalId
externalSite
}
}
posterImage {
views {
width
height
url
}
original {
width
height
url
}
}
startDate
endDate
titles {
canonical
localized
canonicalLocale
}
type
...on Anime {
episodeCount
episodeLength
streamingLinks {
nodes {
dubs
subs
regions
streamer {
id
siteName
}
url
}
}
subtype
}
...on Manga {
chapterCount
volumeCount
subtype
}
}
}
}

View File

@ -255,7 +255,7 @@ final class RequestBuilder extends APIRequestBuilder {
public function mutate(string $name, array $variables = []): array
{
$request = $this->mutateRequest($name, $variables);
$response = $this->getResponseFromRequest($request);
$response = getResponse($request);
return Json::decode(wait($response->getBody()->buffer()));
}
@ -267,7 +267,7 @@ final class RequestBuilder extends APIRequestBuilder {
* @param string $url
* @param array $options
* @return Response
* @throws Throwable
* @throws \Throwable
*/
public function getResponse(string $type, string $url, array $options = []): Response
{
@ -286,27 +286,6 @@ final class RequestBuilder extends APIRequestBuilder {
return $response;
}
/**
* @param Request $request
* @return Response
* @throws Throwable
*/
private function getResponseFromRequest(Request $request): Response
{
$logger = $this->container->getLogger('kitsu-request');
$response = getResponse($request);
$logger->debug('Kitsu GraphQL response', [
'status' => $response->getStatus(),
'reason' => $response->getReason(),
'body' => $response->getBody(),
'headers' => $response->getHeaders(),
'requestHeaders' => $request->getHeaders(),
]);
return $response;
}
/**
* Remove some boilerplate for GraphQL requests
*

View File

@ -34,9 +34,6 @@ final class AnimeTransformer extends AbstractTransformer {
*/
public function transform($item): AnimePage
{
// TODO: missing GraphQL data:
// * streaming links
$base = array_key_exists('findAnimeBySlug', $item['data'])
? $item['data']['findAnimeBySlug']
: $item['data']['findAnimeById'];
@ -52,12 +49,14 @@ final class AnimeTransformer extends AbstractTransformer {
if (count($base['characters']['nodes']) > 0)
{
$characters['main'] = [];
$characters['supporting'] = [];
foreach ($base['characters']['nodes'] as $rawCharacter)
{
$type = $rawCharacter['role'] === 'MAIN' ? 'main' : 'supporting';
$type = mb_strtolower($rawCharacter['role']);
if ( ! isset($characters[$type]))
{
$characters[$type] = [];
}
$details = $rawCharacter['character'];
$characters[$type][$details['id']] = [
'image' => $details['image'],
@ -66,13 +65,19 @@ final class AnimeTransformer extends AbstractTransformer {
];
}
uasort($characters['main'], fn($a, $b) => $a['name'] <=> $b['name']);
uasort($characters['supporting'], fn($a, $b) => $a['name'] <=> $b['name']);
if (empty($characters['supporting']))
foreach (array_keys($characters) as $type)
{
unset($characters['supporting']);
if (empty($characters[$type]))
{
unset($characters[$type]);
}
else
{
uasort($characters[$type], fn($a, $b) => $a['name'] <=> $b['name']);
}
}
krsort($characters);
}
if (count($base['staff']['nodes']) > 0)

View File

@ -0,0 +1,188 @@
<?php declare(strict_types=1);
/**
* Hummingbird Anime List Client
*
* An API client for Kitsu to manage anime and manga watch lists
*
* PHP version 7.4
*
* @package HummingbirdAnimeClient
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2020 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.1
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
*/
namespace Aviat\AnimeClient\API\Kitsu\Transformer;
use Aviat\AnimeClient\API\Kitsu;
use Aviat\AnimeClient\Types\{FormItem, AnimeListItem, MangaListItem, MangaListItemDetail};
use Aviat\Ion\Transformer\AbstractTransformer;
use Aviat\Ion\Type\StringType;
/**
* Transformer for anime list
*/
final class LibraryEntryTransformer extends AbstractTransformer
{
public function transform($item)
{
$type = $item['media']['type'] ?? '';
$genres = [];
if ($type !== '')
{
$genres = array_column($item['media']['categories']['nodes'], 'title');
sort($genres);
}
switch (strtolower($type))
{
case 'anime':
return $this->animeTransform($item, $genres);
case 'manga':
return $this->mangaTransform($item, $genres);
default:
return [];
}
}
private function animeTransform($item, array $genres): AnimeListItem
{
$animeId = $item['media']['id'];
$anime = $item['media'];
$rating = (int) $item['rating'] !== 0
? $item['rating'] / 2
: '-';
$total_episodes = array_key_exists('episodeCount', $anime) && (int) $anime['episodeCount'] !== 0
? (int) $anime['episodeCount']
: '-';
$MALid = NULL;
if (isset($anime['mappings']['nodes']))
{
foreach ($anime['mappings']['nodes'] as $mapping)
{
if ($mapping['externalSite'] === 'MYANIMELIST_ANIME')
{
$MALid = $mapping['externalId'];
break;
}
}
}
$streamingLinks = array_key_exists('nodes', $anime['streamingLinks'])
? Kitsu::parseStreamingLinks($anime['streamingLinks']['nodes'])
: [];
$titles = Kitsu::getFilteredTitles($anime['titles']);
$title = $anime['titles']['canonical'];
return AnimeListItem::from([
'id' => $item['id'],
'mal_id' => $MALid,
'episodes' => [
'watched' => (int) $item['progress'] !== 0
? (int) $item['progress']
: '-',
'total' => $total_episodes,
'length' => $anime['episodeLength'],
],
'airing' => [
'status' => Kitsu::getAiringStatus($anime['startDate'], $anime['endDate']),
'started' => $anime['startDate'],
'ended' => $anime['endDate']
],
'anime' => [
'id' => $animeId,
'age_rating' => $anime['ageRating'],
'title' => $title,
'titles' => $titles,
'slug' => $anime['slug'],
'show_type' => (string)StringType::from($anime['subtype'])->upperCaseFirst(),
'cover_image' => $anime['posterImage']['views'][1]['url'],
'genres' => $genres,
'streaming_links' => $streamingLinks,
],
'watching_status' => $item['status'],
'notes' => $item['notes'],
'rewatching' => (bool) $item['reconsuming'],
'rewatched' => (int) $item['reconsumeCount'],
'user_rating' => $rating,
'private' => $item['private'] ?? FALSE,
]);
}
private function mangaTransform($item, array $genres): MangaListItem
{
$mangaId = $item['media']['id'];
$manga = $item['media'];
$rating = (int) $item['rating'] !== 0
? $item['rating'] / 2
: '-';
$totalChapters = ((int) $manga['chapterCount'] !== 0)
? $manga['chapterCount']
: '-';
$totalVolumes = ((int) $manga['volumeCount'] !== 0)
? $manga['volumeCount']
: '-';
$readChapters = ((int) $item['progress'] !== 0)
? $item['progress']
: '-';
$MALid = NULL;
if (isset($manga['mappings']['nodes']))
{
foreach ($manga['mappings']['nodes'] as $mapping)
{
if ($mapping['externalSite'] === 'MYANIMELIST_MANGA')
{
$MALid = $mapping['externalId'];
break;
}
}
}
$titles = Kitsu::getFilteredTitles($manga['titles']);
$title = $manga['titles']['canonical'];
return MangaListItem::from([
'id' => $item['id'],
'mal_id' => $MALid,
'chapters' => [
'read' => $readChapters,
'total' => $totalChapters
],
'volumes' => [
'read' => '-', //$item['attributes']['volumes_read'],
'total' => $totalVolumes
],
'manga' => MangaListItemDetail::from([
'genres' => $genres,
'id' => $mangaId,
'image' => $manga['posterImage']['views'][1]['url'],
'slug' => $manga['slug'],
'title' => $title,
'titles' => $titles,
'type' => (string)StringType::from($manga['subtype'])->upperCaseFirst(),
'url' => 'https://kitsu.io/manga/' . $manga['slug'],
]),
'reading_status' => strtolower($item['status']),
'notes' => $item['notes'],
'rereading' => (bool)$item['reconsuming'],
'reread' => $item['reconsumeCount'],
'user_rating' => $rating,
]);
}
}

View File

@ -49,12 +49,14 @@ final class MangaTransformer extends AbstractTransformer {
if (count($base['characters']['nodes']) > 0)
{
$characters['main'] = [];
$characters['supporting'] = [];
foreach ($base['characters']['nodes'] as $rawCharacter)
{
$type = $rawCharacter['role'] === 'MAIN' ? 'main' : 'supporting';
$type = mb_strtolower($rawCharacter['role']);
if ( ! isset($characters[$type]))
{
$characters[$type] = [];
}
$details = $rawCharacter['character'];
$characters[$type][$details['id']] = [
'image' => $details['image'],
@ -63,13 +65,19 @@ final class MangaTransformer extends AbstractTransformer {
];
}
uasort($characters['main'], fn($a, $b) => $a['name'] <=> $b['name']);
uasort($characters['supporting'], fn($a, $b) => $a['name'] <=> $b['name']);
if (empty($characters['supporting']))
foreach (array_keys($characters) as $type)
{
unset($characters['supporting']);
if (empty($characters[$type]))
{
unset($characters[$type]);
}
else
{
uasort($characters[$type], fn($a, $b) => $a['name'] <=> $b['name']);
}
}
krsort($characters);
}
if (count($base['staff']['nodes']) > 0)