Merge remote-tracking branch 'origin/develop'
timw4mail/HummingBirdAnimeClient/pipeline/head This commit looks good
Details
timw4mail/HummingBirdAnimeClient/pipeline/head This commit looks good
Details
This commit is contained in:
commit
1eeaa5c515
|
@ -1,5 +1,8 @@
|
|||
# Changelog
|
||||
|
||||
## Version 5.3
|
||||
* Update PHP requirement to 8.2
|
||||
|
||||
## Version 5.2
|
||||
* Updated PHP requirement to 8.1
|
||||
* Updated to support PHP 8.2
|
||||
|
|
|
@ -18,9 +18,9 @@ use const Aviat\AnimeClient\{
|
|||
ALPHA_SLUG_PATTERN,
|
||||
DEFAULT_CONTROLLER,
|
||||
DEFAULT_CONTROLLER_METHOD,
|
||||
KITSU_SLUG_PATTERN,
|
||||
NUM_PATTERN,
|
||||
SLUG_PATTERN,
|
||||
SLUG_SPACE_PATTERN,
|
||||
};
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
|
@ -28,7 +28,7 @@ use const Aviat\AnimeClient\{
|
|||
//
|
||||
// Maps paths to controllers and methods
|
||||
// -------------------------------------------------------------------------
|
||||
$routes = [
|
||||
$base_routes = [
|
||||
// ---------------------------------------------------------------------
|
||||
// AJAX Routes
|
||||
// ---------------------------------------------------------------------
|
||||
|
@ -60,7 +60,7 @@ $routes = [
|
|||
'path' => '/anime/details/{id}',
|
||||
'action' => 'details',
|
||||
'tokens' => [
|
||||
'id' => SLUG_PATTERN,
|
||||
'id' => KITSU_SLUG_PATTERN,
|
||||
],
|
||||
],
|
||||
'anime.delete' => [
|
||||
|
@ -97,7 +97,7 @@ $routes = [
|
|||
'path' => '/manga/details/{id}',
|
||||
'action' => 'details',
|
||||
'tokens' => [
|
||||
'id' => SLUG_PATTERN,
|
||||
'id' => KITSU_SLUG_PATTERN,
|
||||
],
|
||||
],
|
||||
// ---------------------------------------------------------------------
|
||||
|
@ -191,13 +191,13 @@ $routes = [
|
|||
'character' => [
|
||||
'path' => '/character/{slug}',
|
||||
'tokens' => [
|
||||
'slug' => SLUG_PATTERN,
|
||||
'slug' => KITSU_SLUG_PATTERN,
|
||||
],
|
||||
],
|
||||
'person' => [
|
||||
'path' => '/people/{slug}',
|
||||
'tokens' => [
|
||||
'slug' => SLUG_PATTERN,
|
||||
'slug' => KITSU_SLUG_PATTERN,
|
||||
],
|
||||
],
|
||||
'default_user_info' => [
|
||||
|
@ -291,8 +291,8 @@ $routes = [
|
|||
'path' => '/{controller}/edit/{id}/{status}',
|
||||
'action' => 'edit',
|
||||
'tokens' => [
|
||||
'id' => SLUG_PATTERN,
|
||||
'status' => SLUG_SPACE_PATTERN,
|
||||
'id' => KITSU_SLUG_PATTERN,
|
||||
'status' => SLUG_PATTERN,
|
||||
],
|
||||
],
|
||||
'list' => [
|
||||
|
@ -315,15 +315,10 @@ $defaultMap = [
|
|||
'verb' => 'get',
|
||||
];
|
||||
|
||||
foreach ($routes as &$route)
|
||||
$routes = [];
|
||||
foreach ($base_routes as $name => $route)
|
||||
{
|
||||
foreach ($defaultMap as $key => $val)
|
||||
{
|
||||
if ( ! array_key_exists($key, $route))
|
||||
{
|
||||
$route[$key] = $val;
|
||||
}
|
||||
}
|
||||
$routes[$name] = array_merge($defaultMap, $route);
|
||||
}
|
||||
|
||||
return $routes;
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
################################################################################
|
||||
# Anilist API #
|
||||
################################################################################
|
||||
client_id = "your_client_id"
|
||||
client_secret = "your_client_secret"
|
||||
username = "user123"
|
|
@ -0,0 +1,24 @@
|
|||
<?php if ( ! $hasRequiredAnilistConfig): ?>
|
||||
<p class="static-message info">See the <a href="https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient/wiki/anilist">wiki</a> to learn how to set up Anilist integration. </p>
|
||||
<?php else: ?>
|
||||
<?php $auth = $anilistModel->checkAuth(); ?>
|
||||
<?php if (array_key_exists('errors', $auth)): ?>
|
||||
<p class="static-message error">Anilist API Client is Not Authorized.</p>
|
||||
<?= $helper->a(
|
||||
$url->generate('anilist-redirect'),
|
||||
'Link Anilist Account',
|
||||
['class' => 'bracketed user-btn']
|
||||
) ?>
|
||||
<?php else: ?>
|
||||
<?php $expires = $config->get(['anilist', 'access_token_expires']); ?>
|
||||
<p class="static-message info">
|
||||
Linked to Anilist. Your access token will expire around <?= date('F j, Y, g:i a T', $expires) ?>
|
||||
</p>
|
||||
<?php require __DIR__ . '/_form.php' ?>
|
||||
<?= $helper->a(
|
||||
$url->generate('anilist-redirect'),
|
||||
'Update Access Token',
|
||||
['class' => 'bracketed user-btn']
|
||||
) ?>
|
||||
<?php endif ?>
|
||||
<?php endif ?>
|
|
@ -28,29 +28,11 @@ $nestedPrefix = 'config';
|
|||
/>
|
||||
<label for="settings-tab<?= $i ?>"><h3><?= $sectionMapping[$section] ?></h3></label>
|
||||
<section class="content">
|
||||
<?php if ($section === 'anilist'): ?>
|
||||
<?php $auth = $anilistModel->checkAuth(); ?>
|
||||
<?php if (array_key_exists('errors', $auth)): ?>
|
||||
<p class="static-message error">Not Authorized.</p>
|
||||
<?= $helper->a(
|
||||
$url->generate('anilist-redirect'),
|
||||
'Link Anilist Account'
|
||||
) ?>
|
||||
<?php else: ?>
|
||||
<?php $expires = $config->get(['anilist', 'access_token_expires']); ?>
|
||||
<p class="static-message info">
|
||||
Linked to Anilist. Your access token will expire around <?= date('F j, Y, g:i a T', $expires) ?>
|
||||
</p>
|
||||
<?php require __DIR__ . '/_form.php' ?>
|
||||
<?= $helper->a(
|
||||
$url->generate('anilist-redirect'),
|
||||
'Update Access Token',
|
||||
['class' => 'bracketed user-btn']
|
||||
) ?>
|
||||
<?php endif ?>
|
||||
<?php else: ?>
|
||||
<?php require __DIR__ . '/_form.php' ?>
|
||||
<?php endif ?>
|
||||
<?php
|
||||
($section === 'anilist')
|
||||
? require __DIR__ . '/_anilist.php'
|
||||
: require __DIR__ . '/_form.php'
|
||||
?>
|
||||
</section>
|
||||
<?php $i++; ?>
|
||||
<?php endforeach ?>
|
||||
|
@ -62,7 +44,3 @@ $nestedPrefix = 'config';
|
|||
<button type="submit">Save Changes</button>
|
||||
</main>
|
||||
</form>
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -30,9 +30,9 @@
|
|||
"lock": false
|
||||
},
|
||||
"require": {
|
||||
"amphp/http-client": "^4.5.0",
|
||||
"amphp/http-client": "^v5.0.0",
|
||||
"aura/html": "^2.5.0",
|
||||
"aura/router": "^3.1.0",
|
||||
"aura/router": "^3.3.0",
|
||||
"aura/session": "^2.1.0",
|
||||
"aviat/banker": "^4.1.2",
|
||||
"aviat/query": "^4.1.0",
|
||||
|
@ -46,8 +46,8 @@
|
|||
"laminas/laminas-httphandlerrunner": "^2.6.1",
|
||||
"maximebf/consolekit": "^1.0.3",
|
||||
"monolog/monolog": "^3.0.0",
|
||||
"php": ">= 8.1.0",
|
||||
"psr/http-message": "^1.0.1",
|
||||
"php": ">= 8.2.0",
|
||||
"psr/http-message": "^1.0.1 || ^2.0.0",
|
||||
"symfony/polyfill-mbstring": "^1.0.0",
|
||||
"symfony/polyfill-util": "^1.0.0",
|
||||
"tracy/tracy": "^2.8.0",
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
namespace Aviat\AnimeClient;
|
||||
|
||||
use Amp\Future;
|
||||
use Amp\Http\Client\{HttpClient, HttpClientBuilder, Request, Response};
|
||||
|
||||
use Aviat\Ion\{ConfigInterface, ImageBuilder};
|
||||
|
@ -23,7 +24,7 @@ use Throwable;
|
|||
|
||||
use Yosymfony\Toml\{Toml, TomlBuilder};
|
||||
|
||||
use function Amp\Promise\wait;
|
||||
use function Amp\async;
|
||||
use function Aviat\Ion\_dir;
|
||||
|
||||
const SECONDS_IN_MINUTE = 60;
|
||||
|
@ -210,7 +211,11 @@ function getResponse(Request|string $request): Response
|
|||
$request = new Request($request);
|
||||
}
|
||||
|
||||
return wait($client->request($request));
|
||||
$future = async(fn () => $client->request($request));
|
||||
|
||||
[$response] = Future\await([$future]);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -14,17 +14,16 @@
|
|||
|
||||
namespace Aviat\AnimeClient\API;
|
||||
|
||||
// use Amp\Http\Client\Form;
|
||||
use Amp\Http\Client\Body\FormBody;
|
||||
use Amp\Future;
|
||||
use Amp\Http\Client\Form;
|
||||
use Amp\Http\Client\{HttpClientBuilder, HttpException, Request};
|
||||
use Aviat\Ion\Json;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use Psr\Log\LoggerAwareTrait;
|
||||
use Throwable;
|
||||
// use function Amp\async;
|
||||
// use function Amp\Future\await;
|
||||
use function Amp\Promise\wait;
|
||||
|
||||
use function Amp\async;
|
||||
use function Aviat\AnimeClient\getResponse;
|
||||
use const Aviat\AnimeClient\USER_AGENT;
|
||||
|
||||
|
@ -110,7 +109,7 @@ abstract class APIRequestBuilder
|
|||
/**
|
||||
* Set the request body
|
||||
*/
|
||||
public function setBody(FormBody|string $body): self
|
||||
public function setBody(Form|string $body): self
|
||||
{
|
||||
$this->request->setBody($body);
|
||||
|
||||
|
@ -124,8 +123,9 @@ abstract class APIRequestBuilder
|
|||
*/
|
||||
public function setFormFields(array $fields): self
|
||||
{
|
||||
$body = new FormBody();
|
||||
$body->addFields($fields);
|
||||
$body = new Form;
|
||||
|
||||
array_walk($fields, fn ($content, $name) => $body->addField($name, $content));
|
||||
|
||||
return $this->setBody($body);
|
||||
}
|
||||
|
@ -145,7 +145,12 @@ abstract class APIRequestBuilder
|
|||
*/
|
||||
public function setHeader(string $name, ?string $value = NULL): self
|
||||
{
|
||||
if (NULL === $value)
|
||||
if ($name === '')
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
if ($value === NULL)
|
||||
{
|
||||
$this->unsetHeader($name);
|
||||
}
|
||||
|
@ -164,10 +169,7 @@ abstract class APIRequestBuilder
|
|||
*/
|
||||
public function setHeaders(array $headers): self
|
||||
{
|
||||
foreach ($headers as $name => $value)
|
||||
{
|
||||
$this->setHeader($name, $value);
|
||||
}
|
||||
array_walk($headers, fn ($value, $name) => $this->setHeader($name, $value));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
@ -206,11 +208,9 @@ abstract class APIRequestBuilder
|
|||
$this->logger?->debug('API Request', [
|
||||
'request_url' => $this->request->getUri(),
|
||||
'request_headers' => $this->request->getHeaders(),
|
||||
'request_body' => wait(
|
||||
$this->request->getBody()
|
||||
->createBodyStream()
|
||||
->read()
|
||||
),
|
||||
'request_body' => $this->request->getBody()
|
||||
->getContent()
|
||||
->read(),
|
||||
]);
|
||||
|
||||
return $this->request;
|
||||
|
@ -225,7 +225,7 @@ abstract class APIRequestBuilder
|
|||
{
|
||||
$response = getResponse($request);
|
||||
|
||||
return wait($response->getBody()->buffer());
|
||||
return $response->getBody()->buffer();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -24,7 +24,6 @@ use Aviat\Ion\Di\Exception\{ContainerException, NotFoundException};
|
|||
use Aviat\Ion\Json;
|
||||
use InvalidArgumentException;
|
||||
use Throwable;
|
||||
use function Amp\Promise\wait;
|
||||
|
||||
/**
|
||||
* Anilist API Model
|
||||
|
@ -67,7 +66,7 @@ final class Model
|
|||
|
||||
$response = $this->requestBuilder->getResponseFromRequest($request);
|
||||
|
||||
return Json::decode(wait($response->getBody()->buffer()));
|
||||
return Json::decode($response->getBody()->buffer());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -106,7 +105,7 @@ final class Model
|
|||
public function createListItem(array $data, string $type = 'anime'): ?Request
|
||||
{
|
||||
$mediaId = $this->getMediaId($data, $type);
|
||||
if ($mediaId === NULL)
|
||||
if ($mediaId === NULL || $mediaId === "undefined")
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
|
|
@ -22,8 +22,6 @@ use Aviat\Ion\{Json, JsonException};
|
|||
use LogicException;
|
||||
use Throwable;
|
||||
|
||||
use function Amp\Promise\wait;
|
||||
|
||||
use function Aviat\AnimeClient\getResponse;
|
||||
use function in_array;
|
||||
use const Aviat\AnimeClient\USER_AGENT;
|
||||
|
@ -170,7 +168,7 @@ final class RequestBuilder extends APIRequestBuilder
|
|||
$request = $this->mutateRequest($name, $variables);
|
||||
$response = $this->getResponseFromRequest($request);
|
||||
|
||||
return Json::decode(wait($response->getBody()->buffer()));
|
||||
return Json::decode($response->getBody()->buffer());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -246,7 +244,7 @@ final class RequestBuilder extends APIRequestBuilder
|
|||
$logger?->warning('Non 200 response for POST api call', (array) $response->getBody());
|
||||
}
|
||||
|
||||
$rawBody = wait($response->getBody()->buffer());
|
||||
$rawBody = $response->getBody()->buffer();
|
||||
|
||||
try
|
||||
{
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* Hummingbird Anime List Client
|
||||
*
|
||||
* An API client for Kitsu to manage anime and manga watch lists
|
||||
*
|
||||
* PHP version 8.1
|
||||
*
|
||||
* @copyright 2015 - 2023 Timothy J. Warren <tim@timshome.page>
|
||||
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
||||
* @version 5.2
|
||||
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
|
||||
*/
|
||||
|
||||
namespace Aviat\AnimeClient\API\Kitsu\Enum;
|
||||
|
||||
use Aviat\Ion\Enum as BaseEnum;
|
||||
|
||||
/**
|
||||
* Status of when anime is being/was/will be aired
|
||||
*/
|
||||
final class MediaStatus extends BaseEnum
|
||||
{
|
||||
public const CURRENT = 'CURRENT';
|
||||
public const PLANNED = 'PLANNED';
|
||||
public const ON_HOLD = 'ON_HOLD';
|
||||
public const DROPPED = 'DROPPED';
|
||||
public const COMPLETED = 'COMPLETED';
|
||||
}
|
||||
// End of MediaStatus
|
|
@ -14,7 +14,6 @@
|
|||
|
||||
namespace Aviat\AnimeClient\API\Kitsu;
|
||||
|
||||
use Amp;
|
||||
use Aviat\AnimeClient\API\Kitsu\Transformer\{
|
||||
AnimeHistoryTransformer,
|
||||
AnimeListTransformer,
|
||||
|
@ -28,6 +27,7 @@ use Aviat\AnimeClient\API\{
|
|||
CacheTrait,
|
||||
Enum\AnimeWatchingStatus\Kitsu as KitsuWatchingStatus,
|
||||
Enum\MangaReadingStatus\Kitsu as KitsuReadingStatus,
|
||||
Kitsu\Enum\MediaStatus,
|
||||
Mapping\AnimeWatchingStatus,
|
||||
Mapping\MangaReadingStatus
|
||||
};
|
||||
|
@ -40,7 +40,6 @@ use Aviat\Ion\{
|
|||
Json
|
||||
};
|
||||
use Generator;
|
||||
use function Amp\Promise\wait;
|
||||
use function Aviat\AnimeClient\getApiClient;
|
||||
use const Aviat\AnimeClient\SESSION_SEGMENT;
|
||||
|
||||
|
@ -87,7 +86,7 @@ final class Model
|
|||
'password' => $password,
|
||||
],
|
||||
]);
|
||||
$data = Json::decode(wait($response->getBody()->buffer()));
|
||||
$data = Json::decode($response->getBody()->buffer());
|
||||
|
||||
if (array_key_exists('error', $data))
|
||||
{
|
||||
|
@ -124,7 +123,7 @@ final class Model
|
|||
'refresh_token' => $token,
|
||||
],
|
||||
]);
|
||||
$data = Json::decode(wait($response->getBody()->buffer()));
|
||||
$data = Json::decode($response->getBody()->buffer());
|
||||
|
||||
if (array_key_exists('error', $data))
|
||||
{
|
||||
|
@ -553,27 +552,7 @@ final class Model
|
|||
*/
|
||||
public function getThumbList(string $type): array
|
||||
{
|
||||
$statuses = [
|
||||
'CURRENT',
|
||||
'PLANNED',
|
||||
'ON_HOLD',
|
||||
'DROPPED',
|
||||
'COMPLETED',
|
||||
];
|
||||
|
||||
$pages = [];
|
||||
|
||||
// Although I can fetch the whole list without segregating by status,
|
||||
// this way is much faster...
|
||||
foreach ($statuses as $status)
|
||||
{
|
||||
foreach ($this->getPages($this->getThumbListPages(...), strtoupper($type), $status) as $page)
|
||||
{
|
||||
$pages[] = $page;
|
||||
}
|
||||
}
|
||||
|
||||
return array_merge(...$pages);
|
||||
return $this->getZippedListPerStatus('GetLibraryThumbs', $type);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -583,27 +562,7 @@ final class Model
|
|||
*/
|
||||
public function getSyncList(string $type): array
|
||||
{
|
||||
$statuses = [
|
||||
'CURRENT',
|
||||
'PLANNED',
|
||||
'ON_HOLD',
|
||||
'DROPPED',
|
||||
'COMPLETED',
|
||||
];
|
||||
|
||||
$pages = [];
|
||||
|
||||
// Although I can fetch the whole list without segregating by status,
|
||||
// this way is much faster...
|
||||
foreach ($statuses as $status)
|
||||
{
|
||||
foreach ($this->getPages($this->getSyncPages(...), strtoupper($type), $status) as $page)
|
||||
{
|
||||
$pages[] = $page;
|
||||
}
|
||||
}
|
||||
|
||||
return array_merge(...$pages);
|
||||
return $this->getZippedListPerStatus('GetSyncLibrary', $type);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -619,15 +578,39 @@ final class Model
|
|||
}
|
||||
|
||||
/**
|
||||
* Get the raw anime/manga list from GraphQL
|
||||
* Get all the raw data for the current list, chunking by status
|
||||
*
|
||||
* @return mixed[]
|
||||
* @param string $queryName - The GraphQL query
|
||||
* @param string $type - Media type (anime, manga)
|
||||
* @return array
|
||||
*/
|
||||
protected function getList(string $type, string $status = ''): array
|
||||
protected function getZippedListPerStatus(string $queryName, string $type): array
|
||||
{
|
||||
$statusPages = [];
|
||||
|
||||
// Although I can fetch the whole list without segregating by status,
|
||||
// this way is much faster...
|
||||
foreach (MediaStatus::getConstList() as $status)
|
||||
{
|
||||
$statusPages[] = $this->getZippedList($queryName, $type, $status);
|
||||
}
|
||||
|
||||
return array_merge(...$statusPages);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the raw data for the current list
|
||||
*
|
||||
* @param string $queryName - The GraphQL query
|
||||
* @param string $type - Media type (anime, manga)
|
||||
* @param string $status - Media 'consumption' status
|
||||
* @return array
|
||||
*/
|
||||
protected function getZippedList(string $queryName, string $type, string $status): array
|
||||
{
|
||||
$pages = [];
|
||||
|
||||
foreach ($this->getPages($this->getListPages(...), strtoupper($type), strtoupper($status)) as $page)
|
||||
foreach ($this->getListPages($queryName, $type, $status) as $page)
|
||||
{
|
||||
$pages[] = $page;
|
||||
}
|
||||
|
@ -635,158 +618,92 @@ final class Model
|
|||
return array_merge(...$pages);
|
||||
}
|
||||
|
||||
private function getListPages(string $type, string $status = ''): Amp\Iterator
|
||||
/**
|
||||
* Get the raw anime/manga list from GraphQL
|
||||
*
|
||||
* @return mixed[]
|
||||
*/
|
||||
protected function getList(string $type, string $status = ''): array
|
||||
{
|
||||
return $this->getZippedList('GetLibrary', $type, $status);
|
||||
}
|
||||
|
||||
/**
|
||||
* A generator returning the relevant snippet for each 'page' of
|
||||
* a media list request
|
||||
*
|
||||
* @param string $queryName - The GraphQL query
|
||||
* @param string $type - Media type (anime, manga)
|
||||
* @param string $status - Media 'consumption' status
|
||||
* @return iterable
|
||||
*/
|
||||
private function getListPages(string $queryName, string $type, string $status): iterable
|
||||
{
|
||||
$cursor = '';
|
||||
$username = $this->getUsername();
|
||||
|
||||
return new Amp\Producer(function (callable $emit) use ($type, $status, $cursor, $username): Generator {
|
||||
while (TRUE)
|
||||
{
|
||||
$vars = [
|
||||
'type' => $type,
|
||||
'slug' => $username,
|
||||
];
|
||||
if ($status !== '')
|
||||
{
|
||||
$vars['status'] = $status;
|
||||
}
|
||||
|
||||
if ($cursor !== '')
|
||||
{
|
||||
$vars['after'] = $cursor;
|
||||
}
|
||||
|
||||
$request = $this->requestBuilder->queryRequest('GetLibrary', $vars);
|
||||
$response = yield getApiClient()->request($request);
|
||||
$json = yield $response->getBody()->buffer();
|
||||
|
||||
$rawData = Json::decode($json);
|
||||
$data = $rawData['data']['findProfileBySlug']['library']['all'] ?? [];
|
||||
$page = $data['pageInfo'] ?? [];
|
||||
if (empty($data))
|
||||
{
|
||||
// Clear session, in case the error is an invalid token.
|
||||
$segment = $this->container->get('session')
|
||||
->getSegment(SESSION_SEGMENT);
|
||||
$segment->clear();
|
||||
|
||||
// @TODO Proper Error logging
|
||||
dump($rawData);
|
||||
|
||||
exit();
|
||||
}
|
||||
|
||||
$cursor = $page['endCursor'];
|
||||
|
||||
yield $emit($data['nodes']);
|
||||
|
||||
if ($page['hasNextPage'] !== TRUE)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private function getSyncPages(string $type, string $status): Amp\Iterator
|
||||
{
|
||||
$cursor = '';
|
||||
$username = $this->getUsername();
|
||||
|
||||
return new Amp\Producer(function (callable $emit) use ($type, $status, $cursor, $username): Generator {
|
||||
while (TRUE)
|
||||
{
|
||||
$vars = [
|
||||
'type' => $type,
|
||||
'slug' => $username,
|
||||
'status' => $status,
|
||||
];
|
||||
if ($cursor !== '')
|
||||
{
|
||||
$vars['after'] = $cursor;
|
||||
}
|
||||
|
||||
$request = $this->requestBuilder->queryRequest('GetSyncLibrary', $vars);
|
||||
$response = yield getApiClient()->request($request);
|
||||
$json = yield $response->getBody()->buffer();
|
||||
|
||||
$rawData = Json::decode($json);
|
||||
$data = $rawData['data']['findProfileBySlug']['library']['all'] ?? [];
|
||||
$page = $data['pageInfo'];
|
||||
if (empty($data))
|
||||
{
|
||||
dump($rawData);
|
||||
|
||||
exit();
|
||||
}
|
||||
|
||||
$cursor = $page['endCursor'];
|
||||
|
||||
yield $emit($data['nodes']);
|
||||
|
||||
if ($page['hasNextPage'] === FALSE)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private function getThumbListPages(string $type, string $status): Amp\Iterator
|
||||
{
|
||||
$cursor = '';
|
||||
$username = $this->getUsername();
|
||||
|
||||
return new Amp\Producer(function (callable $emit) use ($type, $status, $cursor, $username): Generator {
|
||||
while (TRUE)
|
||||
{
|
||||
$vars = [
|
||||
'type' => $type,
|
||||
'slug' => $username,
|
||||
'status' => $status,
|
||||
];
|
||||
if ($cursor !== '')
|
||||
{
|
||||
$vars['after'] = $cursor;
|
||||
}
|
||||
|
||||
$request = $this->requestBuilder->queryRequest('GetLibraryThumbs', $vars);
|
||||
$response = yield getApiClient()->request($request);
|
||||
$json = yield $response->getBody()->buffer();
|
||||
|
||||
$rawData = Json::decode($json);
|
||||
$data = $rawData['data']['findProfileBySlug']['library']['all'] ?? [];
|
||||
$page = $data['pageInfo'];
|
||||
if (empty($data))
|
||||
{
|
||||
dump($rawData);
|
||||
|
||||
exit();
|
||||
}
|
||||
|
||||
$cursor = $page['endCursor'];
|
||||
|
||||
yield $emit($data['nodes']);
|
||||
|
||||
if ($page['hasNextPage'] === FALSE)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private function getPages(callable $method, mixed ...$args): Generator
|
||||
{
|
||||
$items = $method(...$args);
|
||||
|
||||
while (wait($items->advance()))
|
||||
while (TRUE)
|
||||
{
|
||||
yield $items->getCurrent();
|
||||
$vars = [
|
||||
'type' => strtoupper($type),
|
||||
'slug' => $username,
|
||||
];
|
||||
if ($status !== '')
|
||||
{
|
||||
$vars['status'] = strtoupper($status);
|
||||
}
|
||||
if ($cursor !== '')
|
||||
{
|
||||
$vars['after'] = $cursor;
|
||||
}
|
||||
|
||||
$request = $this->requestBuilder->queryRequest($queryName, $vars);
|
||||
$response = getApiClient()->request($request);
|
||||
$json = $response->getBody()->buffer();
|
||||
|
||||
$rawData = Json::decode($json);
|
||||
$data = $rawData['data']['findProfileBySlug']['library']['all'] ?? [];
|
||||
$page = $data['pageInfo'] ?? [];
|
||||
if (empty($data))
|
||||
{
|
||||
// Clear session, in case the error is an invalid token.
|
||||
$segment = $this->container->get('session')
|
||||
->getSegment(SESSION_SEGMENT);
|
||||
$segment->clear();
|
||||
|
||||
// @TODO Proper Error logging
|
||||
dump($rawData);
|
||||
|
||||
exit();
|
||||
}
|
||||
|
||||
$cursor = $page['endCursor'];
|
||||
|
||||
yield $data['nodes'];
|
||||
|
||||
if ($page['hasNextPage'] === FALSE || $page === [])
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function getListCount(string $type, string $status = ''): int
|
||||
{
|
||||
$args = [
|
||||
'type' => strtoupper($type),
|
||||
'slug' => $this->getUsername(),
|
||||
];
|
||||
if ($status !== '')
|
||||
{
|
||||
$args['status'] = strtoupper($status);
|
||||
}
|
||||
|
||||
$res = $this->requestBuilder->runQuery('GetLibraryCount', $args);
|
||||
|
||||
return $res['data']['findProfileBySlug']['library']['all']['totalCount'];
|
||||
}
|
||||
|
||||
protected function getUserId(): string
|
||||
{
|
||||
static $userId = NULL;
|
||||
|
@ -808,20 +725,4 @@ final class Model
|
|||
->get('config')
|
||||
->get(['kitsu_username']);
|
||||
}
|
||||
|
||||
private function getListCount(string $type, string $status = ''): int
|
||||
{
|
||||
$args = [
|
||||
'type' => strtoupper($type),
|
||||
'slug' => $this->getUsername(),
|
||||
];
|
||||
if ($status !== '')
|
||||
{
|
||||
$args['status'] = strtoupper($status);
|
||||
}
|
||||
|
||||
$res = $this->requestBuilder->runQuery('GetLibraryCount', $args);
|
||||
|
||||
return $res['data']['findProfileBySlug']['library']['all']['totalCount'];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,6 @@ use Aviat\Ion\Di\{ContainerAware, ContainerInterface};
|
|||
use Aviat\Ion\{Event, Json, JsonException};
|
||||
|
||||
use LogicException;
|
||||
use function Amp\Promise\wait;
|
||||
use function Aviat\AnimeClient\getResponse;
|
||||
use function in_array;
|
||||
use const Aviat\AnimeClient\{SESSION_SEGMENT, USER_AGENT};
|
||||
|
@ -125,13 +124,10 @@ final class RequestBuilder extends APIRequestBuilder
|
|||
if ( ! in_array($response->getStatus(), $validResponseCodes, TRUE))
|
||||
{
|
||||
$logger = $this->container->getLogger('kitsu-graphql');
|
||||
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());
|
||||
}
|
||||
|
||||
return Json::decode(wait($response->getBody()->buffer()));
|
||||
return Json::decode($response->getBody()->buffer());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -148,13 +144,10 @@ final class RequestBuilder extends APIRequestBuilder
|
|||
if ( ! in_array($response->getStatus(), $validResponseCodes, TRUE))
|
||||
{
|
||||
$logger = $this->container->getLogger('kitsu-graphql');
|
||||
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());
|
||||
}
|
||||
|
||||
return Json::decode(wait($response->getBody()->buffer()));
|
||||
return Json::decode($response->getBody()->buffer());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -14,13 +14,11 @@
|
|||
|
||||
namespace Aviat\AnimeClient\API;
|
||||
|
||||
use Amp\Http\Client\{HttpException, Request};
|
||||
use Generator;
|
||||
use Amp\Future;
|
||||
use Amp\Http\Client\{Request, Response};
|
||||
use Throwable;
|
||||
use function Amp\call;
|
||||
|
||||
// use function Amp\Future\{async, await};
|
||||
use function Amp\Promise\{all, wait};
|
||||
use function Amp\async;
|
||||
use function Aviat\AnimeClient\getApiClient;
|
||||
|
||||
/**
|
||||
|
@ -69,7 +67,14 @@ final class ParallelAPIRequest
|
|||
*/
|
||||
public function makeRequests(): array
|
||||
{
|
||||
return $this->makeRequestOld();
|
||||
$futures = [];
|
||||
|
||||
foreach ($this->requests as $key => $url)
|
||||
{
|
||||
$futures[$key] = async(static fn () => self::bodyHandler($url));
|
||||
}
|
||||
|
||||
return Future\await($futures);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -78,54 +83,6 @@ final class ParallelAPIRequest
|
|||
* @throws Throwable
|
||||
*/
|
||||
public function getResponses(): array
|
||||
{
|
||||
return $this->getResponsesOld();
|
||||
}
|
||||
|
||||
private function makeRequestOld(): array
|
||||
{
|
||||
$client = getApiClient();
|
||||
|
||||
$promises = [];
|
||||
|
||||
foreach ($this->requests as $key => $url)
|
||||
{
|
||||
$promises[$key] = call(static function () use ($client, $url): Generator {
|
||||
$response = yield $client->request($url);
|
||||
return yield $response->getBody()->buffer();
|
||||
});
|
||||
}
|
||||
|
||||
return wait(all($promises));
|
||||
}
|
||||
|
||||
private function makeRequestsNew(): array
|
||||
{
|
||||
$futures = [];
|
||||
|
||||
foreach ($this->requests as $key => $url)
|
||||
{
|
||||
$futures[$key] = async(static fn () => self::bodyHandler($url));
|
||||
}
|
||||
|
||||
return await($futures);
|
||||
}
|
||||
|
||||
private function getResponsesOld(): array
|
||||
{
|
||||
$client = getApiClient();
|
||||
|
||||
$promises = [];
|
||||
|
||||
foreach ($this->requests as $key => $url)
|
||||
{
|
||||
$promises[$key] = call(static fn () => yield $client->request($url));
|
||||
}
|
||||
|
||||
return wait(all($promises));
|
||||
}
|
||||
|
||||
private function getResponsesNew(): array
|
||||
{
|
||||
$futures = [];
|
||||
|
||||
|
@ -134,7 +91,7 @@ final class ParallelAPIRequest
|
|||
$futures[$key] = async(static fn () => self::responseHandler($url));
|
||||
}
|
||||
|
||||
return await($futures);
|
||||
return Future\await($futures);
|
||||
}
|
||||
|
||||
private static function bodyHandler(string|Request $uri): string
|
||||
|
@ -150,7 +107,7 @@ final class ParallelAPIRequest
|
|||
return $response->getBody()->buffer();
|
||||
}
|
||||
|
||||
private static function responseHandler(string|Request $uri)
|
||||
private static function responseHandler(string|Request $uri): Response
|
||||
{
|
||||
$client = getApiClient();
|
||||
|
||||
|
|
|
@ -17,9 +17,7 @@ namespace Aviat\AnimeClient\Controller;
|
|||
use Aviat\AnimeClient\Controller as BaseController;
|
||||
use Aviat\Ion\Attribute\{Controller, Route};
|
||||
use Throwable;
|
||||
use function Amp\Promise\wait;
|
||||
use function Aviat\AnimeClient\{createPlaceholderImage, getResponse};
|
||||
use function imagepalletetotruecolor;
|
||||
|
||||
use function in_array;
|
||||
|
||||
|
@ -130,7 +128,7 @@ final class Images extends BaseController
|
|||
return;
|
||||
}
|
||||
|
||||
$data = wait($response->getBody()->buffer());
|
||||
$data = $response->getBody()->buffer();
|
||||
|
||||
$size = getimagesizefromstring($data);
|
||||
if ($size === FALSE)
|
||||
|
|
|
@ -57,13 +57,15 @@ final class Settings extends BaseController
|
|||
$auth = $this->container->get('auth');
|
||||
$form = $this->settingsModel->getSettingsForm();
|
||||
|
||||
$hasAnilistLogin = $this->config->has(['anilist', 'access_token']);
|
||||
$hasRequiredAnilistConfig = $this->config->has(['anilist', 'client_secret']) &&
|
||||
$this->config->has(['anilist', 'client_id']) &&
|
||||
$this->config->has(['anilist', 'username']);
|
||||
|
||||
$this->outputHTML('settings/settings', [
|
||||
'anilistModel' => $this->anilistModel,
|
||||
'auth' => $auth,
|
||||
'form' => $form,
|
||||
'hasAnilistLogin' => $hasAnilistLogin,
|
||||
'hasRequiredAnilistConfig' => $hasRequiredAnilistConfig,
|
||||
'config' => $this->config,
|
||||
'title' => $this->config->get('whose_list') . "'s Settings",
|
||||
]);
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
namespace Aviat\AnimeClient;
|
||||
|
||||
use Aura\Router\Generator;
|
||||
use Aviat\Ion\ConfigInterface;
|
||||
use Aviat\Ion\Di\ContainerInterface;
|
||||
use Aviat\Ion\Di\Exception\{ContainerException, NotFoundException};
|
||||
|
@ -24,7 +25,7 @@ use Psr\Http\Message\ServerRequestInterface;
|
|||
/**
|
||||
* Base for routing/url classes
|
||||
*/
|
||||
class RoutingBase
|
||||
abstract class RoutingBase
|
||||
{
|
||||
/**
|
||||
* Config Object
|
||||
|
@ -36,6 +37,11 @@ class RoutingBase
|
|||
*/
|
||||
protected ServerRequestInterface $request;
|
||||
|
||||
/**
|
||||
* Aura url generator
|
||||
*/
|
||||
protected Generator $routerUrl;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
|
@ -47,6 +53,7 @@ class RoutingBase
|
|||
{
|
||||
$this->config = $container->get('config');
|
||||
$this->request = $container->get('request');
|
||||
$this->routerUrl = $container->get('aura-router')->getGenerator();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -54,6 +54,11 @@ class UrlGenerator extends RoutingBase
|
|||
return implode('/', $args);
|
||||
}
|
||||
|
||||
public function fromRoute(string $route, array $args = []): string
|
||||
{
|
||||
return $this->hostUrl($this->routerUrl->generate($route, $args));
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a proper url from the path
|
||||
*/
|
||||
|
@ -81,9 +86,7 @@ class UrlGenerator extends RoutingBase
|
|||
|
||||
$path = implode('/', $pathSegments);
|
||||
|
||||
$scheme = $this->config->get('secure_urls') !== FALSE ? 'https:' : 'http:';
|
||||
|
||||
return "{$scheme}//{$this->host}/{$path}";
|
||||
return $this->hostUrl($path);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -103,6 +106,14 @@ class UrlGenerator extends RoutingBase
|
|||
|
||||
throw new InvalidArgumentException("Invalid default type: '{$type}'");
|
||||
}
|
||||
|
||||
private function hostUrl(string $path): string
|
||||
{
|
||||
$path = trim($path, '/');
|
||||
$scheme = $this->config->get('secure_urls') !== FALSE ? 'https:' : 'http:';
|
||||
|
||||
return "{$scheme}//{$this->host}/{$path}";
|
||||
}
|
||||
}
|
||||
|
||||
// End of UrlGenerator.php
|
||||
|
|
|
@ -25,10 +25,14 @@ const SRC_DIR = __DIR__;
|
|||
const USER_AGENT = "Tim's Anime Client/5.2";
|
||||
|
||||
// Regex patterns
|
||||
const ALPHA_SLUG_PATTERN = '[a-z_]+';
|
||||
const ALPHA_SLUG_PATTERN = '[a-zA-Z_]+';
|
||||
const NUM_PATTERN = '[0-9]+';
|
||||
const SLUG_PATTERN = '[a-z0-9\-]+';
|
||||
const SLUG_SPACE_PATTERN = '[a-zA-Z_\- ]+';
|
||||
/**
|
||||
* Eugh...url slugs can have weird characters
|
||||
* So...if it's not a forward slash, sure it's valid 😅
|
||||
*/
|
||||
const KITSU_SLUG_PATTERN = '[^\/]+';
|
||||
const SLUG_PATTERN = '[a-zA-Z0-9\- ]+';
|
||||
|
||||
// Why doesn't this already exist?
|
||||
const MILLI_FROM_NANO = 1000 * 1000;
|
||||
|
|
|
@ -17,6 +17,8 @@ namespace Aviat\AnimeClient\Tests;
|
|||
use Aviat\AnimeClient\RoutingBase;
|
||||
use JetBrains\PhpStorm\ArrayShape;
|
||||
|
||||
class ConcreteRoutingBase extends RoutingBase {}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
|
@ -50,7 +52,7 @@ final class RoutingBaseTest extends AnimeClientTestCase
|
|||
],
|
||||
]);
|
||||
|
||||
$routingBase = new RoutingBase($this->container);
|
||||
$routingBase = new ConcreteRoutingBase($this->container);
|
||||
|
||||
$this->assertSame($path, $routingBase->path(), 'Path is invalid');
|
||||
$this->assertSame($segments, $routingBase->segments(), 'Segments array is invalid');
|
||||
|
|
Loading…
Reference in New Issue