4.1 Beta...ish #12
@ -49,6 +49,7 @@
|
||||
</div>
|
||||
<?php endif ?>
|
||||
<div class="row">
|
||||
<div><?= $item['manga']['type'] ?></div>
|
||||
<div class="user_rating">Rating: <?= $item['user_rating'] ?> / 10</div>
|
||||
</div>
|
||||
|
||||
|
@ -1,12 +1,61 @@
|
||||
<?php
|
||||
use function Aviat\AnimeClient\arrayToToml;
|
||||
use function Aviat\AnimeClient\loadTomlByFile;
|
||||
|
||||
$settings = loadTomlByFile($config->get('config_dir'));
|
||||
|
||||
if ( ! $auth->isAuthenticated())
|
||||
{
|
||||
echo '<h1>Not Authorized</h1>';
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
function render_settings_form ($data, $file)
|
||||
{
|
||||
ob_start();
|
||||
foreach ($data as $key => $value)
|
||||
{
|
||||
?>
|
||||
<tr>
|
||||
<td><label for="<?= $key ?>"><?= $key ?></label></td>
|
||||
<td>
|
||||
<?php if (is_scalar($value)): ?>
|
||||
<input
|
||||
type="text"
|
||||
id="<?= $key ?>"
|
||||
name="config[<?= $file ?>][<?= $key ?>]"
|
||||
value="<?= $value ?>"
|
||||
/>
|
||||
<?php else: ?>
|
||||
<table><?= render_settings_form($value, $file); ?></table>
|
||||
<?php endif ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php
|
||||
}
|
||||
|
||||
$buffer = ob_get_contents();
|
||||
ob_end_clean();
|
||||
|
||||
return $buffer;
|
||||
}
|
||||
|
||||
?>
|
||||
|
||||
<pre><?= print_r($_POST, TRUE) ?></pre>
|
||||
|
||||
<?php foreach($settings as $file => $properties): ?>
|
||||
<form action="<?= $_SERVER['REQUEST_URI'] ?>" method="POST">
|
||||
<table class="form">
|
||||
<caption><?= $file ?></caption>
|
||||
<tbody>
|
||||
<?= render_settings_form($properties, $file); ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<button type="submit">Save Changes</button>
|
||||
</form>
|
||||
<?php endforeach ?>
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -24,6 +24,7 @@
|
||||
"aura/session": "^2.0",
|
||||
"aviat/banker": "^1.0.0",
|
||||
"aviat/ion": "^2.3.0",
|
||||
"ext-json": "*",
|
||||
"ext-gd":"*",
|
||||
"ext-pdo": "*",
|
||||
"maximebf/consolekit": "^1.0",
|
||||
|
3
console
3
console
@ -17,7 +17,8 @@ try
|
||||
(new Console([
|
||||
'cache:clear' => Command\CacheClear::class,
|
||||
'cache:refresh' => Command\CachePrime::class,
|
||||
// 'lists:sync' => Command\SyncLists::class,
|
||||
'lists:sync' => Command\SyncLists::class,
|
||||
'mal_id:check' => Command\MALIDCheck::class,
|
||||
]))->run();
|
||||
}
|
||||
catch (\Exception $e)
|
||||
|
12
public/js/scripts-authed.min.js
vendored
12
public/js/scripts-authed.min.js
vendored
@ -365,12 +365,10 @@
|
||||
});
|
||||
}
|
||||
|
||||
// Wire up mal checkbox
|
||||
// Click on hidden MAL checkbox so
|
||||
// that MAL id is passed
|
||||
AnimeClient.on('main', 'change', '.big-check', (e) => {
|
||||
const id = e.target.id;
|
||||
AnimeClient.$('.mal-check').forEach(el => {
|
||||
el.checked = false;
|
||||
});
|
||||
document.getElementById(`mal_${id}`).checked = true;
|
||||
});
|
||||
|
||||
@ -386,7 +384,7 @@
|
||||
results.push(`
|
||||
<article class="media search">
|
||||
<div class="name">
|
||||
<input type="checkbox" class="mal-check" id="mal_${item.slug}" name="mal_id" value="${x.mal_id}" />
|
||||
<input type="radio" class="mal-check" id="mal_${item.slug}" name="mal_id" value="${x.mal_id}" />
|
||||
<input type="radio" class="big-check" id="${item.slug}" name="id" value="${x.id}" />
|
||||
<label for="${item.slug}">
|
||||
<img src="/public/images/anime/${x.id}.jpg" alt="" width="220" />
|
||||
@ -422,7 +420,7 @@
|
||||
results.push(`
|
||||
<article class="media search">
|
||||
<div class="name">
|
||||
<input type="checkbox" id="mal_${item.slug}" name="mal_id" value="${x.mal_id}" />
|
||||
<input type="radio" id="mal_${item.slug}" name="mal_id" value="${x.mal_id}" />
|
||||
<input type="radio" class="big-check" id="${item.slug}" name="id" value="${x.id}" />
|
||||
<label for="${item.slug}">
|
||||
<img src="/public/images/manga/${x.id}.jpg" alt="" width="220" />
|
||||
@ -595,7 +593,7 @@
|
||||
|
||||
AnimeClient.show(AnimeClient.$('#loading-shadow')[ 0 ]);
|
||||
|
||||
AnimeClient.ajax(AnimeClient.url('/manga/update'), {
|
||||
AnimeClient.ajax(AnimeClient.url('/manga/increment'), {
|
||||
data,
|
||||
dataType: 'json',
|
||||
type: 'POST',
|
||||
|
File diff suppressed because one or more lines are too long
@ -61,7 +61,7 @@ _.on('.manga.list', 'click', '.edit_buttons button', (e) => {
|
||||
|
||||
_.show(_.$('#loading-shadow')[ 0 ]);
|
||||
|
||||
_.ajax(_.url('/manga/update'), {
|
||||
_.ajax(_.url('/manga/increment'), {
|
||||
data,
|
||||
dataType: 'json',
|
||||
type: 'POST',
|
||||
|
@ -1,11 +1,9 @@
|
||||
import _ from './base/AnimeClient.js';
|
||||
|
||||
// Wire up mal checkbox
|
||||
// Click on hidden MAL checkbox so
|
||||
// that MAL id is passed
|
||||
_.on('main', 'change', '.big-check', (e) => {
|
||||
const id = e.target.id;
|
||||
_.$('.mal-check').forEach(el => {
|
||||
el.checked = false;
|
||||
})
|
||||
document.getElementById(`mal_${id}`).checked = true;
|
||||
});
|
||||
|
||||
@ -21,7 +19,7 @@ export function renderAnimeSearchResults (data) {
|
||||
results.push(`
|
||||
<article class="media search">
|
||||
<div class="name">
|
||||
<input type="checkbox" class="mal-check" id="mal_${item.slug}" name="mal_id" value="${x.mal_id}" />
|
||||
<input type="radio" class="mal-check" id="mal_${item.slug}" name="mal_id" value="${x.mal_id}" />
|
||||
<input type="radio" class="big-check" id="${item.slug}" name="id" value="${x.id}" />
|
||||
<label for="${item.slug}">
|
||||
<img src="/public/images/anime/${x.id}.jpg" alt="" width="220" />
|
||||
@ -57,7 +55,7 @@ export function renderMangaSearchResults (data) {
|
||||
results.push(`
|
||||
<article class="media search">
|
||||
<div class="name">
|
||||
<input type="checkbox" id="mal_${item.slug}" name="mal_id" value="${x.mal_id}" />
|
||||
<input type="radio" id="mal_${item.slug}" name="mal_id" value="${x.mal_id}" />
|
||||
<input type="radio" class="big-check" id="${item.slug}" name="id" value="${x.id}" />
|
||||
<label for="${item.slug}">
|
||||
<img src="/public/images/manga/${x.id}.jpg" alt="" width="220" />
|
||||
|
@ -11,6 +11,7 @@ query ($name: String, $type: MediaType) {
|
||||
private
|
||||
notes
|
||||
status
|
||||
updatedAt
|
||||
media {
|
||||
id
|
||||
idMal
|
||||
|
@ -18,10 +18,12 @@ namespace Aviat\AnimeClient\API\Anilist\Transformer;
|
||||
|
||||
use Aviat\AnimeClient\API\Enum\AnimeWatchingStatus\Anilist as AnilistStatus;
|
||||
use Aviat\AnimeClient\API\Mapping\AnimeWatchingStatus;
|
||||
use Aviat\AnimeClient\Types\{Anime, AnimeListItem, AnimeFormItem};
|
||||
use Aviat\AnimeClient\Types\{AnimeListItem, FormItem};
|
||||
|
||||
use Aviat\Ion\Transformer\AbstractTransformer;
|
||||
|
||||
use DateTime;
|
||||
|
||||
class AnimeListTransformer extends AbstractTransformer {
|
||||
|
||||
public function transform($item): AnimeListItem
|
||||
@ -33,11 +35,11 @@ class AnimeListTransformer extends AbstractTransformer {
|
||||
* Transform Anilist list item to Kitsu form update format
|
||||
*
|
||||
* @param array $item
|
||||
* @return AnimeFormItem
|
||||
* @return FormItem
|
||||
*/
|
||||
public function untransform(array $item): AnimeFormItem
|
||||
public function untransform(array $item): FormItem
|
||||
{
|
||||
return new AnimeFormItem([
|
||||
return new FormItem([
|
||||
'id' => $item['id'],
|
||||
'mal_id' => $item['media']['idMal'],
|
||||
'data' => [
|
||||
@ -48,7 +50,10 @@ class AnimeListTransformer extends AbstractTransformer {
|
||||
'reconsumeCount' => $item['repeat'],
|
||||
'reconsuming' => $item['status'] === AnilistStatus::REPEATING,
|
||||
'status' => AnimeWatchingStatus::ANILIST_TO_KITSU[$item['status']],
|
||||
]
|
||||
'updatedAt' => (new DateTime())
|
||||
->setTimestamp($item['updatedAt'])
|
||||
->format(DateTime::W3C)
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -18,10 +18,12 @@ namespace Aviat\AnimeClient\API\Anilist\Transformer;
|
||||
|
||||
use Aviat\AnimeClient\API\Enum\MangaReadingStatus\Anilist as AnilistStatus;
|
||||
use Aviat\AnimeClient\API\Mapping\MangaReadingStatus;
|
||||
use Aviat\AnimeClient\Types\MangaFormItem;
|
||||
use Aviat\AnimeClient\Types\FormItem;
|
||||
|
||||
use Aviat\Ion\Transformer\AbstractTransformer;
|
||||
|
||||
use DateTime;
|
||||
|
||||
class MangaListTransformer extends AbstractTransformer {
|
||||
|
||||
public function transform($item)
|
||||
@ -33,11 +35,11 @@ class MangaListTransformer extends AbstractTransformer {
|
||||
* Transform Anilist list item to Kitsu form update format
|
||||
*
|
||||
* @param array $item
|
||||
* @return MangaFormItem
|
||||
* @return FormItem
|
||||
*/
|
||||
public function untransform(array $item): MangaFormItem
|
||||
public function untransform(array $item): FormItem
|
||||
{
|
||||
return new MangaFormItem([
|
||||
return new FormItem([
|
||||
'id' => $item['id'],
|
||||
'mal_id' => $item['media']['idMal'],
|
||||
'data' => [
|
||||
@ -48,6 +50,9 @@ class MangaListTransformer extends AbstractTransformer {
|
||||
'reconsumeCount' => $item['repeat'],
|
||||
'reconsuming' => $item['status'] === AnilistStatus::REPEATING,
|
||||
'status' => MangaReadingStatus::ANILIST_TO_KITSU[$item['status']],
|
||||
'updatedAt' => (new DateTime())
|
||||
->setTimestamp($item['updatedAt'])
|
||||
->format(DateTime::W3C),
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
@ -67,7 +67,7 @@ final class Auth {
|
||||
* @param string $password
|
||||
* @return boolean
|
||||
*/
|
||||
public function authenticate($password)
|
||||
public function authenticate(string $password): bool
|
||||
{
|
||||
$config = $this->container->get('config');
|
||||
$username = $config->get(['kitsu_username']);
|
||||
@ -117,7 +117,7 @@ final class Auth {
|
||||
* @param string $token
|
||||
* @return boolean
|
||||
*/
|
||||
public function reAuthenticate(string $token)
|
||||
public function reAuthenticate(string $token): bool
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -162,7 +162,7 @@ final class Auth {
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isAuthenticated()
|
||||
public function isAuthenticated(): bool
|
||||
{
|
||||
return ($this->get_auth_token() !== FALSE);
|
||||
}
|
||||
@ -172,7 +172,7 @@ final class Auth {
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function logout()
|
||||
public function logout(): void
|
||||
{
|
||||
$this->segment->clear();
|
||||
}
|
||||
@ -185,14 +185,18 @@ final class Auth {
|
||||
public function get_auth_token()
|
||||
{
|
||||
$token = $this->segment->get('auth_token', FALSE);
|
||||
$refresh_token = $this->segment->get('refresh_token', FALSE);
|
||||
$refreshToken = $this->segment->get('refresh_token', FALSE);
|
||||
$isExpired = time() > $this->segment->get('auth_token_expires', 0);
|
||||
|
||||
// Attempt to re-authenticate with refresh token
|
||||
if ($isExpired && $refresh_token)
|
||||
if ($isExpired && $refreshToken)
|
||||
{
|
||||
$reauthenticated = $this->reAuthenticate($refresh_token);
|
||||
return $this->segment->get('auth_token', FALSE);
|
||||
if ($this->reAuthenticate($refreshToken))
|
||||
{
|
||||
return $this->segment->get('auth_token', FALSE);
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return $token;
|
||||
|
@ -19,7 +19,7 @@ namespace Aviat\AnimeClient\API\Kitsu\Transformer;
|
||||
use Aviat\AnimeClient\API\Kitsu;
|
||||
use Aviat\AnimeClient\Types\{
|
||||
Anime,
|
||||
AnimeFormItem,
|
||||
FormItem,
|
||||
AnimeListItem
|
||||
};
|
||||
use Aviat\Ion\Transformer\AbstractTransformer;
|
||||
@ -95,7 +95,7 @@ final class AnimeListTransformer extends AbstractTransformer {
|
||||
'title' => $title,
|
||||
'titles' => $titles,
|
||||
'slug' => $anime['slug'],
|
||||
'show_type' => $this->string($anime['showType'])->upperCaseFirst()->__toString(),
|
||||
'show_type' => $this->string($anime['subtype'])->upperCaseFirst()->__toString(),
|
||||
'cover_image' => $anime['posterImage']['small'],
|
||||
'genres' => $genres,
|
||||
'streaming_links' => $streamingLinks,
|
||||
@ -114,14 +114,14 @@ final class AnimeListTransformer extends AbstractTransformer {
|
||||
* api response format
|
||||
*
|
||||
* @param array $item Transformed library item
|
||||
* @return AnimeFormItem API library item
|
||||
* @return FormItem API library item
|
||||
*/
|
||||
public function untransform($item): AnimeFormItem
|
||||
public function untransform($item): FormItem
|
||||
{
|
||||
$privacy = (array_key_exists('private', $item) && $item['private']);
|
||||
$rewatching = (array_key_exists('rewatching', $item) && $item['rewatching']);
|
||||
|
||||
$untransformed = new AnimeFormItem([
|
||||
$untransformed = new FormItem([
|
||||
'id' => $item['id'],
|
||||
'anilist_item_id' => $item['anilist_item_id'] ?? NULL,
|
||||
'mal_id' => $item['mal_id'] ?? NULL,
|
||||
|
@ -18,7 +18,7 @@ namespace Aviat\AnimeClient\API\Kitsu\Transformer;
|
||||
|
||||
use Aviat\AnimeClient\API\Kitsu;
|
||||
use Aviat\AnimeClient\Types\{
|
||||
MangaFormItem, MangaFormItemData,
|
||||
FormItem, FormItemData,
|
||||
MangaListItem, MangaListItemDetail
|
||||
};
|
||||
use Aviat\Ion\StringWrapper;
|
||||
@ -97,7 +97,7 @@ final class MangaListTransformer extends AbstractTransformer {
|
||||
'slug' => $manga['slug'],
|
||||
'title' => $title,
|
||||
'titles' => $titles,
|
||||
'type' => $manga['mangaType'],
|
||||
'type' => $this->string($manga['subtype'])->upperCaseFirst()->__toString(),
|
||||
'url' => 'https://kitsu.io/manga/' . $manga['slug'],
|
||||
]),
|
||||
'reading_status' => $item['attributes']['status'],
|
||||
@ -114,16 +114,16 @@ final class MangaListTransformer extends AbstractTransformer {
|
||||
* Untransform data to update the api
|
||||
*
|
||||
* @param array $item
|
||||
* @return MangaFormItem
|
||||
* @return FormItem
|
||||
*/
|
||||
public function untransform($item): MangaFormItem
|
||||
public function untransform($item): FormItem
|
||||
{
|
||||
$rereading = array_key_exists('rereading', $item) && (bool)$item['rereading'];
|
||||
|
||||
$map = new MangaFormItem([
|
||||
$map = new FormItem([
|
||||
'id' => $item['id'],
|
||||
'mal_id' => $item['mal_id'],
|
||||
'data' => new MangaFormItemData([
|
||||
'data' => new FormItemData([
|
||||
'status' => $item['status'],
|
||||
'reconsuming' => $rereading,
|
||||
'reconsumeCount' => (int)$item['reread_count'],
|
||||
|
@ -51,6 +51,26 @@ function loadToml(string $path): array
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load configuration from toml files, keyed by the original file
|
||||
*
|
||||
* @param string $path
|
||||
* @return array
|
||||
*/
|
||||
function loadTomlByFile(string $path): array
|
||||
{
|
||||
$output = [];
|
||||
$files = glob("{$path}/*.toml");
|
||||
|
||||
foreach ($files as $file)
|
||||
{
|
||||
$config = Toml::parseFile($file);
|
||||
$output[basename($file)] = $config;
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the array sequential, not associative?
|
||||
*
|
||||
|
192
src/Command/MALIDCheck.php
Normal file
192
src/Command/MALIDCheck.php
Normal file
@ -0,0 +1,192 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* Hummingbird Anime List Client
|
||||
*
|
||||
* An API client for Kitsu to manage anime and manga watch lists
|
||||
*
|
||||
* PHP version 7
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||
* @copyright 2015 - 2018 Timothy J. Warren
|
||||
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
||||
* @version 4.0
|
||||
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
|
||||
*/
|
||||
|
||||
namespace Aviat\AnimeClient\Command;
|
||||
|
||||
|
||||
use Aviat\AnimeClient\API\{
|
||||
APIRequestBuilder,
|
||||
JsonAPI,
|
||||
FailedResponseException,
|
||||
ParallelAPIRequest
|
||||
};
|
||||
|
||||
use Aviat\Ion\Json;
|
||||
|
||||
|
||||
final class MALIDCheck extends BaseCommand {
|
||||
|
||||
private $kitsuModel;
|
||||
|
||||
/**
|
||||
* Check MAL mapping validity
|
||||
*
|
||||
* @param array $args
|
||||
* @param array $options
|
||||
* @throws \Aviat\Ion\Di\Exception\ContainerException
|
||||
* @throws \Aviat\Ion\Di\Exception\NotFoundException
|
||||
*/
|
||||
public function execute(array $args, array $options = []): void
|
||||
{
|
||||
$this->setContainer($this->setupContainer());
|
||||
$this->setCache($this->container->get('cache'));
|
||||
$this->kitsuModel = $this->container->get('kitsu-model');
|
||||
|
||||
// @TODO: Stuff!
|
||||
}
|
||||
|
||||
private function getListIds()
|
||||
{
|
||||
$this->getListCounts('anime');
|
||||
$this->getListCounts('manga');
|
||||
|
||||
}
|
||||
|
||||
private function getListCounts($type): void
|
||||
{
|
||||
$uType = ucfirst($type);
|
||||
|
||||
$kitsuCount = 0;
|
||||
try
|
||||
{
|
||||
$kitsuCount = $this->kitsuModel->{"get{$uType}ListCount"}();
|
||||
} catch (FailedResponseException $e)
|
||||
{
|
||||
dump($e);
|
||||
}
|
||||
|
||||
$this->echoBox("Number of Kitsu {$type} list items: {$kitsuCount}");
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a kitsu list for the sake of comparision
|
||||
*
|
||||
* @param string $type
|
||||
* @return array
|
||||
*/
|
||||
protected function formatKitsuList(string $type = 'anime'): array
|
||||
{
|
||||
$data = $this->kitsuModel->{'getFull' . ucfirst($type) . 'List'}();
|
||||
|
||||
if (empty($data))
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
$includes = JsonAPI::organizeIncludes($data['included']);
|
||||
$includes['mappings'] = $this->filterMappings($includes['mappings'], $type);
|
||||
|
||||
$output = [];
|
||||
|
||||
foreach ($data['data'] as $listItem)
|
||||
{
|
||||
$id = $listItem['relationships'][$type]['data']['id'];
|
||||
|
||||
$potentialMappings = $includes[$type][$id]['relationships']['mappings'];
|
||||
$malId = NULL;
|
||||
|
||||
foreach ($potentialMappings as $mappingId)
|
||||
{
|
||||
if (array_key_exists($mappingId, $includes['mappings']))
|
||||
{
|
||||
$malId = $includes['mappings'][$mappingId]['externalId'];
|
||||
}
|
||||
}
|
||||
|
||||
// Skip to the next item if there isn't a MAL ID
|
||||
if ($malId === NULL)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$output[$listItem['id']] = [
|
||||
'id' => $listItem['id'],
|
||||
'malId' => $malId,
|
||||
'data' => $listItem['attributes'],
|
||||
];
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter Kitsu mappings for the specified type
|
||||
*
|
||||
* @param array $includes
|
||||
* @param string $type
|
||||
* @return array
|
||||
*/
|
||||
protected function filterMappings(array $includes, string $type = 'anime'): array
|
||||
{
|
||||
$output = [];
|
||||
|
||||
foreach ($includes as $id => $mapping)
|
||||
{
|
||||
if ($mapping['externalSite'] === "myanimelist/{$type}")
|
||||
{
|
||||
$output[$id] = $mapping;
|
||||
}
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
protected function checkMALIds(array $kitsuList, string $type)
|
||||
{
|
||||
$requester = new ParallelAPIRequest();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create/Update list items on Kitsu
|
||||
*
|
||||
* @param array $itemsToUpdate
|
||||
* @param string $action
|
||||
* @param string $type
|
||||
*/
|
||||
protected function updateKitsuListItems(array $itemsToUpdate, string $action = 'update', string $type = 'anime'): void
|
||||
{
|
||||
$requester = new ParallelAPIRequest();
|
||||
foreach ($itemsToUpdate as $item)
|
||||
{
|
||||
if ($action === 'update')
|
||||
{
|
||||
$requester->addRequest($this->kitsuModel->updateListItem($item));
|
||||
} else if ($action === 'create')
|
||||
{
|
||||
$requester->addRequest($this->kitsuModel->createListItem($item));
|
||||
}
|
||||
}
|
||||
|
||||
$responses = $requester->makeRequests();
|
||||
|
||||
foreach ($responses as $key => $response)
|
||||
{
|
||||
$responseData = Json::decode($response);
|
||||
|
||||
$id = $itemsToUpdate[$key]['id'];
|
||||
if ( ! array_key_exists('errors', $responseData))
|
||||
{
|
||||
$verb = ($action === 'update') ? 'updated' : 'created';
|
||||
$this->echoBox("Successfully {$verb} Kitsu {$type} list item with id: {$id}");
|
||||
} else
|
||||
{
|
||||
dump($responseData);
|
||||
$verb = ($action === 'update') ? 'update' : 'create';
|
||||
$this->echoBox("Failed to {$verb} Kitsu {$type} list item with id: {$id}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -16,14 +16,18 @@
|
||||
|
||||
namespace Aviat\AnimeClient\Command;
|
||||
|
||||
use Aviat\AnimeClient\API\
|
||||
{FailedResponseException, JsonAPI, Kitsu\Transformer\MangaListTransformer, ParallelAPIRequest};
|
||||
use Aviat\AnimeClient\API\{
|
||||
FailedResponseException,
|
||||
JsonAPI,
|
||||
Kitsu\Transformer\MangaListTransformer,
|
||||
ParallelAPIRequest
|
||||
};
|
||||
use Aviat\AnimeClient\API\Anilist\Transformer\{
|
||||
AnimeListTransformer as AALT,
|
||||
MangaListTransformer as AMLT,
|
||||
MangaListTransformer as AMLT
|
||||
};
|
||||
use Aviat\AnimeClient\API\Mapping\{AnimeWatchingStatus, MangaReadingStatus};
|
||||
use Aviat\AnimeClient\Types\{AnimeFormItem, MangaFormItem};
|
||||
use Aviat\AnimeClient\Types\FormItem;
|
||||
use Aviat\Ion\Json;
|
||||
use DateTime;
|
||||
|
||||
@ -49,9 +53,9 @@ final class SyncLists extends BaseCommand {
|
||||
*
|
||||
* @param array $args
|
||||
* @param array $options
|
||||
* @throws \Aviat\Ion\Di\ContainerException
|
||||
* @throws \Aviat\Ion\Di\NotFoundException
|
||||
* @return void
|
||||
* @throws \Aviat\Ion\Di\Exception\ContainerException
|
||||
* @throws \Aviat\Ion\Di\Exception\NotFoundException
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public function execute(array $args, array $options = []): void
|
||||
{
|
||||
@ -65,10 +69,10 @@ final class SyncLists extends BaseCommand {
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to synchronize external apis
|
||||
* Attempt to synchronize external APIs
|
||||
*
|
||||
* @param string $type anime|manga
|
||||
* @return void
|
||||
* @param string $type
|
||||
* @throws \Throwable
|
||||
*/
|
||||
protected function sync(string $type): void
|
||||
{
|
||||
@ -157,6 +161,8 @@ final class SyncLists extends BaseCommand {
|
||||
* Format an Anilist anime list for comparison
|
||||
*
|
||||
* @return array
|
||||
* @throws \Aviat\Ion\Di\Exception\ContainerException
|
||||
* @throws \Aviat\Ion\Di\Exception\NotFoundException
|
||||
*/
|
||||
protected function formatAnilistAnimeList(): array
|
||||
{
|
||||
@ -188,6 +194,8 @@ final class SyncLists extends BaseCommand {
|
||||
* Format an Anilist manga list for comparison
|
||||
*
|
||||
* @return array
|
||||
* @throws \Aviat\Ion\Di\Exception\ContainerException
|
||||
* @throws \Aviat\Ion\Di\Exception\NotFoundException
|
||||
*/
|
||||
protected function formatAnilistMangaList(): array
|
||||
{
|
||||
@ -223,7 +231,8 @@ final class SyncLists extends BaseCommand {
|
||||
*/
|
||||
protected function formatKitsuList(string $type = 'anime'): array
|
||||
{
|
||||
$data = $this->kitsuModel->{'getFullRaw' . ucfirst($type) . 'List'}();
|
||||
$method = 'getFullRaw' . ucfirst($type) . 'List';
|
||||
$data = $this->kitsuModel->$method();
|
||||
|
||||
if (empty($data))
|
||||
{
|
||||
@ -312,7 +321,7 @@ final class SyncLists extends BaseCommand {
|
||||
{
|
||||
$malId = $kitsuItem['malId'];
|
||||
|
||||
if (in_array($malId, $malBlackList))
|
||||
if (\in_array((int)$malId, $malBlackList, TRUE))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@ -356,14 +365,10 @@ final class SyncLists extends BaseCommand {
|
||||
'repeat' => $kItem['reconsumeCount'],
|
||||
'score' => $kItem['ratingTwenty'] / 2,
|
||||
'status' => $newItemStatus,
|
||||
], // $kitsuItem['data']
|
||||
],
|
||||
];
|
||||
|
||||
}
|
||||
|
||||
//dump($itemsToAddToAnilist);
|
||||
//die();
|
||||
|
||||
return [
|
||||
'addToAnilist' => $itemsToAddToAnilist,
|
||||
'updateAnilist' => $anilistUpdateItems,
|
||||
@ -390,6 +395,7 @@ final class SyncLists extends BaseCommand {
|
||||
'status',
|
||||
];
|
||||
$diff = [];
|
||||
$dateDiff = new DateTime($kitsuItem['data']['updatedAt']) <=> new DateTime((string)$anilistItem['data']['updatedAt']);
|
||||
|
||||
// Correct differences in notation
|
||||
$kitsuItem['data']['rating'] = $kitsuItem['data']['ratingTwenty'] / 2;
|
||||
@ -429,7 +435,6 @@ final class SyncLists extends BaseCommand {
|
||||
$return['updateType'][] = 'kitsu';
|
||||
}
|
||||
|
||||
|
||||
// If status is the same, and progress count is different, use greater progress
|
||||
if ($sameStatus && ( ! $sameProgress))
|
||||
{
|
||||
@ -445,16 +450,23 @@ final class SyncLists extends BaseCommand {
|
||||
}
|
||||
}
|
||||
|
||||
// If status is different, go with Kitsu
|
||||
// If status is different, use the status of the more recently updated item
|
||||
if ( ! $sameStatus)
|
||||
{
|
||||
$update['data']['status'] = $kitsuItem['data']['status'];
|
||||
$return['updateType'][] = 'anilist';
|
||||
if ($dateDiff === 1)
|
||||
{
|
||||
$update['data']['status'] = $kitsuItem['data']['status'];
|
||||
$return['updateType'][] = 'anilist';
|
||||
} else if ($dateDiff === -1)
|
||||
{
|
||||
$update['data']['status'] = $anilistItem['data']['status'];
|
||||
$return['updateType'][] = 'kitsu';
|
||||
}
|
||||
}
|
||||
|
||||
// If status and progress are different, it's a bit more complicated...
|
||||
// But, at least for now, assume newer record is correct
|
||||
/* if ( ! ($sameStatus || $sameProgress))
|
||||
if ( ! ($sameStatus || $sameProgress))
|
||||
{
|
||||
if ($dateDiff === 1)
|
||||
{
|
||||
@ -478,18 +490,17 @@ final class SyncLists extends BaseCommand {
|
||||
|
||||
$return['updateType'][] = 'kitsu';
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
// If rating is different, use the kitsu rating, unless the other rating
|
||||
// is set, and the kitsu rating is not set
|
||||
// Use the first set rating, otherwise use the newer rating
|
||||
if ( ! $sameRating)
|
||||
{
|
||||
if ($kitsuItem['data']['rating'] !== 0)
|
||||
if ($kitsuItem['data']['rating'] !== 0 && $dateDiff === 1)
|
||||
{
|
||||
$update['data']['rating'] = $kitsuItem['data']['rating'];
|
||||
$return['updateType'][] = 'anilist';
|
||||
}
|
||||
else
|
||||
else if($dateDiff === -1)
|
||||
{
|
||||
$update['data']['rating'] = $anilistItem['data']['rating'];
|
||||
$return['updateType'][] = 'kitsu';
|
||||
@ -526,25 +537,12 @@ final class SyncLists extends BaseCommand {
|
||||
}
|
||||
}
|
||||
|
||||
// If status is different, use the status of the more recently updated item
|
||||
/* if ( ! $sameStatus)
|
||||
{
|
||||
if ($dateDiff === 1)
|
||||
{
|
||||
$update['data']['status'] = $kitsuItem['data']['status'];
|
||||
$return['updateType'][] = 'anilist';
|
||||
}
|
||||
else if ($dateDiff === -1)
|
||||
{
|
||||
$update['data']['status'] = $anilistItem['data']['status'];
|
||||
$return['updateType'][] = 'kitsu';
|
||||
}
|
||||
} */
|
||||
|
||||
|
||||
$return['meta'] = [
|
||||
'kitsu' => $kitsuItem['data'],
|
||||
'anilist' => $anilistItem['data'],
|
||||
// 'dateDiff' => $dateDiff,
|
||||
'dateDiff' => $dateDiff,
|
||||
'diff' => $diff,
|
||||
];
|
||||
$return['data'] = $update;
|
||||
@ -568,7 +566,7 @@ final class SyncLists extends BaseCommand {
|
||||
$return['data']['data'] = array_merge($prevData, $return['data']['data']);
|
||||
}
|
||||
|
||||
// dump($return);
|
||||
dump($return);
|
||||
|
||||
return $return;
|
||||
}
|
||||
@ -579,18 +577,17 @@ final class SyncLists extends BaseCommand {
|
||||
* @param array $itemsToUpdate
|
||||
* @param string $action
|
||||
* @param string $type
|
||||
* @throws \Throwable
|
||||
*/
|
||||
protected function updateKitsuListItems(array $itemsToUpdate, string $action = 'update', string $type = 'anime'): void
|
||||
{
|
||||
$requester = new ParallelAPIRequest();
|
||||
foreach($itemsToUpdate as $item)
|
||||
{
|
||||
$typeClass = '\\Aviat\\AnimeClient\\Types\\' . ucFirst($type) . 'FormItem';
|
||||
|
||||
if ($action === 'update')
|
||||
{
|
||||
$requester->addRequest(
|
||||
$this->kitsuModel->updateListItem(new $typeClass($item))
|
||||
$this->kitsuModel->updateListItem(new FormItem($item))
|
||||
);
|
||||
}
|
||||
else if ($action === 'create')
|
||||
@ -626,19 +623,18 @@ final class SyncLists extends BaseCommand {
|
||||
* @param array $itemsToUpdate
|
||||
* @param string $action
|
||||
* @param string $type
|
||||
* @throws \Throwable
|
||||
*/
|
||||
protected function updateAnilistListItems(array$itemsToUpdate, string $action = 'update', string $type = 'anime'): void
|
||||
protected function updateAnilistListItems(array $itemsToUpdate, string $action = 'update', string $type = 'anime'): void
|
||||
{
|
||||
$requester = new ParallelAPIRequest();
|
||||
|
||||
$typeClass = '\\Aviat\\AnimeClient\\Types\\' . ucFirst($type) . 'FormItem';
|
||||
|
||||
foreach($itemsToUpdate as $item)
|
||||
{
|
||||
if ($action === 'update')
|
||||
{
|
||||
$requester->addRequest(
|
||||
$this->anilistModel->updateListItem(new $typeClass($item), $type)
|
||||
$this->anilistModel->updateListItem(new FormItem($item), $type)
|
||||
);
|
||||
}
|
||||
else if ($action === 'create')
|
||||
|
@ -20,7 +20,7 @@ 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\Mapping\AnimeWatchingStatus;
|
||||
use Aviat\AnimeClient\Types\AnimeFormItem;
|
||||
use Aviat\AnimeClient\Types\FormItem;
|
||||
use Aviat\Ion\Di\ContainerInterface;
|
||||
use Aviat\Ion\Json;
|
||||
use Aviat\Ion\StringWrapper;
|
||||
@ -204,7 +204,7 @@ final class Anime extends BaseController {
|
||||
// large form-based updates
|
||||
$transformer = new AnimeListTransformer();
|
||||
$postData = $transformer->untransform($data);
|
||||
$fullResult = $this->model->updateLibraryItem(new AnimeFormItem($postData));
|
||||
$fullResult = $this->model->updateLibraryItem(new FormItem($postData));
|
||||
|
||||
if ($fullResult['statusCode'] === 200)
|
||||
{
|
||||
@ -235,7 +235,7 @@ final class Anime extends BaseController {
|
||||
$data = $this->request->getParsedBody();
|
||||
}
|
||||
|
||||
$response = $this->model->incrementLibraryItem(new AnimeFormItem($data));
|
||||
$response = $this->model->incrementLibraryItem(new FormItem($data));
|
||||
|
||||
$this->cache->clear();
|
||||
$this->outputJSON($response['body'], $response['statusCode']);
|
||||
|
@ -175,6 +175,16 @@ final class Index extends BaseController {
|
||||
]);
|
||||
}
|
||||
|
||||
public function settings_post()
|
||||
{
|
||||
$auth = $this->container->get('auth');
|
||||
$this->outputHTML('settings', [
|
||||
'auth' => $auth,
|
||||
'config' => $this->config,
|
||||
'title' => $this->config->get('whose_list') . "'s Settings",
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get image covers from kitsu
|
||||
*
|
||||
|
@ -20,7 +20,7 @@ use Aviat\AnimeClient\Controller;
|
||||
use Aviat\AnimeClient\API\Kitsu\Transformer\MangaListTransformer;
|
||||
use Aviat\AnimeClient\API\Mapping\MangaReadingStatus;
|
||||
use Aviat\AnimeClient\Model\Manga as MangaModel;
|
||||
use Aviat\AnimeClient\Types\MangaFormItem;
|
||||
use Aviat\AnimeClient\Types\FormItem;
|
||||
use Aviat\Ion\Di\ContainerInterface;
|
||||
use Aviat\Ion\{Json, StringWrapper};
|
||||
|
||||
@ -207,7 +207,7 @@ final class Manga extends Controller {
|
||||
// large form-based updates
|
||||
$transformer = new MangaListTransformer();
|
||||
$post_data = $transformer->untransform($data);
|
||||
$full_result = $this->model->updateLibraryItem(new MangaFormItem($post_data));
|
||||
$full_result = $this->model->updateLibraryItem(new FormItem($post_data));
|
||||
|
||||
if ($full_result['statusCode'] === 200)
|
||||
{
|
||||
@ -237,7 +237,7 @@ final class Manga extends Controller {
|
||||
$data = $this->request->getParsedBody();
|
||||
}
|
||||
|
||||
$response = $this->model->incrementLibraryItem(new MangaFormItem($data));
|
||||
$response = $this->model->incrementLibraryItem(new FormItem($data));
|
||||
|
||||
$this->cache->clear();
|
||||
$this->outputJSON($response['body'], $response['statusCode']);
|
||||
|
@ -20,7 +20,7 @@ use Aviat\AnimeClient\API\ParallelAPIRequest;
|
||||
use Aviat\AnimeClient\API\Mapping\AnimeWatchingStatus;
|
||||
use Aviat\AnimeClient\Types\{
|
||||
Anime as AnimeType,
|
||||
AnimeFormItem,
|
||||
FormItem,
|
||||
AnimeListItem
|
||||
};
|
||||
use Aviat\Ion\Di\ContainerInterface;
|
||||
@ -187,10 +187,10 @@ class Anime extends API {
|
||||
/**
|
||||
* Increment progress for the specified anime
|
||||
*
|
||||
* @param AnimeFormItem $data
|
||||
* @param FormItem $data
|
||||
* @return array
|
||||
*/
|
||||
public function incrementLibraryItem(AnimeFormItem $data): array
|
||||
public function incrementLibraryItem(FormItem $data): array
|
||||
{
|
||||
$requester = new ParallelAPIRequest();
|
||||
$requester->addRequest($this->kitsuModel->incrementListItem($data), 'kitsu');
|
||||
@ -216,10 +216,10 @@ class Anime extends API {
|
||||
/**
|
||||
* Update a list entry
|
||||
*
|
||||
* @param AnimeFormItem $data
|
||||
* @param FormItem $data
|
||||
* @return array
|
||||
*/
|
||||
public function updateLibraryItem(AnimeFormItem $data): array
|
||||
public function updateLibraryItem(FormItem $data): array
|
||||
{
|
||||
$requester = new ParallelAPIRequest();
|
||||
$requester->addRequest($this->kitsuModel->updateListItem($data), 'kitsu');
|
||||
|
@ -22,7 +22,7 @@ use Aviat\AnimeClient\API\{
|
||||
ParallelAPIRequest
|
||||
};
|
||||
use Aviat\AnimeClient\Types\{
|
||||
MangaFormItem,
|
||||
FormItem,
|
||||
MangaListItem,
|
||||
MangaPage
|
||||
};
|
||||
@ -149,10 +149,10 @@ class Manga extends API {
|
||||
/**
|
||||
* Update a list entry
|
||||
*
|
||||
* @param MangaFormItem $data
|
||||
* @param FormItem $data
|
||||
* @return array
|
||||
*/
|
||||
public function updateLibraryItem(MangaFormItem $data): array
|
||||
public function updateLibraryItem(FormItem $data): array
|
||||
{
|
||||
$requester = new ParallelAPIRequest();
|
||||
$requester->addRequest($this->kitsuModel->updateListItem($data), 'kitsu');
|
||||
@ -177,10 +177,10 @@ class Manga extends API {
|
||||
/**
|
||||
* Increase the progress of a list entry
|
||||
*
|
||||
* @param MangaFormItem $data
|
||||
* @param FormItem $data
|
||||
* @return array
|
||||
*/
|
||||
public function incrementLibraryItem(MangaFormItem $data): array
|
||||
public function incrementLibraryItem(FormItem $data): array
|
||||
{
|
||||
$requester = new ParallelAPIRequest();
|
||||
$requester->addRequest($this->kitsuModel->incrementListItem($data), 'kitsu');
|
||||
|
@ -26,7 +26,7 @@ abstract class AbstractType implements ArrayAccess {
|
||||
* @param $properties
|
||||
* @return mixed
|
||||
*/
|
||||
public static function __set_state($properties)
|
||||
public static function __set_state($properties): self
|
||||
{
|
||||
return new static($properties);
|
||||
}
|
||||
@ -165,7 +165,7 @@ abstract class AbstractType implements ArrayAccess {
|
||||
{
|
||||
$object = $parent ?? $this;
|
||||
|
||||
if (is_scalar($object))
|
||||
if (is_scalar($object) || empty($object))
|
||||
{
|
||||
return $object;
|
||||
}
|
||||
@ -174,7 +174,7 @@ abstract class AbstractType implements ArrayAccess {
|
||||
|
||||
foreach ($object as $key => $value)
|
||||
{
|
||||
$output[$key] = is_scalar($value)
|
||||
$output[$key] = (is_scalar($value) || empty($value))
|
||||
? $value
|
||||
: $this->toArray((array) $value);
|
||||
}
|
||||
|
@ -1,27 +0,0 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* Hummingbird Anime List Client
|
||||
*
|
||||
* An API client for Kitsu to manage anime and manga watch lists
|
||||
*
|
||||
* PHP version 7
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||
* @copyright 2015 - 2018 Timothy J. Warren
|
||||
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
||||
* @version 4.0
|
||||
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
|
||||
*/
|
||||
|
||||
namespace Aviat\AnimeClient\Types;
|
||||
|
||||
/**
|
||||
* Type representing an Anime object for display
|
||||
*/
|
||||
final class AnimeFormItem extends FormItem {
|
||||
public function setData($value): void
|
||||
{
|
||||
$this->data = new AnimeFormItemData($value);
|
||||
}
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* Hummingbird Anime List Client
|
||||
*
|
||||
* An API client for Kitsu to manage anime and manga watch lists
|
||||
*
|
||||
* PHP version 7
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||
* @copyright 2015 - 2018 Timothy J. Warren
|
||||
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
||||
* @version 4.0
|
||||
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
|
||||
*/
|
||||
|
||||
namespace Aviat\AnimeClient\Types;
|
||||
|
||||
/**
|
||||
* Type representing an Anime object for display
|
||||
*/
|
||||
final class AnimeFormItemData extends FormItemData {}
|
@ -19,12 +19,15 @@ namespace Aviat\AnimeClient\Types;
|
||||
/**
|
||||
* Type representing an Anime object for display
|
||||
*/
|
||||
abstract class FormItem extends AbstractType {
|
||||
class FormItem extends AbstractType {
|
||||
public $id;
|
||||
public $anilist_item_id;
|
||||
public $mal_id;
|
||||
public $data;
|
||||
|
||||
abstract public function setData($value): void;
|
||||
public function setData($value): void
|
||||
{
|
||||
$this->data = new FormItemData($value);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -17,9 +17,9 @@
|
||||
namespace Aviat\AnimeClient\Types;
|
||||
|
||||
/**
|
||||
* Type representing an Anime object for display
|
||||
* Type representing a Media object for editing/syncing
|
||||
*/
|
||||
abstract class FormItemData extends AbstractType {
|
||||
class FormItemData extends AbstractType {
|
||||
public $notes;
|
||||
public $private;
|
||||
public $progress;
|
||||
@ -28,4 +28,5 @@ abstract class FormItemData extends AbstractType {
|
||||
public $reconsumeCount;
|
||||
public $reconsuming;
|
||||
public $status;
|
||||
public $updatedAt;
|
||||
}
|
||||
|
@ -1,28 +0,0 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* Hummingbird Anime List Client
|
||||
*
|
||||
* An API client for Kitsu to manage anime and manga watch lists
|
||||
*
|
||||
* PHP version 7
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||
* @copyright 2015 - 2018 Timothy J. Warren
|
||||
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
||||
* @version 4.0
|
||||
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
|
||||
*/
|
||||
|
||||
namespace Aviat\AnimeClient\Types;
|
||||
|
||||
/**
|
||||
* Form data for updating a Manga List item
|
||||
*/
|
||||
final class MangaFormItem extends FormItem {
|
||||
public function setData($value): void
|
||||
{
|
||||
$this->data = new MangaFormItemData($value);
|
||||
}
|
||||
}
|
||||
|
@ -1,19 +0,0 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* Hummingbird Anime List Client
|
||||
*
|
||||
* An API client for Kitsu to manage anime and manga watch lists
|
||||
*
|
||||
* PHP version 7
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||
* @copyright 2015 - 2018 Timothy J. Warren
|
||||
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
||||
* @version 4.0
|
||||
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
|
||||
*/
|
||||
|
||||
namespace Aviat\AnimeClient\Types;
|
||||
|
||||
final class MangaFormItemData extends FormItemData {}
|
Loading…
Reference in New Issue
Block a user