2017-01-05 13:41:32 -05:00
|
|
|
<?php declare(strict_types=1);
|
|
|
|
/**
|
2017-02-15 16:13:32 -05:00
|
|
|
* Hummingbird Anime List Client
|
2017-01-05 13:41:32 -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
|
|
|
|
* @link https://github.com/timw4mail/HummingBirdAnimeClient
|
2017-01-11 10:34:24 -05:00
|
|
|
*/
|
|
|
|
|
|
|
|
namespace Aviat\AnimeClient\API;
|
2017-01-05 13:41:32 -05:00
|
|
|
|
2017-01-09 20:36:48 -05:00
|
|
|
use Aviat\AnimeClient\API\Kitsu\Enum\{
|
|
|
|
AnimeAiringStatus,
|
|
|
|
AnimeWatchingStatus,
|
|
|
|
MangaReadingStatus
|
|
|
|
};
|
2017-01-05 22:24:45 -05:00
|
|
|
use DateTimeImmutable;
|
2017-01-05 13:41:32 -05:00
|
|
|
|
2017-02-08 15:48:20 -05:00
|
|
|
const AUTH_URL = 'https://kitsu.io/api/oauth/token';
|
|
|
|
const AUTH_USER_ID_KEY = 'kitsu-auth-userid';
|
|
|
|
const AUTH_TOKEN_CACHE_KEY = 'kitsu-auth-token';
|
|
|
|
|
2017-01-05 13:41:32 -05:00
|
|
|
/**
|
2017-01-13 16:53:56 -05:00
|
|
|
* Data massaging helpers for the Kitsu API
|
2017-01-05 13:41:32 -05:00
|
|
|
*/
|
|
|
|
class Kitsu {
|
2017-01-05 22:24:45 -05:00
|
|
|
const AUTH_URL = 'https://kitsu.io/api/oauth/token';
|
2017-01-27 12:35:28 -05:00
|
|
|
const AUTH_USER_ID_KEY = 'kitsu-auth-userid';
|
|
|
|
const AUTH_TOKEN_CACHE_KEY = 'kitsu-auth-token';
|
2017-01-05 13:41:32 -05:00
|
|
|
|
2017-01-05 22:24:45 -05:00
|
|
|
/**
|
|
|
|
* Map of Kitsu status to label for select menus
|
|
|
|
*
|
|
|
|
* @return array
|
|
|
|
*/
|
2017-01-05 13:41:32 -05:00
|
|
|
public static function getStatusToSelectMap()
|
|
|
|
{
|
|
|
|
return [
|
|
|
|
AnimeWatchingStatus::WATCHING => 'Currently Watching',
|
|
|
|
AnimeWatchingStatus::PLAN_TO_WATCH => 'Plan to Watch',
|
|
|
|
AnimeWatchingStatus::COMPLETED => 'Completed',
|
|
|
|
AnimeWatchingStatus::ON_HOLD => 'On Hold',
|
|
|
|
AnimeWatchingStatus::DROPPED => 'Dropped'
|
|
|
|
];
|
|
|
|
}
|
2017-01-05 22:24:45 -05:00
|
|
|
|
2017-01-13 16:53:56 -05:00
|
|
|
/**
|
|
|
|
* Map of Kitsu Manga status to label for select menus
|
|
|
|
*
|
|
|
|
* @return array
|
|
|
|
*/
|
2017-01-09 20:36:48 -05:00
|
|
|
public static function getStatusToMangaSelectMap()
|
|
|
|
{
|
|
|
|
return [
|
|
|
|
MangaReadingStatus::READING => 'Currently Reading',
|
|
|
|
MangaReadingStatus::PLAN_TO_READ => 'Plan to Read',
|
|
|
|
MangaReadingStatus::COMPLETED => 'Completed',
|
|
|
|
MangaReadingStatus::ON_HOLD => 'On Hold',
|
|
|
|
MangaReadingStatus::DROPPED => 'Dropped'
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
2017-01-05 22:24:45 -05:00
|
|
|
/**
|
|
|
|
* Determine whether an anime is airing, finished airing, or has not yet aired
|
|
|
|
*
|
|
|
|
* @param string $startDate
|
|
|
|
* @param string $endDate
|
|
|
|
* @return string
|
|
|
|
*/
|
2017-02-17 10:55:17 -05:00
|
|
|
public static function getAiringStatus(string $startDate = NULL, string $endDate = NULL): string
|
2017-01-05 22:24:45 -05:00
|
|
|
{
|
|
|
|
$startAirDate = new DateTimeImmutable($startDate ?? 'tomorrow');
|
2017-01-27 15:41:52 -05:00
|
|
|
$endAirDate = new DateTimeImmutable($endDate ?? 'next year');
|
2017-01-05 22:24:45 -05:00
|
|
|
$now = new DateTimeImmutable();
|
|
|
|
|
|
|
|
$isDoneAiring = $now > $endAirDate;
|
|
|
|
$isCurrentlyAiring = ($now > $startAirDate) && ! $isDoneAiring;
|
|
|
|
|
2017-02-17 10:55:17 -05:00
|
|
|
switch (TRUE)
|
2017-01-05 22:24:45 -05:00
|
|
|
{
|
|
|
|
case $isCurrentlyAiring:
|
|
|
|
return AnimeAiringStatus::AIRING;
|
|
|
|
|
|
|
|
case $isDoneAiring:
|
|
|
|
return AnimeAiringStatus::FINISHED_AIRING;
|
|
|
|
|
|
|
|
default:
|
|
|
|
return AnimeAiringStatus::NOT_YET_AIRED;
|
|
|
|
}
|
|
|
|
}
|
2017-02-08 15:48:20 -05:00
|
|
|
|
2017-01-05 22:24:45 -05:00
|
|
|
/**
|
2017-01-13 16:53:56 -05:00
|
|
|
* Get the name and logo for the streaming service of the current link
|
2017-01-05 22:24:45 -05:00
|
|
|
*
|
2017-01-13 16:53:56 -05:00
|
|
|
* @param string $hostname
|
|
|
|
* @return array
|
2017-01-05 22:24:45 -05:00
|
|
|
*/
|
2017-02-17 10:55:17 -05:00
|
|
|
protected static function getServiceMetaData(string $hostname = NULL): array
|
2017-01-05 22:24:45 -05:00
|
|
|
{
|
2017-01-13 16:53:56 -05:00
|
|
|
switch($hostname)
|
2017-01-05 22:24:45 -05:00
|
|
|
{
|
2017-01-13 16:53:56 -05:00
|
|
|
case 'www.crunchyroll.com':
|
|
|
|
return [
|
|
|
|
'name' => 'Crunchyroll',
|
2017-02-17 10:55:17 -05:00
|
|
|
'link' => TRUE,
|
2017-02-16 14:30:06 -05:00
|
|
|
'image' => 'streaming-logos/crunchyroll.svg',
|
2017-01-13 16:53:56 -05:00
|
|
|
];
|
2017-02-08 15:48:20 -05:00
|
|
|
|
2017-01-13 16:53:56 -05:00
|
|
|
case 'www.funimation.com':
|
|
|
|
return [
|
|
|
|
'name' => 'Funimation',
|
2017-02-17 10:55:17 -05:00
|
|
|
'link' => TRUE,
|
2017-02-16 14:30:06 -05:00
|
|
|
'image' => 'streaming-logos/funimation.svg',
|
2017-01-13 16:53:56 -05:00
|
|
|
];
|
2017-02-08 15:48:20 -05:00
|
|
|
|
2017-01-13 16:53:56 -05:00
|
|
|
case 'www.hulu.com':
|
|
|
|
return [
|
|
|
|
'name' => 'Hulu',
|
2017-02-17 10:55:17 -05:00
|
|
|
'link' => TRUE,
|
2017-02-16 14:30:06 -05:00
|
|
|
'image' => 'streaming-logos/hulu.svg',
|
2017-01-13 16:53:56 -05:00
|
|
|
];
|
2017-02-08 15:48:20 -05:00
|
|
|
|
|
|
|
// Default to Netflix, because the API links are broken,
|
2017-01-13 16:53:56 -05:00
|
|
|
// and there's no other real identifier for Netflix
|
|
|
|
default:
|
|
|
|
return [
|
|
|
|
'name' => 'Netflix',
|
2017-02-17 10:55:17 -05:00
|
|
|
'link' => FALSE,
|
2017-02-16 14:30:06 -05:00
|
|
|
'image' => 'streaming-logos/netflix.svg',
|
2017-01-13 16:53:56 -05:00
|
|
|
];
|
2017-01-05 22:24:45 -05:00
|
|
|
}
|
|
|
|
}
|
2017-02-08 15:48:20 -05:00
|
|
|
|
2017-01-05 22:24:45 -05:00
|
|
|
/**
|
2017-01-13 16:53:56 -05:00
|
|
|
* Reorganize streaming links
|
2017-01-05 22:24:45 -05:00
|
|
|
*
|
2017-01-13 16:53:56 -05:00
|
|
|
* @param array $included
|
2017-01-05 22:24:45 -05:00
|
|
|
* @return array
|
|
|
|
*/
|
2017-01-13 16:53:56 -05:00
|
|
|
public static function parseStreamingLinks(array $included): array
|
2017-01-05 22:24:45 -05:00
|
|
|
{
|
2017-01-13 16:53:56 -05:00
|
|
|
if ( ! array_key_exists('streamingLinks', $included))
|
2017-01-05 22:24:45 -05:00
|
|
|
{
|
2017-01-13 16:53:56 -05:00
|
|
|
return [];
|
2017-01-05 22:24:45 -05:00
|
|
|
}
|
2017-02-08 15:48:20 -05:00
|
|
|
|
2017-01-13 16:53:56 -05:00
|
|
|
$links = [];
|
2017-02-08 15:48:20 -05:00
|
|
|
|
2017-01-13 16:53:56 -05:00
|
|
|
foreach ($included['streamingLinks'] as $streamingLink)
|
|
|
|
{
|
|
|
|
$host = parse_url($streamingLink['url'], \PHP_URL_HOST);
|
2017-02-08 15:48:20 -05:00
|
|
|
|
2017-01-13 16:53:56 -05:00
|
|
|
$links[] = [
|
|
|
|
'meta' => static::getServiceMetaData($host),
|
|
|
|
'link' => $streamingLink['url'],
|
|
|
|
'subs' => $streamingLink['subs'],
|
|
|
|
'dubs' => $streamingLink['dubs']
|
|
|
|
];
|
|
|
|
}
|
2017-02-08 15:48:20 -05:00
|
|
|
|
2017-01-13 16:53:56 -05:00
|
|
|
return $links;
|
2017-01-05 22:24:45 -05:00
|
|
|
}
|
2017-02-08 15:48:20 -05:00
|
|
|
|
2017-01-25 13:37:39 -05:00
|
|
|
/**
|
|
|
|
* Reorganize streaming links for the current list item
|
|
|
|
*
|
|
|
|
* @param array $included
|
2017-02-17 10:55:17 -05:00
|
|
|
* @param string $animeId
|
2017-01-25 13:37:39 -05:00
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public static function parseListItemStreamingLinks(array $included, string $animeId): array
|
|
|
|
{
|
|
|
|
// Anime lists have a different structure to search through
|
|
|
|
if (array_key_exists('anime', $included) && ! array_key_exists('streamingLinks', $included))
|
|
|
|
{
|
|
|
|
$links = [];
|
|
|
|
$anime = $included['anime'][$animeId];
|
|
|
|
|
|
|
|
if (count($anime['relationships']['streamingLinks']) > 0)
|
|
|
|
{
|
|
|
|
foreach ($anime['relationships']['streamingLinks'] as $streamingLink)
|
|
|
|
{
|
|
|
|
$host = parse_url($streamingLink['url'], \PHP_URL_HOST);
|
|
|
|
|
|
|
|
$links[] = [
|
|
|
|
'meta' => static::getServiceMetaData($host),
|
|
|
|
'link' => $streamingLink['url'],
|
|
|
|
'subs' => $streamingLink['subs'],
|
|
|
|
'dubs' => $streamingLink['dubs']
|
|
|
|
];
|
|
|
|
}
|
|
|
|
}
|
2017-02-08 15:48:20 -05:00
|
|
|
|
2017-01-25 13:37:39 -05:00
|
|
|
return $links;
|
|
|
|
}
|
2017-02-08 15:48:20 -05:00
|
|
|
|
2017-01-27 15:41:52 -05:00
|
|
|
return [];
|
2017-01-25 13:37:39 -05:00
|
|
|
}
|
2017-01-05 22:24:45 -05:00
|
|
|
|
|
|
|
/**
|
2017-01-13 16:53:56 -05:00
|
|
|
* Filter out duplicate and very similar names from
|
2017-01-05 22:24:45 -05:00
|
|
|
*
|
2017-01-13 16:53:56 -05:00
|
|
|
* @param array $data The 'attributes' section of the api data response
|
|
|
|
* @return array List of alternate titles
|
2017-01-05 22:24:45 -05:00
|
|
|
*/
|
2017-01-13 16:53:56 -05:00
|
|
|
public static function filterTitles(array $data): array
|
2017-01-05 22:24:45 -05:00
|
|
|
{
|
2017-01-13 16:53:56 -05:00
|
|
|
// The 'canonical' title is always returned
|
|
|
|
$valid = [$data['canonicalTitle']];
|
2017-01-05 22:24:45 -05:00
|
|
|
|
2017-01-13 16:53:56 -05:00
|
|
|
if (array_key_exists('titles', $data))
|
2017-01-05 22:24:45 -05:00
|
|
|
{
|
2017-01-13 16:53:56 -05:00
|
|
|
foreach($data['titles'] as $alternateTitle)
|
2017-01-05 22:24:45 -05:00
|
|
|
{
|
2017-01-13 16:53:56 -05:00
|
|
|
if (self::titleIsUnique($alternateTitle, $valid))
|
|
|
|
{
|
|
|
|
$valid[] = $alternateTitle;
|
|
|
|
}
|
2017-01-05 22:24:45 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-13 16:53:56 -05:00
|
|
|
return $valid;
|
2017-01-05 22:24:45 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Determine if an alternate title is unique enough to list
|
|
|
|
*
|
|
|
|
* @param string $title
|
|
|
|
* @param array $existingTitles
|
|
|
|
* @return bool
|
|
|
|
*/
|
2017-02-17 10:55:17 -05:00
|
|
|
private static function titleIsUnique(string $title = NULL, array $existingTitles = []): bool
|
2017-01-05 22:24:45 -05:00
|
|
|
{
|
|
|
|
if (empty($title))
|
|
|
|
{
|
2017-02-17 10:55:17 -05:00
|
|
|
return FALSE;
|
2017-01-05 22:24:45 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
foreach($existingTitles as $existing)
|
|
|
|
{
|
|
|
|
$isSubset = stripos($existing, $title) !== FALSE;
|
|
|
|
$diff = levenshtein($existing, $title);
|
|
|
|
$onlydifferentCase = (mb_strtolower($existing) === mb_strtolower($title));
|
|
|
|
|
2017-02-17 10:55:17 -05:00
|
|
|
if ($diff < 3 OR $isSubset OR $onlydifferentCase)
|
2017-01-05 22:24:45 -05:00
|
|
|
{
|
2017-02-17 10:55:17 -05:00
|
|
|
return FALSE;
|
2017-01-05 22:24:45 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-17 10:55:17 -05:00
|
|
|
return TRUE;
|
2017-01-05 22:24:45 -05:00
|
|
|
}
|
2017-01-05 13:41:32 -05:00
|
|
|
}
|