您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Salta anime intro in AnimeWorld
// ==UserScript== // @name AnimeWorld Skipper // @namespace https://github.com/pizidavi // @icon https://static.animeworld.ac/assets/images/favicon/android-icon-192x192.png // @description Salta anime intro in AnimeWorld // @author pizidavi // @version 1.1.1 // @copyright 2023, PIZIDAVI // @license MIT // @require https://cdn.jsdelivr.net/gh/soufianesakhi/node-creation-observer-js@edabdee1caaee6af701333a527a0afd95240aa3b/release/node-creation-observer-latest.min.js // @require https://cdn.jsdelivr.net/gh/sizzlemctwizzle/GM_config@2207c5c1322ebb56e401f03c2e581719f909762a/gm_config.min.js // @require https://greasyfork.org/scripts/457460-aniskip/code/AniSkip.js // @require https://greasyfork.org/scripts/401626-notify-library/code/Notify%20Library.js // @match https://www.animeworld.ac/play/* // @connect api.aniskip.com // @grant GM_getValue // @grant GM_setValue // @grant GM_xmlhttpRequest // @grant GM_registerMenuCommand // @grant GM_getResourceText // @run-at document-body // ==/UserScript== /* global GM_config, NodeCreationObserver, AniSkip, Notify */ (function () { 'use strict'; //* GM_config GM_config.init({ id: GM.info.script.name.toLowerCase().replace(/\s/g, '-'), title: `${GM.info.script.name} v${GM.info.script.version} Settings`, fields: { AniSkipUserId: { label: 'AniSkip User Id', section: ['AniSkip', 'Ottieni su: https://www.uuidgenerator.net/'], labelPos: 'left', type: 'text', title: 'Il tuo AniSkip userId', size: 70, default: '' }, ManualSeekTime: { label: 'Secondi saltati con Salto Manuale (s)', section: ['Settings', 'Script settings'], type: 'int', default: 90 }, SeekWheel: { label: 'Millisecondi saltati con rotella del mouse su input nel Menu (ms)', type: 'int', default: 50 }, SubmittedSkip: { label: 'Times Skip inviati', section: ['Stats', '(solo lettura)'], type: 'int', default: 0, save: false } }, events: { init: () => { }, open: () => { GM_config.set('SubmittedSkip', GM_getValue('SubmittedSkip', 0)); }, save: () => { alert('Impostazioni salvate'); GM_config.close() setTimeout(window.location.reload(false), 500); } } }); GM_registerMenuCommand('Configure', () => GM_config.open()); //* Const const CSS = ` /* Skip Button */ #player { display: flex; } .aws-btn { display: none; position: absolute; bottom: 6em; right: 2em; padding: .5em 1em .4em; font-size: 14px; border: 1px solid #bdc3c7; border-radius: 2px; color: #bdc3c7; background-color: rgba(0, 0, 0, .7); opacity: .35; z-index: 2147483648; } .aws-btn:hover { opacity: .75; } .aws-btn.active { display: block; } /* Menu */ .aws-container { position: absolute; top: 1em; right: 1em; border: none; } .aws-container summary { cursor: pointer; color: white; background-color: rgba(35, 35, 35, 0.5); border-radius: 5px; padding: 1px 5px; user-select: none; opacity: 0.5; transition: opacity 0.3s; } .aws-container[open] summary, .aws-container summary:hover { opacity: 0.8; } .aws-container summary::-webkit-details-marker { display: none; } .aws-menu { position: absolute; right: 0; padding: 10px; border-radius: 3px; color: white; background-color: rgba(20, 20, 20, 0.95); z-index: 2; min-width: 300px; } .aws-menu h4, .aws-menu h5 { margin-top: 0; margin-bottom: 3px; } .aws-menu h5 { margin-bottom: 0; } .aws-menu hr { margin: 10px 0; padding: 0; } #aws-message { text-align: center; margin-bottom: 0; } #aws-reload-list { fill: white; width: 11px; margin-right: 2px; } .aws-skip-list { overflow-y: auto; max-height: 250px; } .aws-skip-list .skip-item { display: flex; align-items: center; padding: 5px; border-bottom: 1px solid #555; } .aws-skip-list .skip-item:last-child { border-bottom: none; } .aws-skip-list .skip-item .icon { display: block; height: 25px; width: 25px; margin-right: 10px; float: left; background-color: #bbb; border: 1px solid white; border-radius: 50%; } .aws-skip-list .skip-item .icon.op { background-color: green } .aws-skip-list .skip-item .icon.ed { background-color: gold } .aws-skip-list .skip-item .icon.mixed-op { background-color: #90ee90 } .aws-skip-list .skip-item .icon.mixed-ed { background-color: #ff0 } .aws-skip-list .skip-item .icon.recap { background-color: #add8e6 } .aws-skip-list .skip-item .info .name { font-weight: 400; font-size: 1.1rem; } .aws-skip-list .skip-item .info p { margin: 0; font-size: .9rem; } .aws-skip-list .skip-item .action { display: flex; margin-left: auto; } .aws-skip-list .skip-item .action button { display: flex; justify-content: center; align-items: center; width: 25px; aspect-ratio: 1; margin-right: 4px; padding: 0; border-radius: 25%; } .aws-skip-list .skip-item .action button:last-child { margin-right: 0; } .aws-skip-list .skip-item .action button svg { width: 12px; } `; const MENU = ` <details id="aws" class="aws-container"> <summary title="Apri/Chiudi menu" role="button">AW Skipper</summary> <div class="aws-menu"> <h4>AnimeWorld Skipper</h4> <hr /> <!-- Form --> <form> <h5>Add Skip Time</h5> <select class="form-control" name="aws-skip-type" style="margin-bottom:5px" required> <option value hidden selected>Seleziona una tipologia</option> <option value="op">Opening</option> <option value="ed">Ending</option> <option value="mixed-op">Mixed Epening</option> <option value="mixed-ed">Mixed Ending</option> <option value="recap">Recap</option> </select> <div class="row"> <div class="col-sm-4" style="padding-right:0;"> <input type="number" class="form-control" name="aws-start" value="0.000" placeholder="Start time" step="0.001" min="0" required data-aws-wheel > <small data-aws-goTo="now" role="button">+Ora</small> </div> <div class="col-sm-4" style="padding-right:0;"> <input type="number" class="form-control" name="aws-end" value="0.000" placeholder="End time" step="0.001" min="0" required data-aws-wheel > <small data-aws-goTo="now" role="button">+Ora</small> <small data-aws-goTo="+90" role="button">+90s</small> <small data-aws-goTo="end" style="margin-left:auto;" role="button">+Fine</small> </div> <div class="col-sm-4"> <button class="btn btn-block btn-primary" type="submit" disabled >Salva</button> </div> </div> </form> <!-- End form --> <hr /> <!-- Skip list --> <h5> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" id="aws-reload-list" role="button"> <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" /> </svg> Skip Times </h5> <div id="aws-skip-list" class="aws-skip-list"><!-- Items --></div> <p id="aws-message" style="text-align:center;">Waiting for video metadata</p> <!-- End skip list --> </div> </details> `; const TEMPLATE_ITEM = ` <div class="skip-item"> <span class="icon"></span> <div class="info"> <span class="name"></span> <p class="times"> <span class="time-start" role="button" title="Go to start"></span> - <span class="time-end" role="button" title="Go to end"></span> </p> </div> <div class="action"> <button class="btn btn-dark" data-aws-vote="upvote"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"> <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> </svg> </button> <button class="btn" data-aws-vote="downvote"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"> <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"/> </svg> </button> </div> </div> `; const aniskip = new AniSkip({ userId: GM_config.get('AniSkipUserId'), providerName: 'AnimeWorld' }); const seekWheel = GM_config.get('SeekWheel', 50) / 1000; //* Script NodeCreationObserver.init('AnimeWorldSkipper'); NodeCreationObserver.onCreation('#player iframe', function (iframe) { const malId = document.querySelector('#mal-button').getAttribute('href').split('/').at(-1); const episodeNumber = document.querySelector('div.server ul a.active').getAttribute('data-base').split('-')[0]; iframe.addEventListener('load', function () { const content = this.contentDocument; const video = content?.querySelector('video'); if (!video) return; injectStyle(CSS, content.head); const menu = injectHTML(MENU, video.parentNode); menu.querySelector('form [name="aws-start"]').onchange = function () { this.value = (parseFloat(this.value) || 0).toFixed(3); video.currentTime = this.value; }; menu.querySelector('form [name="aws-end"]').onchange = function () { this.value = (parseFloat(this.value) || 0).toFixed(3); video.currentTime = this.value; }; const loadskipdata = () => { const videoLength = video.duration; content.querySelectorAll('.aws-btn').forEach(element => { element.remove(); }); menu.querySelector('#aws-message').textContent = 'Loading...'; menu.querySelector('#aws-skip-list').innerHTML = ''; menu.querySelector('#aws-reload-list').onclick = () => { video.dispatchEvent(new Event('loadskipdata')); }; menu.querySelector('form [name="aws-start"]').max = videoLength.toFixed(3); menu.querySelector('form [name="aws-end"]').max = videoLength.toFixed(3); menu.querySelector('form button[type="submit"]').disabled = false; menu.querySelectorAll('form [data-aws-goTo]').forEach(element => { const action = element.getAttribute('data-aws-goTo'); element.onclick = function () { const value = (action === 'now' ? video.currentTime : action === '+90' ? video.currentTime+90 : videoLength).toFixed(3); this.parentNode.querySelector('input').value = value; this.parentNode.querySelector('input').dispatchEvent(new Event('change')); }; }); menu.querySelectorAll('form [data-aws-wheel]').forEach(element => { element.onwheel = function (e) { e.preventDefault(); e.stopPropagation(); let newValue = (parseFloat(this.value) || 0) + (e.deltaY >= 0 ? -seekWheel : seekWheel); if (newValue < 0) newValue = 0; else if (newValue > videoLength) newValue = videoLength; if (this.value !== newValue.toFixed(3)) { this.value = newValue.toFixed(3); this.dispatchEvent(new Event('change')); } }; }); aniskip.getSkipTimes(malId, episodeNumber, videoLength) .then(data => { console.log(data) menu.querySelector('#aws-message').textContent = ''; data.forEach(item => { // Add skip-btn to video const skipButton = createSkipButton({ id: item.skipId, text: 'Salta ' + aniskip.CategoryTitle[item.skipType] }); skipButton.onclick = () => { video.currentTime = item.interval.endTime; video.focus(); }; skipButton.setAttribute('data-time-start', item.interval.startTime); skipButton.setAttribute('data-time-end', item.interval.endTime); video.parentNode.appendChild(skipButton); // Add skip-item to menu const skipItem = injectHTML(TEMPLATE_ITEM, menu.querySelector('#aws-skip-list')); skipItem.querySelector('.icon').classList.add(item.skipType); skipItem.querySelector('.name').textContent = aniskip.CategoryTitle[item.skipType]; skipItem.querySelector('.times .time-start').textContent = item.interval.startTime.toFixed(1); skipItem.querySelector('.times .time-start').onclick = () => { video.currentTime = item.interval.startTime; }; skipItem.querySelector('.times .time-end').textContent = item.interval.endTime.toFixed(1); skipItem.querySelector('.times .time-end').onclick = () => { video.currentTime = item.interval.endTime; }; skipItem.querySelectorAll('.action [data-aws-vote]').forEach(element => { const action = element.getAttribute('data-aws-vote'); element.onclick = function () { this.disabled = true; aniskip.vote(action, item.skipId) .then(data => { new Notify({ text: data.message || 'Votato!', type: 'success' }).show(); }) .catch(data => { console.error(data) new Notify({ text: data.message || 'Errore', type: 'error' }).show(); }) .finally(() => { video.dispatchEvent(new Event('loadskipdata')); }); } }); }); }) .catch(response => { console.log(response) menu.querySelector('#aws-message').textContent = response.message || 'No skip time'; // Add skip-btn to video const skipButton = createSkipButton({ id: 'manual-skip', text: 'Salto Manuale', class: 'aws-btn manual active', }); skipButton.onclick = function () { const seekTime = GM_config.get('ManualSeekTime', 90); const form = menu.querySelector('form'); const currentTime = video.currentTime - 0.4; form.querySelector('[name="aws-start"]').value = currentTime.toFixed(3); form.querySelector('[name="aws-end"]').value = (currentTime + seekTime).toFixed(3); menu.setAttribute('open', true); video.pause(); video.currentTime += seekTime; video.focus(); this.remove(); }; video.parentNode.appendChild(skipButton); }); menu.querySelector('form').onsubmit = function (e) { e.preventDefault(); e.stopPropagation(); const form = this; const skipType = form.querySelector('[name="aws-skip-type"]').value; const timeStart = parseFloat(form.querySelector('[name="aws-start"]').value); const timeEnd = parseFloat(form.querySelector('[name="aws-end"]').value); if ( !skipType || (!timeStart && timeStart !== 0) || (!timeEnd && timeEnd !== 0) || timeStart >= timeEnd || timeStart < 0 || timeEnd > videoLength ) { alert('Campi non validi'); return; } form.querySelector('button[type="submit"]').disabled = true; aniskip.createSkipTime( malId, episodeNumber, { skipType: skipType, startTime: timeStart, endTime: timeEnd, episodeLength: videoLength } ).then(data => { form.querySelector('button[type="submit"]').disabled = false; form.reset(); GM_setValue('SubmittedSkip', GM_getValue('SubmittedSkip', 0) + 1); video.dispatchEvent(new Event('loadskipdata')); }).catch(error => console.error(error)); }; }; video.addEventListener('loadskipdata', loadskipdata); if (video.readyState > 0) { loadskipdata(); video.play(); } else video.onloadedmetadata = loadskipdata; // Show/Hide skipButton base of currentTime video.ontimeupdate = () => { content.querySelectorAll('.aws-btn:not(.manual)').forEach(element => { const startTime = element.getAttribute('data-time-start'); const endTime = element.getAttribute('data-time-end'); const active = video.currentTime >= startTime && video.currentTime < endTime; element.classList.toggle('active', active); }); if (video.currentTime > video.duration / 3) content.querySelector('#manual-skip')?.remove(); }; video.oncanplay = function () { this.play(); this.focus(); this.oncanplay = null; // only at video start }; video.onmouseenter = function () { if (!this.paused && !this.ended) this.focus(); }; content.onkeydown = e => { if (e.keyCode === 13) { const btn = content.querySelector('.aws-btn.active'); if (btn) { e.preventDefault(); btn.click(); } } }; }); // end iframe.addEventListener }); // end NodeCreationObserver.onCreation injectMeta('AnimeWorldSkipper'); //* Functions function createSkipButton(options = {}) { return createElement('button', { text: options.text ?? 'Salta', class: options.class ?? 'aws-btn', ...options }); } function createElement(tagName, options) { const element = document.createElement(tagName); element.textContent = options?.text; element.id = options?.id ?? ''; element.classList = options?.class ?? ''; element.title = options?.title ?? ''; element.value = options?.value ?? ''; element.style = options?.style ?? ''; element.innerHTML = options?.html ?? element.innerHTML; return element; } function injectHTML(html, parent = document.body) { const tag = document.createElement('div'); tag.innerHTML = html; return parent.appendChild(tag.childElementCount === 1 ? tag.firstElementChild : tag); } function injectStyle(style, parent = document.head) { const tag = document.createElement('style'); tag.innerText = style; parent.appendChild(tag); } function injectMeta(name, content = '', parent = document.head) { const tag = document.createElement('meta'); tag.name = name; tag.content = content; parent.appendChild(tag); } })();