Version 5.1 - All the GraphQL #32

Closed
timw4mail wants to merge 1160 commits from develop into master
143 changed files with 1714 additions and 1211 deletions
Showing only changes of commit 4948cdbd46 - Show all commits

2
.gitignore vendored
View File

@ -153,3 +153,5 @@ public/mal_mappings.json
tmp tmp
tools/vendor/ tools/vendor/
tools/phinx/vendor/ tools/phinx/vendor/
/.php-cs-fixer.php
/.php-cs-fixer.cache

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

@ -0,0 +1,551 @@
<?php declare(strict_types=1);
use Nexus\CsConfig\Factory;
use PhpCsFixer\{Config, Finder};
$finder = Finder::create()
->in([
__DIR__,
__DIR__ . '/tools',
// __DIR__ . '/app'
])
->exclude([
'apidocs',
'build',
'coverage',
'frontEndSrc',
'phinx',
'public',
'tools',
'tmp',
'vendor'
]);
$srcFinder = Finder::create()->in(__DIR__ . '/src');
return (new Config())
->setRiskyAllowed(TRUE)
->setFinder($srcFinder)
->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,
'ereg_to_preg' => 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_php4_constructor' => 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,
// 'octal_notation' => false, // requires 8.1+
'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' => ['normal', '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_align' => [
// 'align' => 'vertical',
// 'tags' => [
// 'method',
// 'param',
// 'property',
// 'return',
// 'throws',
// 'type',
// 'var',
// ],
// ],
'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_alias_tag' => [
// 'replacements' => [
// 'property-read' => 'property',
// 'property-write' => 'property',
// 'type' => 'var',
// 'link' => 'see',
// ],
// ],
'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,
],
]);

31
justfile Normal file
View File

@ -0,0 +1,31 @@
# 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
# Run unit tests and generate test-coverage report
coverage:
composer run-script coverage

View File

@ -16,21 +16,25 @@
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;
/** /**
@ -101,6 +105,7 @@ abstract class APIRequestBuilder {
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;
} }
@ -110,6 +115,7 @@ abstract class APIRequestBuilder {
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;
} }
@ -132,15 +138,14 @@ abstract class APIRequestBuilder {
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|null $value
*/ */
public function setHeader(string $name, string $value = NULL): self public function setHeader(string $name, ?string $value = NULL): self
{ {
if (NULL === $value) if (NULL === $value)
{ {
@ -158,8 +163,6 @@ abstract class APIRequestBuilder {
* Set multiple request headers * Set multiple request headers
* *
* name => value * name => value
*
* @param array $headers
*/ */
public function setHeaders(array $headers): self public function setHeaders(array $headers): self
{ {
@ -176,7 +179,7 @@ abstract class APIRequestBuilder {
*/ */
public function setJsonBody(mixed $body): self public function setJsonBody(mixed $body): self
{ {
$requestBody = ( is_string($body)) $requestBody = (is_string($body))
? $body ? $body
: Json::encode($body); : Json::encode($body);
@ -189,13 +192,14 @@ abstract class APIRequestBuilder {
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
* *
* @throws \Throwable * @throws Throwable
*/ */
public function getFullRequest(): Request public function getFullRequest(): Request
{ {
@ -210,7 +214,7 @@ abstract class APIRequestBuilder {
$this->request->getBody() $this->request->getBody()
->createBodyStream() ->createBodyStream()
->read() ->read()
) ),
]); ]);
} }
@ -220,14 +224,15 @@ abstract class APIRequestBuilder {
/** /**
* Get the data from the response of the passed request * Get the data from the response of the passed 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());
} }

View File

@ -22,8 +22,8 @@ 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
* *
@ -62,5 +62,5 @@ abstract class AbstractListItem {
* *
* @param string $id - The id of the list item to delete * @param string $id - The id of the list item to delete
*/ */
abstract public function delete(string $id):?Request; abstract public function delete(string $id): ?Request;
} }

View File

@ -26,7 +26,8 @@ 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;
/** /**
@ -35,6 +36,7 @@ final class ListItem extends AbstractListItem {
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 ?? []);
} }
@ -44,6 +46,7 @@ final class ListItem extends AbstractListItem {
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 ?? []);
} }
@ -82,19 +85,19 @@ final class ListItem extends AbstractListItem {
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,
]); ]);

View File

@ -18,4 +18,6 @@ namespace Aviat\AnimeClient\API\Anilist;
use InvalidArgumentException; use InvalidArgumentException;
class MissingIdException extends InvalidArgumentException {} class MissingIdException extends InvalidArgumentException
{
}

View File

@ -16,19 +16,17 @@
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
@ -179,7 +177,7 @@ final class Model
$data = $this->listItem->get($id)['data']; $data = $this->listItem->get($id)['data'];
return ($data !== null) return ($data !== NULL)
? $data['MediaList'] ? $data['MediaList']
: []; : [];
} }
@ -270,7 +268,7 @@ final class Model
return NULL; return NULL;
} }
return (string)$info['data']['MediaList']['id']; return (string) $info['data']['MediaList']['id'];
} }
/** /**
@ -293,6 +291,6 @@ final class Model
return NULL; return NULL;
} }
return (string)$info['data']['Media']['id']; return (string) $info['data']['Media']['id'];
} }
} }

View File

@ -16,24 +16,22 @@
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;
/** /**
@ -115,27 +113,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,
]); ]);
} }
/** /**
* @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))
@ -146,11 +145,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;
@ -166,7 +167,7 @@ final class RequestBuilder extends APIRequestBuilder {
* @throws Throwable * @throws Throwable
* @return mixed[] * @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);
@ -242,13 +243,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);
@ -257,7 +258,8 @@ final class RequestBuilder extends APIRequestBuilder {
{ {
dump($e); dump($e);
dump($rawBody); dump($rawBody);
die();
exit();
} }
} }
} }

View File

@ -18,7 +18,8 @@ namespace Aviat\AnimeClient\API\Anilist;
use Aviat\Ion\Di\ContainerAware; use Aviat\Ion\Di\ContainerAware;
trait RequestBuilderTrait { trait RequestBuilderTrait
{
use ContainerAware; use ContainerAware;
/** /**
@ -32,6 +33,7 @@ trait RequestBuilderTrait {
public function setRequestBuilder(RequestBuilder $requestBuilder): self public function setRequestBuilder(RequestBuilder $requestBuilder): self
{ {
$this->requestBuilder = $requestBuilder; $this->requestBuilder = $requestBuilder;
return $this; return $this;
} }
} }

View File

@ -16,9 +16,7 @@
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 +24,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,12 +33,10 @@ class AnimeListTransformer extends AbstractTransformer {
/** /**
* Transform Anilist list item to Kitsu form update format * Transform Anilist list item to Kitsu form update format
*
* @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'],
@ -53,11 +49,11 @@ 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

@ -16,19 +16,15 @@
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([]);
@ -39,7 +35,7 @@ class MangaListTransformer extends AbstractTransformer {
*/ */
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'],
@ -52,12 +48,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

@ -18,19 +18,13 @@ 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;
} }

View File

@ -21,8 +21,8 @@ use Psr\SimpleCache\CacheInterface;
/** /**
* Helper methods for dealing with the Cache * Helper methods for dealing with the Cache
*/ */
trait CacheTrait { trait CacheTrait
{
protected CacheInterface $cache; protected CacheInterface $cache;
/** /**
@ -31,6 +31,7 @@ trait CacheTrait {
public function setCache(CacheInterface $cache): self public function setCache(CacheInterface $cache): self
{ {
$this->cache = $cache; $this->cache = $cache;
return $this; return $this;
} }

View File

@ -21,7 +21,8 @@ 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';

View File

@ -21,7 +21,8 @@ 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';

View File

@ -21,7 +21,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

@ -21,7 +21,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

@ -21,7 +21,8 @@ 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';

View File

@ -21,7 +21,8 @@ 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';

View File

@ -21,7 +21,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

@ -21,7 +21,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

@ -21,6 +21,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

@ -18,17 +18,18 @@ 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;
@ -44,8 +45,6 @@ final class Auth {
/** /**
* Constructor * Constructor
*
* @param ContainerInterface $container
*/ */
public function __construct(ContainerInterface $container) public function __construct(ContainerInterface $container)
{ {
@ -94,7 +93,7 @@ final class Auth {
*/ */
public function isAuthenticated(): bool public function isAuthenticated(): bool
{ {
return ($this->getAuthToken() !== NULL); return $this->getAuthToken() !== NULL;
} }
/** /**
@ -136,7 +135,7 @@ final class Auth {
/** /**
* Save the new authentication information * Save the new authentication information
*/ */
private function storeAuth(array|false $auth): bool private function storeAuth(array|FALSE $auth): bool
{ {
if (FALSE !== $auth) if (FALSE !== $auth)
{ {
@ -157,6 +156,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;
} }
} }

View File

@ -21,7 +21,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

@ -21,7 +21,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

@ -26,7 +26,8 @@ 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;
@ -50,23 +51,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))
@ -93,7 +94,7 @@ final class ListItem extends AbstractListItem {
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,
]); ]);
} }
@ -115,7 +116,7 @@ final class ListItem extends AbstractListItem {
{ {
return $this->requestBuilder->mutateRequest('IncrementLibraryItem', [ return $this->requestBuilder->mutateRequest('IncrementLibraryItem', [
'id' => $id, 'id' => $id,
'progress' => $data->progress 'progress' => $data->progress,
]); ]);
} }
@ -128,21 +129,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);
@ -153,7 +154,8 @@ 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}";
} }

View File

