Version 5.1 - All the GraphQL #32

Closed
timw4mail wants to merge 1160 commits from develop into master
29 changed files with 411 additions and 222 deletions
Showing only changes of commit e3b4b9dd32 - Show all commits

View File

@ -49,6 +49,7 @@
</div> </div>
<?php endif ?> <?php endif ?>
<div class="row"> <div class="row">
<div><?= $item['manga']['type'] ?></div>
<div class="user_rating">Rating: <?= $item['user_rating'] ?> / 10</div> <div class="user_rating">Rating: <?= $item['user_rating'] ?> / 10</div>
</div> </div>

View File

@ -1,12 +1,61 @@
<?php <?php
use function Aviat\AnimeClient\arrayToToml; use function Aviat\AnimeClient\loadTomlByFile;
$settings = loadTomlByFile($config->get('config_dir'));
if ( ! $auth->isAuthenticated()) if ( ! $auth->isAuthenticated())
{ {
echo '<h1>Not Authorized</h1>'; 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 ?>

View File

@ -24,6 +24,7 @@
"aura/session": "^2.0", "aura/session": "^2.0",
"aviat/banker": "^1.0.0", "aviat/banker": "^1.0.0",
"aviat/ion": "^2.3.0", "aviat/ion": "^2.3.0",
"ext-json": "*",
"ext-gd":"*", "ext-gd":"*",
"ext-pdo": "*", "ext-pdo": "*",
"maximebf/consolekit": "^1.0", "maximebf/consolekit": "^1.0",

View File

@ -17,7 +17,8 @@ try
(new Console([ (new Console([
'cache:clear' => Command\CacheClear::class, 'cache:clear' => Command\CacheClear::class,
'cache:refresh' => Command\CachePrime::class, 'cache:refresh' => Command\CachePrime::class,
// 'lists:sync' => Command\SyncLists::class, 'lists:sync' => Command\SyncLists::class,
'mal_id:check' => Command\MALIDCheck::class,
]))->run(); ]))->run();
} }
catch (\Exception $e) catch (\Exception $e)

View File

