Version 5.1 - All the GraphQL #32

Closed
timw4mail wants to merge 1160 commits from develop into master
11 changed files with 222 additions and 63 deletions
Showing only changes of commit cd2dcf2873 - Show all commits

View File

@ -17,6 +17,10 @@ try
(new Console([ (new Console([
'cache:clear' => Command\CacheClear::class, 'cache:clear' => Command\CacheClear::class,
'cache:refresh' => Command\CachePrime::class, 'cache:refresh' => Command\CachePrime::class,
'clear:cache' => Command\CacheClear::class,
'clear:thumbnails' => Command\ClearThumbnails::class,
'refresh:cache' => Command\CachePrime::class,
'refresh:thumbnails' => Command\UpdateThumbnails::class,
'regenerate-thumbnails' => Command\UpdateThumbnails::class, 'regenerate-thumbnails' => Command\UpdateThumbnails::class,
'lists:sync' => Command\SyncLists::class, 'lists:sync' => Command\SyncLists::class,
'mal_id:check' => Command\MALIDCheck::class, 'mal_id:check' => Command\MALIDCheck::class,

View File

@ -141,6 +141,8 @@ final class JsonAPI {
$relationship[$dataType][$idKey][$j] = $included[$dataType][$idKey]; $relationship[$dataType][$idKey][$j] = $included[$dataType][$idKey];
} }
unset($item['relationships'][$relType]['data']);
} }
} }
} }
@ -221,6 +223,11 @@ final class JsonAPI {
continue; continue;
} }
if ( ! array_key_exists($dataType, $organized))
{
$organized[$dataType] = [];
}
if (array_key_exists($idKey, $organized[$dataType])) if (array_key_exists($idKey, $organized[$dataType]))
{ {
$relationship[$dataType][$idKey] = $organized[$dataType][$idKey]; $relationship[$dataType][$idKey] = $organized[$dataType][$idKey];
@ -298,7 +305,10 @@ final class JsonAPI {
$type = $item['type']; $type = $item['type'];
$id = $item['id']; $id = $item['id'];
if (array_key_exists('attributes', $item))
{
$organized[$type][$id] = $item['attributes']; $organized[$type][$id] = $item['attributes'];
}
if (array_key_exists('relationships', $item)) if (array_key_exists('relationships', $item))
{ {

View File

@ -235,15 +235,20 @@ final class Model {
{ {
$data = $this->getRequest("people/{$id}", [ $data = $this->getRequest("people/{$id}", [
'query' => [ 'query' => [
'filter' => [
'id' => $id,
],
'fields' => [ 'fields' => [
'characters' => 'canonicalName,slug,image', 'characters' => 'canonicalName,slug,image',
'characterVoices' => 'mediaCharacter',
'anime' => 'canonicalTitle,titles,slug,posterImage', 'anime' => 'canonicalTitle,titles,slug,posterImage',
'manga' => 'canonicalTitle,titles,slug,posterImage', 'manga' => 'canonicalTitle,titles,slug,posterImage',
'mediaCharacters' => 'role,media,character',
'mediaStaff' => 'role,media,person',
], ],
'include' => 'castings.character,castings.media' 'include' => 'voices.mediaCharacter.media,voices.mediaCharacter.character,staff.media',
], ],
]); ]);
$cacheItem->set($data); $cacheItem->set($data);
$cacheItem->save(); $cacheItem->save();
} }
@ -268,7 +273,7 @@ final class Model {
'fields' => [ 'fields' => [
'anime' => 'slug,canonicalTitle,posterImage', 'anime' => 'slug,canonicalTitle,posterImage',
'manga' => 'slug,canonicalTitle,posterImage', 'manga' => 'slug,canonicalTitle,posterImage',
'characters' => 'slug,canonicalName,image' 'characters' => 'slug,canonicalName,image',
], ],
'include' => 'waifu,favorites.item,stats' 'include' => 'waifu,favorites.item,stats'
] ]
@ -364,13 +369,13 @@ final class Model {
* @param string $slug * @param string $slug
* @return Anime * @return Anime
*/ */
public function getAnime(string $slug): Anime public function getAnime(string $slug)
{ {
$baseData = $this->getRawMediaData('anime', $slug); $baseData = $this->getRawMediaData('anime', $slug);
if (empty($baseData)) if (empty($baseData))
{ {
return new Anime(); return (new Anime([]))->toArray();
} }
return $this->animeTransformer->transform($baseData); return $this->animeTransformer->transform($baseData);
@ -966,7 +971,7 @@ final class Model {
'mediaCharacters' => 'character,role', 'mediaCharacters' => 'character,role',
], ],
'include' => ($type === 'anime') 'include' => ($type === 'anime')
? 'staff,staff.person,categories,mappings,streamingLinks,animeCharacters.character' ? 'staff,staff.person,categories,mappings,streamingLinks,animeCharacters.character,characters.character'
: 'staff,staff.person,categories,mappings,characters.character', : 'staff,staff.person,categories,mappings,characters.character',
] ]
]; ];

View File

@ -40,7 +40,9 @@ final class AnimeTransformer extends AbstractTransformer {
sort($item['genres']); sort($item['genres']);
$title = $item['canonicalTitle']; $title = $item['canonicalTitle'];
$titles = array_unique(array_diff($item['titles'], [$title]));
$titles = Kitsu::filterTitles($item);
// $titles = array_unique(array_diff($item['titles'], [$title]));
return new Anime([ return new Anime([
'age_rating' => $item['ageRating'], 'age_rating' => $item['ageRating'],

View File

@ -71,7 +71,14 @@ function loadTomlFile(string $filename): array
return Toml::parseFile($filename); return Toml::parseFile($filename);
} }
function _iterateToml(TomlBuilder $builder, $data, $parentKey = NULL): void /**
* Recursively create a toml file from a data array
*
* @param TomlBuilder $builder
* @param iterable $data
* @param null $parentKey
*/
function _iterateToml(TomlBuilder $builder, iterable $data, $parentKey = NULL): void
{ {
foreach ($data as $key => $value) foreach ($data as $key => $value)
{ {
@ -107,7 +114,7 @@ function _iterateToml(TomlBuilder $builder, $data, $parentKey = NULL): void
* @param mixed $data * @param mixed $data
* @return string * @return string
*/ */
function arrayToToml($data): string function arrayToToml(iterable $data): string
{ {
$builder = new TomlBuilder(); $builder = new TomlBuilder();
@ -197,28 +204,31 @@ function checkFolderPermissions(ConfigInterface $config): array
} }
/** /**
* Generate the path for the cached image from the original iamge * Generate the path for the cached image from the original image
* *
* @param string $kitsuUrl * @param string $kitsuUrl
* @param bool $webp
* @return string * @return string
*/ */
function getLocalImg ($kitsuUrl): string function getLocalImg ($kitsuUrl, $webp = TRUE): string
{ {
if ( ! is_string($kitsuUrl)) if ( ! is_string($kitsuUrl))
{ {
return 'images/404/404.png'; return 'images/placeholder.webp';
} }
$parts = parse_url($kitsuUrl); $parts = parse_url($kitsuUrl);
if ($parts === FALSE) if ($parts === FALSE)
{ {
return 'images/404/404.png'; return 'images/placeholder.webp';
} }
$file = basename($parts['path']); $file = basename($parts['path']);
$fileParts = explode('.', $file); $fileParts = explode('.', $file);
$ext = array_pop($fileParts); $ext = array_pop($fileParts);
$ext = $webp ? 'webp' : $ext;
$segments = explode('/', trim($parts['path'], '/')); $segments = explode('/', trim($parts['path'], '/'));
$type = $segments[0] === 'users' ? $segments[1] : $segments[0]; $type = $segments[0] === 'users' ? $segments[1] : $segments[0];
@ -241,12 +251,15 @@ function createPlaceholderImage ($path, $width, $height, $text = 'Image Unavaila
$width = $width ?? 200; $width = $width ?? 200;
$height = $height ?? 200; $height = $height ?? 200;
$img = imagecreate($width, $height); $img = imagecreatetruecolor($width, $height);
imagealphablending($img, TRUE);
$path = rtrim($path, '/'); $path = rtrim($path, '/');
// Background is the first color by default // Background is the first color by default
imagecolorallocatealpha($img, 255, 255, 255, 127); $fillColor = imagecolorallocatealpha($img, 255, 255, 255, 127);
imagefill($img, 0, 0, $fillColor);
$textColor = imagecolorallocate($img, 64, 64, 64); $textColor = imagecolorallocate($img, 64, 64, 64);
imagealphablending($img, TRUE); imagealphablending($img, TRUE);
@ -266,6 +279,13 @@ function createPlaceholderImage ($path, $width, $height, $text = 'Image Unavaila
// Save the images // Save the images
imagesavealpha($img, TRUE); imagesavealpha($img, TRUE);
imagepng($img, $path . '/placeholder.png', 9); imagepng($img, $path . '/placeholder.png', 9);
imagedestroy($img); imagedestroy($img);
$pngImage = imagecreatefrompng($path . '/placeholder.png');
imagealphablending($pngImage, TRUE);
imagesavealpha($pngImage, TRUE);
imagewebp($pngImage, $path . '/placeholder.webp');
imagedestroy($pngImage);
} }

View File

@ -0,0 +1,59 @@
<?php declare(strict_types=1);
/**
* Hummingbird Anime List Client
*
* An API client for Kitsu to manage anime and manga watch lists
*
* PHP version 7.1
*
* @package HummingbirdAnimeClient
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2018 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 4.1
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
*/
namespace Aviat\AnimeClient\Command;
/**
* Clears out image cache directories
*/
class ClearThumbnails extends BaseCommand {
public function execute(array $args, array $options = []): void
{
$this->clearThumbs();
$this->echoBox('All cached images have been removed');
}
public function clearThumbs()
{
$imgDir = realpath(__DIR__ . '/../../public/images');
$paths = [
'avatars/*.gif',
'avatars/*.jpg',
'avatars/*.png',
'avatars/*.webp',
'anime/*.jpg',
'anime/*.png',
'anime/*.webp',
'manga/*.jpg',
'manga/*.png',
'manga/*.webp',
'characters/*.jpg',
'characters/*.png',
'characters/*.webp',
'people/*.jpg',
'people/*.png',
'people/*.webp',
];
foreach($paths as $path)
{
$cmd = "rm -rf {$imgDir}/{$path}";
exec($cmd);
}
}
}

View File

@ -23,7 +23,7 @@ use Aviat\AnimeClient\Controller\Index;
* Clears out image cache directories, then re-creates the image cache * Clears out image cache directories, then re-creates the image cache
* for manga and anime * for manga and anime
*/ */
final class UpdateThumbnails extends BaseCommand { final class UpdateThumbnails extends ClearThumbnails {
/** /**
* Model for making requests to Kitsu API * Model for making requests to Kitsu API
* @var \Aviat\AnimeClient\API\Kitsu\Model * @var \Aviat\AnimeClient\API\Kitsu\Model
@ -43,13 +43,11 @@ final class UpdateThumbnails extends BaseCommand {
$this->controller = new Index($this->container); $this->controller = new Index($this->container);
$this->kitsuModel = $this->container->get('kitsu-model'); $this->kitsuModel = $this->container->get('kitsu-model');
$this->clearThumbs(); // Clear the existing thunbnails
parent::execute($args, $options);
$ids = $this->getImageList(); $ids = $this->getImageList();
// print_r($ids);
// echo json_encode($ids, \JSON_PRETTY_PRINT);
// Resave the images // Resave the images
foreach($ids as $type => $typeIds) foreach($ids as $type => $typeIds)
{ {
@ -64,26 +62,6 @@ final class UpdateThumbnails extends BaseCommand {
$this->echoBox('Finished regenerating all thumbnails'); $this->echoBox('Finished regenerating all thumbnails');
} }
public function clearThumbs()
{
$imgDir = realpath(__DIR__ . '/../../public/images');
$paths = [
'anime/*.jpg',
'anime/*.webp',
'manga/*.jpg',
'manga/*.webp',
'characters/*.jpg',
'characters/*.webp',
];
foreach($paths as $path)
{
$cmd = "rm -rf {$imgDir}/{$path}";
exec($cmd);
}
}
public function getImageList() public function getImageList()
{ {
$mangaList = $this->kitsuModel->getFullRawMangaList(); $mangaList = $this->kitsuModel->getFullRawMangaList();

View File

@ -363,9 +363,15 @@ final class Dispatcher extends RoutingBase {
? $controllerMap[$routeType] ? $controllerMap[$routeType]
: DEFAULT_CONTROLLER; : DEFAULT_CONTROLLER;
if (array_key_exists($routeType, $controllerMap)) // If there's an explicit controller, try to find
// the full namespaced class name
if (array_key_exists('controller', $route))
{ {
$controllerClass = $controllerMap[$routeType]; $controllerKey = $route['controller'];
if (array_key_exists($controllerKey, $controllerMap))
{
$controllerClass = $controllerMap[$controllerKey];
}
} }
// Prepend the controller to the route parameters // Prepend the controller to the route parameters

85
src/Helper/Picture.php Normal file
View File

@ -0,0 +1,85 @@
<?php declare(strict_types=1);
/**
* Hummingbird Anime List Client
*
* An API client for Kitsu to manage anime and manga watch lists
*
* PHP version 7.1
*
* @package HummingbirdAnimeClient
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2018 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 4.1
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
*/
namespace Aviat\AnimeClient\Helper;
use Aviat\Ion\Di\ContainerAware;
/**
* Simplify picture elements
*/
final class Picture {
use ContainerAware;
/**
* Create the html f
*
* @param string $webp
* @param string $fallbackExt
* @param array $picAttrs
* @param array $imgAttrs
* @return string
*/
public function __invoke(string $webp, string $fallbackExt = 'jpg', $picAttrs = [], $imgAttrs = []): string
{
$urlGenerator = $this->container->get('url-generator');
$helper = $this->container->get('html-helper');
// If it is a placeholder image, make the
// fallback a png, not a jpg
if (strpos($webp, 'placeholder') !== FALSE)
{
$fallbackExt = 'png';
}
if (strpos($webp, '//') === FALSE)
{
$webp = $urlGenerator->assetUrl($webp);
}
$urlParts = explode('.', $webp);
$ext = array_pop($urlParts);
$path = implode('.', $urlParts);
$mime = $ext === 'jpg'
? 'image/jpeg'
: "image/{$ext}";
$fallbackMime = $fallbackExt === 'jpg'
? 'image/jpeg'
: "image/{$fallbackExt}";
$fallbackImg = "{$path}.{$fallbackExt}";
$pictureChildren = [
$helper->void('source', [
'srcset' => $webp,
'type' => $mime,
]),
$helper->void('source', [
'srcset' => $fallbackImg,
'type' => $fallbackMime
]),
$helper->img($fallbackImg, array_merge(['alt' => ''], $imgAttrs)),
];
$sources = implode('', $pictureChildren);
return $helper->elementRaw('picture', $sources, $picAttrs);
}
}
// End of Picture.php

View File

@ -108,7 +108,7 @@ class Anime extends API {
* @param string $slug * @param string $slug
* @return AnimeType * @return AnimeType
*/ */
public function getAnime(string $slug): AnimeType public function getAnime(string $slug)
{ {
return $this->kitsuModel->getAnime($slug); return $this->kitsuModel->getAnime($slug);
} }
@ -173,14 +173,6 @@ class Anime extends API {
$results = $requester->makeRequests(); $results = $requester->makeRequests();
// Debug info
/* $body = Json::decode($results['anilist']);
if ($body['errors'])
{
dump($body);
die();
} */
return count($results) > 0; return count($results) > 0;
} }
@ -261,13 +253,6 @@ class Anime extends API {
$results = $requester->makeRequests(); $results = $requester->makeRequests();
// Debug info
/* $body = Json::decode($results['anilist']);
if (isset($body['errors'])) {
dump($body);
die();
} */
return count($results) > 0; return count($results) > 0;
} }
} }

View File

@ -16,7 +16,7 @@
namespace Aviat\AnimeClient; namespace Aviat\AnimeClient;
const DEFAULT_CONTROLLER = Controller\Index::class; const DEFAULT_CONTROLLER = Controller\Misc::class;
const DEFAULT_CONTROLLER_METHOD = 'index'; const DEFAULT_CONTROLLER_METHOD = 'index';
const DEFAULT_CONTROLLER_NAMESPACE = Controller::class; const DEFAULT_CONTROLLER_NAMESPACE = Controller::class;
const DEFAULT_LIST_CONTROLLER = Controller\Anime::class; const DEFAULT_LIST_CONTROLLER = Controller\Anime::class;
@ -24,7 +24,12 @@ const ERROR_MESSAGE_METHOD = 'errorPage';
const NOT_FOUND_METHOD = 'notFound'; const NOT_FOUND_METHOD = 'notFound';
const SESSION_SEGMENT = 'Aviat\AnimeClient\Auth'; const SESSION_SEGMENT = 'Aviat\AnimeClient\Auth';
const SRC_DIR = __DIR__; const SRC_DIR = __DIR__;
const USER_AGENT = "Tim's Anime Client/4.0"; const USER_AGENT = "Tim's Anime Client/4.1";
// Regex patterns
const ALPHA_SLUG_PATTERN = '[a-z_]+';
const NUM_PATTERN = '[0-9]+';
const SLUG_PATTERN = '[a-z0-9\-]+';
// Why doesn't this already exist? // Why doesn't this already exist?
const MILLI_FROM_NANO = 1000 * 1000; const MILLI_FROM_NANO = 1000 * 1000;