@ -17,13 +17,6 @@
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 +26,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 +48,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,14 +57,7 @@ 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;
/** /**
@ -87,13 +80,13 @@ 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()));
@ -104,7 +97,8 @@ final class Model {
'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))
@ -124,12 +118,12 @@ 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()));
@ -140,7 +134,8 @@ final class Model {
'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))
@ -153,19 +148,17 @@ final class Model {
/** /**
* Get the userid for a username from Kitsu * Get the userid for a username from Kitsu
*
* @param string|null $username
*/ */
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;
@ -180,7 +173,7 @@ final class Model {
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,
]); ]);
} }
@ -192,7 +185,7 @@ final class Model {
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,
])); ]));
} }
@ -217,7 +210,7 @@ final class Model {
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))
@ -231,7 +224,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);
@ -251,6 +244,7 @@ final class Model {
$baseData = $this->requestBuilder->runQuery('AnimeDetailsById', [ $baseData = $this->requestBuilder->runQuery('AnimeDetailsById', [
'id' => $animeId, 'id' => $animeId,
]); ]);
return $this->animeTransformer->transform($baseData); return $this->animeTransformer->transform($baseData);
} }
@ -271,7 +265,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;
@ -303,7 +296,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;
} }
@ -320,7 +313,7 @@ final class Model {
* *
* @param string $status - Optional status to filter by * @param string $status - Optional status to filter by
*/ */
public function getAnimeListCount(string $status = '') : int public function getAnimeListCount(string $status = ''): int
{ {
return $this->getListCount(MediaType::ANIME, $status); return $this->getListCount(MediaType::ANIME, $status);
} }
@ -354,7 +347,7 @@ final class Model {
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))
@ -368,7 +361,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);
@ -382,6 +375,7 @@ final class Model {
$baseData = $this->requestBuilder->runQuery('MangaDetailsById', [ $baseData = $this->requestBuilder->runQuery('MangaDetailsById', [
'id' => $mangaId, 'id' => $mangaId,
]); ]);
return $this->mangaTransformer->transform($baseData); return $this->mangaTransformer->transform($baseData);
} }
@ -432,7 +426,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;
} }
@ -449,7 +443,7 @@ final class Model {
* *
* @param string $status - Optional status to filter by * @param string $status - Optional status to filter by
*/ */
public function getMangaListCount(string $status = '') : int public function getMangaListCount(string $status = ''): int
{ {
return $this->getListCount(MediaType::MANGA, $status); return $this->getListCount(MediaType::MANGA, $status);
} }
@ -463,6 +457,7 @@ final class Model {
{ {
$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];
@ -506,9 +501,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;
@ -517,7 +512,6 @@ final class Model {
} }
$data[] = $searchItem; $data[] = $searchItem;
} }
return $data; return $data;
@ -528,7 +522,7 @@ final class Model {
* *
* @param string $type "anime" or "manga" * @param string $type "anime" or "manga"
*/ */
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,
@ -584,10 +578,8 @@ 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 mixed[]
*/ */
public function getSyncList(string $type): array public function getSyncList(string $type): array
@ -649,7 +641,7 @@ final class Model {
$cursor = ''; $cursor = '';
$username = $this->getUsername(); $username = $this->getUsername();
return new Amp\Producer(function (callable $emit) use ($type, $status, $cursor, $username): \Generator { return new Amp\Producer(function (callable $emit) use ($type, $status, $cursor, $username): Generator {
while (TRUE) while (TRUE)
{ {
$vars = [ $vars = [
@ -682,7 +674,8 @@ final class Model {
// @TODO Proper Error logging // @TODO Proper Error logging
dump($rawData); dump($rawData);
die();
exit();
} }
$cursor = $page['endCursor']; $cursor = $page['endCursor'];
@ -697,11 +690,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): \Generator { return new Amp\Producer(function (callable $emit) use ($type, $status, $cursor, $username): Generator {
while (TRUE) while (TRUE)
{ {
$vars = [ $vars = [
@ -724,7 +718,8 @@ final class Model {
if (empty($data)) if (empty($data))
{ {
dump($rawData); dump($rawData);
die();
exit();
} }
$cursor = $page['endCursor']; $cursor = $page['endCursor'];
@ -744,7 +739,7 @@ final class Model {
$cursor = ''; $cursor = '';
$username = $this->getUsername(); $username = $this->getUsername();
return new Amp\Producer(function (callable $emit) use ($type, $status, $cursor, $username): \Generator { return new Amp\Producer(function (callable $emit) use ($type, $status, $cursor, $username): Generator {
while (TRUE) while (TRUE)
{ {
$vars = [ $vars = [
@ -767,7 +762,8 @@ final class Model {
if (empty($data)) if (empty($data))
{ {
dump($rawData); dump($rawData);
die();
exit();
} }
$cursor = $page['endCursor']; $cursor = $page['endCursor'];
@ -818,7 +814,7 @@ final class Model {
{ {
$args = [ $args = [
'type' => strtoupper($type), 'type' => strtoupper($type),
'slug' => $this->getUsername() 'slug' => $this->getUsername(),
]; ];
if ($status !== '') if ($status !== '')
{ {

View File

@ -22,7 +22,8 @@ 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
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------

View File

@ -16,26 +16,21 @@
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;
/** /**
@ -76,13 +71,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)))
@ -130,12 +125,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());
} }
} }
@ -153,12 +148,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());
} }
} }
@ -198,13 +193,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;
} }
@ -218,7 +214,7 @@ 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
*/ */
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))
@ -228,11 +224,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;
@ -266,7 +264,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);
} }
} }
@ -278,7 +276,8 @@ final class RequestBuilder extends APIRequestBuilder {
{ {
// dump($e); // dump($e);
dump($rawBody); dump($rawBody);
die();
exit();
} }
} }
} }

View File

@ -16,7 +16,8 @@
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
*/ */
@ -25,11 +26,12 @@ trait RequestBuilderTrait {
/** /**
* Set the request builder object * Set the request builder object
* *
* @return RequestBuilderTrait|ListItem|Model * @return ListItem|Model|RequestBuilderTrait
*/ */
public function setRequestBuilder(RequestBuilder $requestBuilder): self public function setRequestBuilder(RequestBuilder $requestBuilder): self
{ {
$this->requestBuilder = $requestBuilder; $this->requestBuilder = $requestBuilder;
return $this; return $this;
} }
} }

View File

@ -18,16 +18,12 @@ 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

@ -18,8 +18,8 @@ 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,25 +27,24 @@ 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
@ -87,7 +86,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 +94,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,
@ -129,8 +128,8 @@ final class AnimeListTransformer extends AbstractTransformer {
'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)

View File

@ -23,8 +23,8 @@ 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
@ -33,12 +33,12 @@ final class AnimeTransformer extends AbstractTransformer {
*/ */
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);
@ -72,7 +72,7 @@ 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']);
} }
} }
@ -89,7 +89,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;
} }
@ -106,7 +106,7 @@ final class AnimeTransformer extends AbstractTransformer {
'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);

View File

@ -25,11 +25,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
{
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 = [
@ -62,19 +62,19 @@ final class CharacterTransformer extends AbstractTransformer {
/** /**
* @return array<int, mixed[]> * @return array<int, mixed[]>
*/ */
protected function organizeMediaAndVoices (array $data): array 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;
@ -102,7 +102,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'])) && (array)$item['voices']['nodes'] !== []); $rawVoices = array_filter($data, static fn ($item) => ( ! empty($item['voices'])) && (array) $item['voices']['nodes'] !== []);
if (empty($rawVoices)) if (empty($rawVoices))
{ {
@ -135,7 +135,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' => [],
]; ];
} }

View File

@ -16,13 +16,14 @@
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
*/ */
@ -70,7 +71,7 @@ abstract class HistoryTransformer {
} }
// Hide private library entries // Hide private library entries
if ($entry['libraryEntry']['private'] === true) if ($entry['libraryEntry']['private'] === TRUE)
{ {
continue; continue;
} }
@ -85,7 +86,7 @@ abstract class HistoryTransformer {
$output[] = $transformed; $output[] = $transformed;
} }
} }
else if ($kind === 'updated') elseif ($kind === 'updated')
{ {
$output[] = $this->transformUpdated($entry); $output[] = $this->transformUpdated($entry);
} }
@ -97,11 +98,12 @@ abstract class HistoryTransformer {
/** /**
* Combine consecutive 'progressed' events * Combine consecutive 'progressed' events
*/ */
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 = [];
@ -109,9 +111,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'];
@ -120,6 +123,7 @@ abstract class HistoryTransformer {
{ {
$nextId++; $nextId++;
$next = $singles[$nextId]; $next = $singles[$nextId];
continue; continue;
} }
@ -160,7 +164,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'],
@ -169,6 +173,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;
} }
@ -178,14 +183,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;
} }
@ -251,12 +256,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,
@ -271,12 +276,12 @@ 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

@ -17,7 +17,7 @@
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 +28,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 = [];
@ -93,7 +93,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 +101,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,
@ -158,11 +158,11 @@ final class LibraryEntryTransformer 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,
@ -171,12 +171,12 @@ 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

@ -18,16 +18,12 @@ 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

@ -27,16 +27,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 +82,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 +95,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,
]); ]);
@ -110,11 +110,10 @@ 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 +121,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)

View File

@ -23,8 +23,8 @@ 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
@ -33,12 +33,12 @@ final class MangaTransformer extends AbstractTransformer {
*/ */
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'];
@ -71,7 +71,7 @@ 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']);
} }
} }
@ -88,7 +88,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;
} }
@ -105,7 +105,7 @@ 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);

View File

@ -23,11 +23,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
{
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']);
@ -61,6 +61,7 @@ final class PersonTransformer extends AbstractTransformer {
if ((is_countable($data['mediaStaff']['nodes']) ? count($data['mediaStaff']['nodes']) : 0) > 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] = [];
@ -88,7 +89,7 @@ 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;
@ -128,7 +129,7 @@ final class PersonTransformer extends AbstractTransformer {
'canonicalName' => $character['names']['canonical'], 'canonicalName' => $character['names']['canonical'],
], ],
'media' => [ 'media' => [
$media['id'] => $media $media['id'] => $media,
], ],
]; ];
} }
@ -143,7 +144,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 +152,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]
); );
} }
} }

View File

@ -17,7 +17,6 @@
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 +27,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'] ?? [];

View File

@ -23,54 +23,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

@ -16,30 +16,29 @@
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,7 +46,6 @@ 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,
@ -55,7 +53,6 @@ final class MangaReadingStatus extends Enum {
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 +61,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,

View File

@ -17,17 +17,18 @@
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
*/ */
@ -36,26 +37,29 @@ final class ParallelAPIRequest {
/** /**
* Add a request * Add a request
*/ */
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
*/ */
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;
} }
@ -73,7 +77,7 @@ final class ParallelAPIRequest {
foreach ($this->requests as $key => $url) foreach ($this->requests as $key => $url)
{ {
$promises[$key] = call(static function () use ($client, $url): \Generator { $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();
}); });
@ -96,7 +100,7 @@ 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

@ -16,23 +16,23 @@
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 +40,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 +47,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 +54,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,

View File

@ -16,21 +16,17 @@
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
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
@ -62,7 +58,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;
} }
@ -96,10 +92,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;
} }
@ -188,6 +184,7 @@ function checkFolderPermissions(ConfigInterface $config): array
if ( ! is_dir($actual)) if ( ! is_dir($actual))
{ {
$errors['missing'][] = $pretty; $errors['missing'][] = $pretty;
continue; continue;
} }
@ -207,7 +204,7 @@ function checkFolderPermissions(ConfigInterface $config): array
/** /**
* Get an API Client, with better defaults * Get an API Client, with better defaults
*/ */
function getApiClient (): HttpClient function getApiClient(): HttpClient
{ {
static $client; static $client;
@ -224,7 +221,7 @@ function getApiClient (): HttpClient
* *
* @throws Throwable * @throws Throwable
*/ */
function getResponse (Request|string $request): Response function getResponse(Request|string $request): Response
{ {
$client = getApiClient(); $client = getApiClient();
@ -239,7 +236,7 @@ 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
*/ */
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)))
{ {
@ -272,7 +269,7 @@ function getLocalImg (string $kitsuUrl, bool $webp = TRUE): string
* *
* @codeCoverageIgnore * @codeCoverageIgnore
*/ */
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)
@ -295,6 +292,7 @@ function createPlaceholderImage (string $path, int $width = 200, int $height = 2
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 $items !== []; return $items !== [];
} }
@ -311,11 +309,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)) ? TRUE : $cache->setMultiple($userData); $saved = (empty($userData)) ? TRUE : $cache->setMultiple($userData);
return $cleared && $saved; return $cleared && $saved;
} }
@ -331,5 +329,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

