Get Manga details from GraphQL, See #27

This commit is contained in:
Timothy Warren 2020-07-29 17:51:58 -04:00
parent 766e3cd71c
commit 83de995951
8 changed files with 403 additions and 113 deletions

View File

@ -7,6 +7,10 @@
<table class="media-details"> <table class="media-details">
<tr> <tr>
<td class="align-right">Publishing Status</td>
<td><?= $data['status'] ?></td>
</tr>
<?php /* <tr>
<td>Manga Type</td> <td>Manga Type</td>
<td><?= ucfirst($data['manga_type']) ?></td> <td><?= ucfirst($data['manga_type']) ?></td>
</tr> </tr>
@ -17,7 +21,15 @@
<tr> <tr>
<td>Chapter Count</td> <td>Chapter Count</td>
<td><?= $data['chapter_count'] ?></td> <td><?= $data['chapter_count'] ?></td>
</tr> */ ?>
<?php if ( ! empty($data['age_rating'])): ?>
<tr>
<td>Age Rating</td>
<td><abbr title="<?= $data['age_rating_guide'] ?>"><?= $data['age_rating'] ?></abbr>
</td>
</tr> </tr>
<?php endif ?>
<tr> <tr>
<td>Genres</td> <td>Genres</td>
<td> <td>
@ -82,16 +94,16 @@
type="radio" name="staff-roles" id="staff-role<?= $i ?>" <?= $i === 0 ? 'checked' : '' ?> /> type="radio" name="staff-roles" id="staff-role<?= $i ?>" <?= $i === 0 ? 'checked' : '' ?> />
<label for="staff-role<?= $i ?>"><?= $role ?></label> <label for="staff-role<?= $i ?>"><?= $role ?></label>
<section class='content media-wrap flex flex-wrap flex-justify-start'> <section class='content media-wrap flex flex-wrap flex-justify-start'>
<?php foreach ($people as $pid => $person): ?> <?php foreach ($people as $person): ?>
<article class='character person'> <article class='character person'>
<?php $link = $url->generate('person', ['id' => $pid]) ?> <?php $link = $url->generate('person', ['id' => $person['id']]) ?>
<div class="name"> <div class="name">
<a href="<?= $link ?>"> <a href="<?= $link ?>">
<?= $person['name'] ?> <?= $person['name'] ?>
</a> </a>
</div> </div>
<a href="<?= $link ?>"> <a href="<?= $link ?>">
<?= $helper->picture("images/people/{$pid}.webp") ?> <?= $helper->picture("images/people/{$person['id']}.webp") ?>
</a> </a>
</article> </article>
<?php endforeach ?> <?php endforeach ?>

View File

@ -17,6 +17,7 @@
namespace Aviat\AnimeClient\API; namespace Aviat\AnimeClient\API;
use Aviat\AnimeClient\API\Kitsu\Enum\AnimeAiringStatus; use Aviat\AnimeClient\API\Kitsu\Enum\AnimeAiringStatus;
use Aviat\AnimeClient\API\Kitsu\Enum\MangaPublishingStatus;
use DateTimeImmutable; use DateTimeImmutable;
/** /**
@ -65,6 +66,28 @@ final class Kitsu {
return AnimeAiringStatus::NOT_YET_AIRED; return AnimeAiringStatus::NOT_YET_AIRED;
} }
public static function getPublishingStatus(string $kitsuStatus, string $startDate = NULL, string $endDate = NULL): string
{
$startPubDate = new DateTimeImmutable($startDate ?? 'tomorrow');
$endPubDate = new DateTimeImmutable($endDate ?? 'next year');
$now = new DateTimeImmutable();
$isDone = $now > $endPubDate;
$isCurrent = ($now > $startPubDate) && ! $isDone;
if ($kitsuStatus === 'CURRENT' || $isCurrent)
{
return MangaPublishingStatus::CURRENT;
}
if ($kitsuStatus === 'FINISHED' || $isDone)
{
return MangaPublishingStatus::FINISHED;
}
return MangaPublishingStatus::NOT_YET_PUBLISHED;
}
/** /**
* Reorganize streaming links * Reorganize streaming links
* *

View File

@ -0,0 +1,29 @@
<?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
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
*/
namespace Aviat\AnimeClient\API\Kitsu\Enum;
use Aviat\Ion\Enum as BaseEnum;
/**
* Status of when anime is being/was/will be aired
*/
final class MangaPublishingStatus extends BaseEnum {
public const NOT_YET_PUBLISHED = 'Not Yet Published';
public const FINISHED = 'Completed';
public const CURRENT = 'Current';
}
// End of MangaPublishingStatus

View File

