All in GraphQL #34
@ -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'
|
||||
return $this->requestBuilder->runQuery('GetLibraryItem', [
|
||||
'id' => $id,
|
||||
]);
|
||||
|
||||
if ($authHeader !== NULL)
|
||||
{
|
||||
$request = $request->setHeader('Authorization', $authHeader);
|
||||
}
|
||||
|
||||
$request = $request->getFullRequest();
|
||||
$response = getResponse($request);
|
||||
return Json::decode(wait($response->getBody()->buffer()));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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 = [
|
||||
|
@ -13,17 +13,6 @@ query ($slug: String!) {
|
||||
canonicalLocale
|
||||
localized
|
||||
},
|
||||
primaryMedia {
|
||||
posterImage {
|
||||
original {
|
||||
url
|
||||
}
|
||||
}
|
||||
titles {
|
||||
canonical
|
||||
}
|
||||
type
|
||||
},
|
||||
media {
|
||||
nodes {
|
||||
media {
|
||||
|
73
src/AnimeClient/API/Kitsu/Queries/GetLibraryItem.graphql
Normal file
73
src/AnimeClient/API/Kitsu/Queries/GetLibraryItem.graphql
Normal 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
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
*
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
]);
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user