Browse Source

Merge remote-tracking branch 'origin/develop'

master
Timothy Warren 4 months ago
parent
commit
dcc5b928c7
  1. 4
      CHANGELOG.md
  2. 2
      app/templates/anime-cover.php
  3. 2
      app/templates/manga-cover.php
  4. 11
      app/views/anime/details.php
  5. 2
      app/views/anime/edit.php
  6. 6
      app/views/anime/list.php
  7. 12
      app/views/character/details.php
  8. 2
      app/views/collection/add.php
  9. 6
      app/views/collection/list.php
  10. 4
      app/views/history.php
  11. 6
      app/views/manga/details.php
  12. 2
      app/views/manga/edit.php
  13. 4
      app/views/manga/list.php
  14. 11
      app/views/person/details.php
  15. 6
      app/views/user/details.php
  16. 6
      console
  17. 23
      frontEndSrc/js/anime.js
  18. 20
      frontEndSrc/js/base/sort-tables.js
  19. 22
      frontEndSrc/js/template-helpers.js
  20. 2
      index.php
  21. 0
      public/images/avatars/.gitkeep
  22. 0
      public/images/characters/.gitkeep
  23. 0
      public/images/manga/.gitkeep
  24. 0
      public/images/people/.gitkeep
  25. BIN
      public/images/placeholder.png
  26. BIN
      public/images/placeholder.webp
  27. 2
      public/js/scripts.min.js
  28. 2
      public/js/scripts.min.js.map
  29. 2
      public/js/tables.min.js
  30. 2
      public/js/tables.min.js.map
  31. 2
      src/AnimeClient/API/APIRequestBuilder.php
  32. 2
      src/AnimeClient/API/AbstractListItem.php
  33. 10
      src/AnimeClient/API/Anilist/Model.php
  34. 22
      src/AnimeClient/API/Anilist/RequestBuilder.php
  35. 3
      src/AnimeClient/API/Anilist/Transformer/AnimeListTransformer.php
  36. 3
      src/AnimeClient/API/Anilist/Transformer/MangaListTransformer.php
  37. 6
      src/AnimeClient/API/Anilist/Types/MediaListEntry.php
  38. 235
      src/AnimeClient/API/Anilist/schema.graphql
  39. 1
      src/AnimeClient/API/CacheTrait.php
  40. 9
      src/AnimeClient/API/Kitsu/Auth.php
  41. 7
      src/AnimeClient/API/Kitsu/ListItem.php
  42. 11
      src/AnimeClient/API/Kitsu/Model.php
  43. 1
      src/AnimeClient/API/Kitsu/MutationTrait.php
  44. 6
      src/AnimeClient/API/Kitsu/Queries/CharacterDetails.graphql
  45. 5
      src/AnimeClient/API/Kitsu/Queries/GetUserHistory.graphql
  46. 2
      src/AnimeClient/API/Kitsu/Queries/PersonDetails.graphql
  47. 8
      src/AnimeClient/API/Kitsu/Queries/SearchAnime.graphql
  48. 8
      src/AnimeClient/API/Kitsu/Queries/SearchManga.graphql
  49. 5
      src/AnimeClient/API/Kitsu/Queries/UserDetails.graphql
  50. 17
      src/AnimeClient/API/Kitsu/RequestBuilder.php
  51. 2
      src/AnimeClient/API/Kitsu/RequestBuilderTrait.php
  52. 2
      src/AnimeClient/API/Kitsu/Transformer/AnimeListTransformer.php
  53. 10
      src/AnimeClient/API/Kitsu/Transformer/AnimeTransformer.php
  54. 3
      src/AnimeClient/API/Kitsu/Transformer/CharacterTransformer.php
  55. 9
      src/AnimeClient/API/Kitsu/Transformer/HistoryTransformer.php
  56. 4
      src/AnimeClient/API/Kitsu/Transformer/LibraryEntryTransformer.php
  57. 2
      src/AnimeClient/API/Kitsu/Transformer/MangaListTransformer.php
  58. 8
      src/AnimeClient/API/Kitsu/Transformer/MangaTransformer.php
  59. 10
      src/AnimeClient/API/Kitsu/Transformer/PersonTransformer.php
  60. 2
      src/AnimeClient/API/Kitsu/Transformer/UserTransformer.php
  61. 97
      src/AnimeClient/AnimeClient.php
  62. 1
      src/AnimeClient/Command/CachePrime.php
  63. 2
      src/AnimeClient/Command/SyncLists.php
  64. 6
      src/AnimeClient/Controller.php
  65. 10
      src/AnimeClient/Controller/Anime.php
  66. 4
      src/AnimeClient/Controller/AnimeCollection.php
  67. 4
      src/AnimeClient/Controller/History.php
  68. 4
      src/AnimeClient/Controller/Images.php
  69. 4
      src/AnimeClient/Controller/Manga.php
  70. 2
      src/AnimeClient/Controller/People.php
  71. 16
      src/AnimeClient/Dispatcher.php
  72. 18
      src/AnimeClient/Kitsu.php
  73. 13
      src/AnimeClient/Model/Anime.php
  74. 2
      src/AnimeClient/Model/AnimeCollection.php
  75. 12
      src/AnimeClient/Model/MediaTrait.php
  76. 2
      src/AnimeClient/Types/Character.php
  77. 2
      src/AnimeClient/Types/Person.php
  78. 2
      src/AnimeClient/Util.php
  79. 18
      src/Ion/Config.php
  80. 16
      src/Ion/ConfigInterface.php
  81. 2
      src/Ion/Enum.php
  82. 26
      src/Ion/Exception/ImageCreationException.php
  83. 8
      src/Ion/Friend.php
  84. 167
      src/Ion/ImageBuilder.php
  85. 4
      src/Ion/View/HttpView.php
  86. 4
      src/Ion/ViewInterface.php
  87. 76
      tests/AnimeClient/API/Kitsu/Transformer/__snapshots__/AnimeTransformerTest__testTransform__1.yml
  88. 3
      tests/AnimeClient/API/Kitsu/Transformer/__snapshots__/CharacterTransformerTest__testTransform__1.yml
  89. 4
      tests/AnimeClient/API/Kitsu/Transformer/__snapshots__/MangaListTransformerTest__testTransform__1.yml
  90. 8
      tests/AnimeClient/API/Kitsu/Transformer/__snapshots__/MangaTransformerTest__testTransform__1.yml
  91. 9
      tests/AnimeClient/API/Kitsu/Transformer/__snapshots__/PersonTransformerTest__testTransform__1.yml
  92. 2
      tests/AnimeClient/API/Kitsu/Transformer/__snapshots__/UserTransformerTest__testTransform__1.yml
  93. 8
      tests/Ion/ConfigTest.php

