您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
Works on both www.wco.tv and wcostream.tv layouts. Pinned, centered panel above the player. Random now waits for episode list and avoids current URL.
当前为
// ==UserScript== // @name WCO/WCOStream Auto-Play Next/Random (v1.5) // @namespace http://tampermonkey.net/ // @version 1.5 // @license GNU GENERAL PUBLIC LICENSE // @description Works on both www.wco.tv and wcostream.tv layouts. Pinned, centered panel above the player. Random now waits for episode list and avoids current URL. // @match *://www.wco.tv/* // @match *://wco.tv/* // @match *://www.wcostream.tv/* // @match *://wcostream.tv/* // @match *://embed.wcostream.com/* // @grant none // @run-at document-start // @SOME UBLOCK FILTERS YOU SHOULD ADD: // @!Watch Cartoons Online https://www.wcostream.com // @wcostream.com##+js(rmnt, script, /embed.html) // @wco.tv##+js(rmnt, script, /embed.html) // @wcostream.com##.announcement-backdrop, #announcement // @wco.tv##.announcement-backdrop, #announcement // @||embed.wcostream.com/inc/embed/index.php?file=$frame,uritransform=/index/video-js/ // ==/UserScript== (() => { 'use strict'; const MAX_ATTEMPTS = 120; const RETRY_MS = 150; const LS_NEXT = 'wco-auto-next'; const LS_RANDOM = 'wco-auto-random'; const sleep = (ms) => new Promise(r => setTimeout(r, ms)); const q = (sel, root=document) => root.querySelector(sel); const qa = (sel, root=document) => Array.from(root.querySelectorAll(sel)); const visible = (el) => { if (!el) return false; const s = getComputedStyle(el); if (s.display === 'none' || s.visibility === 'hidden' || +s.opacity === 0) return false; const r = el.getBoundingClientRect(); return r.width > 0 && r.height > 0; }; const isPlaying = (v) => v && !v.paused && !v.ended && v.readyState >= 2; const norm = (u) => { try { return new URL(u, location.href).href.replace(/\/+$/,''); } catch { return (u||'').replace(/\/+$/,''); } }; const sameURL = (a,b) => norm(a) === norm(b); const goTo = (href) => { if (href) location.href = href; }; // ---------- unmute helpers ---------- const hardUnmute = (v) => { if (!v) return; try { v.muted = false; } catch {} try { v.volume = Math.max(0.7, v.volume || 0.7); } catch {} }; const unmuteViaVJS = (container) => { const vjs = window.videojs || window.videoJS || window.videoJs; if (!vjs) return false; try { let player = null; if (container?.id) { try { player = vjs(container.id); } catch {} } if (!player && typeof vjs.getPlayers === 'function') { const reg = vjs.getPlayers(); const ids = reg ? Object.keys(reg) : []; if (ids.length) player = reg[ids[0]]; } if (player) { try { player.muted(false); } catch {} try { player.volume(0.7); } catch {} return true; } } catch {} return false; }; const attachOneTimeUnmuteHandlers = (cb) => { let done = false; const fire = () => { if (done) return; done = true; off(); try { cb(); } catch {} }; const types = ['pointerdown','mousedown','touchstart','keydown','click']; const on = () => types.forEach(t => window.addEventListener(t, fire, { passive: true, once: true, capture: true })); const off = () => types.forEach(t => window.removeEventListener(t, fire, { capture: true })); on(); }; // ---------- IFRAME (embed.wcostream.com) ---------- if (location.hostname.replace(/^www\./,'') === 'embed.wcostream.com') { let started = false; const start = async () => { if (started) return; let v = null; for (let i=0; i<100 && !v; i++) { v = q('video'); if (!v) await sleep(50); } if (!v) return; try { v.setAttribute('playsinline',''); v.setAttribute('webkit-playsinline',''); v.autoplay = true; v.muted = true; if (v.getAttribute('preload') === 'none') v.setAttribute('preload','metadata'); } catch {} const markPlaying = () => { started = true; v.removeEventListener('playing', markPlaying); }; v.addEventListener('playing', markPlaying, { once: true }); if (!v.__wcoEndedHooked) { v.addEventListener('ended', () => { try { parent.postMessage({ type: 'WCO_VIDEO_ENDED' }, '*'); } catch {} }, { once: true }); v.__wcoEndedHooked = true; } const tryImmediateUnmute = () => { hardUnmute(v); unmuteViaVJS(document); }; v.addEventListener('playing', () => setTimeout(tryImmediateUnmute, 50), { once: true }); attachOneTimeUnmuteHandlers(() => { tryImmediateUnmute(); try { v.play?.(); } catch {} }); const clickBigPlay = () => { const btn = q('.vjs-big-play-button'); if (btn && visible(btn)) { try { btn.click(); } catch {} } }; for (let i=0; i<MAX_ATTEMPTS && !isPlaying(v); i++) { try { await v.play(); } catch {} if (!isPlaying(v)) clickBigPlay(); await sleep(RETRY_MS); } }; const boot = () => { start(); new MutationObserver(() => { if (!started) start(); }) .observe(document.documentElement, { childList: true, subtree: true }); document.addEventListener('visibilitychange', () => { if (!document.hidden && !started) start(); }); }; if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', boot, { once: true }); else boot(); return; } // ---------- PARENT ---------- const episodes = []; let episodesPromise = null; // find candidate links on-page (wcostream sidebar "Episode List", and any obvious episode anchors) const scrapeEpisodesFromPage = () => { let added = 0; // 1) wcostream.tv sidebar Episode List qa('#sidebar .menu .menustyle ul li a[href]').forEach(a => { const url = a.href; if (url && !episodes.some(e => sameURL(e.url, url))) { episodes.push({ url, title: (a.textContent||'').trim() }); added++; } }); // 2) any bookmark episode links on-page qa('a[rel="bookmark"][href]').forEach(a => { const url = a.href; if (url && /\/(episode|season|special)/i.test(url) && !episodes.some(e => sameURL(e.url,url))) { episodes.push({ url, title: (a.textContent||'').trim() }); added++; } }); // 3) older series sidebar used on some pages qa('#sidebar_right3 .cat-eps a[href]').forEach(a => { const url = a.href; if (url && !episodes.some(e => sameURL(e.url, url))) { episodes.push({ url, title: (a.textContent||'').trim() }); added++; } }); return added; }; // try to fetch the series page and scrape its sidebar const fetchEpisodesFromSeriesPage = () => { if (episodesPromise) return episodesPromise; const categoryLink = q('a[rel="category tag"][href*="/anime/"]') || q('a[href*="/anime/"][rel="category tag"]'); if (!categoryLink) return Promise.resolve(0); episodesPromise = fetch(categoryLink.href) .then(r => r.text()) .then(html => { const doc = new DOMParser().parseFromString(html, 'text/html'); let added = 0; doc.querySelectorAll('#sidebar .menu .menustyle ul li a[href]').forEach(a => { const url = a.href; if (url && !episodes.some(e => sameURL(e.url,url))) { episodes.push({ url, title: (a.textContent||'').trim() }); added++; } }); doc.querySelectorAll('#sidebar_right3 .cat-eps a[href]').forEach(a => { const url = a.href; if (url && !episodes.some(e => sameURL(e.url,url))) { episodes.push({ url, title: (a.textContent||'').trim() }); added++; } }); return added; }) .catch(() => 0); return episodesPromise; }; // wait helper for episodes to be available (tries scraping and fetching) const ensureEpisodesReady = async (timeoutMs = 4000) => { // first, scrape what we can synchronously let count = scrapeEpisodesFromPage(); // kick off fetch if still light if (episodes.length < 2) fetchEpisodesFromSeriesPage(); const start = Date.now(); while (episodes.length < 1 && Date.now() - start < timeoutMs) { await sleep(100); // try scraping again in case sidebar arrived late count += scrapeEpisodesFromPage(); } return episodes.length; }; const pickRandomDifferentFromCurrent = () => { const cur = norm(location.href); const pool = episodes.filter(e => !sameURL(e.url, cur)); if (!pool.length) return null; return pool[Math.floor(Math.random() * pool.length)]; }; // smarter sequential for both sites const goNext = () => { let a = q('a[rel="next"]'); if (a?.href) return goTo(a.href); a = q('a[rel="prev"]'); if (a?.href) return goTo(a.href); if (episodes.length) { const cur = location.href; const idx = episodes.findIndex(e => sameURL(e.url, cur)); const target = (idx >= 0 && idx+1 < episodes.length) ? episodes[idx+1] : episodes[0]; if (target?.url && !sameURL(target.url, cur)) return goTo(target.url); } }; const ensureIframeAutoplayAllowed = () => { qa('iframe#cizgi-js-0, .pcat-jwplayer iframe, iframe[src*="embed.wcostream.com"], iframe[data-type="wco-embed"]').forEach(ifr => { try { const cur = (ifr.getAttribute('allow')||'').toLowerCase(); if (!cur.includes('autoplay')) ifr.setAttribute('allow', `${cur} autoplay; fullscreen`.trim()); } catch {} }); }; const wireParentUnmuteForwarder = () => { attachOneTimeUnmuteHandlers(() => { qa('iframe#cizgi-js-0, .pcat-jwplayer iframe, iframe[src*="embed.wcostream.com"], iframe[data-type="wco-embed"]').forEach(ifr => { try { ifr.contentWindow?.postMessage({ type: 'WCO_UNMUTE' }, '*'); } catch {} }); tryInlineUnmute(); }); }; let inlineStarted = false; const tryInlineUnmute = () => { const container = qa('#video-js,.video-js').find(visible) || q('#video-js,.video-js') || document; const v = q('video.vjs-tech', container) || q('.video-js video', container) || q('video'); if (v) { hardUnmute(v); unmuteViaVJS(container); try { v.play?.(); } catch {} } }; const startInlineVideoJS = async () => { if (inlineStarted) return true; const container = qa('#video-js,.video-js').find(visible) || q('#video-js,.video-js') || document; const vids = [...qa('video.vjs-tech', container), ...qa('.video-js video', container), ...qa('video')]; const v = vids.find(visible) || vids[0]; if (!v) return false; if (!v.__wcoEndedHooked) { v.addEventListener('ended', () => { handleEnded(); }, { once: true }); v.__wcoEndedHooked = true; } try { v.setAttribute('playsinline',''); v.setAttribute('webkit-playsinline',''); v.autoplay = true; v.muted = true; if (v.getAttribute('preload') === 'none') v.setAttribute('preload','metadata'); } catch {} const mark = () => { inlineStarted = true; v.removeEventListener('playing', mark); }; v.addEventListener('playing', () => { mark(); setTimeout(() => { hardUnmute(v); unmuteViaVJS(container); }, 50); }, { once: true }); const clickBigPlay = () => { const btn = container.querySelector('.vjs-big-play-button'); if (btn && visible(btn)) { try { btn.click(); } catch {} } }; let usedAPI = false; const vjs = window.videojs || window.videoJS || window.videoJs; if (typeof vjs === 'function') { try { let player = null; if (container.id) { try { player = vjs(container.id); } catch {} } if (!player && typeof vjs.getPlayers === 'function') { const reg = vjs.getPlayers(); const ids = reg ? Object.keys(reg) : []; if (ids.length) player = reg[ids[0]]; } if (player) { usedAPI = true; player.ready(async () => { try { player.muted(true); } catch {} try { player.autoplay(true); } catch {} for (let i=0; i<MAX_ATTEMPTS && !isPlaying(v); i++) { try { await player.play(); } catch {} if (!isPlaying(v)) clickBigPlay(); await sleep(RETRY_MS); } }); } } catch {} } if (!usedAPI) { for (let i=0; i<MAX_ATTEMPTS && !isPlaying(v); i++) { try { await v.play(); } catch {} if (!isPlaying(v)) clickBigPlay(); await sleep(RETRY_MS); } } return inlineStarted || isPlaying(v); }; // --------- prefs + ended decision ---------- const nextOnDefault = () => { const n = localStorage.getItem(LS_NEXT); const r = localStorage.getItem(LS_RANDOM); return (n === null && r === null) ? true : (n === 'true'); }; const randOnDefault = () => localStorage.getItem(LS_RANDOM) === 'true'; const setPrefs = (nextOn, randOn) => { localStorage.setItem(LS_NEXT, String(!!nextOn)); localStorage.setItem(LS_RANDOM, String(!!randOn)); }; const handleEnded = () => { const randOn = localStorage.getItem(LS_RANDOM) === 'true'; const nextOn = (localStorage.getItem(LS_NEXT) === 'true') || (localStorage.getItem(LS_NEXT) === null && localStorage.getItem(LS_RANDOM) === null); if (randOn && episodes.length) { const ep = pickRandomDifferentFromCurrent(); if (ep?.url) { location.href = ep.url; return; } } if (nextOn) { goNext(); return; } goNext(); }; // --------- UI (centered card) ---------- const injectCSS = () => { if (q('#wco-inline-panel-css')) return; const css = document.createElement('style'); css.id = 'wco-inline-panel-css'; css.textContent = ` #wco-inline-panel{ display:block; width:max-content; margin:10px auto 8px; background:#1e1f22; color:#fff; border:1px solid #2d2e33; border-radius:6px; padding:10px 12px; box-shadow:0 2px 8px rgba(0,0,0,.35); font:14px/1.25 system-ui,-apple-system,Segoe UI,Roboto,sans-serif; z-index:2147483647; } #wco-inline-panel .wco-title{font-weight:600;margin-bottom:6px;color:#e6e6e6;text-align:center} #wco-inline-panel .wco-row{display:flex;align-items:center;gap:16px;flex-wrap:wrap;justify-content:center} #wco-inline-panel input[type="checkbox"]{vertical-align:-2px;margin-right:6px} #wco-inline-panel .wco-dice{ font-size:18px; width:36px; height:30px; border:1px solid #444; border-radius:6px; background:#2a2b30; color:#fff; cursor:pointer; } #wco-inline-panel .wco-dice:disabled{opacity:.5;cursor:not-allowed} #wco-inline-panel .wco-dice:active{transform:scale(.98)} `; document.head.appendChild(css); }; const findAnchor = () => { let el = q('div[id^="hide-cizgi-video-"]'); if (el) return el; el = qa('iframe[src*="embed.wcostream.com"], iframe[data-type="wco-embed"]').find(visible); if (el) return el; el = qa('#video-js,.video-js, video').find(visible); if (el) return el; return qa('iframe').find(visible) || null; }; const buildInlinePanel = () => { const anchor = findAnchor(); if (!anchor || !anchor.parentElement) return; let panel = q('#wco-inline-panel'); if (!panel) { panel = document.createElement('div'); panel.id = 'wco-inline-panel'; const title = document.createElement('div'); title.className = 'wco-title'; title.textContent = 'Episode Advance'; panel.appendChild(title); const row = document.createElement('div'); row.className = 'wco-row'; panel.appendChild(row); const mkToggle = (id, label, checked) => { const wrap = document.createElement('label'); const cb = document.createElement('input'); cb.type = 'checkbox'; cb.id = id; cb.checked = !!checked; wrap.appendChild(cb); wrap.appendChild(document.createTextNode(' ' + label)); return {wrap, cb}; }; const nextT = mkToggle('wco-next', 'Sequential', nextOnDefault()); const randT = mkToggle('wco-rand', 'Random', randOnDefault()); row.appendChild(nextT.wrap); row.appendChild(randT.wrap); const dice = document.createElement('button'); dice.type = 'button'; dice.className = 'wco-dice'; dice.textContent = '🎲'; dice.title = 'Play a random episode now'; row.appendChild(dice); const sync = () => setPrefs(nextT.cb.checked, randT.cb.checked); nextT.cb.addEventListener('change', () => { if (nextT.cb.checked) randT.cb.checked = false; sync(); }); randT.cb.addEventListener('change', () => { if (randT.cb.checked) nextT.cb.checked = false; sync(); }); // 🔧 Robust RANDOM click dice.addEventListener('click', async () => { try { dice.disabled = true; // make sure we have something to choose from await ensureEpisodesReady(4000); // if still nothing, one last synchronous scrape (DOM might have changed) if (!episodes.length) scrapeEpisodesFromPage(); const ep = pickRandomDifferentFromCurrent(); if (ep?.url) location.href = ep.url; // if only one item and it's the current page, do nothing (no change) } finally { dice.disabled = false; } }); } if (anchor.previousSibling !== panel) anchor.parentElement.insertBefore(panel, anchor); }; // ---- early watcher (function startEarlyPanelWatcher(){ injectCSS(); let fastTries = 0; const fastLoop = () => { buildInlinePanel(); if (q('#wco-inline-panel')) return; fastTries++; if (fastTries < 300) setTimeout(fastLoop, 50); }; fastLoop(); const mo = new MutationObserver(() => { if (!q('#wco-inline-panel')) buildInlinePanel(); else { const anchor = findAnchor(); if (anchor && q('#wco-inline-panel')?.nextSibling !== anchor) { try { anchor.parentElement.insertBefore(q('#wco-inline-panel'), anchor); } catch {} } } }); mo.observe(document.documentElement, { childList: true, subtree: true }); })(); // ---- boot const bootParent = async () => { window.addEventListener('message', (e) => { if (e?.data?.type === 'WCO_VIDEO_ENDED') handleEnded(); }); ensureIframeAutoplayAllowed(); wireParentUnmuteForwarder(); // pre-warm episodes list in the background (non-blocking) scrapeEpisodesFromPage(); fetchEpisodesFromSeriesPage(); for (let i=0; i<Math.ceil(MAX_ATTEMPTS*1.2); i++) { ensureIframeAutoplayAllowed(); const ok = await startInlineVideoJS(); if (ok) break; await sleep(RETRY_MS); } document.addEventListener('visibilitychange', () => { if (!document.hidden && !inlineStarted) startInlineVideoJS(); }); }; if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', bootParent, { once: true }); } else bootParent(); })();