您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Works on both wco.tv and wcostream.tv layouts. Pinned, centred panel above the player.
// ==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 wco.tv and wcostream.tv layouts. Pinned, centred panel above the player. // @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(); })();