AnimeWorld Scrobbling

Segna automaticamente gli episodi visualizzati su Trakt.TV

目前为 2020-07-24 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name AnimeWorld Scrobbling
  3. // @namespace https://www.pizidavi.altervista.org/
  4. // @description Segna automaticamente gli episodi visualizzati su Trakt.TV
  5. // @author pizidavi
  6. // @version 1.4.1
  7. // @copyright 2020, PIZIDAVI
  8. // @license MIT
  9. // @homepageURL https://www.pizidavi.altervista.org/AnimeWoldScrobbling/
  10. // @require https://greasyfork.org/scripts/401626-notify-library/code/Notify%20Library.js
  11. // @include https://*.animeworld.*/play/*
  12.  
  13. // @connect https://api.trakt.tv/
  14. // @grant GM_addStyle
  15. // @grant GM_getValue
  16. // @grant GM_setValue
  17. // @grant GM_deleteValue
  18. // @grant GM_listValues
  19. // @grant GM_xmlhttpRequest
  20.  
  21. // ==/UserScript==
  22.  
  23. (function($) {
  24. 'use strict';
  25.  
  26. // USER INFO
  27. const CLIENT_ID = '';
  28. const ACCESS_TOKEN = '';
  29. const EXPIRES = '';
  30.  
  31. // SETTINGS
  32. const SHOW_HELPER = true;
  33. const AUTO_NEXT_EPISODE = false;
  34.  
  35.  
  36. /* ----------- CODE ----------- */
  37. const css = '#body .sidebar { float: right; width: 300px; position: relative; z-index: 1; }';
  38. const template = '<div id="trakt" class="sidebar"><div class="widget"><div class="widget-title"><div class="title">Trakt</div></div><div class="widget-body"><div class="row" style="margin-bottom:1em;"><div class="col-sm-10" style=" padding-right: 0; "><button class="btn btn-primary btn-block" id="watched">Episodio Visto</button></div><div class="col-sm-2"><input type="checkbox" id="autoNext" style="margin-top: 8px;" title="Prossimo episodio automatico"></div></div><div class="row"><div class="col-sm-6" style=" padding-right: 0; "><input type="text" class="form-control" placeholder="Trakt Slug"><small id="helper" style="display:none;margin:0.3em 0px -5px 0.5em;"><a href="https://www.pizidavi.altervista.org/AnimeWoldScrobbling/#trakt" target="_blank" style="color:grey;">Dove lo trovo?</a></small></div><div class="col-sm-2" style=" padding-right: 0; "><input type="number" class="form-control" value="1" min="0" placeholder="Season"></div><div class="col-sm-4"><button class="btn btn-success btn-block">Salva</button></div></div></div></div></div>';
  39.  
  40. const AnimeID = getAnimeID();
  41. var Trakt = GM_getValue(AnimeID, {});
  42. var section = $(template);
  43.  
  44. GM_addStyle(css);
  45. $('#body #body-container').append(section);
  46.  
  47. if(!CLIENT_ID || !ACCESS_TOKEN || !EXPIRES) {
  48. section.find('div.widget-body').html('Dati Trakt mancanti. Segui la <a href="https://www.pizidavi.altervista.org/AnimeWoldScrobbling/" target="_blank">guida</a>');
  49. return; }
  50. if(new Date() >= new Date(EXPIRES)) {
  51. section.find('div.widget-body').html('Access Token Trakt Scaduto. <a href="https://www.pizidavi.altervista.org/AnimeWoldScrobbling/#login" target="_blank">Aggiorna</a>');
  52. return; }
  53. if(AnimeID == undefined) {
  54. section.find('div.widget-body').html('Errore. AnimeID non trovato');
  55. return; }
  56.  
  57. section.find('input[type="text"]').val(Trakt.slug);
  58. section.find('input[type="number"]').val((Trakt.season || '1'));
  59. if(AUTO_NEXT_EPISODE)
  60. section.find('input[type="checkbox"]').attr('checked', '');
  61. if(SHOW_HELPER)
  62. section.find('#helper').css('display', 'block');
  63.  
  64. section.find('button.btn:not(#watched)').on('click', function() {
  65. var title = $('#main div.widget.info div.info > div.head h2').text();
  66. var slug = $(this).parent().parent().find('input[type="text"]').val();
  67. var season = $(this).parent().parent().find('input[type="number"]').val();
  68.  
  69. if(slug != '' && season != '') {
  70. Trakt.title = title.trim();
  71. Trakt.slug = slug.trim();
  72. Trakt.season = season;
  73. GM_setValue(AnimeID, Trakt);
  74.  
  75. section.find('button#watched').removeAttr('disabled');
  76.  
  77. new Notify({
  78. text: 'Dati salvati',
  79. type: 'success'
  80. }).show();
  81. } else {
  82. new Notify({
  83. text: 'Completa tutti i campi',
  84. type: 'warn'
  85. }).show();
  86. }
  87. });
  88.  
  89. if(Trakt.slug == undefined || Trakt.season == undefined) {
  90. section.find('button#watched').attr('disabled', ''); }
  91. $('button#watched').on('click', function() {
  92. var _this = $(this);
  93. var episode = $('div.server ul a.active').attr('data-base');
  94. if(Trakt.slug == undefined || Trakt.season == undefined || episode == undefined) {
  95. new Notify({
  96. text: 'Errore',
  97. type: 'error'
  98. }).show();
  99. return;
  100. }
  101. _this.attr('disabled', '');
  102.  
  103. var joData = {
  104. 'shows': [
  105. {
  106. 'ids': {
  107. 'slug': Trakt.slug,
  108. },
  109. 'seasons': [
  110. {
  111. 'number': parseInt(Trakt.season),
  112. 'episodes': [
  113. {
  114. 'watched_at': new Date().toJSON(),
  115. 'number': parseFloat(episode)
  116. }
  117. ]
  118. }
  119. ]
  120. }
  121. ]
  122. };
  123. request(joData, function(data) {
  124. if(data.added.episodes > 0) {
  125. new Notify({
  126. text: 'Episodio '+episode+' salvato',
  127. type: 'success'
  128. }).show();
  129.  
  130. if(AUTO_NEXT_EPISODE || section.find('#autoNext').prop('checked'))
  131. $('#controls > div.prevnext[data-value="next"]').click();
  132. } else {
  133. new Notify({
  134. text: 'Errore. Episodio non trovato',
  135. type: 'error'
  136. }).show();
  137. }
  138. _this.removeAttr('disabled');
  139. });
  140. });
  141. $(document).on('click', '#controls > div.userbookmark > ul > li:nth-child(2), #controls > div.userbookmark > ul > li:nth-child(7)', function() {
  142. var deleted = deleteOne(AnimeID);
  143. if(deleted) {
  144. section.find('input[type="text"]').val('');
  145. section.find('input[type="number"]').val('1');
  146. section.find('button#watched').attr('disabled', '');
  147. Trakt = {};
  148.  
  149. new Notify({
  150. text: 'Dati Trakt rimossi',
  151. type: 'success'
  152. }).show();
  153. }
  154. });
  155.  
  156.  
  157. // Functions
  158. function request(data, success) {
  159. var r = new XMLHttpRequest();
  160. r.open('POST', 'https://api.trakt.tv/sync/history');
  161.  
  162. r.setRequestHeader('Content-Type', 'application/json');
  163. r.setRequestHeader('Authorization', 'Bearer '+ACCESS_TOKEN);
  164. r.setRequestHeader('trakt-api-version', '2');
  165. r.setRequestHeader('trakt-api-key', CLIENT_ID);
  166.  
  167. r.onreadystatechange = function () {
  168. if (this.readyState === 4 && (this.status == 200 || this.status == 201)) {
  169. success(JSON.parse(this.responseText));
  170. } else if (this.readyState === 4) {
  171. new Notify({
  172. text: 'Errore nella richiesta. Ricarica la pagina',
  173. type: 'error'
  174. }).show();
  175. }
  176. };
  177. r.send(JSON.stringify(data));
  178. }
  179.  
  180. function getAnimeID() {
  181. var url = location.pathname;
  182. var start = url.indexOf('.')+1;
  183. var end = start + (url.substring(start).indexOf('/') >= 0 ? url.substring(start).indexOf('/') : url.substring(start).length);
  184. return url.substring(start, end) || undefined;
  185. }
  186.  
  187. function deleteOne(key) {
  188. if(GM_listValues().includes(key)) {
  189. GM_deleteValue(key);
  190. return true;
  191. } else {
  192. return false;
  193. }
  194. }
  195. function deleteAll() {
  196. GM_listValues().forEach(function(key) {
  197. GM_deleteValue(key);
  198. });
  199. return true;
  200. }
  201.  
  202. })(jQuery);