From a7e6b3f198968098fb8de8ede08153491a4abb05 Mon Sep 17 00:00:00 2001 From: "Timothy J. Warren" Date: Fri, 1 May 2020 17:08:20 -0400 Subject: [PATCH] Make authentication more reliable for list syncing --- src/AnimeClient/API/APIRequestBuilder.php | 1 - .../Transformer/AnimeListTransformer.php | 2 +- .../Transformer/MangaListTransformer.php | 9 +- src/AnimeClient/API/Kitsu/Auth.php | 143 +++++++++--------- src/AnimeClient/API/Kitsu/KitsuTrait.php | 16 +- src/AnimeClient/Command/BaseCommand.php | 109 ++++++++----- src/AnimeClient/Enum/ListType.php | 28 ++++ src/AnimeClient/Enum/SyncAction.php | 28 ++++ 8 files changed, 216 insertions(+), 120 deletions(-) create mode 100644 src/AnimeClient/Enum/ListType.php create mode 100644 src/AnimeClient/Enum/SyncAction.php diff --git a/src/AnimeClient/API/APIRequestBuilder.php b/src/AnimeClient/API/APIRequestBuilder.php index 2e018fb0..d638935c 100644 --- a/src/AnimeClient/API/APIRequestBuilder.php +++ b/src/AnimeClient/API/APIRequestBuilder.php @@ -21,7 +21,6 @@ use const Aviat\AnimeClient\USER_AGENT; use function Amp\Promise\wait; use function Aviat\AnimeClient\getResponse; -use Amp; use Amp\Http\Client\Request; use Amp\Http\Client\Body\FormBody; use Aviat\Ion\Json; diff --git a/src/AnimeClient/API/Anilist/Transformer/AnimeListTransformer.php b/src/AnimeClient/API/Anilist/Transformer/AnimeListTransformer.php index 464d423c..8187e60d 100644 --- a/src/AnimeClient/API/Anilist/Transformer/AnimeListTransformer.php +++ b/src/AnimeClient/API/Anilist/Transformer/AnimeListTransformer.php @@ -54,7 +54,7 @@ class AnimeListTransformer extends AbstractTransformer { 'reconsuming' => $reconsuming, 'status' => $reconsuming ? KitsuStatus::WATCHING - :AnimeWatchingStatus::ANILIST_TO_KITSU[$item['status']], + : AnimeWatchingStatus::ANILIST_TO_KITSU[$item['status']], 'updatedAt' => (new DateTime()) ->setTimestamp($item['updatedAt']) ->format(DateTime::W3C) diff --git a/src/AnimeClient/API/Anilist/Transformer/MangaListTransformer.php b/src/AnimeClient/API/Anilist/Transformer/MangaListTransformer.php index 184dd73a..f60c7025 100644 --- a/src/AnimeClient/API/Anilist/Transformer/MangaListTransformer.php +++ b/src/AnimeClient/API/Anilist/Transformer/MangaListTransformer.php @@ -17,6 +17,7 @@ namespace Aviat\AnimeClient\API\Anilist\Transformer; use Aviat\AnimeClient\API\Enum\MangaReadingStatus\Anilist as AnilistStatus; +use Aviat\AnimeClient\API\Enum\MangaReadingStatus\Kitsu as KitsuStatus; use Aviat\AnimeClient\API\Mapping\MangaReadingStatus; use Aviat\AnimeClient\Types\MangaListItem; use Aviat\AnimeClient\Types\FormItem; @@ -40,6 +41,8 @@ class MangaListTransformer extends AbstractTransformer { */ public function untransform(array $item): FormItem { + $reconsuming = $item['status'] === AnilistStatus::REPEATING; + return FormItem::from([ 'id' => $item['id'], 'mal_id' => $item['media']['idMal'], @@ -49,8 +52,10 @@ class MangaListTransformer extends AbstractTransformer { 'progress' => $item['progress'], 'rating' => $item['score'], 'reconsumeCount' => $item['repeat'], - 'reconsuming' => $item['status'] === AnilistStatus::REPEATING, - 'status' => MangaReadingStatus::ANILIST_TO_KITSU[$item['status']], + 'reconsuming' => $reconsuming, + 'status' => $reconsuming + ? KitsuStatus::READING + : MangaReadingStatus::ANILIST_TO_KITSU[$item['status']], 'updatedAt' => (new DateTime()) ->setTimestamp($item['updatedAt']) ->format(DateTime::W3C), diff --git a/src/AnimeClient/API/Kitsu/Auth.php b/src/AnimeClient/API/Kitsu/Auth.php index c0997f6c..1a36b2a2 100644 --- a/src/AnimeClient/API/Kitsu/Auth.php +++ b/src/AnimeClient/API/Kitsu/Auth.php @@ -29,6 +29,7 @@ use Aviat\Ion\Di\{ContainerAware, ContainerInterface}; use Aviat\Ion\Di\Exception\{ContainerException, NotFoundException}; use Throwable; +use const PHP_SAPI; /** * Kitsu API Authentication @@ -83,35 +84,67 @@ final class Auth { $auth = $this->model->authenticate($username, $password); - if (FALSE !== $auth) - { - // Set the token in the cache for command line operations - $cacheItem = $this->cache->getItem(K::AUTH_TOKEN_CACHE_KEY); - $cacheItem->set($auth['access_token']); - $cacheItem->save(); - - // Set the token expiration in the cache - $expireTime = $auth['created_at'] + $auth['expires_in']; - $cacheItem = $this->cache->getItem(K::AUTH_TOKEN_EXP_CACHE_KEY); - $cacheItem->set($expireTime); - $cacheItem->save(); - - // Set the refresh token in the cache - $cacheItem = $this->cache->getItem(K::AUTH_TOKEN_REFRESH_CACHE_KEY); - $cacheItem->set($auth['refresh_token']); - $cacheItem->save(); - - // Set the session values - $this->segment->set('auth_token', $auth['access_token']); - $this->segment->set('auth_token_expires', $expireTime); - $this->segment->set('refresh_token', $auth['refresh_token']); - - return TRUE; - } - - return FALSE; + return $this->storeAuth($auth); } + /** + * Check whether the current user is authenticated + * + * @return boolean + */ + public function isAuthenticated(): bool + { + return ($this->getAuthToken() !== NULL); + } + + /** + * Clear authentication values + * + * @return void + */ + public function logout(): void + { + $this->segment->clear(); + } + + /** + * Retrieve the authentication token from the session + * + * @return string|false + */ + private function getAuthToken(): ?string + { + $now = time(); + + if (PHP_SAPI === 'cli') + { + $token = $this->cacheGet(K::AUTH_TOKEN_CACHE_KEY, NULL); + $refreshToken = $this->cacheGet(K::AUTH_TOKEN_REFRESH_CACHE_KEY, NULL); + $expireTime = $this->cacheGet(K::AUTH_TOKEN_EXP_CACHE_KEY); + $isExpired = $now > $expireTime; + } + else + { + $token = $this->segment->get('auth_token', NULL); + $refreshToken = $this->segment->get('refresh_token', NULL); + $isExpired = $now > $this->segment->get('auth_token_expires', $now + 5000); + } + + // Attempt to re-authenticate with refresh token + /* if ($isExpired === TRUE && $refreshToken !== NULL) + { + if ($this->reAuthenticate($refreshToken) !== NULL) + { + return (PHP_SAPI === 'cli') + ? $this->cacheGet(K::AUTH_TOKEN_CACHE_KEY, NULL) + : $this->segment->get('auth_token', NULL); + } + + return NULL; + }*/ + + return $token; + } /** * Make the call to re-authenticate with the existing refresh token @@ -121,10 +154,15 @@ final class Auth { * @throws InvalidArgumentException * @throws Throwable */ - public function reAuthenticate(string $token): bool + private function reAuthenticate(string $token): bool { $auth = $this->model->reAuthenticate($token); + return $this->storeAuth($auth); + } + + private function storeAuth($auth): bool + { if (FALSE !== $auth) { // Set the token in the cache for command line operations @@ -153,52 +191,15 @@ final class Auth { return FALSE; } - - /** - * Check whether the current user is authenticated - * - * @return boolean - */ - public function isAuthenticated(): bool + private function cacheGet(string $key, $default = NULL) { - return ($this->get_auth_token() !== FALSE); - } - - /** - * Clear authentication values - * - * @return void - */ - public function logout(): void - { - $this->segment->clear(); - } - - /** - * Retrieve the authentication token from the session - * - * @return string|false - */ - public function get_auth_token() - { - $now = time(); - - $token = $this->segment->get('auth_token', FALSE); - $refreshToken = $this->segment->get('refresh_token', FALSE); - $isExpired = time() > $this->segment->get('auth_token_expires', $now + 5000); - - // Attempt to re-authenticate with refresh token - /* if ($isExpired && $refreshToken) + $cacheItem = $this->cache->getItem($key); + if ( ! $cacheItem->isHit()) { - if ($this->reAuthenticate($refreshToken)) - { - return $this->segment->get('auth_token', FALSE); - } + return $default; + } - return FALSE; - } */ - - return $token; + return $cacheItem->get(); } } // End of KitsuAuth.php \ No newline at end of file diff --git a/src/AnimeClient/API/Kitsu/KitsuTrait.php b/src/AnimeClient/API/Kitsu/KitsuTrait.php index 2503a25b..7242246a 100644 --- a/src/AnimeClient/API/Kitsu/KitsuTrait.php +++ b/src/AnimeClient/API/Kitsu/KitsuTrait.php @@ -16,6 +16,7 @@ namespace Aviat\AnimeClient\API\Kitsu; +use const PHP_SAPI; use const Aviat\AnimeClient\SESSION_SEGMENT; use function Amp\Promise\wait; @@ -69,11 +70,14 @@ trait KitsuTrait { ->getSegment(SESSION_SEGMENT); $cache = $this->getContainer()->get('cache'); - $cacheItem = $cache->getItem('kitsu-auth-token'); + $cacheItem = $cache->getItem(K::AUTH_TOKEN_CACHE_KEY); $token = null; - - if ($sessionSegment->get('auth_token') !== NULL && $url !== K::AUTH_URL) + if (PHP_SAPI === 'cli' && $cacheItem->isHit()) + { + $token = $cacheItem->get(); + } + else if ($url !== K::AUTH_URL && $sessionSegment->get('auth_token') !== NULL) { $token = $sessionSegment->get('auth_token'); if ( ! $cacheItem->isHit()) @@ -82,12 +86,8 @@ trait KitsuTrait { $cacheItem->save(); } } - else if ($sessionSegment->get('auth_token') === NULL && $cacheItem->isHit()) - { - $token = $cacheItem->get(); - } - if (NULL !== $token) + if ($token !== NULL) { $request = $request->setAuth('bearer', $token); } diff --git a/src/AnimeClient/Command/BaseCommand.php b/src/AnimeClient/Command/BaseCommand.php index 17e65b24..4308c00b 100644 --- a/src/AnimeClient/Command/BaseCommand.php +++ b/src/AnimeClient/Command/BaseCommand.php @@ -26,8 +26,8 @@ use Aviat\AnimeClient\API\{Anilist, CacheTrait, Kitsu}; use Aviat\AnimeClient\API\Kitsu\KitsuRequestBuilder; use Aviat\Banker\Pool; use Aviat\Ion\Config; -use Aviat\Ion\Di\{Container, ContainerAware}; -use ConsoleKit\{Command, ConsoleException}; +use Aviat\Ion\Di\{Container, ContainerInterface, ContainerAware}; +use ConsoleKit\{Colors, Command, ConsoleException}; use ConsoleKit\Widgets\Box; use Laminas\Diactoros\{Response, ServerRequestFactory}; use Monolog\Handler\RotatingFileHandler; @@ -44,15 +44,30 @@ abstract class BaseCommand extends Command { * Echo text in a box * * @param string $message + * @param string|int|null $fgColor + * @param string|int|null $bgColor * @return void */ - protected function echoBox($message): void + public function echoBox(string $message, $fgColor = NULL, $bgColor = NULL): void { try { - echo "\n"; + $len = strlen($message); + + // color message + $message = Colors::colorize($message, $fgColor, $bgColor); + $colorLen = strlen($message); + + // create the box $box = new Box($this->getConsole(), $message); + + if ($len !== $colorLen) + { + $box->setPadding((($colorLen - $len) / 2) + 2); + } + $box->write(); + echo "\n"; } catch (ConsoleException $e) @@ -61,12 +76,42 @@ abstract class BaseCommand extends Command { } } + public function echo(string $message): void + { + $this->_line($message); + } + + public function echoSuccess(string $message): void + { + $this->_line($message, Colors::GREEN | Colors::BOLD, Colors::BLACK); + } + + public function echoWarning(string $message): void + { + $this->_line($message, Colors::YELLOW | Colors::BOLD, Colors::BLACK); + } + + public function echoWarningBox(string $message): void + { + $this->echoBox($message, Colors::YELLOW | Colors::BOLD, Colors::BLACK); + } + + public function echoError(string $message): void + { + $this->_line($message, Colors::RED | Colors::BOLD, Colors::BLACK); + } + + public function echoErrorBox(string $message): void + { + $this->echoBox($message, Colors::RED | Colors::BOLD, Colors::BLACK); + } + /** * Setup the Di container * - * @return Container + * @return Containerinterface */ - protected function setupContainer(): Container + public function setupContainer(): ContainerInterface { $APP_DIR = realpath(__DIR__ . '/../../../app'); $APPCONF_DIR = realpath("{$APP_DIR}/appConf/"); @@ -82,7 +127,7 @@ abstract class BaseCommand extends Command { $configArray = array_replace_recursive($baseConfig, $config, $overrideConfig); - $di = static function ($configArray) use ($APP_DIR): Container { + $di = static function (array $configArray) use ($APP_DIR): Container { $container = new Container(); // ------------------------------------------------------------------------- @@ -103,9 +148,7 @@ abstract class BaseCommand extends Command { $container->setLogger($kitsu_request_logger, 'kitsu-request'); // Create Config Object - $container->set('config', static function() use ($configArray): Config { - return new Config($configArray); - }); + $container->set('config', fn () => new Config($configArray)); // Create Cache Object $container->set('cache', static function($container) { @@ -115,28 +158,20 @@ abstract class BaseCommand extends Command { }); // Create Aura Router Object - $container->set('aura-router', static function() { - return new RouterContainer; - }); + $container->set('aura-router', fn () => new RouterContainer); // Create Request/Response Objects - $container->set('request', static function() { - return ServerRequestFactory::fromGlobals( - $_SERVER, - $_GET, - $_POST, - $_COOKIE, - $_FILES - ); - }); - $container->set('response', static function(): Response { - return new Response; - }); + $container->set('request', fn () => ServerRequestFactory::fromGlobals( + $_SERVER, + $_GET, + $_POST, + $_COOKIE, + $_FILES + )); + $container->set('response', fn () => new Response); // Create session Object - $container->set('session', static function() { - return (new SessionFactory())->newInstance($_COOKIE); - }); + $container->set('session', fn () => (new SessionFactory())->newInstance($_COOKIE)); // Models $container->set('kitsu-model', static function($container): Kitsu\Model { @@ -175,21 +210,21 @@ abstract class BaseCommand extends Command { return $model; }); - $container->set('auth', static function($container): Kitsu\Auth { - return new Kitsu\Auth($container); - }); + $container->set('auth', fn ($container) => new Kitsu\Auth($container)); - $container->set('url-generator', static function($container): UrlGenerator { - return new UrlGenerator($container); - }); + $container->set('url-generator', fn ($container) => new UrlGenerator($container)); - $container->set('util', static function($container): Util { - return new Util($container); - }); + $container->set('util', fn ($container) => new Util($container)); return $container; }; return $di($configArray); } + + private function _line(string $message, $fgColor = NULL, $bgColor = NULL): void + { + $message = Colors::colorize($message, $fgColor, $bgColor); + $this->getConsole()->writeln($message); + } } \ No newline at end of file diff --git a/src/AnimeClient/Enum/ListType.php b/src/AnimeClient/Enum/ListType.php new file mode 100644 index 00000000..fc13ac37 --- /dev/null +++ b/src/AnimeClient/Enum/ListType.php @@ -0,0 +1,28 @@ + + * @copyright 2015 - 2020 Timothy J. Warren + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @version 5 + * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient + */ + +namespace Aviat\AnimeClient\Enum; + +use Aviat\Ion\Enum as BaseEnum; + +/** + * Types of lists + */ +final class ListType extends BaseEnum { + public const ANIME = 'anime'; + public const DRAMA = 'drama'; + public const MANGA = 'manga'; +} \ No newline at end of file diff --git a/src/AnimeClient/Enum/SyncAction.php b/src/AnimeClient/Enum/SyncAction.php new file mode 100644 index 00000000..1fe2156b --- /dev/null +++ b/src/AnimeClient/Enum/SyncAction.php @@ -0,0 +1,28 @@ + + * @copyright 2015 - 2020 Timothy J. Warren + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @version 5 + * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient + */ + +namespace Aviat\AnimeClient\Enum; + +use Aviat\Ion\Enum as BaseEnum; + +/** + * Types of actions when syncing lists from different APIs + */ +final class SyncAction extends BaseEnum { + public const CREATE = 'create'; + public const UPDATE = 'update'; + public const DELETE = 'delete'; +} \ No newline at end of file