// ==UserScript==
// @name WCO Auto-Play Next/Random with Dice (v1.4 — wcostream fixed)
// @namespace http://tampermonkey.net/
// @author P3k
// @version 1.4
// @license GNU GENERAL PUBLIC LICENSE
// @description Original panel/logic; fixed autoplay & random for wcostream.tv; resilient to ads
// @match *://wcostream.tv/*
// @match *://www.wcostream.tv/*
// @match *://embed.wcostream.com/*
// @grant none
// @run-at document-start
// ==/UserScript==
(function () {
'use strict';
// -----------------------------------------------------------------------------
// 1) STUBS (unchanged)
// -----------------------------------------------------------------------------
window.downloadJSAtOnload = () => {};
window.sub = () => {};
// -----------------------------------------------------------------------------
// 2) IFRAME CONTEXT (now ALWAYS hooks "ended"; autoplay click only if requested)
// -----------------------------------------------------------------------------
if (window.top !== window.self) {
// Always hook ended so parent can decide next/random regardless of autoplay click
const endedObs = new MutationObserver((_, obs) => {
const v = document.querySelector('video');
if (v) {
try { v.muted = false; } catch {}
v.addEventListener('ended', () => {
window.parent.postMessage({ type: 'WCO_VIDEO_ENDED' }, '*');
});
obs.disconnect();
}
});
endedObs.observe(document.documentElement, { childList: true, subtree: true });
// Only auto-click big play when parent tells us autoplay is enabled
window.addEventListener('message', (event) => {
if (event.data?.type === 'WCO_AUTOPLAY_PREF' && event.data.autoplay) {
const playObs = new MutationObserver((_, obs) => {
const btn = document.querySelector('button.vjs-big-play-button');
if (btn) { btn.click(); obs.disconnect(); }
});
playObs.observe(document.documentElement, { childList: true, subtree: true });
}
});
return;
}
// -----------------------------------------------------------------------------
// 3) PARENT PAGE CONTEXT
// -----------------------------------------------------------------------------
const episodes = []; // random + fallback next use this
function abs(href, base) { try { return new URL(href, base || location.origin).href; } catch { return null; } }
function samePath(a, b) {
try {
const A = new URL(a, location.origin);
const B = new URL(b, location.origin);
return A.pathname.replace(/\/+$/,'') === B.pathname.replace(/\/+$/,'');
} catch { return a === b; }
}
function pushEpisode(url, title) {
if (!url) return;
if (!episodes.some(e => samePath(e.url, url))) episodes.push({ url, title: title || url });
}
// Collect from THIS page immediately (sidebar + main list if present)
function collectFromCurrentPage(doc = document, base) {
// Main list (if on the show page like the HTML you pasted)
doc.querySelectorAll('#catlist-listview li a, #catlist-listview a.sonra').forEach(a => {
pushEpisode(abs(a.getAttribute('href'), base), a.textContent.trim());
});
// Sidebar mirror (present on episode pages)
doc.querySelectorAll('#sidebar .menustyle a, #sidebar .menusttyle a').forEach(a => {
pushEpisode(abs(a.getAttribute('href'), base), a.textContent.trim());
});
}
// Also try fetching the canonical show page to be thorough
function deriveSlugFromLocation() {
const seg = location.pathname.replace(/\/+$/,'').split('/')[1] || '';
const m = seg.match(/^(.+?)-(?:season|episode|special|ova)\b/i);
return (m && m[1]) ? m[1] : seg || null;
}
function buildShowCandidates() {
const out = new Set();
document.querySelectorAll('a[href^="/playlist-cat/"]').forEach(a => {
const slug = (a.getAttribute('href') || '').split('/').pop();
if (slug) { out.add(abs('/' + slug)); out.add(abs('/anime/' + slug)); }
});
const slug = deriveSlugFromLocation();
if (slug) { out.add(abs('/' + slug)); out.add(abs('/anime/' + slug)); }
return Array.from(out);
}
function parseShowHTML(html, base) {
const doc = new DOMParser().parseFromString(html, 'text/html');
collectFromCurrentPage(doc, base);
}
async function loadShowEpisodes() {
const candidates = buildShowCandidates();
for (const url of candidates) {
try {
const res = await fetch(url, { credentials: 'include' });
if (!res.ok) continue;
const html = await res.text();
if (!/#catlist-listview/.test(html)) continue; // must look like show page
parseShowHTML(html, url);
if (episodes.length) return true;
} catch {}
}
return false;
}
// -----------------------------------------------------------------------------
// 4) ORIGINAL PANEL (exact layout; layered above ads)
// -----------------------------------------------------------------------------
function makePanel(iframe) {
const storedNext = localStorage.getItem('wco-auto-next');
const storedRand = localStorage.getItem('wco-auto-random');
const storedAuto = localStorage.getItem('wco-auto-play');
const defaultNext = (storedNext === null && storedRand === null) ? true : (storedNext === 'true');
const defaultRand = (storedRand === 'true');
const defaultAuto = (storedAuto === null) ? true : (storedAuto === 'true');
const outer = document.createElement('div');
outer.style.cssText = `
display:flex;flex-direction:row;justify-content:center;gap:16px;margin:12px auto;
font-family:sans-serif;font-size:14px;background:transparent;
position:relative;z-index:2147483647;pointer-events:auto;
`;
function makeToggle(id, labelText, isChecked) {
const label = document.createElement('label');
label.style.cssText = 'display:flex;align-items:center;cursor:pointer;white-space:nowrap;';
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.id = id;
checkbox.checked = isChecked;
checkbox.style.marginRight = '6px';
label.append(checkbox, document.createTextNode(labelText));
return { label, checkbox };
}
// Section 1: Next Episode
const section1 = document.createElement('div');
section1.style.cssText = 'border:1px solid #aaa;padding:8px;background:#fff;min-width:220px;';
const title1 = document.createElement('div');
title1.textContent = 'Next Episode';
title1.style.cssText = 'font-weight:bold;margin-bottom:6px;text-align:center;';
const toggles = document.createElement('div');
toggles.style.cssText = 'display:flex;flex-direction:row;gap:16px;justify-content:center;';
const nextToggle = makeToggle('wco-auto-next', 'Sequential', defaultNext);
const randToggle = makeToggle('wco-auto-random', 'Random', defaultRand);
nextToggle.checkbox.addEventListener('change', () => {
if (nextToggle.checkbox.checked) randToggle.checkbox.checked = false;
savePrefs();
});
randToggle.checkbox.addEventListener('change', () => {
if (randToggle.checkbox.checked) nextToggle.checkbox.checked = false;
savePrefs();
});
toggles.append(nextToggle.label, randToggle.label);
section1.append(title1, toggles);
// Dice
const diceButton = document.createElement('button');
diceButton.innerHTML = '🎲';
diceButton.style.cssText = `
font-size:32px;width:50px;height:50px;border:1px solid #aaa;background:#fff;
cursor:pointer;border-radius:8px;display:flex;align-items:center;justify-content:center;transition:transform .2s;
`;
diceButton.title = 'Play Random Episode';
diceButton.addEventListener('mouseenter', () => { diceButton.style.transform = 'scale(1.1)'; });
diceButton.addEventListener('mouseleave', () => { diceButton.style.transform = 'scale(1)'; });
diceButton.addEventListener('click', () => {
if (episodes.length) {
const pick = episodes[Math.floor(Math.random() * episodes.length)];
if (pick?.url) location.href = pick.url;
} else {
console.log('WCO: Episode list not loaded yet');
}
});
// Section 2: Auto Play
const section2 = document.createElement('div');
section2.style.cssText = 'border:1px solid #aaa;padding:8px;background:#fff;min-width:140px;';
const title2 = document.createElement('div');
title2.textContent = 'Auto Play';
title2.style.cssText = 'font-weight:bold;margin-bottom:6px;text-align:center;';
const box2 = document.createElement('div');
box2.style.cssText = 'display:flex;justify-content:center;';
const autoToggle = makeToggle('wco-auto-play', 'Enabled', defaultAuto);
autoToggle.checkbox.addEventListener('change', () => {
savePrefs();
postAutoplay(); // update iframe immediately
});
box2.appendChild(autoToggle.label);
section2.append(title2, box2);
// Assemble in original order: AutoPlay | 🎲 | Next
outer.append(section2, diceButton, section1);
iframe.parentNode.insertBefore(outer, iframe.nextSibling);
// Persist
function savePrefs() {
localStorage.setItem('wco-auto-next', nextToggle.checkbox.checked);
localStorage.setItem('wco-auto-random', randToggle.checkbox.checked);
localStorage.setItem('wco-auto-play', autoToggle.checkbox.checked);
}
// Push autoplay state to iframe (immediately + a few retries in case it was already loaded)
function postAutoplay() {
try {
iframe.contentWindow.postMessage({ type: 'WCO_AUTOPLAY_PREF', autoplay: autoToggle.checkbox.checked }, '*');
} catch {}
}
// Send now, on load, and ping a few times to guarantee the embed sees it
postAutoplay();
iframe.addEventListener('load', postAutoplay);
const ping = setInterval(postAutoplay, 1500);
setTimeout(() => clearInterval(ping), 8000); // stop after a few tries
savePrefs();
}
// -----------------------------------------------------------------------------
// 5) Handle video ended -> next/random (same behavior; better matching)
// -----------------------------------------------------------------------------
window.addEventListener('message', (event) => {
if (event.data?.type !== 'WCO_VIDEO_ENDED') return;
const rand = localStorage.getItem('wco-auto-random') === 'true';
const next = (localStorage.getItem('wco-auto-next') === 'true') ||
(localStorage.getItem('wco-auto-next') === null && !rand); // default Next on first run
if (rand && episodes.length) {
const pick = episodes[Math.floor(Math.random() * episodes.length)];
if (pick?.url) location.href = pick.url;
return;
}
if (next) {
const rel = document.querySelector('a[rel="next"]');
if (rel?.href) { location.href = rel.href; return; }
// Fallback: use our list (order on site is newest->oldest; "next" goes forward in list)
if (episodes.length) {
const idx = episodes.findIndex(e => samePath(e.url, location.href));
if (idx >= 0 && idx + 1 < episodes.length) {
location.href = episodes[idx + 1].url;
}
}
}
});
// -----------------------------------------------------------------------------
// 6) Install panel when the player iframe shows up (ads-safe), and build episode list
// -----------------------------------------------------------------------------
function isPlayerIframe(node) {
if (!(node instanceof HTMLIFrameElement)) return false;
const src = (node.getAttribute('src') || '').toLowerCase();
return src.includes('embed.wcostream.com') || src.includes('/inc/embed/video-js.php');
}
function findPlayerIframe() {
return Array.from(document.querySelectorAll('iframe')).find(isPlayerIframe) || null;
}
function installWhenIframeAppears() {
const fr = findPlayerIframe();
if (fr) { makePanel(fr); return; }
const mo = new MutationObserver(() => {
const i = findPlayerIframe();
if (i) { makePanel(i); mo.disconnect(); }
});
mo.observe(document.documentElement, { childList: true, subtree: true });
}
// Build episodes immediately from this page, then try fetching the show page too
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
collectFromCurrentPage(); // sidebar list available on episode pages
loadShowEpisodes(); // enhances the list if needed
});
} else {
collectFromCurrentPage();
loadShowEpisodes();
}
if (document.readyState === 'complete') installWhenIframeAppears();
else window.addEventListener('load', installWhenIframeAppears);
})();