Move data transformation out of controllers, and into transformers
timw4mail/HummingBirdAnimeClient/develop This commit looks good Details

This commit is contained in:
Timothy Warren 2018-11-08 11:36:42 -05:00
parent 11475187fc
commit 27977a0c8a
22 changed files with 870 additions and 601 deletions

View File

@ -2,52 +2,52 @@
<main class="details fixed">
<section class="flex">
<aside class="info">
<?= $helper->picture("images/anime/{$show_data['id']}-original.webp") ?>
<?= $helper->picture("images/anime/{$data['id']}-original.webp") ?>
<br />
<table class="media-details">
<tr>
<td class="align-right">Airing Status</td>
<td><?= $show_data['status'] ?></td>
<td><?= $data['status'] ?></td>
</tr>
<tr>
<td>Show Type</td>
<td><?= $show_data['show_type'] ?></td>
<td><?= $data['show_type'] ?></td>
</tr>
<tr>
<td>Episode Count</td>
<td><?= $show_data['episode_count'] ?? '-' ?></td>
<td><?= $data['episode_count'] ?? '-' ?></td>
</tr>
<?php if ( ! empty($show_data['episode_length'])): ?>
<?php if ( ! empty($data['episode_length'])): ?>
<tr>
<td>Episode Length</td>
<td><?= $show_data['episode_length'] ?> minutes</td>
<td><?= $data['episode_length'] ?> minutes</td>
</tr>
<?php endif ?>
<?php if ( ! empty($show_data['age_rating'])): ?>
<?php if ( ! empty($data['age_rating'])): ?>
<tr>
<td>Age Rating</td>
<td><abbr title="<?= $show_data['age_rating_guide'] ?>"><?= $show_data['age_rating'] ?></abbr>
<td><abbr title="<?= $data['age_rating_guide'] ?>"><?= $data['age_rating'] ?></abbr>
</td>
</tr>
<?php endif ?>
<tr>
<td>Genres</td>
<td>
<?= implode(', ', $show_data['genres']) ?>
<?= implode(', ', $data['genres']) ?>
</td>
</tr>
</table>
</aside>
<article class="text">
<h2 class="toph"><a rel="external" href="<?= $show_data['url'] ?>"><?= $show_data['title'] ?></a></h2>
<?php foreach ($show_data['titles'] as $title): ?>
<h2 class="toph"><a rel="external" href="<?= $data['url'] ?>"><?= $data['title'] ?></a></h2>
<?php foreach ($data['titles'] as $title): ?>
<h3><?= $title ?></h3>
<?php endforeach ?>
<br />
<p class="description"><?= nl2br($show_data['synopsis']) ?></p>
<?php if (count($show_data['streaming_links']) > 0): ?>
<p class="description"><?= nl2br($data['synopsis']) ?></p>
<?php if (count($data['streaming_links']) > 0): ?>
<hr />
<h4>Streaming on:</h4>
<table class="full-width invisible streaming-links">
@ -59,13 +59,13 @@
</tr>
</thead>
<tbody>
<?php foreach ($show_data['streaming_links'] as $link): ?>
<?php foreach ($data['streaming_links'] as $link): ?>
<tr>
<td class="align-left">
<?php if ($link['meta']['link'] !== FALSE): ?>
<a
href="<?= $link['link'] ?>"
title="Stream '<?= $show_data['title'] ?>' on <?= $link['meta']['name'] ?>"
title="Stream '<?= $data['title'] ?>' on <?= $link['meta']['name'] ?>"
>
<?= $helper->picture("images/{$link['meta']['image']}", 'svg', [
'class' => 'streaming-logo',
@ -92,13 +92,13 @@
</tbody>
</table>
<?php endif ?>
<?php if ( ! empty($show_data['trailer_id'])): ?>
<?php if ( ! empty($data['trailer_id'])): ?>
<div class="responsive-iframe">
<h4>Trailer</h4>
<iframe
width="560"
height="315"
src="https://www.youtube.com/embed/<?= $show_data['trailer_id'] ?>"
src="https://www.youtube.com/embed/<?= $data['trailer_id'] ?>"
frameborder="0"
allow="autoplay; encrypted-media"
allowfullscreen
@ -108,13 +108,13 @@
</article>
</section>
<?php if (count($characters) > 0): ?>
<?php if (count($data['characters']) > 0): ?>
<section>
<h2>Characters</h2>
<div class="tabs">
<?php $i = 0 ?>
<?php foreach ($characters as $role => $list): ?>
<?php foreach ($data['characters'] as $role => $list): ?>
<input
type="radio" name="character-types"
id="character-types-<?= $i ?>" <?= ($i === 0) ? 'checked' : '' ?> />
@ -140,14 +140,14 @@
</section>
<?php endif ?>
<?php if (count($staff) > 0): ?>
<?php //dump($staff); ?>
<?php if (count($data['staff']) > 0): ?>
<?php //dump($data['staff']); ?>
<section>
<h2>Staff</h2>
<div class="vertical-tabs">
<?php $i = 0; ?>
<?php foreach ($staff as $role => $people): ?>
<?php foreach ($data['staff'] as $role => $people): ?>
<div class="tab">
<input type="radio" name="staff-roles" id="staff-role<?= $i ?>" <?= $i === 0 ? 'checked' : '' ?> />
<label for="staff-role<?= $i ?>"><?= $role ?></label>

View File

@ -7,10 +7,10 @@ use Aviat\AnimeClient\API\Kitsu;
<main class="details fixed">
<section class="flex flex-no-wrap">
<div>
<?= $helper->picture("images/characters/{$data[0]['id']}-original.webp") ?>
<?php if ( ! empty($data[0]['attributes']['otherNames'])): ?>
<?= $helper->picture("images/characters/{$data['id']}-original.webp") ?>
<?php if ( ! empty($data['otherNames'])): ?>
<h3>Nicknames / Other names</h3>
<?php foreach ($data[0]['attributes']['otherNames'] as $name): ?>
<?php foreach ($data['otherNames'] as $name): ?>
<h4><?= $name ?></h4>
<?php endforeach ?>
<?php endif ?>
@ -23,19 +23,19 @@ use Aviat\AnimeClient\API\Kitsu;
<hr />
<p class="description"><?= $data[0]['attributes']['description'] ?></p>
<p class="description"><?= $data['description'] ?></p>
</div>
</section>
<?php if (array_key_exists('anime', $data['included']) || array_key_exists('manga', $data['included'])): ?>
<?php if (array_key_exists('anime', $data['media']) || array_key_exists('manga', $data['media'])): ?>
<h3>Media</h3>
<div class="tabs">
<?php if (array_key_exists('anime', $data['included'])): ?>
<?php if (array_key_exists('anime', $data['media'])): ?>
<input checked="checked" type="radio" id="media-anime" name="media-tabs" />
<label for="media-anime">Anime</label>
<section class="media-wrap content">
<?php foreach ($data['included']['anime'] as $id => $anime): ?>
<?php foreach ($data['media']['anime'] as $id => $anime): ?>
<article class="media">
<?php
$link = $url->generate('anime.details', ['id' => $anime['attributes']['slug']]);
@ -58,12 +58,12 @@ use Aviat\AnimeClient\API\Kitsu;
</section>
<?php endif ?>
<?php if (array_key_exists('manga', $data['included'])): ?>
<?php if (array_key_exists('manga', $data['media'])): ?>
<input type="radio" id="media-manga" name="media-tabs" />
<label for="media-manga">Manga</label>
<section class="media-wrap content">
<?php foreach ($data['included']['manga'] as $id => $manga): ?>
<?php foreach ($data['media']['manga'] as $id => $manga): ?>
<article class="media">
<?php
$link = $url->generate('manga.details', ['id' => $manga['attributes']['slug']]);
@ -89,14 +89,68 @@ use Aviat\AnimeClient\API\Kitsu;
<?php endif ?>
<section>
<?php if ($castCount > 0): ?>
<?php if (count($data['castings']) > 0): ?>
<h3>Castings</h3>
<?php
$vas = $castings['Voice Actor'];
unset($castings['Voice Actor']);
ksort($vas)
$vas = $data['castings']['Voice Actor'];
unset($data['castings']['Voice Actor']);
ksort($vas)
?>
<?php foreach ($data['castings'] as $role => $entries): ?>
<h4><?= $role ?></h4>
<?php foreach ($entries as $language => $casting): ?>
<h5><?= $language ?></h5>
<table class="min-table">
<tr>
<th>Cast Member</th>
<th>Series</th>
</tr>
<?php foreach ($casting as $cid => $c): ?>
<tr>
<td style="width:229px">
<article class="character">
<?php
$link = $url->generate('person', ['id' => $c['person']['id']]);
?>
<a href="<?= $link ?>">
<?= $helper->picture(getLocalImg($c['person']['image'], TRUE)) ?>
<div class="name">
<?= $c['person']['name'] ?>
</div>
</a>
</article>
</td>
<td>
<section class="align-left media-wrap">
<?php foreach ($c['series'] as $series): ?>
<article class="media">
<?php
$link = $url->generate('anime.details', ['id' => $series['attributes']['slug']]);
$titles = Kitsu::filterTitles($series['attributes']);
?>
<a href="<?= $link ?>">
<?= $helper->picture(getLocalImg($series['attributes']['posterImage']['small'], TRUE)) ?>
</a>
<div class="name">
<a href="<?= $link ?>">
<?= array_shift($titles) ?>
<?php foreach ($titles as $title): ?>
<br />
<small><?= $title ?></small>
<?php endforeach ?>
</a>
</div>
</article>
<?php endforeach ?>
</section>
</td>
</tr>
<?php endforeach; ?>
</table>
<?php endforeach ?>
<?php endforeach ?>
<?php if ( ! empty($vas)): ?>
<h4>Voice Actors</h4>
@ -161,61 +215,6 @@ use Aviat\AnimeClient\API\Kitsu;
<?php endforeach ?>
</div>
<?php endif ?>
<?php foreach ($castings as $role => $entries): ?>
<h4><?= $role ?></h4>
<?php foreach ($entries as $language => $casting): ?>
<h5><?= $language ?></h5>
<table class="min-table">
<tr>
<th>Cast Member</th>
<th>Series</th>
</tr>
<?php foreach ($casting as $cid => $c): ?>
<tr>
<td style="width:229px">
<article class="character">
<?php
$link = $url->generate('person', ['id' => $c['person']['id']]);
?>
<a href="<?= $link ?>">
<?= $helper->picture(getLocalImg($c['person']['image'], TRUE)) ?>
<div class="name">
<?= $c['person']['name'] ?>
</div>
</a>
</article>
</td>
<td>
<section class="align-left media-wrap">
<?php foreach ($c['series'] as $series): ?>
<article class="media">
<?php
$link = $url->generate('anime.details', ['id' => $series['attributes']['slug']]);
$titles = Kitsu::filterTitles($series['attributes']);
?>
<a href="<?= $link ?>">
<?= $helper->picture(getLocalImg($series['attributes']['posterImage']['small'], TRUE)) ?>
</a>
<div class="name">
<a href="<?= $link ?>">
<?= array_shift($titles) ?>
<?php foreach ($titles as $title): ?>
<br />
<small><?= $title ?></small>
<?php endforeach ?>
</a>
</div>
</article>
<?php endforeach ?>
</section>
</td>
</tr>
<?php endforeach; ?>
</table>
<?php endforeach ?>
<?php endforeach ?>
<?php endif ?>
</section>
</main>

View File

@ -5,7 +5,7 @@ use Aviat\AnimeClient\API\Kitsu;
<h3>Voice Acting Roles</h3>
<div class="tabs">
<?php $i = 0; ?>
<?php foreach($characters as $role => $characterList): ?>
<?php foreach($data['characters'] as $role => $characterList): ?>
<input <?= $i === 0 ? 'checked="checked"' : '' ?> type="radio" name="character-type-tabs" id="character-type-<?= $i ?>" />
<label for="character-type-<?= $i ?>"><h5><?= ucfirst($role) ?></h5></label>
<section class="content">

View File

@ -9,16 +9,16 @@ use Aviat\AnimeClient\API\Kitsu;
<?= $helper->picture("images/people/{$data['id']}-original.webp", 'jpg', ['class' => 'cover' ]) ?>
</div>
<div>
<h2 class="toph"><?= $data['attributes']['name'] ?></h2>
<h2 class="toph"><?= $data['name'] ?></h2>
</div>
</section>
<?php if ( ! empty($staff)): ?>
<?php if ( ! empty($data['staff'])): ?>
<section>
<h3>Castings</h3>
<div class="vertical-tabs">
<?php $i = 0 ?>
<?php foreach ($staff as $role => $entries): ?>
<?php foreach ($data['staff'] as $role => $entries): ?>
<div class="tab">
<input
type="radio" name="staff-roles" id="staff-role<?= $i ?>" <?= $i === 0 ? 'checked' : '' ?> />
@ -59,7 +59,7 @@ use Aviat\AnimeClient\API\Kitsu;
</section>
<?php endif ?>
<?php if ( ! (empty($characters['main']) || empty($characters['supporting']))): ?>
<?php if ( ! (empty($data['characters']['main']) || empty($data['characters']['supporting']))): ?>
<section>
<?php include 'character-mapping.php' ?>
</section>

View File

@ -1,45 +1,39 @@
<?php
use function Aviat\AnimeClient\getLocalImg;
use Aviat\AnimeClient\API\Kitsu;
?>
<main class="user-page details">
<h2 class="toph">
<?= $helper->a(
"https://kitsu.io/users/{$attributes['slug']}",
$attributes['name'], [
"https://kitsu.io/users/{$data['slug']}",
$data['name'], [
'title' => 'View profile on Kitsu'
])
?>
</h2>
<p><?= $escape->html($attributes['about']) ?></p>
<p><?= $escape->html($data['about']) ?></p>
<section class="flex flex-no-wrap">
<aside class="info">
<center>
<?php
$avatar = $urlGenerator->assetUrl(
getLocalImg($attributes['avatar']['original'], FALSE)
);
echo $helper->img($avatar, ['alt' => '']);
?>
<?= $helper->img($urlGenerator->assetUrl($data['avatar']), ['alt' => '']); ?>
</center>
<br />
<table class="media-details">
<tr>
<td>Location</td>
<td><?= $attributes['location'] ?></td>
<td><?= $data['location'] ?></td>
</tr>
<tr>
<td>Website</td>
<td><?= $helper->a($attributes['website'], $attributes['website']) ?></td>
<td><?= $helper->a($data['website'], $data['website']) ?></td>
</tr>
<?php if (array_key_exists('waifu', $relationships)): ?>
<?php if ( ! empty($data['waifu'])): ?>
<tr>
<td><?= $escape->html($attributes['waifuOrHusbando']) ?></td>
<td><?= $escape->html($data['waifu']['label']) ?></td>
<td>
<?php
$character = $relationships['waifu']['attributes'];
$character = $data['waifu']['character'];
echo $helper->a(
$url->generate('character', ['slug' => $character['slug']]),
$character['canonicalName']
@ -52,42 +46,24 @@ use Aviat\AnimeClient\API\Kitsu;
<h3>User Stats</h3><br />
<table class="media-details">
<?php foreach($data['stats'] as $label => $stat): ?>
<tr>
<td>Time spent watching anime:</td>
<td><?= $timeOnAnime ?></td>
</tr>
<tr>
<td># of Anime episodes watched</td>
<td><?= number_format($stats['anime-amount-consumed']['units']) ?></td>
</tr>
<tr>
<td># of Manga chapters read</td>
<td><?= number_format($stats['manga-amount-consumed']['units']) ?></td>
</tr>
<tr>
<td># of Posts</td>
<td><?= number_format($attributes['postsCount']) ?></td>
</tr>
<tr>
<td># of Comments</td>
<td><?= number_format($attributes['commentsCount']) ?></td>
</tr>
<tr>
<td># of Media Rated</td>
<td><?= number_format($attributes['ratingsCount']) ?></td>
<td><?= $label ?></td>
<td><?= $stat ?></td>
</tr>
<?php endforeach ?>
</table>
</aside>
<article>
<?php if ( ! empty($favorites)): ?>
<?php if ( ! empty($data['favorites'])): ?>
<h3>Favorites</h3>
<div class="tabs">
<?php $i = 0 ?>
<?php if ( ! empty($favorites['characters'])): ?>
<?php if ( ! empty($data['favorites']['characters'])): ?>
<input type="radio" name="user-favorites" id="user-fav-chars" <?= $i === 0 ? 'checked' : '' ?> />
<label for="user-fav-chars">Characters</label>
<section class="content full-width media-wrap">
<?php foreach($favorites['characters'] as $id => $char): ?>
<?php foreach($data['favorites']['characters'] as $id => $char): ?>
<?php if ( ! empty($char['image']['original'])): ?>
<article class="character">
<?php $link = $url->generate('character', ['slug' => $char['slug']]) ?>
@ -101,11 +77,11 @@ use Aviat\AnimeClient\API\Kitsu;
</section>
<?php $i++; ?>
<?php endif ?>
<?php if ( ! empty($favorites['anime'])): ?>
<?php if ( ! empty($data['favorites']['anime'])): ?>
<input type="radio" name="user-favorites" id="user-fav-anime" <?= $i === 0 ? 'checked' : '' ?> />
<label for="user-fav-anime">Anime</label>
<section class="content full-width media-wrap">
<?php foreach($favorites['anime'] as $anime): ?>
<?php foreach($data['favorites']['anime'] as $anime): ?>
<article class="media">
<?php
$link = $url->generate('anime.details', ['id' => $anime['slug']]);
@ -127,11 +103,11 @@ use Aviat\AnimeClient\API\Kitsu;
</section>
<?php $i++; ?>
<?php endif ?>
<?php if ( ! empty($favorites['manga'])): ?>
<?php if ( ! empty($data['favorites']['manga'])): ?>
<input type="radio" name="user-favorites" id="user-fav-manga" <?= $i === 0 ? 'checked' : '' ?> />
<label for="user-fav-manga">Manga</label>
<section class="content full-width media-wrap">
<?php foreach($favorites['manga'] as $manga): ?>
<?php foreach($data['favorites']['manga'] as $manga): ?>
<article class="media">
<?php
$link = $url->generate('manga.details', ['id' => $manga['slug']]);

View File

@ -18,7 +18,6 @@ namespace Aviat\AnimeClient\API\Kitsu\Transformer;
use Aviat\AnimeClient\API\Kitsu;
use Aviat\AnimeClient\Types\{
Anime,
FormItem,
AnimeListItem
};
@ -95,7 +94,7 @@ final class AnimeListTransformer extends AbstractTransformer {
'started' => $anime['startDate'],
'ended' => $anime['endDate']
],
'anime' => new Anime([
'anime' => [
'id' => $animeId,
'age_rating' => $anime['ageRating'],
'title' => $title,
@ -105,7 +104,7 @@ final class AnimeListTransformer extends AbstractTransformer {
'cover_image' => $anime['posterImage']['small'],
'genres' => $genres,
'streaming_links' => $streamingLinks,
]),
],
'watching_status' => $item['attributes']['status'],
'notes' => $item['attributes']['notes'],
'rewatching' => (bool) $item['attributes']['reconsuming'],

View File

@ -17,7 +17,7 @@
namespace Aviat\AnimeClient\API\Kitsu\Transformer;
use Aviat\AnimeClient\API\{JsonAPI, Kitsu};
use Aviat\AnimeClient\Types\Anime;
use Aviat\AnimeClient\Types\AnimePage;
use Aviat\Ion\Transformer\AbstractTransformer;
/**
@ -30,9 +30,9 @@ final class AnimeTransformer extends AbstractTransformer {
* logical and workable structure
*
* @param array $item API library item
* @return Anime
* @return AnimePage
*/
public function transform($item): Anime
public function transform($item): AnimePage
{
$item['included'] = JsonAPI::organizeIncludes($item['included']);
$genres = $item['included']['categories'] ?? [];
@ -40,13 +40,74 @@ final class AnimeTransformer extends AbstractTransformer {
sort($item['genres']);
$title = $item['canonicalTitle'];
$titles = Kitsu::filterTitles($item);
// $titles = array_unique(array_diff($item['titles'], [$title]));
return new Anime([
$characters = [];
$staff = [];
if (array_key_exists('animeCharacters', $item['included']))
{
$animeCharacters = $item['included']['animeCharacters'];
foreach ($animeCharacters as $rel)
{
$charId = $rel['relationships']['character']['data']['id'];
$role = $rel['role'];
if (array_key_exists($charId, $item['included']['characters']))
{
$characters[$role][$charId] = $item['included']['characters'][$charId];
}
}
}
if (array_key_exists('mediaStaff', $item['included']))
{
foreach ($item['included']['mediaStaff'] as $id => $staffing)
{
$personId = $staffing['relationships']['person']['data']['id'];
$personDetails = $item['included']['people'][$personId];
$role = $staffing['role'];
if ( ! array_key_exists($role, $staff))
{
$staff[$role] = [];
}
$staff[$role][$personId] = [
'id' => $personId,
'name' => $personDetails['name'] ?? '??',
'image' => $personDetails['image'],
];
usort($staff[$role], function ($a, $b) {
return $a['name'] <=> $b['name'];
});
}
}
if ( ! empty($characters['main']))
{
uasort($characters['main'], function ($a, $b) {
return $a['name'] <=> $b['name'];
});
}
if ( ! empty($characters['supporting']))
{
uasort($characters['supporting'], function ($a, $b) {
return $a['name'] <=> $b['name'];
});
}
ksort($characters);
ksort($staff);
return new AnimePage([
'age_rating' => $item['ageRating'],
'age_rating_guide' => $item['ageRatingGuide'],
'characters' => $characters,
'cover_image' => $item['posterImage']['small'],
'episode_count' => $item['episodeCount'],
'episode_length' => $item['episodeLength'],
@ -55,6 +116,7 @@ final class AnimeTransformer extends AbstractTransformer {
'included' => $item['included'],
'show_type' => $this->string($item['showType'])->upperCaseFirst()->__toString(),
'slug' => $item['slug'],
'staff' => $staff,
'status' => Kitsu::getAiringStatus($item['startDate'], $item['endDate']),
'streaming_links' => Kitsu::parseStreamingLinks($item['included']),
'synopsis' => $item['synopsis'],

View File

@ -0,0 +1,173 @@
<?php declare(strict_types=1);
/**
* Hummingbird Anime List Client
*
* An API client for Kitsu to manage anime and manga watch lists
*
* PHP version 7.1
*
* @package HummingbirdAnimeClient
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2018 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 4.1
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
*/
namespace Aviat\AnimeClient\API\Kitsu\Transformer;
use Aviat\AnimeClient\API\JsonAPI;
use Aviat\AnimeClient\Types\Character;
use Aviat\Ion\Transformer\AbstractTransformer;
/**
* Data transformation class for zippered Hummingbird manga
*/
final class CharacterTransformer extends AbstractTransformer {
public function transform($characterData): Character
{
$data = JsonAPI::organizeData($characterData);
$attributes = $data[0]['attributes'];
$castings = [];
$names = array_unique(
array_merge(
[$attributes['canonicalName']],
$attributes['names']
)
);
$name = array_shift($names);
if (array_key_exists('included', $data))
{
if (array_key_exists('anime', $data['included']))
{
uasort($data['included']['anime'], function ($a, $b) {
return $a['attributes']['canonicalTitle'] <=> $b['attributes']['canonicalTitle'];
});
}
if (array_key_exists('manga', $data['included']))
{
uasort($data['included']['manga'], function ($a, $b) {
return $a['attributes']['canonicalTitle'] <=> $b['attributes']['canonicalTitle'];
});
}
if (array_key_exists('castings', $data['included']))
{
$castings = $this->organizeCast($data['included']['castings']);
}
}
return new Character([
'castings' => $castings,
'description' => $attributes['description'],
'id' => $data[0]['id'],
'media' => [
'anime' => $data['included']['anime'] ?? [],
'manga' => $data['included']['manga'] ?? [],
],
'name' => $name,
'names' => $names,
'otherNames' => $attributes['otherNames'],
]);
}
/**
* Organize VA => anime relationships
*
* @param array $cast
* @return array
*/
private function dedupeCast(array $cast): array
{
$output = [];
$people = [];
$i = 0;
foreach ($cast as &$role)
{
if (empty($role['attributes']['role']))
{
continue;
}
$person = current($role['relationships']['person']['people'])['attributes'];
$hasName = array_key_exists($person['name'], $people);
if ( ! $hasName)
{
$people[$person['name']] = $i;
$role['relationships']['media']['anime'] = [current($role['relationships']['media']['anime'])];
$output[$i] = $role;
$i++;
continue;
}
if (array_key_exists('anime', $role['relationships']['media']))
{
$key = $people[$person['name']];
$output[$key]['relationships']['media']['anime'][] = current($role['relationships']['media']['anime']);
}
continue;
}
return $output;
}
protected function organizeCast(array $cast): array
{
$cast = $this->dedupeCast($cast);
$output = [];
foreach ($cast as $id => $role)
{
if (empty($role['attributes']['role']))
{
continue;
}
$language = $role['attributes']['language'];
$roleName = $role['attributes']['role'];
$isVA = $role['attributes']['voiceActor'];
if ($isVA)
{
foreach ($role['relationships']['person']['people'] as $pid => $peoples)
{
$p = $peoples;
}
$person = $p['attributes'];
$person['id'] = $pid;
$person['image'] = $person['image']['original'];
uasort($role['relationships']['media']['anime'], function ($a, $b) {
return $a['attributes']['canonicalTitle'] <=> $b['attributes']['canonicalTitle'];
});
$item = [
'person' => $person,
'series' => $role['relationships']['media']['anime']
];
$output[$roleName][$language][] = $item;
} else
{
foreach ($role['relationships']['person']['people'] as $pid => $person)
{
$person['id'] = $pid;
$output[$roleName][$pid] = $person;
}
}
}
return $output;
}
}

View File

@ -0,0 +1,132 @@
<?php declare(strict_types=1);
/**
* Hummingbird Anime List Client
*
* An API client for Kitsu to manage anime and manga watch lists
*
* PHP version 7.1
*
* @package HummingbirdAnimeClient
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2018 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 4.1
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
*/
namespace Aviat\AnimeClient\API\Kitsu\Transformer;
use Aviat\AnimeClient\API\JsonAPI;
use Aviat\AnimeClient\Types\Person;
use Aviat\Ion\Transformer\AbstractTransformer;
/**
* Data transformation class for zippered Hummingbird manga
*/
final class PersonTransformer extends AbstractTransformer {
public function transform($personData): Person
{
$data = JsonAPI::organizeData($personData);
$included = JsonAPI::organizeIncludes($personData['included']);
$orgData = $this->organizeData($included);
return new Person([
'id' => $data['id'],
'name' => $data['attributes']['name'],
'characters' => $orgData['characters'],
'staff' => $orgData['staff'],
]);
}
protected function organizeData(array $data): array
{
$output = [
'characters' => [
'main' => [],
'supporting' => [],
],
'staff' => [],
];
if (array_key_exists('characterVoices', $data))
{
foreach ($data['characterVoices'] as $cv)
{
$mcId = $cv['relationships']['mediaCharacter']['data']['id'];
if ( ! array_key_exists($mcId, $data['mediaCharacters']))
{
continue;
}
$mc = $data['mediaCharacters'][$mcId];
$role = $mc['role'];
$charId = $mc['relationships']['character']['data']['id'];
$mediaId = $mc['relationships']['media']['data']['id'];
$existingMedia = array_key_exists($charId, $output['characters'][$role])
? $output['characters'][$role][$charId]['media']
: [];
$relatedMedia = [
$mediaId => $data['anime'][$mediaId],
];
$includedMedia = array_replace_recursive($existingMedia, $relatedMedia);
uasort($includedMedia, function ($a, $b) {
return $a['canonicalTitle'] <=> $b['canonicalTitle'];
});
$character = $data['characters'][$charId];
$output['characters'][$role][$charId] = [
'character' => $character,
'media' => $includedMedia,
];
}
}
if (array_key_exists('mediaStaff', $data))
{
foreach ($data['mediaStaff'] as $rid => $role)
{
$roleName = $role['role'];
$mediaType = $role['relationships']['media']['data']['type'];
$mediaId = $role['relationships']['media']['data']['id'];
$media = $data[$mediaType][$mediaId];
$output['staff'][$roleName][$mediaType][$mediaId] = $media;
}
}
uasort($output['characters']['main'], function ($a, $b) {
return $a['character']['canonicalName'] <=> $b['character']['canonicalName'];
});
uasort($output['characters']['supporting'], function ($a, $b) {
return $a['character']['canonicalName'] <=> $b['character']['canonicalName'];
});
ksort($output['staff']);
foreach ($output['staff'] as $role => &$media)
{
if (array_key_exists('anime', $media))
{
uasort($media['anime'], function ($a, $b) {
return $a['canonicalTitle'] <=> $b['canonicalTitle'];
});
}
if (array_key_exists('manga', $media))
{
uasort($media['manga'], function ($a, $b) {
return $a['canonicalTitle'] <=> $b['canonicalTitle'];
});
}
}
return $output;
}
}

View File

@ -0,0 +1,147 @@
<?php declare(strict_types=1);
/**
* Hummingbird Anime List Client
*
* An API client for Kitsu to manage anime and manga watch lists
*
* PHP version 7.1
*
* @package HummingbirdAnimeClient
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2018 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 4.1
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
*/
namespace Aviat\AnimeClient\API\Kitsu\Transformer;
use function Aviat\AnimeClient\getLocalImg;
use Aviat\AnimeClient\API\JsonAPI;
use Aviat\AnimeClient\Types\User;
use Aviat\Ion\Transformer\AbstractTransformer;
/**
* Transform user profile data for display
*/
final class UserTransformer extends AbstractTransformer {
public function transform($profileData): User
{
$orgData = JsonAPI::organizeData($profileData)[0];
$attributes = $orgData['attributes'];
$rels = $orgData['relationships'] ?? [];
$favorites = array_key_exists('favorites', $rels) ? $rels['favorites'] : [];
$stats = [];
foreach ($rels['stats'] as $sid => &$item)
{
$key = $item['attributes']['kind'];
$stats[$key] = $item['attributes']['statsData'];
unset($item);
}
$waifu = [];
if (array_key_exists('waifu', $rels))
{
$waifu = [
'label' => $attributes['waifuOrHusbando'],
'character' => $rels['waifu']['attributes'],
];
}
return new User([
'about' => $attributes['about'],
'avatar' => getLocalImg($attributes['avatar']['original'], FALSE),
'favorites' => $this->organizeFavorites($favorites),
'location' => $attributes['location'],
'name' => $attributes['name'],
'slug' => $attributes['slug'],
'stats' => $this->organizeStats($stats, $attributes),
'waifu' => $waifu,
'website' => $attributes['website'],
]);
}
/**
* Reorganize favorites data to be more useful
*
* @param array $rawFavorites
* @return array
*/
private function organizeFavorites(array $rawFavorites): array
{
$output = [];
unset($rawFavorites['data']);
foreach ($rawFavorites as $item)
{
$rank = $item['attributes']['favRank'];
foreach ($item['relationships']['item'] as $key => $fav)
{
$output[$key] = $output[$key] ?? [];
foreach ($fav as $id => $data)
{
$output[$key][$rank] = array_merge(['id' => $id], $data['attributes']);
}
ksort($output[$key]);
}
}
return $output;
}
/**
* Format the time spent on anime in a more readable format
*
* @param int $minutes
* @return string
*/
private function formatAnimeTime(int $minutes): string
{
$minutesPerDay = 1440;
$minutesPerYear = $minutesPerDay * 365;
// Minutes short of a year
$years = (int)floor($minutes / $minutesPerYear);
$minutes %= $minutesPerYear;
// Minutes short of a day
$extraMinutes = $minutes % $minutesPerDay;
$days = ($minutes - $extraMinutes) / $minutesPerDay;
// Minutes short of an hour
$remMinutes = $extraMinutes % 60;
$hours = ($extraMinutes - $remMinutes) / 60;
$output = "{$days} days, {$hours} hours, and {$remMinutes} minutes.";
if ($years > 0)
{
$output = "{$years} year(s),{$output}";
}
return $output;
}
private function organizeStats($stats, $data): array
{
// $timeOnAnime = $this->formatAnimeTime($orgData['attributes']['lifeSpentOnAnime']);
return [
'Time spent watching anime:' => $this->formatAnimeTime($stats['anime-amount-consumed']['time']),
'Anime series watched:' => number_format($stats['anime-amount-consumed']['media']),
'Anime episodes watched:' => number_format($stats['anime-amount-consumed']['units']),
'Manga series read:' => number_format($stats['manga-amount-consumed']['media']),
'Manga chapters read:' => number_format($stats['manga-amount-consumed']['units']),
'Posts:' => number_format($data['postsCount']),
'Comments:' => number_format($data['commentsCount']),
'Media Rated:' => number_format($data['ratingsCount']),
];
}
}

View File

@ -23,15 +23,11 @@ use Aviat\AnimeClient\API\Mapping\AnimeWatchingStatus;
use Aviat\AnimeClient\Types\FormItem;
use Aviat\Ion\Di\ContainerInterface;
use Aviat\Ion\Json;
use Aviat\Ion\StringWrapper;
/**
* Controller for Anime-related pages
*/
final class Anime extends BaseController {
use StringWrapper;
/**
* The anime list model
* @var \Aviat\AnimeClient\Model\Anime $model
@ -276,8 +272,6 @@ final class Anime extends BaseController {
public function details(string $animeId): void
{
$data = $this->model->getAnime($animeId);
$characters = [];
$staff = [];
if (empty($data))
{
@ -291,77 +285,13 @@ final class Anime extends BaseController {
return;
}
if (array_key_exists('animeCharacters', $data['included']))
{
$animeCharacters = $data['included']['animeCharacters'];
foreach ($animeCharacters as $rel)
{
$charId = $rel['relationships']['character']['data']['id'];
$role = $rel['role'];
if (array_key_exists($charId, $data['included']['characters']))
{
$characters[$role][$charId] = $data['included']['characters'][$charId];
}
}
}
if (array_key_exists('mediaStaff', $data['included']))
{
foreach ($data['included']['mediaStaff'] as $id => $staffing)
{
$personId = $staffing['relationships']['person']['data']['id'];
$personDetails = $data['included']['people'][$personId];
$role = $staffing['role'];
if ( ! array_key_exists($role, $staff))
{
$staff[$role] = [];
}
$staff[$role][$personId] = [
'id' => $personId,
'name' => $personDetails['name'] ?? '??',
'image' => $personDetails['image'],
];
usort($staff[$role], function ($a, $b) {
return $a['name'] <=> $b['name'];
});
}
}
if ( ! empty($characters['main']))
{
uasort($characters['main'], function ($a, $b) {
return $a['name'] <=> $b['name'];
});
}
if ( ! empty($characters['supporting']))
{
uasort($characters['supporting'], function ($a, $b) {
return $a['name'] <=> $b['name'];
});
}
ksort($characters);
ksort($staff);
// dump($characters);
// dump($staff);
$this->outputHTML('anime/details', [
'title' => $this->formatTitle(
$this->config->get('whose_list') . "'s Anime List",
'Anime',
$data->title
),
'characters' => $characters,
'show_data' => $data,
'staff' => $staff,
'data' => $data,
]);
}

View File

@ -17,30 +17,42 @@
namespace Aviat\AnimeClient\Controller;
use Aviat\AnimeClient\Controller as BaseController;
use Aviat\AnimeClient\API\JsonAPI;
use Aviat\Ion\ArrayWrapper;
use Aviat\AnimeClient\API\Kitsu\Transformer\CharacterTransformer;
use Aviat\Ion\Di\ContainerInterface;
/**
* Controller for character description pages
*/
class Character extends BaseController {
use ArrayWrapper;
/**
* @var \Aviat\AnimeClient\API\Kitsu\Model
*/
private $model;
/**
* Character constructor.
*
* @param ContainerInterface $container
* @throws \Aviat\Ion\Di\Exception\ContainerException
* @throws \Aviat\Ion\Di\Exception\NotFoundException
*/
public function __construct(ContainerInterface $container)
{
parent::__construct($container);
$this->model = $container->get('kitsu-model');
}
/**
* Show information about a character
*
* @param string $slug
* @throws \Aviat\Ion\Di\ContainerException
* @throws \Aviat\Ion\Di\NotFoundException
* @throws \InvalidArgumentException
* @return void
*/
public function index(string $slug): void
{
$model = $this->container->get('kitsu-model');
$rawData = $model->getCharacter($slug);
$rawData = $this->model->getCharacter($slug);
if (( ! array_key_exists('data', $rawData)) || empty($rawData['data']))
{
@ -55,167 +67,14 @@ class Character extends BaseController {
return;
}
$data = JsonAPI::organizeData($rawData);
$data = (new CharacterTransformer())->transform($rawData)->toArray();
$data['names'] = array_unique(
array_merge(
[ $data[0]['attributes']['canonicalName'] ],
$data[0]['attributes']['names']
)
);
$data['name'] = array_shift($data['names']);
if (array_key_exists('included', $data))
{
if (array_key_exists('anime', $data['included']))
{
uasort($data['included']['anime'], function ($a, $b) {
return $a['attributes']['canonicalTitle'] <=> $b['attributes']['canonicalTitle'];
});
}
if (array_key_exists('manga', $data['included']))
{
uasort($data['included']['manga'], function ($a, $b) {
return $a['attributes']['canonicalTitle'] <=> $b['attributes']['canonicalTitle'];
});
}
}
$viewData = [
$this->outputHTML('character/details', [
'title' => $this->formatTitle(
'Characters',
$data[0]['attributes']['name']
$data['name']
),
'data' => $data,
'castCount' => 0,
'castings' => []
];
if (array_key_exists('included', $data))
{
if (array_key_exists('castings', $data['included']))
{
$viewData['castings'] = $this->organizeCast($data['included']['castings']);
$viewData['castCount'] = $this->getCastCount($viewData['castings']);
}
}
$this->outputHTML('character/details', $viewData);
}
/**
* Organize VA => anime relationships
*
* @param array $cast
* @return array
*/
private function dedupeCast(array $cast): array
{
$output = [];
$people = [];
$i = 0;
foreach ($cast as &$role)
{
if (empty($role['attributes']['role']))
{
continue;
}
$person = current($role['relationships']['person']['people'])['attributes'];
$hasName = array_key_exists($person['name'], $people);
if ( ! $hasName)
{
$people[$person['name']] = $i;
$role['relationships']['media']['anime'] = [current($role['relationships']['media']['anime'])];
$output[$i] = $role;
$i++;
continue;
}
if (array_key_exists('anime', $role['relationships']['media']))
{
$key = $people[$person['name']];
$output[$key]['relationships']['media']['anime'][] = current($role['relationships']['media']['anime']);
}
continue;
}
return $output;
}
protected function getCastCount(array $cast): int
{
$count = 0;
foreach($cast as $role)
{
$count++;
/* if (
array_key_exists('attributes', $role) &&
array_key_exists('role', $role['attributes']) &&
$role['attributes']['role'] !== NULL
) {
$count++;
} */
}
return $count;
}
protected function organizeCast(array $cast): array
{
$cast = $this->dedupeCast($cast);
$output = [];
foreach($cast as $id => $role)
{
if (empty($role['attributes']['role']))
{
continue;
}
$language = $role['attributes']['language'];
$roleName = $role['attributes']['role'];
$isVA = $role['attributes']['voiceActor'];
if ($isVA)
{
foreach($role['relationships']['person']['people'] as $pid => $peoples)
{
$p = $peoples;
}
$person = $p['attributes'];
$person['id'] = $pid;
$person['image'] = $person['image']['original'];
uasort($role['relationships']['media']['anime'], function ($a, $b) {
return $a['attributes']['canonicalTitle'] <=> $b['attributes']['canonicalTitle'];
});
$item = [
'person' => $person,
'series' => $role['relationships']['media']['anime']
];
$output[$roleName][$language][] = $item;
}
else
{
foreach($role['relationships']['person']['people'] as $pid => $person)
{
$person['id'] = $pid;
$output[$roleName][$pid] = $person;
}
}
}
return $output;
]);
}
}

View File

@ -17,12 +17,33 @@
namespace Aviat\AnimeClient\Controller;
use Aviat\AnimeClient\Controller as BaseController;
use Aviat\AnimeClient\API\JsonAPI;
use Aviat\AnimeClient\API\Kitsu\Transformer\PersonTransformer;
use Aviat\Ion\Di\ContainerInterface;
/**
* Controller for People pages
*/
final class People extends BaseController {
/**
* @var \Aviat\AnimeClient\API\Kitsu\Model
*/
private $model;
/**
* People constructor.
*
* @param ContainerInterface $container
* @throws \Aviat\Ion\Di\Exception\ContainerException
* @throws \Aviat\Ion\Di\Exception\NotFoundException
*/
public function __construct(ContainerInterface $container)
{
parent::__construct($container);
$this->model = $container->get('kitsu-model');
}
/**
* Show information about a person
*
@ -31,9 +52,8 @@ final class People extends BaseController {
*/
public function index(string $id): void
{
$model = $this->container->get('kitsu-model');
$rawData = $model->getPerson($id);
$rawData = $this->model->getPerson($id);
$data = (new PersonTransformer())->transform($rawData)->toArray();
if (( ! array_key_exists('data', $rawData)) || empty($rawData['data']))
{
@ -48,114 +68,12 @@ final class People extends BaseController {
return;
}
$data = JsonAPI::organizeData($rawData);
$included = JsonAPI::organizeIncludes($rawData['included']);
$orgData = $this->organizeData($included);
$viewData = [
'included' => $included,
$this->outputHTML('person/details', [
'title' => $this->formatTitle(
'People',
$data['attributes']['name']
$data['name']
),
'data' => $data,
'castCount' => 0,
'castings' => [],
'characters' => $orgData['characters'],
'staff' => $orgData['staff'],
];
$this->outputHTML('person/details', $viewData);
}
protected function organizeData(array $data): array
{
$output = [
'characters' => [
'main' => [],
'supporting' => [],
],
'staff' => [],
];
if (array_key_exists('characterVoices', $data))
{
foreach ($data['characterVoices'] as $cv)
{
$mcId = $cv['relationships']['mediaCharacter']['data']['id'];
if ( ! array_key_exists($mcId, $data['mediaCharacters']))
{
continue;
}
$mc = $data['mediaCharacters'][$mcId];
$role = $mc['role'];
$charId = $mc['relationships']['character']['data']['id'];
$mediaId = $mc['relationships']['media']['data']['id'];
$existingMedia = array_key_exists($charId, $output['characters'][$role])
? $output['characters'][$role][$charId]['media']
: [];
$relatedMedia = [
$mediaId => $data['anime'][$mediaId],
];
$includedMedia = array_replace_recursive($existingMedia, $relatedMedia);
uasort($includedMedia, function ($a, $b) {
return $a['canonicalTitle'] <=> $b['canonicalTitle'];
});
$character = $data['characters'][$charId];
$output['characters'][$role][$charId] = [
'character' => $character,
'media' => $includedMedia,
];
}
}
if (array_key_exists('mediaStaff', $data))
{
foreach($data['mediaStaff'] as $rid => $role)
{
$roleName = $role['role'];
$mediaType = $role['relationships']['media']['data']['type'];
$mediaId = $role['relationships']['media']['data']['id'];
$media = $data[$mediaType][$mediaId];
$output['staff'][$roleName][$mediaType][$mediaId] = $media;
}
}
uasort($output['characters']['main'], function ($a, $b) {
return $a['character']['canonicalName'] <=> $b['character']['canonicalName'];
});
uasort($output['characters']['supporting'], function ($a, $b) {
return $a['character']['canonicalName'] <=> $b['character']['canonicalName'];
});
ksort($output['staff']);
foreach($output['staff'] as $role => &$media)
{
if (array_key_exists('anime', $media))
{
uasort($media['anime'], function ($a, $b) {
return $a['canonicalTitle'] <=> $b['canonicalTitle'];
});
}
if (array_key_exists('manga', $media))
{
uasort($media['manga'], function ($a, $b) {
return $a['canonicalTitle'] <=> $b['canonicalTitle'];
});
}
}
return $output;
]);
}
}

View File

@ -24,7 +24,7 @@ use Aviat\Ion\Di\ContainerInterface;
*/
final class Settings extends BaseController {
/**
* @var \Aviat\API\Anilist\Model
* @var \Aviat\AnimeClient\API\Anilist\Model
*/
private $anilistModel;
@ -33,6 +33,13 @@ final class Settings extends BaseController {
*/
private $settingsModel;
/**
* Settings constructor.
*
* @param ContainerInterface $container
* @throws \Aviat\Ion\Di\Exception\ContainerException
* @throws \Aviat\Ion\Di\Exception\NotFoundException
*/
public function __construct(ContainerInterface $container)
{
parent::__construct($container);
@ -44,7 +51,7 @@ final class Settings extends BaseController {
/**
* Show the user settings, if logged in
*/
public function index()
public function index(): void
{
$auth = $this->container->get('auth');
$form = $this->settingsModel->getSettingsForm();
@ -66,7 +73,7 @@ final class Settings extends BaseController {
*
* @throws \Aura\Router\Exception\RouteNotFound
*/
public function update()
public function update(): void
{
$post = $this->request->getParsedBody();
unset($post['settings-tabs']);
@ -88,14 +95,15 @@ final class Settings extends BaseController {
/**
* Redirect to Anilist to start Oauth flow
*/
public function anilistRedirect()
public function anilistRedirect(): void
{
$redirectUrl = 'https://anilist.co/api/v2/oauth/authorize?' .
http_build_query([
'client_id' => $this->config->get(['anilist', 'client_id']),
'redirect_uri' => $this->urlGenerator->url('/anilist-oauth'),
'response_type' => 'code',
]);
$query = http_build_query([
'client_id' => $this->config->get(['anilist', 'client_id']),
'redirect_uri' => $this->urlGenerator->url('/anilist-oauth'),
'response_type' => 'code',
]);
$redirectUrl = "https://anilist.co/api/v2/oauth/authorize?{$query}";
$this->redirect($redirectUrl, 303);
}
@ -103,7 +111,7 @@ final class Settings extends BaseController {
/**
* Oauth callback for Anilist API
*/
public function anilistCallback()
public function anilistCallback(): void
{
$query = $this->request->getQueryParams();
$authCode = $query['code'];

View File

@ -16,8 +16,9 @@
namespace Aviat\AnimeClient\Controller;
use Aviat\AnimeClient\API\Kitsu\Transformer\UserTransformer;
use Aviat\AnimeClient\Controller as BaseController;
use Aviat\AnimeClient\API\JsonAPI;
use Aviat\Ion\Di\ContainerInterface;
/**
@ -25,8 +26,18 @@ use Aviat\Ion\Di\ContainerInterface;
*/
final class User extends BaseController {
/**
* @var \Aviat\AnimeClient\API\Kitsu\Model
*/
private $kitsuModel;
/**
* User constructor.
*
* @param ContainerInterface $container
* @throws \Aviat\Ion\Di\Exception\ContainerException
* @throws \Aviat\Ion\Di\Exception\NotFoundException
*/
public function __construct(ContainerInterface $container)
{
parent::__construct($container);
@ -56,103 +67,16 @@ final class User extends BaseController {
? $this->config->get(['kitsu_username'])
: $username;
$data = $this->kitsuModel->getUserData($username);
$orgData = JsonAPI::organizeData($data)[0];
$rels = $orgData['relationships'] ?? [];
$favorites = array_key_exists('favorites', $rels) ? $rels['favorites'] : [];
$stats = [];
foreach ($rels['stats'] as $sid => &$item)
{
$key = $item['attributes']['kind'];
$stats[$key] = $item['attributes']['statsData'];
unset($item);
}
//dump($orgData);
// dump($stats);
// $timeOnAnime = $this->formatAnimeTime($orgData['attributes']['lifeSpentOnAnime']);
$timeOnAnime = $this->formatAnimeTime($stats['anime-amount-consumed']['time']);
$whom = $isMainUser
? $this->config->get('whose_list')
: $username;
$rawData = $this->kitsuModel->getUserData($username);
$data = (new UserTransformer())->transform($rawData)->toArray();
$this->outputHTML('user/details', [
'title' => 'About ' . $whom,
'data' => $orgData,
'attributes' => $orgData['attributes'],
'relationships' => $rels,
'favorites' => $this->organizeFavorites($favorites),
'stats' => $stats,
'timeOnAnime' => $timeOnAnime,
'data' => $data,
]);
}
/**
* Reorganize favorites data to be more useful
*
* @param array $rawFavorites
* @return array
*/
private function organizeFavorites(array $rawFavorites): array
{
$output = [];
unset($rawFavorites['data']);
foreach ($rawFavorites as $item)
{
$rank = $item['attributes']['favRank'];
foreach ($item['relationships']['item'] as $key => $fav)
{
$output[$key] = $output[$key] ?? [];
foreach ($fav as $id => $data)
{
$output[$key][$rank] = array_merge(['id' => $id], $data['attributes']);
}
}
ksort($output[$key]);
}
return $output;
}
/**
* Format the time spent on anime in a more readable format
*
* @param int $minutes
* @return string
*/
private function formatAnimeTime(int $minutes): string
{
$minutesPerDay = 1440;
$minutesPerYear = $minutesPerDay * 365;
// Minutes short of a year
$years = (int)floor($minutes / $minutesPerYear);
$minutes %= $minutesPerYear;
// Minutes short of a day
$extraMinutes = $minutes % $minutesPerDay;
$days = ($minutes - $extraMinutes) / $minutesPerDay;
// Minutes short of an hour
$remMinutes = $extraMinutes % 60;
$hours = ($extraMinutes - $remMinutes) / 60;
$output = "{$days} days, {$hours} hours, and {$remMinutes} minutes.";
if ($years > 0)
{
$output = "{$years} year(s),{$output}";
}
return $output;
}
}

View File

@ -17,9 +17,9 @@
namespace Aviat\AnimeClient\Types;
/**
* Type representing an Anime object for display
* Type representing an anime within a watch list
*/
final class Anime extends AbstractType {
class Anime extends AbstractType {
public $age_rating;
public $age_rating_guide;
public $cover_image;

View File

@ -17,7 +17,7 @@
namespace Aviat\AnimeClient\Types;
/**
* Type representing an Anime object for display
* Type representing an anime watch list item
*/
final class AnimeListItem extends AbstractType {
public $id;
@ -40,4 +40,9 @@ final class AnimeListItem extends AbstractType {
public $rewatched;
public $user_rating;
public $watching_status;
public function setAnime($anime): void
{
$this->anime = new Anime($anime);
}
}

25
src/Types/AnimePage.php Normal file
View File

@ -0,0 +1,25 @@
<?php declare(strict_types=1);
/**
* Hummingbird Anime List Client
*
* An API client for Kitsu to manage anime and manga watch lists
*
* PHP version 7.1
*
* @package HummingbirdAnimeClient
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2018 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 4.1
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
*/
namespace Aviat\AnimeClient\Types;
/**
* Type representing an Anime object for a detail page
*/
final class AnimePage extends Anime {
public $characters;
public $staff;
}

39
src/Types/Character.php Normal file
View File

@ -0,0 +1,39 @@
<?php declare(strict_types=1);
/**
* Hummingbird Anime List Client
*
* An API client for Kitsu to manage anime and manga watch lists
*
* PHP version 7.1
*
* @package HummingbirdAnimeClient
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2018 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 4.1
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
*/
namespace Aviat\AnimeClient\Types;
/**
* Type representing a character for display
*/
final class Character extends AbstractType {
public $castings;
public $description;
public $id;
public $included;
public $media;
public $name;
public $names;
public $otherNames;
public function setMedia ($media): void
{
$this->media = new class($media) extends AbstractType {
public $anime;
public $manga;
};
}
}

35
src/Types/Person.php Normal file
View File

@ -0,0 +1,35 @@
<?php declare(strict_types=1);
/**
* Hummingbird Anime List Client
*
* An API client for Kitsu to manage anime and manga watch lists
*
* PHP version 7.1
*
* @package HummingbirdAnimeClient
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2018 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 4.1
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
*/
namespace Aviat\AnimeClient\Types;
/**
* Type representing a person for display
*/
final class Person extends AbstractType {
public $id;
public $name;
public $characters;
public $staff;
public function setCharacters($characters): void
{
$this->characters = new class($characters) extends AbstractType {
public $main;
public $supporting;
};
}
}

32
src/Types/User.php Normal file
View File

@ -0,0 +1,32 @@
<?php declare(strict_types=1);
/**
* Hummingbird Anime List Client
*
* An API client for Kitsu to manage anime and manga watch lists
*
* PHP version 7.1
*
* @package HummingbirdAnimeClient
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2018 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 4.1
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
*/
namespace Aviat\AnimeClient\Types;
/**
* Type representing a Kitsu user for display
*/
final class User extends AbstractType {
public $about;
public $avatar;
public $favorites;
public $location;
public $name;
public $slug;
public $stats;
public $waifu;
public $website;
}

View File

@ -1,4 +1,10 @@
<?php return Aviat\AnimeClient\Types\Anime::__set_state(array(
<?php return Aviat\AnimeClient\Types\AnimePage::__set_state(array(
'characters' =>
array (
),
'staff' =>
array (
),
'age_rating' => 'R',
'age_rating_guide' => 'Violence, Profanity',
'cover_image' => 'https://media.kitsu.io/anime/poster_images/7442/small.jpg?1418580054',