Scans watched threads, looks for the "New" badge, jumps to the last page, and opens posts with media (links, videos, images) in a new tab.
// ==UserScript==
// @name SimpCity Watched Thread Media Scanner
// @namespace GeminiScripts
// @version 44.0
// @description Scans watched threads, looks for the "New" badge, jumps to the last page, and opens posts with media (links, videos, images) in a new tab.
// @author Gemini
// @match *://simpcity.su/*
// @match *://simpcity.cr/*
// @match *://simpcity.li/*
// @icon
// @grant GM_xmlhttpRequest
// @grant GM_openInTab
// @license MIT
// ==/UserScript==
(function() {
'use strict';
// --- CONFIGURATION ---
const SCAN_DELAY_MS = 2500;
const OPEN_DELAY_MS = 2000;
const BUTTON_ID = 'gemini-float-scan-btn';
let isScanning = false;
// --- HOTKEY (Alt + S) ---
document.addEventListener('keydown', (e) => {
if (e.altKey && (e.code === 'KeyS' || e.key === 's')) {
e.preventDefault();
if (isScanning) stopScan();
else startScan();
}
});
// --- INJECTION: FLOATING BUTTON ---
setInterval(() => {
if (!window.location.href.includes('/watched/threads')) return;
if (document.getElementById(BUTTON_ID)) return;
createFloatingButton();
}, 1000);
function createFloatingButton() {
const btn = document.createElement('div');
btn.id = BUTTON_ID;
btn.innerText = 'SCAN MEDIA';
Object.assign(btn.style, {
position: 'fixed',
bottom: '20px',
right: '20px',
zIndex: '9999',
padding: '12px 20px',
backgroundColor: '#e67e22',
color: 'white',
fontWeight: 'bold',
borderRadius: '50px',
boxShadow: '0 4px 6px rgba(0,0,0,0.3)',
cursor: 'pointer',
fontSize: '14px',
fontFamily: 'Arial, sans-serif',
border: '2px solid #d35400',
transition: 'all 0.3s ease'
});
btn.onmouseover = () => { btn.style.transform = 'scale(1.05)'; };
btn.onmouseout = () => { btn.style.transform = 'scale(1.0)'; };
btn.onclick = (e) => {
e.preventDefault();
if (isScanning) stopScan();
else startScan();
};
document.body.appendChild(btn);
}
// --- STATUS UPDATE ---
function updateStatus(text, color, bgColor) {
const btn = document.getElementById(BUTTON_ID);
if (btn) {
btn.innerText = text;
if (color) btn.style.color = color;
if (bgColor) {
btn.style.backgroundColor = bgColor;
btn.style.borderColor = bgColor;
}
}
}
function stopScan() {
isScanning = false;
updateStatus('STOPPING...', 'white', '#7f8c8d');
setTimeout(() => {
updateStatus('SCAN MEDIA', 'white', '#e67e22');
}, 1000);
}
// --- MAIN LOGIC ---
async function startScan() {
isScanning = true;
updateStatus('STOP SCAN', 'white', '#c0392b');
// Find all threads that have an "unread" indicator
const rows = document.querySelectorAll('.structItem--thread');
const unreadRows = Array.from(rows).filter(row => row.querySelector('.structItem-title a[href*="/unread"]'));
if (unreadRows.length === 0) {
updateStatus('NO NEW', 'white', '#e67e22');
isScanning = false;
setTimeout(() => { updateStatus('SCAN MEDIA', 'white', '#e67e22'); }, 2000);
return;
}
for (let i = 0; i < unreadRows.length; i++) {
if (!isScanning) return;
const row = unreadRows[i];
// --- SMART URL SELECTION (Last Page Logic) ---
// 1. Default: The unread link
let targetLink = row.querySelector('.structItem-title a[href*="/unread"]').href;
// 2. Optimization: Check for Page Numbers
// We want the LAST page (highest number)
const pageJumpContainer = row.querySelector('.structItem-pageJump');
if (pageJumpContainer) {
const lastPageLink = pageJumpContainer.querySelector('a:last-child');
if (lastPageLink) {
targetLink = lastPageLink.href;
}
}
if (targetLink) {
updateStatus(`${i + 1} / ${unreadRows.length}`, 'white', '#e67e22');
try {
const result = await checkUrlForMedia(targetLink);
if (!isScanning) return;
if (result.hasMedia) {
row.style.backgroundColor = 'rgba(40, 167, 69, 0.2)';
row.style.borderLeft = '5px solid #28a745';
row.style.opacity = '1.0';
updateStatus('OPENING...', 'white', '#2ecc71');
GM_openInTab(result.specificUrl, { active: false, insert: true });
await sleep(OPEN_DELAY_MS);
} else {
row.style.opacity = '0.3';
row.style.filter = 'grayscale(100%)';
await sleep(SCAN_DELAY_MS);
}
} catch (err) {
await sleep(1000);
}
}
}
if (isScanning) {
updateStatus('DONE', 'white', '#2ecc71');
setTimeout(() => {
isScanning = false;
updateStatus('SCAN MEDIA', 'white', '#e67e22');
}, 3000);
}
}
// --- HELPER: CHECK URL (COOKIES + STRICT BADGE) ---
function checkUrlForMedia(url) {
return new Promise((resolve) => {
GM_xmlhttpRequest({
method: "GET",
url: url,
withCredentials: true, // Sends Cookies to see badges
onload: function(response) {
if (!isScanning) { resolve({ hasMedia: false }); return; }
const parser = new DOMParser();
const doc = parser.parseFromString(response.responseText, "text/html");
const finalUrlBase = (response.finalUrl || url).split('#')[0];
const posts = doc.querySelectorAll('.message');
let result = { hasMedia: false, specificUrl: null };
// SIGNATURES for Media Detection
const MEDIA_SIGNATURES = [
'turbo.cr', 'gofile.io', 'pixeldrain', 'mega.nz', 'saint.to', 'bunkr',
'transfer.it', 'catbox.moe', 'simpcity.su', 'simpcity.cr',
'redgifs', 'imgbox', 'filedit', 'cyberdrop', 'qiwi', 'krakenfiles',
'.mp4', '.mkv', '.webm', '.m3u8', '.mov',
'jwplayer', 'video-js', 'plyr', 'class="video', 'tag="video"',
'img src=', 'attachment', 'bbcodespoiler'
];
for (let post of posts) {
// --- STRICT NEW BADGE CHECK ---
const isNew = post.innerHTML.includes('message-newIndicator');
// SKIP OLD POSTS
if (!isNew) continue;
// --- CONTENT SCAN ---
const body = post.querySelector('.message-body');
if (!body) continue;
const clone = body.cloneNode(true);
clone.querySelectorAll('.bbCodeBlock--quote').forEach(q => q.remove());
clone.querySelectorAll('.smilie').forEach(s => s.remove());
let isMedia = false;
const rawHtml = clone.innerHTML.toLowerCase();
// A. Check for SIGNATURES (Raw HTML)
if (MEDIA_SIGNATURES.some(sig => rawHtml.includes(sig))) isMedia = true;
// B. Standard Checks (Tags)
if (!isMedia) {
if (clone.querySelector('.bbCodeSpoiler-button, .bbCodeInlineSpoiler')) isMedia = true;
if (clone.querySelector('video, iframe, object, embed')) isMedia = true;
if (clone.querySelector('img.bbImage')) isMedia = true;
// Regex for ANY external link
if (/(https?:\/\/(?!(\w+\.)?simpcity))/i.test(rawHtml)) isMedia = true;
}
if (isMedia) {
result.hasMedia = true;
let anchorId = post.id;
if (!anchorId) {
const permalink = post.querySelector('a[data-content-selector^="#post-"]');
if (permalink) anchorId = permalink.getAttribute('data-content-selector').replace('#', '');
}
result.specificUrl = anchorId ? `${finalUrlBase}#${anchorId}` : finalUrlBase;
break;
}
}
resolve(result);
},
onerror: function() { resolve({ hasMedia: false, specificUrl: null }); }
});
});
}
function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); }
})();