Merge remote-tracking branch 'origin/develop'
timw4mail/HummingBirdAnimeClient/pipeline/head This commit looks good Details

This commit is contained in:
Timothy Warren 2023-03-17 09:17:23 -04:00
commit 5076657b38
267 changed files with 11161 additions and 8999 deletions

6
.gitignore vendored
View File

@ -150,4 +150,8 @@ public/mal_mappings.json
.is-dev .is-dev
tmp tmp
tools/vendor/
tools/phinx/vendor/
/.php-cs-fixer.php
/.php-cs-fixer.cache

528
.php-cs-fixer.dist.php Normal file
View File

@ -0,0 +1,528 @@
<?php declare(strict_types=1);
use Nexus\CsConfig\Factory;
use PhpCsFixer\{Config, Finder};
$finder = Finder::create()
->in([
__DIR__,
__DIR__ . '/app',
__DIR__ . '/tools',
])
->exclude([
'apidocs',
'build',
'coverage',
'frontEndSrc',
'phinx',
'public',
'tools',
'tmp',
'vendor',
'views',
'templates',
]);
return (new Config())
->setRiskyAllowed(TRUE)
->setFinder($finder)
->setIndent(' ')
->setRules([
'align_multiline_comment' => false,
'array_indentation' => true,
'array_push' => true,
'array_syntax' => ['syntax' => 'short'],
'assign_null_coalescing_to_coalesce_equal' => true,
'backtick_to_shell_exec' => true,
'binary_operator_spaces' => [
'default' => 'single_space',
'operators' => [
'=' => NULL,
'&' => NULL,
]
],
'blank_line_after_namespace' => true,
'blank_line_after_opening_tag' => false,
'blank_line_before_statement' => [
'statements' => [
'case',
'continue',
'declare',
'default',
'do',
'exit',
'for',
'foreach',
'goto',
'return',
'switch',
'throw',
'try',
'while',
'yield',
'yield_from',
],
],
'braces' => [
'allow_single_line_anonymous_class_with_empty_body' => true,
'allow_single_line_closure' => true,
'position_after_anonymous_constructs' => 'same',
'position_after_control_structures' => 'next',
'position_after_functions_and_oop_constructs' => 'next',
],
'cast_spaces' => ['space' => 'single'],
'class_attributes_separation' => [
'elements' => [
'const' => 'none',
'property' => 'none',
'method' => 'one',
'trait_import' => 'none',
],
],
'class_definition' => [
'multi_line_extends_each_single_line' => true,
'single_item_single_line' => true,
'single_line' => true,
'space_before_parenthesis' => true,
],
'class_reference_name_casing' => true,
'clean_namespace' => true,
'combine_consecutive_issets' => true,
'combine_consecutive_unsets' => true,
'combine_nested_dirname' => true,
'comment_to_phpdoc' => [
'ignored_tags' => [
'todo',
'codeCoverageIgnore',
'codeCoverageIgnoreStart',
'codeCoverageIgnoreEnd',
'phpstan-ignore-line',
'phpstan-ignore-next-line',
],
],
'compact_nullable_typehint' => true,
'concat_space' => ['spacing' => 'one'],
'constant_case' => ['case' => 'upper'],
'control_structure_continuation_position' => ['position' => 'next_line'],
'date_time_immutable' => false,
'declare_equal_normalize' => ['space' => 'none'],
'declare_parentheses' => true,
'declare_strict_types' => true,
'dir_constant' => true,
'doctrine_annotation_array_assignment' => false,
'doctrine_annotation_braces' => false,
'doctrine_annotation_indentation' => false,
'doctrine_annotation_spaces' => false,
'echo_tag_syntax' => [
'format' => 'short',
'long_function' => 'echo',
'shorten_simple_statements_only' => false,
],
'elseif' => true,
'empty_loop_body' => ['style' => 'braces'],
'empty_loop_condition' => ['style' => 'while'],
'encoding' => true,
'error_suppression' => [
'mute_deprecation_error' => true,
'noise_remaining_usages' => false,
'noise_remaining_usages_exclude' => [],
],
'escape_implicit_backslashes' => [
'double_quoted' => true,
'heredoc_syntax' => true,
'single_quoted' => false,
],
'explicit_indirect_variable' => true,
'explicit_string_variable' => true,
'final_class' => false,
'final_internal_class' => [
'annotation_exclude' => ['@no-final'],
'annotation_include' => ['@internal'],
'consider_absent_docblock_as_internal_class' => false,
],
'final_public_method_for_abstract_class' => false,
'fopen_flag_order' => true,
'fopen_flags' => ['b_mode' => true],
'full_opening_tag' => true,
'fully_qualified_strict_types' => true,
'function_declaration' => ['closure_function_spacing' => 'one'],
'function_to_constant' => [
'functions' => [
'get_called_class',
'get_class',
'get_class_this',
'php_sapi_name',
'phpversion',
'pi',
],
],
'function_typehint_space' => true,
'general_phpdoc_annotation_remove' => false,
'general_phpdoc_tag_rename' => false,
'get_class_to_class_keyword' => false,
'global_namespace_import' => [
'import_constants' => true,
'import_functions' => true,
'import_classes' => true,
],
'group_import' => true,
'header_comment' => false, // false by default
'heredoc_indentation' => ['indentation' => 'start_plus_one'],
'heredoc_to_nowdoc' => true,
'implode_call' => true,
'include' => true,
'increment_style' => ['style' => 'post'],
'indentation_type' => true,
'integer_literal_case' => true,
'is_null' => true,
'lambda_not_used_import' => true,
'line_ending' => true,
'linebreak_after_opening_tag' => false,
'list_syntax' => ['syntax' => 'short'],
'logical_operators' => true,
'lowercase_cast' => true,
'lowercase_keywords' => true,
'lowercase_static_reference' => true,
'magic_constant_casing' => true,
'magic_method_casing' => true,
'mb_str_functions' => false,
'method_argument_space' => [
'after_heredoc' => false,
'keep_multiple_spaces_after_comma' => false,
'on_multiline' => 'ensure_fully_multiline',
],
'method_chaining_indentation' => true,
'modernize_strpos' => false, // requires 8.0+
'modernize_types_casting' => true,
'multiline_comment_opening_closing' => true,
'multiline_whitespace_before_semicolons' => ['strategy' => 'no_multi_line'],
'native_constant_invocation' => false,
'native_function_casing' => true,
'native_function_invocation' => false,
'native_function_type_declaration_casing' => true,
'new_with_braces' => true,
'no_alias_functions' => ['sets' => ['@all']],
'no_alias_language_construct_call' => true,
'no_alternative_syntax' => ['fix_non_monolithic_code' => false],
'no_binary_string' => true,
'no_blank_lines_after_class_opening' => true,
'no_blank_lines_after_phpdoc' => true,
'no_blank_lines_before_namespace' => false, // conflicts with `single_blank_line_before_namespace`
'no_break_comment' => ['comment_text' => 'no break'],
'no_closing_tag' => true,
'no_empty_comment' => true,
'no_empty_phpdoc' => true,
'no_empty_statement' => true,
'no_extra_blank_lines' => ['tokens' => ['extra']],
'no_homoglyph_names' => true,
'no_leading_import_slash' => true,
'no_leading_namespace_whitespace' => true,
'no_mixed_echo_print' => ['use' => 'echo'],
'no_multiline_whitespace_around_double_arrow' => true,
'no_null_property_initialization' => true,
'no_short_bool_cast' => true,
'no_singleline_whitespace_before_semicolons' => true,
'no_space_around_double_colon' => true,
'no_spaces_after_function_name' => true,
'no_spaces_around_offset' => ['positions' => ['inside', 'outside']],
'no_spaces_inside_parenthesis' => true,
'no_superfluous_elseif' => true,
'no_superfluous_phpdoc_tags' => [
'allow_mixed' => true,
'allow_unused_params' => true,
'remove_inheritdoc' => false,
],
'no_trailing_comma_in_list_call' => true,
'no_trailing_comma_in_singleline_array' => true,
'no_trailing_whitespace' => true,
'no_trailing_whitespace_in_comment' => true,
'no_trailing_whitespace_in_string' => true,
'no_unneeded_control_parentheses' => [
'statements' => [
'break',
'clone',
'continue',
'echo_print',
'return',
'switch_case',
'yield',
],
],
'no_unneeded_curly_braces' => ['namespaces' => true],
'no_unneeded_final_method' => ['private_methods' => true],
'no_unneeded_import_alias' => true,
'no_unreachable_default_argument_value' => true,
'no_unset_cast' => true,
'no_unset_on_property' => false,
'no_unused_imports' => true,
'no_useless_else' => true,
'no_useless_return' => true,
'no_useless_sprintf' => true,
'no_whitespace_before_comma_in_array' => ['after_heredoc' => true],
'no_whitespace_in_blank_line' => true,
'non_printable_character' => ['use_escape_sequences_in_strings' => true],
'normalize_index_brace' => true,
'not_operator_with_space' => true,
'not_operator_with_successor_space' => true,
'nullable_type_declaration_for_default_null_value' => ['use_nullable_type_declaration' => true],
'object_operator_without_whitespace' => true,
'operator_linebreak' => ['only_booleans' => true, 'position' => 'beginning'],
'ordered_class_elements' => [
'order' => [
'use_trait',
'constant',
'property',
'method',
],
'sort_algorithm' => 'none',
],
'ordered_imports' => [
'sort_algorithm' => 'alpha',
'imports_order' => ['class', 'function', 'const'],
],
'ordered_interfaces' => false,
'ordered_traits' => false,
'php_unit_construct' => [
'assertions' => [
'assertSame',
'assertEquals',
'assertNotEquals',
'assertNotSame',
],
],
'php_unit_dedicate_assert' => ['target' => 'newest'],
'php_unit_dedicate_assert_internal_type' => ['target' => 'newest'],
'php_unit_expectation' => ['target' => 'newest'],
'php_unit_fqcn_annotation' => true,
'php_unit_internal_class' => ['types' => ['final']],
'php_unit_method_casing' => ['case' => 'camel_case'],
'php_unit_mock' => ['target' => 'newest'],
'php_unit_mock_short_will_return' => true,
'php_unit_namespaced' => ['target' => 'newest'],
'php_unit_no_expectation_annotation' => [
'target' => 'newest',
'use_class_const' => true,
],
'php_unit_set_up_tear_down_visibility' => true,
'php_unit_size_class' => false,
// 'php_unit_strict' => [
// 'assertions' => [
// 'assertAttributeEquals',
// 'assertAttributeNotEquals',
// 'assertEquals',
// 'assertNotEquals',
// ],
// ],
'php_unit_test_annotation' => ['style' => 'prefix'],
'php_unit_test_case_static_method_calls' => [
'call_type' => 'this',
'methods' => [],
],
'php_unit_test_class_requires_covers' => false,
'phpdoc_add_missing_param_annotation' => ['only_untyped' => true],
'phpdoc_align' => [
'align' => 'left'
],
'phpdoc_annotation_without_dot' => false,
'phpdoc_indent' => true,
'phpdoc_inline_tag_normalizer' => [
'tags' => [
'example',
'id',
'internal',
'inheritdoc',
'inheritdocs',
'link',
'source',
'toc',
'tutorial',
],
],
'phpdoc_line_span' => [
'const' => 'multi',
'method' => 'multi',
'property' => 'multi',
],
'phpdoc_no_access' => true,
'phpdoc_no_empty_return' => false,
'phpdoc_no_package' => false,
'phpdoc_no_useless_inheritdoc' => true,
'phpdoc_order' => true,
'phpdoc_order_by_value' => [
'annotations' => [
'author',
'covers',
'coversNothing',
'dataProvider',
'depends',
'group',
'internal',
'method',
'property',
'property-read',
'property-write',
'requires',
'throws',
'uses',
],
],
'phpdoc_return_self_reference' => [
'replacements' => [
'this' => '$this',
'@this' => '$this',
'$self' => 'self',
'@self' => 'self',
'$static' => 'static',
'@static' => 'static',
],
],
'phpdoc_scalar' => [
'types' => [
'boolean',
'callback',
'double',
'integer',
'real',
'str',
],
],
'phpdoc_separation' => false,
'phpdoc_single_line_var_spacing' => true,
'phpdoc_summary' => false,
'phpdoc_tag_casing' => ['tags' => ['inheritDoc']],
'phpdoc_tag_type' => ['tags' => ['inheritDoc' => 'inline']],
'phpdoc_to_comment' => false,
'phpdoc_to_param_type' => false,
'phpdoc_to_property_type' => false,
'phpdoc_to_return_type' => false,
'phpdoc_trim' => true,
'phpdoc_trim_consecutive_blank_line_separation' => true,
'phpdoc_types' => ['groups' => ['simple', 'alias', 'meta']],
'phpdoc_types_order' => [
'null_adjustment' => 'always_last',
'sort_algorithm' => 'alpha',
],
'phpdoc_var_annotation_correct_order' => true,
'phpdoc_var_without_name' => true,
'pow_to_exponentiation' => true,
'protected_to_private' => true,
'psr_autoloading' => ['dir' => null],
'random_api_migration' => [
'replacements' => [
'getrandmax' => 'mt_getrandmax',
'rand' => 'mt_rand',
'srand' => 'mt_srand',
],
],
'regular_callable_call' => true,
'return_assignment' => true,
'return_type_declaration' => ['space_before' => 'none'],
'self_accessor' => false,
'self_static_accessor' => true,
'semicolon_after_instruction' => false,
'set_type_to_cast' => true,
'short_scalar_cast' => true,
'simple_to_complex_string_variable' => true,
'simplified_if_return' => true,
'simplified_null_return' => false,
'single_blank_line_at_eof' => true,
'single_blank_line_before_namespace' => true,
'single_class_element_per_statement' => ['elements' => ['const', 'property']],
'single_import_per_statement' => false,
'single_line_after_imports' => true,
'single_line_comment_style' => ['comment_types' => ['asterisk', 'hash']],
'single_line_throw' => false,
'single_quote' => ['strings_containing_single_quote_chars' => false],
'single_space_after_construct' => [
'constructs' => [
'abstract',
'as',
'attribute',
'break',
'case',
'catch',
'class',
'clone',
'comment',
'const',
'const_import',
'continue',
'do',
'echo',
'else',
'elseif',
'extends',
'final',
'finally',
'for',
'foreach',
'function',
'function_import',
'global',
'goto',
'if',
'implements',
'include',
'include_once',
'instanceof',
'insteadof',
'interface',
'match',
'named_argument',
'new',
'open_tag_with_echo',
'php_doc',
'php_open',
'print',
'private',
'protected',
'public',
'require',
'require_once',
'return',
'static',
'throw',
'trait',
'try',
'use',
'use_lambda',
'use_trait',
'var',
'while',
'yield',
'yield_from',
],
],
'single_trait_insert_per_statement' => true,
'space_after_semicolon' => ['remove_in_empty_for_expressions' => true],
'standardize_increment' => true,
'standardize_not_equals' => true,
'static_lambda' => true,
'strict_comparison' => true,
'strict_param' => true,
'string_length_to_empty' => true,
'string_line_ending' => true,
'switch_case_semicolon_to_colon' => true,
'switch_case_space' => true,
'switch_continue_to_break' => true,
'ternary_operator_spaces' => true,
'ternary_to_elvis_operator' => true,
'ternary_to_null_coalescing' => true,
'trailing_comma_in_multiline' => [
'after_heredoc' => true,
'elements' => ['arrays'],
],
'trim_array_spaces' => true,
'types_spaces' => ['space' => 'none'],
'unary_operator_spaces' => false,
'use_arrow_functions' => true,
'visibility_required' => ['elements' => ['const', 'method', 'property']],
'void_return' => false, // changes method signature
'whitespace_after_comma_in_array' => true,
'yoda_style' => [
'equal' => false,
'identical' => null,
'less_and_greater' => false,
'always_move_variable' => false,
],
]);

View File

@ -57,4 +57,4 @@ return array_merge($tomlConfig, [
// Included config files // Included config files
'routes' => require 'routes.php', 'routes' => require 'routes.php',
]); ]);

View File

@ -16,10 +16,10 @@
use const Aviat\AnimeClient\{ use const Aviat\AnimeClient\{
ALPHA_SLUG_PATTERN, ALPHA_SLUG_PATTERN,
NUM_PATTERN, DEFAULT_CONTROLLER,
SLUG_PATTERN,
DEFAULT_CONTROLLER_METHOD, DEFAULT_CONTROLLER_METHOD,
DEFAULT_CONTROLLER NUM_PATTERN,
SLUG_PATTERN
}; };
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
@ -190,14 +190,14 @@ $routes = [
'character' => [ 'character' => [
'path' => '/character/{slug}', 'path' => '/character/{slug}',
'tokens' => [ 'tokens' => [
'slug' => SLUG_PATTERN 'slug' => SLUG_PATTERN,
] ],
], ],
'person' => [ 'person' => [
'path' => '/people/{slug}', 'path' => '/people/{slug}',
'tokens' => [ 'tokens' => [
'slug' => SLUG_PATTERN, 'slug' => SLUG_PATTERN,
] ],
], ],
'default_user_info' => [ 'default_user_info' => [
'path' => '/me', 'path' => '/me',
@ -209,8 +209,8 @@ $routes = [
'controller' => 'user', 'controller' => 'user',
'action' => 'about', 'action' => 'about',
'tokens' => [ 'tokens' => [
'username' => '.*?' 'username' => '.*?',
] ],
], ],
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
// Default / Shared routes // Default / Shared routes
@ -231,8 +231,8 @@ $routes = [
'controller' => 'images', 'controller' => 'images',
'tokens' => [ 'tokens' => [
'type' => SLUG_PATTERN, 'type' => SLUG_PATTERN,
'file' => '[a-z0-9\-]+\.[a-z]{3,4}' 'file' => '[a-z0-9\-]+\.[a-z]{3,4}',
] ],
], ],
'settings' => [ 'settings' => [
'path' => '/settings', 'path' => '/settings',
@ -259,8 +259,8 @@ $routes = [
'controller' => 'history', 'controller' => 'history',
'path' => '/history/{type}', 'path' => '/history/{type}',
'tokens' => [ 'tokens' => [
'type' => SLUG_PATTERN 'type' => SLUG_PATTERN,
] ],
], ],
'increment' => [ 'increment' => [
'path' => '/{controller}/increment', 'path' => '/{controller}/increment',
@ -316,7 +316,7 @@ $defaultMap = [
foreach ($routes as &$route) foreach ($routes as &$route)
{ {
foreach($defaultMap as $key => $val) foreach ($defaultMap as $key => $val)
{ {
if ( ! array_key_exists($key, $route)) if ( ! array_key_exists($key, $route))
{ {

View File

@ -6,12 +6,10 @@
* *
* PHP version 8 * PHP version 8
* *
* @package HummingbirdAnimeClient * @copyright 2015 - 2022 Timothy J. Warren <tim@timshome.page>
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2021 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License * @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.2 * @version 5.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient * @link https://git.timshome.page/timw4mail/HummingBirdAnimeClient
*/ */
namespace Aviat\AnimeClient; namespace Aviat\AnimeClient;
@ -20,12 +18,10 @@ use Aura\Html\HelperLocatorFactory;
use Aura\Router\RouterContainer; use Aura\Router\RouterContainer;
use Aura\Session\SessionFactory; use Aura\Session\SessionFactory;
use Aviat\AnimeClient\API\{Anilist, Kitsu}; use Aviat\AnimeClient\API\{Anilist, Kitsu};
use Aviat\AnimeClient\Component; use Aviat\AnimeClient\{Component, Model};
use Aviat\AnimeClient\Model;
use Aviat\Banker\Teller; use Aviat\Banker\Teller;
use Aviat\Ion\Config; use Aviat\Ion\Config;
use Aviat\Ion\Di\Container; use Aviat\Ion\Di\{Container, ContainerInterface};
use Aviat\Ion\Di\ContainerInterface;
use Laminas\Diactoros\ServerRequestFactory; use Laminas\Diactoros\ServerRequestFactory;
use Monolog\Formatter\JsonFormatter; use Monolog\Formatter\JsonFormatter;
use Monolog\Handler\RotatingFileHandler; use Monolog\Handler\RotatingFileHandler;
@ -38,7 +34,7 @@ if ( ! defined('HB_APP_DIR'))
{ {
define('HB_APP_DIR', __DIR__); define('HB_APP_DIR', __DIR__);
define('ROOT_DIR', dirname(HB_APP_DIR)); define('ROOT_DIR', dirname(HB_APP_DIR));
define('TEMPLATE_DIR', _dir(HB_APP_DIR, 'templates')); define('TEMPLATE_DIR', _dir(HB_APP_DIR, 'templates'));
} }
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -74,18 +70,19 @@ return static function (array $configArray = []): Container {
$container->set('config', static fn () => new Config($configArray)); $container->set('config', static fn () => new Config($configArray));
// Create Cache Object // Create Cache Object
$container->set('cache', static function(ContainerInterface $container): CacheInterface { $container->set('cache', static function (ContainerInterface $container): CacheInterface {
$logger = $container->getLogger(); $logger = $container->getLogger();
$config = $container->get('config')->get('cache'); $config = $container->get('config')->get('cache');
return new Teller($config, $logger); return new Teller($config, $logger);
}); });
// Create Aura Router Object // Create Aura Router Object
$container->set('aura-router', static fn() => new RouterContainer); $container->set('aura-router', static fn () => new RouterContainer());
// Create Html helpers // Create Html helpers
$container->set('html-helper', static function(ContainerInterface $container) { $container->set('html-helper', static function (ContainerInterface $container) {
$htmlHelper = (new HelperLocatorFactory)->newInstance(); $htmlHelper = (new HelperLocatorFactory())->newInstance();
$helpers = [ $helpers = [
'menu' => Helper\Menu::class, 'menu' => Helper\Menu::class,
'field' => Helper\Form::class, 'field' => Helper\Form::class,
@ -94,9 +91,10 @@ return static function (array $configArray = []): Container {
foreach ($helpers as $name => $class) foreach ($helpers as $name => $class)
{ {
$htmlHelper->set($name, static function() use ($class, $container) { $htmlHelper->set($name, static function () use ($class, $container) {
$helper = new $class; $helper = new $class();
$helper->setContainer($container); $helper->setContainer($container);
return $helper; return $helper;
}); });
} }
@ -106,7 +104,7 @@ return static function (array $configArray = []): Container {
// Create Component helpers // Create Component helpers
$container->set('component-helper', static function (ContainerInterface $container) { $container->set('component-helper', static function (ContainerInterface $container) {
$helper = (new HelperLocatorFactory)->newInstance(); $helper = (new HelperLocatorFactory())->newInstance();
$components = [ $components = [
'animeCover' => Component\AnimeCover::class, 'animeCover' => Component\AnimeCover::class,
'mangaCover' => Component\MangaCover::class, 'mangaCover' => Component\MangaCover::class,
@ -119,8 +117,9 @@ return static function (array $configArray = []): Container {
foreach ($components as $name => $componentClass) foreach ($components as $name => $componentClass)
{ {
$helper->set($name, static function () use ($container, $componentClass) { $helper->set($name, static function () use ($container, $componentClass) {
$helper = new $componentClass; $helper = new $componentClass();
$helper->setContainer($container); $helper->setContainer($container);
return $helper; return $helper;
}); });
} }
@ -144,7 +143,7 @@ return static function (array $configArray = []): Container {
$container->set('util', static fn ($container) => new Util($container)); $container->set('util', static fn ($container) => new Util($container));
// Models // Models
$container->set('kitsu-model', static function(ContainerInterface $container): Kitsu\Model { $container->set('kitsu-model', static function (ContainerInterface $container): Kitsu\Model {
$requestBuilder = new Kitsu\RequestBuilder($container); $requestBuilder = new Kitsu\RequestBuilder($container);
$requestBuilder->setLogger($container->getLogger('kitsu-request')); $requestBuilder->setLogger($container->getLogger('kitsu-request'));
@ -158,9 +157,10 @@ return static function (array $configArray = []): Container {
$cache = $container->get('cache'); $cache = $container->get('cache');
$model->setCache($cache); $model->setCache($cache);
return $model; return $model;
}); });
$container->set('anilist-model', static function(ContainerInterface $container): Anilist\Model { $container->set('anilist-model', static function (ContainerInterface $container): Anilist\Model {
$requestBuilder = new Anilist\RequestBuilder($container); $requestBuilder = new Anilist\RequestBuilder($container);
$requestBuilder->setLogger($container->getLogger('anilist-request')); $requestBuilder->setLogger($container->getLogger('anilist-request'));
@ -178,9 +178,10 @@ return static function (array $configArray = []): Container {
$container->set('manga-model', static fn ($container) => new Model\Manga($container)); $container->set('manga-model', static fn ($container) => new Model\Manga($container));
$container->set('anime-collection-model', static fn ($container) => new Model\AnimeCollection($container)); $container->set('anime-collection-model', static fn ($container) => new Model\AnimeCollection($container));
$container->set('manga-collection-model', static fn ($container) => new Model\MangaCollection($container)); $container->set('manga-collection-model', static fn ($container) => new Model\MangaCollection($container));
$container->set('settings-model', static function($container) { $container->set('settings-model', static function ($container) {
$model = new Model\Settings($container->get('config')); $model = new Model\Settings($container->get('config'));
$model->setContainer($container); $model->setContainer($container);
return $model; return $model;
}); });
@ -196,4 +197,4 @@ return static function (array $configArray = []): Container {
return $container; return $container;
}; };
// End of bootstrap.php // End of bootstrap.php

View File

@ -1,6 +1,7 @@
<article <article
class="media" class="media"
data-kitsu-id="<?= $item['id'] ?>" data-kitsu-id="<?= $item['id'] ?>"
data-anilist-id="<?= $item['anilist_id'] ?>"
data-mal-id="<?= $item['mal_id'] ?>" data-mal-id="<?= $item['mal_id'] ?>"
> >
<?php if ($auth->isAuthenticated()): ?> <?php if ($auth->isAuthenticated()): ?>

View File

@ -1,4 +1,16 @@
<?php declare(strict_types=1); <?php declare(strict_types=1);
/**
* Hummingbird Anime List Client
*
* An API client for Kitsu to manage anime and manga watch lists
*
* PHP version 8
*
* @copyright 2015 - 2022 Timothy J. Warren <tim@timshome.page>
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.2
* @link https://git.timshome.page/timw4mail/HummingBirdAnimeClient
*/
namespace Aviat\AnimeClient; namespace Aviat\AnimeClient;

View File

@ -1,99 +0,0 @@
<?php
declare(strict_types=1);
$file_patterns = [
'app/appConf/*.php',
'app/bootstrap.php',
'migrations/*.php',
'src/**/*.php',
'src/*.php',
'tests/**/*.php',
'tests/*.php',
'index.php',
'Robofile.php'
];
if ( ! function_exists('glob_recursive'))
{
// Does not support flag GLOB_BRACE
function glob_recursive(string $pattern, int $flags = 0): array
{
$files = glob($pattern, $flags);
foreach (glob(dirname($pattern) . '/*', GLOB_ONLYDIR | GLOB_NOSORT) as $dir)
{
$files = array_merge($files, glob_recursive($dir . '/' . basename($pattern), $flags));
}
return $files;
}
}
function get_text_to_replace(array $tokens): string
{
$output = '';
// Tokens have the follow structure if arrays:
// [0] => token type constant
// [1] => raw syntax parsed to that token
// [2] => line number
foreach($tokens as $token)
{
// Since we only care about opening docblocks,
// bail out when we get to the namespace token
if (is_array($token) && $token[0] === T_NAMESPACE)
{
break;
}
if (is_array($token))
{
$token = $token[1];
}
$output .= $token;
}
return $output;
}
function get_tokens(string $source): array
{
return token_get_all($source);
}
function replace_files(array $files, string $template): void
{
print_r($files);
foreach ($files as $file)
{
$source = file_get_contents($file);
if ($source === FALSE)
{
continue;
}
if (stripos($source, 'namespace') === FALSE)
{
continue;
}
$tokens = get_tokens($source);
$text_to_replace = get_text_to_replace($tokens);
$header = file_get_contents(__DIR__ . $template);
$new_text = "<?php declare(strict_types=1);\n{$header}";
$new_source = str_replace($text_to_replace, $new_text, $source);
file_put_contents($file, $new_source);
}
}
foreach ($file_patterns as $glob)
{
$files = glob_recursive($glob);
replace_files($files, '/header_comment.txt');
}
echo "Successfully updated headers \n";

View File

@ -27,10 +27,7 @@
} }
}, },
"config": { "config": {
"lock": false, "lock": false
"platform": {
"php": "8"
}
}, },
"require": { "require": {
"amphp/amp": "^2.5.0", "amphp/amp": "^2.5.0",
@ -38,22 +35,20 @@
"aura/html": "^2.5.0", "aura/html": "^2.5.0",
"aura/router": "^3.1.0", "aura/router": "^3.1.0",
"aura/session": "^2.1.0", "aura/session": "^2.1.0",
"aviat/banker": "^3.0.0 || ^4.0.0", "aviat/banker": "^4.1.2",
"aviat/query": "^3.0.0", "aviat/query": "^4.0.0",
"danielstjules/stringy": "^3.1.0",
"ext-dom": "*", "ext-dom": "*",
"ext-gd": "*",
"ext-intl": "*", "ext-intl": "*",
"ext-json": "*", "ext-json": "*",
"ext-gd": "*", "ext-mbstring": "*",
"ext-pdo": "*", "ext-pdo": "*",
"laminas/laminas-diactoros": "^2.5.0", "laminas/laminas-diactoros": "^2.5.0",
"laminas/laminas-httphandlerrunner": "^2.1.0", "laminas/laminas-httphandlerrunner": "^2.1.0",
"maximebf/consolekit": "^1.0.3", "maximebf/consolekit": "^1.0.3",
"monolog/monolog": "^2.0.2", "monolog/monolog": "^3.0.0",
"php": ">= 8.0.0", "php": ">= 8.1.0",
"psr/http-message": "^1.0.1", "psr/http-message": "^1.0.1",
"psr/log": "*",
"robmorgan/phinx": "^0.12.4",
"symfony/polyfill-mbstring": "^1.0.0", "symfony/polyfill-mbstring": "^1.0.0",
"symfony/polyfill-util": "^1.0.0", "symfony/polyfill-util": "^1.0.0",
"tracy/tracy": "^2.8.0", "tracy/tracy": "^2.8.0",
@ -68,7 +63,7 @@
"scripts": { "scripts": {
"build:css": "cd public && npm run build:css && cd ..", "build:css": "cd public && npm run build:css && cd ..",
"build:js": "cd public && npm run build:js && cd ..", "build:js": "cd public && npm run build:js && cd ..",
"coverage": "phpdbg -qrr -- vendor/bin/phpunit -c build", "coverage": "php -dpcov.enabled=1 -dpcov.directory=. -dpcov.exclude=\"~vendor~\" ./vendor/bin/phpunit -c build",
"phpstan": "phpstan analyse -c phpstan.neon", "phpstan": "phpstan analyse -c phpstan.neon",
"watch:css": "cd public && npm run watch:css", "watch:css": "cd public && npm run watch:css",
"watch:js": "cd public && npm run watch:js", "watch:js": "cd public && npm run watch:js",

View File

@ -63,6 +63,7 @@ _.on('body.anime.list', 'click', '.plus-one', (e) => {
// Setup the update data // Setup the update data
let data = { let data = {
id: parentSel.dataset.kitsuId, id: parentSel.dataset.kitsuId,
anilist_id: parentSel.dataset.anilistId,
mal_id: parentSel.dataset.malId, mal_id: parentSel.dataset.malId,
data: { data: {
progress: watchedCount + 1 progress: watchedCount + 1
@ -94,11 +95,18 @@ _.on('body.anime.list', 'click', '.plus-one', (e) => {
_.hide('#loading-shadow'); _.hide('#loading-shadow');
_.showMessage('error', `Failed to update ${title}. `); _.showMessage('error', `Failed to update ${title}. `);
_.scrollToTop(); _.scrollToTop();
return; return;
} }
// We've completed the series
if (resData.data.libraryEntry.update.libraryEntry.status === 'COMPLETED') { if (resData.data.libraryEntry.update.libraryEntry.status === 'COMPLETED') {
_.hide(parentSel); _.hide(parentSel);
_.hide('#loading-shadow');
_.showMessage('success', `Successfully completed ${title}`);
_.scrollToTop();
return;
} }
_.hide('#loading-shadow'); _.hide('#loading-shadow');

View File

@ -83,6 +83,11 @@ _.on('.manga.list', 'click', '.edit-buttons button', (e) => {
if (String(data.data.status).toUpperCase() === 'COMPLETED') { if (String(data.data.status).toUpperCase() === 'COMPLETED') {
_.hide(parentSel); _.hide(parentSel);
_.hide('#loading-shadow');
_.showMessage('success', `Successfully completed ${mangaName}`);
_.scrollToTop();
return;
} }
_.hide('#loading-shadow'); _.hide('#loading-shadow');

View File

@ -5,6 +5,7 @@ import _ from './anime-client.js';
_.on('main', 'change', '.big-check', (e) => { _.on('main', 'change', '.big-check', (e) => {
const id = e.target.id; const id = e.target.id;
document.getElementById(`mal_${id}`).checked = true; document.getElementById(`mal_${id}`).checked = true;
document.getElementById(`anilist_${id}`).checked = true;
}); });
/** /**
@ -55,6 +56,7 @@ export function renderSearchResults (type, data, isCollection = false) {
return ` return `
<article class="media search ${disabled}"> <article class="media search ${disabled}">
<div class="name"> <div class="name">
<input type="radio" class="mal-check" id="anilist_${item.slug}" name="anilist_id" value="${item.anilist_id}" ${disabled} />
<input type="radio" class="mal-check" id="mal_${item.slug}" name="mal_id" value="${item.mal_id}" ${disabled} /> <input type="radio" class="mal-check" id="mal_${item.slug}" name="mal_id" value="${item.mal_id}" ${disabled} />
<input type="radio" class="big-check" id="${item.slug}" name="id" value="${item.id}" ${disabled} /> <input type="radio" class="big-check" id="${item.slug}" name="id" value="${item.id}" ${disabled} />
<label for="${item.slug}"> <label for="${item.slug}">

View File

@ -11,11 +11,11 @@
"devDependencies": { "devDependencies": {
"@swc/cli": "^0.1.39", "@swc/cli": "^0.1.39",
"@swc/core": "^1.2.54", "@swc/core": "^1.2.54",
"concurrently": "^6.0.2", "concurrently": "^7.4.0",
"cssnano": "^5.0.1", "cssnano": "^5.0.1",
"postcss": "^8.2.6", "postcss": "^8.2.6",
"postcss-import": "^14.0.0", "postcss-import": "^15.0.0",
"postcss-preset-env": "^6.7.0", "postcss-preset-env": "^7.8.2",
"watch": "^1.0.2" "watch": "^1.0.2"
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -6,12 +6,10 @@
* *
* PHP version 8 * PHP version 8
* *
* @package HummingbirdAnimeClient * @copyright 2015 - 2022 Timothy J. Warren <tim@timshome.page>
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2021 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License * @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.2 * @version 5.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient * @link https://git.timshome.page/timw4mail/HummingBirdAnimeClient
*/ */
namespace Aviat\AnimeClient; namespace Aviat\AnimeClient;
@ -25,8 +23,8 @@ setlocale(LC_CTYPE, 'en_US');
// Load composer autoloader // Load composer autoloader
require_once __DIR__ . '/vendor/autoload.php'; require_once __DIR__ . '/vendor/autoload.php';
Debugger::$strictMode = E_ALL & ~E_DEPRECATED; // all errors except deprecated notices Debugger::$strictMode = E_ALL;
Debugger::$showBar = false; Debugger::$showBar = FALSE;
Debugger::enable(Debugger::DEVELOPMENT, __DIR__ . '/app/logs'); Debugger::enable(Debugger::DEVELOPMENT, __DIR__ . '/app/logs');
// Define base directories // Define base directories
@ -37,7 +35,7 @@ $CONF_DIR = _dir($APP_DIR, 'config');
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Dependency Injection setup // Dependency Injection setup
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
$baseConfig = require "{$APPCONF_DIR}/base_config.php"; $baseConfig = require _dir($APPCONF_DIR, 'base_config.php');
$di = require "{$APP_DIR}/bootstrap.php"; $di = require "{$APP_DIR}/bootstrap.php";
$config = loadConfig($CONF_DIR); $config = loadConfig($CONF_DIR);
@ -59,7 +57,7 @@ if (is_array($checkedConfig) && array_key_exists('timezone', $checkedConfig) &&
{ {
date_default_timezone_set($checkedConfig['timezone']); date_default_timezone_set($checkedConfig['timezone']);
} }
else if (is_string($timezone) && $timezone !== '') elseif (is_string($timezone) && $timezone !== '')
{ {
date_default_timezone_set($timezone); date_default_timezone_set($timezone);
} }
@ -76,4 +74,4 @@ unset($APP_DIR, $CONF_DIR, $APPCONF_DIR);
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Dispatch to the current route // Dispatch to the current route
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
$container->get('dispatcher')(); $container->get('dispatcher')();

35
justfile Normal file
View File

@ -0,0 +1,35 @@
# Lists the available actions
default:
@just --list
# Runs rector, showing what changes will be make
rector-dry-run:
tools/vendor/bin/rector process --config=tools/rector.php --dry-run src
# Runs rector, and updates the files
rector:
tools/vendor/bin/rector process --config=tools/rector.php src
# Check code formatting
check-fmt:
tools/vendor/bin/php-cs-fixer fix --dry-run --verbose
# Fix code formatting
fmt:
tools/vendor/bin/php-cs-fixer fix --verbose
# Run tests
test:
composer run-script test
# Run tests, update snapshots
test-update:
composer run-script test-update
# Update the per-file header comments
update-headers:
php tools/update_header_comments.php
# Run unit tests and generate test-coverage report
coverage:
composer run-script coverage

View File

@ -2,7 +2,8 @@
use Phinx\Migration\AbstractMigration; use Phinx\Migration\AbstractMigration;
class FirstMigration extends AbstractMigration { class FirstMigration extends AbstractMigration
{
/** /**
* Migrate up * Migrate up
*/ */
@ -16,7 +17,7 @@ class FirstMigration extends AbstractMigration {
// Add items to media table // Add items to media table
if ($this->hasTable('media')) if ($this->hasTable('media'))
{ {
foreach(['DVD & Blu-ray', 'Blu-ray', 'DVD', 'Bootleg DVD'] as $type) foreach (['DVD & Blu-ray', 'Blu-ray', 'DVD', 'Bootleg DVD'] as $type)
{ {
$this->execute('INSERT INTO "media" ("type") VALUES (\'' . $type . '\')'); $this->execute('INSERT INTO "media" ("type") VALUES (\'' . $type . '\')');
} }
@ -25,11 +26,11 @@ class FirstMigration extends AbstractMigration {
// Create anime_set table // Create anime_set table
$anime_set = $this->table('anime_set', ['id' => FALSE, 'primary_key' => ['hummingbird_id']]); $anime_set = $this->table('anime_set', ['id' => FALSE, 'primary_key' => ['hummingbird_id']]);
$anime_set->addColumn('hummingbird_id', 'biginteger') $anime_set->addColumn('hummingbird_id', 'biginteger')
->addColumn('slug', 'string', ['comment' => "URL slug used for image caching and generating links"]) ->addColumn('slug', 'string', ['comment' => 'URL slug used for image caching and generating links'])
->addColumn('title', 'string') ->addColumn('title', 'string')
->addColumn('alternate_title', 'string', ['null' => TRUE]) ->addColumn('alternate_title', 'string', ['null' => TRUE])
->addColumn('media_id', 'integer', ['default' => 3, 'null' => TRUE]) ->addColumn('media_id', 'integer', ['default' => 3, 'null' => TRUE])
->addColumn('show_type', 'string', ['default' => 'TV', 'null' => TRUE, 'comment' => "TV Series/OVA/etc"]) ->addColumn('show_type', 'string', ['default' => 'TV', 'null' => TRUE, 'comment' => 'TV Series/OVA/etc'])
->addColumn('age_rating', 'string', ['default' => 'PG13', 'null' => TRUE]) ->addColumn('age_rating', 'string', ['default' => 'PG13', 'null' => TRUE])
->addColumn('cover_image', 'string', ['null' => TRUE]) ->addColumn('cover_image', 'string', ['null' => TRUE])
->addColumn('episode_count', 'integer', ['null' => TRUE]) ->addColumn('episode_count', 'integer', ['null' => TRUE])

View File

@ -1,35 +1,35 @@
<?php <?php declare(strict_types=1);
use Phinx\Migration\AbstractMigration; use Phinx\Migration\AbstractMigration;
class CacheMigration extends AbstractMigration class CacheMigration extends AbstractMigration
{ {
/** /**
* Change Method. * Change Method.
* *
* Write your reversible migrations using this method. * Write your reversible migrations using this method.
* *
* More information on writing migrations is available here: * More information on writing migrations is available here:
* http://docs.phinx.org/en/latest/migrations.html#the-abstractmigration-class * http://docs.phinx.org/en/latest/migrations.html#the-abstractmigration-class
* *
* The following commands can be used in this method and Phinx will * The following commands can be used in this method and Phinx will
* automatically reverse them when rolling back: * automatically reverse them when rolling back:
* *
* createTable * createTable
* renameTable * renameTable
* addColumn * addColumn
* renameColumn * renameColumn
* addIndex * addIndex
* addForeignKey * addForeignKey
* *
* Remember to call "create()" or "update()" and NOT "save()" when working * Remember to call "create()" or "update()" and NOT "save()" when working
* with the Table class. * with the Table class.
*/ */
public function change() public function change()
{ {
$cacheTable = $this->table('cache', ['id' => FALSE, 'primary_key' => ['key']]); $cacheTable = $this->table('cache', ['id' => FALSE, 'primary_key' => ['key']]);
$cacheTable->addColumn('key', 'text') $cacheTable->addColumn('key', 'text')
->addColumn('value', 'text') ->addColumn('value', 'text')
->create(); ->create();
} }
} }

View File

@ -1,4 +1,4 @@
<?php <?php declare(strict_types=1);
use Phinx\Migration\AbstractMigration; use Phinx\Migration\AbstractMigration;
@ -11,7 +11,7 @@ class ReorganizeAnimeCollectionMedia extends AbstractMigration
{ {
$newLinkTable = $this->table('anime_set_media_link', [ $newLinkTable = $this->table('anime_set_media_link', [
'id' => FALSE, 'id' => FALSE,
'primary_key' => ['hummingbird_id', 'media_id'] 'primary_key' => ['hummingbird_id', 'media_id'],
]); ]);
$newLinkTable->addColumn('hummingbird_id', 'biginteger') $newLinkTable->addColumn('hummingbird_id', 'biginteger')
@ -31,6 +31,7 @@ class ReorganizeAnimeCollectionMedia extends AbstractMigration
foreach ($rows as $row) foreach ($rows as $row)
{ {
$keys = array_keys($row); $keys = array_keys($row);
foreach ($keys as $k) foreach ($keys as $k)
{ {
if (is_numeric($k)) if (is_numeric($k))
@ -49,6 +50,7 @@ class ReorganizeAnimeCollectionMedia extends AbstractMigration
// and replace those rows with the individual entries // and replace those rows with the individual entries
$linkRows = $this->fetchAll('SELECT hummingbird_id FROM anime_set_media_link WHERE media_id=1'); $linkRows = $this->fetchAll('SELECT hummingbird_id FROM anime_set_media_link WHERE media_id=1');
$insertRows = []; $insertRows = [];
foreach ($linkRows as $row) foreach ($linkRows as $row)
{ {
$insertRows[] = [ $insertRows[] = [

View File

@ -1,4 +1,4 @@
<?php <?php declare(strict_types=1);
use Phinx\Migration\AbstractMigration; use Phinx\Migration\AbstractMigration;
@ -18,6 +18,7 @@ class AnimeCollectionRefactorCleanup extends AbstractMigration
{ {
// Add some new media types // Add some new media types
$moreMediaTypes = []; $moreMediaTypes = [];
foreach ($this->newMediaTypes as $id => $medium) foreach ($this->newMediaTypes as $id => $medium)
{ {
$moreMediaTypes[] = [ $moreMediaTypes[] = [
@ -47,7 +48,7 @@ class AnimeCollectionRefactorCleanup extends AbstractMigration
$this->execute("UPDATE media SET type='Bootleg DVD' WHERE id=4"); $this->execute("UPDATE media SET type='Bootleg DVD' WHERE id=4");
// Remove the new media types // Remove the new media types
$values = array_map(fn ($medium) => "'{$medium}'", $this->newMediaTypes); $values = array_map(static fn ($medium) => "'{$medium}'", $this->newMediaTypes);
$valueList = implode(',', $values); $valueList = implode(',', $values);
$this->execute("DELETE FROM media WHERE type IN ({$valueList})"); $this->execute("DELETE FROM media WHERE type IN ({$valueList})");
} }

View File

@ -1,4 +1,4 @@
<?php <?php declare(strict_types=1);
use Phinx\Migration\AbstractMigration; use Phinx\Migration\AbstractMigration;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
var LightTableSorter=function(){var th=null;var cellIndex=null;var order='';var text=function(row){return row.cells.item(cellIndex).textContent.toLowerCase()};var sort=function(a,b){var textA=text(a);var textB=text(b);console.log("Comparing "+textA+" and "+textB);if(th.classList.contains("numeric")){var arrayA=textA.replace('episodes: ','').replace('-',0).split("/");var arrayB=textB.replace('episodes: ','').replace('-',0).split("/");if(arrayA.length>1){textA=parseInt(arrayA[0],10)/parseInt(arrayA[1],10);textB=parseInt(arrayB[0],10)/parseInt(arrayB[1],10)}else{textA=parseInt(arrayA[0],10);textB=parseInt(arrayB[0],10)}}else if(parseInt(textA,10)){textA=parseInt(textA,10);textB=parseInt(textB,10)}if(textA>textB)return 1;if(textA<textB)return -1;return 0};var toggle=function(){var c=order!=='sorting-asc'?'sorting-asc':'sorting-desc';th.className=(th.className.replace(order,'')+' '+c).trim();return order=c};var reset=function(){th.classList.remove('sorting-asc','sorting-desc');th.classList.add('sorting');return order=''};var onClickEvent=function(e){if(th&&cellIndex!==e.target.cellIndex)reset();th=e.target;if(th.nodeName.toLowerCase()==='th'){cellIndex=th.cellIndex;var tbody=th.offsetParent.getElementsByTagName('tbody')[0];var rows=Array.from(tbody.rows);if(rows){rows.sort(sort);if(order==='sorting-asc')rows.reverse();toggle();tbody.innerHtml='';rows.forEach(function(row){tbody.appendChild(row)})}}};return{init:function(){var ths=document.getElementsByTagName('th');var results=[];for(var i=0,len=ths.length;i<len;i++){var th=ths[i];th.classList.add('sorting');th.classList.add('testing');results.push(th.onclick=onClickEvent)}return results}}}();LightTableSorter.init() var LightTableSorter=function(){var th=null;var cellIndex=null;var order="";var text=function(row){return row.cells.item(cellIndex).textContent.toLowerCase()};var sort=function(a,b){var textA=text(a);var textB=text(b);console.log("Comparing "+textA+" and "+textB);if(th.classList.contains("numeric")){var arrayA=textA.replace("episodes: ","").replace("-",0).split("/");var arrayB=textB.replace("episodes: ","").replace("-",0).split("/");if(arrayA.length>1){textA=parseInt(arrayA[0],10)/parseInt(arrayA[1],10);textB=parseInt(arrayB[0],10)/parseInt(arrayB[1],10)}else{textA=parseInt(arrayA[0],10);textB=parseInt(arrayB[0],10)}}else if(parseInt(textA,10)){textA=parseInt(textA,10);textB=parseInt(textB,10)}if(textA>textB)return 1;if(textA<textB)return -1;return 0};var toggle=function(){var c=order!=="sorting-asc"?"sorting-asc":"sorting-desc";th.className=(th.className.replace(order,"")+" "+c).trim();return order=c};var reset=function(){th.classList.remove("sorting-asc","sorting-desc");th.classList.add("sorting");return order=""};var onClickEvent=function(e){if(th&&cellIndex!==e.target.cellIndex)reset();th=e.target;if(th.nodeName.toLowerCase()==="th"){cellIndex=th.cellIndex;var tbody=th.offsetParent.getElementsByTagName("tbody")[0];var rows=Array.from(tbody.rows);if(rows){rows.sort(sort);if(order==="sorting-asc")rows.reverse();toggle();tbody.innerHtml="";rows.forEach(function(row){tbody.appendChild(row)})}}};return{init:function(){var ths=document.getElementsByTagName("th");var results=[];for(var i=0,len=ths.length;i<len;i++){var th=ths[i];th.classList.add("sorting");th.classList.add("testing");results.push(th.onclick=onClickEvent)}return results}}}();LightTableSorter.init();

File diff suppressed because one or more lines are too long

View File

@ -6,31 +6,33 @@
* *
* PHP version 8 * PHP version 8
* *
* @package HummingbirdAnimeClient * @copyright 2015 - 2022 Timothy J. Warren <tim@timshome.page>
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2021 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License * @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.2 * @version 5.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient * @link https://git.timshome.page/timw4mail/HummingBirdAnimeClient
*/ */
namespace Aviat\AnimeClient\API; namespace Aviat\AnimeClient\API;
use const Aviat\AnimeClient\USER_AGENT; use Amp\Http\Client\Body\FormBody;
use function Amp\Promise\wait;
use function Aviat\AnimeClient\getResponse;
use Amp\Http\Client\Request; use Amp\Http\Client\Request;
use Amp\Http\Client\Body\FormBody;
use Aviat\Ion\Json; use Aviat\Ion\Json;
use Error;
use InvalidArgumentException; use InvalidArgumentException;
use Psr\Log\LoggerAwareTrait; use Psr\Log\LoggerAwareTrait;
use Throwable;
use TypeError;
use function Amp\Promise\wait;
use function Aviat\AnimeClient\getResponse;
use const Aviat\AnimeClient\USER_AGENT;
/** /**
* Wrapper around Http\Client to make it easier to build API requests * Wrapper around Http\Client to make it easier to build API requests
*/ */
abstract class APIRequestBuilder { abstract class APIRequestBuilder
{
use LoggerAwareTrait; use LoggerAwareTrait;
/** /**
@ -70,9 +72,6 @@ abstract class APIRequestBuilder {
/** /**
* Do a basic minimal GET request * Do a basic minimal GET request
*
* @param string $uri
* @return Request
*/ */
public static function simpleRequest(string $uri): Request public static function simpleRequest(string $uri): Request
{ {
@ -89,7 +88,6 @@ abstract class APIRequestBuilder {
* *
* @param string $type The type of authorization, eg, basic, bearer, etc. * @param string $type The type of authorization, eg, basic, bearer, etc.
* @param string $value The authorization value * @param string $value The authorization value
* @return self
*/ */
public function setAuth(string $type, string $value): self public function setAuth(string $type, string $value): self
{ {
@ -101,26 +99,21 @@ abstract class APIRequestBuilder {
/** /**
* Set a basic authentication header * Set a basic authentication header
*
* @param string $username
* @param string $password
* @return self
*/ */
public function setBasicAuth(string $username, string $password): self public function setBasicAuth(string $username, string $password): self
{ {
$this->setAuth('basic', base64_encode($username . ':' . $password)); $this->setAuth('basic', base64_encode($username . ':' . $password));
return $this; return $this;
} }
/** /**
* Set the request body * Set the request body
*
* @param FormBody|string $body
* @return self
*/ */
public function setBody(FormBody|string $body): self public function setBody(FormBody|string $body): self
{ {
$this->request->setBody($body); $this->request->setBody($body);
return $this; return $this;
} }
@ -128,7 +121,6 @@ abstract class APIRequestBuilder {
* Set body as form fields * Set body as form fields
* *
* @param array $fields Mapping of field names to values * @param array $fields Mapping of field names to values
* @return self
*/ */
public function setFormFields(array $fields): self public function setFormFields(array $fields): self
{ {
@ -140,24 +132,18 @@ abstract class APIRequestBuilder {
/** /**
* Unset a request header * Unset a request header
*
* @param string $name
* @return self
*/ */
public function unsetHeader(string $name): self public function unsetHeader(string $name): self
{ {
$this->request->removeHeader($name); $this->request->removeHeader($name);
return $this; return $this;
} }
/** /**
* Set a request header * Set a request header
*
* @param string $name
* @param string|null $value
* @return self
*/ */
public function setHeader(string $name, string $value = NULL): self public function setHeader(string $name, ?string $value = NULL): self
{ {
if (NULL === $value) if (NULL === $value)
{ {
@ -175,9 +161,6 @@ abstract class APIRequestBuilder {
* Set multiple request headers * Set multiple request headers
* *
* name => value * name => value
*
* @param array $headers
* @return self
*/ */
public function setHeaders(array $headers): self public function setHeaders(array $headers): self
{ {
@ -191,36 +174,30 @@ abstract class APIRequestBuilder {
/** /**
* Set the request body * Set the request body
*
* @param mixed $body
* @return self
*/ */
public function setJsonBody(mixed $body): self public function setJsonBody(mixed $body): self
{ {
$requestBody = ( ! is_string($body)) $requestBody = (is_string($body))
? Json::encode($body) ? $body
: $body; : Json::encode($body);
return $this->setBody($requestBody); return $this->setBody($requestBody);
} }
/** /**
* Append a query string in array format * Append a query string in array format
*
* @param array $params
* @return self
*/ */
public function setQuery(array $params): self public function setQuery(array $params): self
{ {
$this->query = http_build_query($params); $this->query = http_build_query($params);
return $this; return $this;
} }
/** /**
* Return the promise for the current request * Return the promise for the current request
* *
* @return Request * @throws Throwable
* @throws \Throwable
*/ */
public function getFullRequest(): Request public function getFullRequest(): Request
{ {
@ -235,7 +212,7 @@ abstract class APIRequestBuilder {
$this->request->getBody() $this->request->getBody()
->createBodyStream() ->createBodyStream()
->read() ->read()
) ),
]); ]);
} }
@ -245,25 +222,22 @@ abstract class APIRequestBuilder {
/** /**
* Get the data from the response of the passed request * Get the data from the response of the passed request
* *
* @param Request $request * @throws Error
* @throws Throwable
* @throws TypeError
* @return mixed * @return mixed
* @throws \Error
* @throws \Throwable
* @throws \TypeError
*/ */
public function getResponseData(Request $request) public function getResponseData(Request $request)
{ {
$response = getResponse($request); $response = getResponse($request);
return wait($response->getBody()->buffer()); return wait($response->getBody()->buffer());
} }
/** /**
* Create a new http request * Create a new http request
* *
* @param string $type
* @param string $uri
* @throws InvalidArgumentException * @throws InvalidArgumentException
* @return self
*/ */
public function newRequest(string $type, string $uri): self public function newRequest(string $type, string $uri): self
{ {
@ -292,8 +266,6 @@ abstract class APIRequestBuilder {
/** /**
* Create the full request url * Create the full request url
*
* @return Request
*/ */
private function buildUri(): Request private function buildUri(): Request
{ {
@ -313,10 +285,6 @@ abstract class APIRequestBuilder {
/** /**
* Reset the class state for a new request * Reset the class state for a new request
*
* @param string|null $url
* @param string $type
* @return void
*/ */
private function resetState(?string $url, string $type = 'GET'): void private function resetState(?string $url, string $type = 'GET'): void
{ {
@ -330,4 +298,4 @@ abstract class APIRequestBuilder {
$this->request->setTcpConnectTimeout(300000); $this->request->setTcpConnectTimeout(300000);
$this->request->setTransferTimeout(300000); $this->request->setTransferTimeout(300000);
} }
} }

View File

@ -6,12 +6,10 @@
* *
* PHP version 8 * PHP version 8
* *
* @package HummingbirdAnimeClient * @copyright 2015 - 2022 Timothy J. Warren <tim@timshome.page>
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2021 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License * @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.2 * @version 5.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient * @link https://git.timshome.page/timw4mail/HummingBirdAnimeClient
*/ */
namespace Aviat\AnimeClient\API; namespace Aviat\AnimeClient\API;
@ -22,21 +20,17 @@ use Aviat\AnimeClient\Types\FormItemData;
/** /**
* Common interface for anime and manga list item CRUD * Common interface for anime and manga list item CRUD
*/ */
abstract class AbstractListItem { abstract class AbstractListItem
{
/** /**
* Create a list item * Create a list item
* *
* @param array $data - * @param array $data -
* @return Request
*/ */
abstract public function create(array $data): Request; abstract public function create(array $data): Request;
/** /**
* Create a full list item for syncing * Create a full list item for syncing
*
* @param array $data
* @return Request
*/ */
abstract public function createFull(array $data): Request; abstract public function createFull(array $data): Request;
@ -44,16 +38,12 @@ abstract class AbstractListItem {
* Retrieve a list item * Retrieve a list item
* *
* @param string $id - The id of the list item * @param string $id - The id of the list item
* @return array * @return mixed[]
*/ */
abstract public function get(string $id): array; abstract public function get(string $id): array;
/** /**
* Increase progress on a list item * Increase progress on a list item
*
* @param string $id
* @param FormItemData $data
* @return Request
*/ */
abstract public function increment(string $id, FormItemData $data): Request; abstract public function increment(string $id, FormItemData $data): Request;
@ -62,7 +52,6 @@ abstract class AbstractListItem {
* *
* @param string $id - The id of the list item to update * @param string $id - The id of the list item to update
* @param FormItemData $data - The data with which to update the list item * @param FormItemData $data - The data with which to update the list item
* @return Request
*/ */
abstract public function update(string $id, FormItemData $data): Request; abstract public function update(string $id, FormItemData $data): Request;
@ -70,7 +59,6 @@ abstract class AbstractListItem {
* Delete a list item * Delete a list item
* *
* @param string $id - The id of the list item to delete * @param string $id - The id of the list item to delete
* @return Request|null
*/ */
abstract public function delete(string $id):?Request; abstract public function delete(string $id): ?Request;
} }

View File

@ -6,12 +6,10 @@
* *
* PHP version 8 * PHP version 8
* *
* @package HummingbirdAnimeClient * @copyright 2015 - 2022 Timothy J. Warren <tim@timshome.page>
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2021 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License * @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.2 * @version 5.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient * @link https://git.timshome.page/timw4mail/HummingBirdAnimeClient
*/ */
namespace Aviat\AnimeClient\API\Anilist; namespace Aviat\AnimeClient\API\Anilist;
@ -26,39 +24,32 @@ use Aviat\AnimeClient\Types\FormItemData;
/** /**
* CRUD operations for MAL list items * CRUD operations for MAL list items
*/ */
final class ListItem extends AbstractListItem { final class ListItem extends AbstractListItem
{
use RequestBuilderTrait; use RequestBuilderTrait;
/** /**
* Create a minimal list item * Create a minimal list item
*
* @param array $data
* @return Request
*/ */
public function create(array $data): Request public function create(array $data): Request
{ {
$checkedData = Types\MediaListEntry::check($data); $checkedData = Types\MediaListEntry::check($data);
return $this->requestBuilder->mutateRequest('CreateMediaListEntry', $checkedData ?? []); return $this->requestBuilder->mutateRequest('CreateMediaListEntry', $checkedData ?? []);
} }
/** /**
* Create a fleshed-out list item * Create a fleshed-out list item
*
* @param array $data
* @return Request
*/ */
public function createFull(array $data): Request public function createFull(array $data): Request
{ {
$checkedData = Types\MediaListEntry::check($data); $checkedData = Types\MediaListEntry::check($data);
return $this->requestBuilder->mutateRequest('CreateFullMediaListEntry', $checkedData ?? []); return $this->requestBuilder->mutateRequest('CreateFullMediaListEntry', $checkedData ?? []);
} }
/** /**
* Delete a list item * Delete a list item
*
* @param string $id
* @param string $type
* @return Request
*/ */
public function delete(string $id, string $type = 'anime'): Request public function delete(string $id, string $type = 'anime'): Request
{ {
@ -67,9 +58,6 @@ final class ListItem extends AbstractListItem {
/** /**
* Get the data for a list item * Get the data for a list item
*
* @param string $id
* @return array
*/ */
public function get(string $id): array public function get(string $id): array
{ {
@ -78,10 +66,6 @@ final class ListItem extends AbstractListItem {
/** /**
* Increase the progress on the medium by 1 * Increase the progress on the medium by 1
*
* @param string $id
* @param FormItemData $data
* @return Request
*/ */
public function increment(string $id, FormItemData $data): Request public function increment(string $id, FormItemData $data): Request
{ {
@ -95,31 +79,27 @@ final class ListItem extends AbstractListItem {
/** /**
* Update a list item * Update a list item
*
* @param string $id
* @param FormItemData $data
* @return Request
*/ */
public function update(string $id, FormItemData $data): Request public function update(string $id, FormItemData $data): Request
{ {
$notes = $data->notes ?? ''; $notes = $data->notes ?? '';
$progress = (int)$data->progress; $progress = (int) $data->progress;
$private = (bool)$data->private; $private = (bool) $data->private;
$rating = $data->ratingTwenty; $rating = $data->ratingTwenty;
$status = ($data->reconsuming === TRUE) $status = ($data->reconsuming === TRUE)
? AnilistStatus::REPEATING ? AnilistStatus::REPEATING
: AnimeWatchingStatus::KITSU_TO_ANILIST[$data->status]; : AnimeWatchingStatus::KITSU_TO_ANILIST[$data->status];
$updateData = Types\MediaListEntry::check([ $updateData = Types\MediaListEntry::check([
'id' => (int)$id, 'id' => (int) $id,
'status' => $status, 'status' => $status,
'score' => $rating * 5, 'score' => $rating * 5,
'progress' => $progress, 'progress' => $progress,
'repeat' => (int)$data['reconsumeCount'], 'repeat' => (int) $data['reconsumeCount'],
'private' => $private, 'private' => $private,
'notes' => $notes, 'notes' => $notes,
]); ]);
return $this->requestBuilder->mutateRequest('UpdateMediaListEntry', $updateData ?? []); return $this->requestBuilder->mutateRequest('UpdateMediaListEntry', $updateData ?? []);
} }
} }

View File

@ -6,16 +6,16 @@
* *
* PHP version 8 * PHP version 8
* *
* @package HummingbirdAnimeClient * @copyright 2015 - 2022 Timothy J. Warren <tim@timshome.page>
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2021 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License * @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.2 * @version 5.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient * @link https://git.timshome.page/timw4mail/HummingBirdAnimeClient
*/ */
namespace Aviat\AnimeClient\API\Anilist; namespace Aviat\AnimeClient\API\Anilist;
use InvalidArgumentException; use InvalidArgumentException;
class MissingIdException extends InvalidArgumentException {} class MissingIdException extends InvalidArgumentException
{
}

View File

@ -6,29 +6,25 @@
* *
* PHP version 8 * PHP version 8
* *
* @package HummingbirdAnimeClient * @copyright 2015 - 2022 Timothy J. Warren <tim@timshome.page>
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2021 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License * @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.2 * @version 5.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient * @link https://git.timshome.page/timw4mail/HummingBirdAnimeClient
*/ */
namespace Aviat\AnimeClient\API\Anilist; namespace Aviat\AnimeClient\API\Anilist;
use function Amp\Promise\wait;
use InvalidArgumentException;
use Amp\Http\Client\Request; use Amp\Http\Client\Request;
use Aviat\AnimeClient\Anilist; use Aviat\AnimeClient\Anilist;
use Aviat\AnimeClient\API\Mapping\{AnimeWatchingStatus, MangaReadingStatus}; use Aviat\AnimeClient\API\Mapping\{AnimeWatchingStatus, MangaReadingStatus};
use Aviat\AnimeClient\Types\FormItem; use Aviat\AnimeClient\Types\FormItem;
use Aviat\Ion\Di\Exception\{ContainerException, NotFoundException};
use Aviat\Ion\Json; use Aviat\Ion\Json;
use Aviat\Ion\Di\Exception\ContainerException; use InvalidArgumentException;
use Aviat\Ion\Di\Exception\NotFoundException;
use Throwable; use Throwable;
use function Amp\Promise\wait;
/** /**
* Anilist API Model * Anilist API Model
@ -36,32 +32,24 @@ use Throwable;
final class Model final class Model
{ {
use RequestBuilderTrait; use RequestBuilderTrait;
/**
* @var ListItem
*/
private ListItem $listItem;
/** /**
* Constructor * Constructor
*
* @param ListItem $listItem
*/ */
public function __construct(ListItem $listItem) public function __construct(private ListItem $listItem)
{ {
$this->listItem = $listItem;
} }
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// ! Generic API calls // ! Generic API calls
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
/** /**
* Attempt to get an auth token * Attempt to get an auth token
* *
* @param string $code - The request token * @param string $code - The request token
* @param string $redirectUri - The oauth callback url * @param string $redirectUri - The oauth callback url
* @return array
* @throws Throwable * @throws Throwable
* @return mixed[]
*/ */
public function authenticate(string $code, string $redirectUri): array public function authenticate(string $code, string $redirectUri): array
{ {
@ -84,8 +72,6 @@ final class Model
/** /**
* Check auth status with simple API call * Check auth status with simple API call
*
* @return array
*/ */
public function checkAuth(): array public function checkAuth(): array
{ {
@ -95,8 +81,6 @@ final class Model
/** /**
* Get user list data for syncing with Kitsu * Get user list data for syncing with Kitsu
* *
* @param string $type
* @return array
* @throws ContainerException * @throws ContainerException
* @throws NotFoundException * @throws NotFoundException
*/ */
@ -118,20 +102,10 @@ final class Model
/** /**
* Create a list item * Create a list item
*
* @param array $data
* @param string $type
* @return Request
*/ */
public function createListItem(array $data, string $type = 'anime'): ?Request public function createListItem(array $data, string $type = 'anime'): ?Request
{ {
if ($data['mal_id'] === NULL) $mediaId = $this->getMediaId($data, $type);
{
return NULL;
}
$mediaId = $this->getMediaIdFromMalId($data['mal_id'], mb_strtoupper($type));
if ($mediaId === NULL) if ($mediaId === NULL)
{ {
return NULL; return NULL;
@ -159,15 +133,11 @@ final class Model
/** /**
* Create a list item with all the relevant data * Create a list item with all the relevant data
*
* @param array $data
* @param string $type
* @return Request
*/ */
public function createFullListItem(array $data, string $type): Request public function createFullListItem(array $data, string $type): Request
{ {
$createData = $data['data']; $createData = $data['data'];
$mediaId = $this->getMediaIdFromMalId($data['mal_id'], strtoupper($type)); $mediaId = $this->getMediaId($data, $type);
if (empty($mediaId)) if (empty($mediaId))
{ {
@ -179,39 +149,14 @@ final class Model
return $this->listItem->createFull($createData); return $this->listItem->createFull($createData);
} }
/**
* Get the data for a specific list item, generally for editing
*
* @param string $malId - The unique identifier of that list item
* @param string $type - Them media type (anime/manga)
*
* @return array
*/
public function getListItem(string $malId, string $type): array
{
$id = $this->getListIdFromMalId($malId, $type);
if ($id === NULL)
{
return [];
}
$data = $this->listItem->get($id)['data'];
return ($data !== null)
? $data['MediaList']
: [];
}
/** /**
* Increase the watch count for the current list item * Increase the watch count for the current list item
* *
* @param FormItem $data
* @param string $type - Them media type (anime/manga) * @param string $type - Them media type (anime/manga)
* @return Request|null
*/ */
public function incrementListItem(FormItem $data, string $type): ?Request public function incrementListItem(FormItem $data, string $type): ?Request
{ {
$id = $this->getListIdFromMalId($data['mal_id'], $type); $id = $this->getListIdFromData($data, $type);
if ($id === NULL) if ($id === NULL)
{ {
return NULL; return NULL;
@ -223,14 +168,11 @@ final class Model
/** /**
* Modify a list item * Modify a list item
* *
* @param FormItem $data
* @param string $type - Them media type (anime/manga) * @param string $type - Them media type (anime/manga)
* @return Request|null
*/ */
public function updateListItem(FormItem $data, string $type): ?Request public function updateListItem(FormItem $data, string $type): ?Request
{ {
$id = $this->getListIdFromMalId($data['mal_id'], mb_strtoupper($type)); $id = $this->getListIdFromData($data, $type);
if ($id === NULL) if ($id === NULL)
{ {
return NULL; return NULL;
@ -242,31 +184,32 @@ final class Model
/** /**
* Remove a list item * Remove a list item
* *
* @param string $malId - The id of the list item to remove * @param FormItem $data - The entry to remove
* @param string $type - Them media type (anime/manga) * @param string $type - The media type (anime/manga)
* @return Request|null
*/ */
public function deleteListItem(string $malId, string $type): ?Request public function deleteItem(FormItem $data, string $type): ?Request
{ {
$id = $this->getListIdFromMalId($malId, $type); $mediaId = $this->getMediaId((array)$data, $type);
if ($id === NULL) if ($mediaId === NULL)
{ {
return NULL; return NULL;
} }
return $this->listItem->delete($id); $id = $this->getListIdFromMediaId($mediaId);
if (is_string($id))
{
return $this->listItem->delete($id);
}
return NULL;
} }
/** /**
* Get the id of the specific list entry from the malId * Get the id of the specific list entry from the data
*
* @param string $malId
* @param string $type - The media type (anime/manga)
* @return string|null
*/ */
public function getListIdFromMalId(string $malId, string $type): ?string public function getListIdFromData(FormItem $data, string $type = 'ANIME'): ?string
{ {
$mediaId = $this->getMediaIdFromMalId($malId, $type); $mediaId = $this->getMediaId((array)$data, $type);
if ($mediaId === NULL) if ($mediaId === NULL)
{ {
return NULL; return NULL;
@ -279,9 +222,6 @@ final class Model
* Get the Anilist list item id from the media id from its MAL id * Get the Anilist list item id from the media id from its MAL id
* this way is more accurate than getting the list item id * this way is more accurate than getting the list item id
* directly from the MAL id * directly from the MAL id
*
* @param string $mediaId
* @return string|null
*/ */
private function getListIdFromMediaId(string $mediaId): ?string private function getListIdFromMediaId(string $mediaId): ?string
{ {
@ -298,15 +238,26 @@ final class Model
return NULL; return NULL;
} }
return (string)$info['data']['MediaList']['id']; return (string) $info['data']['MediaList']['id'];
}
/**
* Find the id to update by
*/
private function getMediaId (array $data, string $type = 'ANIME'): ?string
{
if (isset($data['anilist_id']))
{
return $data['anilist_id'];
}
return (isset($data['mal_id']))
? $this->getMediaIdFromMalId($data['mal_id'], mb_strtoupper($type))
: NULL;
} }
/** /**
* Get the Anilist media id from the malId * Get the Anilist media id from the malId
*
* @param string $malId
* @param string $type
* @return string|null
*/ */
private function getMediaIdFromMalId(string $malId, string $type = 'ANIME'): ?string private function getMediaIdFromMalId(string $malId, string $type = 'ANIME'): ?string
{ {
@ -325,6 +276,6 @@ final class Model
return NULL; return NULL;
} }
return (string)$info['data']['Media']['id']; return (string) $info['data']['Media']['id'];
} }
} }

View File

@ -6,52 +6,44 @@
* *
* PHP version 8 * PHP version 8
* *
* @package HummingbirdAnimeClient * @copyright 2015 - 2022 Timothy J. Warren <tim@timshome.page>
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2021 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License * @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.2 * @version 5.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient * @link https://git.timshome.page/timw4mail/HummingBirdAnimeClient
*/ */
namespace Aviat\AnimeClient\API\Anilist; namespace Aviat\AnimeClient\API\Anilist;
use Amp\Http\Client\Request; use Amp\Http\Client\{Request, Response};
use Amp\Http\Client\Response;
use Aviat\AnimeClient\Anilist; use Aviat\AnimeClient\Anilist;
use Aviat\Ion\Di\ContainerAware;
use Aviat\Ion\Di\ContainerInterface;
use Aviat\Ion\Json;
use Aviat\Ion\JsonException;
use function Amp\Promise\wait;
use function Aviat\AnimeClient\getResponse;
use const Aviat\AnimeClient\USER_AGENT;
use Aviat\AnimeClient\API\APIRequestBuilder; use Aviat\AnimeClient\API\APIRequestBuilder;
use Aviat\Ion\Di\{ContainerAware, ContainerInterface};
use Aviat\Ion\{Json, JsonException};
use LogicException; use LogicException;
use Throwable; use Throwable;
final class RequestBuilder extends APIRequestBuilder { use function Amp\Promise\wait;
use function Aviat\AnimeClient\getResponse;
use function in_array;
use const Aviat\AnimeClient\USER_AGENT;
final class RequestBuilder extends APIRequestBuilder
{
use ContainerAware; use ContainerAware;
/** /**
* The base url for api requests * The base url for api requests
* @var string $base_url
*/ */
protected string $baseUrl = Anilist::BASE_URL; protected string $baseUrl = Anilist::BASE_URL;
/** /**
* Valid HTTP request methods * Valid HTTP request methods
* @var array
*/ */
protected array $validMethods = ['POST']; protected array $validMethods = ['POST'];
/** /**
* HTTP headers to send with every request * HTTP headers to send with every request
*
* @var array
*/ */
protected array $defaultHeaders = [ protected array $defaultHeaders = [
'Accept' => 'application/json', 'Accept' => 'application/json',
@ -67,9 +59,6 @@ final class RequestBuilder extends APIRequestBuilder {
/** /**
* Create a request object * Create a request object
* @param string $url
* @param array $options
* @return Request
* @throws Throwable * @throws Throwable
*/ */
public function setUpRequest(string $url, array $options = []): Request public function setUpRequest(string $url, array $options = []): Request
@ -111,10 +100,6 @@ final class RequestBuilder extends APIRequestBuilder {
/** /**
* Run a GraphQL API query * Run a GraphQL API query
*
* @param string $name
* @param array $variables
* @return array
*/ */
public function runQuery(string $name, array $variables = []): array public function runQuery(string $name, array $variables = []): array
{ {
@ -126,30 +111,28 @@ final class RequestBuilder extends APIRequestBuilder {
$query = file_get_contents($file); $query = file_get_contents($file);
$body = [ $body = [
'query' => $query 'query' => $query,
]; ];
if ( ! empty($variables)) if ( ! empty($variables))
{ {
$body['variables'] = []; $body['variables'] = [];
foreach($variables as $key => $val)
foreach ($variables as $key => $val)
{ {
$body['variables'][$key] = $val; $body['variables'][$key] = $val;
} }
} }
return $this->postRequest([ return $this->postRequest([
'body' => $body 'body' => $body,
]); ]);
} }
/** /**
* @param string $name
* @param array $variables
* @return Request
* @throws Throwable * @throws Throwable
*/ */
public function mutateRequest (string $name, array $variables = []): Request public function mutateRequest(string $name, array $variables = []): Request
{ {
$file = __DIR__ . "/Mutations/{$name}.graphql"; $file = __DIR__ . "/Mutations/{$name}.graphql";
if ( ! file_exists($file)) if ( ! file_exists($file))
@ -160,11 +143,13 @@ final class RequestBuilder extends APIRequestBuilder {
$query = file_get_contents($file); $query = file_get_contents($file);
$body = [ $body = [
'query' => $query 'query' => $query,
]; ];
if (!empty($variables)) { if ( ! empty($variables))
{
$body['variables'] = []; $body['variables'] = [];
foreach ($variables as $key => $val) foreach ($variables as $key => $val)
{ {
$body['variables'][$key] = $val; $body['variables'][$key] = $val;
@ -177,12 +162,10 @@ final class RequestBuilder extends APIRequestBuilder {
} }
/** /**
* @param string $name
* @param array $variables
* @return array
* @throws Throwable * @throws Throwable
* @return mixed[]
*/ */
public function mutate (string $name, array $variables = []): array public function mutate(string $name, array $variables = []): array
{ {
$request = $this->mutateRequest($name, $variables); $request = $this->mutateRequest($name, $variables);
$response = $this->getResponseFromRequest($request); $response = $this->getResponseFromRequest($request);
@ -193,9 +176,6 @@ final class RequestBuilder extends APIRequestBuilder {
/** /**
* Make a request * Make a request
* *
* @param string $url
* @param array $options
* @return Response
* @throws Throwable * @throws Throwable
*/ */
private function getResponse(string $url, array $options = []): Response private function getResponse(string $url, array $options = []): Response
@ -220,8 +200,6 @@ final class RequestBuilder extends APIRequestBuilder {
} }
/** /**
* @param Request $request
* @return Response
* @throws Throwable * @throws Throwable
*/ */
public function getResponseFromRequest(Request $request): Response public function getResponseFromRequest(Request $request): Response
@ -247,8 +225,6 @@ final class RequestBuilder extends APIRequestBuilder {
/** /**
* Remove some boilerplate for post requests * Remove some boilerplate for post requests
* *
* @param array $options
* @return array
* @throws Throwable * @throws Throwable
*/ */
protected function postRequest(array $options = []): array protected function postRequest(array $options = []): array
@ -265,13 +241,13 @@ final class RequestBuilder extends APIRequestBuilder {
//'requestHeaders' => $request->getHeaders(), //'requestHeaders' => $request->getHeaders(),
]); ]);
if ( ! in_array($response->getStatus(), $validResponseCodes, TRUE))
if ( ! \in_array($response->getStatus(), $validResponseCodes, TRUE))
{ {
$logger?->warning('Non 200 response for POST api call', (array)$response->getBody()); $logger?->warning('Non 200 response for POST api call', (array) $response->getBody());
} }
$rawBody = wait($response->getBody()->buffer()); $rawBody = wait($response->getBody()->buffer());
try try
{ {
return Json::decode($rawBody); return Json::decode($rawBody);
@ -280,7 +256,8 @@ final class RequestBuilder extends APIRequestBuilder {
{ {
dump($e); dump($e);
dump($rawBody); dump($rawBody);
die();
exit();
} }
} }
} }

View File

@ -6,19 +6,18 @@
* *
* PHP version 8 * PHP version 8
* *
* @package HummingbirdAnimeClient * @copyright 2015 - 2022 Timothy J. Warren <tim@timshome.page>
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2021 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License * @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.2 * @version 5.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient * @link https://git.timshome.page/timw4mail/HummingBirdAnimeClient
*/ */
namespace Aviat\AnimeClient\API\Anilist; namespace Aviat\AnimeClient\API\Anilist;
use Aviat\Ion\Di\ContainerAware; use Aviat\Ion\Di\ContainerAware;
trait RequestBuilderTrait { trait RequestBuilderTrait
{
use ContainerAware; use ContainerAware;
/** /**
@ -28,13 +27,11 @@ trait RequestBuilderTrait {
/** /**
* Set the request builder object * Set the request builder object
*
* @param RequestBuilder $requestBuilder
* @return self
*/ */
public function setRequestBuilder(RequestBuilder $requestBuilder): self public function setRequestBuilder(RequestBuilder $requestBuilder): self
{ {
$this->requestBuilder = $requestBuilder; $this->requestBuilder = $requestBuilder;
return $this; return $this;
} }
} }

View File

@ -6,19 +6,15 @@
* *
* PHP version 8 * PHP version 8
* *
* @package HummingbirdAnimeClient * @copyright 2015 - 2022 Timothy J. Warren <tim@timshome.page>
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2021 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License * @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.2 * @version 5.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient * @link https://git.timshome.page/timw4mail/HummingBirdAnimeClient
*/ */
namespace Aviat\AnimeClient\API\Anilist\Transformer; namespace Aviat\AnimeClient\API\Anilist\Transformer;
use Aviat\AnimeClient\API\Enum\AnimeWatchingStatus\Anilist as AnilistStatus; use Aviat\AnimeClient\API\{Enum, Mapping};
use Aviat\AnimeClient\API\Enum\AnimeWatchingStatus\Kitsu as KitsuStatus;
use Aviat\AnimeClient\API\Mapping\AnimeWatchingStatus;
use Aviat\AnimeClient\Types\{AnimeListItem, FormItem}; use Aviat\AnimeClient\Types\{AnimeListItem, FormItem};
use Aviat\Ion\Transformer\AbstractTransformer; use Aviat\Ion\Transformer\AbstractTransformer;
@ -26,8 +22,8 @@ use Aviat\Ion\Transformer\AbstractTransformer;
use DateTime; use DateTime;
use DateTimeInterface; use DateTimeInterface;
class AnimeListTransformer extends AbstractTransformer { class AnimeListTransformer extends AbstractTransformer
{
public function transform(array|object $item): AnimeListItem public function transform(array|object $item): AnimeListItem
{ {
return AnimeListItem::from([]); return AnimeListItem::from([]);
@ -35,13 +31,10 @@ class AnimeListTransformer extends AbstractTransformer {
/** /**
* Transform Anilist list item to Kitsu form update format * Transform Anilist list item to Kitsu form update format
*
* @param array $item
* @return FormItem
*/ */
public function untransform(array $item): FormItem public function untransform(array $item): FormItem
{ {
$reconsuming = $item['status'] === AnilistStatus::REPEATING; $reconsuming = $item['status'] === Enum\AnimeWatchingStatus\Anilist::REPEATING;
return FormItem::from([ return FormItem::from([
'id' => $item['id'], 'id' => $item['id'],
@ -54,12 +47,12 @@ class AnimeListTransformer extends AbstractTransformer {
'reconsumeCount' => $item['repeat'], 'reconsumeCount' => $item['repeat'],
'reconsuming' => $reconsuming, 'reconsuming' => $reconsuming,
'status' => $reconsuming 'status' => $reconsuming
? KitsuStatus::WATCHING ? Enum\AnimeWatchingStatus\Kitsu::WATCHING
: AnimeWatchingStatus::ANILIST_TO_KITSU[$item['status']], : Mapping\AnimeWatchingStatus::ANILIST_TO_KITSU[$item['status']],
'updatedAt' => (new DateTime()) 'updatedAt' => (new DateTime())
->setTimestamp($item['updatedAt']) ->setTimestamp($item['updatedAt'])
->format(DateTimeInterface::W3C) ->format(DateTimeInterface::W3C),
], ],
]); ]);
} }
} }

View File

@ -6,29 +6,23 @@
* *
* PHP version 8 * PHP version 8
* *
* @package HummingbirdAnimeClient * @copyright 2015 - 2022 Timothy J. Warren <tim@timshome.page>
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2021 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License * @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.2 * @version 5.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient * @link https://git.timshome.page/timw4mail/HummingBirdAnimeClient
*/ */
namespace Aviat\AnimeClient\API\Anilist\Transformer; namespace Aviat\AnimeClient\API\Anilist\Transformer;
use Aviat\AnimeClient\API\Enum\MangaReadingStatus\Anilist as AnilistStatus; use Aviat\AnimeClient\API\{Enum, Mapping};
use Aviat\AnimeClient\API\Enum\MangaReadingStatus\Kitsu as KitsuStatus; use Aviat\AnimeClient\Types\{FormItem, MangaListItem};
use Aviat\AnimeClient\API\Mapping\MangaReadingStatus;
use Aviat\AnimeClient\Types\MangaListItem;
use Aviat\AnimeClient\Types\FormItem;
use Aviat\Ion\Transformer\AbstractTransformer; use Aviat\Ion\Transformer\AbstractTransformer;
use DateTime; use DateTime;
use DateTimeInterface; use DateTimeInterface;
class MangaListTransformer extends AbstractTransformer { class MangaListTransformer extends AbstractTransformer
{
public function transform(array|object $item): MangaListItem public function transform(array|object $item): MangaListItem
{ {
return MangaListItem::from([]); return MangaListItem::from([]);
@ -36,13 +30,10 @@ class MangaListTransformer extends AbstractTransformer {
/** /**
* Transform Anilist list item to Kitsu form update format * Transform Anilist list item to Kitsu form update format
*
* @param array $item
* @return FormItem
*/ */
public function untransform(array $item): FormItem public function untransform(array $item): FormItem
{ {
$reconsuming = $item['status'] === AnilistStatus::REPEATING; $reconsuming = $item['status'] === Enum\MangaReadingStatus\Anilist::REPEATING;
return FormItem::from([ return FormItem::from([
'id' => $item['id'], 'id' => $item['id'],
@ -55,12 +46,12 @@ class MangaListTransformer extends AbstractTransformer {
'reconsumeCount' => $item['repeat'], 'reconsumeCount' => $item['repeat'],
'reconsuming' => $reconsuming, 'reconsuming' => $reconsuming,
'status' => $reconsuming 'status' => $reconsuming
? KitsuStatus::READING ? Enum\MangaReadingStatus\Kitsu::READING
: MangaReadingStatus::ANILIST_TO_KITSU[$item['status']], : Mapping\MangaReadingStatus::ANILIST_TO_KITSU[$item['status']],
'updatedAt' => (new DateTime()) 'updatedAt' => (new DateTime())
->setTimestamp($item['updatedAt']) ->setTimestamp($item['updatedAt'])
->format(DateTimeInterface::W3C), ->format(DateTimeInterface::W3C),
] ],
]); ]);
} }
} }

View File

@ -6,31 +6,23 @@
* *
* PHP version 8 * PHP version 8
* *
* @package HummingbirdAnimeClient * @copyright 2015 - 2022 Timothy J. Warren <tim@timshome.page>
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2021 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License * @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.2 * @version 5.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient * @link https://git.timshome.page/timw4mail/HummingBirdAnimeClient
*/ */
namespace Aviat\AnimeClient\API\Anilist\Types; namespace Aviat\AnimeClient\API\Anilist\Types;
use Aviat\AnimeClient\Types\AbstractType; use Aviat\AnimeClient\Types\AbstractType;
class MediaListEntry extends AbstractType { class MediaListEntry extends AbstractType
{
public int|string $id; public int|string $id;
public ?string $notes; public ?string $notes;
public ?bool $private; public ?bool $private;
public int $progress; public int $progress;
public ?int $repeat; public ?int $repeat;
public string $status; public string $status;
public ?int $score; public ?int $score;
} }

File diff suppressed because it is too large Load Diff

View File

@ -6,12 +6,10 @@
* *
* PHP version 8 * PHP version 8
* *
* @package HummingbirdAnimeClient * @copyright 2015 - 2022 Timothy J. Warren <tim@timshome.page>
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2021 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License * @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.2 * @version 5.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient * @link https://git.timshome.page/timw4mail/HummingBirdAnimeClient
*/ */
namespace Aviat\AnimeClient\API; namespace Aviat\AnimeClient\API;
@ -21,29 +19,22 @@ use Psr\SimpleCache\CacheInterface;
/** /**
* Helper methods for dealing with the Cache * Helper methods for dealing with the Cache
*/ */
trait CacheTrait { trait CacheTrait
{
/**
* @var CacheInterface
*/
protected CacheInterface $cache; protected CacheInterface $cache;
/** /**
* Inject the cache object * Inject the cache object
*
* @param CacheInterface $cache
* @return self
*/ */
public function setCache(CacheInterface $cache): self public function setCache(CacheInterface $cache): self
{ {
$this->cache = $cache; $this->cache = $cache;
return $this; return $this;
} }
/** /**
* Get the cache object if it exists * Get the cache object if it exists
*
* @return CacheInterface
*/ */
public function getCache(): CacheInterface public function getCache(): CacheInterface
{ {
@ -53,11 +44,6 @@ trait CacheTrait {
/** /**
* Get the cached value if it exists, otherwise set the cache value * Get the cached value if it exists, otherwise set the cache value
* and return it. * and return it.
*
* @param string $key
* @param callable $primer
* @param array|null $primeArgs
* @return mixed
*/ */
public function getCached(string $key, callable $primer, ?array $primeArgs = []): mixed public function getCached(string $key, callable $primer, ?array $primeArgs = []): mixed
{ {
@ -77,4 +63,4 @@ trait CacheTrait {
return $value; return $value;
} }
} }

View File

@ -6,12 +6,10 @@
* *
* PHP version 8 * PHP version 8
* *
* @package HummingbirdAnimeClient * @copyright 2015 - 2022 Timothy J. Warren <tim@timshome.page>
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2021 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License * @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.2 * @version 5.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient * @link https://git.timshome.page/timw4mail/HummingBirdAnimeClient
*/ */
namespace Aviat\AnimeClient\API\Enum\AnimeWatchingStatus; namespace Aviat\AnimeClient\API\Enum\AnimeWatchingStatus;
@ -21,11 +19,12 @@ use Aviat\Ion\Enum;
/** /**
* Possible values for watching status for the current anime * Possible values for watching status for the current anime
*/ */
final class Anilist extends Enum { final class Anilist extends Enum
{
public const WATCHING = 'CURRENT'; public const WATCHING = 'CURRENT';
public const COMPLETED = 'COMPLETED'; public const COMPLETED = 'COMPLETED';
public const ON_HOLD = 'PAUSED'; public const ON_HOLD = 'PAUSED';
public const DROPPED = 'DROPPED'; public const DROPPED = 'DROPPED';
public const PLAN_TO_WATCH = 'PLANNING'; public const PLAN_TO_WATCH = 'PLANNING';
public const REPEATING = 'REPEATING'; public const REPEATING = 'REPEATING';
} }

View File

@ -6,12 +6,10 @@
* *
* PHP version 8 * PHP version 8
* *
* @package HummingbirdAnimeClient * @copyright 2015 - 2022 Timothy J. Warren <tim@timshome.page>
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2021 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License * @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.2 * @version 5.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient * @link https://git.timshome.page/timw4mail/HummingBirdAnimeClient
*/ */
namespace Aviat\AnimeClient\API\Enum\AnimeWatchingStatus; namespace Aviat\AnimeClient\API\Enum\AnimeWatchingStatus;
@ -21,10 +19,11 @@ use Aviat\Ion\Enum;
/** /**
* Possible values for watching status for the current anime * Possible values for watching status for the current anime
*/ */
final class Kitsu extends Enum { final class Kitsu extends Enum
{
public const WATCHING = 'current'; public const WATCHING = 'current';
public const PLAN_TO_WATCH = 'planned'; public const PLAN_TO_WATCH = 'planned';
public const ON_HOLD = 'on_hold'; public const ON_HOLD = 'on_hold';
public const DROPPED = 'dropped'; public const DROPPED = 'dropped';
public const COMPLETED = 'completed'; public const COMPLETED = 'completed';
} }

View File

@ -6,12 +6,10 @@
* *
* PHP version 8 * PHP version 8
* *
* @package HummingbirdAnimeClient * @copyright 2015 - 2022 Timothy J. Warren <tim@timshome.page>
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2021 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License * @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.2 * @version 5.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient * @link https://git.timshome.page/timw4mail/HummingBirdAnimeClient
*/ */
namespace Aviat\AnimeClient\API\Enum\AnimeWatchingStatus; namespace Aviat\AnimeClient\API\Enum\AnimeWatchingStatus;
@ -21,7 +19,8 @@ use Aviat\Ion\Enum;
/** /**
* Possible values for current watching status of anime * Possible values for current watching status of anime
*/ */
final class Route extends Enum { final class Route extends Enum
{
public const ALL = 'all'; public const ALL = 'all';
public const WATCHING = 'watching'; public const WATCHING = 'watching';
public const PLAN_TO_WATCH = 'plan_to_watch'; public const PLAN_TO_WATCH = 'plan_to_watch';

View File

@ -6,12 +6,10 @@
* *
* PHP version 8 * PHP version 8
* *
* @package HummingbirdAnimeClient * @copyright 2015 - 2022 Timothy J. Warren <tim@timshome.page>
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2021 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License * @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.2 * @version 5.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient * @link https://git.timshome.page/timw4mail/HummingBirdAnimeClient
*/ */
namespace Aviat\AnimeClient\API\Enum\AnimeWatchingStatus; namespace Aviat\AnimeClient\API\Enum\AnimeWatchingStatus;
@ -21,7 +19,8 @@ use Aviat\Ion\Enum;
/** /**
* Possible values for current watching status of anime * Possible values for current watching status of anime
*/ */
final class Title extends Enum { final class Title extends Enum
{
public const ALL = 'All'; public const ALL = 'All';
public const WATCHING = 'Currently Watching'; public const WATCHING = 'Currently Watching';
public const PLAN_TO_WATCH = 'Plan to Watch'; public const PLAN_TO_WATCH = 'Plan to Watch';

View File

@ -6,12 +6,10 @@
* *
* PHP version 8 * PHP version 8
* *
* @package HummingbirdAnimeClient * @copyright 2015 - 2022 Timothy J. Warren <tim@timshome.page>
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2021 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License * @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.2 * @version 5.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient * @link https://git.timshome.page/timw4mail/HummingBirdAnimeClient
*/ */
namespace Aviat\AnimeClient\API\Enum\MangaReadingStatus; namespace Aviat\AnimeClient\API\Enum\MangaReadingStatus;
@ -21,11 +19,12 @@ use Aviat\Ion\Enum;
/** /**
* Possible values for watching status for the current anime * Possible values for watching status for the current anime
*/ */
final class Anilist extends Enum { final class Anilist extends Enum
{
public const READING = 'CURRENT'; public const READING = 'CURRENT';
public const COMPLETED = 'COMPLETED'; public const COMPLETED = 'COMPLETED';
public const ON_HOLD = 'PAUSED'; public const ON_HOLD = 'PAUSED';
public const DROPPED = 'DROPPED'; public const DROPPED = 'DROPPED';
public const PLAN_TO_READ = 'PLANNING'; public const PLAN_TO_READ = 'PLANNING';
public const REPEATING = 'REPEATING'; public const REPEATING = 'REPEATING';
} }

View File

@ -6,12 +6,10 @@
* *
* PHP version 8 * PHP version 8
* *
* @package HummingbirdAnimeClient * @copyright 2015 - 2022 Timothy J. Warren <tim@timshome.page>
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2021 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License * @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.2 * @version 5.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient * @link https://git.timshome.page/timw4mail/HummingBirdAnimeClient
*/ */
namespace Aviat\AnimeClient\API\Enum\MangaReadingStatus; namespace Aviat\AnimeClient\API\Enum\MangaReadingStatus;
@ -21,10 +19,11 @@ use Aviat\Ion\Enum;
/** /**
* Possible values for current reading status of manga * Possible values for current reading status of manga
*/ */
final class Kitsu extends Enum { final class Kitsu extends Enum
{
public const READING = 'current'; public const READING = 'current';
public const PLAN_TO_READ = 'planned'; public const PLAN_TO_READ = 'planned';
public const DROPPED = 'dropped'; public const DROPPED = 'dropped';
public const ON_HOLD = 'on_hold'; public const ON_HOLD = 'on_hold';
public const COMPLETED = 'completed'; public const COMPLETED = 'completed';
} }

View File

@ -6,12 +6,10 @@
* *
* PHP version 8 * PHP version 8
* *
* @package HummingbirdAnimeClient * @copyright 2015 - 2022 Timothy J. Warren <tim@timshome.page>
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2021 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License * @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.2 * @version 5.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient * @link https://git.timshome.page/timw4mail/HummingBirdAnimeClient
*/ */
namespace Aviat\AnimeClient\API\Enum\MangaReadingStatus; namespace Aviat\AnimeClient\API\Enum\MangaReadingStatus;
@ -21,7 +19,8 @@ use Aviat\Ion\Enum;
/** /**
* Possible values for current reading status of manga * Possible values for current reading status of manga
*/ */
final class Route extends Enum { final class Route extends Enum
{
public const ALL = 'all'; public const ALL = 'all';
public const READING = 'reading'; public const READING = 'reading';
public const PLAN_TO_READ = 'plan_to_read'; public const PLAN_TO_READ = 'plan_to_read';

View File

@ -6,12 +6,10 @@
* *
* PHP version 8 * PHP version 8
* *
* @package HummingbirdAnimeClient * @copyright 2015 - 2022 Timothy J. Warren <tim@timshome.page>
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2021 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License * @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.2 * @version 5.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient * @link https://git.timshome.page/timw4mail/HummingBirdAnimeClient
*/ */
namespace Aviat\AnimeClient\API\Enum\MangaReadingStatus; namespace Aviat\AnimeClient\API\Enum\MangaReadingStatus;
@ -21,7 +19,8 @@ use Aviat\Ion\Enum;
/** /**
* Possible values for current reading status of manga * Possible values for current reading status of manga
*/ */
final class Title extends Enum { final class Title extends Enum
{
public const ALL = 'All'; public const ALL = 'All';
public const READING = 'Currently Reading'; public const READING = 'Currently Reading';
public const PLAN_TO_READ = 'Plan to Read'; public const PLAN_TO_READ = 'Plan to Read';

View File

@ -6,12 +6,10 @@
* *
* PHP version 8 * PHP version 8
* *
* @package HummingbirdAnimeClient * @copyright 2015 - 2022 Timothy J. Warren <tim@timshome.page>
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2021 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License * @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.2 * @version 5.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient * @link https://git.timshome.page/timw4mail/HummingBirdAnimeClient
*/ */
namespace Aviat\AnimeClient\API; namespace Aviat\AnimeClient\API;
@ -21,6 +19,6 @@ use UnexpectedValueException;
/** /**
* Exception for an API Request that fails validation * Exception for an API Request that fails validation
*/ */
class FailedResponseException extends UnexpectedValueException { class FailedResponseException extends UnexpectedValueException
{
} }

View File

@ -6,50 +6,43 @@
* *
* PHP version 8 * PHP version 8
* *
* @package HummingbirdAnimeClient * @copyright 2015 - 2022 Timothy J. Warren <tim@timshome.page>
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2021 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License * @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.2 * @version 5.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient * @link https://git.timshome.page/timw4mail/HummingBirdAnimeClient
*/ */
namespace Aviat\AnimeClient\API\Kitsu; namespace Aviat\AnimeClient\API\Kitsu;
use Aura\Session\Segment; use Aura\Session\Segment;
use const Aviat\AnimeClient\SESSION_SEGMENT; use Aviat\AnimeClient\API\CacheTrait;
use Aviat\AnimeClient\Kitsu as K; use Aviat\AnimeClient\Kitsu as K;
use Aviat\AnimeClient\API\CacheTrait;
use Aviat\Ion\Di\{ContainerAware, ContainerInterface}; use Aviat\Ion\Di\{ContainerAware, ContainerInterface};
use Aviat\Ion\Event; use Aviat\Ion\Event;
use const Aviat\AnimeClient\SESSION_SEGMENT;
/** /**
* Kitsu API Authentication * Kitsu API Authentication
*/ */
final class Auth { final class Auth
{
use CacheTrait; use CacheTrait;
use ContainerAware; use ContainerAware;
/** /**
* Anime API Model * Anime API Model
*
* @var Model
*/ */
private Model $model; private Model $model;
/** /**
* Session object * Session object
*
* @var Segment
*/ */
private Segment $segment; private Segment $segment;
/** /**
* Constructor * Constructor
*
* @param ContainerInterface $container
*/ */
public function __construct(ContainerInterface $container) public function __construct(ContainerInterface $container)
{ {
@ -65,9 +58,6 @@ final class Auth {
/** /**
* Make the appropriate authentication call, * Make the appropriate authentication call,
* and save the resulting auth token if successful * and save the resulting auth token if successful
*
* @param string $password
* @return boolean
*/ */
public function authenticate(string $password): bool public function authenticate(string $password): bool
{ {
@ -81,9 +71,6 @@ final class Auth {
/** /**
* Make the call to re-authenticate with the existing refresh token * Make the call to re-authenticate with the existing refresh token
*
* @param string|null $refreshToken
* @return boolean
*/ */
public function reAuthenticate(?string $refreshToken = NULL): bool public function reAuthenticate(?string $refreshToken = NULL): bool
{ {
@ -101,18 +88,14 @@ final class Auth {
/** /**
* Check whether the current user is authenticated * Check whether the current user is authenticated
*
* @return boolean
*/ */
public function isAuthenticated(): bool public function isAuthenticated(): bool
{ {
return ($this->getAuthToken() !== NULL); return $this->getAuthToken() !== NULL;
} }
/** /**
* Clear authentication values * Clear authentication values
*
* @return void
*/ */
public function logout(): void public function logout(): void
{ {
@ -121,8 +104,6 @@ final class Auth {
/** /**
* Retrieve the authentication token from the session * Retrieve the authentication token from the session
*
* @return string|null
*/ */
public function getAuthToken(): ?string public function getAuthToken(): ?string
{ {
@ -137,8 +118,6 @@ final class Auth {
/** /**
* Retrieve the refresh token * Retrieve the refresh token
*
* @return string|null
*/ */
private function getRefreshToken(): ?string private function getRefreshToken(): ?string
{ {
@ -153,11 +132,8 @@ final class Auth {
/** /**
* Save the new authentication information * Save the new authentication information
*
* @param array|false $auth
* @return bool
*/ */
private function storeAuth(array|false $auth): bool private function storeAuth(array|FALSE $auth): bool
{ {
if (FALSE !== $auth) if (FALSE !== $auth)
{ {
@ -178,6 +154,7 @@ final class Auth {
$this->segment->set('auth_token', $auth['access_token']); $this->segment->set('auth_token', $auth['access_token']);
$this->segment->set('auth_token_expires', $expire_time); $this->segment->set('auth_token_expires', $expire_time);
$this->segment->set('refresh_token', $auth['refresh_token']); $this->segment->set('refresh_token', $auth['refresh_token']);
return TRUE; return TRUE;
} }
} }
@ -185,4 +162,5 @@ final class Auth {
return FALSE; return FALSE;
} }
} }
// End of KitsuAuth.php
// End of KitsuAuth.php

View File

@ -6,12 +6,10 @@
* *
* PHP version 8 * PHP version 8
* *
* @package HummingbirdAnimeClient * @copyright 2015 - 2022 Timothy J. Warren <tim@timshome.page>
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2021 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License * @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.2 * @version 5.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient * @link https://git.timshome.page/timw4mail/HummingBirdAnimeClient
*/ */
namespace Aviat\AnimeClient\API\Kitsu\Enum; namespace Aviat\AnimeClient\API\Kitsu\Enum;
@ -21,7 +19,8 @@ use Aviat\Ion\Enum as BaseEnum;
/** /**
* Status of when anime is being/was/will be aired * Status of when anime is being/was/will be aired
*/ */
final class AnimeAiringStatus extends BaseEnum { final class AnimeAiringStatus extends BaseEnum
{
public const NOT_YET_AIRED = 'Not Yet Aired'; public const NOT_YET_AIRED = 'Not Yet Aired';
public const AIRING = 'Currently Airing'; public const AIRING = 'Currently Airing';
public const FINISHED_AIRING = 'Finished Airing'; public const FINISHED_AIRING = 'Finished Airing';

View File

@ -6,12 +6,10 @@
* *
* PHP version 8 * PHP version 8
* *
* @package HummingbirdAnimeClient * @copyright 2015 - 2022 Timothy J. Warren <tim@timshome.page>
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2021 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License * @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.2 * @version 5.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient * @link https://git.timshome.page/timw4mail/HummingBirdAnimeClient
*/ */
namespace Aviat\AnimeClient\API\Kitsu\Enum; namespace Aviat\AnimeClient\API\Kitsu\Enum;
@ -21,7 +19,8 @@ use Aviat\Ion\Enum as BaseEnum;
/** /**
* Status of when anime is being/was/will be aired * Status of when anime is being/was/will be aired
*/ */
final class MangaPublishingStatus extends BaseEnum { final class MangaPublishingStatus extends BaseEnum
{
public const NOT_YET_PUBLISHED = 'Not Yet Published'; public const NOT_YET_PUBLISHED = 'Not Yet Published';
public const FINISHED = 'Completed'; public const FINISHED = 'Completed';
public const CURRENT = 'Current'; public const CURRENT = 'Current';

View File

@ -6,12 +6,10 @@
* *
* PHP version 8 * PHP version 8
* *
* @package HummingbirdAnimeClient * @copyright 2015 - 2022 Timothy J. Warren <tim@timshome.page>
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2021 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License * @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.2 * @version 5.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient * @link https://git.timshome.page/timw4mail/HummingBirdAnimeClient
*/ */
namespace Aviat\AnimeClient\API\Kitsu; namespace Aviat\AnimeClient\API\Kitsu;
@ -26,13 +24,12 @@ use Throwable;
/** /**
* CRUD operations for Kitsu list items * CRUD operations for Kitsu list items
*/ */
final class ListItem extends AbstractListItem { final class ListItem extends AbstractListItem
{
use ContainerAware; use ContainerAware;
use RequestBuilderTrait; use RequestBuilderTrait;
/** /**
* @param array $data
* @return Request
* @throws Throwable * @throws Throwable
*/ */
public function create(array $data): Request public function create(array $data): Request
@ -52,23 +49,23 @@ final class ListItem extends AbstractListItem {
'type' => 'libraryEntries', 'type' => 'libraryEntries',
'attributes' => [ 'attributes' => [
'status' => $data['status'], 'status' => $data['status'],
'progress' => $data['progress'] ?? 0 'progress' => $data['progress'] ?? 0,
], ],
'relationships' => [ 'relationships' => [
'user' => [ 'user' => [
'data' => [ 'data' => [
'id' => $data['user_id'], 'id' => $data['user_id'],
'type' => 'users' 'type' => 'users',
] ],
], ],
'media' => [ 'media' => [
'data' => [ 'data' => [
'id' => $data['id'], 'id' => $data['id'],
'type' => $data['type'] 'type' => $data['type'],
] ],
] ],
] ],
] ],
]; ];
if (array_key_exists('notes', $data)) if (array_key_exists('notes', $data))
@ -90,21 +87,18 @@ final class ListItem extends AbstractListItem {
} }
/** /**
* @param string $id
* @return Request
* @throws Throwable * @throws Throwable
*/ */
public function delete(string $id): Request public function delete(string $id): Request
{ {
return $this->requestBuilder->mutateRequest('DeleteLibraryItem', [ return $this->requestBuilder->mutateRequest('DeleteLibraryItem', [
'id' => $id 'id' => $id,
]); ]);
} }
/** /**
* @param string $id
* @return array
* @throws Throwable * @throws Throwable
* @return mixed[]
*/ */
public function get(string $id): array public function get(string $id): array
{ {
@ -115,23 +109,16 @@ final class ListItem extends AbstractListItem {
/** /**
* Increase the progress on the medium by 1 * Increase the progress on the medium by 1
*
* @param string $id
* @param FormItemData $data
* @return Request
*/ */
public function increment(string $id, FormItemData $data): Request public function increment(string $id, FormItemData $data): Request
{ {
return $this->requestBuilder->mutateRequest('IncrementLibraryItem', [ return $this->requestBuilder->mutateRequest('IncrementLibraryItem', [
'id' => $id, 'id' => $id,
'progress' => $data->progress 'progress' => $data->progress,
]); ]);
} }
/** /**
* @param string $id
* @param FormItemData $data
* @return Request
* @throws Throwable * @throws Throwable
*/ */
public function update(string $id, FormItemData $data): Request public function update(string $id, FormItemData $data): Request
@ -140,20 +127,21 @@ final class ListItem extends AbstractListItem {
$updateData = [ $updateData = [
'id' => $id, 'id' => $id,
'notes' => $data['notes'], 'notes' => $data['notes'],
'private' => (bool)$data['private'], 'private' => (bool) $data['private'],
'reconsumeCount' => (int)$data['reconsumeCount'], 'reconsumeCount' => (int) $data['reconsumeCount'],
'reconsuming' => (bool)$data['reconsuming'], 'reconsuming' => (bool) $data['reconsuming'],
'status' => strtoupper($data['status']), 'status' => strtoupper($data['status']),
]; ];
// Only send these variables if they have a value // Only send these variables if they have a value
if ($data['progress'] !== NULL) if ($data['progress'] !== NULL)
{ {
$updateData['progress'] = (int)$data['progress']; $updateData['progress'] = (int) $data['progress'];
} }
if ($data['ratingTwenty'] !== NULL) if ($data['ratingTwenty'] !== NULL)
{ {
$updateData['ratingTwenty'] = (int)$data['ratingTwenty']; $updateData['ratingTwenty'] = (int) $data['ratingTwenty'];
} }
return $this->requestBuilder->mutateRequest('UpdateLibraryItem', $updateData); return $this->requestBuilder->mutateRequest('UpdateLibraryItem', $updateData);
@ -164,10 +152,11 @@ final class ListItem extends AbstractListItem {
$auth = $this->getContainer()->get('auth'); $auth = $this->getContainer()->get('auth');
$token = $auth->getAuthToken(); $token = $auth->getAuthToken();
if ( ! empty($token)) { if ( ! empty($token))
{
return "bearer {$token}"; return "bearer {$token}";
} }
return NULL; return NULL;
} }
} }

View File

@ -6,24 +6,15 @@
* *
* PHP version 8 * PHP version 8
* *
* @package HummingbirdAnimeClient * @copyright 2015 - 2022 Timothy J. Warren <tim@timshome.page>
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2021 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License * @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.2 * @version 5.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient * @link https://git.timshome.page/timw4mail/HummingBirdAnimeClient
*/ */
namespace Aviat\AnimeClient\API\Kitsu; namespace Aviat\AnimeClient\API\Kitsu;
use Amp; use Amp;
use Aviat\AnimeClient\API\{
CacheTrait,
Enum\AnimeWatchingStatus\Kitsu as KitsuWatchingStatus,
Enum\MangaReadingStatus\Kitsu as KitsuReadingStatus,
Mapping\AnimeWatchingStatus,
Mapping\MangaReadingStatus
};
use Aviat\AnimeClient\API\Kitsu\Transformer\{ use Aviat\AnimeClient\API\Kitsu\Transformer\{
AnimeHistoryTransformer, AnimeHistoryTransformer,
AnimeListTransformer, AnimeListTransformer,
@ -33,10 +24,16 @@ use Aviat\AnimeClient\API\Kitsu\Transformer\{
MangaListTransformer, MangaListTransformer,
MangaTransformer MangaTransformer
}; };
use Aviat\AnimeClient\API\{
CacheTrait,
Enum\AnimeWatchingStatus\Kitsu as KitsuWatchingStatus,
Enum\MangaReadingStatus\Kitsu as KitsuReadingStatus,
Mapping\AnimeWatchingStatus,
Mapping\MangaReadingStatus
};
use Aviat\AnimeClient\Enum\MediaType; use Aviat\AnimeClient\Enum\MediaType;
use Aviat\AnimeClient\Kitsu as K; use Aviat\AnimeClient\Kitsu as K;
use Aviat\AnimeClient\Types\Anime; use Aviat\AnimeClient\Types\{Anime, MangaPage};
use Aviat\AnimeClient\Types\MangaPage;
use Aviat\Ion\{ use Aviat\Ion\{
Di\ContainerAware, Di\ContainerAware,
Json Json
@ -49,7 +46,8 @@ use const Aviat\AnimeClient\SESSION_SEGMENT;
/** /**
* Kitsu API Model * Kitsu API Model
*/ */
final class Model { final class Model
{
use CacheTrait; use CacheTrait;
use ContainerAware; use ContainerAware;
use RequestBuilderTrait; use RequestBuilderTrait;
@ -57,40 +55,20 @@ final class Model {
protected const LIST_PAGE_SIZE = 100; protected const LIST_PAGE_SIZE = 100;
/**
* @var AnimeTransformer
*/
protected AnimeTransformer $animeTransformer; protected AnimeTransformer $animeTransformer;
/**
* @var MangaTransformer
*/
protected MangaTransformer $mangaTransformer; protected MangaTransformer $mangaTransformer;
/**
* @var ListItem
*/
protected ListItem $listItem;
/** /**
* Constructor * Constructor
*
* @param ListItem $listItem
*/ */
public function __construct(ListItem $listItem) public function __construct(protected ListItem $listItem)
{ {
$this->animeTransformer = new AnimeTransformer(); $this->animeTransformer = new AnimeTransformer();
$this->mangaTransformer = new MangaTransformer(); $this->mangaTransformer = new MangaTransformer();
$this->listItem = $listItem;
} }
/** /**
* Get the access token from the Kitsu API * Get the access token from the Kitsu API
*
* @param string $username
* @param string $password
* @return array|false
*/ */
public function authenticate(string $username, string $password): array|false public function authenticate(string $username, string $password): array|false
{ {
@ -100,24 +78,25 @@ final class Model {
'accept' => NULL, 'accept' => NULL,
'Content-type' => 'application/x-www-form-urlencoded', 'Content-type' => 'application/x-www-form-urlencoded',
'client_id' => NULL, 'client_id' => NULL,
'client_secret' => NULL 'client_secret' => NULL,
], ],
'form_params' => [ 'form_params' => [
'grant_type' => 'password', 'grant_type' => 'password',
'username' => $username, 'username' => $username,
'password' => $password 'password' => $password,
] ],
]); ]);
$data = Json::decode(wait($response->getBody()->buffer())); $data = Json::decode(wait($response->getBody()->buffer()));
if (array_key_exists('error', $data)) if (array_key_exists('error', $data))
{ {
dump([ dump([
'method' => __CLASS__ . '\\' . __METHOD__, 'method' => self::class . '\\' . __METHOD__,
'error' => $data['error'], 'error' => $data['error'],
'response' => $response, 'response' => $response,
]); ]);
die();
exit();
} }
if (array_key_exists('access_token', $data)) if (array_key_exists('access_token', $data))
@ -130,9 +109,6 @@ final class Model {
/** /**
* Extend the current session with a refresh token * Extend the current session with a refresh token
*
* @param string $token
* @return array|false
*/ */
public function reAuthenticate(string $token): array|false public function reAuthenticate(string $token): array|false
{ {
@ -140,23 +116,24 @@ final class Model {
'headers' => [ 'headers' => [
'accept' => NULL, 'accept' => NULL,
'Content-type' => 'application/x-www-form-urlencoded', 'Content-type' => 'application/x-www-form-urlencoded',
'Accept-encoding' => '*' 'Accept-encoding' => '*',
], ],
'form_params' => [ 'form_params' => [
'grant_type' => 'refresh_token', 'grant_type' => 'refresh_token',
'refresh_token' => $token 'refresh_token' => $token,
] ],
]); ]);
$data = Json::decode(wait($response->getBody()->buffer())); $data = Json::decode(wait($response->getBody()->buffer()));
if (array_key_exists('error', $data)) if (array_key_exists('error', $data))
{ {
dump([ dump([
'method' => __CLASS__ . '\\' . __METHOD__, 'method' => self::class . '\\' . __METHOD__,
'error' => $data['error'], 'error' => $data['error'],
'response' => $response, 'response' => $response,
]); ]);
die();
exit();
} }
if (array_key_exists('access_token', $data)) if (array_key_exists('access_token', $data))
@ -169,20 +146,17 @@ final class Model {
/** /**
* Get the userid for a username from Kitsu * Get the userid for a username from Kitsu
*
* @param string|null $username
* @return string
*/ */
public function getUserIdByUsername(string $username = NULL): string public function getUserIdByUsername(?string $username = NULL): string
{ {
if ($username === NULL) if ($username === NULL)
{ {
$username = $this->getUsername(); $username = $this->getUsername();
} }
return $this->getCached(K::AUTH_USER_ID_KEY, function(string $username) { return $this->getCached(K::AUTH_USER_ID_KEY, function (string $username) {
$data = $this->requestBuilder->runQuery('GetUserId', [ $data = $this->requestBuilder->runQuery('GetUserId', [
'slug' => $username 'slug' => $username,
]); ]);
return $data['data']['findProfileBySlug']['id'] ?? NULL; return $data['data']['findProfileBySlug']['id'] ?? NULL;
@ -192,34 +166,31 @@ final class Model {
/** /**
* Get information about a character * Get information about a character
* *
* @param string $slug * @return mixed[]
* @return array
*/ */
public function getCharacter(string $slug): array public function getCharacter(string $slug): array
{ {
return $this->requestBuilder->runQuery('CharacterDetails', [ return $this->requestBuilder->runQuery('CharacterDetails', [
'slug' => $slug 'slug' => $slug,
]); ]);
} }
/** /**
* Get information about a person * Get information about a person
* *
* @param string $slug * @return mixed[]
* @return array
*/ */
public function getPerson(string $slug): array public function getPerson(string $slug): array
{ {
return $this->getCached("kitsu-person-{$slug}", fn () => $this->requestBuilder->runQuery('PersonDetails', [ return $this->getCached("kitsu-person-{$slug}", fn () => $this->requestBuilder->runQuery('PersonDetails', [
'slug' => $slug 'slug' => $slug,
])); ]));
} }
/** /**
* Get profile information for the configured user * Get profile information for the configured user
* *
* @param string $username * @return mixed[]
* @return array
*/ */
public function getUserData(string $username): array public function getUserData(string $username): array
{ {
@ -231,17 +202,13 @@ final class Model {
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// ! Anime-specific methods // ! Anime-specific methods
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
/** /**
* Get information about a particular anime * Get information about a particular anime
*
* @param string $slug
* @return Anime
*/ */
public function getAnime(string $slug): Anime public function getAnime(string $slug): Anime
{ {
$baseData = $this->requestBuilder->runQuery('AnimeDetails', [ $baseData = $this->requestBuilder->runQuery('AnimeDetails', [
'slug' => $slug 'slug' => $slug,
]); ]);
if (empty($baseData)) if (empty($baseData))
@ -255,7 +222,7 @@ final class Model {
public function getRandomAnime(): Anime public function getRandomAnime(): Anime
{ {
$baseData = $this->requestBuilder->runQuery('RandomMedia', [ $baseData = $this->requestBuilder->runQuery('RandomMedia', [
'type' => 'ANIME' 'type' => 'ANIME',
]); ]);
return $this->animeTransformer->transform($baseData); return $this->animeTransformer->transform($baseData);
@ -269,22 +236,20 @@ final class Model {
/** /**
* Get information about a particular anime * Get information about a particular anime
*
* @param string $animeId
* @return Anime
*/ */
public function getAnimeById(string $animeId): Anime public function getAnimeById(string $animeId): Anime
{ {
$baseData = $this->requestBuilder->runQuery('AnimeDetailsById', [ $baseData = $this->requestBuilder->runQuery('AnimeDetailsById', [
'id' => $animeId, 'id' => $animeId,
]); ]);
return $this->animeTransformer->transform($baseData); return $this->animeTransformer->transform($baseData);
} }
/** /**
* Retrieve the data for the anime watch history page * Retrieve the data for the anime watch history page
* *
* @return array * @return mixed[]
*/ */
public function getAnimeHistory(): array public function getAnimeHistory(): array
{ {
@ -298,7 +263,6 @@ final class Model {
$list = (new AnimeHistoryTransformer())->transform($raw); $list = (new AnimeHistoryTransformer())->transform($raw);
$this->cache->set($key, $list); $this->cache->set($key, $list);
} }
return $list; return $list;
@ -308,7 +272,7 @@ final class Model {
* Get the anime list for the configured user * Get the anime list for the configured user
* *
* @param string $status - The watching status to filter the list with * @param string $status - The watching status to filter the list with
* @return array * @return mixed[]
*/ */
public function getAnimeList(string $status): array public function getAnimeList(string $status): array
{ {
@ -330,7 +294,7 @@ final class Model {
$transformed = $transformer->transformCollection($data); $transformed = $transformer->transformCollection($data);
$keyed = []; $keyed = [];
foreach($transformed as $item) foreach ($transformed as $item)
{ {
$keyed[$item['id']] = $item; $keyed[$item['id']] = $item;
} }
@ -346,9 +310,8 @@ final class Model {
* Get the number of anime list items * Get the number of anime list items
* *
* @param string $status - Optional status to filter by * @param string $status - Optional status to filter by
* @return int
*/ */
public function getAnimeListCount(string $status = '') : int public function getAnimeListCount(string $status = ''): int
{ {
return $this->getListCount(MediaType::ANIME, $status); return $this->getListCount(MediaType::ANIME, $status);
} }
@ -356,7 +319,7 @@ final class Model {
/** /**
* Get all the anime entries, that are organized for output to html * Get all the anime entries, that are organized for output to html
* *
* @return array * @return array<string, mixed[]>
*/ */
public function getFullOrganizedAnimeList(): array public function getFullOrganizedAnimeList(): array
{ {
@ -364,7 +327,7 @@ final class Model {
$statuses = KitsuWatchingStatus::getConstList(); $statuses = KitsuWatchingStatus::getConstList();
foreach ($statuses as $key => $status) foreach ($statuses as $status)
{ {
$mappedStatus = AnimeWatchingStatus::KITSU_TO_TITLE[$status]; $mappedStatus = AnimeWatchingStatus::KITSU_TO_TITLE[$status];
$output[$mappedStatus] = $this->getAnimeList($status) ?? []; $output[$mappedStatus] = $this->getAnimeList($status) ?? [];
@ -376,17 +339,13 @@ final class Model {
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// ! Manga-specific methods // ! Manga-specific methods
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
/** /**
* Get information about a particular manga * Get information about a particular manga
*
* @param string $slug
* @return MangaPage
*/ */
public function getManga(string $slug): MangaPage public function getManga(string $slug): MangaPage
{ {
$baseData = $this->requestBuilder->runQuery('MangaDetails', [ $baseData = $this->requestBuilder->runQuery('MangaDetails', [
'slug' => $slug 'slug' => $slug,
]); ]);
if (empty($baseData)) if (empty($baseData))
@ -400,7 +359,7 @@ final class Model {
public function getRandomManga(): MangaPage public function getRandomManga(): MangaPage
{ {
$baseData = $this->requestBuilder->runQuery('RandomMedia', [ $baseData = $this->requestBuilder->runQuery('RandomMedia', [
'type' => 'MANGA' 'type' => 'MANGA',
]); ]);
return $this->mangaTransformer->transform($baseData); return $this->mangaTransformer->transform($baseData);
@ -408,22 +367,20 @@ final class Model {
/** /**
* Get information about a particular manga * Get information about a particular manga
*
* @param string $mangaId
* @return MangaPage
*/ */
public function getMangaById(string $mangaId): MangaPage public function getMangaById(string $mangaId): MangaPage
{ {
$baseData = $this->requestBuilder->runQuery('MangaDetailsById', [ $baseData = $this->requestBuilder->runQuery('MangaDetailsById', [
'id' => $mangaId, 'id' => $mangaId,
]); ]);
return $this->mangaTransformer->transform($baseData); return $this->mangaTransformer->transform($baseData);
} }
/** /**
* Retrieve the data for the manga read history page * Retrieve the data for the manga read history page
* *
* @return array * @return mixed[]
*/ */
public function getMangaHistory(): array public function getMangaHistory(): array
{ {
@ -445,7 +402,7 @@ final class Model {
* Get the manga list for the configured user * Get the manga list for the configured user
* *
* @param string $status - The reading status by which to filter the list * @param string $status - The reading status by which to filter the list
* @return array * @return mixed[]
*/ */
public function getMangaList(string $status): array public function getMangaList(string $status): array
{ {
@ -467,7 +424,7 @@ final class Model {
$transformed = $transformer->transformCollection($data); $transformed = $transformer->transformCollection($data);
$keyed = []; $keyed = [];
foreach($transformed as $item) foreach ($transformed as $item)
{ {
$keyed[$item['id']] = $item; $keyed[$item['id']] = $item;
} }
@ -483,9 +440,8 @@ final class Model {
* Get the number of manga list items * Get the number of manga list items
* *
* @param string $status - Optional status to filter by * @param string $status - Optional status to filter by
* @return int
*/ */
public function getMangaListCount(string $status = '') : int public function getMangaListCount(string $status = ''): int
{ {
return $this->getListCount(MediaType::MANGA, $status); return $this->getListCount(MediaType::MANGA, $status);
} }
@ -493,12 +449,13 @@ final class Model {
/** /**
* Get all Manga lists * Get all Manga lists
* *
* @return array * @return array<string, mixed[]>
*/ */
public function getFullOrganizedMangaList(): array public function getFullOrganizedMangaList(): array
{ {
$statuses = KitsuReadingStatus::getConstList(); $statuses = KitsuReadingStatus::getConstList();
$output = []; $output = [];
foreach ($statuses as $status) foreach ($statuses as $status)
{ {
$mappedStatus = MangaReadingStatus::KITSU_TO_TITLE[$status]; $mappedStatus = MangaReadingStatus::KITSU_TO_TITLE[$status];
@ -511,13 +468,12 @@ final class Model {
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// Base methods // Base methods
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
/** /**
* Search for an anime or manga * Search for an anime or manga
* *
* @param string $type - 'anime' or 'manga' * @param string $type - 'anime' or 'manga'
* @param string $query - name of the item to search for * @param string $query - name of the item to search for
* @return array * @return array<int, array<string, mixed>>
*/ */
public function search(string $type, string $query): array public function search(string $type, string $query): array
{ {
@ -543,9 +499,9 @@ final class Model {
// Search for MAL mapping // Search for MAL mapping
if (is_array($item['mappings']['nodes'])) if (is_array($item['mappings']['nodes']))
{ {
foreach($item['mappings']['nodes'] as $mapping) foreach ($item['mappings']['nodes'] as $mapping)
{ {
if ($mapping['externalSite'] === "MYANIMELIST_" . strtoupper($type)) if ($mapping['externalSite'] === 'MYANIMELIST_' . strtoupper($type))
{ {
$searchItem['mal_id'] = $mapping['externalId']; $searchItem['mal_id'] = $mapping['externalId'];
break; break;
@ -554,7 +510,6 @@ final class Model {
} }
$data[] = $searchItem; $data[] = $searchItem;
} }
return $data; return $data;
@ -563,11 +518,9 @@ final class Model {
/** /**
* Find a media item on Kitsu by its associated MAL id * Find a media item on Kitsu by its associated MAL id
* *
* @param string $malId
* @param string $type "anime" or "manga" * @param string $type "anime" or "manga"
* @return string|NULL
*/ */
public function getKitsuIdFromMALId(string $malId, string $type='anime'): ?string public function getKitsuIdFromMALId(string $malId, string $type = 'anime'): ?string
{ {
$raw = $this->requestBuilder->runQuery('GetIdByMapping', [ $raw = $this->requestBuilder->runQuery('GetIdByMapping', [
'id' => $malId, 'id' => $malId,
@ -594,6 +547,9 @@ final class Model {
return (new LibraryEntryTransformer())->transform($baseData['data']['findLibraryEntryById']); return (new LibraryEntryTransformer())->transform($baseData['data']['findLibraryEntryById']);
} }
/**
* @return mixed[]
*/
public function getThumbList(string $type): array public function getThumbList(string $type): array
{ {
$statuses = [ $statuses = [
@ -620,11 +576,9 @@ final class Model {
} }
/** /**
*
* Get the data to sync Kitsu anime/manga list with another API * Get the data to sync Kitsu anime/manga list with another API
* *
* @param string $type * @return mixed[]
* @return array
*/ */
public function getSyncList(string $type): array public function getSyncList(string $type): array
{ {
@ -654,7 +608,7 @@ final class Model {
/** /**
* Get the aggregated pages of anime or manga history * Get the aggregated pages of anime or manga history
* *
* @return array * @return mixed[]
*/ */
protected function getHistoryList(): array protected function getHistoryList(): array
{ {
@ -666,9 +620,7 @@ final class Model {
/** /**
* Get the raw anime/manga list from GraphQL * Get the raw anime/manga list from GraphQL
* *
* @param string $type * @return mixed[]
* @param string $status
* @return array
*/ */
protected function getList(string $type, string $status = ''): array protected function getList(string $type, string $status = ''): array
{ {
@ -687,7 +639,7 @@ final class Model {
$cursor = ''; $cursor = '';
$username = $this->getUsername(); $username = $this->getUsername();
return new Amp\Producer(function (callable $emit) use ($type, $status, $cursor, $username) { return new Amp\Producer(function (callable $emit) use ($type, $status, $cursor, $username): Generator {
while (TRUE) while (TRUE)
{ {
$vars = [ $vars = [
@ -698,6 +650,7 @@ final class Model {
{ {
$vars['status'] = $status; $vars['status'] = $status;
} }
if ($cursor !== '') if ($cursor !== '')
{ {
$vars['after'] = $cursor; $vars['after'] = $cursor;
@ -719,7 +672,8 @@ final class Model {
// @TODO Proper Error logging // @TODO Proper Error logging
dump($rawData); dump($rawData);
die();
exit();
} }
$cursor = $page['endCursor']; $cursor = $page['endCursor'];
@ -734,11 +688,12 @@ final class Model {
}); });
} }
private function getSyncPages(string $type, string $status): Amp\Iterator { private function getSyncPages(string $type, string $status): Amp\Iterator
{
$cursor = ''; $cursor = '';
$username = $this->getUsername(); $username = $this->getUsername();
return new Amp\Producer(function (callable $emit) use ($type, $status, $cursor, $username) { return new Amp\Producer(function (callable $emit) use ($type, $status, $cursor, $username): Generator {
while (TRUE) while (TRUE)
{ {
$vars = [ $vars = [
@ -761,7 +716,8 @@ final class Model {
if (empty($data)) if (empty($data))
{ {
dump($rawData); dump($rawData);
die();
exit();
} }
$cursor = $page['endCursor']; $cursor = $page['endCursor'];
@ -781,7 +737,7 @@ final class Model {
$cursor = ''; $cursor = '';
$username = $this->getUsername(); $username = $this->getUsername();
return new Amp\Producer(function (callable $emit) use ($type, $status, $cursor, $username) { return new Amp\Producer(function (callable $emit) use ($type, $status, $cursor, $username): Generator {
while (TRUE) while (TRUE)
{ {
$vars = [ $vars = [
@ -804,7 +760,8 @@ final class Model {
if (empty($data)) if (empty($data))
{ {
dump($rawData); dump($rawData);
die();
exit();
} }
$cursor = $page['endCursor']; $cursor = $page['endCursor'];
@ -843,8 +800,6 @@ final class Model {
/** /**
* Get the kitsu username from config * Get the kitsu username from config
*
* @return string
*/ */
private function getUsername(): string private function getUsername(): string
{ {
@ -857,7 +812,7 @@ final class Model {
{ {
$args = [ $args = [
'type' => strtoupper($type), 'type' => strtoupper($type),
'slug' => $this->getUsername() 'slug' => $this->getUsername(),
]; ];
if ($status !== '') if ($status !== '')
{ {
@ -868,4 +823,4 @@ final class Model {
return $res['data']['findProfileBySlug']['library']['all']['totalCount']; return $res['data']['findProfileBySlug']['library']['all']['totalCount'];
} }
} }

View File

@ -6,12 +6,10 @@
* *
* PHP version 8 * PHP version 8
* *
* @package HummingbirdAnimeClient * @copyright 2015 - 2022 Timothy J. Warren <tim@timshome.page>
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2021 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License * @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.2 * @version 5.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient * @link https://git.timshome.page/timw4mail/HummingBirdAnimeClient
*/ */
namespace Aviat\AnimeClient\API\Kitsu; namespace Aviat\AnimeClient\API\Kitsu;
@ -22,16 +20,13 @@ use Aviat\AnimeClient\Types\FormItem;
/** /**
* Kitsu API calls that mutate data, C/U/D parts of CRUD * Kitsu API calls that mutate data, C/U/D parts of CRUD
*/ */
trait MutationTrait { trait MutationTrait
{
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// ! Generic API calls // ! Generic API calls
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
/** /**
* Create a list item * Create a list item
*
* @param array $data
* @return Request|null
*/ */
public function createListItem(array $data): ?Request public function createListItem(array $data): ?Request
{ {
@ -46,9 +41,6 @@ trait MutationTrait {
/** /**
* Increase the progress count for a list item * Increase the progress count for a list item
*
* @param FormItem $data
* @return Request
*/ */
public function incrementListItem(FormItem $data): Request public function incrementListItem(FormItem $data): Request
{ {
@ -57,9 +49,6 @@ trait MutationTrait {
/** /**
* Modify a list item * Modify a list item
*
* @param FormItem $data
* @return Request
*/ */
public function updateListItem(FormItem $data): Request public function updateListItem(FormItem $data): Request
{ {
@ -70,10 +59,20 @@ trait MutationTrait {
* Remove a list item * Remove a list item
* *
* @param string $id - The id of the list item to remove * @param string $id - The id of the list item to remove
* @return Request
*/ */
public function deleteListItem(string $id): Request public function deleteListItem(string $id): Request
{ {
return $this->listItem->delete($id); return $this->listItem->delete($id);
} }
}
/**
* Remove a list item
*
* @param FormItem $data
* @return Request
*/
public function deleteItem(FormItem $data): Request
{
return $this->listItem->delete($data['id']);
}
}

View File

@ -6,57 +6,46 @@
* *
* PHP version 8 * PHP version 8
* *
* @package HummingbirdAnimeClient * @copyright 2015 - 2022 Timothy J. Warren <tim@timshome.page>
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2021 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License * @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.2 * @version 5.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient * @link https://git.timshome.page/timw4mail/HummingBirdAnimeClient
*/ */
namespace Aviat\AnimeClient\API\Kitsu; namespace Aviat\AnimeClient\API\Kitsu;
use const Aviat\AnimeClient\SESSION_SEGMENT; use Amp\Http\Client\{Request, Response};
use const Aviat\AnimeClient\USER_AGENT;
use function Amp\Promise\wait;
use function Aviat\AnimeClient\getResponse;
use Amp\Http\Client\Request;
use Amp\Http\Client\Response;
use Aviat\AnimeClient\Kitsu as K;
use Aviat\AnimeClient\API\APIRequestBuilder; use Aviat\AnimeClient\API\APIRequestBuilder;
use Aviat\AnimeClient\Enum\EventType; use Aviat\AnimeClient\Enum\EventType;
use Aviat\Ion\Di\ContainerAware; use Aviat\AnimeClient\Kitsu as K;
use Aviat\Ion\Di\ContainerInterface; use Aviat\Ion\Di\{ContainerAware, ContainerInterface};
use Aviat\Ion\Event; use Aviat\Ion\{Event, Json, JsonException};
use Aviat\Ion\Json;
use Aviat\Ion\JsonException;
use LogicException; use LogicException;
use function Amp\Promise\wait;
use function Aviat\AnimeClient\getResponse;
use function in_array;
use const Aviat\AnimeClient\{SESSION_SEGMENT, USER_AGENT};
final class RequestBuilder extends APIRequestBuilder { final class RequestBuilder extends APIRequestBuilder
{
use ContainerAware; use ContainerAware;
/** /**
* The base url for api requests * The base url for api requests
* @var string $base_url
*/ */
protected string $baseUrl = K::GRAPHQL_ENDPOINT; protected string $baseUrl = K::GRAPHQL_ENDPOINT;
/** /**
* Where to look for GraphQL request files * Where to look for GraphQL request files
* @var string
*/ */
protected string $filePath = __DIR__; protected string $filePath = __DIR__;
/** /**
* HTTP headers to send with every request * HTTP headers to send with every request
*
* @var array
*/ */
protected array $defaultHeaders = [ protected array $defaultHeaders = [
'User-Agent' => USER_AGENT, 'User-Agent' => USER_AGENT,
'Accept' => 'application/vnd.api+json', 'Accept' => 'application/vnd.api+json',
'Content-Type' => 'application/vnd.api+json', 'Content-Type' => 'application/vnd.api+json',
'CLIENT_ID' => 'dd031b32d2f56c990b1425efe6c42ad847e7fe3ab46bf1299f05ecd856bdb7dd', 'CLIENT_ID' => 'dd031b32d2f56c990b1425efe6c42ad847e7fe3ab46bf1299f05ecd856bdb7dd',
@ -70,11 +59,6 @@ final class RequestBuilder extends APIRequestBuilder {
/** /**
* Create a request object * Create a request object
*
* @param string $type
* @param string $url
* @param array $options
* @return Request
*/ */
public function setUpRequest(string $type, string $url, array $options = []): Request public function setUpRequest(string $type, string $url, array $options = []): Request
{ {
@ -85,13 +69,13 @@ final class RequestBuilder extends APIRequestBuilder {
->getSegment(SESSION_SEGMENT); ->getSegment(SESSION_SEGMENT);
$cache = $this->getContainer()->get('cache'); $cache = $this->getContainer()->get('cache');
$token = null; $token = NULL;
if ($cache->has(K::AUTH_TOKEN_CACHE_KEY)) if ($cache->has(K::AUTH_TOKEN_CACHE_KEY))
{ {
$token = $cache->get(K::AUTH_TOKEN_CACHE_KEY); $token = $cache->get(K::AUTH_TOKEN_CACHE_KEY);
} }
else if ($url !== K::AUTH_URL && $sessionSegment->get('auth_token') !== NULL) elseif ($url !== K::AUTH_URL && $sessionSegment->get('auth_token') !== NULL)
{ {
$token = $sessionSegment->get('auth_token'); $token = $sessionSegment->get('auth_token');
if ( ! (empty($token) || $cache->has(K::AUTH_TOKEN_CACHE_KEY))) if ( ! (empty($token) || $cache->has(K::AUTH_TOKEN_CACHE_KEY)))
@ -131,9 +115,7 @@ final class RequestBuilder extends APIRequestBuilder {
/** /**
* Run a GraphQL API query * Run a GraphQL API query
* *
* @param string $name * @return mixed[]
* @param array $variables
* @return array
*/ */
public function runQuery(string $name, array $variables = []): array public function runQuery(string $name, array $variables = []): array
{ {
@ -141,12 +123,12 @@ final class RequestBuilder extends APIRequestBuilder {
$response = getResponse($request); $response = getResponse($request);
$validResponseCodes = [200, 201]; $validResponseCodes = [200, 201];
if ( ! \in_array($response->getStatus(), $validResponseCodes, TRUE)) if ( ! in_array($response->getStatus(), $validResponseCodes, TRUE))
{ {
$logger = $this->container->getLogger('kitsu-graphql'); $logger = $this->container->getLogger('kitsu-graphql');
if ($logger !== NULL) if ($logger !== NULL)
{ {
$logger->warning('Non 200 response for GraphQL call', (array)$response->getBody()); $logger->warning('Non 200 response for GraphQL call', (array) $response->getBody());
} }
} }
@ -156,9 +138,7 @@ final class RequestBuilder extends APIRequestBuilder {
/** /**
* Run a GraphQL mutation * Run a GraphQL mutation
* *
* @param string $name * @return mixed[]
* @param array $variables
* @return array
*/ */
public function mutate(string $name, array $variables = []): array public function mutate(string $name, array $variables = []): array
{ {
@ -166,12 +146,12 @@ final class RequestBuilder extends APIRequestBuilder {
$response = getResponse($request); $response = getResponse($request);
$validResponseCodes = [200, 201]; $validResponseCodes = [200, 201];
if ( ! \in_array($response->getStatus(), $validResponseCodes, TRUE)) if ( ! in_array($response->getStatus(), $validResponseCodes, TRUE))
{ {
$logger = $this->container->getLogger('kitsu-graphql'); $logger = $this->container->getLogger('kitsu-graphql');
if ($logger !== NULL) if ($logger !== NULL)
{ {
$logger->warning('Non 200 response for GraphQL call', (array)$response->getBody()); $logger->warning('Non 200 response for GraphQL call', (array) $response->getBody());
} }
} }
@ -180,11 +160,6 @@ final class RequestBuilder extends APIRequestBuilder {
/** /**
* Make a request * Make a request
*
* @param string $type
* @param string $url
* @param array $options
* @return Response
*/ */
public function getResponse(string $type, string $url, array $options = []): Response public function getResponse(string $type, string $url, array $options = []): Response
{ {
@ -205,10 +180,6 @@ final class RequestBuilder extends APIRequestBuilder {
/** /**
* Create a GraphQL query and return the Request object * Create a GraphQL query and return the Request object
*
* @param string $name
* @param array $variables
* @return Request
*/ */
public function queryRequest(string $name, array $variables = []): Request public function queryRequest(string $name, array $variables = []): Request
{ {
@ -220,13 +191,14 @@ final class RequestBuilder extends APIRequestBuilder {
$query = file_get_contents($file); $query = file_get_contents($file);
$body = [ $body = [
'query' => $query 'query' => $query,
]; ];
if ( ! empty($variables)) if ( ! empty($variables))
{ {
$body['variables'] = []; $body['variables'] = [];
foreach($variables as $key => $val)
foreach ($variables as $key => $val)
{ {
$body['variables'][$key] = $val; $body['variables'][$key] = $val;
} }
@ -239,12 +211,8 @@ final class RequestBuilder extends APIRequestBuilder {
/** /**
* Create a GraphQL mutation request, and return the Request object * Create a GraphQL mutation request, and return the Request object
*
* @param string $name
* @param array $variables
* @return Request
*/ */
public function mutateRequest (string $name, array $variables = []): Request public function mutateRequest(string $name, array $variables = []): Request
{ {
$file = realpath("{$this->filePath}/Mutations/{$name}.graphql"); $file = realpath("{$this->filePath}/Mutations/{$name}.graphql");
if ($file === FALSE || ! file_exists($file)) if ($file === FALSE || ! file_exists($file))
@ -254,11 +222,13 @@ final class RequestBuilder extends APIRequestBuilder {
$query = file_get_contents($file); $query = file_get_contents($file);
$body = [ $body = [
'query' => $query 'query' => $query,
]; ];
if (!empty($variables)) { if ( ! empty($variables))
{
$body['variables'] = []; $body['variables'] = [];
foreach ($variables as $key => $val) foreach ($variables as $key => $val)
{ {
$body['variables'][$key] = $val; $body['variables'][$key] = $val;
@ -272,11 +242,6 @@ final class RequestBuilder extends APIRequestBuilder {
/** /**
* Make a request * Make a request
*
* @param string $type
* @param string $url
* @param array $options
* @return array
*/ */
private function request(string $type, string $url, array $options = []): array private function request(string $type, string $url, array $options = []): array
{ {
@ -297,7 +262,7 @@ final class RequestBuilder extends APIRequestBuilder {
{ {
if ($logger !== NULL) if ($logger !== NULL)
{ {
$logger->warning('Non 2xx response for api call', (array)$response); $logger->warning('Non 2xx response for api call', (array) $response);
} }
} }
@ -305,11 +270,12 @@ final class RequestBuilder extends APIRequestBuilder {
{ {
return Json::decode($rawBody); return Json::decode($rawBody);
} }
catch (JsonException $e) catch (JsonException)
{ {
// dump($e); // dump($e);
dump($rawBody); dump($rawBody);
die();
exit();
} }
} }
} }

View File

@ -6,32 +6,30 @@
* *
* PHP version 8 * PHP version 8
* *
* @package HummingbirdAnimeClient * @copyright 2015 - 2022 Timothy J. Warren <tim@timshome.page>
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2021 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License * @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.2 * @version 5.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient * @link https://git.timshome.page/timw4mail/HummingBirdAnimeClient
*/ */
namespace Aviat\AnimeClient\API\Kitsu; namespace Aviat\AnimeClient\API\Kitsu;
trait RequestBuilderTrait { trait RequestBuilderTrait
{
/** /**
* The request builder for the Kitsu API * The request builder for the Kitsu API
* @var RequestBuilder
*/ */
protected RequestBuilder $requestBuilder; protected RequestBuilder $requestBuilder;
/** /**
* Set the request builder object * Set the request builder object
* *
* @param RequestBuilder $requestBuilder * @return ListItem|Model|RequestBuilderTrait
* @return RequestBuilderTrait|ListItem|Model
*/ */
public function setRequestBuilder(RequestBuilder $requestBuilder): self public function setRequestBuilder(RequestBuilder $requestBuilder): self
{ {
$this->requestBuilder = $requestBuilder; $this->requestBuilder = $requestBuilder;
return $this; return $this;
} }
} }

View File

@ -6,28 +6,22 @@
* *
* PHP version 8 * PHP version 8
* *
* @package HummingbirdAnimeClient * @copyright 2015 - 2022 Timothy J. Warren <tim@timshome.page>
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2021 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License * @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.2 * @version 5.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient * @link https://git.timshome.page/timw4mail/HummingBirdAnimeClient
*/ */
namespace Aviat\AnimeClient\API\Kitsu\Transformer; namespace Aviat\AnimeClient\API\Kitsu\Transformer;
use Aviat\AnimeClient\API\Mapping\AnimeWatchingStatus; use Aviat\AnimeClient\API\Mapping\AnimeWatchingStatus;
class AnimeHistoryTransformer extends HistoryTransformer { class AnimeHistoryTransformer extends HistoryTransformer
{
protected string $type = 'anime'; protected string $type = 'anime';
protected string $progressAction = 'Watched episode'; protected string $progressAction = 'Watched episode';
protected string $reconsumeAction = 'Rewatched episode'; protected string $reconsumeAction = 'Rewatched episode';
protected string $largeAggregateAction = 'Marathoned episodes'; protected string $largeAggregateAction = 'Marathoned episodes';
protected string $reconsumingStatus = 'Rewatching'; protected string $reconsumingStatus = 'Rewatching';
protected array $statusMap = AnimeWatchingStatus::KITSU_TO_TITLE; protected array $statusMap = AnimeWatchingStatus::KITSU_TO_TITLE;
} }

View File

@ -6,20 +6,18 @@
* *
* PHP version 8 * PHP version 8
* *
* @package HummingbirdAnimeClient * @copyright 2015 - 2022 Timothy J. Warren <tim@timshome.page>
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2021 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License * @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.2 * @version 5.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient * @link https://git.timshome.page/timw4mail/HummingBirdAnimeClient
*/ */
namespace Aviat\AnimeClient\API\Kitsu\Transformer; namespace Aviat\AnimeClient\API\Kitsu\Transformer;
use Aviat\AnimeClient\Kitsu; use Aviat\AnimeClient\Kitsu;
use Aviat\AnimeClient\Types\{ use Aviat\AnimeClient\Types\{
FormItem, AnimeListItem,
AnimeListItem FormItem
}; };
use Aviat\Ion\Transformer\AbstractTransformer; use Aviat\Ion\Transformer\AbstractTransformer;
use Aviat\Ion\Type\StringType; use Aviat\Ion\Type\StringType;
@ -27,31 +25,31 @@ use Aviat\Ion\Type\StringType;
/** /**
* Transformer for anime list * Transformer for anime list
*/ */
final class AnimeListTransformer extends AbstractTransformer { final class AnimeListTransformer extends AbstractTransformer
{
/** /**
* Convert raw api response to a more * Convert raw api response to a more
* logical and workable structure * logical and workable structure
* *
* @param array|object $item API library item * @param array|object $item API library item
* @return AnimeListItem
*/ */
public function transform(array|object $item): AnimeListItem public function transform(array|object $item): AnimeListItem
{ {
$item = (array)$item; $item = (array) $item;
$animeId = $item['media']['id']; $animeId = $item['media']['id'];
$anime = $item['media']; $anime = $item['media'];
$genres = []; $genres = [];
$rating = (int) $item['rating'] !== 0 $rating = (int) $item['rating'] !== 0
? (int)$item['rating'] / 2 ? (int) $item['rating'] / 2
: '-'; : '-';
$total_episodes = (int) $anime['episodeCount'] !== 0 $total_episodes = (int) $anime['episodeCount'] !== 0
? (int) $anime['episodeCount'] ? (int) $anime['episodeCount']
: '-'; : '-';
$AnilistId = NULL;
$MALid = NULL; $MALid = NULL;
$mappings = $anime['mappings']['nodes'] ?? []; $mappings = $anime['mappings']['nodes'] ?? [];
@ -62,7 +60,11 @@ final class AnimeListTransformer extends AbstractTransformer {
if ($mapping['externalSite'] === 'MYANIMELIST_ANIME') if ($mapping['externalSite'] === 'MYANIMELIST_ANIME')
{ {
$MALid = $mapping['externalId']; $MALid = $mapping['externalId'];
break; }
if ($mapping['externalSite'] === 'ANILIST_ANIME')
{
$AnilistId = $mapping['externalId'];
} }
} }
} }
@ -76,6 +78,7 @@ final class AnimeListTransformer extends AbstractTransformer {
return AnimeListItem::from([ return AnimeListItem::from([
'id' => $item['id'], 'id' => $item['id'],
'anilist_id' => $AnilistId,
'mal_id' => $MALid, 'mal_id' => $MALid,
'episodes' => [ 'episodes' => [
'watched' => (int) $item['progress'] !== 0 'watched' => (int) $item['progress'] !== 0
@ -87,7 +90,7 @@ final class AnimeListTransformer extends AbstractTransformer {
'airing' => [ 'airing' => [
'status' => Kitsu::getAiringStatus($anime['startDate'], $anime['endDate']), 'status' => Kitsu::getAiringStatus($anime['startDate'], $anime['endDate']),
'started' => $anime['startDate'], 'started' => $anime['startDate'],
'ended' => $anime['endDate'] 'ended' => $anime['endDate'],
], ],
'anime' => [ 'anime' => [
'id' => $animeId, 'id' => $animeId,
@ -95,7 +98,7 @@ final class AnimeListTransformer extends AbstractTransformer {
'title' => $title, 'title' => $title,
'titles' => $titles, 'titles' => $titles,
'slug' => $anime['slug'], 'slug' => $anime['slug'],
'show_type' => (string)StringType::from($anime['subtype'])->upperCaseFirst(), 'show_type' => (string) StringType::from($anime['subtype'])->upperCaseFirst(),
'cover_image' => Kitsu::getPosterImage($anime), 'cover_image' => Kitsu::getPosterImage($anime),
'genres' => $genres, 'genres' => $genres,
'streaming_links' => $streamingLinks, 'streaming_links' => $streamingLinks,
@ -123,14 +126,15 @@ final class AnimeListTransformer extends AbstractTransformer {
$untransformed = FormItem::from([ $untransformed = FormItem::from([
'id' => $item['id'], 'id' => $item['id'],
'anilist_id' => $item['anilist_id'] ?? NULL,
'mal_id' => $item['mal_id'] ?? NULL, 'mal_id' => $item['mal_id'] ?? NULL,
'data' => [ 'data' => [
'status' => $item['watching_status'], 'status' => $item['watching_status'],
'reconsuming' => $rewatching, 'reconsuming' => $rewatching,
'reconsumeCount' => $item['rewatched'], 'reconsumeCount' => $item['rewatched'],
'notes' => $item['notes'], 'notes' => $item['notes'],
'private' => $privacy 'private' => $privacy,
] ],
]); ]);
if (is_numeric($item['episodes_watched']) && $item['episodes_watched'] > 0) if (is_numeric($item['episodes_watched']) && $item['episodes_watched'] > 0)
@ -146,4 +150,5 @@ final class AnimeListTransformer extends AbstractTransformer {
return $untransformed; return $untransformed;
} }
} }
// End of AnimeListTransformer.php
// End of AnimeListTransformer.php

View File

@ -6,12 +6,10 @@
* *
* PHP version 8 * PHP version 8
* *
* @package HummingbirdAnimeClient * @copyright 2015 - 2022 Timothy J. Warren <tim@timshome.page>
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2021 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License * @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.2 * @version 5.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient * @link https://git.timshome.page/timw4mail/HummingBirdAnimeClient
*/ */
namespace Aviat\AnimeClient\API\Kitsu\Transformer; namespace Aviat\AnimeClient\API\Kitsu\Transformer;
@ -23,23 +21,22 @@ use Aviat\Ion\Transformer\AbstractTransformer;
/** /**
* Transformer for anime description page * Transformer for anime description page
*/ */
final class AnimeTransformer extends AbstractTransformer { final class AnimeTransformer extends AbstractTransformer
{
/** /**
* Convert raw api response to a more * Convert raw api response to a more
* logical and workable structure * logical and workable structure
* *
* @param array|object $item API library item * @param array|object $item API library item
* @return AnimePage
*/ */
public function transform(array|object $item): AnimePage public function transform(array|object $item): AnimePage
{ {
$item = (array)$item; $item = (array) $item;
$base = $item['data']['findAnimeBySlug'] ?? $item['data']['findAnimeById'] ?? $item['data']['randomMedia']; $base = $item['data']['findAnimeBySlug'] ?? $item['data']['findAnimeById'] ?? $item['data']['randomMedia'];
$characters = []; $characters = [];
$links = []; $links = [];
$staff = []; $staff = [];
$genres = array_map(fn ($genre) => $genre['title']['en'], $base['categories']['nodes']); $genres = array_map(static fn ($genre) => $genre['title']['en'], $base['categories']['nodes']);
sort($genres); sort($genres);
@ -47,7 +44,7 @@ final class AnimeTransformer extends AbstractTransformer {
$titles = Kitsu::getTitles($base['titles']); $titles = Kitsu::getTitles($base['titles']);
$titles_more = Kitsu::filterLocalizedTitles($base['titles']); $titles_more = Kitsu::filterLocalizedTitles($base['titles']);
if (count($base['characters']['nodes']) > 0) if ((is_countable($base['characters']['nodes']) ? count($base['characters']['nodes']) : 0) > 0)
{ {
foreach ($base['characters']['nodes'] as $rawCharacter) foreach ($base['characters']['nodes'] as $rawCharacter)
{ {
@ -73,14 +70,14 @@ final class AnimeTransformer extends AbstractTransformer {
} }
else else
{ {
uasort($characters[$type], fn($a, $b) => $a['name'] <=> $b['name']); uasort($characters[$type], static fn ($a, $b) => $a['name'] <=> $b['name']);
} }
} }
krsort($characters); krsort($characters);
} }
if (count($base['staff']['nodes']) > 0) if ((is_countable($base['staff']['nodes']) ? count($base['staff']['nodes']) : 0) > 0)
{ {
foreach ($base['staff']['nodes'] as $staffing) foreach ($base['staff']['nodes'] as $staffing)
{ {
@ -90,7 +87,7 @@ final class AnimeTransformer extends AbstractTransformer {
// If this person object is so broken as to not have a proper image object, // If this person object is so broken as to not have a proper image object,
// just skip it. No point in showing a role with nothing in it. // just skip it. No point in showing a role with nothing in it.
if ($person === null || $person['id'] === null || $person['image'] === null) if ($person === NULL || $person['id'] === NULL || $person['image'] === NULL)
{ {
continue; continue;
} }
@ -103,17 +100,17 @@ final class AnimeTransformer extends AbstractTransformer {
$staff[$role][$person['id']] = [ $staff[$role][$person['id']] = [
'id' => $person['id'], 'id' => $person['id'],
'name' => $name, 'name' => $name,
'image' => $person['image']['original']['url'], 'image' => $person['image']['original']['url'],
'slug' => $person['slug'], 'slug' => $person['slug'],
]; ];
usort($staff[$role], fn ($a, $b) => $a['name'] <=> $b['name']); usort($staff[$role], static fn ($a, $b) => $a['name'] <=> $b['name']);
} }
ksort($staff); ksort($staff);
} }
if (count($base['mappings']['nodes']) > 0) if ((is_countable($base['mappings']['nodes']) ? count($base['mappings']['nodes']) : 0) > 0)
{ {
$links = Kitsu::mappingsToUrls($base['mappings']['nodes'], "https://kitsu.io/anime/{$base['slug']}"); $links = Kitsu::mappingsToUrls($base['mappings']['nodes'], "https://kitsu.io/anime/{$base['slug']}");
} }
@ -143,4 +140,4 @@ final class AnimeTransformer extends AbstractTransformer {
'url' => "https://kitsu.io/anime/{$base['slug']}", 'url' => "https://kitsu.io/anime/{$base['slug']}",
]); ]);
} }
} }

View File

@ -6,12 +6,10 @@
* *
* PHP version 8 * PHP version 8
* *
* @package HummingbirdAnimeClient * @copyright 2015 - 2022 Timothy J. Warren <tim@timshome.page>
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2021 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License * @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.2 * @version 5.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient * @link https://git.timshome.page/timw4mail/HummingBirdAnimeClient
*/ */
namespace Aviat\AnimeClient\API\Kitsu\Transformer; namespace Aviat\AnimeClient\API\Kitsu\Transformer;
@ -25,15 +23,11 @@ use Locale;
/** /**
* Data transformation class for character pages * Data transformation class for character pages
*/ */
final class CharacterTransformer extends AbstractTransformer { final class CharacterTransformer extends AbstractTransformer
{
/**
* @param array|object $item
* @return Character
*/
public function transform(array|object $item): Character public function transform(array|object $item): Character
{ {
$item = (array)$item; $item = (array) $item;
$data = $item['data']['findCharacterBySlug'] ?? []; $data = $item['data']['findCharacterBySlug'] ?? [];
$castings = []; $castings = [];
$media = [ $media = [
@ -42,10 +36,7 @@ final class CharacterTransformer extends AbstractTransformer {
]; ];
$names = array_unique( $names = array_unique(
array_merge( [...[$data['names']['canonical']], ...array_values($data['names']['localized'])]
[$data['names']['canonical']],
array_values($data['names']['localized'])
)
); );
$name = array_shift($names); $name = array_shift($names);
@ -66,19 +57,22 @@ final class CharacterTransformer extends AbstractTransformer {
]); ]);
} }
protected function organizeMediaAndVoices (array $data): array /**
* @return array<int, mixed[]>
*/
protected function organizeMediaAndVoices(array $data): array
{ {
if (empty($data)) if (empty($data))
{ {
return [[], []]; return [[], []];
} }
$titleSort = fn ($a, $b) => $a['title'] <=> $b['title']; $titleSort = static fn ($a, $b) => $a['title'] <=> $b['title'];
// First, let's deal with related media // First, let's deal with related media
$rawMedia = array_column($data, 'media'); $rawMedia = array_column($data, 'media');
$rawAnime = array_filter($rawMedia, fn ($item) => $item['type'] === 'Anime'); $rawAnime = array_filter($rawMedia, static fn ($item) => $item['type'] === 'Anime');
$rawManga = array_filter($rawMedia, fn ($item) => $item['type'] === 'Manga'); $rawManga = array_filter($rawMedia, static fn ($item) => $item['type'] === 'Manga');
$anime = array_map(static function ($item) { $anime = array_map(static function ($item) {
$output = $item; $output = $item;
@ -106,7 +100,7 @@ final class CharacterTransformer extends AbstractTransformer {
]; ];
// And now, reorganize voice actor relationships // And now, reorganize voice actor relationships
$rawVoices = array_filter($data, fn($item) => (! empty($item['voices'])) && count((array)$item['voices']['nodes']) > 0); $rawVoices = array_filter($data, static fn ($item) => ( ! empty($item['voices'])) && (array) $item['voices']['nodes'] !== []);
if (empty($rawVoices)) if (empty($rawVoices))
{ {
@ -139,7 +133,7 @@ final class CharacterTransformer extends AbstractTransformer {
'image' => $voice['person']['image']['original']['url'], 'image' => $voice['person']['image']['original']['url'],
'name' => $voice['person']['name'], 'name' => $voice['person']['name'],
], ],
'series' => [] 'series' => [],
]; ];
} }
@ -158,4 +152,4 @@ final class CharacterTransformer extends AbstractTransformer {
return [$media, $castings]; return [$media, $castings];
} }
} }

View File

@ -6,23 +6,22 @@
* *
* PHP version 8 * PHP version 8
* *
* @package HummingbirdAnimeClient * @copyright 2015 - 2022 Timothy J. Warren <tim@timshome.page>
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2021 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License * @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.2 * @version 5.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient * @link https://git.timshome.page/timw4mail/HummingBirdAnimeClient
*/ */
namespace Aviat\AnimeClient\API\Kitsu\Transformer; namespace Aviat\AnimeClient\API\Kitsu\Transformer;
use Aviat\AnimeClient\Types\HistoryItem;
use Aviat\AnimeClient\Kitsu; use Aviat\AnimeClient\Kitsu;
use Aviat\AnimeClient\Types\HistoryItem;
use DateTimeImmutable; use DateTimeImmutable;
use DateTimeInterface; use DateTimeInterface;
use DateTimeZone; use DateTimeZone;
abstract class HistoryTransformer { abstract class HistoryTransformer
{
/** /**
* @var string The media type * @var string The media type
*/ */
@ -51,13 +50,10 @@ abstract class HistoryTransformer {
/** /**
* @var array The mapping of api status to display status * @var array The mapping of api status to display status
*/ */
protected array $statusMap; protected array $statusMap = [];
/** /**
* Convert raw history * Convert raw history
*
* @param array $data
* @return array
*/ */
public function transform(array $data): array public function transform(array $data): array
{ {
@ -73,7 +69,7 @@ abstract class HistoryTransformer {
} }
// Hide private library entries // Hide private library entries
if ($entry['libraryEntry']['private'] === true) if ($entry['libraryEntry']['private'] === TRUE)
{ {
continue; continue;
} }
@ -88,7 +84,7 @@ abstract class HistoryTransformer {
$output[] = $transformed; $output[] = $transformed;
} }
} }
else if ($kind === 'updated') elseif ($kind === 'updated')
{ {
$output[] = $this->transformUpdated($entry); $output[] = $this->transformUpdated($entry);
} }
@ -99,15 +95,13 @@ abstract class HistoryTransformer {
/** /**
* Combine consecutive 'progressed' events * Combine consecutive 'progressed' events
*
* @param array $singles
* @return array
*/ */
protected function aggregate (array $singles): array protected function aggregate(array $singles): array
{ {
$output = []; $output = [];
$count = count($singles); $count = count($singles);
for ($i = 0; $i < $count; $i++) for ($i = 0; $i < $count; $i++)
{ {
$entries = []; $entries = [];
@ -115,9 +109,10 @@ abstract class HistoryTransformer {
$prevTitle = $entry['title']; $prevTitle = $entry['title'];
$nextId = $i; $nextId = $i;
$next = $singles[$nextId]; $next = $singles[$nextId];
while ( while (
$next['kind'] === 'progressed' && $next['kind'] === 'progressed'
$next['title'] === $prevTitle && $next['title'] === $prevTitle
) { ) {
$entries[] = $next; $entries[] = $next;
$prevTitle = $next['title']; $prevTitle = $next['title'];
@ -126,6 +121,7 @@ abstract class HistoryTransformer {
{ {
$nextId++; $nextId++;
$next = $singles[$nextId]; $next = $singles[$nextId];
continue; continue;
} }
@ -143,6 +139,7 @@ abstract class HistoryTransformer {
$items[] = array_pop($progressItem); $items[] = array_pop($progressItem);
$updated[] = $e['updated']; $updated[] = $e['updated'];
} }
$firstItem = min($items); $firstItem = min($items);
$lastItem = max($items); $lastItem = max($items);
$firstUpdate = min($updated); $firstUpdate = min($updated);
@ -165,7 +162,7 @@ abstract class HistoryTransformer {
'action' => $action, 'action' => $action,
'coverImg' => $entries[0]['coverImg'], 'coverImg' => $entries[0]['coverImg'],
'dateRange' => [$firstUpdate, $lastUpdate], 'dateRange' => [$firstUpdate, $lastUpdate],
'isAggregate' => true, 'isAggregate' => TRUE,
'original' => $entries, 'original' => $entries,
'title' => $title, 'title' => $title,
'updated' => $entries[0]['updated'], 'updated' => $entries[0]['updated'],
@ -174,6 +171,7 @@ abstract class HistoryTransformer {
// Skip the rest of the aggregate in the main loop // Skip the rest of the aggregate in the main loop
$i += count($entries) - 1; $i += count($entries) - 1;
continue; continue;
} }
@ -183,14 +181,14 @@ abstract class HistoryTransformer {
return $output; return $output;
} }
protected function transformProgress (array $entry): ?HistoryItem protected function transformProgress(array $entry): ?HistoryItem
{ {
$data = $entry['media']; $data = $entry['media'];
$title = $this->linkTitle($data); $title = $this->linkTitle($data);
$item = end($entry['changedData']['progress']); $item = end($entry['changedData']['progress']);
// No showing episode 0 nonsense // No showing episode 0 nonsense
if (((int)$item) === 0) if (((int) $item) === 0)
{ {
return NULL; return NULL;
} }
@ -256,12 +254,12 @@ abstract class HistoryTransformer {
return HistoryItem::from($entry); return HistoryItem::from($entry);
} }
protected function linkTitle (array $data): string protected function linkTitle(array $data): string
{ {
return $data['titles']['canonical']; return $data['titles']['canonical'];
} }
protected function parseDate (string $date): DateTimeImmutable protected function parseDate(string $date): DateTimeImmutable
{ {
$dateTime = DateTimeImmutable::createFromFormat( $dateTime = DateTimeImmutable::createFromFormat(
DateTimeInterface::RFC3339, DateTimeInterface::RFC3339,
@ -276,13 +274,13 @@ abstract class HistoryTransformer {
return $dateTime->setTimezone(new DateTimeZone(date_default_timezone_get())); return $dateTime->setTimezone(new DateTimeZone(date_default_timezone_get()));
} }
protected function getUrl (array $data): string protected function getUrl(array $data): string
{ {
return "/{$this->type}/details/{$data['slug']}"; return "/{$this->type}/details/{$data['slug']}";
} }
protected function isReconsuming (array $entry): bool protected function isReconsuming(array $entry): bool
{ {
return $entry['libraryEntry']['reconsuming']; return $entry['libraryEntry']['reconsuming'];
} }
} }

View File

@ -6,18 +6,16 @@
* *
* PHP version 8 * PHP version 8
* *
* @package HummingbirdAnimeClient * @copyright 2015 - 2022 Timothy J. Warren <tim@timshome.page>
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2021 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License * @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.2 * @version 5.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient * @link https://git.timshome.page/timw4mail/HummingBirdAnimeClient
*/ */
namespace Aviat\AnimeClient\API\Kitsu\Transformer; namespace Aviat\AnimeClient\API\Kitsu\Transformer;
use Aviat\AnimeClient\Kitsu; use Aviat\AnimeClient\Kitsu;
use Aviat\AnimeClient\Types\{FormItem, AnimeListItem, MangaListItem, MangaListItemDetail}; use Aviat\AnimeClient\Types\{AnimeListItem, FormItem, MangaListItem, MangaListItemDetail};
use Aviat\Ion\Transformer\AbstractTransformer; use Aviat\Ion\Transformer\AbstractTransformer;
use Aviat\Ion\Type\StringType; use Aviat\Ion\Type\StringType;
@ -28,7 +26,7 @@ final class LibraryEntryTransformer extends AbstractTransformer
{ {
public function transform(array|object $item): AnimeListItem|MangaListItem public function transform(array|object $item): AnimeListItem|MangaListItem
{ {
$item = (array)$item; $item = (array) $item;
$type = $item['media']['type'] ?? ''; $type = $item['media']['type'] ?? '';
$genres = []; $genres = [];
@ -60,6 +58,7 @@ final class LibraryEntryTransformer extends AbstractTransformer
: '-'; : '-';
$MALid = NULL; $MALid = NULL;
$AnilistId = NULL;
if (isset($anime['mappings']['nodes'])) if (isset($anime['mappings']['nodes']))
{ {
@ -68,7 +67,11 @@ final class LibraryEntryTransformer extends AbstractTransformer
if ($mapping['externalSite'] === 'MYANIMELIST_ANIME') if ($mapping['externalSite'] === 'MYANIMELIST_ANIME')
{ {
$MALid = $mapping['externalId']; $MALid = $mapping['externalId'];
break; }
if ($mapping['externalSite'] === 'ANILIST_ANIME')
{
$AnilistId = $mapping['externalId'];
} }
} }
} }
@ -82,6 +85,7 @@ final class LibraryEntryTransformer extends AbstractTransformer
return AnimeListItem::from([ return AnimeListItem::from([
'id' => $item['id'], 'id' => $item['id'],
'anilist_id' => $AnilistId,
'mal_id' => $MALid, 'mal_id' => $MALid,
'episodes' => [ 'episodes' => [
'watched' => (int) $item['progress'] !== 0 'watched' => (int) $item['progress'] !== 0
@ -93,7 +97,7 @@ final class LibraryEntryTransformer extends AbstractTransformer
'airing' => [ 'airing' => [
'status' => Kitsu::getAiringStatus($anime['startDate'], $anime['endDate']), 'status' => Kitsu::getAiringStatus($anime['startDate'], $anime['endDate']),
'started' => $anime['startDate'], 'started' => $anime['startDate'],
'ended' => $anime['endDate'] 'ended' => $anime['endDate'],
], ],
'anime' => [ 'anime' => [
'id' => $animeId, 'id' => $animeId,
@ -101,7 +105,7 @@ final class LibraryEntryTransformer extends AbstractTransformer
'title' => $title, 'title' => $title,
'titles' => $titles, 'titles' => $titles,
'slug' => $anime['slug'], 'slug' => $anime['slug'],
'show_type' => (string)StringType::from($anime['subtype'])->upperCaseFirst(), 'show_type' => (string) StringType::from($anime['subtype'])->upperCaseFirst(),
'cover_image' => Kitsu::getPosterImage($anime), 'cover_image' => Kitsu::getPosterImage($anime),
'genres' => $genres, 'genres' => $genres,
'streaming_links' => $streamingLinks, 'streaming_links' => $streamingLinks,
@ -137,6 +141,7 @@ final class LibraryEntryTransformer extends AbstractTransformer
: '-'; : '-';
$MALid = NULL; $MALid = NULL;
$AnilistId = NULL;
if (isset($manga['mappings']['nodes'])) if (isset($manga['mappings']['nodes']))
{ {
@ -145,7 +150,11 @@ final class LibraryEntryTransformer extends AbstractTransformer
if ($mapping['externalSite'] === 'MYANIMELIST_MANGA') if ($mapping['externalSite'] === 'MYANIMELIST_MANGA')
{ {
$MALid = $mapping['externalId']; $MALid = $mapping['externalId'];
break; }
if ($mapping['externalSite'] === 'ANILIST_MANGA')
{
$AnilistId = $mapping['externalId'];
} }
} }
} }
@ -155,14 +164,15 @@ final class LibraryEntryTransformer extends AbstractTransformer
return MangaListItem::from([ return MangaListItem::from([
'id' => $item['id'], 'id' => $item['id'],
'anilist_id' => $AnilistId,
'mal_id' => $MALid, 'mal_id' => $MALid,
'chapters' => [ 'chapters' => [
'read' => $readChapters, 'read' => $readChapters,
'total' => $totalChapters 'total' => $totalChapters,
], ],
'volumes' => [ 'volumes' => [
'read' => '-', //$item['attributes']['volumes_read'], 'read' => '-', //$item['attributes']['volumes_read'],
'total' => $totalVolumes 'total' => $totalVolumes,
], ],
'manga' => MangaListItemDetail::from([ 'manga' => MangaListItemDetail::from([
'genres' => $genres, 'genres' => $genres,
@ -171,14 +181,14 @@ final class LibraryEntryTransformer extends AbstractTransformer
'slug' => $manga['slug'], 'slug' => $manga['slug'],
'title' => $title, 'title' => $title,
'titles' => $titles, 'titles' => $titles,
'type' => (string)StringType::from($manga['subtype'])->upperCaseFirst(), 'type' => (string) StringType::from($manga['subtype'])->upperCaseFirst(),
'url' => 'https://kitsu.io/manga/' . $manga['slug'], 'url' => 'https://kitsu.io/manga/' . $manga['slug'],
]), ]),
'reading_status' => strtolower($item['status']), 'reading_status' => strtolower($item['status']),
'notes' => $item['notes'], 'notes' => $item['notes'],
'rereading' => (bool)$item['reconsuming'], 'rereading' => (bool) $item['reconsuming'],
'reread' => $item['reconsumeCount'], 'reread' => $item['reconsumeCount'],
'user_rating' => $rating, 'user_rating' => $rating,
]); ]);
} }
} }

View File

@ -6,28 +6,22 @@
* *
* PHP version 8 * PHP version 8
* *
* @package HummingbirdAnimeClient * @copyright 2015 - 2022 Timothy J. Warren <tim@timshome.page>
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2021 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License * @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.2 * @version 5.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient * @link https://git.timshome.page/timw4mail/HummingBirdAnimeClient
*/ */
namespace Aviat\AnimeClient\API\Kitsu\Transformer; namespace Aviat\AnimeClient\API\Kitsu\Transformer;
use Aviat\AnimeClient\API\Mapping\MangaReadingStatus; use Aviat\AnimeClient\API\Mapping\MangaReadingStatus;
class MangaHistoryTransformer extends HistoryTransformer { class MangaHistoryTransformer extends HistoryTransformer
{
protected string $type = 'manga'; protected string $type = 'manga';
protected string $progressAction = 'Read chapter'; protected string $progressAction = 'Read chapter';
protected string $reconsumeAction = 'Reread chapter'; protected string $reconsumeAction = 'Reread chapter';
protected string $largeAggregateAction = 'Blew through chapters'; protected string $largeAggregateAction = 'Blew through chapters';
protected string $reconsumingStatus = 'Rereading'; protected string $reconsumingStatus = 'Rereading';
protected array $statusMap = MangaReadingStatus::KITSU_TO_TITLE; protected array $statusMap = MangaReadingStatus::KITSU_TO_TITLE;
} }

View File

@ -6,12 +6,10 @@
* *
* PHP version 8 * PHP version 8
* *
* @package HummingbirdAnimeClient * @copyright 2015 - 2022 Timothy J. Warren <tim@timshome.page>
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2021 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License * @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.2 * @version 5.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient * @link https://git.timshome.page/timw4mail/HummingBirdAnimeClient
*/ */
namespace Aviat\AnimeClient\API\Kitsu\Transformer; namespace Aviat\AnimeClient\API\Kitsu\Transformer;
@ -27,16 +25,16 @@ use Aviat\Ion\Type\StringType;
/** /**
* Data transformation class for zippered Hummingbird manga * Data transformation class for zippered Hummingbird manga
*/ */
final class MangaListTransformer extends AbstractTransformer { final class MangaListTransformer extends AbstractTransformer
{
/** /**
* Remap zipped anime data to a more logical form * Remap zipped anime data to a more logical form
* *
* @param array|object $item manga entry item * @param array|object $item manga entry item
* @return MangaListItem
*/ */
public function transform(array|object $item): MangaListItem public function transform(array|object $item): MangaListItem
{ {
$item = (array)$item; $item = (array) $item;
$mangaId = $item['media']['id']; $mangaId = $item['media']['id'];
$manga = $item['media']; $manga = $item['media'];
@ -82,11 +80,11 @@ final class MangaListTransformer extends AbstractTransformer {
'mal_id' => $MALid, 'mal_id' => $MALid,
'chapters' => [ 'chapters' => [
'read' => $readChapters, 'read' => $readChapters,
'total' => $totalChapters 'total' => $totalChapters,
], ],
'volumes' => [ 'volumes' => [
'read' => '-', //$item['attributes']['volumes_read'], 'read' => '-', //$item['attributes']['volumes_read'],
'total' => $totalVolumes 'total' => $totalVolumes,
], ],
'manga' => MangaListItemDetail::from([ 'manga' => MangaListItemDetail::from([
'genres' => $genres, 'genres' => $genres,
@ -95,12 +93,12 @@ final class MangaListTransformer extends AbstractTransformer {
'slug' => $manga['slug'], 'slug' => $manga['slug'],
'title' => $title, 'title' => $title,
'titles' => $titles, 'titles' => $titles,
'type' => (string)StringType::from($manga['subtype'])->toLowerCase()->upperCaseFirst(), 'type' => (string) StringType::from($manga['subtype'])->toLowerCase()->upperCaseFirst(),
'url' => 'https://kitsu.io/manga/' . $manga['slug'], 'url' => 'https://kitsu.io/manga/' . $manga['slug'],
]), ]),
'reading_status' => strtolower($item['status']), 'reading_status' => strtolower($item['status']),
'notes' => $item['notes'], 'notes' => $item['notes'],
'rereading' => (bool)$item['reconsuming'], 'rereading' => (bool) $item['reconsuming'],
'reread' => $item['reconsumeCount'], 'reread' => $item['reconsumeCount'],
'user_rating' => $rating, 'user_rating' => $rating,
]); ]);
@ -109,12 +107,11 @@ final class MangaListTransformer extends AbstractTransformer {
/** /**
* Untransform data to update the api * Untransform data to update the api
* *
* @param array $item * @param array $item
* @return FormItem
*/ */
public function untransform($item): FormItem public function untransform($item): FormItem
{ {
$rereading = array_key_exists('rereading', $item) && (bool)$item['rereading']; $rereading = array_key_exists('rereading', $item) && (bool) $item['rereading'];
$map = FormItem::from([ $map = FormItem::from([
'id' => $item['id'], 'id' => $item['id'],
@ -122,14 +119,14 @@ final class MangaListTransformer extends AbstractTransformer {
'data' => FormItemData::from([ 'data' => FormItemData::from([
'status' => $item['status'], 'status' => $item['status'],
'reconsuming' => $rereading, 'reconsuming' => $rereading,
'reconsumeCount' => (int)$item['reread_count'], 'reconsumeCount' => (int) $item['reread_count'],
'notes' => $item['notes'], 'notes' => $item['notes'],
]), ]),
]); ]);
if (is_numeric($item['chapters_read']) && $item['chapters_read'] > 0) if (is_numeric($item['chapters_read']) && $item['chapters_read'] > 0)
{ {
$map['data']['progress'] = (int)$item['chapters_read']; $map['data']['progress'] = (int) $item['chapters_read'];
} }
if (is_numeric($item['new_rating']) && $item['new_rating'] > 0) if (is_numeric($item['new_rating']) && $item['new_rating'] > 0)
@ -140,4 +137,5 @@ final class MangaListTransformer extends AbstractTransformer {
return $map; return $map;
} }
} }
// End of MangaListTransformer.php
// End of MangaListTransformer.php

View File

@ -6,12 +6,10 @@
* *
* PHP version 8 * PHP version 8
* *
* @package HummingbirdAnimeClient * @copyright 2015 - 2022 Timothy J. Warren <tim@timshome.page>
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2021 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License * @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.2 * @version 5.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient * @link https://git.timshome.page/timw4mail/HummingBirdAnimeClient
*/ */
namespace Aviat\AnimeClient\API\Kitsu\Transformer; namespace Aviat\AnimeClient\API\Kitsu\Transformer;
@ -23,30 +21,29 @@ use Aviat\Ion\Transformer\AbstractTransformer;
/** /**
* Transformer for manga description page * Transformer for manga description page
*/ */
final class MangaTransformer extends AbstractTransformer { final class MangaTransformer extends AbstractTransformer
{
/** /**
* Convert raw api response to a more * Convert raw api response to a more
* logical and workable structure * logical and workable structure
* *
* @param array|object $item API library item * @param array|object $item API library item
* @return MangaPage
*/ */
public function transform(array|object $item): MangaPage public function transform(array|object $item): MangaPage
{ {
$item = (array)$item; $item = (array) $item;
$base = $item['data']['findMangaBySlug'] ?? $item['data']['findMangaById'] ?? $item['data']['randomMedia']; $base = $item['data']['findMangaBySlug'] ?? $item['data']['findMangaById'] ?? $item['data']['randomMedia'];
$characters = []; $characters = [];
$links = []; $links = [];
$staff = []; $staff = [];
$genres = array_map(fn ($genre) => $genre['title']['en'], $base['categories']['nodes']); $genres = array_map(static fn ($genre) => $genre['title']['en'], $base['categories']['nodes']);
sort($genres); sort($genres);
$title = $base['titles']['canonical']; $title = $base['titles']['canonical'];
$titles = Kitsu::getTitles($base['titles']); $titles = Kitsu::getTitles($base['titles']);
$titles_more = Kitsu::filterLocalizedTitles($base['titles']); $titles_more = Kitsu::filterLocalizedTitles($base['titles']);
if (count($base['characters']['nodes']) > 0) if ((is_countable($base['characters']['nodes']) ? count($base['characters']['nodes']) : 0) > 0)
{ {
foreach ($base['characters']['nodes'] as $rawCharacter) foreach ($base['characters']['nodes'] as $rawCharacter)
{ {
@ -57,11 +54,14 @@ final class MangaTransformer extends AbstractTransformer {
} }
$details = $rawCharacter['character']; $details = $rawCharacter['character'];
$characters[$type][$details['id']] = [ if (array_key_exists($details['id'], $characters[$type]))
'image' => $details['image']['original']['url'], {
'name' => $details['names']['canonical'], $characters[$type][$details['id']] = [
'slug' => $details['slug'], 'image' => $details['image']['original']['url'],
]; 'name' => $details['names']['canonical'],
'slug' => $details['slug'],
];
}
} }
foreach (array_keys($characters) as $type) foreach (array_keys($characters) as $type)
@ -72,14 +72,14 @@ final class MangaTransformer extends AbstractTransformer {
} }
else else
{ {
uasort($characters[$type], fn($a, $b) => $a['name'] <=> $b['name']); uasort($characters[$type], static fn ($a, $b) => $a['name'] <=> $b['name']);
} }
} }
krsort($characters); krsort($characters);
} }
if (count($base['staff']['nodes']) > 0) if ((is_countable($base['staff']['nodes']) ? count($base['staff']['nodes']) : 0) > 0)
{ {
foreach ($base['staff']['nodes'] as $staffing) foreach ($base['staff']['nodes'] as $staffing)
{ {
@ -89,7 +89,7 @@ final class MangaTransformer extends AbstractTransformer {
// If this person object is so broken as to not have a proper image object, // If this person object is so broken as to not have a proper image object,
// just skip it. No point in showing a role with nothing in it. // just skip it. No point in showing a role with nothing in it.
if ($person === null || $person['id'] === null || $person['image'] === null) if ($person === NULL || $person['id'] === NULL || $person['image'] === NULL)
{ {
continue; continue;
} }
@ -106,13 +106,13 @@ final class MangaTransformer extends AbstractTransformer {
'image' => $person['image']['original']['url'], 'image' => $person['image']['original']['url'],
]; ];
usort($staff[$role], fn ($a, $b) => $a['name'] <=> $b['name']); usort($staff[$role], static fn ($a, $b) => $a['name'] <=> $b['name']);
} }
ksort($staff); ksort($staff);
} }
if (count($base['mappings']['nodes']) > 0) if ((is_countable($base['mappings']['nodes']) ? count($base['mappings']['nodes']) : 0) > 0)
{ {
$links = Kitsu::mappingsToUrls($base['mappings']['nodes'], "https://kitsu.io/manga/{$base['slug']}"); $links = Kitsu::mappingsToUrls($base['mappings']['nodes'], "https://kitsu.io/manga/{$base['slug']}");
} }
@ -139,4 +139,4 @@ final class MangaTransformer extends AbstractTransformer {
return MangaPage::from($data); return MangaPage::from($data);
} }
} }

View File

@ -6,12 +6,10 @@
* *
* PHP version 8 * PHP version 8
* *
* @package HummingbirdAnimeClient * @copyright 2015 - 2022 Timothy J. Warren <tim@timshome.page>
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2021 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License * @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.2 * @version 5.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient * @link https://git.timshome.page/timw4mail/HummingBirdAnimeClient
*/ */
namespace Aviat\AnimeClient\API\Kitsu\Transformer; namespace Aviat\AnimeClient\API\Kitsu\Transformer;
@ -23,15 +21,11 @@ use Aviat\Ion\Transformer\AbstractTransformer;
/** /**
* Data transformation class for people pages * Data transformation class for people pages
*/ */
final class PersonTransformer extends AbstractTransformer { final class PersonTransformer extends AbstractTransformer
{
/**
* @param array|object $item
* @return Person
*/
public function transform(array|object $item): Person public function transform(array|object $item): Person
{ {
$item = (array)$item; $item = (array) $item;
$data = $item['data']['findPersonBySlug'] ?? []; $data = $item['data']['findPersonBySlug'] ?? [];
$canonicalName = $data['names']['localized'][$data['names']['canonical']] $canonicalName = $data['names']['localized'][$data['names']['canonical']]
?? array_shift($data['names']['localized']); ?? array_shift($data['names']['localized']);
@ -49,6 +43,9 @@ final class PersonTransformer extends AbstractTransformer {
]); ]);
} }
/**
* @return array<string, array<int|string, array<int|string, array<int|string, array<int|string, mixed>>>>>
*/
protected function organizeData(array $data): array protected function organizeData(array $data): array
{ {
$output = [ $output = [
@ -59,13 +56,15 @@ final class PersonTransformer extends AbstractTransformer {
$characters = []; $characters = [];
$staff = []; $staff = [];
if (count($data['mediaStaff']['nodes']) > 0) if ((is_countable($data['mediaStaff']['nodes']) ? count($data['mediaStaff']['nodes']) : 0) > 0)
{ {
$roles = array_unique(array_column($data['mediaStaff']['nodes'], 'role')); $roles = array_unique(array_column($data['mediaStaff']['nodes'], 'role'));
foreach ($roles as $role) foreach ($roles as $role)
{ {
$staff[$role] = []; $staff[$role] = [];
} }
ksort($staff); ksort($staff);
foreach ($data['mediaStaff']['nodes'] as $staffing) foreach ($data['mediaStaff']['nodes'] as $staffing)
@ -88,13 +87,13 @@ final class PersonTransformer extends AbstractTransformer {
'slug' => $media['slug'], 'slug' => $media['slug'],
]; ];
uasort($staff[$role][$type], fn ($a, $b) => $a['title'] <=> $b['title']); uasort($staff[$role][$type], static fn ($a, $b) => $a['title'] <=> $b['title']);
} }
$output['staff'] = $staff; $output['staff'] = $staff;
} }
if (count($data['voices']['nodes']) > 0) if ((is_countable($data['voices']['nodes']) ? count($data['voices']['nodes']) : 0) > 0)
{ {
foreach ($data['voices']['nodes'] as $voicing) foreach ($data['voices']['nodes'] as $voicing)
{ {
@ -128,7 +127,7 @@ final class PersonTransformer extends AbstractTransformer {
'canonicalName' => $character['names']['canonical'], 'canonicalName' => $character['names']['canonical'],
], ],
'media' => [ 'media' => [
$media['id'] => $media $media['id'] => $media,
], ],
]; ];
} }
@ -143,7 +142,7 @@ final class PersonTransformer extends AbstractTransformer {
// Sort the characters by name // Sort the characters by name
uasort( uasort(
$characters[$role], $characters[$role],
fn($a, $b) => $a['character']['canonicalName'] <=> $b['character']['canonicalName'] static fn ($a, $b) => $a['character']['canonicalName'] <=> $b['character']['canonicalName']
); );
// Sort the media for the character // Sort the media for the character
@ -151,7 +150,7 @@ final class PersonTransformer extends AbstractTransformer {
{ {
uasort( uasort(
$characters[$role][$charId]['media'], $characters[$role][$charId]['media'],
fn ($a, $b) => $a['titles'][0] <=> $b['titles'][0] static fn ($a, $b) => $a['titles'][0] <=> $b['titles'][0]
); );
} }
} }
@ -163,4 +162,4 @@ final class PersonTransformer extends AbstractTransformer {
return $output; return $output;
} }
} }

View File

@ -6,18 +6,15 @@
* *
* PHP version 8 * PHP version 8
* *
* @package HummingbirdAnimeClient * @copyright 2015 - 2022 Timothy J. Warren <tim@timshome.page>
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2021 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License * @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.2 * @version 5.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient * @link https://git.timshome.page/timw4mail/HummingBirdAnimeClient
*/ */
namespace Aviat\AnimeClient\API\Kitsu\Transformer; namespace Aviat\AnimeClient\API\Kitsu\Transformer;
use Aviat\AnimeClient\Kitsu; use Aviat\AnimeClient\Kitsu;
use function Aviat\AnimeClient\getLocalImg;
use Aviat\AnimeClient\Types\User; use Aviat\AnimeClient\Types\User;
use Aviat\Ion\Transformer\AbstractTransformer; use Aviat\Ion\Transformer\AbstractTransformer;
@ -28,10 +25,11 @@ use Aviat\Ion\Transformer\AbstractTransformer;
* @param array|object $profileData * @param array|object $profileData
* @return User * @return User
*/ */
final class UserTransformer extends AbstractTransformer { final class UserTransformer extends AbstractTransformer
{
public function transform(array|object $item): User public function transform(array|object $item): User
{ {
$item = (array)$item; $item = (array) $item;
$base = $item['data']['findProfileBySlug'] ?? []; $base = $item['data']['findProfileBySlug'] ?? [];
$favorites = $base['favorites']['nodes'] ?? []; $favorites = $base['favorites']['nodes'] ?? [];
$stats = $base['stats'] ?? []; $stats = $base['stats'] ?? [];
@ -56,8 +54,7 @@ final class UserTransformer extends AbstractTransformer {
/** /**
* Reorganize favorites data to be more useful * Reorganize favorites data to be more useful
* *
* @param array $rawFavorites * @return array<string, array<int|string, mixed>>
* @return array
*/ */
private function organizeFavorites(array $rawFavorites): array private function organizeFavorites(array $rawFavorites): array
{ {
@ -72,6 +69,9 @@ final class UserTransformer extends AbstractTransformer {
return $output; return $output;
} }
/**
* @return array<string, string>
*/
private function organizeStats(array $stats, array $data = []): array private function organizeStats(array $stats, array $data = []): array
{ {
$animeStats = []; $animeStats = [];
@ -106,4 +106,4 @@ final class UserTransformer extends AbstractTransformer {
return array_merge($animeStats, $mangaStats, $otherStats); return array_merge($animeStats, $mangaStats, $otherStats);
} }
} }

View File

@ -73,8 +73,10 @@ interface Media {
ageRatingGuide: String ageRatingGuide: String
"The average rating of this media amongst all Kitsu users" "The average rating of this media amongst all Kitsu users"
averageRating: Float averageRating: Float
"The rank of this media by rating"
averageRatingRank: Int
"A large banner image for this media" "A large banner image for this media"
bannerImage: Image! bannerImage: Image
"A list of categories for this media" "A list of categories for this media"
categories( categories(
"Returns the elements in the list that come after the specified cursor." "Returns the elements in the list that come after the specified cursor."
@ -133,10 +135,26 @@ interface Media {
): WikiSubmissionConnection! ): WikiSubmissionConnection!
"The time of the next release of this media" "The time of the next release of this media"
nextRelease: ISO8601DateTime nextRelease: ISO8601DateTime
"The countries in which the media was originally primarily produced"
originCountries: [String!]!
"The languages the media was originally produced in"
originLanguages: [String!]!
"The country in which the media was primarily produced" "The country in which the media was primarily produced"
originalLocale: String originalLocale: String @deprecated(reason: "Replaced with originCountries and originLanguages")
"The poster image of this media" "The poster image of this media"
posterImage: Image! posterImage: Image
"All posts that tag this media."
posts(
"Returns the elements in the list that come after the specified cursor."
after: String,
"Returns the elements in the list that come before the specified cursor."
before: String,
"Returns the first _n_ elements from the list."
first: Int,
"Returns the last _n_ elements from the list."
last: Int,
sort: [PostSortOption]
): PostConnection!
"The companies which helped to produce this media" "The companies which helped to produce this media"
productions( productions(
"Returns the elements in the list that come after the specified cursor." "Returns the elements in the list that come after the specified cursor."
@ -198,6 +216,8 @@ interface Media {
type: String! type: String!
"The number of users with this in their library" "The number of users with this in their library"
userCount: Int userCount: Int
"The rank of this media by popularity"
userCountRank: Int
} }
"Media that is streamable." "Media that is streamable."
@ -230,6 +250,8 @@ interface WithTimestamps {
updatedAt: ISO8601DateTime! updatedAt: ISO8601DateTime!
} }
union AccountCreateErrorsUnion = ValidationError
"Objects which are Favoritable" "Objects which are Favoritable"
union FavoriteItemUnion = Anime | Character | Manga | Person union FavoriteItemUnion = Anime | Character | Manga | Person
@ -274,6 +296,12 @@ type Account implements WithTimestamps {
updatedAt: ISO8601DateTime! updatedAt: ISO8601DateTime!
} }
"Autogenerated return type of AccountCreate"
type AccountCreatePayload {
errors: [AccountCreateErrorsUnion!]
result: Account
}
type AccountMutations { type AccountMutations {
"Send a password reset email" "Send a password reset email"
sendPasswordReset( sendPasswordReset(
@ -294,8 +322,10 @@ type Anime implements Episodic & Media & WithTimestamps {
ageRatingGuide: String ageRatingGuide: String
"The average rating of this media amongst all Kitsu users" "The average rating of this media amongst all Kitsu users"
averageRating: Float averageRating: Float
"The rank of this media by rating"
averageRatingRank: Int
"A large banner image for this media" "A large banner image for this media"
bannerImage: Image! bannerImage: Image
"A list of categories for this media" "A list of categories for this media"
categories( categories(
"Returns the elements in the list that come after the specified cursor." "Returns the elements in the list that come after the specified cursor."
@ -371,10 +401,26 @@ type Anime implements Episodic & Media & WithTimestamps {
): WikiSubmissionConnection! ): WikiSubmissionConnection!
"The time of the next release of this media" "The time of the next release of this media"
nextRelease: ISO8601DateTime nextRelease: ISO8601DateTime
"The countries in which the media was originally primarily produced"
originCountries: [String!]!
"The languages the media was originally produced in"
originLanguages: [String!]!
"The country in which the media was primarily produced" "The country in which the media was primarily produced"
originalLocale: String originalLocale: String @deprecated(reason: "Replaced with originCountries and originLanguages")
"The poster image of this media" "The poster image of this media"
posterImage: Image! posterImage: Image
"All posts that tag this media."
posts(
"Returns the elements in the list that come after the specified cursor."
after: String,
"Returns the elements in the list that come before the specified cursor."
before: String,
"Returns the first _n_ elements from the list."
first: Int,
"Returns the last _n_ elements from the list."
last: Int,
sort: [PostSortOption]
): PostConnection!
"The companies which helped to produce this media" "The companies which helped to produce this media"
productions( productions(
"Returns the elements in the list that come after the specified cursor." "Returns the elements in the list that come after the specified cursor."
@ -454,6 +500,8 @@ type Anime implements Episodic & Media & WithTimestamps {
updatedAt: ISO8601DateTime! updatedAt: ISO8601DateTime!
"The number of users with this in their library" "The number of users with this in their library"
userCount: Int userCount: Int
"The rank of this media by popularity"
userCountRank: Int
"Video id for a trailer on YouTube" "Video id for a trailer on YouTube"
youtubeTrailerVideoId: String youtubeTrailerVideoId: String
} }
@ -598,6 +646,8 @@ type Chapter implements Unit & WithTimestamps {
"A brief summary or description of the unit" "A brief summary or description of the unit"
description(locales: [String!]): Map! description(locales: [String!]): Map!
id: ID! id: ID!
"Number of pages in chapter."
length: Int
"The manga this chapter is in." "The manga this chapter is in."
manga: Manga! manga: Manga!
"The sequence number of this unit" "The sequence number of this unit"
@ -906,7 +956,11 @@ type FranchiseEdge {
node: Franchise node: Franchise
} }
type Generic implements Error { type GenericDelete {
id: ID!
}
type GenericError implements Error {
"The error code." "The error code."
code: String code: String
"A description of the error" "A description of the error"
@ -915,10 +969,6 @@ type Generic implements Error {
path: [String!] path: [String!]
} }
type GenericDelete {
id: ID!
}
type Image { type Image {
"A blurhash-encoded version of this image" "A blurhash-encoded version of this image"
blurhash: String blurhash: String
@ -1273,8 +1323,10 @@ type Manga implements Media & WithTimestamps {
ageRatingGuide: String ageRatingGuide: String
"The average rating of this media amongst all Kitsu users" "The average rating of this media amongst all Kitsu users"
averageRating: Float averageRating: Float
"The rank of this media by rating"
averageRatingRank: Int
"A large banner image for this media" "A large banner image for this media"
bannerImage: Image! bannerImage: Image
"A list of categories for this media" "A list of categories for this media"
categories( categories(
"Returns the elements in the list that come after the specified cursor." "Returns the elements in the list that come after the specified cursor."
@ -1350,10 +1402,26 @@ type Manga implements Media & WithTimestamps {
): WikiSubmissionConnection! ): WikiSubmissionConnection!
"The time of the next release of this media" "The time of the next release of this media"
nextRelease: ISO8601DateTime nextRelease: ISO8601DateTime
"The countries in which the media was originally primarily produced"
originCountries: [String!]!
"The languages the media was originally produced in"
originLanguages: [String!]!
"The country in which the media was primarily produced" "The country in which the media was primarily produced"
originalLocale: String originalLocale: String @deprecated(reason: "Replaced with originCountries and originLanguages")
"The poster image of this media" "The poster image of this media"
posterImage: Image! posterImage: Image
"All posts that tag this media."
posts(
"Returns the elements in the list that come after the specified cursor."
after: String,
"Returns the elements in the list that come before the specified cursor."
before: String,
"Returns the first _n_ elements from the list."
first: Int,
"Returns the last _n_ elements from the list."
last: Int,
sort: [PostSortOption]
): PostConnection!
"The companies which helped to produce this media" "The companies which helped to produce this media"
productions( productions(
"Returns the elements in the list that come after the specified cursor." "Returns the elements in the list that come after the specified cursor."
@ -1418,6 +1486,8 @@ type Manga implements Media & WithTimestamps {
updatedAt: ISO8601DateTime! updatedAt: ISO8601DateTime!
"The number of users with this in their library" "The number of users with this in their library"
userCount: Int userCount: Int
"The rank of this media by popularity"
userCountRank: Int
"The number of volumes in this manga." "The number of volumes in this manga."
volumeCount: Int volumeCount: Int
} }
@ -1639,6 +1709,8 @@ type MediaReaction implements WithTimestamps {
"The author who wrote this reaction." "The author who wrote this reaction."
author: Profile! author: Profile!
createdAt: ISO8601DateTime! createdAt: ISO8601DateTime!
"Whether you have liked this media reaction"
hasLiked: Boolean!
id: ID! id: ID!
"The library entry related to this reaction." "The library entry related to this reaction."
libraryEntry: LibraryEntry! libraryEntry: LibraryEntry!
@ -1718,6 +1790,8 @@ type MediaStaffEdge {
type Mutation { type Mutation {
account: AccountMutations! account: AccountMutations!
"Create a new Kitsu account"
accountCreate(input: AccountCreateInput!): AccountCreatePayload
anime: AnimeMutations! anime: AnimeMutations!
episode: EpisodeMutations! episode: EpisodeMutations!
libraryEntry: LibraryEntryMutations! libraryEntry: LibraryEntryMutations!
@ -1852,6 +1926,12 @@ type PostConnection {
totalCount: Int! totalCount: Int!
} }
"Autogenerated return type of PostCreate"
type PostCreatePayload {
errors: [Error!]
post: Post
}
"An edge in a connection." "An edge in a connection."
type PostEdge { type PostEdge {
"A cursor for use in pagination." "A cursor for use in pagination."
@ -1867,6 +1947,11 @@ type PostLockPayload {
} }
type PostMutations { type PostMutations {
"Create a Post."
create(
"Create a Post"
input: PostCreateInput!
): PostCreatePayload
"Lock a Post." "Lock a Post."
lock( lock(
"Lock a Post." "Lock a Post."
@ -2116,6 +2201,19 @@ type ProfileEdge {
node: Profile node: Profile
} }
"An external site that can be linked to a user."
type ProfileLinkSite implements WithTimestamps {
createdAt: ISO8601DateTime!
id: ID!
"Name of the external profile website."
name: String!
updatedAt: ISO8601DateTime!
"Regex pattern used to validate the profile link."
validateFind: String!
"Pattern to be replaced after validation."
validateReplace: String!
}
"The different types of user stats that we calculate." "The different types of user stats that we calculate."
type ProfileStats { type ProfileStats {
"The total amount of anime you have watched over your whole life." "The total amount of anime you have watched over your whole life."
@ -2185,6 +2283,8 @@ type Query {
findMangaById(id: ID!): Manga findMangaById(id: ID!): Manga
"Find a single Manga by Slug" "Find a single Manga by Slug"
findMangaBySlug(slug: String!): Manga findMangaBySlug(slug: String!): Manga
"Find a single Media by ID and Type"
findMediaByIdAndType(id: ID!, mediaType: MediaTypeEnum!): Media
"Find a single Person by ID" "Find a single Person by ID"
findPersonById(id: ID!): Person findPersonById(id: ID!): Person
"Find a single Person by Slug" "Find a single Person by Slug"
@ -2471,7 +2571,7 @@ type Report implements WithTimestamps {
"The moderator who responded to this report" "The moderator who responded to this report"
moderator: Profile moderator: Profile
"The entity that the report is related to" "The entity that the report is related to"
naughty: ReportItemUnion! naughty: ReportItemUnion
"The reason for why the report was made" "The reason for why the report was made"
reason: ReportReasonEnum! reason: ReportReasonEnum!
"The user who made this report" "The user who made this report"
@ -2573,6 +2673,8 @@ type SiteLink implements WithTimestamps {
author: Profile! author: Profile!
createdAt: ISO8601DateTime! createdAt: ISO8601DateTime!
id: ID! id: ID!
"The actual linked website."
site: ProfileLinkSite!
updatedAt: ISO8601DateTime! updatedAt: ISO8601DateTime!
"A fully qualified URL of the user profile on an external site." "A fully qualified URL of the user profile on an external site."
url: String! url: String!
@ -2672,11 +2774,34 @@ type TitlesList {
"A list of additional, alternative, abbreviated, or unofficial titles" "A list of additional, alternative, abbreviated, or unofficial titles"
alternatives: [String!] alternatives: [String!]
"The official or de facto international title" "The official or de facto international title"
canonical: String canonical: String!
"The locale code that identifies which title is used as the canonical title" "The locale code that identifies which title is used as the canonical title"
canonicalLocale: String canonicalLocale: String
"The list of localized titles keyed by locale" "The list of localized titles keyed by locale"
localized(locales: [String!]): Map! localized(locales: [String!]): Map!
"The original title of the media in the original language"
original: String
"The locale code that identifies which title is used as the original title"
originalLocale: String
"The title that best matches the user's preferred settings"
preferred: String!
"The original title, romanized into latin script"
romanized: String
"The locale code that identifies which title is used as the romanized title"
romanizedLocale: String
"The title translated into the user's locale"
translated: String
"The locale code that identifies which title is used as the translated title"
translatedLocale: String
}
type ValidationError implements Error {
"The error code."
code: String
"A description of the error"
message: String!
"Which input value this error came from"
path: [String!]
} }
"The media video." "The media video."
@ -2875,6 +3000,11 @@ enum EpisodeSortEnum {
UPDATED_AT UPDATED_AT
} }
enum ExternalIdentityProviderEnum {
"Facebook identity"
FACEBOOK
}
enum FollowSortEnum { enum FollowSortEnum {
CREATED_AT CREATED_AT
FOLLOWING_FOLLOWED FOLLOWING_FOLLOWED
@ -3029,9 +3159,9 @@ enum PostSortEnum {
enum ProTierEnum { enum ProTierEnum {
"Aozora Pro (only hides ads)" "Aozora Pro (only hides ads)"
AO_PRO @deprecated(reason : "No longer for sale") AO_PRO @deprecated(reason: "No longer for sale")
"Aozora Pro+ (only hides ads)" "Aozora Pro+ (only hides ads)"
AO_PRO_PLUS @deprecated(reason : "No longer for sale") AO_PRO_PLUS @deprecated(reason: "No longer for sale")
"Top tier of Kitsu Pro" "Top tier of Kitsu Pro"
PATRON PATRON
"Basic tier of Kitsu Pro" "Basic tier of Kitsu Pro"
@ -3134,6 +3264,39 @@ enum WikiSubmissionStatusEnum {
REJECTED REJECTED
} }
"A date, expressed as an ISO8601 string"
scalar Date
"An ISO 8601-encoded date"
scalar ISO8601Date
"An ISO 8601-encoded datetime"
scalar ISO8601DateTime
"Represents untyped JSON"
scalar JSON
"A loose key-value map in GraphQL"
scalar Map
scalar Upload
input AccountCreateInput {
"The email address to reset the password for"
email: String!
"An external identity to associate with the account on creation"
externalIdentity: AccountExternalIdentityInput
"The name of the user"
name: String!
"The password for the user"
password: String!
}
input AccountExternalIdentityInput {
id: String!
provider: ExternalIdentityProviderEnum!
}
input AnimeCreateInput { input AnimeCreateInput {
ageRating: AgeRatingEnum ageRating: AgeRatingEnum
ageRatingGuide: String ageRatingGuide: String
@ -3324,6 +3487,16 @@ input MediaReactionVoteSortOption {
on: MediaReactionVoteSortEnum! on: MediaReactionVoteSortEnum!
} }
input PostCreateInput {
content: String!
isNsfw: Boolean = false
isSpoiler: Boolean = false
mediaId: ID
mediaType: MediaTypeEnum
spoiledUnitId: ID
spoiledUnitType: String
}
input PostLikeSortOption { input PostLikeSortOption {
direction: SortDirection! direction: SortDirection!
on: PostLikeSortEnum! on: PostLikeSortEnum!
@ -3373,21 +3546,3 @@ input WikiSubmissionUpdateDraftInput {
id: ID! id: ID!
notes: String notes: String
} }
"A date, expressed as an ISO8601 string"
scalar Date
"An ISO 8601-encoded date"
scalar ISO8601Date
"An ISO 8601-encoded datetime"
scalar ISO8601DateTime
"Represents untyped JSON"
scalar JSON
"A loose key-value map in GraphQL"
scalar Map
scalar Upload

View File

@ -6,12 +6,10 @@
* *
* PHP version 8 * PHP version 8
* *
* @package HummingbirdAnimeClient * @copyright 2015 - 2022 Timothy J. Warren <tim@timshome.page>
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2021 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License * @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.2 * @version 5.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient * @link https://git.timshome.page/timw4mail/HummingBirdAnimeClient
*/ */
namespace Aviat\AnimeClient\API\Mapping; namespace Aviat\AnimeClient\API\Mapping;
@ -23,54 +21,50 @@ use Aviat\Ion\Enum;
* Anime watching status mappings, among Kitsu, MAL, Page titles * Anime watching status mappings, among Kitsu, MAL, Page titles
* and url route segments * and url route segments
*/ */
final class AnimeWatchingStatus extends Enum { final class AnimeWatchingStatus extends Enum
{
public const ANILIST_TO_KITSU = [ public const ANILIST_TO_KITSU = [
Anilist::WATCHING => Kitsu::WATCHING, Anilist::WATCHING => Kitsu::WATCHING,
Anilist::PLAN_TO_WATCH => Kitsu::PLAN_TO_WATCH, Anilist::PLAN_TO_WATCH => Kitsu::PLAN_TO_WATCH,
Anilist::COMPLETED => Kitsu::COMPLETED, Anilist::COMPLETED => Kitsu::COMPLETED,
Anilist::ON_HOLD => Kitsu::ON_HOLD, Anilist::ON_HOLD => Kitsu::ON_HOLD,
Anilist::DROPPED => Kitsu::DROPPED Anilist::DROPPED => Kitsu::DROPPED,
]; ];
public const KITSU_TO_ANILIST = [ public const KITSU_TO_ANILIST = [
Kitsu::WATCHING => Anilist::WATCHING, Kitsu::WATCHING => Anilist::WATCHING,
Kitsu::PLAN_TO_WATCH => Anilist::PLAN_TO_WATCH, Kitsu::PLAN_TO_WATCH => Anilist::PLAN_TO_WATCH,
Kitsu::COMPLETED => Anilist::COMPLETED, Kitsu::COMPLETED => Anilist::COMPLETED,
Kitsu::ON_HOLD => Anilist::ON_HOLD, Kitsu::ON_HOLD => Anilist::ON_HOLD,
Kitsu::DROPPED => Anilist::DROPPED Kitsu::DROPPED => Anilist::DROPPED,
]; ];
public const KITSU_TO_TITLE = [ public const KITSU_TO_TITLE = [
Kitsu::WATCHING => Title::WATCHING, Kitsu::WATCHING => Title::WATCHING,
Kitsu::PLAN_TO_WATCH => Title::PLAN_TO_WATCH, Kitsu::PLAN_TO_WATCH => Title::PLAN_TO_WATCH,
Kitsu::ON_HOLD => Title::ON_HOLD, Kitsu::ON_HOLD => Title::ON_HOLD,
Kitsu::DROPPED => Title::DROPPED, Kitsu::DROPPED => Title::DROPPED,
Kitsu::COMPLETED => Title::COMPLETED Kitsu::COMPLETED => Title::COMPLETED,
]; ];
public const ROUTE_TO_KITSU = [ public const ROUTE_TO_KITSU = [
Route::WATCHING => Kitsu::WATCHING, Route::WATCHING => Kitsu::WATCHING,
Route::PLAN_TO_WATCH => Kitsu::PLAN_TO_WATCH, Route::PLAN_TO_WATCH => Kitsu::PLAN_TO_WATCH,
Route::ON_HOLD => Kitsu::ON_HOLD, Route::ON_HOLD => Kitsu::ON_HOLD,
Route::DROPPED => Kitsu::DROPPED, Route::DROPPED => Kitsu::DROPPED,
Route::COMPLETED => Kitsu::COMPLETED Route::COMPLETED => Kitsu::COMPLETED,
]; ];
public const ROUTE_TO_TITLE = [ public const ROUTE_TO_TITLE = [
Route::ALL => Title::ALL, Route::ALL => Title::ALL,
Route::WATCHING => Title::WATCHING, Route::WATCHING => Title::WATCHING,
Route::PLAN_TO_WATCH => Title::PLAN_TO_WATCH, Route::PLAN_TO_WATCH => Title::PLAN_TO_WATCH,
Route::ON_HOLD => Title::ON_HOLD, Route::ON_HOLD => Title::ON_HOLD,
Route::DROPPED => Title::DROPPED, Route::DROPPED => Title::DROPPED,
Route::COMPLETED => Title::COMPLETED Route::COMPLETED => Title::COMPLETED,
]; ];
public const TITLE_TO_ROUTE = [ public const TITLE_TO_ROUTE = [
Title::ALL => Route::ALL, Title::ALL => Route::ALL,
Title::WATCHING => Route::WATCHING, Title::WATCHING => Route::WATCHING,
Title::PLAN_TO_WATCH => Route::PLAN_TO_WATCH, Title::PLAN_TO_WATCH => Route::PLAN_TO_WATCH,
Title::ON_HOLD => Route::ON_HOLD, Title::ON_HOLD => Route::ON_HOLD,
Title::DROPPED => Route::DROPPED, Title::DROPPED => Route::DROPPED,
Title::COMPLETED => Route::COMPLETED Title::COMPLETED => Route::COMPLETED,
]; ];
} }

View File

@ -6,40 +6,37 @@
* *
* PHP version 8 * PHP version 8
* *
* @package HummingbirdAnimeClient * @copyright 2015 - 2022 Timothy J. Warren <tim@timshome.page>
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2021 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License * @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.2 * @version 5.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient * @link https://git.timshome.page/timw4mail/HummingBirdAnimeClient
*/ */
namespace Aviat\AnimeClient\API\Mapping; namespace Aviat\AnimeClient\API\Mapping;
use Aviat\AnimeClient\API\Enum\MangaReadingStatus\{Anilist, Kitsu, Title, Route}; use Aviat\AnimeClient\API\Enum\MangaReadingStatus\{Anilist, Kitsu, Route, Title};
use Aviat\Ion\Enum; use Aviat\Ion\Enum;
/** /**
* Manga reading status mappings, among Kitsu, MAL, Page titles * Manga reading status mappings, among Kitsu, MAL, Page titles
* and url route segments * and url route segments
*/ */
final class MangaReadingStatus extends Enum { final class MangaReadingStatus extends Enum
{
public const ANILIST_TO_KITSU = [ public const ANILIST_TO_KITSU = [
Anilist::READING => Kitsu::READING, Anilist::READING => Kitsu::READING,
Anilist::PLAN_TO_READ => Kitsu::PLAN_TO_READ, Anilist::PLAN_TO_READ => Kitsu::PLAN_TO_READ,
Anilist::COMPLETED => Kitsu::COMPLETED, Anilist::COMPLETED => Kitsu::COMPLETED,
Anilist::ON_HOLD => Kitsu::ON_HOLD, Anilist::ON_HOLD => Kitsu::ON_HOLD,
Anilist::DROPPED => Kitsu::DROPPED Anilist::DROPPED => Kitsu::DROPPED,
]; ];
public const KITSU_TO_ANILIST = [ public const KITSU_TO_ANILIST = [
Kitsu::READING => Anilist::READING, Kitsu::READING => Anilist::READING,
Kitsu::PLAN_TO_READ => Anilist::PLAN_TO_READ, Kitsu::PLAN_TO_READ => Anilist::PLAN_TO_READ,
Kitsu::COMPLETED => Anilist::COMPLETED, Kitsu::COMPLETED => Anilist::COMPLETED,
Kitsu::ON_HOLD => Anilist::ON_HOLD, Kitsu::ON_HOLD => Anilist::ON_HOLD,
Kitsu::DROPPED => Anilist::DROPPED Kitsu::DROPPED => Anilist::DROPPED,
]; ];
public const KITSU_TO_TITLE = [ public const KITSU_TO_TITLE = [
Kitsu::READING => Title::READING, Kitsu::READING => Title::READING,
Kitsu::PLAN_TO_READ => Title::PLAN_TO_READ, Kitsu::PLAN_TO_READ => Title::PLAN_TO_READ,
@ -47,15 +44,13 @@ final class MangaReadingStatus extends Enum {
Kitsu::ON_HOLD => Title::ON_HOLD, Kitsu::ON_HOLD => Title::ON_HOLD,
Kitsu::DROPPED => Title::DROPPED, Kitsu::DROPPED => Title::DROPPED,
]; ];
public const ROUTE_TO_KITSU = [
public const ROUTE_TO_KITSU = [
Route::PLAN_TO_READ => Kitsu::PLAN_TO_READ, Route::PLAN_TO_READ => Kitsu::PLAN_TO_READ,
Route::READING => Kitsu::READING, Route::READING => Kitsu::READING,
Route::COMPLETED => Kitsu::COMPLETED, Route::COMPLETED => Kitsu::COMPLETED,
Route::DROPPED => Kitsu::DROPPED, Route::DROPPED => Kitsu::DROPPED,
Route::ON_HOLD => Kitsu::ON_HOLD, Route::ON_HOLD => Kitsu::ON_HOLD,
]; ];
public const ROUTE_TO_TITLE = [ public const ROUTE_TO_TITLE = [
Route::ALL => Title::ALL, Route::ALL => Title::ALL,
Route::PLAN_TO_READ => Title::PLAN_TO_READ, Route::PLAN_TO_READ => Title::PLAN_TO_READ,
@ -64,7 +59,6 @@ final class MangaReadingStatus extends Enum {
Route::DROPPED => Title::DROPPED, Route::DROPPED => Title::DROPPED,
Route::ON_HOLD => Title::ON_HOLD, Route::ON_HOLD => Title::ON_HOLD,
]; ];
public const TITLE_TO_KITSU = [ public const TITLE_TO_KITSU = [
Title::PLAN_TO_READ => Kitsu::PLAN_TO_READ, Title::PLAN_TO_READ => Kitsu::PLAN_TO_READ,
Title::READING => Kitsu::READING, Title::READING => Kitsu::READING,
@ -72,4 +66,4 @@ final class MangaReadingStatus extends Enum {
Title::DROPPED => Kitsu::DROPPED, Title::DROPPED => Kitsu::DROPPED,
Title::ON_HOLD => Kitsu::ON_HOLD, Title::ON_HOLD => Kitsu::ON_HOLD,
]; ];
} }

View File

@ -6,71 +6,66 @@
* *
* PHP version 8 * PHP version 8
* *
* @package HummingbirdAnimeClient * @copyright 2015 - 2022 Timothy J. Warren <tim@timshome.page>
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2021 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License * @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.2 * @version 5.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient * @link https://git.timshome.page/timw4mail/HummingBirdAnimeClient
*/ */
namespace Aviat\AnimeClient\API; namespace Aviat\AnimeClient\API;
use Amp\Http\Client\Request; use Amp\Http\Client\Request;
use Generator;
use Throwable;
use function Amp\call; use function Amp\call;
use function Amp\Promise\{all, wait}; use function Amp\Promise\{all, wait};
use function Aviat\AnimeClient\getApiClient; use function Aviat\AnimeClient\getApiClient;
use Throwable;
/** /**
* Class to simplify making and validating simultaneous requests * Class to simplify making and validating simultaneous requests
*/ */
final class ParallelAPIRequest { final class ParallelAPIRequest
{
/** /**
* Set of requests to make in parallel * Set of requests to make in parallel
*
* @var array
*/ */
private array $requests = []; private array $requests = [];
/** /**
* Add a request * Add a request
*
* @param string|Request $request
* @param string|int|null $key
* @return self
*/ */
public function addRequest(string|Request $request, string|int|null $key = NULL): self public function addRequest(string|Request $request, string|int|NULL $key = NULL): self
{ {
if ($key !== NULL) if ($key !== NULL)
{ {
$this->requests[$key] = $request; $this->requests[$key] = $request;
return $this; return $this;
} }
$this->requests[] = $request; $this->requests[] = $request;
return $this; return $this;
} }
/** /**
* Add multiple requests * Add multiple requests
* *
* @param string[]|Request[] $requests * @param Request[]|string[] $requests
* @return self
*/ */
public function addRequests(array $requests): self public function addRequests(array $requests): self
{ {
array_walk($requests, [$this, 'addRequest']); array_walk($requests, [$this, 'addRequest']);
return $this; return $this;
} }
/** /**
* Make the requests, and return the body for each * Make the requests, and return the body for each
* *
* @return array
* @throws Throwable * @throws Throwable
* @return mixed[]
*/ */
public function makeRequests(): array public function makeRequests(): array
{ {
@ -80,7 +75,7 @@ final class ParallelAPIRequest {
foreach ($this->requests as $key => $url) foreach ($this->requests as $key => $url)
{ {
$promises[$key] = call(static function () use ($client, $url) { $promises[$key] = call(static function () use ($client, $url): Generator {
$response = yield $client->request($url); $response = yield $client->request($url);
return yield $response->getBody()->buffer(); return yield $response->getBody()->buffer();
}); });
@ -92,8 +87,8 @@ final class ParallelAPIRequest {
/** /**
* Make the requests and return the response objects * Make the requests and return the response objects
* *
* @return array
* @throws Throwable * @throws Throwable
* @return mixed[]
*/ */
public function getResponses(): array public function getResponses(): array
{ {
@ -103,9 +98,9 @@ final class ParallelAPIRequest {
foreach ($this->requests as $key => $url) foreach ($this->requests as $key => $url)
{ {
$promises[$key] = call(fn () => yield $client->request($url)); $promises[$key] = call(static fn () => yield $client->request($url));
} }
return wait(all($promises)); return wait(all($promises));
} }
} }

View File

@ -6,33 +6,31 @@
* *
* PHP version 8 * PHP version 8
* *
* @package HummingbirdAnimeClient * @copyright 2015 - 2022 Timothy J. Warren <tim@timshome.page>
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2021 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License * @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.2 * @version 5.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient * @link https://git.timshome.page/timw4mail/HummingBirdAnimeClient
*/ */
namespace Aviat\AnimeClient; namespace Aviat\AnimeClient;
use Aviat\AnimeClient\API\Enum\{
AnimeWatchingStatus\Kitsu as KAWS,
MangaReadingStatus\Kitsu as KMRS
};
use Aviat\AnimeClient\API\Enum\{ use Aviat\AnimeClient\API\Enum\{
AnimeWatchingStatus\Anilist as AnimeWatchingStatus, AnimeWatchingStatus\Anilist as AnimeWatchingStatus,
MangaReadingStatus\Anilist as MangaReadingStatus MangaReadingStatus\Anilist as MangaReadingStatus
}; };
use Aviat\AnimeClient\API\Enum\{
AnimeWatchingStatus\Kitsu as KAWS,
MangaReadingStatus\Kitsu as KMRS
};
/** /**
* Constants and mappings for the Anilist API * Constants and mappings for the Anilist API
*/ */
final class Anilist { final class Anilist
{
public const AUTH_URL = 'https://anilist.co/api/v2/oauth/authorize'; public const AUTH_URL = 'https://anilist.co/api/v2/oauth/authorize';
public const TOKEN_URL = 'https://anilist.co/api/v2/oauth/token'; public const TOKEN_URL = 'https://anilist.co/api/v2/oauth/token';
public const BASE_URL = 'https://graphql.anilist.co'; public const BASE_URL = 'https://graphql.anilist.co';
public const KITSU_ANILIST_WATCHING_STATUS_MAP = [ public const KITSU_ANILIST_WATCHING_STATUS_MAP = [
KAWS::WATCHING => AnimeWatchingStatus::WATCHING, KAWS::WATCHING => AnimeWatchingStatus::WATCHING,
KAWS::COMPLETED => AnimeWatchingStatus::COMPLETED, KAWS::COMPLETED => AnimeWatchingStatus::COMPLETED,
@ -40,7 +38,6 @@ final class Anilist {
KAWS::DROPPED => AnimeWatchingStatus::DROPPED, KAWS::DROPPED => AnimeWatchingStatus::DROPPED,
KAWS::PLAN_TO_WATCH => AnimeWatchingStatus::PLAN_TO_WATCH, KAWS::PLAN_TO_WATCH => AnimeWatchingStatus::PLAN_TO_WATCH,
]; ];
public const ANILIST_KITSU_WATCHING_STATUS_MAP = [ public const ANILIST_KITSU_WATCHING_STATUS_MAP = [
AnimeWatchingStatus::WATCHING => KAWS::WATCHING, AnimeWatchingStatus::WATCHING => KAWS::WATCHING,
AnimeWatchingStatus::COMPLETED => KAWS::COMPLETED, AnimeWatchingStatus::COMPLETED => KAWS::COMPLETED,
@ -48,7 +45,6 @@ final class Anilist {
AnimeWatchingStatus::DROPPED => KAWS::DROPPED, AnimeWatchingStatus::DROPPED => KAWS::DROPPED,
AnimeWatchingStatus::PLAN_TO_WATCH => KAWS::PLAN_TO_WATCH, AnimeWatchingStatus::PLAN_TO_WATCH => KAWS::PLAN_TO_WATCH,
]; ];
public const KITSU_ANILIST_READING_STATUS_MAP = [ public const KITSU_ANILIST_READING_STATUS_MAP = [
KMRS::READING => MangaReadingStatus::READING, KMRS::READING => MangaReadingStatus::READING,
KMRS::COMPLETED => MangaReadingStatus::COMPLETED, KMRS::COMPLETED => MangaReadingStatus::COMPLETED,
@ -56,7 +52,6 @@ final class Anilist {
KMRS::DROPPED => MangaReadingStatus::DROPPED, KMRS::DROPPED => MangaReadingStatus::DROPPED,
KMRS::PLAN_TO_READ => MangaReadingStatus::PLAN_TO_READ, KMRS::PLAN_TO_READ => MangaReadingStatus::PLAN_TO_READ,
]; ];
public const ANILIST_KITSU_READING_STATUS_MAP = [ public const ANILIST_KITSU_READING_STATUS_MAP = [
MangaReadingStatus::READING => KMRS::READING, MangaReadingStatus::READING => KMRS::READING,
MangaReadingStatus::COMPLETED => KMRS::COMPLETED, MangaReadingStatus::COMPLETED => KMRS::COMPLETED,
@ -64,4 +59,4 @@ final class Anilist {
MangaReadingStatus::DROPPED => KMRS::DROPPED, MangaReadingStatus::DROPPED => KMRS::DROPPED,
MangaReadingStatus::PLAN_TO_READ => KMRS::PLAN_TO_READ, MangaReadingStatus::PLAN_TO_READ => KMRS::PLAN_TO_READ,
]; ];
} }

View File

@ -6,41 +6,33 @@
* *
* PHP version 8 * PHP version 8
* *
* @package HummingbirdAnimeClient * @copyright 2015 - 2022 Timothy J. Warren <tim@timshome.page>
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2021 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License * @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.2 * @version 5.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient * @link https://git.timshome.page/timw4mail/HummingBirdAnimeClient
*/ */
namespace Aviat\AnimeClient; namespace Aviat\AnimeClient;
use Aviat\Ion\ImageBuilder; use Amp\Http\Client\{HttpClient, HttpClientBuilder, Request, Response};
use Aviat\Ion\{ConfigInterface, ImageBuilder};
use Psr\SimpleCache\CacheInterface; use Psr\SimpleCache\CacheInterface;
use Amp\Http\Client\Request;
use Amp\Http\Client\Response;
use Amp\Http\Client\HttpClient;
use Amp\Http\Client\HttpClientBuilder;
use Aviat\Ion\ConfigInterface;
use Yosymfony\Toml\{Toml, TomlBuilder};
use Throwable; use Throwable;
use Yosymfony\Toml\{Toml, TomlBuilder};
use function Amp\Promise\wait; use function Amp\Promise\wait;
use function Aviat\Ion\_dir; use function Aviat\Ion\_dir;
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
//! TOML Functions //! TOML Functions
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
/** /**
* Load configuration options from .toml files * Load configuration options from .toml files
* *
* @codeCoverageIgnore * @codeCoverageIgnore
* @param string $path - Path to load config * @param string $path - Path to load config
* @return array
*/ */
function loadConfig(string $path): array function loadConfig(string $path): array
{ {
@ -64,7 +56,7 @@ function loadConfig(string $path): array
if ($key === 'config') if ($key === 'config')
{ {
foreach($config as $name => $value) foreach ($config as $name => $value)
{ {
$output[$name] = $value; $output[$name] = $value;
} }
@ -82,8 +74,6 @@ function loadConfig(string $path): array
* Load config from one specific TOML file * Load config from one specific TOML file
* *
* @codeCoverageIgnore * @codeCoverageIgnore
* @param string $filename
* @return array
*/ */
function loadTomlFile(string $filename): array function loadTomlFile(string $filename): array
{ {
@ -100,10 +90,10 @@ function _iterateToml(TomlBuilder $builder, iterable $data, mixed $parentKey = N
continue; continue;
} }
if (is_scalar($value) || isSequentialArray($value)) if (is_scalar($value) || isSequentialArray($value))
{ {
$builder->addValue($key, $value); $builder->addValue($key, $value);
continue; continue;
} }
@ -111,10 +101,7 @@ function _iterateToml(TomlBuilder $builder, iterable $data, mixed $parentKey = N
? "{$parentKey}.{$key}" ? "{$parentKey}.{$key}"
: $key; : $key;
if ( ! isSequentialArray($value)) $builder->addTable($newKey);
{
$builder->addTable($newKey);
}
_iterateToml($builder, $value, $newKey); _iterateToml($builder, $value, $newKey);
} }
@ -122,9 +109,6 @@ function _iterateToml(TomlBuilder $builder, iterable $data, mixed $parentKey = N
/** /**
* Serialize config data into a Toml file * Serialize config data into a Toml file
*
* @param iterable $data
* @return string
*/ */
function arrayToToml(iterable $data): string function arrayToToml(iterable $data): string
{ {
@ -137,9 +121,6 @@ function arrayToToml(iterable $data): string
/** /**
* Serialize toml back to an array * Serialize toml back to an array
*
* @param string $toml
* @return array
*/ */
function tomlToArray(string $toml): array function tomlToArray(string $toml): array
{ {
@ -156,8 +137,6 @@ if ( ! function_exists('array_is_list'))
* Polyfill for PHP 8 * Polyfill for PHP 8
* *
* @see https://www.php.net/manual/en/function.array-is-list * @see https://www.php.net/manual/en/function.array-is-list
* @param array $a
* @return bool
*/ */
function array_is_list(array $a): bool function array_is_list(array $a): bool
{ {
@ -167,25 +146,14 @@ if ( ! function_exists('array_is_list'))
/** /**
* Is the array sequential, not associative? * Is the array sequential, not associative?
*
* @param mixed $array
* @return bool
*/ */
function isSequentialArray(mixed $array): bool function isSequentialArray(mixed $array): bool
{ {
if ( ! is_array($array)) return is_array($array) && array_is_list($array);
{
return FALSE;
}
return array_is_list($array);
} }
/** /**
* Check that folder permissions are correct for proper operation * Check that folder permissions are correct for proper operation
*
* @param ConfigInterface $config
* @return array
*/ */
function checkFolderPermissions(ConfigInterface $config): array function checkFolderPermissions(ConfigInterface $config): array
{ {
@ -206,6 +174,7 @@ function checkFolderPermissions(ConfigInterface $config): array
if ( ! is_dir($actual)) if ( ! is_dir($actual))
{ {
$errors['missing'][] = $pretty; $errors['missing'][] = $pretty;
continue; continue;
} }
@ -224,10 +193,8 @@ function checkFolderPermissions(ConfigInterface $config): array
/** /**
* Get an API Client, with better defaults * Get an API Client, with better defaults
*
* @return HttpClient
*/ */
function getApiClient (): HttpClient function getApiClient(): HttpClient
{ {
static $client; static $client;
@ -242,11 +209,9 @@ function getApiClient (): HttpClient
/** /**
* Simplify making a request with Http\Client * Simplify making a request with Http\Client
* *
* @param string|Request $request
* @return Response
* @throws Throwable * @throws Throwable
*/ */
function getResponse (Request|string $request): Response function getResponse(Request|string $request): Response
{ {
$client = getApiClient(); $client = getApiClient();
@ -260,12 +225,8 @@ function getResponse (Request|string $request): Response
/** /**
* Generate the path for the cached image from the original image * Generate the path for the cached image from the original image
*
* @param string $kitsuUrl
* @param bool $webp
* @return string
*/ */
function getLocalImg (string $kitsuUrl, bool $webp = TRUE): string function getLocalImg(string $kitsuUrl, bool $webp = TRUE): string
{ {
if (empty($kitsuUrl) || ( ! is_string($kitsuUrl))) if (empty($kitsuUrl) || ( ! is_string($kitsuUrl)))
{ {
@ -297,13 +258,8 @@ function getLocalImg (string $kitsuUrl, bool $webp = TRUE): string
* Create a transparent placeholder image * Create a transparent placeholder image
* *
* @codeCoverageIgnore * @codeCoverageIgnore
* @param string $path
* @param int $width
* @param int $height
* @param string $text
* @return bool
*/ */
function createPlaceholderImage (string $path, int $width = 200, int $height = 200, string $text = 'Image Unavailable'): bool function createPlaceholderImage(string $path, int $width = 200, int $height = 200, string $text = 'Image Unavailable'): bool
{ {
$img = ImageBuilder::new($width, $height) $img = ImageBuilder::new($width, $height)
->enableAlphaBlending(TRUE) ->enableAlphaBlending(TRUE)
@ -322,22 +278,16 @@ function createPlaceholderImage (string $path, int $width = 200, int $height = 2
/** /**
* Check that there is a value for at least one item in a collection with the specified key * Check that there is a value for at least one item in a collection with the specified key
*
* @param array $search
* @param string $key
* @return bool
*/ */
function colNotEmpty(array $search, string $key): bool function colNotEmpty(array $search, string $key): bool
{ {
$items = array_filter(array_column($search, $key), static fn ($x) => ( ! empty($x))); $items = array_filter(array_column($search, $key), static fn ($x) => ( ! empty($x)));
return count($items) > 0;
return $items !== [];
} }
/** /**
* Clear the cache, but save user auth data * Clear the cache, but save user auth data
*
* @param CacheInterface $cache
* @return bool
*/ */
function clearCache(CacheInterface $cache): bool function clearCache(CacheInterface $cache): bool
{ {
@ -349,10 +299,11 @@ function clearCache(CacheInterface $cache): bool
Kitsu::AUTH_TOKEN_REFRESH_CACHE_KEY, Kitsu::AUTH_TOKEN_REFRESH_CACHE_KEY,
]); ]);
$userData = array_filter((array)$userData, static fn ($value) => $value !== NULL); $userData = array_filter((array) $userData, static fn ($value) => $value !== NULL);
$cleared = $cache->clear(); $cleared = $cache->clear();
$saved = ( ! empty($userData)) ? $cache->setMultiple($userData) : TRUE; $saved = (empty($userData)) ? TRUE : $cache->setMultiple($userData);
return $cleared && $saved; return $cleared && $saved;
} }
@ -361,9 +312,6 @@ function clearCache(CacheInterface $cache): bool
* Render a PHP code template as a string * Render a PHP code template as a string
* *
* @codeCoverageIgnore * @codeCoverageIgnore
* @param string $path
* @param array $data
* @return string
*/ */
function renderTemplate(string $path, array $data): string function renderTemplate(string $path, array $data): string
{ {
@ -371,5 +319,6 @@ function renderTemplate(string $path, array $data): string
extract($data, EXTR_OVERWRITE); extract($data, EXTR_OVERWRITE);
include $path; include $path;
$rawOutput = ob_get_clean(); $rawOutput = ob_get_clean();
return (is_string($rawOutput)) ? $rawOutput : ''; return (is_string($rawOutput)) ? $rawOutput : '';
} }

View File

@ -6,53 +6,45 @@
* *
* PHP version 8 * PHP version 8
* *
* @package HummingbirdAnimeClient * @copyright 2015 - 2022 Timothy J. Warren <tim@timshome.page>
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2021 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License * @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.2 * @version 5.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient * @link https://git.timshome.page/timw4mail/HummingBirdAnimeClient
*/ */
namespace Aviat\AnimeClient\Command; namespace Aviat\AnimeClient\Command;
use Monolog\Formatter\JsonFormatter;
use function Aviat\Ion\_dir;
use const Aviat\AnimeClient\SRC_DIR;
use function Aviat\AnimeClient\loadConfig;
use function Aviat\AnimeClient\loadTomlFile;
use Aura\Router\RouterContainer; use Aura\Router\RouterContainer;
use Aura\Session\SessionFactory; use Aura\Session\SessionFactory;
use Aviat\AnimeClient\{Model, UrlGenerator, Util};
use Aviat\AnimeClient\API\{Anilist, CacheTrait, Kitsu}; use Aviat\AnimeClient\API\{Anilist, CacheTrait, Kitsu};
use Aviat\AnimeClient\{Model, UrlGenerator, Util};
use Aviat\Banker\Teller; use Aviat\Banker\Teller;
use Aviat\Ion\Config; use Aviat\Ion\Config;
use Aviat\Ion\Di\{Container, ContainerInterface, ContainerAware}; use Aviat\Ion\Di\{Container, ContainerAware, ContainerInterface};
use ConsoleKit\{Colors, Command, ConsoleException};
use ConsoleKit\Widgets\Box; use ConsoleKit\Widgets\Box;
use ConsoleKit\{Colors, Command, ConsoleException};
use Laminas\Diactoros\{Response, ServerRequestFactory}; use Laminas\Diactoros\{Response, ServerRequestFactory};
use Monolog\Formatter\JsonFormatter;
use Monolog\Handler\RotatingFileHandler; use Monolog\Handler\RotatingFileHandler;
use Monolog\Logger; use Monolog\Logger;
use function Aviat\AnimeClient\{loadConfig, loadTomlFile};
use function Aviat\Ion\_dir;
use const Aviat\AnimeClient\SRC_DIR;
/** /**
* Base class for console command setup * Base class for console command setup
*/ */
abstract class BaseCommand extends Command { abstract class BaseCommand extends Command
{
use CacheTrait; use CacheTrait;
use ContainerAware; use ContainerAware;
/** /**
* Echo text in a box * Echo text in a box
*
* @param string|array $message
* @param string|int|null $fgColor
* @param string|int|null $bgColor
* @return void
*/ */
public function echoBox(string|array $message, string|int|null $fgColor = NULL, string|int|null $bgColor = NULL): void public function echoBox(string|array $message, string|int|NULL $fgColor = NULL, string|int|NULL $bgColor = NULL): void
{ {
if (is_array($message)) if (is_array($message))
{ {
@ -61,11 +53,12 @@ abstract class BaseCommand extends Command {
if ($fgColor !== NULL) if ($fgColor !== NULL)
{ {
$fgColor = (int)$fgColor; $fgColor = (int) $fgColor;
} }
if ($bgColor !== NULL) if ($bgColor !== NULL)
{ {
$bgColor = (int)$bgColor; $bgColor = (int) $bgColor;
} }
// Colorize the CLI output // Colorize the CLI output
@ -118,15 +111,13 @@ abstract class BaseCommand extends Command {
/** /**
* Setup the Di container * Setup the Di container
*
* @return Containerinterface
*/ */
public function setupContainer(): ContainerInterface public function setupContainer(): ContainerInterface
{ {
$APP_DIR = _dir(dirname(dirname(SRC_DIR)), 'app'); $APP_DIR = _dir(dirname(SRC_DIR, 2), 'app');
$APPCONF_DIR = _dir($APP_DIR, 'appConf'); $APPCONF_DIR = _dir($APP_DIR, 'appConf');
$CONF_DIR = _dir($APP_DIR, 'config'); $CONF_DIR = _dir($APP_DIR, 'config');
$baseConfig = require _dir($APPCONF_DIR, 'base_config.php'); $baseConfig = require _dir($APPCONF_DIR, 'base_config.php');
$config = loadConfig($CONF_DIR); $config = loadConfig($CONF_DIR);
@ -140,15 +131,16 @@ abstract class BaseCommand extends Command {
return $this->_di($configArray, $APP_DIR); return $this->_di($configArray, $APP_DIR);
} }
private function _line(string $message, int|string|null $fgColor = NULL, int|string|null $bgColor = NULL): void private function _line(string $message, int|string|NULL $fgColor = NULL, int|string|NULL $bgColor = NULL): void
{ {
if ($fgColor !== NULL) if ($fgColor !== NULL)
{ {
$fgColor = (int)$fgColor; $fgColor = (int) $fgColor;
} }
if ($bgColor !== NULL) if ($bgColor !== NULL)
{ {
$bgColor = (int)$bgColor; $bgColor = (int) $bgColor;
} }
// Colorize the CLI output // Colorize the CLI output
@ -168,12 +160,13 @@ abstract class BaseCommand extends Command {
$appLogger = new Logger('animeclient'); $appLogger = new Logger('animeclient');
$appLogger->pushHandler(new RotatingFileHandler($APP_DIR . '/logs/app-cli.log', 2, Logger::WARNING)); $appLogger->pushHandler(new RotatingFileHandler($APP_DIR . '/logs/app-cli.log', 2, Logger::WARNING));
$container->setLogger($appLogger); $container->setLogger($appLogger);
foreach (['kitsu-request', 'anilist-request', 'anilist-request-cli', 'kitsu-request-cli'] as $channel) foreach (['kitsu-request', 'anilist-request', 'anilist-request-cli', 'kitsu-request-cli'] as $channel)
{ {
$logger = new Logger($channel); $logger = new Logger($channel);
$handler = new RotatingFileHandler( "{$APP_DIR}/logs/{$channel}.log", 2, Logger::WARNING); $handler = new RotatingFileHandler("{$APP_DIR}/logs/{$channel}.log", 2, Logger::WARNING);
$handler->setFormatter(new JsonFormatter()); $handler->setFormatter(new JsonFormatter());
$logger->pushHandler($handler); $logger->pushHandler($handler);
@ -181,33 +174,34 @@ abstract class BaseCommand extends Command {
} }
// Create Config Object // Create Config Object
$container->set('config', fn () => new Config($configArray)); $container->set('config', static fn () => new Config($configArray));
// Create Cache Object // Create Cache Object
$container->set('cache', static function($container) { $container->set('cache', static function ($container): Teller {
$logger = $container->getLogger(); $logger = $container->getLogger();
$config = $container->get('config')->get('cache'); $config = $container->get('config')->get('cache');
return new Teller($config, $logger); return new Teller($config, $logger);
}); });
// Create Aura Router Object // Create Aura Router Object
$container->set('aura-router', fn () => new RouterContainer); $container->set('aura-router', static fn () => new RouterContainer());
// Create Request/Response Objects // Create Request/Response Objects
$container->set('request', fn () => ServerRequestFactory::fromGlobals( $container->set('request', static fn () => ServerRequestFactory::fromGlobals(
$GLOBALS['_SERVER'], $GLOBALS['_SERVER'],
$_GET, $_GET,
$_POST, $_POST,
$_COOKIE, $_COOKIE,
$_FILES $_FILES
)); ));
$container->set('response', fn () => new Response); $container->set('response', static fn () => new Response());
// Create session Object // Create session Object
$container->set('session', fn () => (new SessionFactory())->newInstance($_COOKIE)); $container->set('session', static fn () => (new SessionFactory())->newInstance($_COOKIE));
// Models // Models
$container->set('kitsu-model', static function($container): Kitsu\Model { $container->set('kitsu-model', static function ($container): Kitsu\Model {
$requestBuilder = new Kitsu\RequestBuilder($container); $requestBuilder = new Kitsu\RequestBuilder($container);
$requestBuilder->setLogger($container->getLogger('kitsu-request')); $requestBuilder->setLogger($container->getLogger('kitsu-request'));
@ -221,6 +215,7 @@ abstract class BaseCommand extends Command {
$cache = $container->get('cache'); $cache = $container->get('cache');
$model->setCache($cache); $model->setCache($cache);
return $model; return $model;
}); });
$container->set('anilist-model', static function ($container): Anilist\Model { $container->set('anilist-model', static function ($container): Anilist\Model {
@ -237,18 +232,19 @@ abstract class BaseCommand extends Command {
return $model; return $model;
}); });
$container->set('settings-model', static function($container): Model\Settings { $container->set('settings-model', static function ($container): Model\Settings {
$model = new Model\Settings($container->get('config')); $model = new Model\Settings($container->get('config'));
$model->setContainer($container); $model->setContainer($container);
return $model; return $model;
}); });
$container->set('auth', fn ($container) => new Kitsu\Auth($container)); $container->set('auth', static fn ($container) => new Kitsu\Auth($container));
$container->set('url-generator', fn ($container) => new UrlGenerator($container)); $container->set('url-generator', static fn ($container) => new UrlGenerator($container));
$container->set('util', fn ($container) => new Util($container)); $container->set('util', static fn ($container) => new Util($container));
return $container; return $container;
} }
} }

View File

@ -6,32 +6,27 @@
* *
* PHP version 8 * PHP version 8
* *
* @package HummingbirdAnimeClient * @copyright 2015 - 2022 Timothy J. Warren <tim@timshome.page>
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2021 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License * @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.2 * @version 5.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient * @link https://git.timshome.page/timw4mail/HummingBirdAnimeClient
*/ */
namespace Aviat\AnimeClient\Command; namespace Aviat\AnimeClient\Command;
use Aviat\Ion\Di\Exception\ContainerException; use Aviat\Ion\Di\Exception\{ContainerException, NotFoundException};
use Aviat\Ion\Di\Exception\NotFoundException;
use function Aviat\AnimeClient\clearCache; use function Aviat\AnimeClient\clearCache;
/** /**
* Clears the API Cache * Clears the API Cache
*/ */
final class CacheClear extends BaseCommand { final class CacheClear extends BaseCommand
{
/** /**
* Clear the API cache * Clear the API cache
* *
* @param array $args
* @param array $options
* @throws ContainerException * @throws ContainerException
* @throws NotFoundException * @throws NotFoundException
* @return void
*/ */
public function execute(array $args, array $options = []): void public function execute(array $args, array $options = []): void
{ {

View File

@ -6,32 +6,27 @@
* *
* PHP version 8 * PHP version 8
* *
* @package HummingbirdAnimeClient * @copyright 2015 - 2022 Timothy J. Warren <tim@timshome.page>
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2021 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License * @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.2 * @version 5.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient * @link https://git.timshome.page/timw4mail/HummingBirdAnimeClient
*/ */
namespace Aviat\AnimeClient\Command; namespace Aviat\AnimeClient\Command;
use Aviat\Ion\Di\Exception\ContainerException; use Aviat\Ion\Di\Exception\{ContainerException, NotFoundException};
use Aviat\Ion\Di\Exception\NotFoundException;
use function Aviat\AnimeClient\clearCache; use function Aviat\AnimeClient\clearCache;
/** /**
* Clears the API Cache * Clears the API Cache
*/ */
final class CachePrime extends BaseCommand { final class CachePrime extends BaseCommand
{
/** /**
* Clear, then prime the API cache * Clear, then prime the API cache
* *
* @param array $args
* @param array $options
* @throws ContainerException * @throws ContainerException
* @throws NotFoundException * @throws NotFoundException
* @return void
*/ */
public function execute(array $args, array $options = []): void public function execute(array $args, array $options = []): void
{ {
@ -42,6 +37,7 @@ final class CachePrime extends BaseCommand {
if ( ! $cleared) if ( ! $cleared)
{ {
$this->echoErrorBox('Failed to clear cache.'); $this->echoErrorBox('Failed to clear cache.');
return; return;
} }

View File

@ -6,12 +6,10 @@
* *
* PHP version 8 * PHP version 8
* *
* @package HummingbirdAnimeClient * @copyright 2015 - 2022 Timothy J. Warren <tim@timshome.page>
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2021 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License * @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.2 * @version 5.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient * @link https://git.timshome.page/timw4mail/HummingBirdAnimeClient
*/ */
namespace Aviat\AnimeClient\Command; namespace Aviat\AnimeClient\Command;
@ -19,8 +17,8 @@ namespace Aviat\AnimeClient\Command;
/** /**
* Clears out image cache directories * Clears out image cache directories
*/ */
class ClearThumbnails extends BaseCommand { class ClearThumbnails extends BaseCommand
{
public function execute(array $args, array $options = []): void public function execute(array $args, array $options = []): void
{ {
$this->clearThumbs(); $this->clearThumbs();
@ -50,10 +48,10 @@ class ClearThumbnails extends BaseCommand {
'people/*.webp', 'people/*.webp',
]; ];
foreach($paths as $path) foreach ($paths as $path)
{ {
$cmd = "find {$imgDir} -path \"*/{$path}\" | xargs rm -f"; $cmd = "find {$imgDir} -path \"*/{$path}\" | xargs rm -f";
exec($cmd); exec($cmd);
} }
} }
} }

View File

@ -6,74 +6,64 @@
* *
* PHP version 8 * PHP version 8
* *
* @package HummingbirdAnimeClient * @copyright 2015 - 2022 Timothy J. Warren <tim@timshome.page>
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2021 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License * @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.2 * @version 5.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient * @link https://git.timshome.page/timw4mail/HummingBirdAnimeClient
*/ */
namespace Aviat\AnimeClient\Command; namespace Aviat\AnimeClient\Command;
use Aviat\Ion\JsonException; use Aviat\AnimeClient\API\Anilist;
use ConsoleKit\Widgets;
use Aviat\AnimeClient\API\Mapping\{AnimeWatchingStatus, MangaReadingStatus};
use Aviat\AnimeClient\API\{ use Aviat\AnimeClient\API\{
Anilist\MissingIdException, Anilist\MissingIdException,
ParallelAPIRequest ParallelAPIRequest
}; };
use Aviat\AnimeClient\API;
use Aviat\AnimeClient\API\Anilist;
use Aviat\AnimeClient\API\Mapping\{AnimeWatchingStatus, MangaReadingStatus};
use Aviat\AnimeClient\Enum;
use Aviat\AnimeClient\Enum\{MediaType, SyncAction}; use Aviat\AnimeClient\Enum\{MediaType, SyncAction};
use Aviat\AnimeClient\Types\FormItem; use Aviat\AnimeClient\Types\FormItem;
use Aviat\Ion\Di\Exception\ContainerException; use Aviat\AnimeClient\{API, Enum};
use Aviat\Ion\Di\Exception\NotFoundException; use Aviat\Ion\Di\Exception\{ContainerException, NotFoundException};
use Aviat\Ion\Json; use Aviat\Ion\{Json, JsonException};
use ConsoleKit\Widgets;
use DateTime; use DateTime;
use Throwable; use Throwable;
use function in_array;
/** /**
* Syncs list data between Anilist and Kitsu * Syncs list data between Anilist and Kitsu
*/ */
final class SyncLists extends BaseCommand { final class SyncLists extends BaseCommand
{
protected const KITSU_GREATER = 1; protected const KITSU_GREATER = 1;
protected const ANILIST_GREATER = -1; protected const ANILIST_GREATER = -1;
protected const SAME = 0; protected const SAME = 0;
/** /**
* Model for making requests to Anilist API * Model for making requests to Anilist API
* @var Anilist\Model
*/ */
private Anilist\Model $anilistModel; private Anilist\Model $anilistModel;
/** /**
* Model for making requests to Kitsu API * Model for making requests to Kitsu API
* @var API\Kitsu\Model
*/ */
private API\Kitsu\Model $kitsuModel; private API\Kitsu\Model $kitsuModel;
/**
* Does the Kitsu API have valid authentication?
* @var bool
*/
private bool $isKitsuAuthenticated = FALSE;
/** /**
* Sync Kitsu <=> Anilist * Sync Kitsu <=> Anilist
* *
* @param array $args
* @param array $options
* @throws ContainerException * @throws ContainerException
* @throws NotFoundException * @throws NotFoundException
* @throws Throwable * @throws Throwable
*/ */
public function execute(array $args, array $options = []): void public function execute(array $args, array $options = []): void
{ {
$this->init(); $canRun = $this->init();
if ( ! $canRun)
{
return;
}
foreach ([MediaType::MANGA, MediaType::ANIME] as $type) foreach ([MediaType::MANGA, MediaType::ANIME] as $type)
{ {
@ -96,7 +86,7 @@ final class SyncLists extends BaseCommand {
* @throws ContainerException * @throws ContainerException
* @throws NotFoundException * @throws NotFoundException
*/ */
protected function init(): void protected function init(): bool
{ {
$this->setContainer($this->setupContainer()); $this->setContainer($this->setupContainer());
$this->setCache($this->container->get('cache')); $this->setCache($this->container->get('cache'));
@ -108,28 +98,32 @@ final class SyncLists extends BaseCommand {
if ( ! $anilistEnabled) if ( ! $anilistEnabled)
{ {
$this->echoErrorBox('Anlist API is not enabled. Can not sync.'); $this->echoErrorBox('Anlist API is not enabled. Can not sync.');
exit(); return false;
} }
// Authentication is required to update Kitsu // Authentication is required to update Kitsu
$this->isKitsuAuthenticated = $this->container->get('auth')->isAuthenticated(); $isKitsuAuthenticated = $this->container->get('auth')->isAuthenticated();
if ( ! $this->isKitsuAuthenticated) if ( !$isKitsuAuthenticated)
{ {
$this->echoWarningBox('Kitsu is not authenticated. Kitsu list can not be updated.'); $this->echoErrorBox('Kitsu is not authenticated. Kitsu list can not be updated.');
return false;
} }
$this->anilistModel = $this->container->get('anilist-model'); $this->anilistModel = $this->container->get('anilist-model');
$this->kitsuModel = $this->container->get('kitsu-model'); $this->kitsuModel = $this->container->get('kitsu-model');
return true;
} }
/** /**
* Get and display the count of items for each API * Get and display the count of items for each API
*
* @param string $type
*/ */
protected function fetchCount(string $type): void protected function fetchCount(string $type): void
{ {
$this->echo('Fetching List Counts'); // This pulls too much data from Anilist, so skipping this step should result
// in fewer instances of API throttling
/* $this->echo('Fetching List Counts');
$progress = new Widgets\ProgressBar($this->getConsole(), 2, 50, FALSE); $progress = new Widgets\ProgressBar($this->getConsole(), 2, 50, FALSE);
$displayLines = []; $displayLines = [];
@ -144,18 +138,17 @@ final class SyncLists extends BaseCommand {
$this->clearLine(); $this->clearLine();
$this->echoBox($displayLines); $this->echoBox($displayLines); */
} }
/** /**
* Get the list data * Get the list data
* *
* @param string $type * @return array<string, mixed[]>
* @return array
*/ */
protected function fetch(string $type): array protected function fetch(string $type): array
{ {
$this->echo('Fetching List Data'); $this->echo("Fetching $type List Data");
$progress = new Widgets\ProgressBar($this->getConsole(), 2, 50, FALSE); $progress = new Widgets\ProgressBar($this->getConsole(), 2, 50, FALSE);
$anilist = $this->fetchAnilist($type); $anilist = $this->fetchAnilist($type);
@ -175,9 +168,7 @@ final class SyncLists extends BaseCommand {
/** /**
* Normalize the list data for comparison * Normalize the list data for comparison
* *
* @param string $type * @return array<string, mixed[]>
* @param array $data
* @return array
*/ */
protected function transform(string $type, array $data): array protected function transform(string $type, array $data): array
{ {
@ -201,9 +192,7 @@ final class SyncLists extends BaseCommand {
/** /**
* Compare the lists data * Compare the lists data
* *
* @param string $type * @return array<string, mixed[]>
* @param array $data
* @return array
*/ */
protected function compare(string $type, array $data): array protected function compare(string $type, array $data): array
{ {
@ -215,45 +204,36 @@ final class SyncLists extends BaseCommand {
/** /**
* Updated outdated list items * Updated outdated list items
* *
* @param string $type
* @param array $data
* @throws Throwable * @throws Throwable
*/ */
protected function update(string $type, array $data): void protected function update(string $type, array $data): void
{ {
if ( ! empty($data['addToAnilist'])) if ( ! empty($data['addToAnilist']))
{ {
$count = count($data['addToAnilist']); $count = is_countable($data['addToAnilist']) ? count($data['addToAnilist']) : 0;
$this->echoBox("Adding {$count} missing {$type} list items to Anilist"); $this->echoBox("Adding {$count} missing {$type} list items to Anilist");
$this->updateAnilistListItems($data['addToAnilist'], SyncAction::CREATE, $type); $this->updateAnilistListItems($data['addToAnilist'], SyncAction::CREATE, $type);
} }
if ( ! empty($data['updateAnilist'])) if ( ! empty($data['updateAnilist']))
{ {
$count = count($data['updateAnilist']); $count = is_countable($data['updateAnilist']) ? count($data['updateAnilist']) : 0;
$this->echoBox("Updating {$count} outdated Anilist {$type} list items"); $this->echoBox("Updating {$count} outdated Anilist {$type} list items");
$this->updateAnilistListItems($data['updateAnilist'], SyncAction::UPDATE, $type); $this->updateAnilistListItems($data['updateAnilist'], SyncAction::UPDATE, $type);
} }
if ($this->isKitsuAuthenticated) if ( ! empty($data['addToKitsu']))
{ {
if ( ! empty($data['addToKitsu'])) $count = is_countable($data['addToKitsu']) ? count($data['addToKitsu']) : 0;
{ $this->echoBox("Adding {$count} missing {$type} list items to Kitsu");
$count = count($data['addToKitsu']); $this->updateKitsuListItems($data['addToKitsu'], SyncAction::CREATE, $type);
$this->echoBox("Adding {$count} missing {$type} list items to Kitsu");
$this->updateKitsuListItems($data['addToKitsu'], SyncAction::CREATE, $type);
}
if ( ! empty($data['updateKitsu']))
{
$count = count($data['updateKitsu']);
$this->echoBox("Updating {$count} outdated Kitsu {$type} list items");
$this->updateKitsuListItems($data['updateKitsu'], SyncAction::UPDATE, $type);
}
} }
else
if ( ! empty($data['updateKitsu']))
{ {
$this->echoErrorBox('Kitsu is not authenticated, so lists can not be updated'); $count = is_countable($data['updateKitsu']) ? count($data['updateKitsu']) : 0;
$this->echoBox("Updating {$count} outdated Kitsu {$type} list items");
$this->updateKitsuListItems($data['updateKitsu'], SyncAction::UPDATE, $type);
} }
} }
@ -273,12 +253,15 @@ final class SyncLists extends BaseCommand {
foreach ($list['data']['MediaListCollection']['lists'] as $subList) foreach ($list['data']['MediaListCollection']['lists'] as $subList)
{ {
$count += array_reduce($subList, fn ($carry, $item) => $carry + count(array_values($item)), 0); $count += array_reduce($subList, static fn ($carry, $item) => $carry + count(array_values($item)), 0);
} }
return $count; return $count;
} }
/**
* @return mixed[]
*/
private function fetchAnilist(string $type): array private function fetchAnilist(string $type): array
{ {
static $list = [ static $list = [
@ -296,7 +279,8 @@ final class SyncLists extends BaseCommand {
catch (JsonException) catch (JsonException)
{ {
$this->echoErrorBox('Anlist API exception. Can not sync.'); $this->echoErrorBox('Anlist API exception. Can not sync.');
die();
exit();
} }
} }
@ -310,6 +294,9 @@ final class SyncLists extends BaseCommand {
return $this->kitsuModel->{"get{$uType}ListCount"}() ?? 0; return $this->kitsuModel->{"get{$uType}ListCount"}() ?? 0;
} }
/**
* @return mixed[]
*/
private function fetchKitsu(string $type): array private function fetchKitsu(string $type): array
{ {
return $this->kitsuModel->getSyncList($type); return $this->kitsuModel->getSyncList($type);
@ -318,7 +305,9 @@ final class SyncLists extends BaseCommand {
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// Transform Helpers // Transform Helpers
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
/**
* @return mixed[]
*/
private function transformKitsu(string $type, array $data): array private function transformKitsu(string $type, array $data): array
{ {
if (empty($data)) if (empty($data))
@ -328,13 +317,14 @@ final class SyncLists extends BaseCommand {
$output = []; $output = [];
foreach($data as $listItem) foreach ($data as $listItem)
{ {
// If there's no mapping, we can't sync, so continue // If there's no mapping, we can't sync, so continue
if ( ! is_array($listItem['media']['mappings']['nodes'])) if ( ! is_array($listItem['media']['mappings']['nodes']))
{ {
continue; continue;
} }
$malId = NULL; $malId = NULL;
foreach ($listItem['media']['mappings']['nodes'] as $mapping) foreach ($listItem['media']['mappings']['nodes'] as $mapping)
@ -368,18 +358,21 @@ final class SyncLists extends BaseCommand {
'reconsuming' => $listItem['reconsuming'], 'reconsuming' => $listItem['reconsuming'],
'status' => strtolower($listItem['status']), 'status' => strtolower($listItem['status']),
'updatedAt' => $listItem['progressedAt'], 'updatedAt' => $listItem['progressedAt'],
] ],
]; ];
} }
return $output; return $output;
} }
/**
* @return array<int|string, mixed>
*/
private function transformAnilist(string $type, array $data): array private function transformAnilist(string $type, array $data): array
{ {
$uType = ucfirst($type); $uType = ucfirst($type);
$className = "\\Aviat\\AnimeClient\\API\\Anilist\\Transformer\\{$uType}ListTransformer"; $className = "\\Aviat\\AnimeClient\\API\\Anilist\\Transformer\\{$uType}ListTransformer";
$transformer = new $className; $transformer = new $className();
$firstTransformed = []; $firstTransformed = [];
@ -392,6 +385,7 @@ final class SyncLists extends BaseCommand {
// Key the array by mal_id // Key the array by mal_id
$output = []; $output = [];
foreach ($transformed as $item) foreach ($transformed as $item)
{ {
$output[$item['mal_id']] = $item->toArray(); $output[$item['mal_id']] = $item->toArray();
@ -403,7 +397,9 @@ final class SyncLists extends BaseCommand {
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// Compare Helpers // Compare Helpers
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
/**
* @return array<string, mixed[]>
*/
private function compareLists(string $type, array $anilistList, array $kitsuList): array private function compareLists(string $type, array $anilistList, array $kitsuList): array
{ {
$itemsToAddToAnilist = []; $itemsToAddToAnilist = [];
@ -413,10 +409,10 @@ final class SyncLists extends BaseCommand {
$malIds = array_keys($anilistList); $malIds = array_keys($anilistList);
$kitsuMalIds = array_map('intval', array_column($kitsuList, 'malId')); $kitsuMalIds = array_map('intval', array_column($kitsuList, 'malId'));
$missingMalIds = array_filter($malIds, fn ($id) => ! in_array($id, $kitsuMalIds)); $missingMalIds = array_filter($malIds, static fn ($id) => ! in_array($id, $kitsuMalIds, TRUE));
// Add items on Anilist, but not Kitsu to Kitsu // Add items on Anilist, but not Kitsu to Kitsu
foreach($missingMalIds as $mid) foreach ($missingMalIds as $mid)
{ {
if ( ! array_key_exists($mid, $anilistList)) if ( ! array_key_exists($mid, $anilistList))
{ {
@ -424,13 +420,13 @@ final class SyncLists extends BaseCommand {
} }
$data = $anilistList[$mid]['data']; $data = $anilistList[$mid]['data'];
$data['id'] = $this->kitsuModel->getKitsuIdFromMALId((string)$mid, $type); $data['id'] = $this->kitsuModel->getKitsuIdFromMALId((string) $mid, $type);
$data['type'] = $type; $data['type'] = $type;
$itemsToAddToKitsu[] = $data; $itemsToAddToKitsu[] = $data;
} }
foreach($kitsuList as $kitsuItem) foreach ($kitsuList as $kitsuItem)
{ {
$malId = $kitsuItem['malId']; $malId = $kitsuItem['malId'];
@ -462,7 +458,7 @@ final class SyncLists extends BaseCommand {
// Looks like this item only exists on Kitsu // Looks like this item only exists on Kitsu
$kItem = $kitsuItem['data']; $kItem = $kitsuItem['data'];
$newItemStatus = ($kItem['reconsuming'] === true) ? 'REPEATING' : $statusMap::KITSU_TO_ANILIST[$kItem['status']]; $newItemStatus = ($kItem['reconsuming'] === TRUE) ? 'REPEATING' : $statusMap::KITSU_TO_ANILIST[$kItem['status']];
$itemsToAddToAnilist[] = [ $itemsToAddToAnilist[] = [
'mal_id' => $malId, 'mal_id' => $malId,
'data' => [ 'data' => [
@ -480,16 +476,12 @@ final class SyncLists extends BaseCommand {
'addToAnilist' => $itemsToAddToAnilist, 'addToAnilist' => $itemsToAddToAnilist,
'updateAnilist' => $anilistUpdateItems, 'updateAnilist' => $anilistUpdateItems,
'addToKitsu' => $itemsToAddToKitsu, 'addToKitsu' => $itemsToAddToKitsu,
'updateKitsu' => $kitsuUpdateItems 'updateKitsu' => $kitsuUpdateItems,
]; ];
} }
/** /**
* Compare two list items, and return the out of date one, if one exists * Compare two list items, and return the out of date one, if one exists
*
* @param array $kitsuItem
* @param array $anilistItem
* @return array|null
*/ */
private function compareListItems(array $kitsuItem, array $anilistItem): ?array private function compareListItems(array $kitsuItem, array $anilistItem): ?array
{ {
@ -503,10 +495,10 @@ final class SyncLists extends BaseCommand {
]; ];
$diff = []; $diff = [];
$dateDiff = ($kitsuItem['data']['updatedAt'] !== NULL) $dateDiff = ($kitsuItem['data']['updatedAt'] !== NULL)
? new DateTime($kitsuItem['data']['updatedAt']) <=> new DateTime((string)$anilistItem['data']['updatedAt']) ? new DateTime($kitsuItem['data']['updatedAt']) <=> new DateTime((string) $anilistItem['data']['updatedAt'])
: 0; : 0;
foreach($compareKeys as $key) foreach ($compareKeys as $key)
{ {
$diff[$key] = $kitsuItem['data'][$key] <=> $anilistItem['data'][$key]; $diff[$key] = $kitsuItem['data'][$key] <=> $anilistItem['data'][$key];
} }
@ -522,10 +514,10 @@ final class SyncLists extends BaseCommand {
$update = [ $update = [
'id' => $kitsuItem['id'], 'id' => $kitsuItem['id'],
'mal_id' => $kitsuItem['malId'], 'mal_id' => $kitsuItem['malId'],
'data' => [] 'data' => [],
]; ];
$return = [ $return = [
'updateType' => [] 'updateType' => [],
]; ];
$sameNotes = $diff['notes'] === 0; $sameNotes = $diff['notes'] === 0;
@ -549,7 +541,7 @@ final class SyncLists extends BaseCommand {
$update['data']['progress'] = $kitsuItem['data']['progress']; $update['data']['progress'] = $kitsuItem['data']['progress'];
$return['updateType'][] = Enum\API::ANILIST; $return['updateType'][] = Enum\API::ANILIST;
} }
else if($diff['progress'] === self::ANILIST_GREATER) elseif ($diff['progress'] === self::ANILIST_GREATER)
{ {
$update['data']['progress'] = $anilistItem['data']['progress']; $update['data']['progress'] = $anilistItem['data']['progress'];
$return['updateType'][] = Enum\API::KITSU; $return['updateType'][] = Enum\API::KITSU;
@ -564,7 +556,7 @@ final class SyncLists extends BaseCommand {
$update['data']['status'] = $kitsuItem['data']['status']; $update['data']['status'] = $kitsuItem['data']['status'];
$return['updateType'][] = Enum\API::ANILIST; $return['updateType'][] = Enum\API::ANILIST;
} }
else if ($dateDiff === self::ANILIST_GREATER) elseif ($dateDiff === self::ANILIST_GREATER)
{ {
$update['data']['status'] = $anilistItem['data']['status']; $update['data']['status'] = $anilistItem['data']['status'];
$return['updateType'][] = Enum\API::KITSU; $return['updateType'][] = Enum\API::KITSU;
@ -579,18 +571,18 @@ final class SyncLists extends BaseCommand {
{ {
$update['data']['status'] = $kitsuItem['data']['status']; $update['data']['status'] = $kitsuItem['data']['status'];
if ((int)$kitsuItem['data']['progress'] !== 0) if ((int) $kitsuItem['data']['progress'] !== 0)
{ {
$update['data']['progress'] = $kitsuItem['data']['progress']; $update['data']['progress'] = $kitsuItem['data']['progress'];
} }
$return['updateType'][] = Enum\API::ANILIST; $return['updateType'][] = Enum\API::ANILIST;
} }
else if($dateDiff === self::ANILIST_GREATER) elseif ($dateDiff === self::ANILIST_GREATER)
{ {
$update['data']['status'] = $anilistItem['data']['status']; $update['data']['status'] = $anilistItem['data']['status'];
if ((int)$anilistItem['data']['progress'] !== 0) if ((int) $anilistItem['data']['progress'] !== 0)
{ {
$update['data']['progress'] = $kitsuItem['data']['progress']; $update['data']['progress'] = $kitsuItem['data']['progress'];
} }
@ -603,15 +595,14 @@ final class SyncLists extends BaseCommand {
if ( ! $sameRating) if ( ! $sameRating)
{ {
if ( if (
$dateDiff === self::KITSU_GREATER && $dateDiff === self::KITSU_GREATER
$kitsuItem['data']['rating'] !== 0 && && $kitsuItem['data']['rating'] !== 0
$kitsuItem['data']['ratingTwenty'] !== 0 && $kitsuItem['data']['ratingTwenty'] !== 0
) ) {
{
$update['data']['ratingTwenty'] = $kitsuItem['data']['rating']; $update['data']['ratingTwenty'] = $kitsuItem['data']['rating'];
$return['updateType'][] = Enum\API::ANILIST; $return['updateType'][] = Enum\API::ANILIST;
} }
else if($dateDiff === self::ANILIST_GREATER && $anilistItem['data']['rating'] !== 0) elseif ($dateDiff === self::ANILIST_GREATER && $anilistItem['data']['rating'] !== 0)
{ {
$update['data']['ratingTwenty'] = $anilistItem['data']['rating'] * 2; $update['data']['ratingTwenty'] = $anilistItem['data']['rating'] * 2;
$return['updateType'][] = Enum\API::KITSU; $return['updateType'][] = Enum\API::KITSU;
@ -641,7 +632,7 @@ final class SyncLists extends BaseCommand {
$update['data']['reconsumeCount'] = $kitsuItem['data']['reconsumeCount']; $update['data']['reconsumeCount'] = $kitsuItem['data']['reconsumeCount'];
$return['updateType'][] = Enum\API::ANILIST; $return['updateType'][] = Enum\API::ANILIST;
} }
else if ($diff['reconsumeCount'] === self::ANILIST_GREATER) elseif ($diff['reconsumeCount'] === self::ANILIST_GREATER)
{ {
$update['data']['reconsumeCount'] = $anilistItem['data']['reconsumeCount']; $update['data']['reconsumeCount'] = $anilistItem['data']['reconsumeCount'];
$return['updateType'][] = Enum\API::KITSU; $return['updateType'][] = Enum\API::KITSU;
@ -683,7 +674,7 @@ final class SyncLists extends BaseCommand {
$return['data']['data'] = array_merge($prevData, $return['data']['data']); $return['data']['data'] = array_merge($prevData, $return['data']['data']);
} }
else if ($return['updateType'][0] === Enum\API::KITSU) elseif ($return['updateType'][0] === Enum\API::KITSU)
{ {
$prevData = [ $prevData = [
'notes' => $anilistItem['data']['notes'], 'notes' => $anilistItem['data']['notes'],
@ -691,7 +682,7 @@ final class SyncLists extends BaseCommand {
'progress' => $anilistItem['data']['progress'] ?? 0, 'progress' => $anilistItem['data']['progress'] ?? 0,
// Anilist returns a rating between 1-100 // Anilist returns a rating between 1-100
// Kitsu expects a rating from 1-20 // Kitsu expects a rating from 1-20
'rating' => (((int)$anilistItem['data']['rating']) > 0) 'rating' => (((int) $anilistItem['data']['rating']) > 0)
? (int) $anilistItem['data']['rating'] / 5 ? (int) $anilistItem['data']['rating'] / 5
: 0, : 0,
'reconsumeCount' => $anilistItem['data']['reconsumeCount'], 'reconsumeCount' => $anilistItem['data']['reconsumeCount'],
@ -708,19 +699,16 @@ final class SyncLists extends BaseCommand {
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// Update Helpers // Update Helpers
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
/** /**
* Create/Update list items on Kitsu * Create/Update list items on Kitsu
* *
* @param array $itemsToUpdate
* @param string $action
* @param string $type
* @throws Throwable * @throws Throwable
*/ */
private function updateKitsuListItems(array $itemsToUpdate, string $action = SyncAction::UPDATE, string $type = MediaType::ANIME): void private function updateKitsuListItems(array $itemsToUpdate, string $action = SyncAction::UPDATE, string $type = MediaType::ANIME): void
{ {
$requester = new ParallelAPIRequest(); $requester = new ParallelAPIRequest();
foreach($itemsToUpdate as $item)
foreach ($itemsToUpdate as $item)
{ {
if ($action === SyncAction::UPDATE) if ($action === SyncAction::UPDATE)
{ {
@ -728,21 +716,23 @@ final class SyncLists extends BaseCommand {
$this->kitsuModel->updateListItem(FormItem::from($item)) $this->kitsuModel->updateListItem(FormItem::from($item))
); );
} }
else if ($action === SyncAction::CREATE) elseif ($action === SyncAction::CREATE)
{ {
$maybeRequest = $this->kitsuModel->createListItem($item); $maybeRequest = $this->kitsuModel->createListItem($item);
if ($maybeRequest === NULL) if ($maybeRequest === NULL)
{ {
$this->echoWarning("Skipped creating Kitsu {$type} due to missing id ¯\_(ツ)_/¯"); $this->echoWarning("Skipped creating Kitsu {$type} due to missing id ¯\\_(ツ)_/¯");
continue; continue;
} }
$requester->addRequest($maybeRequest); $requester->addRequest($maybeRequest);
} }
} }
$responses = $requester->makeRequests(); $responses = $requester->makeRequests();
foreach($responses as $key => $response) foreach ($responses as $key => $response)
{ {
$responseData = Json::decode($response); $responseData = Json::decode($response);
@ -752,6 +742,7 @@ final class SyncLists extends BaseCommand {
{ {
$verb = ($action === SyncAction::UPDATE) ? 'updated' : 'created'; $verb = ($action === SyncAction::UPDATE) ? 'updated' : 'created';
$this->echoSuccess("Successfully {$verb} Kitsu {$type} list item with id: {$id}"); $this->echoSuccess("Successfully {$verb} Kitsu {$type} list item with id: {$id}");
continue; continue;
} }
@ -763,6 +754,7 @@ final class SyncLists extends BaseCommand {
if ($errorTitle === 'cannot exceed length of media') if ($errorTitle === 'cannot exceed length of media')
{ {
$this->echoWarning("Skipped Kitsu {$type} {$id} due to episode count mismatch with other API"); $this->echoWarning("Skipped Kitsu {$type} {$id} due to episode count mismatch with other API");
continue; continue;
} }
} }
@ -774,23 +766,19 @@ final class SyncLists extends BaseCommand {
]); ]);
$verb = ($action === SyncAction::UPDATE) ? SyncAction::UPDATE : SyncAction::CREATE; $verb = ($action === SyncAction::UPDATE) ? SyncAction::UPDATE : SyncAction::CREATE;
$this->echoError("Failed to {$verb} Kitsu {$type} list item with id: {$id}, and mal_id: {$mal_id}"); $this->echoError("Failed to {$verb} Kitsu {$type} list item with id: {$id}, and mal_id: {$mal_id}");
} }
} }
/** /**
* Create/Update list items on Anilist * Create/Update list items on Anilist
* *
* @param array $itemsToUpdate
* @param string $action
* @param string $type
* @throws Throwable * @throws Throwable
*/ */
private function updateAnilistListItems(array $itemsToUpdate, string $action = SyncAction::UPDATE, string $type = MediaType::ANIME): void private function updateAnilistListItems(array $itemsToUpdate, string $action = SyncAction::UPDATE, string $type = MediaType::ANIME): void
{ {
$requester = new ParallelAPIRequest(); $requester = new ParallelAPIRequest();
foreach($itemsToUpdate as $item) foreach ($itemsToUpdate as $item)
{ {
if ($action === SyncAction::UPDATE) if ($action === SyncAction::UPDATE)
{ {
@ -800,24 +788,27 @@ final class SyncLists extends BaseCommand {
$requester->addRequest($maybeRequest); $requester->addRequest($maybeRequest);
} }
} }
else if ($action === SyncAction::CREATE) else
{ {
try if ($action === SyncAction::CREATE)
{ {
$requester->addRequest($this->anilistModel->createFullListItem($item, $type)); try
} {
catch (MissingIdException $e) $requester->addRequest($this->anilistModel->createFullListItem($item, $type));
{ }
// Case where there's a MAL mapping from Kitsu, but no equivalent Anlist item catch (MissingIdException)
$id = $item['mal_id']; {
$this->echoWarning("Skipping Anilist ${type} with MAL id: {$id} due to missing mapping"); // Case where there's a MAL mapping from Kitsu, but no equivalent Anlist item
$id = $item['mal_id'];
$this->echoWarning("Skipping Anilist {$type} with MAL id: {$id} due to missing mapping");
}
} }
} }
} }
$responses = $requester->makeRequests(); $responses = $requester->makeRequests();
foreach($responses as $key => $response) foreach ($responses as $key => $response)
{ {
$id = $itemsToUpdate[$key]['mal_id']; $id = $itemsToUpdate[$key]['mal_id'];

View File

@ -6,12 +6,10 @@
* *
* PHP version 8 * PHP version 8
* *
* @package HummingbirdAnimeClient * @copyright 2015 - 2022 Timothy J. Warren <tim@timshome.page>
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2021 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License * @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.2 * @version 5.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient * @link https://git.timshome.page/timw4mail/HummingBirdAnimeClient
*/ */
namespace Aviat\AnimeClient\Command; namespace Aviat\AnimeClient\Command;
@ -23,10 +21,10 @@ use Aviat\AnimeClient\Controller\Images;
* 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 ClearThumbnails { final class UpdateThumbnails extends ClearThumbnails
{
/** /**
* Model for making requests to Kitsu API * Model for making requests to Kitsu API
* @var KitsuModel
*/ */
protected KitsuModel $kitsuModel; protected KitsuModel $kitsuModel;
@ -49,7 +47,7 @@ final class UpdateThumbnails extends ClearThumbnails {
$ids = $this->getImageList(); $ids = $this->getImageList();
// Resave the images // Resave the images
foreach($ids as $type => $typeIds) foreach ($ids as $type => $typeIds)
{ {
foreach ($typeIds as $id) foreach ($typeIds as $id)
{ {
@ -82,4 +80,4 @@ final class UpdateThumbnails extends ClearThumbnails {
'manga' => $mangaIds, 'manga' => $mangaIds,
]; ];
} }
} }

View File

@ -6,19 +6,18 @@
* *
* PHP version 8 * PHP version 8
* *
* @package HummingbirdAnimeClient * @copyright 2015 - 2022 Timothy J. Warren <tim@timshome.page>
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2021 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License * @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.2 * @version 5.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient * @link https://git.timshome.page/timw4mail/HummingBirdAnimeClient
*/ */
namespace Aviat\AnimeClient\Component; namespace Aviat\AnimeClient\Component;
use Aviat\AnimeClient\Types\AnimeListItem; use Aviat\AnimeClient\Types\AnimeListItem;
final class AnimeCover { final class AnimeCover
{
use ComponentTrait; use ComponentTrait;
public function __invoke(AnimeListItem $item): string public function __invoke(AnimeListItem $item): string
@ -27,4 +26,4 @@ final class AnimeCover {
'item' => $item, 'item' => $item,
]); ]);
} }
} }

View File

@ -6,17 +6,16 @@
* *
* PHP version 8 * PHP version 8
* *
* @package HummingbirdAnimeClient * @copyright 2015 - 2022 Timothy J. Warren <tim@timshome.page>
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2021 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License * @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.2 * @version 5.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient * @link https://git.timshome.page/timw4mail/HummingBirdAnimeClient
*/ */
namespace Aviat\AnimeClient\Component; namespace Aviat\AnimeClient\Component;
final class Character { final class Character
{
use ComponentTrait; use ComponentTrait;
public function __invoke(string $name, string $link, string $picture, string $className = 'character'): string public function __invoke(string $name, string $link, string $picture, string $className = 'character'): string
@ -28,4 +27,4 @@ final class Character {
'className' => $className, 'className' => $className,
]); ]);
} }
} }

View File

@ -6,32 +6,26 @@
* *
* PHP version 8 * PHP version 8
* *
* @package HummingbirdAnimeClient * @copyright 2015 - 2022 Timothy J. Warren <tim@timshome.page>
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2021 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License * @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.2 * @version 5.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient * @link https://git.timshome.page/timw4mail/HummingBirdAnimeClient
*/ */
namespace Aviat\AnimeClient\Component; namespace Aviat\AnimeClient\Component;
use Aviat\Ion\Di\ContainerAware; use Aviat\Ion\Di\ContainerAware;
use const TEMPLATE_DIR;
use function Aviat\AnimeClient\renderTemplate; use function Aviat\AnimeClient\renderTemplate;
/** /**
* Shared logic for component-based functionality, like Tabs * Shared logic for component-based functionality, like Tabs
*/ */
trait ComponentTrait { trait ComponentTrait
{
use ContainerAware; use ContainerAware;
/** /**
* Render a template with common container values * Render a template with common container values
*
* @param string $path
* @param array $data
* @return string
*/ */
public function render(string $path, array $data): string public function render(string $path, array $data): string
{ {
@ -47,4 +41,4 @@ trait ComponentTrait {
return renderTemplate(TEMPLATE_DIR . '/' . $path, array_merge($baseData, $data)); return renderTemplate(TEMPLATE_DIR . '/' . $path, array_merge($baseData, $data));
} }
} }

View File

@ -6,19 +6,18 @@
* *
* PHP version 8 * PHP version 8
* *
* @package HummingbirdAnimeClient * @copyright 2015 - 2022 Timothy J. Warren <tim@timshome.page>
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2021 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License * @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.2 * @version 5.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient * @link https://git.timshome.page/timw4mail/HummingBirdAnimeClient
*/ */
namespace Aviat\AnimeClient\Component; namespace Aviat\AnimeClient\Component;
use Aviat\AnimeClient\Types\MangaListItem; use Aviat\AnimeClient\Types\MangaListItem;
final class MangaCover { final class MangaCover
{
use ComponentTrait; use ComponentTrait;
public function __invoke(MangaListItem $item, string $name): string public function __invoke(MangaListItem $item, string $name): string
@ -28,4 +27,4 @@ final class MangaCover {
'name' => $name, 'name' => $name,
]); ]);
} }
} }

View File

@ -6,17 +6,16 @@
* *
* PHP version 8 * PHP version 8
* *
* @package HummingbirdAnimeClient * @copyright 2015 - 2022 Timothy J. Warren <tim@timshome.page>
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2021 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License * @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.2 * @version 5.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient * @link https://git.timshome.page/timw4mail/HummingBirdAnimeClient
*/ */
namespace Aviat\AnimeClient\Component; namespace Aviat\AnimeClient\Component;
final class Media { final class Media
{
use ComponentTrait; use ComponentTrait;
public function __invoke(array $titles, string $link, string $picture, string $className = 'media'): string public function __invoke(array $titles, string $link, string $picture, string $className = 'media'): string
@ -28,4 +27,4 @@ final class Media {
'className' => $className, 'className' => $className,
]); ]);
} }
} }

View File

@ -6,38 +6,33 @@
* *
* PHP version 8 * PHP version 8
* *
* @package HummingbirdAnimeClient * @copyright 2015 - 2022 Timothy J. Warren <tim@timshome.page>
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2021 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License * @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.2 * @version 5.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient * @link https://git.timshome.page/timw4mail/HummingBirdAnimeClient
*/ */
namespace Aviat\AnimeClient\Component; namespace Aviat\AnimeClient\Component;
final class Tabs { final class Tabs
{
use ComponentTrait; use ComponentTrait;
/** /**
* Creates a tabbed content view * Creates a tabbed content view
* *
* @param string $name the name attribute for the input[type-option] form elements * @param string $name the name attribute for the input[type-option] form elements
* also used to generate id attributes * also used to generate id attributes
* @param array $tabData The data used to create the tab content, indexed by the tab label * @param array $tabData The data used to create the tab content, indexed by the tab label
* @param callable $cb The function to generate the tab content * @param callable $cb The function to generate the tab content
* @param string $className
* @param bool $hasSectionWrapper
* @return string
*/ */
public function __invoke( public function __invoke(
string $name, string $name,
array $tabData, array $tabData,
callable $cb, callable $cb,
string $className = 'content media-wrap flex flex-wrap flex-justify-start', string $className = 'content media-wrap flex flex-wrap flex-justify-start',
bool $hasSectionWrapper = false bool $hasSectionWrapper = FALSE
): string ): string {
{
if (count($tabData) < 2) if (count($tabData) < 2)
{ {
return $this->render('single-tab.php', [ return $this->render('single-tab.php', [
@ -57,4 +52,4 @@ final class Tabs {
'hasSectionWrapper' => $hasSectionWrapper, 'hasSectionWrapper' => $hasSectionWrapper,
]); ]);
} }
} }

View File

@ -6,36 +6,32 @@
* *
* PHP version 8 * PHP version 8
* *
* @package HummingbirdAnimeClient * @copyright 2015 - 2022 Timothy J. Warren <tim@timshome.page>
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2021 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License * @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.2 * @version 5.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient * @link https://git.timshome.page/timw4mail/HummingBirdAnimeClient
*/ */
namespace Aviat\AnimeClient\Component; namespace Aviat\AnimeClient\Component;
final class VerticalTabs { final class VerticalTabs
{
use ComponentTrait; use ComponentTrait;
/** /**
* Creates a vertical tab content view * Creates a vertical tab content view
* *
* @param string $name the name attribute for the input[type-option] form elements * @param string $name the name attribute for the input[type-option] form elements
* also used to generate id attributes * also used to generate id attributes
* @param array $tabData The data used to create the tab content, indexed by the tab label * @param array $tabData The data used to create the tab content, indexed by the tab label
* @param callable $cb The function to generate the tab content * @param callable $cb The function to generate the tab content
* @param string $className
* @return string
*/ */
public function __invoke( public function __invoke(
string $name, string $name,
array $tabData, array $tabData,
callable $cb, callable $cb,
string $className='content media-wrap flex flex-wrap flex-justify-start' string $className = 'content media-wrap flex flex-wrap flex-justify-start'
): string ): string {
{
return $this->render('vertical-tabs.php', [ return $this->render('vertical-tabs.php', [
'name' => $name, 'name' => $name,
'data' => $tabData, 'data' => $tabData,
@ -43,4 +39,4 @@ final class VerticalTabs {
'className' => $className, 'className' => $className,
]); ]);
} }
} }

View File

@ -6,42 +6,40 @@
* *
* PHP version 8 * PHP version 8
* *
* @package HummingbirdAnimeClient * @copyright 2015 - 2022 Timothy J. Warren <tim@timshome.page>
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2021 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License * @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.2 * @version 5.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient * @link https://git.timshome.page/timw4mail/HummingBirdAnimeClient
*/ */
namespace Aviat\AnimeClient; namespace Aviat\AnimeClient;
use function Aviat\Ion\_dir;
use Aviat\AnimeClient\Enum\EventType;
use Aura\Router\Generator; use Aura\Router\Generator;
use Aura\Session\Segment; use Aura\Session\Segment;
use Aviat\AnimeClient\API\Kitsu\Auth; use Aviat\AnimeClient\API\Kitsu\Auth;
use Aviat\Ion\ConfigInterface; use Aviat\AnimeClient\Enum\EventType;
use Psr\Http\Message\ServerRequestInterface;
use Psr\SimpleCache\CacheInterface;
use Aviat\Ion\Di\{ use Aviat\Ion\Di\{
ContainerAware, ContainerAware,
ContainerInterface, ContainerInterface,
Exception\ContainerException, Exception\ContainerException,
Exception\NotFoundException Exception\NotFoundException
}; };
use Aviat\Ion\Event;
use Aviat\Ion\Exception\DoubleRenderException; use Aviat\Ion\Exception\DoubleRenderException;
use Aviat\Ion\View\{HtmlView, HttpView, JsonView}; use Aviat\Ion\View\{HtmlView, HttpView, JsonView};
use Aviat\Ion\{ConfigInterface, Event};
use InvalidArgumentException; use InvalidArgumentException;
use Psr\Http\Message\ServerRequestInterface;
use Psr\SimpleCache\CacheInterface;
use function Aviat\Ion\_dir;
use function is_array;
/** /**
* Controller base, defines output methods * Controller base, defines output methods
*/ */
class Controller { class Controller
{
use ContainerAware; use ContainerAware;
/** /**
@ -87,7 +85,6 @@ class Controller {
/** /**
* Controller constructor. * Controller constructor.
* *
* @param ContainerInterface $container
* @throws ContainerException * @throws ContainerException
* @throws NotFoundException * @throws NotFoundException
*/ */
@ -100,7 +97,7 @@ class Controller {
$urlGenerator = $container->get('url-generator'); $urlGenerator = $container->get('url-generator');
$this->auth = $container->get('auth'); $this->auth = $container->get('auth');
$this->cache = $container->get('cache'); $this->cache = $container->get('cache');
$this->config = $container->get('config'); $this->config = $container->get('config');
$this->request = $container->get('request'); $this->request = $container->get('request');
$this->session = $session->getSegment(SESSION_SEGMENT); $this->session = $session->getSegment(SESSION_SEGMENT);
@ -127,11 +124,10 @@ class Controller {
* Set the current url in the session as the target of a future redirect * Set the current url in the session as the target of a future redirect
* *
* @codeCoverageIgnore * @codeCoverageIgnore
* @param string|NULL $url
* @throws ContainerException * @throws ContainerException
* @throws NotFoundException * @throws NotFoundException
*/ */
public function setSessionRedirect(string $url = NULL): void public function setSessionRedirect(?string $url = NULL): void
{ {
$serverParams = $this->request->getServerParams(); $serverParams = $this->request->getServerParams();
@ -169,7 +165,6 @@ class Controller {
* *
* @codeCoverageIgnore * @codeCoverageIgnore
* @throws InvalidArgumentException * @throws InvalidArgumentException
* @return void
*/ */
public function sessionRedirect(): void public function sessionRedirect(): void
{ {
@ -199,10 +194,6 @@ class Controller {
* Get the string output of a partial template * Get the string output of a partial template
* *
* @codeCoverageIgnore * @codeCoverageIgnore
* @param HtmlView $view
* @param string $template
* @param array $data
* @return string
*/ */
protected function loadPartial(HtmlView $view, string $template, array $data = []): string protected function loadPartial(HtmlView $view, string $template, array $data = []): string
{ {
@ -216,7 +207,6 @@ class Controller {
$route = $router->getRoute(); $route = $router->getRoute();
$data['route_path'] = $route !== FALSE ? $route->path : ''; $data['route_path'] = $route !== FALSE ? $route->path : '';
$templatePath = _dir($this->config->get('view_path'), "{$template}.php"); $templatePath = _dir($this->config->get('view_path'), "{$template}.php");
if ( ! is_file($templatePath)) if ( ! is_file($templatePath))
@ -231,10 +221,6 @@ class Controller {
* Render a template with header and footer * Render a template with header and footer
* *
* @codeCoverageIgnore * @codeCoverageIgnore
* @param HtmlView $view
* @param string $template
* @param array $data
* @return HtmlView
*/ */
protected function renderFullPage(HtmlView $view, string $template, array $data): HtmlView protected function renderFullPage(HtmlView $view, string $template, array $data): HtmlView
{ {
@ -247,7 +233,7 @@ class Controller {
$view->addHeader('Content-Security-Policy', implode('; ', $csp)); $view->addHeader('Content-Security-Policy', implode('; ', $csp));
$view->appendOutput($this->loadPartial($view, 'header', $data)); $view->appendOutput($this->loadPartial($view, 'header', $data));
if (array_key_exists('message', $data) && \is_array($data['message'])) if (array_key_exists('message', $data) && is_array($data['message']))
{ {
$view->appendOutput($this->loadPartial($view, 'message', $data['message'])); $view->appendOutput($this->loadPartial($view, 'message', $data['message']));
} }
@ -262,20 +248,17 @@ class Controller {
* 404 action * 404 action
* *
* @codeCoverageIgnore * @codeCoverageIgnore
* @param string $title
* @param string $message
* @throws InvalidArgumentException * @throws InvalidArgumentException
* @return void
*/ */
public function notFound( public function notFound(
string $title = 'Sorry, page not found', string $title = 'Sorry, page not found',
string $message = 'Page Not Found' string $message = 'Page Not Found'
): void ): void {
{
$this->outputHTML('404', [ $this->outputHTML('404', [
'title' => $title, 'title' => $title,
'message' => $message, 'message' => $message,
], NULL, 404); ], NULL, 404);
exit(); exit();
} }
@ -283,19 +266,14 @@ class Controller {
* Display a generic error page * Display a generic error page
* *
* @codeCoverageIgnore * @codeCoverageIgnore
* @param int $httpCode
* @param string $title
* @param string $message
* @param string $longMessage
* @throws InvalidArgumentException * @throws InvalidArgumentException
* @return void
*/ */
public function errorPage(int $httpCode, string $title, string $message, string $longMessage = ''): void public function errorPage(int $httpCode, string $title, string $message, string $longMessage = ''): void
{ {
$this->outputHTML('error', [ $this->outputHTML('error', [
'title' => $title, 'title' => $title,
'message' => $message, 'message' => $message,
'long_message' => $longMessage 'long_message' => $longMessage,
], NULL, $httpCode); ], NULL, $httpCode);
} }
@ -304,7 +282,6 @@ class Controller {
* *
* @codeCoverageIgnore * @codeCoverageIgnore
* @throws InvalidArgumentException * @throws InvalidArgumentException
* @return void
*/ */
public function redirectToDefaultRoute(): void public function redirectToDefaultRoute(): void
{ {
@ -317,9 +294,6 @@ class Controller {
* next page load * next page load
* *
* @codeCoverageIgnore * @codeCoverageIgnore
* @param string $message
* @param string $type
* @return void
*/ */
public function setFlashMessage(string $message, string $type = 'info'): void public function setFlashMessage(string $message, string $type = 'info'): void
{ {
@ -332,7 +306,7 @@ class Controller {
$messages[] = [ $messages[] = [
'message_type' => $type, 'message_type' => $type,
'message' => $message 'message' => $message,
]; ];
$this->session->setFlash('message', $messages); $this->session->setFlash('message', $messages);
@ -342,9 +316,8 @@ class Controller {
* Helper for consistent page titles * Helper for consistent page titles
* *
* @param string ...$parts Title segments * @param string ...$parts Title segments
* @return string
*/ */
public function formatTitle(string ...$parts) : string public function formatTitle(string ...$parts): string
{ {
return implode(' &middot; ', $parts); return implode(' &middot; ', $parts);
} }
@ -353,17 +326,13 @@ class Controller {
* Add a message box to the page * Add a message box to the page
* *
* @codeCoverageIgnore * @codeCoverageIgnore
* @param HtmlView $view
* @param string $type
* @param string $message
* @throws InvalidArgumentException * @throws InvalidArgumentException
* @return string
*/ */
protected function showMessage(HtmlView $view, string $type, string $message): string protected function showMessage(HtmlView $view, string $type, string $message): string
{ {
return $this->loadPartial($view, 'message', [ return $this->loadPartial($view, 'message', [
'message_type' => $type, 'message_type' => $type,
'message' => $message 'message' => $message,
]); ]);
} }
@ -371,14 +340,9 @@ class Controller {
* Output a template to HTML, using the provided data * Output a template to HTML, using the provided data
* *
* @codeCoverageIgnore * @codeCoverageIgnore
* @param string $template * @throws InvalidArgumentException
* @param array $data
* @param HtmlView|NULL $view
* @param int $code
* @return void
*@throws InvalidArgumentException
*/ */
protected function outputHTML(string $template, array $data = [], HtmlView $view = NULL, int $code = 200): void protected function outputHTML(string $template, array $data = [], ?HtmlView $view = NULL, int $code = 200): void
{ {
if (NULL === $view) if (NULL === $view)
{ {
@ -393,14 +357,12 @@ class Controller {
* Output a JSON Response * Output a JSON Response
* *
* @codeCoverageIgnore * @codeCoverageIgnore
* @param mixed $data
* @param int $code - the http status code * @param int $code - the http status code
* @throws DoubleRenderException * @throws DoubleRenderException
* @return void
*/ */
protected function outputJSON(mixed $data, int $code): void protected function outputJSON(mixed $data, int $code): void
{ {
(new JsonView()) JsonView::new()
->setOutput($data) ->setOutput($data)
->setStatusCode($code) ->setStatusCode($code)
->send(); ->send();
@ -410,17 +372,13 @@ class Controller {
* Redirect to the selected page * Redirect to the selected page
* *
* @codeCoverageIgnore * @codeCoverageIgnore
* @param string $url
* @param int $code
* @return void
*/ */
protected function redirect(string $url, int $code): void protected function redirect(string $url, int $code): void
{ {
try HttpView::new()
{ ->redirect($url, $code)
(new HttpView())->redirect($url, $code)->send(); ->send();
}
catch (\Throwable) {}
} }
} }
// End of BaseController.php
// End of BaseController.php

View File

@ -6,26 +6,25 @@
* *
* PHP version 8 * PHP version 8
* *
* @package HummingbirdAnimeClient * @copyright 2015 - 2022 Timothy J. Warren <tim@timshome.page>
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2021 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License * @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.2 * @version 5.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient * @link https://git.timshome.page/timw4mail/HummingBirdAnimeClient
*/ */
namespace Aviat\AnimeClient\Controller; namespace Aviat\AnimeClient\Controller;
use Aura\Router\Exception\RouteNotFound; use Aura\Router\Exception\RouteNotFound;
use Aviat\AnimeClient\Controller as BaseController;
use Aviat\AnimeClient\API\Kitsu\Transformer\AnimeListTransformer;
use Aviat\AnimeClient\API\Enum\AnimeWatchingStatus\Kitsu as KitsuWatchingStatus; use Aviat\AnimeClient\API\Enum\AnimeWatchingStatus\Kitsu as KitsuWatchingStatus;
use Aviat\AnimeClient\API\Kitsu\Transformer\AnimeListTransformer;
use Aviat\AnimeClient\API\Mapping\AnimeWatchingStatus; use Aviat\AnimeClient\API\Mapping\AnimeWatchingStatus;
use Aviat\AnimeClient\Controller as BaseController;
use Aviat\AnimeClient\Model\Anime as AnimeModel; use Aviat\AnimeClient\Model\Anime as AnimeModel;
use Aviat\AnimeClient\Types\FormItem; use Aviat\AnimeClient\Types\FormItem;
use Aviat\Ion\Attribute\Controller;
use Aviat\Ion\Attribute\Route;
use Aviat\Ion\Di\ContainerInterface; use Aviat\Ion\Di\ContainerInterface;
use Aviat\Ion\Di\Exception\ContainerException; use Aviat\Ion\Di\Exception\{ContainerException, NotFoundException};
use Aviat\Ion\Di\Exception\NotFoundException;
use Aviat\Ion\Json; use Aviat\Ion\Json;
use InvalidArgumentException; use InvalidArgumentException;
@ -35,18 +34,17 @@ use TypeError;
/** /**
* Controller for Anime-related pages * Controller for Anime-related pages
*/ */
final class Anime extends BaseController { #[Controller('anime')]
final class Anime extends BaseController
{
/** /**
* The anime list model * The anime list model
* @var AnimeModel $model
*/ */
protected AnimeModel $model; protected AnimeModel $model;
/** /**
* Constructor * Constructor
* *
* @param ContainerInterface $container
* @throws ContainerException * @throws ContainerException
* @throws NotFoundException * @throws NotFoundException
*/ */
@ -68,10 +66,10 @@ final class Anime extends BaseController {
* *
* @param int|string $status - The section of the list * @param int|string $status - The section of the list
* @param string|null $view - List or cover view * @param string|null $view - List or cover view
* @return void
* @throws InvalidArgumentException * @throws InvalidArgumentException
* @throws Throwable * @throws Throwable
*/ */
#[Route('anime.list', '/anime/{status}{/view}')]
public function index(int|string $status = KitsuWatchingStatus::WATCHING, ?string $view = NULL): void public function index(int|string $status = KitsuWatchingStatus::WATCHING, ?string $view = NULL): void
{ {
if ( ! in_array($status, [ if ( ! in_array($status, [
@ -95,7 +93,7 @@ final class Anime extends BaseController {
$viewMap = [ $viewMap = [
'' => 'cover', '' => 'cover',
'list' => 'list' 'list' => 'list',
]; ];
$data = ($status !== 'all') $data = ($status !== 'all')
@ -104,7 +102,7 @@ final class Anime extends BaseController {
$this->outputHTML('anime/' . $viewMap[$view], [ $this->outputHTML('anime/' . $viewMap[$view], [
'title' => $title, 'title' => $title,
'sections' => $data 'sections' => $data,
]); ]);
} }
@ -112,12 +110,12 @@ final class Anime extends BaseController {
* Form to add an anime * Form to add an anime
* *
* @throws ContainerException * @throws ContainerException
* @throws InvalidArgumentException
* @throws NotFoundException * @throws NotFoundException
* @throws RouteNotFound * @throws RouteNotFound
* @throws InvalidArgumentException
* @throws Throwable * @throws Throwable
* @return void
*/ */
#[Route('anime.add.get', '/anime/add')]
public function addForm(): void public function addForm(): void
{ {
$this->checkAuth(); $this->checkAuth();
@ -129,7 +127,7 @@ final class Anime extends BaseController {
'Add' 'Add'
), ),
'action_url' => $this->url->generate('anime.add.post'), 'action_url' => $this->url->generate('anime.add.post'),
'status_list' => AnimeWatchingStatus::KITSU_TO_TITLE 'status_list' => AnimeWatchingStatus::KITSU_TO_TITLE,
]); ]);
} }
@ -137,13 +135,13 @@ final class Anime extends BaseController {
* Add an anime to the list * Add an anime to the list
* *
* @throws Throwable * @throws Throwable
* @return void
*/ */
#[Route('anime.add.post', '/anime/add', Route::POST)]
public function add(): void public function add(): void
{ {
$this->checkAuth(); $this->checkAuth();
$data = (array)$this->request->getParsedBody(); $data = (array) $this->request->getParsedBody();
if (empty($data['mal_id'])) if (empty($data['mal_id']))
{ {
@ -155,7 +153,7 @@ final class Anime extends BaseController {
$this->redirect('anime/add', 303); $this->redirect('anime/add', 303);
} }
$result = $this->model->createLibraryItem($data); $result = $this->model->createItem($data);
if ($result) if ($result)
{ {
@ -172,15 +170,13 @@ final class Anime extends BaseController {
/** /**
* Form to edit details about a series * Form to edit details about a series
*
* @param string $id
* @param string $status
*/ */
#[Route('anime.edit', '/anime/edit/{id}/{status}')]
public function edit(string $id, string $status = 'all'): void public function edit(string $id, string $status = 'all'): void
{ {
$this->checkAuth(); $this->checkAuth();
$item = $this->model->getLibraryItem($id); $item = $this->model->getItem($id);
$this->setSessionRedirect(); $this->setSessionRedirect();
$this->outputHTML('anime/edit', [ $this->outputHTML('anime/edit', [
@ -191,16 +187,15 @@ final class Anime extends BaseController {
'item' => $item, 'item' => $item,
'statuses' => AnimeWatchingStatus::KITSU_TO_TITLE, 'statuses' => AnimeWatchingStatus::KITSU_TO_TITLE,
'action' => $this->url->generate('update.post', [ 'action' => $this->url->generate('update.post', [
'controller' => 'anime' 'controller' => 'anime',
]), ]),
]); ]);
} }
/** /**
* Search for anime * Search for anime
*
* @return void
*/ */
#[Route('anime.search', '/anime/search')]
public function search(): void public function search(): void
{ {
$queryParams = $this->request->getQueryParams(); $queryParams = $this->request->getQueryParams();
@ -212,19 +207,19 @@ final class Anime extends BaseController {
* Update an anime item via a form submission * Update an anime item via a form submission
* *
* @throws Throwable * @throws Throwable
* @return void
*/ */
#[Route('anime.update.post', '/anime/update_form', Route::POST)]
public function formUpdate(): void public function formUpdate(): void
{ {
$this->checkAuth(); $this->checkAuth();
$data = (array)$this->request->getParsedBody(); $data = (array) $this->request->getParsedBody();
// Do some minor data manipulation for // Do some minor data manipulation for
// large form-based updates // large form-based updates
$transformer = new AnimeListTransformer(); $transformer = new AnimeListTransformer();
$postData = $transformer->untransform($data); $postData = $transformer->untransform($data);
$fullResult = $this->model->updateLibraryItem(FormItem::from($postData)); $fullResult = $this->model->updateItem(FormItem::from($postData));
if ($fullResult['statusCode'] === 200) if ($fullResult['statusCode'] === 200)
{ {
@ -243,28 +238,24 @@ final class Anime extends BaseController {
* Increase the watched count for an anime item * Increase the watched count for an anime item
* *
* @throws Throwable * @throws Throwable
* @return void
*/ */
#[Route('anime.increment', '/anime/increment', Route::POST)]
public function increment(): void public function increment(): void
{ {
$this->checkAuth(); $this->checkAuth();
if (str_contains($this->request->getHeader('content-type')[0], 'application/json')) $data = str_contains($this->request->getHeader('content-type')[0], 'application/json')
{ ? Json::decode((string) $this->request->getBody())
$data = Json::decode((string)$this->request->getBody()); : (array) $this->request->getParsedBody();
}
else
{
$data = (array)$this->request->getParsedBody();
}
if (empty($data)) if (empty($data))
{ {
$this->errorPage(400, 'Bad Request', ''); $this->errorPage(400, 'Bad Request', '');
exit(); exit();
} }
$response = $this->model->incrementLibraryItem(FormItem::from($data)); $response = $this->model->incrementItem(FormItem::from($data));
$this->cache->clear(); $this->cache->clear();
$this->outputJSON($response['body'], $response['statusCode']); $this->outputJSON($response['body'], $response['statusCode']);
@ -274,14 +265,14 @@ final class Anime extends BaseController {
* Remove an anime from the list * Remove an anime from the list
* *
* @throws Throwable * @throws Throwable
* @return void
*/ */
#[Route('anime.delete', '/anime/delete', Route::POST)]
public function delete(): void public function delete(): void
{ {
$this->checkAuth(); $this->checkAuth();
$body = (array)$this->request->getParsedBody(); $body = (array) $this->request->getParsedBody();
$response = $this->model->deleteLibraryItem($body['id'], $body['mal_id']); $response = $this->model->deleteItem(FormItem::from($body));
if ($response === TRUE) if ($response === TRUE)
{ {
@ -299,10 +290,9 @@ final class Anime extends BaseController {
/** /**
* View details of an anime * View details of an anime
* *
* @param string $id
* @throws InvalidArgumentException * @throws InvalidArgumentException
* @return void
*/ */
#[Route('anime.details', '/anime/details/{id}')]
public function details(string $id): void public function details(string $id): void
{ {
try try
@ -341,6 +331,7 @@ final class Anime extends BaseController {
} }
} }
#[Route('anime.random', '/anime/details/random')]
public function random(): void public function random(): void
{ {
try try
@ -379,4 +370,5 @@ final class Anime extends BaseController {
} }
} }
} }
// End of AnimeController.php
// End of AnimeController.php

View File

@ -6,12 +6,10 @@
* *
* PHP version 8 * PHP version 8
* *
* @package HummingbirdAnimeClient * @copyright 2015 - 2022 Timothy J. Warren <tim@timshome.page>
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2021 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License * @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.2 * @version 5.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient * @link https://git.timshome.page/timw4mail/HummingBirdAnimeClient
*/ */
namespace Aviat\AnimeClient\Controller; namespace Aviat\AnimeClient\Controller;
@ -22,9 +20,11 @@ use Aviat\AnimeClient\Model\{
Anime as AnimeModel, Anime as AnimeModel,
AnimeCollection as AnimeCollectionModel AnimeCollection as AnimeCollectionModel
}; };
use Aviat\Ion\Attribute\Controller;
use Aviat\Ion\Attribute\Route;
use Aviat\Ion\Di\ContainerInterface; use Aviat\Ion\Di\ContainerInterface;
use Aviat\Ion\Di\Exception\ContainerException; use Aviat\Ion\Di\Exception\{ContainerException, NotFoundException};
use Aviat\Ion\Di\Exception\NotFoundException; use Aviat\Ion\Json;
use Aviat\Ion\Exception\DoubleRenderException; use Aviat\Ion\Exception\DoubleRenderException;
use InvalidArgumentException; use InvalidArgumentException;
@ -32,24 +32,22 @@ use InvalidArgumentException;
/** /**
* Controller for Anime collection pages * Controller for Anime collection pages
*/ */
final class AnimeCollection extends BaseController { #[Controller('anime.collection')]
final class AnimeCollection extends BaseController
{
/** /**
* The anime collection model * The anime collection model
* @var AnimeCollectionModel $animeCollectionModel
*/ */
private AnimeCollectionModel $animeCollectionModel; private AnimeCollectionModel $animeCollectionModel;
/** /**
* The anime API model * The anime API model
* @var AnimeModel $animeModel
*/ */
private AnimeModel $animeModel; private AnimeModel $animeModel;
/** /**
* Constructor * Constructor
* *
* @param ContainerInterface $container
* @throws ContainerException * @throws ContainerException
* @throws NotFoundException * @throws NotFoundException
*/ */
@ -67,6 +65,8 @@ final class AnimeCollection extends BaseController {
]); ]);
} }
#[Route('anime.collection.redirect', '/anime-collection')]
#[Route('anime.collection.redirect2', '/anime-collection/')]
public function index(): void public function index(): void
{ {
$this->redirect('/anime-collection/view', 303); $this->redirect('/anime-collection/view', 303);
@ -76,8 +76,8 @@ final class AnimeCollection extends BaseController {
* Search for anime * Search for anime
* *
* @throws DoubleRenderException * @throws DoubleRenderException
* @return void
*/ */
#[Route('anime.collection.search', '/anime-collection/search')]
public function search(): void public function search(): void
{ {
$queryParams = $this->request->getQueryParams(); $queryParams = $this->request->getQueryParams();
@ -88,17 +88,16 @@ final class AnimeCollection extends BaseController {
/** /**
* Show the anime collection page * Show the anime collection page
* *
* @param string|null $view
* @throws ContainerException * @throws ContainerException
* @throws NotFoundException
* @throws InvalidArgumentException * @throws InvalidArgumentException
* @return void * @throws NotFoundException
*/ */
#[Route('anime.collection.view', '/anime-collection/view{/view}')]
public function view(?string $view = ''): void public function view(?string $view = ''): void
{ {
$viewMap = [ $viewMap = [
'' => 'cover', '' => 'cover',
'list' => 'list' 'list' => 'list',
]; ];
$sections = array_merge( $sections = array_merge(
@ -115,14 +114,15 @@ final class AnimeCollection extends BaseController {
/** /**
* Show the anime collection add/edit form * Show the anime collection add/edit form
* *
* @param integer|null $id * @param int|null $id
* @throws ContainerException * @throws ContainerException
* @throws InvalidArgumentException
* @throws NotFoundException * @throws NotFoundException
* @throws RouteNotFound * @throws RouteNotFound
* @throws InvalidArgumentException
* @return void
*/ */
public function form($id = NULL): void #[Route('anime.collection.add.get', '/anime-collection/add')]
#[Route('anime.collection.edit.get', '/anime-collection/edit/{id}')]
public function form(?int $id = NULL): void
{ {
$this->checkAuth(); $this->checkAuth();
@ -139,7 +139,7 @@ final class AnimeCollection extends BaseController {
$action $action
), ),
'media_items' => $this->animeCollectionModel->getMediaTypeList(), 'media_items' => $this->animeCollectionModel->getMediaTypeList(),
'item' => ($action === 'Edit' && $id !== NULL) ? $this->animeCollectionModel->get($id) : [] 'item' => ($action === 'Edit' && $id !== NULL) ? $this->animeCollectionModel->get($id) : [],
]); ]);
} }
@ -147,29 +147,29 @@ final class AnimeCollection extends BaseController {
* Update a collection item * Update a collection item
* *
* @throws ContainerException * @throws ContainerException
* @throws NotFoundException
* @throws InvalidArgumentException * @throws InvalidArgumentException
* @return void * @throws NotFoundException
*/ */
#[Route('anime.collection.edit.post', '/anime-collection/edit', Route::POST)]
public function edit(): void public function edit(): void
{ {
$this->checkAuth(); $this->checkAuth();
$this->update((array)$this->request->getParsedBody()); $this->update((array) $this->request->getParsedBody());
} }
/** /**
* Add a collection item * Add a collection item
* *
* @throws ContainerException * @throws ContainerException
* @throws NotFoundException
* @throws InvalidArgumentException * @throws InvalidArgumentException
* @return void * @throws NotFoundException
*/ */
#[Route('anime.collection.add.post', '/anime-collection/add', Route::POST)]
public function add(): void public function add(): void
{ {
$this->checkAuth(); $this->checkAuth();
$data = (array)$this->request->getParsedBody(); $data = (array) $this->request->getParsedBody();
if (array_key_exists('id', $data)) if (array_key_exists('id', $data))
{ {
// Check for existing entry // Check for existing entry
@ -191,6 +191,7 @@ final class AnimeCollection extends BaseController {
} }
$this->update($data); $this->update($data);
return; return;
} }
@ -201,6 +202,7 @@ final class AnimeCollection extends BaseController {
{ {
$this->setFlashMessage('Successfully added collection item', 'success'); $this->setFlashMessage('Successfully added collection item', 'success');
$this->sessionRedirect(); $this->sessionRedirect();
return; return;
} }
} }
@ -211,14 +213,13 @@ final class AnimeCollection extends BaseController {
/** /**
* Remove a collection item * Remove a collection item
*
* @return void
*/ */
#[Route('anime.collection.delete', '/anime-collection/delete', Route::POST)]
public function delete(): void public function delete(): void
{ {
$this->checkAuth(); $this->checkAuth();
$data = (array)$this->request->getParsedBody(); $data = (array) $this->request->getParsedBody();
if ( ! array_key_exists('hummingbird_id', $data)) if ( ! array_key_exists('hummingbird_id', $data))
{ {
$this->setFlashMessage("Can't delete item that doesn't exist", 'error'); $this->setFlashMessage("Can't delete item that doesn't exist", 'error');
@ -237,8 +238,6 @@ final class AnimeCollection extends BaseController {
/** /**
* Update a collection item * Update a collection item
*
* @param array $data
*/ */
protected function update(array $data): void protected function update(array $data): void
{ {
@ -259,4 +258,5 @@ final class AnimeCollection extends BaseController {
$this->sessionRedirect(); $this->sessionRedirect();
} }
} }
// End of AnimeCollection.php
// End of AnimeCollection.php

View File

@ -6,38 +6,34 @@
* *
* PHP version 8 * PHP version 8
* *
* @package HummingbirdAnimeClient * @copyright 2015 - 2022 Timothy J. Warren <tim@timshome.page>
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2021 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License * @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.2 * @version 5.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient * @link https://git.timshome.page/timw4mail/HummingBirdAnimeClient
*/ */
namespace Aviat\AnimeClient\Controller; namespace Aviat\AnimeClient\Controller;
use Aviat\AnimeClient\API\Kitsu\Model; use Aviat\AnimeClient\API\Kitsu\Model;
use Aviat\AnimeClient\Controller as BaseController;
use Aviat\AnimeClient\API\Kitsu\Transformer\CharacterTransformer; use Aviat\AnimeClient\API\Kitsu\Transformer\CharacterTransformer;
use Aviat\AnimeClient\Controller as BaseController;
use Aviat\Ion\Attribute\Controller;
use Aviat\Ion\Attribute\Route;
use Aviat\Ion\Di\ContainerInterface; use Aviat\Ion\Di\ContainerInterface;
use Aviat\Ion\Di\Exception\ContainerException; use Aviat\Ion\Di\Exception\{ContainerException, NotFoundException};
use Aviat\Ion\Di\Exception\NotFoundException;
/** /**
* Controller for character description pages * Controller for character description pages
*/ */
final class Character extends BaseController { #[Controller]
final class Character extends BaseController
/** {
* @var Model
*/
private Model $model; private Model $model;
/** /**
* Character constructor. * Character constructor.
* *
* @param ContainerInterface $container
* @throws ContainerException * @throws ContainerException
* @throws NotFoundException * @throws NotFoundException
*/ */
@ -49,10 +45,8 @@ final class Character extends BaseController {
/** /**
* Show information about a character * Show information about a character
*
* @param string $slug
* @return void
*/ */
#[Route('character', '/character/{slug}')]
public function index(string $slug): void public function index(string $slug): void
{ {
$rawData = $this->model->getCharacter($slug); $rawData = $this->model->getCharacter($slug);
@ -80,4 +74,4 @@ final class Character extends BaseController {
'data' => $data, 'data' => $data,
]); ]);
} }
} }

View File

@ -6,43 +6,39 @@
* *
* PHP version 8 * PHP version 8
* *
* @package HummingbirdAnimeClient * @copyright 2015 - 2022 Timothy J. Warren <tim@timshome.page>
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2021 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License * @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.2 * @version 5.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient * @link https://git.timshome.page/timw4mail/HummingBirdAnimeClient
*/ */
namespace Aviat\AnimeClient\Controller; namespace Aviat\AnimeClient\Controller;
use Aviat\AnimeClient\Controller as BaseController; use Aviat\Ion\Attribute\Controller;
use Aviat\AnimeClient\Model\Anime as AnimeModel; use Aviat\Ion\Attribute\Route;
use Aviat\AnimeClient\Model\Manga as MangaModel; use Aviat\AnimeClient\{Controller as BaseController, Model};
use Aviat\Ion\Di\ContainerInterface; use Aviat\Ion\Di\ContainerInterface;
use Aviat\Ion\Di\Exception\ContainerException; use Aviat\Ion\Di\Exception\{ContainerException, NotFoundException};
use Aviat\Ion\Di\Exception\NotFoundException;
/** /**
* Controller for Anime-related pages * Controller for Anime-related pages
*/ */
final class History extends BaseController { #[Controller]
final class History extends BaseController
{
/** /**
* The anime list model * The anime list model
* @var AnimeModel
*/ */
protected AnimeModel $animeModel; protected Model\Anime $animeModel;
/** /**
* The manga list model * The manga list model
* @var MangaModel
*/ */
protected MangaModel $mangaModel; protected Model\Manga $mangaModel;
/** /**
* Constructor * Constructor
* *
* @param ContainerInterface $container
* @throws ContainerException * @throws ContainerException
* @throws NotFoundException * @throws NotFoundException
*/ */
@ -54,11 +50,13 @@ final class History extends BaseController {
$this->mangaModel = $container->get('manga-model'); $this->mangaModel = $container->get('manga-model');
} }
#[Route('history', '/history/{type}')]
public function index(string $type = 'anime'): void public function index(string $type = 'anime'): void
{ {
if (method_exists($this, $type)) if (method_exists($this, $type))
{ {
$this->$type(); $this->{$type}();
return; return;
} }

View File

@ -6,40 +6,42 @@
* *
* PHP version 8 * PHP version 8
* *
* @package HummingbirdAnimeClient * @copyright 2015 - 2022 Timothy J. Warren <tim@timshome.page>
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2021 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License * @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.2 * @version 5.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient * @link https://git.timshome.page/timw4mail/HummingBirdAnimeClient
*/ */
namespace Aviat\AnimeClient\Controller; namespace Aviat\AnimeClient\Controller;
use function Amp\Promise\wait;
use function Aviat\AnimeClient\getResponse;
use function Aviat\AnimeClient\createPlaceholderImage;
use Aviat\AnimeClient\Controller as BaseController; use Aviat\AnimeClient\Controller as BaseController;
use Aviat\Ion\Attribute\Controller;
use Aviat\Ion\Attribute\Route;
use Throwable; use Throwable;
use function Amp\Promise\wait;
use function Aviat\AnimeClient\{createPlaceholderImage, getResponse};
use function imagepalletetotruecolor;
use function in_array;
/** /**
* Controller for handling routes that don't fit elsewhere * Controller for handling routes that don't fit elsewhere
*/ */
final class Images extends BaseController { #[Controller]
final class Images extends BaseController
{
/** /**
* Get image covers from kitsu * Get image covers from kitsu
* *
* @param string $type The category of image * @param string $type The category of image
* @param string $file The filename to look for * @param string $file The filename to look for
* @param bool $display Whether to output the image to the server * @param bool $display Whether to output the image to the server
* @return void
* @throws Throwable * @throws Throwable
*/ */
#[Route('image_proxy', '/public/images/{type}/{file}')]
public function cache(string $type, string $file, bool $display = TRUE): void public function cache(string $type, string $file, bool $display = TRUE): void
{ {
$currentUrl = (string)$this->request->getUri(); $currentUrl = (string) $this->request->getUri();
$kitsuUrl = 'https://media.kitsu.io/'; $kitsuUrl = 'https://media.kitsu.io/';
$fileName = str_replace('-original', '', $file); $fileName = str_replace('-original', '', $file);
@ -49,8 +51,8 @@ final class Images extends BaseController {
// Kitsu doesn't serve webp, but for most use cases, // Kitsu doesn't serve webp, but for most use cases,
// jpg is a safe assumption // jpg is a safe assumption
$tryJpg = ['anime','characters','manga','people']; $tryJpg = ['anime', 'characters', 'manga', 'people'];
if ($ext === 'webp' && \in_array($type, $tryJpg, TRUE)) if ($ext === 'webp' && in_array($type, $tryJpg, TRUE))
{ {
$ext = 'jpg'; $ext = 'jpg';
$currentUrl = str_replace('webp', 'jpg', $currentUrl); $currentUrl = str_replace('webp', 'jpg', $currentUrl);
@ -64,8 +66,8 @@ final class Images extends BaseController {
], ],
'avatars' => [ 'avatars' => [
'kitsuUrl' => "users/avatars/{$id}/original.{$ext}", 'kitsuUrl' => "users/avatars/{$id}/original.{$ext}",
'width' => null, 'width' => NULL,
'height' => null, 'height' => NULL,
], ],
'characters' => [ 'characters' => [
'kitsuUrl' => "characters/images/{$id}/original.{$ext}", 'kitsuUrl' => "characters/images/{$id}/original.{$ext}",
@ -79,8 +81,8 @@ final class Images extends BaseController {
], ],
'people' => [ 'people' => [
'kitsuUrl' => "people/images/{$id}/original.{$ext}", 'kitsuUrl' => "people/images/{$id}/original.{$ext}",
'width' => null, 'width' => NULL,
'height' => null, 'height' => NULL,
], ],
]; ];
@ -89,6 +91,7 @@ final class Images extends BaseController {
if (NULL === $imageType) if (NULL === $imageType)
{ {
$this->getPlaceholder($baseSavePath, 200, 200); $this->getPlaceholder($baseSavePath, 200, 200);
return; return;
} }
@ -112,6 +115,7 @@ final class Images extends BaseController {
{ {
$newUrl = str_replace($ext, $nextType[$ext], $currentUrl); $newUrl = str_replace($ext, $nextType[$ext], $currentUrl);
$this->redirect($newUrl, 303); $this->redirect($newUrl, 303);
return; return;
} }
@ -123,6 +127,7 @@ final class Images extends BaseController {
{ {
createPlaceholderImage("{$baseSavePath}/{$type}", $width, $height); createPlaceholderImage("{$baseSavePath}/{$type}", $width, $height);
} }
return; return;
} }
@ -144,7 +149,7 @@ final class Images extends BaseController {
if ($ext === 'gif') if ($ext === 'gif')
{ {
file_put_contents("{$filePrefix}.gif", $data); file_put_contents("{$filePrefix}.gif", $data);
\imagepalletetotruecolor($gdImg); imagepalletetotruecolor($gdImg);
} }
// save the webp versions // save the webp versions
@ -177,14 +182,10 @@ final class Images extends BaseController {
/** /**
* Get a placeholder for a missing image * Get a placeholder for a missing image
*
* @param string $path
* @param int|null $width
* @param int|null $height
*/ */
private function getPlaceholder (string $path, ?int $width = 200, ?int $height = NULL): void private function getPlaceholder(string $path, ?int $width = 200, ?int $height = NULL): void
{ {
$height = $height ?? $width; $height ??= $width;
$filename = $path . '/placeholder.png'; $filename = $path . '/placeholder.png';
@ -196,4 +197,4 @@ final class Images extends BaseController {
header('Content-Type: image/png'); header('Content-Type: image/png');
echo file_get_contents($filename); echo file_get_contents($filename);
} }
} }

View File

@ -6,22 +6,22 @@
* *
* PHP version 8 * PHP version 8
* *
* @package HummingbirdAnimeClient * @copyright 2015 - 2022 Timothy J. Warren <tim@timshome.page>
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2021 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License * @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.2 * @version 5.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient * @link https://git.timshome.page/timw4mail/HummingBirdAnimeClient
*/ */
namespace Aviat\AnimeClient\Controller; namespace Aviat\AnimeClient\Controller;
use Aura\Router\Exception\RouteNotFound; use Aura\Router\Exception\RouteNotFound;
use Aviat\AnimeClient\Controller;
use Aviat\AnimeClient\API\Kitsu\Transformer\MangaListTransformer; use Aviat\AnimeClient\API\Kitsu\Transformer\MangaListTransformer;
use Aviat\AnimeClient\API\Mapping\MangaReadingStatus; use Aviat\AnimeClient\API\Mapping\MangaReadingStatus;
use Aviat\AnimeClient\Controller as BaseController;
use Aviat\AnimeClient\Model\Manga as MangaModel; use Aviat\AnimeClient\Model\Manga as MangaModel;
use Aviat\AnimeClient\Types\FormItem; use Aviat\AnimeClient\Types\FormItem;
use Aviat\Ion\Attribute\Controller;
use Aviat\Ion\Attribute\Route;
use Aviat\Ion\Di\ContainerInterface; use Aviat\Ion\Di\ContainerInterface;
use Aviat\Ion\Di\Exception\{ContainerException, NotFoundException}; use Aviat\Ion\Di\Exception\{ContainerException, NotFoundException};
use Aviat\Ion\Json; use Aviat\Ion\Json;
@ -32,18 +32,17 @@ use Throwable;
/** /**
* Controller for manga list * Controller for manga list
*/ */
final class Manga extends Controller { #[Controller('manga')]
final class Manga extends BaseController
{
/** /**
* The manga model * The manga model
* @var MangaModel $model
*/ */
protected MangaModel $model; protected MangaModel $model;
/** /**
* Constructor * Constructor
* *
* @param ContainerInterface $container
* @throws ContainerException * @throws ContainerException
* @throws NotFoundException * @throws NotFoundException
*/ */
@ -61,12 +60,8 @@ final class Manga extends Controller {
/** /**
* Get a section of the manga list * Get a section of the manga list
*
* @param string $status
* @param string $view
* @return void
*@throws InvalidArgumentException
*/ */
#[Route('manga.list', '/list/{status}{/view}')]
public function index(string $status = 'all', ?string $view = ''): void public function index(string $status = 'all', ?string $view = ''): void
{ {
if ( ! in_array($status, [ if ( ! in_array($status, [
@ -90,11 +85,11 @@ final class Manga extends Controller {
$view_map = [ $view_map = [
'' => 'cover', '' => 'cover',
'list' => 'list' 'list' => 'list',
]; ];
$data = ($status !== 'all') $data = ($status !== 'all')
? [ $statusTitle => $this->model->getList($statusTitle) ] ? [$statusTitle => $this->model->getList($statusTitle)]
: $this->model->getList('All'); : $this->model->getList('All');
$this->outputHTML('manga/' . $view_map[$view], [ $this->outputHTML('manga/' . $view_map[$view], [
@ -104,14 +99,9 @@ final class Manga extends Controller {
} }
/** /**
* Form to add an manga * Form to add a manga
*
* @throws ContainerException
* @throws NotFoundException
* @throws RouteNotFound
* @throws InvalidArgumentException
* @return void
*/ */
#[Route('manga.add.get', '/manga/add')]
public function addForm(): void public function addForm(): void
{ {
$this->checkAuth(); $this->checkAuth();
@ -125,21 +115,19 @@ final class Manga extends Controller {
'Add' 'Add'
), ),
'action_url' => $this->url->generate('manga.add.post'), 'action_url' => $this->url->generate('manga.add.post'),
'status_list' => $statuses 'status_list' => $statuses,
]); ]);
} }
/** /**
* Add an manga to the list * Add a manga to the list
*
* @return void
* @throws Throwable
*/ */
#[Route('manage.add.post', '/manga/add', Route::POST)]
public function add(): void public function add(): void
{ {
$this->checkAuth(); $this->checkAuth();
$data = (array)$this->request->getParsedBody(); $data = (array) $this->request->getParsedBody();
if ( ! array_key_exists('id', $data)) if ( ! array_key_exists('id', $data))
{ {
$this->redirect('manga/add', 303); $this->redirect('manga/add', 303);
@ -150,7 +138,7 @@ final class Manga extends Controller {
unset($data['mal_id']); unset($data['mal_id']);
} }
$result = $this->model->createLibraryItem($data); $result = $this->model->createItem($data);
if ($result) if ($result)
{ {
@ -167,21 +155,14 @@ final class Manga extends Controller {
/** /**
* Show the manga edit form * Show the manga edit form
*
* @param string $id
* @param string $status
* @throws ContainerException
* @throws NotFoundException
* @throws RouteNotFound
* @throws InvalidArgumentException
* @return void
*/ */
#[Route('manga.edit', '/manga/edit/{id}/{status}')]
public function edit(string $id, string $status = 'All'): void public function edit(string $id, string $status = 'All'): void
{ {
$this->checkAuth(); $this->checkAuth();
$this->setSessionRedirect(); $this->setSessionRedirect();
$item = $this->model->getLibraryItem($id); $item = $this->model->getItem($id);
$title = $this->formatTitle( $title = $this->formatTitle(
$this->config->get('whose_list') . "'s Manga List", $this->config->get('whose_list') . "'s Manga List",
'Edit' 'Edit'
@ -192,16 +173,15 @@ final class Manga extends Controller {
'status_list' => MangaReadingStatus::KITSU_TO_TITLE, 'status_list' => MangaReadingStatus::KITSU_TO_TITLE,
'item' => $item, 'item' => $item,
'action' => $this->url->generate('update.post', [ 'action' => $this->url->generate('update.post', [
'controller' => 'manga' 'controller' => 'manga',
]), ]),
]); ]);
} }
/** /**
* Search for a manga to add to the list * Search for a manga to add to the list
*
* @return void
*/ */
#[Route('manga.search', '/manga/search')]
public function search(): void public function search(): void
{ {
$queryParams = $this->request->getQueryParams(); $queryParams = $this->request->getQueryParams();
@ -211,21 +191,19 @@ final class Manga extends Controller {
/** /**
* Update an manga item via a form submission * Update an manga item via a form submission
*
* @return void
* @throws Throwable
*/ */
#[Route('manga.update.post', '/manga/update', Route::POST)]
public function formUpdate(): void public function formUpdate(): void
{ {
$this->checkAuth(); $this->checkAuth();
$data = (array)$this->request->getParsedBody(); $data = (array) $this->request->getParsedBody();
// Do some minor data manipulation for // Do some minor data manipulation for
// large form-based updates // large form-based updates
$transformer = new MangaListTransformer(); $transformer = new MangaListTransformer();
$post_data = $transformer->untransform($data); $post_data = $transformer->untransform($data);
$full_result = $this->model->updateLibraryItem(FormItem::from($post_data)); $full_result = $this->model->updateItem(FormItem::from($post_data));
if ($full_result['statusCode'] === 200) if ($full_result['statusCode'] === 200)
{ {
@ -235,7 +213,6 @@ final class Manga extends Controller {
else else
{ {
$this->setFlashMessage('Failed to update manga.', 'error'); $this->setFlashMessage('Failed to update manga.', 'error');
} }
$this->sessionRedirect(); $this->sessionRedirect();
@ -243,22 +220,22 @@ final class Manga extends Controller {
/** /**
* Increment the progress of a manga item * Increment the progress of a manga item
* @throws Throwable
*/ */
#[Route('manga.increment', '/manga/increment', Route::POST)]
public function increment(): void public function increment(): void
{ {
$this->checkAuth(); $this->checkAuth();
if (str_contains($this->request->getHeader('content-type')[0], 'application/json')) if (str_contains($this->request->getHeader('content-type')[0], 'application/json'))
{ {
$data = Json::decode((string)$this->request->getBody()); $data = Json::decode((string) $this->request->getBody());
} }
else else
{ {
$data = $this->request->getParsedBody(); $data = $this->request->getParsedBody();
} }
$res = $this->model->incrementLibraryItem(FormItem::from($data)); $res = $this->model->incrementItem(FormItem::from($data));
$body = $res['body']; $body = $res['body'];
$statusCode = $res['statusCode']; $statusCode = $res['statusCode'];
@ -268,16 +245,14 @@ final class Manga extends Controller {
/** /**
* Remove an manga from the list * Remove an manga from the list
*
* @throws Throwable
* @return void
*/ */
#[Route('manga.delete', '/manga/delete', Route::POST)]
public function delete(): void public function delete(): void
{ {
$this->checkAuth(); $this->checkAuth();
$body = (array)$this->request->getParsedBody(); $body = (array) $this->request->getParsedBody();
$response = $this->model->deleteLibraryItem($body['id'], $body['mal_id']); $response = $this->model->deleteItem(FormItem::from($body));
if ($response) if ($response)
{ {
@ -294,12 +269,8 @@ final class Manga extends Controller {
/** /**
* View details of an manga * View details of an manga
*
* @param string $id
* @throws InvalidArgumentException
* @throws Throwable
* @return void
*/ */
#[Route('manga.details', '/manga/details/{id}')]
public function details(string $id): void public function details(string $id): void
{ {
$data = $this->model->getManga($id); $data = $this->model->getManga($id);
@ -311,6 +282,7 @@ final class Manga extends Controller {
'Manga not found', 'Manga not found',
'Manga Not Found' 'Manga Not Found'
); );
return; return;
} }
@ -326,11 +298,8 @@ final class Manga extends Controller {
/** /**
* View details of a random manga * View details of a random manga
*
* @throws InvalidArgumentException
* @throws Throwable
* @return void
*/ */
#[Route('manga.random', '/manga/details/random')]
public function random(): void public function random(): void
{ {
$data = $this->model->getRandomManga(); $data = $this->model->getRandomManga();
@ -342,6 +311,7 @@ final class Manga extends Controller {
'Manga not found', 'Manga not found',
'Manga Not Found' 'Manga Not Found'
); );
return; return;
} }
@ -355,4 +325,5 @@ final class Manga extends Controller {
]); ]);
} }
} }
// End of MangaController.php // End of MangaController.php

View File

@ -6,30 +6,52 @@
* *
* PHP version 8 * PHP version 8
* *
* @package HummingbirdAnimeClient * @copyright 2015 - 2022 Timothy J. Warren <tim@timshome.page>
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2021 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License * @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.2 * @version 5.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient * @link https://git.timshome.page/timw4mail/HummingBirdAnimeClient
*/ */
namespace Aviat\AnimeClient\Controller; namespace Aviat\AnimeClient\Controller;
use Aviat\AnimeClient\API\Kitsu\Model;
use Aviat\AnimeClient\API\Kitsu\Transformer\CharacterTransformer;
use Aviat\AnimeClient\API\Kitsu\Transformer\PersonTransformer;
use Aviat\AnimeClient\Controller as BaseController; use Aviat\AnimeClient\Controller as BaseController;
use Aviat\AnimeClient\Enum\EventType; use Aviat\AnimeClient\Enum\EventType;
use Aviat\Ion\Attribute\DefaultController;
use Aviat\Ion\Attribute\Route;
use Aviat\Ion\Di\ContainerInterface;
use Aviat\Ion\Event; use Aviat\Ion\Event;
use Aviat\Ion\View\HtmlView; use Aviat\Ion\View\HtmlView;
/** /**
* Controller for handling routes that don't fit elsewhere * Controller for handling routes that don't fit elsewhere
*/ */
final class Misc extends BaseController { #[DefaultController]
final class Misc extends BaseController
{
private Model $model;
public function __construct(ContainerInterface $container)
{
parent::__construct($container);
$this->model = $container->get('kitsu-model');
}
/**
* Redirect to the default controller/url from an empty path
*/
#[Route('index_redirect', '/')]
public function index(): void
{
parent::redirectToDefaultRoute();
}
/** /**
* Purges the API cache * Purges the API cache
*
* @return void
*/ */
#[Route('cache_purge', '/cache_purge')]
public function clearCache(): void public function clearCache(): void
{ {
$this->checkAuth(); $this->checkAuth();
@ -37,16 +59,14 @@ final class Misc extends BaseController {
Event::emit(EventType::CLEAR_CACHE); Event::emit(EventType::CLEAR_CACHE);
$this->outputHTML('blank', [ $this->outputHTML('blank', [
'title' => 'Cache cleared' 'title' => 'Cache cleared',
]); ]);
} }
/** /**
* Show the login form * Show the login form
*
* @param string $status
* @return void
*/ */
#[Route('login', '/login')]
public function login(string $status = ''): void public function login(string $status = ''): void
{ {
$message = ''; $message = '';
@ -63,22 +83,22 @@ final class Misc extends BaseController {
$this->outputHTML('login', [ $this->outputHTML('login', [
'title' => 'Api login', 'title' => 'Api login',
'message' => $message 'message' => $message,
], $view); ], $view);
} }
/** /**
* Attempt login authentication * Attempt login authentication
*
* @return void
*/ */
#[Route('login.post', '/login', Route::POST)]
public function loginAction(): void public function loginAction(): void
{ {
$post = (array)$this->request->getParsedBody(); $post = (array) $this->request->getParsedBody();
if ($this->auth->authenticate($post['password'])) if ($this->auth->authenticate($post['password']))
{ {
$this->sessionRedirect(); $this->sessionRedirect();
return; return;
} }
@ -92,9 +112,8 @@ final class Misc extends BaseController {
/** /**
* Deauthorize the current user * Deauthorize the current user
*
* @return void
*/ */
#[Route('logout', '/logout')]
public function logout(): void public function logout(): void
{ {
$this->auth->logout(); $this->auth->logout();
@ -105,8 +124,72 @@ final class Misc extends BaseController {
/** /**
* Check if the current user is logged in * Check if the current user is logged in
*/ */
#[Route('heartbeat', '/heartbeat')]
public function heartbeat(): void public function heartbeat(): void
{ {
$this->outputJSON(['hasAuth' => $this->auth->isAuthenticated()], 200); $this->outputJSON(['hasAuth' => $this->auth->isAuthenticated()], 200);
} }
}
/**
* Show information about a character
*/
#[Route('character', '/character/{slug}')]
public function character(string $slug): void
{
$rawData = $this->model->getCharacter($slug);
if (( ! array_key_exists('data', $rawData)) || empty($rawData['data']))
{
$this->notFound(
$this->formatTitle(
'Characters',
'Character not found'
),
'Character Not Found'
);
return;
}
$data = (new CharacterTransformer())->transform($rawData)->toArray();
$this->outputHTML('character/details', [
'title' => $this->formatTitle(
'Characters',
$data['name']
),
'data' => $data,
]);
}
/**
* Show information about a person
*/
#[Route('person', '/people/{slug}')]
public function person(string $slug): void
{
$rawData = $this->model->getPerson($slug);
$data = (new PersonTransformer())->transform($rawData)->toArray();
if (( ! array_key_exists('data', $rawData)) || empty($rawData['data']))
{
$this->notFound(
$this->formatTitle(
'People',
'Person not found'
),
'Person Not Found'
);
return;
}
$this->outputHTML('person/details', [
'title' => $this->formatTitle(
'People',
$data['name']
),
'data' => $data,
]);
}
}

View File

@ -6,38 +6,34 @@
* *
* PHP version 8 * PHP version 8
* *
* @package HummingbirdAnimeClient * @copyright 2015 - 2022 Timothy J. Warren <tim@timshome.page>
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2021 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License * @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.2 * @version 5.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient * @link https://git.timshome.page/timw4mail/HummingBirdAnimeClient
*/ */
namespace Aviat\AnimeClient\Controller; namespace Aviat\AnimeClient\Controller;
use Aviat\AnimeClient\API\Kitsu\Model; use Aviat\AnimeClient\API\Kitsu\Model;
use Aviat\AnimeClient\Controller as BaseController;
use Aviat\AnimeClient\API\Kitsu\Transformer\PersonTransformer; use Aviat\AnimeClient\API\Kitsu\Transformer\PersonTransformer;
use Aviat\AnimeClient\Controller as BaseController;
use Aviat\Ion\Attribute\Controller;
use Aviat\Ion\Attribute\Route;
use Aviat\Ion\Di\ContainerInterface; use Aviat\Ion\Di\ContainerInterface;
use Aviat\Ion\Di\Exception\ContainerException; use Aviat\Ion\Di\Exception\{ContainerException, NotFoundException};
use Aviat\Ion\Di\Exception\NotFoundException;
/** /**
* Controller for People pages * Controller for People pages
*/ */
final class People extends BaseController { #[Controller]
final class People extends BaseController
/** {
* @var Model
*/
private Model $model; private Model $model;
/** /**
* People constructor. * People constructor.
* *
* @param ContainerInterface $container
* @throws ContainerException * @throws ContainerException
* @throws NotFoundException * @throws NotFoundException
*/ */
@ -49,10 +45,8 @@ final class People extends BaseController {
/** /**
* Show information about a person * Show information about a person
*
* @param string $slug
* @return void
*/ */
#[Route('person', '/people/{slug}')]
public function index(string $slug): void public function index(string $slug): void
{ {
$rawData = $this->model->getPerson($slug); $rawData = $this->model->getPerson($slug);
@ -79,4 +73,4 @@ final class People extends BaseController {
'data' => $data, 'data' => $data,
]); ]);
} }
} }

View File

@ -6,12 +6,10 @@
* *
* PHP version 8 * PHP version 8
* *
* @package HummingbirdAnimeClient * @copyright 2015 - 2022 Timothy J. Warren <tim@timshome.page>
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2021 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License * @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.2 * @version 5.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient * @link https://git.timshome.page/timw4mail/HummingBirdAnimeClient
*/ */
namespace Aviat\AnimeClient\Controller; namespace Aviat\AnimeClient\Controller;
@ -20,29 +18,23 @@ use Aura\Router\Exception\RouteNotFound;
use Aviat\AnimeClient\API\Anilist\Model as AnilistModel; use Aviat\AnimeClient\API\Anilist\Model as AnilistModel;
use Aviat\AnimeClient\Controller as BaseController; use Aviat\AnimeClient\Controller as BaseController;
use Aviat\AnimeClient\Model\Settings as SettingsModel; use Aviat\AnimeClient\Model\Settings as SettingsModel;
use Aviat\Ion\Attribute\Controller;
use Aviat\Ion\Attribute\Route;
use Aviat\Ion\Di\ContainerInterface; use Aviat\Ion\Di\ContainerInterface;
use Aviat\Ion\Di\Exception\ContainerException; use Aviat\Ion\Di\Exception\{ContainerException, NotFoundException};
use Aviat\Ion\Di\Exception\NotFoundException;
/** /**
* Controller for user settings * Controller for user settings
*/ */
final class Settings extends BaseController { #[Controller]
final class Settings extends BaseController
/** {
* @var AnilistModel
*/
private AnilistModel $anilistModel; private AnilistModel $anilistModel;
/**
* @var SettingsModel
*/
private SettingsModel $settingsModel; private SettingsModel $settingsModel;
/** /**
* Settings constructor. * Settings constructor.
* *
* @param ContainerInterface $container
* @throws ContainerException * @throws ContainerException
* @throws NotFoundException * @throws NotFoundException
*/ */
@ -60,6 +52,7 @@ final class Settings extends BaseController {
/** /**
* Show the user settings, if logged in * Show the user settings, if logged in
*/ */
#[Route('settings', '/settings')]
public function index(): void public function index(): void
{ {
$auth = $this->container->get('auth'); $auth = $this->container->get('auth');
@ -82,9 +75,10 @@ final class Settings extends BaseController {
* *
* @throws RouteNotFound * @throws RouteNotFound
*/ */
#[Route('settings-post', '/settings/update', Route::POST)]
public function update(): void public function update(): void
{ {
$post = (array)$this->request->getParsedBody(); $post = (array) $this->request->getParsedBody();
unset($post['settings-tabs']); unset($post['settings-tabs']);
$saved = $this->settingsModel->saveSettingsFile($post); $saved = $this->settingsModel->saveSettingsFile($post);
@ -102,6 +96,7 @@ final class Settings extends BaseController {
/** /**
* Redirect to Anilist to start Oauth flow * Redirect to Anilist to start Oauth flow
*/ */
#[Route('anilist-redirect', '/anilist-redirect')]
public function anilistRedirect(): void public function anilistRedirect(): void
{ {
$query = http_build_query([ $query = http_build_query([
@ -118,6 +113,7 @@ final class Settings extends BaseController {
/** /**
* Oauth callback for Anilist API * Oauth callback for Anilist API
*/ */
#[Route('anilist-callback', '/anilist-oauth')]
public function anilistCallback(): void public function anilistCallback(): void
{ {
$query = $this->request->getQueryParams(); $query = $this->request->getQueryParams();
@ -130,6 +126,7 @@ final class Settings extends BaseController {
if (array_key_exists('error', $authData)) if (array_key_exists('error', $authData))
{ {
$this->errorPage(400, 'Error Linking Account', $authData['hint']); $this->errorPage(400, 'Error Linking Account', $authData['hint']);
return; return;
} }
@ -147,6 +144,7 @@ final class Settings extends BaseController {
{ {
$newSettings[$key] = $value; $newSettings[$key] = $value;
} }
unset($newSettings['config']); unset($newSettings['config']);
$saved = $this->settingsModel->saveSettingsFile($newSettings); $saved = $this->settingsModel->saveSettingsFile($newSettings);
@ -160,4 +158,4 @@ final class Settings extends BaseController {
$this->redirect($redirectUrl, 303); $this->redirect($redirectUrl, 303);
} }
} }

View File

@ -6,12 +6,10 @@
* *
* PHP version 8 * PHP version 8
* *
* @package HummingbirdAnimeClient * @copyright 2015 - 2022 Timothy J. Warren <tim@timshome.page>
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2021 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License * @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.2 * @version 5.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient * @link https://git.timshome.page/timw4mail/HummingBirdAnimeClient
*/ */
namespace Aviat\AnimeClient\Controller; namespace Aviat\AnimeClient\Controller;
@ -20,24 +18,22 @@ use Aviat\AnimeClient\API\Kitsu\Model;
use Aviat\AnimeClient\API\Kitsu\Transformer\UserTransformer; use Aviat\AnimeClient\API\Kitsu\Transformer\UserTransformer;
use Aviat\AnimeClient\Controller as BaseController; use Aviat\AnimeClient\Controller as BaseController;
use Aviat\Ion\Attribute\Controller;
use Aviat\Ion\Attribute\Route;
use Aviat\Ion\Di\ContainerInterface; use Aviat\Ion\Di\ContainerInterface;
use Aviat\Ion\Di\Exception\ContainerException; use Aviat\Ion\Di\Exception\{ContainerException, NotFoundException};
use Aviat\Ion\Di\Exception\NotFoundException;
/** /**
* Controller for handling routes that don't fit elsewhere * Controller for handling routes that don't fit elsewhere
*/ */
final class User extends BaseController { #[Controller]
final class User extends BaseController
/** {
* @var Model
*/
private Model $kitsuModel; private Model $kitsuModel;
/** /**
* User constructor. * User constructor.
* *
* @param ContainerInterface $container
* @throws ContainerException * @throws ContainerException
* @throws NotFoundException * @throws NotFoundException
*/ */
@ -51,6 +47,7 @@ final class User extends BaseController {
/** /**
* Show the user profile page for the configured user * Show the user profile page for the configured user
*/ */
#[Route('default_user_info', '/me')]
public function me(): void public function me(): void
{ {
$this->about('me'); $this->about('me');
@ -58,10 +55,8 @@ final class User extends BaseController {
/** /**
* Show the user profile page * Show the user profile page
*
* @param string $username
* @return void
*/ */
#[Route('user_info', '/user/{username}')]
public function about(string $username): void public function about(string $username): void
{ {
$isMainUser = $username === 'me'; $isMainUser = $username === 'me';
@ -82,4 +77,4 @@ final class User extends BaseController {
'data' => $data, 'data' => $data,
]); ]);
} }
} }

View File

@ -6,19 +6,14 @@
* *
* PHP version 8 * PHP version 8
* *
* @package HummingbirdAnimeClient * @copyright 2015 - 2022 Timothy J. Warren <tim@timshome.page>
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2021 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License * @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.2 * @version 5.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient * @link https://git.timshome.page/timw4mail/HummingBirdAnimeClient
*/ */
namespace Aviat\AnimeClient; namespace Aviat\AnimeClient;
use Aviat\AnimeClient\Enum\EventType;
use Aviat\Ion\Event;
use Aviat\Ion\Json;
use Aura\Router\{ use Aura\Router\{
Map, Map,
Matcher, Matcher,
@ -26,9 +21,10 @@ use Aura\Router\{
Rule, Rule,
}; };
use Aviat\AnimeClient\API\FailedResponseException; use Aviat\AnimeClient\API\FailedResponseException;
use Aviat\AnimeClient\Enum\EventType;
use Aviat\Ion\Di\ContainerInterface; use Aviat\Ion\Di\ContainerInterface;
use Aviat\Ion\Friend;
use Aviat\Ion\Type\StringType; use Aviat\Ion\Type\StringType;
use Aviat\Ion\{Event, Friend, Json};
use LogicException; use LogicException;
use ReflectionException; use ReflectionException;
@ -37,42 +33,37 @@ use function Aviat\Ion\_dir;
/** /**
* Basic routing/ dispatch * Basic routing/ dispatch
*/ */
final class Dispatcher extends RoutingBase { final class Dispatcher extends RoutingBase
{
/** /**
* The route-matching object * The route-matching object
* @var Map $router
*/ */
protected Map $router; protected Map $router;
/** /**
* The route matcher * The route matcher
* @var Matcher $matcher
*/ */
protected Matcher $matcher; protected Matcher $matcher;
/** /**
* Routing array * Routing array
* @var array
*/ */
protected array $routes; protected array $routes = [];
/** /**
* Routes added to router * Routes added to router
* @var array $outputRoutes
*/ */
protected array $outputRoutes; protected array $outputRoutes = [];
/** /**
* Constructor * Constructor
*
* @param ContainerInterface $container
*/ */
public function __construct(ContainerInterface $container) public function __construct(ContainerInterface $container)
{ {
parent::__construct($container); parent::__construct($container);
$router = $this->container->get('aura-router'); $router = $this->container->get('aura-router');
$this->router = $router->getMap(); $this->router = $router->getMap();
$this->matcher = $router->getMatcher(); $this->matcher = $router->getMatcher();
$this->routes = $this->config->get('routes'); $this->routes = $this->config->get('routes');
$this->outputRoutes = $this->setupRoutes(); $this->outputRoutes = $this->setupRoutes();
@ -80,10 +71,8 @@ final class Dispatcher extends RoutingBase {
/** /**
* Get the current route object, if one matches * Get the current route object, if one matches
*
* @return Route|false
*/ */
public function getRoute(): Route | false public function getRoute(): Route|false
{ {
$logger = $this->container->getLogger(); $logger = $this->container->getLogger();
@ -94,7 +83,7 @@ final class Dispatcher extends RoutingBase {
{ {
$logger->info('Dispatcher - Routing data from get_route method'); $logger->info('Dispatcher - Routing data from get_route method');
$logger->info(print_r([ $logger->info(print_r([
'route_path' => $routePath 'route_path' => $routePath,
], TRUE)); ], TRUE));
} }
@ -104,7 +93,7 @@ final class Dispatcher extends RoutingBase {
/** /**
* Get list of routes applied * Get list of routes applied
* *
* @return array * @return mixed[]
*/ */
public function getOutputRoutes(): array public function getOutputRoutes(): array
{ {
@ -114,11 +103,9 @@ final class Dispatcher extends RoutingBase {
/** /**
* Handle the current route * Handle the current route
* *
* @param object|null $route
* @return void
* @throws ReflectionException * @throws ReflectionException
*/ */
public function __invoke(object $route = NULL): void public function __invoke(?object $route = NULL): void
{ {
$logger = $this->container->getLogger(); $logger = $this->container->getLogger();
@ -142,6 +129,7 @@ final class Dispatcher extends RoutingBase {
$actionMethod = $errorRoute['action_method']; $actionMethod = $errorRoute['action_method'];
$params = $errorRoute['params']; $params = $errorRoute['params'];
$this->call($controllerName, $actionMethod, $params); $this->call($controllerName, $actionMethod, $params);
return; return;
} }
@ -157,9 +145,8 @@ final class Dispatcher extends RoutingBase {
* Parse out the arguments for the appropriate controller for * Parse out the arguments for the appropriate controller for
* the current route * the current route
* *
* @param Friend $route
* @throws LogicException * @throws LogicException
* @return array * @return array<string, mixed>
*/ */
protected function processRoute(Friend $route): array protected function processRoute(Friend $route): array
{ {
@ -185,6 +172,7 @@ final class Dispatcher extends RoutingBase {
if ( ! empty($route->__get('tokens'))) if ( ! empty($route->__get('tokens')))
{ {
$tokens = array_keys($route->__get('tokens')); $tokens = array_keys($route->__get('tokens'));
foreach ($tokens as $param) foreach ($tokens as $param)
{ {
if (array_key_exists($param, $route->attributes)) if (array_key_exists($param, $route->attributes))
@ -193,6 +181,7 @@ final class Dispatcher extends RoutingBase {
} }
} }
} }
$logger = $this->container->getLogger(); $logger = $this->container->getLogger();
if ($logger !== NULL) if ($logger !== NULL)
{ {
@ -202,14 +191,12 @@ final class Dispatcher extends RoutingBase {
return [ return [
'controller_name' => $controllerName, 'controller_name' => $controllerName,
'action_method' => $actionMethod, 'action_method' => $actionMethod,
'params' => $params 'params' => $params,
]; ];
} }
/** /**
* Get the type of route, to select the current controller * Get the type of route, to select the current controller
*
* @return string
*/ */
public function getController(): string public function getController(): string
{ {
@ -237,7 +224,7 @@ final class Dispatcher extends RoutingBase {
/** /**
* Get the list of controllers in the default namespace * Get the list of controllers in the default namespace
* *
* @return array * @return mixed[]
*/ */
public function getControllerList(): array public function getControllerList(): array
{ {
@ -247,6 +234,7 @@ final class Dispatcher extends RoutingBase {
$path = str_replace($find, $replace, $defaultNamespace); $path = str_replace($find, $replace, $defaultNamespace);
$path = trim($path, '/'); $path = trim($path, '/');
$actualPath = realpath(_dir(SRC_DIR, $path)); $actualPath = realpath(_dir(SRC_DIR, $path));
$classFiles = glob("{$actualPath}/*.php"); $classFiles = glob("{$actualPath}/*.php");
if ($classFiles === FALSE) if ($classFiles === FALSE)
@ -259,7 +247,7 @@ final class Dispatcher extends RoutingBase {
foreach ($classFiles as $file) foreach ($classFiles as $file)
{ {
$rawClassName = basename(str_replace('.php', '', $file)); $rawClassName = basename(str_replace('.php', '', $file));
$path = (string)StringType::from($rawClassName)->dasherize(); $path = (string) StringType::from($rawClassName)->dasherize();
$className = trim($defaultNamespace . '\\' . $rawClassName, '\\'); $className = trim($defaultNamespace . '\\' . $rawClassName, '\\');
$controllers[$path] = $className; $controllers[$path] = $className;
@ -272,10 +260,7 @@ final class Dispatcher extends RoutingBase {
* Create the controller object and call the appropriate * Create the controller object and call the appropriate
* method * method
* *
* @param string $controllerName - The full namespace of the controller class * @param string $controllerName - The full namespace of the controller class
* @param string $method
* @param array $params
* @return void
*/ */
protected function call(string $controllerName, string $method, array $params): void protected function call(string $controllerName, string $method, array $params): void
{ {
@ -289,16 +274,19 @@ final class Dispatcher extends RoutingBase {
$logger?->debug('Dispatcher - controller arguments', $params); $logger?->debug('Dispatcher - controller arguments', $params);
$params = array_values($params); $params = array_values($params);
$controller->$method(...$params); $controller->{$method}(...$params);
} }
catch (FailedResponseException) catch (FailedResponseException)
{ {
$controllerName = DEFAULT_CONTROLLER; $controllerName = DEFAULT_CONTROLLER;
$controller = new $controllerName($this->container); $controller = new $controllerName($this->container);
$controller->errorPage(500, $controller->errorPage(
500,
'API request timed out', 'API request timed out',
'Failed to retrieve data from API (╯°□°)╯︵ ┻━┻'); 'Failed to retrieve data from API (╯°□°)╯︵ ┻━┻'
);
} }
/* finally /* finally
{ {
// Log out on session/api token expiration // Log out on session/api token expiration
@ -312,6 +300,7 @@ final class Dispatcher extends RoutingBase {
/** /**
* Get the appropriate params for the error page * Get the appropriate params for the error page
* passed on the failed route * passed on the failed route
* @return mixed[][]
*/ */
protected function getErrorParams(): array protected function getErrorParams(): array
{ {
@ -328,12 +317,12 @@ final class Dispatcher extends RoutingBase {
$params = []; $params = [];
switch($failure->failedRule) { switch ($failure->failedRule) {
case Rule\Allows::class: case Rule\Allows::class:
$params = [ $params = [
'http_code' => 405, 'http_code' => 405,
'title' => '405 Method Not Allowed', 'title' => '405 Method Not Allowed',
'message' => 'Invalid HTTP Verb' 'message' => 'Invalid HTTP Verb',
]; ];
break; break;
@ -341,7 +330,7 @@ final class Dispatcher extends RoutingBase {
$params = [ $params = [
'http_code' => 406, 'http_code' => 406,
'title' => '406 Not Acceptable', 'title' => '406 Not Acceptable',
'message' => 'Unacceptable content type' 'message' => 'Unacceptable content type',
]; ];
break; break;
@ -353,14 +342,14 @@ final class Dispatcher extends RoutingBase {
return [ return [
'params' => $params, 'params' => $params,
'action_method' => $actionMethod 'action_method' => $actionMethod,
]; ];
} }
/** /**
* Select controller based on the current url, and apply its relevant routes * Select controller based on the current url, and apply its relevant routes
* *
* @return array * @return mixed[]
*/ */
protected function setupRoutes(): array protected function setupRoutes(): array
{ {
@ -368,6 +357,7 @@ final class Dispatcher extends RoutingBase {
// Add routes // Add routes
$routes = []; $routes = [];
foreach ($this->routes as $name => &$route) foreach ($this->routes as $name => &$route)
{ {
$path = $route['path']; $path = $route['path'];
@ -400,14 +390,15 @@ final class Dispatcher extends RoutingBase {
// Add the route to the router object // Add the route to the router object
if ( ! array_key_exists('tokens', $route)) if ( ! array_key_exists('tokens', $route))
{ {
$routes[] = $this->router->$verb($name, $path)->defaults($route); $routes[] = $this->router->{$verb}($name, $path)->defaults($route);
continue; continue;
} }
$tokens = $route['tokens']; $tokens = $route['tokens'];
unset($route['tokens']); unset($route['tokens']);
$routes[] = $this->router->$verb($name, $path) $routes[] = $this->router->{$verb}($name, $path)
->defaults($route) ->defaults($route)
->tokens($tokens); ->tokens($tokens);
} }
@ -415,4 +406,5 @@ final class Dispatcher extends RoutingBase {
return $routes; return $routes;
} }
} }
// End of Dispatcher.php
// End of Dispatcher.php

View File

@ -6,19 +6,18 @@
* *
* PHP version 8 * PHP version 8
* *
* @package HummingbirdAnimeClient * @copyright 2015 - 2022 Timothy J. Warren <tim@timshome.page>
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2021 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License * @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 5.2 * @version 5.2
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient * @link https://git.timshome.page/timw4mail/HummingBirdAnimeClient
*/ */
namespace Aviat\AnimeClient\Enum; namespace Aviat\AnimeClient\Enum;
use Aviat\Ion\Enum; use Aviat\Ion\Enum;
final class API extends Enum { final class API extends Enum
{
public const ANILIST = 'anilist'; public const ANILIST = 'anilist';
public const KITSU = 'kitsu'; public const KITSU = 'kitsu';
} }

Some files were not shown because too many files have changed in this diff Show More