Merge remote-tracking branch 'origin/develop'
This commit is contained in:
commit
650ddc781f
@ -3,7 +3,10 @@
|
|||||||
## Version 4
|
## Version 4
|
||||||
* Updated to use Kitsu API after discontinuation of Hummingbird
|
* Updated to use Kitsu API after discontinuation of Hummingbird
|
||||||
* Added streaming links to list entries from the Kitsu API
|
* Added streaming links to list entries from the Kitsu API
|
||||||
* Added simple integration with MyAnimeList, so an update can cross-post to both Kitsu and MyAnimeList
|
* Added simple integration with MyAnimeList, so an update can cross-post to both Kitsu and MyAnimeList (anime and manga)
|
||||||
|
* Added console command to sync Kitsu and MyAnimeList data
|
||||||
|
|
||||||
|
* Added character pages
|
||||||
|
|
||||||
## Version 3
|
## Version 3
|
||||||
* Converted user configuration to toml files
|
* Converted user configuration to toml files
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
* @link https://github.com/timw4mail/HummingBirdAnimeClient
|
* @link https://github.com/timw4mail/HummingBirdAnimeClient
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
use function Aviat\AnimeClient\loadToml;
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
// Lower level configuration
|
// Lower level configuration
|
||||||
@ -23,7 +24,9 @@
|
|||||||
$APP_DIR = realpath(__DIR__ . '/../');
|
$APP_DIR = realpath(__DIR__ . '/../');
|
||||||
$ROOT_DIR = realpath("{$APP_DIR}/../");
|
$ROOT_DIR = realpath("{$APP_DIR}/../");
|
||||||
|
|
||||||
$base_config = [
|
$tomlConfig = loadToml(__DIR__);
|
||||||
|
|
||||||
|
$base_config = array_merge($tomlConfig, [
|
||||||
'asset_dir' => "{$ROOT_DIR}/public",
|
'asset_dir' => "{$ROOT_DIR}/public",
|
||||||
|
|
||||||
// Template file path
|
// Template file path
|
||||||
@ -34,6 +37,5 @@ $base_config = [
|
|||||||
'img_cache_path' => "{$ROOT_DIR}/public/images",
|
'img_cache_path' => "{$ROOT_DIR}/public/images",
|
||||||
|
|
||||||
// Included config files
|
// Included config files
|
||||||
'menus' => require 'menus.php',
|
|
||||||
'routes' => require 'routes.php',
|
'routes' => require 'routes.php',
|
||||||
];
|
]);
|
@ -1,41 +0,0 @@
|
|||||||
<?php declare(strict_types=1);
|
|
||||||
/**
|
|
||||||
* Hummingbird Anime List Client
|
|
||||||
*
|
|
||||||
* An API client for Kitsu and MyAnimeList to manage anime and manga watch lists
|
|
||||||
*
|
|
||||||
* PHP version 7
|
|
||||||
*
|
|
||||||
* @package HummingbirdAnimeClient
|
|
||||||
* @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
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
return [
|
|
||||||
'anime_list' => [
|
|
||||||
'route_prefix' => '/anime',
|
|
||||||
'items' => [
|
|
||||||
'watching' => '/watching',
|
|
||||||
'plan_to_watch' => '/plan_to_watch',
|
|
||||||
'on_hold' => '/on_hold',
|
|
||||||
'dropped' => '/dropped',
|
|
||||||
'completed' => '/completed',
|
|
||||||
'all' => '/all'
|
|
||||||
]
|
|
||||||
],
|
|
||||||
'manga_list' => [
|
|
||||||
'route_prefix' => '/manga',
|
|
||||||
'items' => [
|
|
||||||
'reading' => '/reading',
|
|
||||||
'plan_to_read' => '/plan_to_read',
|
|
||||||
'on_hold' => '/on_hold',
|
|
||||||
'dropped' => '/dropped',
|
|
||||||
'completed' => '/completed',
|
|
||||||
'all' => '/all'
|
|
||||||
]
|
|
||||||
]
|
|
||||||
];
|
|
19
app/appConf/menus.toml
Normal file
19
app/appConf/menus.toml
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
[anime_list]
|
||||||
|
route_prefix = "/anime"
|
||||||
|
[anime_list.items]
|
||||||
|
watching = '/watching'
|
||||||
|
plan_to_watch = '/plan_to_watch'
|
||||||
|
on_hold = '/on_hold'
|
||||||
|
dropped = '/dropped'
|
||||||
|
completed = '/completed'
|
||||||
|
all = '/all'
|
||||||
|
|
||||||
|
[manga_list]
|
||||||
|
route_prefix = "/manga"
|
||||||
|
[manga_list.items]
|
||||||
|
reading = '/reading'
|
||||||
|
plan_to_read = '/plan_to_read'
|
||||||
|
on_hold = '/on_hold'
|
||||||
|
dropped = '/dropped'
|
||||||
|
completed = '/completed'
|
||||||
|
all = '/all'
|
19
app/appConf/route_config.toml
Normal file
19
app/appConf/route_config.toml
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
################################################################################
|
||||||
|
# Route config
|
||||||
|
#
|
||||||
|
# Default views and paths
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
# Path to public directory, where images/css/javascript are located,
|
||||||
|
# appended to the url
|
||||||
|
asset_path = "/public"
|
||||||
|
|
||||||
|
# Which list should be the default?
|
||||||
|
default_list = "anime" # anime or manga
|
||||||
|
|
||||||
|
# Default pages for anime/manga
|
||||||
|
default_anime_list_path = "watching" # watching|plan_to_watch|on_hold|dropped|completed|all
|
||||||
|
default_manga_list_path = "reading" # reading|plan_to_read|on_hold|dropped|completed|all
|
||||||
|
|
||||||
|
# Default view type (cover_view/list_view)
|
||||||
|
default_view_type = "cover_view"
|
@ -17,38 +17,17 @@
|
|||||||
|
|
||||||
use const Aviat\AnimeClient\{
|
use const Aviat\AnimeClient\{
|
||||||
DEFAULT_CONTROLLER_METHOD,
|
DEFAULT_CONTROLLER_METHOD,
|
||||||
DEFAULT_CONTROLLER_NAMESPACE
|
DEFAULT_CONTROLLER
|
||||||
};
|
};
|
||||||
|
|
||||||
use Aviat\AnimeClient\AnimeClient;
|
use Aviat\AnimeClient\AnimeClient;
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// Routing Config
|
||||||
|
//
|
||||||
|
// Maps paths to controlers and methods
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
return [
|
return [
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
// Routing options
|
|
||||||
//
|
|
||||||
// Specify default paths and views
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
'route_config' => [
|
|
||||||
// Path to public directory, where images/css/javascript are located,
|
|
||||||
// appended to the url
|
|
||||||
'asset_path' => '/public',
|
|
||||||
|
|
||||||
// Which list should be the default?
|
|
||||||
'default_list' => 'anime', // anime or manga
|
|
||||||
|
|
||||||
// Default pages for anime/manga
|
|
||||||
'default_anime_list_path' => "watching", // watching|plan_to_watch|on_hold|dropped|completed|all
|
|
||||||
'default_manga_list_path' => "reading", // reading|plan_to_read|on_hold|dropped|completed|all
|
|
||||||
|
|
||||||
// Default view type (cover_view/list_view)
|
|
||||||
'default_view_type' => 'cover_view',
|
|
||||||
],
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
// Routing Config
|
|
||||||
//
|
|
||||||
// Maps paths to controlers and methods
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
'routes' => [
|
|
||||||
// ---------------------------------------------------------------------
|
// ---------------------------------------------------------------------
|
||||||
// Anime List Routes
|
// Anime List Routes
|
||||||
// ---------------------------------------------------------------------
|
// ---------------------------------------------------------------------
|
||||||
@ -171,25 +150,25 @@ return [
|
|||||||
'cache_purge' => [
|
'cache_purge' => [
|
||||||
'path' => '/cache_purge',
|
'path' => '/cache_purge',
|
||||||
'action' => 'clearCache',
|
'action' => 'clearCache',
|
||||||
'controller' => DEFAULT_CONTROLLER_NAMESPACE,
|
'controller' => DEFAULT_CONTROLLER,
|
||||||
'verb' => 'get',
|
'verb' => 'get',
|
||||||
],
|
],
|
||||||
'login' => [
|
'login' => [
|
||||||
'path' => '/login',
|
'path' => '/login',
|
||||||
'action' => 'login',
|
'action' => 'login',
|
||||||
'controller' => DEFAULT_CONTROLLER_NAMESPACE,
|
'controller' => DEFAULT_CONTROLLER,
|
||||||
'verb' => 'get',
|
'verb' => 'get',
|
||||||
],
|
],
|
||||||
'login.post' => [
|
'login.post' => [
|
||||||
'path' => '/login',
|
'path' => '/login',
|
||||||
'action' => 'loginAction',
|
'action' => 'loginAction',
|
||||||
'controller' => DEFAULT_CONTROLLER_NAMESPACE,
|
'controller' => DEFAULT_CONTROLLER,
|
||||||
'verb' => 'post',
|
'verb' => 'post',
|
||||||
],
|
],
|
||||||
'logout' => [
|
'logout' => [
|
||||||
'path' => '/logout',
|
'path' => '/logout',
|
||||||
'action' => 'logout',
|
'action' => 'logout',
|
||||||
'controller' => DEFAULT_CONTROLLER_NAMESPACE,
|
'controller' => DEFAULT_CONTROLLER,
|
||||||
],
|
],
|
||||||
'update' => [
|
'update' => [
|
||||||
'path' => '/{controller}/update',
|
'path' => '/{controller}/update',
|
||||||
@ -225,8 +204,7 @@ return [
|
|||||||
],
|
],
|
||||||
'index_redirect' => [
|
'index_redirect' => [
|
||||||
'path' => '/',
|
'path' => '/',
|
||||||
'controller' => DEFAULT_CONTROLLER_NAMESPACE,
|
'controller' => DEFAULT_CONTROLLER,
|
||||||
'action' => 'redirectToDefaultRoute',
|
'action' => 'redirectToDefaultRoute',
|
||||||
],
|
],
|
||||||
],
|
|
||||||
];
|
];
|
19
app/config/route_config.toml.example
Normal file
19
app/config/route_config.toml.example
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
################################################################################
|
||||||
|
# Route config
|
||||||
|
#
|
||||||
|
# Default views and paths
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
# Path to public directory, where images/css/javascript are located,
|
||||||
|
# appended to the url
|
||||||
|
asset_path = "/public"
|
||||||
|
|
||||||
|
# Which list should be the default?
|
||||||
|
default_list = "anime" # anime or manga
|
||||||
|
|
||||||
|
# Default pages for anime/manga
|
||||||
|
default_anime_list_path = "watching" # watching|plan_to_watch|on_hold|dropped|completed|all
|
||||||
|
default_manga_list_path = "reading" # reading|plan_to_read|on_hold|dropped|completed|all
|
||||||
|
|
||||||
|
# Default view type (cover_view/list_view)
|
||||||
|
default_view_type = "cover_view"
|
@ -1,4 +1,4 @@
|
|||||||
<main class="details">
|
<main class="details fixed">
|
||||||
<section class="flex flex-no-wrap">
|
<section class="flex flex-no-wrap">
|
||||||
<div>
|
<div>
|
||||||
<img class="cover" width="402" height="284" src="<?= $data['cover_image'] ?>" alt="" />
|
<img class="cover" width="402" height="284" src="<?= $data['cover_image'] ?>" alt="" />
|
||||||
@ -74,30 +74,27 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
|
|
||||||
|
|
||||||
<?php /* <pre><?= print_r($characters, TRUE) ?></pre> */ ?>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<section>
|
|
||||||
<?php if (count($characters) > 0): ?>
|
<?php if (count($characters) > 0): ?>
|
||||||
<h2>Characters</h2>
|
<h2>Characters</h2>
|
||||||
<div class="flex flex-wrap">
|
<section class="media-wrap">
|
||||||
<?php foreach($characters as $char): ?>
|
<?php foreach($characters as $char): ?>
|
||||||
<?php if ( ! empty($char['image']['original'])): ?>
|
<?php if ( ! empty($char['image']['original'])): ?>
|
||||||
<div class="character">
|
<article class="character">
|
||||||
<?php $link = $url->generate('character', ['slug' => $char['slug']]) ?>
|
<?php $link = $url->generate('character', ['slug' => $char['slug']]) ?>
|
||||||
|
<div class="name">
|
||||||
<?= $helper->a($link, $char['name']); ?>
|
<?= $helper->a($link, $char['name']); ?>
|
||||||
<br />
|
</div>
|
||||||
<a href="<?= $link ?>">
|
<a href="<?= $link ?>">
|
||||||
<?= $helper->img($char['image']['original'], [
|
<?= $helper->img($char['image']['original'], [
|
||||||
'width' => '225'
|
'width' => '225'
|
||||||
]) ?>
|
]) ?>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</article>
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
<?php endforeach ?>
|
<?php endforeach ?>
|
||||||
</div>
|
|
||||||
<?php endif ?>
|
|
||||||
</section>
|
</section>
|
||||||
|
<?php endif ?>
|
||||||
</main>
|
</main>
|
@ -30,7 +30,11 @@
|
|||||||
<tr id="a-<?= $item['id'] ?>">
|
<tr id="a-<?= $item['id'] ?>">
|
||||||
<?php if ($auth->isAuthenticated()): ?>
|
<?php if ($auth->isAuthenticated()): ?>
|
||||||
<td>
|
<td>
|
||||||
<a class="bracketed" href="<?= $urlGenerator->url("/anime/edit/{$item['id']}/{$item['watching_status']}") ?>">Edit</a>
|
<a class="bracketed" href="<?= $url->generate('edit', [
|
||||||
|
'controller' => 'anime',
|
||||||
|
'id' => $item['id'],
|
||||||
|
'status' => $item['watching_status']
|
||||||
|
]) ?>">Edit</a>
|
||||||
</td>
|
</td>
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
<td class="justify">
|
<td class="justify">
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<main class="details">
|
<main class="details fixed">
|
||||||
<section class="flex flex-no-wrap">
|
<section class="flex flex-no-wrap">
|
||||||
<div>
|
<div>
|
||||||
<img class="cover" width="402" height="284" src="<?= $data['image']['original'] ?>" alt="" />
|
<img class="cover" width="284" src="<?= $data['image']['original'] ?>" alt="" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h2><?= $data['name'] ?></h2>
|
<h2><?= $data['name'] ?></h2>
|
||||||
|
@ -21,8 +21,11 @@
|
|||||||
<div class="table">
|
<div class="table">
|
||||||
<?php if ($auth->isAuthenticated()): ?>
|
<?php if ($auth->isAuthenticated()): ?>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<span class="edit"><a class="bracketed" href="<?= $urlGenerator->url("collection/edit/{$item['hummingbird_id']}") ?>">Edit</a></span>
|
<span class="edit">
|
||||||
<?php /*<span class="delete"><a class="bracketed" href="<?= $urlGenerator->url("collection/delete/{$item['hummingbird_id']}") ?>">Delete</a></span> */ ?>
|
<a class="bracketed" href="<?= $url->generate('collection.edit.get', [
|
||||||
|
'id' => $item['hummingbird_id']
|
||||||
|
]) ?>">Edit</a>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<main>
|
<main>
|
||||||
<?php if ($auth->isAuthenticated()): ?>
|
<?php if ($auth->isAuthenticated()): ?>
|
||||||
<a class="bracketed" href="<?= $urlGenerator->fullUrl('collection/add', 'anime') ?>">Add Item</a>
|
<a class="bracketed" href="<?= $url->generate('collection.add.get') ?>">Add Item</a>
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
<?php if (empty($sections)): ?>
|
<?php if (empty($sections)): ?>
|
||||||
<h3>There's nothing here!</h3>
|
<h3>There's nothing here!</h3>
|
||||||
@ -26,12 +26,11 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<?php if($auth->isAuthenticated()): ?>
|
<?php if($auth->isAuthenticated()): ?>
|
||||||
<td>
|
<td>
|
||||||
<a class="bracketed" href="<?= $urlGenerator->fullUrl("collection/edit/{$item['hummingbird_id']}") ?>">Edit</a>
|
<a class="bracketed" href="<?= $url->generate('collection.edit.get', ['id' => $item['hummingbird_id']]) ?>">Edit</a>
|
||||||
<?php /*<a class="bracketed" href="<?= $urlGenerator->fullUrl("collection/delete/{$item['hummingbird_id']}") ?>">Delete</a>*/ ?>
|
|
||||||
</td>
|
</td>
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
<td class="align_left">
|
<td class="align_left">
|
||||||
<a href="https://hummingbird.me/anime/<?= $item['slug'] ?>">
|
<a href="<?= $url->generate('anime.details', ['id' => $item['slug']]) ?>">
|
||||||
<?= $item['title'] ?>
|
<?= $item['title'] ?>
|
||||||
</a>
|
</a>
|
||||||
<?= ( ! empty($item['alternate_title'])) ? " <br /><small> " . $item['alternate_title'] . "</small>" : "" ?>
|
<?= ( ! empty($item['alternate_title'])) ? " <br /><small> " . $item['alternate_title'] . "</small>" : "" ?>
|
||||||
|
@ -6,11 +6,11 @@
|
|||||||
<?= $config->get('whose_list') ?>'s <?= ucfirst($url_type) ?> List
|
<?= $config->get('whose_list') ?>'s <?= ucfirst($url_type) ?> List
|
||||||
</a>
|
</a>
|
||||||
<?php if($config->get("show_{$url_type}_collection")): ?>
|
<?php if($config->get("show_{$url_type}_collection")): ?>
|
||||||
[<a href="<?= $urlGenerator->url('collection/view') ?>"><?= ucfirst($url_type) ?> Collection</a>]
|
[<a href="<?= $url->generate('collection.view') ?>"><?= ucfirst($url_type) ?> Collection</a>]
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
[<a href="<?= $urlGenerator->defaultUrl($other_type) ?>"><?= ucfirst($other_type) ?> List</a>]
|
[<a href="<?= $urlGenerator->defaultUrl($other_type) ?>"><?= ucfirst($other_type) ?> List</a>]
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<a href="<?= $urlGenerator->url('collection/view') ?>">
|
<a href="<?= $url->generate('collection.view') ?>">
|
||||||
<?= $config->get('whose_list') ?>'s <?= ucfirst($url_type) ?> Collection
|
<?= $config->get('whose_list') ?>'s <?= ucfirst($url_type) ?> Collection
|
||||||
</a>
|
</a>
|
||||||
[<a href="<?= $urlGenerator->defaultUrl('anime') ?>">Anime List</a>]
|
[<a href="<?= $urlGenerator->defaultUrl('anime') ?>">Anime List</a>]
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<main>
|
<main>
|
||||||
<?php if ($auth->isAuthenticated()): ?>
|
<?php if ($auth->isAuthenticated()): ?>
|
||||||
<a class="bracketed" href="<?= $urlGenerator->url('manga/add') ?>">Add Item</a>
|
<a class="bracketed" href="<?= $url->generate('manga.add.get') ?>">Add Item</a>
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
<?php if (empty($sections)): ?>
|
<?php if (empty($sections)): ?>
|
||||||
<h3>There's nothing here!</h3>
|
<h3>There's nothing here!</h3>
|
||||||
@ -10,10 +10,11 @@
|
|||||||
<h2><?= $escape->html($name) ?></h2>
|
<h2><?= $escape->html($name) ?></h2>
|
||||||
<section class="media-wrap">
|
<section class="media-wrap">
|
||||||
<?php foreach($items as $item): ?>
|
<?php foreach($items as $item): ?>
|
||||||
<article class="media" id="manga-<?= $item['id'] ?>">
|
<article class="media" data-kitsu-id="<?= $item['id'] ?>" data-mal-id="<?= $item['mal_id'] ?>">
|
||||||
<?php if ($auth->isAuthenticated()): ?>
|
<?php if ($auth->isAuthenticated()): ?>
|
||||||
<div class="edit_buttons" hidden>
|
<div class="edit_buttons" hidden>
|
||||||
<button class="plus_one_chapter">+1 Chapter</button>
|
<button class="plus_one_chapter">+1 Chapter</button>
|
||||||
|
<?php /* <button class="plus_one_volume">+1 Volume</button> */ ?>
|
||||||
</div>
|
</div>
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
<img src="<?= $escape->attr($item['manga']['image']) ?>" />
|
<img src="<?= $escape->attr($item['manga']['image']) ?>" />
|
||||||
@ -29,13 +30,38 @@
|
|||||||
<?php if ($auth->isAuthenticated()): ?>
|
<?php if ($auth->isAuthenticated()): ?>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<span class="edit">
|
<span class="edit">
|
||||||
<a class="bracketed" title="Edit information about this manga" href="<?= $urlGenerator->url("manga/edit/{$item['id']}/{$name}") ?>">Edit</a>
|
<a class="bracketed"
|
||||||
|
title="Edit information about this manga"
|
||||||
|
href="<?= $url->generate('edit', [
|
||||||
|
'controller' => 'manga',
|
||||||
|
'id' => $item['id'],
|
||||||
|
'status' => $name
|
||||||
|
]) ?>">
|
||||||
|
Edit
|
||||||
|
</a>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="user_rating">Rating: <?= $item['user_rating'] ?> / 10</div>
|
<div class="user_rating">Rating: <?= $item['user_rating'] ?> / 10</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<?php if ($item['rereading']): ?>
|
||||||
|
<div class="row">
|
||||||
|
<?php foreach(['rereading'] as $attr): ?>
|
||||||
|
<?php if($item[$attr]): ?>
|
||||||
|
<span class="item-<?= $attr ?>"><?= ucfirst($attr) ?></span>
|
||||||
|
<?php endif ?>
|
||||||
|
<?php endforeach ?>
|
||||||
|
</div>
|
||||||
|
<?php endif ?>
|
||||||
|
|
||||||
|
<?php if ($item['reread'] > 0): ?>
|
||||||
|
<div class="row">
|
||||||
|
<div>Reread <?= $item['reread'] ?> time(s)</div>
|
||||||
|
</div>
|
||||||
|
<?php endif ?>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="chapter_completion">
|
<div class="chapter_completion">
|
||||||
Chapters: <span class="chapters_read"><?= $item['chapters']['read'] ?></span> /
|
Chapters: <span class="chapters_read"><?= $item['chapters']['read'] ?></span> /
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<main class="details">
|
<main class="details fixed">
|
||||||
<section class="flex flex-no-wrap">
|
<section class="flex flex-no-wrap">
|
||||||
<div>
|
<div>
|
||||||
<img class="cover" src="<?= $data['cover_image'] ?>" alt="<?= $data['title'] ?> cover image" />
|
<img class="cover" src="<?= $data['cover_image'] ?>" alt="<?= $data['title'] ?> cover image" />
|
||||||
@ -35,26 +35,25 @@
|
|||||||
<p><?= nl2br($data['synopsis']) ?></p>
|
<p><?= nl2br($data['synopsis']) ?></p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<section>
|
|
||||||
<?php if (count($characters) > 0): ?>
|
<?php if (count($characters) > 0): ?>
|
||||||
<h2>Characters</h2>
|
<h2>Characters</h2>
|
||||||
<div class="flex flex-wrap">
|
<section class="media-wrap">
|
||||||
<?php foreach($characters as $char): ?>
|
<?php foreach($characters as $char): ?>
|
||||||
<?php if ( ! empty($char['image']['original'])): ?>
|
<?php if ( ! empty($char['image']['original'])): ?>
|
||||||
<div class="character">
|
<article class="character">
|
||||||
<?php $link = $url->generate('character', ['slug' => $char['slug']]) ?>
|
<?php $link = $url->generate('character', ['slug' => $char['slug']]) ?>
|
||||||
|
<div class="name">
|
||||||
<?= $helper->a($link, $char['name']); ?>
|
<?= $helper->a($link, $char['name']); ?>
|
||||||
<br />
|
</div>
|
||||||
<a href="<?= $link ?>">
|
<a href="<?= $link ?>">
|
||||||
<?= $helper->img($char['image']['original'], [
|
<?= $helper->img($char['image']['original'], [
|
||||||
'width' => '225'
|
'width' => '225'
|
||||||
]) ?>
|
]) ?>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</article>
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
<?php endforeach ?>
|
<?php endforeach ?>
|
||||||
</div>
|
|
||||||
<?php endif ?>
|
|
||||||
</section>
|
</section>
|
||||||
|
<?php endif ?>
|
||||||
</main>
|
</main>
|
@ -74,6 +74,7 @@
|
|||||||
<td> </td>
|
<td> </td>
|
||||||
<td>
|
<td>
|
||||||
<input type="hidden" value="<?= $item['id'] ?>" name="id" />
|
<input type="hidden" value="<?= $item['id'] ?>" name="id" />
|
||||||
|
<input type="hidden" value="<?= $item['mal_id'] ?>" name="mal_id" />
|
||||||
<input type="hidden" value="<?= $item['manga']['slug'] ?>" name="manga_id" />
|
<input type="hidden" value="<?= $item['manga']['slug'] ?>" name="manga_id" />
|
||||||
<input type="hidden" value="<?= $item['user_rating'] ?>" name="old_rating" />
|
<input type="hidden" value="<?= $item['user_rating'] ?>" name="old_rating" />
|
||||||
<input type="hidden" value="true" name="edit" />
|
<input type="hidden" value="true" name="edit" />
|
||||||
@ -92,6 +93,7 @@
|
|||||||
<td> </td>
|
<td> </td>
|
||||||
<td>
|
<td>
|
||||||
<input type="hidden" value="<?= $item['id'] ?>" name="id" />
|
<input type="hidden" value="<?= $item['id'] ?>" name="id" />
|
||||||
|
<input type="hidden" value="<?= $item['mal_id'] ?>" name="mal_id" />
|
||||||
<button type="submit" class="danger">Delete Entry</button>
|
<button type="submit" class="danger">Delete Entry</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<main>
|
<main>
|
||||||
<?php if ($auth->isAuthenticated()): ?>
|
<?php if ($auth->isAuthenticated()): ?>
|
||||||
<a class="bracketed" href="<?= $urlGenerator->url('manga/add') ?>">Add Item</a>
|
<a class="bracketed" href="<?= $url->generate('manga.add.get') ?>">Add Item</a>
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
<?php if (empty($sections)): ?>
|
<?php if (empty($sections)): ?>
|
||||||
<h3>There's nothing here!</h3>
|
<h3>There's nothing here!</h3>
|
||||||
@ -17,7 +17,9 @@
|
|||||||
<th>Rating</th>
|
<th>Rating</th>
|
||||||
<th>Completed Chapters</th>
|
<th>Completed Chapters</th>
|
||||||
<th># of Volumes</th>
|
<th># of Volumes</th>
|
||||||
|
<th>Attributes</th>
|
||||||
<th>Type</th>
|
<th>Type</th>
|
||||||
|
<th>Genres</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@ -25,7 +27,11 @@
|
|||||||
<tr id="manga-<?= $item['id'] ?>">
|
<tr id="manga-<?= $item['id'] ?>">
|
||||||
<?php if($auth->isAuthenticated()): ?>
|
<?php if($auth->isAuthenticated()): ?>
|
||||||
<td>
|
<td>
|
||||||
<a class="bracketed" href="<?= $urlGenerator->url("manga/edit/{$item['id']}/{$name}") ?>">Edit</a>
|
<a class="bracketed" href="<?= $url->generate('edit', [
|
||||||
|
'controller' => 'manga',
|
||||||
|
'id' => $item['id'],
|
||||||
|
'status' => $name
|
||||||
|
]) ?>">Edit</a>
|
||||||
</td>
|
</td>
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
<td class="align_left">
|
<td class="align_left">
|
||||||
@ -39,7 +45,22 @@
|
|||||||
<td><?= $item['user_rating'] ?> / 10</td>
|
<td><?= $item['user_rating'] ?> / 10</td>
|
||||||
<td><?= $item['chapters']['read'] ?> / <?= $item['chapters']['total'] ?></td>
|
<td><?= $item['chapters']['read'] ?> / <?= $item['chapters']['total'] ?></td>
|
||||||
<td><?= $item['volumes']['total'] ?></td>
|
<td><?= $item['volumes']['total'] ?></td>
|
||||||
|
<td>
|
||||||
|
<ul>
|
||||||
|
<?php if ($item['reread'] > 0): ?>
|
||||||
|
<li>Reread <?= $item['reread'] ?> time(s)</li>
|
||||||
|
<?php endif ?>
|
||||||
|
<?php foreach(['rereading'] as $attr): ?>
|
||||||
|
<?php if($item[$attr]): ?>
|
||||||
|
<li><?= ucfirst($attr); ?></li>
|
||||||
|
<?php endif ?>
|
||||||
|
<?php endforeach ?>
|
||||||
|
</ul>
|
||||||
|
</td>
|
||||||
<td><?= $item['manga']['type'] ?></td>
|
<td><?= $item['manga']['type'] ?></td>
|
||||||
|
<td class="align_left">
|
||||||
|
<?= implode(', ', $item['manga']['genres']) ?>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<?php endforeach ?>
|
<?php endforeach ?>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
@ -1,11 +1,22 @@
|
|||||||
<main class="details">
|
<?php use Aviat\AnimeClient\API\Kitsu; ?>
|
||||||
|
<main class="user-page details">
|
||||||
<section class="flex flex-no-wrap">
|
<section class="flex flex-no-wrap">
|
||||||
<div>
|
<div>
|
||||||
<h2><?= $attributes['name'] ?></h2>
|
<center>
|
||||||
|
<h2>
|
||||||
|
<a title='View profile on Kisu'
|
||||||
|
href="https://kitsu.io/users/<?= $attributes['name'] ?>">
|
||||||
|
<?= $attributes['name'] ?>
|
||||||
|
</a>
|
||||||
|
</h2>
|
||||||
<img src="<?= $attributes['avatar']['original'] ?>" alt="" />
|
<img src="<?= $attributes['avatar']['original'] ?>" alt="" />
|
||||||
|
</center>
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
<table class="media_details">
|
<table class="media_details">
|
||||||
|
<tr>
|
||||||
|
<th colspan="2">General</th>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Location</td>
|
<td>Location</td>
|
||||||
<td><?= $attributes['location'] ?></td>
|
<td><?= $attributes['location'] ?></td>
|
||||||
@ -28,6 +39,21 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
|
<tr>
|
||||||
|
<th colspan="2">User Stats</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td># of Posts</td>
|
||||||
|
<td><?= $attributes['postsCount'] ?></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td># of Comments</td>
|
||||||
|
<td><?= $attributes['commentsCount'] ?></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td># of Media Rated</td>
|
||||||
|
<td><?= $attributes['ratingsCount'] ?></td>
|
||||||
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@ -35,9 +61,72 @@
|
|||||||
<dt>About:</dt>
|
<dt>About:</dt>
|
||||||
<dd><?= $escape->html($attributes['bio']) ?></dd>
|
<dd><?= $escape->html($attributes['bio']) ?></dd>
|
||||||
</dl>
|
</dl>
|
||||||
<?php /* <pre><?= json_encode($attributes, \JSON_PRETTY_PRINT) ?></pre>
|
<?php if ( ! empty($favorites)): ?>
|
||||||
<pre><?= json_encode($relationships, \JSON_PRETTY_PRINT) ?></pre>
|
<?php if ( ! empty($favorites['characters'])): ?>
|
||||||
<pre><?= json_encode($included, \JSON_PRETTY_PRINT) ?></pre> */ ?>
|
<h4>Favorite Characters</h4>
|
||||||
|
<section class="media-wrap">
|
||||||
|
<?php foreach($favorites['characters'] as $char): ?>
|
||||||
|
<?php if ( ! empty($char['image']['original'])): ?>
|
||||||
|
<article class="small_character">
|
||||||
|
<?php $link = $url->generate('character', ['slug' => $char['slug']]) ?>
|
||||||
|
<div class="name"><?= $helper->a($link, $char['name']); ?></div>
|
||||||
|
<a href="<?= $link ?>">
|
||||||
|
<?= $helper->img($char['image']['original']) ?>
|
||||||
|
</a>
|
||||||
|
</article>
|
||||||
|
<?php endif ?>
|
||||||
|
<?php endforeach ?>
|
||||||
|
</section>
|
||||||
|
<?php endif ?>
|
||||||
|
<?php if ( ! empty($favorites['anime'])): ?>
|
||||||
|
<h4>Favorite Anime</h4>
|
||||||
|
<section class="media-wrap">
|
||||||
|
<?php foreach($favorites['anime'] as $anime): ?>
|
||||||
|
<article class="media">
|
||||||
|
<?php
|
||||||
|
$link = $url->generate('anime.details', ['id' => $anime['slug']]);
|
||||||
|
$titles = Kitsu::filterTitles($anime);
|
||||||
|
?>
|
||||||
|
<a href="<?= $link ?>">
|
||||||
|
<img src="<?= $anime['posterImage']['small'] ?>" width="220" alt="" />
|
||||||
|
</a>
|
||||||
|
<div class="name">
|
||||||
|
<a href="<?= $link ?>">
|
||||||
|
<?= array_shift($titles) ?>
|
||||||
|
<?php foreach ($titles as $title): ?>
|
||||||
|
<br /><small><?= $title ?></small>
|
||||||
|
<?php endforeach ?>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
<?php endforeach ?>
|
||||||
|
</section>
|
||||||
|
<?php endif ?>
|
||||||
|
<?php if ( ! empty($favorites['manga'])): ?>
|
||||||
|
<h4>Favorite Manga</h4>
|
||||||
|
<section class="media-wrap">
|
||||||
|
<?php foreach($favorites['manga'] as $manga): ?>
|
||||||
|
<article class="media">
|
||||||
|
<?php
|
||||||
|
$link = $url->generate('manga.details', ['id' => $manga['slug']]);
|
||||||
|
$titles = Kitsu::filterTitles($manga);
|
||||||
|
?>
|
||||||
|
<a href="<?= $link ?>">
|
||||||
|
<img src="<?= $manga['posterImage']['small'] ?>" width="220" alt="" />
|
||||||
|
</a>
|
||||||
|
<div class="name">
|
||||||
|
<a href="<?= $link ?>">
|
||||||
|
<?= array_shift($titles) ?>
|
||||||
|
<?php foreach ($titles as $title): ?>
|
||||||
|
<br /><small><?= $title ?></small>
|
||||||
|
<?php endforeach ?>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
<?php endforeach ?>
|
||||||
|
</section>
|
||||||
|
<?php endif ?>
|
||||||
|
<?php endif ?>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
@ -7,7 +7,9 @@ $file_patterns = [
|
|||||||
'src/**/*.php',
|
'src/**/*.php',
|
||||||
'src/*.php',
|
'src/*.php',
|
||||||
'tests/**/*.php',
|
'tests/**/*.php',
|
||||||
'tests/*.php'
|
'tests/*.php',
|
||||||
|
'index.php',
|
||||||
|
'Robofile.php'
|
||||||
];
|
];
|
||||||
|
|
||||||
if ( ! function_exists('glob_recursive'))
|
if ( ! function_exists('glob_recursive'))
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
"aura/router": "^3.0",
|
"aura/router": "^3.0",
|
||||||
"aura/session": "^2.0",
|
"aura/session": "^2.0",
|
||||||
"aviat/banker": "^1.0.0",
|
"aviat/banker": "^1.0.0",
|
||||||
"aviat/ion": "dev-master",
|
"aviat/ion": "^2.0.0",
|
||||||
"monolog/monolog": "^1.0",
|
"monolog/monolog": "^1.0",
|
||||||
"psr/http-message": "~1.0",
|
"psr/http-message": "~1.0",
|
||||||
"psr/log": "~1.0",
|
"psr/log": "~1.0",
|
||||||
|
18
index.php
18
index.php
@ -1,23 +1,23 @@
|
|||||||
<?php
|
<?php declare(strict_types=1);
|
||||||
/**
|
/**
|
||||||
* Hummingbird Anime Client
|
* Hummingbird Anime List Client
|
||||||
*
|
*
|
||||||
* An API client for Hummingbird to manage anime and manga watch lists
|
* An API client for Kitsu and MyAnimeList to manage anime and manga watch lists
|
||||||
*
|
*
|
||||||
* PHP version 5.6
|
* PHP version 7
|
||||||
*
|
*
|
||||||
* @package HummingbirdAnimeClient
|
* @package HummingbirdAnimeClient
|
||||||
* @author Timothy J. Warren <tim@timshomepage.net>
|
* @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
|
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
||||||
* @version 3.1
|
* @version 4.0
|
||||||
* @link https://github.com/timw4mail/HummingBirdAnimeClient
|
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace Aviat\AnimeClient;
|
namespace Aviat\AnimeClient;
|
||||||
|
|
||||||
use function Aviat\AnimeClient\loadToml;
|
use function Aviat\AnimeClient\loadToml;
|
||||||
|
use function Aviat\Ion\_dir;
|
||||||
use Aviat\AnimeClient\AnimeClient;
|
|
||||||
|
|
||||||
// Work around the silly timezone error
|
// Work around the silly timezone error
|
||||||
$timezone = ini_get('date.timezone');
|
$timezone = ini_get('date.timezone');
|
||||||
|
@ -14,7 +14,6 @@
|
|||||||
* @link https://github.com/timw4mail/HummingBirdAnimeClient
|
* @link https://github.com/timw4mail/HummingBirdAnimeClient
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
namespace Aviat\EasyMin;
|
namespace Aviat\EasyMin;
|
||||||
|
|
||||||
require_once('./min.php');
|
require_once('./min.php');
|
||||||
|
@ -1025,7 +1025,7 @@ a:hover, a:active {
|
|||||||
Base list styles
|
Base list styles
|
||||||
------------------------------------------------------------------------------*/
|
------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
.media {
|
.media, .character, .small_character {
|
||||||
position:relative;
|
position:relative;
|
||||||
vertical-align:top;
|
vertical-align:top;
|
||||||
display:inline-block;
|
display:inline-block;
|
||||||
@ -1035,7 +1035,9 @@ a:hover, a:active {
|
|||||||
margin:0.25em 0.125em;
|
margin:0.25em 0.125em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.media > img {
|
.media > img,
|
||||||
|
.character > img,
|
||||||
|
.small_character > img {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1076,7 +1078,9 @@ a:hover, a:active {
|
|||||||
top: 0;
|
top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.media:hover > .name,
|
.small_character:hover > .name,
|
||||||
|
.character:hover > .name,
|
||||||
|
.media:hover > .name,
|
||||||
.media:hover > .media_metadata > div,
|
.media:hover > .media_metadata > div,
|
||||||
.media:hover > .medium_metadata > div,
|
.media:hover > .medium_metadata > div,
|
||||||
.media:hover > .table .row
|
.media:hover > .table .row
|
||||||
@ -1094,7 +1098,11 @@ a:hover, a:active {
|
|||||||
display:block;
|
display:block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.media > .name a,
|
.small_character > .name a,
|
||||||
|
.small_character > .name a small,
|
||||||
|
.character > .name a,
|
||||||
|
.character > .name a small,
|
||||||
|
.media > .name a,
|
||||||
.media > .name a small
|
.media > .name a small
|
||||||
{
|
{
|
||||||
background:none;
|
background:none;
|
||||||
@ -1250,17 +1258,20 @@ a:hover, a:active {
|
|||||||
.details {
|
.details {
|
||||||
margin:15px auto 0 auto;
|
margin:15px auto 0 auto;
|
||||||
margin: 1.5rem auto 0 auto;
|
margin: 1.5rem auto 0 auto;
|
||||||
max-width:930px;
|
|
||||||
max-width:93rem;
|
|
||||||
padding:10px;
|
padding:10px;
|
||||||
padding:1rem;
|
padding:1rem;
|
||||||
font-size:inherit;
|
font-size:inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.details.fixed {
|
||||||
|
max-width:930px;
|
||||||
|
max-width:93rem;
|
||||||
|
}
|
||||||
|
|
||||||
.details .cover {
|
.details .cover {
|
||||||
display: block;
|
display: block;
|
||||||
width: 284px;
|
width: 284px;
|
||||||
height: 402px;
|
/* height: 402px; */
|
||||||
}
|
}
|
||||||
|
|
||||||
.details h2 {
|
.details h2 {
|
||||||
@ -1295,6 +1306,58 @@ a:hover, a:active {
|
|||||||
text-align:left;
|
text-align:left;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.character,
|
||||||
|
.small_character {
|
||||||
|
background: rgba(0, 0, 0, .5);
|
||||||
|
width: 225px;
|
||||||
|
height: 350px;
|
||||||
|
vertical-align: middle;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.small_character a {
|
||||||
|
display:inline-block;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.small_character .name,
|
||||||
|
.character .name {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.small_character img,
|
||||||
|
.character img {
|
||||||
|
position: relative;
|
||||||
|
top: 50%;
|
||||||
|
-webkit-transform: translateY(-50%);
|
||||||
|
transform: translateY(-50%);
|
||||||
|
z-index: 5;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
User page styles
|
||||||
|
-----------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
.small_character {
|
||||||
|
width: 160px;
|
||||||
|
height: 250px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-page .media-wrap {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.media a {
|
||||||
|
display: inline-block;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------------
|
/* ----------------------------------------------------------------------------
|
||||||
Viewport-based styles
|
Viewport-based styles
|
||||||
-----------------------------------------------------------------------------*/
|
-----------------------------------------------------------------------------*/
|
||||||
|
@ -297,7 +297,7 @@ a:hover, a:active {
|
|||||||
Base list styles
|
Base list styles
|
||||||
------------------------------------------------------------------------------*/
|
------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
.media {
|
.media, .character, .small_character {
|
||||||
position:relative;
|
position:relative;
|
||||||
vertical-align:top;
|
vertical-align:top;
|
||||||
display:inline-block;
|
display:inline-block;
|
||||||
@ -307,7 +307,9 @@ a:hover, a:active {
|
|||||||
margin: var(--normal-padding);
|
margin: var(--normal-padding);
|
||||||
}
|
}
|
||||||
|
|
||||||
.media > img {
|
.media > img,
|
||||||
|
.character > img,
|
||||||
|
.small_character > img {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -347,7 +349,8 @@ a:hover, a:active {
|
|||||||
position:absolute;
|
position:absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
}
|
}
|
||||||
|
.small_character:hover > .name,
|
||||||
|
.character:hover > .name,
|
||||||
.media:hover > .name,
|
.media:hover > .name,
|
||||||
.media:hover > .media_metadata > div,
|
.media:hover > .media_metadata > div,
|
||||||
.media:hover > .medium_metadata > div,
|
.media:hover > .medium_metadata > div,
|
||||||
@ -364,6 +367,10 @@ a:hover, a:active {
|
|||||||
display:block;
|
display:block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.small_character > .name a,
|
||||||
|
.small_character > .name a small,
|
||||||
|
.character > .name a,
|
||||||
|
.character > .name a small,
|
||||||
.media > .name a,
|
.media > .name a,
|
||||||
.media > .name a small
|
.media > .name a small
|
||||||
{
|
{
|
||||||
@ -510,15 +517,18 @@ a:hover, a:active {
|
|||||||
-----------------------------------------------------------------------------*/
|
-----------------------------------------------------------------------------*/
|
||||||
.details {
|
.details {
|
||||||
margin: 1.5rem auto 0 auto;
|
margin: 1.5rem auto 0 auto;
|
||||||
max-width:93rem;
|
|
||||||
padding:1rem;
|
padding:1rem;
|
||||||
font-size:inherit;
|
font-size:inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.details.fixed {
|
||||||
|
max-width:93rem;
|
||||||
|
}
|
||||||
|
|
||||||
.details .cover {
|
.details .cover {
|
||||||
display: block;
|
display: block;
|
||||||
width: 284px;
|
width: 284px;
|
||||||
height: 402px;
|
/* height: 402px; */
|
||||||
}
|
}
|
||||||
|
|
||||||
.details h2 {
|
.details h2 {
|
||||||
@ -549,6 +559,55 @@ a:hover, a:active {
|
|||||||
text-align:left;
|
text-align:left;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.character,
|
||||||
|
.small_character {
|
||||||
|
background: rgba(0,0,0,0.5);
|
||||||
|
width: 225px;
|
||||||
|
height: 350px;
|
||||||
|
vertical-align: middle;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.small_character a {
|
||||||
|
display:inline-block;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.small_character .name,
|
||||||
|
.character .name {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.small_character img,
|
||||||
|
.character img {
|
||||||
|
position: relative;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
z-index: 5;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
User page styles
|
||||||
|
-----------------------------------------------------------------------------*/
|
||||||
|
.small_character {
|
||||||
|
width: 160px;
|
||||||
|
height: 250px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-page .media-wrap {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.media a {
|
||||||
|
display: inline-block;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------------
|
/* ----------------------------------------------------------------------------
|
||||||
Viewport-based styles
|
Viewport-based styles
|
||||||
-----------------------------------------------------------------------------*/
|
-----------------------------------------------------------------------------*/
|
||||||
|
@ -8,6 +8,11 @@
|
|||||||
searchResults = JSON.parse(searchResults);
|
searchResults = JSON.parse(searchResults);
|
||||||
_.$('.cssload-loader')[0].setAttribute('hidden', 'hidden');
|
_.$('.cssload-loader')[0].setAttribute('hidden', 'hidden');
|
||||||
|
|
||||||
|
// Give mustache a key to iterate over
|
||||||
|
searchResults = {
|
||||||
|
data: searchResults.data
|
||||||
|
};
|
||||||
|
|
||||||
Mustache.parse(tempHtml);
|
Mustache.parse(tempHtml);
|
||||||
_.$('#series_list')[0].innerHTML = Mustache.render(tempHtml, searchResults);
|
_.$('#series_list')[0].innerHTML = Mustache.render(tempHtml, searchResults);
|
||||||
});
|
});
|
||||||
|
@ -8,7 +8,6 @@
|
|||||||
_.on('.manga.list', 'click', '.edit_buttons button', (e) => {
|
_.on('.manga.list', 'click', '.edit_buttons button', (e) => {
|
||||||
let thisSel = e.target;
|
let thisSel = e.target;
|
||||||
let parentSel = _.closestParent(e.target, 'article');
|
let parentSel = _.closestParent(e.target, 'article');
|
||||||
let mangaId = parentSel.id.replace('manga-', '');
|
|
||||||
let type = thisSel.classList.contains('plus_one_chapter') ? 'chapter' : 'volume';
|
let type = thisSel.classList.contains('plus_one_chapter') ? 'chapter' : 'volume';
|
||||||
let completed = parseInt(_.$(`.${type}s_read`, parentSel)[0].textContent, 10);
|
let completed = parseInt(_.$(`.${type}s_read`, parentSel)[0].textContent, 10);
|
||||||
let total = parseInt(_.$(`.${type}_count`, parentSel)[0].textContent, 10);
|
let total = parseInt(_.$(`.${type}_count`, parentSel)[0].textContent, 10);
|
||||||
@ -20,7 +19,8 @@
|
|||||||
|
|
||||||
// Setup the update data
|
// Setup the update data
|
||||||
let data = {
|
let data = {
|
||||||
id: mangaId,
|
id: parentSel.dataset.kitsuId,
|
||||||
|
mal_id: parentSel.dataset.malId,
|
||||||
data: {
|
data: {
|
||||||
progress: completed
|
progress: completed
|
||||||
}
|
}
|
||||||
|
@ -40,6 +40,201 @@ class JsonAPI {
|
|||||||
*/
|
*/
|
||||||
protected $data = [];
|
protected $data = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inline all included data
|
||||||
|
*
|
||||||
|
* @param array $data - The raw JsonAPI response data
|
||||||
|
* @return data
|
||||||
|
*/
|
||||||
|
public static function organizeData(array $data): array
|
||||||
|
{
|
||||||
|
// relationships that have singular data
|
||||||
|
$singular = [
|
||||||
|
'waifu'
|
||||||
|
];
|
||||||
|
|
||||||
|
// Reorganize included data
|
||||||
|
$included = static::organizeIncluded($data['included']);
|
||||||
|
|
||||||
|
// Inline organized data
|
||||||
|
foreach($data['data'] as $i => $item)
|
||||||
|
{
|
||||||
|
if (array_key_exists('relationships', $item))
|
||||||
|
{
|
||||||
|
foreach($item['relationships'] as $relType => $props)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (array_keys($props) === ['links'])
|
||||||
|
{
|
||||||
|
unset($data['data'][$i]['relationships'][$relType]);
|
||||||
|
|
||||||
|
if (empty($data['data'][$i]['relationships']))
|
||||||
|
{
|
||||||
|
unset($data['data'][$i]['relationships']);
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (array_key_exists('links', $props))
|
||||||
|
{
|
||||||
|
unset($data['data'][$i]['relationships'][$relType]['links']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (array_key_exists('data', $props))
|
||||||
|
{
|
||||||
|
if (empty($props['data']))
|
||||||
|
{
|
||||||
|
unset($data['data'][$i]['relationships'][$relType]['data']);
|
||||||
|
|
||||||
|
if (empty($data['data'][$i]['relationships'][$relType]))
|
||||||
|
{
|
||||||
|
unset($data['data'][$i]['relationships'][$relType]);
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Single data item
|
||||||
|
else if (array_key_exists('id', $props['data']))
|
||||||
|
{
|
||||||
|
$idKey = $props['data']['id'];
|
||||||
|
$typeKey = $props['data']['type'];
|
||||||
|
$relationship =& $data['data'][$i]['relationships'][$relType];
|
||||||
|
unset($relationship['data']);
|
||||||
|
|
||||||
|
if (in_array($relType, $singular))
|
||||||
|
{
|
||||||
|
$relationship = $included[$typeKey][$idKey];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($relType === $typeKey)
|
||||||
|
{
|
||||||
|
$relationship[$idKey] = $included[$typeKey][$idKey];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$relationship[$typeKey][$idKey] = $included[$typeKey][$idKey];
|
||||||
|
}
|
||||||
|
// Multiple data items
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach($props['data'] as $j => $datum)
|
||||||
|
{
|
||||||
|
$idKey = $props['data'][$j]['id'];
|
||||||
|
$typeKey = $props['data'][$j]['type'];
|
||||||
|
$relationship =& $data['data'][$i]['relationships'][$relType];
|
||||||
|
|
||||||
|
unset($relationship['data'][$j]);
|
||||||
|
|
||||||
|
if (empty($relationship['data']))
|
||||||
|
{
|
||||||
|
unset($relationship['data']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($relType === $typeKey)
|
||||||
|
{
|
||||||
|
$relationship[$idKey] = $included[$typeKey][$idKey];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$relationship[$typeKey][$idKey] = array_merge(
|
||||||
|
$included[$typeKey][$idKey],
|
||||||
|
$relationship[$typeKey][$idKey] ?? []
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $data['data'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restructure included data to make it simpler to inline
|
||||||
|
*
|
||||||
|
* @param array $included
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public static function organizeIncluded(array $included): array
|
||||||
|
{
|
||||||
|
$organized = [];
|
||||||
|
|
||||||
|
// First pass, create [ type => items[] ] structure
|
||||||
|
foreach($included as &$item)
|
||||||
|
{
|
||||||
|
$type = $item['type'];
|
||||||
|
$id = $item['id'];
|
||||||
|
$organized[$type] = $organized[$type] ?? [];
|
||||||
|
$newItem = [];
|
||||||
|
|
||||||
|
foreach(['attributes', 'relationships'] as $key)
|
||||||
|
{
|
||||||
|
if (array_key_exists($key, $item))
|
||||||
|
{
|
||||||
|
// Remove 'links' type relationships
|
||||||
|
if ($key === 'relationships')
|
||||||
|
{
|
||||||
|
foreach($item['relationships'] as $relType => $props)
|
||||||
|
{
|
||||||
|
if (array_keys($props) === ['links'])
|
||||||
|
{
|
||||||
|
unset($item['relationships'][$relType]);
|
||||||
|
if (empty($item['relationships']))
|
||||||
|
{
|
||||||
|
continue 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$newItem[$key] = $item[$key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$organized[$type][$id] = $newItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Second pass, go through and fill missing relationships in the first pass
|
||||||
|
foreach($organized as $type => $items)
|
||||||
|
{
|
||||||
|
foreach($items as $id => $item)
|
||||||
|
{
|
||||||
|
if (array_key_exists('relationships', $item))
|
||||||
|
{
|
||||||
|
foreach($item['relationships'] as $relType => $props)
|
||||||
|
{
|
||||||
|
if (array_key_exists('data', $props))
|
||||||
|
{
|
||||||
|
if (array_key_exists($props['data']['id'], $organized[$props['data']['type']]))
|
||||||
|
{
|
||||||
|
$idKey = $props['data']['id'];
|
||||||
|
$typeKey = $props['data']['type'];
|
||||||
|
|
||||||
|
|
||||||
|
$relationship =& $organized[$type][$id]['relationships'][$relType];
|
||||||
|
unset($relationship['links']);
|
||||||
|
unset($relationship['data']);
|
||||||
|
|
||||||
|
if ($relType === $typeKey)
|
||||||
|
{
|
||||||
|
$relationship[$idKey] = $included[$typeKey][$idKey];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$relationship[$typeKey][$idKey] = $organized[$typeKey][$idKey];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $organized;
|
||||||
|
}
|
||||||
|
|
||||||
public static function inlineRawIncludes(array &$data, string $key): array
|
public static function inlineRawIncludes(array &$data, string $key): array
|
||||||
{
|
{
|
||||||
foreach($data['data'] as $i => &$item)
|
foreach($data['data'] as $i => &$item)
|
||||||
@ -118,27 +313,7 @@ class JsonAPI {
|
|||||||
*/
|
*/
|
||||||
public static function lightlyOrganizeIncludes(array $includes): array
|
public static function lightlyOrganizeIncludes(array $includes): array
|
||||||
{
|
{
|
||||||
$organized = [];
|
return static::organizeIncluded($includes);
|
||||||
|
|
||||||
foreach($includes as $item)
|
|
||||||
{
|
|
||||||
$type = $item['type'];
|
|
||||||
$id = $item['id'];
|
|
||||||
$organized[$type] = $organized[$type] ?? [];
|
|
||||||
$newItem = [];
|
|
||||||
|
|
||||||
foreach(['attributes', 'relationships'] as $key)
|
|
||||||
{
|
|
||||||
if (array_key_exists($key, $item))
|
|
||||||
{
|
|
||||||
$newItem[$key] = $item[$key];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$organized[$type][$id] = $newItem;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $organized;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -33,6 +33,8 @@ class ListItem extends AbstractListItem {
|
|||||||
|
|
||||||
private function getAuthHeader()
|
private function getAuthHeader()
|
||||||
{
|
{
|
||||||
|
$cache = $this->getContainer()->get('cache');
|
||||||
|
$cacheItem = $cache->getItem('kitsu-auth-token');
|
||||||
$sessionSegment = $this->getContainer()
|
$sessionSegment = $this->getContainer()
|
||||||
->get('session')
|
->get('session')
|
||||||
->getSegment(SESSION_SEGMENT);
|
->getSegment(SESSION_SEGMENT);
|
||||||
@ -43,6 +45,12 @@ class ListItem extends AbstractListItem {
|
|||||||
return "bearer {$token}";
|
return "bearer {$token}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($cacheItem->isHit())
|
||||||
|
{
|
||||||
|
$token = $cacheItem->get();
|
||||||
|
return "bearer {$token}";
|
||||||
|
}
|
||||||
|
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,7 +19,12 @@ namespace Aviat\AnimeClient\API\Kitsu;
|
|||||||
use function Amp\{all, wait};
|
use function Amp\{all, wait};
|
||||||
|
|
||||||
use Amp\Artax\{Client, Request};
|
use Amp\Artax\{Client, Request};
|
||||||
use Aviat\AnimeClient\API\{CacheTrait, JsonAPI, Kitsu as K};
|
use Aviat\AnimeClient\API\{
|
||||||
|
CacheTrait,
|
||||||
|
JsonAPI,
|
||||||
|
Kitsu as K,
|
||||||
|
ParallelAPIRequest
|
||||||
|
};
|
||||||
use Aviat\AnimeClient\API\Enum\{
|
use Aviat\AnimeClient\API\Enum\{
|
||||||
AnimeWatchingStatus\Title,
|
AnimeWatchingStatus\Title,
|
||||||
AnimeWatchingStatus\Kitsu as KitsuWatchingStatus,
|
AnimeWatchingStatus\Kitsu as KitsuWatchingStatus,
|
||||||
@ -73,7 +78,6 @@ class Model {
|
|||||||
*/
|
*/
|
||||||
protected $mangaListTransformer;
|
protected $mangaListTransformer;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
*
|
*
|
||||||
@ -88,6 +92,34 @@ class Model {
|
|||||||
$this->mangaListTransformer = new MangaListTransformer();
|
$this->mangaListTransformer = new MangaListTransformer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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->getResponse('POST', K::AUTH_URL, [
|
||||||
|
'headers' => [],
|
||||||
|
'form_params' => [
|
||||||
|
'grant_type' => 'password',
|
||||||
|
'username' => $username,
|
||||||
|
'password' => $password
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$data = Json::decode((string)$response->getBody());
|
||||||
|
|
||||||
|
if (array_key_exists('access_token', $data))
|
||||||
|
{
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the userid for a username from Kitsu
|
* Get the userid for a username from Kitsu
|
||||||
*
|
*
|
||||||
@ -132,7 +164,7 @@ class Model {
|
|||||||
$data = $this->getRequest('/characters', [
|
$data = $this->getRequest('/characters', [
|
||||||
'query' => [
|
'query' => [
|
||||||
'filter' => [
|
'filter' => [
|
||||||
'slug' => $slug
|
'name' => $slug
|
||||||
],
|
],
|
||||||
// 'include' => 'primaryMedia,castings'
|
// 'include' => 'primaryMedia,castings'
|
||||||
]
|
]
|
||||||
@ -149,45 +181,86 @@ class Model {
|
|||||||
*/
|
*/
|
||||||
public function getUserData(string $username): array
|
public function getUserData(string $username): array
|
||||||
{
|
{
|
||||||
$userId = $this->getUserIdByUsername($username);
|
// $userId = $this->getUserIdByUsername($username);
|
||||||
$data = $this->getRequest("/users/{$userId}", [
|
$data = $this->getRequest("/users", [
|
||||||
'query' => [
|
'query' => [
|
||||||
'include' => 'waifu,pinnedPost,blocks,linkedAccounts,profileLinks,profileLinks.profileLinkSite,mediaFollows,userRoles'
|
'filter' => [
|
||||||
|
'name' => $username,
|
||||||
|
],
|
||||||
|
'fields' => [
|
||||||
|
// 'anime' => 'slug,name,canonicalTitle',
|
||||||
|
'characters' => 'slug,name,image'
|
||||||
|
],
|
||||||
|
'include' => 'waifu,pinnedPost,blocks,linkedAccounts,profileLinks,profileLinks.profileLinkSite,mediaFollows,userRoles,favorites.item'
|
||||||
]
|
]
|
||||||
]);
|
]);
|
||||||
// $data['included'] = JsonAPI::organizeIncludes($data['included']);
|
|
||||||
|
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the access token from the Kitsu API
|
* Search for an anime or manga
|
||||||
*
|
*
|
||||||
* @param string $username
|
* @param string $type - 'anime' or 'manga'
|
||||||
* @param string $password
|
* @param string $query - name of the item to search for
|
||||||
* @return bool|string
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function authenticate(string $username, string $password)
|
public function search(string $type, string $query): array
|
||||||
{
|
{
|
||||||
$response = $this->getResponse('POST', K::AUTH_URL, [
|
$options = [
|
||||||
'headers' => [],
|
'query' => [
|
||||||
'form_params' => [
|
'filter' => [
|
||||||
'grant_type' => 'password',
|
'text' => $query
|
||||||
'username' => $username,
|
],
|
||||||
'password' => $password
|
'page' => [
|
||||||
|
'offset' => 0,
|
||||||
|
'limit' => 20
|
||||||
|
],
|
||||||
]
|
]
|
||||||
]);
|
];
|
||||||
|
|
||||||
$data = Json::decode((string)$response->getBody());
|
$raw = $this->getRequest($type, $options);
|
||||||
|
|
||||||
if (array_key_exists('access_token', $data))
|
foreach ($raw['data'] as &$item)
|
||||||
{
|
{
|
||||||
return $data;
|
$item['attributes']['titles'] = K::filterTitles($item['attributes']);
|
||||||
|
array_shift($item['attributes']['titles']);
|
||||||
}
|
}
|
||||||
|
|
||||||
return FALSE;
|
return $raw;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find a media item on Kitsu by its associated MAL id
|
||||||
|
*
|
||||||
|
* @param string $malId
|
||||||
|
* @param string $type "anime" or "manga"
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getKitsuIdFromMALId(string $malId, string $type="anime"): string
|
||||||
|
{
|
||||||
|
$options = [
|
||||||
|
'query' => [
|
||||||
|
'filter' => [
|
||||||
|
'external_site' => "myanimelist/{$type}",
|
||||||
|
'external_id' => $malId
|
||||||
|
],
|
||||||
|
'fields' => [
|
||||||
|
'media' => 'id,slug'
|
||||||
|
],
|
||||||
|
'include' => 'media'
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
$raw = $this->getRequest('mappings', $options);
|
||||||
|
|
||||||
|
return $raw['included'][0]['id'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// ! Anime-specific methods
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get information about a particular anime
|
* Get information about a particular anime
|
||||||
*
|
*
|
||||||
@ -220,202 +293,6 @@ class Model {
|
|||||||
return $this->animeTransformer->transform($baseData);
|
return $this->animeTransformer->transform($baseData);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the mal id for the anime represented by the kitsu id
|
|
||||||
* to enable updating MyAnimeList
|
|
||||||
*
|
|
||||||
* @param string $kitsuAnimeId The id of the anime on Kitsu
|
|
||||||
* @return string|null Returns the mal id if it exists, otherwise null
|
|
||||||
*/
|
|
||||||
public function getMalIdForAnime(string $kitsuAnimeId)
|
|
||||||
{
|
|
||||||
$options = [
|
|
||||||
'query' => [
|
|
||||||
'include' => 'mappings'
|
|
||||||
]
|
|
||||||
];
|
|
||||||
$data = $this->getRequest("anime/{$kitsuAnimeId}", $options);
|
|
||||||
$mappings = array_column($data['included'], 'attributes');
|
|
||||||
|
|
||||||
foreach($mappings as $map)
|
|
||||||
{
|
|
||||||
if ($map['externalSite'] === 'myanimelist/anime')
|
|
||||||
{
|
|
||||||
return $map['externalId'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get information about a particular manga
|
|
||||||
*
|
|
||||||
* @param string $mangaId
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function getManga(string $mangaId): array
|
|
||||||
{
|
|
||||||
$baseData = $this->getRawMediaData('manga', $mangaId);
|
|
||||||
|
|
||||||
if (empty($baseData))
|
|
||||||
{
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
$transformed = $this->mangaTransformer->transform($baseData);
|
|
||||||
$transformed['included'] = $baseData['included'];
|
|
||||||
return $transformed;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the number of anime list items
|
|
||||||
*
|
|
||||||
* @param string $status - Optional status to filter by
|
|
||||||
* @return int
|
|
||||||
*/
|
|
||||||
public function getAnimeListCount(string $status = '') : int
|
|
||||||
{
|
|
||||||
$options = [
|
|
||||||
'query' => [
|
|
||||||
'filter' => [
|
|
||||||
'user_id' => $this->getUserIdByUsername(),
|
|
||||||
'media_type' => 'Anime'
|
|
||||||
],
|
|
||||||
'page' => [
|
|
||||||
'limit' => 1
|
|
||||||
],
|
|
||||||
'sort' => '-updated_at'
|
|
||||||
]
|
|
||||||
];
|
|
||||||
|
|
||||||
if ( ! empty($status))
|
|
||||||
{
|
|
||||||
$options['query']['filter']['status'] = $status;
|
|
||||||
}
|
|
||||||
|
|
||||||
$response = $this->getRequest('library-entries', $options);
|
|
||||||
|
|
||||||
return $response['meta']['count'];
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the full anime list in paginated form
|
|
||||||
*
|
|
||||||
* @param int $limit
|
|
||||||
* @param int $offset
|
|
||||||
* @param array $options
|
|
||||||
* @return Request
|
|
||||||
*/
|
|
||||||
public function getPagedAnimeList(int $limit = 100, int $offset = 0, array $options = [
|
|
||||||
'include' => 'anime.mappings'
|
|
||||||
]): Request
|
|
||||||
{
|
|
||||||
$defaultOptions = [
|
|
||||||
'filter' => [
|
|
||||||
'user_id' => $this->getUserIdByUsername($this->getUsername()),
|
|
||||||
'media_type' => 'Anime'
|
|
||||||
],
|
|
||||||
'page' => [
|
|
||||||
'offset' => $offset,
|
|
||||||
'limit' => $limit
|
|
||||||
],
|
|
||||||
'sort' => '-updated_at'
|
|
||||||
];
|
|
||||||
$options = array_merge($defaultOptions, $options);
|
|
||||||
|
|
||||||
return $this->setUpRequest('GET', 'library-entries', ['query' => $options]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the full anime list
|
|
||||||
*
|
|
||||||
* @param array $options
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function getFullAnimeList(array $options = [
|
|
||||||
'include' => 'anime.mappings'
|
|
||||||
]): array
|
|
||||||
{
|
|
||||||
$status = $options['filter']['status'] ?? '';
|
|
||||||
$count = $this->getAnimeListCount($status);
|
|
||||||
$size = 100;
|
|
||||||
$pages = ceil($count / $size);
|
|
||||||
|
|
||||||
$requests = [];
|
|
||||||
|
|
||||||
// Set up requests
|
|
||||||
for ($i = 0; $i < $pages; $i++)
|
|
||||||
{
|
|
||||||
$offset = $i * $size;
|
|
||||||
$requests[] = $this->getPagedAnimeList($size, $offset, $options);
|
|
||||||
}
|
|
||||||
|
|
||||||
$promiseArray = (new Client())->requestMulti($requests);
|
|
||||||
|
|
||||||
$responses = wait(all($promiseArray));
|
|
||||||
$output = [];
|
|
||||||
|
|
||||||
foreach($responses as $response)
|
|
||||||
{
|
|
||||||
$data = Json::decode($response->getBody());
|
|
||||||
$output = array_merge_recursive($output, $data);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $output;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the raw (unorganized) anime list for the configured user
|
|
||||||
*
|
|
||||||
* @param string $status - The watching status to filter the list with
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function getRawAnimeList(string $status): array
|
|
||||||
{
|
|
||||||
|
|
||||||
$options = [
|
|
||||||
'filter' => [
|
|
||||||
'user_id' => $this->getUserIdByUsername($this->getUsername()),
|
|
||||||
'media_type' => 'Anime',
|
|
||||||
'status' => $status,
|
|
||||||
],
|
|
||||||
'include' => 'media,media.genres,media.mappings,anime.streamingLinks',
|
|
||||||
'sort' => '-updated_at'
|
|
||||||
];
|
|
||||||
|
|
||||||
return $this->getFullAnimeList($options);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get all the anine entries, that are organized for output to html
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function getFullOrganizedAnimeList(): array
|
|
||||||
{
|
|
||||||
$cacheItem = $this->cache->getItem(self::FULL_TRANSFORMED_LIST_CACHE_KEY);
|
|
||||||
|
|
||||||
if ( ! $cacheItem->isHit())
|
|
||||||
{
|
|
||||||
$output = [];
|
|
||||||
|
|
||||||
$statuses = KitsuWatchingStatus::getConstList();
|
|
||||||
|
|
||||||
foreach ($statuses as $key => $status)
|
|
||||||
{
|
|
||||||
$mappedStatus = AnimeWatchingStatus::KITSU_TO_TITLE[$status];
|
|
||||||
$output[$mappedStatus] = $this->getAnimeList($status) ?? [];
|
|
||||||
}
|
|
||||||
|
|
||||||
$cacheItem->set($output);
|
|
||||||
$cacheItem->save();
|
|
||||||
}
|
|
||||||
|
|
||||||
return $cacheItem->get();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the anime list for the configured user
|
* Get the anime list for the configured user
|
||||||
*
|
*
|
||||||
@ -455,23 +332,200 @@ class Model {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all Manga lists
|
* Get the number of anime list items
|
||||||
*
|
*
|
||||||
|
* @param string $status - Optional status to filter by
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function getAnimeListCount(string $status = '') : int
|
||||||
|
{
|
||||||
|
$options = [
|
||||||
|
'query' => [
|
||||||
|
'filter' => [
|
||||||
|
'user_id' => $this->getUserIdByUsername(),
|
||||||
|
'media_type' => 'Anime'
|
||||||
|
],
|
||||||
|
'page' => [
|
||||||
|
'limit' => 1
|
||||||
|
],
|
||||||
|
'sort' => '-updated_at'
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
if ( ! empty($status))
|
||||||
|
{
|
||||||
|
$options['query']['filter']['status'] = $status;
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = $this->getRequest('library-entries', $options);
|
||||||
|
|
||||||
|
return $response['meta']['count'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the full anime list
|
||||||
|
*
|
||||||
|
* @param array $options
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function getFullOrganizedMangaList(): array
|
public function getFullAnimeList(array $options = [
|
||||||
|
'include' => 'anime.mappings'
|
||||||
|
]): array
|
||||||
{
|
{
|
||||||
$statuses = KitsuReadingStatus::getConstList();
|
$status = $options['filter']['status'] ?? '';
|
||||||
|
$count = $this->getAnimeListCount($status);
|
||||||
|
$size = 100;
|
||||||
|
$pages = ceil($count / $size);
|
||||||
|
|
||||||
|
$requester = new ParallelAPIRequest();
|
||||||
|
|
||||||
|
// Set up requests
|
||||||
|
for ($i = 0; $i < $pages; $i++)
|
||||||
|
{
|
||||||
|
$offset = $i * $size;
|
||||||
|
$requester->addRequest($this->getPagedAnimeList($size, $offset, $options));
|
||||||
|
}
|
||||||
|
|
||||||
|
$responses = $requester->makeRequests();
|
||||||
$output = [];
|
$output = [];
|
||||||
foreach ($statuses as $status)
|
|
||||||
|
foreach($responses as $response)
|
||||||
{
|
{
|
||||||
$mappedStatus = MangaReadingStatus::KITSU_TO_TITLE[$status];
|
$data = Json::decode($response->getBody());
|
||||||
$output[$mappedStatus] = $this->getMangaList($status);
|
$output = array_merge_recursive($output, $data);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $output;
|
return $output;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all the anine entries, that are organized for output to html
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getFullOrganizedAnimeList(): array
|
||||||
|
{
|
||||||
|
$output = [];
|
||||||
|
|
||||||
|
$statuses = KitsuWatchingStatus::getConstList();
|
||||||
|
|
||||||
|
foreach ($statuses as $key => $status)
|
||||||
|
{
|
||||||
|
$mappedStatus = AnimeWatchingStatus::KITSU_TO_TITLE[$status];
|
||||||
|
$output[$mappedStatus] = $this->getAnimeList($status) ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $output;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the mal id for the anime represented by the kitsu id
|
||||||
|
* to enable updating MyAnimeList
|
||||||
|
*
|
||||||
|
* @param string $kitsuAnimeId The id of the anime on Kitsu
|
||||||
|
* @return string|null Returns the mal id if it exists, otherwise null
|
||||||
|
*/
|
||||||
|
public function getMalIdForAnime(string $kitsuAnimeId)
|
||||||
|
{
|
||||||
|
$options = [
|
||||||
|
'query' => [
|
||||||
|
'include' => 'mappings'
|
||||||
|
]
|
||||||
|
];
|
||||||
|
$data = $this->getRequest("anime/{$kitsuAnimeId}", $options);
|
||||||
|
|
||||||
|
if ( ! array_key_exists('included', $data))
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
$mappings = array_column($data['included'], 'attributes');
|
||||||
|
|
||||||
|
foreach($mappings as $map)
|
||||||
|
{
|
||||||
|
if ($map['externalSite'] === 'myanimelist/anime')
|
||||||
|
{
|
||||||
|
return $map['externalId'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the full anime list in paginated form
|
||||||
|
*
|
||||||
|
* @param int $limit
|
||||||
|
* @param int $offset
|
||||||
|
* @param array $options
|
||||||
|
* @return Request
|
||||||
|
*/
|
||||||
|
public function getPagedAnimeList(int $limit = 100, int $offset = 0, array $options = [
|
||||||
|
'include' => 'anime.mappings'
|
||||||
|
]): Request
|
||||||
|
{
|
||||||
|
$defaultOptions = [
|
||||||
|
'filter' => [
|
||||||
|
'user_id' => $this->getUserIdByUsername($this->getUsername()),
|
||||||
|
'media_type' => 'Anime'
|
||||||
|
],
|
||||||
|
'page' => [
|
||||||
|
'offset' => $offset,
|
||||||
|
'limit' => $limit
|
||||||
|
],
|
||||||
|
'sort' => '-updated_at'
|
||||||
|
];
|
||||||
|
$options = array_merge($defaultOptions, $options);
|
||||||
|
|
||||||
|
return $this->setUpRequest('GET', 'library-entries', ['query' => $options]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the raw (unorganized) anime list for the configured user
|
||||||
|
*
|
||||||
|
* @param string $status - The watching status to filter the list with
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getRawAnimeList(string $status): array
|
||||||
|
{
|
||||||
|
|
||||||
|
$options = [
|
||||||
|
'filter' => [
|
||||||
|
'user_id' => $this->getUserIdByUsername($this->getUsername()),
|
||||||
|
'media_type' => 'Anime',
|
||||||
|
'status' => $status,
|
||||||
|
],
|
||||||
|
'include' => 'media,media.genres,media.mappings,anime.streamingLinks',
|
||||||
|
'sort' => '-updated_at'
|
||||||
|
];
|
||||||
|
|
||||||
|
return $this->getFullAnimeList($options);
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// ! Manga-specific methods
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get information about a particular manga
|
||||||
|
*
|
||||||
|
* @param string $slug
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getManga(string $slug): array
|
||||||
|
{
|
||||||
|
$baseData = $this->getRawMediaData('manga', $slug);
|
||||||
|
|
||||||
|
if (empty($baseData))
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$transformed = $this->mangaTransformer->transform($baseData);
|
||||||
|
$transformed['included'] = $baseData['included'];
|
||||||
|
return $transformed;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the manga list for the configured user
|
* Get the manga list for the configured user
|
||||||
*
|
*
|
||||||
@ -489,7 +543,7 @@ class Model {
|
|||||||
'media_type' => 'Manga',
|
'media_type' => 'Manga',
|
||||||
'status' => $status,
|
'status' => $status,
|
||||||
],
|
],
|
||||||
'include' => 'media',
|
'include' => 'media,media.genres,media.mappings',
|
||||||
'page' => [
|
'page' => [
|
||||||
'offset' => $offset,
|
'offset' => $offset,
|
||||||
'limit' => $limit
|
'limit' => $limit
|
||||||
@ -503,9 +557,16 @@ class Model {
|
|||||||
if ( ! $cacheItem->isHit())
|
if ( ! $cacheItem->isHit())
|
||||||
{
|
{
|
||||||
$data = $this->getRequest('library-entries', $options);
|
$data = $this->getRequest('library-entries', $options);
|
||||||
$data = JsonAPI::inlineRawIncludes($data, 'manga');
|
|
||||||
|
|
||||||
$transformed = $this->mangaListTransformer->transformCollection($data);
|
$included = JsonAPI::organizeIncludes($data['included']);
|
||||||
|
$included = JsonAPI::inlineIncludedRelationships($included, 'manga');
|
||||||
|
|
||||||
|
foreach($data['data'] as $i => &$item)
|
||||||
|
{
|
||||||
|
$item['included'] = $included;
|
||||||
|
}
|
||||||
|
|
||||||
|
$transformed = $this->mangaListTransformer->transformCollection($data['data']);
|
||||||
|
|
||||||
$cacheItem->set($transformed);
|
$cacheItem->set($transformed);
|
||||||
$cacheItem->save();
|
$cacheItem->save();
|
||||||
@ -515,37 +576,150 @@ class Model {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Search for an anime or manga
|
* Get the number of manga list items
|
||||||
*
|
*
|
||||||
* @param string $type - 'anime' or 'manga'
|
* @param string $status - Optional status to filter by
|
||||||
* @param string $query - name of the item to search for
|
* @return int
|
||||||
* @return array
|
|
||||||
*/
|
*/
|
||||||
public function search(string $type, string $query): array
|
public function getMangaListCount(string $status = '') : int
|
||||||
{
|
{
|
||||||
$options = [
|
$options = [
|
||||||
'query' => [
|
'query' => [
|
||||||
'filter' => [
|
'filter' => [
|
||||||
'text' => $query
|
'user_id' => $this->getUserIdByUsername(),
|
||||||
|
'media_type' => 'Manga'
|
||||||
],
|
],
|
||||||
'page' => [
|
'page' => [
|
||||||
'offset' => 0,
|
'limit' => 1
|
||||||
'limit' => 20
|
|
||||||
],
|
],
|
||||||
|
'sort' => '-updated_at'
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
|
|
||||||
$raw = $this->getRequest($type, $options);
|
if ( ! empty($status))
|
||||||
|
|
||||||
foreach ($raw['data'] as &$item)
|
|
||||||
{
|
{
|
||||||
$item['attributes']['titles'] = K::filterTitles($item['attributes']);
|
$options['query']['filter']['status'] = $status;
|
||||||
array_shift($item['attributes']['titles']);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $raw;
|
$response = $this->getRequest('library-entries', $options);
|
||||||
|
|
||||||
|
return $response['meta']['count'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the full manga list
|
||||||
|
*
|
||||||
|
* @param array $options
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getFullMangaList(array $options = [
|
||||||
|
'include' => 'manga.mappings'
|
||||||
|
]): array
|
||||||
|
{
|
||||||
|
$status = $options['filter']['status'] ?? '';
|
||||||
|
$count = $this->getMangaListCount($status);
|
||||||
|
$size = 100;
|
||||||
|
$pages = ceil($count / $size);
|
||||||
|
|
||||||
|
$requester = new ParallelAPIRequest();
|
||||||
|
|
||||||
|
// Set up requests
|
||||||
|
for ($i = 0; $i < $pages; $i++)
|
||||||
|
{
|
||||||
|
$offset = $i * $size;
|
||||||
|
$requester->addRequest($this->getPagedMangaList($size, $offset, $options));
|
||||||
|
}
|
||||||
|
|
||||||
|
$responses = $requester->makeRequests();
|
||||||
|
$output = [];
|
||||||
|
|
||||||
|
foreach($responses as $response)
|
||||||
|
{
|
||||||
|
$data = Json::decode($response->getBody());
|
||||||
|
$output = array_merge_recursive($output, $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $output;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all Manga lists
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getFullOrganizedMangaList(): array
|
||||||
|
{
|
||||||
|
$statuses = KitsuReadingStatus::getConstList();
|
||||||
|
$output = [];
|
||||||
|
foreach ($statuses as $status)
|
||||||
|
{
|
||||||
|
$mappedStatus = MangaReadingStatus::KITSU_TO_TITLE[$status];
|
||||||
|
$output[$mappedStatus] = $this->getMangaList($status);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $output;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the full manga list in paginated form
|
||||||
|
*
|
||||||
|
* @param int $limit
|
||||||
|
* @param int $offset
|
||||||
|
* @param array $options
|
||||||
|
* @return Request
|
||||||
|
*/
|
||||||
|
public function getPagedMangaList(int $limit = 100, int $offset = 0, array $options = [
|
||||||
|
'include' => 'manga.mappings'
|
||||||
|
]): Request
|
||||||
|
{
|
||||||
|
$defaultOptions = [
|
||||||
|
'filter' => [
|
||||||
|
'user_id' => $this->getUserIdByUsername($this->getUsername()),
|
||||||
|
'media_type' => 'Manga'
|
||||||
|
],
|
||||||
|
'page' => [
|
||||||
|
'offset' => $offset,
|
||||||
|
'limit' => $limit
|
||||||
|
],
|
||||||
|
'sort' => '-updated_at'
|
||||||
|
];
|
||||||
|
$options = array_merge($defaultOptions, $options);
|
||||||
|
|
||||||
|
return $this->setUpRequest('GET', 'library-entries', ['query' => $options]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the mal id for the manga represented by the kitsu id
|
||||||
|
* to enable updating MyAnimeList
|
||||||
|
*
|
||||||
|
* @param string $kitsuAnimeId The id of the anime on Kitsu
|
||||||
|
* @return string|null Returns the mal id if it exists, otherwise null
|
||||||
|
*/
|
||||||
|
public function getMalIdForManga(string $kitsuMangaId)
|
||||||
|
{
|
||||||
|
$options = [
|
||||||
|
'query' => [
|
||||||
|
'include' => 'mappings'
|
||||||
|
]
|
||||||
|
];
|
||||||
|
$data = $this->getRequest("manga/{$kitsuMangaId}", $options);
|
||||||
|
$mappings = array_column($data['included'], 'attributes');
|
||||||
|
|
||||||
|
foreach($mappings as $map)
|
||||||
|
{
|
||||||
|
if ($map['externalSite'] === 'myanimelist/manga')
|
||||||
|
{
|
||||||
|
return $map['externalId'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// ! Generic API calls
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a list item
|
* Create a list item
|
||||||
*
|
*
|
||||||
@ -665,6 +839,9 @@ class Model {
|
|||||||
'filter' => [
|
'filter' => [
|
||||||
'slug' => $slug
|
'slug' => $slug
|
||||||
],
|
],
|
||||||
|
'fields' => [
|
||||||
|
'characters' => 'slug,name,image'
|
||||||
|
],
|
||||||
'include' => ($type === 'anime')
|
'include' => ($type === 'anime')
|
||||||
? 'genres,mappings,streamingLinks,animeCharacters.character'
|
? 'genres,mappings,streamingLinks,animeCharacters.character'
|
||||||
: 'genres,mappings,mangaCharacters.character,castings.character',
|
: 'genres,mappings,mangaCharacters.character,castings.character',
|
||||||
|
@ -123,7 +123,7 @@ class AnimeListTransformer extends AbstractTransformer {
|
|||||||
]
|
]
|
||||||
];
|
];
|
||||||
|
|
||||||
if ( ! empty($item['user_rating']))
|
if (is_numeric($item['user_rating']))
|
||||||
{
|
{
|
||||||
$untransformed['data']['rating'] = $item['user_rating'] / 2;
|
$untransformed['data']['rating'] = $item['user_rating'] / 2;
|
||||||
}
|
}
|
||||||
|
@ -35,22 +35,42 @@ class MangaListTransformer extends AbstractTransformer {
|
|||||||
*/
|
*/
|
||||||
public function transform($item)
|
public function transform($item)
|
||||||
{
|
{
|
||||||
$manga =& $item['manga'];
|
$included = $item['included'];
|
||||||
|
$mangaId = $item['relationships']['media']['data']['id'];
|
||||||
|
$manga = $included['manga'][$mangaId];
|
||||||
|
|
||||||
|
$genres = array_column($manga['relationships']['genres'], 'name') ?? [];
|
||||||
|
sort($genres);
|
||||||
|
|
||||||
$rating = (is_numeric($item['attributes']['rating']))
|
$rating = (is_numeric($item['attributes']['rating']))
|
||||||
? intval(2 * $item['attributes']['rating'])
|
? intval(2 * $item['attributes']['rating'])
|
||||||
: '-';
|
: '-';
|
||||||
|
|
||||||
$totalChapters = ($manga['attributes']['chapterCount'] > 0)
|
$totalChapters = ($manga['chapterCount'] > 0)
|
||||||
? $manga['attributes']['chapterCount']
|
? $manga['chapterCount']
|
||||||
: '-';
|
: '-';
|
||||||
|
|
||||||
$totalVolumes = ($manga['attributes']['volumeCount'] > 0)
|
$totalVolumes = ($manga['volumeCount'] > 0)
|
||||||
? $manga['attributes']['volumeCount']
|
? $manga['volumeCount']
|
||||||
: '-';
|
: '-';
|
||||||
|
|
||||||
|
$MALid = NULL;
|
||||||
|
|
||||||
|
if (array_key_exists('mappings', $manga['relationships']))
|
||||||
|
{
|
||||||
|
foreach ($manga['relationships']['mappings'] as $mapping)
|
||||||
|
{
|
||||||
|
if ($mapping['externalSite'] === 'myanimelist/manga')
|
||||||
|
{
|
||||||
|
$MALid = $mapping['externalId'];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$map = [
|
$map = [
|
||||||
'id' => $item['id'],
|
'id' => $item['id'],
|
||||||
|
'mal_id' => $MALid,
|
||||||
'chapters' => [
|
'chapters' => [
|
||||||
'read' => $item['attributes']['progress'],
|
'read' => $item['attributes']['progress'],
|
||||||
'total' => $totalChapters
|
'total' => $totalChapters
|
||||||
@ -60,13 +80,13 @@ class MangaListTransformer extends AbstractTransformer {
|
|||||||
'total' => $totalVolumes
|
'total' => $totalVolumes
|
||||||
],
|
],
|
||||||
'manga' => [
|
'manga' => [
|
||||||
'titles' => Kitsu::filterTitles($manga['attributes']),
|
'titles' => Kitsu::filterTitles($manga),
|
||||||
'alternate_title' => NULL,
|
'alternate_title' => NULL,
|
||||||
'slug' => $manga['attributes']['slug'],
|
'slug' => $manga['slug'],
|
||||||
'url' => 'https://kitsu.io/manga/' . $manga['attributes']['slug'],
|
'url' => 'https://kitsu.io/manga/' . $manga['slug'],
|
||||||
'type' => $manga['attributes']['mangaType'],
|
'type' => $manga['mangaType'],
|
||||||
'image' => $manga['attributes']['posterImage']['small'],
|
'image' => $manga['posterImage']['small'],
|
||||||
'genres' => [], //$manga['genres'],
|
'genres' => $genres,
|
||||||
],
|
],
|
||||||
'reading_status' => $item['attributes']['status'],
|
'reading_status' => $item['attributes']['status'],
|
||||||
'notes' => $item['attributes']['notes'],
|
'notes' => $item['attributes']['notes'],
|
||||||
@ -90,16 +110,21 @@ class MangaListTransformer extends AbstractTransformer {
|
|||||||
|
|
||||||
$map = [
|
$map = [
|
||||||
'id' => $item['id'],
|
'id' => $item['id'],
|
||||||
|
'mal_id' => $item['mal_id'],
|
||||||
'data' => [
|
'data' => [
|
||||||
'status' => $item['status'],
|
'status' => $item['status'],
|
||||||
'progress' => (int)$item['chapters_read'],
|
'progress' => (int)$item['chapters_read'],
|
||||||
'reconsuming' => $rereading,
|
'reconsuming' => $rereading,
|
||||||
'reconsumeCount' => (int)$item['reread_count'],
|
'reconsumeCount' => (int)$item['reread_count'],
|
||||||
'notes' => $item['notes'],
|
'notes' => $item['notes'],
|
||||||
'rating' => $item['new_rating'] / 2
|
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
|
if (is_numeric($item['new_rating']))
|
||||||
|
{
|
||||||
|
$map['data']['rating'] = $item['new_rating'] / 2;
|
||||||
|
}
|
||||||
|
|
||||||
return $map;
|
return $map;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,14 @@ class ListItem {
|
|||||||
use ContainerAware;
|
use ContainerAware;
|
||||||
use MALTrait;
|
use MALTrait;
|
||||||
|
|
||||||
public function create(array $data): Request
|
/**
|
||||||
|
* Create a list item
|
||||||
|
*
|
||||||
|
* @param array $data
|
||||||
|
* @param string $type
|
||||||
|
* @return Request
|
||||||
|
*/
|
||||||
|
public function create(array $data, string $type = 'anime'): Request
|
||||||
{
|
{
|
||||||
$id = $data['id'];
|
$id = $data['id'];
|
||||||
$createData = [
|
$createData = [
|
||||||
@ -42,17 +49,24 @@ class ListItem {
|
|||||||
|
|
||||||
$config = $this->container->get('config');
|
$config = $this->container->get('config');
|
||||||
|
|
||||||
return $this->requestBuilder->newRequest('POST', "animelist/add/{$id}.xml")
|
return $this->requestBuilder->newRequest('POST', "{$type}list/add/{$id}.xml")
|
||||||
->setFormFields($createData)
|
->setFormFields($createData)
|
||||||
->setBasicAuth($config->get(['mal','username']), $config->get(['mal', 'password']))
|
->setBasicAuth($config->get(['mal','username']), $config->get(['mal', 'password']))
|
||||||
->getFullRequest();
|
->getFullRequest();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function delete(string $id): Request
|
/**
|
||||||
|
* Delete a list item
|
||||||
|
*
|
||||||
|
* @param string $id
|
||||||
|
* @param string $type
|
||||||
|
* @return Request
|
||||||
|
*/
|
||||||
|
public function delete(string $id, string $type = 'anime'): Request
|
||||||
{
|
{
|
||||||
$config = $this->container->get('config');
|
$config = $this->container->get('config');
|
||||||
|
|
||||||
return $this->requestBuilder->newRequest('DELETE', "animelist/delete/{$id}.xml")
|
return $this->requestBuilder->newRequest('DELETE', "{$type}list/delete/{$id}.xml")
|
||||||
->setFormFields([
|
->setFormFields([
|
||||||
'id' => $id
|
'id' => $id
|
||||||
])
|
])
|
||||||
@ -67,7 +81,15 @@ class ListItem {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function update(string $id, array $data): Request
|
/**
|
||||||
|
* Update a list item
|
||||||
|
*
|
||||||
|
* @param string $id
|
||||||
|
* @param array $data
|
||||||
|
* @param string $type
|
||||||
|
* @return Request
|
||||||
|
*/
|
||||||
|
public function update(string $id, array $data, string $type = 'anime'): Request
|
||||||
{
|
{
|
||||||
$config = $this->container->get('config');
|
$config = $this->container->get('config');
|
||||||
|
|
||||||
@ -76,7 +98,7 @@ class ListItem {
|
|||||||
->addField('id', $id)
|
->addField('id', $id)
|
||||||
->addField('data', $xml);
|
->addField('data', $xml);
|
||||||
|
|
||||||
return $this->requestBuilder->newRequest('POST', "animelist/update/{$id}.xml")
|
return $this->requestBuilder->newRequest('POST', "{$type}list/update/{$id}.xml")
|
||||||
->setFormFields([
|
->setFormFields([
|
||||||
'id' => $id,
|
'id' => $id,
|
||||||
'data' => $xml
|
'data' => $xml
|
||||||
|
@ -64,21 +64,6 @@ trait MALTrait {
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Unencode the dual-encoded ampersands in the body
|
|
||||||
*
|
|
||||||
* This is a dirty hack until I can fully track down where
|
|
||||||
* the dual-encoding happens
|
|
||||||
*
|
|
||||||
* @param FormBody $formBody The form builder object to fix
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
private function fixBody(FormBody $formBody): string
|
|
||||||
{
|
|
||||||
$rawBody = \Amp\wait($formBody->getBody());
|
|
||||||
return html_entity_decode($rawBody, \ENT_HTML5, 'UTF-8');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a request object
|
* Create a request object
|
||||||
*
|
*
|
||||||
|
@ -17,10 +17,13 @@
|
|||||||
namespace Aviat\AnimeClient\API\MAL;
|
namespace Aviat\AnimeClient\API\MAL;
|
||||||
|
|
||||||
use Amp\Artax\Request;
|
use Amp\Artax\Request;
|
||||||
use Aviat\AnimeClient\API\MAL\ListItem;
|
use Aviat\AnimeClient\API\MAL\{
|
||||||
use Aviat\AnimeClient\API\MAL\Transformer\AnimeListTransformer;
|
ListItem,
|
||||||
|
Transformer\AnimeListTransformer,
|
||||||
|
Transformer\MangaListTransformer
|
||||||
|
};
|
||||||
use Aviat\AnimeClient\API\XML;
|
use Aviat\AnimeClient\API\XML;
|
||||||
use Aviat\AnimeClient\API\Mapping\AnimeWatchingStatus;
|
use Aviat\AnimeClient\API\Mapping\{AnimeWatchingStatus, MangaReadingStatus};
|
||||||
use Aviat\Ion\Di\ContainerAware;
|
use Aviat\Ion\Di\ContainerAware;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -48,15 +51,25 @@ class Model {
|
|||||||
public function __construct(ListItem $listItem)
|
public function __construct(ListItem $listItem)
|
||||||
{
|
{
|
||||||
$this->animeListTransformer = new AnimeListTransformer();
|
$this->animeListTransformer = new AnimeListTransformer();
|
||||||
|
$this->mangaListTransformer = new MangaListTransformer();
|
||||||
$this->listItem = $listItem;
|
$this->listItem = $listItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function createFullListItem(array $data): Request
|
/**
|
||||||
|
* Create a list item on MAL
|
||||||
|
*
|
||||||
|
* @param array $data
|
||||||
|
* @param string $type "anime" or "manga"
|
||||||
|
* @return Request
|
||||||
|
*/
|
||||||
|
public function createFullListItem(array $data, string $type = 'anime'): Request
|
||||||
{
|
{
|
||||||
return $this->listItem->create($data);
|
return $this->listItem->create($data, $type);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function createListItem(array $data): Request
|
public function createListItem(array $data, string $type = 'anime'): Request
|
||||||
|
{
|
||||||
|
if ($type === 'anime')
|
||||||
{
|
{
|
||||||
$createData = [
|
$createData = [
|
||||||
'id' => $data['id'],
|
'id' => $data['id'],
|
||||||
@ -64,11 +77,55 @@ class Model {
|
|||||||
'status' => AnimeWatchingStatus::KITSU_TO_MAL[$data['status']]
|
'status' => AnimeWatchingStatus::KITSU_TO_MAL[$data['status']]
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
|
}
|
||||||
return $this->listItem->create($createData);
|
elseif ($type === 'manga')
|
||||||
|
{
|
||||||
|
$createData = [
|
||||||
|
'id' => $data['id'],
|
||||||
|
'data' => [
|
||||||
|
'status' => MangaReadingStatus::KITSU_TO_MAL[$data['status']]
|
||||||
|
]
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getFullList(): array
|
return $this->listItem->create($createData, $type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMangaList(): array
|
||||||
|
{
|
||||||
|
return $this->getList('manga');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAnimeList(): array
|
||||||
|
{
|
||||||
|
return $this->getList('anime');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getListItem(string $listId): array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateListItem(array $data, string $type = 'anime'): Request
|
||||||
|
{
|
||||||
|
if ($type === 'anime')
|
||||||
|
{
|
||||||
|
$updateData = $this->animeListTransformer->untransform($data);
|
||||||
|
}
|
||||||
|
else if ($type === 'manga')
|
||||||
|
{
|
||||||
|
$updateData = $this->mangaListTransformer->untransform($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->listItem->update($updateData['id'], $updateData['data'], $type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function deleteListItem(string $id, string $type = 'anime'): Request
|
||||||
|
{
|
||||||
|
return $this->listItem->delete($id, $type);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getList(string $type): array
|
||||||
{
|
{
|
||||||
$config = $this->container->get('config');
|
$config = $this->container->get('config');
|
||||||
$userName = $config->get(['mal', 'username']);
|
$userName = $config->get(['mal', 'username']);
|
||||||
@ -78,26 +135,11 @@ class Model {
|
|||||||
],
|
],
|
||||||
'query' => [
|
'query' => [
|
||||||
'u' => $userName,
|
'u' => $userName,
|
||||||
'status' => 'all'
|
'status' => 'all',
|
||||||
|
'type' => $type
|
||||||
]
|
]
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return $list['myanimelist']['anime'];
|
return $list['myanimelist'][$type];
|
||||||
}
|
|
||||||
|
|
||||||
public function getListItem(string $listId): array
|
|
||||||
{
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function updateListItem(array $data): Request
|
|
||||||
{
|
|
||||||
$updateData = $this->animeListTransformer->untransform($data);
|
|
||||||
return $this->listItem->update($updateData['id'], $updateData['data']);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function deleteListItem(string $id): Request
|
|
||||||
{
|
|
||||||
return $this->listItem->delete($id);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -24,26 +24,14 @@ use Aviat\Ion\Transformer\AbstractTransformer;
|
|||||||
*/
|
*/
|
||||||
class AnimeListTransformer extends AbstractTransformer {
|
class AnimeListTransformer extends AbstractTransformer {
|
||||||
/**
|
/**
|
||||||
* Transform MAL episode data to Kitsu episode data
|
* Identity transformation
|
||||||
*
|
*
|
||||||
* @param array $item
|
* @param array $item
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function transform($item)
|
public function transform($item)
|
||||||
{
|
{
|
||||||
$rewatching = (array_key_exists('rewatching', $item) && $item['rewatching']);
|
return $item;
|
||||||
|
|
||||||
return [
|
|
||||||
'id' => $item['mal_id'],
|
|
||||||
'data' => [
|
|
||||||
'status' => AnimeWatchingStatus::KITSU_TO_MAL[$item['watching_status']],
|
|
||||||
'rating' => $item['user_rating'],
|
|
||||||
'rewatch_value' => (int) $rewatching,
|
|
||||||
'times_rewatched' => $item['rewatched'],
|
|
||||||
'comments' => $item['notes'],
|
|
||||||
'episode' => $item['episodes_watched']
|
|
||||||
]
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
85
src/API/MAL/Transformer/MangaListTransformer.php
Normal file
85
src/API/MAL/Transformer/MangaListTransformer.php
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* Hummingbird Anime List Client
|
||||||
|
*
|
||||||
|
* An API client for Kitsu and MyAnimeList to manage anime and manga watch lists
|
||||||
|
*
|
||||||
|
* PHP version 7
|
||||||
|
*
|
||||||
|
* @package HummingbirdAnimeClient
|
||||||
|
* @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://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Aviat\AnimeClient\API\MAL\Transformer;
|
||||||
|
|
||||||
|
use Aviat\AnimeClient\API\Mapping\MangaReadingStatus;
|
||||||
|
use Aviat\Ion\Transformer\AbstractTransformer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transformer for updating MAL List
|
||||||
|
*/
|
||||||
|
class MangaListTransformer extends AbstractTransformer {
|
||||||
|
/**
|
||||||
|
* Identity transformation
|
||||||
|
*
|
||||||
|
* @param array $item
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function transform($item)
|
||||||
|
{
|
||||||
|
return $item;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform Kitsu data to MAL data
|
||||||
|
*
|
||||||
|
* @param array $item
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function untransform(array $item): array
|
||||||
|
{
|
||||||
|
$map = [
|
||||||
|
'id' => $item['mal_id'],
|
||||||
|
'data' => [
|
||||||
|
'chapter' => $item['data']['progress']
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
$data =& $item['data'];
|
||||||
|
|
||||||
|
foreach($item['data'] as $key => $value)
|
||||||
|
{
|
||||||
|
switch($key)
|
||||||
|
{
|
||||||
|
case 'notes':
|
||||||
|
$map['data']['comments'] = $value;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'rating':
|
||||||
|
$map['data']['score'] = $value * 2;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'reconsuming':
|
||||||
|
$map['data']['enable_rereading'] = (bool) $value;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'reconsumeCount':
|
||||||
|
$map['data']['times_reread'] = $value;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'status':
|
||||||
|
$map['data']['status'] = MangaReadingStatus::KITSU_TO_MAL[$value];
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $map;
|
||||||
|
}
|
||||||
|
}
|
@ -29,7 +29,7 @@ use Aviat\Ion\Enum;
|
|||||||
* and url route segments
|
* and url route segments
|
||||||
*/
|
*/
|
||||||
class MangaReadingStatus extends Enum {
|
class MangaReadingStatus extends Enum {
|
||||||
const MAL_TO_KITSU = [
|
const KITSU_TO_MAL = [
|
||||||
Kitsu::READING => MAL::READING,
|
Kitsu::READING => MAL::READING,
|
||||||
Kitsu::PLAN_TO_READ => MAL::PLAN_TO_READ,
|
Kitsu::PLAN_TO_READ => MAL::PLAN_TO_READ,
|
||||||
Kitsu::COMPLETED => MAL::COMPLETED,
|
Kitsu::COMPLETED => MAL::COMPLETED,
|
||||||
@ -37,12 +37,17 @@ class MangaReadingStatus extends Enum {
|
|||||||
Kitsu::DROPPED => MAL::DROPPED
|
Kitsu::DROPPED => MAL::DROPPED
|
||||||
];
|
];
|
||||||
|
|
||||||
const KITSU_TO_MAL = [
|
const MAL_TO_KITSU = [
|
||||||
|
'1' => Kitsu::READING,
|
||||||
|
'2' => Kitsu::COMPLETED,
|
||||||
|
'3' => Kitsu::ON_HOLD,
|
||||||
|
'4' => Kitsu::DROPPED,
|
||||||
|
'6' => Kitsu::PLAN_TO_READ,
|
||||||
MAL::READING => Kitsu::READING,
|
MAL::READING => Kitsu::READING,
|
||||||
MAL::PLAN_TO_READ => Kitsu::PLAN_TO_READ,
|
|
||||||
MAL::COMPLETED => Kitsu::COMPLETED,
|
MAL::COMPLETED => Kitsu::COMPLETED,
|
||||||
MAL::ON_HOLD => Kitsu::ON_HOLD,
|
MAL::ON_HOLD => Kitsu::ON_HOLD,
|
||||||
MAL::DROPPED => Kitsu::DROPPED
|
MAL::DROPPED => Kitsu::DROPPED,
|
||||||
|
MAL::PLAN_TO_READ => Kitsu::PLAN_TO_READ,
|
||||||
];
|
];
|
||||||
|
|
||||||
const KITSU_TO_TITLE = [
|
const KITSU_TO_TITLE = [
|
||||||
@ -50,7 +55,7 @@ class MangaReadingStatus extends Enum {
|
|||||||
Kitsu::PLAN_TO_READ => Title::PLAN_TO_READ,
|
Kitsu::PLAN_TO_READ => Title::PLAN_TO_READ,
|
||||||
Kitsu::COMPLETED => Title::COMPLETED,
|
Kitsu::COMPLETED => Title::COMPLETED,
|
||||||
Kitsu::ON_HOLD => Title::ON_HOLD,
|
Kitsu::ON_HOLD => Title::ON_HOLD,
|
||||||
Kitsu::DROPPED => Title::DROPPED
|
Kitsu::DROPPED => Title::DROPPED,
|
||||||
];
|
];
|
||||||
|
|
||||||
const ROUTE_TO_KITSU = [
|
const ROUTE_TO_KITSU = [
|
||||||
@ -58,7 +63,7 @@ class MangaReadingStatus extends Enum {
|
|||||||
Route::READING => Kitsu::READING,
|
Route::READING => Kitsu::READING,
|
||||||
Route::COMPLETED => Kitsu::COMPLETED,
|
Route::COMPLETED => Kitsu::COMPLETED,
|
||||||
Route::DROPPED => Kitsu::DROPPED,
|
Route::DROPPED => Kitsu::DROPPED,
|
||||||
Route::ON_HOLD => Kitsu::ON_HOLD
|
Route::ON_HOLD => Kitsu::ON_HOLD,
|
||||||
];
|
];
|
||||||
|
|
||||||
const ROUTE_TO_TITLE = [
|
const ROUTE_TO_TITLE = [
|
||||||
@ -67,7 +72,7 @@ class MangaReadingStatus extends Enum {
|
|||||||
Route::READING => Title::READING,
|
Route::READING => Title::READING,
|
||||||
Route::COMPLETED => Title::COMPLETED,
|
Route::COMPLETED => Title::COMPLETED,
|
||||||
Route::DROPPED => Title::DROPPED,
|
Route::DROPPED => Title::DROPPED,
|
||||||
Route::ON_HOLD => Title::ON_HOLD
|
Route::ON_HOLD => Title::ON_HOLD,
|
||||||
];
|
];
|
||||||
|
|
||||||
const TITLE_TO_KITSU = [
|
const TITLE_TO_KITSU = [
|
||||||
@ -75,6 +80,6 @@ class MangaReadingStatus extends Enum {
|
|||||||
Title::READING => Kitsu::READING,
|
Title::READING => Kitsu::READING,
|
||||||
Title::COMPLETED => Kitsu::COMPLETED,
|
Title::COMPLETED => Kitsu::COMPLETED,
|
||||||
Title::DROPPED => Kitsu::DROPPED,
|
Title::DROPPED => Kitsu::DROPPED,
|
||||||
Title::ON_HOLD => Kitsu::ON_HOLD
|
Title::ON_HOLD => Kitsu::ON_HOLD,
|
||||||
];
|
];
|
||||||
}
|
}
|
@ -21,25 +21,14 @@ use Yosymfony\Toml\Toml;
|
|||||||
define('SRC_DIR', realpath(__DIR__));
|
define('SRC_DIR', realpath(__DIR__));
|
||||||
|
|
||||||
const SESSION_SEGMENT = 'Aviat\AnimeClient\Auth';
|
const SESSION_SEGMENT = 'Aviat\AnimeClient\Auth';
|
||||||
|
const DEFAULT_CONTROLLER = 'Aviat\AnimeClient\Controller\Index';
|
||||||
const DEFAULT_CONTROLLER_NAMESPACE = 'Aviat\AnimeClient\Controller';
|
const DEFAULT_CONTROLLER_NAMESPACE = 'Aviat\AnimeClient\Controller';
|
||||||
const DEFAULT_CONTROLLER = 'Aviat\AnimeClient\Controller\Anime';
|
const DEFAULT_LIST_CONTROLLER = 'Aviat\AnimeClient\Controller\Anime';
|
||||||
const DEFAULT_CONTROLLER_METHOD = 'index';
|
const DEFAULT_CONTROLLER_METHOD = 'index';
|
||||||
const NOT_FOUND_METHOD = 'notFound';
|
const NOT_FOUND_METHOD = 'notFound';
|
||||||
const ERROR_MESSAGE_METHOD = 'errorPage';
|
const ERROR_MESSAGE_METHOD = 'errorPage';
|
||||||
const SRC_DIR = SRC_DIR;
|
const SRC_DIR = SRC_DIR;
|
||||||
|
|
||||||
/**
|
|
||||||
* Joins paths together. Variadic to take an
|
|
||||||
* arbitrary number of arguments
|
|
||||||
*
|
|
||||||
* @param string[] ...$args
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
function _dir(...$args)
|
|
||||||
{
|
|
||||||
return implode(DIRECTORY_SEPARATOR, $args);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load configuration options from .toml files
|
* Load configuration options from .toml files
|
||||||
*
|
*
|
||||||
|
@ -19,8 +19,16 @@ namespace Aviat\AnimeClient\Command;
|
|||||||
use function Amp\{all, wait};
|
use function Amp\{all, wait};
|
||||||
|
|
||||||
use Amp\Artax\Client;
|
use Amp\Artax\Client;
|
||||||
use Aviat\AnimeClient\API\{JsonAPI, Mapping\AnimeWatchingStatus};
|
use Aviat\AnimeClient\API\{
|
||||||
use Aviat\AnimeClient\API\MAL\Transformer\AnimeListTransformer as ALT;
|
JsonAPI,
|
||||||
|
ParallelAPIRequest,
|
||||||
|
Mapping\AnimeWatchingStatus,
|
||||||
|
Mapping\MangaReadingStatus
|
||||||
|
};
|
||||||
|
use Aviat\AnimeClient\API\MAL\Transformer\{
|
||||||
|
AnimeListTransformer as ALT,
|
||||||
|
MangaListTransformer as MLT
|
||||||
|
};
|
||||||
use Aviat\Ion\Json;
|
use Aviat\Ion\Json;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -55,63 +63,71 @@ class SyncKitsuWithMal extends BaseCommand {
|
|||||||
$this->kitsuModel = $this->container->get('kitsu-model');
|
$this->kitsuModel = $this->container->get('kitsu-model');
|
||||||
$this->malModel = $this->container->get('mal-model');
|
$this->malModel = $this->container->get('mal-model');
|
||||||
|
|
||||||
$malCount = count($this->getMALAnimeList());
|
$this->syncAnime();
|
||||||
$kitsuCount = $this->getKitsuAnimeListPageCount();
|
$this->syncManga();
|
||||||
|
}
|
||||||
|
|
||||||
$this->echoBox("Number of MAL list items: {$malCount}");
|
public function syncAnime()
|
||||||
$this->echoBox("Number of Kitsu list items: {$kitsuCount}");
|
{
|
||||||
|
$malCount = count($this->malModel->getAnimeList());
|
||||||
|
$kitsuCount = $this->kitsuModel->getAnimeListCount();
|
||||||
|
|
||||||
|
$this->echoBox("Number of MAL anime list items: {$malCount}");
|
||||||
|
$this->echoBox("Number of Kitsu anime list items: {$kitsuCount}");
|
||||||
|
|
||||||
$data = $this->diffAnimeLists();
|
$data = $this->diffAnimeLists();
|
||||||
$this->echoBox("Number of items that need to be added to MAL: " . count($data));
|
|
||||||
|
$this->echoBox("Number of anime items that need to be added to MAL: " . count($data['addToMAL']));
|
||||||
|
|
||||||
if ( ! empty($data['addToMAL']))
|
if ( ! empty($data['addToMAL']))
|
||||||
{
|
{
|
||||||
$this->echoBox("Adding missing list items to MAL");
|
$this->echoBox("Adding missing anime list items to MAL");
|
||||||
$this->createMALAnimeListItems($data['addToMAL']);
|
$this->createMALListItems($data['addToMAL'], 'anime');
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getKitsuAnimeList()
|
$this->echoBox('Number of anime items that need to be added to Kitsu: ' . count($data['addToKitsu']));
|
||||||
|
|
||||||
|
if ( ! empty($data['addToKitsu']))
|
||||||
{
|
{
|
||||||
$count = $this->getKitsuAnimeListPageCount();
|
$this->echoBox("Adding missing anime list items to Kitsu");
|
||||||
$size = 100;
|
$this->createKitusListItems($data['addToKitsu'], 'anime');
|
||||||
$pages = ceil($count / $size);
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$requests = [];
|
public function syncManga()
|
||||||
|
|
||||||
// Set up requests
|
|
||||||
for ($i = 0; $i < $pages; $i++)
|
|
||||||
{
|
{
|
||||||
$offset = $i * $size;
|
$malCount = count($this->malModel->getMangaList());
|
||||||
$requests[] = $this->kitsuModel->getPagedAnimeList($size, $offset);
|
$kitsuCount = $this->kitsuModel->getMangaListCount();
|
||||||
}
|
|
||||||
|
|
||||||
$promiseArray = (new Client())->requestMulti($requests);
|
$this->echoBox("Number of MAL manga list items: {$malCount}");
|
||||||
|
$this->echoBox("Number of Kitsu manga list items: {$kitsuCount}");
|
||||||
|
|
||||||
$responses = wait(all($promiseArray));
|
$data = $this->diffMangaLists();
|
||||||
$output = [];
|
|
||||||
|
|
||||||
foreach($responses as $response)
|
$this->echoBox("Number of manga items that need to be added to MAL: " . count($data['addToMAL']));
|
||||||
|
|
||||||
|
if ( ! empty($data['addToMAL']))
|
||||||
{
|
{
|
||||||
$data = Json::decode($response->getBody());
|
$this->echoBox("Adding missing manga list items to MAL");
|
||||||
$output = array_merge_recursive($output, $data);
|
$this->createMALListItems($data['addToMAL'], 'manga');
|
||||||
}
|
}
|
||||||
|
|
||||||
return $output;
|
$this->echoBox('Number of manga items that need to be added to Kitsu: ' . count($data['addToKitsu']));
|
||||||
}
|
|
||||||
|
|
||||||
public function getMALAnimeList()
|
if ( ! empty($data['addToKitsu']))
|
||||||
{
|
{
|
||||||
return $this->malModel->getFullList();
|
$this->echoBox("Adding missing manga list items to Kitsu");
|
||||||
|
$this->createKitsuListItems($data['addToKitsu'], 'manga');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function filterMappings(array $includes): array
|
public function filterMappings(array $includes, string $type = 'anime'): array
|
||||||
{
|
{
|
||||||
$output = [];
|
$output = [];
|
||||||
|
|
||||||
foreach($includes as $id => $mapping)
|
foreach($includes as $id => $mapping)
|
||||||
{
|
{
|
||||||
if ($mapping['externalSite'] === 'myanimelist/anime')
|
if ($mapping['externalSite'] === "myanimelist/{$type}")
|
||||||
{
|
{
|
||||||
$output[$id] = $mapping;
|
$output[$id] = $mapping;
|
||||||
}
|
}
|
||||||
@ -122,7 +138,7 @@ class SyncKitsuWithMal extends BaseCommand {
|
|||||||
|
|
||||||
public function formatMALAnimeList()
|
public function formatMALAnimeList()
|
||||||
{
|
{
|
||||||
$orig = $this->getMALAnimeList();
|
$orig = $this->malModel->getAnimeList();
|
||||||
$output = [];
|
$output = [];
|
||||||
|
|
||||||
foreach($orig as $item)
|
foreach($orig as $item)
|
||||||
@ -137,7 +153,37 @@ class SyncKitsuWithMal extends BaseCommand {
|
|||||||
? $item['times_rewatched']
|
? $item['times_rewatched']
|
||||||
: 0,
|
: 0,
|
||||||
// 'notes' => ,
|
// 'notes' => ,
|
||||||
'rating' => $item['my_score'],
|
'rating' => $item['my_score'] / 2,
|
||||||
|
'updatedAt' => (new \DateTime())
|
||||||
|
->setTimestamp((int)$item['my_last_updated'])
|
||||||
|
->format(\DateTime::W3C),
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $output;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function formatMALMangaList()
|
||||||
|
{
|
||||||
|
$orig = $this->malModel->getMangaList();
|
||||||
|
$output = [];
|
||||||
|
|
||||||
|
foreach($orig as $item)
|
||||||
|
{
|
||||||
|
$output[$item['series_mangadb_id']] = [
|
||||||
|
'id' => $item['series_mangadb_id'],
|
||||||
|
'data' => [
|
||||||
|
'my_status' => $item['my_status'],
|
||||||
|
'status' => MangaReadingStatus::MAL_TO_KITSU[$item['my_status']],
|
||||||
|
'progress' => $item['my_read_chapters'],
|
||||||
|
'volumes' => $item['my_read_volumes'],
|
||||||
|
'reconsuming' => (bool) $item['my_rereadingg'],
|
||||||
|
/* 'reconsumeCount' => array_key_exists('times_rewatched', $item)
|
||||||
|
? $item['times_rewatched']
|
||||||
|
: 0, */
|
||||||
|
// 'notes' => ,
|
||||||
|
'rating' => $item['my_score'] / 2,
|
||||||
'updatedAt' => (new \DateTime())
|
'updatedAt' => (new \DateTime())
|
||||||
->setTimestamp((int)$item['my_last_updated'])
|
->setTimestamp((int)$item['my_last_updated'])
|
||||||
->format(\DateTime::W3C),
|
->format(\DateTime::W3C),
|
||||||
@ -186,9 +232,84 @@ class SyncKitsuWithMal extends BaseCommand {
|
|||||||
return $output;
|
return $output;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getKitsuAnimeListPageCount()
|
public function filterKitsuMangaList()
|
||||||
{
|
{
|
||||||
return $this->kitsuModel->getAnimeListCount();
|
$data = $this->kitsuModel->getFullMangaList();
|
||||||
|
$includes = JsonAPI::organizeIncludes($data['included']);
|
||||||
|
$includes['mappings'] = $this->filterMappings($includes['mappings'], 'manga');
|
||||||
|
|
||||||
|
$output = [];
|
||||||
|
|
||||||
|
foreach($data['data'] as $listItem)
|
||||||
|
{
|
||||||
|
$mangaId = $listItem['relationships']['manga']['data']['id'];
|
||||||
|
$potentialMappings = $includes['manga'][$mangaId]['relationships']['mappings'];
|
||||||
|
$malId = NULL;
|
||||||
|
|
||||||
|
foreach ($potentialMappings as $mappingId)
|
||||||
|
{
|
||||||
|
if (array_key_exists($mappingId, $includes['mappings']))
|
||||||
|
{
|
||||||
|
$malId = $includes['mappings'][$mappingId]['externalId'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip to the next item if there isn't a MAL ID
|
||||||
|
if (is_null($malId))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$output[$listItem['id']] = [
|
||||||
|
'id' => $listItem['id'],
|
||||||
|
'malId' => $malId,
|
||||||
|
'data' => $listItem['attributes'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $output;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function diffMangaLists()
|
||||||
|
{
|
||||||
|
$kitsuList = $this->filterKitsuMangaList();
|
||||||
|
$malList = $this->formatMALMangaList();
|
||||||
|
|
||||||
|
$itemsToAddToMAL = [];
|
||||||
|
$itemsToAddToKitsu = [];
|
||||||
|
|
||||||
|
$malIds = array_column($malList, 'id');
|
||||||
|
$kitsuMalIds = array_column($kitsuList, 'malId');
|
||||||
|
$missingMalIds = array_diff($malIds, $kitsuMalIds);
|
||||||
|
|
||||||
|
foreach($missingMalIds as $mid)
|
||||||
|
{
|
||||||
|
$itemsToAddToKitsu[] = array_merge($malList[$mid]['data'], [
|
||||||
|
'id' => $this->kitsuModel->getKitsuIdFromMALId($mid, 'manga'),
|
||||||
|
'type' => 'manga'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach($kitsuList as $kitsuItem)
|
||||||
|
{
|
||||||
|
if (in_array($kitsuItem['malId'], $malIds))
|
||||||
|
{
|
||||||
|
// Eventually, compare the list entries, and determine which
|
||||||
|
// needs to be updated
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Looks like this item only exists on Kitsu
|
||||||
|
$itemsToAddToMAL[] = [
|
||||||
|
'mal_id' => $kitsuItem['malId'],
|
||||||
|
'data' => $kitsuItem['data']
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'addToMAL' => $itemsToAddToMAL,
|
||||||
|
'addToKitsu' => $itemsToAddToKitsu
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function diffAnimeLists()
|
public function diffAnimeLists()
|
||||||
@ -201,10 +322,26 @@ class SyncKitsuWithMal extends BaseCommand {
|
|||||||
$malList = $this->formatMALAnimeList();
|
$malList = $this->formatMALAnimeList();
|
||||||
|
|
||||||
$itemsToAddToMAL = [];
|
$itemsToAddToMAL = [];
|
||||||
|
$itemsToAddToKitsu = [];
|
||||||
|
$malUpdateItems = [];
|
||||||
|
$kitsuUpdateItems = [];
|
||||||
|
|
||||||
|
$malIds = array_column($malList, 'id');
|
||||||
|
$kitsuMalIds = array_column($kitsuList, 'malId');
|
||||||
|
$missingMalIds = array_diff($malIds, $kitsuMalIds);
|
||||||
|
|
||||||
|
foreach($missingMalIds as $mid)
|
||||||
|
{
|
||||||
|
// print_r($malList[$mid]);
|
||||||
|
$itemsToAddToKitsu[] = array_merge($malList[$mid]['data'], [
|
||||||
|
'id' => $this->kitsuModel->getKitsuIdFromMALId($mid),
|
||||||
|
'type' => 'anime'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
foreach($kitsuList as $kitsuItem)
|
foreach($kitsuList as $kitsuItem)
|
||||||
{
|
{
|
||||||
if (array_key_exists($kitsuItem['malId'], $malList))
|
if (in_array($kitsuItem['malId'], $malIds))
|
||||||
{
|
{
|
||||||
// Eventually, compare the list entries, and determine which
|
// Eventually, compare the list entries, and determine which
|
||||||
// needs to be updated
|
// needs to be updated
|
||||||
@ -230,34 +367,60 @@ class SyncKitsuWithMal extends BaseCommand {
|
|||||||
|
|
||||||
return [
|
return [
|
||||||
'addToMAL' => $itemsToAddToMAL,
|
'addToMAL' => $itemsToAddToMAL,
|
||||||
|
'updateMAL' => $malUpdateItems,
|
||||||
|
'addToKitsu' => $itemsToAddToKitsu,
|
||||||
|
'updateKitsu' => $kitsuUpdateItems
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function createMALAnimeListItems($itemsToAdd)
|
public function createKitusAnimeListItems($itemsToAdd, $type = 'anime')
|
||||||
|
{
|
||||||
|
$requester = new ParallelAPIRequest();
|
||||||
|
foreach($itemsToAdd as $item)
|
||||||
|
{
|
||||||
|
$requester->addRequest($this->kitsuModel->createListItem($item));
|
||||||
|
}
|
||||||
|
|
||||||
|
$responses = $requester->makeRequests();
|
||||||
|
|
||||||
|
foreach($responses as $key => $response)
|
||||||
|
{
|
||||||
|
$id = $itemsToAdd[$key]['id'];
|
||||||
|
if ($response->getStatus() === 201)
|
||||||
|
{
|
||||||
|
$this->echoBox("Successfully created Kitsu {$type} list item with id: {$id}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
echo $response->getBody();
|
||||||
|
$this->echoBox("Failed to create Kitsu {$type} list item with id: {$id}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createMALListItems($itemsToAdd, $type = 'anime')
|
||||||
{
|
{
|
||||||
$transformer = new ALT();
|
$transformer = new ALT();
|
||||||
$requests = [];
|
$requester = new ParallelAPIRequest();
|
||||||
|
|
||||||
foreach($itemsToAdd as $item)
|
foreach($itemsToAdd as $item)
|
||||||
{
|
{
|
||||||
$data = $transformer->untransform($item);
|
$data = $transformer->untransform($item);
|
||||||
$requests[] = $this->malModel->createFullListItem($data);
|
$requester->addRequest($this->malModel->createFullListItem($data, $type));
|
||||||
}
|
}
|
||||||
|
|
||||||
$promiseArray = (new Client())->requestMulti($requests);
|
$responses = $requester->makeRequests();
|
||||||
|
|
||||||
$responses = wait(all($promiseArray));
|
|
||||||
|
|
||||||
foreach($responses as $key => $response)
|
foreach($responses as $key => $response)
|
||||||
{
|
{
|
||||||
$id = $itemsToAdd[$key]['mal_id'];
|
$id = $itemsToAdd[$key]['mal_id'];
|
||||||
if ($response->getBody() === 'Created')
|
if ($response->getBody() === 'Created')
|
||||||
{
|
{
|
||||||
$this->echoBox("Successfully create list item with id: {$id}");
|
$this->echoBox("Successfully created MAL {$type} list item with id: {$id}");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
$this->echoBox("Failed to create list item with id: {$id}");
|
$this->echoBox("Failed to create MAL {$type} list item with id: {$id}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ namespace Aviat\AnimeClient;
|
|||||||
|
|
||||||
use const Aviat\AnimeClient\SESSION_SEGMENT;
|
use const Aviat\AnimeClient\SESSION_SEGMENT;
|
||||||
|
|
||||||
use function Aviat\AnimeClient\_dir;
|
use function Aviat\Ion\_dir;
|
||||||
|
|
||||||
use Aviat\AnimeClient\API\JsonAPI;
|
use Aviat\AnimeClient\API\JsonAPI;
|
||||||
use Aviat\Ion\Di\{ContainerAware, ContainerInterface};
|
use Aviat\Ion\Di\{ContainerAware, ContainerInterface};
|
||||||
@ -31,7 +31,66 @@ use InvalidArgumentException;
|
|||||||
* @property Response object $response
|
* @property Response object $response
|
||||||
*/
|
*/
|
||||||
class Controller {
|
class Controller {
|
||||||
use ControllerTrait;
|
|
||||||
|
use ContainerAware;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cache manager
|
||||||
|
* @var \Psr\Cache\CacheItemPoolInterface
|
||||||
|
*/
|
||||||
|
protected $cache;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The global configuration object
|
||||||
|
* @var \Aviat\Ion\ConfigInterface $config
|
||||||
|
*/
|
||||||
|
public $config;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request object
|
||||||
|
* @var object $request
|
||||||
|
*/
|
||||||
|
protected $request;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response object
|
||||||
|
* @var object $response
|
||||||
|
*/
|
||||||
|
public $response;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The api model for the current controller
|
||||||
|
* @var object
|
||||||
|
*/
|
||||||
|
protected $model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Url generation class
|
||||||
|
* @var UrlGenerator
|
||||||
|
*/
|
||||||
|
protected $urlGenerator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Aura url generator
|
||||||
|
* @var \Aura\Router\Generator
|
||||||
|
*/
|
||||||
|
protected $url;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Session segment
|
||||||
|
* @var \Aura\Session\Segment
|
||||||
|
*/
|
||||||
|
protected $session;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Common data to be sent to views
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $baseData = [
|
||||||
|
'url_type' => 'anime',
|
||||||
|
'other_type' => 'manga',
|
||||||
|
'menu_name' => ''
|
||||||
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
@ -55,6 +114,7 @@ class Controller {
|
|||||||
'config' => $this->config
|
'config' => $this->config
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
$this->url = $auraUrlGenerator;
|
||||||
$this->urlGenerator = $urlGenerator;
|
$this->urlGenerator = $urlGenerator;
|
||||||
|
|
||||||
$session = $container->get('session');
|
$session = $container->get('session');
|
||||||
@ -72,81 +132,267 @@ class Controller {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show the user profile page
|
* Redirect to the previous page
|
||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function me()
|
public function redirectToPrevious()
|
||||||
{
|
{
|
||||||
$username = $this->config->get(['kitsu_username']);
|
$previous = $this->session->getFlash('previous');
|
||||||
$model = $this->container->get('kitsu-model');
|
$this->redirect($previous, 303);
|
||||||
$data = $model->getUserData($username);
|
}
|
||||||
$included = JsonAPI::lightlyOrganizeIncludes($data['included']);
|
|
||||||
$relationships = JsonAPI::fillRelationshipsFromIncludes($data['data']['relationships'], $included);
|
/**
|
||||||
$this->outputHTML('me', [
|
* Set the current url in the session as the target of a future redirect
|
||||||
'title' => 'About' . $this->config->get('whose_list'),
|
*
|
||||||
'attributes' => $data['data']['attributes'],
|
* @param string|null $url
|
||||||
'relationships' => $relationships,
|
* @return void
|
||||||
'included' => $included
|
*/
|
||||||
|
public function setSessionRedirect(string $url = NULL)
|
||||||
|
{
|
||||||
|
$serverParams = $this->request->getServerParams();
|
||||||
|
|
||||||
|
if ( ! array_key_exists('HTTP_REFERER', $serverParams))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$util = $this->container->get('util');
|
||||||
|
$doubleFormPage = $serverParams['HTTP_REFERER'] === $this->request->getUri();
|
||||||
|
|
||||||
|
// Don't attempt to set the redirect url if
|
||||||
|
// the page is one of the form type pages,
|
||||||
|
// and the previous page is also a form type page_segments
|
||||||
|
if ($doubleFormPage)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_null($url))
|
||||||
|
{
|
||||||
|
$url = $util->isViewPage()
|
||||||
|
? $this->request->url->get()
|
||||||
|
: $serverParams['HTTP_REFERER'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->session->set('redirect_url', $url);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redirect to the url previously set in the session
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function sessionRedirect()
|
||||||
|
{
|
||||||
|
$target = $this->session->get('redirect_url');
|
||||||
|
if (empty($target))
|
||||||
|
{
|
||||||
|
$this->notFound();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$this->redirect($target, 303);
|
||||||
|
$this->session->set('redirect_url', NULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the string output of a partial template
|
||||||
|
*
|
||||||
|
* @param HtmlView $view
|
||||||
|
* @param string $template
|
||||||
|
* @param array $data
|
||||||
|
* @throws InvalidArgumentException
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function loadPartial($view, string $template, array $data = [])
|
||||||
|
{
|
||||||
|
$router = $this->container->get('dispatcher');
|
||||||
|
|
||||||
|
if (isset($this->baseData))
|
||||||
|
{
|
||||||
|
$data = array_merge($this->baseData, $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
$route = $router->getRoute();
|
||||||
|
$data['route_path'] = $route ? $router->getRoute()->path : '';
|
||||||
|
|
||||||
|
|
||||||
|
$templatePath = _dir($this->config->get('view_path'), "{$template}.php");
|
||||||
|
|
||||||
|
if ( ! is_file($templatePath))
|
||||||
|
{
|
||||||
|
throw new InvalidArgumentException("Invalid template : {$template}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return $view->renderTemplate($templatePath, (array)$data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render a template with header and footer
|
||||||
|
*
|
||||||
|
* @param HtmlView $view
|
||||||
|
* @param string $template
|
||||||
|
* @param array $data
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
protected function renderFullPage($view, string $template, array $data)
|
||||||
|
{
|
||||||
|
$view->appendOutput($this->loadPartial($view, 'header', $data));
|
||||||
|
|
||||||
|
if (array_key_exists('message', $data) && is_array($data['message']))
|
||||||
|
{
|
||||||
|
$view->appendOutput($this->loadPartial($view, 'message', $data['message']));
|
||||||
|
}
|
||||||
|
|
||||||
|
$view->appendOutput($this->loadPartial($view, $template, $data));
|
||||||
|
$view->appendOutput($this->loadPartial($view, 'footer', $data));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 404 action
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function notFound(
|
||||||
|
string $title = 'Sorry, page not found',
|
||||||
|
string $message = 'Page Not Found'
|
||||||
|
)
|
||||||
|
{
|
||||||
|
$this->outputHTML('404', [
|
||||||
|
'title' => $title,
|
||||||
|
'message' => $message,
|
||||||
|
], NULL, 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display a generic error page
|
||||||
|
*
|
||||||
|
* @param int $httpCode
|
||||||
|
* @param string $title
|
||||||
|
* @param string $message
|
||||||
|
* @param string $long_message
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function errorPage(int $httpCode, string $title, string $message, string $long_message = "")
|
||||||
|
{
|
||||||
|
$this->outputHTML('error', [
|
||||||
|
'title' => $title,
|
||||||
|
'message' => $message,
|
||||||
|
'long_message' => $long_message
|
||||||
|
], NULL, $httpCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redirect to the default controller/url from an empty path
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function redirectToDefaultRoute()
|
||||||
|
{
|
||||||
|
$defaultType = $this->config->get(['routes', 'route_config', 'default_list']) ?? 'anime';
|
||||||
|
$this->redirect($this->urlGenerator->defaultUrl($defaultType), 303);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a session flash variable to display a message on
|
||||||
|
* next page load
|
||||||
|
*
|
||||||
|
* @param string $message
|
||||||
|
* @param string $type
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function setFlashMessage(string $message, string $type = "info")
|
||||||
|
{
|
||||||
|
static $messages;
|
||||||
|
|
||||||
|
if ( ! $messages)
|
||||||
|
{
|
||||||
|
$messages = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$messages[] = [
|
||||||
|
'message_type' => $type,
|
||||||
|
'message' => $message
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->session->setFlash('message', $messages);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper for consistent page titles
|
||||||
|
*
|
||||||
|
* @param string ...$parts Title segements
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function formatTitle(string ...$parts) : string
|
||||||
|
{
|
||||||
|
return implode(' · ', $parts);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a message box to the page
|
||||||
|
*
|
||||||
|
* @param HtmlView $view
|
||||||
|
* @param string $type
|
||||||
|
* @param string $message
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function showMessage($view, string $type, string $message): string
|
||||||
|
{
|
||||||
|
return $this->loadPartial($view, 'message', [
|
||||||
|
'message_type' => $type,
|
||||||
|
'message' => $message
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show the login form
|
* Output a template to HTML, using the provided data
|
||||||
*
|
*
|
||||||
* @param string $status
|
* @param string $template
|
||||||
|
* @param array $data
|
||||||
|
* @param HtmlView|null $view
|
||||||
|
* @param int $code
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function login(string $status = '')
|
protected function outputHTML(string $template, array $data = [], $view = NULL, int $code = 200)
|
||||||
|
{
|
||||||
|
if (is_null($view))
|
||||||
{
|
{
|
||||||
$message = '';
|
|
||||||
|
|
||||||
$view = new HtmlView($this->container);
|
$view = new HtmlView($this->container);
|
||||||
|
|
||||||
if ($status !== '')
|
|
||||||
{
|
|
||||||
$message = $this->showMessage($view, 'error', $status);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the redirect url
|
$view->setStatusCode($code);
|
||||||
$this->setSessionRedirect();
|
$this->renderFullPage($view, $template, $data);
|
||||||
|
|
||||||
$this->outputHTML('login', [
|
|
||||||
'title' => 'Api login',
|
|
||||||
'message' => $message
|
|
||||||
], $view);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempt login authentication
|
* Output a JSON Response
|
||||||
*
|
*
|
||||||
|
* @param mixed $data
|
||||||
|
* @param int $code - the http status code
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function loginAction()
|
protected function outputJSON($data = 'Empty response', int $code = 200)
|
||||||
{
|
{
|
||||||
$auth = $this->container->get('auth');
|
(new JsonView($this->container))
|
||||||
$post = $this->request->getParsedBody();
|
->setStatusCode($code)
|
||||||
if ($auth->authenticate($post['password']))
|
->setOutput($data)
|
||||||
{
|
->send();
|
||||||
$this->sessionRedirect();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->setFlashMessage('Invalid username or password.');
|
|
||||||
$this->redirect($this->urlGenerator->url('login'), 303);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deauthorize the current user
|
* Redirect to the selected page
|
||||||
*
|
*
|
||||||
|
* @param string $url
|
||||||
|
* @param int $code
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function logout()
|
protected function redirect(string $url, int $code)
|
||||||
{
|
{
|
||||||
$auth = $this->container->get('auth');
|
$http = new HttpView($this->container);
|
||||||
$auth->logout();
|
$http->redirect($url, $code);
|
||||||
|
|
||||||
$this->redirectToDefaultRoute();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// End of BaseController.php
|
// End of BaseController.php
|
@ -116,7 +116,7 @@ class Anime extends BaseController {
|
|||||||
$this->config->get('whose_list') . "'s Anime List",
|
$this->config->get('whose_list') . "'s Anime List",
|
||||||
'Add'
|
'Add'
|
||||||
),
|
),
|
||||||
'action_url' => $this->urlGenerator->url('anime/add'),
|
'action_url' => $this->url->generate('anime.add.post'),
|
||||||
'status_list' => AnimeWatchingStatus::KITSU_TO_TITLE
|
'status_list' => AnimeWatchingStatus::KITSU_TO_TITLE
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@ -168,8 +168,9 @@ class Anime extends BaseController {
|
|||||||
),
|
),
|
||||||
'item' => $item,
|
'item' => $item,
|
||||||
'statuses' => AnimeWatchingStatus::KITSU_TO_TITLE,
|
'statuses' => AnimeWatchingStatus::KITSU_TO_TITLE,
|
||||||
'action' => $this->container->get('url-generator')
|
'action' => $this->url->generate('update.post', [
|
||||||
->url('/anime/update_form'),
|
'controller' => 'anime'
|
||||||
|
]),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,18 +41,6 @@ class Collection extends BaseController {
|
|||||||
*/
|
*/
|
||||||
private $animeModel;
|
private $animeModel;
|
||||||
|
|
||||||
/**
|
|
||||||
* Data to be sent to all routes in this controller
|
|
||||||
* @var array $baseData
|
|
||||||
*/
|
|
||||||
protected $baseData;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Url Generator class
|
|
||||||
* @var UrlGenerator
|
|
||||||
*/
|
|
||||||
protected $urlGenerator;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
*
|
*
|
||||||
@ -62,7 +50,6 @@ class Collection extends BaseController {
|
|||||||
{
|
{
|
||||||
parent::__construct($container);
|
parent::__construct($container);
|
||||||
|
|
||||||
$this->urlGenerator = $container->get('url-generator');
|
|
||||||
$this->animeModel = $container->get('anime-model');
|
$this->animeModel = $container->get('anime-model');
|
||||||
$this->animeCollectionModel = $container->get('anime-collection-model');
|
$this->animeCollectionModel = $container->get('anime-collection-model');
|
||||||
$this->baseData = array_merge($this->baseData, [
|
$this->baseData = array_merge($this->baseData, [
|
||||||
@ -118,10 +105,11 @@ class Collection extends BaseController {
|
|||||||
$this->setSessionRedirect();
|
$this->setSessionRedirect();
|
||||||
|
|
||||||
$action = (is_null($id)) ? "Add" : "Edit";
|
$action = (is_null($id)) ? "Add" : "Edit";
|
||||||
|
$urlAction = strtolower($action);
|
||||||
|
|
||||||
$this->outputHTML('collection/' . strtolower($action), [
|
$this->outputHTML('collection/' . $urlAction, [
|
||||||
'action' => $action,
|
'action' => $action,
|
||||||
'action_url' => $this->urlGenerator->fullUrl('collection/' . strtolower($action)),
|
'action_url' => $this->url->generate("collection.{$urlAction}.post"),
|
||||||
'title' => $this->formatTitle(
|
'title' => $this->formatTitle(
|
||||||
$this->config->get('whose_list') . "'s Anime Collection",
|
$this->config->get('whose_list') . "'s Anime Collection",
|
||||||
$action
|
$action
|
||||||
|
138
src/Controller/Index.php
Normal file
138
src/Controller/Index.php
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* Hummingbird Anime List Client
|
||||||
|
*
|
||||||
|
* An API client for Kitsu and MyAnimeList to manage anime and manga watch lists
|
||||||
|
*
|
||||||
|
* PHP version 7
|
||||||
|
*
|
||||||
|
* @package HummingbirdAnimeClient
|
||||||
|
* @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://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Aviat\AnimeClient\Controller;
|
||||||
|
|
||||||
|
use Aviat\AnimeClient\Controller as BaseController;
|
||||||
|
use Aviat\AnimeClient\API\JsonAPI;
|
||||||
|
use Aviat\Ion\View\HtmlView;
|
||||||
|
|
||||||
|
class Index extends BaseController {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Purges the API cache
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function clearCache()
|
||||||
|
{
|
||||||
|
$this->cache->clear();
|
||||||
|
$this->outputHTML('blank', [
|
||||||
|
'title' => 'Cache cleared'
|
||||||
|
], NULL, 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the login form
|
||||||
|
*
|
||||||
|
* @param string $status
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function login(string $status = '')
|
||||||
|
{
|
||||||
|
$message = '';
|
||||||
|
|
||||||
|
$view = new HtmlView($this->container);
|
||||||
|
|
||||||
|
if ($status !== '')
|
||||||
|
{
|
||||||
|
$message = $this->showMessage($view, 'error', $status);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the redirect url
|
||||||
|
$this->setSessionRedirect();
|
||||||
|
|
||||||
|
$this->outputHTML('login', [
|
||||||
|
'title' => 'Api login',
|
||||||
|
'message' => $message
|
||||||
|
], $view);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempt login authentication
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function loginAction()
|
||||||
|
{
|
||||||
|
$auth = $this->container->get('auth');
|
||||||
|
$post = $this->request->getParsedBody();
|
||||||
|
if ($auth->authenticate($post['password']))
|
||||||
|
{
|
||||||
|
$this->sessionRedirect();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->setFlashMessage('Invalid username or password.');
|
||||||
|
$this->redirect($this->url->generate('login'), 303);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deauthorize the current user
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function logout()
|
||||||
|
{
|
||||||
|
$auth = $this->container->get('auth');
|
||||||
|
$auth->logout();
|
||||||
|
|
||||||
|
$this->redirectToDefaultRoute();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the user profile page
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function me()
|
||||||
|
{
|
||||||
|
$username = $this->config->get(['kitsu_username']);
|
||||||
|
$model = $this->container->get('kitsu-model');
|
||||||
|
$data = $model->getUserData($username);
|
||||||
|
$orgData = JsonAPI::organizeData($data);
|
||||||
|
$this->outputHTML('me', [
|
||||||
|
'title' => 'About' . $this->config->get('whose_list'),
|
||||||
|
'data' => $orgData[0],
|
||||||
|
'attributes' => $orgData[0]['attributes'],
|
||||||
|
'relationships' => $orgData[0]['relationships'],
|
||||||
|
'favorites' => $this->organizeFavorites($orgData[0]['relationships']['favorites']),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function organizeFavorites(array $rawfavorites): array
|
||||||
|
{
|
||||||
|
// return $rawfavorites;
|
||||||
|
$output = [];
|
||||||
|
|
||||||
|
foreach($rawfavorites as $item)
|
||||||
|
{
|
||||||
|
$rank = $item['attributes']['favRank'];
|
||||||
|
foreach($item['relationships']['item'] as $key => $fav)
|
||||||
|
{
|
||||||
|
$output[$key] = $output[$key] ?? [];
|
||||||
|
foreach ($fav as $id => $data)
|
||||||
|
{
|
||||||
|
$output[$key][$rank] = $data['attributes'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ksort($output[$key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $output;
|
||||||
|
}
|
||||||
|
}
|
@ -98,17 +98,7 @@ class Manga extends Controller {
|
|||||||
*/
|
*/
|
||||||
public function addForm()
|
public function addForm()
|
||||||
{
|
{
|
||||||
$raw_status_list = MangaReadingStatus::getConstList();
|
$statuses = MangaReadingStatus::KITSU_TO_TITLE;
|
||||||
|
|
||||||
$statuses = [];
|
|
||||||
|
|
||||||
foreach ($raw_status_list as $status_item)
|
|
||||||
{
|
|
||||||
$statuses[$status_item] = (string)$this->string($status_item)
|
|
||||||
->underscored()
|
|
||||||
->humanize()
|
|
||||||
->titleize();
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->setSessionRedirect();
|
$this->setSessionRedirect();
|
||||||
$this->outputHTML('manga/add', [
|
$this->outputHTML('manga/add', [
|
||||||
@ -116,7 +106,7 @@ class Manga extends Controller {
|
|||||||
$this->config->get('whose_list') . "'s Manga List",
|
$this->config->get('whose_list') . "'s Manga List",
|
||||||
'Add'
|
'Add'
|
||||||
),
|
),
|
||||||
'action_url' => $this->urlGenerator->url('manga/add'),
|
'action_url' => $this->url->generate('manga.add.post'),
|
||||||
'status_list' => $statuses
|
'status_list' => $statuses
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@ -169,8 +159,9 @@ class Manga extends Controller {
|
|||||||
'title' => $title,
|
'title' => $title,
|
||||||
'status_list' => MangaReadingStatus::KITSU_TO_TITLE,
|
'status_list' => MangaReadingStatus::KITSU_TO_TITLE,
|
||||||
'item' => $item,
|
'item' => $item,
|
||||||
'action' => $this->container->get('url-generator')
|
'action' => $this->url->generate('update.post', [
|
||||||
->url('/manga/update_form'),
|
'controller' => 'manga'
|
||||||
|
]),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -221,7 +212,7 @@ class Manga extends Controller {
|
|||||||
*/
|
*/
|
||||||
public function update()
|
public function update()
|
||||||
{
|
{
|
||||||
if ($this->request->getHeader('content-type')[0] === 'application/json')
|
if (stripos($this->request->getHeader('content-type')[0], 'application/json') !== FALSE)
|
||||||
{
|
{
|
||||||
$data = Json::decode((string)$this->request->getBody());
|
$data = Json::decode((string)$this->request->getBody());
|
||||||
}
|
}
|
||||||
@ -245,7 +236,8 @@ class Manga extends Controller {
|
|||||||
{
|
{
|
||||||
$body = $this->request->getParsedBody();
|
$body = $this->request->getParsedBody();
|
||||||
$id = $body['id'];
|
$id = $body['id'];
|
||||||
$response = $this->model->deleteLibraryItem($id);
|
$malId = $body['mal_id'];
|
||||||
|
$response = $this->model->deleteLibraryItem($id, $malId);
|
||||||
|
|
||||||
if ($response)
|
if ($response)
|
||||||
{
|
{
|
||||||
|
@ -1,378 +0,0 @@
|
|||||||
<?php declare(strict_types=1);
|
|
||||||
/**
|
|
||||||
* Hummingbird Anime List Client
|
|
||||||
*
|
|
||||||
* An API client for Kitsu and MyAnimeList to manage anime and manga watch lists
|
|
||||||
*
|
|
||||||
* PHP version 7
|
|
||||||
*
|
|
||||||
* @package HummingbirdAnimeClient
|
|
||||||
* @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://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Aviat\AnimeClient;
|
|
||||||
|
|
||||||
use const Aviat\AnimeClient\SESSION_SEGMENT;
|
|
||||||
|
|
||||||
use function Aviat\AnimeClient\_dir;
|
|
||||||
|
|
||||||
use Aviat\AnimeClient\API\JsonAPI;
|
|
||||||
use Aviat\Ion\Di\{ContainerAware, ContainerInterface};
|
|
||||||
use Aviat\Ion\View\{HtmlView, HttpView, JsonView};
|
|
||||||
use InvalidArgumentException;
|
|
||||||
|
|
||||||
trait ControllerTrait {
|
|
||||||
|
|
||||||
use ContainerAware;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cache manager
|
|
||||||
* @var \Psr\Cache\CacheItemPoolInterface
|
|
||||||
*/
|
|
||||||
protected $cache;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The global configuration object
|
|
||||||
* @var \Aviat\Ion\ConfigInterface $config
|
|
||||||
*/
|
|
||||||
protected $config;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request object
|
|
||||||
* @var object $request
|
|
||||||
*/
|
|
||||||
protected $request;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Response object
|
|
||||||
* @var object $response
|
|
||||||
*/
|
|
||||||
protected $response;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The api model for the current controller
|
|
||||||
* @var object
|
|
||||||
*/
|
|
||||||
protected $model;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Url generation class
|
|
||||||
* @var UrlGenerator
|
|
||||||
*/
|
|
||||||
protected $urlGenerator;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Session segment
|
|
||||||
* @var \Aura\Session\Segment
|
|
||||||
*/
|
|
||||||
protected $session;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Common data to be sent to views
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
protected $baseData = [
|
|
||||||
'url_type' => 'anime',
|
|
||||||
'other_type' => 'manga',
|
|
||||||
'menu_name' => ''
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Redirect to the default controller/url from an empty path
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function redirectToDefaultRoute()
|
|
||||||
{
|
|
||||||
$defaultType = $this->config->get(['routes', 'route_config', 'default_list']);
|
|
||||||
$this->redirect($this->urlGenerator->defaultUrl($defaultType), 303);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Redirect to the previous page
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function redirectToPrevious()
|
|
||||||
{
|
|
||||||
$previous = $this->session->getFlash('previous');
|
|
||||||
$this->redirect($previous, 303);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the current url in the session as the target of a future redirect
|
|
||||||
*
|
|
||||||
* @param string|null $url
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function setSessionRedirect($url = NULL)
|
|
||||||
{
|
|
||||||
$serverParams = $this->request->getServerParams();
|
|
||||||
|
|
||||||
if ( ! array_key_exists('HTTP_REFERER', $serverParams))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$util = $this->container->get('util');
|
|
||||||
$doubleFormPage = $serverParams['HTTP_REFERER'] === $this->request->getUri();
|
|
||||||
|
|
||||||
// Don't attempt to set the redirect url if
|
|
||||||
// the page is one of the form type pages,
|
|
||||||
// and the previous page is also a form type page_segments
|
|
||||||
if ($doubleFormPage)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is_null($url))
|
|
||||||
{
|
|
||||||
$url = $util->isViewPage()
|
|
||||||
? $this->request->url->get()
|
|
||||||
: $serverParams['HTTP_REFERER'];
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->session->set('redirect_url', $url);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Redirect to the url previously set in the session
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function sessionRedirect()
|
|
||||||
{
|
|
||||||
$target = $this->session->get('redirect_url');
|
|
||||||
if (empty($target))
|
|
||||||
{
|
|
||||||
$this->notFound();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
$this->redirect($target, 303);
|
|
||||||
$this->session->set('redirect_url', NULL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a class member
|
|
||||||
*
|
|
||||||
* @param string $key
|
|
||||||
* @return mixed
|
|
||||||
*/
|
|
||||||
public function __get(string $key)
|
|
||||||
{
|
|
||||||
$allowed = ['response', 'config'];
|
|
||||||
|
|
||||||
if (in_array($key, $allowed))
|
|
||||||
{
|
|
||||||
return $this->$key;
|
|
||||||
}
|
|
||||||
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the string output of a partial template
|
|
||||||
*
|
|
||||||
* @param HtmlView $view
|
|
||||||
* @param string $template
|
|
||||||
* @param array $data
|
|
||||||
* @throws InvalidArgumentException
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
protected function loadPartial($view, $template, array $data = [])
|
|
||||||
{
|
|
||||||
$router = $this->container->get('dispatcher');
|
|
||||||
|
|
||||||
if (isset($this->baseData))
|
|
||||||
{
|
|
||||||
$data = array_merge($this->baseData, $data);
|
|
||||||
}
|
|
||||||
|
|
||||||
$route = $router->getRoute();
|
|
||||||
$data['route_path'] = $route ? $router->getRoute()->path : '';
|
|
||||||
|
|
||||||
|
|
||||||
$templatePath = _dir($this->config->get('view_path'), "{$template}.php");
|
|
||||||
|
|
||||||
if ( ! is_file($templatePath))
|
|
||||||
{
|
|
||||||
throw new InvalidArgumentException("Invalid template : {$template}");
|
|
||||||
}
|
|
||||||
|
|
||||||
return $view->renderTemplate($templatePath, (array)$data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Render a template with header and footer
|
|
||||||
*
|
|
||||||
* @param HtmlView $view
|
|
||||||
* @param string $template
|
|
||||||
* @param array $data
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
protected function renderFullPage($view, $template, array $data)
|
|
||||||
{
|
|
||||||
$view->appendOutput($this->loadPartial($view, 'header', $data));
|
|
||||||
|
|
||||||
if (array_key_exists('message', $data) && is_array($data['message']))
|
|
||||||
{
|
|
||||||
$view->appendOutput($this->loadPartial($view, 'message', $data['message']));
|
|
||||||
}
|
|
||||||
|
|
||||||
$view->appendOutput($this->loadPartial($view, $template, $data));
|
|
||||||
$view->appendOutput($this->loadPartial($view, 'footer', $data));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 404 action
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function notFound(
|
|
||||||
string $title = 'Sorry, page not found',
|
|
||||||
string $message = 'Page Not Found'
|
|
||||||
)
|
|
||||||
{
|
|
||||||
$this->outputHTML('404', [
|
|
||||||
'title' => $title,
|
|
||||||
'message' => $message,
|
|
||||||
], NULL, 404);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Display a generic error page
|
|
||||||
*
|
|
||||||
* @param int $httpCode
|
|
||||||
* @param string $title
|
|
||||||
* @param string $message
|
|
||||||
* @param string $long_message
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function errorPage($httpCode, $title, $message, $long_message = "")
|
|
||||||
{
|
|
||||||
$this->outputHTML('error', [
|
|
||||||
'title' => $title,
|
|
||||||
'message' => $message,
|
|
||||||
'long_message' => $long_message
|
|
||||||
], NULL, $httpCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set a session flash variable to display a message on
|
|
||||||
* next page load
|
|
||||||
*
|
|
||||||
* @param string $message
|
|
||||||
* @param string $type
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function setFlashMessage($message, $type = "info")
|
|
||||||
{
|
|
||||||
static $messages;
|
|
||||||
|
|
||||||
if ( ! $messages)
|
|
||||||
{
|
|
||||||
$messages = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
$messages[] = [
|
|
||||||
'message_type' => $type,
|
|
||||||
'message' => $message
|
|
||||||
];
|
|
||||||
|
|
||||||
$this->session->setFlash('message', $messages);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Purges the API cache
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function clearCache()
|
|
||||||
{
|
|
||||||
$this->cache->clear();
|
|
||||||
$this->outputHTML('blank', [
|
|
||||||
'title' => 'Cache cleared'
|
|
||||||
], NULL, 200);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper for consistent page titles
|
|
||||||
*
|
|
||||||
* @param string ...$parts Title segements
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function formatTitle(string ...$parts) : string
|
|
||||||
{
|
|
||||||
return implode(' · ', $parts);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a message box to the page
|
|
||||||
*
|
|
||||||
* @param HtmlView $view
|
|
||||||
* @param string $type
|
|
||||||
* @param string $message
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
protected function showMessage($view, $type, $message)
|
|
||||||
{
|
|
||||||
return $this->loadPartial($view, 'message', [
|
|
||||||
'message_type' => $type,
|
|
||||||
'message' => $message
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Output a template to HTML, using the provided data
|
|
||||||
*
|
|
||||||
* @param string $template
|
|
||||||
* @param array $data
|
|
||||||
* @param HtmlView|null $view
|
|
||||||
* @param int $code
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
protected function outputHTML($template, array $data = [], $view = NULL, $code = 200)
|
|
||||||
{
|
|
||||||
if (is_null($view))
|
|
||||||
{
|
|
||||||
$view = new HtmlView($this->container);
|
|
||||||
}
|
|
||||||
|
|
||||||
$view->setStatusCode($code);
|
|
||||||
$this->renderFullPage($view, $template, $data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Output a JSON Response
|
|
||||||
*
|
|
||||||
* @param mixed $data
|
|
||||||
* @param int $code - the http status code
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
protected function outputJSON($data = 'Empty response', int $code = 200)
|
|
||||||
{
|
|
||||||
(new JsonView($this->container))
|
|
||||||
->setStatusCode($code)
|
|
||||||
->setOutput($data)
|
|
||||||
->send();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Redirect to the selected page
|
|
||||||
*
|
|
||||||
* @param string $url
|
|
||||||
* @param int $code
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
protected function redirect($url, $code)
|
|
||||||
{
|
|
||||||
$http = new HttpView($this->container);
|
|
||||||
$http->redirect($url, $code);
|
|
||||||
}
|
|
||||||
}
|
|
@ -24,7 +24,7 @@ use const Aviat\AnimeClient\{
|
|||||||
SRC_DIR
|
SRC_DIR
|
||||||
};
|
};
|
||||||
|
|
||||||
use function Aviat\AnimeClient\_dir;
|
use function Aviat\Ion\_dir;
|
||||||
|
|
||||||
use Aviat\Ion\Di\ContainerInterface;
|
use Aviat\Ion\Di\ContainerInterface;
|
||||||
use Aviat\Ion\Friend;
|
use Aviat\Ion\Friend;
|
||||||
|
@ -16,10 +16,7 @@
|
|||||||
|
|
||||||
namespace Aviat\AnimeClient;
|
namespace Aviat\AnimeClient;
|
||||||
|
|
||||||
use Aviat\Ion\
|
use Aviat\Ion\{ArrayWrapper, StringWrapper};
|
||||||
{
|
|
||||||
ArrayWrapper, StringWrapper
|
|
||||||
};
|
|
||||||
use Aviat\Ion\Di\ContainerInterface;
|
use Aviat\Ion\Di\ContainerInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -21,6 +21,13 @@ namespace Aviat\AnimeClient\Model;
|
|||||||
*/
|
*/
|
||||||
class API extends AbstractModel {
|
class API extends AbstractModel {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to use the MAL api
|
||||||
|
*
|
||||||
|
* @var boolean
|
||||||
|
*/
|
||||||
|
protected $useMALAPI;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sort the list entries by their title
|
* Sort the list entries by their title
|
||||||
*
|
*
|
||||||
|
@ -39,13 +39,6 @@ class Anime extends API {
|
|||||||
*/
|
*/
|
||||||
protected $malModel;
|
protected $malModel;
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether to use the MAL api
|
|
||||||
*
|
|
||||||
* @var boolean
|
|
||||||
*/
|
|
||||||
protected $useMALAPI;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Anime constructor.
|
* Anime constructor.
|
||||||
*
|
*
|
||||||
@ -53,10 +46,10 @@ class Anime extends API {
|
|||||||
*/
|
*/
|
||||||
public function __construct(ContainerInterface $container)
|
public function __construct(ContainerInterface $container)
|
||||||
{
|
{
|
||||||
$config = $container->get('config');
|
|
||||||
$this->kitsuModel = $container->get('kitsu-model');
|
$this->kitsuModel = $container->get('kitsu-model');
|
||||||
$this->malModel = $container->get('mal-model');
|
$this->malModel = $container->get('mal-model');
|
||||||
|
|
||||||
|
$config = $container->get('config');
|
||||||
$this->useMALAPI = $config->get(['use_mal_api']) === TRUE;
|
$this->useMALAPI = $config->get(['use_mal_api']) === TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,7 +59,7 @@ class Anime extends API {
|
|||||||
* @param string $status
|
* @param string $status
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function getList($status)
|
public function getList($status): array
|
||||||
{
|
{
|
||||||
$data = $this->kitsuModel->getAnimeList($status);
|
$data = $this->kitsuModel->getAnimeList($status);
|
||||||
$this->sortByName($data, 'anime');
|
$this->sortByName($data, 'anime');
|
||||||
@ -79,7 +72,12 @@ class Anime extends API {
|
|||||||
return $output;
|
return $output;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getAllLists()
|
/**
|
||||||
|
* Get data for the 'all' anime page
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getAllLists(): array
|
||||||
{
|
{
|
||||||
$data = $this->kitsuModel->getFullOrganizedAnimeList();
|
$data = $this->kitsuModel->getFullOrganizedAnimeList();
|
||||||
|
|
||||||
@ -97,7 +95,7 @@ class Anime extends API {
|
|||||||
* @param string $slug
|
* @param string $slug
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function getAnime($slug)
|
public function getAnime(string $slug): array
|
||||||
{
|
{
|
||||||
return $this->kitsuModel->getAnime($slug);
|
return $this->kitsuModel->getAnime($slug);
|
||||||
}
|
}
|
||||||
@ -108,7 +106,7 @@ class Anime extends API {
|
|||||||
* @param string $animeId
|
* @param string $animeId
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function getAnimeById($animeId)
|
public function getAnimeById(string $animeId): array
|
||||||
{
|
{
|
||||||
return $this->kitsuModel->getAnimeById($animeId);
|
return $this->kitsuModel->getAnimeById($animeId);
|
||||||
}
|
}
|
||||||
@ -119,7 +117,7 @@ class Anime extends API {
|
|||||||
* @param string $name
|
* @param string $name
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function search($name)
|
public function search(string $name): array
|
||||||
{
|
{
|
||||||
return $this->kitsuModel->search('anime', $name);
|
return $this->kitsuModel->search('anime', $name);
|
||||||
}
|
}
|
||||||
|
@ -16,9 +16,13 @@
|
|||||||
|
|
||||||
namespace Aviat\AnimeClient\Model;
|
namespace Aviat\AnimeClient\Model;
|
||||||
|
|
||||||
use Aviat\AnimeClient\API\Enum\MangaReadingStatus\Title;
|
use Aviat\AnimeClient\API\{
|
||||||
use Aviat\AnimeClient\API\Mapping\MangaReadingStatus;
|
Enum\MangaReadingStatus\Title,
|
||||||
|
Mapping\MangaReadingStatus,
|
||||||
|
ParallelAPIRequest
|
||||||
|
};
|
||||||
use Aviat\Ion\Di\ContainerInterface;
|
use Aviat\Ion\Di\ContainerInterface;
|
||||||
|
use Aviat\Ion\Json;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Model for handling requests dealing with the manga list
|
* Model for handling requests dealing with the manga list
|
||||||
@ -46,6 +50,9 @@ class Manga extends API
|
|||||||
{
|
{
|
||||||
$this->kitsuModel = $container->get('kitsu-model');
|
$this->kitsuModel = $container->get('kitsu-model');
|
||||||
$this->malModel = $container->get('mal-model');
|
$this->malModel = $container->get('mal-model');
|
||||||
|
|
||||||
|
$config = $container->get('config');
|
||||||
|
$this->useMALAPI = $config->get(['use_mal_api']) === TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -77,17 +84,6 @@ class Manga extends API
|
|||||||
return $this->kitsuModel->getManga($manga_id);
|
return $this->kitsuModel->getManga($manga_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new manga list item
|
|
||||||
*
|
|
||||||
* @param array $data
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function createLibraryItem(array $data): bool
|
|
||||||
{
|
|
||||||
return $this->kitsuModel->createListItem($data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get information about a specific list item
|
* Get information about a specific list item
|
||||||
* for editing/updating that item
|
* for editing/updating that item
|
||||||
@ -100,6 +96,35 @@ class Manga extends API
|
|||||||
return $this->kitsuModel->getListItem($itemId);
|
return $this->kitsuModel->getListItem($itemId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new manga list item
|
||||||
|
*
|
||||||
|
* @param array $data
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function createLibraryItem(array $data): bool
|
||||||
|
{
|
||||||
|
$requester = new ParallelAPIRequest();
|
||||||
|
|
||||||
|
if ($this->useMALAPI)
|
||||||
|
{
|
||||||
|
$malData = $data;
|
||||||
|
$malId = $this->kitsuModel->getMalIdForManga($malData['id']);
|
||||||
|
|
||||||
|
if ( ! is_null($malId))
|
||||||
|
{
|
||||||
|
$malData['id'] = $malId;
|
||||||
|
$requester->addRequest($this->malModel->createListItem($malData, 'manga'), 'mal');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$requester->addRequest($this->kitsuModel->createListItem($data), 'kitsu');
|
||||||
|
|
||||||
|
$results = $requester->makeRequests(TRUE);
|
||||||
|
|
||||||
|
return count($results[1]) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update a list entry
|
* Update a list entry
|
||||||
*
|
*
|
||||||
@ -108,18 +133,44 @@ class Manga extends API
|
|||||||
*/
|
*/
|
||||||
public function updateLibraryItem(array $data): array
|
public function updateLibraryItem(array $data): array
|
||||||
{
|
{
|
||||||
return $this->kitsuModel->updateListItem($data);
|
$requester = new ParallelAPIRequest();
|
||||||
|
|
||||||
|
if ($this->useMALAPI)
|
||||||
|
{
|
||||||
|
$requester->addRequest($this->malModel->updateListItem($data, 'manga'), 'mal');
|
||||||
|
}
|
||||||
|
|
||||||
|
$requester->addRequest($this->kitsuModel->updateListItem($data), 'kitsu');
|
||||||
|
|
||||||
|
$results = $requester->makeRequests(TRUE);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'body' => Json::decode($results[1]['kitsu']->getBody()),
|
||||||
|
'statusCode' => $results[1]['kitsu']->getStatus()
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove a list entry
|
* Delete a list entry
|
||||||
*
|
*
|
||||||
* @param string $itemId
|
* @param string $id
|
||||||
|
* @param string|null $malId
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function deleteLibraryItem(string $itemId): bool
|
public function deleteLibraryItem(string $id, string $malId = NULL): bool
|
||||||
{
|
{
|
||||||
return $this->kitsuModel->deleteListItem($itemId);
|
$requester = new ParallelAPIRequest();
|
||||||
|
|
||||||
|
if ($this->useMALAPI && ! is_null($malId))
|
||||||
|
{
|
||||||
|
$requester->addRequest($this->malModel->deleteListItem($malId, 'manga'), 'MAL');
|
||||||
|
}
|
||||||
|
|
||||||
|
$requester->addRequest($this->kitsuModel->deleteListItem($id), 'kitsu');
|
||||||
|
|
||||||
|
$results = $requester->makeRequests(TRUE);
|
||||||
|
|
||||||
|
return count($results[1]) > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -59,9 +59,8 @@ class RoutingBase {
|
|||||||
{
|
{
|
||||||
$this->container = $container;
|
$this->container = $container;
|
||||||
$this->config = $container->get('config');
|
$this->config = $container->get('config');
|
||||||
$baseRoutes = $this->config->get('routes');
|
$this->routes = $this->config->get('routes');
|
||||||
$this->routes = $baseRoutes['routes'];
|
$this->routeConfig = $this->config->get('route_config');
|
||||||
$this->routeConfig = $baseRoutes['route_config'];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -72,11 +71,9 @@ class RoutingBase {
|
|||||||
*/
|
*/
|
||||||
public function __get($key)
|
public function __get($key)
|
||||||
{
|
{
|
||||||
$routingConfig =& $this->routeConfig;
|
if (array_key_exists($key, $this->routeConfig))
|
||||||
|
|
||||||
if (array_key_exists($key, $routingConfig))
|
|
||||||
{
|
{
|
||||||
return $routingConfig[$key];
|
return $this->routeConfig[$key];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,32 +108,5 @@ class UrlGenerator extends RoutingBase {
|
|||||||
|
|
||||||
throw new InvalidArgumentException("Invalid default type: '{$type}'");
|
throw new InvalidArgumentException("Invalid default type: '{$type}'");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate full url path from the route path based on config
|
|
||||||
*
|
|
||||||
* @param string $path - (optional) The route path
|
|
||||||
* @param string $type - (optional) The controller (anime or manga), defaults to anime
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function fullUrl(string $path = "", string $type = "anime"): string
|
|
||||||
{
|
|
||||||
$configDefaultRoute = $this->__get("default_{$type}_path");
|
|
||||||
|
|
||||||
// Remove beginning/trailing slashes
|
|
||||||
$path = trim($path, '/');
|
|
||||||
|
|
||||||
// Set the default view
|
|
||||||
if ($path === '')
|
|
||||||
{
|
|
||||||
$path .= trim($configDefaultRoute, '/');
|
|
||||||
if ($this->__get('default_to_list_view'))
|
|
||||||
{
|
|
||||||
$path .= '/list';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->url($path);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// End of UrlGenerator.php
|
// End of UrlGenerator.php
|
@ -32,10 +32,21 @@ class MangaListTransformerTest extends AnimeClientTestCase {
|
|||||||
public function setUp()
|
public function setUp()
|
||||||
{
|
{
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
|
|
||||||
|
$kitsuModel = $this->container->get('kitsu-model');
|
||||||
|
|
||||||
$this->dir = AnimeClientTestCase::TEST_DATA_DIR . '/Kitsu';
|
$this->dir = AnimeClientTestCase::TEST_DATA_DIR . '/Kitsu';
|
||||||
|
|
||||||
|
// Prep for transform
|
||||||
$rawBefore = Json::decodeFile("{$this->dir}/mangaListBeforeTransform.json");
|
$rawBefore = Json::decodeFile("{$this->dir}/mangaListBeforeTransform.json");
|
||||||
$this->beforeTransform = JsonAPI::inlineRawIncludes($rawBefore, 'manga');
|
$included = JsonAPI::organizeIncludes($rawBefore['included']);
|
||||||
|
$included = JsonAPI::inlineIncludedRelationships($included, 'manga');
|
||||||
|
foreach($rawBefore['data'] as $i => &$item)
|
||||||
|
{
|
||||||
|
$item['included'] = $included;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->beforeTransform = $rawBefore['data'];
|
||||||
$this->afterTransform = Json::decodeFile("{$this->dir}/mangaListAfterTransform.json");
|
$this->afterTransform = Json::decodeFile("{$this->dir}/mangaListAfterTransform.json");
|
||||||
|
|
||||||
$this->transformer = new MangaListTransformer();
|
$this->transformer = new MangaListTransformer();
|
||||||
@ -54,7 +65,8 @@ class MangaListTransformerTest extends AnimeClientTestCase {
|
|||||||
public function testUntransform()
|
public function testUntransform()
|
||||||
{
|
{
|
||||||
$input = [
|
$input = [
|
||||||
'id' => "15084773",
|
'id' => '15084773',
|
||||||
|
'mal_id' => '26769',
|
||||||
'chapters_read' => 67,
|
'chapters_read' => 67,
|
||||||
'manga' => [
|
'manga' => [
|
||||||
'titles' => ["Bokura wa Minna Kawaisou"],
|
'titles' => ["Bokura wa Minna Kawaisou"],
|
||||||
@ -75,6 +87,7 @@ class MangaListTransformerTest extends AnimeClientTestCase {
|
|||||||
$actual = $this->transformer->untransform($input);
|
$actual = $this->transformer->untransform($input);
|
||||||
$expected = [
|
$expected = [
|
||||||
'id' => '15084773',
|
'id' => '15084773',
|
||||||
|
'mal_id' => '26769',
|
||||||
'data' => [
|
'data' => [
|
||||||
'status' => 'current',
|
'status' => 'current',
|
||||||
'progress' => 67,
|
'progress' => 67,
|
||||||
|
40
tests/API/MAL/ListItemTest.php
Normal file
40
tests/API/MAL/ListItemTest.php
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* Hummingbird Anime List Client
|
||||||
|
*
|
||||||
|
* An API client for Kitsu and MyAnimeList to manage anime and manga watch lists
|
||||||
|
*
|
||||||
|
* PHP version 7
|
||||||
|
*
|
||||||
|
* @package HummingbirdAnimeClient
|
||||||
|
* @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://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Aviat\AnimeClient\Tests\API\MAL;
|
||||||
|
|
||||||
|
use Aviat\AnimeClient\API\MAL\ListItem;
|
||||||
|
use Aviat\AnimeClient\API\MAL\MALRequestBuilder;
|
||||||
|
use Aviat\AnimeClient\Tests\AnimeClientTestCase;
|
||||||
|
use Aviat\Ion\Di\ContainerAware;
|
||||||
|
|
||||||
|
class ListItemTest extends AnimeClientTestCase {
|
||||||
|
|
||||||
|
protected $listItem;
|
||||||
|
|
||||||
|
public function setUp()
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
$this->listItem = new ListItem();
|
||||||
|
$this->listItem->setContainer($this->container);
|
||||||
|
$this->listItem->setRequestBuilder(new MALRequestBuilder());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGet()
|
||||||
|
{
|
||||||
|
$this->assertEquals([], $this->listItem->get('foo'));
|
||||||
|
}
|
||||||
|
}
|
51
tests/API/MAL/MALTraitTest.php
Normal file
51
tests/API/MAL/MALTraitTest.php
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* Hummingbird Anime List Client
|
||||||
|
*
|
||||||
|
* An API client for Kitsu and MyAnimeList to manage anime and manga watch lists
|
||||||
|
*
|
||||||
|
* PHP version 7
|
||||||
|
*
|
||||||
|
* @package HummingbirdAnimeClient
|
||||||
|
* @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://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Aviat\AnimeClient\Tests\API\MAL;
|
||||||
|
|
||||||
|
use Aviat\AnimeClient\API\MAL\MALRequestBuilder;
|
||||||
|
use Aviat\AnimeClient\API\MAL\MALTrait;
|
||||||
|
use Aviat\AnimeClient\Tests\AnimeClientTestCase;
|
||||||
|
use Aviat\Ion\Di\ContainerAware;
|
||||||
|
|
||||||
|
class MALTraitTest extends AnimeClientTestCase {
|
||||||
|
|
||||||
|
protected $obj;
|
||||||
|
|
||||||
|
public function setUp()
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
$this->obj = new class {
|
||||||
|
use ContainerAware;
|
||||||
|
use MALTrait;
|
||||||
|
};
|
||||||
|
$this->obj->setContainer($this->container);
|
||||||
|
$this->obj->setRequestBuilder(new MALRequestBuilder());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSetupRequest()
|
||||||
|
{
|
||||||
|
$request = $this->obj->setUpRequest('GET', 'foo', [
|
||||||
|
'query' => [
|
||||||
|
'foo' => 'bar'
|
||||||
|
],
|
||||||
|
'body' => ''
|
||||||
|
]);
|
||||||
|
$this->assertInstanceOf(\Amp\Artax\Request::class, $request);
|
||||||
|
$this->assertEquals($request->getUri(), 'https://myanimelist.net/api/foo?foo=bar');
|
||||||
|
$this->assertEquals($request->getBody(), '');
|
||||||
|
}
|
||||||
|
}
|
@ -14,26 +14,22 @@
|
|||||||
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
|
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace Aviat\AnimeClient\Tests;
|
namespace Aviat\AnimeClient\Tests\API\MAL;
|
||||||
|
|
||||||
use Aviat\AnimeClient\ControllerTrait;
|
use Aviat\AnimeClient\Tests\AnimeClientTestCase;
|
||||||
|
|
||||||
class ControllerTraitTest extends AnimeClientTestCase {
|
class ModelTest extends AnimeClientTestCase {
|
||||||
|
|
||||||
|
protected $model;
|
||||||
|
|
||||||
public function setUp()
|
public function setUp()
|
||||||
{
|
{
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
|
$this->model = $this->container->get('mal-model');
|
||||||
$this->controller = new class {
|
|
||||||
use ControllerTrait;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testFormatTitle()
|
public function testGetListItem()
|
||||||
{
|
{
|
||||||
$this->assertEquals(
|
$this->assertEquals([], $this->model->getListItem('foo'));
|
||||||
$this->controller->formatTitle('foo', 'bar', 'baz'),
|
|
||||||
'foo · bar · baz'
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,29 +0,0 @@
|
|||||||
<?php declare(strict_types=1);
|
|
||||||
/**
|
|
||||||
* Hummingbird Anime List Client
|
|
||||||
*
|
|
||||||
* An API client for Kitsu and MyAnimeList to manage anime and manga watch lists
|
|
||||||
*
|
|
||||||
* PHP version 7
|
|
||||||
*
|
|
||||||
* @package HummingbirdAnimeClient
|
|
||||||
* @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://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Aviat\AnimeClient\Tests;
|
|
||||||
|
|
||||||
use function Aviat\AnimeClient\_dir;
|
|
||||||
|
|
||||||
class AnimeClientTest extends AnimeClientTestCase {
|
|
||||||
/**
|
|
||||||
* Basic sanity test for _dir function
|
|
||||||
*/
|
|
||||||
public function testDir()
|
|
||||||
{
|
|
||||||
$this->assertEquals('foo' . \DIRECTORY_SEPARATOR . 'bar', _dir('foo', 'bar'));
|
|
||||||
}
|
|
||||||
}
|
|
@ -18,7 +18,7 @@ namespace Aviat\AnimeClient\Tests;
|
|||||||
|
|
||||||
use const Aviat\AnimeClient\SRC_DIR;
|
use const Aviat\AnimeClient\SRC_DIR;
|
||||||
|
|
||||||
use function Aviat\AnimeClient\_dir;
|
use function Aviat\Ion\_dir;
|
||||||
|
|
||||||
use Aura\Web\WebFactory;
|
use Aura\Web\WebFactory;
|
||||||
use Aviat\Ion\Json;
|
use Aviat\Ion\Json;
|
||||||
@ -95,13 +95,15 @@ class AnimeClientTestCase extends TestCase {
|
|||||||
'file' => ':memory:',
|
'file' => ':memory:',
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
'routes' => [
|
|
||||||
'route_config' => [
|
'route_config' => [
|
||||||
'asset_path' => '/assets'
|
'asset_path' => '/assets'
|
||||||
],
|
],
|
||||||
'routes' => [
|
'routes' => [
|
||||||
|
|
||||||
]
|
],
|
||||||
|
'mal' => [
|
||||||
|
'username' => 'foo',
|
||||||
|
'password' => 'bar'
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -80,31 +80,12 @@ class ControllerTest extends AnimeClientTestCase {
|
|||||||
$this->assertTrue(is_object($this->BaseController));
|
$this->assertTrue(is_object($this->BaseController));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function dataGet()
|
public function testFormatTitle()
|
||||||
{
|
{
|
||||||
return [
|
$this->assertEquals(
|
||||||
'response' => [
|
$this->BaseController->formatTitle('foo', 'bar', 'baz'),
|
||||||
'key' => 'response',
|
'foo · bar · baz'
|
||||||
],
|
);
|
||||||
'config' => [
|
|
||||||
'key' => 'config',
|
|
||||||
]
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @dataProvider dataGet
|
|
||||||
*/
|
|
||||||
public function testGet($key)
|
|
||||||
{
|
|
||||||
$result = $this->BaseController->__get($key);
|
|
||||||
$this->assertEquals($this->container->get($key), $result);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testGetNull()
|
|
||||||
{
|
|
||||||
$result = $this->BaseController->__get('foo');
|
|
||||||
$this->assertNull($result);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -71,7 +71,6 @@ class DispatcherTest extends AnimeClientTestCase {
|
|||||||
public function dataRoute()
|
public function dataRoute()
|
||||||
{
|
{
|
||||||
$defaultConfig = [
|
$defaultConfig = [
|
||||||
'routes' => [
|
|
||||||
'routes' => [
|
'routes' => [
|
||||||
'login_form' => [
|
'login_form' => [
|
||||||
'path' => '/login',
|
'path' => '/login',
|
||||||
@ -104,7 +103,6 @@ class DispatcherTest extends AnimeClientTestCase {
|
|||||||
'manga_path' => 'manga',
|
'manga_path' => 'manga',
|
||||||
'default_list' => 'anime'
|
'default_list' => 'anime'
|
||||||
]
|
]
|
||||||
],
|
|
||||||
];
|
];
|
||||||
|
|
||||||
$data = [
|
$data = [
|
||||||
@ -134,8 +132,8 @@ class DispatcherTest extends AnimeClientTestCase {
|
|||||||
]
|
]
|
||||||
];
|
];
|
||||||
|
|
||||||
$data['manga_default_routing_anime']['config']['routes']['route_config']['default_list'] = 'manga';
|
$data['manga_default_routing_anime']['config']['route_config']['default_list'] = 'manga';
|
||||||
$data['manga_default_routing_manga']['config']['routes']['route_config']['default_list'] = 'manga';
|
$data['manga_default_routing_manga']['config']['route_config']['default_list'] = 'manga';
|
||||||
|
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
@ -169,7 +167,6 @@ class DispatcherTest extends AnimeClientTestCase {
|
|||||||
public function testDefaultRoute()
|
public function testDefaultRoute()
|
||||||
{
|
{
|
||||||
$config = [
|
$config = [
|
||||||
'routes' => [
|
|
||||||
'route_config' => [
|
'route_config' => [
|
||||||
'anime_path' => 'anime',
|
'anime_path' => 'anime',
|
||||||
'manga_path' => 'manga',
|
'manga_path' => 'manga',
|
||||||
@ -201,7 +198,6 @@ class DispatcherTest extends AnimeClientTestCase {
|
|||||||
]
|
]
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
]
|
|
||||||
];
|
];
|
||||||
|
|
||||||
$this->doSetUp($config, "/", "localhost");
|
$this->doSetUp($config, "/", "localhost");
|
||||||
@ -217,7 +213,6 @@ class DispatcherTest extends AnimeClientTestCase {
|
|||||||
return [
|
return [
|
||||||
'controller_list_sanity_check' => [
|
'controller_list_sanity_check' => [
|
||||||
'config' => [
|
'config' => [
|
||||||
'routes' => [
|
|
||||||
'routes' => [
|
'routes' => [
|
||||||
|
|
||||||
],
|
],
|
||||||
@ -228,18 +223,17 @@ class DispatcherTest extends AnimeClientTestCase {
|
|||||||
'default_manga_list_path' => 'all',
|
'default_manga_list_path' => 'all',
|
||||||
'default_list' => 'manga'
|
'default_list' => 'manga'
|
||||||
],
|
],
|
||||||
]
|
|
||||||
],
|
],
|
||||||
'expected' => [
|
'expected' => [
|
||||||
'anime' => 'Aviat\AnimeClient\Controller\Anime',
|
'anime' => 'Aviat\AnimeClient\Controller\Anime',
|
||||||
'manga' => 'Aviat\AnimeClient\Controller\Manga',
|
'manga' => 'Aviat\AnimeClient\Controller\Manga',
|
||||||
'collection' => 'Aviat\AnimeClient\Controller\Collection',
|
'collection' => 'Aviat\AnimeClient\Controller\Collection',
|
||||||
'character' => 'Aviat\AnimeClient\Controller\Character',
|
'character' => 'Aviat\AnimeClient\Controller\Character',
|
||||||
|
'index' => 'Aviat\AnimeClient\Controller\Index',
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
'empty_controller_list' => [
|
'empty_controller_list' => [
|
||||||
'config' => [
|
'config' => [
|
||||||
'routes' => [
|
|
||||||
'routes' => [
|
'routes' => [
|
||||||
|
|
||||||
],
|
],
|
||||||
@ -250,13 +244,13 @@ class DispatcherTest extends AnimeClientTestCase {
|
|||||||
'default_manga_path' => '/manga/all',
|
'default_manga_path' => '/manga/all',
|
||||||
'default_list' => 'manga'
|
'default_list' => 'manga'
|
||||||
],
|
],
|
||||||
]
|
|
||||||
],
|
],
|
||||||
'expected' => [
|
'expected' => [
|
||||||
'anime' => 'Aviat\AnimeClient\Controller\Anime',
|
'anime' => 'Aviat\AnimeClient\Controller\Anime',
|
||||||
'manga' => 'Aviat\AnimeClient\Controller\Manga',
|
'manga' => 'Aviat\AnimeClient\Controller\Manga',
|
||||||
'collection' => 'Aviat\AnimeClient\Controller\Collection',
|
'collection' => 'Aviat\AnimeClient\Controller\Collection',
|
||||||
'character' => 'Aviat\AnimeClient\Controller\Character',
|
'character' => 'Aviat\AnimeClient\Controller\Character',
|
||||||
|
'index' => 'Aviat\AnimeClient\Controller\Index',
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
|
@ -49,60 +49,4 @@ class UrlGeneratorTest extends AnimeClientTestCase {
|
|||||||
$result = $urlGenerator->assetUrl(...$args);
|
$result = $urlGenerator->assetUrl(...$args);
|
||||||
$this->assertEquals($expected, $result);
|
$this->assertEquals($expected, $result);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function dataFullUrl()
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'default_view' => [
|
|
||||||
'config' => [
|
|
||||||
'routes' => [
|
|
||||||
'routes' => [],
|
|
||||||
'route_config' => [
|
|
||||||
'anime_path' => 'anime',
|
|
||||||
'manga_path' => 'manga',
|
|
||||||
'default_list' => 'manga',
|
|
||||||
'default_anime_path' => '/anime/watching',
|
|
||||||
'default_manga_path' => '/manga/all',
|
|
||||||
'default_to_list_view' => FALSE,
|
|
||||||
]
|
|
||||||
],
|
|
||||||
],
|
|
||||||
'path' => '',
|
|
||||||
'type' => 'manga',
|
|
||||||
'expected' => '//localhost/manga/all',
|
|
||||||
],
|
|
||||||
'default_view_list' => [
|
|
||||||
'config' => [
|
|
||||||
'routes' => [
|
|
||||||
'routes' => [],
|
|
||||||
'route_config' => [
|
|
||||||
'anime_path' => 'anime',
|
|
||||||
'manga_path' => 'manga',
|
|
||||||
'default_list' => 'manga',
|
|
||||||
'default_anime_path' => '/anime/watching',
|
|
||||||
'default_manga_path' => '/manga/all',
|
|
||||||
'default_to_list_view' => TRUE,
|
|
||||||
]
|
|
||||||
],
|
|
||||||
],
|
|
||||||
'path' => '',
|
|
||||||
'type' => 'manga',
|
|
||||||
'expected' => '//localhost/manga/all/list',
|
|
||||||
]
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @dataProvider dataFullUrl
|
|
||||||
*/
|
|
||||||
public function testFullUrl($config, $path, $type, $expected)
|
|
||||||
{
|
|
||||||
$config = new Config($config);
|
|
||||||
$this->container->setInstance('config', $config);
|
|
||||||
$urlGenerator = new UrlGenerator($this->container);
|
|
||||||
|
|
||||||
$result = $urlGenerator->fullUrl($path, $type);
|
|
||||||
|
|
||||||
$this->assertEquals($expected, $result);
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -1,5 +1,6 @@
|
|||||||
[{
|
[{
|
||||||
"id": "15084773",
|
"id": "15084773",
|
||||||
|
"mal_id": "26769",
|
||||||
"chapters": {
|
"chapters": {
|
||||||
"read": 67,
|
"read": 67,
|
||||||
"total": "-"
|
"total": "-"
|
||||||
@ -15,7 +16,7 @@
|
|||||||
"url": "https:\/\/kitsu.io\/manga\/bokura-wa-minna-kawaisou",
|
"url": "https:\/\/kitsu.io\/manga\/bokura-wa-minna-kawaisou",
|
||||||
"type": "manga",
|
"type": "manga",
|
||||||
"image": "https:\/\/media.kitsu.io\/manga\/poster_images\/20286\/small.jpg?1434293999",
|
"image": "https:\/\/media.kitsu.io\/manga\/poster_images\/20286\/small.jpg?1434293999",
|
||||||
"genres": []
|
"genres": ["Comedy", "Romance", "School", "Slice of Life", "Thriller"]
|
||||||
},
|
},
|
||||||
"reading_status": "current",
|
"reading_status": "current",
|
||||||
"notes": "",
|
"notes": "",
|
||||||
@ -24,6 +25,7 @@
|
|||||||
"user_rating": 9
|
"user_rating": 9
|
||||||
}, {
|
}, {
|
||||||
"id": "15085607",
|
"id": "15085607",
|
||||||
|
"mal_id": "16",
|
||||||
"chapters": {
|
"chapters": {
|
||||||
"read": 17,
|
"read": 17,
|
||||||
"total": 120
|
"total": 120
|
||||||
@ -39,7 +41,7 @@
|
|||||||
"url": "https:\/\/kitsu.io\/manga\/love-hina",
|
"url": "https:\/\/kitsu.io\/manga\/love-hina",
|
||||||
"type": "manga",
|
"type": "manga",
|
||||||
"image": "https:\/\/media.kitsu.io\/manga\/poster_images\/47\/small.jpg?1434249493",
|
"image": "https:\/\/media.kitsu.io\/manga\/poster_images\/47\/small.jpg?1434249493",
|
||||||
"genres": []
|
"genres": ["Comedy", "Ecchi", "Harem", "Romance", "Sports"]
|
||||||
},
|
},
|
||||||
"reading_status": "current",
|
"reading_status": "current",
|
||||||
"notes": "",
|
"notes": "",
|
||||||
@ -48,6 +50,7 @@
|
|||||||
"user_rating": 7
|
"user_rating": 7
|
||||||
}, {
|
}, {
|
||||||
"id": "15084529",
|
"id": "15084529",
|
||||||
|
"mal_id": "35003",
|
||||||
"chapters": {
|
"chapters": {
|
||||||
"read": 16,
|
"read": 16,
|
||||||
"total": "-"
|
"total": "-"
|
||||||
@ -63,7 +66,7 @@
|
|||||||
"url": "https:\/\/kitsu.io\/manga\/yamada-kun-to-7-nin-no-majo",
|
"url": "https:\/\/kitsu.io\/manga\/yamada-kun-to-7-nin-no-majo",
|
||||||
"type": "manga",
|
"type": "manga",
|
||||||
"image": "https:\/\/media.kitsu.io\/manga\/poster_images\/11777\/small.jpg?1438784325",
|
"image": "https:\/\/media.kitsu.io\/manga\/poster_images\/11777\/small.jpg?1438784325",
|
||||||
"genres": []
|
"genres": ["Comedy", "Ecchi", "Gender Bender", "Romance", "School", "Sports", "Supernatural"]
|
||||||
},
|
},
|
||||||
"reading_status": "current",
|
"reading_status": "current",
|
||||||
"notes": "",
|
"notes": "",
|
||||||
@ -72,6 +75,7 @@
|
|||||||
"user_rating": 9
|
"user_rating": 9
|
||||||
}, {
|
}, {
|
||||||
"id": "15312827",
|
"id": "15312827",
|
||||||
|
"mal_id": "78523",
|
||||||
"chapters": {
|
"chapters": {
|
||||||
"read": 68,
|
"read": 68,
|
||||||
"total": "-"
|
"total": "-"
|
||||||
@ -87,7 +91,7 @@
|
|||||||
"url": "https:\/\/kitsu.io\/manga\/relife",
|
"url": "https:\/\/kitsu.io\/manga\/relife",
|
||||||
"type": "manga",
|
"type": "manga",
|
||||||
"image": "https:\/\/media.kitsu.io\/manga\/poster_images\/27175\/small.jpg?1464379411",
|
"image": "https:\/\/media.kitsu.io\/manga\/poster_images\/27175\/small.jpg?1464379411",
|
||||||
"genres": []
|
"genres": ["Romance", "School", "Slice of Life"]
|
||||||
},
|
},
|
||||||
"reading_status": "current",
|
"reading_status": "current",
|
||||||
"notes": "",
|
"notes": "",
|
||||||
@ -95,33 +99,10 @@
|
|||||||
"reread": 0,
|
"reread": 0,
|
||||||
"user_rating": "-"
|
"user_rating": "-"
|
||||||
}, {
|
}, {
|
||||||
"id": "15084772",
|
"id": "15084769",
|
||||||
|
"mal_id": "60815",
|
||||||
"chapters": {
|
"chapters": {
|
||||||
"read": 28,
|
"read": 43,
|
||||||
"total": 62
|
|
||||||
},
|
|
||||||
"volumes": {
|
|
||||||
"read": "-",
|
|
||||||
"total": 10
|
|
||||||
},
|
|
||||||
"manga": {
|
|
||||||
"titles": ["Usagi Drop", "Bunny Drop"],
|
|
||||||
"alternate_title": null,
|
|
||||||
"slug": "usagi-drop",
|
|
||||||
"url": "https:\/\/kitsu.io\/manga\/usagi-drop",
|
|
||||||
"type": "manga",
|
|
||||||
"image": "https:\/\/media.kitsu.io\/manga\/poster_images\/7629\/small.jpg?1434265873",
|
|
||||||
"genres": []
|
|
||||||
},
|
|
||||||
"reading_status": "on_hold",
|
|
||||||
"notes": "",
|
|
||||||
"rereading": false,
|
|
||||||
"reread": 0,
|
|
||||||
"user_rating": 8
|
|
||||||
}, {
|
|
||||||
"id": "15251749",
|
|
||||||
"chapters": {
|
|
||||||
"read": 1,
|
|
||||||
"total": "-"
|
"total": "-"
|
||||||
},
|
},
|
||||||
"volumes": {
|
"volumes": {
|
||||||
@ -129,111 +110,15 @@
|
|||||||
"total": "-"
|
"total": "-"
|
||||||
},
|
},
|
||||||
"manga": {
|
"manga": {
|
||||||
"titles": ["Shishunki Bitter Change"],
|
"titles": ["Joshikausei"],
|
||||||
"alternate_title": null,
|
"alternate_title": null,
|
||||||
"slug": "shishunki-bitter-change",
|
"slug": "joshikausei",
|
||||||
"url": "https:\/\/kitsu.io\/manga\/shishunki-bitter-change",
|
"url": "https:\/\/kitsu.io\/manga\/joshikausei",
|
||||||
"type": "manga",
|
"type": "manga",
|
||||||
"image": "https:\/\/media.kitsu.io\/manga\/poster_images\/25512\/small.jpg?1434305092",
|
"image": "https:\/\/media.kitsu.io\/manga\/poster_images\/25491\/small.jpg?1434305043",
|
||||||
"genres": []
|
"genres": ["Comedy", "School", "Slice of Life"]
|
||||||
},
|
},
|
||||||
"reading_status": "planned",
|
"reading_status": "current",
|
||||||
"notes": "",
|
|
||||||
"rereading": false,
|
|
||||||
"reread": 0,
|
|
||||||
"user_rating": "-"
|
|
||||||
}, {
|
|
||||||
"id": "15312881",
|
|
||||||
"chapters": {
|
|
||||||
"read": 0,
|
|
||||||
"total": "-"
|
|
||||||
},
|
|
||||||
"volumes": {
|
|
||||||
"read": "-",
|
|
||||||
"total": "-"
|
|
||||||
},
|
|
||||||
"manga": {
|
|
||||||
"titles": ["Kuragehime", "Princess Jellyfish"],
|
|
||||||
"alternate_title": null,
|
|
||||||
"slug": "kuragehime",
|
|
||||||
"url": "https:\/\/kitsu.io\/manga\/kuragehime",
|
|
||||||
"type": "manga",
|
|
||||||
"image": "https:\/\/media.kitsu.io\/manga\/poster_images\/5531\/small.jpg?1434261214",
|
|
||||||
"genres": []
|
|
||||||
},
|
|
||||||
"reading_status": "planned",
|
|
||||||
"notes": "",
|
|
||||||
"rereading": false,
|
|
||||||
"reread": 0,
|
|
||||||
"user_rating": "-"
|
|
||||||
}, {
|
|
||||||
"id": "15315190",
|
|
||||||
"chapters": {
|
|
||||||
"read": 0,
|
|
||||||
"total": 80
|
|
||||||
},
|
|
||||||
"volumes": {
|
|
||||||
"read": "-",
|
|
||||||
"total": 9
|
|
||||||
},
|
|
||||||
"manga": {
|
|
||||||
"titles": ["Boku wa Mari no Naka", "Inside Mari"],
|
|
||||||
"alternate_title": null,
|
|
||||||
"slug": "boku-wa-mari-no-naka",
|
|
||||||
"url": "https:\/\/kitsu.io\/manga\/boku-wa-mari-no-naka",
|
|
||||||
"type": "manga",
|
|
||||||
"image": "https:\/\/media.kitsu.io\/manga\/poster_images\/14261\/small.jpg?1434280674",
|
|
||||||
"genres": []
|
|
||||||
},
|
|
||||||
"reading_status": "planned",
|
|
||||||
"notes": null,
|
|
||||||
"rereading": false,
|
|
||||||
"reread": 0,
|
|
||||||
"user_rating": "-"
|
|
||||||
}, {
|
|
||||||
"id": "15315189",
|
|
||||||
"chapters": {
|
|
||||||
"read": 0,
|
|
||||||
"total": "-"
|
|
||||||
},
|
|
||||||
"volumes": {
|
|
||||||
"read": "-",
|
|
||||||
"total": "-"
|
|
||||||
},
|
|
||||||
"manga": {
|
|
||||||
"titles": ["Aizawa-san Zoushoku"],
|
|
||||||
"alternate_title": null,
|
|
||||||
"slug": "aizawa-san-zoushoku",
|
|
||||||
"url": "https:\/\/kitsu.io\/manga\/aizawa-san-zoushoku",
|
|
||||||
"type": "manga",
|
|
||||||
"image": "https:\/\/media.kitsu.io\/manga\/poster_images\/25316\/small.jpg?1434304656",
|
|
||||||
"genres": []
|
|
||||||
},
|
|
||||||
"reading_status": "planned",
|
|
||||||
"notes": null,
|
|
||||||
"rereading": false,
|
|
||||||
"reread": 0,
|
|
||||||
"user_rating": "-"
|
|
||||||
}, {
|
|
||||||
"id": "15288185",
|
|
||||||
"chapters": {
|
|
||||||
"read": 28,
|
|
||||||
"total": "-"
|
|
||||||
},
|
|
||||||
"volumes": {
|
|
||||||
"read": "-",
|
|
||||||
"total": "-"
|
|
||||||
},
|
|
||||||
"manga": {
|
|
||||||
"titles": ["Tonari no Seki-kun", "My Neighbour Seki"],
|
|
||||||
"alternate_title": null,
|
|
||||||
"slug": "tonari-no-seki-kun",
|
|
||||||
"url": "https:\/\/kitsu.io\/manga\/tonari-no-seki-kun",
|
|
||||||
"type": "manga",
|
|
||||||
"image": "https:\/\/media.kitsu.io\/manga\/poster_images\/21733\/small.jpg?1434297086",
|
|
||||||
"genres": []
|
|
||||||
},
|
|
||||||
"reading_status": "on_hold",
|
|
||||||
"notes": "",
|
"notes": "",
|
||||||
"rereading": false,
|
"rereading": false,
|
||||||
"reread": 0,
|
"reread": 0,
|
||||||
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user