@ -16,38 +16,37 @@
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
*/ */
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))
{ {
@ -56,12 +55,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
@ -114,8 +113,6 @@ abstract class BaseCommand extends Command {
/** /**
* Setup the Di container * Setup the Di container
*
* @return Containerinterface
*/ */
public function setupContainer(): ContainerInterface public function setupContainer(): ContainerInterface
{ {
@ -136,16 +133,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
@ -171,7 +168,7 @@ abstract class BaseCommand extends Command {
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);
@ -179,33 +176,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): \Aviat\Banker\Teller { $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'));
@ -219,6 +217,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 {
@ -235,17 +234,18 @@ 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

@ -16,14 +16,14 @@
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
* *

View File

@ -16,14 +16,14 @@
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
* *
@ -39,6 +39,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

@ -19,8 +19,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,7 +50,7 @@ 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

@ -16,30 +16,27 @@
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;
@ -103,6 +100,7 @@ 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(); exit();
} }
@ -259,7 +257,7 @@ 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;
@ -285,7 +283,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();
} }
} }
@ -322,7 +321,7 @@ 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']))
@ -363,7 +362,7 @@ 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'],
] ],
]; ];
} }
@ -377,7 +376,7 @@ final class SyncLists extends BaseCommand {
{ {
$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 = [];
@ -390,6 +389,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();
@ -413,10 +413,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 +424,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 +462,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,7 +480,7 @@ final class SyncLists extends BaseCommand {
'addToAnilist' => $itemsToAddToAnilist, 'addToAnilist' => $itemsToAddToAnilist,
'updateAnilist' => $anilistUpdateItems, 'updateAnilist' => $anilistUpdateItems,
'addToKitsu' => $itemsToAddToKitsu, 'addToKitsu' => $itemsToAddToKitsu,
'updateKitsu' => $kitsuUpdateItems 'updateKitsu' => $kitsuUpdateItems,
]; ];
} }
@ -499,10 +499,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];
} }
@ -518,10 +518,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;
@ -545,7 +545,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;
@ -560,7 +560,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;
@ -575,18 +575,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'];
} }
@ -599,15 +599,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;
@ -637,7 +636,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;
@ -679,7 +678,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'],
@ -687,7 +686,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'],
@ -712,7 +711,8 @@ final class SyncLists extends BaseCommand {
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)
{ {
@ -720,12 +720,13 @@ 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;
} }
@ -735,7 +736,7 @@ final class SyncLists extends BaseCommand {
$responses = $requester->makeRequests(); $responses = $requester->makeRequests();
foreach($responses as $key => $response) foreach ($responses as $key => $response)
{ {
$responseData = Json::decode($response); $responseData = Json::decode($response);
@ -745,6 +746,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;
} }
@ -756,6 +758,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;
} }
} }
@ -767,7 +770,6 @@ 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}");
} }
} }
@ -780,7 +782,7 @@ final class SyncLists extends BaseCommand {
{ {
$requester = new ParallelAPIRequest(); $requester = new ParallelAPIRequest();
foreach($itemsToUpdate as $item) foreach ($itemsToUpdate as $item)
{ {
if ($action === SyncAction::UPDATE) if ($action === SyncAction::UPDATE)
{ {
@ -790,7 +792,9 @@ final class SyncLists extends BaseCommand {
$requester->addRequest($maybeRequest); $requester->addRequest($maybeRequest);
} }
} }
else if ($action === SyncAction::CREATE) else
{
if ($action === SyncAction::CREATE)
{ {
try try
{ {
@ -804,10 +808,11 @@ final class SyncLists extends BaseCommand {
} }
} }
} }
}
$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

@ -23,7 +23,8 @@ 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
*/ */
@ -48,7 +49,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)
{ {

View File

@ -18,7 +18,8 @@ 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

View File

@ -16,7 +16,8 @@
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

View File

@ -17,13 +17,13 @@
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;
/** /**

View File

@ -18,7 +18,8 @@ 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

View File

@ -16,7 +16,8 @@
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

View File

@ -16,7 +16,8 @@
namespace Aviat\AnimeClient\Component; namespace Aviat\AnimeClient\Component;
final class Tabs { final class Tabs
{
use ComponentTrait; use ComponentTrait;
/** /**
@ -32,9 +33,8 @@ final class Tabs {
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', [

View File

@ -16,7 +16,8 @@
namespace Aviat\AnimeClient\Component; namespace Aviat\AnimeClient\Component;
final class VerticalTabs { final class VerticalTabs
{
use ComponentTrait; use ComponentTrait;
/** /**
@ -31,9 +32,8 @@ final class VerticalTabs {
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,

View File

@ -16,32 +16,32 @@
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 +87,6 @@ class Controller {
/** /**
* Controller constructor. * Controller constructor.
* *
* @param ContainerInterface $container
* @throws ContainerException * @throws ContainerException
* @throws NotFoundException * @throws NotFoundException
*/ */
@ -127,11 +126,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();
@ -198,7 +196,6 @@ class Controller {
* Get the string output of a partial template * Get the string output of a partial template
* *
* @codeCoverageIgnore * @codeCoverageIgnore
* @param HtmlView $view
*/ */
protected function loadPartial(HtmlView $view, string $template, array $data = []): string protected function loadPartial(HtmlView $view, string $template, array $data = []): string
{ {
@ -212,7 +209,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))
@ -227,8 +223,6 @@ class Controller {
* Render a template with header and footer * Render a template with header and footer
* *
* @codeCoverageIgnore * @codeCoverageIgnore
* @param HtmlView $view
* @return HtmlView
*/ */
protected function renderFullPage(HtmlView $view, string $template, array $data): HtmlView protected function renderFullPage(HtmlView $view, string $template, array $data): HtmlView
{ {
@ -241,7 +235,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']));
} }
@ -261,12 +255,12 @@ class Controller {
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();
} }
@ -281,7 +275,7 @@ class Controller {
$this->outputHTML('error', [ $this->outputHTML('error', [
'title' => $title, 'title' => $title,
'message' => $message, 'message' => $message,
'long_message' => $longMessage 'long_message' => $longMessage,
], NULL, $httpCode); ], NULL, $httpCode);
} }
@ -314,7 +308,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);
@ -325,7 +319,7 @@ class Controller {
* *
* @param string ...$parts Title segments * @param string ...$parts Title segments
*/ */
public function formatTitle(string ...$parts) : string public function formatTitle(string ...$parts): string
{ {
return implode(' &middot; ', $parts); return implode(' &middot; ', $parts);
} }
@ -334,14 +328,13 @@ class Controller {
* Add a message box to the page * Add a message box to the page
* *
* @codeCoverageIgnore * @codeCoverageIgnore
* @param HtmlView $view
* @throws InvalidArgumentException * @throws InvalidArgumentException
*/ */
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,
]); ]);
} }
@ -349,10 +342,9 @@ class Controller {
* Output a template to HTML, using the provided data * Output a template to HTML, using the provided data
* *
* @codeCoverageIgnore * @codeCoverageIgnore
* @param HtmlView|NULL $view
*@throws InvalidArgumentException *@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)
{ {
@ -389,7 +381,9 @@ class Controller {
{ {
(new HttpView())->redirect($url, $code)->send(); (new HttpView())->redirect($url, $code)->send();
} }
catch (\Throwable) {} catch (\Throwable)
{
}
} }
} }

View File

@ -17,15 +17,14 @@
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\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,8 +34,8 @@ use TypeError;
/** /**
* Controller for Anime-related pages * Controller for Anime-related pages
*/ */
final class Anime extends BaseController { final class Anime extends BaseController
{
/** /**
* The anime list model * The anime list model
*/ */
@ -92,7 +91,7 @@ final class Anime extends BaseController {
$viewMap = [ $viewMap = [
'' => 'cover', '' => 'cover',
'list' => 'list' 'list' => 'list',
]; ];
$data = ($status !== 'all') $data = ($status !== 'all')
@ -101,7 +100,7 @@ final class Anime extends BaseController {
$this->outputHTML('anime/' . $viewMap[$view], [ $this->outputHTML('anime/' . $viewMap[$view], [
'title' => $title, 'title' => $title,
'sections' => $data 'sections' => $data,
]); ]);
} }
@ -109,9 +108,9 @@ 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
*/ */
public function addForm(): void public function addForm(): void
@ -125,7 +124,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,
]); ]);
} }
@ -138,7 +137,7 @@ final class Anime extends BaseController {
{ {
$this->checkAuth(); $this->checkAuth();
$data = (array)$this->request->getParsedBody(); $data = (array) $this->request->getParsedBody();
if (empty($data['mal_id'])) if (empty($data['mal_id']))
{ {
@ -183,7 +182,7 @@ 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',
]), ]),
]); ]);
} }
@ -207,7 +206,7 @@ final class Anime extends BaseController {
{ {
$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
@ -239,16 +238,17 @@ final class Anime extends BaseController {
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 = (array)$this->request->getParsedBody(); $data = (array) $this->request->getParsedBody();
} }
if (empty($data)) if (empty($data))
{ {
$this->errorPage(400, 'Bad Request', ''); $this->errorPage(400, 'Bad Request', '');
exit(); exit();
} }
@ -267,7 +267,7 @@ final class Anime extends BaseController {
{ {
$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->deleteLibraryItem($body['id'], $body['mal_id']);
if ($response === TRUE) if ($response === TRUE)

View File

@ -23,8 +23,7 @@ use Aviat\AnimeClient\Model\{
AnimeCollection as AnimeCollectionModel AnimeCollection as AnimeCollectionModel
}; };
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\Exception\DoubleRenderException; use Aviat\Ion\Exception\DoubleRenderException;
use InvalidArgumentException; use InvalidArgumentException;
@ -32,17 +31,15 @@ use InvalidArgumentException;
/** /**
* Controller for Anime collection pages * Controller for Anime collection pages
*/ */
final class AnimeCollection extends BaseController { 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;
@ -87,14 +84,14 @@ final class AnimeCollection extends BaseController {
* Show the anime collection page * Show the anime collection page
* *
* @throws ContainerException * @throws ContainerException
* @throws NotFoundException
* @throws InvalidArgumentException * @throws InvalidArgumentException
* @throws NotFoundException
*/ */
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(
@ -111,11 +108,11 @@ 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
*/ */
public function form($id = NULL): void public function form($id = NULL): void
{ {
@ -134,7 +131,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) : [],
]); ]);
} }
@ -142,27 +139,27 @@ final class AnimeCollection extends BaseController {
* Update a collection item * Update a collection item
* *
* @throws ContainerException * @throws ContainerException
* @throws NotFoundException
* @throws InvalidArgumentException * @throws InvalidArgumentException
* @throws NotFoundException
*/ */
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
* @throws NotFoundException
*/ */
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
@ -184,6 +181,7 @@ final class AnimeCollection extends BaseController {
} }
$this->update($data); $this->update($data);
return; return;
} }
@ -194,6 +192,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;
} }
} }
@ -209,7 +208,7 @@ final class AnimeCollection extends BaseController {
{ {
$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');

View File

@ -17,18 +17,17 @@
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\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 { final class Character extends BaseController
{
private Model $model; private Model $model;
/** /**

View File

@ -16,26 +16,24 @@
namespace Aviat\AnimeClient\Controller; namespace Aviat\AnimeClient\Controller;
use Aviat\AnimeClient\Controller as BaseController; use Aviat\AnimeClient\{Controller as BaseController, Model};
use Aviat\AnimeClient\Model\Anime as AnimeModel;
use Aviat\AnimeClient\Model\Manga as MangaModel;
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 { final class History extends BaseController
{
/** /**
* The anime list model * The anime list model
*/ */
protected AnimeModel $animeModel; protected Model\Anime $animeModel;
/** /**
* The manga list model * The manga list model
*/ */
protected MangaModel $mangaModel; protected Model\Manga $mangaModel;
/** /**
* Constructor * Constructor
@ -55,7 +53,8 @@ final class History extends BaseController {
{ {
if (method_exists($this, $type)) if (method_exists($this, $type))
{ {
$this->$type(); $this->{$type}();
return; return;
} }

View File

@ -16,18 +16,19 @@
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 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 { final class Images extends BaseController
{
/** /**
* Get image covers from kitsu * Get image covers from kitsu
* *
@ -38,7 +39,7 @@ final class Images extends BaseController {
*/ */
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);
@ -48,8 +49,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);
@ -63,8 +64,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}",
@ -78,8 +79,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,
], ],
]; ];
@ -88,6 +89,7 @@ final class Images extends BaseController {
if (NULL === $imageType) if (NULL === $imageType)
{ {
$this->getPlaceholder($baseSavePath, 200, 200); $this->getPlaceholder($baseSavePath, 200, 200);
return; return;
} }
@ -111,6 +113,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;
} }
@ -144,7 +147,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
@ -178,7 +181,7 @@ final class Images extends BaseController {
/** /**
* Get a placeholder for a missing image * Get a placeholder for a missing image
*/ */
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 ??= $width; $height ??= $width;

View File

@ -17,9 +17,9 @@
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;
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\Di\ContainerInterface; use Aviat\Ion\Di\ContainerInterface;
@ -32,8 +32,8 @@ use Throwable;
/** /**
* Controller for manga list * Controller for manga list
*/ */
final class Manga extends Controller { final class Manga extends Controller
{
/** /**
* The manga model * The manga model
*/ */
@ -86,11 +86,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], [
@ -103,9 +103,9 @@ final class Manga extends Controller {
* Form to add an manga * Form to add an manga
* *
* @throws ContainerException * @throws ContainerException
* @throws InvalidArgumentException
* @throws NotFoundException * @throws NotFoundException
* @throws RouteNotFound * @throws RouteNotFound
* @throws InvalidArgumentException
*/ */
public function addForm(): void public function addForm(): void
{ {
@ -120,7 +120,7 @@ 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,
]); ]);
} }
@ -133,7 +133,7 @@ final class Manga extends Controller {
{ {
$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);
@ -163,9 +163,9 @@ final class Manga extends Controller {
* Show the manga edit form * Show the manga edit form
* *
* @throws ContainerException * @throws ContainerException
* @throws InvalidArgumentException
* @throws NotFoundException * @throws NotFoundException
* @throws RouteNotFound * @throws RouteNotFound
* @throws InvalidArgumentException
*/ */
public function edit(string $id, string $status = 'All'): void public function edit(string $id, string $status = 'All'): void
{ {
@ -183,7 +183,7 @@ 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',
]), ]),
]); ]);
} }
@ -207,7 +207,7 @@ final class Manga extends Controller {
{ {
$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
@ -223,7 +223,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();
@ -239,7 +238,7 @@ final class Manga extends Controller {
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
{ {
@ -263,7 +262,7 @@ final class Manga extends Controller {
{ {
$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->deleteLibraryItem($body['id'], $body['mal_id']);
if ($response) if ($response)
@ -296,6 +295,7 @@ final class Manga extends Controller {
'Manga not found', 'Manga not found',
'Manga Not Found' 'Manga Not Found'
); );
return; return;
} }
@ -326,6 +326,7 @@ final class Manga extends Controller {
'Manga not found', 'Manga not found',
'Manga Not Found' 'Manga Not Found'
); );
return; return;
} }

View File

@ -24,7 +24,8 @@ 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 { final class Misc extends BaseController
{
/** /**
* Purges the API cache * Purges the API cache
*/ */
@ -35,7 +36,7 @@ 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',
]); ]);
} }
@ -58,7 +59,7 @@ final class Misc extends BaseController {
$this->outputHTML('login', [ $this->outputHTML('login', [
'title' => 'Api login', 'title' => 'Api login',
'message' => $message 'message' => $message,
], $view); ], $view);
} }
@ -67,11 +68,12 @@ final class Misc extends BaseController {
*/ */
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;
} }

