@ -1,25 +1,24 @@ | |||
<?xml version="1.0" encoding="UTF-8"?> | |||
<phpunit | |||
colors="true" | |||
stopOnFailure="false" | |||
bootstrap="tests/bootstrap.php" | |||
beStrictAboutTestsThatDoNotTestAnything="true" | |||
colors="true" | |||
stopOnFailure="false" | |||
bootstrap="tests/bootstrap.php" | |||
> | |||
<filter> | |||
<whitelist> | |||
<directory suffix=".php">src</directory> | |||
</whitelist> | |||
</filter> | |||
<testsuites> | |||
<testsuite name="AnimeClient"> | |||
<directory>tests</directory> | |||
</testsuite> | |||
</testsuites> | |||
<php> | |||
<server name="HTTP_USER_AGENT" value="Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:38.0) Gecko/20100101 Firefox/38.0" /> | |||
<server name="HTTP_HOST" value="localhost" /> | |||
<server name="SERVER_NAME" value="localhost" /> | |||
<server name="REQUEST_URI" value="/" /> | |||
<server name="REQUEST_METHOD" value="GET" /> | |||
</php> | |||
<filter> | |||
<whitelist> | |||
<directory suffix=".php">src</directory> | |||
</whitelist> | |||
</filter> | |||
<testsuites> | |||
<testsuite name="AnimeClient"> | |||
<directory>tests</directory> | |||
</testsuite> | |||
</testsuites> | |||
<php> | |||
<server name="HTTP_USER_AGENT" value="Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:38.0) Gecko/20100101 Firefox/38.0" /> | |||
<server name="HTTP_HOST" value="localhost" /> | |||
<server name="SERVER_NAME" value="localhost" /> | |||
<server name="REQUEST_URI" value="/" /> | |||
<server name="REQUEST_METHOD" value="GET" /> | |||
</php> | |||
</phpunit> |
@ -0,0 +1,52 @@ | |||
<?php declare(strict_types=1); | |||
/** | |||
* Anime List Client | |||
* | |||
* An API client for Kitsu and MyAnimeList to manage anime and manga watch lists | |||
* | |||
* PHP version 7 | |||
* | |||
* @package AnimeListClient | |||
* @author Timothy J. Warren <tim@timshomepage.net> | |||
* @copyright 2015 - 2017 Timothy J. Warren | |||
* @license http://www.opensource.org/licenses/mit-license.html MIT License | |||
* @version 4.0 | |||
* @link https://github.com/timw4mail/HummingBirdAnimeClient | |||
*/ | |||
namespace Aviat\AnimeClient\API\MAL; | |||
use Aviat\AnimeClient\API\AbstractListItem; | |||
use Aviat\Ion\Di\ContainerAware; | |||
/** | |||
* CRUD operations for MAL list items | |||
*/ | |||
class ListItem extends AbstractListItem { | |||
use ContainerAware; | |||
public function __construct() | |||
{ | |||
$this->init(); | |||
} | |||
public function create(array $data): bool | |||
{ | |||
return FALSE; | |||
} | |||
public function delete(string $id): bool | |||
{ | |||
return FALSE; | |||
} | |||
public function get(string $id): array | |||
{ | |||
return []; | |||
} | |||
public function update(string $id, array $data): Response | |||
{ | |||
// @TODO implement | |||
} | |||
} |
@ -0,0 +1,190 @@ | |||
<?php declare(strict_types=1); | |||
/** | |||
* Anime List Client | |||
* | |||
* An API client for Kitsu and MyAnimeList to manage anime and manga watch lists | |||
* | |||
* PHP version 7 | |||
* | |||
* @package AnimeListClient | |||
* @author Timothy J. Warren <tim@timshomepage.net> | |||
* @copyright 2015 - 2017 Timothy J. Warren | |||
* @license http://www.opensource.org/licenses/mit-license.html MIT License | |||
* @version 4.0 | |||
* @link https://github.com/timw4mail/HummingBirdAnimeClient | |||
*/ | |||
namespace Aviat\AnimeClient\API\MAL; | |||
use Aviat\AnimeClient\API\{ | |||
GuzzleTrait, | |||
MAL as M, | |||
XML | |||
}; | |||
use GuzzleHttp\Client; | |||
use GuzzleHttp\Cookie\CookieJar; | |||
use GuzzleHttp\Psr7\Response; | |||
use InvalidArgumentException; | |||
trait MALTrait { | |||
use GuzzleTrait; | |||
/** | |||
* The base url for api requests | |||
* @var string $base_url | |||
*/ | |||
protected $baseUrl = M::BASE_URL; | |||
/** | |||
* HTTP headers to send with every request | |||
* | |||
* @var array | |||
*/ | |||
protected $defaultHeaders = [ | |||
'User-Agent' => "Tim's Anime Client/4.0" | |||
]; | |||
/** | |||
* Set up the class properties | |||
* | |||
* @return void | |||
*/ | |||
protected function init() | |||
{ | |||
$defaults = [ | |||
'cookies' => $this->cookieJar, | |||
'headers' => $this->defaultHeaders, | |||
'timeout' => 25, | |||
'connect_timeout' => 25 | |||
]; | |||
$this->cookieJar = new CookieJar(); | |||
$this->client = new Client([ | |||
'base_uri' => $this->baseUrl, | |||
'cookies' => TRUE, | |||
'http_errors' => TRUE, | |||
'defaults' => $defaults | |||
]); | |||
} | |||
/** | |||
* Make a request via Guzzle | |||
* | |||
* @param string $type | |||
* @param string $url | |||
* @param array $options | |||
* @return Response | |||
*/ | |||
private function getResponse(string $type, string $url, array $options = []) | |||
{ | |||
$type = strtoupper($type); | |||
$validTypes = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS']; | |||
if ( ! in_array($type, $validTypes)) | |||
{ | |||
throw new InvalidArgumentException('Invalid http request type'); | |||
} | |||
$config = $this->container->get('config'); | |||
$logger = $this->container->getLogger('request'); | |||
$defaultOptions = [ | |||
'auth' => [ | |||
$config->get(['mal','username']), | |||
$config->get(['mal','password']) | |||
], | |||
'headers' => $this->defaultHeaders | |||
]; | |||
$options = array_merge($defaultOptions, $options); | |||
$logger->debug(Json::encode([$type, $url])); | |||
$logger->debug(Json::encode($options)); | |||
return $this->client->request($type, $url, $options); | |||
} | |||
/** | |||
* Make a request via Guzzle | |||
* | |||
* @param string $type | |||
* @param string $url | |||
* @param array $options | |||
* @return array | |||
*/ | |||
private function request(string $type, string $url, array $options = []): array | |||
{ | |||
$logger = null; | |||
if ($this->getContainer()) | |||
{ | |||
$logger = $this->container->getLogger('request'); | |||
} | |||
$response = $this->getResponse($type, $url, $options); | |||
if ((int) $response->getStatusCode() > 299 || (int) $response->getStatusCode() < 200) | |||
{ | |||
if ($logger) | |||
{ | |||
$logger->warning('Non 200 response for api call'); | |||
$logger->warning($response->getBody()); | |||
} | |||
// throw new RuntimeException($response->getBody()); | |||
} | |||
return XML::toArray((string) $response->getBody()); | |||
} | |||
/** | |||
* Remove some boilerplate for get requests | |||
* | |||
* @param array $args | |||
* @return array | |||
*/ | |||
protected function getRequest(...$args): array | |||
{ | |||
return $this->request('GET', ...$args); | |||
} | |||
/** | |||
* Remove some boilerplate for post requests | |||
* | |||
* @param array $args | |||
* @return array | |||
*/ | |||
protected function postRequest(...$args): array | |||
{ | |||
$logger = null; | |||
if ($this->getContainer()) | |||
{ | |||
$logger = $this->container->getLogger('request'); | |||
} | |||
$response = $this->getResponse('POST', ...$args); | |||
$validResponseCodes = [200, 201]; | |||
if ( ! in_array((int) $response->getStatusCode(), $validResponseCodes)) | |||
{ | |||
if ($logger) | |||
{ | |||
$logger->warning('Non 201 response for POST api call'); | |||
$logger->warning($response->getBody()); | |||
} | |||
} | |||
return XML::toArray((string) $response->getBody()); | |||
} | |||
/** | |||
* Remove some boilerplate for delete requests | |||
* | |||
* @param array $args | |||
* @return bool | |||
*/ | |||
protected function deleteRequest(...$args): bool | |||
{ | |||
$response = $this->getResponse('DELETE', ...$args); | |||
return ((int) $response->getStatusCode() === 204); | |||
} | |||
} |
@ -1,63 +1,49 @@ | |||
<?php declare(strict_types=1); | |||
/** | |||
* Anime List Client | |||
* | |||
* An API client for Kitsu and MyAnimeList to manage anime and manga watch lists | |||
* | |||
* PHP version 7 | |||
* | |||
* @package AnimeListClient | |||
* @author Timothy J. Warren <tim@timshomepage.net> | |||
* @copyright 2015 - 2017 Timothy J. Warren | |||
* @license http://www.opensource.org/licenses/mit-license.html MIT License | |||
* @version 4.0 | |||
* @link https://github.com/timw4mail/HummingBirdAnimeClient | |||
*/ | |||
namespace Aviat\AnimeClient\API\Kitsu; | |||
use Aviat\AnimeClient\Model\API; | |||
<?php declare(strict_types=1); | |||
/** | |||
* Anime List Client | |||
* | |||
* An API client for Kitsu and MyAnimeList to manage anime and manga watch lists | |||
* | |||
* PHP version 7 | |||
* | |||
* @package AnimeListClient | |||
* @author Timothy J. Warren <tim@timshomepage.net> | |||
* @copyright 2015 - 2017 Timothy J. Warren | |||
* @license http://www.opensource.org/licenses/mit-license.html MIT License | |||
* @version 4.0 | |||
* @link https://github.com/timw4mail/HummingBirdAnimeClient | |||
*/ | |||
namespace Aviat\AnimeClient\API\MAL; | |||
use Aviat\AnimeClient\API\MAL as M; | |||
use Aviat\Ion\Di\ContainerAware; | |||
/** | |||
* MyAnimeList API Model | |||
*/ | |||
class Model extends API { | |||
/** | |||
* Base url for Kitsu API | |||
*/ | |||
protected $baseUrl = 'https://myanimelist.net/api/'; | |||
/** | |||
* Default settings for Guzzle | |||
* @var array | |||
*/ | |||
protected $connectionDefaults = []; | |||
/** | |||
* Get the access token from the Kitsu API | |||
* | |||
* @param string $username | |||
* @param string $password | |||
* @return bool|string | |||
*/ | |||
public function authenticate(string $username, string $password) | |||
{ | |||
$response = $this->post('account/', [ | |||
'body' => http_build_query([ | |||
'grant_type' => 'password', | |||
'username' => $username, | |||
'password' => $password | |||
]) | |||
]); | |||
$info = $response->getBody(); | |||
if (array_key_exists('access_token', $info)) { | |||
// @TODO save token | |||
return true; | |||
} | |||
return false; | |||
} | |||
class Model { | |||
use ContainerAware; | |||
use MALTrait; | |||
public function createListItem(array $data): bool | |||
{ | |||
} | |||
public function getListItem(string $listId): array | |||
{ | |||
} | |||
public function updateListItem(array $data) | |||
{ | |||
} | |||
public function deleteListItem(string $id): bool | |||
{ | |||
} | |||
} |
@ -0,0 +1,46 @@ | |||
<?php declare(strict_types=1); | |||
/** | |||
* Anime List Client | |||
* | |||
* An API client for Kitsu and MyAnimeList to manage anime and manga watch lists | |||
* | |||
* PHP version 7 | |||
* | |||
* @package AnimeListClient | |||
* @author Timothy J. Warren <tim@timshomepage.net> | |||
* @copyright 2015 - 2017 Timothy J. Warren | |||
* @license http://www.opensource.org/licenses/mit-license.html MIT License | |||
* @version 4.0 | |||
* @link https://github.com/timw4mail/HummingBirdAnimeClient | |||
*/ | |||
namespace Aviat\AnimeClient\API\MAL; | |||
use Aviat\Ion\Transformer\AbstractTransformer; | |||
/** | |||
* Transformer for updating MAL List | |||
*/ | |||
class AnimeListTransformer extends AbstractTransformer { | |||
public function transform($item) | |||
{ | |||
$rewatching = 'false'; | |||
if (array_key_exists('rewatching', $item) && $item['rewatching']) | |||
{ | |||
$rewatching = 'true'; | |||
} | |||
return [ | |||
'id' => $item['id'], | |||
'data' => [ | |||
'status' => $item['watching_status'], | |||
'rating' => $item['user_rating'], | |||
'rewatch_value' => (int) $rewatching, | |||
'times_rewatched' => $item['rewatched'], | |||
'comments' => $item['notes'], | |||
'episode' => $item['episodes_watched'] | |||
] | |||
]; | |||
} | |||
} |
@ -0,0 +1,33 @@ | |||
<?php declare(strict_types=1); | |||
/** | |||
* Anime List Client | |||
* | |||
* An API client for Kitsu and MyAnimeList to manage anime and manga watch lists | |||
* | |||
* PHP version 7 | |||
* | |||
* @package AnimeListClient | |||
* @author Timothy J. Warren <tim@timshomepage.net> | |||
* @copyright 2015 - 2017 Timothy J. Warren | |||
* @license http://www.opensource.org/licenses/mit-license.html MIT License | |||
* @version 4.0 | |||
* @link https://github.com/timw4mail/HummingBirdAnimeClient | |||
*/ | |||
namespace Aviat\AnimeClient\API\MAL; | |||
use Aviat\Ion\Transformer\AbstractTransformer; | |||
class MALToKitsuTransformer extends AbstractTransformer { | |||
public function transform($item) | |||
{ | |||
} | |||
public function untransform($item) | |||
{ | |||
} | |||
} |
@ -0,0 +1,48 @@ | |||
<?php declare(strict_types=1); | |||
/** | |||
* Anime List Client | |||
* | |||
* An API client for Kitsu and MyAnimeList to manage anime and manga watch lists | |||
* | |||
* PHP version 7 | |||
* | |||
* @package AnimeListClient | |||
* @author Timothy J. Warren <tim@timshomepage.net> | |||
* @copyright 2015 - 2017 Timothy J. Warren | |||
* @license http://www.opensource.org/licenses/mit-license.html MIT License | |||
* @version 4.0 | |||
* @link https://github.com/timw4mail/HummingBirdAnimeClient | |||
*/ | |||
namespace Aviat\AnimeClient\Tests\API; | |||
use Aviat\AnimeClient\API\JsonAPI; | |||
use Aviat\Ion\Json; | |||
use PHPUnit\Framework\TestCase; | |||
class JsonAPITest extends TestCase { | |||
public function setUp() | |||
{ | |||
$dir = __DIR__ . '/../test_data/JsonAPI'; | |||
$this->startData = Json::decodeFile("{$dir}/jsonApiExample.json"); | |||
$this->organizedIncludes = Json::decodeFile("{$dir}/organizedIncludes.json"); | |||
$this->inlineIncluded = Json::decodeFile("{$dir}/inlineIncluded.json"); | |||
} | |||
public function testOrganizeIncludes() | |||
{ | |||
$expected = $this->organizedIncludes; | |||
$actual = JsonAPI::organizeIncludes($this->startData['included']); | |||
$this->assertEquals($expected, $actual); | |||
} | |||
public function testInlineIncludedRelationships() | |||
{ | |||
$expected = $this->inlineIncluded; | |||
$actual = JsonAPI::inlineIncludedRelationships($this->organizedIncludes, 'anime'); | |||
$this->assertEquals($expected, $actual); | |||
} | |||
} |
@ -0,0 +1,90 @@ | |||
<?php declare(strict_types=1); | |||
namespace Aviat\AnimeClient\Tests\API\Kitsu\Transformer; | |||
use AnimeClient_TestCase; | |||
use Aviat\AnimeClient\API\Kitsu\Transformer\AnimeListTransformer; | |||
use Aviat\Ion\Friend; | |||
use Aviat\Ion\Json; | |||
class AnimeListTransformerTest extends AnimeClient_TestCase { | |||
public function setUp() | |||
{ | |||
parent::setUp(); | |||
$dir = AnimeClient_TestCase::TEST_DATA_DIR . '/Kitsu'; | |||
$this->beforeTransform = Json::decodeFile("{$dir}/animeListItemBeforeTransform.json"); | |||
$this->afterTransform = Json::decodeFile("{$dir}/animeListItemAfterTransform.json"); | |||
$this->transformer = new AnimeListTransformer(); | |||
} | |||
public function testTransform() | |||
{ | |||
$expected = $this->afterTransform; | |||
$actual = $this->transformer->transform($this->beforeTransform); | |||
$this->assertEquals($expected, $actual); | |||
} | |||
public function dataUntransform() | |||
{ | |||
return [[ | |||
'input' => [ | |||
'id' => 14047981, | |||
'watching_status' => 'current', | |||
'user_rating' => 8, | |||
'episodes_watched' => 38, | |||
'rewatched' => 0, | |||
'notes' => 'Very formulaic.', | |||
'edit' => true | |||
], | |||
'expected' => [ | |||
'id' => 14047981, | |||
'data' => [ | |||
'status' => 'current', | |||
'rating' => 4, | |||
'reconsuming' => false, | |||
'reconsumeCount' => 0, | |||
'notes' => 'Very formulaic.', | |||
'progress' => 38, | |||
'private' => false | |||
] | |||
] | |||
], [ | |||
'input' => [ | |||
'id' => 14047981, | |||
'watching_status' => 'current', | |||
'user_rating' => 8, | |||
'episodes_watched' => 38, | |||
'rewatched' => 0, | |||
'notes' => 'Very formulaic.', | |||
'edit' => 'true', | |||
'private' => 'On', | |||
'rewatching' => 'On' | |||
], | |||
'expected' => [ | |||
'id' => 14047981, | |||
'data' => [ | |||
'status' => 'current', | |||
'rating' => 4, | |||
'reconsuming' => true, | |||
'reconsumeCount' => 0, | |||
'notes' => 'Very formulaic.', | |||
'progress' => 38, | |||
'private' => true, | |||
] | |||
] | |||
]]; | |||
} | |||
/** | |||
* @dataProvider dataUntransform | |||
*/ | |||
public function testUntransform($input, $expected) | |||
{ | |||
$actual = $this->transformer->untransform($input); | |||
$this->assertEquals($expected, $actual); | |||
} | |||
} |
@ -0,0 +1,30 @@ | |||
<?php declare(strict_types=1); | |||
namespace Aviat\AnimeClient\Tests\API\Kitsu\Transformer; | |||
use AnimeClient_TestCase; | |||
use Aviat\AnimeClient\API\Kitsu\Transformer\AnimeTransformer; | |||
use Aviat\Ion\Friend; | |||
use Aviat\Ion\Json; | |||
class AnimeTransformerTest extends AnimeClient_TestCase { | |||
public function setUp() | |||
{ | |||
parent::setUp(); | |||
$dir = AnimeClient_TestCase::TEST_DATA_DIR . '/Kitsu'; | |||
//$this->beforeTransform = Json::decodeFile("{$dir}/animeBeforeTransform.json"); | |||
//$this->afterTransform = Json::decodeFile("{$dir}/animeAfterTransform.json"); | |||
$this->transformer = new AnimeTransformer(); | |||
} | |||
public function testTransform() | |||
{ | |||
/*$expected = $this->afterTransform; | |||
$actual = $this->transformer->transform($this->beforeTransform); | |||
$this->assertEquals($expected, $actual);*/ | |||
} | |||
} |
@ -0,0 +1,385 @@ | |||
{ | |||
"anime": { | |||
"11474": { | |||
"slug": "hibike-euphonium-2", | |||
"synopsis": "Second season of Hibike! Euphonium.", | |||
"coverImageTopOffset": 120, | |||
"titles": { | |||
"en": "Sound! Euphonium 2", | |||
"en_jp": "Hibike! Euphonium 2", | |||
"ja_jp": "\u97ff\u3051\uff01\u30e6\u30fc\u30d5\u30a9\u30cb\u30a2\u30e0 \uff12" | |||
}, | |||
"canonicalTitle": "Hibike! Euphonium 2", | |||
"abbreviatedTitles": null, | |||
"averageRating": 4.1684326428476, | |||
"ratingFrequencies": { | |||
"0.5": "1", | |||
"1.0": "1", | |||
"1.5": "2", | |||
"2.0": "8", | |||
"2.5": "13", | |||
"3.0": "42", | |||
"3.5": "90", | |||
"4.0": "193", | |||
"4.5": "180", | |||
"5.0": "193", | |||
"nil": "1972" | |||
}, | |||
"startDate": "2016-10-06", | |||
"endDate": null, | |||
"posterImage": { | |||
"tiny": "https:\/\/media.kitsu.io\/anime\/poster_images\/11474\/tiny.jpg?1470781430", | |||
"small": "https:\/\/media.kitsu.io\/anime\/poster_images\/11474\/small.jpg?1470781430", | |||
"medium": "https:\/\/media.kitsu.io\/anime\/poster_images\/11474\/medium.jpg?1470781430", | |||
"large": "https:\/\/media.kitsu.io\/anime\/poster_images\/11474\/large.jpg?1470781430", | |||
"original": "https:\/\/media.kitsu.io\/anime\/poster_images\/11474\/original.jpg?1470781430" | |||
}, | |||
"coverImage": { | |||
"small": "https:\/\/media.kitsu.io\/anime\/cover_images\/11474\/small.jpg?1476203965", | |||
"large": "https:\/\/media.kitsu.io\/anime\/cover_images\/11474\/large.jpg?1476203965", | |||
"original": "https:\/\/media.kitsu.io\/anime\/cover_images\/11474\/original.jpg?1476203965" | |||
}, | |||
"episodeCount": 13, | |||
"episodeLength": 25, | |||
"subtype": "TV", | |||
"youtubeVideoId": "d2Di5swwzxg", | |||
"ageRating": "PG", | |||
"ageRatingGuide": "", | |||
"showType": "TV", | |||
"nsfw": false, | |||
"relationships": { | |||
"genres": ["24", "35", "4"], | |||
"mappings": ["3155"] | |||
} | |||
}, | |||
"10802": { | |||
"slug": "nisekoimonogatari", | |||
"synopsis": "Trailer for a fake anime created by Shaft as an April Fool's Day joke.", | |||
"coverImageTopOffset": 80, | |||
"titles": { | |||
"en": "", | |||
"en_jp": "Nisekoimonogatari", | |||
"ja_jp": "" | |||
}, | |||
"canonicalTitle": "Nisekoimonogatari", | |||
"abbreviatedTitles": null, | |||
"averageRating": 3.4857993435287, | |||
"ratingFrequencies": { | |||
"0.5": "22", | |||
"1.0": "10", | |||
"1.5": "16", | |||
"2.0": "32", | |||
"2.5": "74", | |||
"3.0": "97", | |||
"3.5": "118", | |||
"4.0": "72", | |||
"4.5": "34", | |||
"5.0": "136", | |||
"nil": "597", | |||
"0.89": "-1", | |||
"3.63": "-1", | |||
"4.11": "-1", | |||
"0.068": "-1", | |||
"0.205": "-1", | |||
"0.274": "-2", | |||
"0.479": "-1", | |||
"0.548": "-1", | |||
"1.096": "-2", | |||
"1.164": "-1", | |||
"1.438": "-1", | |||
"1.918": "-1", | |||
"2.055": "-1", | |||
"3.973": "-1", | |||
"4.178": "-3", | |||
"4.247": "-1", | |||
"4.726": "-1", | |||
"4.932": "-3", | |||
"1.0958904109589": "3", | |||
"0.89041095890411": "2", | |||
"1.02739726027397": "1", | |||
"1.16438356164384": "2", | |||
"1.43835616438356": "2", | |||
"1.57534246575342": "1", | |||
"1.91780821917808": "1", | |||
"2.05479452054794": "2", | |||
"2.12328767123288": "1", | |||
"2.73972602739726": "1", | |||
"2.80821917808219": "2", | |||
"2.94520547945205": "1", | |||
"3.15068493150685": "1", | |||
"3.35616438356164": "2", | |||
"3.63013698630137": "2", | |||
"3.97260273972603": "1", | |||
"4.10958904109589": "2", | |||
"4.17808219178082": "3", | |||
"4.24657534246575": "1", | |||
"4.38356164383562": "2", | |||
"4.65753424657534": "1", | |||
"4.72602739726027": "2", | |||
"4.86301369863014": "1", | |||
"4.93150684931507": "10", | |||
"0.205479452054795": "1", | |||
"0.273972602739726": "2", | |||
"0.479452054794521": "2", | |||
"0.547945205479452": "2", | |||
"0.753424657534246": "1", | |||
"0.0684931506849315": "1" | |||
}, | |||
"startDate": "2015-04-01", | |||
"endDate": null, | |||
"posterImage": { | |||
"tiny": "https:\/\/media.kitsu.io\/anime\/poster_images\/10802\/tiny.jpg?1427974534", | |||
"small": "https:\/\/media.kitsu.io\/anime\/poster_images\/10802\/small.jpg?1427974534", | |||
"medium": "https:\/\/media.kitsu.io\/anime\/poster_images\/10802\/medium.jpg?1427974534", | |||
"large": "https:\/\/media.kitsu.io\/anime\/poster_images\/10802\/large.jpg?1427974534", | |||
"original": "https:\/\/media.kitsu.io\/anime\/poster_images\/10802\/original.jpg?1427974534" | |||
}, | |||
"coverImage": { | |||
"small": "https:\/\/media.kitsu.io\/anime\/cover_images\/10802\/small.jpg?1427928458", | |||
"large": "https:\/\/media.kitsu.io\/anime\/cover_images\/10802\/large.jpg?1427928458", | |||
"original": "https:\/\/media.kitsu.io\/anime\/cover_images\/10802\/original.jpg?1427928458" | |||
}, | |||
"episodeCount": 1, | |||
"episodeLength": 1, | |||
"subtype": "ONA", | |||
"youtubeVideoId": "", | |||
"ageRating": "PG", | |||
"ageRatingGuide": "Teens 13 or older", | |||
"showType": "ONA", | |||
"nsfw": false, | |||
"relationships": { | |||
"genres": ["3"], | |||
"mappings": ["1755"] | |||
} | |||
}, | |||
"11887": { | |||
"slug": "brave-witches", | |||
"synopsis": "In September 1944, allied forces led by the 501st Joint Fighter Wing \"Strike Witches\" successfully eliminate the Neuroi threat from the skies of the Republic of Gallia, thus ensuring the security of western Europe. Taking advantage of this victory, allied forces begin a full-fledged push toward central and eastern Europe. From a base in Petersburg in the Empire of Orussia, the 502nd Joint Fighter Wing \"Brave Witches,\" upon whom mankind has placed its hopes, flies with courage in the cold skies of eastern Europe.\n\n(Source: MAL News)", | |||
"coverImageTopOffset": 380, | |||
"titles": { | |||
"en": "", | |||
"en_jp": "Brave Witches", | |||
"ja_jp": "\u30d6\u30ec\u30a4\u30d6\u30a6\u30a3\u30c3\u30c1\u30fc\u30ba" | |||
}, | |||
"canonicalTitle": "Brave Witches", | |||
"abbreviatedTitles": null, | |||
"averageRating": 3.5846888163849, | |||
"ratingFrequencies": { | |||
"0.5": "1", | |||
"1.0": "4", | |||
"1.5": "8", | |||
"2.0": "12", | |||
"2.5": "17", | |||
"3.0": "33", | |||
"3.5": "41", | |||
"4.0": "32", | |||
"4.5": "9", | |||
"5.0": "19", | |||
"nil": "620" | |||
}, | |||
"startDate": "2016-10-06", | |||
"endDate": null, | |||
"posterImage": { | |||
"tiny": "https:\/\/media.kitsu.io\/anime\/poster_images\/11887\/tiny.jpg?1476481854", | |||
"small": "https:\/\/media.kitsu.io\/anime\/poster_images\/11887\/small.jpg?1476481854", | |||
"medium": "https:\/\/media.kitsu.io\/anime\/poster_images\/11887\/medium.jpg?1476481854", | |||
"large": "https:\/\/media.kitsu.io\/anime\/poster_images\/11887\/large.jpg?1476481854", | |||
"original": "https:\/\/media.kitsu.io\/anime\/poster_images\/11887\/original.png?1476481854" | |||
}, | |||
"coverImage": { | |||
"small": "https:\/\/media.kitsu.io\/anime\/cover_images\/11887\/small.jpg?1479834725", | |||
"large": "https:\/\/media.kitsu.io\/anime\/cover_images\/11887\/large.jpg?1479834725", | |||
"original": "https:\/\/media.kitsu.io\/anime\/cover_images\/11887\/original.jpg?1479834725" | |||
}, | |||
"episodeCount": 12, | |||
"episodeLength": 24, | |||
"subtype": "TV", | |||
"youtubeVideoId": "VLUqd-jEBuE", | |||
"ageRating": "R", | |||
"ageRatingGuide": "Mild Nudity", | |||
"showType": "TV", | |||
"nsfw": false, | |||
"relationships": { | |||
"genres": ["5", "8", "28", "1", "25"], | |||
"mappings": ["2593"] | |||
} | |||
}, | |||
"12024": { | |||
"slug": "www-working", | |||
"synopsis": "Daisuke Higashida is a serious first-year student at Higashizaka High School. He lives a peaceful everyday life even though he is not satisfied with the family who doesn't laugh at all and makes him tired. However, his father's company goes bankrupt one day, and he can no longer afford allowances, cellphone bills, and commuter tickets. When his father orders him to take up a part-time job, Daisuke decides to work at a nearby family restaurant in order to avoid traveling 15 kilometers to school by bicycle.", | |||
"coverImageTopOffset": 165, | |||
"titles": { | |||
"en": "WWW.WAGNARIA!!", | |||
"en_jp": "WWW.Working!!", | |||
"ja_jp": "" | |||
}, | |||
"canonicalTitle": "WWW.Working!!", | |||
"abbreviatedTitles": null, | |||
"averageRating": 3.8238374224378, | |||
"ratingFrequencies": { | |||
"1.0": "2", | |||
"1.5": "7", | |||
"2.0": "19", | |||
"2.5": "28", | |||
"3.0": "68", | |||
"3.5": "114", | |||
"4.0": "144", | |||
"4.5": "78", | |||
"5.0": "74", | |||
"nil": "1182" | |||
}, | |||
"startDate": "2016-10-01", | |||
"endDate": "2016-12-24", | |||
"posterImage": { | |||
"tiny": "https:\/\/media.kitsu.io\/anime\/poster_images\/12024\/tiny.jpg?1473990267", | |||
"small": "https:\/\/media.kitsu.io\/anime\/poster_images\/12024\/small.jpg?1473990267", | |||
"medium": "https:\/\/media.kitsu.io\/anime\/poster_images\/12024\/medium.jpg?1473990267", | |||
"large": "https:\/\/media.kitsu.io\/anime\/poster_images\/12024\/large.jpg?1473990267", | |||
"original": "https:\/\/media.kitsu.io\/anime\/poster_images\/12024\/original.jpg?1473990267" | |||
}, | |||
"coverImage": { | |||
"small": "https:\/\/media.kitsu.io\/anime\/cover_images\/12024\/small.jpg?1479834612", | |||
"large": "https:\/\/media.kitsu.io\/anime\/cover_images\/12024\/large.jpg?1479834612", | |||
"original": "https:\/\/media.kitsu.io\/anime\/cover_images\/12024\/original.png?1479834612" | |||
}, | |||
"episodeCount": 13, | |||
"episodeLength": 23, | |||
"subtype": "TV", | |||
"youtubeVideoId": "", | |||
"ageRating": "PG", | |||
"ageRatingGuide": "Teens 13 or older", | |||
"showType": "TV", | |||
"nsfw": false, | |||
"relationships": { | |||
"genres": ["3", "16"], | |||
"mappings": ["2538"] | |||
} | |||
}, | |||
"12465": { | |||
"slug": "bishoujo-yuugi-unit-crane-game-girls-galaxy", | |||
"synopsis": "Second season of Bishoujo Yuugi Unit Crane Game Girls.", | |||
"coverImageTopOffset": 0, | |||
"titles": { | |||
"en": "Crane Game Girls Galaxy", | |||
"en_jp": "Bishoujo Yuugi Unit Crane Game Girls Galaxy", | |||
"ja_jp": "\u7f8e\u5c11\u5973\u904a\u622f\u30e6\u30cb\u30c3\u30c8 \u30af\u30ec\u30fc\u30f3\u30b2\u30fc\u30eb\u30ae\u30e3\u30e9\u30af\u30b7\u30fc" | |||
}, | |||
"canonicalTitle": "Bishoujo Yuugi Unit Crane Game Girls Galaxy", | |||
"abbreviatedTitles": null, | |||
"averageRating": null, | |||
"ratingFrequencies": { | |||
"0.5": "2", | |||
"1.0": "2", | |||
"1.5": "0", | |||
"2.0": "4", | |||
"2.5": "6", | |||
"3.0": "2", | |||
"3.5": "4", | |||
"4.0": "1", | |||
"4.5": "2", | |||
"nil": "66" | |||
}, | |||
"startDate": "2016-10-05", | |||
"endDate": null, | |||
"posterImage": { | |||
"tiny": "https:\/\/media.kitsu.io\/anime\/poster_images\/12465\/tiny.jpg?1473601756", | |||
"small": "https:\/\/media.kitsu.io\/anime\/poster_images\/12465\/small.jpg?1473601756", | |||
"medium": "https:\/\/media.kitsu.io\/anime\/poster_images\/12465\/medium.jpg?1473601756", | |||
"large": "https:\/\/media.kitsu.io\/anime\/poster_images\/12465\/large.jpg?1473601756", | |||
"original": "https:\/\/media.kitsu.io\/anime\/poster_images\/12465\/original.png?1473601756" | |||
}, | |||
"coverImage": null, | |||
"episodeCount": null, | |||
"episodeLength": 13, | |||
"subtype": "TV", | |||
"youtubeVideoId": "", | |||
"ageRating": "PG", | |||
"ageRatingGuide": "Children", | |||
"showType": "TV", | |||
"nsfw": false, | |||
"relationships": { | |||
"genres": ["3"], | |||
"mappings": ["9871"] | |||
} | |||
} | |||
}, | |||
"genres": { | |||
"24": { | |||
"name": "School", | |||
"slug": "school", | |||
"description": null | |||
}, | |||
"35": { | |||
"name": "Music", | |||
"slug": "music", | |||
"description": null | |||
}, | |||
"4": { | |||
"name": "Drama", | |||
"slug": "drama", | |||
"description": "" | |||
}, | |||
"3": { | |||
"name": "Comedy", | |||
"slug": "comedy", | |||
"description": null | |||
}, | |||
"5": { | |||
"name": "Sci-Fi", | |||
"slug": "sci-fi", | |||
"description": null | |||
}, | |||
"8": { | |||
"name": "Magic", | |||
"slug": "magic", | |||
"description": null | |||
}, | |||
"28": { | |||
"name": "Military", | |||
"slug": "military", | |||
"description": null | |||
}, | |||
"1": { | |||
"name": "Action", | |||
"slug": "action", | |||
"description": "" | |||
}, | |||
"25": { | |||
"name": "Ecchi", | |||
"slug": "ecchi", | |||
"description": "" | |||
}, | |||
"16": { | |||
"name": "Slice of Life", | |||
"slug": "slice-of-life", | |||
"description": "" | |||
} | |||
}, | |||
"mappings": { | |||
"3155": { | |||