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
|
|
|
*
|
2018-08-22 13:48:27 -04:00
|
|
|
* An API client for Kitsu to manage anime and manga watch lists
|
2016-12-21 12:46:20 -05:00
|
|
|
*
|
2021-02-04 11:57:01 -05:00
|
|
|
* PHP version 8
|
2016-12-21 12:46:20 -05:00
|
|
|
*
|
2022-03-04 15:50:35 -05:00
|
|
|
* @copyright 2015 - 2022 Timothy J. Warren <tim@timshome.page>
|
2017-01-06 23:34:56 -05:00
|
|
|
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
2020-12-10 17:06:50 -05:00
|
|
|
* @version 5.2
|
2022-03-04 15:50:35 -05:00
|
|
|
* @link https://git.timshome.page/timw4mail/HummingBirdAnimeClient
|
2017-01-11 10:34:24 -05:00
|
|
|
*/
|
|
|
|
|
|
|
|
namespace Aviat\AnimeClient\API\Kitsu;
|
2016-12-21 12:46:20 -05:00
|
|
|
|
2020-10-09 16:18:45 -04:00
|
|
|
use Amp;
|
2017-01-03 21:06:49 -05:00
|
|
|
use Aviat\AnimeClient\API\Kitsu\Transformer\{
|
2020-10-21 17:06:50 -04:00
|
|
|
AnimeHistoryTransformer,
|
|
|
|
AnimeListTransformer,
|
2017-02-04 15:18:34 -05:00
|
|
|
AnimeTransformer,
|
2020-08-24 13:07:47 -04:00
|
|
|
LibraryEntryTransformer,
|
2020-10-21 17:06:50 -04:00
|
|
|
MangaHistoryTransformer,
|
|
|
|
MangaListTransformer,
|
|
|
|
MangaTransformer
|
2017-01-03 21:06:49 -05:00
|
|
|
};
|
2022-03-03 17:26:09 -05:00
|
|
|
use Aviat\AnimeClient\API\{
|
|
|
|
CacheTrait,
|
|
|
|
Enum\AnimeWatchingStatus\Kitsu as KitsuWatchingStatus,
|
|
|
|
Enum\MangaReadingStatus\Kitsu as KitsuReadingStatus,
|
|
|
|
Mapping\AnimeWatchingStatus,
|
|
|
|
Mapping\MangaReadingStatus
|
|
|
|
};
|
2020-12-10 15:59:37 -05:00
|
|
|
use Aviat\AnimeClient\Enum\MediaType;
|
2020-10-21 17:06:50 -04:00
|
|
|
use Aviat\AnimeClient\Kitsu as K;
|
2022-03-03 17:26:09 -05:00
|
|
|
use Aviat\AnimeClient\Types\{Anime, MangaPage};
|
2020-10-21 17:06:50 -04:00
|
|
|
use Aviat\Ion\{
|
|
|
|
Di\ContainerAware,
|
|
|
|
Json
|
|
|
|
};
|
2021-02-12 13:09:57 -05:00
|
|
|
use Generator;
|
2020-10-21 17:06:50 -04:00
|
|
|
use function Amp\Promise\wait;
|
|
|
|
use function Aviat\AnimeClient\getApiClient;
|
2022-01-06 12:50:26 -05:00
|
|
|
use const Aviat\AnimeClient\SESSION_SEGMENT;
|
2019-12-09 14:34:23 -05:00
|
|
|
|
2016-12-21 12:46:20 -05:00
|
|
|
/**
|
|
|
|
* Kitsu API Model
|
|
|
|
*/
|
2022-03-03 17:26:09 -05:00
|
|
|
final class Model
|
|
|
|
{
|
2017-01-13 16:53:56 -05:00
|
|
|
use CacheTrait;
|
2017-01-05 13:41:32 -05:00
|
|
|
use ContainerAware;
|
2020-08-06 09:39:12 -04:00
|
|
|
use RequestBuilderTrait;
|
|
|
|
use MutationTrait;
|
2016-12-21 12:46:20 -05:00
|
|
|
|
2020-10-09 16:18:45 -04:00
|
|
|
protected const LIST_PAGE_SIZE = 100;
|
2016-12-22 21:36:23 -05:00
|
|
|
|
2020-10-21 17:06:50 -04:00
|
|
|
protected AnimeTransformer $animeTransformer;
|
|
|
|
protected MangaTransformer $mangaTransformer;
|
|
|
|
|
2016-12-22 21:36:23 -05:00
|
|
|
/**
|
2017-02-17 11:37:22 -05:00
|
|
|
* Constructor
|
2016-12-22 21:36:23 -05:00
|
|
|
*/
|
2022-03-03 13:25:10 -05:00
|
|
|
public function __construct(protected ListItem $listItem)
|
2016-12-21 12:46:20 -05:00
|
|
|
{
|
2016-12-22 21:36:23 -05:00
|
|
|
$this->animeTransformer = new AnimeTransformer();
|
2017-01-04 13:16:58 -05:00
|
|
|
$this->mangaTransformer = new MangaTransformer();
|
2016-12-21 12:46:20 -05:00
|
|
|
}
|
|
|
|
|
2017-03-28 14:34:33 -04:00
|
|
|
/**
|
|
|
|
* Get the access token from the Kitsu API
|
|
|
|
*/
|
2021-02-16 14:43:51 -05:00
|
|
|
public function authenticate(string $username, string $password): array|false
|
2017-03-28 14:34:33 -04:00
|
|
|
{
|
2017-12-08 22:32:00 -05:00
|
|
|
// K::AUTH_URL
|
2020-07-31 19:03:27 -04:00
|
|
|
$response = $this->requestBuilder->getResponse('POST', K::AUTH_URL, [
|
2017-12-08 22:32:00 -05:00
|
|
|
'headers' => [
|
|
|
|
'accept' => NULL,
|
|
|
|
'Content-type' => 'application/x-www-form-urlencoded',
|
|
|
|
'client_id' => NULL,
|
2022-03-03 17:26:09 -05:00
|
|
|
'client_secret' => NULL,
|
2017-12-08 22:32:00 -05:00
|
|
|
],
|
2017-03-28 14:34:33 -04:00
|
|
|
'form_params' => [
|
|
|
|
'grant_type' => 'password',
|
|
|
|
'username' => $username,
|
2022-03-03 17:26:09 -05:00
|
|
|
'password' => $password,
|
|
|
|
],
|
2017-03-28 14:34:33 -04:00
|
|
|
]);
|
2020-03-11 16:26:17 -04:00
|
|
|
$data = Json::decode(wait($response->getBody()->buffer()));
|
2018-01-16 14:58:07 -05:00
|
|
|
|
2017-12-08 22:32:00 -05:00
|
|
|
if (array_key_exists('error', $data))
|
|
|
|
{
|
2020-08-17 10:23:32 -04:00
|
|
|
dump([
|
2022-03-03 13:25:10 -05:00
|
|
|
'method' => self::class . '\\' . __METHOD__,
|
2020-08-17 10:23:32 -04:00
|
|
|
'error' => $data['error'],
|
|
|
|
'response' => $response,
|
|
|
|
]);
|
2022-03-03 17:26:09 -05:00
|
|
|
|
|
|
|
exit();
|
2017-12-08 22:32:00 -05:00
|
|
|
}
|
|
|
|
|
2018-10-05 14:32:05 -04:00
|
|
|
if (array_key_exists('access_token', $data))
|
|
|
|
{
|
|
|
|
return $data;
|
|
|
|
}
|
|
|
|
|
2017-03-28 14:34:33 -04:00
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
2017-06-19 15:31:24 -04:00
|
|
|
/**
|
|
|
|
* Extend the current session with a refresh token
|
|
|
|
*/
|
2021-02-16 14:43:51 -05:00
|
|
|
public function reAuthenticate(string $token): array|false
|
2017-06-19 15:31:24 -04:00
|
|
|
{
|
2020-07-31 19:03:27 -04:00
|
|
|
$response = $this->requestBuilder->getResponse('POST', K::AUTH_URL, [
|
2017-12-08 22:32:00 -05:00
|
|
|
'headers' => [
|
2020-05-08 21:34:36 -04:00
|
|
|
'accept' => NULL,
|
|
|
|
'Content-type' => 'application/x-www-form-urlencoded',
|
2022-03-03 17:26:09 -05:00
|
|
|
'Accept-encoding' => '*',
|
2017-12-08 22:32:00 -05:00
|
|
|
],
|
2017-06-19 15:31:24 -04:00
|
|
|
'form_params' => [
|
|
|
|
'grant_type' => 'refresh_token',
|
2022-03-03 17:26:09 -05:00
|
|
|
'refresh_token' => $token,
|
|
|
|
],
|
2017-06-19 15:31:24 -04:00
|
|
|
]);
|
2020-03-11 16:26:17 -04:00
|
|
|
$data = Json::decode(wait($response->getBody()->buffer()));
|
2017-06-19 15:31:24 -04:00
|
|
|
|
2020-05-08 21:34:36 -04:00
|
|
|
if (array_key_exists('error', $data))
|
|
|
|
{
|
2020-08-17 10:23:32 -04:00
|
|
|
dump([
|
2022-03-03 13:25:10 -05:00
|
|
|
'method' => self::class . '\\' . __METHOD__,
|
2020-08-17 10:23:32 -04:00
|
|
|
'error' => $data['error'],
|
|
|
|
'response' => $response,
|
|
|
|
]);
|
2022-03-03 17:26:09 -05:00
|
|
|
|
|
|
|
exit();
|
2020-05-08 21:34:36 -04:00
|
|
|
}
|
|
|
|
|
2017-06-19 15:31:24 -04:00
|
|
|
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
|
|
|
|
*/
|
2022-03-03 17:26:09 -05:00
|
|
|
public function getUserIdByUsername(?string $username = NULL): string
|
2017-01-05 13:41:32 -05:00
|
|
|
{
|
2018-02-02 09:50:58 -05:00
|
|
|
if ($username === NULL)
|
2017-01-27 12:35:28 -05:00
|
|
|
{
|
|
|
|
$username = $this->getUsername();
|
|
|
|
}
|
2017-02-04 15:18:34 -05:00
|
|
|
|
2022-03-03 17:26:09 -05:00
|
|
|
return $this->getCached(K::AUTH_USER_ID_KEY, function (string $username) {
|
2020-10-21 17:59:43 -04:00
|
|
|
$data = $this->requestBuilder->runQuery('GetUserId', [
|
2022-03-03 17:26:09 -05:00
|
|
|
'slug' => $username,
|
2017-01-26 13:03:38 -05:00
|
|
|
]);
|
2017-01-05 13:41:32 -05:00
|
|
|
|
2020-10-21 17:59:43 -04:00
|
|
|
return $data['data']['findProfileBySlug']['id'] ?? NULL;
|
2020-05-08 19:15:21 -04:00
|
|
|
}, [$username]);
|
2017-01-05 13:41:32 -05:00
|
|
|
}
|
|
|
|
|
2017-03-08 12:55:49 -05:00
|
|
|
/**
|
|
|
|
* Get information about a character
|
|
|
|
*
|
2022-03-03 13:25:10 -05:00
|
|
|
* @return mixed[]
|
2017-03-08 12:55:49 -05:00
|
|
|
*/
|
|
|
|
public function getCharacter(string $slug): array
|
|
|
|
{
|
2020-08-17 14:01:55 -04:00
|
|
|
return $this->requestBuilder->runQuery('CharacterDetails', [
|
2022-03-03 17:26:09 -05:00
|
|
|
'slug' => $slug,
|
2017-03-08 12:55:49 -05:00
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
2018-10-19 09:30:27 -04:00
|
|
|
/**
|
|
|
|
* Get information about a person
|
|
|
|
*
|
2022-03-03 13:25:10 -05:00
|
|
|
* @return mixed[]
|
2018-10-19 09:30:27 -04:00
|
|
|
*/
|
2020-08-27 15:01:00 -04:00
|
|
|
public function getPerson(string $slug): array
|
2018-10-19 09:30:27 -04:00
|
|
|
{
|
2020-08-27 15:01:00 -04:00
|
|
|
return $this->getCached("kitsu-person-{$slug}", fn () => $this->requestBuilder->runQuery('PersonDetails', [
|
2022-03-03 17:26:09 -05:00
|
|
|
'slug' => $slug,
|
2020-05-08 19:18:10 -04:00
|
|
|
]));
|
2018-10-19 09:30:27 -04:00
|
|
|
}
|
|
|
|
|
2017-03-08 13:46:50 -05:00
|
|
|
/**
|
|
|
|
* Get profile information for the configured user
|
|
|
|
*
|
2022-03-03 13:25:10 -05:00
|
|
|
* @return mixed[]
|
2017-03-08 13:46:50 -05:00
|
|
|
*/
|
2017-03-08 12:55:49 -05:00
|
|
|
public function getUserData(string $username): array
|
|
|
|
{
|
2020-08-24 15:20:07 -04:00
|
|
|
return $this->requestBuilder->runQuery('UserDetails', [
|
|
|
|
'slug' => $username,
|
2017-03-08 12:55:49 -05:00
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
2020-10-21 17:06:50 -04:00
|
|
|
// -------------------------------------------------------------------------
|
|
|
|
// ! Anime-specific methods
|
|
|
|
// -------------------------------------------------------------------------
|
|
|
|
/**
|
|
|
|
* Get information about a particular anime
|
|
|
|
*/
|
|
|
|
public function getAnime(string $slug): Anime
|
|
|
|
{
|
|
|
|
$baseData = $this->requestBuilder->runQuery('AnimeDetails', [
|
2022-03-03 17:26:09 -05:00
|
|
|
'slug' => $slug,
|
2020-10-21 17:06:50 -04:00
|
|
|
]);
|
|
|
|
|
|
|
|
if (empty($baseData))
|
|
|
|
{
|
|
|
|
return Anime::from([]);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this->animeTransformer->transform($baseData);
|
|
|
|
}
|
|
|
|
|
2020-12-02 12:42:47 -05:00
|
|
|
public function getRandomAnime(): Anime
|
|
|
|
{
|
|
|
|
$baseData = $this->requestBuilder->runQuery('RandomMedia', [
|
2022-03-03 17:26:09 -05:00
|
|
|
'type' => 'ANIME',
|
2020-12-02 12:42:47 -05:00
|
|
|
]);
|
|
|
|
|
|
|
|
return $this->animeTransformer->transform($baseData);
|
|
|
|
}
|
|
|
|
|
2020-12-10 17:06:50 -05:00
|
|
|
public function getRandomLibraryAnime(string $status): Anime
|
|
|
|
{
|
|
|
|
// @TODO
|
|
|
|
return Anime::from([]);
|
|
|
|
}
|
|
|
|
|
2020-10-21 17:06:50 -04:00
|
|
|
/**
|
|
|
|
* Get information about a particular anime
|
|
|
|
*/
|
|
|
|
public function getAnimeById(string $animeId): Anime
|
|
|
|
{
|
|
|
|
$baseData = $this->requestBuilder->runQuery('AnimeDetailsById', [
|
|
|
|
'id' => $animeId,
|
|
|
|
]);
|
2022-03-03 17:26:09 -05:00
|
|
|
|
2020-10-21 17:06:50 -04:00
|
|
|
return $this->animeTransformer->transform($baseData);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Retrieve the data for the anime watch history page
|
|
|
|
*
|
2022-03-03 13:25:10 -05:00
|
|
|
* @return mixed[]
|
2020-10-21 17:06:50 -04:00
|
|
|
*/
|
|
|
|
public function getAnimeHistory(): array
|
|
|
|
{
|
|
|
|
$key = K::ANIME_HISTORY_LIST_CACHE_KEY;
|
|
|
|
$list = $this->cache->get($key, NULL);
|
|
|
|
|
|
|
|
if ($list === NULL)
|
|
|
|
{
|
|
|
|
$raw = $this->getHistoryList();
|
|
|
|
|
|
|
|
$list = (new AnimeHistoryTransformer())->transform($raw);
|
|
|
|
|
|
|
|
$this->cache->set($key, $list);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $list;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the anime list for the configured user
|
|
|
|
*
|
|
|
|
* @param string $status - The watching status to filter the list with
|
2022-03-03 13:25:10 -05:00
|
|
|
* @return mixed[]
|
2020-10-21 17:06:50 -04:00
|
|
|
*/
|
|
|
|
public function getAnimeList(string $status): array
|
|
|
|
{
|
|
|
|
$key = "kitsu-anime-list-{$status}";
|
|
|
|
|
|
|
|
$list = $this->cache->get($key, NULL);
|
|
|
|
|
|
|
|
if ($list === NULL)
|
|
|
|
{
|
2020-12-10 15:59:37 -05:00
|
|
|
$data = $this->getList(MediaType::ANIME, $status) ?? [];
|
2020-10-21 17:06:50 -04:00
|
|
|
|
|
|
|
// Bail out on no data
|
|
|
|
if (empty($data))
|
|
|
|
{
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
|
|
|
$transformer = new AnimeListTransformer();
|
|
|
|
$transformed = $transformer->transformCollection($data);
|
|
|
|
$keyed = [];
|
|
|
|
|
2022-03-03 17:26:09 -05:00
|
|
|
foreach ($transformed as $item)
|
2020-10-21 17:06:50 -04:00
|
|
|
{
|
|
|
|
$keyed[$item['id']] = $item;
|
|
|
|
}
|
|
|
|
|
|
|
|
$list = $keyed;
|
|
|
|
$this->cache->set($key, $list);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $list;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the number of anime list items
|
|
|
|
*
|
|
|
|
* @param string $status - Optional status to filter by
|
|
|
|
*/
|
2022-03-03 17:26:09 -05:00
|
|
|
public function getAnimeListCount(string $status = ''): int
|
2020-10-21 17:06:50 -04:00
|
|
|
{
|
2020-12-10 15:59:37 -05:00
|
|
|
return $this->getListCount(MediaType::ANIME, $status);
|
2020-10-21 17:06:50 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get all the anime entries, that are organized for output to html
|
|
|
|
*
|
2022-03-03 13:25:10 -05:00
|
|
|
* @return array<string, mixed[]>
|
2020-10-21 17:06:50 -04:00
|
|
|
*/
|
|
|
|
public function getFullOrganizedAnimeList(): array
|
|
|
|
{
|
|
|
|
$output = [];
|
|
|
|
|
|
|
|
$statuses = KitsuWatchingStatus::getConstList();
|
|
|
|
|
2022-03-03 13:25:10 -05:00
|
|
|
foreach ($statuses as $status)
|
2020-10-21 17:06:50 -04:00
|
|
|
{
|
|
|
|
$mappedStatus = AnimeWatchingStatus::KITSU_TO_TITLE[$status];
|
|
|
|
$output[$mappedStatus] = $this->getAnimeList($status) ?? [];
|
|
|
|
}
|
|
|
|
|
|
|
|
return $output;
|
|
|
|
}
|
|
|
|
|
|
|
|
// -------------------------------------------------------------------------
|
|
|
|
// ! Manga-specific methods
|
|
|
|
// -------------------------------------------------------------------------
|
|
|
|
/**
|
|
|
|
* Get information about a particular manga
|
|
|
|
*/
|
|
|
|
public function getManga(string $slug): MangaPage
|
|
|
|
{
|
|
|
|
$baseData = $this->requestBuilder->runQuery('MangaDetails', [
|
2022-03-03 17:26:09 -05:00
|
|
|
'slug' => $slug,
|
2020-10-21 17:06:50 -04:00
|
|
|
]);
|
|
|
|
|
|
|
|
if (empty($baseData))
|
|
|
|
{
|
|
|
|
return MangaPage::from([]);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this->mangaTransformer->transform($baseData);
|
|
|
|
}
|
|
|
|
|
2020-12-02 12:42:47 -05:00
|
|
|
public function getRandomManga(): MangaPage
|
|
|
|
{
|
|
|
|
$baseData = $this->requestBuilder->runQuery('RandomMedia', [
|
2022-03-03 17:26:09 -05:00
|
|
|
'type' => 'MANGA',
|
2020-12-02 12:42:47 -05:00
|
|
|
]);
|
|
|
|
|
|
|
|
return $this->mangaTransformer->transform($baseData);
|
|
|
|
}
|
|
|
|
|
2020-10-21 17:06:50 -04:00
|
|
|
/**
|
|
|
|
* Get information about a particular manga
|
|
|
|
*/
|
|
|
|
public function getMangaById(string $mangaId): MangaPage
|
|
|
|
{
|
|
|
|
$baseData = $this->requestBuilder->runQuery('MangaDetailsById', [
|
|
|
|
'id' => $mangaId,
|
|
|
|
]);
|
2022-03-03 17:26:09 -05:00
|
|
|
|
2020-10-21 17:06:50 -04:00
|
|
|
return $this->mangaTransformer->transform($baseData);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Retrieve the data for the manga read history page
|
|
|
|
*
|
2022-03-03 13:25:10 -05:00
|
|
|
* @return mixed[]
|
2020-10-21 17:06:50 -04:00
|
|
|
*/
|
|
|
|
public function getMangaHistory(): array
|
|
|
|
{
|
|
|
|
$key = K::MANGA_HISTORY_LIST_CACHE_KEY;
|
|
|
|
$list = $this->cache->get($key, NULL);
|
|
|
|
|
|
|
|
if ($list === NULL)
|
|
|
|
{
|
|
|
|
$raw = $this->getHistoryList();
|
|
|
|
$list = (new MangaHistoryTransformer())->transform($raw);
|
|
|
|
|
|
|
|
$this->cache->set($key, $list);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $list;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the manga list for the configured user
|
|
|
|
*
|
|
|
|
* @param string $status - The reading status by which to filter the list
|
2022-03-03 13:25:10 -05:00
|
|
|
* @return mixed[]
|
2020-10-21 17:06:50 -04:00
|
|
|
*/
|
|
|
|
public function getMangaList(string $status): array
|
|
|
|
{
|
|
|
|
$key = "kitsu-manga-list-{$status}";
|
|
|
|
|
|
|
|
$list = $this->cache->get($key, NULL);
|
|
|
|
|
|
|
|
if ($list === NULL)
|
|
|
|
{
|
2020-12-10 15:59:37 -05:00
|
|
|
$data = $this->getList(MediaType::MANGA, $status) ?? [];
|
2020-10-21 17:06:50 -04:00
|
|
|
|
|
|
|
// Bail out on no data
|
|
|
|
if (empty($data))
|
|
|
|
{
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
|
|
|
$transformer = new MangaListTransformer();
|
|
|
|
$transformed = $transformer->transformCollection($data);
|
|
|
|
$keyed = [];
|
|
|
|
|
2022-03-03 17:26:09 -05:00
|
|
|
foreach ($transformed as $item)
|
2020-10-21 17:06:50 -04:00
|
|
|
{
|
|
|
|
$keyed[$item['id']] = $item;
|
|
|
|
}
|
|
|
|
|
|
|
|
$list = $keyed;
|
|
|
|
$this->cache->set($key, $list);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $list;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the number of manga list items
|
|
|
|
*
|
|
|
|
* @param string $status - Optional status to filter by
|
|
|
|
*/
|
2022-03-03 17:26:09 -05:00
|
|
|
public function getMangaListCount(string $status = ''): int
|
2020-10-21 17:06:50 -04:00
|
|
|
{
|
2020-12-10 15:59:37 -05:00
|
|
|
return $this->getListCount(MediaType::MANGA, $status);
|
2020-10-21 17:06:50 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get all Manga lists
|
|
|
|
*
|
2022-03-03 13:25:10 -05:00
|
|
|
* @return array<string, mixed[]>
|
2020-10-21 17:06:50 -04:00
|
|
|
*/
|
|
|
|
public function getFullOrganizedMangaList(): array
|
|
|
|
{
|
|
|
|
$statuses = KitsuReadingStatus::getConstList();
|
|
|
|
$output = [];
|
2022-03-03 17:26:09 -05:00
|
|
|
|
2020-10-21 17:06:50 -04:00
|
|
|
foreach ($statuses as $status)
|
|
|
|
{
|
|
|
|
$mappedStatus = MangaReadingStatus::KITSU_TO_TITLE[$status];
|
|
|
|
$output[$mappedStatus] = $this->getMangaList($status);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $output;
|
|
|
|
}
|
|
|
|
|
|
|
|
// ------------------------------------------------------------------------
|
|
|
|
// Base methods
|
|
|
|
// ------------------------------------------------------------------------
|
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
|
2022-03-03 13:25:10 -05:00
|
|
|
* @return array<int, array<string, mixed>>
|
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
|
|
|
{
|
2020-10-21 15:45:30 -04:00
|
|
|
$uType = ucfirst(strtolower($type));
|
|
|
|
$raw = $this->requestBuilder->runQuery("Search{$uType}", [
|
|
|
|
'query' => $query,
|
|
|
|
]);
|
2016-12-21 12:46:20 -05:00
|
|
|
|
2020-10-21 15:45:30 -04:00
|
|
|
$nodes = $raw['data']["search{$uType}ByTitle"]['nodes'];
|
|
|
|
$data = [];
|
2017-01-09 20:36:48 -05:00
|
|
|
|
2020-10-21 15:45:30 -04:00
|
|
|
foreach ($nodes as $item)
|
2017-01-05 13:41:32 -05:00
|
|
|
{
|
2020-10-21 15:45:30 -04:00
|
|
|
$searchItem = [
|
|
|
|
'id' => $item['id'],
|
|
|
|
'slug' => $item['slug'],
|
2022-01-12 18:23:40 -05:00
|
|
|
'coverImage' => K::getPosterImage($item),
|
2020-10-21 15:45:30 -04:00
|
|
|
'canonicalTitle' => $item['titles']['canonical'],
|
|
|
|
'titles' => array_values(K::getTitles($item['titles'])),
|
2021-10-08 12:06:08 -04:00
|
|
|
'libraryEntry' => $item['myLibraryEntry'],
|
2020-10-21 15:45:30 -04:00
|
|
|
];
|
2018-09-20 16:08:46 -04:00
|
|
|
|
2020-10-21 15:45:30 -04:00
|
|
|
// Search for MAL mapping
|
|
|
|
if (is_array($item['mappings']['nodes']))
|
2018-09-20 16:08:46 -04:00
|
|
|
{
|
2022-03-03 17:26:09 -05:00
|
|
|
foreach ($item['mappings']['nodes'] as $mapping)
|
2018-09-20 16:08:46 -04:00
|
|
|
{
|
2022-03-03 17:26:09 -05:00
|
|
|
if ($mapping['externalSite'] === 'MYANIMELIST_' . strtoupper($type))
|
2020-10-21 15:45:30 -04:00
|
|
|
{
|
|
|
|
$searchItem['mal_id'] = $mapping['externalId'];
|
|
|
|
break;
|
|
|
|
}
|
2018-09-20 16:08:46 -04:00
|
|
|
}
|
|
|
|
}
|
2020-10-21 15:45:30 -04:00
|
|
|
|
|
|
|
$data[] = $searchItem;
|
2016-12-21 12:46:20 -05:00
|
|
|
}
|
|
|
|
|
2020-10-21 15:45:30 -04:00
|
|
|
return $data;
|
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 $type "anime" or "manga"
|
|
|
|
*/
|
2022-03-03 17:26:09 -05:00
|
|
|
public function getKitsuIdFromMALId(string $malId, string $type = 'anime'): ?string
|
2017-03-28 16:52:27 -04:00
|
|
|
{
|
2020-10-21 14:51:17 -04:00
|
|
|
$raw = $this->requestBuilder->runQuery('GetIdByMapping', [
|
|
|
|
'id' => $malId,
|
|
|
|
'site' => strtoupper("MYANIMELIST_{$type}"),
|
|
|
|
]);
|
2017-04-10 15:31:35 -04:00
|
|
|
|
2020-10-21 14:51:17 -04:00
|
|
|
return $raw['data']['lookupMapping']['id'] ?? NULL;
|
2017-03-28 16:52:27 -04:00
|
|
|
}
|
|
|
|
|
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
|
2018-08-08 10:12:45 -04:00
|
|
|
* @return mixed
|
2017-01-26 13:03:38 -05:00
|
|
|
*/
|
2018-08-08 10:12:45 -04:00
|
|
|
public function getListItem(string $listId)
|
2017-01-06 21:39:01 -05:00
|
|
|
{
|
|
|
|
$baseData = $this->listItem->get($listId);
|
2020-08-24 13:07:47 -04:00
|
|
|
if ( ! isset($baseData['data']['findLibraryEntryById']))
|
2017-01-06 21:39:01 -05:00
|
|
|
{
|
2020-08-24 13:07:47 -04:00
|
|
|
return [];
|
2017-01-06 21:39:01 -05:00
|
|
|
}
|
2018-10-19 10:40:11 -04:00
|
|
|
|
2020-08-24 13:07:47 -04:00
|
|
|
return (new LibraryEntryTransformer())->transform($baseData['data']['findLibraryEntryById']);
|
2017-01-06 21:39:01 -05:00
|
|
|
}
|
|
|
|
|
2022-03-03 13:25:10 -05:00
|
|
|
/**
|
|
|
|
* @return mixed[]
|
|
|
|
*/
|
2020-10-21 17:06:50 -04:00
|
|
|
public function getThumbList(string $type): array
|
|
|
|
{
|
|
|
|
$statuses = [
|
|
|
|
'CURRENT',
|
|
|
|
'PLANNED',
|
|
|
|
'ON_HOLD',
|
|
|
|
'DROPPED',
|
|
|
|
'COMPLETED',
|
|
|
|
];
|
|
|
|
|
|
|
|
$pages = [];
|
|
|
|
|
|
|
|
// Although I can fetch the whole list without segregating by status,
|
|
|
|
// this way is much faster...
|
|
|
|
foreach ($statuses as $status)
|
|
|
|
{
|
|
|
|
foreach ($this->getPages([$this, 'getThumbListPages'], strtoupper($type), $status) as $page)
|
|
|
|
{
|
|
|
|
$pages[] = $page;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return array_merge(...$pages);
|
|
|
|
}
|
|
|
|
|
2018-09-20 10:41:28 -04:00
|
|
|
/**
|
2020-07-28 16:11:13 -04:00
|
|
|
* Get the data to sync Kitsu anime/manga list with another API
|
2017-01-26 13:03:38 -05:00
|
|
|
*
|
2022-03-03 13:25:10 -05:00
|
|
|
* @return mixed[]
|
2017-01-26 13:03:38 -05:00
|
|
|
*/
|
2020-05-04 17:13:03 -04:00
|
|
|
public function getSyncList(string $type): array
|
|
|
|
{
|
2020-10-16 16:18:56 -04:00
|
|
|
$statuses = [
|
|
|
|
'CURRENT',
|
|
|
|
'PLANNED',
|
|
|
|
'ON_HOLD',
|
|
|
|
'DROPPED',
|
|
|
|
'COMPLETED',
|
2020-05-04 17:13:03 -04:00
|
|
|
];
|
|
|
|
|
2020-10-16 16:18:56 -04:00
|
|
|
$pages = [];
|
|
|
|
|
|
|
|
// Although I can fetch the whole list without segregating by status,
|
|
|
|
// this way is much faster...
|
|
|
|
foreach ($statuses as $status)
|
|
|
|
{
|
2020-10-21 17:06:50 -04:00
|
|
|
foreach ($this->getPages([$this, 'getSyncPages'], strtoupper($type), $status) as $page)
|
2020-10-16 16:18:56 -04:00
|
|
|
{
|
|
|
|
$pages[] = $page;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return array_merge(...$pages);
|
2020-05-04 17:13:03 -04:00
|
|
|
}
|
|
|
|
|
2020-04-21 19:22:56 -04:00
|
|
|
/**
|
|
|
|
* Get the aggregated pages of anime or manga history
|
|
|
|
*
|
2022-03-03 13:25:10 -05:00
|
|
|
* @return mixed[]
|
2020-04-21 19:22:56 -04:00
|
|
|
*/
|
2020-10-21 17:06:50 -04:00
|
|
|
protected function getHistoryList(): array
|
2020-04-21 19:22:56 -04:00
|
|
|
{
|
2020-08-24 19:17:41 -04:00
|
|
|
return $this->requestBuilder->runQuery('GetUserHistory', [
|
|
|
|
'slug' => $this->getUsername(),
|
2020-04-21 19:22:56 -04:00
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
2020-10-09 16:18:45 -04:00
|
|
|
/**
|
|
|
|
* Get the raw anime/manga list from GraphQL
|
|
|
|
*
|
2022-03-03 13:25:10 -05:00
|
|
|
* @return mixed[]
|
2020-10-09 16:18:45 -04:00
|
|
|
*/
|
2020-10-21 17:06:50 -04:00
|
|
|
protected function getList(string $type, string $status = ''): array
|
2020-10-09 16:18:45 -04:00
|
|
|
{
|
|
|
|
$pages = [];
|
|
|
|
|
2020-10-21 17:06:50 -04:00
|
|
|
foreach ($this->getPages([$this, 'getListPages'], strtoupper($type), strtoupper($status)) as $page)
|
2020-10-09 16:18:45 -04:00
|
|
|
{
|
|
|
|
$pages[] = $page;
|
|
|
|
}
|
|
|
|
|
|
|
|
return array_merge(...$pages);
|
|
|
|
}
|
|
|
|
|
2020-10-21 17:06:50 -04:00
|
|
|
private function getListPages(string $type, string $status = ''): Amp\Iterator
|
2020-10-09 16:18:45 -04:00
|
|
|
{
|
|
|
|
$cursor = '';
|
|
|
|
$username = $this->getUsername();
|
|
|
|
|
2022-03-03 17:26:09 -05:00
|
|
|
return new Amp\Producer(function (callable $emit) use ($type, $status, $cursor, $username): Generator {
|
2020-10-09 16:18:45 -04:00
|
|
|
while (TRUE)
|
|
|
|
{
|
|
|
|
$vars = [
|
|
|
|
'type' => $type,
|
|
|
|
'slug' => $username,
|
|
|
|
];
|
|
|
|
if ($status !== '')
|
|
|
|
{
|
|
|
|
$vars['status'] = $status;
|
|
|
|
}
|
2022-03-03 13:25:10 -05:00
|
|
|
|
2020-10-09 16:18:45 -04:00
|
|
|
if ($cursor !== '')
|
|
|
|
{
|
|
|
|
$vars['after'] = $cursor;
|
|
|
|
}
|
|
|
|
|
|
|
|
$request = $this->requestBuilder->queryRequest('GetLibrary', $vars);
|
|
|
|
$response = yield getApiClient()->request($request);
|
|
|
|
$json = yield $response->getBody()->buffer();
|
|
|
|
|
|
|
|
$rawData = Json::decode($json);
|
|
|
|
$data = $rawData['data']['findProfileBySlug']['library']['all'] ?? [];
|
2022-01-06 12:50:26 -05:00
|
|
|
$page = $data['pageInfo'] ?? [];
|
2020-10-09 16:18:45 -04:00
|
|
|
if (empty($data))
|
|
|
|
{
|
2022-01-06 12:50:26 -05:00
|
|
|
// Clear session, in case the error is an invalid token.
|
|
|
|
$segment = $this->container->get('session')
|
|
|
|
->getSegment(SESSION_SEGMENT);
|
|
|
|
$segment->clear();
|
|
|
|
|
2021-02-16 14:43:51 -05:00
|
|
|
// @TODO Proper Error logging
|
2020-10-16 16:18:56 -04:00
|
|
|
dump($rawData);
|
2022-03-03 17:26:09 -05:00
|
|
|
|
|
|
|
exit();
|
2020-10-09 16:18:45 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
$cursor = $page['endCursor'];
|
|
|
|
|
|
|
|
yield $emit($data['nodes']);
|
|
|
|
|
2022-01-06 12:50:26 -05:00
|
|
|
if ($page['hasNextPage'] !== TRUE)
|
2020-10-09 16:18:45 -04:00
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2022-03-03 17:26:09 -05:00
|
|
|
private function getSyncPages(string $type, string $status): Amp\Iterator
|
|
|
|
{
|
2020-10-21 17:06:50 -04:00
|
|
|
$cursor = '';
|
|
|
|
$username = $this->getUsername();
|
2020-10-16 16:18:56 -04:00
|
|
|
|
2022-03-03 17:26:09 -05:00
|
|
|
return new Amp\Producer(function (callable $emit) use ($type, $status, $cursor, $username): Generator {
|
2020-10-21 17:06:50 -04:00
|
|
|
while (TRUE)
|
|
|
|
{
|
|
|
|
$vars = [
|
|
|
|
'type' => $type,
|
|
|
|
'slug' => $username,
|
|
|
|
'status' => $status,
|
|
|
|
];
|
|
|
|
if ($cursor !== '')
|
|
|
|
{
|
|
|
|
$vars['after'] = $cursor;
|
|
|
|
}
|
|
|
|
|
|
|
|
$request = $this->requestBuilder->queryRequest('GetSyncLibrary', $vars);
|
|
|
|
$response = yield getApiClient()->request($request);
|
|
|
|
$json = yield $response->getBody()->buffer();
|
|
|
|
|
|
|
|
$rawData = Json::decode($json);
|
|
|
|
$data = $rawData['data']['findProfileBySlug']['library']['all'] ?? [];
|
|
|
|
$page = $data['pageInfo'];
|
|
|
|
if (empty($data))
|
|
|
|
{
|
|
|
|
dump($rawData);
|
2022-03-03 17:26:09 -05:00
|
|
|
|
|
|
|
exit();
|
2020-10-21 17:06:50 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
$cursor = $page['endCursor'];
|
|
|
|
|
|
|
|
yield $emit($data['nodes']);
|
|
|
|
|
|
|
|
if ($page['hasNextPage'] === FALSE)
|
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
2020-10-16 16:18:56 -04:00
|
|
|
}
|
|
|
|
|
2020-10-21 17:06:50 -04:00
|
|
|
private function getThumbListPages(string $type, string $status): Amp\Iterator
|
|
|
|
{
|
2020-10-16 16:18:56 -04:00
|
|
|
$cursor = '';
|
|
|
|
$username = $this->getUsername();
|
|
|
|
|
2022-03-03 17:26:09 -05:00
|
|
|
return new Amp\Producer(function (callable $emit) use ($type, $status, $cursor, $username): Generator {
|
2020-10-16 16:18:56 -04:00
|
|
|
while (TRUE)
|
|
|
|
{
|
|
|
|
$vars = [
|
|
|
|
'type' => $type,
|
|
|
|
'slug' => $username,
|
|
|
|
'status' => $status,
|
|
|
|
];
|
|
|
|
if ($cursor !== '')
|
|
|
|
{
|
|
|
|
$vars['after'] = $cursor;
|
|
|
|
}
|
|
|
|
|
2020-10-21 17:06:50 -04:00
|
|
|
$request = $this->requestBuilder->queryRequest('GetLibraryThumbs', $vars);
|
2020-10-16 16:18:56 -04:00
|
|
|
$response = yield getApiClient()->request($request);
|
|
|
|
$json = yield $response->getBody()->buffer();
|
|
|
|
|
|
|
|
$rawData = Json::decode($json);
|
|
|
|
$data = $rawData['data']['findProfileBySlug']['library']['all'] ?? [];
|
|
|
|
$page = $data['pageInfo'];
|
|
|
|
if (empty($data))
|
|
|
|
{
|
|
|
|
dump($rawData);
|
2022-03-03 17:26:09 -05:00
|
|
|
|
|
|
|
exit();
|
2020-10-16 16:18:56 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
$cursor = $page['endCursor'];
|
|
|
|
|
|
|
|
yield $emit($data['nodes']);
|
|
|
|
|
|
|
|
if ($page['hasNextPage'] === FALSE)
|
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-02-16 14:43:51 -05:00
|
|
|
private function getPages(callable $method, mixed ...$args): Generator
|
2020-10-21 17:06:50 -04:00
|
|
|
{
|
|
|
|
$items = $method(...$args);
|
|
|
|
|
|
|
|
while (wait($items->advance()))
|
|
|
|
{
|
|
|
|
yield $items->getCurrent();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-06 13:16:40 -04:00
|
|
|
private function getUserId(): string
|
|
|
|
{
|
|
|
|
static $userId = NULL;
|
|
|
|
|
|
|
|
if ($userId === NULL)
|
|
|
|
{
|
|
|
|
$userId = $this->getUserIdByUsername($this->getUsername());
|
|
|
|
}
|
|
|
|
|
|
|
|
return $userId;
|
|
|
|
}
|
|
|
|
|
2017-03-07 20:49:31 -05:00
|
|
|
/**
|
|
|
|
* Get the kitsu username from config
|
|
|
|
*/
|
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
|
|
|
|
2020-05-04 17:13:03 -04:00
|
|
|
private function getListCount(string $type, string $status = ''): int
|
|
|
|
{
|
2020-08-25 13:22:38 -04:00
|
|
|
$args = [
|
|
|
|
'type' => strtoupper($type),
|
2022-03-03 17:26:09 -05:00
|
|
|
'slug' => $this->getUsername(),
|
2020-05-04 17:13:03 -04:00
|
|
|
];
|
2020-08-25 13:22:38 -04:00
|
|
|
if ($status !== '')
|
2020-05-04 17:13:03 -04:00
|
|
|
{
|
2020-08-25 13:22:38 -04:00
|
|
|
$args['status'] = strtoupper($status);
|
2020-05-04 17:13:03 -04:00
|
|
|
}
|
|
|
|
|
2020-08-25 13:22:38 -04:00
|
|
|
$res = $this->requestBuilder->runQuery('GetLibraryCount', $args);
|
2020-05-04 17:13:03 -04:00
|
|
|
|
2020-08-25 13:22:38 -04:00
|
|
|
return $res['data']['findProfileBySlug']['library']['all']['totalCount'];
|
2020-05-04 17:13:03 -04:00
|
|
|
}
|
2022-03-03 17:26:09 -05:00
|
|
|
}
|