Version 5.1 - All the GraphQL #32
@ -14,7 +14,6 @@
|
|||||||
* @link https://github.com/timw4mail/HummingBirdAnimeClient
|
* @link https://github.com/timw4mail/HummingBirdAnimeClient
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
namespace Aviat\AnimeClient;
|
namespace Aviat\AnimeClient;
|
||||||
|
|
||||||
use Aura\Html\HelperLocatorFactory;
|
use Aura\Html\HelperLocatorFactory;
|
||||||
|
@ -14,5 +14,11 @@ show_anime_collection = true
|
|||||||
# do you wish to show the manga collection?
|
# do you wish to show the manga collection?
|
||||||
show_manga_collection = false
|
show_manga_collection = false
|
||||||
|
|
||||||
|
# do you have a My Anime List account set up in mal.toml?
|
||||||
|
use_mal_api = false
|
||||||
|
|
||||||
|
# cache driver for api calls (NullDriver, SQLDriver, RedisDriver)
|
||||||
|
cache_driver = "NullDriver"
|
||||||
|
|
||||||
# path to public directory on the server
|
# path to public directory on the server
|
||||||
asset_dir = "/../../public"
|
asset_dir = "/../../public"
|
@ -11,11 +11,11 @@
|
|||||||
<section class="media-wrap">
|
<section class="media-wrap">
|
||||||
<?php foreach($items as $item): ?>
|
<?php foreach($items as $item): ?>
|
||||||
<?php if ($item['private'] && ! $auth->is_authenticated()) continue; ?>
|
<?php if ($item['private'] && ! $auth->is_authenticated()) continue; ?>
|
||||||
<article class="media" id="<?= $item['id'] ?>">
|
<article class="media" id="<?= $item['id'] ?>" data-kitsu-id="<?= $item['id'] ?>" data-mal-id="<?= $item['mal_id'] ?>">
|
||||||
<?php if ($auth->is_authenticated()): ?>
|
<?php if ($auth->is_authenticated()): ?>
|
||||||
<button title="Increment episode count" class="plus_one" hidden>+1 Episode</button>
|
<button title="Increment episode count" class="plus_one" hidden>+1 Episode</button>
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
<?= $helper->img($item['anime']['image']); ?>
|
<img src="<?= $item['anime']['image'] ?>" alt="" />
|
||||||
<div class="name">
|
<div class="name">
|
||||||
<a href="<?= $url->generate('anime.details', ['id' => $item['anime']['slug']]); ?>">
|
<a href="<?= $url->generate('anime.details', ['id' => $item['anime']['slug']]); ?>">
|
||||||
<?= array_shift($item['anime']['titles']) ?>
|
<?= array_shift($item['anime']['titles']) ?>
|
||||||
|
@ -15,16 +15,18 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Episode Count</td>
|
<td>Episode Count</td>
|
||||||
<td><?= $data['episode_count'] ?></td>
|
<td><?= $data['episode_count'] ?? '-' ?></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Episode Length</td>
|
<td>Episode Length</td>
|
||||||
<td><?= $data['episode_length'] ?> minutes</td>
|
<td><?= $data['episode_length'] ?> minutes</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<?php if ( ! empty($data['age_rating'])): ?>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Age Rating</td>
|
<td>Age Rating</td>
|
||||||
<td><abbr title="<?= $data['age_rating_guide'] ?>"><?= $data['age_rating'] ?></abbr></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>
|
||||||
@ -40,6 +42,7 @@
|
|||||||
<?php endforeach ?>
|
<?php endforeach ?>
|
||||||
<br />
|
<br />
|
||||||
<p><?= nl2br($data['synopsis']) ?></p>
|
<p><?= nl2br($data['synopsis']) ?></p>
|
||||||
|
<?php /*<pre><?= json_encode($data['included'], \JSON_PRETTY_PRINT) ?></pre> */ ?>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
@ -77,6 +77,7 @@
|
|||||||
<td> </td>
|
<td> </td>
|
||||||
<td>
|
<td>
|
||||||
<input type="hidden" value="<?= $item['id'] ?>" name="id" />
|
<input type="hidden" value="<?= $item['id'] ?>" name="id" />
|
||||||
|
<input type="hidden" value="<?= $item['mal_id'] ?>" name="mal_id" />
|
||||||
<input type="hidden" value="true" name="edit" />
|
<input type="hidden" value="true" name="edit" />
|
||||||
<button type="submit">Submit</button>
|
<button type="submit">Submit</button>
|
||||||
</td>
|
</td>
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
stopOnFailure="false"
|
stopOnFailure="false"
|
||||||
bootstrap="../tests/bootstrap.php"
|
bootstrap="../tests/bootstrap.php"
|
||||||
beStrictAboutTestsThatDoNotTestAnything="true"
|
beStrictAboutTestsThatDoNotTestAnything="true"
|
||||||
checkForUnintentionallyCoveredCode="true"
|
|
||||||
>
|
>
|
||||||
<filter>
|
<filter>
|
||||||
<whitelist>
|
<whitelist>
|
||||||
|
@ -14,7 +14,6 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"abeautifulsite/simpleimage": "2.5.*",
|
|
||||||
"aura/html": "2.*",
|
"aura/html": "2.*",
|
||||||
"aura/router": "3.*",
|
"aura/router": "3.*",
|
||||||
"aura/session": "2.*",
|
"aura/session": "2.*",
|
||||||
@ -35,15 +34,16 @@
|
|||||||
"theseer/phpdox": "0.8.1.1",
|
"theseer/phpdox": "0.8.1.1",
|
||||||
"phploc/phploc": "^3.0",
|
"phploc/phploc": "^3.0",
|
||||||
"phpmd/phpmd": "^2.4",
|
"phpmd/phpmd": "^2.4",
|
||||||
"phpunit/phpunit": "^5.4",
|
"phpunit/phpunit": "^5.7",
|
||||||
"robmorgan/phinx": "^0.6.4",
|
"robmorgan/phinx": "^0.6.4",
|
||||||
"humbug/humbug": "~1.0@dev",
|
"humbug/humbug": "~1.0@dev",
|
||||||
"consolidation/robo": "~1.0@RC",
|
"consolidation/robo": "~1.0",
|
||||||
"henrikbjorn/lurker": "^1.1.0",
|
"henrikbjorn/lurker": "^1.1.0",
|
||||||
"symfony/var-dumper": "^3.1",
|
"symfony/var-dumper": "^3.1",
|
||||||
"squizlabs/php_codesniffer": "^3.0.0@beta"
|
"squizlabs/php_codesniffer": "^3.0.0@beta"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build:css": "cd public && npm run build && cd .."
|
"build:css": "cd public && npm run build && cd ..",
|
||||||
|
"watch:css": "cd public && npm run watch"
|
||||||
}
|
}
|
||||||
}
|
}
|
41
phpunit.xml
41
phpunit.xml
@ -1,25 +1,24 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<phpunit
|
<phpunit
|
||||||
colors="true"
|
colors="true"
|
||||||
stopOnFailure="false"
|
stopOnFailure="false"
|
||||||
bootstrap="tests/bootstrap.php"
|
bootstrap="tests/bootstrap.php"
|
||||||
beStrictAboutTestsThatDoNotTestAnything="true"
|
|
||||||
>
|
>
|
||||||
<filter>
|
<filter>
|
||||||
<whitelist>
|
<whitelist>
|
||||||
<directory suffix=".php">src</directory>
|
<directory suffix=".php">src</directory>
|
||||||
</whitelist>
|
</whitelist>
|
||||||
</filter>
|
</filter>
|
||||||
<testsuites>
|
<testsuites>
|
||||||
<testsuite name="AnimeClient">
|
<testsuite name="AnimeClient">
|
||||||
<directory>tests</directory>
|
<directory>tests</directory>
|
||||||
</testsuite>
|
</testsuite>
|
||||||
</testsuites>
|
</testsuites>
|
||||||
<php>
|
<php>
|
||||||
<server name="HTTP_USER_AGENT" value="Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:38.0) Gecko/20100101 Firefox/38.0" />
|
<server name="HTTP_USER_AGENT" value="Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:38.0) Gecko/20100101 Firefox/38.0" />
|
||||||
<server name="HTTP_HOST" value="localhost" />
|
<server name="HTTP_HOST" value="localhost" />
|
||||||
<server name="SERVER_NAME" value="localhost" />
|
<server name="SERVER_NAME" value="localhost" />
|
||||||
<server name="REQUEST_URI" value="/" />
|
<server name="REQUEST_URI" value="/" />
|
||||||
<server name="REQUEST_METHOD" value="GET" />
|
<server name="REQUEST_METHOD" value="GET" />
|
||||||
</php>
|
</php>
|
||||||
</phpunit>
|
</phpunit>
|
@ -16,6 +16,8 @@
|
|||||||
|
|
||||||
namespace Aviat\AnimeClient\API;
|
namespace Aviat\AnimeClient\API;
|
||||||
|
|
||||||
|
use Aviat\Ion\Json;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class encapsulating Json API data structure for a request or response
|
* Class encapsulating Json API data structure for a request or response
|
||||||
*/
|
*/
|
||||||
@ -52,7 +54,7 @@ class JsonAPI {
|
|||||||
*
|
*
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $included = [];
|
public $included = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pagination links
|
* Pagination links
|
||||||
@ -70,6 +72,11 @@ class JsonAPI {
|
|||||||
{
|
{
|
||||||
$this->data = $initial;
|
$this->data = $initial;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function parseFromString(string $json)
|
||||||
|
{
|
||||||
|
$this->parse(Json::decode($json));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse a JsonAPI response into its components
|
* Parse a JsonAPI response into its components
|
||||||
@ -78,7 +85,7 @@ class JsonAPI {
|
|||||||
*/
|
*/
|
||||||
public function parse(array $data)
|
public function parse(array $data)
|
||||||
{
|
{
|
||||||
|
$this->included = static::organizeIncludes($data['included']);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -91,4 +98,107 @@ class JsonAPI {
|
|||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Take inlined included data and inline it into the main object's relationships
|
||||||
|
*
|
||||||
|
* @param array $mainObject
|
||||||
|
* @param array $included
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public static function inlineIncludedIntoMainObject(array $mainObject, array $included): array
|
||||||
|
{
|
||||||
|
$output = clone $mainObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Take organized includes and inline them, where applicable
|
||||||
|
*
|
||||||
|
* @param array $included
|
||||||
|
* @param string $key The key of the include to inline the other included values into
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public static function inlineIncludedRelationships(array $included, string $key): array
|
||||||
|
{
|
||||||
|
$inlined = [
|
||||||
|
$key => []
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($included[$key] as $itemId => $item)
|
||||||
|
{
|
||||||
|
// Duplicate the item for the output
|
||||||
|
$inlined[$key][$itemId] = $item;
|
||||||
|
|
||||||
|
foreach($item['relationships'] as $type => $ids)
|
||||||
|
{
|
||||||
|
$inlined[$key][$itemId]['relationships'][$type] = [];
|
||||||
|
foreach($ids as $id)
|
||||||
|
{
|
||||||
|
$inlined[$key][$itemId]['relationships'][$type][$id] = $included[$type][$id];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $inlined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reorganizes 'included' data to be keyed by
|
||||||
|
* type => [
|
||||||
|
* id => data/attributes,
|
||||||
|
* ]
|
||||||
|
*
|
||||||
|
* @param array $includes
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public static function organizeIncludes(array $includes): array
|
||||||
|
{
|
||||||
|
$organized = [];
|
||||||
|
|
||||||
|
foreach ($includes as $item)
|
||||||
|
{
|
||||||
|
$type = $item['type'];
|
||||||
|
$id = $item['id'];
|
||||||
|
$organized[$type] = $organized[$type] ?? [];
|
||||||
|
$organized[$type][$id] = $item['attributes'];
|
||||||
|
|
||||||
|
if (array_key_exists('relationships', $item))
|
||||||
|
{
|
||||||
|
$organized[$type][$id]['relationships'] = static::organizeRelationships($item['relationships']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $organized;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reorganize relationship mappings to make them simpler to use
|
||||||
|
*
|
||||||
|
* Remove verbose structure, and just map:
|
||||||
|
* type => [ idArray ]
|
||||||
|
*
|
||||||
|
* @param array $relationships
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public static function organizeRelationships(array $relationships): array
|
||||||
|
{
|
||||||
|
$organized = [];
|
||||||
|
|
||||||
|
foreach($relationships as $key => $data)
|
||||||
|
{
|
||||||
|
if ( ! array_key_exists('data', $data))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$organized[$key] = $organized[$key] ?? [];
|
||||||
|
|
||||||
|
foreach ($data['data'] as $item)
|
||||||
|
{
|
||||||
|
$organized[$key][] = $item['id'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $organized;
|
||||||
|
}
|
||||||
}
|
}
|
@ -18,6 +18,7 @@ namespace Aviat\AnimeClient\API\Kitsu;
|
|||||||
|
|
||||||
use Aviat\AnimeClient\AnimeClient;
|
use Aviat\AnimeClient\AnimeClient;
|
||||||
use Aviat\Ion\Di\{ContainerAware, ContainerInterface};
|
use Aviat\Ion\Di\{ContainerAware, ContainerInterface};
|
||||||
|
use Exception;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Kitsu API Authentication
|
* Kitsu API Authentication
|
||||||
@ -64,7 +65,16 @@ class Auth {
|
|||||||
{
|
{
|
||||||
$config = $this->container->get('config');
|
$config = $this->container->get('config');
|
||||||
$username = $config->get(['kitsu_username']);
|
$username = $config->get(['kitsu_username']);
|
||||||
$auth_token = $this->model->authenticate($username, $password);
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
$auth_token = $this->model->authenticate($username, $password);
|
||||||
|
}
|
||||||
|
catch (Exception $e)
|
||||||
|
{
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if (FALSE !== $auth_token)
|
if (FALSE !== $auth_token)
|
||||||
{
|
{
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
namespace Aviat\AnimeClient\API\Kitsu;
|
namespace Aviat\AnimeClient\API\Kitsu;
|
||||||
|
|
||||||
|
use Aviat\AnimeClient\API\JsonAPI;
|
||||||
use Aviat\AnimeClient\API\Kitsu as K;
|
use Aviat\AnimeClient\API\Kitsu as K;
|
||||||
use Aviat\AnimeClient\API\Kitsu\Transformer\{
|
use Aviat\AnimeClient\API\Kitsu\Transformer\{
|
||||||
AnimeTransformer, AnimeListTransformer, MangaTransformer, MangaListTransformer
|
AnimeTransformer, AnimeListTransformer, MangaTransformer, MangaListTransformer
|
||||||
@ -155,30 +156,23 @@ class KitsuModel {
|
|||||||
'media_type' => 'Anime',
|
'media_type' => 'Anime',
|
||||||
'status' => $status,
|
'status' => $status,
|
||||||
],
|
],
|
||||||
'include' => 'media,media.genres',
|
'include' => 'media,media.genres,media.mappings',
|
||||||
'page' => [
|
'page' => [
|
||||||
'offset' => 0,
|
'offset' => 0,
|
||||||
'limit' => 1000
|
'limit' => 500
|
||||||
],
|
],
|
||||||
'sort' => '-updated_at'
|
'sort' => '-updated_at'
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
|
|
||||||
$data = $this->getRequest('library-entries', $options);
|
$data = $this->getRequest('library-entries', $options);
|
||||||
$included = K::organizeIncludes($data['included']);
|
$included = JsonAPI::organizeIncludes($data['included']);
|
||||||
|
$included = JsonAPI::inlineIncludedRelationships($included, 'anime');
|
||||||
|
|
||||||
foreach($data['data'] as $i => &$item)
|
foreach($data['data'] as $i => &$item)
|
||||||
{
|
{
|
||||||
$item['anime'] = $included['anime'][$item['relationships']['media']['data']['id']];
|
$item['included'] =& $included;
|
||||||
|
|
||||||
$animeGenres = $item['anime']['relationships']['genres'];
|
|
||||||
|
|
||||||
foreach($animeGenres as $id)
|
|
||||||
{
|
|
||||||
$item['genres'][] = $included['genres'][$id]['name'];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$transformed = $this->animeListTransformer->transformCollection($data['data']);
|
$transformed = $this->animeListTransformer->transformCollection($data['data']);
|
||||||
|
|
||||||
return $transformed;
|
return $transformed;
|
||||||
@ -309,11 +303,7 @@ class KitsuModel {
|
|||||||
];
|
];
|
||||||
|
|
||||||
$data = $this->getRequest($type, $options);
|
$data = $this->getRequest($type, $options);
|
||||||
|
|
||||||
$baseData = $data['data'][0]['attributes'];
|
$baseData = $data['data'][0]['attributes'];
|
||||||
$rawGenres = array_pluck($data['included'], 'attributes');
|
|
||||||
$genres = array_pluck($rawGenres, 'name');
|
|
||||||
$baseData['genres'] = $genres;
|
|
||||||
$baseData['included'] = $data['included'];
|
$baseData['included'] = $data['included'];
|
||||||
return $baseData;
|
return $baseData;
|
||||||
}
|
}
|
||||||
|
@ -33,19 +33,35 @@ class AnimeListTransformer extends AbstractTransformer {
|
|||||||
*/
|
*/
|
||||||
public function transform($item)
|
public function transform($item)
|
||||||
{
|
{
|
||||||
/* ?><pre><?= print_r($item, TRUE) ?></pre><?php
|
$included = $item['included'] ?? [];
|
||||||
// die(); */
|
$animeId = $item['relationships']['media']['data']['id'];
|
||||||
$anime = $item['anime']['attributes'] ?? $item['anime'];
|
$anime = $included['anime'][$animeId] ?? $item['anime'];
|
||||||
$genres = $item['genres'] ?? [];
|
$genres = array_column($anime['relationships']['genres'], 'name') ?? [];
|
||||||
|
|
||||||
$rating = (int) 2 * $item['attributes']['rating'];
|
$rating = (int) 2 * $item['attributes']['rating'];
|
||||||
|
|
||||||
$total_episodes = array_key_exists('episodeCount', $anime) && (int) $anime['episodeCount'] !== 0
|
$total_episodes = array_key_exists('episodeCount', $anime) && (int) $anime['episodeCount'] !== 0
|
||||||
? (int) $anime['episodeCount']
|
? (int) $anime['episodeCount']
|
||||||
: '-';
|
: '-';
|
||||||
|
|
||||||
|
$progress = (int) $item['attributes']['progress'] ?? '-';
|
||||||
|
|
||||||
|
$MALid = NULL;
|
||||||
|
|
||||||
|
if (array_key_exists('mappings', $included))
|
||||||
|
{
|
||||||
|
foreach ($included['mappings'] as $mapping)
|
||||||
|
{
|
||||||
|
if ($mapping['externalSite'] === 'myanimelist/anime')
|
||||||
|
{
|
||||||
|
$MALid = $mapping['externalId'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'id' => $item['id'],
|
'id' => $item['id'],
|
||||||
|
'mal_id' => $MALid,
|
||||||
'episodes' => [
|
'episodes' => [
|
||||||
'watched' => $item['attributes']['progress'],
|
'watched' => $item['attributes']['progress'],
|
||||||
'total' => $total_episodes,
|
'total' => $total_episodes,
|
||||||
@ -60,7 +76,6 @@ class AnimeListTransformer extends AbstractTransformer {
|
|||||||
'age_rating' => $anime['ageRating'],
|
'age_rating' => $anime['ageRating'],
|
||||||
'titles' => Kitsu::filterTitles($anime),
|
'titles' => Kitsu::filterTitles($anime),
|
||||||
'slug' => $anime['slug'],
|
'slug' => $anime['slug'],
|
||||||
'url' => $anime['url'] ?? '',
|
|
||||||
'type' => $this->string($anime['showType'])->upperCaseFirst()->__toString(),
|
'type' => $this->string($anime['showType'])->upperCaseFirst()->__toString(),
|
||||||
'image' => $anime['posterImage']['small'],
|
'image' => $anime['posterImage']['small'],
|
||||||
'genres' => $genres,
|
'genres' => $genres,
|
||||||
@ -69,7 +84,7 @@ class AnimeListTransformer extends AbstractTransformer {
|
|||||||
'notes' => $item['attributes']['notes'],
|
'notes' => $item['attributes']['notes'],
|
||||||
'rewatching' => (bool) $item['attributes']['reconsuming'],
|
'rewatching' => (bool) $item['attributes']['reconsuming'],
|
||||||
'rewatched' => (int) $item['attributes']['reconsumeCount'],
|
'rewatched' => (int) $item['attributes']['reconsumeCount'],
|
||||||
'user_rating' => ($rating === 0) ? '-' : $rating,
|
'user_rating' => ($rating === 0) ? '-' : (int) $rating,
|
||||||
'private' => (bool) $item['attributes']['private'] ?? false,
|
'private' => (bool) $item['attributes']['private'] ?? false,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@ -83,18 +98,8 @@ class AnimeListTransformer extends AbstractTransformer {
|
|||||||
*/
|
*/
|
||||||
public function untransform($item)
|
public function untransform($item)
|
||||||
{
|
{
|
||||||
// Messy mapping of boolean values to their API string equivalents
|
$privacy = (array_key_exists('private', $item) && $item['private']);
|
||||||
$privacy = 'false';
|
$rewatching = (array_key_exists('rewatching', $item) && $item['rewatching']);
|
||||||
if (array_key_exists('private', $item) && $item['private'])
|
|
||||||
{
|
|
||||||
$privacy = 'true';
|
|
||||||
}
|
|
||||||
|
|
||||||
$rewatching = 'false';
|
|
||||||
if (array_key_exists('rewatching', $item) && $item['rewatching'])
|
|
||||||
{
|
|
||||||
$rewatching = 'true';
|
|
||||||
}
|
|
||||||
|
|
||||||
$untransformed = [
|
$untransformed = [
|
||||||
'id' => $item['id'],
|
'id' => $item['id'],
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
namespace Aviat\AnimeClient\API\Kitsu\Transformer;
|
namespace Aviat\AnimeClient\API\Kitsu\Transformer;
|
||||||
|
|
||||||
use Aviat\AnimeClient\API\Kitsu;
|
use Aviat\AnimeClient\API\{JsonAPI, Kitsu};
|
||||||
use Aviat\Ion\Transformer\AbstractTransformer;
|
use Aviat\Ion\Transformer\AbstractTransformer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -33,7 +33,8 @@ class AnimeTransformer extends AbstractTransformer {
|
|||||||
*/
|
*/
|
||||||
public function transform($item)
|
public function transform($item)
|
||||||
{
|
{
|
||||||
$item['genres'] = $item['genres'] ?? [];
|
$item['included'] = JsonAPI::organizeIncludes($item['included']);
|
||||||
|
$item['genres'] = array_column($item['included']['genres'], 'name') ?? [];
|
||||||
sort($item['genres']);
|
sort($item['genres']);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
@ -48,6 +49,7 @@ class AnimeTransformer extends AbstractTransformer {
|
|||||||
'age_rating_guide' => $item['ageRatingGuide'],
|
'age_rating_guide' => $item['ageRatingGuide'],
|
||||||
'url' => "https://kitsu.io/anime/{$item['slug']}",
|
'url' => "https://kitsu.io/anime/{$item['slug']}",
|
||||||
'genres' => $item['genres'],
|
'genres' => $item['genres'],
|
||||||
|
'included' => $item['included']
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
namespace Aviat\AnimeClient\API;
|
namespace Aviat\AnimeClient\API;
|
||||||
|
|
||||||
use Aviat\AnimeClient\Enum\{AnimeWatchingStatus, MangaReadingStatus};
|
use Aviat\AnimeClient\API\MAL\Enum\{AnimeWatchingStatus, MangaReadingStatus};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constants and mappings for the My Anime List API
|
* Constants and mappings for the My Anime List API
|
||||||
|
52
src/API/MAL/ListItem.php
Normal file
52
src/API/MAL/ListItem.php
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* Anime List Client
|
||||||
|
*
|
||||||
|
* An API client for Kitsu and MyAnimeList to manage anime and manga watch lists
|
||||||
|
*
|
||||||
|
* PHP version 7
|
||||||
|
*
|
||||||
|
* @package AnimeListClient
|
||||||
|
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||||
|
* @copyright 2015 - 2017 Timothy J. Warren
|
||||||
|
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
||||||
|
* @version 4.0
|
||||||
|
* @link https://github.com/timw4mail/HummingBirdAnimeClient
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Aviat\AnimeClient\API\MAL;
|
||||||
|
|
||||||
|
use Aviat\AnimeClient\API\AbstractListItem;
|
||||||
|
use Aviat\Ion\Di\ContainerAware;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CRUD operations for MAL list items
|
||||||
|
*/
|
||||||
|
class ListItem extends AbstractListItem {
|
||||||
|
use ContainerAware;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->init();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create(array $data): bool
|
||||||
|
{
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete(string $id): bool
|
||||||
|
{
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get(string $id): array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(string $id, array $data): Response
|
||||||
|
{
|
||||||
|
// @TODO implement
|
||||||
|
}
|
||||||
|
}
|
190
src/API/MAL/MALTrait.php
Normal file
190
src/API/MAL/MALTrait.php
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* Anime List Client
|
||||||
|
*
|
||||||
|
* An API client for Kitsu and MyAnimeList to manage anime and manga watch lists
|
||||||
|
*
|
||||||
|
* PHP version 7
|
||||||
|
*
|
||||||
|
* @package AnimeListClient
|
||||||
|
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||||
|
* @copyright 2015 - 2017 Timothy J. Warren
|
||||||
|
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
||||||
|
* @version 4.0
|
||||||
|
* @link https://github.com/timw4mail/HummingBirdAnimeClient
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Aviat\AnimeClient\API\MAL;
|
||||||
|
|
||||||
|
use Aviat\AnimeClient\API\{
|
||||||
|
GuzzleTrait,
|
||||||
|
MAL as M,
|
||||||
|
XML
|
||||||
|
};
|
||||||
|
use GuzzleHttp\Client;
|
||||||
|
use GuzzleHttp\Cookie\CookieJar;
|
||||||
|
use GuzzleHttp\Psr7\Response;
|
||||||
|
use InvalidArgumentException;
|
||||||
|
|
||||||
|
trait MALTrait {
|
||||||
|
use GuzzleTrait;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The base url for api requests
|
||||||
|
* @var string $base_url
|
||||||
|
*/
|
||||||
|
protected $baseUrl = M::BASE_URL;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTTP headers to send with every request
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $defaultHeaders = [
|
||||||
|
'User-Agent' => "Tim's Anime Client/4.0"
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set up the class properties
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
protected function init()
|
||||||
|
{
|
||||||
|
$defaults = [
|
||||||
|
'cookies' => $this->cookieJar,
|
||||||
|
'headers' => $this->defaultHeaders,
|
||||||
|
'timeout' => 25,
|
||||||
|
'connect_timeout' => 25
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->cookieJar = new CookieJar();
|
||||||
|
$this->client = new Client([
|
||||||
|
'base_uri' => $this->baseUrl,
|
||||||
|
'cookies' => TRUE,
|
||||||
|
'http_errors' => TRUE,
|
||||||
|
'defaults' => $defaults
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make a request via Guzzle
|
||||||
|
*
|
||||||
|
* @param string $type
|
||||||
|
* @param string $url
|
||||||
|
* @param array $options
|
||||||
|
* @return Response
|
||||||
|
*/
|
||||||
|
private function getResponse(string $type, string $url, array $options = [])
|
||||||
|
{
|
||||||
|
$type = strtoupper($type);
|
||||||
|
$validTypes = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'];
|
||||||
|
|
||||||
|
if ( ! in_array($type, $validTypes))
|
||||||
|
{
|
||||||
|
throw new InvalidArgumentException('Invalid http request type');
|
||||||
|
}
|
||||||
|
|
||||||
|
$config = $this->container->get('config');
|
||||||
|
$logger = $this->container->getLogger('request');
|
||||||
|
|
||||||
|
$defaultOptions = [
|
||||||
|
'auth' => [
|
||||||
|
$config->get(['mal','username']),
|
||||||
|
$config->get(['mal','password'])
|
||||||
|
],
|
||||||
|
'headers' => $this->defaultHeaders
|
||||||
|
];
|
||||||
|
|
||||||
|
$options = array_merge($defaultOptions, $options);
|
||||||
|
|
||||||
|
$logger->debug(Json::encode([$type, $url]));
|
||||||
|
$logger->debug(Json::encode($options));
|
||||||
|
|
||||||
|
return $this->client->request($type, $url, $options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make a request via Guzzle
|
||||||
|
*
|
||||||
|
* @param string $type
|
||||||
|
* @param string $url
|
||||||
|
* @param array $options
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private function request(string $type, string $url, array $options = []): array
|
||||||
|
{
|
||||||
|
$logger = null;
|
||||||
|
if ($this->getContainer())
|
||||||
|
{
|
||||||
|
$logger = $this->container->getLogger('request');
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = $this->getResponse($type, $url, $options);
|
||||||
|
|
||||||
|
if ((int) $response->getStatusCode() > 299 || (int) $response->getStatusCode() < 200)
|
||||||
|
{
|
||||||
|
if ($logger)
|
||||||
|
{
|
||||||
|
$logger->warning('Non 200 response for api call');
|
||||||
|
$logger->warning($response->getBody());
|
||||||
|
}
|
||||||
|
|
||||||
|
// throw new RuntimeException($response->getBody());
|
||||||
|
}
|
||||||
|
|
||||||
|
return XML::toArray((string) $response->getBody());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove some boilerplate for get requests
|
||||||
|
*
|
||||||
|
* @param array $args
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function getRequest(...$args): array
|
||||||
|
{
|
||||||
|
return $this->request('GET', ...$args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove some boilerplate for post requests
|
||||||
|
*
|
||||||
|
* @param array $args
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function postRequest(...$args): array
|
||||||
|
{
|
||||||
|
$logger = null;
|
||||||
|
if ($this->getContainer())
|
||||||
|
{
|
||||||
|
$logger = $this->container->getLogger('request');
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = $this->getResponse('POST', ...$args);
|
||||||
|
$validResponseCodes = [200, 201];
|
||||||
|
|
||||||
|
if ( ! in_array((int) $response->getStatusCode(), $validResponseCodes))
|
||||||
|
{
|
||||||
|
if ($logger)
|
||||||
|
{
|
||||||
|
$logger->warning('Non 201 response for POST api call');
|
||||||
|
$logger->warning($response->getBody());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return XML::toArray((string) $response->getBody());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove some boilerplate for delete requests
|
||||||
|
*
|
||||||
|
* @param array $args
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
protected function deleteRequest(...$args): bool
|
||||||
|
{
|
||||||
|
$response = $this->getResponse('DELETE', ...$args);
|
||||||
|
return ((int) $response->getStatusCode() === 204);
|
||||||
|
}
|
||||||
|
}
|
@ -1,63 +1,49 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php declare(strict_types=1);
|
||||||
/**
|
/**
|
||||||
* Anime List Client
|
* Anime List Client
|
||||||
*
|
*
|
||||||
* An API client for Kitsu and MyAnimeList to manage anime and manga watch lists
|
* An API client for Kitsu and MyAnimeList to manage anime and manga watch lists
|
||||||
*
|
*
|
||||||
* PHP version 7
|
* PHP version 7
|
||||||
*
|
*
|
||||||
* @package AnimeListClient
|
* @package AnimeListClient
|
||||||
* @author Timothy J. Warren <tim@timshomepage.net>
|
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||||
* @copyright 2015 - 2017 Timothy J. Warren
|
* @copyright 2015 - 2017 Timothy J. Warren
|
||||||
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
||||||
* @version 4.0
|
* @version 4.0
|
||||||
* @link https://github.com/timw4mail/HummingBirdAnimeClient
|
* @link https://github.com/timw4mail/HummingBirdAnimeClient
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace Aviat\AnimeClient\API\Kitsu;
|
|
||||||
|
|
||||||
use Aviat\AnimeClient\Model\API;
|
namespace Aviat\AnimeClient\API\MAL;
|
||||||
|
|
||||||
|
use Aviat\AnimeClient\API\MAL as M;
|
||||||
|
use Aviat\Ion\Di\ContainerAware;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MyAnimeList API Model
|
* MyAnimeList API Model
|
||||||
*/
|
*/
|
||||||
class Model extends API {
|
class Model {
|
||||||
|
|
||||||
|
use ContainerAware;
|
||||||
|
use MALTrait;
|
||||||
|
|
||||||
/**
|
public function createListItem(array $data): bool
|
||||||
* Base url for Kitsu API
|
{
|
||||||
*/
|
|
||||||
protected $baseUrl = 'https://myanimelist.net/api/';
|
|
||||||
|
|
||||||
/**
|
}
|
||||||
* Default settings for Guzzle
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
protected $connectionDefaults = [];
|
|
||||||
|
|
||||||
/**
|
public function getListItem(string $listId): array
|
||||||
* Get the access token from the Kitsu API
|
{
|
||||||
*
|
|
||||||
* @param string $username
|
|
||||||
* @param string $password
|
|
||||||
* @return bool|string
|
|
||||||
*/
|
|
||||||
public function authenticate(string $username, string $password)
|
|
||||||
{
|
|
||||||
$response = $this->post('account/', [
|
|
||||||
'body' => http_build_query([
|
|
||||||
'grant_type' => 'password',
|
|
||||||
'username' => $username,
|
|
||||||
'password' => $password
|
|
||||||
])
|
|
||||||
]);
|
|
||||||
|
|
||||||
$info = $response->getBody();
|
}
|
||||||
|
|
||||||
if (array_key_exists('access_token', $info)) {
|
public function updateListItem(array $data)
|
||||||
// @TODO save token
|
{
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
}
|
||||||
}
|
|
||||||
|
public function deleteListItem(string $id): bool
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
46
src/API/MAL/Transformer/AnimeListTransformer.php
Normal file
46
src/API/MAL/Transformer/AnimeListTransformer.php
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* Anime List Client
|
||||||
|
*
|
||||||
|
* An API client for Kitsu and MyAnimeList to manage anime and manga watch lists
|
||||||
|
*
|
||||||
|
* PHP version 7
|
||||||
|
*
|
||||||
|
* @package AnimeListClient
|
||||||
|
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||||
|
* @copyright 2015 - 2017 Timothy J. Warren
|
||||||
|
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
||||||
|
* @version 4.0
|
||||||
|
* @link https://github.com/timw4mail/HummingBirdAnimeClient
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Aviat\AnimeClient\API\MAL;
|
||||||
|
|
||||||
|
use Aviat\Ion\Transformer\AbstractTransformer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transformer for updating MAL List
|
||||||
|
*/
|
||||||
|
class AnimeListTransformer extends AbstractTransformer {
|
||||||
|
|
||||||
|
public function transform($item)
|
||||||
|
{
|
||||||
|
$rewatching = 'false';
|
||||||
|
if (array_key_exists('rewatching', $item) && $item['rewatching'])
|
||||||
|
{
|
||||||
|
$rewatching = 'true';
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'id' => $item['id'],
|
||||||
|
'data' => [
|
||||||
|
'status' => $item['watching_status'],
|
||||||
|
'rating' => $item['user_rating'],
|
||||||
|
'rewatch_value' => (int) $rewatching,
|
||||||
|
'times_rewatched' => $item['rewatched'],
|
||||||
|
'comments' => $item['notes'],
|
||||||
|
'episode' => $item['episodes_watched']
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
33
src/API/MAL/Transformer/MALToKitsuTransformer.php
Normal file
33
src/API/MAL/Transformer/MALToKitsuTransformer.php
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* Anime List Client
|
||||||
|
*
|
||||||
|
* An API client for Kitsu and MyAnimeList to manage anime and manga watch lists
|
||||||
|
*
|
||||||
|
* PHP version 7
|
||||||
|
*
|
||||||
|
* @package AnimeListClient
|
||||||
|
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||||
|
* @copyright 2015 - 2017 Timothy J. Warren
|
||||||
|
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
||||||
|
* @version 4.0
|
||||||
|
* @link https://github.com/timw4mail/HummingBirdAnimeClient
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Aviat\AnimeClient\API\MAL;
|
||||||
|
|
||||||
|
use Aviat\Ion\Transformer\AbstractTransformer;
|
||||||
|
|
||||||
|
class MALToKitsuTransformer extends AbstractTransformer {
|
||||||
|
|
||||||
|
|
||||||
|
public function transform($item)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function untransform($item)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -304,7 +304,8 @@ class Controller {
|
|||||||
return $this->session_redirect();
|
return $this->session_redirect();
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->login("Invalid username or password.");
|
$this->set_flash_message('Invalid username or password.');
|
||||||
|
$this->redirect($this->urlGenerator->url('login'), 303);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -16,7 +16,6 @@
|
|||||||
|
|
||||||
namespace Aviat\AnimeClient;
|
namespace Aviat\AnimeClient;
|
||||||
|
|
||||||
use abeautifulsite\SimpleImage;
|
|
||||||
use Aviat\Ion\ConfigInterface;
|
use Aviat\Ion\ConfigInterface;
|
||||||
use Aviat\Ion\Di\{ContainerAware, ContainerInterface};
|
use Aviat\Ion\Di\{ContainerAware, ContainerInterface};
|
||||||
use DomainException;
|
use DomainException;
|
||||||
|
48
tests/API/JsonAPITest.php
Normal file
48
tests/API/JsonAPITest.php
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* Anime List Client
|
||||||
|
*
|
||||||
|
* An API client for Kitsu and MyAnimeList to manage anime and manga watch lists
|
||||||
|
*
|
||||||
|
* PHP version 7
|
||||||
|
*
|
||||||
|
* @package AnimeListClient
|
||||||
|
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||||
|
* @copyright 2015 - 2017 Timothy J. Warren
|
||||||
|
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
||||||
|
* @version 4.0
|
||||||
|
* @link https://github.com/timw4mail/HummingBirdAnimeClient
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Aviat\AnimeClient\Tests\API;
|
||||||
|
|
||||||
|
use Aviat\AnimeClient\API\JsonAPI;
|
||||||
|
use Aviat\Ion\Json;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
class JsonAPITest extends TestCase {
|
||||||
|
|
||||||
|
public function setUp()
|
||||||
|
{
|
||||||
|
$dir = __DIR__ . '/../test_data/JsonAPI';
|
||||||
|
$this->startData = Json::decodeFile("{$dir}/jsonApiExample.json");
|
||||||
|
$this->organizedIncludes = Json::decodeFile("{$dir}/organizedIncludes.json");
|
||||||
|
$this->inlineIncluded = Json::decodeFile("{$dir}/inlineIncluded.json");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testOrganizeIncludes()
|
||||||
|
{
|
||||||
|
$expected = $this->organizedIncludes;
|
||||||
|
$actual = JsonAPI::organizeIncludes($this->startData['included']);
|
||||||
|
|
||||||
|
$this->assertEquals($expected, $actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testInlineIncludedRelationships()
|
||||||
|
{
|
||||||
|
$expected = $this->inlineIncluded;
|
||||||
|
$actual = JsonAPI::inlineIncludedRelationships($this->organizedIncludes, 'anime');
|
||||||
|
|
||||||
|
$this->assertEquals($expected, $actual);
|
||||||
|
}
|
||||||
|
}
|
90
tests/API/Kitsu/Transformer/AnimeListTransformerTest.php
Normal file
90
tests/API/Kitsu/Transformer/AnimeListTransformerTest.php
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Aviat\AnimeClient\Tests\API\Kitsu\Transformer;
|
||||||
|
|
||||||
|
use AnimeClient_TestCase;
|
||||||
|
use Aviat\AnimeClient\API\Kitsu\Transformer\AnimeListTransformer;
|
||||||
|
use Aviat\Ion\Friend;
|
||||||
|
use Aviat\Ion\Json;
|
||||||
|
|
||||||
|
class AnimeListTransformerTest extends AnimeClient_TestCase {
|
||||||
|
|
||||||
|
public function setUp()
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
$dir = AnimeClient_TestCase::TEST_DATA_DIR . '/Kitsu';
|
||||||
|
|
||||||
|
$this->beforeTransform = Json::decodeFile("{$dir}/animeListItemBeforeTransform.json");
|
||||||
|
$this->afterTransform = Json::decodeFile("{$dir}/animeListItemAfterTransform.json");
|
||||||
|
|
||||||
|
$this->transformer = new AnimeListTransformer();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testTransform()
|
||||||
|
{
|
||||||
|
$expected = $this->afterTransform;
|
||||||
|
$actual = $this->transformer->transform($this->beforeTransform);
|
||||||
|
|
||||||
|
$this->assertEquals($expected, $actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function dataUntransform()
|
||||||
|
{
|
||||||
|
return [[
|
||||||
|
'input' => [
|
||||||
|
'id' => 14047981,
|
||||||
|
'watching_status' => 'current',
|
||||||
|
'user_rating' => 8,
|
||||||
|
'episodes_watched' => 38,
|
||||||
|
'rewatched' => 0,
|
||||||
|
'notes' => 'Very formulaic.',
|
||||||
|
'edit' => true
|
||||||
|
],
|
||||||
|
'expected' => [
|
||||||
|
'id' => 14047981,
|
||||||
|
'data' => [
|
||||||
|
'status' => 'current',
|
||||||
|
'rating' => 4,
|
||||||
|
'reconsuming' => false,
|
||||||
|
'reconsumeCount' => 0,
|
||||||
|
'notes' => 'Very formulaic.',
|
||||||
|
'progress' => 38,
|
||||||
|
'private' => false
|
||||||
|
]
|
||||||
|
]
|
||||||
|
], [
|
||||||
|
'input' => [
|
||||||
|
'id' => 14047981,
|
||||||
|
'watching_status' => 'current',
|
||||||
|
'user_rating' => 8,
|
||||||
|
'episodes_watched' => 38,
|
||||||
|
'rewatched' => 0,
|
||||||
|
'notes' => 'Very formulaic.',
|
||||||
|
'edit' => 'true',
|
||||||
|
'private' => 'On',
|
||||||
|
'rewatching' => 'On'
|
||||||
|
],
|
||||||
|
'expected' => [
|
||||||
|
'id' => 14047981,
|
||||||
|
'data' => [
|
||||||
|
'status' => 'current',
|
||||||
|
'rating' => 4,
|
||||||
|
'reconsuming' => true,
|
||||||
|
'reconsumeCount' => 0,
|
||||||
|
'notes' => 'Very formulaic.',
|
||||||
|
'progress' => 38,
|
||||||
|
'private' => true,
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider dataUntransform
|
||||||
|
*/
|
||||||
|
public function testUntransform($input, $expected)
|
||||||
|
{
|
||||||
|
$actual = $this->transformer->untransform($input);
|
||||||
|
$this->assertEquals($expected, $actual);
|
||||||
|
}
|
||||||
|
}
|
30
tests/API/Kitsu/Transformer/AnimeTransformerTest.php
Normal file
30
tests/API/Kitsu/Transformer/AnimeTransformerTest.php
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Aviat\AnimeClient\Tests\API\Kitsu\Transformer;
|
||||||
|
|
||||||
|
use AnimeClient_TestCase;
|
||||||
|
use Aviat\AnimeClient\API\Kitsu\Transformer\AnimeTransformer;
|
||||||
|
use Aviat\Ion\Friend;
|
||||||
|
use Aviat\Ion\Json;
|
||||||
|
|
||||||
|
class AnimeTransformerTest extends AnimeClient_TestCase {
|
||||||
|
|
||||||
|
public function setUp()
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
$dir = AnimeClient_TestCase::TEST_DATA_DIR . '/Kitsu';
|
||||||
|
|
||||||
|
//$this->beforeTransform = Json::decodeFile("{$dir}/animeBeforeTransform.json");
|
||||||
|
//$this->afterTransform = Json::decodeFile("{$dir}/animeAfterTransform.json");
|
||||||
|
|
||||||
|
$this->transformer = new AnimeTransformer();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testTransform()
|
||||||
|
{
|
||||||
|
/*$expected = $this->afterTransform;
|
||||||
|
$actual = $this->transformer->transform($this->beforeTransform);
|
||||||
|
|
||||||
|
$this->assertEquals($expected, $actual);*/
|
||||||
|
}
|
||||||
|
}
|
@ -1,18 +1,4 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php declare(strict_types=1);
|
||||||
/**
|
|
||||||
* Anime List Client
|
|
||||||
*
|
|
||||||
* An API client for Kitsu and MyAnimeList to manage anime and manga watch lists
|
|
||||||
*
|
|
||||||
* PHP version 7
|
|
||||||
*
|
|
||||||
* @package AnimeListClient
|
|
||||||
* @author Timothy J. Warren <tim@timshomepage.net>
|
|
||||||
* @copyright 2015 - 2017 Timothy J. Warren
|
|
||||||
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
|
||||||
* @version 4.0
|
|
||||||
* @link https://github.com/timw4mail/HummingBirdAnimeClient
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Aviat\AnimeClient\Tests\API;
|
namespace Aviat\AnimeClient\Tests\API;
|
||||||
|
|
||||||
@ -23,8 +9,8 @@ class XMLTest extends TestCase {
|
|||||||
|
|
||||||
public function setUp()
|
public function setUp()
|
||||||
{
|
{
|
||||||
$this->xml = file_get_contents(__DIR__ . '/../test_data/xmlTestFile.xml');
|
$this->xml = file_get_contents(__DIR__ . '/../test_data/XML/xmlTestFile.xml');
|
||||||
$this->expectedXml = file_get_contents(__DIR__ . '/../test_data/minifiedXmlTestFile.xml');
|
$this->expectedXml = file_get_contents(__DIR__ . '/../test_data/XML/minifiedXmlTestFile.xml');
|
||||||
|
|
||||||
$this->array = [
|
$this->array = [
|
||||||
'entry' => [
|
'entry' => [
|
||||||
|
@ -7,8 +7,11 @@ use GuzzleHttp\Client;
|
|||||||
use GuzzleHttp\Handler\MockHandler;
|
use GuzzleHttp\Handler\MockHandler;
|
||||||
use GuzzleHttp\HandlerStack;
|
use GuzzleHttp\HandlerStack;
|
||||||
use GuzzleHttp\Psr7\Response;
|
use GuzzleHttp\Psr7\Response;
|
||||||
use Zend\Diactoros\Response as HttpResponse;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Zend\Diactoros\ServerRequestFactory;
|
use Zend\Diactoros\{
|
||||||
|
Response as HttpResponse,
|
||||||
|
ServerRequestFactory
|
||||||
|
};
|
||||||
|
|
||||||
define('ROOT_DIR', __DIR__ . '/../');
|
define('ROOT_DIR', __DIR__ . '/../');
|
||||||
define('TEST_DATA_DIR', __DIR__ . '/test_data');
|
define('TEST_DATA_DIR', __DIR__ . '/test_data');
|
||||||
@ -17,7 +20,7 @@ define('TEST_VIEW_DIR', __DIR__ . '/test_views');
|
|||||||
/**
|
/**
|
||||||
* Base class for TestCases
|
* Base class for TestCases
|
||||||
*/
|
*/
|
||||||
class AnimeClient_TestCase extends PHPUnit_Framework_TestCase {
|
class AnimeClient_TestCase extends TestCase {
|
||||||
// Test directory constants
|
// Test directory constants
|
||||||
const ROOT_DIR = ROOT_DIR;
|
const ROOT_DIR = ROOT_DIR;
|
||||||
const SRC_DIR = AnimeClient::SRC_DIR;
|
const SRC_DIR = AnimeClient::SRC_DIR;
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
<?php
|
<?php declare(strict_types=1);
|
||||||
use Aura\Router\RouterFactory;
|
use Aura\Router\RouterFactory;
|
||||||
use Aura\Web\WebFactory;
|
use Aura\Web\WebFactory;
|
||||||
use Aviat\AnimeClient\Controller;
|
use Aviat\AnimeClient\Controller;
|
||||||
use Aviat\AnimeClient\Controller\Anime as AnimeController;
|
use Aviat\AnimeClient\Controller\{
|
||||||
use Aviat\AnimeClient\Controller\Collection as CollectionController;
|
Anime as AnimeController,
|
||||||
use Aviat\AnimeClient\Controller\Manga as MangaController;
|
Collection as CollectionController,
|
||||||
|
Manga as MangaController
|
||||||
|
};
|
||||||
|
|
||||||
class ControllerTest extends AnimeClient_TestCase {
|
class ControllerTest extends AnimeClient_TestCase {
|
||||||
|
|
||||||
|
1
tests/test_data/JsonAPI/inlineIncluded.json
Normal file
1
tests/test_data/JsonAPI/inlineIncluded.json
Normal file
File diff suppressed because one or more lines are too long
385
tests/test_data/JsonAPI/organizedIncludes.json
Normal file
385
tests/test_data/JsonAPI/organizedIncludes.json
Normal file
@ -0,0 +1,385 @@
|
|||||||
|
{
|
||||||
|
"anime": {
|
||||||
|
"11474": {
|
||||||
|
"slug": "hibike-euphonium-2",
|
||||||
|
"synopsis": "Second season of Hibike! Euphonium.",
|
||||||
|
"coverImageTopOffset": 120,
|
||||||
|
"titles": {
|
||||||
|
"en": "Sound! Euphonium 2",
|
||||||
|
"en_jp": "Hibike! Euphonium 2",
|
||||||
|
"ja_jp": "\u97ff\u3051\uff01\u30e6\u30fc\u30d5\u30a9\u30cb\u30a2\u30e0 \uff12"
|
||||||
|
},
|
||||||
|
"canonicalTitle": "Hibike! Euphonium 2",
|
||||||
|
"abbreviatedTitles": null,
|
||||||
|
"averageRating": 4.1684326428476,
|
||||||
|
"ratingFrequencies": {
|
||||||
|
"0.5": "1",
|
||||||
|
"1.0": "1",
|
||||||
|
"1.5": "2",
|
||||||
|
"2.0": "8",
|
||||||
|
"2.5": "13",
|
||||||
|
"3.0": "42",
|
||||||
|
"3.5": "90",
|
||||||
|
"4.0": "193",
|
||||||
|
"4.5": "180",
|
||||||
|
"5.0": "193",
|
||||||
|
"nil": "1972"
|
||||||
|
},
|
||||||
|
"startDate": "2016-10-06",
|
||||||
|
"endDate": null,
|
||||||
|
"posterImage": {
|
||||||
|
"tiny": "https:\/\/media.kitsu.io\/anime\/poster_images\/11474\/tiny.jpg?1470781430",
|
||||||
|
"small": "https:\/\/media.kitsu.io\/anime\/poster_images\/11474\/small.jpg?1470781430",
|
||||||
|
"medium": "https:\/\/media.kitsu.io\/anime\/poster_images\/11474\/medium.jpg?1470781430",
|
||||||
|
"large": "https:\/\/media.kitsu.io\/anime\/poster_images\/11474\/large.jpg?1470781430",
|
||||||
|
"original": "https:\/\/media.kitsu.io\/anime\/poster_images\/11474\/original.jpg?1470781430"
|
||||||
|
},
|
||||||
|
"coverImage": {
|
||||||
|
"small": "https:\/\/media.kitsu.io\/anime\/cover_images\/11474\/small.jpg?1476203965",
|
||||||
|
"large": "https:\/\/media.kitsu.io\/anime\/cover_images\/11474\/large.jpg?1476203965",
|
||||||
|
"original": "https:\/\/media.kitsu.io\/anime\/cover_images\/11474\/original.jpg?1476203965"
|
||||||
|
},
|
||||||
|
"episodeCount": 13,
|
||||||
|
"episodeLength": 25,
|
||||||
|
"subtype": "TV",
|
||||||
|
"youtubeVideoId": "d2Di5swwzxg",
|
||||||
|
"ageRating": "PG",
|
||||||
|
"ageRatingGuide": "",
|
||||||
|
"showType": "TV",
|
||||||
|
"nsfw": false,
|
||||||
|
"relationships": {
|
||||||
|
"genres": ["24", "35", "4"],
|
||||||
|
"mappings": ["3155"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"10802": {
|
||||||
|
"slug": "nisekoimonogatari",
|
||||||
|
"synopsis": "Trailer for a fake anime created by Shaft as an April Fool's Day joke.",
|
||||||
|
"coverImageTopOffset": 80,
|
||||||
|
"titles": {
|
||||||
|
"en": "",
|
||||||
|
"en_jp": "Nisekoimonogatari",
|
||||||
|
"ja_jp": ""
|
||||||
|
},
|
||||||
|
"canonicalTitle": "Nisekoimonogatari",
|
||||||
|
"abbreviatedTitles": null,
|
||||||
|
"averageRating": 3.4857993435287,
|
||||||
|
"ratingFrequencies": {
|
||||||
|
"0.5": "22",
|
||||||
|
"1.0": "10",
|
||||||
|
"1.5": "16",
|
||||||
|
"2.0": "32",
|
||||||
|
"2.5": "74",
|
||||||
|
"3.0": "97",
|
||||||
|
"3.5": "118",
|
||||||
|
"4.0": "72",
|
||||||
|
"4.5": "34",
|
||||||
|
"5.0": "136",
|
||||||
|
"nil": "597",
|
||||||
|
"0.89": "-1",
|
||||||
|
"3.63": "-1",
|
||||||
|
"4.11": "-1",
|
||||||
|
"0.068": "-1",
|
||||||
|
"0.205": "-1",
|
||||||
|
"0.274": "-2",
|
||||||
|
"0.479": "-1",
|
||||||
|
"0.548": "-1",
|
||||||
|
"1.096": "-2",
|
||||||
|
"1.164": "-1",
|
||||||
|
"1.438": "-1",
|
||||||
|
"1.918": "-1",
|
||||||
|
"2.055": "-1",
|
||||||
|
"3.973": "-1",
|
||||||
|
"4.178": "-3",
|
||||||
|
"4.247": "-1",
|
||||||
|
"4.726": "-1",
|
||||||
|
"4.932": "-3",
|
||||||
|
"1.0958904109589": "3",
|
||||||
|
"0.89041095890411": "2",
|
||||||
|
"1.02739726027397": "1",
|
||||||
|
"1.16438356164384": "2",
|
||||||
|
"1.43835616438356": "2",
|
||||||
|
"1.57534246575342": "1",
|
||||||
|
"1.91780821917808": "1",
|
||||||
|
"2.05479452054794": "2",
|
||||||
|
"2.12328767123288": "1",
|
||||||
|
"2.73972602739726": "1",
|
||||||
|
"2.80821917808219": "2",
|
||||||
|
"2.94520547945205": "1",
|
||||||
|
"3.15068493150685": "1",
|
||||||
|
"3.35616438356164": "2",
|
||||||
|
"3.63013698630137": "2",
|
||||||
|
"3.97260273972603": "1",
|
||||||
|
"4.10958904109589": "2",
|
||||||
|
"4.17808219178082": "3",
|
||||||
|
"4.24657534246575": "1",
|
||||||
|
"4.38356164383562": "2",
|
||||||
|
"4.65753424657534": "1",
|
||||||
|
"4.72602739726027": "2",
|
||||||
|
"4.86301369863014": "1",
|
||||||
|
"4.93150684931507": "10",
|
||||||
|
"0.205479452054795": "1",
|
||||||
|
"0.273972602739726": "2",
|
||||||
|
"0.479452054794521": "2",
|
||||||
|
"0.547945205479452": "2",
|
||||||
|
"0.753424657534246": "1",
|
||||||
|
"0.0684931506849315": "1"
|
||||||
|
},
|
||||||
|
"startDate": "2015-04-01",
|
||||||
|
"endDate": null,
|
||||||
|
"posterImage": {
|
||||||
|
"tiny": "https:\/\/media.kitsu.io\/anime\/poster_images\/10802\/tiny.jpg?1427974534",
|
||||||
|
"small": "https:\/\/media.kitsu.io\/anime\/poster_images\/10802\/small.jpg?1427974534",
|
||||||
|
"medium": "https:\/\/media.kitsu.io\/anime\/poster_images\/10802\/medium.jpg?1427974534",
|
||||||
|
"large": "https:\/\/media.kitsu.io\/anime\/poster_images\/10802\/large.jpg?1427974534",
|
||||||
|
"original": "https:\/\/media.kitsu.io\/anime\/poster_images\/10802\/original.jpg?1427974534"
|
||||||
|
},
|
||||||
|
"coverImage": {
|
||||||
|
"small": "https:\/\/media.kitsu.io\/anime\/cover_images\/10802\/small.jpg?1427928458",
|
||||||
|
"large": "https:\/\/media.kitsu.io\/anime\/cover_images\/10802\/large.jpg?1427928458",
|
||||||
|
"original": "https:\/\/media.kitsu.io\/anime\/cover_images\/10802\/original.jpg?1427928458"
|
||||||
|
},
|
||||||
|
"episodeCount": 1,
|
||||||
|
"episodeLength": 1,
|
||||||
|
"subtype": "ONA",
|
||||||
|
"youtubeVideoId": "",
|
||||||
|
"ageRating": "PG",
|
||||||
|
"ageRatingGuide": "Teens 13 or older",
|
||||||
|
"showType": "ONA",
|
||||||
|
"nsfw": false,
|
||||||
|
"relationships": {
|
||||||
|
"genres": ["3"],
|
||||||
|
"mappings": ["1755"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"11887": {
|
||||||
|
"slug": "brave-witches",
|
||||||
|
"synopsis": "In September 1944, allied forces led by the 501st Joint Fighter Wing \"Strike Witches\" successfully eliminate the Neuroi threat from the skies of the Republic of Gallia, thus ensuring the security of western Europe. Taking advantage of this victory, allied forces begin a full-fledged push toward central and eastern Europe. From a base in Petersburg in the Empire of Orussia, the 502nd Joint Fighter Wing \"Brave Witches,\" upon whom mankind has placed its hopes, flies with courage in the cold skies of eastern Europe.\n\n(Source: MAL News)",
|
||||||
|
"coverImageTopOffset": 380,
|
||||||
|
"titles": {
|
||||||
|
"en": "",
|
||||||
|
"en_jp": "Brave Witches",
|
||||||
|
"ja_jp": "\u30d6\u30ec\u30a4\u30d6\u30a6\u30a3\u30c3\u30c1\u30fc\u30ba"
|
||||||
|
},
|
||||||
|
"canonicalTitle": "Brave Witches",
|
||||||
|
"abbreviatedTitles": null,
|
||||||
|
"averageRating": 3.5846888163849,
|
||||||
|
"ratingFrequencies": {
|
||||||
|
"0.5": "1",
|
||||||
|
"1.0": "4",
|
||||||
|
"1.5": "8",
|
||||||
|
"2.0": "12",
|
||||||
|
"2.5": "17",
|
||||||
|
"3.0": "33",
|
||||||
|
"3.5": "41",
|
||||||
|
"4.0": "32",
|
||||||
|
"4.5": "9",
|
||||||
|
"5.0": "19",
|
||||||
|
"nil": "620"
|
||||||
|
},
|
||||||
|
"startDate": "2016-10-06",
|
||||||
|
"endDate": null,
|
||||||
|
"posterImage": {
|
||||||
|
"tiny": "https:\/\/media.kitsu.io\/anime\/poster_images\/11887\/tiny.jpg?1476481854",
|
||||||
|
"small": "https:\/\/media.kitsu.io\/anime\/poster_images\/11887\/small.jpg?1476481854",
|
||||||
|
"medium": "https:\/\/media.kitsu.io\/anime\/poster_images\/11887\/medium.jpg?1476481854",
|
||||||
|
"large": "https:\/\/media.kitsu.io\/anime\/poster_images\/11887\/large.jpg?1476481854",
|
||||||
|
"original": "https:\/\/media.kitsu.io\/anime\/poster_images\/11887\/original.png?1476481854"
|
||||||
|
},
|
||||||
|
"coverImage": {
|
||||||
|
"small": "https:\/\/media.kitsu.io\/anime\/cover_images\/11887\/small.jpg?1479834725",
|
||||||
|
"large": "https:\/\/media.kitsu.io\/anime\/cover_images\/11887\/large.jpg?1479834725",
|
||||||
|
"original": "https:\/\/media.kitsu.io\/anime\/cover_images\/11887\/original.jpg?1479834725"
|
||||||
|
},
|
||||||
|
"episodeCount": 12,
|
||||||
|
"episodeLength": 24,
|
||||||
|
"subtype": "TV",
|
||||||
|
"youtubeVideoId": "VLUqd-jEBuE",
|
||||||
|
"ageRating": "R",
|
||||||
|
"ageRatingGuide": "Mild Nudity",
|
||||||
|
"showType": "TV",
|
||||||
|
"nsfw": false,
|
||||||
|
"relationships": {
|
||||||
|
"genres": ["5", "8", "28", "1", "25"],
|
||||||
|
"mappings": ["2593"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"12024": {
|
||||||
|
"slug": "www-working",
|
||||||
|
"synopsis": "Daisuke Higashida is a serious first-year student at Higashizaka High School. He lives a peaceful everyday life even though he is not satisfied with the family who doesn't laugh at all and makes him tired. However, his father's company goes bankrupt one day, and he can no longer afford allowances, cellphone bills, and commuter tickets. When his father orders him to take up a part-time job, Daisuke decides to work at a nearby family restaurant in order to avoid traveling 15 kilometers to school by bicycle.",
|
||||||
|
"coverImageTopOffset": 165,
|
||||||
|
"titles": {
|
||||||
|
"en": "WWW.WAGNARIA!!",
|
||||||
|
"en_jp": "WWW.Working!!",
|
||||||
|
"ja_jp": ""
|
||||||
|
},
|
||||||
|
"canonicalTitle": "WWW.Working!!",
|
||||||
|
"abbreviatedTitles": null,
|
||||||
|
"averageRating": 3.8238374224378,
|
||||||
|
"ratingFrequencies": {
|
||||||
|
"1.0": "2",
|
||||||
|
"1.5": "7",
|
||||||
|
"2.0": "19",
|
||||||
|
"2.5": "28",
|
||||||
|
"3.0": "68",
|
||||||
|
"3.5": "114",
|
||||||
|
"4.0": "144",
|
||||||
|
"4.5": "78",
|
||||||
|
"5.0": "74",
|
||||||
|
"nil": "1182"
|
||||||
|
},
|
||||||
|
"startDate": "2016-10-01",
|
||||||
|
"endDate": "2016-12-24",
|
||||||
|
"posterImage": {
|
||||||
|
"tiny": "https:\/\/media.kitsu.io\/anime\/poster_images\/12024\/tiny.jpg?1473990267",
|
||||||
|
"small": "https:\/\/media.kitsu.io\/anime\/poster_images\/12024\/small.jpg?1473990267",
|
||||||
|
"medium": "https:\/\/media.kitsu.io\/anime\/poster_images\/12024\/medium.jpg?1473990267",
|
||||||
|
"large": "https:\/\/media.kitsu.io\/anime\/poster_images\/12024\/large.jpg?1473990267",
|
||||||
|
"original": "https:\/\/media.kitsu.io\/anime\/poster_images\/12024\/original.jpg?1473990267"
|
||||||
|
},
|
||||||
|
"coverImage": {
|
||||||
|
"small": "https:\/\/media.kitsu.io\/anime\/cover_images\/12024\/small.jpg?1479834612",
|
||||||
|
"large": "https:\/\/media.kitsu.io\/anime\/cover_images\/12024\/large.jpg?1479834612",
|
||||||
|
"original": "https:\/\/media.kitsu.io\/anime\/cover_images\/12024\/original.png?1479834612"
|
||||||
|
},
|
||||||
|
"episodeCount": 13,
|
||||||
|
"episodeLength": 23,
|
||||||
|
"subtype": "TV",
|
||||||
|
"youtubeVideoId": "",
|
||||||
|
"ageRating": "PG",
|
||||||
|
"ageRatingGuide": "Teens 13 or older",
|
||||||
|
"showType": "TV",
|
||||||
|
"nsfw": false,
|
||||||
|
"relationships": {
|
||||||
|
"genres": ["3", "16"],
|
||||||
|
"mappings": ["2538"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"12465": {
|
||||||
|
"slug": "bishoujo-yuugi-unit-crane-game-girls-galaxy",
|
||||||
|
"synopsis": "Second season of Bishoujo Yuugi Unit Crane Game Girls.",
|
||||||
|
"coverImageTopOffset": 0,
|
||||||
|
"titles": {
|
||||||
|
"en": "Crane Game Girls Galaxy",
|
||||||
|
"en_jp": "Bishoujo Yuugi Unit Crane Game Girls Galaxy",
|
||||||
|
"ja_jp": "\u7f8e\u5c11\u5973\u904a\u622f\u30e6\u30cb\u30c3\u30c8 \u30af\u30ec\u30fc\u30f3\u30b2\u30fc\u30eb\u30ae\u30e3\u30e9\u30af\u30b7\u30fc"
|
||||||
|
},
|
||||||
|
"canonicalTitle": "Bishoujo Yuugi Unit Crane Game Girls Galaxy",
|
||||||
|
"abbreviatedTitles": null,
|
||||||
|
"averageRating": null,
|
||||||
|
"ratingFrequencies": {
|
||||||
|
"0.5": "2",
|
||||||
|
"1.0": "2",
|
||||||
|
"1.5": "0",
|
||||||
|
"2.0": "4",
|
||||||
|
"2.5": "6",
|
||||||
|
"3.0": "2",
|
||||||
|
"3.5": "4",
|
||||||
|
"4.0": "1",
|
||||||
|
"4.5": "2",
|
||||||
|
"nil": "66"
|
||||||
|
},
|
||||||
|
"startDate": "2016-10-05",
|
||||||
|
"endDate": null,
|
||||||
|
"posterImage": {
|
||||||
|
"tiny": "https:\/\/media.kitsu.io\/anime\/poster_images\/12465\/tiny.jpg?1473601756",
|
||||||
|
"small": "https:\/\/media.kitsu.io\/anime\/poster_images\/12465\/small.jpg?1473601756",
|
||||||
|
"medium": "https:\/\/media.kitsu.io\/anime\/poster_images\/12465\/medium.jpg?1473601756",
|
||||||
|
"large": "https:\/\/media.kitsu.io\/anime\/poster_images\/12465\/large.jpg?1473601756",
|
||||||
|
"original": "https:\/\/media.kitsu.io\/anime\/poster_images\/12465\/original.png?1473601756"
|
||||||
|
},
|
||||||
|
"coverImage": null,
|
||||||
|
"episodeCount": null,
|
||||||
|
"episodeLength": 13,
|
||||||
|
"subtype": "TV",
|
||||||
|
"youtubeVideoId": "",
|
||||||
|
"ageRating": "PG",
|
||||||
|
"ageRatingGuide": "Children",
|
||||||
|
"showType": "TV",
|
||||||
|
"nsfw": false,
|
||||||
|
"relationships": {
|
||||||
|
"genres": ["3"],
|
||||||
|
"mappings": ["9871"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"genres": {
|
||||||
|
"24": {
|
||||||
|
"name": "School",
|
||||||
|
"slug": "school",
|
||||||
|
"description": null
|
||||||
|
},
|
||||||
|
"35": {
|
||||||
|
"name": "Music",
|
||||||
|
"slug": "music",
|
||||||
|
"description": null
|
||||||
|
},
|
||||||
|
"4": {
|
||||||
|
"name": "Drama",
|
||||||
|
"slug": "drama",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"3": {
|
||||||
|
"name": "Comedy",
|
||||||
|
"slug": "comedy",
|
||||||
|
"description": null
|
||||||
|
},
|
||||||
|
"5": {
|
||||||
|
"name": "Sci-Fi",
|
||||||
|
"slug": "sci-fi",
|
||||||
|
"description": null
|
||||||
|
},
|
||||||
|
"8": {
|
||||||
|
"name": "Magic",
|
||||||
|
"slug": "magic",
|
||||||
|
"description": null
|
||||||
|
},
|
||||||
|
"28": {
|
||||||
|
"name": "Military",
|
||||||
|
"slug": "military",
|
||||||
|
"description": null
|
||||||
|
},
|
||||||
|
"1": {
|
||||||
|
"name": "Action",
|
||||||
|
"slug": "action",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"25": {
|
||||||
|
"name": "Ecchi",
|
||||||
|
"slug": "ecchi",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"16": {
|
||||||
|
"name": "Slice of Life",
|
||||||
|
"slug": "slice-of-life",
|
||||||
|
"description": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mappings": {
|
||||||
|
"3155": {
|
||||||
|
"externalSite": "myanimelist\/anime",
|
||||||
|
"externalId": "31988",
|
||||||
|
"relationships": []
|
||||||
|
},
|
||||||
|
"1755": {
|
||||||
|
"externalSite": "myanimelist\/anime",
|
||||||
|
"externalId": "30514",
|
||||||
|
"relationships": []
|
||||||
|
},
|
||||||
|
"2593": {
|
||||||
|
"externalSite": "myanimelist\/anime",
|
||||||
|
"externalId": "32866",
|
||||||
|
"relationships": []
|
||||||
|
},
|
||||||
|
"2538": {
|
||||||
|
"externalSite": "myanimelist\/anime",
|
||||||
|
"externalId": "33094",
|
||||||
|
"relationships": []
|
||||||
|
},
|
||||||
|
"9871": {
|
||||||
|
"externalSite": "myanimelist\/anime",
|
||||||
|
"externalId": "33541",
|
||||||
|
"relationships": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
28
tests/test_data/Kitsu/animeListItemAfterTransform.json
Normal file
28
tests/test_data/Kitsu/animeListItemAfterTransform.json
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"id": "14047981",
|
||||||
|
"mal_id": null,
|
||||||
|
"episodes": {
|
||||||
|
"watched": 38,
|
||||||
|
"total": 48,
|
||||||
|
"length": 24
|
||||||
|
},
|
||||||
|
"airing": {
|
||||||
|
"status": "Finished Airing",
|
||||||
|
"started": "2012-02-05",
|
||||||
|
"ended": "2013-01-27"
|
||||||
|
},
|
||||||
|
"anime": {
|
||||||
|
"age_rating": "PG",
|
||||||
|
"titles": ["Smile Precure!", "Glitter Force", "\u30b9\u30de\u30a4\u30eb\u30d7\u30ea\u30ad\u30e5\u30a2\uff01"],
|
||||||
|
"slug": "smile-precure",
|
||||||
|
"type": "TV",
|
||||||
|
"image": "https:\/\/media.kitsu.io\/anime\/poster_images\/6687\/small.jpg?1408459122",
|
||||||
|
"genres": ["Magic", "Kids", "Mahou Shoujo", "Fantasy"]
|
||||||
|
},
|
||||||
|
"watching_status": "current",
|
||||||
|
"notes": "Very formulaic.",
|
||||||
|
"rewatching": false,
|
||||||
|
"rewatched": 0,
|
||||||
|
"user_rating": 8,
|
||||||
|
"private": false
|
||||||
|
}
|
155
tests/test_data/Kitsu/animeListItemBeforeTransform.json
Normal file
155
tests/test_data/Kitsu/animeListItemBeforeTransform.json
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
{
|
||||||
|
"id": "14047981",
|
||||||
|
"type": "libraryEntries",
|
||||||
|
"links": {
|
||||||
|
"self": "https:\/\/kitsu.io\/api\/edge\/library-entries\/14047981"
|
||||||
|
},
|
||||||
|
"attributes": {
|
||||||
|
"status": "current",
|
||||||
|
"progress": 38,
|
||||||
|
"reconsuming": false,
|
||||||
|
"reconsumeCount": 0,
|
||||||
|
"notes": "Very formulaic.",
|
||||||
|
"private": false,
|
||||||
|
"rating": "4.0",
|
||||||
|
"updatedAt": "2017-01-12T18:24:24.867Z"
|
||||||
|
},
|
||||||
|
"relationships": {
|
||||||
|
"user": {
|
||||||
|
"links": {
|
||||||
|
"self": "https:\/\/kitsu.io\/api\/edge\/library-entries\/14047981\/relationships\/user",
|
||||||
|
"related": "https:\/\/kitsu.io\/api\/edge\/library-entries\/14047981\/user"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"anime": {
|
||||||
|
"links": {
|
||||||
|
"self": "https:\/\/kitsu.io\/api\/edge\/library-entries\/14047981\/relationships\/anime",
|
||||||
|
"related": "https:\/\/kitsu.io\/api\/edge\/library-entries\/14047981\/anime"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"manga": {
|
||||||
|
"links": {
|
||||||
|
"self": "https:\/\/kitsu.io\/api\/edge\/library-entries\/14047981\/relationships\/manga",
|
||||||
|
"related": "https:\/\/kitsu.io\/api\/edge\/library-entries\/14047981\/manga"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"drama": {
|
||||||
|
"links": {
|
||||||
|
"self": "https:\/\/kitsu.io\/api\/edge\/library-entries\/14047981\/relationships\/drama",
|
||||||
|
"related": "https:\/\/kitsu.io\/api\/edge\/library-entries\/14047981\/drama"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"review": {
|
||||||
|
"links": {
|
||||||
|
"self": "https:\/\/kitsu.io\/api\/edge\/library-entries\/14047981\/relationships\/review",
|
||||||
|
"related": "https:\/\/kitsu.io\/api\/edge\/library-entries\/14047981\/review"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"media": {
|
||||||
|
"links": {
|
||||||
|
"self": "https:\/\/kitsu.io\/api\/edge\/library-entries\/14047981\/relationships\/media",
|
||||||
|
"related": "https:\/\/kitsu.io\/api\/edge\/library-entries\/14047981\/media"
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"type": "anime",
|
||||||
|
"id": "6687"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"unit": {
|
||||||
|
"links": {
|
||||||
|
"self": "https:\/\/kitsu.io\/api\/edge\/library-entries\/14047981\/relationships\/unit",
|
||||||
|
"related": "https:\/\/kitsu.io\/api\/edge\/library-entries\/14047981\/unit"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nextUnit": {
|
||||||
|
"links": {
|
||||||
|
"self": "https:\/\/kitsu.io\/api\/edge\/library-entries\/14047981\/relationships\/next-unit",
|
||||||
|
"related": "https:\/\/kitsu.io\/api\/edge\/library-entries\/14047981\/next-unit"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"anime": {
|
||||||
|
"slug": "smile-precure",
|
||||||
|
"synopsis": "Once upon a time, there was a kingdom of fairy tales called \"M\u00e4rchenland\", where many fairy tale characters live together in joy. Suddenly, the evil emperor Pierrot made an invasion on M\u00e4rchenland, sealing its Queen in the process. To revive the Queen, the symbol of happiness called Cure Decor, \"the Queen's scattered power of light of happiness\", is required. To collect the Cure Decor, a fairy named Candy searches for the Pretty Cures on Earth. There, Candy meets a girl, who decides to collect the Cure Decor. Now, will the world earn a \"happy ending\"?",
|
||||||
|
"coverImageTopOffset": 100,
|
||||||
|
"titles": {
|
||||||
|
"en": "Glitter Force",
|
||||||
|
"en_jp": "Smile Precure!",
|
||||||
|
"ja_jp": "\u30b9\u30de\u30a4\u30eb\u30d7\u30ea\u30ad\u30e5\u30a2\uff01"
|
||||||
|
},
|
||||||
|
"canonicalTitle": "Smile Precure!",
|
||||||
|
"abbreviatedTitles": null,
|
||||||
|
"averageRating": 3.6674651842659,
|
||||||
|
"ratingFrequencies": {
|
||||||
|
"0.5": "4",
|
||||||
|
"1.0": "8",
|
||||||
|
"1.5": "3",
|
||||||
|
"2.0": "17",
|
||||||
|
"2.5": "30",
|
||||||
|
"3.0": "54",
|
||||||
|
"3.5": "69",
|
||||||
|
"4.0": "96",
|
||||||
|
"4.5": "42",
|
||||||
|
"5.0": "57",
|
||||||
|
"nil": "594"
|
||||||
|
},
|
||||||
|
"startDate": "2012-02-05",
|
||||||
|
"endDate": "2013-01-27",
|
||||||
|
"posterImage": {
|
||||||
|
"tiny": "https:\/\/media.kitsu.io\/anime\/poster_images\/6687\/tiny.jpg?1408459122",
|
||||||
|
"small": "https:\/\/media.kitsu.io\/anime\/poster_images\/6687\/small.jpg?1408459122",
|
||||||
|
"medium": "https:\/\/media.kitsu.io\/anime\/poster_images\/6687\/medium.jpg?1408459122",
|
||||||
|
"large": "https:\/\/media.kitsu.io\/anime\/poster_images\/6687\/large.jpg?1408459122",
|
||||||
|
"original": "https:\/\/media.kitsu.io\/anime\/poster_images\/6687\/original.jpg?1408459122"
|
||||||
|
},
|
||||||
|
"coverImage": {
|
||||||
|
"small": "https:\/\/media.kitsu.io\/anime\/cover_images\/6687\/small.jpg?1452609041",
|
||||||
|
"large": "https:\/\/media.kitsu.io\/anime\/cover_images\/6687\/large.jpg?1452609041",
|
||||||
|
"original": "https:\/\/media.kitsu.io\/anime\/cover_images\/6687\/original.png?1452609041"
|
||||||
|
},
|
||||||
|
"episodeCount": 48,
|
||||||
|
"episodeLength": 24,
|
||||||
|
"subtype": "TV",
|
||||||
|
"youtubeVideoId": "",
|
||||||
|
"ageRating": "PG",
|
||||||
|
"ageRatingGuide": "Children",
|
||||||
|
"showType": "TV",
|
||||||
|
"nsfw": false,
|
||||||
|
"relationships": {
|
||||||
|
"genres": {
|
||||||
|
"8": {
|
||||||
|
"name": "Magic",
|
||||||
|
"slug": "magic",
|
||||||
|
"description": null
|
||||||
|
},
|
||||||
|
"40": {
|
||||||
|
"name": "Kids",
|
||||||
|
"slug": "kids",
|
||||||
|
"description": null
|
||||||
|
},
|
||||||
|
"47": {
|
||||||
|
"name": "Mahou Shoujo",
|
||||||
|
"slug": "mahou-shoujo",
|
||||||
|
"description": "Magical Girls"
|
||||||
|
},
|
||||||
|
"11": {
|
||||||
|
"name": "Fantasy",
|
||||||
|
"slug": "fantasy",
|
||||||
|
"description": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mappings": {
|
||||||
|
"778": {
|
||||||
|
"externalSite": "myanimelist\/anime",
|
||||||
|
"externalId": "12191",
|
||||||
|
"relationships": []
|
||||||
|
},
|
||||||
|
"12547": {
|
||||||
|
"externalSite": "thetvdb\/series",
|
||||||
|
"externalId": "255904",
|
||||||
|
"relationships": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user