AnimeWorld Skipper

Salta anime intro in AnimeWorld

安裝腳本?
作者推薦腳本

您可能也會喜歡 BetterAnimeWorld

安裝腳本
  1. // ==UserScript==
  2. // @name AnimeWorld Skipper
  3. // @namespace https://github.com/pizidavi
  4. // @icon https://static.animeworld.ac/assets/images/favicon/android-icon-192x192.png
  5. // @description Salta anime intro in AnimeWorld
  6. // @author pizidavi
  7. // @version 1.1.1
  8. // @copyright 2023, PIZIDAVI
  9. // @license MIT
  10. // @require https://cdn.jsdelivr.net/gh/soufianesakhi/node-creation-observer-js@edabdee1caaee6af701333a527a0afd95240aa3b/release/node-creation-observer-latest.min.js
  11. // @require https://cdn.jsdelivr.net/gh/sizzlemctwizzle/GM_config@2207c5c1322ebb56e401f03c2e581719f909762a/gm_config.min.js
  12. // @require https://gf.qytechs.cn/scripts/457460-aniskip/code/AniSkip.js
  13. // @require https://gf.qytechs.cn/scripts/401626-notify-library/code/Notify%20Library.js
  14. // @match https://www.animeworld.ac/play/*
  15. // @connect api.aniskip.com
  16. // @grant GM_getValue
  17. // @grant GM_setValue
  18. // @grant GM_xmlhttpRequest
  19. // @grant GM_registerMenuCommand
  20. // @grant GM_getResourceText
  21. // @run-at document-body
  22. // ==/UserScript==
  23.  
  24. /* global GM_config, NodeCreationObserver, AniSkip, Notify */
  25.  
  26. (function () {
  27. 'use strict';
  28.  
  29. //* GM_config
  30. GM_config.init({
  31. id: GM.info.script.name.toLowerCase().replace(/\s/g, '-'),
  32. title: `${GM.info.script.name} v${GM.info.script.version} Settings`,
  33. fields: {
  34. AniSkipUserId: {
  35. label: 'AniSkip User Id',
  36. section: ['AniSkip', 'Ottieni su: https://www.uuidgenerator.net/'],
  37. labelPos: 'left',
  38. type: 'text',
  39. title: 'Il tuo AniSkip userId',
  40. size: 70,
  41. default: ''
  42. },
  43. ManualSeekTime: {
  44. label: 'Secondi saltati con Salto Manuale (s)',
  45. section: ['Settings', 'Script settings'],
  46. type: 'int',
  47. default: 90
  48. },
  49. SeekWheel: {
  50. label: 'Millisecondi saltati con rotella del mouse su input nel Menu (ms)',
  51. type: 'int',
  52. default: 50
  53. },
  54. SubmittedSkip: {
  55. label: 'Times Skip inviati',
  56. section: ['Stats', '(solo lettura)'],
  57. type: 'int',
  58. default: 0,
  59. save: false
  60. }
  61. },
  62. events: {
  63. init: () => { },
  64. open: () => {
  65. GM_config.set('SubmittedSkip', GM_getValue('SubmittedSkip', 0));
  66. },
  67. save: () => {
  68. alert('Impostazioni salvate');
  69. GM_config.close()
  70. setTimeout(window.location.reload(false), 500);
  71. }
  72. }
  73. });
  74. GM_registerMenuCommand('Configure', () => GM_config.open());
  75.  
  76. //* Const
  77. const CSS = `
  78. /* Skip Button */
  79. #player { display: flex; }
  80. .aws-btn {
  81. display: none;
  82. position: absolute;
  83. bottom: 6em;
  84. right: 2em;
  85. padding: .5em 1em .4em;
  86. font-size: 14px;
  87. border: 1px solid #bdc3c7;
  88. border-radius: 2px;
  89. color: #bdc3c7;
  90. background-color: rgba(0, 0, 0, .7);
  91. opacity: .35;
  92. z-index: 2147483648;
  93. }
  94. .aws-btn:hover {
  95. opacity: .75;
  96. }
  97. .aws-btn.active {
  98. display: block;
  99. }
  100. /* Menu */
  101. .aws-container {
  102. position: absolute;
  103. top: 1em;
  104. right: 1em;
  105. border: none;
  106. }
  107. .aws-container summary {
  108. cursor: pointer;
  109. color: white;
  110. background-color: rgba(35, 35, 35, 0.5);
  111. border-radius: 5px;
  112. padding: 1px 5px;
  113. user-select: none;
  114. opacity: 0.5;
  115. transition: opacity 0.3s;
  116. }
  117. .aws-container[open] summary,
  118. .aws-container summary:hover {
  119. opacity: 0.8;
  120. }
  121. .aws-container summary::-webkit-details-marker {
  122. display: none;
  123. }
  124. .aws-menu {
  125. position: absolute;
  126. right: 0;
  127. padding: 10px;
  128. border-radius: 3px;
  129. color: white;
  130. background-color: rgba(20, 20, 20, 0.95);
  131. z-index: 2;
  132. min-width: 300px;
  133. }
  134. .aws-menu h4,
  135. .aws-menu h5 {
  136. margin-top: 0;
  137. margin-bottom: 3px;
  138. }
  139. .aws-menu h5 {
  140. margin-bottom: 0;
  141. }
  142. .aws-menu hr {
  143. margin: 10px 0;
  144. padding: 0;
  145. }
  146. #aws-message {
  147. text-align: center;
  148. margin-bottom: 0;
  149. }
  150. #aws-reload-list {
  151. fill: white;
  152. width: 11px;
  153. margin-right: 2px;
  154. }
  155. .aws-skip-list {
  156. overflow-y: auto;
  157. max-height: 250px;
  158. }
  159. .aws-skip-list .skip-item {
  160. display: flex;
  161. align-items: center;
  162. padding: 5px;
  163. border-bottom: 1px solid #555;
  164. }
  165. .aws-skip-list .skip-item:last-child {
  166. border-bottom: none;
  167. }
  168. .aws-skip-list .skip-item .icon {
  169. display: block;
  170. height: 25px;
  171. width: 25px;
  172. margin-right: 10px;
  173. float: left;
  174. background-color: #bbb;
  175. border: 1px solid white;
  176. border-radius: 50%;
  177. }
  178. .aws-skip-list .skip-item .icon.op {
  179. background-color: green
  180. }
  181. .aws-skip-list .skip-item .icon.ed {
  182. background-color: gold
  183. }
  184. .aws-skip-list .skip-item .icon.mixed-op {
  185. background-color: #90ee90
  186. }
  187. .aws-skip-list .skip-item .icon.mixed-ed {
  188. background-color: #ff0
  189. }
  190. .aws-skip-list .skip-item .icon.recap {
  191. background-color: #add8e6
  192. }
  193. .aws-skip-list .skip-item .info .name {
  194. font-weight: 400;
  195. font-size: 1.1rem;
  196. }
  197. .aws-skip-list .skip-item .info p {
  198. margin: 0;
  199. font-size: .9rem;
  200. }
  201. .aws-skip-list .skip-item .action {
  202. display: flex;
  203. margin-left: auto;
  204. }
  205. .aws-skip-list .skip-item .action button {
  206. display: flex;
  207. justify-content: center;
  208. align-items: center;
  209. width: 25px;
  210. aspect-ratio: 1;
  211. margin-right: 4px;
  212. padding: 0;
  213. border-radius: 25%;
  214. }
  215. .aws-skip-list .skip-item .action button:last-child {
  216. margin-right: 0;
  217. }
  218. .aws-skip-list .skip-item .action button svg {
  219. width: 12px;
  220. }
  221. `;
  222. const MENU = `
  223. <details id="aws" class="aws-container">
  224. <summary title="Apri/Chiudi menu" role="button">AW Skipper</summary>
  225. <div class="aws-menu">
  226. <h4>AnimeWorld Skipper</h4>
  227. <hr />
  228. <!-- Form -->
  229. <form>
  230. <h5>Add Skip Time</h5>
  231. <select class="form-control" name="aws-skip-type" style="margin-bottom:5px" required>
  232. <option value hidden selected>Seleziona una tipologia</option>
  233. <option value="op">Opening</option>
  234. <option value="ed">Ending</option>
  235. <option value="mixed-op">Mixed Epening</option>
  236. <option value="mixed-ed">Mixed Ending</option>
  237. <option value="recap">Recap</option>
  238. </select>
  239. <div class="row">
  240. <div class="col-sm-4" style="padding-right:0;">
  241. <input
  242. type="number"
  243. class="form-control"
  244. name="aws-start"
  245. value="0.000"
  246. placeholder="Start time"
  247. step="0.001"
  248. min="0"
  249. required
  250. data-aws-wheel
  251. >
  252. <small data-aws-goTo="now" role="button">+Ora</small>
  253. </div>
  254. <div class="col-sm-4" style="padding-right:0;">
  255. <input
  256. type="number"
  257. class="form-control"
  258. name="aws-end"
  259. value="0.000"
  260. placeholder="End time"
  261. step="0.001"
  262. min="0"
  263. required
  264. data-aws-wheel
  265. >
  266. <small data-aws-goTo="now" role="button">+Ora</small>
  267. <small data-aws-goTo="+90" role="button">+90s</small>
  268. <small data-aws-goTo="end" style="margin-left:auto;" role="button">+Fine</small>
  269. </div>
  270. <div class="col-sm-4">
  271. <button
  272. class="btn btn-block btn-primary"
  273. type="submit"
  274. disabled
  275. >Salva</button>
  276. </div>
  277. </div>
  278. </form>
  279. <!-- End form -->
  280. <hr />
  281. <!-- Skip list -->
  282. <h5>
  283. <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" id="aws-reload-list" role="button">
  284. <path d="M463.5 224H472c13.3 0 24-10.7 24-24V72c0-9.7-5.8-18.5-14.8-22.2s-19.3-1.7-26.2 5.2L413.4 96.6c-87.6-86.5-228.7-86.2-315.8 1c-87.5 87.5-87.5 229.3 0 316.8s229.3 87.5 316.8 0c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0c-62.5 62.5-163.8 62.5-226.3 0s-62.5-163.8 0-226.3c62.2-62.2 162.7-62.5 225.3-1L327 183c-6.9 6.9-8.9 17.2-5.2 26.2s12.5 14.8 22.2 14.8H463.5z" />
  285. </svg>
  286. Skip Times
  287. </h5>
  288. <div id="aws-skip-list" class="aws-skip-list"><!-- Items --></div>
  289. <p id="aws-message" style="text-align:center;">Waiting for video metadata</p>
  290. <!-- End skip list -->
  291. </div>
  292. </details>
  293. `;
  294. const TEMPLATE_ITEM = `
  295. <div class="skip-item">
  296. <span class="icon"></span>
  297. <div class="info">
  298. <span class="name"></span>
  299. <p class="times">
  300. <span class="time-start" role="button" title="Go to start"></span>
  301. -
  302. <span class="time-end" role="button" title="Go to end"></span>
  303. </p>
  304. </div>
  305. <div class="action">
  306. <button class="btn btn-dark" data-aws-vote="upvote">
  307. <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
  308. <path d="M313.4 32.9c26 5.2 42.9 30.5 37.7 56.5l-2.3 11.4c-5.3 26.7-15.1 52.1-28.8 75.2H464c26.5 0 48 21.5 48 48c0 25.3-19.5 46-44.3 47.9c7.7 8.5 12.3 19.8 12.3 32.1c0 23.4-16.8 42.9-38.9 47.1c4.4 7.2 6.9 15.8 6.9 24.9c0 21.3-13.9 39.4-33.1 45.6c.7 3.3 1.1 6.8 1.1 10.4c0 26.5-21.5 48-48 48H294.5c-19 0-37.5-5.6-53.3-16.1l-38.5-25.7C176 420.4 160 390.4 160 358.3V320 272 247.1c0-29.2 13.3-56.7 36-75l7.4-5.9c26.5-21.2 44.6-51 51.2-84.2l2.3-11.4c5.2-26 30.5-42.9 56.5-37.7zM32 192H96c17.7 0 32 14.3 32 32V448c0 17.7-14.3 32-32 32H32c-17.7 0-32-14.3-32-32V224c0-17.7 14.3-32 32-32z" fill="black"></path>
  309. </svg>
  310. </button>
  311. <button class="btn" data-aws-vote="downvote">
  312. <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
  313. <path d="M313.4 479.1c26-5.2 42.9-30.5 37.7-56.5l-2.3-11.4c-5.3-26.7-15.1-52.1-28.8-75.2H464c26.5 0 48-21.5 48-48c0-25.3-19.5-46-44.3-47.9c7.7-8.5 12.3-19.8 12.3-32.1c0-23.4-16.8-42.9-38.9-47.1c4.4-7.3 6.9-15.8 6.9-24.9c0-21.3-13.9-39.4-33.1-45.6c.7-3.3 1.1-6.8 1.1-10.4c0-26.5-21.5-48-48-48H294.5c-19 0-37.5 5.6-53.3 16.1L202.7 73.8C176 91.6 160 121.6 160 153.7V192v48 24.9c0 29.2 13.3 56.7 36 75l7.4 5.9c26.5 21.2 44.6 51 51.2 84.2l2.3 11.4c5.2 26 30.5 42.9 56.5 37.7zM32 320H96c17.7 0 32-14.3 32-32V64c0-17.7-14.3-32-32-32H32C14.3 32 0 46.3 0 64V288c0 17.7 14.3 32 32 32z"/>
  314. </svg>
  315. </button>
  316. </div>
  317. </div>
  318. `;
  319.  
  320. const aniskip = new AniSkip({
  321. userId: GM_config.get('AniSkipUserId'),
  322. providerName: 'AnimeWorld'
  323. });
  324. const seekWheel = GM_config.get('SeekWheel', 50) / 1000;
  325.  
  326. //* Script
  327. NodeCreationObserver.init('AnimeWorldSkipper');
  328. NodeCreationObserver.onCreation('#player iframe', function (iframe) {
  329. const malId = document.querySelector('#mal-button').getAttribute('href').split('/').at(-1);
  330. const episodeNumber = document.querySelector('div.server ul a.active').getAttribute('data-base').split('-')[0];
  331.  
  332. iframe.addEventListener('load', function () {
  333. const content = this.contentDocument;
  334. const video = content?.querySelector('video');
  335. if (!video) return;
  336.  
  337. injectStyle(CSS, content.head);
  338. const menu = injectHTML(MENU, video.parentNode);
  339. menu.querySelector('form [name="aws-start"]').onchange = function () {
  340. this.value = (parseFloat(this.value) || 0).toFixed(3);
  341. video.currentTime = this.value;
  342. };
  343. menu.querySelector('form [name="aws-end"]').onchange = function () {
  344. this.value = (parseFloat(this.value) || 0).toFixed(3);
  345. video.currentTime = this.value;
  346. };
  347.  
  348. const loadskipdata = () => {
  349. const videoLength = video.duration;
  350.  
  351. content.querySelectorAll('.aws-btn').forEach(element => {
  352. element.remove();
  353. });
  354. menu.querySelector('#aws-message').textContent = 'Loading...';
  355. menu.querySelector('#aws-skip-list').innerHTML = '';
  356. menu.querySelector('#aws-reload-list').onclick = () => {
  357. video.dispatchEvent(new Event('loadskipdata'));
  358. };
  359.  
  360. menu.querySelector('form [name="aws-start"]').max = videoLength.toFixed(3);
  361. menu.querySelector('form [name="aws-end"]').max = videoLength.toFixed(3);
  362. menu.querySelector('form button[type="submit"]').disabled = false;
  363.  
  364. menu.querySelectorAll('form [data-aws-goTo]').forEach(element => {
  365. const action = element.getAttribute('data-aws-goTo');
  366. element.onclick = function () {
  367. const value = (action === 'now' ? video.currentTime : action === '+90' ? video.currentTime+90 : videoLength).toFixed(3);
  368. this.parentNode.querySelector('input').value = value;
  369. this.parentNode.querySelector('input').dispatchEvent(new Event('change'));
  370. };
  371. });
  372. menu.querySelectorAll('form [data-aws-wheel]').forEach(element => {
  373. element.onwheel = function (e) {
  374. e.preventDefault();
  375. e.stopPropagation();
  376. let newValue = (parseFloat(this.value) || 0) + (e.deltaY >= 0 ? -seekWheel : seekWheel);
  377. if (newValue < 0)
  378. newValue = 0;
  379. else if (newValue > videoLength)
  380. newValue = videoLength;
  381. if (this.value !== newValue.toFixed(3)) {
  382. this.value = newValue.toFixed(3);
  383. this.dispatchEvent(new Event('change'));
  384. }
  385. };
  386. });
  387.  
  388. aniskip.getSkipTimes(malId, episodeNumber, videoLength)
  389. .then(data => {
  390. console.log(data)
  391. menu.querySelector('#aws-message').textContent = '';
  392.  
  393. data.forEach(item => {
  394. // Add skip-btn to video
  395. const skipButton = createSkipButton({
  396. id: item.skipId,
  397. text: 'Salta ' + aniskip.CategoryTitle[item.skipType]
  398. });
  399. skipButton.onclick = () => {
  400. video.currentTime = item.interval.endTime;
  401. video.focus();
  402. };
  403. skipButton.setAttribute('data-time-start', item.interval.startTime);
  404. skipButton.setAttribute('data-time-end', item.interval.endTime);
  405. video.parentNode.appendChild(skipButton);
  406.  
  407. // Add skip-item to menu
  408. const skipItem = injectHTML(TEMPLATE_ITEM, menu.querySelector('#aws-skip-list'));
  409. skipItem.querySelector('.icon').classList.add(item.skipType);
  410. skipItem.querySelector('.name').textContent = aniskip.CategoryTitle[item.skipType];
  411.  
  412. skipItem.querySelector('.times .time-start').textContent = item.interval.startTime.toFixed(1);
  413. skipItem.querySelector('.times .time-start').onclick = () => {
  414. video.currentTime = item.interval.startTime;
  415. };
  416. skipItem.querySelector('.times .time-end').textContent = item.interval.endTime.toFixed(1);
  417. skipItem.querySelector('.times .time-end').onclick = () => {
  418. video.currentTime = item.interval.endTime;
  419. };
  420. skipItem.querySelectorAll('.action [data-aws-vote]').forEach(element => {
  421. const action = element.getAttribute('data-aws-vote');
  422. element.onclick = function () {
  423. this.disabled = true;
  424. aniskip.vote(action, item.skipId)
  425. .then(data => {
  426. new Notify({
  427. text: data.message || 'Votato!',
  428. type: 'success'
  429. }).show();
  430. })
  431. .catch(data => {
  432. console.error(data)
  433. new Notify({
  434. text: data.message || 'Errore',
  435. type: 'error'
  436. }).show();
  437. })
  438. .finally(() => {
  439. video.dispatchEvent(new Event('loadskipdata'));
  440. });
  441. }
  442. });
  443. });
  444. })
  445. .catch(response => {
  446. console.log(response)
  447. menu.querySelector('#aws-message').textContent = response.message || 'No skip time';
  448.  
  449. // Add skip-btn to video
  450. const skipButton = createSkipButton({
  451. id: 'manual-skip',
  452. text: 'Salto Manuale',
  453. class: 'aws-btn manual active',
  454. });
  455. skipButton.onclick = function () {
  456. const seekTime = GM_config.get('ManualSeekTime', 90);
  457. const form = menu.querySelector('form');
  458. const currentTime = video.currentTime - 0.4;
  459. form.querySelector('[name="aws-start"]').value = currentTime.toFixed(3);
  460. form.querySelector('[name="aws-end"]').value = (currentTime + seekTime).toFixed(3);
  461. menu.setAttribute('open', true);
  462. video.pause();
  463.  
  464. video.currentTime += seekTime;
  465. video.focus();
  466. this.remove();
  467. };
  468. video.parentNode.appendChild(skipButton);
  469. });
  470.  
  471. menu.querySelector('form').onsubmit = function (e) {
  472. e.preventDefault();
  473. e.stopPropagation();
  474. const form = this;
  475. const skipType = form.querySelector('[name="aws-skip-type"]').value;
  476. const timeStart = parseFloat(form.querySelector('[name="aws-start"]').value);
  477. const timeEnd = parseFloat(form.querySelector('[name="aws-end"]').value);
  478. if (
  479. !skipType ||
  480. (!timeStart && timeStart !== 0) ||
  481. (!timeEnd && timeEnd !== 0) ||
  482. timeStart >= timeEnd ||
  483. timeStart < 0 ||
  484. timeEnd > videoLength
  485. ) {
  486. alert('Campi non validi');
  487. return;
  488. }
  489.  
  490. form.querySelector('button[type="submit"]').disabled = true;
  491. aniskip.createSkipTime(
  492. malId,
  493. episodeNumber,
  494. {
  495. skipType: skipType,
  496. startTime: timeStart,
  497. endTime: timeEnd,
  498. episodeLength: videoLength
  499. }
  500. ).then(data => {
  501. form.querySelector('button[type="submit"]').disabled = false;
  502. form.reset();
  503. GM_setValue('SubmittedSkip', GM_getValue('SubmittedSkip', 0) + 1);
  504. video.dispatchEvent(new Event('loadskipdata'));
  505. }).catch(error => console.error(error));
  506. };
  507. };
  508.  
  509. video.addEventListener('loadskipdata', loadskipdata);
  510. if (video.readyState > 0) {
  511. loadskipdata();
  512. video.play();
  513. } else
  514. video.onloadedmetadata = loadskipdata;
  515.  
  516. // Show/Hide skipButton base of currentTime
  517. video.ontimeupdate = () => {
  518. content.querySelectorAll('.aws-btn:not(.manual)').forEach(element => {
  519. const startTime = element.getAttribute('data-time-start');
  520. const endTime = element.getAttribute('data-time-end');
  521. const active = video.currentTime >= startTime && video.currentTime < endTime;
  522. element.classList.toggle('active', active);
  523. });
  524. if (video.currentTime > video.duration / 3)
  525. content.querySelector('#manual-skip')?.remove();
  526. };
  527.  
  528. video.oncanplay = function () {
  529. this.play();
  530. this.focus();
  531. this.oncanplay = null; // only at video start
  532. };
  533.  
  534. video.onmouseenter = function () {
  535. if (!this.paused && !this.ended)
  536. this.focus();
  537. };
  538.  
  539. content.onkeydown = e => {
  540. if (e.keyCode === 13) {
  541. const btn = content.querySelector('.aws-btn.active');
  542. if (btn) {
  543. e.preventDefault();
  544. btn.click();
  545. }
  546. }
  547. };
  548.  
  549. }); // end iframe.addEventListener
  550. }); // end NodeCreationObserver.onCreation
  551.  
  552. injectMeta('AnimeWorldSkipper');
  553.  
  554.  
  555. //* Functions
  556. function createSkipButton(options = {}) {
  557. return createElement('button', {
  558. text: options.text ?? 'Salta',
  559. class: options.class ?? 'aws-btn',
  560. ...options
  561. });
  562. }
  563.  
  564. function createElement(tagName, options) {
  565. const element = document.createElement(tagName);
  566. element.textContent = options?.text;
  567. element.id = options?.id ?? '';
  568. element.classList = options?.class ?? '';
  569. element.title = options?.title ?? '';
  570. element.value = options?.value ?? '';
  571. element.style = options?.style ?? '';
  572. element.innerHTML = options?.html ?? element.innerHTML;
  573. return element;
  574. }
  575.  
  576. function injectHTML(html, parent = document.body) {
  577. const tag = document.createElement('div');
  578. tag.innerHTML = html;
  579. return parent.appendChild(tag.childElementCount === 1 ? tag.firstElementChild : tag);
  580. }
  581.  
  582. function injectStyle(style, parent = document.head) {
  583. const tag = document.createElement('style');
  584. tag.innerText = style;
  585. parent.appendChild(tag);
  586. }
  587.  
  588. function injectMeta(name, content = '', parent = document.head) {
  589. const tag = document.createElement('meta');
  590. tag.name = name;
  591. tag.content = content;
  592. parent.appendChild(tag);
  593. }
  594.  
  595. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址