// ==UserScript==
// @name SubsPlease Episode 01 Highlighter ✨ (PRECISE)
// @namespace http://tampermonkey.net/
// @version 9.0
// @description Color-coded episode highlighting (different colors per show) with click-to-scroll! 🎯🌈
// @author Zotikus1001
// @license MIT
// @match https://subsplease.org/*
// @grant none
// @run-at document-end
// ==/UserScript==
(function() {
'use strict';
// 🚫 Only run on main page - exit immediately if on show pages
const currentURL = window.location.href;
if (currentURL.includes('/shows/') ||
currentURL.includes('/schedule/') ||
currentURL.includes('/xdcc/') ||
!currentURL.match(/https:\/\/subsplease\.org\/?$/)) {
return; // Exit completely - no initialization, no elements, nothing
}
// 🎨 Dark-mode-friendly styles for highlighting episode 01s
const styles = `
.episode-01-highlight {
color: #000 !important;
border-radius: 6px !important;
border: 2px solid #00ffff !important;
transform: scale(1.02) !important;
transition: all 0.3s ease !important;
position: relative !important;
z-index: 1000 !important;
margin: 2px !important;
font-weight: bold !important;
animation: neon-pulse 2s ease-in-out infinite alternate !important;
}
.episode-01-highlight:hover {
transform: scale(1.05) !important;
background: #00ffff !important;
box-shadow: 0 0 25px rgba(0, 255, 255, 1), inset 0 0 15px rgba(0, 255, 136, 0.5) !important;
border-color: #00ff88 !important;
}
.episode-01-highlight::before {
content: "🆕 NEW!" !important;
position: absolute !important;
top: -12px !important;
right: -8px !important;
background: #ff4757 !important;
color: white !important;
padding: 2px 6px !important;
border-radius: 8px !important;
font-size: 9px !important;
font-weight: bold !important;
animation: bounce 1.5s infinite !important;
box-shadow: 0 0 10px rgba(255, 71, 87, 0.8) !important;
z-index: 1001 !important;
white-space: nowrap !important;
border: 1px solid #fff !important;
}
/* 📺 Episode 02 Highlight - Dimmer, different colors per show */
.episode-02-highlight {
border-radius: 6px !important;
border: 1px solid currentColor !important;
box-shadow: 0 0 8px currentColor !important;
transform: scale(1.01) !important;
transition: all 0.3s ease !important;
position: relative !important;
z-index: 999 !important;
margin: 2px !important;
font-weight: bold !important;
opacity: 0.7 !important;
color: #000 !important;
}
/* 📺 Episode 03 Highlight - Even Dimmer */
.episode-03-highlight {
border-radius: 4px !important;
border: 1px solid currentColor !important;
box-shadow: 0 0 5px currentColor !important;
transform: scale(1.005) !important;
transition: all 0.3s ease !important;
position: relative !important;
z-index: 998 !important;
margin: 2px !important;
font-weight: normal !important;
opacity: 0.5 !important;
color: #000 !important;
}
/* 📺 Episode 04+ Highlight - Very Subtle */
.episode-other-highlight {
border-radius: 3px !important;
border: 1px solid currentColor !important;
box-shadow: 0 0 3px currentColor !important;
transition: all 0.3s ease !important;
position: relative !important;
z-index: 997 !important;
margin: 2px !important;
font-weight: normal !important;
opacity: 0.3 !important;
color: #000 !important;
}
.episode-02-highlight:hover,
.episode-03-highlight:hover,
.episode-other-highlight:hover {
transform: scale(1.02) !important;
filter: brightness(1.1) !important;
}
@keyframes neon-pulse {
0% {
box-shadow: 0 0 15px rgba(0, 255, 136, 0.8), inset 0 0 10px rgba(0, 255, 255, 0.3);
background: #00ff88;
}
100% {
box-shadow: 0 0 25px rgba(0, 255, 136, 1), inset 0 0 15px rgba(0, 255, 255, 0.5);
background: #00ffaa;
}
}
@keyframes bounce {
0%, 100% { transform: translateY(0px) rotate(0deg); }
25% { transform: translateY(-2px) rotate(1deg); }
50% { transform: translateY(-4px) rotate(0deg); }
75% { transform: translateY(-2px) rotate(-1deg); }
}
.episode-counter {
position: fixed !important;
top: 20px !important;
right: 20px !important;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
color: white !important;
padding: 10px 15px !important;
border-radius: 25px !important;
font-weight: bold !important;
box-shadow: 0 4px 15px rgba(0,0,0,0.2) !important;
z-index: 10000 !important;
animation: counter-glow 2s infinite alternate !important;
font-family: Arial, sans-serif !important;
}
@keyframes counter-glow {
0% { box-shadow: 0 4px 15px rgba(0,0,0,0.2); }
100% { box-shadow: 0 6px 25px rgba(102, 126, 234, 0.4); }
}
.found-list {
position: fixed !important;
top: 70px !important;
right: 20px !important;
width: 400px !important;
max-height: 500px !important;
background: rgba(0,0,0,0.9) !important;
color: white !important;
padding: 15px !important;
border-radius: 10px !important;
font-size: 13px !important;
overflow-y: auto !important;
z-index: 10001 !important;
font-family: Arial, sans-serif !important;
border: 1px solid rgba(0, 255, 136, 0.3) !important;
}
.found-list h3 {
margin: 0 0 12px 0 !important;
color: #4ecdc4 !important;
font-size: 14px !important;
}
.found-entry {
margin-bottom: 5px !important;
padding: 8px 10px !important;
background: rgba(255,255,255,0.1) !important;
border-radius: 5px !important;
font-size: 12px !important;
cursor: pointer !important;
transition: all 0.2s ease !important;
line-height: 1.3 !important;
}
.found-entry:hover {
background: rgba(0, 255, 136, 0.3) !important;
transform: translateX(3px) !important;
box-shadow: 0 2px 8px rgba(0, 255, 136, 0.4) !important;
}
.debug-toggle {
position: fixed !important;
bottom: 20px !important;
right: 20px !important;
background: rgba(0,0,0,0.8) !important;
color: white !important;
padding: 8px 15px !important;
border-radius: 20px !important;
font-size: 11px !important;
cursor: pointer !important;
z-index: 10002 !important;
font-family: Arial, sans-serif !important;
border: 1px solid rgba(255,255,255,0.3) !important;
transition: all 0.2s ease !important;
white-space: nowrap !important;
}
.debug-toggle:hover {
background: rgba(0, 255, 136, 0.2) !important;
border-color: #00ff88 !important;
transform: scale(1.05) !important;
}
.debug-toggle.debug-on {
background: rgba(255, 71, 87, 0.8) !important;
border-color: #ff4757 !important;
}
/* 🖼️ Fix image hover z-index to appear above episode names */
.preview-image,
[id*="preview"],
[class*="preview"],
[class*="tooltip"],
[class*="hover"],
img[src*="subsplease"],
img[src*="preview"],
.image-preview,
.hover-image {
z-index: 10003 !important;
}
`;
let scanCount = 0;
let foundEpisodes = [];
let showsWithEp01 = new Set(); // Track shows that have episode 01
let debugMode = false;
// 🎨 Function to generate unique colors for different shows
function getShowColor(showName) {
// Improved hash function for better distribution
let hash = 0;
for (let i = 0; i < showName.length; i++) {
const char = showName.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash; // Convert to 32-bit integer
}
// More distinct color palette - very different colors that are easy to distinguish
const colors = [
'#ff4757', // Red
'#3742fa', // Blue
'#2ed573', // Green
'#ffa502', // Orange
'#9c88ff', // Purple
'#00d2d3', // Cyan
'#ff6348', // Coral
'#dda0dd', // Plum
'#ffb8b8', // Pink
'#78e08f', // Light Green
'#60a3bc', // Steel Blue
'#f8b500', // Amber
'#e74c3c', // Crimson
'#8e44ad', // Dark Purple
'#16a085', // Teal
'#f39c12', // Dark Orange
'#2980b9', // Strong Blue
'#27ae60', // Emerald
'#e67e22', // Carrot
'#c0392b', // Dark Red
'#9b59b6', // Amethyst
'#1abc9c', // Turquoise
'#f1c40f', // Yellow
'#34495e', // Dark Gray
'#95a5a6', // Silver
];
// Add secondary hash to reduce collisions
let secondHash = 0;
for (let i = 0; i < showName.length; i++) {
secondHash += showName.charCodeAt(i) * (i + 1);
}
const combinedHash = Math.abs(hash + secondHash);
return colors[combinedHash % colors.length];
}
// 🔧 Debug logging function
function debugLog(...args) {
if (debugMode) {
console.log(...args);
}
}
// 🔧 Create debug toggle button
function createDebugToggle() {
let toggle = document.querySelector('.debug-toggle');
if (!toggle) {
toggle = document.createElement('div');
toggle.className = 'debug-toggle';
toggle.title = 'Toggle console logging for SubsPlease Episode 01 Highlighter script';
document.body.appendChild(toggle);
toggle.addEventListener('click', function() {
debugMode = !debugMode;
updateDebugToggle();
// Show user-friendly message about what was toggled
if (debugMode) {
console.log('🔧 SubsPlease Episode 01 Highlighter: Console logging ENABLED');
console.log('📝 You will now see detailed scan information in the console');
} else {
console.log('🔧 SubsPlease Episode 01 Highlighter: Console logging DISABLED');
console.log('🔇 Script will run silently (no console spam)');
}
});
}
updateDebugToggle();
}
// 🔧 Update debug toggle appearance
function updateDebugToggle() {
const toggle = document.querySelector('.debug-toggle');
if (toggle) {
toggle.textContent = debugMode ?
'🔧 Script Debug: ON' :
'🔧 Script Debug: OFF';
toggle.className = `debug-toggle ${debugMode ? 'debug-on' : ''}`;
toggle.title = debugMode ?
'Click to disable console logging for Episode 01 Highlighter' :
'Click to enable console logging for Episode 01 Highlighter';
}
}
// 📊 Function to update episode counter
function updateEpisodeCounter(count) {
let counter = document.querySelector('.episode-counter');
if (!counter) {
counter = document.createElement('div');
counter.className = 'episode-counter';
document.body.appendChild(counter);
}
// Only count episode 01s for the counter
const ep01Count = foundEpisodes.length;
counter.innerHTML = `🆕 Episode 01s: ${ep01Count}`;
}
// 📋 Function to update found episodes list
function updateFoundList(episodes) {
let list = document.querySelector('.found-list');
if (!list) {
list = document.createElement('div');
list.className = 'found-list';
document.body.appendChild(list);
}
const uniqueEpisodes = [...new Set(episodes)]; // Remove duplicates
list.innerHTML = `
<h3>🆕 Episode 01s Found</h3>
${uniqueEpisodes.length === 0 ? '<div>No episode 01s found yet...</div>' :
uniqueEpisodes.map((ep, index) => `<div class="found-entry" data-episode="${ep}">🎯 ${ep}</div>`).join('')}
`;
// 🎯 Add click handlers to scroll to episodes
const entries = list.querySelectorAll('.found-entry');
entries.forEach(entry => {
entry.addEventListener('click', function() {
const episodeName = this.getAttribute('data-episode');
scrollToEpisode(episodeName);
});
});
}
// 📍 Function to scroll to a specific episode
function scrollToEpisode(episodeName) {
debugLog(`🎯 Scrolling to episode: "${episodeName}"`);
// Find the episode link that matches the name (any highlight type)
const episodeLinks = document.querySelectorAll('#releases-table a.episode-01-highlight, #releases-table a.episode-02-highlight, #releases-table a.episode-03-highlight, #releases-table a.episode-other-highlight');
for (let link of episodeLinks) {
const linkText = link.textContent.trim();
if (linkText === episodeName) {
// Scroll to the element with smooth animation
link.scrollIntoView({
behavior: 'smooth',
block: 'center',
inline: 'nearest'
});
// Add temporary highlight flash
flashElement(link);
debugLog(`✅ Scrolled to: "${episodeName}"`);
return;
}
}
debugLog(`❌ Episode not found: "${episodeName}"`);
}
// ✨ Function to flash highlight an element
function flashElement(element) {
const originalBoxShadow = element.style.boxShadow;
// Add intense flash
element.style.boxShadow = '0 0 30px #fff, 0 0 60px #00ffff, 0 0 90px #00ff88';
element.style.transform = 'scale(1.1)';
// Return to normal after animation
setTimeout(() => {
element.style.boxShadow = originalBoxShadow;
element.style.transform = 'scale(1.02)';
}, 800);
}
// 🎯 ENHANCED function to highlight episode 01s AND subsequent episodes of same shows
function highlightEpisode01s() {
scanCount++;
debugLog(`🔍 === ENHANCED SCAN ${scanCount} ===`);
let newEpisode01Count = 0;
let newFoundEpisodes = [];
let newShowsWithEp01 = new Set();
// 🎯 Get all episode title links
const episodeTitleLinks = document.querySelectorAll('#releases-table a');
debugLog(`🔍 Found ${episodeTitleLinks.length} episode title links`);
// 📍 FIRST PASS: Find episode 01s and track show names
episodeTitleLinks.forEach((link, index) => {
const text = link.textContent || link.innerText || '';
// Skip quality links and short text
if (/^\d+p$/.test(text.trim()) ||
text.trim() === 'XDCC' ||
text.trim() === 'New!' ||
text.length < 5) {
return;
}
// Look for episode 01 patterns
const episode01Patterns = [
/—\s*01(?:\s|$)/i, // "— 01" with em dash (main pattern)
/-\s*01(?:\s|$)/i, // "- 01" with regular dash
/\s01(?:\s|$)/i, // " 01" at end or followed by space
];
const matchedPattern = episode01Patterns.find(pattern => pattern.test(text));
if (matchedPattern && !link.classList.contains('episode-01-highlight')) {
link.classList.add('episode-01-highlight');
newEpisode01Count++;
newFoundEpisodes.push(text.trim());
// Extract show name (everything before the episode number)
const showName = text.replace(/\s*[—-]\s*01.*$/i, '').trim();
newShowsWithEp01.add(showName);
showsWithEp01.add(showName);
// Apply show-specific color for episode 01 too
const showColor = getShowColor(showName);
link.style.setProperty('background-color', showColor, 'important');
link.style.setProperty('border-color', '#00ffff', 'important'); // Keep cyan border for episode 01s
link.style.setProperty('box-shadow', `0 0 15px ${showColor}, inset 0 0 10px rgba(0, 255, 255, 0.3)`, 'important');
debugLog(`🆕 FOUND Episode 01: "${text}" (Show: "${showName}", Color: ${showColor})`);
// Add click handler
link.addEventListener('click', function() {
debugLog('🆕 Clicked on Episode 01:', text);
});
}
});
// 📍 SECOND PASS: Find subsequent episodes for shows with episode 01
let subsequentEpisodesCount = 0;
episodeTitleLinks.forEach((link, index) => {
const text = link.textContent || link.innerText || '';
// Skip if already highlighted or is quality/short text
if (link.classList.contains('episode-01-highlight') ||
link.classList.contains('episode-02-highlight') ||
link.classList.contains('episode-03-highlight') ||
link.classList.contains('episode-other-highlight') ||
/^\d+p$/.test(text.trim()) ||
text.trim() === 'XDCC' ||
text.trim() === 'New!' ||
text.length < 5) {
return;
}
// Look for other episode patterns (02, 03, 04, etc.)
const otherEpisodePattern = /(.+?)\s*[—-]\s*(\d{2,3})(?:\s|$)/i;
const match = text.match(otherEpisodePattern);
if (match) {
const showName = match[1].trim();
const episodeNum = match[2];
// Check if this show has episode 01 present
if (showsWithEp01.has(showName)) {
const epNum = parseInt(episodeNum);
let highlightClass = '';
if (epNum === 2) {
highlightClass = 'episode-02-highlight';
} else if (epNum === 3) {
highlightClass = 'episode-03-highlight';
} else if (epNum >= 4) {
highlightClass = 'episode-other-highlight';
}
if (highlightClass) {
link.classList.add(highlightClass);
// Apply show-specific color
const showColor = getShowColor(showName);
link.style.setProperty('background-color', showColor, 'important');
link.style.setProperty('border-color', showColor, 'important');
link.style.setProperty('box-shadow', `0 0 ${highlightClass.includes('02') ? '8' : highlightClass.includes('03') ? '5' : '3'}px ${showColor}`, 'important');
subsequentEpisodesCount++;
debugLog(`📺 FOUND Episode ${episodeNum}: "${text}" (Show: "${showName}", Color: ${showColor})`);
// Add click handler
link.addEventListener('click', function() {
debugLog(`📺 Clicked on Episode ${episodeNum}:`, text);
});
}
}
}
});
// Update found episodes list (only episode 01s)
foundEpisodes = [...foundEpisodes, ...newFoundEpisodes];
updateFoundList(foundEpisodes);
updateEpisodeCounter(foundEpisodes.length);
debugLog(`🔍 Scan ${scanCount}: Found ${newEpisode01Count} NEW episode 01s, ${subsequentEpisodesCount} subsequent episodes (Total episode 01s: ${foundEpisodes.length})`);
if (newFoundEpisodes.length > 0) {
debugLog('🆕 New Episode 01s found:', newFoundEpisodes);
}
}
// 🕵️ Monitor XHR requests to see when data loads
const originalXHR = window.XMLHttpRequest;
window.XMLHttpRequest = function() {
const xhr = new originalXHR();
const originalOpen = xhr.open;
xhr.open = function(method, url) {
if (url.includes('api/?f=latest')) {
debugLog('🌐 Latest releases API call detected');
xhr.addEventListener('load', function() {
debugLog('🌐 Latest releases API response received - scanning in 1 second...');
setTimeout(highlightEpisode01s, 1000);
});
}
return originalOpen.apply(this, arguments);
};
return xhr;
};
// 🚀 Initialize the script
function init() {
debugLog('🌟 SubsPlease Episode 01 Highlighter PRECISE VERSION starting...');
// Add styles to the page
const styleSheet = document.createElement('style');
styleSheet.textContent = styles;
document.head.appendChild(styleSheet);
// Create debug toggle
createDebugToggle();
// Initial scans with delays to catch dynamic content
setTimeout(highlightEpisode01s, 1000);
setTimeout(highlightEpisode01s, 3000);
setTimeout(highlightEpisode01s, 5000);
// 👀 MutationObserver for table changes
const observer = new MutationObserver(function(mutations) {
let tableChanged = false;
mutations.forEach(function(mutation) {
if (mutation.target.id === 'releases-table' ||
mutation.target.closest('#releases-table')) {
tableChanged = true;
}
});
if (tableChanged) {
debugLog('🔄 Table content changed - scanning...');
setTimeout(highlightEpisode01s, 300);
}
});
// Observe the releases table specifically
const table = document.querySelector('#releases-table');
if (table) {
observer.observe(table, {
childList: true,
subtree: true
});
debugLog('📊 Table monitoring enabled');
}
// 🔄 Periodic scanning (less frequent)
setInterval(highlightEpisode01s, 10000);
// 🎯 Monitor "See more" button
function monitorSeeMoreButton() {
const seeMoreButton = document.querySelector('#latest-load-more');
if (seeMoreButton && !seeMoreButton.hasAttribute('data-monitored')) {
seeMoreButton.setAttribute('data-monitored', 'true');
seeMoreButton.addEventListener('click', function() {
debugLog('🔄 "See more" clicked - will scan in 2 seconds...');
setTimeout(highlightEpisode01s, 2000);
});
debugLog('📱 "See more" button monitoring enabled');
}
}
setTimeout(monitorSeeMoreButton, 1000);
debugLog('🌟 SubsPlease Episode 01 Highlighter PRECISE VERSION activated!');
}
// 🎬 Start the script
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
window.addEventListener('load', function() {
setTimeout(highlightEpisode01s, 2000);
});
})();