@ -0,0 +1,112 @@
query ($slug: String!) {
findMangaBySlug(slug: $slug) {
id
ageRating
ageRatingGuide
posterImage {
original {
height
name
url
width
}
views {
height
name
url
width
}
}
categories {
nodes {
title
}
}
characters {
nodes {
character {
id
names {
canonical
alternatives
}
image {
original {
height
name
url
width
}
}
slug
}
role
}
pageInfo {
endCursor
hasNextPage
hasPreviousPage
startCursor
}
}
startDate
endDate
posterImage {
original {
height
name
url
width
}
views {
height
name
url
width
}
}
sfw
slug
staff {
nodes {
person {
id
birthday
image {
original {
height
name
url
width
}
views {
height
name
url
width
}
}
names {
alternatives
canonical
localized
}
}
role
}
pageInfo {
endCursor
hasNextPage
hasPreviousPage
startCursor
}
}
status
synopsis
titles {
canonical
canonicalLocale
localized
}
}
}

View File

@ -0,0 +1,112 @@
query ($id: ID!) {
findMangaById(id: $id) {
id
ageRating
ageRatingGuide
posterImage {
original {
height
name
url
width
}
views {
height
name
url
width
}
}
categories {
nodes {
title
}
}
characters {
nodes {
character {
id
names {
canonical
alternatives
}
image {
original {
height
name
url
width
}
}
slug
}
role
}
pageInfo {
endCursor
hasNextPage
hasPreviousPage
startCursor
}
}
startDate
endDate
posterImage {
original {
height
name
url
width
}
views {
height
name
url
width
}
}
sfw
slug
staff {
nodes {
person {
id
birthday
image {
original {
height
name
url
width
}
views {
height
name
url
width
}
}
names {
alternatives
canonical
localized
}
}
role
}
pageInfo {
endCursor
hasNextPage
hasPreviousPage
startCursor
}
}
status
synopsis
titles {
canonical
canonicalLocale
localized
}
}
}

View File

