Version 5.1 - All the GraphQL #32
@ -1,5 +1,8 @@
|
||||
# Changelog
|
||||
|
||||
## Version 4
|
||||
* Updated to use Kitsu API after discontinuation of Hummingbird
|
||||
|
||||
## Version 3
|
||||
* Converted user configuration to toml files
|
||||
* Added a caching layer for api calls, which resets upon updates from the
|
||||
|
@ -2,7 +2,6 @@
|
||||
|
||||
A self-hosted client that allows custom formatting of data from the hummingbird api
|
||||
|
||||
[![Build Status](https://jenkins.timshomepage.net/buildStatus/icon?job=animeclient)](https://jenkins.timshomepage.net/job/animeclient/)
|
||||
[![Build Status](https://travis-ci.org/timw4mail/HummingBirdAnimeClient.svg?branch=master)](https://travis-ci.org/timw4mail/HummingBirdAnimeClient)
|
||||
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/timw4mail/HummingBirdAnimeClient/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/timw4mail/HummingBirdAnimeClient/?branch=master)
|
||||
|
||||
|
@ -8,7 +8,7 @@
|
||||
*
|
||||
* @package AnimeListClient
|
||||
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||
* @copyright 2015 - 2016 Timothy J. Warren
|
||||
* @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
|
||||
@ -19,9 +19,15 @@ namespace Aviat\AnimeClient;
|
||||
use Aura\Html\HelperLocatorFactory;
|
||||
use Aura\Router\RouterContainer;
|
||||
use Aura\Session\SessionFactory;
|
||||
use Aviat\AnimeClient\API\Kitsu\Auth as KitsuAuth;
|
||||
use Aviat\AnimeClient\API\Kitsu\ListItem as KitsuListItem;
|
||||
use Aviat\AnimeClient\API\Kitsu\KitsuModel;
|
||||
use Aviat\AnimeClient\API\Kitsu\{
|
||||
Auth as KitsuAuth,
|
||||
ListItem as KitsuListItem,
|
||||
KitsuModel
|
||||
};
|
||||
use Aviat\AnimeClient\API\MAL\{
|
||||
ListItem as MALListItem,
|
||||
Model as MALModel
|
||||
};
|
||||
use Aviat\AnimeClient\Model;
|
||||
use Aviat\Banker\Pool;
|
||||
use Aviat\Ion\Config;
|
||||
@ -111,6 +117,15 @@ return function(array $config_array = []) {
|
||||
$listItem->setContainer($container);
|
||||
$model = new KitsuModel($listItem);
|
||||
$model->setContainer($container);
|
||||
$cache = $container->get('cache');
|
||||
$model->setCache($cache);
|
||||
return $model;
|
||||
});
|
||||
$container->set('mal-model', function($container) {
|
||||
$listItem = new MALListItem();
|
||||
$listItem->setContainer($container);
|
||||
$model = new MALModel($listItem);
|
||||
$model->setContainer($container);
|
||||
return $model;
|
||||
});
|
||||
$container->set('api-model', function($container) {
|
||||
|
@ -2,6 +2,10 @@
|
||||
# Cache Setup #
|
||||
################################################################################
|
||||
|
||||
# See https://git.timshomepage.net/timw4mail/banker for more information
|
||||
|
||||
# Available drivers are memcache, memcached, redis or null
|
||||
# Null cache driver means no caching
|
||||
driver = "redis"
|
||||
|
||||
[connection]
|
||||
|
@ -3,7 +3,7 @@
|
||||
################################################################################
|
||||
|
||||
# Username for anime and manga lists
|
||||
hummingbird_username = "timw4mail"
|
||||
kitsu_username = "timw4mail"
|
||||
|
||||
# Whose list is it?
|
||||
whose_list = "Tim"
|
||||
@ -14,6 +14,9 @@ show_anime_collection = true
|
||||
# do you wish to show the manga collection?
|
||||
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"
|
||||
|
||||
|
6
app/config/mal.toml.example
Normal file
6
app/config/mal.toml.example
Normal file
@ -0,0 +1,6 @@
|
||||
################################################################################
|
||||
# My Anime LIst Integration Config #
|
||||
################################################################################
|
||||
|
||||
username = "timw4mail"
|
||||
password = "mysecretpassword"
|
@ -1,16 +0,0 @@
|
||||
################################################################################
|
||||
# Redis Cache Configuration #
|
||||
################################################################################
|
||||
|
||||
# Host or socket to connect to
|
||||
# Socket must be prefixed with 'unix:'
|
||||
host = "127.0.0.1"
|
||||
|
||||
# Connection port
|
||||
#port = 6379
|
||||
|
||||
# Connection password
|
||||
#password = ""
|
||||
|
||||
# Database number
|
||||
database = 13
|
@ -11,11 +11,11 @@
|
||||
<section class="media-wrap">
|
||||
<?php foreach($items as $item): ?>
|
||||
<?php if ($item['private'] && ! $auth->is_authenticated()) continue; ?>
|
||||
<article class="media" id="<?= $item['id'] ?>">
|
||||
<article class="media" data-kitsu-id="<?= $item['id'] ?>" data-mal-id="<?= $item['mal_id'] ?>">
|
||||
<?php if ($auth->is_authenticated()): ?>
|
||||
<button title="Increment episode count" class="plus_one" hidden>+1 Episode</button>
|
||||
<?php endif ?>
|
||||
<?= $helper->img($item['anime']['image']); ?>
|
||||
<img src="<?= $item['anime']['image'] ?>" alt="" />
|
||||
<div class="name">
|
||||
<a href="<?= $url->generate('anime.details', ['id' => $item['anime']['slug']]); ?>">
|
||||
<?= array_shift($item['anime']['titles']) ?>
|
||||
@ -32,6 +32,7 @@
|
||||
</span>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if ($item['private'] || $item['rewatching']): ?>
|
||||
<div class="row">
|
||||
<?php foreach(['private', 'rewatching'] as $attr): ?>
|
||||
@ -41,11 +42,29 @@
|
||||
<?php endforeach ?>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if ($item['rewatched'] > 0): ?>
|
||||
<div class="row">
|
||||
<div>Rewatched <?= $item['rewatched'] ?> time(s)</div>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if (count($item['anime']['streaming_links']) > 0): ?>
|
||||
<div class="row">
|
||||
<?php foreach($item['anime']['streaming_links'] as $link): ?>
|
||||
<div class="cover_streaming_link">
|
||||
<?php if($link['meta']['link']): ?>
|
||||
<a href="<?= $link['link']?>">
|
||||
<?= $link['meta']['logo'] ?>
|
||||
</a>
|
||||
<?php else: ?>
|
||||
<?= $link['meta']['logo'] ?>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
<?php endforeach ?>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
|
||||
<div class="row">
|
||||
<div class="user_rating">Rating: <?= $item['user_rating'] ?> / 10</div>
|
||||
<div class="completion">Episodes:
|
||||
|
@ -1,10 +1,10 @@
|
||||
<main class="details">
|
||||
<section class="flex flex-no-wrap">
|
||||
<div>
|
||||
<img class="cover" src="<?= $data['cover_image'] ?>" alt="<?= $data['title'] ?> cover image" />
|
||||
<img class="cover" src="<?= $data['cover_image'] ?>" alt="" />
|
||||
<br />
|
||||
<br />
|
||||
<table>
|
||||
<table class="media_details">
|
||||
<tr>
|
||||
<td class="align_right">Airing Status</td>
|
||||
<td><?= $data['status'] ?></td>
|
||||
@ -15,16 +15,18 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Episode Count</td>
|
||||
<td><?= $data['episode_count'] ?></td>
|
||||
<td><?= $data['episode_count'] ?? '-' ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Episode Length</td>
|
||||
<td><?= $data['episode_length'] ?> minutes</td>
|
||||
</tr>
|
||||
<?php if ( ! empty($data['age_rating'])): ?>
|
||||
<tr>
|
||||
<td>Age Rating</td>
|
||||
<td><abbr title="<?= $data['age_rating_guide'] ?>"><?= $data['age_rating'] ?></abbr></td>
|
||||
</tr>
|
||||
<?php endif ?>
|
||||
<tr>
|
||||
<td>Genres</td>
|
||||
<td>
|
||||
@ -40,6 +42,39 @@
|
||||
<?php endforeach ?>
|
||||
<br />
|
||||
<p><?= nl2br($data['synopsis']) ?></p>
|
||||
<?php if (count($data['streaming_links']) > 0): ?>
|
||||
<hr />
|
||||
<h4>Streaming on:</h4>
|
||||
<table class="full_width invisible">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="align_left">Service</th>
|
||||
<th>Subtitles</th>
|
||||
<th>Dubs</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach($data['streaming_links'] as $streaming_link): ?>
|
||||
<tr>
|
||||
<td class="align_left">
|
||||
<?php if ($streaming_link['meta']['link'] !== FALSE): ?>
|
||||
<a href="<?= $streaming_link['link'] ?>">
|
||||
<?= $streaming_link['meta']['logo'] ?>
|
||||
<?= $streaming_link['meta']['name'] ?>
|
||||
</a>
|
||||
<?php else: ?>
|
||||
<?= $streaming_link['meta']['logo'] ?>
|
||||
<?= $streaming_link['meta']['name'] ?>
|
||||
<?php endif ?>
|
||||
</td>
|
||||
<td><?= implode(', ', $streaming_link['subs']) ?></td>
|
||||
<td><?= implode(', ', $streaming_link['dubs']) ?></td>
|
||||
</tr>
|
||||
<?php endforeach ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php endif ?>
|
||||
<?php /*<pre><?= print_r($data, TRUE) ?></pre> */ ?>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
@ -1,4 +1,5 @@
|
||||
<?php if ($auth->is_authenticated()): ?>
|
||||
<?php /* <pre><?= json_encode($item, \JSON_PRETTY_PRINT); ?></pre> */ ?>
|
||||
<main>
|
||||
<h2>Edit Anime List Item</h2>
|
||||
<form action="<?= $action ?>" method="post">
|
||||
@ -77,6 +78,7 @@
|
||||
<td> </td>
|
||||
<td>
|
||||
<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" />
|
||||
<button type="submit">Submit</button>
|
||||
</td>
|
||||
|
@ -19,7 +19,7 @@
|
||||
<th>Type</th>
|
||||
<th>Progress</th>
|
||||
<th>Rated</th>
|
||||
<th>Attributes</th>
|
||||
<th colspan="2">Attributes</th>
|
||||
<th>Notes</th>
|
||||
<th>Genres</th>
|
||||
</tr>
|
||||
@ -61,6 +61,17 @@
|
||||
<?php endforeach ?>
|
||||
</ul>
|
||||
</td>
|
||||
<td>
|
||||
<?php foreach($item['anime']['streaming_links'] as $link): ?>
|
||||
<?php if ($link['meta']['link'] !== FALSE): ?>
|
||||
<a href="<?= $link['link'] ?>">
|
||||
<?= $link['meta']['logo'] ?>
|
||||
</a>
|
||||
<?php else: ?>
|
||||
<?= $link['meta']['logo'] ?>
|
||||
<?php endif ?>
|
||||
<?php endforeach ?>
|
||||
</td>
|
||||
<td>
|
||||
<p><?= $escape->html($item['notes']) ?></p>
|
||||
</td>
|
||||
|
@ -17,13 +17,13 @@
|
||||
[<a href="<?= $urlGenerator->default_url('manga') ?>">Manga List</a>]
|
||||
<?php endif ?>
|
||||
</span>
|
||||
<?php /* if ($auth->is_authenticated()): ?>
|
||||
<?php if ($auth->is_authenticated()): ?>
|
||||
<span class="flex-no-wrap"> </span>
|
||||
<span class="flex-no-wrap small-font">
|
||||
<button type="button" class="js-clear-cache user-btn">Clear API Cache</button>
|
||||
</span>
|
||||
<span class="flex-no-wrap"> </span>
|
||||
<?php endif */ ?>
|
||||
<?php endif ?>
|
||||
<span class="flex-no-wrap small-font">
|
||||
<?php if ($auth->is_authenticated()): ?>
|
||||
<a class="bracketed" href="<?= $url->generate('logout') ?>">Logout</a>
|
||||
|
@ -7,7 +7,7 @@
|
||||
*
|
||||
* @package AnimeListClient
|
||||
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||
* @copyright 2015 - 2016 Timothy J. Warren
|
||||
* @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
|
||||
|
@ -4,7 +4,6 @@
|
||||
stopOnFailure="false"
|
||||
bootstrap="../tests/bootstrap.php"
|
||||
beStrictAboutTestsThatDoNotTestAnything="true"
|
||||
checkForUnintentionallyCoveredCode="true"
|
||||
>
|
||||
<filter>
|
||||
<whitelist>
|
||||
|
@ -14,7 +14,6 @@
|
||||
}
|
||||
},
|
||||
"require": {
|
||||
"abeautifulsite/simpleimage": "2.5.*",
|
||||
"aura/html": "2.*",
|
||||
"aura/router": "3.*",
|
||||
"aura/session": "2.*",
|
||||
@ -35,15 +34,16 @@
|
||||
"theseer/phpdox": "0.8.1.1",
|
||||
"phploc/phploc": "^3.0",
|
||||
"phpmd/phpmd": "^2.4",
|
||||
"phpunit/phpunit": "^5.4",
|
||||
"phpunit/phpunit": "^5.7",
|
||||
"robmorgan/phinx": "^0.6.4",
|
||||
"humbug/humbug": "~1.0@dev",
|
||||
"consolidation/robo": "~1.0@RC",
|
||||
"consolidation/robo": "~1.0",
|
||||
"henrikbjorn/lurker": "^1.1.0",
|
||||
"symfony/var-dumper": "^3.1",
|
||||
"squizlabs/php_codesniffer": "^3.0.0@beta"
|
||||
},
|
||||
"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"?>
|
||||
<phpunit
|
||||
colors="true"
|
||||
stopOnFailure="false"
|
||||
bootstrap="tests/bootstrap.php"
|
||||
beStrictAboutTestsThatDoNotTestAnything="true"
|
||||
colors="true"
|
||||
stopOnFailure="false"
|
||||
bootstrap="tests/bootstrap.php"
|
||||
>
|
||||
<filter>
|
||||
<whitelist>
|
||||
<directory suffix=".php">src</directory>
|
||||
</whitelist>
|
||||
</filter>
|
||||
<testsuites>
|
||||
<testsuite name="AnimeClient">
|
||||
<directory>tests</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
<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_HOST" value="localhost" />
|
||||
<server name="SERVER_NAME" value="localhost" />
|
||||
<server name="REQUEST_URI" value="/" />
|
||||
<server name="REQUEST_METHOD" value="GET" />
|
||||
</php>
|
||||
<filter>
|
||||
<whitelist>
|
||||
<directory suffix=".php">src</directory>
|
||||
</whitelist>
|
||||
</filter>
|
||||
<testsuites>
|
||||
<testsuite name="AnimeClient">
|
||||
<directory>tests</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
<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_HOST" value="localhost" />
|
||||
<server name="SERVER_NAME" value="localhost" />
|
||||
<server name="REQUEST_URI" value="/" />
|
||||
<server name="REQUEST_METHOD" value="GET" />
|
||||
</php>
|
||||
</phpunit>
|
@ -794,6 +794,10 @@ a:hover, a:active {
|
||||
background-color:#db7d12;
|
||||
}
|
||||
|
||||
.full_width {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
CSS loading icon
|
||||
------------------------------------------------------------------------------*/
|
||||
@ -1208,7 +1212,7 @@ a:hover, a:active {
|
||||
text-align:center;
|
||||
color: greenyellow;
|
||||
position:absolute;
|
||||
top:5px;
|
||||
top:147px;
|
||||
left:0;
|
||||
height:100%;
|
||||
width:100%;
|
||||
@ -1248,8 +1252,9 @@ a:hover, a:active {
|
||||
}
|
||||
|
||||
.details .cover {
|
||||
max-width: 300px;
|
||||
max-height: 435px;
|
||||
display: block;
|
||||
width: 284px;
|
||||
height: 402px;
|
||||
}
|
||||
|
||||
.details h2 {
|
||||
@ -1261,11 +1266,11 @@ a:hover, a:active {
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
.details table {
|
||||
.details .media_details {
|
||||
max-width:300px;
|
||||
}
|
||||
|
||||
.details td {
|
||||
.details .media_details td {
|
||||
padding:0 15px;
|
||||
padding:0 1.5rem;
|
||||
}
|
||||
@ -1274,13 +1279,13 @@ a:hover, a:active {
|
||||
text-align:justify;
|
||||
}
|
||||
|
||||
.details td:nth-child(odd) {
|
||||
.details .media_details td:nth-child(odd) {
|
||||
width:1%;
|
||||
white-space:nowrap;
|
||||
text-align:right;
|
||||
}
|
||||
|
||||
.details td:nth-child(even) {
|
||||
.details .media_details td:nth-child(even) {
|
||||
text-align:left;
|
||||
}
|
||||
|
||||
|
@ -115,6 +115,10 @@ a:hover, a:active {
|
||||
background-color: var(--edit-link-hover-color);
|
||||
}
|
||||
|
||||
.full_width {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
CSS loading icon
|
||||
------------------------------------------------------------------------------*/
|
||||
@ -471,7 +475,7 @@ a:hover, a:active {
|
||||
text-align:center;
|
||||
color: greenyellow;
|
||||
position:absolute;
|
||||
top:5px;
|
||||
top:147px;
|
||||
left:0;
|
||||
height:100%;
|
||||
width:100%;
|
||||
@ -505,8 +509,9 @@ a:hover, a:active {
|
||||
}
|
||||
|
||||
.details .cover {
|
||||
max-width: 300px;
|
||||
max-height: 435px;
|
||||
display: block;
|
||||
width: 284px;
|
||||
height: 402px;
|
||||
}
|
||||
|
||||
.details h2 {
|
||||
@ -517,10 +522,10 @@ a:hover, a:active {
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
.details table {
|
||||
.details .media_details {
|
||||
max-width:300px;
|
||||
}
|
||||
.details td {
|
||||
.details .media_details td {
|
||||
padding:0 1.5rem;
|
||||
}
|
||||
|
||||
@ -528,12 +533,12 @@ a:hover, a:active {
|
||||
text-align:justify;
|
||||
}
|
||||
|
||||
.details td:nth-child(odd) {
|
||||
.details .media_details td:nth-child(odd) {
|
||||
width:1%;
|
||||
white-space:nowrap;
|
||||
text-align:right;
|
||||
}
|
||||
.details td:nth-child(even) {
|
||||
.details .media_details td:nth-child(even) {
|
||||
text-align:left;
|
||||
}
|
||||
|
||||
|
@ -6,15 +6,16 @@
|
||||
'use strict';
|
||||
|
||||
// Action to increment episode count
|
||||
_.on('body.anime.list', 'click', '.plus_one', function() {
|
||||
let parent_sel = _.closestParent(this, 'article');
|
||||
_.on('body.anime.list', 'click', '.plus_one', e => {
|
||||
let parent_sel = _.closestParent(e.target, 'article');
|
||||
let watched_count = parseInt(_.$('.completed_number', parent_sel)[0].textContent, 10);
|
||||
let total_count = parseInt(_.$('.total_number', parent_sel)[0].textContent, 10);
|
||||
let title = _.$('.name a', parent_sel)[0].textContent;
|
||||
|
||||
// Setup the update data
|
||||
let data = {
|
||||
id: parent_sel.id,
|
||||
id: parent_sel.dataset.kitsuId,
|
||||
mal_id: parent_sel.dataset.malId,
|
||||
data: {
|
||||
progress: watched_count + 1
|
||||
}
|
||||
@ -41,7 +42,7 @@
|
||||
_.hide(parent_sel);
|
||||
}
|
||||
|
||||
_.showMessage('success', `Sucessfully updated ${title}`);
|
||||
_.showMessage('success', `Successfully updated ${title}`);
|
||||
_.$('.completed_number', parent_sel)[0].textContent = ++watched_count;
|
||||
_.scrollToTop();
|
||||
},
|
||||
|
@ -5,9 +5,9 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
_.on('.manga.list', 'click', '.edit_buttons button', function() {
|
||||
let this_sel = this;
|
||||
let parent_sel = _.closestParent(this, 'article');
|
||||
_.on('.manga.list', 'click', '.edit_buttons button', e => {
|
||||
let this_sel = e.target;
|
||||
let parent_sel = _.closestParent(e.target, 'article');
|
||||
let manga_id = parent_sel.id.replace("manga-", "");
|
||||
let type = this_sel.classList.contains("plus_one_chapter") ? 'chapter' : 'volume';
|
||||
let completed = parseInt(_.$(`.${type}s_read`, parent_sel)[0].textContent, 10);
|
||||
|
@ -8,7 +8,7 @@
|
||||
*
|
||||
* @package AnimeListClient
|
||||
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||
* @copyright 2015 - 2016 Timothy J. Warren
|
||||
* @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
|
||||
|
73
src/API/CacheTrait.php
Normal file
73
src/API/CacheTrait.php
Normal file
@ -0,0 +1,73 @@
|
||||
<?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;
|
||||
|
||||
use Aviat\Banker\Pool;
|
||||
use Aviat\Ion\Di\ContainerAware;
|
||||
|
||||
/**
|
||||
* Helper methods for dealing with the Cache
|
||||
*/
|
||||
trait CacheTrait {
|
||||
|
||||
/**
|
||||
* @var Aviat\Banker\Pool
|
||||
*/
|
||||
protected $cache;
|
||||
|
||||
/**
|
||||
* Inject the cache object
|
||||
*
|
||||
* @param Pool $cache
|
||||
* @return $this
|
||||
*/
|
||||
public function setCache(Pool $cache): self
|
||||
{
|
||||
$this->cache = $cache;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cache object if it exists
|
||||
*
|
||||
* @return Pool
|
||||
*/
|
||||
public function getCache()
|
||||
{
|
||||
return $this->cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a hash as a cache key from the current method call
|
||||
*
|
||||
* @param object $object
|
||||
* @param string $method
|
||||
* @param array $args
|
||||
* @return string
|
||||
*/
|
||||
public function getHashForMethodCall($object, string $method, array $args = []): string
|
||||
{
|
||||
$classname = get_class($object);
|
||||
$keyObj = [
|
||||
'class' => $classname,
|
||||
'method' => $method,
|
||||
'args' => $args,
|
||||
];
|
||||
$hash = sha1(json_encode($keyObj));
|
||||
return $hash;
|
||||
}
|
||||
}
|
@ -8,7 +8,7 @@
|
||||
*
|
||||
* @package AnimeListClient
|
||||
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||
* @copyright 2015 - 2016 Timothy J. Warren
|
||||
* @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
|
||||
|
@ -8,7 +8,7 @@
|
||||
*
|
||||
* @package AnimeListClient
|
||||
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||
* @copyright 2015 - 2016 Timothy J. Warren
|
||||
* @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
|
||||
@ -16,6 +16,8 @@
|
||||
|
||||
namespace Aviat\AnimeClient\API;
|
||||
|
||||
use Aviat\Ion\Json;
|
||||
|
||||
/**
|
||||
* Class encapsulating Json API data structure for a request or response
|
||||
*/
|
||||
@ -40,12 +42,19 @@ class JsonAPI {
|
||||
*/
|
||||
protected $data = [];
|
||||
|
||||
/**
|
||||
* Data array parsed out from a request
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $parsedData = [];
|
||||
|
||||
/**
|
||||
* Related objects included with the request
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $included = [];
|
||||
public $included = [];
|
||||
|
||||
/**
|
||||
* Pagination links
|
||||
@ -54,13 +63,142 @@ class JsonAPI {
|
||||
*/
|
||||
protected $links = [];
|
||||
|
||||
/**
|
||||
* JsonAPI constructor
|
||||
*
|
||||
* @param array $initital
|
||||
*/
|
||||
public function __construct(array $initial = [])
|
||||
{
|
||||
$this->data = $initial;
|
||||
}
|
||||
|
||||
public function parseFromString(string $json)
|
||||
{
|
||||
$this->parse(Json::decode($json));
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a JsonAPI response into its components
|
||||
*
|
||||
* @param array $data
|
||||
*/
|
||||
public function parse(array $data)
|
||||
{
|
||||
$this->included = static::organizeIncludes($data['included']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return data array after input is parsed
|
||||
* to inline includes inside of relationship objects
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getParsedData(): array
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
@ -8,7 +8,7 @@
|
||||
*
|
||||
* @package AnimeListClient
|
||||
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||
* @copyright 2015 - 2016 Timothy J. Warren
|
||||
* @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
|
||||
@ -24,7 +24,7 @@ use Aviat\AnimeClient\API\Kitsu\Enum\{
|
||||
use DateTimeImmutable;
|
||||
|
||||
/**
|
||||
* Constants and mappings for the Kitsu API
|
||||
* Data massaging helpers for the Kitsu API
|
||||
*/
|
||||
class Kitsu {
|
||||
const AUTH_URL = 'https://kitsu.io/api/oauth/token';
|
||||
@ -45,6 +45,11 @@ class Kitsu {
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Map of Kitsu Manga status to label for select menus
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getStatusToMangaSelectMap()
|
||||
{
|
||||
return [
|
||||
@ -85,6 +90,78 @@ class Kitsu {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name and logo for the streaming service of the current link
|
||||
*
|
||||
* @param string $hostname
|
||||
* @return array
|
||||
*/
|
||||
protected static function getServiceMetaData(string $hostname = null): array
|
||||
{
|
||||
switch($hostname)
|
||||
{
|
||||
case 'www.crunchyroll.com':
|
||||
return [
|
||||
'name' => 'Crunchyroll',
|
||||
'link' => true,
|
||||
'logo' => '<svg width="50" height="50" viewBox="0 0 50 50" xmlns="http://www.w3.org/2000/svg"><g fill="#F78B24" fill-rule="evenodd"><path d="M22.549 49.145c-.815-.077-2.958-.456-3.753-.663-6.873-1.79-12.693-6.59-15.773-13.009C1.335 31.954.631 28.807.633 24.788c.003-4.025.718-7.235 2.38-10.686 1.243-2.584 2.674-4.609 4.706-6.66 3.8-3.834 8.614-6.208 14.067-6.936 1.783-.239 5.556-.161 7.221.148 3.463.642 6.571 1.904 9.357 3.797 5.788 3.934 9.542 9.951 10.52 16.861.21 1.48.332 4.559.19 4.816-.077.14-.117-.007-.167-.615-.25-3.015-1.528-6.66-3.292-9.388C40.253 7.836 30.249 4.32 20.987 7.467c-7.15 2.43-12.522 8.596-13.997 16.06-.73 3.692-.51 7.31.658 10.882a21.426 21.426 0 0 0 13.247 13.518c1.475.515 3.369.944 4.618 1.047 1.496.122 1.119.239-.727.224-1.006-.008-2.013-.032-2.237-.053z"></path><path d="M27.685 46.1c-7.731-.575-14.137-6.455-15.474-14.204-.243-1.41-.29-4.047-.095-5.345 1.16-7.706 6.97-13.552 14.552-14.639 1.537-.22 4.275-.143 5.746.162 1.28.266 2.7.737 3.814 1.266l.865.411-.814.392c-2.936 1.414-4.748 4.723-4.323 7.892.426 3.173 2.578 5.664 5.667 6.56 1.112.322 2.812.322 3.925 0 1.438-.417 2.566-1.1 3.593-2.173.346-.362.652-.621.68-.576.027.046.106.545.176 1.11.171 1.395.07 4.047-.204 5.371-.876 4.218-3.08 7.758-6.463 10.374-3.2 2.476-7.434 3.711-11.645 3.399z"></path></g></svg>'
|
||||
];
|
||||
|
||||
case 'www.funimation.com':
|
||||
return [
|
||||
'name' => 'Funimation',
|
||||
'link' => true,
|
||||
'logo' => '<svg width="50" height="50" viewBox="0 0 50 50" xmlns="http://www.w3.org/2000/svg"><path d="M24.066.017a24.922 24.922 0 0 1 13.302 3.286 25.098 25.098 0 0 1 7.833 7.058 24.862 24.862 0 0 1 4.207 9.575c.82 4.001.641 8.201-.518 12.117a24.946 24.946 0 0 1-4.868 9.009 24.98 24.98 0 0 1-7.704 6.118 24.727 24.727 0 0 1-10.552 2.718A24.82 24.82 0 0 1 13.833 47.3c-5.815-2.872-10.408-8.107-12.49-14.25-2.162-6.257-1.698-13.375 1.303-19.28C5.483 8.07 10.594 3.55 16.602 1.435A24.94 24.94 0 0 1 24.066.017zm-8.415 33.31c.464 2.284 1.939 4.358 3.99 5.48 2.174 1.217 4.765 1.444 7.202 1.181 2.002-.217 3.986-.992 5.455-2.397 1.173-1.151 2.017-2.648 2.33-4.267-1.189-.027-2.378 0-3.566-.03-.568.082-1.137-.048-1.705.014-1.232.012-2.465.003-3.697-.01-.655.066-1.309-.035-1.963.013-1.166-.053-2.334.043-3.5-.025-1.515.08-3.03-.035-4.546.042z" fill="#411299" fill-rule="evenodd"></path></svg>'
|
||||
];
|
||||
|
||||
case 'www.hulu.com':
|
||||
return [
|
||||
'name' => 'Hulu',
|
||||
'link' => true,
|
||||
'logo' => '<svg width="50" height="50" viewBox="0 0 34 50" xmlns="http://www.w3.org/2000/svg"><path d="M22.222 13.889h-11.11V0H0v50h11.111V27.778c0-1.39 1.111-2.778 2.778-2.778h5.555c1.39 0 2.778 1.111 2.778 2.778V50h11.111V25c0-6.111-5-11.111-11.11-11.111z" fill="#8BC34A" fill-rule="evenodd"></path></svg>'
|
||||
];
|
||||
|
||||
// Default to Netflix, because the API links are broken,
|
||||
// and there's no other real identifier for Netflix
|
||||
default:
|
||||
return [
|
||||
'name' => 'Netflix',
|
||||
'link' => false,
|
||||
'logo' => '<svg width="50" height="50" viewBox="0 0 26 50" xmlns="http://www.w3.org/2000/svg"><path d="M.057.258C2.518.253 4.982.263 7.446.253c2.858 7.76 5.621 15.556 8.456 23.324.523 1.441 1.003 2.897 1.59 4.312.078-9.209.01-18.42.034-27.631h7.763v46.36c-2.812.372-5.637.627-8.457.957-1.203-3.451-2.396-6.902-3.613-10.348-1.796-5.145-3.557-10.302-5.402-15.428.129 8.954.015 17.912.057 26.871-2.603.39-5.227.637-7.815 1.119C.052 33.279.06 16.768.057.258z" fill="#E21221" fill-rule="evenodd"></path></svg>'
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reorganize streaming links
|
||||
*
|
||||
* @param array $included
|
||||
* @return array
|
||||
*/
|
||||
public static function parseStreamingLinks(array $included): array
|
||||
{
|
||||
if ( ! array_key_exists('streamingLinks', $included))
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
$links = [];
|
||||
|
||||
foreach ($included['streamingLinks'] as $streamingLink)
|
||||
{
|
||||
$host = parse_url($streamingLink['url'], \PHP_URL_HOST);
|
||||
|
||||
$links[] = [
|
||||
'meta' => static::getServiceMetaData($host),
|
||||
'link' => $streamingLink['url'],
|
||||
'subs' => $streamingLink['subs'],
|
||||
'dubs' => $streamingLink['dubs']
|
||||
];
|
||||
}
|
||||
|
||||
return $links;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter out duplicate and very similar names from
|
||||
*
|
||||
@ -110,66 +187,6 @@ class Kitsu {
|
||||
return $valid;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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'] = self::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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if an alternate title is unique enough to list
|
||||
*
|
||||
|
@ -8,7 +8,7 @@
|
||||
*
|
||||
* @package AnimeListClient
|
||||
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||
* @copyright 2015 - 2016 Timothy J. Warren
|
||||
* @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
|
||||
@ -18,6 +18,7 @@ namespace Aviat\AnimeClient\API\Kitsu;
|
||||
|
||||
use Aviat\AnimeClient\AnimeClient;
|
||||
use Aviat\Ion\Di\{ContainerAware, ContainerInterface};
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Kitsu API Authentication
|
||||
@ -64,7 +65,16 @@ class Auth {
|
||||
{
|
||||
$config = $this->container->get('config');
|
||||
$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)
|
||||
{
|
||||
|
@ -8,7 +8,7 @@
|
||||
*
|
||||
* @package AnimeListClient
|
||||
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||
* @copyright 2015 - 2016 Timothy J. Warren
|
||||
* @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
|
||||
|
@ -8,7 +8,7 @@
|
||||
*
|
||||
* @package AnimeListClient
|
||||
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||
* @copyright 2015 - 2016 Timothy J. Warren
|
||||
* @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
|
||||
|
@ -8,7 +8,7 @@
|
||||
*
|
||||
* @package AnimeListClient
|
||||
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||
* @copyright 2015 - 2016 Timothy J. Warren
|
||||
* @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
|
||||
|
@ -8,7 +8,7 @@
|
||||
*
|
||||
* @package AnimeListClient
|
||||
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||
* @copyright 2015 - 2016 Timothy J. Warren
|
||||
* @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
|
||||
@ -16,6 +16,8 @@
|
||||
|
||||
namespace Aviat\AnimeClient\API\Kitsu;
|
||||
|
||||
use Aviat\AnimeClient\API\CacheTrait;
|
||||
use Aviat\AnimeClient\API\JsonAPI;
|
||||
use Aviat\AnimeClient\API\Kitsu as K;
|
||||
use Aviat\AnimeClient\API\Kitsu\Transformer\{
|
||||
AnimeTransformer, AnimeListTransformer, MangaTransformer, MangaListTransformer
|
||||
@ -28,6 +30,7 @@ use GuzzleHttp\Exception\ClientException;
|
||||
* Kitsu API Model
|
||||
*/
|
||||
class KitsuModel {
|
||||
use CacheTrait;
|
||||
use ContainerAware;
|
||||
use KitsuTrait;
|
||||
|
||||
@ -60,6 +63,7 @@ class KitsuModel {
|
||||
*/
|
||||
protected $mangaListTransformer;
|
||||
|
||||
|
||||
/**
|
||||
* KitsuModel constructor.
|
||||
*/
|
||||
@ -130,6 +134,7 @@ class KitsuModel {
|
||||
*/
|
||||
public function getAnime(string $animeId): array
|
||||
{
|
||||
// @TODO catch non-existent anime
|
||||
$baseData = $this->getRawMediaData('anime', $animeId);
|
||||
return $this->animeTransformer->transform($baseData);
|
||||
}
|
||||
@ -146,7 +151,13 @@ class KitsuModel {
|
||||
return $this->mangaTransformer->transform($baseData);
|
||||
}
|
||||
|
||||
public function getAnimeList($status): array
|
||||
/**
|
||||
* Get the anime list for the configured user
|
||||
*
|
||||
* @param string $status - The watching status to filter the list with
|
||||
* @return array
|
||||
*/
|
||||
public function getAnimeList(string $status): array
|
||||
{
|
||||
$options = [
|
||||
'query' => [
|
||||
@ -155,33 +166,33 @@ class KitsuModel {
|
||||
'media_type' => 'Anime',
|
||||
'status' => $status,
|
||||
],
|
||||
'include' => 'media,media.genres',
|
||||
'include' => 'media,media.genres,media.mappings,anime.streamingLinks',
|
||||
'page' => [
|
||||
'offset' => 0,
|
||||
'limit' => 1000
|
||||
],
|
||||
'sort' => '-updated_at'
|
||||
'limit' => 500
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
$data = $this->getRequest('library-entries', $options);
|
||||
$included = K::organizeIncludes($data['included']);
|
||||
$cacheItem = $this->cache->getItem($this->getHashForMethodCall($this, __METHOD__, $options));
|
||||
|
||||
foreach($data['data'] as $i => &$item)
|
||||
if ( ! $cacheItem->isHit())
|
||||
{
|
||||
$item['anime'] = $included['anime'][$item['relationships']['media']['data']['id']];
|
||||
$data = $this->getRequest('library-entries', $options);
|
||||
$included = JsonAPI::organizeIncludes($data['included']);
|
||||
$included = JsonAPI::inlineIncludedRelationships($included, 'anime');
|
||||
|
||||
$animeGenres = $item['anime']['relationships']['genres'];
|
||||
|
||||
foreach($animeGenres as $id)
|
||||
foreach($data['data'] as $i => &$item)
|
||||
{
|
||||
$item['genres'][] = $included['genres'][$id]['name'];
|
||||
$item['included'] = $included;
|
||||
}
|
||||
$transformed = $this->animeListTransformer->transformCollection($data['data']);
|
||||
|
||||
$cacheItem->set($transformed);
|
||||
$cacheItem->save();
|
||||
}
|
||||
|
||||
$transformed = $this->animeListTransformer->transformCollection($data['data']);
|
||||
|
||||
return $transformed;
|
||||
return $cacheItem->get();
|
||||
}
|
||||
|
||||
public function getMangaList($status): array
|
||||
@ -248,19 +259,24 @@ class KitsuModel {
|
||||
public function getListItem(string $listId): array
|
||||
{
|
||||
$baseData = $this->listItem->get($listId);
|
||||
$included = JsonAPI::organizeIncludes($baseData['included']);
|
||||
|
||||
switch ($baseData['included'][0]['type'])
|
||||
|
||||
switch (TRUE)
|
||||
{
|
||||
case 'anime':
|
||||
$baseData['data']['anime'] = $baseData['included'][0];
|
||||
case in_array('anime', array_keys($included)):
|
||||
$included = JsonAPI::inlineIncludedRelationships($included, 'anime');
|
||||
$baseData['data']['included'] = $included;
|
||||
return $this->animeListTransformer->transform($baseData['data']);
|
||||
|
||||
case 'manga':
|
||||
case in_array('manga', array_keys($included)):
|
||||
$included = JsonAPI::inlineIncludedRelationships($included, 'manga');
|
||||
$baseData['data']['included'] = $included;
|
||||
$baseData['data']['manga'] = $baseData['included'][0];
|
||||
return $this->mangaListTransformer->transform($baseData['data']);
|
||||
|
||||
default:
|
||||
return $baseData['data']['attributes'];
|
||||
return $baseData['data'];
|
||||
}
|
||||
}
|
||||
|
||||
@ -309,11 +325,7 @@ class KitsuModel {
|
||||
];
|
||||
|
||||
$data = $this->getRequest($type, $options);
|
||||
|
||||
$baseData = $data['data'][0]['attributes'];
|
||||
$rawGenres = array_pluck($data['included'], 'attributes');
|
||||
$genres = array_pluck($rawGenres, 'name');
|
||||
$baseData['genres'] = $genres;
|
||||
$baseData['included'] = $data['included'];
|
||||
return $baseData;
|
||||
}
|
||||
|
@ -8,7 +8,7 @@
|
||||
*
|
||||
* @package AnimeListClient
|
||||
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||
* @copyright 2015 - 2016 Timothy J. Warren
|
||||
* @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
|
||||
|
@ -8,7 +8,7 @@
|
||||
*
|
||||
* @package AnimeListClient
|
||||
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||
* @copyright 2015 - 2016 Timothy J. Warren
|
||||
* @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
|
||||
@ -37,7 +37,6 @@ class ListItem extends AbstractListItem {
|
||||
|
||||
public function create(array $data): bool
|
||||
{
|
||||
/*?><pre><?= print_r($data, TRUE) ?></pre><?php */
|
||||
$response = $this->getResponse('POST', 'library-entries', [
|
||||
'body' => Json::encode([
|
||||
'data' => [
|
||||
@ -77,7 +76,7 @@ class ListItem extends AbstractListItem {
|
||||
{
|
||||
return $this->getRequest("library-entries/{$id}", [
|
||||
'query' => [
|
||||
'include' => 'media'
|
||||
'include' => 'media,media.genres,media.mappings'
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
@ -8,7 +8,7 @@
|
||||
*
|
||||
* @package AnimeListClient
|
||||
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||
* @copyright 2015 - 2016 Timothy J. Warren
|
||||
* @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
|
||||
@ -33,10 +33,13 @@ class AnimeListTransformer extends AbstractTransformer {
|
||||
*/
|
||||
public function transform($item)
|
||||
{
|
||||
/* ?><pre><?= print_r($item, TRUE) ?></pre><?php
|
||||
// die(); */
|
||||
$anime = $item['anime']['attributes'] ?? $item['anime'];
|
||||
$genres = $item['genres'] ?? [];
|
||||
/* ?><pre><?= json_encode($item, \JSON_PRETTY_PRINT) ?></pre><?php */
|
||||
$included = $item['included'];
|
||||
$animeId = $item['relationships']['media']['data']['id'];
|
||||
$anime = $included['anime'][$animeId];
|
||||
|
||||
$genres = array_column($anime['relationships']['genres'], 'name') ?? [];
|
||||
sort($genres);
|
||||
|
||||
$rating = (int) 2 * $item['attributes']['rating'];
|
||||
|
||||
@ -44,10 +47,27 @@ class AnimeListTransformer extends AbstractTransformer {
|
||||
? (int) $anime['episodeCount']
|
||||
: '-';
|
||||
|
||||
$MALid = NULL;
|
||||
|
||||
if (array_key_exists('mappings', $anime['relationships']))
|
||||
{
|
||||
foreach ($anime['relationships']['mappings'] as $mapping)
|
||||
{
|
||||
if ($mapping['externalSite'] === 'myanimelist/anime')
|
||||
{
|
||||
$MALid = $mapping['externalId'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => $item['id'],
|
||||
'mal_id' => $MALid,
|
||||
'episodes' => [
|
||||
'watched' => $item['attributes']['progress'],
|
||||
'watched' => (int) $item['attributes']['progress'] !== '0'
|
||||
? (int) $item['attributes']['progress']
|
||||
: '-',
|
||||
'total' => $total_episodes,
|
||||
'length' => $anime['episodeLength'],
|
||||
],
|
||||
@ -60,16 +80,16 @@ class AnimeListTransformer extends AbstractTransformer {
|
||||
'age_rating' => $anime['ageRating'],
|
||||
'titles' => Kitsu::filterTitles($anime),
|
||||
'slug' => $anime['slug'],
|
||||
'url' => $anime['url'] ?? '',
|
||||
'type' => $this->string($anime['showType'])->upperCaseFirst()->__toString(),
|
||||
'image' => $anime['posterImage']['small'],
|
||||
'genres' => $genres,
|
||||
'streaming_links' => Kitsu::parseStreamingLinks($included),
|
||||
],
|
||||
'watching_status' => $item['attributes']['status'],
|
||||
'notes' => $item['attributes']['notes'],
|
||||
'rewatching' => (bool) $item['attributes']['reconsuming'],
|
||||
'rewatched' => (int) $item['attributes']['reconsumeCount'],
|
||||
'user_rating' => ($rating === 0) ? '-' : $rating,
|
||||
'user_rating' => ($rating === 0) ? '-' : (int) $rating,
|
||||
'private' => (bool) $item['attributes']['private'] ?? false,
|
||||
];
|
||||
}
|
||||
@ -83,18 +103,8 @@ class AnimeListTransformer extends AbstractTransformer {
|
||||
*/
|
||||
public function untransform($item)
|
||||
{
|
||||
// Messy mapping of boolean values to their API string equivalents
|
||||
$privacy = 'false';
|
||||
if (array_key_exists('private', $item) && $item['private'])
|
||||
{
|
||||
$privacy = 'true';
|
||||
}
|
||||
|
||||
$rewatching = 'false';
|
||||
if (array_key_exists('rewatching', $item) && $item['rewatching'])
|
||||
{
|
||||
$rewatching = 'true';
|
||||
}
|
||||
$privacy = (array_key_exists('private', $item) && $item['private']);
|
||||
$rewatching = (array_key_exists('rewatching', $item) && $item['rewatching']);
|
||||
|
||||
$untransformed = [
|
||||
'id' => $item['id'],
|
||||
|
@ -8,7 +8,7 @@
|
||||
*
|
||||
* @package AnimeListClient
|
||||
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||
* @copyright 2015 - 2016 Timothy J. Warren
|
||||
* @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
|
||||
@ -16,7 +16,7 @@
|
||||
|
||||
namespace Aviat\AnimeClient\API\Kitsu\Transformer;
|
||||
|
||||
use Aviat\AnimeClient\API\Kitsu;
|
||||
use Aviat\AnimeClient\API\{JsonAPI, Kitsu};
|
||||
use Aviat\Ion\Transformer\AbstractTransformer;
|
||||
|
||||
/**
|
||||
@ -33,14 +33,18 @@ class AnimeTransformer extends AbstractTransformer {
|
||||
*/
|
||||
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']);
|
||||
|
||||
$titles = Kitsu::filterTitles($item);
|
||||
|
||||
return [
|
||||
'titles' => Kitsu::filterTitles($item),
|
||||
'title' => $titles[0],
|
||||
'titles' => $titles,
|
||||
'status' => Kitsu::getAiringStatus($item['startDate'], $item['endDate']),
|
||||
'cover_image' => $item['posterImage']['small'],
|
||||
'show_type' => $item['showType'],
|
||||
'show_type' => $this->string($item['showType'])->upperCaseFirst()->__toString(),
|
||||
'episode_count' => $item['episodeCount'],
|
||||
'episode_length' => $item['episodeLength'],
|
||||
'synopsis' => $item['synopsis'],
|
||||
@ -48,6 +52,7 @@ class AnimeTransformer extends AbstractTransformer {
|
||||
'age_rating_guide' => $item['ageRatingGuide'],
|
||||
'url' => "https://kitsu.io/anime/{$item['slug']}",
|
||||
'genres' => $item['genres'],
|
||||
'streaming_links' => Kitsu::parseStreamingLinks($item['included'])
|
||||
];
|
||||
}
|
||||
}
|
@ -8,7 +8,7 @@
|
||||
*
|
||||
* @package AnimeListClient
|
||||
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||
* @copyright 2015 - 2016 Timothy J. Warren
|
||||
* @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
|
||||
|
@ -8,7 +8,7 @@
|
||||
*
|
||||
* @package AnimeListClient
|
||||
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||
* @copyright 2015 - 2016 Timothy J. Warren
|
||||
* @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
|
||||
|
@ -8,7 +8,7 @@
|
||||
*
|
||||
* @package AnimeListClient
|
||||
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||
* @copyright 2015 - 2016 Timothy J. Warren
|
||||
* @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
|
||||
|
49
src/API/MAL.php
Normal file
49
src/API/MAL.php
Normal file
@ -0,0 +1,49 @@
|
||||
<?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;
|
||||
|
||||
use Aviat\AnimeClient\API\MAL\Enum\{AnimeWatchingStatus, MangaReadingStatus};
|
||||
|
||||
/**
|
||||
* Constants and mappings for the My Anime List API
|
||||
*/
|
||||
class MAL {
|
||||
const AUTH_URL = 'https://myanimelist.net/api/account/verify_credentials.xml';
|
||||
const BASE_URL = 'https://myanimelist.net/api/';
|
||||
|
||||
public static function getIdToWatchingStatusMap()
|
||||
{
|
||||
return [
|
||||
1 => AnimeWatchingStatus::WATCHING,
|
||||
2 => AnimeWatchingStatus::COMPLETED,
|
||||
3 => AnimeWatchingStatus::ON_HOLD,
|
||||
4 => AnimeWatchingStatus::DROPPED,
|
||||
5 => AnimeWatchingStatus::PLAN_TO_WATCH
|
||||
];
|
||||
}
|
||||
|
||||
public static function getIdToReadingStatusMap()
|
||||
{
|
||||
return [
|
||||
1 => MangaReadingStatus::READING,
|
||||
2 => MangaReadingStatus::COMPLETED,
|
||||
3 => MangaReadingStatus::ON_HOLD,
|
||||
4 => MangaReadingStatus::DROPPED,
|
||||
5 => MangaReadingStatus::PLAN_TO_READ
|
||||
];
|
||||
}
|
||||
}
|
@ -1,108 +0,0 @@
|
||||
<?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 - 2016 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\AnimeClient;
|
||||
use Aviat\Ion\Di\{ContainerAware, ContainerInterface};
|
||||
|
||||
/**
|
||||
* MAL API Authentication
|
||||
*/
|
||||
class Auth {
|
||||
|
||||
use \Aviat\Ion\Di\ContainerAware;
|
||||
|
||||
/**
|
||||
* Anime API Model
|
||||
*
|
||||
* @var \Aviat\AnimeClient\Model\API
|
||||
*/
|
||||
protected $model;
|
||||
|
||||
/**
|
||||
* Session object
|
||||
*
|
||||
* @var Aura\Session\Segment
|
||||
*/
|
||||
protected $segment;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param ContainerInterface $container
|
||||
*/
|
||||
public function __construct(ContainerInterface $container)
|
||||
{
|
||||
$this->setContainer($container);
|
||||
$this->segment = $container->get('session')
|
||||
->getSegment(AnimeClient::SESSION_SEGMENT);
|
||||
$this->model = $container->get('api-model');
|
||||
}
|
||||
|
||||
/**
|
||||
* Make the appropriate authentication call,
|
||||
* and save the resulting auth token if successful
|
||||
*
|
||||
* @param string $password
|
||||
* @return boolean
|
||||
*/
|
||||
public function authenticate($password)
|
||||
{
|
||||
$username = $this->container->get('config')
|
||||
->get('hummingbird_username');
|
||||
$auth_token = $this->model->authenticate($username, $password);
|
||||
|
||||
if (FALSE !== $auth_token)
|
||||
{
|
||||
$this->segment->set('auth_token', $auth_token);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the current user is authenticated
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function is_authenticated()
|
||||
{
|
||||
return ($this->get_auth_token() !== FALSE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear authentication values
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function logout()
|
||||
{
|
||||
$this->segment->clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the authentication token from the session
|
||||
*
|
||||
* @return string|false
|
||||
*/
|
||||
public function get_auth_token()
|
||||
{
|
||||
return $this->segment->get('auth_token', FALSE);
|
||||
}
|
||||
}
|
||||
// End of KitsuAuth.php
|
30
src/API/MAL/Enum/AnimeWatchingStatus.php
Normal file
30
src/API/MAL/Enum/AnimeWatchingStatus.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?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\Enum;
|
||||
|
||||
use Aviat\Ion\Enum as BaseEnum;
|
||||
|
||||
/**
|
||||
* Possible values for watching status for the current anime
|
||||
*/
|
||||
class AnimeWatchingStatus extends BaseEnum {
|
||||
const WATCHING = 'watching';
|
||||
const COMPLETED = 'completed';
|
||||
const ON_HOLD = 'onhold';
|
||||
const DROPPED = 'dropped';
|
||||
const PLAN_TO_WATCH = 'plantowatch';
|
||||
}
|
30
src/API/MAL/Enum/MangaReadingStatus.php
Normal file
30
src/API/MAL/Enum/MangaReadingStatus.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?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\Enum;
|
||||
|
||||
use Aviat\Ion\Enum as BaseEnum;
|
||||
|
||||
/**
|
||||
* Possible values for watching status for the current anime
|
||||
*/
|
||||
class MangaReadingStatus extends BaseEnum {
|
||||
const READING = 'reading';
|
||||
const COMPLETED = 'completed';
|
||||
const ON_HOLD = 'onhold';
|
||||
const DROPPED = 'dropped';
|
||||
const PLAN_TO_READ = 'plantoread';
|
||||
}
|
53
src/API/MAL/ListItem.php
Normal file
53
src/API/MAL/ListItem.php
Normal file
@ -0,0 +1,53 @@
|
||||
<?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;
|
||||
use MALTrait;
|
||||
|
||||
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
|
||||
{
|
||||
|
||||
}
|
||||
}
|
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);
|
||||
}
|
||||
}
|
@ -8,56 +8,63 @@
|
||||
*
|
||||
* @package AnimeListClient
|
||||
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||
* @copyright 2015 - 2016 Timothy J. Warren
|
||||
* @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\Kitsu;
|
||||
namespace Aviat\AnimeClient\API\MAL;
|
||||
|
||||
use Aviat\AnimeClient\Model\API;
|
||||
use Aviat\AnimeClient\API\MAL as M;
|
||||
use Aviat\AnimeClient\API\MAL\{
|
||||
AnimeListTransformer,
|
||||
ListItem
|
||||
};
|
||||
use Aviat\AnimeClient\API\XML;
|
||||
use Aviat\Ion\Di\ContainerAware;
|
||||
|
||||
/**
|
||||
* MyAnimeList API Model
|
||||
*/
|
||||
class Model extends API {
|
||||
class Model {
|
||||
use ContainerAware;
|
||||
use MALTrait;
|
||||
|
||||
/**
|
||||
* Base url for Kitsu API
|
||||
*/
|
||||
protected $baseUrl = 'https://myanimelist.net/api/';
|
||||
/**
|
||||
* @var AnimeListTransformer
|
||||
*/
|
||||
protected $animeListTransformer;
|
||||
|
||||
/**
|
||||
* Default settings for Guzzle
|
||||
* @var array
|
||||
*/
|
||||
protected $connectionDefaults = [];
|
||||
/**
|
||||
* KitsuModel constructor.
|
||||
*/
|
||||
public function __construct(ListItem $listItem)
|
||||
{
|
||||
// Set up Guzzle trait
|
||||
$this->init();
|
||||
$this->animeListTransformer = new AnimeListTransformer();
|
||||
$this->listItem = $listItem;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
])
|
||||
]);
|
||||
public function createListItem(array $data): bool
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
$info = $response->getBody();
|
||||
public function getListItem(string $listId): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
if (array_key_exists('access_token', $info)) {
|
||||
// @TODO save token
|
||||
return true;
|
||||
}
|
||||
public function updateListItem(array $data)
|
||||
{
|
||||
$updateData = $this->animeListTransformer->transform($data['data']);
|
||||
return $this->listItem->update($data['mal_id'], $updateData);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
220
src/API/XML.php
Normal file
220
src/API/XML.php
Normal file
@ -0,0 +1,220 @@
|
||||
<?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;
|
||||
|
||||
use DOMDocument, DOMNode, DOMNodelist;
|
||||
|
||||
/**
|
||||
* XML <=> PHP Array codec
|
||||
*/
|
||||
class XML {
|
||||
|
||||
/**
|
||||
* XML representation of the data
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $xml;
|
||||
|
||||
/**
|
||||
* PHP array version of the data
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $data;
|
||||
|
||||
/**
|
||||
* XML constructor
|
||||
*/
|
||||
public function __construct(string $xml = '', array $data = [])
|
||||
{
|
||||
$this->setXML($xml)->setData($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize the data to an xml string
|
||||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
return static::toXML($this->getData());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the data parsed from the XML
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getData(): array
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the data to create xml from
|
||||
*
|
||||
* @param array $data
|
||||
* @return $this
|
||||
*/
|
||||
public function setData(array $data): self
|
||||
{
|
||||
$this->data = $data;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the xml created from the data
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getXML(): string
|
||||
{
|
||||
return $this->xml;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the xml to parse the data from
|
||||
*
|
||||
* @param string $xml
|
||||
* @return $this
|
||||
*/
|
||||
public function setXML(string $xml): self
|
||||
{
|
||||
$this->xml = $xml;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an xml document string to a php array
|
||||
*
|
||||
* @param string $xml
|
||||
* @return array
|
||||
*/
|
||||
public static function toArray(string $xml): array
|
||||
{
|
||||
$data = [];
|
||||
|
||||
// Get rid of unimportant text nodes by removing
|
||||
// whitespace characters from between xml tags,
|
||||
// except for the xml declaration tag, Which looks
|
||||
// something like:
|
||||
/* <?xml version="1.0" encoding="UTF-8"?> */
|
||||
$xml = preg_replace('/([^\?])>\s+</', '$1><', $xml);
|
||||
|
||||
$dom = new DOMDocument();
|
||||
$dom->loadXML($xml);
|
||||
$root = $dom->documentElement;
|
||||
|
||||
$data[$root->tagName] = [];
|
||||
|
||||
if ($root->hasChildNodes())
|
||||
{
|
||||
static::childNodesToArray($data[$root->tagName], $root->childNodes);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform the array into XML
|
||||
*
|
||||
* @param array $data
|
||||
* @return string
|
||||
*/
|
||||
public static function toXML(array $data): string
|
||||
{
|
||||
$dom = new DOMDocument();
|
||||
$dom->encoding = 'UTF-8';
|
||||
|
||||
static::arrayPropertiesToXmlNodes($dom, $dom, $data);
|
||||
|
||||
return $dom->saveXML();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the xml document string to a php array
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function parse(): array
|
||||
{
|
||||
$xml = $this->getXML();
|
||||
$data = static::toArray($xml);
|
||||
return $this->setData($data)->getData();
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform the array into XML
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function createXML(): string
|
||||
{
|
||||
return static::toXML($this->getData());
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively create array structure based on xml structure
|
||||
*
|
||||
* @param array &$root A reference to the current array location
|
||||
* @param DOMNodeList $nodeList The current NodeList object
|
||||
* @return void
|
||||
*/
|
||||
private static function childNodesToArray(array &$root, DOMNodelist $nodeList)
|
||||
{
|
||||
$length = $nodeList->length;
|
||||
for ($i = 0; $i < $length; $i++)
|
||||
{
|
||||
$el = $nodeList->item($i);
|
||||
if (is_a($el->childNodes->item(0), 'DomText') || ( ! $el->hasChildNodes()))
|
||||
{
|
||||
$root[$el->nodeName] = $el->textContent;
|
||||
}
|
||||
else
|
||||
{
|
||||
$root[$el->nodeName] = [];
|
||||
static::childNodesToArray($root[$el->nodeName], $el->childNodes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively create xml nodes from array properties
|
||||
*
|
||||
* @param DOMDocument $dom The current DOM object
|
||||
* @param DOMNode $parent The parent element to append children to
|
||||
* @param array $data The data for the current node
|
||||
* @return void
|
||||
*/
|
||||
private static function arrayPropertiesToXmlNodes(DOMDocument &$dom, DOMNode &$parent, array $data)
|
||||
{
|
||||
foreach($data as $key => $props)
|
||||
{
|
||||
$node = $dom->createElement($key);
|
||||
if (is_array($props))
|
||||
{
|
||||
static::arrayPropertiesToXmlNodes($dom, $node, $props);
|
||||
}
|
||||
else
|
||||
{
|
||||
$tNode = $dom->createTextNode((string)$props);
|
||||
$node->appendChild($tNode);
|
||||
}
|
||||
|
||||
$parent->appendChild($node);
|
||||
}
|
||||
}
|
||||
}
|
@ -8,7 +8,7 @@
|
||||
*
|
||||
* @package AnimeListClient
|
||||
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||
* @copyright 2015 - 2016 Timothy J. Warren
|
||||
* @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
|
||||
|
@ -8,7 +8,7 @@
|
||||
*
|
||||
* @package AnimeListClient
|
||||
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||
* @copyright 2015 - 2016 Timothy J. Warren
|
||||
* @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
|
||||
|
@ -8,7 +8,7 @@
|
||||
*
|
||||
* @package AnimeListClient
|
||||
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||
* @copyright 2015 - 2016 Timothy J. Warren
|
||||
* @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
|
||||
|
@ -304,7 +304,8 @@ class Controller {
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -373,7 +374,7 @@ class Controller {
|
||||
*/
|
||||
public function clearCache()
|
||||
{
|
||||
$this->cache->purge();
|
||||
$this->cache->clear();
|
||||
$this->outputHTML('blank', [
|
||||
'title' => 'Cache cleared'
|
||||
], NULL, 200);
|
||||
|
@ -8,7 +8,7 @@
|
||||
*
|
||||
* @package AnimeListClient
|
||||
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||
* @copyright 2015 - 2016 Timothy J. Warren
|
||||
* @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
|
||||
@ -159,7 +159,7 @@ class Anime extends BaseController {
|
||||
if ($result)
|
||||
{
|
||||
$this->set_flash_message('Added new anime to list', 'success');
|
||||
// $this->cache->purge();
|
||||
$this->cache->clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -233,7 +233,7 @@ class Anime extends BaseController {
|
||||
if ($full_result['statusCode'] === 200)
|
||||
{
|
||||
$this->set_flash_message("Successfully updated.", 'success');
|
||||
// $this->cache->purge();
|
||||
$this->cache->clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -261,7 +261,7 @@ class Anime extends BaseController {
|
||||
|
||||
$response = $this->model->updateLibraryItem($data);
|
||||
|
||||
// $this->cache->purge();
|
||||
$this->cache->clear();
|
||||
$this->outputJSON($response['body'], $response['statusCode']);
|
||||
}
|
||||
|
||||
@ -278,7 +278,7 @@ class Anime extends BaseController {
|
||||
if ((bool)$response === TRUE)
|
||||
{
|
||||
$this->set_flash_message("Successfully deleted anime.", 'success');
|
||||
// $this->cache->purge();
|
||||
$this->cache->clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -8,7 +8,7 @@
|
||||
*
|
||||
* @package AnimeListClient
|
||||
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||
* @copyright 2015 - 2016 Timothy J. Warren
|
||||
* @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
|
||||
|
@ -8,7 +8,7 @@
|
||||
*
|
||||
* @package AnimeListClient
|
||||
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||
* @copyright 2015 - 2016 Timothy J. Warren
|
||||
* @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
|
||||
@ -142,7 +142,7 @@ class Manga extends Controller {
|
||||
if ($result)
|
||||
{
|
||||
$this->set_flash_message('Added new manga to list', 'success');
|
||||
// $this->cache->purge();
|
||||
$this->cache->clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -203,7 +203,7 @@ class Manga extends Controller {
|
||||
if ($full_result['statusCode'] === 200)
|
||||
{
|
||||
$this->set_flash_message("Successfully updated manga.", 'success');
|
||||
// $this->cache->purge();
|
||||
$this->cache->clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -232,7 +232,7 @@ class Manga extends Controller {
|
||||
|
||||
$response = $this->model->updateLibraryItem($data);
|
||||
|
||||
// $this->cache->purge();
|
||||
$this->cache->clear();
|
||||
$this->outputJSON($response['body'], $response['statusCode']);
|
||||
}
|
||||
|
||||
@ -250,7 +250,7 @@ class Manga extends Controller {
|
||||
if ($response)
|
||||
{
|
||||
$this->set_flash_message("Successfully deleted manga.", 'success');
|
||||
//$this->cache->purge();
|
||||
$this->cache->clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -18,6 +18,7 @@ namespace Aviat\AnimeClient;
|
||||
|
||||
use Aviat\Ion\Di\ContainerInterface;
|
||||
use Aviat\Ion\Friend;
|
||||
use GuzzleHttp\Exception\ServerException;
|
||||
|
||||
/**
|
||||
* Basic routing/ dispatch
|
||||
@ -129,8 +130,22 @@ class Dispatcher extends RoutingBase {
|
||||
$params = $error_route['params'];
|
||||
}
|
||||
|
||||
// Actually instantiate the controller
|
||||
$this->call($controllerName, $actionMethod, $params);
|
||||
// Try to catch API errors in a presentable fashion
|
||||
try
|
||||
{
|
||||
// Actually instantiate the controller
|
||||
$this->call($controllerName, $actionMethod, $params);
|
||||
}
|
||||
catch (ServerException $e)
|
||||
{
|
||||
$response = $e->getResponse();
|
||||
$this->call(AnimeClient::DEFAULT_CONTROLLER, AnimeClient::ERROR_MESSAGE_METHOD, [
|
||||
$response->getStatusCode(),
|
||||
'API Error',
|
||||
'There was a problem getting data from an external source.',
|
||||
(string) $response->getBody()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -8,7 +8,7 @@
|
||||
*
|
||||
* @package AnimeListClient
|
||||
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||
* @copyright 2015 - 2016 Timothy J. Warren
|
||||
* @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
|
||||
|
@ -8,7 +8,7 @@
|
||||
*
|
||||
* @package AnimeListClient
|
||||
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||
* @copyright 2015 - 2016 Timothy J. Warren
|
||||
* @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
|
||||
|
@ -8,7 +8,7 @@
|
||||
*
|
||||
* @package AnimeListClient
|
||||
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||
* @copyright 2015 - 2016 Timothy J. Warren
|
||||
* @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
|
||||
|
@ -8,7 +8,7 @@
|
||||
*
|
||||
* @package AnimeListClient
|
||||
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||
* @copyright 2015 - 2016 Timothy J. Warren
|
||||
* @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
|
||||
|
@ -8,7 +8,7 @@
|
||||
*
|
||||
* @package AnimeListClient
|
||||
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||
* @copyright 2015 - 2016 Timothy J. Warren
|
||||
* @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
|
||||
|
@ -8,7 +8,7 @@
|
||||
*
|
||||
* @package AnimeListClient
|
||||
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||
* @copyright 2015 - 2016 Timothy J. Warren
|
||||
* @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
|
||||
|
@ -8,7 +8,7 @@
|
||||
*
|
||||
* @package AnimeListClient
|
||||
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||
* @copyright 2015 - 2016 Timothy J. Warren
|
||||
* @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
|
||||
|
@ -16,7 +16,6 @@
|
||||
|
||||
namespace Aviat\AnimeClient;
|
||||
|
||||
use abeautifulsite\SimpleImage;
|
||||
use Aviat\Ion\ConfigInterface;
|
||||
use Aviat\Ion\Di\{ContainerAware, ContainerInterface};
|
||||
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);
|
||||
}
|
||||
}
|
92
tests/API/Kitsu/Transformer/AnimeListTransformerTest.php
Normal file
92
tests/API/Kitsu/Transformer/AnimeListTransformerTest.php
Normal file
@ -0,0 +1,92 @@
|
||||
<?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();
|
||||
$this->dir = AnimeClient_TestCase::TEST_DATA_DIR . '/Kitsu';
|
||||
|
||||
$this->beforeTransform = Json::decodeFile("{$this->dir}/animeListItemBeforeTransform.json");
|
||||
$this->afterTransform = Json::decodeFile("{$this->dir}/animeListItemAfterTransform.json");
|
||||
|
||||
$this->transformer = new AnimeListTransformer();
|
||||
}
|
||||
|
||||
public function testTransform()
|
||||
{
|
||||
$expected = $this->afterTransform;
|
||||
$actual = $this->transformer->transform($this->beforeTransform);
|
||||
|
||||
// Json::encodeFile("{$this->dir}/animeListItemAfterTransform.json", $actual);
|
||||
|
||||
$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);
|
||||
}
|
||||
}
|
31
tests/API/Kitsu/Transformer/AnimeTransformerTest.php
Normal file
31
tests/API/Kitsu/Transformer/AnimeTransformerTest.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?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();
|
||||
$this->dir = AnimeClient_TestCase::TEST_DATA_DIR . '/Kitsu';
|
||||
|
||||
$this->beforeTransform = Json::decodeFile("{$this->dir}/animeBeforeTransform.json");
|
||||
$this->afterTransform = Json::decodeFile("{$this->dir}/animeAfterTransform.json");
|
||||
|
||||
$this->transformer = new AnimeTransformer();
|
||||
}
|
||||
|
||||
public function testTransform()
|
||||
{
|
||||
$expected = $this->afterTransform;
|
||||
$actual = $this->transformer->transform($this->beforeTransform);
|
||||
// Json::encodeFile("{$this->dir}/animeAfterTransform.json", $actual);
|
||||
|
||||
$this->assertEquals($expected, $actual);
|
||||
}
|
||||
}
|
70
tests/API/XMLTest.php
Normal file
70
tests/API/XMLTest.php
Normal file
@ -0,0 +1,70 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Aviat\AnimeClient\Tests\API;
|
||||
|
||||
use Aviat\AnimeClient\API\XML;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class XMLTest extends TestCase {
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->xml = file_get_contents(__DIR__ . '/../test_data/XML/xmlTestFile.xml');
|
||||
$this->expectedXml = file_get_contents(__DIR__ . '/../test_data/XML/minifiedXmlTestFile.xml');
|
||||
|
||||
$this->array = [
|
||||
'entry' => [
|
||||
'foo' => [
|
||||
'bar' => [
|
||||
'baz' => 42
|
||||
]
|
||||
],
|
||||
'episode' => '11',
|
||||
'status' => 'watching',
|
||||
'score' => '7',
|
||||
'storage_type' => '1',
|
||||
'storage_value' => '2.5',
|
||||
'times_rewatched' => '1',
|
||||
'rewatch_value' => '3',
|
||||
'date_start' => '01152015',
|
||||
'date_finish' => '10232016',
|
||||
'priority' => '2',
|
||||
'enable_discussion' => '0',
|
||||
'enable_rewatching' => '1',
|
||||
'comments' => 'Should you say something?',
|
||||
'tags' => 'test tag, 2nd tag'
|
||||
]
|
||||
];
|
||||
|
||||
$this->object = new XML();
|
||||
}
|
||||
|
||||
public function testToArray()
|
||||
{
|
||||
$this->assertEquals($this->array, XML::toArray($this->xml));
|
||||
}
|
||||
|
||||
public function testParse()
|
||||
{
|
||||
$this->object->setXML($this->xml);
|
||||
$this->assertEquals($this->array, $this->object->parse());
|
||||
}
|
||||
|
||||
public function testToXML()
|
||||
{
|
||||
$this->assertEquals($this->expectedXml, XML::toXML($this->array));
|
||||
}
|
||||
|
||||
public function testCreateXML()
|
||||
{
|
||||
$this->object->setData($this->array);
|
||||
$this->assertEquals($this->expectedXml, $this->object->createXML());
|
||||
}
|
||||
|
||||
public function testToString()
|
||||
{
|
||||
$this->object->setData($this->array);
|
||||
$this->assertEquals($this->expectedXml, $this->object->__toString());
|
||||
$this->assertEquals($this->expectedXml, (string) $this->object);
|
||||
}
|
||||
}
|
@ -7,8 +7,11 @@ use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Handler\MockHandler;
|
||||
use GuzzleHttp\HandlerStack;
|
||||
use GuzzleHttp\Psr7\Response;
|
||||
use Zend\Diactoros\Response as HttpResponse;
|
||||
use Zend\Diactoros\ServerRequestFactory;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Zend\Diactoros\{
|
||||
Response as HttpResponse,
|
||||
ServerRequestFactory
|
||||
};
|
||||
|
||||
define('ROOT_DIR', __DIR__ . '/../');
|
||||
define('TEST_DATA_DIR', __DIR__ . '/test_data');
|
||||
@ -17,7 +20,7 @@ define('TEST_VIEW_DIR', __DIR__ . '/test_views');
|
||||
/**
|
||||
* Base class for TestCases
|
||||
*/
|
||||
class AnimeClient_TestCase extends PHPUnit_Framework_TestCase {
|
||||
class AnimeClient_TestCase extends TestCase {
|
||||
// Test directory constants
|
||||
const ROOT_DIR = ROOT_DIR;
|
||||
const SRC_DIR = AnimeClient::SRC_DIR;
|
||||
|
@ -1,10 +1,12 @@
|
||||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
use Aura\Router\RouterFactory;
|
||||
use Aura\Web\WebFactory;
|
||||
use Aviat\AnimeClient\Controller;
|
||||
use Aviat\AnimeClient\Controller\Anime as AnimeController;
|
||||
use Aviat\AnimeClient\Controller\Collection as CollectionController;
|
||||
use Aviat\AnimeClient\Controller\Manga as MangaController;
|
||||
use Aviat\AnimeClient\Controller\{
|
||||
Anime as AnimeController,
|
||||
Collection as CollectionController,
|
||||
Manga as MangaController
|
||||
};
|
||||
|
||||
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
1152
tests/test_data/JsonAPI/jsonApiExample.json
Normal file
1152
tests/test_data/JsonAPI/jsonApiExample.json
Normal file
File diff suppressed because it is too large
Load Diff
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": []
|
||||
}
|
||||
}
|
||||
}
|
51
tests/test_data/Kitsu/animeAfterTransform.json
Normal file
51
tests/test_data/Kitsu/animeAfterTransform.json
Normal file
@ -0,0 +1,51 @@
|
||||
{
|
||||
"title": "Attack on Titan",
|
||||
"titles": ["Attack on Titan", "Shingeki no Kyojin", "\u9032\u6483\u306e\u5de8\u4eba"],
|
||||
"status": "Finished Airing",
|
||||
"cover_image": "https:\/\/media.kitsu.io\/anime\/poster_images\/7442\/small.jpg?1418580054",
|
||||
"show_type": "TV",
|
||||
"episode_count": 25,
|
||||
"episode_length": 24,
|
||||
"synopsis": "Several hundred years ago, humans were nearly exterminated by titans. Titans are typically several stories tall, seem to have no intelligence, devour human beings and, worst of all, seem to do it for the pleasure rather than as a food source. A small percentage of humanity survived by enclosing themselves in a city protected by extremely high walls, even taller than the biggest of titans. Flash forward to the present and the city has not seen a titan in over 100 years. Teenage boy Eren and his foster sister Mikasa witness something horrific as the city walls are destroyed by a colossal titan that appears out of thin air. As the smaller titans flood the city, the two kids watch in horror as their mother is eaten alive. Eren vows that he will murder every single titan and take revenge for all of mankind.\n\n(Source: ANN)",
|
||||
"age_rating": "R",
|
||||
"age_rating_guide": "Violence, Profanity",
|
||||
"url": "https:\/\/kitsu.io\/anime\/attack-on-titan",
|
||||
"genres": ["Action", "Drama", "Fantasy", "Super Power"],
|
||||
"streaming_links": [{
|
||||
"meta": {
|
||||
"name": "Crunchyroll",
|
||||
"link": true,
|
||||
"logo": "<svg width=\"50\" height=\"50\" viewBox=\"0 0 50 50\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\"><g fill=\"#F78B24\" fill-rule=\"evenodd\"><path d=\"M22.549 49.145c-.815-.077-2.958-.456-3.753-.663-6.873-1.79-12.693-6.59-15.773-13.009C1.335 31.954.631 28.807.633 24.788c.003-4.025.718-7.235 2.38-10.686 1.243-2.584 2.674-4.609 4.706-6.66 3.8-3.834 8.614-6.208 14.067-6.936 1.783-.239 5.556-.161 7.221.148 3.463.642 6.571 1.904 9.357 3.797 5.788 3.934 9.542 9.951 10.52 16.861.21 1.48.332 4.559.19 4.816-.077.14-.117-.007-.167-.615-.25-3.015-1.528-6.66-3.292-9.388C40.253 7.836 30.249 4.32 20.987 7.467c-7.15 2.43-12.522 8.596-13.997 16.06-.73 3.692-.51 7.31.658 10.882a21.426 21.426 0 0 0 13.247 13.518c1.475.515 3.369.944 4.618 1.047 1.496.122 1.119.239-.727.224-1.006-.008-2.013-.032-2.237-.053z\"><\/path><path d=\"M27.685 46.1c-7.731-.575-14.137-6.455-15.474-14.204-.243-1.41-.29-4.047-.095-5.345 1.16-7.706 6.97-13.552 14.552-14.639 1.537-.22 4.275-.143 5.746.162 1.28.266 2.7.737 3.814 1.266l.865.411-.814.392c-2.936 1.414-4.748 4.723-4.323 7.892.426 3.173 2.578 5.664 5.667 6.56 1.112.322 2.812.322 3.925 0 1.438-.417 2.566-1.1 3.593-2.173.346-.362.652-.621.68-.576.027.046.106.545.176 1.11.171 1.395.07 4.047-.204 5.371-.876 4.218-3.08 7.758-6.463 10.374-3.2 2.476-7.434 3.711-11.645 3.399z\"><\/path><\/g><\/svg>"
|
||||
},
|
||||
"link": "http:\/\/www.crunchyroll.com\/attack-on-titan",
|
||||
"subs": ["en"],
|
||||
"dubs": ["ja"]
|
||||
}, {
|
||||
"meta": {
|
||||
"name": "Hulu",
|
||||
"link": true,
|
||||
"logo": "<svg width=\"50\" height=\"50\" viewBox=\"0 0 34 50\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\"><path d=\"M22.222 13.889h-11.11V0H0v50h11.111V27.778c0-1.39 1.111-2.778 2.778-2.778h5.555c1.39 0 2.778 1.111 2.778 2.778V50h11.111V25c0-6.111-5-11.111-11.11-11.111z\" fill=\"#8BC34A\" fill-rule=\"evenodd\"><\/path><\/svg>"
|
||||
},
|
||||
"link": "http:\/\/www.hulu.com\/attack-on-titan",
|
||||
"subs": ["en"],
|
||||
"dubs": ["ja"]
|
||||
}, {
|
||||
"meta": {
|
||||
"name": "Funimation",
|
||||
"link": true,
|
||||
"logo": "<svg width=\"50\" height=\"50\" viewBox=\"0 0 50 50\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\"><path d=\"M24.066.017a24.922 24.922 0 0 1 13.302 3.286 25.098 25.098 0 0 1 7.833 7.058 24.862 24.862 0 0 1 4.207 9.575c.82 4.001.641 8.201-.518 12.117a24.946 24.946 0 0 1-4.868 9.009 24.98 24.98 0 0 1-7.704 6.118 24.727 24.727 0 0 1-10.552 2.718A24.82 24.82 0 0 1 13.833 47.3c-5.815-2.872-10.408-8.107-12.49-14.25-2.162-6.257-1.698-13.375 1.303-19.28C5.483 8.07 10.594 3.55 16.602 1.435A24.94 24.94 0 0 1 24.066.017zm-8.415 33.31c.464 2.284 1.939 4.358 3.99 5.48 2.174 1.217 4.765 1.444 7.202 1.181 2.002-.217 3.986-.992 5.455-2.397 1.173-1.151 2.017-2.648 2.33-4.267-1.189-.027-2.378 0-3.566-.03-.568.082-1.137-.048-1.705.014-1.232.012-2.465.003-3.697-.01-.655.066-1.309-.035-1.963.013-1.166-.053-2.334.043-3.5-.025-1.515.08-3.03-.035-4.546.042z\" fill=\"#411299\" fill-rule=\"evenodd\"><\/path><\/svg>"
|
||||
},
|
||||
"link": "http:\/\/www.funimation.com\/shows\/attack-on-titan\/videos\/episodes",
|
||||
"subs": ["en"],
|
||||
"dubs": ["ja"]
|
||||
}, {
|
||||
"meta": {
|
||||
"name": "Netflix",
|
||||
"link": false,
|
||||
"logo": "<svg width=\"50\" height=\"50\" viewBox=\"0 0 26 50\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\"><path d=\"M.057.258C2.518.253 4.982.263 7.446.253c2.858 7.76 5.621 15.556 8.456 23.324.523 1.441 1.003 2.897 1.59 4.312.078-9.209.01-18.42.034-27.631h7.763v46.36c-2.812.372-5.637.627-8.457.957-1.203-3.451-2.396-6.902-3.613-10.348-1.796-5.145-3.557-10.302-5.402-15.428.129 8.954.015 17.912.057 26.871-2.603.39-5.227.637-7.815 1.119C.052 33.279.06 16.768.057.258z\" fill=\"#E21221\" fill-rule=\"evenodd\"><\/path><\/svg>"
|
||||
},
|
||||
"link": "t",
|
||||
"subs": ["en"],
|
||||
"dubs": ["ja"]
|
||||
}]
|
||||
}
|
291
tests/test_data/Kitsu/animeBeforeTransform.json
Normal file
291
tests/test_data/Kitsu/animeBeforeTransform.json
Normal file
@ -0,0 +1,291 @@
|
||||
{
|
||||
"slug": "attack-on-titan",
|
||||
"synopsis": "Several hundred years ago, humans were nearly exterminated by titans. Titans are typically several stories tall, seem to have no intelligence, devour human beings and, worst of all, seem to do it for the pleasure rather than as a food source. A small percentage of humanity survived by enclosing themselves in a city protected by extremely high walls, even taller than the biggest of titans. Flash forward to the present and the city has not seen a titan in over 100 years. Teenage boy Eren and his foster sister Mikasa witness something horrific as the city walls are destroyed by a colossal titan that appears out of thin air. As the smaller titans flood the city, the two kids watch in horror as their mother is eaten alive. Eren vows that he will murder every single titan and take revenge for all of mankind.\n\n(Source: ANN)",
|
||||
"coverImageTopOffset": 263,
|
||||
"titles": {
|
||||
"en": "Attack on Titan",
|
||||
"en_jp": "Shingeki no Kyojin",
|
||||
"ja_jp": "\u9032\u6483\u306e\u5de8\u4eba"
|
||||
},
|
||||
"canonicalTitle": "Attack on Titan",
|
||||
"abbreviatedTitles": null,
|
||||
"averageRating": 4.2678183033371,
|
||||
"ratingFrequencies": {
|
||||
"0.0": "3",
|
||||
"0.5": "126",
|
||||
"1.0": "292",
|
||||
"1.5": "172",
|
||||
"2.0": "394",
|
||||
"2.5": "817",
|
||||
"3.0": "2423",
|
||||
"3.5": "3210",
|
||||
"4.0": "5871",
|
||||
"4.5": "6159",
|
||||
"5.0": "13117",
|
||||
"nil": "18571",
|
||||
"0.479": "-1",
|
||||
"4.658": "-3",
|
||||
"4.726": "-1",
|
||||
"4.932": "-1",
|
||||
"2.05479452054794": "1",
|
||||
"2.53424657534247": "1",
|
||||
"4.10958904109589": "1",
|
||||
"4.65753424657534": "3",
|
||||
"4.72602739726027": "3",
|
||||
"4.86301369863014": "1",
|
||||
"4.93150684931507": "2",
|
||||
"0.273972602739726": "1",
|
||||
"0.410958904109589": "2",
|
||||
"0.479452054794521": "1",
|
||||
"0.684931506849315": "1"
|
||||
},
|
||||
"startDate": "2013-04-07",
|
||||
"endDate": "2013-09-28",
|
||||
"posterImage": {
|
||||
"tiny": "https:\/\/media.kitsu.io\/anime\/poster_images\/7442\/tiny.jpg?1418580054",
|
||||
"small": "https:\/\/media.kitsu.io\/anime\/poster_images\/7442\/small.jpg?1418580054",
|
||||
"medium": "https:\/\/media.kitsu.io\/anime\/poster_images\/7442\/medium.jpg?1418580054",
|
||||
"large": "https:\/\/media.kitsu.io\/anime\/poster_images\/7442\/large.jpg?1418580054",
|
||||
"original": "https:\/\/media.kitsu.io\/anime\/poster_images\/7442\/original.jpg?1418580054"
|
||||
},
|
||||
"coverImage": {
|
||||
"small": "https:\/\/media.kitsu.io\/anime\/cover_images\/7442\/small.jpg?1471880659",
|
||||
"large": "https:\/\/media.kitsu.io\/anime\/cover_images\/7442\/large.jpg?1471880659",
|
||||
"original": "https:\/\/media.kitsu.io\/anime\/cover_images\/7442\/original.png?1471880659"
|
||||
},
|
||||
"episodeCount": 25,
|
||||
"episodeLength": 24,
|
||||
"subtype": "TV",
|
||||
"youtubeVideoId": "n4Nj6Y_SNYI",
|
||||
"ageRating": "R",
|
||||
"ageRatingGuide": "Violence, Profanity",
|
||||
"showType": "TV",
|
||||
"nsfw": false,
|
||||
"included": [
|
||||
{
|
||||
"id": "23",
|
||||
"type": "genres",
|
||||
"links": {
|
||||
"self": "https:\/\/kitsu.io\/api\/edge\/genres\/23"
|
||||
},
|
||||
"attributes": {
|
||||
"name": "Super Power",
|
||||
"slug": "super-power",
|
||||
"description": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "11",
|
||||
"type": "genres",
|
||||
"links": {
|
||||
"self": "https:\/\/kitsu.io\/api\/edge\/genres\/11"
|
||||
},
|
||||
"attributes": {
|
||||
"name": "Fantasy",
|
||||
"slug": "fantasy",
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "4",
|
||||
"type": "genres",
|
||||
"links": {
|
||||
"self": "https:\/\/kitsu.io\/api\/edge\/genres\/4"
|
||||
},
|
||||
"attributes": {
|
||||
"name": "Drama",
|
||||
"slug": "drama",
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "1",
|
||||
"type": "genres",
|
||||
"links": {
|
||||
"self": "https:\/\/kitsu.io\/api\/edge\/genres\/1"
|
||||
},
|
||||
"attributes": {
|
||||
"name": "Action",
|
||||
"slug": "action",
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "5686",
|
||||
"type": "mappings",
|
||||
"links": {
|
||||
"self": "https:\/\/kitsu.io\/api\/edge\/mappings\/5686"
|
||||
},
|
||||
"attributes": {
|
||||
"externalSite": "myanimelist\/anime",
|
||||
"externalId": "16498"
|
||||
},
|
||||
"relationships": {
|
||||
"media": {
|
||||
"links": {
|
||||
"self": "https:\/\/kitsu.io\/api\/edge\/mappings\/5686\/relationships\/media",
|
||||
"related": "https:\/\/kitsu.io\/api\/edge\/mappings\/5686\/media"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "14153",
|
||||
"type": "mappings",
|
||||
"links": {
|
||||
"self": "https:\/\/kitsu.io\/api\/edge\/mappings\/14153"
|
||||
},
|
||||
"attributes": {
|
||||
"externalSite": "thetvdb\/series",
|
||||
"externalId": "267440"
|
||||
},
|
||||
"relationships": {
|
||||
"media": {
|
||||
"links": {
|
||||
"self": "https:\/\/kitsu.io\/api\/edge\/mappings\/14153\/relationships\/media",
|
||||
"related": "https:\/\/kitsu.io\/api\/edge\/mappings\/14153\/media"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "15073",
|
||||
"type": "mappings",
|
||||
"links": {
|
||||
"self": "https:\/\/kitsu.io\/api\/edge\/mappings\/15073"
|
||||
},
|
||||
"attributes": {
|
||||
"externalSite": "thetvdb\/season",
|
||||
"externalId": "514060"
|
||||
},
|
||||
"relationships": {
|
||||
"media": {
|
||||
"links": {
|
||||
"self": "https:\/\/kitsu.io\/api\/edge\/mappings\/15073\/relationships\/media",
|
||||
"related": "https:\/\/kitsu.io\/api\/edge\/mappings\/15073\/media"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "103",
|
||||
"type": "streamingLinks",
|
||||
"links": {
|
||||
"self": "https:\/\/kitsu.io\/api\/edge\/streaming-links\/103"
|
||||
},
|
||||
"attributes": {
|
||||
"url": "http:\/\/www.crunchyroll.com\/attack-on-titan",
|
||||
"subs": [
|
||||
"en"
|
||||
],
|
||||
"dubs": [
|
||||
"ja"
|
||||
]
|
||||
},
|
||||
"relationships": {
|
||||
"streamer": {
|
||||
"links": {
|
||||
"self": "https:\/\/kitsu.io\/api\/edge\/streaming-links\/103\/relationships\/streamer",
|
||||
"related": "https:\/\/kitsu.io\/api\/edge\/streaming-links\/103\/streamer"
|
||||
}
|
||||
},
|
||||
"media": {
|
||||
"links": {
|
||||
"self": "https:\/\/kitsu.io\/api\/edge\/streaming-links\/103\/relationships\/media",
|
||||
"related": "https:\/\/kitsu.io\/api\/edge\/streaming-links\/103\/media"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "102",
|
||||
"type": "streamingLinks",
|
||||
"links": {
|
||||
"self": "https:\/\/kitsu.io\/api\/edge\/streaming-links\/102"
|
||||
},
|
||||
"attributes": {
|
||||
"url": "http:\/\/www.hulu.com\/attack-on-titan",
|
||||
"subs": [
|
||||
"en"
|
||||
],
|
||||
"dubs": [
|
||||
"ja"
|
||||
]
|
||||
},
|
||||
"relationships": {
|
||||
"streamer": {
|
||||
"links": {
|
||||
"self": "https:\/\/kitsu.io\/api\/edge\/streaming-links\/102\/relationships\/streamer",
|
||||
"related": "https:\/\/kitsu.io\/api\/edge\/streaming-links\/102\/streamer"
|
||||
}
|
||||
},
|
||||
"media": {
|
||||
"links": {
|
||||
"self": "https:\/\/kitsu.io\/api\/edge\/streaming-links\/102\/relationships\/media",
|
||||
"related": "https:\/\/kitsu.io\/api\/edge\/streaming-links\/102\/media"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "101",
|
||||
"type": "streamingLinks",
|
||||
"links": {
|
||||
"self": "https:\/\/kitsu.io\/api\/edge\/streaming-links\/101"
|
||||
},
|
||||
"attributes": {
|
||||
"url": "http:\/\/www.funimation.com\/shows\/attack-on-titan\/videos\/episodes",
|
||||
"subs": [
|
||||
"en"
|
||||
],
|
||||
"dubs": [
|
||||
"ja"
|
||||
]
|
||||
},
|
||||
"relationships": {
|
||||
"streamer": {
|
||||
"links": {
|
||||
"self": "https:\/\/kitsu.io\/api\/edge\/streaming-links\/101\/relationships\/streamer",
|
||||
"related": "https:\/\/kitsu.io\/api\/edge\/streaming-links\/101\/streamer"
|
||||
}
|
||||
},
|
||||
"media": {
|
||||
"links": {
|
||||
"self": "https:\/\/kitsu.io\/api\/edge\/streaming-links\/101\/relationships\/media",
|
||||
"related": "https:\/\/kitsu.io\/api\/edge\/streaming-links\/101\/media"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "100",
|
||||
"type": "streamingLinks",
|
||||
"links": {
|
||||
"self": "https:\/\/kitsu.io\/api\/edge\/streaming-links\/100"
|
||||
},
|
||||
"attributes": {
|
||||
"url": "t",
|
||||
"subs": [
|
||||
"en"
|
||||
],
|
||||
"dubs": [
|
||||
"ja"
|
||||
]
|
||||
},
|
||||
"relationships": {
|
||||
"streamer": {
|
||||
"links": {
|
||||
"self": "https:\/\/kitsu.io\/api\/edge\/streaming-links\/100\/relationships\/streamer",
|
||||
"related": "https:\/\/kitsu.io\/api\/edge\/streaming-links\/100\/streamer"
|
||||
}
|
||||
},
|
||||
"media": {
|
||||
"links": {
|
||||
"self": "https:\/\/kitsu.io\/api\/edge\/streaming-links\/100\/relationships\/media",
|
||||
"related": "https:\/\/kitsu.io\/api\/edge\/streaming-links\/100\/media"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
1
tests/test_data/Kitsu/animeListItemAfterTransform.json
Normal file
1
tests/test_data/Kitsu/animeListItemAfterTransform.json
Normal file
@ -0,0 +1 @@
|
||||
{"id":"15839442","mal_id":"33206","episodes":{"watched":0,"total":"-","length":null},"airing":{"status":"Currently Airing","started":"2017-01-12","ended":null},"anime":{"age_rating":null,"titles":["Kobayashi-san Chi no Maid Dragon","Miss Kobayashi's Dragon Maid","\u5c0f\u6797\u3055\u3093\u3061\u306e\u30e1\u30a4\u30c9\u30e9\u30b4\u30f3"],"slug":"kobayashi-san-chi-no-maid-dragon","type":"TV","image":"https:\/\/media.kitsu.io\/anime\/poster_images\/12243\/small.jpg?1481144116","genres":["Comedy","Fantasy","Slice of Life"],"streaming_links":[]},"watching_status":"current","notes":null,"rewatching":false,"rewatched":0,"user_rating":"-","private":false}
|
1096
tests/test_data/Kitsu/animeListItemBeforeTransform.json
Normal file
1096
tests/test_data/Kitsu/animeListItemBeforeTransform.json
Normal file
File diff suppressed because it is too large
Load Diff
14491
tests/test_data/XML/MALExport.xml
Normal file
14491
tests/test_data/XML/MALExport.xml
Normal file
File diff suppressed because it is too large
Load Diff
2
tests/test_data/XML/minifiedXmlTestFile.xml
Normal file
2
tests/test_data/XML/minifiedXmlTestFile.xml
Normal file
@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<entry><foo><bar><baz>42</baz></bar></foo><episode>11</episode><status>watching</status><score>7</score><storage_type>1</storage_type><storage_value>2.5</storage_value><times_rewatched>1</times_rewatched><rewatch_value>3</rewatch_value><date_start>01152015</date_start><date_finish>10232016</date_finish><priority>2</priority><enable_discussion>0</enable_discussion><enable_rewatching>1</enable_rewatching><comments>Should you say something?</comments><tags>test tag, 2nd tag</tags></entry>
|
22
tests/test_data/XML/xmlTestFile.xml
Normal file
22
tests/test_data/XML/xmlTestFile.xml
Normal file
@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<entry>
|
||||
<foo>
|
||||
<bar>
|
||||
<baz>42</baz>
|
||||
</bar>
|
||||
</foo>
|
||||
<episode>11</episode>
|
||||
<status>watching</status>
|
||||
<score>7</score>
|
||||
<storage_type>1</storage_type>
|
||||
<storage_value>2.5</storage_value>
|
||||
<times_rewatched>1</times_rewatched>
|
||||
<rewatch_value>3</rewatch_value>
|
||||
<date_start>01152015</date_start>
|
||||
<date_finish>10232016</date_finish>
|
||||
<priority>2</priority>
|
||||
<enable_discussion>0</enable_discussion>
|
||||
<enable_rewatching>1</enable_rewatching>
|
||||
<comments>Should you say something?</comments>
|
||||
<tags>test tag, 2nd tag</tags>
|
||||
</entry>
|
Loading…
Reference in New Issue
Block a user