4
CHANGELOG.md

@ -1,10 +1,8 @@
# Changelog
## Version 5.3
* Updated to support PHP 8.1
## Version 5.2
* Updated PHP requirement to 8
* Updated to support PHP 8.1
## Version 5.1
* Added session check, so when coming back to a page, if the session is expired, the page will refresh.

2
app/templates/anime-cover.php

@ -6,7 +6,7 @@
<?php if ($auth->isAuthenticated()): ?>
<button title="Increment episode count" class="plus-one" hidden>+1 Episode</button>
<?php endif ?>
<?= $helper->picture("images/anime/{$item['anime']['id']}.webp") ?>
<?= $helper->img($item['anime']['cover_image'], ['width' => 220, 'loading' => 'lazy']) ?>
<div class="name">
<a href="<?= $url->generate('anime.details', ['id' => $item['anime']['slug']]) ?>">

2
app/templates/manga-cover.php

@ -4,7 +4,7 @@
<button class="plus-one-chapter">+1 Chapter</button>
</div>
<?php endif ?>
<?= $helper->picture("images/manga/{$item['manga']['id']}.webp") ?>
<?= $helper->img($item['manga']['image'], ['width' => 220, 'loading' => 'lazy']) ?>
<div class="name">
<a href="<?= $url->generate('manga.details', ['id' => $item['manga']['slug']]) ?>">
<?= $escape->html($item['manga']['title']) ?>

11
app/views/anime/details.php

@ -1,13 +1,12 @@
<?php
use Aviat\AnimeClient\Kitsu;
use function Aviat\AnimeClient\getLocalImg;
?>
<main class="details fixed">
<section class="flex" unselectable>
<aside class="info">
<?= $helper->picture("images/anime/{$data['id']}-original.webp") ?>
<?= $helper->img($data['cover_image'], ['width' => '390']) ?>
<br />
@ -162,14 +161,14 @@ use function Aviat\AnimeClient\getLocalImg;
use ($component, $url, $helper) {
$rendered = [];
foreach ($characterList as $id => $character):
if (empty($character['image']['original']))
if (empty($character['image']))
{
continue;
}
$rendered[] = $component->character(
$character['name'],
$url->generate('character', ['slug' => $character['slug']]),
$helper->picture("images/characters/{$id}.webp"),
$helper->img($character['image']),
(strtolower($role) !== 'main') ? 'small-character' : 'character'
);
endforeach;
@ -187,14 +186,14 @@ use function Aviat\AnimeClient\getLocalImg;
use ($component, $url, $helper) {
$rendered = [];
foreach ($staffList as $id => $person):
if (empty($person['image']['original']))
if (empty($person['image']))
{
continue;
}
$rendered[] = $component->character(
$person['name'],
$url->generate('person', ['slug' => $person['slug']]),
$helper->picture(getLocalImg($person['image']['original'] ?? NULL)),
$helper->img($person['image']),
'character small-person',
);
endforeach;

2
app/views/anime/edit.php

@ -16,7 +16,7 @@
<tbody>
<tr>
<td rowspan="9">
<?= $helper->picture("images/anime/{$item['anime']['id']}-original.webp", "jpg", [], ["width" => "390"]) ?>
<?= $helper->img($item['anime']['cover_image']) ?>
</td>
</tr>
<tr>

6
app/views/anime/list.php

@ -25,10 +25,10 @@
<?php endif ?>
<th>Title</th>
<th>Airing Status</th>
<th>Score</th>
<th class='numeric'>Score</th>
<th>Type</th>
<th>Progress</th>
<th>Rated</th>
<th class='numeric'>Progress</th>
<th class='rating'>Age Rating</th>
<th>Attributes</th>
<?php if($hasNotes): ?><th>Notes</th><?php endif ?>
</tr>

12
app/views/character/details.php

@ -7,7 +7,7 @@ use Aviat\AnimeClient\Kitsu;
<main class="character-page details fixed">
<section class="flex flex-no-wrap">
<aside>
<?= $helper->picture("images/characters/{$data['id']}-original.webp") ?>
<?= $helper->img($data['image']) ?>
</aside>
<div>
<h2 class="toph"><?= $data['name'] ?></h2>
@ -41,7 +41,7 @@ use Aviat\AnimeClient\Kitsu;
$rendered[] = $component->media(
array_merge([$item['title']], $item['titles']),
$url->generate("{$mediaType}.details", ['id' => $item['slug']]),
$helper->picture("images/{$mediaType}/{$item['id']}.webp")
$helper->img(Kitsu::getPosterImage($item), ['width' => 220, 'loading' => 'lazy']),
);
}
@ -75,7 +75,7 @@ use Aviat\AnimeClient\Kitsu;
$link = $url->generate('person', ['id' => $c['person']['id']]);
?>
<a href="<?= $link ?>">
<?= $helper->picture(getLocalImg($c['person']['image'], TRUE)) ?>
<?= $helper->img($c['person']['image']) ?>
<div class="name">
<?= $c['person']['name'] ?>
</div>
@ -91,7 +91,7 @@ use Aviat\AnimeClient\Kitsu;
$titles = Kitsu::filterTitles($series['attributes']);
?>
<a href="<?= $link ?>">
<?= $helper->picture(getLocalImg($series['attributes']['posterImage']['small'], TRUE)) ?>
<?= $helper->img(Kitsu::getPosterImage($series['attributes'])) ?>
</a>
<div class="name">
<a href="<?= $link ?>">
@ -121,12 +121,12 @@ use Aviat\AnimeClient\Kitsu;
$person = $component->character(
$c['person']['name'],
$url->generate('person', ['slug' => $c['person']['slug']]),
$helper->picture(getLocalImg($c['person']['image']))
$helper->img($c['person']['image']['original']['url']),
);
$medias = array_map(fn ($series) => $component->media(
array_merge([$series['title']], $series['titles']),
$url->generate('anime.details', ['id' => $series['slug']]),
$helper->picture(getLocalImg($series['posterImage'], TRUE))
$helper->img(Kitsu::getPosterImage($series)),
), $c['series']);
$media = implode('', array_map('mb_trim', $medias));

2
app/views/collection/add.php

@ -9,7 +9,7 @@
<div class="cssload-inner cssload-two"></div>
<div class="cssload-inner cssload-three"></div>
</div>
<label for="search">Search for <?= $collection_type ?> by name:&nbsp;&nbsp;&nbsp;&nbsp;<input type="search" id="search" name="search" /></label>
<label for="search-anime-collection">Search for <?= $collection_type ?> by name:&nbsp;&nbsp;&nbsp;&nbsp;<input type="search" id="search-anime-collection" name="search" /></label>
<section id="series-list" class="media-wrap">
</section>
</section>

6
app/views/collection/list.php

@ -38,10 +38,10 @@
{$firstTh}
<th>Title</th>
{$mediaTh}
<th>Episode Count</th>
<th>Episode Length</th>
<th class='numeric'>Episode Count</th>
<th class='numeric'>Episode Length</th>
<th>Show Type</th>
<th>Age Rating</th>
<th class='rating'>Age Rating</th>
{$noteTh}
<th>Genres</th>
</tr>

4
app/views/history.php

@ -7,11 +7,9 @@
<article class="flex flex-no-wrap flex-justify-start">
<section class="flex-self-center history-img">
<a href="<?= $item['url'] ?>">
<?= $helper->picture(
<?= $helper->img(
$item['coverImg'],
'jpg',
['width' => '110px', 'height' => '156px'],
['width' => '110px', 'height' => '156px']
) ?>
</a>
</section>

6
app/views/manga/details.php

@ -1,7 +1,7 @@
<main class="details fixed">
<section class="flex flex-no-wrap">
<aside class="info">
<?= $helper->picture("images/manga/{$data['id']}-original.webp", 'jpg', ['class' => 'cover']) ?>
<?= $helper->img($data['cover_image'], ['class' => 'cover', 'width' => '350']) ?>
<br />
@ -79,7 +79,7 @@
$rendered[] = $component->character(
$char['name'],
$url->generate('character', ['slug' => $char['slug']]),
$helper->picture("images/characters/{$id}.webp"),
$helper->img($char['image'], ['loading' => 'lazy']),
($role !== 'main') ? 'small-character' : 'character'
);
}
@ -96,7 +96,7 @@
fn ($person) => $component->character(
$person['name'],
$url->generate('person', ['slug' => $person['slug']]),
$helper->picture("images/people/{$person['id']}.webp")
$helper->img($person['image']),
),
$people
))

