Version 5.1 - All the GraphQL #32
@ -110,6 +110,7 @@ $routes = [
|
||||
],
|
||||
'anime.collection.view' => [
|
||||
'path' => '/anime-collection/view{/view}',
|
||||
'action' => 'view',
|
||||
'tokens' => [
|
||||
'view' => ALPHA_SLUG_PATTERN,
|
||||
],
|
||||
@ -119,6 +120,12 @@ $routes = [
|
||||
'action' => 'delete',
|
||||
'verb' => 'post',
|
||||
],
|
||||
'anime.collection.redirect' => [
|
||||
'path' => '/anime-collection',
|
||||
],
|
||||
'anime.collection.redirect2' => [
|
||||
'path' => '/anime-collection/',
|
||||
],
|
||||
// ---------------------------------------------------------------------
|
||||
// Manga Collection Routes
|
||||
// ---------------------------------------------------------------------
|
||||
|
@ -5,6 +5,9 @@
|
||||
<?php if (empty($sections)): ?>
|
||||
<h3>There's nothing here!</h3>
|
||||
<?php else: ?>
|
||||
<br />
|
||||
<label>Filter: <input type='text' class='media-filter' /></label>
|
||||
<br />
|
||||
<?php foreach ($sections as $name => $items): ?>
|
||||
<?php if (empty($items)): ?>
|
||||
<section class="status">
|
||||
|
@ -5,12 +5,15 @@
|
||||
<?php if (empty($sections)): ?>
|
||||
<h3>There's nothing here!</h3>
|
||||
<?php else: ?>
|
||||
<br />
|
||||
<label>Filter: <input type='text' class='media-filter' /></label>
|
||||
<br />
|
||||
<?php foreach ($sections as $name => $items): ?>
|
||||
<h2><?= $name ?></h2>
|
||||
<?php if (empty($items)): ?>
|
||||
<h3>There's nothing here!</h3>
|
||||
<?php else: ?>
|
||||
<table>
|
||||
<table class='media-wrap'>
|
||||
<thead>
|
||||
<tr>
|
||||
<?php if($auth->isAuthenticated()): ?>
|
||||
@ -40,7 +43,7 @@
|
||||
]) ?>">Edit</a>
|
||||
</td>
|
||||
<?php endif ?>
|
||||
<td class="justify">
|
||||
<td class="align-left justify">
|
||||
<a href="<?= $url->generate('anime.details', ['id' => $item['anime']['slug']]) ?>">
|
||||
<?= $item['anime']['title'] ?>
|
||||
</a>
|
||||
|
@ -5,6 +5,9 @@
|
||||
<?php if (empty($sections)): ?>
|
||||
<h3>There's nothing here!</h3>
|
||||
<?php else: ?>
|
||||
<br />
|
||||
<label>Filter: <input type='text' class='media-filter' /></label>
|
||||
<br />
|
||||
<div class="tabs">
|
||||
<?php $i = 0; ?>
|
||||
<?php foreach ($sections as $name => $items): ?>
|
||||
@ -19,6 +22,19 @@
|
||||
</div>
|
||||
<?php $i++; ?>
|
||||
<?php endforeach ?>
|
||||
<!-- All Tab -->
|
||||
<input type='radio' id='collection-tab-<?= $i ?>' name='collection-tabs' />
|
||||
<label for='collection-tab-<?= $i ?>'><h2>All</h2></label>
|
||||
<div class='content full-height'>
|
||||
<?php foreach ($sections as $name => $items): ?>
|
||||
<h3><?= $name ?></h3>
|
||||
<section class="media-wrap">
|
||||
<?php foreach ($items as $item): ?>
|
||||
<?php include __DIR__ . '/cover-item.php'; ?>
|
||||
<?php endforeach ?>
|
||||
</section>
|
||||
<?php endforeach ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
</main>
|
||||
|
@ -26,7 +26,7 @@
|
||||
<td class="align-left">
|
||||
<select name="media_id" id="media_id">
|
||||
<?php foreach($media_items as $id => $name): ?>
|
||||
<option <?= $item['media_id'] === (string)$id ? 'selected="selected"' : '' ?> value="<?= $id ?>"><?= $name ?></option>
|
||||
<option <?= $item['media_id'] === $id ? 'selected="selected"' : '' ?> value="<?= $id ?>"><?= $name ?></option>
|
||||
<?php endforeach ?>
|
||||
</select>
|
||||
</td>
|
||||
|
@ -5,6 +5,9 @@
|
||||
<?php if (empty($sections)): ?>
|
||||
<h3>There's nothing here!</h3>
|
||||
<?php else: ?>
|
||||
<br />
|
||||
<label>Filter: <input type='text' class='media-filter' /></label>
|
||||
<br />
|
||||
<?php $i = 0; ?>
|
||||
<div class="tabs">
|
||||
<?php foreach ($sections as $name => $items): ?>
|
||||
@ -12,7 +15,7 @@
|
||||
name="collection-tabs"/>
|
||||
<label for="collection-tab-<?= $i ?>"><h2><?= $name ?></h2></label>
|
||||
<div class="content full-height">
|
||||
<table class="full-width">
|
||||
<table class="full-width media-wrap">
|
||||
<thead>
|
||||
<tr>
|
||||
<?php if ($auth->isAuthenticated()): ?>
|
||||
@ -36,6 +39,35 @@
|
||||
</div>
|
||||
<?php $i++ ?>
|
||||
<?php endforeach ?>
|
||||
<!-- All -->
|
||||
<input type='radio' id='collection-tab-<?= $i ?>' name='collection-tabs' />
|
||||
<label for='collection-tab-<?= $i ?>'><h2>All</h2></label>
|
||||
<div class="content full-height">
|
||||
<?php foreach ($sections as $name => $items): ?>
|
||||
<h3><?= $name ?></h3>
|
||||
<table class="full-width media-wrap">
|
||||
<thead>
|
||||
<tr>
|
||||
<?php if ($auth->isAuthenticated()): ?>
|
||||
<td>Actions</td>
|
||||
<?php endif ?>
|
||||
<th>Title</th>
|
||||
<th>Episode Count</th>
|
||||
<th>Episode Length</th>
|
||||
<th>Show Type</th>
|
||||
<th>Age Rating</th>
|
||||
<th>Genres</th>
|
||||
<th>Notes</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($items as $item): ?>
|
||||
<?php include __DIR__ . '/list-item.php' ?>
|
||||
<?php endforeach ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
</main>
|
||||
|
@ -5,6 +5,9 @@
|
||||
<?php if (empty($sections)): ?>
|
||||
<h3>There's nothing here!</h3>
|
||||
<?php else: ?>
|
||||
<br />
|
||||
<label>Filter: <input type='text' class='media-filter' /></label>
|
||||
<br />
|
||||
<?php foreach ($sections as $name => $items): ?>
|
||||
<?php if (empty($items)): ?>
|
||||
<section class="status">
|
||||
|
@ -5,12 +5,15 @@
|
||||
<?php if (empty($sections)): ?>
|
||||
<h3>There's nothing here!</h3>
|
||||
<?php else: ?>
|
||||
<br />
|
||||
<label>Filter: <input type='text' class='media-filter' /></label>
|
||||
<br />
|
||||
<?php foreach ($sections as $name => $items): ?>
|
||||
<h2><?= $name ?></h2>
|
||||
<?php if (empty($items)): ?>
|
||||
<h3>There's nothing here!</h3>
|
||||
<?php else: ?>
|
||||
<table>
|
||||
<table class='media-wrap'>
|
||||
<thead>
|
||||
<tr>
|
||||
<?php if ($auth->isAuthenticated()): ?>
|
||||
|
1
console
1
console
@ -23,7 +23,6 @@ try
|
||||
'refresh:thumbnails' => Command\UpdateThumbnails::class,
|
||||
'regenerate-thumbnails' => Command\UpdateThumbnails::class,
|
||||
'lists:sync' => Command\SyncLists::class,
|
||||
'mal_id:check' => Command\MALIDCheck::class,
|
||||
]))->run();
|
||||
}
|
||||
catch (\Exception $e)
|
||||
|
29
public/js/scripts-authed.min.js
vendored
29
public/js/scripts-authed.min.js
vendored
@ -7,18 +7,19 @@ sel.addEventListener(event,listener,false)}function delegateEvent(sel,target,eve
|
||||
"GET")url+=url.match(/\?/)?ajaxSerialize(config.data):"?"+ajaxSerialize(config.data);request.open(method,url);request.onreadystatechange=function(){if(request.readyState===4){var responseText="";if(request.responseType==="json")responseText=JSON.parse(request.responseText);else responseText=request.responseText;if(request.status>299)config.error.call(null,request.status,responseText,request.response);else config.success.call(null,responseText,request.status)}};if(config.dataType==="json"){config.data=
|
||||
JSON.stringify(config.data);config.mimeType="application/json"}else config.data=ajaxSerialize(config.data);request.setRequestHeader("Content-Type",config.mimeType);switch(method){case "GET":request.send(null);break;default:request.send(config.data);break}};AnimeClient.get=function(url,data,callback){callback=callback===undefined?null:callback;if(callback===null){callback=data;data={}}return AnimeClient.ajax(url,{data:data,success:callback})};AnimeClient.on("header","click",".message",function(e){AnimeClient.hide(e.target)});
|
||||
AnimeClient.on("form.js-delete","submit",function(event){var proceed=confirm("Are you ABSOLUTELY SURE you want to delete this item?");if(proceed===false){event.preventDefault();event.stopPropagation()}});AnimeClient.on(".js-clear-cache","click",function(){AnimeClient.get("/cache_purge",function(){AnimeClient.showMessage("success","Successfully purged api cache")})});AnimeClient.on(".vertical-tabs input","change",function(event){var el=event.currentTarget.parentElement;var rect=el.getBoundingClientRect();
|
||||
var top=rect.top+window.pageYOffset;window.scrollTo({top:top,behavior:"smooth"})});if("serviceWorker"in navigator)navigator.serviceWorker.register("/sw.js").then(function(reg){console.log("Service worker registered",reg.scope)})["catch"](function(error){console.error("Failed to register service worker",error)});AnimeClient.on("main","change",".big-check",function(e){var id=e.target.id;document.getElementById("mal_"+id).checked=true});function renderAnimeSearchResults(data){var results=[];data.forEach(function(x){var item=
|
||||
x.attributes;var titles=item.titles.reduce(function(prev,current){return prev+(current+"<br />")},[]);results.push('\n\t\t\t<article class="media search">\n\t\t\t\t<div class="name">\n\t\t\t\t\t<input type="radio" class="mal-check" id="mal_'+item.slug+'" name="mal_id" value="'+x.mal_id+'" />\n\t\t\t\t\t<input type="radio" class="big-check" id="'+item.slug+'" name="id" value="'+x.id+'" />\n\t\t\t\t\t<label for="'+item.slug+'">\n\t\t\t\t\t\t<picture width="220">\n\t\t\t\t\t\t\t<source srcset="/public/images/anime/'+
|
||||
x.id+'.webp" type="image/webp" />\n\t\t\t\t\t\t\t<source srcset="/public/images/anime/'+x.id+'.jpg" type="image/jpeg" />\n\t\t\t\t\t\t\t<img src="/public/images/anime/'+x.id+'.jpg" alt="" width="220" />\n\t\t\t\t\t\t</picture>\n\t\t\t\t\t\t\n\t\t\t\t\t\t<span class="name">\n\t\t\t\t\t\t\t'+item.canonicalTitle+"<br />\n\t\t\t\t\t\t\t<small>"+titles+'</small>\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</label>\n\t\t\t\t</div>\n\t\t\t\t<div class="table">\n\t\t\t\t\t<div class="row">\n\t\t\t\t\t\t<span class="edit">\n\t\t\t\t\t\t\t<a class="bracketed" href="/anime/details/'+
|
||||
item.slug+'">Info Page</a>\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</article>\n\t\t')});return results.join("")}function renderMangaSearchResults(data){var results=[];data.forEach(function(x){var item=x.attributes;var titles=item.titles.reduce(function(prev,current){return prev+(current+"<br />")},[]);results.push('\n\t\t\t<article class="media search">\n\t\t\t\t<div class="name">\n\t\t\t\t\t<input type="radio" id="mal_'+item.slug+'" name="mal_id" value="'+x.mal_id+'" />\n\t\t\t\t\t<input type="radio" class="big-check" id="'+
|
||||
item.slug+'" name="id" value="'+x.id+'" />\n\t\t\t\t\t<label for="'+item.slug+'">\n\t\t\t\t\t\t<picture width="220">\n\t\t\t\t\t\t\t<source srcset="/public/images/manga/'+x.id+'.webp" type="image/webp" />\n\t\t\t\t\t\t\t<source srcset="/public/images/manga/'+x.id+'.jpg" type="image/jpeg" />\n\t\t\t\t\t\t\t<img src="/public/images/manga/'+x.id+'.jpg" alt="" width="220" />\n\t\t\t\t\t\t</picture>\n\t\t\t\t\t\t<span class="name">\n\t\t\t\t\t\t\t'+item.canonicalTitle+"<br />\n\t\t\t\t\t\t\t<small>"+titles+
|
||||
'</small>\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</label>\n\t\t\t\t</div>\n\t\t\t\t<div class="table">\n\t\t\t\t\t<div class="row">\n\t\t\t\t\t\t<span class="edit">\n\t\t\t\t\t\t\t<a class="bracketed" href="/manga/details/'+item.slug+'">Info Page</a>\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</article>\n\t\t')});return results.join("")}var search=function(query){AnimeClient.$(".cssload-loader")[0].removeAttribute("hidden");AnimeClient.get(AnimeClient.url("/anime-collection/search"),{query:query},
|
||||
function(searchResults,status){searchResults=JSON.parse(searchResults);AnimeClient.$(".cssload-loader")[0].setAttribute("hidden","hidden");AnimeClient.$("#series-list")[0].innerHTML=renderAnimeSearchResults(searchResults.data)})};if(AnimeClient.hasElement(".anime #search"))AnimeClient.on("#search","keyup",AnimeClient.throttle(250,function(e){var query=encodeURIComponent(e.target.value);if(query==="")return;search(query)}));AnimeClient.on("body.anime.list","click",".plus-one",function(e){var parentSel=
|
||||
AnimeClient.closestParent(e.target,"article");var watchedCount=parseInt(AnimeClient.$(".completed_number",parentSel)[0].textContent,10)||0;var totalCount=parseInt(AnimeClient.$(".total_number",parentSel)[0].textContent,10);var title=AnimeClient.$(".name a",parentSel)[0].textContent;var data={id:parentSel.dataset.kitsuId,mal_id:parentSel.dataset.malId,data:{progress:watchedCount+1}};if(isNaN(watchedCount)||watchedCount===0)data.data.status="current";if(!isNaN(watchedCount)&&watchedCount+1===totalCount)data.data.status=
|
||||
"completed";AnimeClient.show(AnimeClient.$("#loading-shadow")[0]);AnimeClient.ajax(AnimeClient.url("/anime/increment"),{data:data,dataType:"json",type:"POST",success:function(res){var resData=JSON.parse(res);if(resData.errors){AnimeClient.hide(AnimeClient.$("#loading-shadow")[0]);AnimeClient.showMessage("error","Failed to update "+title+". ");AnimeClient.scrollToTop();return}if(resData.data.attributes.status==="completed")AnimeClient.hide(parentSel);AnimeClient.hide(AnimeClient.$("#loading-shadow")[0]);
|
||||
AnimeClient.showMessage("success","Successfully updated "+title);AnimeClient.$(".completed_number",parentSel)[0].textContent=++watchedCount;AnimeClient.scrollToTop()},error:function(){AnimeClient.hide(AnimeClient.$("#loading-shadow")[0]);AnimeClient.showMessage("error","Failed to update "+title+". ");AnimeClient.scrollToTop()}})});var search$1=function(query){AnimeClient.$(".cssload-loader")[0].removeAttribute("hidden");AnimeClient.get(AnimeClient.url("/manga/search"),{query:query},function(searchResults,
|
||||
status){searchResults=JSON.parse(searchResults);AnimeClient.$(".cssload-loader")[0].setAttribute("hidden","hidden");AnimeClient.$("#series-list")[0].innerHTML=renderMangaSearchResults(searchResults.data)})};if(AnimeClient.hasElement(".manga #search"))AnimeClient.on("#search","keyup",AnimeClient.throttle(250,function(e){var query=encodeURIComponent(e.target.value);if(query==="")return;search$1(query)}));AnimeClient.on(".manga.list","click",".edit-buttons button",function(e){var thisSel=e.target;var parentSel=
|
||||
AnimeClient.closestParent(e.target,"article");var type=thisSel.classList.contains("plus-one-chapter")?"chapter":"volume";var completed=parseInt(AnimeClient.$("."+type+"s_read",parentSel)[0].textContent,10)||0;var total=parseInt(AnimeClient.$("."+type+"_count",parentSel)[0].textContent,10);var mangaName=AnimeClient.$(".name",parentSel)[0].textContent;if(isNaN(completed))completed=0;var data={id:parentSel.dataset.kitsuId,mal_id:parentSel.dataset.malId,data:{progress:completed}};if(isNaN(completed)||
|
||||
completed===0)data.data.status="current";if(!isNaN(completed)&&completed+1===total)data.data.status="completed";data.data.progress=++completed;AnimeClient.show(AnimeClient.$("#loading-shadow")[0]);AnimeClient.ajax(AnimeClient.url("/manga/increment"),{data:data,dataType:"json",type:"POST",mimeType:"application/json",success:function(){if(data.data.status==="completed")AnimeClient.hide(parentSel);AnimeClient.hide(AnimeClient.$("#loading-shadow")[0]);AnimeClient.$("."+type+"s_read",parentSel)[0].textContent=
|
||||
completed;AnimeClient.showMessage("success","Successfully updated "+mangaName);AnimeClient.scrollToTop()},error:function(){AnimeClient.hide(AnimeClient.$("#loading-shadow")[0]);AnimeClient.showMessage("error","Failed to update "+mangaName);AnimeClient.scrollToTop()}})})})();
|
||||
var top=rect.top+window.pageYOffset;window.scrollTo({top:top,behavior:"smooth"})});AnimeClient.on(".media-filter","input",function(event){var rawFilter=event.target.value;var filter=new RegExp(rawFilter,"i");if(rawFilter!==""){AnimeClient.$("article.media").forEach(function(article){var titleLink=AnimeClient.$(".name a",article)[0];var title=String(titleLink.textContent).trim();if(!filter.test(title))AnimeClient.hide(article);else AnimeClient.show(article)});AnimeClient.$("table.media-wrap tbody tr").forEach(function(tr){var titleCell=
|
||||
AnimeClient.$("td.align-left",tr)[0];var titleLink=AnimeClient.$("a",titleCell)[0];var linkTitle=String(titleLink.textContent).trim();var textTitle=String(titleCell.textContent).trim();if(!(filter.test(linkTitle)||filter.test(textTitle)))AnimeClient.hide(tr);else AnimeClient.show(tr)})}else{AnimeClient.$("article.media").forEach(function(article){return AnimeClient.show(article)});AnimeClient.$("table.media-wrap tbody tr").forEach(function(tr){return AnimeClient.show(tr)})}});if("serviceWorker"in
|
||||
navigator)navigator.serviceWorker.register("/sw.js").then(function(reg){console.log("Service worker registered",reg.scope)})["catch"](function(error){console.error("Failed to register service worker",error)});AnimeClient.on("main","change",".big-check",function(e){var id=e.target.id;document.getElementById("mal_"+id).checked=true});function renderAnimeSearchResults(data){var results=[];data.forEach(function(x){var item=x.attributes;var titles=item.titles.reduce(function(prev,current){return prev+
|
||||
(current+"<br />")},[]);results.push('\n\t\t\t<article class="media search">\n\t\t\t\t<div class="name">\n\t\t\t\t\t<input type="radio" class="mal-check" id="mal_'+item.slug+'" name="mal_id" value="'+x.mal_id+'" />\n\t\t\t\t\t<input type="radio" class="big-check" id="'+item.slug+'" name="id" value="'+x.id+'" />\n\t\t\t\t\t<label for="'+item.slug+'">\n\t\t\t\t\t\t<picture width="220">\n\t\t\t\t\t\t\t<source srcset="/public/images/anime/'+x.id+'.webp" type="image/webp" />\n\t\t\t\t\t\t\t<source srcset="/public/images/anime/'+
|
||||
x.id+'.jpg" type="image/jpeg" />\n\t\t\t\t\t\t\t<img src="/public/images/anime/'+x.id+'.jpg" alt="" width="220" />\n\t\t\t\t\t\t</picture>\n\t\t\t\t\t\t\n\t\t\t\t\t\t<span class="name">\n\t\t\t\t\t\t\t'+item.canonicalTitle+"<br />\n\t\t\t\t\t\t\t<small>"+titles+'</small>\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</label>\n\t\t\t\t</div>\n\t\t\t\t<div class="table">\n\t\t\t\t\t<div class="row">\n\t\t\t\t\t\t<span class="edit">\n\t\t\t\t\t\t\t<a class="bracketed" href="/anime/details/'+item.slug+'">Info Page</a>\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</article>\n\t\t')});
|
||||
return results.join("")}function renderMangaSearchResults(data){var results=[];data.forEach(function(x){var item=x.attributes;var titles=item.titles.reduce(function(prev,current){return prev+(current+"<br />")},[]);results.push('\n\t\t\t<article class="media search">\n\t\t\t\t<div class="name">\n\t\t\t\t\t<input type="radio" id="mal_'+item.slug+'" name="mal_id" value="'+x.mal_id+'" />\n\t\t\t\t\t<input type="radio" class="big-check" id="'+item.slug+'" name="id" value="'+x.id+'" />\n\t\t\t\t\t<label for="'+
|
||||
item.slug+'">\n\t\t\t\t\t\t<picture width="220">\n\t\t\t\t\t\t\t<source srcset="/public/images/manga/'+x.id+'.webp" type="image/webp" />\n\t\t\t\t\t\t\t<source srcset="/public/images/manga/'+x.id+'.jpg" type="image/jpeg" />\n\t\t\t\t\t\t\t<img src="/public/images/manga/'+x.id+'.jpg" alt="" width="220" />\n\t\t\t\t\t\t</picture>\n\t\t\t\t\t\t<span class="name">\n\t\t\t\t\t\t\t'+item.canonicalTitle+"<br />\n\t\t\t\t\t\t\t<small>"+titles+'</small>\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</label>\n\t\t\t\t</div>\n\t\t\t\t<div class="table">\n\t\t\t\t\t<div class="row">\n\t\t\t\t\t\t<span class="edit">\n\t\t\t\t\t\t\t<a class="bracketed" href="/manga/details/'+
|
||||
item.slug+'">Info Page</a>\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</article>\n\t\t')});return results.join("")}var search=function(query){AnimeClient.$(".cssload-loader")[0].removeAttribute("hidden");AnimeClient.get(AnimeClient.url("/anime-collection/search"),{query:query},function(searchResults,status){searchResults=JSON.parse(searchResults);AnimeClient.$(".cssload-loader")[0].setAttribute("hidden","hidden");AnimeClient.$("#series-list")[0].innerHTML=renderAnimeSearchResults(searchResults.data)})};
|
||||
if(AnimeClient.hasElement(".anime #search"))AnimeClient.on("#search","input",AnimeClient.throttle(250,function(e){var query=encodeURIComponent(e.target.value);if(query==="")return;search(query)}));AnimeClient.on("body.anime.list","click",".plus-one",function(e){var parentSel=AnimeClient.closestParent(e.target,"article");var watchedCount=parseInt(AnimeClient.$(".completed_number",parentSel)[0].textContent,10)||0;var totalCount=parseInt(AnimeClient.$(".total_number",parentSel)[0].textContent,10);var title=
|
||||
AnimeClient.$(".name a",parentSel)[0].textContent;var data={id:parentSel.dataset.kitsuId,mal_id:parentSel.dataset.malId,data:{progress:watchedCount+1}};if(isNaN(watchedCount)||watchedCount===0)data.data.status="current";if(!isNaN(watchedCount)&&watchedCount+1===totalCount)data.data.status="completed";AnimeClient.show(AnimeClient.$("#loading-shadow")[0]);AnimeClient.ajax(AnimeClient.url("/anime/increment"),{data:data,dataType:"json",type:"POST",success:function(res){var resData=JSON.parse(res);if(resData.errors){AnimeClient.hide(AnimeClient.$("#loading-shadow")[0]);
|
||||
AnimeClient.showMessage("error","Failed to update "+title+". ");AnimeClient.scrollToTop();return}if(resData.data.attributes.status==="completed")AnimeClient.hide(parentSel);AnimeClient.hide(AnimeClient.$("#loading-shadow")[0]);AnimeClient.showMessage("success","Successfully updated "+title);AnimeClient.$(".completed_number",parentSel)[0].textContent=++watchedCount;AnimeClient.scrollToTop()},error:function(){AnimeClient.hide(AnimeClient.$("#loading-shadow")[0]);AnimeClient.showMessage("error","Failed to update "+
|
||||
title+". ");AnimeClient.scrollToTop()}})});var search$1=function(query){AnimeClient.$(".cssload-loader")[0].removeAttribute("hidden");AnimeClient.get(AnimeClient.url("/manga/search"),{query:query},function(searchResults,status){searchResults=JSON.parse(searchResults);AnimeClient.$(".cssload-loader")[0].setAttribute("hidden","hidden");AnimeClient.$("#series-list")[0].innerHTML=renderMangaSearchResults(searchResults.data)})};if(AnimeClient.hasElement(".manga #search"))AnimeClient.on("#search","input",
|
||||
AnimeClient.throttle(250,function(e){var query=encodeURIComponent(e.target.value);if(query==="")return;search$1(query)}));AnimeClient.on(".manga.list","click",".edit-buttons button",function(e){var thisSel=e.target;var parentSel=AnimeClient.closestParent(e.target,"article");var type=thisSel.classList.contains("plus-one-chapter")?"chapter":"volume";var completed=parseInt(AnimeClient.$("."+type+"s_read",parentSel)[0].textContent,10)||0;var total=parseInt(AnimeClient.$("."+type+"_count",parentSel)[0].textContent,
|
||||
10);var mangaName=AnimeClient.$(".name",parentSel)[0].textContent;if(isNaN(completed))completed=0;var data={id:parentSel.dataset.kitsuId,mal_id:parentSel.dataset.malId,data:{progress:completed}};if(isNaN(completed)||completed===0)data.data.status="current";if(!isNaN(completed)&&completed+1===total)data.data.status="completed";data.data.progress=++completed;AnimeClient.show(AnimeClient.$("#loading-shadow")[0]);AnimeClient.ajax(AnimeClient.url("/manga/increment"),{data:data,dataType:"json",type:"POST",
|
||||
mimeType:"application/json",success:function(){if(data.data.status==="completed")AnimeClient.hide(parentSel);AnimeClient.hide(AnimeClient.$("#loading-shadow")[0]);AnimeClient.$("."+type+"s_read",parentSel)[0].textContent=completed;AnimeClient.showMessage("success","Successfully updated "+mangaName);AnimeClient.scrollToTop()},error:function(){AnimeClient.hide(AnimeClient.$("#loading-shadow")[0]);AnimeClient.showMessage("error","Failed to update "+mangaName);AnimeClient.scrollToTop()}})})})();
|
||||
//# sourceMappingURL=scripts-authed.min.js.map
|
||||
|
File diff suppressed because one or more lines are too long
4
public/js/scripts.min.js
vendored
4
public/js/scripts.min.js
vendored
@ -7,5 +7,7 @@ sel.addEventListener(event,listener,false)}function delegateEvent(sel,target,eve
|
||||
"GET")url+=url.match(/\?/)?ajaxSerialize(config.data):"?"+ajaxSerialize(config.data);request.open(method,url);request.onreadystatechange=function(){if(request.readyState===4){var responseText="";if(request.responseType==="json")responseText=JSON.parse(request.responseText);else responseText=request.responseText;if(request.status>299)config.error.call(null,request.status,responseText,request.response);else config.success.call(null,responseText,request.status)}};if(config.dataType==="json"){config.data=
|
||||
JSON.stringify(config.data);config.mimeType="application/json"}else config.data=ajaxSerialize(config.data);request.setRequestHeader("Content-Type",config.mimeType);switch(method){case "GET":request.send(null);break;default:request.send(config.data);break}};AnimeClient.get=function(url,data,callback){callback=callback===undefined?null:callback;if(callback===null){callback=data;data={}}return AnimeClient.ajax(url,{data:data,success:callback})};AnimeClient.on("header","click",".message",function(e){AnimeClient.hide(e.target)});
|
||||
AnimeClient.on("form.js-delete","submit",function(event){var proceed=confirm("Are you ABSOLUTELY SURE you want to delete this item?");if(proceed===false){event.preventDefault();event.stopPropagation()}});AnimeClient.on(".js-clear-cache","click",function(){AnimeClient.get("/cache_purge",function(){AnimeClient.showMessage("success","Successfully purged api cache")})});AnimeClient.on(".vertical-tabs input","change",function(event){var el=event.currentTarget.parentElement;var rect=el.getBoundingClientRect();
|
||||
var top=rect.top+window.pageYOffset;window.scrollTo({top:top,behavior:"smooth"})});if("serviceWorker"in navigator)navigator.serviceWorker.register("/sw.js").then(function(reg){console.log("Service worker registered",reg.scope)})["catch"](function(error){console.error("Failed to register service worker",error)})})();
|
||||
var top=rect.top+window.pageYOffset;window.scrollTo({top:top,behavior:"smooth"})});AnimeClient.on(".media-filter","input",function(event){var rawFilter=event.target.value;var filter=new RegExp(rawFilter,"i");if(rawFilter!==""){AnimeClient.$("article.media").forEach(function(article){var titleLink=AnimeClient.$(".name a",article)[0];var title=String(titleLink.textContent).trim();if(!filter.test(title))AnimeClient.hide(article);else AnimeClient.show(article)});AnimeClient.$("table.media-wrap tbody tr").forEach(function(tr){var titleCell=
|
||||
AnimeClient.$("td.align-left",tr)[0];var titleLink=AnimeClient.$("a",titleCell)[0];var linkTitle=String(titleLink.textContent).trim();var textTitle=String(titleCell.textContent).trim();if(!(filter.test(linkTitle)||filter.test(textTitle)))AnimeClient.hide(tr);else AnimeClient.show(tr)})}else{AnimeClient.$("article.media").forEach(function(article){return AnimeClient.show(article)});AnimeClient.$("table.media-wrap tbody tr").forEach(function(tr){return AnimeClient.show(tr)})}});if("serviceWorker"in
|
||||
navigator)navigator.serviceWorker.register("/sw.js").then(function(reg){console.log("Service worker registered",reg.scope)})["catch"](function(error){console.error("Failed to register service worker",error)})})();
|
||||
//# sourceMappingURL=scripts.min.js.map
|
||||
|
File diff suppressed because one or more lines are too long
@ -18,7 +18,7 @@ const search = (query) => {
|
||||
};
|
||||
|
||||
if (_.hasElement('.anime #search')) {
|
||||
_.on('#search', 'keyup', _.throttle(250, (e) => {
|
||||
_.on('#search', 'input', _.throttle(250, (e) => {
|
||||
const query = encodeURIComponent(e.target.value);
|
||||
if (query === '') {
|
||||
return;
|
||||
|
@ -36,3 +36,40 @@ _.on('.js-clear-cache', 'click', () => {
|
||||
behavior: 'smooth',
|
||||
});
|
||||
});
|
||||
|
||||
// Filter the current page (cover view)
|
||||
_.on('.media-filter', 'input', (event) => {
|
||||
const rawFilter = event.target.value;
|
||||
const filter = new RegExp(rawFilter, 'i');
|
||||
|
||||
// console.log('Filtering items by: ', filter);
|
||||
|
||||
if (rawFilter !== '') {
|
||||
// Filter the cover view
|
||||
_.$('article.media').forEach(article => {
|
||||
const titleLink = _.$('.name a', article)[0];
|
||||
const title = String(titleLink.textContent).trim();
|
||||
if ( ! filter.test(title)) {
|
||||
_.hide(article);
|
||||
} else {
|
||||
_.show(article);
|
||||
}
|
||||
});
|
||||
|
||||
// Filter the list view
|
||||
_.$('table.media-wrap tbody tr').forEach(tr => {
|
||||
const titleCell = _.$('td.align-left', tr)[0];
|
||||
const titleLink = _.$('a', titleCell)[0];
|
||||
const linkTitle = String(titleLink.textContent).trim();
|
||||
const textTitle = String(titleCell.textContent).trim();
|
||||
if ( ! (filter.test(linkTitle) || filter.test(textTitle))) {
|
||||
_.hide(tr);
|
||||
} else {
|
||||
_.show(tr);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
_.$('article.media').forEach(article => _.show(article));
|
||||
_.$('table.media-wrap tbody tr').forEach(tr => _.show(tr));
|
||||
}
|
||||
});
|
||||
|
@ -11,7 +11,7 @@ const search = (query) => {
|
||||
};
|
||||
|
||||
if (_.hasElement('.manga #search')) {
|
||||
_.on('#search', 'keyup', _.throttle(250, (e) => {
|
||||
_.on('#search', 'input', _.throttle(250, (e) => {
|
||||
let query = encodeURIComponent(e.target.value);
|
||||
if (query === '') {
|
||||
return;
|
||||
|
19
src/API/Anilist/MissingIdException.php
Normal file
19
src/API/Anilist/MissingIdException.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* Hummingbird Anime List Client
|
||||
*
|
||||
* An API client for Kitsu to manage anime and manga watch lists
|
||||
*
|
||||
* PHP version 7.1
|
||||
*
|
||||
* @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.1
|
||||
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
|
||||
*/
|
||||
|
||||
namespace Aviat\AnimeClient\API\Anilist;
|
||||
|
||||
class MissingIdException extends \InvalidArgumentException {}
|
@ -100,7 +100,7 @@ final class Model
|
||||
$config = $this->container->get('config');
|
||||
$anilistUser = $config->get(['anilist', 'username']);
|
||||
|
||||
if ( ! \is_string($anilistUser))
|
||||
if ( ! (is_string($anilistUser) && $anilistUser !== ''))
|
||||
{
|
||||
throw new InvalidArgumentException('Anilist username is not defined in config');
|
||||
}
|
||||
@ -124,10 +124,10 @@ final class Model
|
||||
|
||||
$mediaId = $this->getMediaIdFromMalId($data['mal_id'], mb_strtoupper($type));
|
||||
|
||||
if (empty($mediaId))
|
||||
/* if (empty($mediaId))
|
||||
{
|
||||
throw new InvalidArgumentException('Media id missing');
|
||||
}
|
||||
} */
|
||||
|
||||
if ($type === 'ANIME')
|
||||
{
|
||||
@ -151,12 +151,18 @@ final class Model
|
||||
* Create a list item with all the relevant data
|
||||
*
|
||||
* @param array $data
|
||||
* @param string $type
|
||||
* @return Request
|
||||
*/
|
||||
public function createFullListItem(array $data): Request
|
||||
public function createFullListItem(array $data, string $type): Request
|
||||
{
|
||||
$createData = $data['data'];
|
||||
$mediaId = $this->getMediaIdFromMalId($data['mal_id']);
|
||||
$mediaId = $this->getMediaIdFromMalId($data['mal_id'], strtoupper($type));
|
||||
|
||||
if (empty($mediaId))
|
||||
{
|
||||
throw new MissingIdException('No id mapping found');
|
||||
}
|
||||
|
||||
$createData['id'] = $mediaId;
|
||||
|
||||
@ -213,6 +219,7 @@ final class Model
|
||||
* Remove a list item
|
||||
*
|
||||
* @param string $malId - The id of the list item to remove
|
||||
* @param string $type - Them media type (anime/manga)
|
||||
* @return Request
|
||||
*/
|
||||
public function deleteListItem(string $malId, string $type): Request
|
||||
|
@ -1,241 +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.1
|
||||
*
|
||||
* @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.1
|
||||
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
|
||||
*/
|
||||
|
||||
namespace Aviat\AnimeClient\Command;
|
||||
|
||||
use const Aviat\AnimeClient\SRC_DIR;
|
||||
|
||||
use function Amp\Promise\wait;
|
||||
|
||||
use Aviat\AnimeClient\API\{
|
||||
APIRequestBuilder,
|
||||
JsonAPI,
|
||||
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
|
||||
* @throws \Throwable
|
||||
*/
|
||||
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');
|
||||
|
||||
$kitsuAnimeIdList = $this->formatKitsuList('anime');
|
||||
$animeCount = count($kitsuAnimeIdList);
|
||||
$this->echoBox("{$animeCount} mappings for Anime");
|
||||
$animeMappings = $this->checkMALIds($kitsuAnimeIdList, 'anime');
|
||||
$this->mappingStatus($animeMappings, $animeCount, 'anime');
|
||||
|
||||
$kitsuMangaIdList = $this->formatKitsuList('manga');
|
||||
$mangaCount = count($kitsuMangaIdList);
|
||||
$this->echoBox("{$mangaCount} mappings for Manga");
|
||||
$mangaMappings = $this->checkMALIds($kitsuMangaIdList, 'manga');
|
||||
$this->mappingStatus($mangaMappings, $mangaCount, 'manga');
|
||||
|
||||
$publicDir = realpath(SRC_DIR . '/../public') . '/';
|
||||
file_put_contents($publicDir . 'mal_mappings.json', Json::encode([
|
||||
'anime' => $animeMappings,
|
||||
'manga' => $mangaMappings,
|
||||
]));
|
||||
|
||||
$this->echoBox('Mapping file saved to "' . $publicDir . 'mal_mappings.json' . '"');
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a kitsu list for the sake of comparision
|
||||
*
|
||||
* @param string $type
|
||||
* @return array
|
||||
*/
|
||||
private function formatKitsuList(string $type = 'anime'): array
|
||||
{
|
||||
$options = [
|
||||
'include' => 'media,media.mappings',
|
||||
];
|
||||
$data = $this->kitsuModel->{'getFullRaw' . ucfirst($type) . 'List'}($options);
|
||||
|
||||
if (empty($data))
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
$includes = JsonAPI::organizeIncludes($data['included']);
|
||||
|
||||
// Only bother with mappings from MAL that are of the specified media type
|
||||
$includes['mappings'] = array_filter($includes['mappings'], function ($mapping) use ($type) {
|
||||
return $mapping['externalSite'] === "myanimelist/{$type}";
|
||||
});
|
||||
|
||||
$output = [];
|
||||
|
||||
foreach ($data['data'] as $listItem)
|
||||
{
|
||||
$id = $listItem['relationships']['media']['data']['id'];
|
||||
$mediaItem = $includes[$type][$id];
|
||||
|
||||
// Set titles
|
||||
$listItem['titles'] = $mediaItem['titles'];
|
||||
|
||||
$potentialMappings = $mediaItem['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;
|
||||
}
|
||||
|
||||
// Group by malIds to simplify lookup of media details
|
||||
// for checking validity of the malId mappings
|
||||
$output[$malId] = $listItem;
|
||||
}
|
||||
|
||||
ksort($output);
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for valid Kitsu -> MAL mapping
|
||||
*
|
||||
* @param array $kitsuList
|
||||
* @param string $type
|
||||
* @return array
|
||||
* @throws \Throwable
|
||||
*/
|
||||
private function checkMALIds(array $kitsuList, string $type): array
|
||||
{
|
||||
$goodMappings = [];
|
||||
$badMappings = [];
|
||||
$suspectMappings = [];
|
||||
|
||||
$responses = $this->makeMALRequests(array_keys($kitsuList), $type);
|
||||
|
||||
// If the page returns a 404, put it in the bad mappings list
|
||||
// otherwise, do a search against the titles, to see if the mapping
|
||||
// seems valid
|
||||
foreach($responses as $id => $response)
|
||||
{
|
||||
$body = wait($response->getBody());
|
||||
$titles = $kitsuList[$id]['titles'];
|
||||
|
||||
if ($response->getStatus() === 404)
|
||||
{
|
||||
$badMappings[$id] = $titles;
|
||||
}
|
||||
else
|
||||
{
|
||||
$titleMatches = FALSE;
|
||||
|
||||
// Attempt to determine if the id matches
|
||||
// By searching for a matching title
|
||||
foreach($titles as $title)
|
||||
{
|
||||
if (empty($title))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (mb_stripos($body, $title) !== FALSE)
|
||||
{
|
||||
$titleMatches = TRUE;
|
||||
$goodMappings[$id] = $title;
|
||||
|
||||
// Continue on outer loop
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! $titleMatches)
|
||||
{
|
||||
$suspectMappings[$id] = $titles;
|
||||
}
|
||||
else
|
||||
{
|
||||
$goodMappings[$id] = $titles;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'good' => $goodMappings,
|
||||
'bad' => $badMappings,
|
||||
'suspect' => $suspectMappings,
|
||||
];
|
||||
}
|
||||
|
||||
private function makeMALRequests(array $ids, string $type): array
|
||||
{
|
||||
$baseUrl = "https://myanimelist.net/{$type}/";
|
||||
|
||||
$requestChunks = array_chunk($ids, 50, TRUE);
|
||||
$responses = [];
|
||||
|
||||
// Chunk parallel requests so that we don't hit rate
|
||||
// limiting, and get spurious 404 HTML responses
|
||||
foreach($requestChunks as $idChunk)
|
||||
{
|
||||
$requester = new ParallelAPIRequest();
|
||||
|
||||
foreach($idChunk as $id)
|
||||
{
|
||||
$request = APIRequestBuilder::simpleRequest($baseUrl . $id);
|
||||
$requester->addRequest($request, (string)$id);
|
||||
}
|
||||
|
||||
foreach($requester->getResponses() as $id => $response)
|
||||
{
|
||||
$responses[$id] = $response;
|
||||
}
|
||||
}
|
||||
|
||||
return $responses;
|
||||
}
|
||||
|
||||
private function mappingStatus(array $mapping, int $count, string $type): void
|
||||
{
|
||||
$good = count($mapping['good']);
|
||||
$bad = count($mapping['bad']);
|
||||
$suspect = count($mapping['suspect']);
|
||||
|
||||
$uType = ucfirst($type);
|
||||
|
||||
$this->echoBox("{$uType} mappings: {$good}/{$count} Good, {$suspect}/{$count} Suspect, {$bad}/{$count} Broken");
|
||||
}
|
||||
}
|
@ -16,11 +16,8 @@
|
||||
|
||||
namespace Aviat\AnimeClient\Command;
|
||||
|
||||
use Aviat\AnimeClient\API\{
|
||||
FailedResponseException,
|
||||
JsonAPI,
|
||||
ParallelAPIRequest
|
||||
};
|
||||
use Aviat\AnimeClient\API\
|
||||
{Anilist\MissingIdException, FailedResponseException, JsonAPI, ParallelAPIRequest};
|
||||
use Aviat\AnimeClient\API\Anilist\Transformer\{
|
||||
AnimeListTransformer as AALT,
|
||||
MangaListTransformer as AMLT
|
||||
@ -311,20 +308,11 @@ final class SyncLists extends BaseCommand {
|
||||
$anilistUpdateItems = [];
|
||||
$kitsuUpdateItems = [];
|
||||
|
||||
$malBlackList = ($type === 'anime')
|
||||
? [
|
||||
27821, // Fate/stay night: Unlimited Blade Works - Prologue
|
||||
29317, // Saekano: How to Raise a Boring Girlfriend Prologue
|
||||
30514, // Nisekoinogatari
|
||||
] : [
|
||||
114638, // Cells at Work: Black
|
||||
];
|
||||
|
||||
$malIds = array_keys($anilistList);
|
||||
$kitsuMalIds = array_map('intval', array_column($kitsuList, 'malId'));
|
||||
$missingMalIds = array_diff($malIds, $kitsuMalIds);
|
||||
$missingMalIds = array_diff($missingMalIds, $malBlackList);
|
||||
|
||||
// Add items on Anilist, but not Kitsu to Kitsu
|
||||
foreach($missingMalIds as $mid)
|
||||
{
|
||||
$itemsToAddToKitsu[] = array_merge($anilistList[$mid]['data'], [
|
||||
@ -337,11 +325,6 @@ final class SyncLists extends BaseCommand {
|
||||
{
|
||||
$malId = $kitsuItem['malId'];
|
||||
|
||||
if (\in_array((int)$malId, $malBlackList, TRUE))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (array_key_exists($malId, $anilistList))
|
||||
{
|
||||
$anilistItem = $anilistList[$malId];
|
||||
@ -628,13 +611,25 @@ final class SyncLists extends BaseCommand {
|
||||
{
|
||||
$verb = ($action === 'update') ? 'updated' : 'created';
|
||||
$this->echoBox("Successfully {$verb} Kitsu {$type} list item with id: {$id}");
|
||||
continue;
|
||||
}
|
||||
else
|
||||
|
||||
// Show a different message when you have an episode count mismatch
|
||||
if (isset($responseData['errors'][0]['title']))
|
||||
{
|
||||
dump($responseData);
|
||||
$verb = ($action === 'update') ? 'update' : 'create';
|
||||
$this->echoBox("Failed to {$verb} Kitsu {$type} list item with id: {$id}");
|
||||
$errorTitle = $responseData['errors'][0]['title'];
|
||||
|
||||
if ($errorTitle === 'cannot exceed length of media')
|
||||
{
|
||||
$this->echoBox("Skipped Kitsu {$type} {$id} due to episode count mismatch with other API");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
dump($responseData);
|
||||
$verb = ($action === 'update') ? 'update' : 'create';
|
||||
$this->echoBox("Failed to {$verb} Kitsu {$type} list item with id: {$id}");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -660,7 +655,16 @@ final class SyncLists extends BaseCommand {
|
||||
}
|
||||
else if ($action === 'create')
|
||||
{
|
||||
$requester->addRequest($this->anilistModel->createFullListItem($item, $type));
|
||||
try
|
||||
{
|
||||
$requester->addRequest($this->anilistModel->createFullListItem($item, $type));
|
||||
}
|
||||
catch (MissingIdException $e)
|
||||
{
|
||||
// Case where there's a MAL mapping from Kitsu, but no equivalent Anlist item
|
||||
$id = $item['mal_id'];
|
||||
$this->echoBox("Skipping Anilist ${type} with mal_id: {$id} due to missing mapping");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -61,6 +61,11 @@ final class AnimeCollection extends BaseController {
|
||||
]);
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
$this->redirect('/anime-collection/view', 303);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for anime
|
||||
*
|
||||
@ -83,7 +88,7 @@ final class AnimeCollection extends BaseController {
|
||||
* @throws \InvalidArgumentException
|
||||
* @return void
|
||||
*/
|
||||
public function index($view): void
|
||||
public function view($view): void
|
||||
{
|
||||
$viewMap = [
|
||||
'' => 'cover',
|
||||
@ -145,13 +150,21 @@ final class AnimeCollection extends BaseController {
|
||||
$data = $this->request->getParsedBody();
|
||||
if (array_key_exists('hummingbird_id', $data))
|
||||
{
|
||||
// @TODO verify data was updated correctly
|
||||
$this->animeCollectionModel->update($data);
|
||||
$this->setFlashMessage('Successfully updated collection item.', 'success');
|
||||
|
||||
// Verify the item was actually updated
|
||||
if ($this->animeCollectionModel->wasUpdated($data))
|
||||
{
|
||||
$this->setFlashMessage('Successfully updated collection item.', 'success');
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->setFlashMessage('Failed to update collection item.', 'error');
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->setFlashMessage('Failed to update collection item', 'error');
|
||||
$this->setFlashMessage('No item id to update. Update failed.', 'error');
|
||||
}
|
||||
|
||||
$this->sessionRedirect();
|
||||
@ -175,21 +188,26 @@ final class AnimeCollection extends BaseController {
|
||||
// Check for existing entry
|
||||
if ($this->animeCollectionModel->get($data['id']) !== FALSE)
|
||||
{
|
||||
$this->setFlashMessage('Anime already exists, can not create duplicate', 'info');
|
||||
// Redirect to the edit screen, because that's probably what you want!
|
||||
$this->setFlashMessage('Anime already exists, update instead.', 'info');
|
||||
$this->redirect("/anime-collection/edit/{$data['id']}", 303);
|
||||
return;
|
||||
}
|
||||
else
|
||||
|
||||
$this->animeCollectionModel->add($data);
|
||||
|
||||
// Verify the item was added
|
||||
if ($this->animeCollectionModel->wasAdded($data))
|
||||
{
|
||||
// @TODO actually verify that collection item was added
|
||||
$this->animeCollectionModel->add($data);
|
||||
$this->setFlashMessage('Successfully added collection item', 'success');
|
||||
$this->sessionRedirect();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->setFlashMessage('Failed to add collection item.', 'error');
|
||||
$this->redirect('/anime-collection/add', 303);
|
||||
}
|
||||
|
||||
$this->sessionRedirect();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -204,12 +222,21 @@ final class AnimeCollection extends BaseController {
|
||||
$data = $this->request->getParsedBody();
|
||||
if ( ! array_key_exists('hummingbird_id', $data))
|
||||
{
|
||||
$this->setFlashMessage("Can't delete item that doesn't exist", 'error');
|
||||
$this->redirect('/anime-collection/view', 303);
|
||||
}
|
||||
|
||||
// @TODO verify that item was actually deleted
|
||||
$this->animeCollectionModel->delete($data);
|
||||
$this->setFlashMessage('Successfully removed anime from collection.', 'success');
|
||||
|
||||
// Verify that item was actually deleted
|
||||
if ($this->animeCollectionModel->wasDeleted($data))
|
||||
{
|
||||
$this->setFlashMessage('Successfully removed anime from collection.', 'success');
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->setFlashMessage('Failed to delete item from collection.', 'error');
|
||||
}
|
||||
|
||||
$this->redirect('/anime-collection/view', 303);
|
||||
}
|
||||
|
@ -166,7 +166,7 @@ class Anime extends API {
|
||||
$requester = new ParallelAPIRequest();
|
||||
$requester->addRequest($this->kitsuModel->createListItem($data), 'kitsu');
|
||||
|
||||
if (array_key_exists('mal_id', $data) && $this->anilistEnabled)
|
||||
if ($data['mal_id'] !== null && $this->anilistEnabled)
|
||||
{
|
||||
$requester->addRequest($this->anilistModel->createListItem($data, 'ANIME'), 'anilist');
|
||||
}
|
||||
@ -189,7 +189,7 @@ class Anime extends API {
|
||||
|
||||
$array = $data->toArray();
|
||||
|
||||
if (array_key_exists('mal_id', $array) && $this->anilistEnabled)
|
||||
if ($array['mal_id'] !== null && $this->anilistEnabled)
|
||||
{
|
||||
$requester->addRequest($this->anilistModel->incrementListItem($data, 'ANIME'), 'anilist');
|
||||
}
|
||||
@ -218,7 +218,7 @@ class Anime extends API {
|
||||
|
||||
$array = $data->toArray();
|
||||
|
||||
if (array_key_exists('mal_id', $array) && $this->anilistEnabled)
|
||||
if ($array['mal_id'] !== null && $this->anilistEnabled)
|
||||
{
|
||||
$requester->addRequest($this->anilistModel->updateListItem($data, 'ANIME'), 'anilist');
|
||||
}
|
||||
|
@ -140,7 +140,7 @@ final class AnimeCollection extends Collection {
|
||||
|
||||
// Check that the anime doesn't already exist
|
||||
$existing = $this->get($id);
|
||||
if ($existing === FALSE)
|
||||
if ( ! empty($existing))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@ -160,7 +160,20 @@ final class AnimeCollection extends Collection {
|
||||
'notes' => $data['notes']
|
||||
])->insert('anime_set');
|
||||
|
||||
$this->updateGenre($data['id']);
|
||||
$this->updateGenre($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that an item was added
|
||||
*
|
||||
* @param $data
|
||||
* @return bool
|
||||
*/
|
||||
public function wasAdded($data): bool
|
||||
{
|
||||
$row = $this->get($data['id']);
|
||||
|
||||
return ! empty($row);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -183,6 +196,30 @@ final class AnimeCollection extends Collection {
|
||||
$this->db->set($data)
|
||||
->where('hummingbird_id', $id)
|
||||
->update('anime_set');
|
||||
|
||||
// Just in case, also update genres
|
||||
$this->updateGenre($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the collection item was updated
|
||||
*
|
||||
* @param $data
|
||||
* @return bool
|
||||
*/
|
||||
public function wasUpdated($data): bool
|
||||
{
|
||||
$row = $this->get($data['hummingbird_id']);
|
||||
|
||||
foreach ($data as $key => $value)
|
||||
{
|
||||
if ((string)$row[$key] !== (string)$value)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -206,6 +243,13 @@ final class AnimeCollection extends Collection {
|
||||
->delete('anime_set');
|
||||
}
|
||||
|
||||
public function wasDeleted($data): bool
|
||||
{
|
||||
$animeRow = $this->get($data['hummingbird_id']);
|
||||
|
||||
return empty($animeRow);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the details of a collection item
|
||||
*
|
||||
@ -264,7 +308,8 @@ final class AnimeCollection extends Collection {
|
||||
if (array_key_exists($id, $output))
|
||||
{
|
||||
$output[$id][] = $genre;
|
||||
} else
|
||||
}
|
||||
else
|
||||
{
|
||||
$output[$id] = [$genre];
|
||||
}
|
||||
@ -272,6 +317,8 @@ final class AnimeCollection extends Collection {
|
||||
}
|
||||
catch (PDOException $e) {}
|
||||
|
||||
$this->db->reset_query();
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
@ -283,44 +330,67 @@ final class AnimeCollection extends Collection {
|
||||
*/
|
||||
private function updateGenre($animeId): void
|
||||
{
|
||||
// Get api information
|
||||
$anime = $this->animeModel->getAnimeById($animeId);
|
||||
|
||||
$this->addNewGenres($anime['genres']);
|
||||
|
||||
$genreInfo = $this->getGenreData();
|
||||
$genres = $genreInfo['genres'];
|
||||
$links = $genreInfo['links'];
|
||||
|
||||
// Get api information
|
||||
$anime = $this->animeModel->getAnimeById($animeId);
|
||||
$linksToInsert = [];
|
||||
|
||||
foreach ($anime['genres'] as $genre)
|
||||
foreach ($anime['genres'] as $animeGenre)
|
||||
{
|
||||
// Add genres that don't currently exist
|
||||
if ( ! \in_array($genre, $genres, TRUE))
|
||||
{
|
||||
$this->db->set('genre', $genre)
|
||||
->insert('genres');
|
||||
|
||||
$genres[] = $genre;
|
||||
}
|
||||
|
||||
// Update link table
|
||||
// Get id of genre to put in link table
|
||||
$flippedGenres = array_flip($genres);
|
||||
$genreId = $flippedGenres[$animeGenre];
|
||||
|
||||
$insertArray = [
|
||||
'hummingbird_id' => $animeId,
|
||||
'genre_id' => $flippedGenres[$genre]
|
||||
$animeLinks = $links[$animeId] ?? [];
|
||||
|
||||
if ( ! \in_array($flippedGenres[$animeGenre], $animeLinks, TRUE))
|
||||
{
|
||||
$linksToInsert[] = [
|
||||
'hummingbird_id' => $animeId,
|
||||
'genre_id' => $genreId,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty($linksToInsert))
|
||||
{
|
||||
$this->db->insertBatch('genre_anime_set_link', $linksToInsert);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add genres to the database
|
||||
*
|
||||
* @param array $genres
|
||||
*/
|
||||
private function addNewGenres(array $genres): void
|
||||
{
|
||||
$existingGenres = $this->getExistingGenres();
|
||||
$newGenres = array_diff($genres, $existingGenres);
|
||||
|
||||
$insert = [];
|
||||
|
||||
foreach ($newGenres as $genre)
|
||||
{
|
||||
$insert[] = [
|
||||
'genre' => $genre,
|
||||
];
|
||||
}
|
||||
|
||||
if (array_key_exists($animeId, $links))
|
||||
{
|
||||
if ( ! \in_array($flippedGenres[$genre], $links[$animeId], TRUE))
|
||||
{
|
||||
$this->db->set($insertArray)->insert('genre_anime_set_link');
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->db->set($insertArray)->insert('genre_anime_set_link');
|
||||
}
|
||||
try
|
||||
{
|
||||
$this->db->insert_batch('genres', $insert);
|
||||
}
|
||||
catch (PDOException $e)
|
||||
{
|
||||
dump($e);
|
||||
}
|
||||
}
|
||||
|
||||
@ -345,11 +415,14 @@ final class AnimeCollection extends Collection {
|
||||
$query = $this->db->select('id, genre')
|
||||
->from('genres')
|
||||
->get();
|
||||
|
||||
foreach ($query->fetchAll(PDO::FETCH_ASSOC) as $genre)
|
||||
{
|
||||
$genres[$genre['id']] = $genre['genre'];
|
||||
}
|
||||
|
||||
$this->db->reset_query();
|
||||
|
||||
return $genres;
|
||||
}
|
||||
|
||||
@ -360,6 +433,7 @@ final class AnimeCollection extends Collection {
|
||||
$query = $this->db->select('hummingbird_id, genre_id')
|
||||
->from('genre_anime_set_link')
|
||||
->get();
|
||||
|
||||
foreach ($query->fetchAll(PDO::FETCH_ASSOC) as $link)
|
||||
{
|
||||
if (array_key_exists($link['hummingbird_id'], $links))
|
||||
@ -371,6 +445,8 @@ final class AnimeCollection extends Collection {
|
||||
}
|
||||
}
|
||||
|
||||
$this->db->reset_query();
|
||||
|
||||
return $links;
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,8 @@ namespace Aviat\AnimeClient\Model;
|
||||
use Aviat\Ion\Di\ContainerInterface;
|
||||
use PDOException;
|
||||
|
||||
use function Query;
|
||||
|
||||
/**
|
||||
* Base model for anime and manga collections
|
||||
*/
|
||||
@ -47,7 +49,7 @@ class Collection extends DB {
|
||||
|
||||
try
|
||||
{
|
||||
$this->db = \Query($this->dbConfig);
|
||||
$this->db = Query($this->dbConfig);
|
||||
$this->validDatabase = TRUE;
|
||||
}
|
||||
catch (PDOException $e) {}
|
||||
|
@ -61,7 +61,6 @@ const SETTINGS_MAP = [
|
||||
'type' => 'string',
|
||||
'title' => 'Anilist Username',
|
||||
'default' => '',
|
||||
'readonly' => TRUE,
|
||||
'description' => 'Login username for Anilist account to integrate with',
|
||||
],
|
||||
'access_token' => [
|
||||
|
Loading…
Reference in New Issue
Block a user