@ -56,7 +56,10 @@ trait KitsuMangaTrait {
*/ */
public function getManga(string $slug): MangaPage public function getManga(string $slug): MangaPage
{ {
$baseData = $this->getRawMediaData('manga', $slug); $baseData = $this->requestBuilder->runQuery('MangaDetails', [
'slug' => $slug
]);
// $baseData = $this->getRawMediaData('manga', $slug);
if (empty($baseData)) if (empty($baseData))
{ {
@ -66,6 +69,21 @@ trait KitsuMangaTrait {
return $this->mangaTransformer->transform($baseData); return $this->mangaTransformer->transform($baseData);
} }
/**
* Get information about a particular manga
*
* @param string $mangaId
* @return MangaPage
*/
public function getMangaById(string $mangaId): MangaPage
{
$baseData = $this->requestBuilder->runQuery('MangaDetailsById', [
'id' => $mangaId,
]);
// $baseData = $this->getRawMediaDataById('manga', $mangaId);
return $this->mangaTransformer->transform($baseData);
}
/** /**
* Retrieve the data for the manga read history page * Retrieve the data for the manga read history page
* *
@ -92,18 +110,6 @@ trait KitsuMangaTrait {
return $list; return $list;
} }
/**
* Get information about a particular manga
*
* @param string $mangaId
* @return MangaPage
*/
public function getMangaById(string $mangaId): MangaPage
{
$baseData = $this->getRawMediaDataById('manga', $mangaId);
return $this->mangaTransformer->transform($baseData);
}
/** /**
* Get the manga list for the configured user * Get the manga list for the configured user
* *

View File

@ -16,7 +16,7 @@
namespace Aviat\AnimeClient\API\Kitsu\Transformer; namespace Aviat\AnimeClient\API\Kitsu\Transformer;
use Aviat\AnimeClient\API\JsonAPI; use Aviat\AnimeClient\API\Kitsu;
use Aviat\AnimeClient\Types\MangaPage; use Aviat\AnimeClient\Types\MangaPage;
use Aviat\Ion\Transformer\AbstractTransformer; use Aviat\Ion\Transformer\AbstractTransformer;
@ -34,106 +34,90 @@ final class MangaTransformer extends AbstractTransformer {
*/ */
public function transform($item): MangaPage public function transform($item): MangaPage
{ {
$genres = []; // TODO: missing GraphQL data:
// * chapter count
// * volume count
// * manga type
$item['included'] = JsonAPI::organizeIncluded($item['included']); $base = array_key_exists('findMangaBySlug', $item['data'])
? $item['data']['findMangaBySlug']
if (array_key_exists('categories', $item['included'])) : $item['data']['findMangaById'];
{
foreach ($item['included']['categories'] as $cat)
{
$genres[] = $cat['attributes']['title'];
}
sort($genres);
}
$title = $item['canonicalTitle'];
$rawTitles = array_values($item['titles']);
$titles = array_unique(array_diff($rawTitles, [$title]));
$characters = []; $characters = [];
$staff = []; $staff = [];
$genres = array_map(fn ($genre) => $genre['title']['en'], $base['categories']['nodes']);
sort($genres);
if (array_key_exists('mediaCharacters', $item['included'])) $title = $base['titles']['canonical'];
{ $titles = Kitsu::filterLocalizedTitles($base['titles']);
$mediaCharacters = $item['included']['mediaCharacters'];
foreach ($mediaCharacters as $rel) if (count($base['characters']['nodes']) > 0)
{ {
// dd($rel); $characters['main'] = [];
// $charId = $rel['relationships']['character']['data']['id']; $characters['supporting'] = [];
$role = $rel['attributes']['role'];
foreach ($rel['relationships']['character']['characters'] as $charId => $char) foreach ($base['characters']['nodes'] as $rawCharacter)
{ {
if (array_key_exists($charId, $item['included']['characters'])) $type = $rawCharacter['role'] === 'MAIN' ? 'main' : 'supporting';
{ $details = $rawCharacter['character'];
$characters[$role][$charId] = $char['attributes']; $characters[$type][$details['id']] = [
} 'image' => $details['image'],
'name' => $details['names']['canonical'],
'slug' => $details['slug'],
];
} }
uasort($characters['main'], fn($a, $b) => $a['name'] <=> $b['name']);
uasort($characters['supporting'], fn($a, $b) => $a['name'] <=> $b['name']);
if (empty($characters['supporting']))
{
unset($characters['supporting']);
} }
} }
if (array_key_exists('mediaStaff', $item['included'])) if (count($base['staff']['nodes']) > 0)
{ {
foreach ($item['included']['mediaStaff'] as $id => $staffing) foreach ($base['staff']['nodes'] as $staffing)
{ {
$role = $staffing['attributes']['role']; $person = $staffing['person'];
$role = $staffing['role'];
$name = $person['names']['localized'][$person['names']['canonical']];
foreach ($staffing['relationships']['person']['people'] as $personId => $personDetails)
{
if ( ! array_key_exists($role, $staff)) if ( ! array_key_exists($role, $staff))
{ {
$staff[$role] = []; $staff[$role] = [];
} }
$staff[$role][$personId] = [ $staff[$role][$person['id']] = [
'id' => $personId, 'id' => $person['id'],
'name' => $personDetails['attributes']['name'] ?? '??', 'name' => $name,
'image' => $personDetails['attributes']['image'], 'image' => [
'original' => $person['image']['original']['url'],
],
]; ];
}
} usort($staff[$role], fn ($a, $b) => $a['name'] <=> $b['name']);
} }
if ( ! empty($characters['main']))
{
uasort($characters['main'], fn ($a, $b) => $a['name'] <=> $b['name']);
}
if ( ! empty($characters['supporting']))
{
uasort($characters['supporting'], fn ($a, $b) => $a['name'] <=> $b['name']);
}
ksort($characters);
ksort($staff); ksort($staff);
}
return MangaPage::from([ $data = [
'age_rating' => $base['ageRating'],
'age_rating_guide' => $base['ageRatingGuide'],
'characters' => $characters, 'characters' => $characters,
'chapter_count' => $this->count($item['chapterCount']), 'cover_image' => $base['posterImage']['views'][1]['url'],
'cover_image' => $item['posterImage']['small'],
'genres' => $genres, 'genres' => $genres,
'id' => $item['id'], 'id' => $base['id'],
'included' => $item['included'],
'manga_type' => $item['mangaType'],
'staff' => $staff, 'staff' => $staff,
'synopsis' => $item['synopsis'], 'status' => Kitsu::getPublishingStatus($base['status'], $base['startDate'], $base['endDate']),
'synopsis' => $base['synopsis']['en'],
'title' => $title, 'title' => $title,
'titles' => $titles, 'titles' => $titles,
'url' => "https://kitsu.io/manga/{$item['slug']}", 'url' => "https://kitsu.io/manga/{$base['slug']}",
'volume_count' => $this->count($item['volumeCount']), ];
]);
}
/** return MangaPage::from($data);
* @param int|null $value
* @return string
*/
private function count(int $value = NULL): string
{
return ((int)$value === 0)
? '-'
: (string)$value;
} }
} }

View File

@ -16,10 +16,22 @@
namespace Aviat\AnimeClient\Types; namespace Aviat\AnimeClient\Types;
use Aviat\AnimeClient\API\Kitsu\Enum\MangaPublishingStatus;
/** /**
* Type representing an Anime object for display * Type representing an Anime object for display
*/ */
final class MangaPage extends AbstractType { final class MangaPage extends AbstractType {
/**
* @var string
*/
public ?string $age_rating;
/**
* @var string
*/
public ?string $age_rating_guide;
/** /**
* @var array * @var array
*/ */
@ -45,43 +57,43 @@ final class MangaPage extends AbstractType {
*/ */
public $id; public $id;
/**
* @var array
*/
public $included;
/** /**
* @var string * @var string
*/ */
public $manga_type; public $manga_type;
/** /**
* @var array * @var MangaPublishingStatus
*/ */
public $staff; public string $status = MangaPublishingStatus::FINISHED;
/**
* @var string
*/
public $synopsis;
/**
* @var string
*/
public $title;
/** /**
* @var array * @var array
*/ */
public $titles; public array $staff;
/** /**
* @var string * @var string
*/ */
public $url; public string $synopsis;
/**
* @var string
*/
public string $title;
/**
* @var array
*/
public array $titles;
/**
* @var string
*/
public string $url;
/** /**
* @var int * @var int
*/ */
public $volume_count; public ?int $volume_count;
} }