2016-12-21 12:46:20 -05:00
|
|
|
<?php declare(strict_types=1);
|
|
|
|
/**
|
2017-02-15 16:13:32 -05:00
|
|
|
* Hummingbird Anime List Client
|
2016-12-21 12:46:20 -05:00
|
|
|
*
|
|
|
|
* An API client for Kitsu and MyAnimeList to manage anime and manga watch lists
|
|
|
|
*
|
|
|
|
* PHP version 7
|
|
|
|
*
|
2017-02-15 16:13:32 -05:00
|
|
|
* @package HummingbirdAnimeClient
|
2017-01-06 23:34:56 -05:00
|
|
|
* @author Timothy J. Warren <tim@timshomepage.net>
|
2017-01-11 10:30:53 -05:00
|
|
|
* @copyright 2015 - 2017 Timothy J. Warren
|
2017-01-06 23:34:56 -05:00
|
|
|
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
|
|
|
* @version 4.0
|
2017-03-07 20:53:58 -05:00
|
|
|
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
|
2017-01-11 10:34:24 -05:00
|
|
|
*/
|
|
|
|
|
|
|
|
namespace Aviat\AnimeClient\API\Kitsu;
|
2016-12-21 12:46:20 -05:00
|
|
|
|
2017-03-07 17:51:08 -05:00
|
|
|
use function Amp\{all, wait};
|
|
|
|
|
|
|
|
use Amp\Artax\{Client, Request};
|
2017-03-28 11:01:38 -04:00
|
|
|
use Aviat\AnimeClient\API\{
|
|
|
|
CacheTrait,
|
|
|
|
JsonAPI,
|
|
|
|
Kitsu as K,
|
|
|
|
ParallelAPIRequest
|
|
|
|
};
|
2017-03-07 18:41:51 -05:00
|
|
|
use Aviat\AnimeClient\API\Enum\{
|
|
|
|
AnimeWatchingStatus\Title,
|
2017-03-22 11:13:50 -04:00
|
|
|
AnimeWatchingStatus\Kitsu as KitsuWatchingStatus,
|
2017-03-07 18:41:51 -05:00
|
|
|
MangaReadingStatus\Kitsu as KitsuReadingStatus
|
2017-02-20 13:37:08 -05:00
|
|
|
};
|
2017-03-07 20:49:31 -05:00
|
|
|
use Aviat\AnimeClient\API\Mapping\{AnimeWatchingStatus, MangaReadingStatus};
|
2017-01-03 21:06:49 -05:00
|
|
|
use Aviat\AnimeClient\API\Kitsu\Transformer\{
|
2017-02-04 15:18:34 -05:00
|
|
|
AnimeTransformer,
|
|
|
|
AnimeListTransformer,
|
|
|
|
MangaTransformer,
|
2017-01-27 12:35:28 -05:00
|
|
|
MangaListTransformer
|
2017-01-03 21:06:49 -05:00
|
|
|
};
|
2017-03-07 20:49:31 -05:00
|
|
|
use Aviat\Ion\{Di\ContainerAware, Json};
|
2016-12-21 12:46:20 -05:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Kitsu API Model
|
|
|
|
*/
|
2017-01-26 13:03:38 -05:00
|
|
|
class Model {
|
2017-01-13 16:53:56 -05:00
|
|
|
use CacheTrait;
|
2017-01-05 13:41:32 -05:00
|
|
|
use ContainerAware;
|
2016-12-21 12:46:20 -05:00
|
|
|
use KitsuTrait;
|
|
|
|
|
2017-03-10 12:50:29 -05:00
|
|
|
const FULL_TRANSFORMED_LIST_CACHE_KEY = 'kitsu-full-organized-anime-list';
|
2017-03-07 17:51:08 -05:00
|
|
|
|
2016-12-21 12:46:20 -05:00
|
|
|
/**
|
|
|
|
* Class to map anime list items
|
|
|
|
* to a common format used by
|
|
|
|
* templates
|
|
|
|
*
|
|
|
|
* @var AnimeListTransformer
|
|
|
|
*/
|
|
|
|
protected $animeListTransformer;
|
|
|
|
|
2016-12-22 21:36:23 -05:00
|
|
|
/**
|
|
|
|
* @var AnimeTransformer
|
|
|
|
*/
|
|
|
|
protected $animeTransformer;
|
|
|
|
|
2017-01-06 21:39:01 -05:00
|
|
|
/**
|
|
|
|
* @var ListItem
|
|
|
|
*/
|
|
|
|
protected $listItem;
|
|
|
|
|
2017-01-05 13:41:32 -05:00
|
|
|
/**
|
|
|
|
* @var MangaTransformer
|
|
|
|
*/
|
|
|
|
protected $mangaTransformer;
|
|
|
|
|
2017-01-04 13:16:58 -05:00
|
|
|
/**
|
|
|
|
* @var MangaListTransformer
|
|
|
|
*/
|
2017-01-03 21:06:49 -05:00
|
|
|
protected $mangaListTransformer;
|
2017-02-04 15:18:34 -05:00
|
|
|
|
2016-12-22 21:36:23 -05:00
|
|
|
/**
|
2017-02-17 11:37:22 -05:00
|
|
|
* Constructor
|
|
|
|
*
|
|
|
|
* @param ListItem $listItem
|
2016-12-22 21:36:23 -05:00
|
|
|
*/
|
2017-01-06 21:39:01 -05:00
|
|
|
public function __construct(ListItem $listItem)
|
2016-12-21 12:46:20 -05:00
|
|
|
{
|
2016-12-22 21:36:23 -05:00
|
|
|
$this->animeTransformer = new AnimeTransformer();
|
2016-12-21 12:46:20 -05:00
|
|
|
$this->animeListTransformer = new AnimeListTransformer();
|
2017-01-06 21:39:01 -05:00
|
|
|
$this->listItem = $listItem;
|
2017-01-04 13:16:58 -05:00
|
|
|
$this->mangaTransformer = new MangaTransformer();
|
2017-01-03 21:06:49 -05:00
|
|
|
$this->mangaListTransformer = new MangaListTransformer();
|
2016-12-21 12:46:20 -05:00
|
|
|
}
|
|
|
|
|
2017-03-28 14:34:33 -04:00
|
|
|
/**
|
|
|
|
* Get the access token from the Kitsu API
|
|
|
|
*
|
|
|
|
* @param string $username
|
|
|
|
* @param string $password
|
|
|
|
* @return bool|string
|
|
|
|
*/
|
|
|
|
public function authenticate(string $username, string $password)
|
|
|
|
{
|
|
|
|
$response = $this->getResponse('POST', K::AUTH_URL, [
|
|
|
|
'headers' => [],
|
|
|
|
'form_params' => [
|
|
|
|
'grant_type' => 'password',
|
|
|
|
'username' => $username,
|
|
|
|
'password' => $password
|
|
|
|
]
|
|
|
|
]);
|
|
|
|
|
|
|
|
$data = Json::decode((string)$response->getBody());
|
|
|
|
|
|
|
|
if (array_key_exists('access_token', $data))
|
|
|
|
{
|
|
|
|
return $data;
|
|
|
|
}
|
|
|
|
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
2017-01-10 12:35:46 -05:00
|
|
|
/**
|
|
|
|
* Get the userid for a username from Kitsu
|
|
|
|
*
|
|
|
|
* @param string $username
|
|
|
|
* @return string
|
|
|
|
*/
|
2017-03-08 12:55:49 -05:00
|
|
|
public function getUserIdByUsername(string $username = NULL): string
|
2017-01-05 13:41:32 -05:00
|
|
|
{
|
2017-01-27 12:35:28 -05:00
|
|
|
if (is_null($username))
|
|
|
|
{
|
|
|
|
$username = $this->getUsername();
|
|
|
|
}
|
2017-02-04 15:18:34 -05:00
|
|
|
|
2017-01-27 12:35:28 -05:00
|
|
|
$cacheItem = $this->cache->getItem(K::AUTH_USER_ID_KEY);
|
2017-02-04 15:18:34 -05:00
|
|
|
|
2017-01-26 13:03:38 -05:00
|
|
|
if ( ! $cacheItem->isHit())
|
|
|
|
{
|
|
|
|
$data = $this->getRequest('users', [
|
|
|
|
'query' => [
|
|
|
|
'filter' => [
|
|
|
|
'name' => $username
|
|
|
|
]
|
2017-01-05 13:41:32 -05:00
|
|
|
]
|
2017-01-26 13:03:38 -05:00
|
|
|
]);
|
2017-01-05 13:41:32 -05:00
|
|
|
|
2017-01-26 13:03:38 -05:00
|
|
|
$cacheItem->set($data['data'][0]['id']);
|
|
|
|
$cacheItem->save();
|
|
|
|
}
|
2017-02-04 15:18:34 -05:00
|
|
|
|
2017-01-26 13:03:38 -05:00
|
|
|
return $cacheItem->get();
|
2017-01-05 13:41:32 -05:00
|
|
|
}
|
|
|
|
|
2017-03-08 12:55:49 -05:00
|
|
|
/**
|
|
|
|
* Get information about a character
|
|
|
|
*
|
|
|
|
* @param string $slug
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function getCharacter(string $slug): array
|
|
|
|
{
|
2017-03-20 13:14:01 -04:00
|
|
|
// @todo catch non-existent characters and show 404
|
2017-03-08 12:55:49 -05:00
|
|
|
$data = $this->getRequest('/characters', [
|
|
|
|
'query' => [
|
|
|
|
'filter' => [
|
2017-03-31 13:37:53 -04:00
|
|
|
'name' => $slug
|
2017-03-08 12:55:49 -05:00
|
|
|
],
|
|
|
|
// 'include' => 'primaryMedia,castings'
|
|
|
|
]
|
|
|
|
]);
|
|
|
|
|
|
|
|
return $data;
|
|
|
|
}
|
|
|
|
|
2017-03-08 13:46:50 -05:00
|
|
|
/**
|
|
|
|
* Get profile information for the configured user
|
|
|
|
*
|
|
|
|
* @param string $username
|
|
|
|
* @return array
|
|
|
|
*/
|
2017-03-08 12:55:49 -05:00
|
|
|
public function getUserData(string $username): array
|
|
|
|
{
|
2017-03-31 13:37:53 -04:00
|
|
|
// $userId = $this->getUserIdByUsername($username);
|
|
|
|
$data = $this->getRequest("/users", [
|
2017-03-08 12:55:49 -05:00
|
|
|
'query' => [
|
2017-03-31 13:37:53 -04:00
|
|
|
'filter' => [
|
|
|
|
'name' => $username,
|
|
|
|
],
|
|
|
|
'fields' => [
|
|
|
|
// 'anime' => 'slug,name,canonicalTitle',
|
|
|
|
'characters' => 'slug,name,image'
|
|
|
|
],
|
|
|
|
'include' => 'waifu,pinnedPost,blocks,linkedAccounts,profileLinks,profileLinks.profileLinkSite,mediaFollows,userRoles,favorites.item'
|
2017-03-08 12:55:49 -05:00
|
|
|
]
|
|
|
|
]);
|
|
|
|
|
|
|
|
return $data;
|
|
|
|
}
|
|
|
|
|
2016-12-21 12:46:20 -05:00
|
|
|
/**
|
2017-03-28 14:34:33 -04:00
|
|
|
* Search for an anime or manga
|
2016-12-21 12:46:20 -05:00
|
|
|
*
|
2017-03-28 14:34:33 -04:00
|
|
|
* @param string $type - 'anime' or 'manga'
|
|
|
|
* @param string $query - name of the item to search for
|
|
|
|
* @return array
|
2016-12-21 12:46:20 -05:00
|
|
|
*/
|
2017-03-28 14:34:33 -04:00
|
|
|
public function search(string $type, string $query): array
|
2016-12-21 12:46:20 -05:00
|
|
|
{
|
2017-03-28 14:34:33 -04:00
|
|
|
$options = [
|
|
|
|
'query' => [
|
|
|
|
'filter' => [
|
|
|
|
'text' => $query
|
|
|
|
],
|
|
|
|
'page' => [
|
|
|
|
'offset' => 0,
|
|
|
|
'limit' => 20
|
|
|
|
],
|
2017-01-05 13:41:32 -05:00
|
|
|
]
|
2017-03-28 14:34:33 -04:00
|
|
|
];
|
2016-12-21 12:46:20 -05:00
|
|
|
|
2017-03-28 14:34:33 -04:00
|
|
|
$raw = $this->getRequest($type, $options);
|
2017-01-09 20:36:48 -05:00
|
|
|
|
2017-03-28 14:34:33 -04:00
|
|
|
foreach ($raw['data'] as &$item)
|
2017-01-05 13:41:32 -05:00
|
|
|
{
|
2017-03-28 14:34:33 -04:00
|
|
|
$item['attributes']['titles'] = K::filterTitles($item['attributes']);
|
|
|
|
array_shift($item['attributes']['titles']);
|
2016-12-21 12:46:20 -05:00
|
|
|
}
|
|
|
|
|
2017-03-28 14:34:33 -04:00
|
|
|
return $raw;
|
2016-12-21 12:46:20 -05:00
|
|
|
}
|
|
|
|
|
2017-03-28 16:52:27 -04:00
|
|
|
/**
|
|
|
|
* Find a media item on Kitsu by its associated MAL id
|
|
|
|
*
|
|
|
|
* @param string $malId
|
|
|
|
* @param string $type "anime" or "manga"
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
public function getKitsuIdFromMALId(string $malId, string $type="anime"): string
|
|
|
|
{
|
|
|
|
$options = [
|
|
|
|
'query' => [
|
|
|
|
'filter' => [
|
|
|
|
'external_site' => "myanimelist/{$type}",
|
|
|
|
'external_id' => $malId
|
|
|
|
],
|
|
|
|
'fields' => [
|
|
|
|
'media' => 'id,slug'
|
|
|
|
],
|
|
|
|
'include' => 'media'
|
|
|
|
]
|
|
|
|
];
|
|
|
|
|
|
|
|
$raw = $this->getRequest('mappings', $options);
|
|
|
|
|
|
|
|
return $raw['included'][0]['id'];
|
|
|
|
}
|
|
|
|
|
2017-03-28 14:34:33 -04:00
|
|
|
// -------------------------------------------------------------------------
|
|
|
|
// ! Anime-specific methods
|
|
|
|
// -------------------------------------------------------------------------
|
|
|
|
|
2017-01-05 13:41:32 -05:00
|
|
|
/**
|
|
|
|
* Get information about a particular anime
|
|
|
|
*
|
2017-01-16 13:49:51 -05:00
|
|
|
* @param string $slug
|
2017-01-05 13:41:32 -05:00
|
|
|
* @return array
|
|
|
|
*/
|
2017-01-16 13:49:51 -05:00
|
|
|
public function getAnime(string $slug): array
|
2016-12-22 21:36:23 -05:00
|
|
|
{
|
2017-01-16 13:49:51 -05:00
|
|
|
$baseData = $this->getRawMediaData('anime', $slug);
|
2017-03-24 09:08:39 -04:00
|
|
|
|
|
|
|
if (empty($baseData))
|
|
|
|
{
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
2017-03-20 13:14:01 -04:00
|
|
|
$transformed = $this->animeTransformer->transform($baseData);
|
|
|
|
$transformed['included'] = $baseData['included'];
|
|
|
|
return $transformed;
|
2017-01-16 13:49:51 -05:00
|
|
|
}
|
2017-02-04 15:18:34 -05:00
|
|
|
|
2017-01-26 13:03:38 -05:00
|
|
|
/**
|
|
|
|
* Get information about a particular anime
|
|
|
|
*
|
|
|
|
* @param string $animeId
|
|
|
|
* @return array
|
|
|
|
*/
|
2017-01-16 13:49:51 -05:00
|
|
|
public function getAnimeById(string $animeId): array
|
|
|
|
{
|
|
|
|
$baseData = $this->getRawMediaDataById('anime', $animeId);
|
2016-12-22 21:36:23 -05:00
|
|
|
return $this->animeTransformer->transform($baseData);
|
|
|
|
}
|
|
|
|
|
2017-02-04 15:18:34 -05:00
|
|
|
/**
|
2017-03-28 14:34:33 -04:00
|
|
|
* Get the anime list for the configured user
|
2017-02-04 15:18:34 -05:00
|
|
|
*
|
2017-03-28 14:34:33 -04:00
|
|
|
* @param string $status - The watching status to filter the list with
|
|
|
|
* @return array
|
2017-02-04 15:18:34 -05:00
|
|
|
*/
|
2017-03-28 14:34:33 -04:00
|
|
|
public function getAnimeList(string $status): array
|
2017-02-04 15:18:34 -05:00
|
|
|
{
|
2017-03-28 14:34:33 -04:00
|
|
|
$cacheItem = $this->cache->getItem("kitsu-anime-list-{$status}");
|
2017-02-04 15:18:34 -05:00
|
|
|
|
2017-03-28 14:34:33 -04:00
|
|
|
if ( ! $cacheItem->isHit())
|
2017-02-04 15:18:34 -05:00
|
|
|
{
|
2017-03-28 14:34:33 -04:00
|
|
|
$data = $this->getRawAnimeList($status) ?? [];
|
|
|
|
|
|
|
|
// Bail out on no data
|
|
|
|
if (empty($data))
|
2017-02-04 15:18:34 -05:00
|
|
|
{
|
2017-03-28 14:34:33 -04:00
|
|
|
$cacheItem->set([]);
|
|
|
|
$cacheItem->save();
|
|
|
|
return $cacheItem->get();
|
2017-02-04 15:18:34 -05:00
|
|
|
}
|
|
|
|
|
2017-03-28 14:34:33 -04:00
|
|
|
$included = JsonAPI::organizeIncludes($data['included']);
|
|
|
|
$included = JsonAPI::inlineIncludedRelationships($included, 'anime');
|
2017-02-04 15:18:34 -05:00
|
|
|
|
2017-03-28 14:34:33 -04:00
|
|
|
foreach($data['data'] as $i => &$item)
|
|
|
|
{
|
|
|
|
$item['included'] = $included;
|
|
|
|
}
|
|
|
|
$transformed = $this->animeListTransformer->transformCollection($data['data']);
|
2017-03-24 10:59:07 -04:00
|
|
|
|
2017-03-28 14:34:33 -04:00
|
|
|
$cacheItem->set($transformed);
|
|
|
|
$cacheItem->save();
|
2017-03-24 10:59:07 -04:00
|
|
|
}
|
|
|
|
|
2017-03-28 14:34:33 -04:00
|
|
|
return $cacheItem->get();
|
2016-12-22 21:36:23 -05:00
|
|
|
}
|
2017-03-22 11:13:50 -04:00
|
|
|
|
2017-02-14 15:29:13 -05:00
|
|
|
/**
|
|
|
|
* Get the number of anime list items
|
|
|
|
*
|
2017-03-14 14:28:08 -04:00
|
|
|
* @param string $status - Optional status to filter by
|
2017-02-14 15:29:13 -05:00
|
|
|
* @return int
|
|
|
|
*/
|
2017-03-14 14:28:08 -04:00
|
|
|
public function getAnimeListCount(string $status = '') : int
|
2017-02-14 15:29:13 -05:00
|
|
|
{
|
|
|
|
$options = [
|
|
|
|
'query' => [
|
|
|
|
'filter' => [
|
|
|
|
'user_id' => $this->getUserIdByUsername(),
|
|
|
|
'media_type' => 'Anime'
|
|
|
|
],
|
|
|
|
'page' => [
|
|
|
|
'limit' => 1
|
|
|
|
],
|
|
|
|
'sort' => '-updated_at'
|
|
|
|
]
|
|
|
|
];
|
2017-03-22 11:13:50 -04:00
|
|
|
|
2017-03-14 14:28:08 -04:00
|
|
|
if ( ! empty($status))
|
|
|
|
{
|
|
|
|
$options['query']['filter']['status'] = $status;
|
|
|
|
}
|
2017-03-22 11:13:50 -04:00
|
|
|
|
2017-02-14 15:29:13 -05:00
|
|
|
$response = $this->getRequest('library-entries', $options);
|
2017-03-22 11:13:50 -04:00
|
|
|
|
2017-02-14 15:29:13 -05:00
|
|
|
return $response['meta']['count'];
|
2017-02-04 15:18:34 -05:00
|
|
|
}
|
2017-03-22 11:13:50 -04:00
|
|
|
|
2017-03-07 17:51:08 -05:00
|
|
|
/**
|
|
|
|
* Get the full anime list
|
|
|
|
*
|
2017-03-14 14:28:08 -04:00
|
|
|
* @param array $options
|
|
|
|
* @return array
|
2017-03-07 17:51:08 -05:00
|
|
|
*/
|
2017-03-14 14:28:08 -04:00
|
|
|
public function getFullAnimeList(array $options = [
|
|
|
|
'include' => 'anime.mappings'
|
|
|
|
]): array
|
2017-03-07 17:51:08 -05:00
|
|
|
{
|
2017-03-22 11:13:50 -04:00
|
|
|
$status = $options['filter']['status'] ?? '';
|
|
|
|
$count = $this->getAnimeListCount($status);
|
|
|
|
$size = 100;
|
2017-03-07 17:51:08 -05:00
|
|
|
$pages = ceil($count / $size);
|
2017-03-22 11:13:50 -04:00
|
|
|
|
2017-03-28 11:01:38 -04:00
|
|
|
$requester = new ParallelAPIRequest();
|
2017-03-22 11:13:50 -04:00
|
|
|
|
2017-03-07 17:51:08 -05:00
|
|
|
// Set up requests
|
|
|
|
for ($i = 0; $i < $pages; $i++)
|
|
|
|
{
|
|
|
|
$offset = $i * $size;
|
2017-03-28 11:01:38 -04:00
|
|
|
$requester->addRequest($this->getPagedAnimeList($size, $offset, $options));
|
2017-03-07 17:51:08 -05:00
|
|
|
}
|
2017-03-22 11:13:50 -04:00
|
|
|
|
2017-03-28 11:01:38 -04:00
|
|
|
$responses = $requester->makeRequests();
|
2017-03-07 17:51:08 -05:00
|
|
|
$output = [];
|
|
|
|
|
|
|
|
foreach($responses as $response)
|
|
|
|
{
|
|
|
|
$data = Json::decode($response->getBody());
|
|
|
|
$output = array_merge_recursive($output, $data);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $output;
|
|
|
|
}
|
2017-02-04 15:18:34 -05:00
|
|
|
|
2017-01-13 16:53:56 -05:00
|
|
|
/**
|
2017-03-28 14:34:33 -04:00
|
|
|
* Get all the anine entries, that are organized for output to html
|
2017-01-13 16:53:56 -05:00
|
|
|
*
|
|
|
|
* @return array
|
|
|
|
*/
|
2017-03-28 14:34:33 -04:00
|
|
|
public function getFullOrganizedAnimeList(): array
|
2016-12-22 21:36:23 -05:00
|
|
|
{
|
2017-03-28 14:34:33 -04:00
|
|
|
$output = [];
|
2017-03-22 11:13:50 -04:00
|
|
|
|
2017-03-28 14:34:33 -04:00
|
|
|
$statuses = KitsuWatchingStatus::getConstList();
|
2017-02-04 15:18:34 -05:00
|
|
|
|
2017-03-28 14:34:33 -04:00
|
|
|
foreach ($statuses as $key => $status)
|
|
|
|
{
|
|
|
|
$mappedStatus = AnimeWatchingStatus::KITSU_TO_TITLE[$status];
|
|
|
|
$output[$mappedStatus] = $this->getAnimeList($status) ?? [];
|
|
|
|
}
|
|
|
|
|
|
|
|
return $output;
|
2017-01-27 12:35:28 -05:00
|
|
|
}
|
|
|
|
|
2017-03-07 20:49:31 -05:00
|
|
|
/**
|
2017-03-28 14:34:33 -04:00
|
|
|
* Get the mal id for the anime represented by the kitsu id
|
|
|
|
* to enable updating MyAnimeList
|
2017-03-07 20:49:31 -05:00
|
|
|
*
|
2017-03-28 14:34:33 -04:00
|
|
|
* @param string $kitsuAnimeId The id of the anime on Kitsu
|
|
|
|
* @return string|null Returns the mal id if it exists, otherwise null
|
2017-03-07 20:49:31 -05:00
|
|
|
*/
|
2017-03-28 14:34:33 -04:00
|
|
|
public function getMalIdForAnime(string $kitsuAnimeId)
|
2017-03-07 17:51:08 -05:00
|
|
|
{
|
2017-03-28 14:34:33 -04:00
|
|
|
$options = [
|
|
|
|
'query' => [
|
|
|
|
'include' => 'mappings'
|
|
|
|
]
|
|
|
|
];
|
|
|
|
$data = $this->getRequest("anime/{$kitsuAnimeId}", $options);
|
|
|
|
$mappings = array_column($data['included'], 'attributes');
|
2017-03-07 17:51:08 -05:00
|
|
|
|
2017-03-28 14:34:33 -04:00
|
|
|
foreach($mappings as $map)
|
2017-03-07 17:51:08 -05:00
|
|
|
{
|
2017-03-28 14:34:33 -04:00
|
|
|
if ($map['externalSite'] === 'myanimelist/anime')
|
2017-03-22 16:53:46 -04:00
|
|
|
{
|
2017-03-28 14:34:33 -04:00
|
|
|
return $map['externalId'];
|
2017-03-22 16:53:46 -04:00
|
|
|
}
|
2017-03-07 17:51:08 -05:00
|
|
|
}
|
|
|
|
|
2017-03-28 14:34:33 -04:00
|
|
|
return NULL;
|
2017-03-07 17:51:08 -05:00
|
|
|
}
|
|
|
|
|
2017-01-27 12:35:28 -05:00
|
|
|
/**
|
2017-03-28 14:34:33 -04:00
|
|
|
* Get the full anime list in paginated form
|
2017-01-27 12:35:28 -05:00
|
|
|
*
|
2017-03-28 14:34:33 -04:00
|
|
|
* @param int $limit
|
|
|
|
* @param int $offset
|
|
|
|
* @param array $options
|
|
|
|
* @return Request
|
2017-01-27 12:35:28 -05:00
|
|
|
*/
|
2017-03-28 14:34:33 -04:00
|
|
|
public function getPagedAnimeList(int $limit = 100, int $offset = 0, array $options = [
|
|
|
|
'include' => 'anime.mappings'
|
|
|
|
]): Request
|
2017-01-27 12:35:28 -05:00
|
|
|
{
|
2017-03-28 14:34:33 -04:00
|
|
|
$defaultOptions = [
|
|
|
|
'filter' => [
|
|
|
|
'user_id' => $this->getUserIdByUsername($this->getUsername()),
|
|
|
|
'media_type' => 'Anime'
|
|
|
|
],
|
|
|
|
'page' => [
|
|
|
|
'offset' => $offset,
|
|
|
|
'limit' => $limit
|
|
|
|
],
|
|
|
|
'sort' => '-updated_at'
|
|
|
|
];
|
|
|
|
$options = array_merge($defaultOptions, $options);
|
2017-03-22 16:53:46 -04:00
|
|
|
|
2017-03-28 14:34:33 -04:00
|
|
|
return $this->setUpRequest('GET', 'library-entries', ['query' => $options]);
|
|
|
|
}
|
2017-01-13 16:53:56 -05:00
|
|
|
|
2017-03-28 14:34:33 -04:00
|
|
|
/**
|
|
|
|
* Get the raw (unorganized) anime list for the configured user
|
|
|
|
*
|
|
|
|
* @param string $status - The watching status to filter the list with
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function getRawAnimeList(string $status): array
|
|
|
|
{
|
2017-02-04 15:18:34 -05:00
|
|
|
|
2017-03-28 14:34:33 -04:00
|
|
|
$options = [
|
|
|
|
'filter' => [
|
|
|
|
'user_id' => $this->getUserIdByUsername($this->getUsername()),
|
|
|
|
'media_type' => 'Anime',
|
|
|
|
'status' => $status,
|
|
|
|
],
|
|
|
|
'include' => 'media,media.genres,media.mappings,anime.streamingLinks',
|
|
|
|
'sort' => '-updated_at'
|
|
|
|
];
|
2016-12-21 12:46:20 -05:00
|
|
|
|
2017-03-28 14:34:33 -04:00
|
|
|
return $this->getFullAnimeList($options);
|
2016-12-21 12:46:20 -05:00
|
|
|
}
|
2016-12-22 21:36:23 -05:00
|
|
|
|
2017-03-28 14:34:33 -04:00
|
|
|
// -------------------------------------------------------------------------
|
|
|
|
// ! Manga-specific methods
|
|
|
|
// -------------------------------------------------------------------------
|
|
|
|
|
2017-03-07 20:49:31 -05:00
|
|
|
/**
|
2017-03-28 14:34:33 -04:00
|
|
|
* Get information about a particular manga
|
2017-03-07 20:49:31 -05:00
|
|
|
*
|
2017-03-28 14:34:33 -04:00
|
|
|
* @param string $slug
|
2017-03-07 20:49:31 -05:00
|
|
|
* @return array
|
|
|
|
*/
|
2017-03-28 14:34:33 -04:00
|
|
|
public function getManga(string $slug): array
|
2017-03-07 18:41:51 -05:00
|
|
|
{
|
2017-03-28 14:34:33 -04:00
|
|
|
$baseData = $this->getRawMediaData('manga', $slug);
|
|
|
|
|
|
|
|
if (empty($baseData))
|
2017-03-07 18:41:51 -05:00
|
|
|
{
|
2017-03-28 14:34:33 -04:00
|
|
|
return [];
|
2017-03-07 18:41:51 -05:00
|
|
|
}
|
2017-03-22 11:13:50 -04:00
|
|
|
|
2017-03-28 14:34:33 -04:00
|
|
|
$transformed = $this->mangaTransformer->transform($baseData);
|
|
|
|
$transformed['included'] = $baseData['included'];
|
|
|
|
return $transformed;
|
2017-03-07 18:41:51 -05:00
|
|
|
}
|
|
|
|
|
2017-01-26 13:03:38 -05:00
|
|
|
/**
|
|
|
|
* Get the manga list for the configured user
|
|
|
|
*
|
|
|
|
* @param string $status - The reading status by which to filter the list
|
|
|
|
* @param int $limit - The number of list items to fetch per page
|
|
|
|
* @param int $offset - The page offset
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function getMangaList(string $status, int $limit = 200, int $offset = 0): array
|
2017-01-04 13:16:58 -05:00
|
|
|
{
|
|
|
|
$options = [
|
|
|
|
'query' => [
|
|
|
|
'filter' => [
|
2017-01-05 13:41:32 -05:00
|
|
|
'user_id' => $this->getUserIdByUsername($this->getUsername()),
|
2017-01-04 13:16:58 -05:00
|
|
|
'media_type' => 'Manga',
|
|
|
|
'status' => $status,
|
|
|
|
],
|
2017-03-29 13:29:03 -04:00
|
|
|
'include' => 'media,media.genres,media.mappings',
|
2017-01-04 13:16:58 -05:00
|
|
|
'page' => [
|
2017-01-26 13:03:38 -05:00
|
|
|
'offset' => $offset,
|
|
|
|
'limit' => $limit
|
2017-01-04 13:16:58 -05:00
|
|
|
],
|
|
|
|
'sort' => '-updated_at'
|
|
|
|
]
|
|
|
|
];
|
2017-02-04 15:18:34 -05:00
|
|
|
|
2017-03-10 12:50:29 -05:00
|
|
|
$cacheItem = $this->cache->getItem("kitsu-manga-list-{$status}");
|
2017-01-04 13:16:58 -05:00
|
|
|
|
2017-01-16 14:14:45 -05:00
|
|
|
if ( ! $cacheItem->isHit())
|
2017-01-04 13:16:58 -05:00
|
|
|
{
|
2017-01-16 14:14:45 -05:00
|
|
|
$data = $this->getRequest('library-entries', $options);
|
2017-01-04 13:16:58 -05:00
|
|
|
|
2017-03-29 13:29:03 -04:00
|
|
|
$included = JsonAPI::organizeIncludes($data['included']);
|
|
|
|
$included = JsonAPI::inlineIncludedRelationships($included, 'manga');
|
|
|
|
|
|
|
|
foreach($data['data'] as $i => &$item)
|
|
|
|
{
|
|
|
|
$item['included'] = $included;
|
|
|
|
}
|
|
|
|
|
|
|
|
$transformed = $this->mangaListTransformer->transformCollection($data['data']);
|
2017-01-04 13:16:58 -05:00
|
|
|
|
2017-01-16 14:14:45 -05:00
|
|
|
$cacheItem->set($transformed);
|
|
|
|
$cacheItem->save();
|
|
|
|
}
|
2017-02-04 15:18:34 -05:00
|
|
|
|
2017-01-16 14:14:45 -05:00
|
|
|
return $cacheItem->get();
|
2017-01-04 13:16:58 -05:00
|
|
|
}
|
2017-01-03 21:06:49 -05:00
|
|
|
|
2017-03-29 12:32:36 -04:00
|
|
|
/**
|
|
|
|
* Get the number of manga list items
|
|
|
|
*
|
|
|
|
* @param string $status - Optional status to filter by
|
|
|
|
* @return int
|
|
|
|
*/
|
|
|
|
public function getMangaListCount(string $status = '') : int
|
|
|
|
{
|
|
|
|
$options = [
|
|
|
|
'query' => [
|
|
|
|
'filter' => [
|
|
|
|
'user_id' => $this->getUserIdByUsername(),
|
|
|
|
'media_type' => 'Manga'
|
|
|
|
],
|
|
|
|
'page' => [
|
|
|
|
'limit' => 1
|
|
|
|
],
|
|
|
|
'sort' => '-updated_at'
|
|
|
|
]
|
|
|
|
];
|
|
|
|
|
|
|
|
if ( ! empty($status))
|
|
|
|
{
|
|
|
|
$options['query']['filter']['status'] = $status;
|
|
|
|
}
|
|
|
|
|
|
|
|
$response = $this->getRequest('library-entries', $options);
|
|
|
|
|
|
|
|
return $response['meta']['count'];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the full manga list
|
|
|
|
*
|
|
|
|
* @param array $options
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function getFullMangaList(array $options = [
|
|
|
|
'include' => 'manga.mappings'
|
|
|
|
]): array
|
|
|
|
{
|
|
|
|
$status = $options['filter']['status'] ?? '';
|
|
|
|
$count = $this->getMangaListCount($status);
|
|
|
|
$size = 100;
|
|
|
|
$pages = ceil($count / $size);
|
|
|
|
|
|
|
|
$requester = new ParallelAPIRequest();
|
|
|
|
|
|
|
|
// Set up requests
|
|
|
|
for ($i = 0; $i < $pages; $i++)
|
|
|
|
{
|
|
|
|
$offset = $i * $size;
|
|
|
|
$requester->addRequest($this->getPagedMangaList($size, $offset, $options));
|
|
|
|
}
|
|
|
|
|
|
|
|
$responses = $requester->makeRequests();
|
|
|
|
$output = [];
|
|
|
|
|
|
|
|
foreach($responses as $response)
|
|
|
|
{
|
|
|
|
$data = Json::decode($response->getBody());
|
|
|
|
$output = array_merge_recursive($output, $data);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $output;
|
|
|
|
}
|
|
|
|
|
2017-01-26 13:03:38 -05:00
|
|
|
/**
|
2017-03-28 14:34:33 -04:00
|
|
|
* Get all Manga lists
|
2017-01-26 13:03:38 -05:00
|
|
|
*
|
|
|
|
* @return array
|
|
|
|
*/
|
2017-03-28 14:34:33 -04:00
|
|
|
public function getFullOrganizedMangaList(): array
|
|
|
|
{
|
|
|
|
$statuses = KitsuReadingStatus::getConstList();
|
|
|
|
$output = [];
|
|
|
|
foreach ($statuses as $status)
|
|
|
|
{
|
|
|
|
$mappedStatus = MangaReadingStatus::KITSU_TO_TITLE[$status];
|
|
|
|
$output[$mappedStatus] = $this->getMangaList($status);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $output;
|
|
|
|
}
|
|
|
|
|
2017-03-29 12:32:36 -04:00
|
|
|
/**
|
|
|
|
* Get the full manga list in paginated form
|
|
|
|
*
|
|
|
|
* @param int $limit
|
|
|
|
* @param int $offset
|
|
|
|
* @param array $options
|
|
|
|
* @return Request
|
|
|
|
*/
|
|
|
|
public function getPagedMangaList(int $limit = 100, int $offset = 0, array $options = [
|
|
|
|
'include' => 'manga.mappings'
|
|
|
|
]): Request
|
|
|
|
{
|
|
|
|
$defaultOptions = [
|
|
|
|
'filter' => [
|
|
|
|
'user_id' => $this->getUserIdByUsername($this->getUsername()),
|
|
|
|
'media_type' => 'Manga'
|
|
|
|
],
|
|
|
|
'page' => [
|
|
|
|
'offset' => $offset,
|
|
|
|
'limit' => $limit
|
|
|
|
],
|
|
|
|
'sort' => '-updated_at'
|
|
|
|
];
|
|
|
|
$options = array_merge($defaultOptions, $options);
|
|
|
|
|
|
|
|
return $this->setUpRequest('GET', 'library-entries', ['query' => $options]);
|
|
|
|
}
|
|
|
|
|
2017-03-28 14:34:33 -04:00
|
|
|
/**
|
|
|
|
* Get the mal id for the manga represented by the kitsu id
|
|
|
|
* to enable updating MyAnimeList
|
|
|
|
*
|
|
|
|
* @param string $kitsuAnimeId The id of the anime on Kitsu
|
|
|
|
* @return string|null Returns the mal id if it exists, otherwise null
|
|
|
|
*/
|
|
|
|
public function getMalIdForManga(string $kitsuMangaId)
|
2016-12-22 21:36:23 -05:00
|
|
|
{
|
2017-01-05 13:41:32 -05:00
|
|
|
$options = [
|
|
|
|
'query' => [
|
2017-03-28 14:34:33 -04:00
|
|
|
'include' => 'mappings'
|
2017-01-10 21:13:44 -05:00
|
|
|
]
|
2017-01-05 13:41:32 -05:00
|
|
|
];
|
2017-03-28 14:34:33 -04:00
|
|
|
$data = $this->getRequest("manga/{$kitsuMangaId}", $options);
|
|
|
|
$mappings = array_column($data['included'], 'attributes');
|
2017-01-05 13:41:32 -05:00
|
|
|
|
2017-03-28 14:34:33 -04:00
|
|
|
foreach($mappings as $map)
|
2017-01-09 20:36:48 -05:00
|
|
|
{
|
2017-03-29 14:25:03 -04:00
|
|
|
if ($map['externalSite'] === 'myanimelist/manga')
|
2017-03-28 14:34:33 -04:00
|
|
|
{
|
|
|
|
return $map['externalId'];
|
|
|
|
}
|
2017-01-09 20:36:48 -05:00
|
|
|
}
|
|
|
|
|
2017-03-28 14:34:33 -04:00
|
|
|
return NULL;
|
2017-01-06 21:39:01 -05:00
|
|
|
}
|
2016-12-22 21:36:23 -05:00
|
|
|
|
2017-03-28 14:34:33 -04:00
|
|
|
// -------------------------------------------------------------------------
|
|
|
|
// ! Generic API calls
|
|
|
|
// -------------------------------------------------------------------------
|
|
|
|
|
2017-01-26 13:03:38 -05:00
|
|
|
/**
|
|
|
|
* Create a list item
|
|
|
|
*
|
|
|
|
* @param array $data
|
2017-02-08 15:48:20 -05:00
|
|
|
* @return Request
|
2017-01-26 13:03:38 -05:00
|
|
|
*/
|
2017-02-08 15:48:20 -05:00
|
|
|
public function createListItem(array $data): Request
|
2017-01-10 21:13:44 -05:00
|
|
|
{
|
|
|
|
$data['user_id'] = $this->getUserIdByUsername($this->getUsername());
|
|
|
|
return $this->listItem->create($data);
|
|
|
|
}
|
|
|
|
|
2017-01-26 13:03:38 -05:00
|
|
|
/**
|
|
|
|
* Get the data for a specific list item, generally for editing
|
|
|
|
*
|
|
|
|
* @param string $listId - The unique identifier of that list item
|
|
|
|
* @return array
|
|
|
|
*/
|
2017-01-06 21:39:01 -05:00
|
|
|
public function getListItem(string $listId): array
|
|
|
|
{
|
|
|
|
$baseData = $this->listItem->get($listId);
|
2017-01-13 16:53:56 -05:00
|
|
|
$included = JsonAPI::organizeIncludes($baseData['included']);
|
|
|
|
|
2017-01-06 21:39:01 -05:00
|
|
|
|
2017-01-13 16:53:56 -05:00
|
|
|
switch (TRUE)
|
2017-01-06 21:39:01 -05:00
|
|
|
{
|
2017-01-13 16:53:56 -05:00
|
|
|
case in_array('anime', array_keys($included)):
|
|
|
|
$included = JsonAPI::inlineIncludedRelationships($included, 'anime');
|
|
|
|
$baseData['data']['included'] = $included;
|
2017-01-06 21:39:01 -05:00
|
|
|
return $this->animeListTransformer->transform($baseData['data']);
|
|
|
|
|
2017-01-13 16:53:56 -05:00
|
|
|
case in_array('manga', array_keys($included)):
|
|
|
|
$included = JsonAPI::inlineIncludedRelationships($included, 'manga');
|
|
|
|
$baseData['data']['included'] = $included;
|
2017-01-06 21:39:01 -05:00
|
|
|
$baseData['data']['manga'] = $baseData['included'][0];
|
|
|
|
return $this->mangaListTransformer->transform($baseData['data']);
|
|
|
|
|
|
|
|
default:
|
2017-01-13 16:53:56 -05:00
|
|
|
return $baseData['data'];
|
2017-01-06 21:39:01 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-26 13:03:38 -05:00
|
|
|
/**
|
|
|
|
* Modify a list item
|
|
|
|
*
|
|
|
|
* @param array $data
|
2017-02-08 15:48:20 -05:00
|
|
|
* @return Request
|
2017-01-26 13:03:38 -05:00
|
|
|
*/
|
2017-02-08 15:48:20 -05:00
|
|
|
public function updateListItem(array $data): Request
|
2017-01-06 21:39:01 -05:00
|
|
|
{
|
2017-02-09 20:10:13 -05:00
|
|
|
return $this->listItem->update($data['id'], $data['data']);
|
2016-12-22 21:36:23 -05:00
|
|
|
}
|
|
|
|
|
2017-01-26 13:03:38 -05:00
|
|
|
/**
|
|
|
|
* Remove a list item
|
|
|
|
*
|
|
|
|
* @param string $id - The id of the list item to remove
|
2017-02-08 15:48:20 -05:00
|
|
|
* @return Request
|
2017-01-26 13:03:38 -05:00
|
|
|
*/
|
2017-02-08 15:48:20 -05:00
|
|
|
public function deleteListItem(string $id): Request
|
2017-01-09 20:36:48 -05:00
|
|
|
{
|
|
|
|
return $this->listItem->delete($id);
|
|
|
|
}
|
|
|
|
|
2017-03-07 20:49:31 -05:00
|
|
|
/**
|
|
|
|
* Get the kitsu username from config
|
|
|
|
*
|
|
|
|
* @return string
|
|
|
|
*/
|
2017-01-05 13:41:32 -05:00
|
|
|
private function getUsername(): string
|
2016-12-22 21:36:23 -05:00
|
|
|
{
|
2017-01-05 13:41:32 -05:00
|
|
|
return $this->getContainer()
|
|
|
|
->get('config')
|
|
|
|
->get(['kitsu_username']);
|
|
|
|
}
|
2017-02-04 15:18:34 -05:00
|
|
|
|
2017-03-07 20:49:31 -05:00
|
|
|
/**
|
|
|
|
* Get the raw data for the anime id
|
|
|
|
*
|
|
|
|
* @param string $type
|
|
|
|
* @param string $id
|
|
|
|
* @return array
|
|
|
|
*/
|
2017-01-16 13:49:51 -05:00
|
|
|
private function getRawMediaDataById(string $type, string $id): array
|
|
|
|
{
|
|
|
|
$options = [
|
|
|
|
'query' => [
|
|
|
|
'include' => ($type === 'anime')
|
|
|
|
? 'genres,mappings,streamingLinks'
|
|
|
|
: 'genres,mappings',
|
|
|
|
]
|
|
|
|
];
|
|
|
|
|
|
|
|
$data = $this->getRequest("{$type}/{$id}", $options);
|
2017-03-24 10:59:07 -04:00
|
|
|
|
|
|
|
if (empty($data['data']))
|
|
|
|
{
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
2017-01-16 13:49:51 -05:00
|
|
|
$baseData = $data['data']['attributes'];
|
|
|
|
$baseData['included'] = $data['included'];
|
|
|
|
return $baseData;
|
|
|
|
}
|
2016-12-22 21:36:23 -05:00
|
|
|
|
2017-03-07 20:49:31 -05:00
|
|
|
/**
|
|
|
|
* Get media item by slug
|
|
|
|
*
|
|
|
|
* @param string $type
|
|
|
|
* @param string $slug
|
|
|
|
* @return array
|
|
|
|
*/
|
2017-01-05 13:41:32 -05:00
|
|
|
private function getRawMediaData(string $type, string $slug): array
|
|
|
|
{
|
|
|
|
$options = [
|
|
|
|
'query' => [
|
|
|
|
'filter' => [
|
|
|
|
'slug' => $slug
|
|
|
|
],
|
2017-03-31 13:37:53 -04:00
|
|
|
'fields' => [
|
|
|
|
'characters' => 'slug,name,image'
|
|
|
|
],
|
2017-01-05 22:24:45 -05:00
|
|
|
'include' => ($type === 'anime')
|
2017-03-20 13:14:01 -04:00
|
|
|
? 'genres,mappings,streamingLinks,animeCharacters.character'
|
2017-03-24 10:59:07 -04:00
|
|
|
: 'genres,mappings,mangaCharacters.character,castings.character',
|
2017-01-05 13:41:32 -05:00
|
|
|
]
|
|
|
|
];
|
|
|
|
|
|
|
|
$data = $this->getRequest($type, $options);
|
2017-03-24 09:08:39 -04:00
|
|
|
|
|
|
|
if (empty($data['data']))
|
|
|
|
{
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
2017-01-05 13:41:32 -05:00
|
|
|
$baseData = $data['data'][0]['attributes'];
|
|
|
|
$baseData['included'] = $data['included'];
|
2016-12-22 21:36:23 -05:00
|
|
|
return $baseData;
|
|
|
|
}
|
2016-12-21 12:46:20 -05:00
|
|
|
}
|