2
app/views/manga/edit.php

@ -18,7 +18,7 @@
<tbody>
<tr>
<td rowspan="9">
<?= $helper->picture("images/manga/{$item['manga']['id']}-original.webp", "jpg", [], ["width" => "390"]) ?>
<?= $helper->img($item['manga']['image']) ?>
</td>
</tr>
<tr>

4
app/views/manga/list.php

@ -20,8 +20,8 @@
<td>&nbsp;</td>
<?php endif ?>
<th>Title</th>
<th>Rating</th>
<th>Completed Chapters</th>
<th class='numeric'>Score</th>
<th class='numeric'>Completed Chapters</th>
<th>Attributes</th>
<th>Type</th>
</tr>

11
app/views/person/details.php

@ -1,10 +1,7 @@
<?php
use function Aviat\AnimeClient\getLocalImg;
?>
<main class="details fixed">
<section class="flex flex-no-wrap">
<div>
<?= $helper->picture("images/people/{$data['id']}-original.webp", 'jpg', ['class' => 'cover' ]) ?>
<?= $helper->img($data['image'], ['class' => 'cover' ]) ?>
</div>
<div>
<h2 class="toph"><?= $data['name'] ?></h2>
@ -40,7 +37,7 @@ use function Aviat\AnimeClient\getLocalImg;
<?= $component->media(
$series['titles'],
$url->generate("{$mediaType}.details", ['id' => $series['slug']]),
$helper->picture("images/{$type}/{$sid}.webp")
$helper->img($series['image'], ['width' => 220, 'loading' => 'lazy'])
) ?>
<?php endforeach; ?>
</section>
@ -61,7 +58,7 @@ use function Aviat\AnimeClient\getLocalImg;
$character = $component->character(
$item['character']['canonicalName'],
$url->generate('character', ['slug' => $item['character']['slug']]),
$helper->picture(getLocalImg($item['character']['image']['original'] ?? null))
$helper->img($item['character']['image'], ['loading' => 'lazy']),
);
$medias = [];
foreach ($item['media'] as $sid => $series)
@ -69,7 +66,7 @@ use function Aviat\AnimeClient\getLocalImg;
$medias[] = $component->media(
$series['titles'],
$url->generate('anime.details', ['id' => $series['slug']]),
$helper->picture("images/anime/{$sid}.webp")
$helper->img($series['image'], ['width' => 220, 'loading' => 'lazy'])
);
}
$media = implode('', array_map('mb_trim', $medias));

6
app/views/user/details.php

