// ==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();
})();