@ -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) => { AnimeClient.on('main', 'change', '.big-check', (e) => {
const id = e.target.id; const id = e.target.id;
AnimeClient.$('.mal-check').forEach(el => {
el.checked = false;
});
document.getElementById(`mal_${id}`).checked = true; document.getElementById(`mal_${id}`).checked = true;
}); });
@ -386,7 +384,7 @@
results.push(` results.push(`
<article class="media search"> <article class="media search">
<div class="name"> <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}" /> <input type="radio" class="big-check" id="${item.slug}" name="id" value="${x.id}" />
<label for="${item.slug}"> <label for="${item.slug}">
<img src="/public/images/anime/${x.id}.jpg" alt="" width="220" /> <img src="/public/images/anime/${x.id}.jpg" alt="" width="220" />
@ -422,7 +420,7 @@
results.push(` results.push(`
<article class="media search"> <article class="media search">
<div class="name"> <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}" /> <input type="radio" class="big-check" id="${item.slug}" name="id" value="${x.id}" />
<label for="${item.slug}"> <label for="${item.slug}">
<img src="/public/images/manga/${x.id}.jpg" alt="" width="220" /> <img src="/public/images/manga/${x.id}.jpg" alt="" width="220" />
@ -595,7 +593,7 @@
AnimeClient.show(AnimeClient.$('#loading-shadow')[ 0 ]); AnimeClient.show(AnimeClient.$('#loading-shadow')[ 0 ]);
AnimeClient.ajax(AnimeClient.url('/manga/update'), { AnimeClient.ajax(AnimeClient.url('/manga/increment'), {
data, data,
dataType: 'json', dataType: 'json',
type: 'POST', type: 'POST',

File diff suppressed because one or more lines are too long

View File

@ -61,7 +61,7 @@ _.on('.manga.list', 'click', '.edit_buttons button', (e) => {
_.show(_.$('#loading-shadow')[ 0 ]); _.show(_.$('#loading-shadow')[ 0 ]);
_.ajax(_.url('/manga/update'), { _.ajax(_.url('/manga/increment'), {
data, data,
dataType: 'json', dataType: 'json',
type: 'POST', type: 'POST',

View File

@ -1,11 +1,9 @@
import _ from './base/AnimeClient.js'; 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) => { _.on('main', 'change', '.big-check', (e) => {
const id = e.target.id; const id = e.target.id;
_.$('.mal-check').forEach(el => {
el.checked = false;
})
document.getElementById(`mal_${id}`).checked = true; document.getElementById(`mal_${id}`).checked = true;
}); });
@ -21,7 +19,7 @@ export function renderAnimeSearchResults (data) {
results.push(` results.push(`
<article class="media search"> <article class="media search">
<div class="name"> <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}" /> <input type="radio" class="big-check" id="${item.slug}" name="id" value="${x.id}" />
<label for="${item.slug}"> <label for="${item.slug}">
<img src="/public/images/anime/${x.id}.jpg" alt="" width="220" /> <img src="/public/images/anime/${x.id}.jpg" alt="" width="220" />
@ -57,7 +55,7 @@ export function renderMangaSearchResults (data) {
results.push(` results.push(`
<article class="media search"> <article class="media search">
<div class="name"> <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}" /> <input type="radio" class="big-check" id="${item.slug}" name="id" value="${x.id}" />
<label for="${item.slug}"> <label for="${item.slug}">
<img src="/public/images/manga/${x.id}.jpg" alt="" width="220" /> <img src="/public/images/manga/${x.id}.jpg" alt="" width="220" />

View File

@ -11,6 +11,7 @@ query ($name: String, $type: MediaType) {
private private
notes notes
status status
updatedAt
media { media {
id id
idMal idMal

View File

@ -18,10 +18,12 @@ namespace Aviat\AnimeClient\API\Anilist\Transformer;
use Aviat\AnimeClient\API\Enum\AnimeWatchingStatus\Anilist as AnilistStatus; use Aviat\AnimeClient\API\Enum\AnimeWatchingStatus\Anilist as AnilistStatus;
use Aviat\AnimeClient\API\Mapping\AnimeWatchingStatus; 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 Aviat\Ion\Transformer\AbstractTransformer;
use DateTime;
class AnimeListTransformer extends AbstractTransformer { class AnimeListTransformer extends AbstractTransformer {
public function transform($item): AnimeListItem public function transform($item): AnimeListItem
@ -33,11 +35,11 @@ class AnimeListTransformer extends AbstractTransformer {
* Transform Anilist list item to Kitsu form update format * Transform Anilist list item to Kitsu form update format
* *
* @param array $item * @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'], 'id' => $item['id'],
'mal_id' => $item['media']['idMal'], 'mal_id' => $item['media']['idMal'],
'data' => [ 'data' => [
@ -48,7 +50,10 @@ class AnimeListTransformer extends AbstractTransformer {
'reconsumeCount' => $item['repeat'], 'reconsumeCount' => $item['repeat'],
'reconsuming' => $item['status'] === AnilistStatus::REPEATING, 'reconsuming' => $item['status'] === AnilistStatus::REPEATING,
'status' => AnimeWatchingStatus::ANILIST_TO_KITSU[$item['status']], 'status' => AnimeWatchingStatus::ANILIST_TO_KITSU[$item['status']],
] 'updatedAt' => (new DateTime())
->setTimestamp($item['updatedAt'])
->format(DateTime::W3C)
],
]); ]);
} }

View File

@ -18,10 +18,12 @@ namespace Aviat\AnimeClient\API\Anilist\Transformer;
use Aviat\AnimeClient\API\Enum\MangaReadingStatus\Anilist as AnilistStatus; use Aviat\AnimeClient\API\Enum\MangaReadingStatus\Anilist as AnilistStatus;
use Aviat\AnimeClient\API\Mapping\MangaReadingStatus; use Aviat\AnimeClient\API\Mapping\MangaReadingStatus;
use Aviat\AnimeClient\Types\MangaFormItem; use Aviat\AnimeClient\Types\FormItem;
use Aviat\Ion\Transformer\AbstractTransformer; use Aviat\Ion\Transformer\AbstractTransformer;
use DateTime;
class MangaListTransformer extends AbstractTransformer { class MangaListTransformer extends AbstractTransformer {
public function transform($item) public function transform($item)
@ -33,11 +35,11 @@ class MangaListTransformer extends AbstractTransformer {
* Transform Anilist list item to Kitsu form update format * Transform Anilist list item to Kitsu form update format
* *
* @param array $item * @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'], 'id' => $item['id'],
'mal_id' => $item['media']['idMal'], 'mal_id' => $item['media']['idMal'],
'data' => [ 'data' => [
@ -48,6 +50,9 @@ class MangaListTransformer extends AbstractTransformer {
'reconsumeCount' => $item['repeat'], 'reconsumeCount' => $item['repeat'],
'reconsuming' => $item['status'] === AnilistStatus::REPEATING, 'reconsuming' => $item['status'] === AnilistStatus::REPEATING,
'status' => MangaReadingStatus::ANILIST_TO_KITSU[$item['status']], 'status' => MangaReadingStatus::ANILIST_TO_KITSU[$item['status']],
'updatedAt' => (new DateTime())
->setTimestamp($item['updatedAt'])
->format(DateTime::W3C),
] ]
]); ]);
} }

View File

@ -67,7 +67,7 @@ final class Auth {
* @param string $password * @param string $password
* @return boolean * @return boolean
*/ */
public function authenticate($password) public function authenticate(string $password): bool
{ {
$config = $this->container->get('config'); $config = $this->container->get('config');
$username = $config->get(['kitsu_username']); $username = $config->get(['kitsu_username']);
@ -117,7 +117,7 @@ final class Auth {
* @param string $token * @param string $token
* @return boolean * @return boolean
*/ */
public function reAuthenticate(string $token) public function reAuthenticate(string $token): bool
{ {
try try
{ {
@ -162,7 +162,7 @@ final class Auth {
* *
* @return boolean * @return boolean
*/ */
public function isAuthenticated() public function isAuthenticated(): bool
{ {
return ($this->get_auth_token() !== FALSE); return ($this->get_auth_token() !== FALSE);
} }
@ -172,7 +172,7 @@ final class Auth {
* *
* @return void * @return void
*/ */
public function logout() public function logout(): void
{ {
$this->segment->clear(); $this->segment->clear();
} }
@ -185,14 +185,18 @@ final class Auth {
public function get_auth_token() public function get_auth_token()
{ {
$token = $this->segment->get('auth_token', FALSE); $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); $isExpired = time() > $this->segment->get('auth_token_expires', 0);
// Attempt to re-authenticate with refresh token // Attempt to re-authenticate with refresh token
if ($isExpired && $refresh_token) if ($isExpired && $refreshToken)
{ {
$reauthenticated = $this->reAuthenticate($refresh_token); if ($this->reAuthenticate($refreshToken))
return $this->segment->get('auth_token', FALSE); {
return $this->segment->get('auth_token', FALSE);
}
return FALSE;
} }
return $token; return $token;

View File

@ -19,7 +19,7 @@ namespace Aviat\AnimeClient\API\Kitsu\Transformer;
use Aviat\AnimeClient\API\Kitsu; use Aviat\AnimeClient\API\Kitsu;
use Aviat\AnimeClient\Types\{ use Aviat\AnimeClient\Types\{
Anime, Anime,
AnimeFormItem, FormItem,
AnimeListItem AnimeListItem
}; };
use Aviat\Ion\Transformer\AbstractTransformer; use Aviat\Ion\Transformer\AbstractTransformer;
@ -95,7 +95,7 @@ final class AnimeListTransformer extends AbstractTransformer {
'title' => $title, 'title' => $title,
'titles' => $titles, 'titles' => $titles,
'slug' => $anime['slug'], 'slug' => $anime['slug'],
'show_type' => $this->string($anime['showType'])->upperCaseFirst()->__toString(), 'show_type' => $this->string($anime['subtype'])->upperCaseFirst()->__toString(),
'cover_image' => $anime['posterImage']['small'], 'cover_image' => $anime['posterImage']['small'],
'genres' => $genres, 'genres' => $genres,
'streaming_links' => $streamingLinks, 'streaming_links' => $streamingLinks,
@ -114,14 +114,14 @@ final class AnimeListTransformer extends AbstractTransformer {
* api response format * api response format
* *
* @param array $item Transformed library item * @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']); $privacy = (array_key_exists('private', $item) && $item['private']);
$rewatching = (array_key_exists('rewatching', $item) && $item['rewatching']); $rewatching = (array_key_exists('rewatching', $item) && $item['rewatching']);
$untransformed = new AnimeFormItem([ $untransformed = new FormItem([
'id' => $item['id'], 'id' => $item['id'],
'anilist_item_id' => $item['anilist_item_id'] ?? NULL, 'anilist_item_id' => $item['anilist_item_id'] ?? NULL,
'mal_id' => $item['mal_id'] ?? NULL, 'mal_id' => $item['mal_id'] ?? NULL,

View File

@ -18,7 +18,7 @@ namespace Aviat\AnimeClient\API\Kitsu\Transformer;
use Aviat\AnimeClient\API\Kitsu; use Aviat\AnimeClient\API\Kitsu;
use Aviat\AnimeClient\Types\{ use Aviat\AnimeClient\Types\{
MangaFormItem, MangaFormItemData, FormItem, FormItemData,
MangaListItem, MangaListItemDetail MangaListItem, MangaListItemDetail
}; };
use Aviat\Ion\StringWrapper; use Aviat\Ion\StringWrapper;
@ -97,7 +97,7 @@ final class MangaListTransformer extends AbstractTransformer {
'slug' => $manga['slug'], 'slug' => $manga['slug'],
'title' => $title, 'title' => $title,
'titles' => $titles, 'titles' => $titles,
'type' => $manga['mangaType'], 'type' => $this->string($manga['subtype'])->upperCaseFirst()->__toString(),
'url' => 'https://kitsu.io/manga/' . $manga['slug'], 'url' => 'https://kitsu.io/manga/' . $manga['slug'],
]), ]),
'reading_status' => $item['attributes']['status'], 'reading_status' => $item['attributes']['status'],
@ -114,16 +114,16 @@ final class MangaListTransformer extends AbstractTransformer {
* Untransform data to update the api * Untransform data to update the api
* *
* @param array $item * @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']; $rereading = array_key_exists('rereading', $item) && (bool)$item['rereading'];
$map = new MangaFormItem([ $map = new FormItem([
'id' => $item['id'], 'id' => $item['id'],
'mal_id' => $item['mal_id'], 'mal_id' => $item['mal_id'],
'data' => new MangaFormItemData([ 'data' => new FormItemData([
'status' => $item['status'], 'status' => $item['status'],
'reconsuming' => $rereading, 'reconsuming' => $rereading,
'reconsumeCount' => (int)$item['reread_count'], 'reconsumeCount' => (int)$item['reread_count'],

View File

@ -51,6 +51,26 @@ function loadToml(string $path): array
return $output; 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? * Is the array sequential, not associative?
* *

192
src/Command/MALIDCheck.php Normal file
View 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}");
}
}
}
}

View File

@ -16,14 +16,18 @@
namespace Aviat\AnimeClient\Command; namespace Aviat\AnimeClient\Command;
use Aviat\AnimeClient\API\ use Aviat\AnimeClient\API\{
{FailedResponseException, JsonAPI, Kitsu\Transformer\MangaListTransformer, ParallelAPIRequest}; FailedResponseException,
JsonAPI,
Kitsu\Transformer\MangaListTransformer,
ParallelAPIRequest
};
use Aviat\AnimeClient\API\Anilist\Transformer\{ use Aviat\AnimeClient\API\Anilist\Transformer\{
AnimeListTransformer as AALT, AnimeListTransformer as AALT,
MangaListTransformer as AMLT, MangaListTransformer as AMLT
}; };
use Aviat\AnimeClient\API\Mapping\{AnimeWatchingStatus, MangaReadingStatus}; use Aviat\AnimeClient\API\Mapping\{AnimeWatchingStatus, MangaReadingStatus};
use Aviat\AnimeClient\Types\{AnimeFormItem, MangaFormItem}; use Aviat\AnimeClient\Types\FormItem;
use Aviat\Ion\Json; use Aviat\Ion\Json;
use DateTime; use DateTime;
@ -49,9 +53,9 @@ final class SyncLists extends BaseCommand {
* *
* @param array $args * @param array $args
* @param array $options * @param array $options
* @throws \Aviat\Ion\Di\ContainerException * @throws \Aviat\Ion\Di\Exception\ContainerException
* @throws \Aviat\Ion\Di\NotFoundException * @throws \Aviat\Ion\Di\Exception\NotFoundException
* @return void * @throws \Throwable
*/ */
public function execute(array $args, array $options = []): void 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 * @param string $type
* @return void * @throws \Throwable
*/ */
protected function sync(string $type): void protected function sync(string $type): void
{ {
@ -157,6 +161,8 @@ final class SyncLists extends BaseCommand {
* Format an Anilist anime list for comparison * Format an Anilist anime list for comparison
* *
* @return array * @return array
* @throws \Aviat\Ion\Di\Exception\ContainerException
* @throws \Aviat\Ion\Di\Exception\NotFoundException
*/ */
protected function formatAnilistAnimeList(): array protected function formatAnilistAnimeList(): array
{ {
@ -188,6 +194,8 @@ final class SyncLists extends BaseCommand {
* Format an Anilist manga list for comparison * Format an Anilist manga list for comparison
* *
* @return array * @return array
* @throws \Aviat\Ion\Di\Exception\ContainerException
* @throws \Aviat\Ion\Di\Exception\NotFoundException
*/ */
protected function formatAnilistMangaList(): array protected function formatAnilistMangaList(): array
{ {
@ -223,7 +231,8 @@ final class SyncLists extends BaseCommand {
*/ */
protected function formatKitsuList(string $type = 'anime'): array 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)) if (empty($data))
{ {
@ -294,14 +303,14 @@ final class SyncLists extends BaseCommand {
] : [ ] : [
114638, // Cells at Work: Black 114638, // Cells at Work: Black
]; ];
$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_diff($malIds, $kitsuMalIds); $missingMalIds = array_diff($malIds, $kitsuMalIds);
$missingMalIds = array_diff($missingMalIds, $malBlackList); $missingMalIds = array_diff($missingMalIds, $malBlackList);
foreach($missingMalIds as $mid) foreach($missingMalIds as $mid)
{ {
$itemsToAddToKitsu[] = array_merge($anilistList[$mid]['data'], [ $itemsToAddToKitsu[] = array_merge($anilistList[$mid]['data'], [
'id' => $this->kitsuModel->getKitsuIdFromMALId((string)$mid, $type), 'id' => $this->kitsuModel->getKitsuIdFromMALId((string)$mid, $type),
'type' => $type 'type' => $type
@ -311,8 +320,8 @@ final class SyncLists extends BaseCommand {
foreach($kitsuList as $kitsuItem) foreach($kitsuList as $kitsuItem)
{ {
$malId = $kitsuItem['malId']; $malId = $kitsuItem['malId'];
if (in_array($malId, $malBlackList)) if (\in_array((int)$malId, $malBlackList, TRUE))
{ {
continue; continue;
} }
@ -341,7 +350,7 @@ final class SyncLists extends BaseCommand {
continue; continue;
} }
$statusMap = ($type === 'anime') ? AnimeWatchingStatus::class : MangaReadingStatus::class; $statusMap = ($type === 'anime') ? AnimeWatchingStatus::class : MangaReadingStatus::class;
// Looks like this item only exists on Kitsu // Looks like this item only exists on Kitsu
@ -356,13 +365,9 @@ final class SyncLists extends BaseCommand {
'repeat' => $kItem['reconsumeCount'], 'repeat' => $kItem['reconsumeCount'],
'score' => $kItem['ratingTwenty'] / 2, 'score' => $kItem['ratingTwenty'] / 2,
'status' => $newItemStatus, 'status' => $newItemStatus,
], // $kitsuItem['data'] ],
]; ];
} }
//dump($itemsToAddToAnilist);
//die();
return [ return [
'addToAnilist' => $itemsToAddToAnilist, 'addToAnilist' => $itemsToAddToAnilist,
@ -390,6 +395,7 @@ final class SyncLists extends BaseCommand {
'status', 'status',
]; ];
$diff = []; $diff = [];
$dateDiff = new DateTime($kitsuItem['data']['updatedAt']) <=> new DateTime((string)$anilistItem['data']['updatedAt']);
// Correct differences in notation // Correct differences in notation
$kitsuItem['data']['rating'] = $kitsuItem['data']['ratingTwenty'] / 2; $kitsuItem['data']['rating'] = $kitsuItem['data']['ratingTwenty'] / 2;
@ -429,7 +435,6 @@ final class SyncLists extends BaseCommand {
$return['updateType'][] = 'kitsu'; $return['updateType'][] = 'kitsu';
} }
// If status is the same, and progress count is different, use greater progress // If status is the same, and progress count is different, use greater progress
if ($sameStatus && ( ! $sameProgress)) if ($sameStatus && ( ! $sameProgress))
{ {
@ -444,17 +449,24 @@ final class SyncLists extends BaseCommand {
$return['updateType'][] = 'kitsu'; $return['updateType'][] = 'kitsu';
} }
} }
// If status is different, go with Kitsu // If status is different, use the status of the more recently updated item
if ( ! $sameStatus) if ( ! $sameStatus)
{ {
$update['data']['status'] = $kitsuItem['data']['status']; if ($dateDiff === 1)
$return['updateType'][] = 'anilist'; {
$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... // If status and progress are different, it's a bit more complicated...
// But, at least for now, assume newer record is correct // But, at least for now, assume newer record is correct
/* if ( ! ($sameStatus || $sameProgress)) if ( ! ($sameStatus || $sameProgress))
{ {
if ($dateDiff === 1) if ($dateDiff === 1)
{ {
@ -478,18 +490,17 @@ final class SyncLists extends BaseCommand {
$return['updateType'][] = 'kitsu'; $return['updateType'][] = 'kitsu';
} }
}*/ }
// If rating is different, use the kitsu rating, unless the other rating // Use the first set rating, otherwise use the newer rating
// is set, and the kitsu rating is not set
if ( ! $sameRating) if ( ! $sameRating)
{ {
if ($kitsuItem['data']['rating'] !== 0) if ($kitsuItem['data']['rating'] !== 0 && $dateDiff === 1)
{ {
$update['data']['rating'] = $kitsuItem['data']['rating']; $update['data']['rating'] = $kitsuItem['data']['rating'];
$return['updateType'][] = 'anilist'; $return['updateType'][] = 'anilist';
} }
else else if($dateDiff === -1)
{ {
$update['data']['rating'] = $anilistItem['data']['rating']; $update['data']['rating'] = $anilistItem['data']['rating'];
$return['updateType'][] = 'kitsu'; $return['updateType'][] = 'kitsu';
@ -526,30 +537,17 @@ 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'] = [ $return['meta'] = [
'kitsu' => $kitsuItem['data'], 'kitsu' => $kitsuItem['data'],
'anilist' => $anilistItem['data'], 'anilist' => $anilistItem['data'],
// 'dateDiff' => $dateDiff, 'dateDiff' => $dateDiff,
'diff' => $diff, 'diff' => $diff,
]; ];
$return['data'] = $update; $return['data'] = $update;
$return['updateType'] = array_unique($return['updateType']); $return['updateType'] = array_unique($return['updateType']);
// Fill in missing data values for update on Anlist // Fill in missing data values for update on Anlist
// so I don't have to create a really complex graphql query // so I don't have to create a really complex graphql query
// to handle each combination of fields // to handle each combination of fields
@ -564,11 +562,11 @@ final class SyncLists extends BaseCommand {
'reconsuming' => $kitsuItem['data']['reconsuming'], 'reconsuming' => $kitsuItem['data']['reconsuming'],
'status' => $kitsuItem['data']['status'], 'status' => $kitsuItem['data']['status'],
]; ];
$return['data']['data'] = array_merge($prevData, $return['data']['data']); $return['data']['data'] = array_merge($prevData, $return['data']['data']);
} }
// dump($return); dump($return);
return $return; return $return;
} }
@ -579,18 +577,17 @@ final class SyncLists extends BaseCommand {
* @param array $itemsToUpdate * @param array $itemsToUpdate
* @param string $action * @param string $action
* @param string $type * @param string $type
* @throws \Throwable
*/ */
protected function updateKitsuListItems(array $itemsToUpdate, string $action = 'update', string $type = 'anime'): void protected function updateKitsuListItems(array $itemsToUpdate, string $action = 'update', string $type = 'anime'): void
{ {
$requester = new ParallelAPIRequest(); $requester = new ParallelAPIRequest();
foreach($itemsToUpdate as $item) foreach($itemsToUpdate as $item)
{ {
$typeClass = '\\Aviat\\AnimeClient\\Types\\' . ucFirst($type) . 'FormItem';
if ($action === 'update') if ($action === 'update')
{ {
$requester->addRequest( $requester->addRequest(
$this->kitsuModel->updateListItem(new $typeClass($item)) $this->kitsuModel->updateListItem(new FormItem($item))
); );
} }
else if ($action === 'create') else if ($action === 'create')
@ -626,19 +623,18 @@ final class SyncLists extends BaseCommand {
* @param array $itemsToUpdate * @param array $itemsToUpdate
* @param string $action * @param string $action
* @param string $type * @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(); $requester = new ParallelAPIRequest();
$typeClass = '\\Aviat\\AnimeClient\\Types\\' . ucFirst($type) . 'FormItem';
foreach($itemsToUpdate as $item) foreach($itemsToUpdate as $item)
{ {
if ($action === 'update') if ($action === 'update')
{ {
$requester->addRequest( $requester->addRequest(
$this->anilistModel->updateListItem(new $typeClass($item), $type) $this->anilistModel->updateListItem(new FormItem($item), $type)
); );
} }
else if ($action === 'create') else if ($action === 'create')

View File

@ -20,7 +20,7 @@ use Aviat\AnimeClient\Controller as BaseController;
use Aviat\AnimeClient\API\Kitsu\Transformer\AnimeListTransformer; 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\Mapping\AnimeWatchingStatus; use Aviat\AnimeClient\API\Mapping\AnimeWatchingStatus;
use Aviat\AnimeClient\Types\AnimeFormItem; use Aviat\AnimeClient\Types\FormItem;
use Aviat\Ion\Di\ContainerInterface; use Aviat\Ion\Di\ContainerInterface;
use Aviat\Ion\Json; use Aviat\Ion\Json;
use Aviat\Ion\StringWrapper; use Aviat\Ion\StringWrapper;
@ -204,7 +204,7 @@ final class Anime extends BaseController {
// large form-based updates // large form-based updates
$transformer = new AnimeListTransformer(); $transformer = new AnimeListTransformer();
$postData = $transformer->untransform($data); $postData = $transformer->untransform($data);
$fullResult = $this->model->updateLibraryItem(new AnimeFormItem($postData)); $fullResult = $this->model->updateLibraryItem(new FormItem($postData));
if ($fullResult['statusCode'] === 200) if ($fullResult['statusCode'] === 200)
{ {
@ -235,7 +235,7 @@ final class Anime extends BaseController {
$data = $this->request->getParsedBody(); $data = $this->request->getParsedBody();
} }
$response = $this->model->incrementLibraryItem(new AnimeFormItem($data)); $response = $this->model->incrementLibraryItem(new FormItem($data));
$this->cache->clear(); $this->cache->clear();
$this->outputJSON($response['body'], $response['statusCode']); $this->outputJSON($response['body'], $response['statusCode']);

View File

@ -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 * Get image covers from kitsu
* *

View File

@ -20,7 +20,7 @@ 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\Model\Manga as MangaModel; 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\Di\ContainerInterface;
use Aviat\Ion\{Json, StringWrapper}; use Aviat\Ion\{Json, StringWrapper};
@ -207,7 +207,7 @@ final class Manga extends Controller {
// large form-based updates // large form-based updates
$transformer = new MangaListTransformer(); $transformer = new MangaListTransformer();
$post_data = $transformer->untransform($data); $post_data = $transformer->untransform($data);
$full_result = $this->model->updateLibraryItem(new MangaFormItem($post_data)); $full_result = $this->model->updateLibraryItem(new FormItem($post_data));
if ($full_result['statusCode'] === 200) if ($full_result['statusCode'] === 200)
{ {
@ -237,7 +237,7 @@ final class Manga extends Controller {
$data = $this->request->getParsedBody(); $data = $this->request->getParsedBody();
} }
$response = $this->model->incrementLibraryItem(new MangaFormItem($data)); $response = $this->model->incrementLibraryItem(new FormItem($data));
$this->cache->clear(); $this->cache->clear();
$this->outputJSON($response['body'], $response['statusCode']); $this->outputJSON($response['body'], $response['statusCode']);

View File

@ -20,7 +20,7 @@ use Aviat\AnimeClient\API\ParallelAPIRequest;
use Aviat\AnimeClient\API\Mapping\AnimeWatchingStatus; use Aviat\AnimeClient\API\Mapping\AnimeWatchingStatus;
use Aviat\AnimeClient\Types\{ use Aviat\AnimeClient\Types\{
Anime as AnimeType, Anime as AnimeType,
AnimeFormItem, FormItem,
AnimeListItem AnimeListItem
}; };
use Aviat\Ion\Di\ContainerInterface; use Aviat\Ion\Di\ContainerInterface;
@ -187,10 +187,10 @@ class Anime extends API {
/** /**
* Increment progress for the specified anime * Increment progress for the specified anime
* *
* @param AnimeFormItem $data * @param FormItem $data
* @return array * @return array
*/ */
public function incrementLibraryItem(AnimeFormItem $data): array public function incrementLibraryItem(FormItem $data): array
{ {
$requester = new ParallelAPIRequest(); $requester = new ParallelAPIRequest();
$requester->addRequest($this->kitsuModel->incrementListItem($data), 'kitsu'); $requester->addRequest($this->kitsuModel->incrementListItem($data), 'kitsu');
@ -216,10 +216,10 @@ class Anime extends API {
/** /**
* Update a list entry * Update a list entry
* *
* @param AnimeFormItem $data * @param FormItem $data
* @return array * @return array
*/ */
public function updateLibraryItem(AnimeFormItem $data): array public function updateLibraryItem(FormItem $data): array
{ {
$requester = new ParallelAPIRequest(); $requester = new ParallelAPIRequest();
$requester->addRequest($this->kitsuModel->updateListItem($data), 'kitsu'); $requester->addRequest($this->kitsuModel->updateListItem($data), 'kitsu');

View File

@ -22,7 +22,7 @@ use Aviat\AnimeClient\API\{
ParallelAPIRequest ParallelAPIRequest
}; };
use Aviat\AnimeClient\Types\{ use Aviat\AnimeClient\Types\{
MangaFormItem, FormItem,
MangaListItem, MangaListItem,
MangaPage MangaPage
}; };
@ -149,10 +149,10 @@ class Manga extends API {
/** /**
* Update a list entry * Update a list entry
* *
* @param MangaFormItem $data * @param FormItem $data
* @return array * @return array
*/ */
public function updateLibraryItem(MangaFormItem $data): array public function updateLibraryItem(FormItem $data): array
{ {
$requester = new ParallelAPIRequest(); $requester = new ParallelAPIRequest();
$requester->addRequest($this->kitsuModel->updateListItem($data), 'kitsu'); $requester->addRequest($this->kitsuModel->updateListItem($data), 'kitsu');
@ -177,10 +177,10 @@ class Manga extends API {
/** /**
* Increase the progress of a list entry * Increase the progress of a list entry
* *
* @param MangaFormItem $data * @param FormItem $data
* @return array * @return array
*/ */
public function incrementLibraryItem(MangaFormItem $data): array public function incrementLibraryItem(FormItem $data): array
{ {
$requester = new ParallelAPIRequest(); $requester = new ParallelAPIRequest();
$requester->addRequest($this->kitsuModel->incrementListItem($data), 'kitsu'); $requester->addRequest($this->kitsuModel->incrementListItem($data), 'kitsu');

View File

@ -26,7 +26,7 @@ abstract class AbstractType implements ArrayAccess {
* @param $properties * @param $properties
* @return mixed * @return mixed
*/ */
public static function __set_state($properties) public static function __set_state($properties): self
{ {
return new static($properties); return new static($properties);
} }
@ -165,7 +165,7 @@ abstract class AbstractType implements ArrayAccess {
{ {
$object = $parent ?? $this; $object = $parent ?? $this;
if (is_scalar($object)) if (is_scalar($object) || empty($object))
{ {
return $object; return $object;
} }
@ -174,7 +174,7 @@ abstract class AbstractType implements ArrayAccess {
foreach ($object as $key => $value) foreach ($object as $key => $value)
{ {
$output[$key] = is_scalar($value) $output[$key] = (is_scalar($value) || empty($value))
? $value ? $value
: $this->toArray((array) $value); : $this->toArray((array) $value);
} }

View File

@ -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);
}
}

View File

@ -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 {}

View File

@ -19,12 +19,15 @@ namespace Aviat\AnimeClient\Types;
/** /**
* Type representing an Anime object for display * Type representing an Anime object for display
*/ */
abstract class FormItem extends AbstractType { class FormItem extends AbstractType {
public $id; public $id;
public $anilist_item_id; public $anilist_item_id;
public $mal_id; public $mal_id;
public $data; public $data;
abstract public function setData($value): void; public function setData($value): void
{
$this->data = new FormItemData($value);
}
} }

View File

@ -17,9 +17,9 @@
namespace Aviat\AnimeClient\Types; 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 $notes;
public $private; public $private;
public $progress; public $progress;
@ -28,4 +28,5 @@ abstract class FormItemData extends AbstractType {
public $reconsumeCount; public $reconsumeCount;
public $reconsuming; public $reconsuming;
public $status; public $status;
public $updatedAt;
} }

View File

@ -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);
}
}

View File

@ -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 {}