@ -16,7 +16,7 @@ use Aviat\AnimeClient\Kitsu;
<section class="flex flex-no-wrap">
<aside class="info">
<center>
<?= $helper->img($urlGenerator->assetUrl($data['avatar']), ['alt' => '']); ?>
<?= $helper->img($data['avatar'], ['alt' => '']); ?>
</center>
<br />
<table class="media-details">
@ -75,7 +75,7 @@ use Aviat\AnimeClient\Kitsu;
$rendered[] = $component->character(
$item['names']['canonical'],
$url->generate('character', ['slug' => $item['slug']]),
$helper->picture("images/characters/{$item['id']}.webp")
$helper->img($item['image']['original']['url'])
);
}
else
@ -86,7 +86,7 @@ use Aviat\AnimeClient\Kitsu;
Kitsu::getFilteredTitles($item['titles']),
),
$url->generate("{$type}.details", ['id' => $item['slug']]),
$helper->picture("images/{$type}/{$item['id']}.webp"),
$helper->img(Kitsu::getPosterImage($item), ['width' => 220]),
);
}
}

6
console

@ -9,8 +9,8 @@ use ConsoleKit\Console;
$GLOBALS['_SERVER']['HTTP_HOST'] = 'localhost';
define('APP_DIR', __DIR__ . '/app');
define('TEMPLATE_DIR', APP_DIR . '/templates');
const APP_DIR = __DIR__ . '/app';
const TEMPLATE_DIR = APP_DIR . '/templates';
// -----------------------------------------------------------------------------
// Start console script
@ -26,7 +26,7 @@ try
'sync:lists' => Command\SyncLists::class
]))->run();
}
catch (\Throwable)
catch (Throwable)
{
}

23
frontEndSrc/js/anime.js