View File

@ -17,18 +17,17 @@
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\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 { final class People extends BaseController
{
private Model $model; private Model $model;
/** /**

View File

@ -21,16 +21,14 @@ 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\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 { final class Settings extends BaseController
{
private AnilistModel $anilistModel; private AnilistModel $anilistModel;
private SettingsModel $settingsModel; private SettingsModel $settingsModel;
/** /**
@ -77,7 +75,7 @@ final class Settings extends BaseController {
*/ */
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);
@ -123,6 +121,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;
} }

View File

@ -21,14 +21,13 @@ use Aviat\AnimeClient\API\Kitsu\Transformer\UserTransformer;
use Aviat\AnimeClient\Controller as BaseController; use Aviat\AnimeClient\Controller as BaseController;
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 { final class User extends BaseController
{
private Model $kitsuModel; private Model $kitsuModel;
/** /**

View File

@ -16,9 +16,6 @@
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 +23,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,17 +35,15 @@ 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;
@ -77,10 +73,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();
@ -91,7 +85,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));
} }
@ -111,10 +105,9 @@ final class Dispatcher extends RoutingBase {
/** /**
* Handle the current route * Handle the current route
* *
* @param object|null $route
* @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();
@ -138,6 +131,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;
} }
@ -180,6 +174,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))
@ -198,7 +193,7 @@ final class Dispatcher extends RoutingBase {
return [ return [
'controller_name' => $controllerName, 'controller_name' => $controllerName,
'action_method' => $actionMethod, 'action_method' => $actionMethod,
'params' => $params 'params' => $params,
]; ];
} }
@ -254,7 +249,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;
@ -281,15 +276,17 @@ 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
@ -322,12 +319,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;
@ -335,7 +332,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;
@ -347,7 +344,7 @@ final class Dispatcher extends RoutingBase {
return [ return [
'params' => $params, 'params' => $params,
'action_method' => $actionMethod 'action_method' => $actionMethod,
]; ];
} }
@ -362,6 +359,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'];
@ -394,14 +392,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);
} }

View File

@ -18,7 +18,8 @@ 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';
} }

View File

@ -18,7 +18,8 @@ namespace Aviat\AnimeClient\Enum;
use Aviat\Ion\Enum as BaseEnum; use Aviat\Ion\Enum as BaseEnum;
final class EventType extends BaseEnum { final class EventType extends BaseEnum
{
public const CLEAR_CACHE = '::clear-cache::'; public const CLEAR_CACHE = '::clear-cache::';
public const RESET_CACHE_KEY = '::reset-cache-key::'; public const RESET_CACHE_KEY = '::reset-cache-key::';
public const UNAUTHORIZED = '::unauthorized::'; public const UNAUTHORIZED = '::unauthorized::';

View File

@ -21,7 +21,8 @@ use Aviat\Ion\Enum as BaseEnum;
/** /**
* Types of media * Types of media
*/ */
final class MediaType extends BaseEnum { final class MediaType extends BaseEnum
{
public const ANIME = 'anime'; public const ANIME = 'anime';
public const DRAMA = 'drama'; public const DRAMA = 'drama';
public const MANGA = 'manga'; public const MANGA = 'manga';

View File

@ -21,7 +21,8 @@ use Aviat\Ion\Enum as BaseEnum;
/** /**
* Types of actions when syncing lists from different APIs * Types of actions when syncing lists from different APIs
*/ */
final class SyncAction extends BaseEnum { final class SyncAction extends BaseEnum
{
public const CREATE = 'create'; public const CREATE = 'create';
public const UPDATE = 'update'; public const UPDATE = 'update';
public const DELETE = 'delete'; public const DELETE = 'delete';

View File

@ -18,13 +18,13 @@ namespace Aviat\AnimeClient;
use Aura\Html\HelperLocator; use Aura\Html\HelperLocator;
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;
/** /**
* Helper object to manage form generation, especially for config editing * Helper object to manage form generation, especially for config editing
*/ */
final class FormGenerator { final class FormGenerator
{
/** /**
* Html generation helper * Html generation helper
*/ */
@ -60,7 +60,7 @@ final class FormGenerator {
if ($display === FALSE) if ($display === FALSE)
{ {
return (string)$this->helper->input([ return (string) $this->helper->input([
'type' => 'hidden', 'type' => 'hidden',
'name' => $name, 'name' => $name,
'value' => $value, 'value' => $value,
@ -75,7 +75,7 @@ final class FormGenerator {
], ],
]; ];
switch($type) switch ($type)
{ {
case 'boolean': case 'boolean':
$params['type'] = 'radio'; $params['type'] = 'radio';
@ -83,7 +83,7 @@ final class FormGenerator {
'1' => 'Yes', '1' => 'Yes',
'0' => 'No', '0' => 'No',
]; ];
$params['strict'] = true; $params['strict'] = TRUE;
unset($params['attribs']['id']); unset($params['attribs']['id']);
break; break;
@ -108,6 +108,6 @@ final class FormGenerator {
} }
} }
return (string)$this->helper->input($params); return (string) $this->helper->input($params);
} }
} }

View File

@ -22,8 +22,8 @@ use Aviat\Ion\Di\ContainerAware;
/** /**
* FormGenerator helper wrapper * FormGenerator helper wrapper
*/ */
final class Form { final class Form
{
use ContainerAware; use ContainerAware;
/** /**

View File

@ -22,8 +22,8 @@ use Aviat\Ion\Di\ContainerAware;
/** /**
* MenuGenerator helper wrapper * MenuGenerator helper wrapper
*/ */
final class Menu { final class Menu
{
use ContainerAware; use ContainerAware;
/** /**
@ -35,7 +35,6 @@ final class Menu {
{ {
return MenuGenerator::new($this->container)->generate($menuName); return MenuGenerator::new($this->container)->generate($menuName);
} }
} }
// End of Menu.php // End of Menu.php

View File

@ -17,12 +17,13 @@
namespace Aviat\AnimeClient\Helper; namespace Aviat\AnimeClient\Helper;
use Aviat\Ion\Di\ContainerAware; use Aviat\Ion\Di\ContainerAware;
use function in_array;
/** /**
* Simplify picture elements * Simplify picture elements
*/ */
final class Picture { final class Picture
{
use ContainerAware; use ContainerAware;
private const SIMPLE_IMAGE_TYPES = [ private const SIMPLE_IMAGE_TYPES = [
@ -60,7 +61,8 @@ final class Picture {
$ext = array_pop($urlParts); $ext = array_pop($urlParts);
$path = implode('.', $urlParts); $path = implode('.', $urlParts);
$mime = match ($ext) { $mime = match ($ext)
{
'avif' => 'image/avif', 'avif' => 'image/avif',
'apng' => 'image/vnd.mozilla.apng', 'apng' => 'image/vnd.mozilla.apng',
'bmp' => 'image/bmp', 'bmp' => 'image/bmp',
@ -74,7 +76,8 @@ final class Picture {
default => 'image/jpeg', default => 'image/jpeg',
}; };
$fallbackMime = match ($fallbackExt) { $fallbackMime = match ($fallbackExt)
{
'gif' => 'image/gif', 'gif' => 'image/gif',
'png' => 'image/png', 'png' => 'image/png',
default => 'image/jpeg', default => 'image/jpeg',
@ -83,10 +86,9 @@ final class Picture {
// For image types that are well-established, just return a // For image types that are well-established, just return a
// simple <img /> element instead // simple <img /> element instead
if ( if (
$ext === $fallbackExt || $ext === $fallbackExt
\in_array($ext, Picture::SIMPLE_IMAGE_TYPES, TRUE) || in_array($ext, Picture::SIMPLE_IMAGE_TYPES, TRUE)
) ) {
{
$attrs = (count($imgAttrs) > 1) $attrs = (count($imgAttrs) > 1)
? $imgAttrs ? $imgAttrs
: $picAttrs; : $picAttrs;
@ -103,7 +105,7 @@ final class Picture {
]), ]),
$helper->void('source', [ $helper->void('source', [
'srcset' => $fallbackImg, 'srcset' => $fallbackImg,
'type' => $fallbackMime 'type' => $fallbackMime,
]), ]),
$helper->img($fallbackImg, array_merge(['alt' => ''], $imgAttrs)), $helper->img($fallbackImg, array_merge(['alt' => ''], $imgAttrs)),
]; ];

View File

@ -16,14 +16,15 @@
namespace Aviat\AnimeClient; namespace Aviat\AnimeClient;
use Aviat\AnimeClient\API\Kitsu\Enum\AnimeAiringStatus; use Aviat\AnimeClient\API\Kitsu\Enum\{AnimeAiringStatus, MangaPublishingStatus};
use Aviat\AnimeClient\API\Kitsu\Enum\MangaPublishingStatus;
use DateTimeImmutable; use DateTimeImmutable;
use const PHP_URL_HOST;
/** /**
* Data massaging helpers for the Kitsu API * Data massaging helpers for the Kitsu API
*/ */
final class Kitsu { final class Kitsu
{
public const AUTH_URL = 'https://kitsu.io/api/oauth/token'; public const AUTH_URL = 'https://kitsu.io/api/oauth/token';
public const AUTH_USER_ID_KEY = 'kitsu-auth-userid'; public const AUTH_USER_ID_KEY = 'kitsu-auth-userid';
public const AUTH_TOKEN_CACHE_KEY = 'kitsu-auth-token'; public const AUTH_TOKEN_CACHE_KEY = 'kitsu-auth-token';
@ -31,9 +32,7 @@ final class Kitsu {
public const AUTH_TOKEN_REFRESH_CACHE_KEY = 'kitsu-auth-token-refresh'; public const AUTH_TOKEN_REFRESH_CACHE_KEY = 'kitsu-auth-token-refresh';
public const ANIME_HISTORY_LIST_CACHE_KEY = 'kitsu-anime-history-list'; public const ANIME_HISTORY_LIST_CACHE_KEY = 'kitsu-anime-history-list';
public const MANGA_HISTORY_LIST_CACHE_KEY = 'kitsu-manga-history-list'; public const MANGA_HISTORY_LIST_CACHE_KEY = 'kitsu-manga-history-list';
public const GRAPHQL_ENDPOINT = 'https://kitsu.io/api/graphql'; public const GRAPHQL_ENDPOINT = 'https://kitsu.io/api/graphql';
public const SECONDS_IN_MINUTE = 60; public const SECONDS_IN_MINUTE = 60;
public const MINUTES_IN_HOUR = 60; public const MINUTES_IN_HOUR = 60;
public const MINUTES_IN_DAY = 1440; public const MINUTES_IN_DAY = 1440;
@ -41,11 +40,8 @@ final class Kitsu {
/** /**
* Determine whether an anime is airing, finished airing, or has not yet aired * Determine whether an anime is airing, finished airing, or has not yet aired
*
* @param string|null $startDate
* @param string|null $endDate
*/ */
public static function getAiringStatus(string $startDate = NULL, string $endDate = NULL): string public static function getAiringStatus(?string $startDate = NULL, ?string $endDate = NULL): string
{ {
$startAirDate = new DateTimeImmutable($startDate ?? 'tomorrow'); $startAirDate = new DateTimeImmutable($startDate ?? 'tomorrow');
$endAirDate = new DateTimeImmutable($endDate ?? 'next year'); $endAirDate = new DateTimeImmutable($endDate ?? 'next year');
@ -69,11 +65,8 @@ final class Kitsu {
/** /**
* Reformat the airing date range for an Anime * Reformat the airing date range for an Anime
*
* @param string|null $startDate
* @param string|null $endDate
*/ */
public static function formatAirDates(string $startDate = NULL, string $endDate = NULL): string public static function formatAirDates(?string $startDate = NULL, ?string $endDate = NULL): string
{ {
if (empty($startDate)) if (empty($startDate))
{ {
@ -117,7 +110,7 @@ final class Kitsu {
return "{$monthMap[$startMonth]} {$startYear} - {$monthMap[$endMonth]} {$endYear}"; return "{$monthMap[$startMonth]} {$startYear} - {$monthMap[$endMonth]} {$endYear}";
} }
public static function getPublishingStatus(string $kitsuStatus, string $startDate = NULL, string $endDate = NULL): string public static function getPublishingStatus(string $kitsuStatus, ?string $startDate = NULL, ?string $endDate = NULL): string
{ {
$startPubDate = new DateTimeImmutable($startDate ?? 'tomorrow'); $startPubDate = new DateTimeImmutable($startDate ?? 'tomorrow');
$endPubDate = new DateTimeImmutable($endDate ?? 'next year'); $endPubDate = new DateTimeImmutable($endDate ?? 'next year');
@ -196,7 +189,6 @@ final class Kitsu {
$key = $uMap['key']; $key = $uMap['key'];
$url = str_replace('{}', $mapping['externalId'], $uMap['url']); $url = str_replace('{}', $mapping['externalId'], $uMap['url']);
$output[$key] = $url; $output[$key] = $url;
} }
@ -235,7 +227,7 @@ final class Kitsu {
$url = '//' . $url; $url = '//' . $url;
} }
$host = parse_url($url, \PHP_URL_HOST); $host = parse_url($url, PHP_URL_HOST);
if ($host === FALSE) if ($host === FALSE)
{ {
return []; return [];
@ -245,7 +237,7 @@ final class Kitsu {
'meta' => self::getServiceMetaData($host), 'meta' => self::getServiceMetaData($host),
'link' => $streamingLink['url'], 'link' => $streamingLink['url'],
'subs' => $streamingLink['subs'], 'subs' => $streamingLink['subs'],
'dubs' => $streamingLink['dubs'] 'dubs' => $streamingLink['dubs'],
]; ];
} }
@ -266,7 +258,7 @@ final class Kitsu {
...array_values($titles['localized']), ...array_values($titles['localized']),
]); ]);
return array_diff($raw,[$titles['canonical']]); return array_diff($raw, [$titles['canonical']]);
} }
/** /**
@ -283,7 +275,7 @@ final class Kitsu {
{ {
if (array_key_exists($search, $titles) && is_array($titles[$search])) if (array_key_exists($search, $titles) && is_array($titles[$search]))
{ {
foreach($titles[$search] as $alternateTitle) foreach ($titles[$search] as $alternateTitle)
{ {
if (self::titleIsUnique($alternateTitle, $valid)) if (self::titleIsUnique($alternateTitle, $valid))
{ {
@ -311,11 +303,11 @@ final class Kitsu {
if (array_key_exists('localized', $titles) && is_array($titles['localized'])) if (array_key_exists('localized', $titles) && is_array($titles['localized']))
{ {
foreach($titles['localized'] as $locale => $alternateTitle) foreach ($titles['localized'] as $locale => $alternateTitle)
{ {
// Really don't care about languages that aren't english // Really don't care about languages that aren't english
// or Japanese for titles // or Japanese for titles
if ( ! in_array($locale, ['en', 'en_us', 'en_jp', 'ja_jp'])) if ( ! in_array($locale, ['en', 'en_us', 'en_jp', 'ja_jp'], TRUE))
{ {
continue; continue;
} }
@ -344,16 +336,15 @@ final class Kitsu {
$parts = explode('?', $rawUrl); $parts = explode('?', $rawUrl);
return ( empty($parts)) ? $rawUrl : $parts[0]; return (empty($parts)) ? $rawUrl : $parts[0];
} }
/** /**
* Get the name and logo for the streaming service of the current link * Get the name and logo for the streaming service of the current link
* *
* @param string|null $hostname * @return bool[]|string[]
* @return string[]|bool[]
*/ */
protected static function getServiceMetaData(string $hostname = NULL): array private static function getServiceMetaData(?string $hostname = NULL): array
{ {
$hostname = str_replace('www.', '', $hostname ?? ''); $hostname = str_replace('www.', '', $hostname ?? '');
@ -376,7 +367,7 @@ final class Kitsu {
'daisuki.net' => [ 'daisuki.net' => [
'name' => 'Daisuki', 'name' => 'Daisuki',
'link' => TRUE, 'link' => TRUE,
'image' => 'streaming-logos/daisuki.svg' 'image' => 'streaming-logos/daisuki.svg',
], ],
'funimation.com' => [ 'funimation.com' => [
'name' => 'Funimation', 'name' => 'Funimation',
@ -401,13 +392,13 @@ final class Kitsu {
'viewster.com' => [ 'viewster.com' => [
'name' => 'Viewster', 'name' => 'Viewster',
'link' => TRUE, 'link' => TRUE,
'image' => 'streaming-logos/viewster.svg' 'image' => 'streaming-logos/viewster.svg',
], ],
'vrv.co' => [ 'vrv.co' => [
'name' => 'VRV', 'name' => 'VRV',
'link' => TRUE, 'link' => TRUE,
'image' => 'streaming-logos/vrv.svg', 'image' => 'streaming-logos/vrv.svg',
] ],
]; ];
if (array_key_exists($hostname, $serviceMap)) if (array_key_exists($hostname, $serviceMap))
@ -434,7 +425,7 @@ final class Kitsu {
$minutes = ($seconds - $remSeconds) / self::SECONDS_IN_MINUTE; $minutes = ($seconds - $remSeconds) / self::SECONDS_IN_MINUTE;
// Minutes short of a year // Minutes short of a year
$years = (int)floor($minutes / self::MINUTES_IN_YEAR); $years = (int) floor($minutes / self::MINUTES_IN_YEAR);
$minutes %= self::MINUTES_IN_YEAR; $minutes %= self::MINUTES_IN_YEAR;
// Minutes short of a day // Minutes short of a day
@ -446,12 +437,13 @@ final class Kitsu {
$hours = ($extraMinutes - $remMinutes) / self::MINUTES_IN_HOUR; $hours = ($extraMinutes - $remMinutes) / self::MINUTES_IN_HOUR;
$parts = []; $parts = [];
foreach ([ foreach ([
'year' => $years, 'year' => $years,
'day' => $days, 'day' => $days,
'hour' => $hours, 'hour' => $hours,
'minute' => $remMinutes, 'minute' => $remMinutes,
'second' => $remSeconds 'second' => $remSeconds,
] as $label => $value) ] as $label => $value)
{ {
if ($value === 0) if ($value === 0)
@ -482,14 +474,14 @@ final class Kitsu {
/** /**
* Determine if an alternate title is unique enough to list * Determine if an alternate title is unique enough to list
*/ */
protected static function titleIsUnique(?string $title = '', array $existingTitles = []): bool private static function titleIsUnique(?string $title = '', array $existingTitles = []): bool
{ {
if (empty($title)) if (empty($title))
{ {
return FALSE; return FALSE;
} }
foreach($existingTitles as $existing) foreach ($existingTitles as $existing)
{ {
$isSubset = mb_substr_count($existing, $title) > 0; $isSubset = mb_substr_count($existing, $title) > 0;
$diff = levenshtein(mb_strtolower($existing), mb_strtolower($title)); $diff = levenshtein(mb_strtolower($existing), mb_strtolower($title));

View File

@ -16,19 +16,18 @@
namespace Aviat\AnimeClient; namespace Aviat\AnimeClient;
use Aviat\Ion\Di\Exception\{ContainerException, NotFoundException};
use Aura\Html\HelperLocator; use Aura\Html\HelperLocator;
use Aviat\Ion\Di\ContainerInterface; use Aviat\Ion\Di\ContainerInterface;
use Aviat\Ion\Di\Exception\{ContainerException, NotFoundException};
use Aviat\Ion\Exception\ConfigException; use Aviat\Ion\Exception\ConfigException;
use Aviat\Ion\Type\ArrayType; use Aviat\Ion\Type\{ArrayType, StringType};
use Aviat\Ion\Type\StringType;
use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ServerRequestInterface;
/** /**
* Helper object to manage menu creation and selection * Helper object to manage menu creation and selection
*/ */
final class MenuGenerator extends UrlGenerator { final class MenuGenerator extends UrlGenerator
{
/** /**
* Html generation helper * Html generation helper
*/ */
@ -49,7 +48,7 @@ final class MenuGenerator extends UrlGenerator {
* *
* @throws ConfigException * @throws ConfigException
*/ */
public function generate(string $menu) : string public function generate(string $menu): string
{ {
$menus = $this->config->get('menus'); $menus = $this->config->get('menus');
$parsedConfig = $this->parseConfig($menus); $parsedConfig = $this->parseConfig($menus);
@ -101,17 +100,18 @@ final class MenuGenerator extends UrlGenerator {
* *
* @return array<mixed, array<string, string>> * @return array<mixed, array<string, string>>
*/ */
private function parseConfig(array $menus) : array private function parseConfig(array $menus): array
{ {
$parsed = []; $parsed = [];
foreach ($menus as $name => $menu) foreach ($menus as $name => $menu)
{ {
$parsed[$name] = []; $parsed[$name] = [];
foreach ($menu['items'] as $pathName => $partialPath) foreach ($menu['items'] as $pathName => $partialPath)
{ {
$title = (string)StringType::from($pathName)->humanize()->titleize(); $title = (string) StringType::from($pathName)->humanize()->titleize();
$parsed[$name][$title] = (string)StringType::from($menu['route_prefix'])->append($partialPath); $parsed[$name][$title] = (string) StringType::from($menu['route_prefix'])->append($partialPath);
} }
} }

View File

@ -19,7 +19,8 @@ namespace Aviat\AnimeClient\Model;
/** /**
* Base model for api interaction * Base model for api interaction
*/ */
abstract class API { abstract class API
{
/** /**
* Sort the list entries by their title * Sort the list entries by their title
*/ */
@ -40,11 +41,11 @@ abstract class API {
array_multisort($sort, SORT_ASC, $array); array_multisort($sort, SORT_ASC, $array);
// Re-key array items by their ids // Re-key array items by their ids
if (array_key_exists('id', (array)$array[0])) if (array_key_exists('id', (array) $array[0]))
{ {
$keyed = []; $keyed = [];
foreach($array as $item) foreach ($array as $item)
{ {
$keyed[$item['id']] = $item; $keyed[$item['id']] = $item;
} }

View File

@ -22,7 +22,8 @@ use Aviat\AnimeClient\Types\Anime as AnimeType;
/** /**
* Model for handling requests dealing with the anime list * Model for handling requests dealing with the anime list
*/ */
class Anime extends API { class Anime extends API
{
use MediaTrait; use MediaTrait;
protected string $type = 'anime'; protected string $type = 'anime';
@ -54,7 +55,7 @@ class Anime extends API {
{ {
$data = $this->kitsuModel->getFullOrganizedAnimeList(); $data = $this->kitsuModel->getFullOrganizedAnimeList();
foreach($data as &$list) foreach ($data as &$list)
{ {
$this->sortByName($list, 'anime'); $this->sortByName($list, 'anime');
} }
@ -95,6 +96,4 @@ class Anime extends API {
{ {
return $this->kitsuModel->getAnimeHistory(); return $this->kitsuModel->getAnimeHistory();
} }
} }

View File

@ -19,13 +19,12 @@ namespace Aviat\AnimeClient\Model;
use Aviat\Ion\Di\ContainerInterface; use Aviat\Ion\Di\ContainerInterface;
use PDO; use PDO;
use PDOException; use PDOException;
use function in_array;
/** /**
* Model for getting anime collection data * Model for getting anime collection data
*/ */
final class AnimeCollection extends Collection { final class AnimeCollection extends Collection
{
/** /**
* Anime API Model * Anime API Model
*/ */
@ -99,7 +98,7 @@ final class AnimeCollection extends Collection {
return []; return [];
} }
foreach($rows as &$row) foreach ($rows as &$row)
{ {
$id = $row['hummingbird_id']; $id = $row['hummingbird_id'];
@ -164,7 +163,7 @@ final class AnimeCollection extends Collection {
'Other' => [ 'Other' => [
10 => $flatList[10], // UMD 10 => $flatList[10], // UMD
11 => $flatList[11], // Other 11 => $flatList[11], // Other
] ],
]; ];
} }
@ -185,7 +184,7 @@ final class AnimeCollection extends Collection {
} }
$id = $data['id']; $id = $data['id'];
$anime = (object)$this->animeModel->getAnimeById($id); $anime = (object) $this->animeModel->getAnimeById($id);
$this->db->set([ $this->db->set([
'hummingbird_id' => $id, 'hummingbird_id' => $id,
@ -197,7 +196,7 @@ final class AnimeCollection extends Collection {
'cover_image' => $anime->cover_image, 'cover_image' => $anime->cover_image,
'episode_count' => $anime->episode_count, 'episode_count' => $anime->episode_count,
'episode_length' => $anime->episode_length, 'episode_length' => $anime->episode_length,
'notes' => $data['notes'] 'notes' => $data['notes'],
])->insert('anime_set'); ])->insert('anime_set');
$this->updateMediaLink($id, $data['media_id']); $this->updateMediaLink($id, $data['media_id']);
@ -256,8 +255,6 @@ final class AnimeCollection extends Collection {
/** /**
* Verify that the collection item was updated * Verify that the collection item was updated
*
*
*/ */
public function wasUpdated(array $data): bool public function wasUpdated(array $data): bool
{ {
@ -275,7 +272,7 @@ final class AnimeCollection extends Collection {
continue; continue;
} }
if ((string)$row[$key] !== (string)$value) if ((string) $row[$key] !== (string) $value)
{ {
return FALSE; return FALSE;
} }
@ -406,7 +403,6 @@ final class AnimeCollection extends Collection {
->from('anime_set_genre_link gl') ->from('anime_set_genre_link gl')
->join('genres g', 'g.id=gl.genre_id', 'left'); ->join('genres g', 'g.id=gl.genre_id', 'left');
if ( ! empty($filter)) if ( ! empty($filter))
{ {
$this->db->whereIn('hummingbird_id', $filter); $this->db->whereIn('hummingbird_id', $filter);
@ -443,7 +439,9 @@ final class AnimeCollection extends Collection {
} }
} }
} }
catch (PDOException) {} catch (PDOException)
{
}
$this->db->resetQuery(); $this->db->resetQuery();
@ -473,7 +471,6 @@ final class AnimeCollection extends Collection {
->from('anime_set_media_link ml') ->from('anime_set_media_link ml')
->join('media m', 'm.id=ml.media_id', 'left'); ->join('media m', 'm.id=ml.media_id', 'left');
if ( ! empty($filter)) if ( ! empty($filter))
{ {
$this->db->whereIn('hummingbird_id', $filter); $this->db->whereIn('hummingbird_id', $filter);
@ -510,7 +507,9 @@ final class AnimeCollection extends Collection {
} }
} }
} }
catch (PDOException) {} catch (PDOException)
{
}
$this->db->resetQuery(); $this->db->resetQuery();
@ -532,6 +531,7 @@ final class AnimeCollection extends Collection {
// Add the new entries // Add the new entries
$entries = []; $entries = [];
foreach ($media as $id) foreach ($media as $id)
{ {
$entries[] = [ $entries[] = [
@ -592,7 +592,9 @@ final class AnimeCollection extends Collection {
{ {
$this->db->insertBatch('anime_set_genre_link', $linksToInsert); $this->db->insertBatch('anime_set_genre_link', $linksToInsert);
} }
catch (PDOException) {} catch (PDOException)
{
}
} }
} }
@ -744,7 +746,7 @@ final class AnimeCollection extends Collection {
$genres = $this->getGenreList(); $genres = $this->getGenreList();
foreach($rows as &$row) foreach ($rows as &$row)
{ {
$id = $row['hummingbird_id']; $id = $row['hummingbird_id'];

View File

@ -25,8 +25,8 @@ use function Query;
/** /**
* Base model for anime and manga collections * Base model for anime and manga collections
*/ */
class Collection extends DB { class Collection extends DB
{
/** /**
* The query builder object * The query builder object
*/ */

View File

@ -21,7 +21,8 @@ use Aviat\Ion\Di\{ContainerAware, ContainerInterface};
/** /**
* Base model for database interaction * Base model for database interaction
*/ */
abstract class DB { abstract class DB
{
use ContainerAware; use ContainerAware;
/** /**
@ -31,8 +32,6 @@ abstract class DB {
/** /**
* Constructor * Constructor
*
* @param ContainerInterface $container
*/ */
public function __construct(ContainerInterface $container) public function __construct(ContainerInterface $container)
{ {

View File

@ -27,7 +27,8 @@ use Aviat\AnimeClient\Types\{
/** /**
* Model for handling requests dealing with the manga list * Model for handling requests dealing with the manga list
*/ */
class Manga extends API { class Manga extends API
{
use MediaTrait; use MediaTrait;
protected string $type = 'manga'; protected string $type = 'manga';
@ -42,7 +43,8 @@ class Manga extends API {
if ($status === 'All') if ($status === 'All')
{ {
$data = $this->kitsuModel->getFullOrganizedMangaList(); $data = $this->kitsuModel->getFullOrganizedMangaList();
foreach($data as &$section)
foreach ($data as &$section)
{ {
$this->sortByName($section, 'manga'); $this->sortByName($section, 'manga');
} }
@ -53,13 +55,12 @@ class Manga extends API {
$APIstatus = MangaReadingStatus::TITLE_TO_KITSU[$status]; $APIstatus = MangaReadingStatus::TITLE_TO_KITSU[$status];
$data = $this->mapByStatus($this->kitsuModel->getMangaList($APIstatus)); $data = $this->mapByStatus($this->kitsuModel->getMangaList($APIstatus));
$this->sortByName($data[$status], 'manga'); $this->sortByName($data[$status], 'manga');
return $data[$status]; return $data[$status];
} }
/** /**
* Get the details of a manga * Get the details of a manga
*
* @return MangaPage
*/ */
public function getManga(string $manga_id): MangaPage public function getManga(string $manga_id): MangaPage
{ {
@ -68,8 +69,6 @@ class Manga extends API {
/** /**
* Get the details of a random manga * Get the details of a random manga
*
* @return MangaPage
*/ */
public function getRandomManga(): MangaPage public function getRandomManga(): MangaPage
{ {
@ -78,8 +77,6 @@ class Manga extends API {
/** /**
* Get anime by its kitsu id * Get anime by its kitsu id
*
* @return MangaPage
*/ */
public function getMangaById(string $animeId): MangaPage public function getMangaById(string $animeId): MangaPage
{ {
@ -111,7 +108,8 @@ class Manga extends API {
Title::COMPLETED => [], Title::COMPLETED => [],
]; ];
foreach ($data as $entry) { foreach ($data as $entry)
{
$statusMap = MangaReadingStatus::KITSU_TO_TITLE; $statusMap = MangaReadingStatus::KITSU_TO_TITLE;
$key = $statusMap[$entry['reading_status']]; $key = $statusMap[$entry['reading_status']];
$output[$key][] = $entry; $output[$key][] = $entry;

View File

@ -16,12 +16,8 @@
namespace Aviat\AnimeClient\Model; namespace Aviat\AnimeClient\Model;
use Aviat\AnimeClient\API\Anilist; use Aviat\AnimeClient\API\{Anilist, Kitsu, ParallelAPIRequest};
use Aviat\AnimeClient\API\Kitsu; use Aviat\AnimeClient\Types\{AnimeListItem, FormItem, MangaListItem};
use Aviat\AnimeClient\API\ParallelAPIRequest;
use Aviat\AnimeClient\Types\AnimeListItem;
use Aviat\AnimeClient\Types\FormItem;
use Aviat\AnimeClient\Types\MangaListItem;
use Aviat\Ion\Di\ContainerInterface; use Aviat\Ion\Di\ContainerInterface;
use Aviat\Ion\Json; use Aviat\Ion\Json;
@ -30,8 +26,8 @@ use Throwable;
/** /**
* Common functionality for Anime/Manga Models * Common functionality for Anime/Manga Models
*/ */
trait MediaTrait { trait MediaTrait
{
/** /**
* Is the Anilist API enabled? * Is the Anilist API enabled?
*/ */
@ -64,7 +60,7 @@ trait MediaTrait {
* *
* @return mixed[] * @return mixed[]
*/ */
public function search(string $name, bool $inCollection = false): array public function search(string $name, bool $inCollection = FALSE): array
{ {
$data = $this->kitsuModel->search($this->type, urldecode($name)); $data = $this->kitsuModel->search($this->type, urldecode($name));
@ -101,7 +97,7 @@ trait MediaTrait {
$requester->addRequest($kitsuRequest, 'kitsu'); $requester->addRequest($kitsuRequest, 'kitsu');
if ($this->anilistEnabled && $data['mal_id'] !== null) if ($this->anilistEnabled && $data['mal_id'] !== NULL)
{ {
// If can't map MAL id, this will be null // If can't map MAL id, this will be null
$maybeRequest = $this->anilistModel->createListItem($data, strtoupper($this->type)); $maybeRequest = $this->anilistModel->createListItem($data, strtoupper($this->type));
@ -144,7 +140,7 @@ trait MediaTrait {
return [ return [
'body' => Json::decode($results['kitsu']), 'body' => Json::decode($results['kitsu']),
'statusCode' => $statusCode 'statusCode' => $statusCode,
]; ];
} }
@ -172,26 +168,25 @@ trait MediaTrait {
$results = $requester->makeRequests(); $results = $requester->makeRequests();
$body = Json::decode($results['kitsu']); $body = Json::decode($results['kitsu']);
$statusCode = array_key_exists('errors', $body) ? 400: 200; $statusCode = array_key_exists('errors', $body) ? 400 : 200;
return [ return [
'body' => Json::decode($results['kitsu']), 'body' => Json::decode($results['kitsu']),
'statusCode' => $statusCode 'statusCode' => $statusCode,
]; ];
} }
/** /**
* Delete a list entry * Delete a list entry
* *
* @param string|null $malId
* @throws Throwable * @throws Throwable
*/ */
public function deleteLibraryItem(string $id, string $malId = NULL): bool public function deleteLibraryItem(string $id, ?string $malId = NULL): bool
{ {
$requester = new ParallelAPIRequest(); $requester = new ParallelAPIRequest();
$requester->addRequest($this->kitsuModel->deleteListItem($id), 'kitsu'); $requester->addRequest($this->kitsuModel->deleteListItem($id), 'kitsu');
if ($this->anilistEnabled && $malId !== null) if ($this->anilistEnabled && $malId !== NULL)
{ {
// If can't map MAL id, this will be null // If can't map MAL id, this will be null
$maybeRequest = $this->anilistModel->deleteListItem($malId, strtoupper($this->type)); $maybeRequest = $this->anilistModel->deleteListItem($malId, strtoupper($this->type));

View File

@ -16,21 +16,21 @@
namespace Aviat\AnimeClient\Model; namespace Aviat\AnimeClient\Model;
use function is_array;
use const Aviat\AnimeClient\SETTINGS_MAP;
use function Aviat\AnimeClient\arrayToToml;
use function Aviat\Ion\_dir;
use Aviat\AnimeClient\Types\{Config, UndefinedPropertyException}; use Aviat\AnimeClient\Types\{Config, UndefinedPropertyException};
use Aviat\Ion\ConfigInterface; use Aviat\Ion\ConfigInterface;
use Aviat\Ion\Di\ContainerAware; use Aviat\Ion\Di\ContainerAware;
use function Aviat\AnimeClient\arrayToToml;
use function Aviat\Ion\_dir;
use const Aviat\AnimeClient\SETTINGS_MAP;
/** /**
* Model for handling settings control panel * Model for handling settings control panel
*/ */
final class Settings { final class Settings
{
use ContainerAware; use ContainerAware;
public function __construct(private ConfigInterface $config) public function __construct(private ConfigInterface $config)
@ -46,12 +46,13 @@ final class Settings {
'config' => [], 'config' => [],
]; ];
foreach(SETTINGS_MAP as $file => $values) foreach (SETTINGS_MAP as $file => $values)
{ {
if ($file === 'config') if ($file === 'config')
{ {
$keys = array_keys($values); $keys = array_keys($values);
foreach($keys as $key)
foreach ($keys as $key)
{ {
$settings['config'][$key] = $this->config->get($key); $settings['config'][$key] = $this->config->get($key);
} }
@ -72,19 +73,20 @@ final class Settings {
{ {
$output = []; $output = [];
foreach($this->getSettings() as $file => $values) foreach ($this->getSettings() as $file => $values)
{ {
$values ??= []; $values ??= [];
foreach(SETTINGS_MAP[$file] as $key => $value) foreach (SETTINGS_MAP[$file] as $key => $value)
{ {
if ($value['type'] === 'subfield') if ($value['type'] === 'subfield')
{ {
foreach($value['fields'] as $k => $field) foreach ($value['fields'] as $k => $field)
{ {
if (empty($values[$key][$k])) if (empty($values[$key][$k]))
{ {
unset($value['fields'][$k]); unset($value['fields'][$k]);
continue; continue;
} }
@ -159,13 +161,13 @@ final class Settings {
} }
elseif (is_array($val) && ! empty($val)) elseif (is_array($val) && ! empty($val))
{ {
foreach($val as $k => $v) foreach ($val as $k => $v)
{ {
if ($v === '1') if ($v === '1')
{ {
$keyedConfig[$key][$k] = TRUE; $keyedConfig[$key][$k] = TRUE;
} }
elseif($v === '0') elseif ($v === '0')
{ {
$keyedConfig[$key][$k] = FALSE; $keyedConfig[$key][$k] = FALSE;
} }
@ -182,12 +184,12 @@ final class Settings {
$output = []; $output = [];
foreach($looseConfig as $k => $v) foreach ($looseConfig as $k => $v)
{ {
$output[$k] = $v; $output[$k] = $v;
} }
foreach($keyedConfig as $k => $v) foreach ($keyedConfig as $k => $v)
{ {
$output[$k] = $v; $output[$k] = $v;
} }
@ -211,6 +213,7 @@ final class Settings {
{ {
dump($e); dump($e);
dump($settings); dump($settings);
return FALSE; return FALSE;
} }

View File

@ -18,8 +18,7 @@ namespace Aviat\AnimeClient;
use Aviat\Ion\ConfigInterface; use Aviat\Ion\ConfigInterface;
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\Exception\ConfigException; use Aviat\Ion\Exception\ConfigException;
use Aviat\Ion\Type\StringType; use Aviat\Ion\Type\StringType;
use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ServerRequestInterface;
@ -27,8 +26,8 @@ use Psr\Http\Message\ServerRequestInterface;
/** /**
* Base for routing/url classes * Base for routing/url classes
*/ */
class RoutingBase { class RoutingBase
{
/** /**
* Config Object * Config Object
*/ */
@ -42,9 +41,9 @@ class RoutingBase {
/** /**
* Constructor * Constructor
* *
* @throws ConfigException
* @throws ContainerException * @throws ContainerException
* @throws NotFoundException * @throws NotFoundException
* @throws ConfigException
*/ */
public function __construct(protected ContainerInterface $container) public function __construct(protected ContainerInterface $container)
{ {
@ -64,7 +63,7 @@ class RoutingBase {
->trimRight('/') ->trimRight('/')
->ensureLeft('/'); ->ensureLeft('/');
return (string)$cleanedPath; return (string) $cleanedPath;
} }
/** /**
@ -73,17 +72,17 @@ class RoutingBase {
public function segments(): array public function segments(): array
{ {
$path = $this->path(); $path = $this->path();
return explode('/', $path); return explode('/', $path);
} }
/** /**
* Get a segment of the current url * Get a segment of the current url
*
*
*/ */
public function getSegment(int $num): ?string public function getSegment(int $num): ?string
{ {
$segments = $this->segments(); $segments = $this->segments();
return $segments[$num] ?? NULL; return $segments[$num] ?? NULL;
} }
@ -93,6 +92,7 @@ class RoutingBase {
public function lastSegment(): string public function lastSegment(): string
{ {
$segments = $this->segments(); $segments = $this->segments();
return end($segments); return end($segments);
} }
} }

View File

@ -18,8 +18,10 @@ namespace Aviat\AnimeClient\Types;
use ArrayAccess; use ArrayAccess;
use Countable; use Countable;
use Stringable;
abstract class AbstractType implements ArrayAccess, Countable, \Stringable { abstract class AbstractType implements ArrayAccess, Countable, Stringable
{
/** /**
* Populate values for un-serializing data * Populate values for un-serializing data
*/ */
@ -56,8 +58,8 @@ abstract class AbstractType implements ArrayAccess, Countable, \Stringable {
*/ */
final private function __construct(mixed $data = []) final private function __construct(mixed $data = [])
{ {
$typeKeys = array_keys((array)$this); $typeKeys = array_keys((array) $this);
$dataKeys = array_keys((array)$data); $dataKeys = array_keys((array) $data);
$unsetKeys = array_diff($typeKeys, $dataKeys); $unsetKeys = array_diff($typeKeys, $dataKeys);
@ -69,7 +71,7 @@ abstract class AbstractType implements ArrayAccess, Countable, \Stringable {
// Remove unset keys so that they aren't serialized // Remove unset keys so that they aren't serialized
foreach ($unsetKeys as $k) foreach ($unsetKeys as $k)
{ {
unset($this->$k); unset($this->{$k});
} }
} }
@ -78,7 +80,7 @@ abstract class AbstractType implements ArrayAccess, Countable, \Stringable {
*/ */
final public function __isset(string $name): bool final public function __isset(string $name): bool
{ {
return property_exists($this, $name) && isset($this->$name); return property_exists($this, $name) && isset($this->{$name});
} }
/** /**
@ -90,7 +92,8 @@ abstract class AbstractType implements ArrayAccess, Countable, \Stringable {
if (method_exists($this, $setterMethod)) if (method_exists($this, $setterMethod))
{ {
$this->$setterMethod($value); $this->{$setterMethod}($value);
return; return;
} }
@ -101,7 +104,7 @@ abstract class AbstractType implements ArrayAccess, Countable, \Stringable {
throw new UndefinedPropertyException("Trying to set undefined property: '{$name}'. Existing properties: {$existing}"); throw new UndefinedPropertyException("Trying to set undefined property: '{$name}'. Existing properties: {$existing}");
} }
$this->$name = $value; $this->{$name} = $value;
} }
/** /**
@ -111,7 +114,7 @@ abstract class AbstractType implements ArrayAccess, Countable, \Stringable {
{ {
// Be a bit more lenient here, so that you can easily typecast missing // Be a bit more lenient here, so that you can easily typecast missing
// values to reasonable defaults, and not have to resort to array indexes // values to reasonable defaults, and not have to resort to array indexes
return ($this->__isset($name)) ? $this->$name : NULL; return ($this->__isset($name)) ? $this->{$name} : NULL;
} }
/** /**
@ -127,7 +130,7 @@ abstract class AbstractType implements ArrayAccess, Countable, \Stringable {
*/ */
final public function offsetExists(mixed $offset): bool final public function offsetExists(mixed $offset): bool
{ {
return $this->__isset((string)$offset); return $this->__isset((string) $offset);
} }
/** /**
@ -135,7 +138,7 @@ abstract class AbstractType implements ArrayAccess, Countable, \Stringable {
*/ */
final public function offsetGet(mixed $offset): mixed final public function offsetGet(mixed $offset): mixed
{ {
return $this->__get((string)$offset); return $this->__get((string) $offset);
} }
/** /**
@ -143,7 +146,7 @@ abstract class AbstractType implements ArrayAccess, Countable, \Stringable {
*/ */
final public function offsetSet(mixed $offset, mixed $value): void final public function offsetSet(mixed $offset, mixed $value): void
{ {
$this->__set((string)$offset, $value); $this->__set((string) $offset, $value);
} }
/** /**
@ -153,8 +156,8 @@ abstract class AbstractType implements ArrayAccess, Countable, \Stringable {
{ {
if ($this->offsetExists($offset)) if ($this->offsetExists($offset))
{ {
$strOffset = (string)$offset; $strOffset = (string) $offset;
unset($this->$strOffset); unset($this->{$strOffset});
} }
} }
@ -164,6 +167,7 @@ abstract class AbstractType implements ArrayAccess, Countable, \Stringable {
final public function count(): int final public function count(): int
{ {
$keys = array_keys($this->toArray()); $keys = array_keys($this->toArray());
return count($keys); return count($keys);
} }
@ -174,9 +178,10 @@ abstract class AbstractType implements ArrayAccess, Countable, \Stringable {
* *
* @param mixed $parent * @param mixed $parent
*/ */
final public function toArray(mixed $parent = null): array final public function toArray(mixed $parent = NULL): array
{ {
$fromObject = $this->fromObject($parent); $fromObject = $this->fromObject($parent);
return (is_array($fromObject)) ? $fromObject : []; return (is_array($fromObject)) ? $fromObject : [];
} }
@ -186,6 +191,7 @@ abstract class AbstractType implements ArrayAccess, Countable, \Stringable {
final public function isEmpty(): bool final public function isEmpty(): bool
{ {
$self = $this->toArray(); $self = $this->toArray();
foreach ($self as $value) foreach ($self as $value)
{ {
if ( ! empty($value)) if ( ! empty($value))
@ -200,7 +206,7 @@ abstract class AbstractType implements ArrayAccess, Countable, \Stringable {
/** /**
* @codeCoverageIgnore * @codeCoverageIgnore
*/ */
final protected function fromObject(mixed $parent = null): float|null|bool|int|array|string final protected function fromObject(mixed $parent = NULL): float|NULL|bool|int|array|string
{ {
$object = $parent ?? $this; $object = $parent ?? $this;

View File

@ -21,37 +21,23 @@ use Aviat\AnimeClient\API\Kitsu\Enum\AnimeAiringStatus;
/** /**
* Type representing an anime within a watch list * Type representing an anime within a watch list
*/ */
class Anime extends AbstractType { class Anime extends AbstractType
{
public ?string $age_rating; public ?string $age_rating;
public ?string $age_rating_guide; public ?string $age_rating_guide;
public ?string $cover_image; public ?string $cover_image;
public ?int $episode_count; public ?int $episode_count;
public ?int $episode_length; public ?int $episode_length;
public array $genres = []; public array $genres = [];
public string $id = ''; public string $id = '';
public ?string $show_type; public ?string $show_type;
public ?string $slug; public ?string $slug;
public string $status = AnimeAiringStatus::FINISHED_AIRING; public string $status = AnimeAiringStatus::FINISHED_AIRING;
public ?array $streaming_links = []; public ?array $streaming_links = [];
public ?string $synopsis; public ?string $synopsis;
public ?string $title; public ?string $title;
public array $titles = []; public array $titles = [];
public array $titles_more = []; public array $titles_more = [];
public ?string $trailer_id; public ?string $trailer_id;
/** /**

View File

@ -19,35 +19,26 @@ namespace Aviat\AnimeClient\Types;
/** /**
* Type representing an anime watch list item * Type representing an anime watch list item
*/ */
final class AnimeListItem extends AbstractType { final class AnimeListItem extends AbstractType
{
public ?string $id; public ?string $id;
public ?string $anilist_id; public ?string $anilist_id;
public ?string $mal_id; public ?string $mal_id;
public array $episodes = [ public array $episodes = [
'length' => 0, 'length' => 0,
'total' => 0, 'total' => 0,
'watched' => '', 'watched' => '',
]; ];
public array $airing = [ public array $airing = [
'status' => '', 'status' => '',
'started' => '', 'started' => '',
'ended' => '', 'ended' => '',
]; ];
public ?Anime $anime; public ?Anime $anime;
public ?string $notes; public ?string $notes;
public bool $private = FALSE; public bool $private = FALSE;
public bool $rewatching = FALSE; public bool $rewatching = FALSE;
public int $rewatched = 0; public int $rewatched = 0;
public string|int $user_rating = ''; public string|int $user_rating = '';
/** /**

View File

@ -19,12 +19,10 @@ namespace Aviat\AnimeClient\Types;
/** /**
* Type representing an Anime object for a detail page * Type representing an Anime object for a detail page
*/ */
final class AnimePage extends Anime { final class AnimePage extends Anime
{
public array $characters = []; public array $characters = [];
public array $links = []; public array $links = [];
public array $staff = []; public array $staff = [];
public ?string $airDate = ''; public ?string $airDate = '';
} }

View File

@ -19,24 +19,18 @@ namespace Aviat\AnimeClient\Types;
/** /**
* Type representing a character for display * Type representing a character for display
*/ */
final class Character extends AbstractType { final class Character extends AbstractType
{
public array $castings = []; public array $castings = [];
public ?string $description; public ?string $description;
public string $id; public string $id;
public ?Media $media; public ?Media $media;
public string $image; public string $image;
public ?string $name; public ?string $name;
public array $names = []; public array $names = [];
public array $otherNames = []; public array $otherNames = [];
public function setMedia (mixed $media): void public function setMedia(mixed $media): void
{ {
$this->media = Media::from($media); $this->media = Media::from($media);
} }

View File

@ -16,8 +16,8 @@
namespace Aviat\AnimeClient\Types; namespace Aviat\AnimeClient\Types;
final class Characters extends AbstractType { final class Characters extends AbstractType
{
public array $main = []; public array $main = [];
public array $supporting = []; public array $supporting = [];
} }

View File

@ -16,16 +16,14 @@
namespace Aviat\AnimeClient\Types; namespace Aviat\AnimeClient\Types;
class Config extends AbstractType { class Config extends AbstractType
{
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// Config files/namespaces // Config files/namespaces
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
public ?Config\Anilist $anilist; public ?Config\Anilist $anilist;
public ?Config\Cache $cache; public ?Config\Cache $cache;
public ?Config\Database $database; public ?Config\Database $database;
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
@ -33,7 +31,6 @@ class Config extends AbstractType {
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
public string $root; // Path to app root public string $root; // Path to app root
public ?string $asset_path; // Path to public folder for urls public ?string $asset_path; // Path to public folder for urls
/** /**
@ -66,11 +63,8 @@ class Config extends AbstractType {
public ?string $default_view_type; public ?string $default_view_type;
public ?string $kitsu_username; public ?string $kitsu_username;
public bool $secure_urls = TRUE; public bool $secure_urls = TRUE;
public string|bool $show_anime_collection = FALSE; public string|bool $show_anime_collection = FALSE;
public string|bool $show_manga_collection = FALSE; public string|bool $show_manga_collection = FALSE;
/** /**
@ -80,9 +74,7 @@ class Config extends AbstractType {
public ?string $theme = 'auto'; public ?string $theme = 'auto';
public ?string $whose_list; public ?string $whose_list;
public array $menus = []; public array $menus = [];
public array $routes = []; public array $routes = [];
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
@ -90,28 +82,23 @@ class Config extends AbstractType {
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
public ?string $asset_dir; // Path to public folder for local files public ?string $asset_dir; // Path to public folder for local files
public ?string $base_config_dir; public ?string $base_config_dir;
public ?string $config_dir; public ?string $config_dir;
public ?string $data_cache_path; public ?string $data_cache_path;
public ?string $img_cache_path; public ?string $img_cache_path;
public ?string $view_path; public ?string $view_path;
public function setAnilist (mixed $data): void public function setAnilist(mixed $data): void
{ {
$this->anilist = Config\Anilist::from($data); $this->anilist = Config\Anilist::from($data);
} }
public function setCache (mixed $data): void public function setCache(mixed $data): void
{ {
$this->cache = Config\Cache::from($data); $this->cache = Config\Cache::from($data);
} }
public function setDatabase (mixed $data): void public function setDatabase(mixed $data): void
{ {
$this->database = Config\Database::from($data); $this->database = Config\Database::from($data);
} }

View File

@ -18,18 +18,13 @@ namespace Aviat\AnimeClient\Types\Config;
use Aviat\AnimeClient\Types\AbstractType; use Aviat\AnimeClient\Types\AbstractType;
class Anilist extends AbstractType { class Anilist extends AbstractType
{
public bool|string $enabled = FALSE; public bool|string $enabled = FALSE;
public ?string $client_id; public ?string $client_id;
public ?string $client_secret; public ?string $client_secret;
public ?string $access_token; public ?string $access_token;
public int|string|NULL $access_token_expires;
public int|string|null $access_token_expires;
public ?string $refresh_token; public ?string $refresh_token;
public ?string $username; public ?string $username;
} }

View File

@ -18,16 +18,12 @@ namespace Aviat\AnimeClient\Types\Config;
use Aviat\AnimeClient\Types\AbstractType; use Aviat\AnimeClient\Types\AbstractType;
class Cache extends AbstractType { class Cache extends AbstractType
{
public string $driver = 'null'; public string $driver = 'null';
public ?string $host; public ?string $host;
public string|int|NULL $port;
public string|int|null $port;
public ?string $database; public ?string $database;
public array $connection = []; public array $connection = [];
public ?array $options; public ?array $options;
} }

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