Decouple and generalise
This commit is contained in:
parent
c788cf5d87
commit
602759b471
@ -9,64 +9,74 @@ use \Whoops\Handler\PrettyPageHandler;
|
||||
use \Whoops\Handler\JsonResponseHandler;
|
||||
use \Aura\Web\WebFactory;
|
||||
use \Aura\Router\RouterFactory;
|
||||
use \Aura\Di\Container as DiContainer;
|
||||
use \Aura\Di\Factory as DiFactory;
|
||||
use \Aura\Session\SessionFactory;
|
||||
|
||||
use Aviat\Ion\Di\Container;
|
||||
|
||||
require _dir(SRC_DIR, '/functions.php');
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Setup DI container
|
||||
// -----------------------------------------------------------------------------
|
||||
$container = new Container();
|
||||
$di = function() {
|
||||
$container = new Container();
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Setup error handling
|
||||
// -----------------------------------------------------------------------------
|
||||
$whoops = new \Whoops\Run();
|
||||
// -------------------------------------------------------------------------
|
||||
// Setup error handling
|
||||
// -------------------------------------------------------------------------
|
||||
$whoops = new \Whoops\Run();
|
||||
|
||||
// Set up default handler for general errors
|
||||
$defaultHandler = new PrettyPageHandler();
|
||||
$whoops->pushHandler($defaultHandler);
|
||||
// Set up default handler for general errors
|
||||
$defaultHandler = new PrettyPageHandler();
|
||||
$whoops->pushHandler($defaultHandler);
|
||||
|
||||
// Set up json handler for ajax errors
|
||||
$jsonHandler = new JsonResponseHandler();
|
||||
$jsonHandler->onlyForAjaxRequests(true);
|
||||
$whoops->pushHandler($jsonHandler);
|
||||
// Set up json handler for ajax errors
|
||||
$jsonHandler = new JsonResponseHandler();
|
||||
$jsonHandler->onlyForAjaxRequests(true);
|
||||
$whoops->pushHandler($jsonHandler);
|
||||
|
||||
$whoops->register();
|
||||
//$whoops->register();
|
||||
|
||||
$container->set('error-handler', $defaultHandler);
|
||||
$container->set('error-handler', $defaultHandler);
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Injected Objects
|
||||
// -----------------------------------------------------------------------------
|
||||
// -------------------------------------------------------------------------
|
||||
// Injected Objects
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
// Create Config Object
|
||||
$config = new Config();
|
||||
$container->set('config', $config);
|
||||
// Create Config Object
|
||||
$config = new Config();
|
||||
$container->set('config', $config);
|
||||
|
||||
// Create Aura Router Object
|
||||
$router_factory = new RouterFactory();
|
||||
$aura_router = $router_factory->newInstance();
|
||||
$container->set('aura-router', $aura_router);
|
||||
// Create Aura Router Object
|
||||
$aura_router = (new RouterFactory())->newInstance();
|
||||
$container->set('aura-router', $aura_router);
|
||||
|
||||
// Create Request/Response Objects
|
||||
$web_factory = new WebFactory([
|
||||
'_GET' => $_GET,
|
||||
'_POST' => $_POST,
|
||||
'_COOKIE' => $_COOKIE,
|
||||
'_SERVER' => $_SERVER,
|
||||
'_FILES' => $_FILES
|
||||
]);
|
||||
$container->set('request', $web_factory->newRequest());
|
||||
$container->set('response', $web_factory->newResponse());
|
||||
// Create Request/Response Objects
|
||||
$web_factory = new WebFactory([
|
||||
'_GET' => $_GET,
|
||||
'_POST' => $_POST,
|
||||
'_COOKIE' => $_COOKIE,
|
||||
'_SERVER' => $_SERVER,
|
||||
'_FILES' => $_FILES
|
||||
]);
|
||||
$container->set('request', $web_factory->newRequest());
|
||||
$container->set('response', $web_factory->newResponse());
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Router
|
||||
// -----------------------------------------------------------------------------
|
||||
$container->set('url-generator', new UrlGenerator($container));
|
||||
// Create session Object
|
||||
$session = (new SessionFactory())->newInstance($_COOKIE);
|
||||
$container->set('session', $session);
|
||||
|
||||
$router = new Router($container);
|
||||
$router->dispatch();
|
||||
$container->set('url-generator', new UrlGenerator($container));
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Router
|
||||
// -------------------------------------------------------------------------
|
||||
$router = new Router($container);
|
||||
$container->set('router', $router);
|
||||
|
||||
return $container;
|
||||
};
|
||||
|
||||
$di()->get('router')->dispatch();
|
||||
|
||||
// End of bootstrap.php
|
@ -5,6 +5,9 @@
|
||||
// You shouldn't generally need to change anything below this line
|
||||
// ----------------------------------------------------------------------------
|
||||
$base_config = [
|
||||
// Template file path
|
||||
'view_path' => _dir(APP_DIR, 'views'),
|
||||
|
||||
// Cache paths
|
||||
'data_cache_path' => _dir(APP_DIR, 'cache'),
|
||||
'img_cache_path' => _dir(ROOT_DIR, 'public/images'),
|
||||
|
@ -5,9 +5,6 @@ return [
|
||||
'default_namespace' => '\\Aviat\\AnimeClient\\Controller',
|
||||
'default_controller' => '\\Aviat\\AnimeClient\\Controller\\Anime',
|
||||
'default_method' => 'index'
|
||||
],
|
||||
'configuration' => [
|
||||
|
||||
],
|
||||
// Routes on all controllers
|
||||
'common' => [
|
||||
@ -71,70 +68,19 @@ return [
|
||||
'action' => ['redirect'],
|
||||
'params' => [
|
||||
'url' => '', // Determined by config
|
||||
'code' => '301'
|
||||
'code' => '301',
|
||||
'type' => 'anime'
|
||||
]
|
||||
],
|
||||
'search' => [
|
||||
'path' => '/anime/search',
|
||||
'action' => ['search'],
|
||||
],
|
||||
'all' => [
|
||||
'path' => '/anime/all{/view}',
|
||||
'anime_list' => [
|
||||
'path' => '/anime/{type}{/view}',
|
||||
'action' => ['anime_list'],
|
||||
'params' => [
|
||||
'type' => 'all',
|
||||
],
|
||||
'tokens' => [
|
||||
'view' => '[a-z_]+'
|
||||
]
|
||||
],
|
||||
'watching' => [
|
||||
'path' => '/anime/watching{/view}',
|
||||
'action' => ['anime_list'],
|
||||
'params' => [
|
||||
'type' => 'watching',
|
||||
],
|
||||
'tokens' => [
|
||||
'view' => '[a-z_]+'
|
||||
]
|
||||
],
|
||||
'plan_to_watch' => [
|
||||
'path' => '/anime/plan_to_watch{/view}',
|
||||
'action' => ['anime_list'],
|
||||
'params' => [
|
||||
'type' => 'plan_to_watch',
|
||||
],
|
||||
'tokens' => [
|
||||
'view' => '[a-z_]+'
|
||||
]
|
||||
],
|
||||
'on_hold' => [
|
||||
'path' => '/anime/on_hold{/view}',
|
||||
'action' => ['anime_list'],
|
||||
'params' => [
|
||||
'type' => 'on_hold',
|
||||
],
|
||||
'tokens' => [
|
||||
'view' => '[a-z_]+'
|
||||
]
|
||||
],
|
||||
'dropped' => [
|
||||
'path' => '/anime/dropped{/view}',
|
||||
'action' => ['anime_list'],
|
||||
'params' => [
|
||||
'type' => 'dropped',
|
||||
],
|
||||
'tokens' => [
|
||||
'view' => '[a-z_]+'
|
||||
]
|
||||
],
|
||||
'completed' => [
|
||||
'path' => '/anime/completed{/view}',
|
||||
'action' => ['anime_list'],
|
||||
'params' => [
|
||||
'type' => 'completed',
|
||||
],
|
||||
'tokens' => [
|
||||
'type' => '[a-z_]+',
|
||||
'view' => '[a-z_]+'
|
||||
]
|
||||
],
|
||||
@ -149,63 +95,11 @@ return [
|
||||
'type' => 'manga'
|
||||
]
|
||||
],
|
||||
'all' => [
|
||||
'path' => '/manga/all{/view}',
|
||||
'manga_list' => [
|
||||
'path' => '/manga/{type}{/view}',
|
||||
'action' => ['manga_list'],
|
||||
'params' => [
|
||||
'type' => 'all',
|
||||
],
|
||||
'tokens' => [
|
||||
'view' => '[a-z_]+'
|
||||
]
|
||||
],
|
||||
'reading' => [
|
||||
'path' => '/manga/reading{/view}',
|
||||
'action' => ['manga_list'],
|
||||
'params' => [
|
||||
'type' => 'reading',
|
||||
],
|
||||
'tokens' => [
|
||||
'view' => '[a-z_]+'
|
||||
]
|
||||
],
|
||||
'plan_to_read' => [
|
||||
'path' => '/manga/plan_to_read{/view}',
|
||||
'action' => ['manga_list'],
|
||||
'params' => [
|
||||
'type' => 'plan_to_read',
|
||||
],
|
||||
'tokens' => [
|
||||
'view' => '[a-z_]+'
|
||||
]
|
||||
],
|
||||
'on_hold' => [
|
||||
'path' => '/manga/on_hold{/view}',
|
||||
'action' => ['manga_list'],
|
||||
'params' => [
|
||||
'type' => 'on_hold',
|
||||
],
|
||||
'tokens' => [
|
||||
'view' => '[a-z_]+'
|
||||
]
|
||||
],
|
||||
'dropped' => [
|
||||
'path' => '/manga/dropped{/view}',
|
||||
'action' => ['manga_list'],
|
||||
'params' => [
|
||||
'type' => 'dropped',
|
||||
],
|
||||
'tokens' => [
|
||||
'view' => '[a-z_]+'
|
||||
]
|
||||
],
|
||||
'completed' => [
|
||||
'path' => '/manga/completed{/view}',
|
||||
'action' => ['manga_list'],
|
||||
'params' => [
|
||||
'type' => 'completed',
|
||||
],
|
||||
'tokens' => [
|
||||
'type' => '[a-z_]+',
|
||||
'view' => '[a-z_]+'
|
||||
]
|
||||
]
|
||||
|
@ -4,17 +4,17 @@
|
||||
<?php else: ?>
|
||||
<?php foreach ($sections as $name => $items): ?>
|
||||
<section class="status">
|
||||
<h2><?= $name ?></h2>
|
||||
<h2><?= $escape->html($name) ?></h2>
|
||||
<section class="media-wrap">
|
||||
<?php foreach($items as $item): ?>
|
||||
<article class="media" id="a-<?= $item['anime']['id'] ?>">
|
||||
<?php if (is_logged_in()): ?>
|
||||
<button class="plus_one" hidden>+1 Episode</button>
|
||||
<?php endif ?>
|
||||
<img src="<?= $item['anime']['cover_image'] ?>" />
|
||||
<?= $helper->img($item['anime']['cover_image']); ?>
|
||||
<div class="name">
|
||||
<a href="<?= $item['anime']['url'] ?>">
|
||||
<?= $item['anime']['title'] ?>
|
||||
<a href="<?= $escape->attr($item['anime']['url']) ?>">
|
||||
<?= $escape->html($item['anime']['title']) ?>
|
||||
<?= ($item['anime']['alternate_title'] != "") ? "<br />({$item['anime']['alternate_title']})" : ""; ?>
|
||||
</a>
|
||||
</div>
|
||||
@ -27,9 +27,9 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="media_type"><?= $item['anime']['show_type'] ?></div>
|
||||
<div class="airing_status"><?= $item['anime']['status'] ?></div>
|
||||
<div class="age_rating"><?= $item['anime']['age_rating'] ?></div>
|
||||
<div class="media_type"><?= $escape->html($item['anime']['show_type']) ?></div>
|
||||
<div class="airing_status"><?= $escape->html($item['anime']['status']) ?></div>
|
||||
<div class="age_rating"><?= $escape->html($item['anime']['age_rating']) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
@ -3,16 +3,16 @@
|
||||
<head>
|
||||
<title><?= $title ?></title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="<?= $urlGenerator->asset_url('css.php?g=base') ?>" />
|
||||
<link rel="stylesheet" href="<?= $escape->attr($urlGenerator->asset_url('css.php?g=base')) ?>" />
|
||||
<script>
|
||||
var BASE_URL = "<?= $urlGenerator->base_url($url_type) ?>";
|
||||
var CONTROLLER = "<?= $url_type ?>";
|
||||
</script>
|
||||
</head>
|
||||
<body class="<?= $url_type ?> list">
|
||||
<body class="<?= $escape->attr($url_type) ?> list">
|
||||
<h1 class="flex flex-align-end flex-wrap">
|
||||
<span class="flex-no-wrap grow-1">
|
||||
<a href="<?= $urlGenerator->default_url($url_type) ?>">
|
||||
<a href="<?= $escape->attr($urlGenerator->default_url($url_type)) ?>">
|
||||
<?= $config->whose_list ?>'s <?= ucfirst($url_type) ?> <?= (strpos($route_path, 'collection') !== FALSE) ? 'Collection' : 'List' ?>
|
||||
</a> [<a href="<?= $urlGenerator->default_url($other_type) ?>"><?= ucfirst($other_type) ?> List</a>]
|
||||
</span>
|
||||
|
@ -4,7 +4,7 @@
|
||||
<?php else: ?>
|
||||
<?php foreach ($sections as $name => $items): ?>
|
||||
<section class="status">
|
||||
<h2><?= $name ?></h2>
|
||||
<h2><?= $escape->html($name) ?></h2>
|
||||
<section class="media-wrap">
|
||||
<?php foreach($items as $item): ?>
|
||||
<article class="media" id="manga-<?= $item['id'] ?>">
|
||||
@ -14,10 +14,10 @@
|
||||
<button class="plus_one_volume">+1 Volume</button>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
<img src="<?= $item['manga']['poster_image'] ?>" />
|
||||
<img src="<?= $escape->attr($item['manga']['poster_image']) ?>" />
|
||||
<div class="name">
|
||||
<a href="https://hummingbird.me/manga/<?= $item['manga_id'] ?>">
|
||||
<?= $item['manga']['romaji_title'] ?>
|
||||
<?= $escape->html($item['manga']['romaji_title']) ?>
|
||||
<?= (isset($item['manga']['english_title'])) ? "<br />({$item['manga']['english_title']})" : ""; ?>
|
||||
</a>
|
||||
</div>
|
||||
@ -38,9 +38,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php /*<div class="medium_metadata">
|
||||
<div class="media_type"><?= $item['manga']['manga_type'] ?></div>
|
||||
</div> */ ?>
|
||||
</article>
|
||||
<?php endforeach ?>
|
||||
</section>
|
||||
@ -49,5 +46,5 @@
|
||||
<?php endif ?>
|
||||
</main>
|
||||
<?php if (is_logged_in()): ?>
|
||||
<script src="<?= $config->asset_url('js.php?g=edit') ?>"></script>
|
||||
<script src="<?= $urlGenerator->asset_url('js.php?g=edit') ?>"></script>
|
||||
<?php endif ?>
|
@ -1,5 +1,5 @@
|
||||
<div class="message <?= $stat_class ?>">
|
||||
<div class="message <?= $escape->attr($stat_class) ?>">
|
||||
<span class="icon"></span>
|
||||
<?= $message ?>
|
||||
<?= $escape->html($message) ?>
|
||||
<span class="close" onclick="this.parentElement.style.display='none'">x</span>
|
||||
</div>
|
@ -3,6 +3,7 @@
|
||||
"description": "A self-hosted anime/manga client for hummingbird.",
|
||||
"license":"MIT",
|
||||
"require": {
|
||||
"container-interop/container-interop": "1.*",
|
||||
"guzzlehttp/guzzle": "5.3.*",
|
||||
"filp/whoops": "dev-php7#fe32a402b086b21360e82013e8a0355575c7c6f4",
|
||||
"aura/router": "2.2.*",
|
||||
@ -12,6 +13,7 @@
|
||||
"aviat4ion/query": "2.5.*",
|
||||
"robmorgan/phinx": "0.4.*",
|
||||
"abeautifulsite/simpleimage": "2.5.*",
|
||||
"szymach/c-pchart": "1.*"
|
||||
"szymach/c-pchart": "1.*",
|
||||
"league/fractal": "0.12.0"
|
||||
}
|
||||
}
|
@ -2,7 +2,6 @@
|
||||
/**
|
||||
* Here begins everything!
|
||||
*/
|
||||
|
||||
session_start();
|
||||
|
||||
// Work around the silly timezone error
|
||||
@ -30,9 +29,6 @@ function _dir()
|
||||
return implode(DIRECTORY_SEPARATOR, func_get_args());
|
||||
}
|
||||
|
||||
// Set up composer autoloader
|
||||
require _dir(ROOT_DIR, '/vendor/autoload.php');
|
||||
|
||||
/**
|
||||
* Set up autoloaders
|
||||
*
|
||||
@ -50,7 +46,8 @@ spl_autoload_register(function ($class) {
|
||||
}
|
||||
});
|
||||
|
||||
// Do dependency injection, and go!
|
||||
// Dependency setup
|
||||
require _dir(ROOT_DIR, '/vendor/autoload.php');
|
||||
require _dir(APP_DIR, 'bootstrap.php');
|
||||
|
||||
// End of index.php
|
50
src/Aviat/AnimeClient/Auth/HummingbirdAuth.php
Normal file
50
src/Aviat/AnimeClient/Auth/HummingbirdAuth.php
Normal file
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace Aviat\AnimeClient\Auth;
|
||||
|
||||
use Aviat\Ion\Di\ContainerInterface;
|
||||
use Aviat\AnimeClient\Model\Anime as AnimeModel;
|
||||
|
||||
class HummingbirdAuth {
|
||||
|
||||
use \Aviat\Ion\Di\ContainerAware;
|
||||
|
||||
/**
|
||||
* Anime API Model
|
||||
*
|
||||
* @var AnimeModel
|
||||
*/
|
||||
protected $model;
|
||||
|
||||
/**
|
||||
* Session object
|
||||
*
|
||||
* @var Aura\Session\Segment
|
||||
*/
|
||||
protected $session;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param ContainerInterface $container
|
||||
*/
|
||||
public function __construct(ContainerInterface $container)
|
||||
{
|
||||
$this->setContainer($container);
|
||||
$this->session = $container->get('sesion')
|
||||
->getSegment(__NAMESPACE__);
|
||||
$this->model = new AnimeModel($container);
|
||||
}
|
||||
|
||||
public function authenticate($username, $password)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function get_auth_token()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
// End of HummingbirdAuth.php
|
@ -2,10 +2,14 @@
|
||||
|
||||
namespace Aviat\AnimeClient;
|
||||
|
||||
use Aviat\Ion\Di\Container as BaseContainer;
|
||||
use Aviat\Ion\Di\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Dependency container
|
||||
*/
|
||||
class Container extends \Aviat\Ion\Base\Container {
|
||||
class Container
|
||||
extends BaseContainer {
|
||||
|
||||
}
|
||||
// End of Container.php
|
@ -4,23 +4,26 @@
|
||||
*/
|
||||
namespace Aviat\AnimeClient;
|
||||
|
||||
use Aura\Web\ResponseSender;
|
||||
|
||||
use \Aviat\Ion\Di\ContainerInterface;
|
||||
use \Aviat\Ion\View\HttpView;
|
||||
use \Aviat\Ion\View\HtmlView;
|
||||
use \Aviat\Ion\View\JsonView;
|
||||
|
||||
/**
|
||||
* Base class for controllers, defines output methods
|
||||
* Controller base, defines output methods
|
||||
*/
|
||||
class Controller {
|
||||
|
||||
use \Aviat\Ion\Di\ContainerAware;
|
||||
|
||||
/**
|
||||
* The global configuration object
|
||||
* @var object $config
|
||||
*/
|
||||
protected $config;
|
||||
|
||||
/**
|
||||
* Request object
|
||||
* @var object $request
|
||||
*/
|
||||
protected $request;
|
||||
|
||||
/**
|
||||
* Response object
|
||||
* @var object $response
|
||||
@ -54,26 +57,15 @@ class Controller {
|
||||
*
|
||||
* @param Container $container
|
||||
*/
|
||||
public function __construct(Container $container)
|
||||
public function __construct(ContainerInterface $container)
|
||||
{
|
||||
$this->setContainer($container);
|
||||
$urlGenerator = $container->get('url-generator');
|
||||
$this->config = $container->get('config');
|
||||
$this->base_data['config'] = $this->config;
|
||||
$this->base_data['urlGenerator'] = $container->get('url-generator');
|
||||
|
||||
$this->request = $container->get('request');
|
||||
$this->response = $container->get('response');
|
||||
|
||||
$this->urlGenerator = $container->get('url-generator');
|
||||
}
|
||||
|
||||
/**
|
||||
* Destructor
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
$this->output();
|
||||
$this->base_data['urlGenerator'] = $urlGenerator;
|
||||
$this->urlGenerator = $urlGenerator;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -84,7 +76,7 @@ class Controller {
|
||||
*/
|
||||
public function __get($key)
|
||||
{
|
||||
$allowed = ['request', 'response', 'config'];
|
||||
$allowed = ['response', 'config'];
|
||||
|
||||
if (in_array($key, $allowed))
|
||||
{
|
||||
@ -97,78 +89,94 @@ class Controller {
|
||||
/**
|
||||
* Get the string output of a partial template
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
* @param HTMLView $view
|
||||
* @param string $template
|
||||
* @param array|object $data
|
||||
* @return string
|
||||
*/
|
||||
public function load_partial($template, $data=[])
|
||||
public function load_partial($view, $template, $data=[])
|
||||
{
|
||||
$errorHandler = $this->container->get('error-handler');
|
||||
$router = $this->container->get('router');
|
||||
|
||||
if (isset($this->base_data))
|
||||
{
|
||||
$data = array_merge($this->base_data, $data);
|
||||
}
|
||||
|
||||
global $router, $defaultHandler;
|
||||
$route = $router->get_route();
|
||||
$data['route_path'] = ($route) ? $router->get_route()->path : "";
|
||||
|
||||
$defaultHandler->addDataTable('Template Data', $data);
|
||||
|
||||
$template_path = _dir(APP_DIR, 'views', "{$template}.php");
|
||||
$errorHandler->addDataTable('Template Data', $data);
|
||||
$template_path = _dir($this->config->__get('view_path'), "{$template}.php");
|
||||
|
||||
if ( ! is_file($template_path))
|
||||
{
|
||||
throw new \InvalidArgumentException("Invalid template : {$path}");
|
||||
throw new \InvalidArgumentException("Invalid template : {$template}");
|
||||
}
|
||||
|
||||
ob_start();
|
||||
extract($data);
|
||||
include _dir(APP_DIR, 'views', 'header.php');
|
||||
include $template_path;
|
||||
include _dir(APP_DIR, 'views', 'footer.php');
|
||||
$buffer = ob_get_contents();
|
||||
ob_end_clean();
|
||||
return $view->render_template($template_path, $data);
|
||||
}
|
||||
|
||||
return $buffer;
|
||||
/**
|
||||
* Render a template with header and footer
|
||||
*
|
||||
* @param HTMLView $view
|
||||
* @param string $template
|
||||
* @param array|object $data
|
||||
* @return void
|
||||
*/
|
||||
public function render_full_page($view, $template, $data)
|
||||
{
|
||||
$view->appendOutput($this->load_partial($view, 'header', $data));
|
||||
$view->appendOutput($this->load_partial($view, $template, $data));
|
||||
$view->appendOutput($this->load_partial($view, 'footer', $data));
|
||||
}
|
||||
|
||||
/**
|
||||
* Output a template to HTML, using the provided data
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
* @param string $template
|
||||
* @param array|object $data
|
||||
* @return void
|
||||
*/
|
||||
public function outputHTML($template, $data=[])
|
||||
{
|
||||
$buffer = $this->load_partial($template, $data);
|
||||
$view = new HtmlView($this->container);
|
||||
$this->render_full_page($view, $template, $data);
|
||||
}
|
||||
|
||||
$this->response->content->setType('text/html');
|
||||
$this->response->content->set($buffer);
|
||||
/**
|
||||
* Output a JSON Response
|
||||
*
|
||||
* @param mixed $data
|
||||
* @return void
|
||||
*/
|
||||
public function outputJSON($data=[])
|
||||
{
|
||||
$view = new JsonView($this->container);
|
||||
$view->setOutput($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect to the selected page
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
* @param string $url
|
||||
* @param string $path
|
||||
* @param int $code
|
||||
* @param string $type
|
||||
* @return void
|
||||
*/
|
||||
public function redirect($url, $code, $type="anime")
|
||||
public function redirect($path, $code, $type="anime")
|
||||
{
|
||||
$url = $this->urlGenerator->full_url($url, $type);
|
||||
$url = $this->urlGenerator->full_url($path, $type);
|
||||
$http = new HttpView($this->container);
|
||||
|
||||
$this->response->redirect->to($url, $code);
|
||||
$http->redirect($url, $code);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a message box to the page
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
* @param string $type
|
||||
* @param string $message
|
||||
* @return string
|
||||
@ -184,7 +192,6 @@ class Controller {
|
||||
/**
|
||||
* Clear the api session
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
* @return void
|
||||
*/
|
||||
public function logout()
|
||||
@ -196,7 +203,6 @@ class Controller {
|
||||
/**
|
||||
* Show the login form
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
* @param string $status
|
||||
* @return void
|
||||
*/
|
||||
@ -222,53 +228,22 @@ class Controller {
|
||||
*/
|
||||
public function login_action()
|
||||
{
|
||||
$request = $this->container->get('request');
|
||||
|
||||
if (
|
||||
$this->model->authenticate(
|
||||
$this->config->hummingbird_username,
|
||||
$this->request->post->get('password')
|
||||
$request->post->get('password')
|
||||
)
|
||||
)
|
||||
{
|
||||
$this->response->redirect->afterPost($this->urlGenerator->full_url('', $this->base_data['url_type']));
|
||||
$this->response->redirect->afterPost(
|
||||
$this->urlGenerator->full_url('', $this->base_data['url_type'])
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
$this->login("Invalid username or password.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the appropriate response
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
* @return void
|
||||
*/
|
||||
private function output()
|
||||
{
|
||||
// send status
|
||||
@header($this->response->status->get(), true, $this->response->status->getCode());
|
||||
|
||||
// headers
|
||||
foreach($this->response->headers->get() as $label => $value)
|
||||
{
|
||||
@header("{$label}: {$value}");
|
||||
}
|
||||
|
||||
// cookies
|
||||
foreach($this->response->cookies->get() as $name => $cookie)
|
||||
{
|
||||
@setcookie(
|
||||
$name,
|
||||
$cookie['value'],
|
||||
$cookie['expire'],
|
||||
$cookie['path'],
|
||||
$cookie['domain'],
|
||||
$cookie['secure'],
|
||||
$cookie['httponly']
|
||||
);
|
||||
}
|
||||
|
||||
// send the actual response
|
||||
echo $this->response->content->get();
|
||||
}
|
||||
}
|
||||
// End of BaseController.php
|
@ -5,9 +5,8 @@
|
||||
|
||||
namespace Aviat\AnimeClient\Controller;
|
||||
|
||||
use Aviat\AnimeClient\Container;
|
||||
use Aviat\Ion\Di\ContainerInterface;
|
||||
use Aviat\AnimeClient\Controller as BaseController;
|
||||
use Aviat\AnimeClient\Config;
|
||||
use Aviat\AnimeClient\Model\Anime as AnimeModel;
|
||||
use Aviat\AnimeClient\Model\AnimeCollection as AnimeCollectionModel;
|
||||
|
||||
@ -53,12 +52,10 @@ class Anime extends BaseController {
|
||||
*
|
||||
* @param Container $container
|
||||
*/
|
||||
public function __construct(Container $container)
|
||||
public function __construct(ContainerInterface $container)
|
||||
{
|
||||
parent::__construct($container);
|
||||
|
||||
$config = $container->get('config');
|
||||
|
||||
if ($this->config->show_anime_collection === FALSE)
|
||||
{
|
||||
unset($this->nav_routes['Collection']);
|
||||
|
@ -5,7 +5,7 @@
|
||||
|
||||
namespace Aviat\AnimeClient\Controller;
|
||||
|
||||
use Aviat\AnimeClient\Container;
|
||||
use Aviat\Ion\Di\ContainerInterface;
|
||||
use Aviat\AnimeClient\Controller as BaseController;
|
||||
use Aviat\AnimeClient\Config;
|
||||
use Aviat\AnimeClient\UrlGenerator;
|
||||
@ -54,7 +54,7 @@ class Collection extends BaseController {
|
||||
*
|
||||
* @param Container $container
|
||||
*/
|
||||
public function __construct(Container $container)
|
||||
public function __construct(ContainerInterface $container)
|
||||
{
|
||||
parent::__construct($container);
|
||||
|
||||
|
@ -4,7 +4,7 @@
|
||||
*/
|
||||
namespace Aviat\AnimeClient\Controller;
|
||||
|
||||
use Aviat\AnimeClient\Container;
|
||||
use Aviat\Ion\Di\ContainerInterface;
|
||||
use Aviat\AnimeClient\Controller;
|
||||
use Aviat\AnimeClient\Config;
|
||||
use Aviat\AnimeClient\Model\Manga as MangaModel;
|
||||
@ -45,7 +45,7 @@ class Manga extends Controller {
|
||||
*
|
||||
* @param Container $container
|
||||
*/
|
||||
public function __construct(Container $container)
|
||||
public function __construct(ContainerInterface $container)
|
||||
{
|
||||
parent::__construct($container);
|
||||
$config = $container->get('config');
|
||||
@ -86,7 +86,7 @@ class Manga extends Controller {
|
||||
'on_hold' => 'On Hold'
|
||||
];
|
||||
|
||||
$title = $this->config->whose_list . "' Manga List · {$map[$status]}";
|
||||
$title = $this->config->whose_list . "'s Manga List · {$map[$status]}";
|
||||
|
||||
$view_map = [
|
||||
'' => 'cover',
|
||||
@ -99,7 +99,7 @@ class Manga extends Controller {
|
||||
|
||||
$this->outputHTML('manga/' . $view_map[$view], [
|
||||
'title' => $title,
|
||||
'sections' => $data
|
||||
'sections' => $data,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
namespace Aviat\AnimeClient\Controller;
|
||||
|
||||
use Aviat\AnimeClient\Container;
|
||||
use Aviat\Ion\Di\ContainerInterface;
|
||||
use Aviat\AnimeClient\Controller;
|
||||
|
||||
class Stats extends Controller {
|
||||
|
@ -4,6 +4,7 @@
|
||||
*/
|
||||
namespace Aviat\AnimeClient;
|
||||
|
||||
use Aviat\Ion\Di\ContainerInterface;
|
||||
use abeautifulsite\SimpleImage;
|
||||
|
||||
/**
|
||||
@ -28,7 +29,7 @@ class Model {
|
||||
*
|
||||
* @param Container $container
|
||||
*/
|
||||
public function __construct(Container $container)
|
||||
public function __construct(ContainerInterface $container)
|
||||
{
|
||||
$this->container = $container;
|
||||
$this->config = $container->get('config');
|
||||
|
@ -6,12 +6,13 @@ namespace Aviat\AnimeClient\Model;
|
||||
|
||||
use \GuzzleHttp\Client;
|
||||
use \GuzzleHttp\Cookie\CookieJar;
|
||||
use \Aviat\AnimeClient\Container;
|
||||
use Aviat\Ion\Di\ContainerInterface;
|
||||
use Aviat\AnimeClient\Model as BaseModel;
|
||||
|
||||
/**
|
||||
* Base model for api interaction
|
||||
*/
|
||||
class API extends \Aviat\AnimeClient\Model {
|
||||
class API extends BaseModel {
|
||||
|
||||
/**
|
||||
* Base url for making api requests
|
||||
@ -36,7 +37,7 @@ class API extends \Aviat\AnimeClient\Model {
|
||||
*
|
||||
* @param Container $container
|
||||
*/
|
||||
public function __construct(Container $container)
|
||||
public function __construct(ContainerInterface $container)
|
||||
{
|
||||
parent::__construct($container);
|
||||
$this->cookieJar = new CookieJar();
|
||||
|
@ -138,7 +138,7 @@ class Anime extends API {
|
||||
*/
|
||||
public function search($name)
|
||||
{
|
||||
global $defaultHandler;
|
||||
$errorHandler = $this->container->get('error-handler');
|
||||
|
||||
$config = [
|
||||
'query' => [
|
||||
@ -147,7 +147,7 @@ class Anime extends API {
|
||||
];
|
||||
|
||||
$response = $this->client->get('search/anime', $config);
|
||||
$defaultHandler->addDataTable('anime_search_response', (array)$response);
|
||||
$errorHandler->addDataTable('anime_search_response', (array)$response);
|
||||
|
||||
if ($response->getStatusCode() != 200)
|
||||
{
|
||||
@ -165,7 +165,7 @@ class Anime extends API {
|
||||
*/
|
||||
private function _get_list($status="all")
|
||||
{
|
||||
global $defaultHandler;
|
||||
$errorHandler = $this->container->get('error-handler');
|
||||
|
||||
$cache_file = "{$this->config->data_cache_path}/anime-{$status}.json";
|
||||
|
||||
@ -180,7 +180,7 @@ class Anime extends API {
|
||||
|
||||
$response = $this->client->get("users/{$this->config->hummingbird_username}/library", $config);
|
||||
|
||||
$defaultHandler->addDataTable('anime_list_response', (array)$response);
|
||||
$errorHandler->addDataTable('anime_list_response', (array)$response);
|
||||
|
||||
if ($response->getStatusCode() != 200)
|
||||
{
|
||||
|
@ -5,8 +5,8 @@
|
||||
|
||||
namespace Aviat\AnimeClient\Model;
|
||||
|
||||
use Aviat\Ion\Di\ContainerInterface;
|
||||
use Aviat\AnimeClient\Model\DB;
|
||||
use Aviat\AnimeClient\Container;
|
||||
use Aviat\AnimeClient\Model\Anime as AnimeModel;
|
||||
|
||||
/**
|
||||
@ -31,7 +31,7 @@ class AnimeCollection extends DB {
|
||||
*
|
||||
* @param Container $container
|
||||
*/
|
||||
public function __construct(Container $container)
|
||||
public function __construct(ContainerInterface $container)
|
||||
{
|
||||
parent::__construct($container);
|
||||
|
||||
|
@ -4,12 +4,13 @@
|
||||
*/
|
||||
namespace Aviat\AnimeClient\Model;
|
||||
|
||||
use Aviat\AnimeClient\Container;
|
||||
use Aviat\Ion\Di\ContainerInterface;
|
||||
use Aviat\AnimeClient\Model as BaseModel;
|
||||
|
||||
/**
|
||||
* Base model for database interaction
|
||||
*/
|
||||
class DB extends \Aviat\AnimeClient\Model {
|
||||
class DB extends BaseModel {
|
||||
/**
|
||||
* The query builder object
|
||||
* @var object $db
|
||||
@ -27,7 +28,7 @@ class DB extends \Aviat\AnimeClient\Model {
|
||||
*
|
||||
* @param Container $container
|
||||
*/
|
||||
public function __construct(Container $container)
|
||||
public function __construct(ContainerInterface $container)
|
||||
{
|
||||
parent::__construct($container);
|
||||
$this->db_config = $this->config->database;
|
||||
|
@ -76,7 +76,7 @@ class Manga extends API {
|
||||
*/
|
||||
private function _get_list($status="all")
|
||||
{
|
||||
global $defaultHandler;
|
||||
$errorHandler = $this->container->get('error-handler');
|
||||
|
||||
$cache_file = _dir($this->config->data_cache_path, 'manga.json');
|
||||
|
||||
@ -89,7 +89,7 @@ class Manga extends API {
|
||||
|
||||
$response = $this->client->get('manga_library_entries', $config);
|
||||
|
||||
$defaultHandler->addDataTable('response', (array)$response);
|
||||
$errorHandler->addDataTable('response', (array)$response);
|
||||
|
||||
if ($response->getStatusCode() != 200)
|
||||
{
|
||||
|
@ -5,19 +5,22 @@
|
||||
|
||||
namespace Aviat\AnimeClient\Model;
|
||||
|
||||
use Avait\Ion\Di\ContainerInterface;
|
||||
use Aviat\AnimeClient\Model\DB;
|
||||
use Aviat\AnimeClient\Container;
|
||||
|
||||
use StatsChartsTrait;
|
||||
|
||||
/**
|
||||
* Base Model for stats about lists and collection(s)
|
||||
*/
|
||||
class Stats extends DB {
|
||||
|
||||
use StatsChartsTrait;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param Container $container
|
||||
*/
|
||||
public function __construct(Container $container)
|
||||
public function __construct(ContainerInterface $container)
|
||||
{
|
||||
parent::__construct($container);
|
||||
$this->chartSetup();
|
||||
|
@ -7,6 +7,8 @@ namespace Aviat\AnimeClient;
|
||||
use Aura\Web\Request;
|
||||
use Aura\Web\Response;
|
||||
|
||||
use Aviat\Ion\Di\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Basic routing/ dispatch
|
||||
*/
|
||||
@ -35,7 +37,7 @@ class Router extends RoutingBase {
|
||||
*
|
||||
* @param Container $container
|
||||
*/
|
||||
public function __construct(Container $container)
|
||||
public function __construct(ContainerInterface $container)
|
||||
{
|
||||
parent::__construct($container);
|
||||
$this->router = $container->get('aura-router');
|
||||
@ -100,6 +102,12 @@ class Router extends RoutingBase {
|
||||
else
|
||||
{
|
||||
list($controller_name, $action_method) = $route->params['action'];
|
||||
|
||||
if (is_null($controller_name))
|
||||
{
|
||||
throw new \LogicException("Missing controller");
|
||||
}
|
||||
|
||||
$params = (isset($route->params['params'])) ? $route->params['params'] : [];
|
||||
|
||||
if ( ! empty($route->tokens))
|
||||
@ -205,7 +213,8 @@ class Router extends RoutingBase {
|
||||
array_unshift($route['action'], $controller_class);
|
||||
|
||||
// Select the appropriate router method based on the http verb
|
||||
$add = (array_key_exists('verb', $route)) ? "add" . ucfirst(strtolower($route['verb'])) : "addGet";
|
||||
$add = (array_key_exists('verb', $route))
|
||||
? "add" . ucfirst(strtolower($route['verb'])) : "addGet";
|
||||
|
||||
// Add the route to the router object
|
||||
if ( ! array_key_exists('tokens', $route))
|
||||
|
@ -5,6 +5,8 @@
|
||||
*/
|
||||
namespace Aviat\AnimeClient;
|
||||
|
||||
use Aviat\Ion\Di\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Base for routing/url classes
|
||||
*/
|
||||
@ -32,7 +34,7 @@ class RoutingBase {
|
||||
*
|
||||
* @param Container $container
|
||||
*/
|
||||
public function __construct(Container $container)
|
||||
public function __construct(ContainerInterface $container)
|
||||
{
|
||||
$this->container = $container;
|
||||
$this->config = $container->get('config');
|
||||
|
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace Aviat\AnimeClient\Transformer\Hummingbird;
|
||||
|
||||
use League\Fractal;
|
||||
|
||||
class AnimeListTransformer extends Fractal\TransformerAbstract {
|
||||
|
||||
public function transform($item)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
// End of AnimeListTransformer.php
|
@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace Aviat\AnimeClient\Transformer\Hummingbird;
|
||||
|
||||
/**
|
||||
* Merges the two separate manga lists together
|
||||
*/
|
||||
class MangaListsZipper {
|
||||
|
||||
/**
|
||||
* List of manga information
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $manga_series_list = [];
|
||||
|
||||
/**
|
||||
* List of manga tracking information
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $manga_tracking_list = [];
|
||||
|
||||
/**
|
||||
* Create the transformer
|
||||
*
|
||||
* @param array $merge_lists The raw manga data
|
||||
*/
|
||||
public function __construct(array $merge_lists)
|
||||
{
|
||||
$this->manga_series_list = $merge_lists['manga'];
|
||||
$this->manga_tracking_list = $merge_lists['manga_library_entries'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Do the transformation, and return the output
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function transform()
|
||||
{
|
||||
$this->index_manga_entries();
|
||||
|
||||
$output = [];
|
||||
|
||||
foreach($this->manga_tracking_list as &$entry)
|
||||
{
|
||||
$id = $entry['manga_id'];
|
||||
$entry['manga'] = $this->manga_series_list[$id];
|
||||
unset($entry['manga_id']);
|
||||
|
||||
$output[] = $entry;
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Index manga series by the id
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function index_manga_entries()
|
||||
{
|
||||
$orig_list = $this->manga_series_list;
|
||||
$indexed_list = [];
|
||||
|
||||
foreach($orig_list as $manga)
|
||||
{
|
||||
$id = $manga['id'];
|
||||
$indexed_list[$id] = $manga;
|
||||
}
|
||||
|
||||
$this->manga_series_list = $indexed_list;
|
||||
}
|
||||
|
||||
}
|
||||
// End of ManagListsZipper.php
|
@ -1,54 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Aviat\Ion\Base;
|
||||
|
||||
/**
|
||||
* Dependency container
|
||||
*/
|
||||
class Container {
|
||||
|
||||
/**
|
||||
* Array with class instances
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $container = [];
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param array $values (optional)
|
||||
*/
|
||||
public function __construct(array $values = [])
|
||||
{
|
||||
$this->container = $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a value
|
||||
*
|
||||
* @param string $key
|
||||
* @retun mixed
|
||||
*/
|
||||
public function get($key)
|
||||
{
|
||||
if (array_key_exists($key, $this->container))
|
||||
{
|
||||
return $this->container[$key];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a value to the container
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
* @return Container
|
||||
*/
|
||||
public function set($key, $value)
|
||||
{
|
||||
$this->container[$key] = $value;
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
// End of Container.php
|
82
src/Aviat/Ion/Di/Container.php
Normal file
82
src/Aviat/Ion/Di/Container.php
Normal file
@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
namespace Aviat\Ion\Di;
|
||||
|
||||
use ArrayObject;
|
||||
use Aviat\Ion\Di\Exception\ContainerException;
|
||||
use Aviat\Ion\Di\Exception\NotFoundException;
|
||||
|
||||
/**
|
||||
* Dependency container
|
||||
*/
|
||||
class Container implements ContainerInterface {
|
||||
|
||||
/**
|
||||
* Array with class instances
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $container = [];
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param array $values (optional)
|
||||
*/
|
||||
public function __construct(array $values = [])
|
||||
{
|
||||
$this->container = new ArrayObject($values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds an entry of the container by its identifier and returns it.
|
||||
*
|
||||
* @param string $id Identifier of the entry to look for.
|
||||
*
|
||||
* @throws NotFoundException No entry was found for this identifier.
|
||||
* @throws ContainerException Error while retrieving the entry.
|
||||
*
|
||||
* @return mixed Entry.
|
||||
*/
|
||||
public function get($id)
|
||||
{
|
||||
if ( ! is_string($id))
|
||||
{
|
||||
throw new Exception\ContainerException("Id must be a string");
|
||||
}
|
||||
|
||||
if ($this->has($id))
|
||||
{
|
||||
return $this->container[$id];
|
||||
}
|
||||
|
||||
throw new Exception\NotFoundException("Item {$id} does not exist in container.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a value to the container
|
||||
*
|
||||
* @param string $id
|
||||
* @param mixed $value
|
||||
* @return Container
|
||||
*/
|
||||
public function set($id, $value)
|
||||
{
|
||||
$this->container[$id] = $value;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the container can return an entry for the given identifier.
|
||||
* Returns false otherwise.
|
||||
*
|
||||
* @param string $id Identifier of the entry to look for.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function has($id)
|
||||
{
|
||||
return $this->container->offsetExists($id);
|
||||
}
|
||||
}
|
||||
// End of Container.php
|
36
src/Aviat/Ion/Di/ContainerAware.php
Normal file
36
src/Aviat/Ion/Di/ContainerAware.php
Normal file
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace Aviat\Ion\Di;
|
||||
|
||||
trait ContainerAware {
|
||||
|
||||
/**
|
||||
* Di Container
|
||||
*
|
||||
* @var ContainerInterface
|
||||
*/
|
||||
protected $container;
|
||||
|
||||
/**
|
||||
* Set the container for the current object
|
||||
*
|
||||
* @param ContainerInterface $container
|
||||
* @return $this
|
||||
*/
|
||||
public function setContainer(ContainerInterface $container)
|
||||
{
|
||||
$this->container = $container;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the container object
|
||||
*
|
||||
* @return ContainerInterface
|
||||
*/
|
||||
public function getContainer()
|
||||
{
|
||||
return $this->container;
|
||||
}
|
||||
}
|
||||
// End of ContainerAware.php
|
23
src/Aviat/Ion/Di/ContainerAwareInterface.php
Normal file
23
src/Aviat/Ion/Di/ContainerAwareInterface.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace Aviat\Ion\Di;
|
||||
|
||||
interface ContainerAwareInterface {
|
||||
|
||||
/**
|
||||
* Set the container for the current object
|
||||
*
|
||||
* @param ContainerInterface $container
|
||||
* @return void
|
||||
*/
|
||||
public function setContainer(ContainerInterface $container);
|
||||
|
||||
/**
|
||||
* Get the container object
|
||||
*
|
||||
* @return ContainerInterface
|
||||
*/
|
||||
public function getContainer();
|
||||
|
||||
}
|
||||
// End of ContainerAwareInterface.php
|
15
src/Aviat/Ion/Di/ContainerInterface.php
Normal file
15
src/Aviat/Ion/Di/ContainerInterface.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace Aviat\Ion\Di;
|
||||
|
||||
interface ContainerInterface extends \Interop\Container\ContainerInterface {
|
||||
|
||||
/**
|
||||
* Add a value to the container
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
* @return ContainerInterface
|
||||
*/
|
||||
public function set($key, $value);
|
||||
}
|
10
src/Aviat/Ion/Di/Exception/ContainerException.php
Normal file
10
src/Aviat/Ion/Di/Exception/ContainerException.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Aviat\Ion\Di\Exception;
|
||||
|
||||
class ContainerException
|
||||
extends \Exception
|
||||
implements \Interop\Container\Exception\ContainerException {
|
||||
|
||||
}
|
||||
// End of ContainerException.php
|
10
src/Aviat/Ion/Di/Exception/NotFoundException.php
Normal file
10
src/Aviat/Ion/Di/Exception/NotFoundException.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Aviat\Ion\Di\Exception;
|
||||
|
||||
class NotFoundException
|
||||
extends ContainerException
|
||||
implements \Interop\Container\Exception\NotFoundException {
|
||||
|
||||
}
|
||||
// End of NotFoundException.php
|
105
src/Aviat/Ion/View.php
Normal file
105
src/Aviat/Ion/View.php
Normal file
@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
namespace Aviat\Ion;
|
||||
|
||||
use Aviat\Ion\Di\ContainerInterface;
|
||||
|
||||
abstract class View {
|
||||
|
||||
use Di\ContainerAware;
|
||||
|
||||
/**
|
||||
* DI Container
|
||||
*
|
||||
* @var ContainerInterface
|
||||
*/
|
||||
protected $container;
|
||||
|
||||
/**
|
||||
* HTTP response Object
|
||||
*
|
||||
* @var Aura\Web\Response
|
||||
*/
|
||||
protected $response;
|
||||
|
||||
/**
|
||||
* Response mime type
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $contentType = '';
|
||||
|
||||
/**
|
||||
* String of response to be output
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $output = '';
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param ContainerInterface $container
|
||||
*/
|
||||
public function __construct(ContainerInterface $container)
|
||||
{
|
||||
$this->setContainer($container);
|
||||
$this->response = $container->get('response');
|
||||
}
|
||||
|
||||
/**
|
||||
* Send output to client
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
$this->output();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the output string
|
||||
*
|
||||
* @param string $string
|
||||
* @return View
|
||||
*/
|
||||
public function setOutput($string)
|
||||
{
|
||||
$this->output = $string;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Append additional output
|
||||
*
|
||||
* @param string $string
|
||||
* @return View
|
||||
*/
|
||||
public function appendOutput($string)
|
||||
{
|
||||
$this->output .= $string;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current output string
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getOutput()
|
||||
{
|
||||
return $this->output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the appropriate response
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function output()
|
||||
{
|
||||
$content =& $this->response->content;
|
||||
$content->set($this->output);
|
||||
$content->setType($this->contentType);
|
||||
$content->setCharset('utf-8');
|
||||
}
|
||||
}
|
||||
// End of View.php
|
50
src/Aviat/Ion/View/HtmlView.php
Normal file
50
src/Aviat/Ion/View/HtmlView.php
Normal file
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace Aviat\Ion\View;
|
||||
|
||||
use Aura\Html\HelperLocatorFactory;
|
||||
|
||||
use Aviat\Ion\View\HttpView;
|
||||
use Aviat\Ion\Di\ContainerInterface;
|
||||
|
||||
class HtmlView extends HttpView {
|
||||
|
||||
protected $helper;
|
||||
|
||||
public function __construct(ContainerInterface $container)
|
||||
{
|
||||
parent::__construct($container);
|
||||
$this->helper = (new HelperLocatorFactory)->newInstance();
|
||||
}
|
||||
|
||||
/**
|
||||
* Response mime type
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $contentType = 'text/html';
|
||||
|
||||
/**
|
||||
* Render a basic html Template
|
||||
*
|
||||
* @param string $path
|
||||
* @param array $data
|
||||
* @return string
|
||||
*/
|
||||
public function render_template($path, $data)
|
||||
{
|
||||
$buffer = "";
|
||||
|
||||
$data['helper'] = $this->helper;
|
||||
$data['escape'] = $this->helper->escape();
|
||||
|
||||
ob_start();
|
||||
extract($data);
|
||||
include $path;
|
||||
$buffer = ob_get_contents();
|
||||
ob_end_clean();
|
||||
|
||||
return $buffer;
|
||||
}
|
||||
}
|
||||
// End of HtmlView.php
|
36
src/Aviat/Ion/View/HttpView.php
Normal file
36
src/Aviat/Ion/View/HttpView.php
Normal file
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace Aviat\Ion\View;
|
||||
|
||||
use Aura\Web\ResponseSender;
|
||||
|
||||
use Aviat\Ion\View as BaseView;
|
||||
|
||||
class HttpView extends BaseView {
|
||||
|
||||
/**
|
||||
* Do a redirect
|
||||
*
|
||||
* @param string $url
|
||||
* @param int $code
|
||||
* @return void
|
||||
*/
|
||||
public function redirect($url, $code)
|
||||
{
|
||||
$this->response->redirect->to($url, $code);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the appropriate response
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function output()
|
||||
{
|
||||
parent::output();
|
||||
|
||||
$sender = new ResponseSender($this->response);
|
||||
$sender->__invoke();
|
||||
}
|
||||
|
||||
}
|
32
src/Aviat/Ion/View/JsonView.php
Normal file
32
src/Aviat/Ion/View/JsonView.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace Aviat\Ion\View;
|
||||
|
||||
use Aviat\Ion\View\HttpView;
|
||||
|
||||
class JsonView extends HttpView {
|
||||
|
||||
/**
|
||||
* Response mime type
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $contentType = 'application/json';
|
||||
|
||||
/**
|
||||
* Set the output string
|
||||
*
|
||||
* @param mixed $string
|
||||
* @return View
|
||||
*/
|
||||
public function setOutput($string)
|
||||
{
|
||||
if ( ! is_string($string))
|
||||
{
|
||||
$string = json_encode($string);
|
||||
}
|
||||
|
||||
return parent::setOutput($string);
|
||||
}
|
||||
}
|
||||
// End of JsonView.php
|
@ -1,7 +1,6 @@
|
||||
<?php
|
||||
|
||||
use Aviat\AnimeClient\Model as BaseModel;
|
||||
use Aviat\AnimeClient\Container;
|
||||
|
||||
class BaseModelTest extends AnimeClient_TestCase {
|
||||
|
||||
|
@ -31,9 +31,6 @@ class ControllerTest extends AnimeClient_TestCase {
|
||||
public function dataGet()
|
||||
{
|
||||
return [
|
||||
'request' => [
|
||||
'key' => 'request',
|
||||
],
|
||||
'response' => [
|
||||
'key' => 'response',
|
||||
],
|
||||
|
@ -1,11 +1,12 @@
|
||||
<?php
|
||||
|
||||
use Aviat\Ion\Di\ContainerInterface;
|
||||
use Aviat\AnimeClient\Container;
|
||||
use Aviat\AnimeClient\Model\API as BaseApiModel;
|
||||
|
||||
class MockBaseApiModel extends BaseApiModel {
|
||||
|
||||
public function __construct(Container $container)
|
||||
public function __construct(ContainerInterface $container)
|
||||
{
|
||||
parent::__construct($container);
|
||||
}
|
||||
|
@ -74,7 +74,6 @@ class RouterTest extends AnimeClient_TestCase {
|
||||
'action' => ['anime_list'],
|
||||
'params' => [
|
||||
'type' => 'currently-watching',
|
||||
'title' => WHOSE . " Anime List · Watching"
|
||||
],
|
||||
'tokens' => [
|
||||
'view' => '[a-z_]+'
|
||||
@ -87,7 +86,6 @@ class RouterTest extends AnimeClient_TestCase {
|
||||
'action' => ['manga_list'],
|
||||
'params' => [
|
||||
'type' => 'Plan to Read',
|
||||
'title' => WHOSE . " Manga List · Plan to Read"
|
||||
],
|
||||
'tokens' => [
|
||||
'view' => '[a-z_]+'
|
||||
|
24
tests/AnimeClient/Transformer/MangaListsZipperTest.php
Normal file
24
tests/AnimeClient/Transformer/MangaListsZipperTest.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
use Aviat\AnimeClient\Transformer\Hummingbird\MangaListsZipper;
|
||||
|
||||
class MangaListsZipperTest extends AnimeClient_TestCase {
|
||||
|
||||
protected $start_file = __DIR__ . '/../../test_data/manga_list/manga.json';
|
||||
protected $res_file = __DIR__ . '/../../test_data/manga_list/manga-zippered.json';
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$json = json_decode(file_get_contents($this->start_file), TRUE);
|
||||
$this->mangaListsZipper = new MangaListsZipper($json);
|
||||
}
|
||||
|
||||
public function testTransform()
|
||||
{
|
||||
$zippered_json = json_decode(file_get_contents($this->res_file), TRUE);
|
||||
$transformed = $this->mangaListsZipper->transform();
|
||||
|
||||
$this->assertEquals($zippered_json, $transformed);
|
||||
}
|
||||
|
||||
}
|
@ -5,6 +5,7 @@
|
||||
|
||||
use Aviat\AnimeClient\Config;
|
||||
use Aviat\AnimeClient\Container;
|
||||
use Aviat\AnimeClient\UrlGenerator;
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Mock the default error handler
|
||||
@ -14,8 +15,6 @@ class MockErrorHandler {
|
||||
public function addDataTable($name, Array $values) {}
|
||||
}
|
||||
|
||||
$defaultHandler = new MockErrorHandler();
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Define a base testcase class
|
||||
// -----------------------------------------------------------------------------
|
||||
@ -41,9 +40,12 @@ class AnimeClient_TestCase extends PHPUnit_Framework_TestCase {
|
||||
]);
|
||||
|
||||
$container = new Container([
|
||||
'config' => $config
|
||||
'config' => $config,
|
||||
'error-handler' => new MockErrorHandler()
|
||||
]);
|
||||
|
||||
$container->set('url-generator', new UrlGenerator($container));
|
||||
|
||||
$this->container = $container;
|
||||
}
|
||||
}
|
||||
@ -52,12 +54,6 @@ class AnimeClient_TestCase extends PHPUnit_Framework_TestCase {
|
||||
// Autoloaders
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// Define WHOSE constant
|
||||
define('WHOSE', "Foo's");
|
||||
|
||||
// Define base path constants
|
||||
define('ROOT_DIR', realpath(__DIR__ . DIRECTORY_SEPARATOR . "/../"));
|
||||
|
||||
/**
|
||||
* Joins paths together. Variadic to take an
|
||||
* arbitrary number of arguments
|
||||
@ -69,10 +65,14 @@ function _dir()
|
||||
return implode(DIRECTORY_SEPARATOR, func_get_args());
|
||||
}
|
||||
|
||||
// Define base path constants
|
||||
define('ROOT_DIR', realpath(__DIR__ . DIRECTORY_SEPARATOR . "/../"));
|
||||
define('APP_DIR', _dir(ROOT_DIR, 'app'));
|
||||
define('CONF_DIR', _dir(APP_DIR, 'config'));
|
||||
define('SRC_DIR', _dir(ROOT_DIR, 'src'));
|
||||
define('BASE_DIR', _dir(SRC_DIR, 'Base'));
|
||||
require _dir(ROOT_DIR, '/vendor/autoload.php');
|
||||
require _dir(SRC_DIR, 'functions.php');
|
||||
|
||||
/**
|
||||
* Set up autoloaders
|
||||
@ -80,24 +80,16 @@ define('BASE_DIR', _dir(SRC_DIR, 'Base'));
|
||||
* @codeCoverageIgnore
|
||||
* @return void
|
||||
*/
|
||||
function _setup_autoloaders()
|
||||
{
|
||||
require _dir(ROOT_DIR, '/vendor/autoload.php');
|
||||
spl_autoload_register(function ($class) {
|
||||
$class_parts = explode('\\', $class);
|
||||
$ns_path = SRC_DIR . '/' . implode('/', $class_parts) . ".php";
|
||||
spl_autoload_register(function ($class) {
|
||||
$class_parts = explode('\\', $class);
|
||||
$ns_path = SRC_DIR . '/' . implode('/', $class_parts) . ".php";
|
||||
|
||||
if (file_exists($ns_path))
|
||||
{
|
||||
require_once($ns_path);
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Setup autoloaders
|
||||
_setup_autoloaders();
|
||||
require(_dir(SRC_DIR, 'functions.php'));
|
||||
if (file_exists($ns_path))
|
||||
{
|
||||
require_once($ns_path);
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
// Pre-define some superglobals
|
||||
$_SESSION = [];
|
||||
|
797
tests/test_data/anime_list/anime-watching.json
Normal file
797
tests/test_data/anime_list/anime-watching.json
Normal file
@ -0,0 +1,797 @@
|
||||
[
|
||||
{
|
||||
"id": 9131610,
|
||||
"episodes_watched": 2,
|
||||
"last_watched": "2015-09-17T16:52:19.028Z",
|
||||
"updated_at": "2015-09-17T16:52:19.029Z",
|
||||
"rewatched_times": 0,
|
||||
"notes": null,
|
||||
"notes_present": null,
|
||||
"status": "currently-watching",
|
||||
"private": false,
|
||||
"rewatching": false,
|
||||
"anime": {
|
||||
"id": 7190,
|
||||
"mal_id": 14967,
|
||||
"slug": "boku-wa-tomodachi-ga-sukunai-next",
|
||||
"status": "Finished Airing",
|
||||
"url": "https://hummingbird.me/anime/boku-wa-tomodachi-ga-sukunai-next",
|
||||
"title": "Boku wa Tomodachi ga Sukunai NEXT",
|
||||
"alternate_title": "Haganai NEXT",
|
||||
"episode_count": 12,
|
||||
"episode_length": 24,
|
||||
"cover_image": "https://static.hummingbird.me/anime/poster_images/000/007/190/large/0.jpg?1417468876",
|
||||
"synopsis": "The Neighbor's Club—a club founded for the purpose of making friends,where misfortunate boys and girls with few friends live out their regrettable lives.\r\nAlthough Yozora Mikazuki faced a certain incident at the end of summer,the daily life of the Neighbor's Club goes on as usual.A strange nun,members of the student council and other new faces make an appearance,causing Kodaka Hasegawa's life to grow even busier.\r\nWhile they all enjoy going to the amusement park,playing games,celebrating birthdays,and challenging the\"school festival\"—a symbol of the school life normal people live—the relations amongst the members slowly begins to change...\r\nLet the next stage begin,on this unfortunate coming-of-age love comedy!!\r\n(Source:ANN)",
|
||||
"show_type": "TV",
|
||||
"started_airing": "2013-01-11",
|
||||
"finished_airing": "2013-03-29",
|
||||
"community_rating": 3.8820340732555,
|
||||
"age_rating": "R17+",
|
||||
"genres": [
|
||||
{
|
||||
"name": "Comedy"
|
||||
},
|
||||
{
|
||||
"name": "Romance"
|
||||
},
|
||||
{
|
||||
"name": "School"
|
||||
},
|
||||
{
|
||||
"name": "Harem"
|
||||
}
|
||||
]
|
||||
},
|
||||
"rating": {
|
||||
"type": "advanced",
|
||||
"value": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 10177172,
|
||||
"episodes_watched": 11,
|
||||
"last_watched": "2015-09-14T23:49:37.044Z",
|
||||
"updated_at": "2015-09-14T23:49:37.045Z",
|
||||
"rewatched_times": 0,
|
||||
"notes": null,
|
||||
"notes_present": null,
|
||||
"status": "currently-watching",
|
||||
"private": false,
|
||||
"rewatching": false,
|
||||
"anime": {
|
||||
"id": 10350,
|
||||
"mal_id": 29785,
|
||||
"slug": "jitsu-wa-watashi-wa",
|
||||
"status": "Currently Airing",
|
||||
"url": "https://hummingbird.me/anime/jitsu-wa-watashi-wa",
|
||||
"title": "Jitsu wa Watashi wa",
|
||||
"alternate_title": "Actually, I Am…",
|
||||
"episode_count": 13,
|
||||
"episode_length": 24,
|
||||
"cover_image": "https://static.hummingbird.me/anime/poster_images/000/010/350/large/ndmkhu.jpg?1431603318",
|
||||
"synopsis": "Asahi Kuromine has a crush on a cute girl named Youko Shiragami. Shiragami just happens to be a vampire. Asahi cannot keep a secret, but he is determined to keep Shiragami's secret anyway.\r\n\r\n(Source: ANN)",
|
||||
"show_type": "TV",
|
||||
"started_airing": "2015-07-07",
|
||||
"finished_airing": null,
|
||||
"community_rating": 3.768867119294,
|
||||
"age_rating": "PG13",
|
||||
"genres": [
|
||||
{
|
||||
"name": "Comedy"
|
||||
},
|
||||
{
|
||||
"name": "Fantasy"
|
||||
},
|
||||
{
|
||||
"name": "Romance"
|
||||
},
|
||||
{
|
||||
"name": "School"
|
||||
}
|
||||
]
|
||||
},
|
||||
"rating": {
|
||||
"type": "advanced",
|
||||
"value": "4.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 9131615,
|
||||
"episodes_watched": 8,
|
||||
"last_watched": "2015-09-12T18:14:16.370Z",
|
||||
"updated_at": "2015-09-12T18:14:16.371Z",
|
||||
"rewatched_times": 0,
|
||||
"notes": null,
|
||||
"notes_present": null,
|
||||
"status": "currently-watching",
|
||||
"private": false,
|
||||
"rewatching": false,
|
||||
"anime": {
|
||||
"id": 9095,
|
||||
"mal_id": 27525,
|
||||
"slug": "fate-kaleid-liner-prisma-illya-2wei-herz",
|
||||
"status": "Currently Airing",
|
||||
"url": "https://hummingbird.me/anime/fate-kaleid-liner-prisma-illya-2wei-herz",
|
||||
"title": "Fate/kaleid liner Prisma☆Illya 2wei Herz!",
|
||||
"alternate_title": null,
|
||||
"episode_count": 10,
|
||||
"episode_length": 23,
|
||||
"cover_image": "https://static.hummingbird.me/anime/poster_images/000/009/095/large/Q0l30yH.jpg?1427031275",
|
||||
"synopsis": "Third season of Fate/kaleid Liner Prisma Illya.",
|
||||
"show_type": "TV",
|
||||
"started_airing": "2015-07-24",
|
||||
"finished_airing": null,
|
||||
"community_rating": 3.7841266617022,
|
||||
"age_rating": "PG13",
|
||||
"genres": [
|
||||
{
|
||||
"name": "Action"
|
||||
},
|
||||
{
|
||||
"name": "Comedy"
|
||||
},
|
||||
{
|
||||
"name": "Magic"
|
||||
},
|
||||
{
|
||||
"name": "Fantasy"
|
||||
},
|
||||
{
|
||||
"name": "Mahou Shoujo"
|
||||
}
|
||||
]
|
||||
},
|
||||
"rating": {
|
||||
"type": "advanced",
|
||||
"value": "4.5"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 10426033,
|
||||
"episodes_watched": 8,
|
||||
"last_watched": "2015-08-01T23:26:21.869Z",
|
||||
"updated_at": "2015-08-01T23:26:21.870Z",
|
||||
"rewatched_times": 0,
|
||||
"notes": null,
|
||||
"notes_present": null,
|
||||
"status": "currently-watching",
|
||||
"private": false,
|
||||
"rewatching": false,
|
||||
"anime": {
|
||||
"id": 475,
|
||||
"mal_id": 516,
|
||||
"slug": "keroro-gunsou",
|
||||
"status": "Finished Airing",
|
||||
"url": "https://hummingbird.me/anime/keroro-gunsou",
|
||||
"title": "Keroro Gunsou",
|
||||
"alternate_title": "Sergeant Frog",
|
||||
"episode_count": 358,
|
||||
"episode_length": 23,
|
||||
"cover_image": "https://static.hummingbird.me/anime/poster_images/000/000/475/large/475.jpg?1416242348",
|
||||
"synopsis": "Keroro is a frog-like alien sent from his home planet on a mission to conquer Earth. But when his cover is blown, his battalion abandons him and he ends up in the home of the Hinata family. There, he's forced to do household chores and sleep in a dark basement that was once supposedly a prison cell haunted by the ghost of an innocent girl. He even spends his free time assembling Gundam model kits. During his stay, Keroro meets up with subordinates who were also stranded during their failed invasion. \n(Source: ANN)",
|
||||
"show_type": "TV",
|
||||
"started_airing": "2004-04-03",
|
||||
"finished_airing": "2011-04-04",
|
||||
"community_rating": 3.8815806455204,
|
||||
"age_rating": "PG13",
|
||||
"genres": [
|
||||
{
|
||||
"name": "Comedy"
|
||||
},
|
||||
{
|
||||
"name": "Sci-Fi"
|
||||
}
|
||||
]
|
||||
},
|
||||
"rating": {
|
||||
"type": "advanced",
|
||||
"value": "3.5"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 10299917,
|
||||
"episodes_watched": 11,
|
||||
"last_watched": "2015-09-14T23:55:59.297Z",
|
||||
"updated_at": "2015-09-14T23:55:59.298Z",
|
||||
"rewatched_times": 0,
|
||||
"notes": null,
|
||||
"notes_present": null,
|
||||
"status": "currently-watching",
|
||||
"private": false,
|
||||
"rewatching": false,
|
||||
"anime": {
|
||||
"id": 10352,
|
||||
"mal_id": 29786,
|
||||
"slug": "shimoneta-to-iu-gainen-ga-sonzai-shinai-taikutsu-na-sekai",
|
||||
"status": "Currently Airing",
|
||||
"url": "https://hummingbird.me/anime/shimoneta-to-iu-gainen-ga-sonzai-shinai-taikutsu-na-sekai",
|
||||
"title": "Shimoneta to Iu Gainen ga Sonzai Shinai Taikutsu na Sekai",
|
||||
"alternate_title": "SHIMONETA: A Boring World Where the Concept of Dirty Jokes Doesn't Exist",
|
||||
"episode_count": 12,
|
||||
"episode_length": 24,
|
||||
"cover_image": "https://static.hummingbird.me/anime/poster_images/000/010/352/large/shimoneta02.jpg?1433942845",
|
||||
"synopsis": "Who is the panty-masked villainess spreading obscenity in a country where even the mildest off-color musing can land you in jail?\r\n\r\nWhen the student council president of the most elite public morals school in the country has a feeling that the lewd is coming from within the walls, she recruits Tanukichi, a recent transfer student, to her upstanding moral squad.\r\n\r\nLittle does she know he’s already been blackmailed by Ayame, her own vice president who is secretly the panty-masked bandit, into committing mass acts of public obscenity in the name of SOX—a brigade of sorts—dedicated to spreading the good news of being lewd.\r\n\r\n(Source: FUNimation)",
|
||||
"show_type": "TV",
|
||||
"started_airing": "2015-07-04",
|
||||
"finished_airing": null,
|
||||
"community_rating": 4.0207233212975,
|
||||
"age_rating": "R17+",
|
||||
"genres": [
|
||||
{
|
||||
"name": "Comedy"
|
||||
},
|
||||
{
|
||||
"name": "School"
|
||||
},
|
||||
{
|
||||
"name": "Ecchi"
|
||||
}
|
||||
]
|
||||
},
|
||||
"rating": {
|
||||
"type": "advanced",
|
||||
"value": "4.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 10299832,
|
||||
"episodes_watched": 10,
|
||||
"last_watched": "2015-09-14T01:56:45.778Z",
|
||||
"updated_at": "2015-09-14T01:56:45.778Z",
|
||||
"rewatched_times": 0,
|
||||
"notes": null,
|
||||
"notes_present": null,
|
||||
"status": "currently-watching",
|
||||
"private": false,
|
||||
"rewatching": false,
|
||||
"anime": {
|
||||
"id": 10621,
|
||||
"mal_id": 30123,
|
||||
"slug": "akagami-no-shirayuki-hime",
|
||||
"status": "Currently Airing",
|
||||
"url": "https://hummingbird.me/anime/akagami-no-shirayuki-hime",
|
||||
"title": "Akagami no Shirayuki-hime",
|
||||
"alternate_title": "Snow White with the Red Hair",
|
||||
"episode_count": 12,
|
||||
"episode_length": 24,
|
||||
"cover_image": "https://static.hummingbird.me/anime/poster_images/000/010/621/large/e5ac7bdd2b175b6aee20d8f8528147731432360559_full.jpg?1432401413",
|
||||
"synopsis": "In the kingdom of Tanbarun lives Shirayuki, an independent and strong-willed young woman. Her resourceful intelligence has led her become a skilled pharmacist, but her most defining trait is her shock of beautiful apple-red hair. Her dazzling mane gets her noticed by the prince of the kingdom, but instead of romancing her, he demands she be his concubine. Shirayuki refuses, chops off her lovely locks, and runs away to the neighboring kingdom of Clarines. There, she befriends a young man named Zen, who, SURPRISE, is also a prince, although with a much better temperament than the previous one. Watch as Shirayuki finds her place in the new kingdom, and in Zen’s heart.\r\n\r\n(Source: FUNimation)",
|
||||
"show_type": "TV",
|
||||
"started_airing": "2015-07-07",
|
||||
"finished_airing": "2015-09-22",
|
||||
"community_rating": 4.1011684647044,
|
||||
"age_rating": "PG13",
|
||||
"genres": [
|
||||
{
|
||||
"name": "Drama"
|
||||
},
|
||||
{
|
||||
"name": "Fantasy"
|
||||
},
|
||||
{
|
||||
"name": "Romance"
|
||||
},
|
||||
{
|
||||
"name": "Historical"
|
||||
}
|
||||
]
|
||||
},
|
||||
"rating": {
|
||||
"type": "advanced",
|
||||
"value": "4.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 10299826,
|
||||
"episodes_watched": 11,
|
||||
"last_watched": "2015-09-12T13:36:42.211Z",
|
||||
"updated_at": "2015-09-12T13:36:42.212Z",
|
||||
"rewatched_times": 0,
|
||||
"notes": null,
|
||||
"notes_present": null,
|
||||
"status": "currently-watching",
|
||||
"private": false,
|
||||
"rewatching": false,
|
||||
"anime": {
|
||||
"id": 10069,
|
||||
"mal_id": 28819,
|
||||
"slug": "oku-sama-ga-seito-kaichou",
|
||||
"status": "Finished Airing",
|
||||
"url": "https://hummingbird.me/anime/oku-sama-ga-seito-kaichou",
|
||||
"title": "Oku-sama ga Seito Kaichou!",
|
||||
"alternate_title": "My Wife is the Student Council President!",
|
||||
"episode_count": 12,
|
||||
"episode_length": 8,
|
||||
"cover_image": "https://static.hummingbird.me/anime/poster_images/000/010/069/large/173744.jpg?1432381972",
|
||||
"synopsis": "The story begins with Izumi Hayato running to be student body president. But when a beautiful girl swings in promising the liberalization of love while flinging condoms into the audience, he ends up losing to her and becoming the vice president. At the student council meeting, the newly-elected president invites herself over to Izumi's house, where she promptly announces she is to become Izumi's wife thanks to an agreement—facilitated by alcohol—made between their parents when they were only 3.\r\n\r\n(Source: MAL Scanlations)",
|
||||
"show_type": "TV",
|
||||
"started_airing": "2015-07-02",
|
||||
"finished_airing": "2015-09-17",
|
||||
"community_rating": 3.3822121645568,
|
||||
"age_rating": "R17+",
|
||||
"genres": [
|
||||
{
|
||||
"name": "Comedy"
|
||||
},
|
||||
{
|
||||
"name": "Romance"
|
||||
},
|
||||
{
|
||||
"name": "School"
|
||||
},
|
||||
{
|
||||
"name": "Ecchi"
|
||||
}
|
||||
]
|
||||
},
|
||||
"rating": {
|
||||
"type": "advanced",
|
||||
"value": "4.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 10271011,
|
||||
"episodes_watched": 11,
|
||||
"last_watched": "2015-09-14T01:26:22.895Z",
|
||||
"updated_at": "2015-09-14T01:26:22.895Z",
|
||||
"rewatched_times": 0,
|
||||
"notes": null,
|
||||
"notes_present": null,
|
||||
"status": "currently-watching",
|
||||
"private": false,
|
||||
"rewatching": false,
|
||||
"anime": {
|
||||
"id": 10029,
|
||||
"mal_id": 28497,
|
||||
"slug": "rokka-no-yuusha",
|
||||
"status": "Currently Airing",
|
||||
"url": "https://hummingbird.me/anime/rokka-no-yuusha",
|
||||
"title": "Rokka no Yuusha",
|
||||
"alternate_title": "Rokka: Braves of the Six Flowers",
|
||||
"episode_count": 12,
|
||||
"episode_length": 24,
|
||||
"cover_image": "https://static.hummingbird.me/anime/poster_images/000/010/029/large/rokkanoyuusha.jpg?1436386289",
|
||||
"synopsis": "Legend says, when the Evil God awakens from the deepest of darkness, the god of fate will summon Six Braves and grant them with the power to save the world. \r\n \r\nAdlet, who claims to be the strongest on the face of this earth, is chosen as one of the “Brave Six Flowers,” and sets out on a battle to prevent the resurrection of the Evil God. However, it turns out that there are Seven Braves who gathered at the promised land... \r\n \r\nThe Seven Braves notice there must be one enemy among themselves, and feelings of suspicion toward each other spreads throughout the group, with Adlet being the one who gets suspected first and foremost. \r\n \r\nThus begins an overwhelming fantasy adventure that brings upon mystery after mystery!\r\n\r\n(Source: Crunchyroll)",
|
||||
"show_type": "TV",
|
||||
"started_airing": "2015-07-05",
|
||||
"finished_airing": "2015-09-20",
|
||||
"community_rating": 3.9872922407283,
|
||||
"age_rating": "PG13",
|
||||
"genres": [
|
||||
{
|
||||
"name": "Action"
|
||||
},
|
||||
{
|
||||
"name": "Adventure"
|
||||
},
|
||||
{
|
||||
"name": "Mystery"
|
||||
},
|
||||
{
|
||||
"name": "Magic"
|
||||
},
|
||||
{
|
||||
"name": "Fantasy"
|
||||
}
|
||||
]
|
||||
},
|
||||
"rating": {
|
||||
"type": "advanced",
|
||||
"value": "4.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 10296163,
|
||||
"episodes_watched": 10,
|
||||
"last_watched": "2015-09-12T18:38:06.334Z",
|
||||
"updated_at": "2015-09-12T18:38:06.335Z",
|
||||
"rewatched_times": 0,
|
||||
"notes": null,
|
||||
"notes_present": null,
|
||||
"status": "currently-watching",
|
||||
"private": false,
|
||||
"rewatching": false,
|
||||
"anime": {
|
||||
"id": 9726,
|
||||
"mal_id": 27831,
|
||||
"slug": "durarara-x2-ten",
|
||||
"status": "Currently Airing",
|
||||
"url": "https://hummingbird.me/anime/durarara-x2-ten",
|
||||
"title": "Durarara!!x2 Ten",
|
||||
"alternate_title": "Durarara!! x2 The Second Arc",
|
||||
"episode_count": 12,
|
||||
"episode_length": 24,
|
||||
"cover_image": "https://static.hummingbird.me/anime/poster_images/000/009/726/large/durararax2tenv2.jpg?1435341790",
|
||||
"synopsis": "Ikebukuro, a city teeming with the most peculiar characters and the twisted schemes they indulge in. In the aftermath of the assault against the information broker, signs of new disorder begin to develop like ripples across the water. Holding his own ideals, the young man who gains the powers of both the “Dollars” and the “Blue Squares” treads the path to total annihilation. Someone struggles to save their best friend while a psychopath creeps up on a popular idol. Slowly but surely a new threat gains power within the city’s shadows…\n\nPaths cross and trouble brews as the plot thickens in this complicated web of conspiracies.\n\n(Source: Aniplex USA)",
|
||||
"show_type": "TV",
|
||||
"started_airing": "2015-07-04",
|
||||
"finished_airing": "2015-09-26",
|
||||
"community_rating": 4.3105326468495,
|
||||
"age_rating": "R17+",
|
||||
"genres": [
|
||||
{
|
||||
"name": "Action"
|
||||
},
|
||||
{
|
||||
"name": "Mystery"
|
||||
},
|
||||
{
|
||||
"name": "Supernatural"
|
||||
}
|
||||
]
|
||||
},
|
||||
"rating": {
|
||||
"type": "advanced",
|
||||
"value": "4.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 10295958,
|
||||
"episodes_watched": 11,
|
||||
"last_watched": "2015-09-17T01:54:16.472Z",
|
||||
"updated_at": "2015-09-17T01:54:16.472Z",
|
||||
"rewatched_times": 0,
|
||||
"notes": null,
|
||||
"notes_present": null,
|
||||
"status": "currently-watching",
|
||||
"private": false,
|
||||
"rewatching": false,
|
||||
"anime": {
|
||||
"id": 10748,
|
||||
"mal_id": 30307,
|
||||
"slug": "monster-musume-no-iru-nichijou",
|
||||
"status": "Currently Airing",
|
||||
"url": "https://hummingbird.me/anime/monster-musume-no-iru-nichijou",
|
||||
"title": "Monster Musume no Iru Nichijou",
|
||||
"alternate_title": "Everyday Life with Monster Girls",
|
||||
"episode_count": 12,
|
||||
"episode_length": 23,
|
||||
"cover_image": "https://static.hummingbird.me/anime/poster_images/000/010/748/large/hcxfyjp1_l-anime-monster-musume-no-iru-nichijou-en-promotion-video.jpg?1434653745",
|
||||
"synopsis": "Monsters—they're real, and they want to date us! Three years ago, the world learned that harpies, centaurs, catgirls, and all manners of fabulous creatures are not merely fiction; they are flesh and blood—not to mention scale, feather, horn, and fang. Thanks to the \"Cultural Exchange Between Species Act,\" these once-mythical creatures have assimilated into society, or at least, they're trying.\r\n\r\nWhen a hapless human named Kurusu Kimihito is inducted as a \"volunteer\" into the government exchange program, his world is turned upside down. A snake-like lamia named Miia comes to live with him, and it is Kurusu's job to take care of her and make sure she integrates into his everyday life. Unfortunately for Kurusu, Miia is undeniably sexy, and the law against interspecies breeding is very strict. Even worse, when a ravishing centaur girl and a flirtatious harpy move in, what's a full-blooded young man with raging hormones to do?!\r\n\r\n(Source: Seven Seas Entertainment)",
|
||||
"show_type": "TV",
|
||||
"started_airing": "2015-07-08",
|
||||
"finished_airing": "2015-09-23",
|
||||
"community_rating": 3.8451747238313,
|
||||
"age_rating": "PG13",
|
||||
"genres": [
|
||||
{
|
||||
"name": "Comedy"
|
||||
},
|
||||
{
|
||||
"name": "Fantasy"
|
||||
},
|
||||
{
|
||||
"name": "Romance"
|
||||
},
|
||||
{
|
||||
"name": "Ecchi"
|
||||
},
|
||||
{
|
||||
"name": "Harem"
|
||||
}
|
||||
]
|
||||
},
|
||||
"rating": {
|
||||
"type": "advanced",
|
||||
"value": "3.5"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 10295719,
|
||||
"episodes_watched": 11,
|
||||
"last_watched": "2015-09-12T14:20:43.922Z",
|
||||
"updated_at": "2015-09-12T14:20:43.923Z",
|
||||
"rewatched_times": 0,
|
||||
"notes": null,
|
||||
"notes_present": null,
|
||||
"status": "currently-watching",
|
||||
"private": false,
|
||||
"rewatching": false,
|
||||
"anime": {
|
||||
"id": 10085,
|
||||
"mal_id": 28907,
|
||||
"slug": "gate-jieitai-kanochi-nite-kaku-tatakaeri",
|
||||
"status": "Currently Airing",
|
||||
"url": "https://hummingbird.me/anime/gate-jieitai-kanochi-nite-kaku-tatakaeri",
|
||||
"title": "Gate: Jieitai Kanochi nite, Kaku Tatakaeri",
|
||||
"alternate_title": "GATE",
|
||||
"episode_count": 12,
|
||||
"episode_length": 24,
|
||||
"cover_image": "https://static.hummingbird.me/anime/poster_images/000/010/085/large/85a5d8cc2972ae422158be7069076be41435868848_full.jpg?1435924413",
|
||||
"synopsis": "In August of 20XX, a portal to a parallel world, known as the \"Gate,\" suddenly appeared in Ginza, Tokyo. Monsters and troops poured out of the portal, turning the shopping district into a bloody inferno.\r\n\r\nThe Japan Ground-Self Defence Force immediately took action and pushed the fantasy creatures back to the \"Gate.\" To facilitate negotiations and prepare for future fights, the JGSDF dispatched the Third Reconnaissance Team to the \"Special Region\" at the other side of the Gate.\r\n\r\nYouji Itami, a JSDF officer as well as a 33-year-old otaku, was appointed as the leader of the Team. Amid attacks from enemy troops the team visited a variety of places and learnt a lot about the local culture and geography.\r\n\r\nThanks to their efforts in humanitarian relief, although with some difficulties they were gradually able to reach out to the locals. They even had a cute elf, a sorceress and a demigoddess in their circle of new friends. On the other hand, the major powers outside the Gate such as the United States, China, and Russia were extremely interested in the abundant resources available in the Special Region. They began to exert diplomatic pressure over Japan.\r\n\r\nA suddenly appearing portal to an unknown world—to the major powers it may be no more than a mere asset for toppling the international order. But to our protagonists it is an invaluable opportunity to broaden knowledge, friendship, and ultimately their perspective towards the world.\r\n\r\n(Source: Baka-Tsuki)",
|
||||
"show_type": "TV",
|
||||
"started_airing": "2015-07-04",
|
||||
"finished_airing": null,
|
||||
"community_rating": 4.10315369511,
|
||||
"age_rating": null,
|
||||
"genres": [
|
||||
{
|
||||
"name": "Action"
|
||||
},
|
||||
{
|
||||
"name": "Adventure"
|
||||
},
|
||||
{
|
||||
"name": "Fantasy"
|
||||
},
|
||||
{
|
||||
"name": "Military"
|
||||
}
|
||||
]
|
||||
},
|
||||
"rating": {
|
||||
"type": "advanced",
|
||||
"value": "4.5"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 10215731,
|
||||
"episodes_watched": 11,
|
||||
"last_watched": "2015-09-12T13:45:12.780Z",
|
||||
"updated_at": "2015-09-12T13:45:12.780Z",
|
||||
"rewatched_times": 0,
|
||||
"notes": null,
|
||||
"notes_present": null,
|
||||
"status": "currently-watching",
|
||||
"private": false,
|
||||
"rewatching": false,
|
||||
"anime": {
|
||||
"id": 10765,
|
||||
"mal_id": 30384,
|
||||
"slug": "miss-monochrome-the-animation-2nd-season",
|
||||
"status": "Currently Airing",
|
||||
"url": "https://hummingbird.me/anime/miss-monochrome-the-animation-2nd-season",
|
||||
"title": "Miss Monochrome: The Animation 2nd Season",
|
||||
"alternate_title": null,
|
||||
"episode_count": 13,
|
||||
"episode_length": 8,
|
||||
"cover_image": "https://static.hummingbird.me/anime/poster_images/000/010/765/large/20150511_summer02.jpg?1432381052",
|
||||
"synopsis": "The Ultra Super Pictures Special Stage event announced at AnimeJapan 2015 on Saturday that the Miss Monochrome television anime series will receive a second season. The season will run within the 30-minute Ultra Super Anime Time block beginning on July 3, 2015.\r\n\r\n(Source: ANN)",
|
||||
"show_type": "TV",
|
||||
"started_airing": "2015-07-03",
|
||||
"finished_airing": null,
|
||||
"community_rating": 3.6555205723713,
|
||||
"age_rating": "G",
|
||||
"genres": [
|
||||
{
|
||||
"name": "Slice of Life"
|
||||
},
|
||||
{
|
||||
"name": "Music"
|
||||
}
|
||||
]
|
||||
},
|
||||
"rating": {
|
||||
"type": "advanced",
|
||||
"value": "4.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 10177163,
|
||||
"episodes_watched": 11,
|
||||
"last_watched": "2015-09-12T17:47:16.672Z",
|
||||
"updated_at": "2015-09-12T17:47:16.672Z",
|
||||
"rewatched_times": 0,
|
||||
"notes": null,
|
||||
"notes_present": null,
|
||||
"status": "currently-watching",
|
||||
"private": false,
|
||||
"rewatching": false,
|
||||
"anime": {
|
||||
"id": 10782,
|
||||
"mal_id": 30383,
|
||||
"slug": "classroom-crisis",
|
||||
"status": "Currently Airing",
|
||||
"url": "https://hummingbird.me/anime/classroom-crisis",
|
||||
"title": "Classroom☆Crisis",
|
||||
"alternate_title": "",
|
||||
"episode_count": 12,
|
||||
"episode_length": 24,
|
||||
"cover_image": "https://static.hummingbird.me/anime/poster_images/000/010/782/large/gUUHG7u.jpg?1432480149",
|
||||
"synopsis": "Fourth Tokyo--one of Japan’s new prefectures on Mars. Kirishina City, Fourth Tokyo’s special economic zone, is home to the Kirishina Corporation, an elite corporation renowned for its aerospace business. The company has been expanding its market share in various industries, while also running a private school, the Kirishina Science and Technology Academy High School. That alone would make it unique, but there’s also a high-profile class on campus. \r\n \r\nDevoting themselves to their studies during the day, they then report to the company after school to take part in a crucial project, the development of prototype variants for rockets. This is the Kirishina Corporation’s Advanced Technological Development Department, Educational Development Class, a.k.a. A-TEC. A-TEC’s chief, the young engineering genius, Kaito Sera, is also the homeroom teacher of the A- TEC students attending the academy, affectionately (?) known as the Raving Rocket Teacher. \r\n \r\nThe story begins with the arrival of a transfer student to A-TEC. \r\n \r\nThe A-TEC members are ready to welcome their new classmate, but the student in question is kidnapped en route to Mars. Determining that they themselves will have to be the ones to overcome this crisis, Kaito and the A-TEC students embark on an unprecedented rescue mission.\r\n\r\n(Source: Aniplex USA)",
|
||||
"show_type": "TV",
|
||||
"started_airing": "2015-07-04",
|
||||
"finished_airing": null,
|
||||
"community_rating": 3.3777667556786,
|
||||
"age_rating": "PG13",
|
||||
"genres": [
|
||||
{
|
||||
"name": "Comedy"
|
||||
},
|
||||
{
|
||||
"name": "Sci-Fi"
|
||||
},
|
||||
{
|
||||
"name": "Romance"
|
||||
},
|
||||
{
|
||||
"name": "Slice of Life"
|
||||
},
|
||||
{
|
||||
"name": "School"
|
||||
}
|
||||
]
|
||||
},
|
||||
"rating": {
|
||||
"type": "advanced",
|
||||
"value": "4.5"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 10177168,
|
||||
"episodes_watched": 11,
|
||||
"last_watched": "2015-09-14T01:01:35.226Z",
|
||||
"updated_at": "2015-09-14T01:01:35.227Z",
|
||||
"rewatched_times": 0,
|
||||
"notes": null,
|
||||
"notes_present": null,
|
||||
"status": "currently-watching",
|
||||
"private": false,
|
||||
"rewatching": false,
|
||||
"anime": {
|
||||
"id": 10103,
|
||||
"mal_id": 28999,
|
||||
"slug": "charlotte",
|
||||
"status": "Currently Airing",
|
||||
"url": "https://hummingbird.me/anime/charlotte",
|
||||
"title": "Charlotte",
|
||||
"alternate_title": "",
|
||||
"episode_count": 13,
|
||||
"episode_length": 24,
|
||||
"cover_image": "https://static.hummingbird.me/anime/poster_images/000/010/103/large/charlotte.jpg?1434903194",
|
||||
"synopsis": "In a world where children have a chance to develop special powers upon reaching puberty. Otosaka Yuu is one such child, choosing to live a relatively normal, satisfying life despite possessing an ability to control others’ bodies for a short period of time. One day, Yuu is suddenly approached by Tomori Nao, another child with special powers. Their meeting sets the stage for a story about growth, their many experiences, and a cruel fate that links the two. A routine life changes to one filled with the unexpected, a promise to return home becoming their only guide down an uncertain road. \r\n\r\n(Source: Aniplex of America)",
|
||||
"show_type": "TV",
|
||||
"started_airing": "2015-07-05",
|
||||
"finished_airing": "2015-09-27",
|
||||
"community_rating": 4.0201120371147,
|
||||
"age_rating": "PG13",
|
||||
"genres": [
|
||||
{
|
||||
"name": "Comedy"
|
||||
},
|
||||
{
|
||||
"name": "Drama"
|
||||
},
|
||||
{
|
||||
"name": "Super Power"
|
||||
},
|
||||
{
|
||||
"name": "School"
|
||||
}
|
||||
]
|
||||
},
|
||||
"rating": {
|
||||
"type": "advanced",
|
||||
"value": "4.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 9131652,
|
||||
"episodes_watched": 12,
|
||||
"last_watched": "2015-09-12T20:06:39.355Z",
|
||||
"updated_at": "2015-09-12T20:06:39.355Z",
|
||||
"rewatched_times": 0,
|
||||
"notes": null,
|
||||
"notes_present": null,
|
||||
"status": "currently-watching",
|
||||
"private": false,
|
||||
"rewatching": false,
|
||||
"anime": {
|
||||
"id": 8712,
|
||||
"mal_id": 25879,
|
||||
"slug": "working-3",
|
||||
"status": "Currently Airing",
|
||||
"url": "https://hummingbird.me/anime/working-3",
|
||||
"title": "Working!!!",
|
||||
"alternate_title": "Wagnaria!!!",
|
||||
"episode_count": 13,
|
||||
"episode_length": 23,
|
||||
"cover_image": "https://static.hummingbird.me/anime/poster_images/000/008/712/large/Working-Saison-3-Visual-Art.jpg?1427928008",
|
||||
"synopsis": "The third season of the Working!! series.",
|
||||
"show_type": "TV",
|
||||
"started_airing": "2015-07-05",
|
||||
"finished_airing": null,
|
||||
"community_rating": 4.2499808378722,
|
||||
"age_rating": "PG13",
|
||||
"genres": [
|
||||
{
|
||||
"name": "Comedy"
|
||||
},
|
||||
{
|
||||
"name": "Slice of Life"
|
||||
}
|
||||
]
|
||||
},
|
||||
"rating": {
|
||||
"type": "advanced",
|
||||
"value": "4.5"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 9719799,
|
||||
"episodes_watched": 23,
|
||||
"last_watched": "2015-09-12T13:02:56.219Z",
|
||||
"updated_at": "2015-09-12T13:02:56.220Z",
|
||||
"rewatched_times": 0,
|
||||
"notes": null,
|
||||
"notes_present": null,
|
||||
"status": "currently-watching",
|
||||
"private": false,
|
||||
"rewatching": false,
|
||||
"anime": {
|
||||
"id": 10016,
|
||||
"mal_id": 28297,
|
||||
"slug": "ore-monogatari",
|
||||
"status": "Currently Airing",
|
||||
"url": "https://hummingbird.me/anime/ore-monogatari",
|
||||
"title": "Ore Monogatari!!",
|
||||
"alternate_title": "My Love Story!!",
|
||||
"episode_count": 24,
|
||||
"episode_length": 22,
|
||||
"cover_image": "https://static.hummingbird.me/anime/poster_images/000/010/016/large/98d170b9e221550a05ea0309462510041423372549_full.jpg?1429885511",
|
||||
"synopsis": "Gouda Takeo is a freshman in high school. (Both estimates) Weight: 120kg, Height: 2 meters. He spends his days peacefully with his super-popular-with-girls, yet insensitive childhood friend, Sunakawa. One morning, on the train to school, Takeo saves a girl, Yamato, from being molested by a pervert. Could this be the beginning of spring for Takeo?\n\n(Source: MU)",
|
||||
"show_type": "TV",
|
||||
"started_airing": "2015-04-09",
|
||||
"finished_airing": "2015-09-24",
|
||||
"community_rating": 4.2192057339959,
|
||||
"age_rating": "PG13",
|
||||
"genres": [
|
||||
{
|
||||
"name": "Comedy"
|
||||
},
|
||||
{
|
||||
"name": "Romance"
|
||||
}
|
||||
]
|
||||
},
|
||||
"rating": {
|
||||
"type": "advanced",
|
||||
"value": "4.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 9131608,
|
||||
"episodes_watched": 24,
|
||||
"last_watched": "2015-09-13T11:36:06.608Z",
|
||||
"updated_at": "2015-09-13T11:36:06.609Z",
|
||||
"rewatched_times": 0,
|
||||
"notes": null,
|
||||
"notes_present": null,
|
||||
"status": "currently-watching",
|
||||
"private": false,
|
||||
"rewatching": false,
|
||||
"anime": {
|
||||
"id": 9142,
|
||||
"mal_id": 27663,
|
||||
"slug": "baby-steps-2",
|
||||
"status": "Currently Airing",
|
||||
"url": "https://hummingbird.me/anime/baby-steps-2",
|
||||
"title": "Baby Steps 2nd Season",
|
||||
"alternate_title": "",
|
||||
"episode_count": 0,
|
||||
"episode_length": 24,
|
||||
"cover_image": "https://static.hummingbird.me/anime/poster_images/000/009/142/large/0WCindC.jpg?1428540276",
|
||||
"synopsis": "Season 2 of Baby Steps. ",
|
||||
"show_type": "TV",
|
||||
"started_airing": "2015-04-05",
|
||||
"finished_airing": null,
|
||||
"community_rating": 4.2037554731763,
|
||||
"age_rating": "PG13",
|
||||
"genres": [
|
||||
{
|
||||
"name": "Sports"
|
||||
},
|
||||
{
|
||||
"name": "Romance"
|
||||
},
|
||||
{
|
||||
"name": "School"
|
||||
}
|
||||
]
|
||||
},
|
||||
"rating": {
|
||||
"type": "advanced",
|
||||
"value": "4.5"
|
||||
}
|
||||
}
|
||||
]
|
0
tests/test_data/manga_list/manga-transformed.json
Normal file
0
tests/test_data/manga_list/manga-transformed.json
Normal file
1
tests/test_data/manga_list/manga-zippered.json
Normal file
1
tests/test_data/manga_list/manga-zippered.json
Normal file
File diff suppressed because one or more lines are too long
1288
tests/test_data/manga_list/manga.json
Normal file
1288
tests/test_data/manga_list/manga.json
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user