@ -1,7 +1,7 @@
import _ from './anime-client.js'
import { renderSearchResults } from './template-helpers.js'
const search = (query) => {
const search = (query, isCollection = false) => {
// Show the loader
_.show('.cssload-loader');
@ -13,10 +13,11 @@ const search = (query) => {
_.hide('.cssload-loader');
// Show the results
_.$('#series-list')[ 0 ].innerHTML = renderSearchResults('anime', searchResults);
_.$('#series-list')[ 0 ].innerHTML = renderSearchResults('anime', searchResults, isCollection);
});
};
// Anime list search
if (_.hasElement('.anime #search')) {
let prevRequest = null;
@ -34,6 +35,24 @@ if (_.hasElement('.anime #search')) {
}));
}
// Anime collection search
if (_.hasElement('#search-anime-collection')) {
let prevRequest = null;
_.on('#search-anime-collection', 'input', _.throttle(250, (e) => {
const query = encodeURIComponent(e.target.value);
if (query === '') {
return;
}
if (prevRequest !== null) {
prevRequest.abort();
}
prevRequest = search(query, true);
}));
}
// Action to increment episode count
_.on('body.anime.list', 'click', '.plus-one', (e) => {
let parentSel = _.closestParent(e.target, 'article');

20
frontEndSrc/js/base/sort-tables.js

@ -6,9 +6,22 @@ const LightTableSorter = (() => {
const sort = (a, b) => {
let textA = text(a);
let textB = text(b);
const n = parseInt(textA, 10);
if (n) {
textA = n;
console.log("Comparing " + textA + " and " + textB)
if(th.classList.contains("numeric")){
let arrayA = textA.replace('episodes: ','').replace('-',0).split("/");
let arrayB = textB.replace('episodes: ','').replace('-',0).split("/");
if(arrayA.length > 1) {
textA = parseInt(arrayA[0],10) / parseInt(arrayA[1],10);
textB = parseInt(arrayB[0],10) / parseInt(arrayB[1],10);
}
else{
textA = parseInt(arrayA[0],10);
textB = parseInt(arrayB[0],10);
}
}
else if (parseInt(textA, 10)) {
textA = parseInt(textA, 10);
textB = parseInt(textB, 10);
}
if (textA > textB) {
@ -59,6 +72,7 @@ const LightTableSorter = (() => {
for (let i = 0, len = ths.length; i < len; i++) {
let th = ths[i];
th.classList.add('sorting');
th.classList.add('testing');
results.push(th.onclick = onClickEvent);
}
return results;

22
frontEndSrc/js/template-helpers.js

@ -13,10 +13,11 @@ _.on('main', 'change', '.big-check', (e) => {
*
* @param {'anime'|'manga'} type
* @param {Object} item
* @param isCollection
* @returns {String}
*/
function renderEditLink (type, item) {
if (item.libraryEntry === null) {
function renderEditLink (type, item, isCollection = false) {
if (isCollection || item.libraryEntry === null) {
return '';
}
@ -38,13 +39,18 @@ function renderEditLink (type, item) {
*
* @param {'anime'|'manga'} type
* @param {Object} data
* @param {boolean} isCollection
* @returns {String}
*/
export function renderSearchResults (type, data) {
export function renderSearchResults (type, data, isCollection = false) {
return data.map(item => {
const titles = item.titles.join('<br />');
const disabled = item.libraryEntry !== null ? 'disabled' : '';
const editLink = renderEditLink(type, item);
let disabled = item.libraryEntry !== null ? 'disabled' : '';
const editLink = renderEditLink(type, item, isCollection);
if (isCollection) {
disabled = '';
}
return `
<article class="media search ${disabled}">
@ -52,11 +58,7 @@ export function renderSearchResults (type, data) {
<input type="radio" class="mal-check" id="mal_${item.slug}" name="mal_id" value="${item.mal_id}" ${disabled} />
<input type="radio" class="big-check" id="${item.slug}" name="id" value="${item.id}" ${disabled} />
<label for="${item.slug}">
<picture width="220">
<source srcset="/public/images/${type}/${item.id}.webp" type="image/webp" />
<source srcset="/public/images/${type}/${item.id}.jpg" type="image/jpeg" />
<img src="/public/images/${type}/${item.id}.jpg" alt="" width="220" />
</picture>
<img src="${item.coverImage}" alt="" width="220" />
<span class="name">
${item.canonicalTitle}<br />
<small>${titles}</small>

2
index.php

@ -25,7 +25,7 @@ setlocale(LC_CTYPE, 'en_US');
// Load composer autoloader
require_once __DIR__ . '/vendor/autoload.php';
Debugger::$strictMode = E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED; // all errors except deprecated notices
Debugger::$strictMode = E_ALL & ~E_DEPRECATED; // all errors except deprecated notices
Debugger::$showBar = false;
Debugger::enable(Debugger::DEVELOPMENT, __DIR__ . '/app/logs');

0
public/images/avatars/.gitkeep

0
public/images/characters/.gitkeep

0
public/images/manga/.gitkeep

0
public/images/people/.gitkeep

BIN
public/images/placeholder.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 807 B

BIN
public/images/placeholder.webp

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

2
public/js/scripts.min.js vendored

File diff suppressed because one or more lines are too long

2
public/js/scripts.min.js.map

File diff suppressed because one or more lines are too long

2
public/js/tables.min.js vendored

@ -1 +1 @@
var LightTableSorter=function(){var th=null;var cellIndex=null;var order='';var text=function(row){return row.cells.item(cellIndex).textContent.toLowerCase();};var sort=function(a,b){var textA=text(a);var textB=text(b);var n=parseInt(textA,10);if(n){textA=n;textB=parseInt(textB,10);}if(textA>textB)return 1;if(textA<textB)return -1;return 0;};var toggle=function(){var c=order!=='sorting-asc'?'sorting-asc':'sorting-desc';th.className=(th.className.replace(order,'')+' '+c).trim();return order=c;};var reset=function(){th.classList.remove('sorting-asc','sorting-desc');th.classList.add('sorting');return order='';};var onClickEvent=function(e){if(th&&cellIndex!==e.target.cellIndex)reset();th=e.target;if(th.nodeName.toLowerCase()==='th'){cellIndex=th.cellIndex;var tbody=th.offsetParent.getElementsByTagName('tbody')[0];var rows=Array.from(tbody.rows);if(rows){rows.sort(sort);if(order==='sorting-asc')rows.reverse();toggle();tbody.innerHtml='';rows.forEach(function(row){tbody.appendChild(row);});}}};return {init:function(){var ths=document.getElementsByTagName('th');var results=[];for(var i=0,len=ths.length;i<len;i++){var th1=ths[i];th1.classList.add('sorting');results.push(th1.onclick=onClickEvent);}return results;}};}();LightTableSorter.init();
var LightTableSorter=function(){var th=null;var cellIndex=null;var order='';var text=function(row){return row.cells.item(cellIndex).textContent.toLowerCase()};var sort=function(a,b){var textA=text(a);var textB=text(b);console.log("Comparing "+textA+" and "+textB);if(th.classList.contains("numeric")){var arrayA=textA.replace('episodes: ','').replace('-',0).split("/");var arrayB=textB.replace('episodes: ','').replace('-',0).split("/");if(arrayA.length>1){textA=parseInt(arrayA[0],10)/parseInt(arrayA[1],10);textB=parseInt(arrayB[0],10)/parseInt(arrayB[1],10)}else{textA=parseInt(arrayA[0],10);textB=parseInt(arrayB[0],10)}}else if(parseInt(textA,10)){textA=parseInt(textA,10);textB=parseInt(textB,10)}if(textA>textB)return 1;if(textA<textB)return -1;return 0};var toggle=function(){var c=order!=='sorting-asc'?'sorting-asc':'sorting-desc';th.className=(th.className.replace(order,'')+' '+c).trim();return order=c};var reset=function(){th.classList.remove('sorting-asc','sorting-desc');th.classList.add('sorting');return order=''};var onClickEvent=function(e){if(th&&cellIndex!==e.target.cellIndex)reset();th=e.target;if(th.nodeName.toLowerCase()==='th'){cellIndex=th.cellIndex;var tbody=th.offsetParent.getElementsByTagName('tbody')[0];var rows=Array.from(tbody.rows);if(rows){rows.sort(sort);if(order==='sorting-asc')rows.reverse();toggle();tbody.innerHtml='';rows.forEach(function(row){tbody.appendChild(row)})}}};return{init:function(){var ths=document.getElementsByTagName('th');var results=[];for(var i=0,len=ths.length;i<len;i++){var th=ths[i];th.classList.add('sorting');th.classList.add('testing');results.push(th.onclick=onClickEvent)}return results}}}();LightTableSorter.init()

2
public/js/tables.min.js.map

File diff suppressed because one or more lines are too long

2
src/AnimeClient/API/APIRequestBuilder.php

@ -154,7 +154,7 @@ abstract class APIRequestBuilder {
* Set a request header
*
* @param string $name
* @param string $value
* @param string|null $value
* @return self
*/
public function setHeader(string $name, string $value = NULL): self

2
src/AnimeClient/API/AbstractListItem.php

@ -70,7 +70,7 @@ abstract class AbstractListItem {
* Delete a list item
*
* @param string $id - The id of the list item to delete
* @return Request
* @return Request|null
*/
abstract public function delete(string $id):?Request;
}

10
src/AnimeClient/API/Anilist/Model.php

@ -207,7 +207,7 @@ final class Model
*
* @param FormItem $data
* @param string $type - Them media type (anime/manga)
* @return Request
* @return Request|null
*/
public function incrementListItem(FormItem $data, string $type): ?Request
{
@ -225,7 +225,7 @@ final class Model
*
* @param FormItem $data
* @param string $type - Them media type (anime/manga)
* @return Request
* @return Request|null
*/
public function updateListItem(FormItem $data, string $type): ?Request
{
@ -244,7 +244,7 @@ final class Model
*
* @param string $malId - The id of the list item to remove
* @param string $type - Them media type (anime/manga)
* @return Request
* @return Request|null
*/
public function deleteListItem(string $malId, string $type): ?Request
{
@ -262,7 +262,7 @@ final class Model
*
* @param string $malId
* @param string $type - The media type (anime/manga)
* @return string
* @return string|null
*/
public function getListIdFromMalId(string $malId, string $type): ?string
{
@ -306,7 +306,7 @@ final class Model
*
* @param string $malId
* @param string $type
* @return string
* @return string|null
*/
private function getMediaIdFromMalId(string $malId, string $type = 'ANIME'): ?string
{

22
src/AnimeClient/API/Anilist/RequestBuilder.php

@ -257,24 +257,18 @@ final class RequestBuilder extends APIRequestBuilder {
$validResponseCodes = [200, 201];
$logger = $this->container->getLogger('anilist-request');
if ($logger !== NULL)
{
$logger->debug('Anilist response', [
'status' => $response->getStatus(),
'reason' => $response->getReason(),
'body' => $response->getBody(),
'headers' => $response->getHeaders(),
//'requestHeaders' => $request->getHeaders(),
]);
}
$logger?->debug('Anilist response', [
'status' => $response->getStatus(),
'reason' => $response->getReason(),
'body' => $response->getBody(),
'headers' => $response->getHeaders(),
//'requestHeaders' => $request->getHeaders(),
]);
if ( ! \in_array($response->getStatus(), $validResponseCodes, TRUE))
{
if ($logger !== NULL)
{
$logger->warning('Non 200 response for POST api call', (array)$response->getBody());
}
$logger?->warning('Non 200 response for POST api call', (array)$response->getBody());
}
$rawBody = wait($response->getBody()->buffer());

3
src/AnimeClient/API/Anilist/Transformer/AnimeListTransformer.php

@ -24,6 +24,7 @@ use Aviat\AnimeClient\Types\{AnimeListItem, FormItem};
use Aviat\Ion\Transformer\AbstractTransformer;
use DateTime;
use DateTimeInterface;
class AnimeListTransformer extends AbstractTransformer {
@ -57,7 +58,7 @@ class AnimeListTransformer extends AbstractTransformer {
: AnimeWatchingStatus::ANILIST_TO_KITSU[$item['status']],
'updatedAt' => (new DateTime())
->setTimestamp($item['updatedAt'])
->format(DateTime::W3C)
->format(DateTimeInterface::W3C)
],
]);
}

3
src/AnimeClient/API/Anilist/Transformer/MangaListTransformer.php

@ -25,6 +25,7 @@ use Aviat\AnimeClient\Types\FormItem;
use Aviat\Ion\Transformer\AbstractTransformer;
use DateTime;
use DateTimeInterface;
class MangaListTransformer extends AbstractTransformer {
@ -58,7 +59,7 @@ class MangaListTransformer extends AbstractTransformer {
: MangaReadingStatus::ANILIST_TO_KITSU[$item['status']],
'updatedAt' => (new DateTime())
->setTimestamp($item['updatedAt'])
->format(DateTime::W3C),
->format(DateTimeInterface::W3C),
]
]);
}

6
src/AnimeClient/API/Anilist/Types/MediaListEntry.php

@ -19,10 +19,8 @@ namespace Aviat\AnimeClient\API\Anilist\Types;
use Aviat\AnimeClient\Types\AbstractType;
class MediaListEntry extends AbstractType {
/**
* @var int|string
*/
public $id;
public int|string $id;
public ?string $notes;

235
src/AnimeClient/API/Anilist/schema.graphql

@ -12,7 +12,7 @@ union ActivityUnion = ListActivity | MessageActivity | TextActivity
union LikeableUnion = ActivityReply | ListActivity | MessageActivity | TextActivity | Thread | ThreadComment
"Notification union type"
union NotificationUnion = ActivityLikeNotification | ActivityMentionNotification | ActivityMessageNotification | ActivityReplyLikeNotification | ActivityReplyNotification | ActivityReplySubscribedNotification | AiringNotification | FollowingNotification | RelatedMediaAdditionNotification | ThreadCommentLikeNotification | ThreadCommentMentionNotification | ThreadCommentReplyNotification | ThreadCommentSubscribedNotification | ThreadLikeNotification
union NotificationUnion = ActivityLikeNotification | ActivityMentionNotification | ActivityMessageNotification | ActivityReplyLikeNotification | ActivityReplyNotification | ActivityReplySubscribedNotification | AiringNotification | FollowingNotification | MediaDataChangeNotification | MediaDeletionNotification | MediaMergeNotification | RelatedMediaAdditionNotification | ThreadCommentLikeNotification | ThreadCommentMentionNotification | ThreadCommentReplyNotification | ThreadCommentSubscribedNotification | ThreadLikeNotification
"Notification for when a activity is liked"
type ActivityLikeNotification {
@ -227,6 +227,8 @@ type AniChartUser {
type Character {
"The character's age. Note this is a string, not an int, it may contain further text and additional ages."
age: String
"The characters blood type"
bloodType: String
"The character's birth date"
dateOfBirth: FuzzyDate
"A general description of the character"
@ -262,7 +264,7 @@ type Character {
name: CharacterName
"The url for the character page on the AniList website"
siteUrl: String
updatedAt: Int @deprecated(reason : "No data available")
updatedAt: Int @deprecated(reason: "No data available")
}
type CharacterConnection {
@ -314,15 +316,21 @@ type CharacterName {
middle: String
"The character's full name in their native language"
native: String
"The currently authenticated users preferred name language. Default romaji for non-authenticated"
userPreferred: String
}
"A submission for a character that features in an anime or manga"
type CharacterSubmission {
"Data Mod assigned to handle the submission"
assignee: User
"Character that the submission is referencing"
character: Character
createdAt: Int
"The id of the submission"
id: Int!
"Whether the submission is locked"
locked: Boolean
"Inner details of submission status"
notes: String
source: String
@ -543,6 +551,7 @@ type InternalPage {
sort: [AiringSort]
): [AiringSchedule]
characterSubmissions(
assigneeId: Int,
characterId: Int,
"The order the results will be returned in"
sort: [SubmissionSort],
@ -654,6 +663,8 @@ type InternalPage {
id_not_in: [Int],
"Filter by if the media's intended for 18+ adult audiences"
isAdult: Boolean,
"If the media is officially licensed or a self-published doujin release"
isLicensed: Boolean,
"Filter media by sites with a online streaming or reading license"
licensedBy: String,
"Filter media by sites with a online streaming or reading license"
@ -772,6 +783,7 @@ type InternalPage {
userName: String
): [MediaList]
mediaSubmissions(
assigneeId: Int,
mediaId: Int,
"The order the results will be returned in"
sort: [SubmissionSort],
@ -864,7 +876,7 @@ type InternalPage {
"Filter by user who created the recommendation"
userId: Int
): [Recommendation]
reports: [Report]
reports(reportedId: Int, reporterId: Int): [Report]
reviews(
"Filter by Review id"
id: Int,
@ -906,6 +918,7 @@ type InternalPage {
sort: [StaffSort]
): [Staff]
staffSubmissions(
assigneeId: Int,
"The order the results will be returned in"
sort: [SubmissionSort],
staffId: Int,
@ -958,9 +971,15 @@ type InternalPage {
"Filter by the user id of the thread's creator"
userId: Int
): [Thread]
userBlockSearch(
"Filter by search query"
search: String
): [User]
users(
"Filter by the user id"
id: Int,
"Filter to moderators only if true"
isModerator: Boolean,
"Filter by the name of the user"
name: String,
"Filter by search query"
@ -1073,6 +1092,8 @@ type Media {
isAdult: Boolean
"If the media is marked as favourite by the current authenticated user"
isFavourite: Boolean!
"If the media is blocked from being added to favourites"
isFavouriteBlocked: Boolean!
"If the media is officially licensed or a self-published doujin release"
isLicensed: Boolean
"Locked media may not be added to lists our favorited. This may be due to the entry pending for deletion or other reasons."
@ -1120,7 +1141,7 @@ type Media {
siteUrl: String
"Source type the media was adapted from."
source(
"Provide 2 to use new version 2 of sources enum"
"Provide 2 or 3 to use new version 2 or 3 of sources enum"
version: Int
): MediaSource
"The staff who produced the media"
@ -1205,6 +1226,40 @@ type MediaCoverImage {
medium: String
}
"Notification for when a media entry's data was changed in a significant way impacting users' list tracking"
type MediaDataChangeNotification {
"The reason for the media data change"
context: String
"The time the notification was created at"
createdAt: Int
"The id of the Notification"
id: Int!
"The media that received data changes"
media: Media
"The id of the media that received data changes"
mediaId: Int!
"The reason for the media data change"
reason: String
"The type of notification"
type: NotificationType
}
"Notification for when a media tracked in a user's list is deleted from the site"
type MediaDeletionNotification {
"The reason for the media deletion"
context: String
"The time the notification was created at"
createdAt: Int
"The title of the deleted media"
deletedMediaTitle: String
"The id of the Notification"
id: Int!
"The reason for the media deletion"
reason: String
"The type of notification"
type: NotificationType
}
"Media connection edge"
type MediaEdge {
"Media specific character name"
@ -1298,13 +1353,13 @@ type MediaList {
"List of anime or manga"
type MediaListCollection {
"A map of media list entry arrays grouped by custom lists"
customLists(asArray: Boolean): [[MediaList]] @deprecated(reason : "Not GraphQL spec compliant, use lists field instead.")
customLists(asArray: Boolean): [[MediaList]] @deprecated(reason: "Not GraphQL spec compliant, use lists field instead.")
"If there is another chunk"
hasNextChunk: Boolean
"Grouped media list entries"
lists: [MediaListGroup]
"A map of media list entry arrays grouped by status"
statusLists(asArray: Boolean): [[MediaList]] @deprecated(reason : "Not GraphQL spec compliant, use lists field instead.")
statusLists(asArray: Boolean): [[MediaList]] @deprecated(reason: "Not GraphQL spec compliant, use lists field instead.")
"The owner of the list"
user: User
}
@ -1330,10 +1385,10 @@ type MediaListOptions {
"The score format the user is using for media lists"
scoreFormat: ScoreFormat
"The list theme options for both lists"
sharedTheme: Json @deprecated(reason : "No longer used")
sharedTheme: Json @deprecated(reason: "No longer used")
"If the shared theme should be used instead of the individual list themes"
sharedThemeEnabled: Boolean @deprecated(reason : "No longer used")
useLegacyLists: Boolean @deprecated(reason : "No longer used")
sharedThemeEnabled: Boolean @deprecated(reason: "No longer used")
useLegacyLists: Boolean @deprecated(reason: "No longer used")
}
"A user's list options for anime or manga lists"
@ -1349,7 +1404,27 @@ type MediaListTypeOptions {
"If the completed sections of the list should be separated by format"
splitCompletedSectionByFormat: Boolean
"The list theme options"
theme: Json @deprecated(reason : "This field has not yet been fully implemented and may change without warning")
theme: Json @deprecated(reason: "This field has not yet been fully implemented and may change without warning")
}
"Notification for when a media entry is merged into another for a user who had it on their list"
type MediaMergeNotification {
"The reason for the media data change"
context: String
"The time the notification was created at"
createdAt: Int
"The title of the deleted media"
deletedMediaTitles: [String]
"The id of the Notification"
id: Int!
"The media that was merged into"
media: Media
"The id of the media that was merged into"
mediaId: Int!
"The reason for the media merge"
reason: String
"The type of notification"
type: NotificationType
}
"The ranking of a media in a particular time span and format compared to other media"
@ -1374,7 +1449,7 @@ type MediaRank {
"A media's statistics"
type MediaStats {
airingProgression: [AiringProgression] @deprecated(reason : "Replaced by MediaTrends")
airingProgression: [AiringProgression] @deprecated(reason: "Replaced by MediaTrends")
scoreDistribution: [ScoreDistribution]
statusDistribution: [StatusDistribution]
}
@ -1393,12 +1468,16 @@ type MediaStreamingEpisode {
"Media submission"
type MediaSubmission {
"Data Mod assigned to handle the submission"
assignee: User
changes: [String]
characters: [MediaSubmissionComparison]
createdAt: Int
externalLinks: [MediaExternalLink]
"The id of the submission"
id: Int!
"Whether the submission is locked"
locked: Boolean
media: Media
notes: String
relations: [MediaEdge]
@ -1458,6 +1537,8 @@ type MediaTag {
name: String!
"The relevance ranking of the tag out of the 100 for this media"
rank: Int
"The user who submitted the tag"
userId: Int
}
"The official titles of the media in various languages"
@ -1738,6 +1819,8 @@ type Mutation {
comment: String,
"The comment id, required for updating"
id: Int,
"If the comment tree should be locked. (Mod Only)"
locked: Boolean,
"The id of thread comment to reply to"
parentCommentId: Int,
"The id of thread the comment belongs to"
@ -1872,6 +1955,8 @@ type Mutation {
rowOrder: String,
"The user's list scoring system"
scoreFormat: ScoreFormat,
"The language the user wants to see staff and character names in"
staffNameLanguage: UserStaffNameLanguage,
"Timezone offset format: -?HH:MM"
timezone: String,
"User's title language"
@ -2094,6 +2179,8 @@ type Page {
id_not_in: [Int],
"Filter by if the media's intended for 18+ adult audiences"
isAdult: Boolean,
"If the media is officially licensed or a self-published doujin release"
isLicensed: Boolean,
"Filter media by sites with a online streaming or reading license"
licensedBy: String,
"Filter media by sites with a online streaming or reading license"
@ -2368,6 +2455,8 @@ type Page {
users(
"Filter by the user id"
id: Int,
"Filter to moderators only if true"
isModerator: Boolean,
"Filter by the name of the user"
name: String,
"Filter by search query"
@ -2618,6 +2707,8 @@ type Query {
id_not_in: [Int],
"Filter by if the media's intended for 18+ adult audiences"
isAdult: Boolean,
"If the media is officially licensed or a self-published doujin release"
isLicensed: Boolean,
"Filter media by sites with a online streaming or reading license"
licensedBy: String,
"Filter media by sites with a online streaming or reading license"
@ -2958,6 +3049,8 @@ type Query {
User(
"Filter by the user id"
id: Int,
"Filter to moderators only if true"
isModerator: Boolean,
"Filter by the name of the user"
name: String,
"Filter by search query"
@ -3014,6 +3107,7 @@ type RelatedMediaAdditionNotification {
}
type Report {
cleared: Boolean
"When the entry data was created"
createdAt: Int
id: Int!
@ -3179,6 +3273,8 @@ type SiteTrendEdge {
type Staff {
"The person's age in years"
age: Int
"The persons blood type"
bloodType: String
"Media the actor voiced characters in. (Same data as characters with media as node instead of characters)"
characterMedia(
onList: Boolean,
@ -3218,7 +3314,7 @@ type Staff {
"If the staff member is blocked from being added to favourites"
isFavouriteBlocked: Boolean!
"The primary language the staff member dub's in"
language: StaffLanguage @deprecated(reason : "Replaced with languageV2")
language: StaffLanguage @deprecated(reason: "Replaced with languageV2")
"The primary language of the staff member. Current values: Japanese, English, Korean, Italian, Spanish, Portuguese, French, German, Hebrew, Hungarian, Chinese, Arabic, Filipino, Catalan"
languageV2: String
"Notes for site moderators"
@ -3247,7 +3343,7 @@ type Staff {
submissionStatus: Int
"Submitter for the submission"
submitter: User
updatedAt: Int @deprecated(reason : "No data available")
updatedAt: Int @deprecated(reason: "No data available")
"[startYear, endYear] (If the 2nd value is not present staff is still active)"
yearsActive: [Int]
}
@ -3291,6 +3387,8 @@ type StaffName {
middle: String
"The person's full name in their native language"
native: String
"The currently authenticated users preferred name language. Default romaji for non-authenticated"
userPreferred: String
}
"Voice actor role for a character"
@ -3314,9 +3412,13 @@ type StaffStats {
"A submission for a staff that features in an anime or manga"
type StaffSubmission {
"Data Mod assigned to handle the submission"
assignee: User
createdAt: Int
"The id of the submission"
id: Int!
"Whether the submission is locked"
locked: Boolean
"Inner details of submission status"
notes: String
source: String
@ -3511,6 +3613,8 @@ type ThreadComment {
id: Int!
"If the currently authenticated user liked the comment"
isLiked: Boolean
"If the comment tree is locked and may not receive replies or edits"
isLocked: Boolean
"The amount of likes the comment has"
likeCount: Int!
"The users who liked the comment"
@ -3651,6 +3755,8 @@ type User {
"The user's banner images"
bannerImage: String
bans: Json
"When the user's account was created. (Does not exist for accounts created before 2020)"
createdAt: Int
"Custom donation badge text"
donatorBadge: String
"The donation tier of the user"
@ -3670,18 +3776,22 @@ type User {
isFollowing: Boolean
"The user's media list options"
mediaListOptions: MediaListOptions
"The user's moderator roles if they are a site moderator"
moderatorRoles: [ModRole]
"If the user is a moderator or data moderator"
moderatorStatus: String
moderatorStatus: String @deprecated(reason: "Deprecated. Replaced with moderatorRoles field.")
"The name of the user"
name: String!
"The user's general options"
options: UserOptions
"The user's previously used names."
previousNames: [UserPreviousName]
"The url for the user page on the AniList website"
siteUrl: String
"The users anime & manga list statistics"
statistics: UserStatisticTypes
"The user's statistics"
stats: UserStats @deprecated(reason : "Deprecated. Replaced with statistics field.")
stats: UserStats @deprecated(reason: "Deprecated. Replaced with statistics field.")
"The number of unread notifications the user has"
unreadNotificationCount: Int
"When the user's data was last updated"
@ -3747,7 +3857,9 @@ type UserModData {
alts: [User]
bans: Json
counts: Json
email: String
ip: Json
privacy: Int
}
"A user's general options"