Sleazy Fork is available in English.
Unified Erome script: content filtering, photo/video toggles, multi-video playback, watched tracking, disclaimer bypass. Enhanced with IntersectionObserver, resilient storage, and debug utilities. Auto-removes repost/flag buttons.
// ==UserScript== // @name Rome // @namespace n2ch // @version 1.7.203 // @description Unified Erome script: content filtering, photo/video toggles, multi-video playback, watched tracking, disclaimer bypass. Enhanced with IntersectionObserver, resilient storage, and debug utilities. Auto-removes repost/flag buttons. // @icon https://www.google.com/s2/favicons?sz=64&domain=erome.com // @match *://*.erome.com/* // @require https://cdn.jsdelivr.net/npm/[email protected]/dist/billy-herrington-utils.umd.js // @require https://cdn.jsdelivr.net/npm/[email protected]/dist/jabroni-outfit.umd.js // @grant GM_addStyle // @grant GM_getValue // @grant GM_info // @grant GM_registerMenuCommand // @grant GM_setValue // @grant unsafeWindow // @inject-into page // @run-at document-idle // @noframes // ==/UserScript== (function () { 'use strict'; var __defProp = Object.defineProperty; var __typeError = (msg) => { throw TypeError(msg); }; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg); var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj)); var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value); var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), member.set(obj, value), value); var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method); var __privateWrapper = (obj, member, setter, getter) => ({ set _(value) { __privateSet(obj, member, value); }, get _() { return __privateGet(obj, member, getter); } }); var _VALID_POSITIONS, _deps, _initialized, _overlayEl, _watchStarted, _textObserver, _totalPages, _positionSide, _keydownHandler, _resizeHandler, _bodyObserver, _rewriteFailCount, _rafPending, _pendingDesired, _pendingTextSpan, _pageFromEvent, _lastSearch, _pollId, _settleTimers, _waitTimeoutId, _childWatcherTTL, _childWatcher, _docObserver, _listeners, _InfyScrollOverlay_instances, loadPosition_fn, isOverlayRendering_fn, findTextSpan_fn, findIconEl_fn, styleOverlay_fn, getEromeTotalPages_fn, getCurrentPage_fn, updateCounter_fn, watchPageText_fn, initOverlay_fn, measureLayout_fn, applyDynamicPosition_fn, setupResizeHandler_fn, setupKeyboardShortcut_fn, waitForOverlay_fn, reanchorBodyObserver_fn, onNavigation_fn, onAppend_fn; var _GM_addStyle = (() => typeof GM_addStyle != "undefined" ? GM_addStyle : void 0)(); var _GM_getValue = (() => typeof GM_getValue != "undefined" ? GM_getValue : void 0)(); var _GM_registerMenuCommand = (() => typeof GM_registerMenuCommand != "undefined" ? GM_registerMenuCommand : void 0)(); var _GM_setValue = (() => typeof GM_setValue != "undefined" ? GM_setValue : void 0)(); var _unsafeWindow = (() => typeof unsafeWindow != "undefined" ? unsafeWindow : void 0)(); function getVideoId(url) { const match = url.match(/\/a\/([^/?]+)/); return match ? match[1] : null; } function derivePosterFromVideoUrl(videoUrl) { return videoUrl.replace(/^(https?:\/\/)v(\d+)\.erome\.com\//, "$1s$2.erome.com/").replace(/_\d{3,4}p\.mp4$/, ".jpg"); } const WARM_BUFFER_COUNT = 2; const _romePreloadIntersecting = new Map(); let _romePreloadObserver = null; function getRomePreloadObserver() { if (_romePreloadObserver) return _romePreloadObserver; if (typeof IntersectionObserver === "undefined") return null; _romePreloadObserver = new IntersectionObserver( (entries) => { for (const entry of entries) { const v = entry.target; if (entry.isIntersecting) { _romePreloadIntersecting.set(v, Math.abs(entry.boundingClientRect.top)); } else { _romePreloadIntersecting.delete(v); if (v.preload !== "metadata") v.preload = "metadata"; } } const ranked = [..._romePreloadIntersecting.entries()].sort( (a, b) => a[1] - b[1] ); ranked.forEach(([v], i) => { const want = i < WARM_BUFFER_COUNT ? "auto" : "metadata"; if (v.preload !== want) v.preload = want; }); }, { rootMargin: "800px 0px", threshold: 0.01 } ); return _romePreloadObserver; } let _romePlaybackObserver = null; function getRomePlaybackObserver() { if (_romePlaybackObserver) return _romePlaybackObserver; if (typeof IntersectionObserver === "undefined") return null; _romePlaybackObserver = new IntersectionObserver( (entries) => { entries.forEach((entry) => { if (entry.isIntersecting) return; const v = entry.target; if (!v.paused) v.pause(); }); }, { rootMargin: "0px", threshold: 0 } ); return _romePlaybackObserver; } function prioritizeVideoForPlayback(v) { if (v.preload !== "auto") v.preload = "auto"; v.fetchPriority = "high"; } function createAlbumMutationObserver({ target, itemSelector, onItemsAdded }) { if (!target) return null; if (typeof MutationObserver === "undefined") return null; const observer = new MutationObserver((records) => { var _a; const matched = []; for (const rec of records) { for (const node of rec.addedNodes) { if (node.nodeType !== 1) continue; const el = node; if ((_a = el.matches) == null ? void 0 : _a.call(el, itemSelector)) { matched.push(el); } else if (el.querySelectorAll) { for (const descendant of el.querySelectorAll(itemSelector)) { matched.push(descendant); } } } } if (matched.length > 0) onItemsAdded(matched); }); observer.observe(target, { childList: true, subtree: true }); return observer; } const INFY_PAGE_IFRAME_SELECTOR = 'iframe[id^="infy-scroll-page"]'; function removeDisclaimersIn(doc, selector) { if (!doc) return 0; const found = doc.querySelectorAll(selector); if (found.length === 0) return 0; found.forEach((el) => el.remove()); if (doc.body) doc.body.style.overflow = "visible"; return found.length; } function createDisclaimerWatcher({ selector, doc, root, dismiss, onLog = () => { }, onError = (...args) => console.error("[Rome] DisclaimerBypass", ...args) }) { let dismissState = "idle"; const handledIframes = new WeakSet(); function onDisclaimersRemoved(count, where) { onLog("DisclaimerBypass", `removed ${count} disclaimer element(s) from ${where}`); if (dismissState !== "idle") return; dismissState = "pending"; try { Promise.resolve(dismiss()).then( () => { dismissState = "done"; onLog("DisclaimerBypass", "server-side dismissal acknowledged"); }, (err) => { dismissState = "idle"; onError("server-side dismissal failed", err); } ); } catch (err) { dismissState = "idle"; onError("server-side dismissal threw", err); } } function sweep(target, where) { const removed = removeDisclaimersIn(target, selector); if (removed > 0) onDisclaimersRemoved(removed, where); } function sweepIframe(iframe) { try { sweep(iframe.contentDocument, iframe.id || "infy iframe"); } catch (err) { if (err instanceof DOMException && err.name === "SecurityError") return; onError("iframe sweep failed", err); } } function watchIframe(iframe) { if (handledIframes.has(iframe)) return; handledIframes.add(iframe); const frame = iframe; sweepIframe(frame); frame.addEventListener("load", () => sweepIframe(frame)); } sweep(doc, "document"); doc.querySelectorAll(INFY_PAGE_IFRAME_SELECTOR).forEach(watchIframe); if (!root) return null; if (typeof MutationObserver === "undefined") return null; const observer = new MutationObserver((records) => { var _a, _b, _c, _d; try { let needsDocumentSweep = false; const iframes = []; for (const rec of records) { for (const node of rec.addedNodes) { if (node.nodeType !== 1) continue; const el = node; if (((_a = el.matches) == null ? void 0 : _a.call(el, selector)) || ((_b = el.querySelector) == null ? void 0 : _b.call(el, selector))) { needsDocumentSweep = true; } if ((_c = el.matches) == null ? void 0 : _c.call(el, INFY_PAGE_IFRAME_SELECTOR)) iframes.push(el); (_d = el.querySelectorAll) == null ? void 0 : _d.call(el, INFY_PAGE_IFRAME_SELECTOR).forEach((f) => iframes.push(f)); } } if (needsDocumentSweep) sweep(doc, "document"); iframes.forEach(watchIframe); } catch (err) { onError("observer error", err); } }); observer.observe(root, { childList: true, subtree: true }); return { disconnect() { observer.disconnect(); } }; } function computeOverlayCss({ layout, viewportWidth, config, positionSide }) { if (layout === null) { return { top: "auto", bottom: `${config.SIDE_GAP}px`, left: "auto", right: `${config.SIDE_GAP}px` }; } const top = `${Math.max(layout.tabsTop + config.TOP_GAP, config.SIDE_GAP)}px`; if (positionSide === "left") { return { top, right: "auto", bottom: "auto", left: `${layout.tabsRight + config.SIDE_GAP}px` }; } if (positionSide === "bottom-right") { return { top: "auto", bottom: `${config.SIDE_GAP}px`, left: `${layout.rightX}px`, right: "auto" }; } if (positionSide === "bottom-left") { return { top: "auto", bottom: `${config.SIDE_GAP}px`, left: "auto", right: `${Math.max(viewportWidth - layout.leftX, 0)}px` }; } if (positionSide !== "right") { console.warn("[InfyOverlay] computeOverlayCss: unrecognized positionSide:", positionSide, "-- falling back to right"); } return { top, right: `${Math.max(viewportWidth - layout.rightX, 0)}px`, bottom: "auto", left: "auto" }; } function notifyStorageFailure(context) { const TOAST_ID = "rome-storage-toast"; let toast = document.getElementById(TOAST_ID); if (!toast) { toast = document.createElement("div"); toast.id = TOAST_ID; Object.assign(toast.style, { position: "fixed", bottom: "20px", right: "20px", zIndex: "99999", padding: "10px 16px", background: "rgba(30, 30, 30, 0.9)", color: "#fff", fontSize: "13px", borderRadius: "6px", fontFamily: "system-ui, sans-serif", pointerEvents: "none", opacity: "1", transition: "opacity 0.4s ease" }); document.body.appendChild(toast); } toast.textContent = `Rome: Failed to save ${context}`; toast.style.opacity = "1"; clearTimeout(toast._romeTimer); toast._romeTimer = setTimeout(() => { toast.style.opacity = "0"; setTimeout(() => toast.remove(), 400); }, 4e3); } function maxPageFromTexts(texts) { let max = 0; for (const t of texts) { const n = parseInt(t.trim(), 10); if (!isNaN(n) && n > max) max = n; } return max > 0 ? max : null; } function parsePageParam(search) { const raw = new URLSearchParams(search).get("page"); const n = parseInt(raw ?? "", 10); return !isNaN(n) && n > 0 ? n : null; } function parsePageFromUrl(url) { if (!url) return null; try { return parsePageParam(new URL(url, "https://invalid.local").search); } catch { return null; } } function computeCounter(currentPage, liveTotal, prevTotal) { const knownTotal = Math.max(prevTotal ?? 0, liveTotal ?? 0); const total = knownTotal > 0 ? knownTotal : null; let desired = null; if (currentPage !== null && total !== null) desired = currentPage + "/" + total; else if (currentPage !== null) desired = currentPage + "/?"; else if (total !== null) desired = "?/" + total; return { knownTotal, desired }; } const _InfyScrollOverlay = class _InfyScrollOverlay { constructor(deps) { __privateAdd(this, _InfyScrollOverlay_instances); __privateAdd(this, _deps); __privateAdd(this, _initialized, false); __privateAdd(this, _overlayEl, null); __privateAdd(this, _watchStarted, false); __privateAdd(this, _textObserver, null); __privateAdd(this, _totalPages, null); __privateAdd(this, _positionSide); __privateAdd(this, _keydownHandler, null); __privateAdd(this, _resizeHandler, null); __privateAdd(this, _bodyObserver, null); __privateAdd(this, _rewriteFailCount, 0); __privateAdd(this, _rafPending, false); __privateAdd(this, _pendingDesired, null); __privateAdd(this, _pendingTextSpan, null); __privateAdd(this, _pageFromEvent, null); __privateAdd(this, _lastSearch, null); __privateAdd(this, _pollId, null); __privateAdd(this, _settleTimers, new Set()); __privateAdd(this, _waitTimeoutId, null); __privateAdd(this, _childWatcherTTL, null); __privateAdd(this, _childWatcher, null); __privateAdd(this, _docObserver, null); __privateAdd(this, _listeners, []); __privateSet(this, _deps, deps); __privateSet(this, _positionSide, __privateMethod(this, _InfyScrollOverlay_instances, loadPosition_fn).call(this)); } start() { __privateMethod(this, _InfyScrollOverlay_instances, waitForOverlay_fn).call(this); const on = (target, type, fn) => { target.addEventListener(type, fn); __privateGet(this, _listeners).push({ target, type, fn }); }; on(document, "GM_AutoPagerizeLoaded", () => __privateMethod(this, _InfyScrollOverlay_instances, onNavigation_fn).call(this, "GM_AutoPagerizeLoaded")); on(document, "AutoPagerize_DOMNodeInserted", (e) => { const detail = e.detail; __privateMethod(this, _InfyScrollOverlay_instances, onAppend_fn).call(this, "AutoPagerize_DOMNodeInserted", detail == null ? void 0 : detail.url); }); on(document, "GM_AutoPagerizeNextPageLoaded", () => { const timerId = setTimeout(() => { __privateGet(this, _settleTimers).delete(timerId); const barPage = parsePageParam(window.location.search) ?? 1; const barIsLagging = __privateGet(this, _pageFromEvent) !== null && barPage === __privateGet(this, _pageFromEvent) - 1; if (!barIsLagging) __privateSet(this, _pageFromEvent, null); __privateMethod(this, _InfyScrollOverlay_instances, onAppend_fn).call(this, "GM_AutoPagerizeNextPageLoaded"); }, _InfyScrollOverlay.APPEND_SETTLE_MS); __privateGet(this, _settleTimers).add(timerId); }); const orig = { pushState: history.pushState.bind(history), replaceState: history.replaceState.bind(history) }; history.pushState = (...args) => { orig.pushState(...args); __privateMethod(this, _InfyScrollOverlay_instances, onNavigation_fn).call(this, "pushState"); }; history.replaceState = (...args) => { orig.replaceState(...args); __privateMethod(this, _InfyScrollOverlay_instances, onNavigation_fn).call(this, "replaceState"); }; on(window, "popstate", () => __privateMethod(this, _InfyScrollOverlay_instances, onNavigation_fn).call(this, "popstate")); __privateSet(this, _docObserver, new MutationObserver((mutations) => { for (const m of mutations) { for (const node of m.addedNodes) { if (node === document.body) { __privateGet(this, _deps).logger.log( "[InfyOverlay]", "document.body replaced -- re-anchoring observer" ); __privateMethod(this, _InfyScrollOverlay_instances, reanchorBodyObserver_fn).call(this); } } } })); __privateGet(this, _docObserver).observe(document.documentElement, { childList: true }); __privateSet(this, _pollId, setInterval(() => { const el = document.getElementById(_InfyScrollOverlay.OVERLAY_ID); if (!el) return; if (!__privateGet(this, _initialized)) { __privateGet(this, _deps).logger.log( "[InfyOverlay]", "poll: uninitialized -- forcing _initOverlay", { pathname: window.location.pathname } ); __privateMethod(this, _InfyScrollOverlay_instances, initOverlay_fn).call(this, el); return; } if (!__privateMethod(this, _InfyScrollOverlay_instances, isOverlayRendering_fn).call(this, el)) { __privateGet(this, _deps).logger.log( "[InfyOverlay]", "poll: initialized but hidden -- re-applying styles", { pathname: window.location.pathname } ); __privateMethod(this, _InfyScrollOverlay_instances, styleOverlay_fn).call(this, el); return; } const search = window.location.search; if (search !== __privateGet(this, _lastSearch)) { __privateSet(this, _lastSearch, search); const fromUrl = parsePageParam(search); const barIsLagging = fromUrl !== null && __privateGet(this, _pageFromEvent) !== null && fromUrl === __privateGet(this, _pageFromEvent) - 1; if (fromUrl !== null && !barIsLagging) __privateSet(this, _pageFromEvent, null); __privateGet(this, _deps).logger.log("[InfyOverlay]", "poll: url changed", { search, fromUrl, barIsLagging }); __privateMethod(this, _InfyScrollOverlay_instances, updateCounter_fn).call(this, el); } }, 1e3)); } stop() { if (__privateGet(this, _pollId) !== null) { clearInterval(__privateGet(this, _pollId)); __privateSet(this, _pollId, null); } for (const id of __privateGet(this, _settleTimers)) clearTimeout(id); __privateGet(this, _settleTimers).clear(); for (const id of [__privateGet(this, _waitTimeoutId), __privateGet(this, _childWatcherTTL)]) { if (id !== null) clearTimeout(id); } __privateSet(this, _waitTimeoutId, null); __privateSet(this, _childWatcherTTL, null); for (const { target, type, fn } of __privateGet(this, _listeners)) { target.removeEventListener(type, fn); } __privateSet(this, _listeners, []); for (const obs of [ __privateGet(this, _docObserver), __privateGet(this, _bodyObserver), __privateGet(this, _textObserver), __privateGet(this, _childWatcher) ]) { obs == null ? void 0 : obs.disconnect(); } __privateSet(this, _docObserver, null); __privateSet(this, _bodyObserver, null); __privateSet(this, _textObserver, null); __privateSet(this, _childWatcher, null); if (__privateGet(this, _keydownHandler)) { document.removeEventListener("keydown", __privateGet(this, _keydownHandler)); __privateSet(this, _keydownHandler, null); } if (__privateGet(this, _resizeHandler)) { window.removeEventListener("resize", __privateGet(this, _resizeHandler)); __privateSet(this, _resizeHandler, null); } __privateSet(this, _watchStarted, false); __privateSet(this, _initialized, false); __privateSet(this, _overlayEl, null); __privateSet(this, _pageFromEvent, null); __privateSet(this, _lastSearch, null); __privateSet(this, _totalPages, null); __privateSet(this, _pendingDesired, null); __privateSet(this, _pendingTextSpan, null); __privateSet(this, _rewriteFailCount, 0); } }; _VALID_POSITIONS = new WeakMap(); _deps = new WeakMap(); _initialized = new WeakMap(); _overlayEl = new WeakMap(); _watchStarted = new WeakMap(); _textObserver = new WeakMap(); _totalPages = new WeakMap(); _positionSide = new WeakMap(); _keydownHandler = new WeakMap(); _resizeHandler = new WeakMap(); _bodyObserver = new WeakMap(); _rewriteFailCount = new WeakMap(); _rafPending = new WeakMap(); _pendingDesired = new WeakMap(); _pendingTextSpan = new WeakMap(); _pageFromEvent = new WeakMap(); _lastSearch = new WeakMap(); _pollId = new WeakMap(); _settleTimers = new WeakMap(); _waitTimeoutId = new WeakMap(); _childWatcherTTL = new WeakMap(); _childWatcher = new WeakMap(); _docObserver = new WeakMap(); _listeners = new WeakMap(); _InfyScrollOverlay_instances = new WeakSet(); loadPosition_fn = function() { try { const stored = __privateGet(this, _deps).getValue( __privateGet(this, _deps).config.STORAGE_KEYS.OVERLAY_POSITION, null ); if (stored && __privateGet(_InfyScrollOverlay, _VALID_POSITIONS).includes(stored)) { return stored; } if (stored) console.warn("[InfyOverlay] discarding invalid persisted position:", stored); } catch (e) { console.warn("[InfyOverlay] failed to load persisted position:", e); } return __privateGet(this, _deps).config.INFY_OVERLAY.DEFAULT_SIDE; }; isOverlayRendering_fn = function(el) { if (!el) return false; const s = getComputedStyle(el); if (s.display === "none" || s.visibility === "hidden" || s.opacity === "0") return false; const rect = el.getBoundingClientRect(); return rect.width > 0 && rect.height > 0; }; findTextSpan_fn = function(overlay) { const numberSpan = overlay.querySelector(`#${_InfyScrollOverlay.NUMBER_ID}`); if (numberSpan) { numberSpan.setAttribute("data-infy-text", "number"); return numberSpan; } const tagged = overlay.querySelector("[data-infy-text]"); if (tagged && tagged.getAttribute("data-infy-text") !== "fallback") return tagged; for (const child of overlay.children) { if (_InfyScrollOverlay.PAGE_PATTERN.test(child.textContent ?? "")) { child.setAttribute("data-infy-text", ""); return child; } } const fallback = overlay.children[1]; if (fallback && fallback.tagName === "SPAN") { fallback.setAttribute("data-infy-text", "fallback"); __privateGet(this, _deps).logger.log( "[InfyOverlay]", "Using fallback text span (children[1]); PAGE_PATTERN did not match" ); return fallback; } __privateGet(this, _deps).logger.log("[InfyOverlay]", "_findTextSpan: no valid text span found"); return null; }; findIconEl_fn = function(overlay) { return overlay.querySelector("#infy-scroll-overlay-icon") || overlay.querySelector("svg") || null; }; styleOverlay_fn = function(overlay) { const s = overlay.style; const c = __privateGet(this, _deps).config.INFY_OVERLAY; s.setProperty("display", "block", "important"); s.setProperty("position", "fixed", "important"); s.setProperty("background", c.BACKGROUND, "important"); s.setProperty("width", "fit-content", "important"); s.setProperty("max-width", "none", "important"); __privateMethod(this, _InfyScrollOverlay_instances, applyDynamicPosition_fn).call(this, overlay); s.setProperty("padding", c.PADDING, "important"); s.setProperty("border-radius", c.BORDER_RADIUS, "important"); s.setProperty("box-shadow", "none", "important"); s.setProperty("border", "none", "important"); const textSpan = __privateMethod(this, _InfyScrollOverlay_instances, findTextSpan_fn).call(this, overlay); if (textSpan) { textSpan.style.setProperty("background", "transparent", "important"); textSpan.style.setProperty("color", c.TEXT_COLOR, "important"); textSpan.style.setProperty("font-size", c.FONT_SIZE, "important"); textSpan.style.setProperty("font-family", c.FONT_FAMILY, "important"); textSpan.style.setProperty("font-weight", "normal", "important"); textSpan.style.setProperty("white-space", "nowrap", "important"); textSpan.style.setProperty("display", "block", "important"); textSpan.style.setProperty("width", "fit-content", "important"); __privateMethod(this, _InfyScrollOverlay_instances, updateCounter_fn).call(this, overlay); } const iconEl = __privateMethod(this, _InfyScrollOverlay_instances, findIconEl_fn).call(this, overlay); if (iconEl) { iconEl.style.setProperty("display", "none", "important"); } const closeEl = overlay.querySelector("#infy-scroll-overlay-close"); const moveEl = overlay.querySelector("#infy-scroll-overlay-move"); if (closeEl) closeEl.style.setProperty("display", "none", "important"); if (moveEl) moveEl.style.setProperty("display", "none", "important"); }; getEromeTotalPages_fn = function() { const items = document.querySelectorAll("ul.pagination li a, ul.pagination li span"); const texts = []; for (const el of items) texts.push(el.textContent ?? ""); return maxPageFromTexts(texts); }; getCurrentPage_fn = function() { if (__privateGet(this, _pageFromEvent) !== null) return __privateGet(this, _pageFromEvent); const urlPage = parsePageParam(window.location.search); if (urlPage !== null) return urlPage; const activeSpan = document.querySelector("ul.pagination li.active span"); if (activeSpan) { const n = parseInt((activeSpan.textContent ?? "").trim(), 10); if (!isNaN(n)) return n; } return null; }; updateCounter_fn = function(overlay) { const textSpan = __privateMethod(this, _InfyScrollOverlay_instances, findTextSpan_fn).call(this, overlay); if (!textSpan) { __privateGet(this, _deps).logger.log("[InfyOverlay]", "_updateCounter: textSpan not found; skipping"); return; } const currentPage = __privateMethod(this, _InfyScrollOverlay_instances, getCurrentPage_fn).call(this); const liveTotal = __privateMethod(this, _InfyScrollOverlay_instances, getEromeTotalPages_fn).call(this); __privateGet(this, _deps).logger.log( "[InfyOverlay]", `_updateCounter: page=${currentPage} total=${liveTotal} url=${window.location.search} spanInDoc=${document.contains(textSpan)}` ); const { knownTotal, desired } = computeCounter(currentPage, liveTotal, __privateGet(this, _totalPages)); if (knownTotal > 0) __privateSet(this, _totalPages, knownTotal); if (desired === null) return; if (textSpan.textContent === desired) return; __privateSet(this, _pendingDesired, desired); __privateSet(this, _pendingTextSpan, textSpan); if (__privateGet(this, _rafPending)) return; __privateSet(this, _rafPending, true); requestAnimationFrame(() => { __privateSet(this, _rafPending, false); const span = __privateGet(this, _pendingTextSpan); const value = __privateGet(this, _pendingDesired); try { if (span && document.contains(span) && span.textContent !== value) { span.textContent = value; } } catch (e) { __privateSet(this, _rewriteFailCount, __privateGet(this, _rewriteFailCount) + 1); console.error("[InfyOverlay] rAF counter update failed:", e); } }); }; watchPageText_fn = function(overlay) { if (__privateGet(this, _textObserver)) { __privateGet(this, _textObserver).disconnect(); } __privateSet(this, _textObserver, new MutationObserver(() => { const liveOverlay = document.getElementById(_InfyScrollOverlay.OVERLAY_ID); if (!liveOverlay) return; const currentSpan = __privateMethod(this, _InfyScrollOverlay_instances, findTextSpan_fn).call(this, liveOverlay); if (!currentSpan) return; try { __privateGet(this, _textObserver).disconnect(); __privateMethod(this, _InfyScrollOverlay_instances, updateCounter_fn).call(this, liveOverlay); __privateMethod(this, _InfyScrollOverlay_instances, applyDynamicPosition_fn).call(this, liveOverlay); __privateSet(this, _rewriteFailCount, 0); } catch (e) { __privateSet(this, _rewriteFailCount, __privateGet(this, _rewriteFailCount) + 1); if (__privateGet(this, _rewriteFailCount) >= 5) { if (__privateGet(this, _rewriteFailCount) >= 10) { console.error( "[InfyOverlay] _updateCounter failed", __privateGet(this, _rewriteFailCount), "times -- giving up on recovery:", e ); return; } console.warn( "[InfyOverlay] _updateCounter failed", __privateGet(this, _rewriteFailCount), "times consecutively -- triggering re-init:", e ); const recoveryOverlay = document.getElementById(_InfyScrollOverlay.OVERLAY_ID); if (recoveryOverlay) { __privateSet(this, _initialized, false); try { __privateMethod(this, _InfyScrollOverlay_instances, initOverlay_fn).call(this, recoveryOverlay); __privateSet(this, _rewriteFailCount, 0); } catch (e2) { __privateWrapper(this, _rewriteFailCount)._++; console.error("[InfyOverlay] Recovery _initOverlay failed:", e2); } } return; } __privateGet(this, _deps).logger.log("[InfyOverlay]", "Observer error:", e); } finally { if (__privateGet(this, _rewriteFailCount) < 5) { try { __privateGet(this, _textObserver).observe(currentSpan, { characterData: true, childList: true, subtree: true }); } catch (observeErr) { __privateGet(this, _deps).logger.log( "[InfyOverlay]", "Failed to re-observe text span:", observeErr ); } } } })); const initialSpan = __privateMethod(this, _InfyScrollOverlay_instances, findTextSpan_fn).call(this, overlay); let spanObserveStarted = false; if (initialSpan) { try { __privateGet(this, _textObserver).observe(initialSpan, { characterData: true, childList: true, subtree: true }); spanObserveStarted = true; } catch (e) { console.error("[InfyOverlay] _textObserver.observe failed on initialSpan:", e); } } if (!spanObserveStarted) { __privateSet(this, _childWatcherTTL, setTimeout(() => { __privateSet(this, _childWatcherTTL, null); childWatcher.disconnect(); if (__privateGet(this, _childWatcher) === childWatcher) __privateSet(this, _childWatcher, null); __privateGet(this, _deps).logger.log( "[InfyOverlay]", "childWatcher timed out waiting for text span" ); }, __privateGet(this, _deps).config.INFY_OVERLAY.CHILD_WAIT_TIMEOUT)); const childWatcher = new MutationObserver((_, obs) => { const liveOverlay = document.getElementById(_InfyScrollOverlay.OVERLAY_ID); const span = liveOverlay && __privateMethod(this, _InfyScrollOverlay_instances, findTextSpan_fn).call(this, liveOverlay); if (span) { if (__privateGet(this, _childWatcherTTL) !== null) { clearTimeout(__privateGet(this, _childWatcherTTL)); __privateSet(this, _childWatcherTTL, null); } obs.disconnect(); if (__privateGet(this, _childWatcher) === obs) __privateSet(this, _childWatcher, null); try { __privateGet(this, _textObserver).observe(span, { characterData: true, childList: true, subtree: true }); } catch (e) { console.error( "[InfyOverlay] childWatcher _textObserver.observe failed:", e ); return; } __privateMethod(this, _InfyScrollOverlay_instances, updateCounter_fn).call(this, overlay); return; } }); __privateSet(this, _childWatcher, childWatcher); childWatcher.observe(overlay, { childList: true }); } }; initOverlay_fn = function(overlay) { __privateGet(this, _deps).logger.log("[InfyOverlay]", "_initOverlay called", { id: overlay == null ? void 0 : overlay.id, initialized: __privateGet(this, _initialized), pathname: window.location.pathname }); if (__privateGet(this, _initialized)) { if (__privateGet(this, _overlayEl) === overlay) { __privateGet(this, _deps).logger.log( "[InfyOverlay]", "Overlay re-inserted but already initialized; skipping re-init" ); return; } console.warn( "[InfyOverlay] Overlay element replaced -- re-initializing for new element" ); __privateSet(this, _initialized, false); __privateSet(this, _totalPages, null); if (__privateGet(this, _textObserver)) { __privateGet(this, _textObserver).disconnect(); __privateSet(this, _textObserver, null); } if (__privateGet(this, _keydownHandler)) { document.removeEventListener("keydown", __privateGet(this, _keydownHandler)); __privateSet(this, _keydownHandler, null); } if (__privateGet(this, _resizeHandler)) { window.removeEventListener("resize", __privateGet(this, _resizeHandler)); __privateSet(this, _resizeHandler, null); } __privateSet(this, _rewriteFailCount, 0); __privateSet(this, _positionSide, __privateMethod(this, _InfyScrollOverlay_instances, loadPosition_fn).call(this)); } __privateSet(this, _initialized, true); __privateSet(this, _overlayEl, overlay); __privateSet(this, _totalPages, __privateMethod(this, _InfyScrollOverlay_instances, getEromeTotalPages_fn).call(this)); try { __privateMethod(this, _InfyScrollOverlay_instances, styleOverlay_fn).call(this, overlay); } catch (e) { console.error("[InfyOverlay] _styleOverlay failed:", e); __privateSet(this, _initialized, false); } try { __privateMethod(this, _InfyScrollOverlay_instances, watchPageText_fn).call(this, overlay); } catch (e) { console.error("[InfyOverlay] _watchPageText failed:", e); __privateSet(this, _initialized, false); } try { __privateMethod(this, _InfyScrollOverlay_instances, setupResizeHandler_fn).call(this); } catch (e) { console.error("[InfyOverlay] _setupResizeHandler failed:", e); } try { __privateMethod(this, _InfyScrollOverlay_instances, setupKeyboardShortcut_fn).call(this); } catch (e) { console.error("[InfyOverlay] _setupKeyboardShortcut failed:", e); } }; measureLayout_fn = function() { const tabs = document.querySelector("#tabs"); const tabsRect = tabs ? tabs.getBoundingClientRect() : null; const tabsTop = tabsRect ? tabsRect.top : 150; let tabsRight = tabsRect ? tabsRect.right : 0; if (tabs && tabs.children.length > 0) { const childRights = []; for (const child of tabs.children) { const cs = getComputedStyle(child); if (cs.display === "none") continue; const r = child.getBoundingClientRect(); if (r.width === 0) continue; childRights.push(r.right); } if (childRights.length > 0) { tabsRight = Math.max(...childRights); } } const albumsGrid = document.querySelector("#albums"); if (!albumsGrid) { __privateGet(this, _deps).logger.log( "[InfyOverlay]", "_measureLayout: #albums not found, using fallback position" ); return null; } const gridRect = albumsGrid.getBoundingClientRect(); if (gridRect.width === 0) { __privateGet(this, _deps).logger.log( "[InfyOverlay]", "_measureLayout: #albums has zero width, using fallback position" ); return null; } return { tabsTop, tabsRight, leftX: gridRect.left, rightX: gridRect.right }; }; applyDynamicPosition_fn = function(overlay) { const layout = __privateMethod(this, _InfyScrollOverlay_instances, measureLayout_fn).call(this); const css = computeOverlayCss({ layout, viewportWidth: window.innerWidth, config: __privateGet(this, _deps).config.INFY_OVERLAY, positionSide: __privateGet(this, _positionSide) }); const s = overlay.style; s.setProperty("top", css.top, "important"); s.setProperty("right", css.right, "important"); s.setProperty("bottom", css.bottom, "important"); s.setProperty("left", css.left, "important"); }; setupResizeHandler_fn = function() { if (__privateGet(this, _resizeHandler)) { window.removeEventListener("resize", __privateGet(this, _resizeHandler)); } __privateSet(this, _resizeHandler, () => { const liveOverlay = document.getElementById(_InfyScrollOverlay.OVERLAY_ID); if (!liveOverlay) return; try { __privateMethod(this, _InfyScrollOverlay_instances, applyDynamicPosition_fn).call(this, liveOverlay); } catch (err) { console.warn("[InfyOverlay] resize handler error:", err); } }); const _capturedResizeHandler = __privateGet(this, _resizeHandler); window.addEventListener("resize", __privateGet(this, _resizeHandler)); window.addEventListener( "pagehide", () => { if (_capturedResizeHandler) { window.removeEventListener("resize", _capturedResizeHandler); } }, { once: true } ); }; setupKeyboardShortcut_fn = function() { if (__privateGet(this, _keydownHandler)) { document.removeEventListener("keydown", __privateGet(this, _keydownHandler)); } __privateSet(this, _keydownHandler, (e) => { var _a, _b; if (e.key !== "f" && e.key !== "F") return; const tag = (_a = document.activeElement) == null ? void 0 : _a.tagName; if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return; if ((_b = document.activeElement) == null ? void 0 : _b.isContentEditable) return; if (e.metaKey || e.ctrlKey || e.altKey) return; try { __privateGet(this, _deps).logger.performance("infy-overlay-flip", () => { const liveOverlay = document.getElementById(_InfyScrollOverlay.OVERLAY_ID); if (!liveOverlay || !__privateMethod(this, _InfyScrollOverlay_instances, isOverlayRendering_fn).call(this, liveOverlay)) { __privateGet(this, _deps).logger.log( "[InfyOverlay]", "overlay not rendering -- flip ignored (key not consumed)", { inDOM: !!liveOverlay, rendering: liveOverlay ? __privateMethod(this, _InfyScrollOverlay_instances, isOverlayRendering_fn).call(this, liveOverlay) : false } ); return; } e.preventDefault(); const _cycle = __privateGet(_InfyScrollOverlay, _VALID_POSITIONS); const _idx = _cycle.indexOf(__privateGet(this, _positionSide)); __privateSet(this, _positionSide, _cycle[(_idx + 1) % _cycle.length]); __privateGet(this, _deps).logger.log("[InfyOverlay]", `flip to ${__privateGet(this, _positionSide)}`); try { __privateGet(this, _deps).setValue( __privateGet(this, _deps).config.STORAGE_KEYS.OVERLAY_POSITION, __privateGet(this, _positionSide) ); } catch (saveErr) { console.warn("[InfyOverlay] failed to persist position:", saveErr); notifyStorageFailure("overlay position"); } __privateMethod(this, _InfyScrollOverlay_instances, applyDynamicPosition_fn).call(this, liveOverlay); }); } catch (err) { console.warn( "[InfyOverlay] keydown handler error (overlay may have been removed):", err ); } }); document.addEventListener("keydown", __privateGet(this, _keydownHandler)); const _capturedKeydownHandler = __privateGet(this, _keydownHandler); window.addEventListener( "pagehide", () => { document.removeEventListener("keydown", _capturedKeydownHandler); }, { once: true } ); }; waitForOverlay_fn = function() { if (__privateGet(this, _watchStarted)) return; __privateSet(this, _watchStarted, true); __privateSet(this, _bodyObserver, new MutationObserver((mutations) => { var _a; for (const mutation of mutations) { for (const node of mutation.removedNodes) { if (node.nodeType === Node.ELEMENT_NODE && node.id === _InfyScrollOverlay.OVERLAY_ID) { __privateGet(this, _deps).logger.log( "[InfyOverlay]", "overlay removed from DOM -- marking uninitialized" ); __privateSet(this, _initialized, false); __privateSet(this, _overlayEl, null); } } for (const node of mutation.addedNodes) { if (node.nodeType === Node.ELEMENT_NODE && node.id === _InfyScrollOverlay.OVERLAY_ID) { __privateGet(this, _deps).logger.log( "[InfyOverlay]", "overlay detected via bodyObserver addedNodes -- calling _initOverlay" ); __privateMethod(this, _InfyScrollOverlay_instances, initOverlay_fn).call(this, node); return; } if (node.nodeType === Node.ELEMENT_NODE) { const nested = (_a = node.querySelector) == null ? void 0 : _a.call(node, `#${_InfyScrollOverlay.OVERLAY_ID}`); if (nested) { __privateGet(this, _deps).logger.log( "[InfyOverlay]", "overlay detected as nested child in addedNodes -- calling _initOverlay", node.tagName, node.id || node.className ); __privateMethod(this, _InfyScrollOverlay_instances, initOverlay_fn).call(this, nested); return; } } } } })); try { __privateGet(this, _bodyObserver).observe(document.body, { childList: true, subtree: true }); } catch (err) { console.error("[InfyOverlay] Failed to observe document.body:", err); __privateSet(this, _watchStarted, false); setTimeout(() => __privateMethod(this, _InfyScrollOverlay_instances, waitForOverlay_fn).call(this), 2e3); return; } const existing = document.getElementById(_InfyScrollOverlay.OVERLAY_ID); if (existing) { __privateMethod(this, _InfyScrollOverlay_instances, initOverlay_fn).call(this, existing); } else { __privateSet(this, _waitTimeoutId, setTimeout(() => { __privateSet(this, _waitTimeoutId, null); __privateGet(this, _deps).logger.performance("infy-overlay-wait-timeout", () => { const atTimeout = document.getElementById(_InfyScrollOverlay.OVERLAY_ID); console.warn( "[InfyOverlay] Timed out waiting for overlay element -- will continue watching", { topLevelFound: !!atTimeout, initialized: __privateGet(this, _initialized), pathname: window.location.pathname } ); }); }, __privateGet(this, _deps).config.INFY_OVERLAY.WAIT_TIMEOUT)); } }; reanchorBodyObserver_fn = function() { if (!__privateGet(this, _bodyObserver)) return; try { __privateGet(this, _bodyObserver).disconnect(); __privateGet(this, _bodyObserver).observe(document.body, { childList: true, subtree: true }); __privateGet(this, _deps).logger.log( "[InfyOverlay]", "re-anchored bodyObserver to new document.body" ); } catch (err) { console.warn("[InfyOverlay] _reanchorBodyObserver failed:", err); } const existing = document.getElementById(_InfyScrollOverlay.OVERLAY_ID); if (existing && !__privateGet(this, _initialized)) { __privateGet(this, _deps).logger.log( "[InfyOverlay]", "overlay found in new body after re-anchor -- initializing" ); __privateMethod(this, _InfyScrollOverlay_instances, initOverlay_fn).call(this, existing); } }; onNavigation_fn = function(source) { __privateGet(this, _deps).logger.log( "[InfyOverlay]", `navigation signal: ${source}`, window.location.pathname ); __privateSet(this, _initialized, false); __privateSet(this, _overlayEl, null); __privateSet(this, _pageFromEvent, null); __privateSet(this, _lastSearch, null); __privateMethod(this, _InfyScrollOverlay_instances, reanchorBodyObserver_fn).call(this); }; onAppend_fn = function(source, url) { const parsed = parsePageFromUrl(url); if (parsed !== null) __privateSet(this, _pageFromEvent, parsed); __privateGet(this, _deps).logger.log("[InfyOverlay]", `append signal: ${source}`, { url, parsed }); const el = document.getElementById(_InfyScrollOverlay.OVERLAY_ID); if (!el) return; if (!__privateGet(this, _initialized)) { __privateMethod(this, _InfyScrollOverlay_instances, initOverlay_fn).call(this, el); return; } __privateMethod(this, _InfyScrollOverlay_instances, updateCounter_fn).call(this, el); try { __privateMethod(this, _InfyScrollOverlay_instances, applyDynamicPosition_fn).call(this, el); } catch (e) { __privateGet(this, _deps).logger.log("[InfyOverlay]", "append: reposition failed", e); } }; __publicField(_InfyScrollOverlay, "OVERLAY_ID", "infy-scroll-overlay"); __publicField(_InfyScrollOverlay, "NUMBER_ID", "infy-scroll-overlay-number"); __publicField(_InfyScrollOverlay, "PAGE_PATTERN", /Page\s+(\d+)\s*\/\s*(\d+)/i); __publicField(_InfyScrollOverlay, "APPEND_SETTLE_MS", 250); __privateAdd(_InfyScrollOverlay, _VALID_POSITIONS, [ "right", "left", "bottom-left", "bottom-right" ]); let InfyScrollOverlay = _InfyScrollOverlay; function getVidData(vidDiv) { const sourceEl = vidDiv.querySelector(".video video source") || vidDiv.querySelector("video source") || vidDiv.querySelector("source"); const urL = sourceEl ? sourceEl.src : ""; var bi = vidDiv.getAttribute("data-poster") || ""; var vjsPoster = vidDiv.querySelector(".vjs-poster"); if (!bi && vjsPoster && vjsPoster.style && vjsPoster.style.backgroundImage) { bi = vjsPoster.style.backgroundImage.slice(4, -1).replace(/"/g, ""); } if (!bi) { var nativeVideo = vidDiv.querySelector("video"); if (nativeVideo) { bi = nativeVideo.getAttribute("poster") || ""; } } if (!bi && vjsPoster) { var computed = getComputedStyle(vjsPoster).backgroundImage; if (computed && computed !== "none") { bi = computed.slice(4, -1).replace(/"/g, ""); } } if (!bi && urL) { bi = derivePosterFromVideoUrl(urL); } return { urL, bi }; } const ROME_CONFIG = { DEBUG: false, TIMEOUTS: { DOM_RETRY: 250, DOM_MAX_DELAY: 2e3, WATCHED_CHECK: 5e3, FILTER_DEBOUNCE: 150, INITIAL_DELAY: 100, CHUNK_PROCESSING: 16, STORAGE_RETRY_BASE: 1e3, STORAGE_MAX_RETRIES: 3 }, SELECTORS: { USER_NAME: "#user_name", ALBUMS_CONTAINER: "#albums", INFY_APPEND_ROOT: "#page", DISCLAIMER: "#disclaimer", ALBUM_ITEMS: "#albums > div", ALBUM_TITLE: ".album-title", ALBUM_USER: ".album-user", THUMBNAIL_CONTAINER: ".album-thumbnail-container" }, STORAGE_KEYS: { BANNED_ALBUMS: "bannedAlbums", WATCHED_VIDEOS: "watchedVideos", SESSION_STATE: "eromeSessionState", OVERLAY_POSITION: "infyOverlayPosition" }, CACHE: { MAX_SIZE: 1e3, MAX_AGE: 3 * 60 * 1e3, CHUNK_SIZE: 100, MEMORY_CHECK_INTERVAL: 30 * 1e3 }, FALLBACK_SELECTORS: [".username", ".user-info", ".profile-name", "body"], INFY_OVERLAY: { BACKGROUND: "rgba(0, 0, 0, 0.75)", TEXT_COLOR: "rgba(255, 255, 255, 0.9)", TOP_GAP: 8, SIDE_GAP: 8, DEFAULT_SIDE: "right", PADDING: "clamp(4px, 0.4vw, 9px) clamp(8px, 0.7vw, 14px)", BORDER_RADIUS: "7px", FONT_SIZE: "clamp(14px, 1.2vw, 22px)", FONT_FAMILY: "monospace", WAIT_TIMEOUT: 15e3, CHILD_WAIT_TIMEOUT: 5e3 } }; function wrap(fn, context = "Rome") { return function(...args) { try { return fn.apply(this, args); } catch (error) { console.error(`${context} Error:`, error); return null; } }; } function createLogger(config) { function log(context, message, data = {}) { if (config.DEBUG) { console.group(`Rome ${context}`); console.log(message, data); console.groupEnd(); } } function measurePerformance(label, fn) { if (config.DEBUG) { performance.mark(`rome-${label}-start`); } const result = fn(); if (config.DEBUG) { performance.mark(`rome-${label}-end`); performance.measure(`rome-${label}`, `rome-${label}-start`, `rome-${label}-end`); console.log( `Rome Performance: ${label}`, performance.getEntriesByName(`rome-${label}`)[0] ); } return result; } function setDebug(enabled) { config.DEBUG = enabled; console.log(`Rome Debug Mode: ${enabled ? "Enabled" : "Disabled"}`); } return { log, performance: measurePerformance, setDebug }; } function createStorage({ config, logger, getValue, setValue }) { async function setWithRetry(key, value, maxRetries = config.TIMEOUTS.STORAGE_MAX_RETRIES) { for (let i = 0; i < maxRetries; i++) { try { setValue(key, value); logger.log("Storage", `Successfully saved ${key}`, { attempt: i + 1 }); return true; } catch (error) { logger.log("Storage", `Failed to save ${key}`, { attempt: i + 1, error }); if (i === maxRetries - 1) { console.error( `Rome: Failed to save ${key} after ${maxRetries} attempts:`, error ); throw error; } const delay = config.TIMEOUTS.STORAGE_RETRY_BASE * Math.pow(2, i); await new Promise((resolve) => setTimeout(resolve, delay)); } } throw new Error(`Rome: setWithRetry(${key}) made no attempts (maxRetries=${maxRetries})`); } function getWithFallback(key, defaultValue = null) { try { const value = getValue(key); logger.log("Storage", `Retrieved ${key}`, { value }); return value !== void 0 ? value : defaultValue; } catch (error) { console.warn(`Rome: Failed to retrieve ${key}, using default:`, error); return defaultValue; } } return { setWithRetry, getWithFallback }; } function checkMemoryUsage(logger) { const memory = performance.memory; if (memory) { const used = memory.usedJSHeapSize; const limit = memory.jsHeapSizeLimit; const usagePercent = used / limit * 100; logger.log("Memory", "Memory usage check", { used: `${Math.round(used / 1024 / 1024)}MB`, limit: `${Math.round(limit / 1024 / 1024)}MB`, percentage: `${usagePercent.toFixed(1)}%` }); if (used > limit * 0.8) { console.warn( "Rome: High memory usage detected:", `${usagePercent.toFixed(1)}%` ); return true; } } return false; } const ALBUM_PAGE = /^\/a\/[A-Za-z0-9]+$/; const EXPLORE_NEW = /^\/explore\/new/; function detectPage(location, document2, logger) { const pathname = location.pathname; const hasPageParam = new URLSearchParams(location.search).has("page"); const hasPagination = document2.querySelector(".pagination") !== null; if (ALBUM_PAGE.test(pathname)) return "ALBUM"; if (EXPLORE_NEW.test(pathname) && (hasPageParam || hasPagination)) return "EXPLORE"; if (hasPagination && (pathname.includes("/explore/") || pathname.includes("/search") || pathname.includes("/user/"))) { logger.log("URLDetection", "Detected paginated page", { pathname, hasPagination }); return "EXPLORE"; } if (/^\/[^\/\s]+$/.test(pathname) && !pathname.match(/\.(html|php|aspx?)$/i)) { logger.log("URLDetection", "Detected user profile page", { pathname }); return "EXPLORE"; } return "OTHER"; } class JabronioStoreFallback { constructor() { __publicField(this, "state"); __publicField(this, "stateSubject"); __publicField(this, "eventSubject"); this.state = { filterExclude: false, filterExcludeWords: "", filterInclude: false, filterIncludeWords: "", showPhotos: true, filterPrivate: false, filterPublic: false }; const subscribers = []; this.stateSubject = { subscribe(fn) { if (typeof fn === "function") { subscribers.push(fn); setTimeout(fn, 100); } }, next(value) { subscribers.forEach((fn) => fn(value)); } }; this.eventSubject = { subscribe() { } }; console.warn("Rome: Using JabronioStore fallback"); } add(key, value) { if (!(key in this.state)) { this.state[key] = value; } } } function safeLibraryAccess() { try { const w = window; const jabronioutfit = w.jabronioutfit || {}; return { JabronioStore: jabronioutfit.JabronioStore || JabronioStoreFallback }; } catch (error) { console.error("Rome: Failed to access external libraries:", error); return null; } } function createVjsObserverCallback({ countFn, ensureFn, getObserver }) { let noOpCount = 0; let errorCount = 0; let rafPending = false; const callback = () => { if (rafPending) return; rafPending = true; requestAnimationFrame(() => { var _a, _b; rafPending = false; if (!getObserver()) return; try { const before = countFn(); ensureFn(); const after = countFn(); errorCount = 0; if (after === before) { noOpCount++; if (noOpCount >= 10) (_a = getObserver()) == null ? void 0 : _a.disconnect(); } else { noOpCount = 0; } } catch (e) { errorCount++; console.warn( "[AlbumPageManager] vjs observer callback error (", errorCount, "):", e ); if (errorCount >= 3) { console.warn( "[AlbumPageManager] Disconnecting vjsObserver after repeated errors" ); (_b = getObserver()) == null ? void 0 : _b.disconnect(); } } }); }; return { callback, getNoOpCount: () => noOpCount, isRafPending: () => rafPending }; } class EromeUnifiedState { constructor(deps) { __publicField(this, "deps"); __publicField(this, "persistentStore"); __publicField(this, "sessionState"); this.deps = deps; this.persistentStore = null; this.sessionState = this.loadSessionState(); this.initializePersistentStore(); } initializePersistentStore() { if (this.deps.currentPage !== "EXPLORE") return; try { this.persistentStore = new this.deps.libs.JabronioStore(); } catch (error) { console.error("Rome: Failed to initialize JabronioStore:", error); this.persistentStore = null; } } loadSessionState() { const defaults = { showPhotosSession: true, showSBSSession: false, videoRotation: 0, hidePhotoOnlyAlbums: false }; return defaults; } saveSessionState() { try { localStorage.setItem( this.deps.config.STORAGE_KEYS.SESSION_STATE, JSON.stringify(this.sessionState) ); } catch (error) { console.warn("Rome: Failed to save session state:", error); } } } function createMultiVideoPlayback({ config, logger, perf }) { function addProperID() { perf.mark("addProperID-start"); let count = 0; const containerMapping = []; logger.log("MultiVideo", "addProperID - starting ID assignment"); document.querySelectorAll(".media-group img.img-front, .media-group video").forEach((item) => { count++; const mediaGroup = item.closest(".media-group"); const oldId = mediaGroup.id; mediaGroup.removeAttribute("id"); mediaGroup.id = String(count); containerMapping.push(count); logger.log("MultiVideo", `Assigned ID ${count} to media group`, { oldId, mediaGroup }); }); logger.log("MultiVideo", "All media groups after ID assignment"); document.querySelectorAll(".media-group").forEach((group, index) => { logger.log("MultiVideo", `Media group ${index + 1}`, { id: group.id, group }); }); window.romeContainerMapping = containerMapping; logger.log("MultiVideo", "Container mapping for videos", { containerMapping }); perf.mark("addProperID-end"); perf.measure("addProperID", "addProperID-start", "addProperID-end"); return containerMapping; } function playerVersion(data, videoIndex = 1, targetContainer = null) { try { logger.log("Video", "playerVersion called", { data, videoIndex }); const mediaDiv = document.createElement("div"); const videoDiv = document.createElement("div"); mediaDiv.classList.add("media-group"); videoDiv.classList.add("video"); const mainMedia = targetContainer || document.querySelector(".media-group"); logger.log("Video", "Looking for main media container", { found: mainMedia }); logger.log("Video", "Found main media element", { mainMedia }); if (!mainMedia || !mainMedia.parentElement) { console.error("Rome: No main media element found for video placement"); return; } mainMedia.parentElement.insertBefore(mediaDiv, mainMedia.nextSibling); mediaDiv.appendChild(videoDiv); logger.log("Video", "Created video container structure"); logger.log("Video", "Container hierarchy", { mainMediaParent: mainMedia.parentElement.tagName, mediaDiv, videoDiv, mediaParentChildren: Array.from(mainMedia.parentElement.children).map( (c) => c.className ) }); const video = document.createElement("video"); video.controls = true; video.preload = "metadata"; video.classList.add("rome-video"); logger.log("Video", "Created video element with controls"); const preloadObs = getRomePreloadObserver(); if (preloadObs) preloadObs.observe(video); const playbackObs = getRomePlaybackObserver(); if (playbackObs) playbackObs.observe(video); if (data.bi) { video.poster = data.bi; logger.log("Video", "Set video poster", { poster: data.bi }); } const src = document.createElement("source"); src.setAttribute("src", data.urL); src.setAttribute("type", "video/mp4"); video.appendChild(src); videoDiv.appendChild(video); logger.log("Video", "Added source and appended video to DOM"); video.addEventListener("loadedmetadata", () => { const aspectRatio = video.videoWidth / video.videoHeight; const isHorizontal = aspectRatio >= 1; if (isHorizontal) { video.style.cssText += "width: 100% !important; max-width: 800px !important; height: auto !important; display: block !important; position: relative !important; object-fit: contain !important; margin: 0 auto !important; background-color: #000000 !important;"; } else { video.style.cssText += "width: 100% !important; max-width: 600px !important; max-height: 500px !important; height: auto !important; display: block !important; position: relative !important; object-fit: contain !important; margin: 0 auto !important; background-color: #000000 !important;"; } logger.log( "Video", `Applied ${isHorizontal ? "horizontal" : "vertical"} video sizing`, { aspectRatio: aspectRatio.toFixed(2) } ); }); video.style.cssText += "width: 100% !important; max-width: 800px !important; max-height: 500px !important; height: auto !important; display: block !important; position: relative !important; object-fit: contain !important; margin: 0 auto !important; background-color: #000000 !important;"; } catch (error) { console.error("Rome: Error creating player version:", error); } } function removeWithData(vidDiv) { const data = getVidData(vidDiv); if (data.urL) { logger.log("MultiVideo", "Removing original video div", { vidDiv }); vidDiv.remove(); return data; } else { console.warn("Rome: Failed to extract video data, skipping removal:", vidDiv); return null; } } function videoCleanseReplace(_vidDivs) { perf.mark("videoCleanseReplace-start"); const vidDivs = document.getElementsByClassName("video"); logger.log("MultiVideo", "Started video cleansing for multi-playback"); addProperID(); logger.log("MultiVideo", "Assigned proper IDs to media groups"); Array.from(vidDivs).forEach((vidDiv, index) => { logger.log("MultiVideo", `Processing video ${index + 1}`, { vidDiv }); const parentMediaGroup = vidDiv.closest(".media-group"); const data = removeWithData(vidDiv); logger.log("MultiVideo", `Video ${index + 1} data`, { data }); if (data && data.urL) { logger.log("MultiVideo", `Creating player for video ${index + 1}`, { url: data.urL }); playerVersion(data, index + 1, parentMediaGroup); logger.log("MultiVideo", `Player created for video ${index + 1}`); } else { console.warn(`Rome: Skipping video ${index + 1} - no valid URL found`); } }); logger.log("MultiVideo", "Cleaning up original video containers"); Array.from(document.querySelectorAll(".video-lg")).forEach((lgVid) => { logger.log("MultiVideo", "Removing large video container", { lgVid }); lgVid.parentElement.remove(); }); Array.from(document.querySelectorAll(".media-group video:not(.rome-video)")).forEach( (video, index) => { logger.log("MultiVideo", `Removing original video ${index + 1}`, { video }); video.remove(); } ); Array.from(document.querySelectorAll(".media-group .video:empty")).forEach( (emptyVideo) => { logger.log("MultiVideo", "Removing empty video container", { emptyVideo }); emptyVideo.remove(); } ); const messageDiv = document.querySelector("#user_message"); if (messageDiv && !messageDiv.textContent.trim()) { messageDiv.textContent = "Multi-video playback enabled"; messageDiv.style.display = "block"; setTimeout(() => { if (messageDiv.textContent === "Multi-video playback enabled") { messageDiv.style.display = "none"; messageDiv.textContent = ""; } }, 1e3); } perf.mark("videoCleanseReplace-end"); perf.measure( "videoCleanseReplace", "videoCleanseReplace-start", "videoCleanseReplace-end" ); perf.mark("bootstrap-end"); perf.measure("bootstrap", "script-start", "bootstrap-end"); logger.log("MultiVideo", "Multi-video setup cleanup completed"); if (config.DEBUG) { setTimeout(() => { logger.log("MultiVideo", "Checking video health 2 seconds after setup"); const videos = document.querySelectorAll("video"); videos.forEach((video, index) => { logger.log("MultiVideo", `Video ${index + 1} health check`, { src: video.currentSrc, readyState: video.readyState, networkState: video.networkState, paused: video.paused, ended: video.ended, error: video.error, canPlay: !video.error && video.readyState >= 3 }); }); }, 2e3); } } return { videoCleanseReplace }; } class HybridContentFilter { constructor(jabroniStore, deps) { __publicField(this, "deps"); __publicField(this, "jabroniStore"); __publicField(this, "regexPatterns"); __publicField(this, "censoredIMG"); __publicField(this, "filterTimeout"); __publicField(this, "albumCache"); __publicField(this, "cacheTimestamps"); __publicField(this, "lastAlbumCount"); __publicField(this, "isProcessing"); __publicField(this, "processingQueue"); __publicField(this, "unicodeToAsciiMap"); __publicField(this, "_patternVersion"); __publicField(this, "_memoryMonitorId", null); __publicField(this, "_albumMutationObserver", null); __publicField(this, "_photoToggle"); __publicField(this, "_compiledPatterns"); __publicField(this, "_lastPatternUpdate"); this.deps = deps; this.jabroniStore = jabroniStore; this.regexPatterns = this.deps.storage.getWithFallback( this.deps.config.STORAGE_KEYS.BANNED_ALBUMS, [] ); this.censoredIMG = "https://www.onlygfx.com/wp-content/uploads/2018/04/censored-stamp-2.png"; this.filterTimeout = null; this.albumCache = new Map(); this.cacheTimestamps = new Map(); this.lastAlbumCount = 0; this.isProcessing = false; this.processingQueue = []; this._patternVersion = 0; this.unicodeToAsciiMap = this.buildUnicodeMap(); this.startMemoryMonitoring(); window.addEventListener("beforeunload", () => { this.stopMemoryMonitoring(); }); } buildUnicodeMap() { const map = new Map(); const asciiLower = "abcdefghijklmnopqrstuvwxyz"; const asciiUpper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; const digits = "0123456789"; const ranges = [ { upper: 119808, lower: 119834 }, { upper: 119860, lower: 119886 }, { upper: 119912, lower: 119938 }, { upper: 119964, lower: 119990 }, { upper: 120016, lower: 120042 }, { upper: 120068, lower: 120094 }, { upper: 120120, lower: 120146 }, { upper: 120172, lower: 120198 }, { upper: 120224, lower: 120250 }, { upper: 120276, lower: 120302 }, { upper: 120328, lower: 120354 }, { upper: 120380, lower: 120406 }, { upper: 120432, lower: 120458 } ]; ranges.forEach(({ upper, lower }) => { for (let i = 0; i < 26; i++) { map.set(String.fromCodePoint(upper + i), asciiUpper[i]); map.set(String.fromCodePoint(lower + i), asciiLower[i]); } }); for (let i = 0; i < 26; i++) { map.set(String.fromCodePoint(65313 + i), asciiUpper[i]); map.set(String.fromCodePoint(65345 + i), asciiLower[i]); } for (let i = 0; i < 10; i++) { map.set(String.fromCodePoint(65296 + i), digits[i]); } for (let i = 0; i < 26; i++) { map.set(String.fromCodePoint(9398 + i), asciiUpper[i]); map.set(String.fromCodePoint(9424 + i), asciiLower[i]); } map.set("", "h"); return map; } normalizeUnicode(text) { let result = ""; for (const char of text) { result += this.unicodeToAsciiMap.get(char) || char; } return result; } startMemoryMonitoring() { this._memoryMonitorId = setInterval(() => { if (this.deps.checkMemoryUsage(this.deps.logger)) { this.clearCache(); } this.cleanExpiredCache(); }, this.deps.config.CACHE.MEMORY_CHECK_INTERVAL); } stopMemoryMonitoring() { if (this._memoryMonitorId) { clearInterval(this._memoryMonitorId); this._memoryMonitorId = null; } } cleanup() { this.stopMemoryMonitoring(); if (this._albumMutationObserver) { this._albumMutationObserver.disconnect(); this._albumMutationObserver = null; } this.deps.logger.log("HybridContentFilter", "Cleaned up"); } clearCache() { this.albumCache.clear(); this.cacheTimestamps.clear(); console.log("Rome: Cache cleared due to memory pressure"); } cleanExpiredCache() { const now = Date.now(); for (const [key, timestamp] of this.cacheTimestamps) { if (now - timestamp > this.deps.config.CACHE.MAX_AGE) { this.albumCache.delete(key); this.cacheTimestamps.delete(key); } } } initialize() { if (!this.jabroniStore) { console.error("Rome: JabroniOutfit not available, using regex filtering only"); this.addRegexUI(); this.applyAllFilters(); this.setupAlbumMutationObserver(); return; } try { this.jabroniStore.stateSubject.subscribe(() => this.debouncedApplyFilters()); } catch (error) { console.error("Rome: JabroniOutfit state subscription failed:", error); this.jabroniStore = null; } this.addRegexUI(); this.applyAllFilters(); this.setupAlbumMutationObserver(); } setupAlbumMutationObserver() { if (this._albumMutationObserver) { this._albumMutationObserver.disconnect(); this._albumMutationObserver = null; } const target = document.querySelector(this.deps.config.SELECTORS.INFY_APPEND_ROOT) || document.querySelector(this.deps.config.SELECTORS.ALBUMS_CONTAINER); if (!target) { console.warn("[HybridContentFilter] Append root missing at init -- mutation observer not attached"); return; } this._albumMutationObserver = this.deps.createAlbumMutationObserver({ target, itemSelector: this.deps.config.SELECTORS.ALBUM_ITEMS, onItemsAdded: this.deps.wrap( () => this.debouncedApplyFilters(), "HybridContentFilter.onItemsAdded" ) }); } debouncedApplyFilters() { clearTimeout(this.filterTimeout ?? void 0); this.filterTimeout = setTimeout(() => { this.applyAllFiltersInChunks(); }, this.deps.config.TIMEOUTS.FILTER_DEBOUNCE); } setPhotoToggle(photoToggle) { this._photoToggle = photoToggle; } addRegexUI() { this.createSettingsModal(); this.createNavbarButton(); this.createQuickAddInput(); } createSettingsModal() { const modalHTML = ` <div id="romeModal" class="rome-modal" style="display: none;"> <div class="rome-modal-content"> <div class="rome-modal-header"> <span class="rome-close-modal">×</span> <span class="rome-modal-title">Rome Settings - Content Filtering</span> </div> <div class="rome-modal-body"> <div class="rome-settings-section"> <h4>Debug Mode</h4> <label style="display: flex; align-items: center; gap: 10px; cursor: pointer; color: #ccc; font-size: 14px;"> <input type="checkbox" id="romeDebugToggle" style="width: 18px; height: 18px; accent-color: #eb6395; cursor: pointer;"> Enable debug logging to console </label> </div> <div class="rome-settings-section"> <h4>Custom Blocklist (Regex Support)</h4> <p style="color: #ccc; font-size: 12px; margin-bottom: 10px;"> Add words or patterns to block. Supports wildcards (*) and regex patterns. </p> <div class="blocklist-display" id="regexBlocklistDisplay"></div> <div class="add-word-container"> <input type="text" id="regexBlockWord" placeholder="Add word/pattern to block..."> <button id="addRegexBlock">Add</button> </div> </div> </div> </div> </div> `; document.body.insertAdjacentHTML("beforeend", modalHTML); this.setupModalEvents(); this.setupRegexEvents(); this.updateRegexDisplay(); } createNavbarButton() { const scriptMenuBtn = document.createElement("div"); scriptMenuBtn.classList.add("sp", "no-select", "rome-menu-btn"); scriptMenuBtn.setAttribute("data-type", "RomeMenu"); scriptMenuBtn.title = "Rome Settings"; scriptMenuBtn.innerHTML = `⚙️ Rome`; scriptMenuBtn.style.cssText = ` cursor: pointer; background: rgba(235, 99, 149, 0.8); color: white; border-radius: 4px; font-weight: bold; margin: 0 5px; user-select: none; transition: background 0.3s; `; const xChild = document.querySelector(".container > div > div:nth-child(6)"); if (xChild) xChild.parentNode.insertBefore(scriptMenuBtn, xChild); scriptMenuBtn.addEventListener("click", () => { const modal = document.getElementById("romeModal"); if (modal) { modal.style.display = "block"; this.updateRegexDisplay(); const debugToggle = document.getElementById("romeDebugToggle"); if (debugToggle) debugToggle.checked = this.deps.config.DEBUG; } }); scriptMenuBtn.addEventListener("mouseenter", () => { scriptMenuBtn.style.background = "rgba(235, 99, 149, 1)"; }); scriptMenuBtn.addEventListener("mouseleave", () => { scriptMenuBtn.style.background = "rgba(235, 99, 149, 0.8)"; }); } createQuickAddInput() { const quickAddHTML = ` <div id="romeQuickAdd" class="rome-quick-add"> <h3>Quick Add Blocklist</h3> <textarea id="quickAddTextarea" placeholder="Enter words to block (one per line)..."></textarea> <div class="instructions"> <kbd>Q</kbd> to open • <kbd>Cmd+Enter</kbd> to add all • <kbd>Escape</kbd> to cancel </div> </div> `; document.body.insertAdjacentHTML("beforeend", quickAddHTML); this.setupQuickAddEvents(); } setupQuickAddEvents() { const quickAdd = document.getElementById("romeQuickAdd"); const textarea = document.getElementById("quickAddTextarea"); if (!quickAdd || !textarea) return; document.addEventListener("keydown", (e) => { const t = e.target; if (t.tagName === "INPUT" || t.tagName === "TEXTAREA") return; const modal = document.getElementById("romeModal"); if (modal && modal.style.display === "block") return; if (e.key === "q" || e.key === "Q") { e.preventDefault(); this.showQuickAdd(); } }); textarea.addEventListener("keydown", (e) => { if (e.key === "Enter" && e.metaKey) { e.preventDefault(); this.processQuickAddWords(); } else if (e.key === "Escape") { e.preventDefault(); this.hideQuickAdd(); } }); quickAdd.addEventListener("click", (e) => { if (e.target === quickAdd) { this.hideQuickAdd(); } }); } showQuickAdd() { const quickAdd = document.getElementById("romeQuickAdd"); const textarea = document.getElementById("quickAddTextarea"); if (quickAdd && textarea) { quickAdd.style.display = "block"; textarea.focus(); textarea.value = ""; } } hideQuickAdd() { const quickAdd = document.getElementById("romeQuickAdd"); if (quickAdd) { quickAdd.style.display = "none"; } } processQuickAddWords() { const textarea = document.getElementById("quickAddTextarea"); if (!textarea) return; const words = textarea.value.split("\n").map((word) => word.trim().toLowerCase()).filter((word) => word && !this.regexPatterns.includes(word)); if (words.length === 0) { this.hideQuickAdd(); return; } this.regexPatterns.push(...words); this._patternVersion++; this.deps.storage.setWithRetry( this.deps.config.STORAGE_KEYS.BANNED_ALBUMS, this.regexPatterns ).catch((err) => { console.error("Rome: storage write failed", err); this.deps.notifyStorageFailure("filter patterns"); }); this.updateRegexDisplay(); this.debouncedApplyFilters(); this.showQuickAddFeedback( `Added ${words.length} word${words.length === 1 ? "" : "s"} to blocklist` ); this.hideQuickAdd(); } showQuickAddFeedback(message) { const feedback = document.createElement("div"); feedback.textContent = message; feedback.style.cssText = ` position: fixed; top: 20px; right: 20px; background: linear-gradient(135deg, #26de81 0%, #20bf6b 100%); color: white; padding: 12px 20px; border-radius: 8px; font-weight: bold; z-index: 100000; box-shadow: 0 4px 12px rgba(38, 222, 129, 0.3); animation: slideIn 0.3s ease-out; `; document.body.appendChild(feedback); setTimeout(() => { if (feedback.parentNode) { feedback.parentNode.removeChild(feedback); } }, 3e3); } setupModalEvents() { const modal = document.getElementById("romeModal"); const closeBtn = document.querySelector(".rome-close-modal"); if (closeBtn) { closeBtn.addEventListener("click", () => { modal.style.display = "none"; }); } window.addEventListener("click", (event) => { if (event.target === modal) { modal.style.display = "none"; } }); document.addEventListener("keydown", (e) => { if (e.key === "Escape" && modal.style.display === "block") { modal.style.display = "none"; } }); const debugToggle = document.getElementById("romeDebugToggle"); if (debugToggle) { debugToggle.addEventListener("change", (e) => { this.deps.logger.setDebug(e.target.checked); }); } } setupRegexEvents() { const addButton = document.getElementById("addRegexBlock"); const input = document.getElementById("regexBlockWord"); if (addButton && input) { addButton.addEventListener("click", () => { const word = input.value.trim().toLowerCase(); if (word && !this.regexPatterns.includes(word)) { this.regexPatterns.push(word); this._patternVersion++; this.deps.storage.setWithRetry( this.deps.config.STORAGE_KEYS.BANNED_ALBUMS, this.regexPatterns ).catch((err) => { console.error("Rome: storage write failed", err); this.deps.notifyStorageFailure("filter patterns"); }); this.updateRegexDisplay(); this.debouncedApplyFilters(); input.value = ""; } }); input.addEventListener("keypress", (e) => { if (e.key === "Enter") { addButton.click(); } }); } } updateRegexDisplay() { const display = document.getElementById("regexBlocklistDisplay"); if (!display) return; display.innerHTML = ""; if (this.regexPatterns.length === 0) { const emptyMsg = document.createElement("div"); emptyMsg.style.cssText = "color: #999; font-style: italic;"; emptyMsg.textContent = "No blocked words yet"; display.appendChild(emptyMsg); return; } this.regexPatterns.forEach((pattern, index) => { const item = document.createElement("div"); item.className = "blocklist-item"; const span = document.createElement("span"); span.textContent = pattern; const button = document.createElement("button"); button.textContent = "Remove"; button.addEventListener("click", () => this.removePattern(index)); item.appendChild(span); item.appendChild(button); display.appendChild(item); }); } removePattern(index) { this.regexPatterns.splice(index, 1); this._patternVersion++; this.deps.storage.setWithRetry( this.deps.config.STORAGE_KEYS.BANNED_ALBUMS, this.regexPatterns ).catch((err) => { console.error("Rome: storage write failed", err); this.deps.notifyStorageFailure("filter patterns"); }); this.updateRegexDisplay(); this.debouncedApplyFilters(); } applyAllFilters() { setTimeout(() => this.applyRegexFiltering(), 100); } applyAllFiltersInChunks() { const albums = this.getAlbums(); this.processAlbumsInChunks(albums); } processAlbumsInChunks(albums) { if (this.isProcessing) { this.processingQueue = albums; return; } this.isProcessing = true; let index = 0; const processChunk = () => { const chunk = albums.slice(index, index + this.deps.config.CACHE.CHUNK_SIZE); chunk.forEach((albumData) => { this.applyCensoringToAlbum(albumData); }); index += this.deps.config.CACHE.CHUNK_SIZE; if (index < albums.length) { requestAnimationFrame(processChunk); } else { this.isProcessing = false; if (this._photoToggle) this._photoToggle.applyPhotoOnlyFilter(); if (this.processingQueue.length > 0) { const queuedAlbums = this.processingQueue; this.processingQueue = []; this.processAlbumsInChunks(queuedAlbums); } } }; processChunk(); } applyCensoringToAlbum(albumData) { const { albumTitle, albumUser, albumUrl, element } = albumData; const normalizedTitle = this.normalizeUnicode(albumTitle); const normalizedUser = this.normalizeUnicode(albumUser); const normalizedUrl = this.normalizeUnicode(albumUrl || ""); const patterns = this.getCompiledPatterns(); const isBlocked = patterns.some( (re) => ( re.test(albumTitle) || re.test(albumUser) || re.test(normalizedTitle) || re.test(normalizedUser) || re.test(albumUrl) || re.test(normalizedUrl) || normalizedTitle.split(/[ _.:;?!~,`"&|()<>{}\[\]\r\n/\\]+/g).some((word) => re.test(word)) || normalizedUser.split(/[ _.:;?!~,`"&|()<>{}\[\]\r\n/\\]+/g).some((word) => re.test(word)) || normalizedUrl.split(/[/_\-?&=]+/g).some((word) => re.test(word)) ) ); if (isBlocked) { this.censorAlbum(element || albumTitle); } } getCompiledPatterns() { if (!this._compiledPatterns || this._lastPatternUpdate !== this._patternVersion) { this._compiledPatterns = this.regexPatterns.map((pattern) => { try { let pat = pattern.trim(); if (pat.length > 200) { console.warn(`Rome: Skipping blocklist pattern exceeding 200 chars`); return /(?!.*)/; } if (pat.includes("*")) { pat = pat.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/\\\*/g, "[^\\s]*"); if (pat.includes(" ")) { return new RegExp(pat, "i"); } else { return new RegExp("\\b" + pat, "i"); } } else if (pat.includes(" ")) { const escaped = pat.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); return new RegExp(escaped, "i"); } else { const escaped = pat.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); return new RegExp("\\b" + escaped + "\\b", "i"); } } catch (error) { console.error(`Rome: Failed to compile pattern "${pattern}":`, error); return /(?!.*)/; } }); this._lastPatternUpdate = this._patternVersion; } return this._compiledPatterns; } applyRegexFiltering() { this.applyAllFiltersInChunks(); } getAlbums() { const currentCount = document.querySelectorAll( this.deps.config.SELECTORS.ALBUM_ITEMS ).length; if (currentCount === this.lastAlbumCount && this.albumCache.size > 0) { return Array.from(this.albumCache.values()); } this.albumCache.clear(); this.cacheTimestamps.clear(); this.lastAlbumCount = currentCount; const now = Date.now(); const albums = Array.from( document.querySelectorAll(this.deps.config.SELECTORS.ALBUM_ITEMS) ).map((albumEl) => { const titleEl = albumEl.querySelector( `div > ${this.deps.config.SELECTORS.ALBUM_TITLE}` ); const userEl = albumEl.querySelector( `div > ${this.deps.config.SELECTORS.ALBUM_USER}` ); const linkEl = albumEl.querySelector("a[href]"); let albumUrl = ""; if (linkEl) { try { albumUrl = decodeURIComponent(linkEl.href); } catch (e) { albumUrl = linkEl.href; } } return { albumTitle: (titleEl == null ? void 0 : titleEl.innerText) || "", albumUser: (userEl == null ? void 0 : userEl.innerText) || "", albumUrl, element: albumEl }; }).filter((item) => item.albumTitle !== ""); albums.forEach((album, index) => { this.albumCache.set(index, album); this.cacheTimestamps.set(index, now); }); return albums; } censorAlbum(albumOrTitle) { const applyOverlay = (album) => { const container = album.querySelector(this.deps.config.SELECTORS.THUMBNAIL_CONTAINER); if (container) { const overlay = document.createElement("a"); overlay.className = "album-link"; overlay.style.cssText = ` position: absolute; content: ''; background: URL('${this.censoredIMG}') center no-repeat; background-size: contain; resize: both; pointer-events: none; width: 100%; height: 100%; `; container.innerHTML = ""; container.appendChild(overlay); } }; if (albumOrTitle instanceof HTMLElement) { applyOverlay(albumOrTitle); } else { document.querySelectorAll(this.deps.config.SELECTORS.ALBUM_ITEMS).forEach((album) => { const titleElement = album.querySelector( `div > ${this.deps.config.SELECTORS.ALBUM_TITLE}` ); if (titleElement && titleElement.innerText === albumOrTitle) { applyOverlay(album); } }); } } } class PhotoOnlyAlbumToggle { constructor(unifiedState, deps) { __publicField(this, "deps"); __publicField(this, "state"); __publicField(this, "button"); this.deps = deps; this.state = unifiedState; this.initialize(); } initialize() { if (this.deps.currentPage !== "EXPLORE") return; this.createToggleButton(); this.applyPhotoOnlyFilter(); this.updateButtonText(); this.removeUnwantedButtons(); } createToggleButton() { const toggleBtn = document.createElement("div"); toggleBtn.classList.add("sp", "no-select", "rome-menu-btn"); toggleBtn.setAttribute("data-type", "PhotoToggle"); toggleBtn.title = "Toggle Photo-Only Albums"; toggleBtn.innerHTML = `📸 Photos`; toggleBtn.style.cssText = ` cursor: pointer; background: rgba(235, 99, 149, 0.8); color: white; border-radius: 4px; font-weight: bold; margin: 0 5px; user-select: none; transition: background 0.3s; `; const romeBtn = document.querySelector('.rome-menu-btn[data-type="RomeMenu"]'); if (romeBtn && romeBtn.parentNode) { romeBtn.parentNode.insertBefore(toggleBtn, romeBtn.nextSibling); } else { const navChild = document.querySelector(".container > div > div:nth-child(6)"); if (navChild && navChild.parentNode) { navChild.parentNode.insertBefore(toggleBtn, navChild); } } toggleBtn.addEventListener("click", () => { this.togglePhotoOnlyAlbums(); }); toggleBtn.addEventListener("mouseenter", () => { toggleBtn.style.background = "rgba(235, 99, 149, 1)"; }); toggleBtn.addEventListener("mouseleave", () => { toggleBtn.style.background = "rgba(235, 99, 149, 0.8)"; }); document.addEventListener("keydown", (e) => { const t = e.target; if (t.tagName === "INPUT" || t.tagName === "TEXTAREA") return; const modal = document.getElementById("romeModal"); if (modal && modal.style.display === "block") return; if (e.key === "a" || e.key === "A") { if (this.deps.currentPage === "EXPLORE") { e.preventDefault(); this.togglePhotoOnlyAlbums(); } } }); this.button = toggleBtn; } togglePhotoOnlyAlbums() { this.state.sessionState.hidePhotoOnlyAlbums = !this.state.sessionState.hidePhotoOnlyAlbums; this.applyPhotoOnlyFilter(); this.state.saveSessionState(); this.updateButtonText(); } updateButtonText() { if (this.button) { const isHidden = this.state.sessionState.hidePhotoOnlyAlbums; this.button.innerHTML = isHidden ? `📸 Show Photos` : `📸 Hide Photos`; this.button.style.background = isHidden ? "rgba(160, 159, 157, 0.8)" : "rgba(235, 99, 149, 0.8)"; } } applyPhotoOnlyFilter() { const hidePhotos = this.state.sessionState.hidePhotoOnlyAlbums; const albums = document.querySelectorAll( this.deps.config.SELECTORS.ALBUM_ITEMS ); albums.forEach((album) => { const hasVideos = album.querySelector(".album-videos") !== null; if (hidePhotos && !hasVideos) { album.style.display = "none"; } else { album.style.display = ""; } }); this.deps.logger.log("PhotoToggle", `Applied photo filter: hidePhotos=${hidePhotos}`, { totalAlbums: albums.length, hiddenAlbums: hidePhotos ? Array.from(albums).filter((a) => a.style.display === "none").length : 0 }); } removeUnwantedButtons() { const removeButtons = () => { let removed = 0; document.querySelectorAll("BUTTON.btn.btn-grey.album-repost").forEach((btn) => { btn.remove(); removed++; this.deps.logger.log("PhotoToggle", "Removed album-repost button"); }); document.querySelectorAll("BUTTON#album-flag").forEach((btn) => { btn.remove(); removed++; this.deps.logger.log("PhotoToggle", "Removed album-flag button"); }); return removed; }; removeButtons(); let noOpCount = 0; const observer = new MutationObserver(() => { const removed = removeButtons(); if (removed === 0) { noOpCount++; if (noOpCount >= 10) observer.disconnect(); } else { noOpCount = 0; } }); const albumsContainer = document.querySelector( this.deps.config.SELECTORS.ALBUMS_CONTAINER ); if (albumsContainer) { observer.observe(albumsContainer, { childList: true, subtree: true }); } } } class AlbumPageManager { constructor(unifiedState, deps) { __publicField(this, "deps"); __publicField(this, "state"); __publicField(this, "_vjsObserver", null); this.deps = deps; this.state = unifiedState; this.initialize(); } initialize() { const vidDivs = document.getElementsByClassName("video"); if (vidDivs.length > 1) { this.deps.logger.log("MultiVideo", "Enabling multi-video", { count: vidDivs.length }); let setupOk = false; try { this.deps.multiVideo.videoCleanseReplace(vidDivs); setupOk = true; } catch (error) { console.error("Rome: Multi-video setup failed:", error); } document.dispatchEvent( new CustomEvent("rome:videos-ready", { detail: { success: setupOk } }) ); } const initWrappers = () => { this.addSessionButtons(); this.ensureVideoWrappers(); this.ensureVjsCustomControls(); this.applySessionPhotoToggle(); this.applySessionSBSToggle(); this.applyVideoRotation(); const vjsObserveTarget = document.querySelector(".album-videos") || document.body; this._vjsObserver = new MutationObserver( this.deps.createVjsObserverCallback({ countFn: () => document.querySelectorAll(".rome-vjs-controls").length, ensureFn: () => { this.ensureVjsCustomControls(); this.applySessionPhotoToggle(); }, getObserver: () => this._vjsObserver }).callback ); this._vjsObserver.observe(vjsObserveTarget, { childList: true, subtree: true }); }; if (vidDivs.length > 1) { initWrappers(); } else { setTimeout(initWrappers, this.deps.config.TIMEOUTS.INITIAL_DELAY); } } addSessionButtons() { const userNameParent = $("#user_name").parent(); if (userNameParent.length === 0) { console.warn("Rome: Could not find #user_name parent for buttons"); return; } userNameParent.append( '<button id="togglePhotos" class="btn btn-pink">show photos</button>' ); userNameParent.append(document.createTextNode(" ")); userNameParent.append('<button id="sbsBtn" class="btn btn-pink">SBS: Off</button>'); userNameParent.append(document.createTextNode(" ")); userNameParent.append( '<button id="rotateBtn" class="btn btn-pink">Rotate: 0°</button>' ); this.setupEventHandlers(); this.setupKeyboardShortcuts(); this.applySessionPhotoToggle(); this.applySessionSBSToggle(); } togglePhotos() { this.state.sessionState.showPhotosSession = !this.state.sessionState.showPhotosSession; this.applySessionPhotoToggle(); this.applySessionSBSToggle(); this.state.saveSessionState(); } toggleSBS() { this.state.sessionState.showSBSSession = !this.state.sessionState.showSBSSession; this.applySessionSBSToggle(); this.applySessionPhotoToggle(); } setupEventHandlers() { $("#togglePhotos").on("click", () => this.togglePhotos()); $("#sbsBtn").on("click", () => this.toggleSBS()); $("#rotateBtn").on("click", () => this.rotateVideos()); } setupKeyboardShortcuts() { document.addEventListener("keydown", (e) => { const t = e.target; if (t.tagName === "INPUT" || t.tagName === "TEXTAREA") return; if (e.metaKey || e.ctrlKey || e.altKey) return; if (e.key === "a") { this.togglePhotos(); e.preventDefault(); } else if (e.key === "s") { this.toggleSBS(); e.preventDefault(); } else if (e.key === "r") { this.rotateVideos(); e.preventDefault(); } }); } _isPhotoOnlyGroup(group) { return !group.querySelector( "video, .video, .video-js, .rome-video, .rome-video-wrapper" ); } applySessionPhotoToggle() { const show = this.state.sessionState.showPhotosSession; document.querySelectorAll(".media-group").forEach((group) => { if (this._isPhotoOnlyGroup(group)) { group.style.display = show ? "" : "none"; group.dataset.romePhotoHidden = show ? "" : "1"; if (!show) { group.classList.remove("col-sm-6"); } else if (this.state.sessionState.showSBSSession) { group.classList.add("col-sm-6"); } } }); $("#togglePhotos").css("backgroundColor", show ? "#eb6395" : "#a09f9d").text(show ? "hide photos" : "show photos"); } applySessionSBSToggle() { const show = this.state.sessionState.showSBSSession; document.querySelectorAll(".media-group").forEach((group) => { if (group.dataset.romePhotoHidden === "1") return; if (show) { group.classList.add("col-sm-6"); } else { group.classList.remove("col-sm-6"); } }); $("#sbsBtn").css("backgroundColor", show ? "#a09f9d" : "#eb6395").text(show ? "SBS: On" : "SBS: Off"); } rotateVideos() { this.state.sessionState.videoRotation += 90; if (this.state.sessionState.videoRotation >= 360) { this.state.sessionState.videoRotation = 0; } this.applyVideoRotation(); this.state.saveSessionState(); } _computeVideoFitStyles(rotation) { let normalized = rotation % 360; if (normalized < 0) normalized += 360; if (normalized === 0) { return { position: "", top: "", left: "", transformOrigin: "", objectFit: "", maxWidth: "", maxHeight: "", width: "", height: "", transform: "" }; } const sideways = normalized === 90 || normalized === 270; return { position: "absolute", top: "50%", left: "50%", transformOrigin: "center center", objectFit: "contain", maxWidth: "none", maxHeight: "none", width: sideways ? "100vh" : "100%", height: sideways ? "100vw" : "100%", transform: `translate(-50%, -50%) rotate(${normalized}deg)` }; } applyVideoRotation() { const rotation = this.state.sessionState.videoRotation; document.querySelectorAll(".media-group video").forEach((video) => { const vjsContainer = video.closest(".video-js"); if (vjsContainer) { const tech = vjsContainer.querySelector(".vjs-tech") || video; vjsContainer.style.overflow = rotation === 0 ? "" : "hidden"; Object.assign(tech.style, this._computeVideoFitStyles(rotation)); } else { const wrapper = video.closest(".rome-video-wrapper"); const customControls = wrapper ? wrapper.querySelector(".rome-custom-controls") : null; video.controls = false; if (customControls) customControls.classList.add("active"); Object.assign(video.style, this._computeVideoFitStyles(rotation)); } }); $("#rotateBtn").text(`Rotate: ${rotation}°`); } ensureVjsCustomControls() { document.querySelectorAll(".video-js").forEach((vjsEl) => { if (vjsEl.querySelector(".rome-vjs-controls") || vjsEl.dataset.romeVjsPending) return; const player = this.deps.getVjsPlayer(vjsEl); if (!player) return; vjsEl.dataset.romeVjsPending = "1"; player.ready(function() { var _a, _b, _c, _d; if (((_a = player.isDisposed) == null ? void 0 : _a.call(player)) || player.isDisposed_) { delete vjsEl.dataset.romeVjsPending; return; } delete vjsEl.dataset.romeVjsPending; if (vjsEl.querySelector(".rome-vjs-controls")) return; const ac = new AbortController(); const sig = ac.signal; (_b = player.controlBar) == null ? void 0 : _b.hide(); const controls = document.createElement("div"); controls.className = "rome-vjs-controls"; const playBtn = document.createElement("button"); playBtn.className = "rome-vjs-play-btn"; playBtn.title = "Play/Pause"; playBtn.textContent = "▶"; const progress = document.createElement("div"); progress.className = "rome-vjs-progress"; const fill = document.createElement("div"); fill.className = "rome-vjs-fill"; progress.appendChild(fill); const time = document.createElement("span"); time.className = "rome-vjs-time"; time.textContent = "0:00 / 0:00"; controls.append(playBtn, progress, time); player.el().appendChild(controls); const stopBubble = (e) => { e.stopPropagation(); }; for (const evt of [ "mousedown", "mouseup", "mousemove", "mouseover", "mouseout", "click", "pointerdown", "pointerup", "pointermove", "pointerover", "pointerout" ]) { controls.addEventListener(evt, stopBubble, { signal: sig }); } const fmt = (s) => { if (!isFinite(s)) return "0:00"; const m = Math.floor(s / 60); const sec = String(Math.floor(s % 60)).padStart(2, "0"); return `${m}:${sec}`; }; const syncPlayBtn = () => { playBtn.textContent = player.paused() ? "▶" : "⏸"; }; player.on("play", syncPlayBtn); player.on("pause", syncPlayBtn); const onTimeUpdate = () => { const cur = player.currentTime(); const dur = player.duration(); if (isFinite(dur) && dur > 0) { fill.style.width = cur / dur * 100 + "%"; time.textContent = `${fmt(cur)} / ${fmt(dur)}`; } }; player.on("timeupdate", onTimeUpdate); playBtn.addEventListener( "click", (e) => { e.stopPropagation(); const p = player.paused() ? player.play() : player.pause(); if (p && typeof p.catch === "function") { p.catch((err) => { if (err.name !== "AbortError" && err.name !== "NotAllowedError") console.error("Rome: play/pause failed:", err); }); } }, { signal: sig } ); const scrub = (e) => { const dur = player.duration(); if (!isFinite(dur) || dur <= 0) return; const rect = progress.getBoundingClientRect(); if (!rect.width) return; const ratio = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width)); player.currentTime(ratio * dur); fill.style.width = ratio * 100 + "%"; }; progress.addEventListener( "click", (e) => { e.stopPropagation(); scrub(e); }, { signal: sig } ); progress.addEventListener( "pointerdown", (e) => { e.stopPropagation(); progress.setPointerCapture(e.pointerId); const onMove = (ev) => { ev.stopPropagation(); scrub(ev); }; const cleanup = (ev) => { ev.stopPropagation(); progress.removeEventListener("pointermove", onMove); progress.removeEventListener("pointerup", cleanup); progress.removeEventListener("pointercancel", cleanup); }; progress.addEventListener("pointermove", onMove, { signal: sig }); progress.addEventListener("pointerup", cleanup, { signal: sig }); progress.addEventListener("pointercancel", cleanup, { signal: sig }); scrub(e); }, { signal: sig } ); if (((_d = (_c = player.options_) == null ? void 0 : _c.userActions) == null ? void 0 : _d.click) === false) { vjsEl.addEventListener( "click", (e) => { if (e.target.closest(".rome-vjs-controls")) return; const p = player.paused() ? player.play() : player.pause(); if (p && typeof p.catch === "function") { p.catch((err) => { if (err.name !== "AbortError" && err.name !== "NotAllowedError") console.error("Rome: play/pause failed:", err); }); } }, { signal: sig } ); } player.on("dispose", () => { ac.abort(); player.off("play", syncPlayBtn); player.off("pause", syncPlayBtn); player.off("timeupdate", onTimeUpdate); controls.remove(); }); }); }); } ensureVideoWrappers() { document.querySelectorAll(".media-group video").forEach((video, idx) => { if (video.closest(".rome-video-wrapper")) return; if (video.closest(".video-js")) return; if (!video.parentNode) return; try { const wrapper = document.createElement("div"); wrapper.className = "rome-video-wrapper"; video.parentNode.insertBefore(wrapper, video); wrapper.appendChild(video); this.createCustomControls(video); } catch (err) { console.error(`Rome: ensureVideoWrappers failed for video ${idx}:`, err); } }); } createCustomControls(video) { const wrapper = video.closest(".rome-video-wrapper"); if (!wrapper || wrapper.querySelector(".rome-custom-controls")) return; const ac = new AbortController(); const { signal } = ac; wrapper._romeControlsAbort = ac; const playBtn = document.createElement("button"); playBtn.className = "rome-play-btn"; playBtn.title = "Play/Pause"; playBtn.textContent = "▶"; const progressBar = document.createElement("div"); progressBar.className = "rome-progress-bar"; const progressFill = document.createElement("div"); progressFill.className = "rome-progress-fill"; progressBar.appendChild(progressFill); const timeDisplay = document.createElement("span"); timeDisplay.className = "rome-time-display"; timeDisplay.textContent = "0:00 / 0:00"; const controls = document.createElement("div"); controls.className = "rome-custom-controls"; controls.append(playBtn, progressBar, timeDisplay); wrapper.appendChild(controls); controls.classList.add("active"); wrapper.addEventListener( "mouseenter", () => wrapper.classList.add("rome-bar-expanded"), { signal } ); wrapper.addEventListener( "mouseleave", () => wrapper.classList.remove("rome-bar-expanded"), { signal } ); playBtn.addEventListener( "click", () => { if (video.paused) { prioritizeVideoForPlayback(video); video.play().catch((err) => { if (err.name !== "AbortError") console.warn("Rome: play() rejected:", err.message); }); } else { video.pause(); } }, { signal } ); video.addEventListener( "click", (e) => { const wrapperEl = video.closest(".rome-video-wrapper"); if (!wrapperEl) { console.warn( "Rome: video click handler - video not inside .rome-video-wrapper, skipping play toggle" ); return; } const ctrl = wrapperEl.querySelector(".rome-custom-controls"); if (ctrl && ctrl.classList.contains("active")) { e.preventDefault(); if (video.paused) { prioritizeVideoForPlayback(video); video.play().catch((err) => { if (err.name !== "AbortError") console.warn("Rome: play() rejected:", err.message); }); } else { video.pause(); } } }, { signal } ); video.addEventListener( "play", () => { playBtn.textContent = "▮▮"; }, { signal } ); video.addEventListener( "pause", () => { playBtn.textContent = "▶"; }, { signal } ); video.addEventListener( "timeupdate", () => { const dur = video.duration; const cur = video.currentTime; const validDuration = Number.isFinite(dur) && dur > 0; if (validDuration) { progressFill.style.width = cur / dur * 100 + "%"; timeDisplay.textContent = this.formatTime(cur) + " / " + this.formatTime(dur); } else { progressFill.style.width = "0%"; timeDisplay.textContent = this.formatTime(cur) + " / --:--"; } }, { signal } ); let isDragging = false; let wasPaused = false; let pendingSeekRatio = null; let seekRafId = null; const commitSeek = () => { seekRafId = null; if (pendingSeekRatio !== null && video.duration && isFinite(video.duration)) { video.currentTime = pendingSeekRatio * video.duration; pendingSeekRatio = null; } }; const seekToPosition = (clientX, immediate) => { const rect = progressBar.getBoundingClientRect(); if (rect.width <= 0) return; const ratio = Math.max(0, Math.min(1, (clientX - rect.left) / rect.width)); if (!Number.isFinite(video.duration) || video.duration <= 0) return; progressFill.style.width = ratio * 100 + "%"; timeDisplay.textContent = this.formatTime(ratio * video.duration) + " / " + this.formatTime(video.duration); if (immediate) { video.currentTime = ratio * video.duration; } else { pendingSeekRatio = ratio; if (!seekRafId) { seekRafId = requestAnimationFrame(commitSeek); } } }; progressBar.addEventListener( "pointerdown", (e) => { if (!Number.isFinite(video.duration) || video.duration <= 0) return; isDragging = true; wasPaused = video.paused; video.pause(); progressBar.setPointerCapture(e.pointerId); seekToPosition(e.clientX, true); e.preventDefault(); }, { signal } ); progressBar.addEventListener( "pointermove", (e) => { if (isDragging) { seekToPosition(e.clientX, false); } }, { signal } ); const endDrag = () => { if (isDragging) { if (seekRafId) { cancelAnimationFrame(seekRafId); seekRafId = null; } if (pendingSeekRatio !== null && video.duration && isFinite(video.duration)) { video.currentTime = pendingSeekRatio * video.duration; pendingSeekRatio = null; } if (!wasPaused) video.play().catch((err) => { if (err.name !== "AbortError") console.warn("Rome: play() rejected on seek resume:", err.message); }); } isDragging = false; }; progressBar.addEventListener("pointerup", endDrag, { signal }); progressBar.addEventListener("pointercancel", endDrag, { signal }); video.addEventListener( "emptied", () => { isDragging = false; if (seekRafId) { cancelAnimationFrame(seekRafId); seekRafId = null; } pendingSeekRatio = null; ac.abort(); }, { signal } ); } formatTime(seconds) { if (!Number.isFinite(seconds) || seconds < 0) return "0:00"; const mins = Math.floor(seconds / 60); const secs = Math.floor(seconds % 60); return mins + ":" + (secs < 10 ? "0" : "") + secs; } } class WatchedTracker { constructor(deps) { __publicField(this, "deps"); __publicField(this, "watchedVideos"); __publicField(this, "currentVideoId"); __publicField(this, "intervalId"); __publicField(this, "observer"); __publicField(this, "observedAlbums"); __publicField(this, "_containerObserver", null); this.deps = deps; this.watchedVideos = this.deps.storage.getWithFallback( this.deps.config.STORAGE_KEYS.WATCHED_VIDEOS, [] ); this.currentVideoId = this.deps.getVideoId(window.location.href); this.intervalId = null; this.observer = null; this.observedAlbums = new Set(); this.initialize(); } initialize() { this.saveCurrentVideo(); this.setupIntersectionObserver(); this.setupMiddleClickTracking(); this.startPeriodicCheck(); window.addEventListener("beforeunload", () => { this.cleanup(); }); document.addEventListener("visibilitychange", () => { if (document.hidden) { this.pause(); } else { this.resume(); } }); } setupIntersectionObserver() { this.deps.logger.log("WatchedTracker", "Setting up IntersectionObserver"); this.observer = new IntersectionObserver( (entries) => { this.deps.logger.performance("watched-intersection", () => { entries.forEach((entry) => { if (entry.isIntersecting) { this.markAlbumIfWatched(entry.target); } }); }); }, { root: null, rootMargin: "50px", threshold: 0.1 } ); this.observeExistingAlbums(); const appendRoot = document.querySelector(this.deps.config.SELECTORS.INFY_APPEND_ROOT) || document.querySelector(this.deps.config.SELECTORS.ALBUMS_CONTAINER); this._containerObserver = this.deps.createAlbumMutationObserver({ target: appendRoot, itemSelector: this.deps.config.SELECTORS.ALBUM_ITEMS, onItemsAdded: (albums) => { const handleAlbum = this.deps.wrap((album) => { this.observeAlbum(album); this.markAlbumIfWatched(album); }, "WatchedTracker.onItemsAdded"); albums.forEach(handleAlbum); } }); } setupMiddleClickTracking() { this.deps.logger.log("WatchedTracker", "Setting up middle-click tracking"); const albumsContainer = document.querySelector( this.deps.config.SELECTORS.ALBUMS_CONTAINER ); if (!albumsContainer) { this.deps.logger.log( "WatchedTracker", "No albums container found for middle-click tracking" ); return; } albumsContainer.addEventListener("auxclick", (event) => { if (event.button !== 1) return; this.deps.wrap(() => { const albumItem = event.target.closest('div[id^="album-"]'); if (!albumItem) return; const albumLink = albumItem.querySelector("a[href]"); if (!albumLink) return; const videoId = this.deps.getVideoId(albumLink.href); if (!videoId) return; this.markAlbumAsWatchedInstantly(albumItem, videoId); this.deps.logger.log( "WatchedTracker", `Middle-clicked album marked as watched: ${videoId}` ); }, "MiddleClickTracking")(); }); this.deps.logger.log("WatchedTracker", "Middle-click tracking initialized"); } observeExistingAlbums() { const albums = document.querySelectorAll(this.deps.config.SELECTORS.ALBUM_ITEMS); this.deps.logger.log("WatchedTracker", `Observing ${albums.length} existing albums`); albums.forEach((album) => this.observeAlbum(album)); } observeAlbum(album) { if (!this.observedAlbums.has(album)) { this.observer.observe(album); this.observedAlbums.add(album); } } markAlbumIfWatched(album) { const link = album.querySelector("a[href]"); if (link) { const videoId = this.deps.getVideoId(link.href); if (videoId && this.watchedVideos.includes(videoId)) { const container = album.querySelector( this.deps.config.SELECTORS.THUMBNAIL_CONTAINER ); if (container && !container.style.borderBottom) { container.style.borderBottom = "5px solid red"; this.deps.logger.log("WatchedTracker", `Marked album as watched: ${videoId}`); } } } } markAlbumAsWatchedInstantly(album, videoId) { if (!this.watchedVideos.includes(videoId)) { this.watchedVideos.push(videoId); if (this.watchedVideos.length > 5e3) { this.watchedVideos = this.watchedVideos.slice(-5e3); } this.deps.storage.setWithRetry( this.deps.config.STORAGE_KEYS.WATCHED_VIDEOS, this.watchedVideos ).catch((err) => { console.error("Rome: storage write failed", err); this.deps.notifyStorageFailure("watched videos"); }); } const container = album.querySelector( this.deps.config.SELECTORS.THUMBNAIL_CONTAINER ); if (container) { container.style.borderBottom = "5px solid red"; this.deps.logger.log( "WatchedTracker", `Instantly marked album as watched: ${videoId}` ); } else { const altContainer = album.querySelector(".album-thumbnail-container") || album.querySelector("img") || album.querySelector("a"); if (altContainer) { altContainer.style.borderBottom = "5px solid red"; this.deps.logger.log( "WatchedTracker", `Instantly marked album as watched (alt): ${videoId}` ); } } } startPeriodicCheck() { this.intervalId = setInterval(() => { this.deps.wrap(() => { const unobservedAlbums = Array.from( document.querySelectorAll(this.deps.config.SELECTORS.ALBUM_ITEMS) ).filter((album) => !this.observedAlbums.has(album)); if (unobservedAlbums.length > 0) { this.deps.logger.log( "WatchedTracker", `Processing ${unobservedAlbums.length} unobserved albums` ); unobservedAlbums.forEach((album) => { this.observeAlbum(album); this.markAlbumIfWatched(album); }); } }, "WatchedTracker")(); }, this.deps.config.TIMEOUTS.WATCHED_CHECK); } pause() { if (this.intervalId) { clearInterval(this.intervalId); this.intervalId = null; } if (this.observer) { this.observer.disconnect(); } this.deps.logger.log("WatchedTracker", "Paused"); } resume() { if (!this.intervalId) { this.startPeriodicCheck(); } if (this.observer) { this.observeExistingAlbums(); } this.deps.logger.log("WatchedTracker", "Resumed"); } cleanup() { if (this.intervalId) { clearInterval(this.intervalId); this.intervalId = null; } if (this.observer) { this.observer.disconnect(); this.observer = null; } if (this._containerObserver) { this._containerObserver.disconnect(); this._containerObserver = null; } this.observedAlbums.clear(); this.deps.logger.log("WatchedTracker", "Cleaned up"); } saveCurrentVideo() { if (this.currentVideoId && !this.watchedVideos.includes(this.currentVideoId)) { this.watchedVideos.push(this.currentVideoId); if (this.watchedVideos.length > 5e3) { this.watchedVideos = this.watchedVideos.slice(-5e3); } this.deps.storage.setWithRetry( this.deps.config.STORAGE_KEYS.WATCHED_VIDEOS, this.watchedVideos ).catch((err) => { console.error("Rome: storage write failed", err); this.deps.notifyStorageFailure("watched videos"); }); this.deps.logger.log("WatchedTracker", `Saved current video: ${this.currentVideoId}`); } } markWatchedAlbums() { document.querySelectorAll(this.deps.config.SELECTORS.ALBUM_ITEMS).forEach((album) => { this.markAlbumIfWatched(album); }); } } async function main() { try { performance.mark("rome-perf-script-start"); } catch (_e) { } const logger = createLogger(ROME_CONFIG); const storage = createStorage({ config: ROME_CONFIG, logger, getValue: _GM_getValue, setValue: _GM_setValue }); const currentPage = detectPage(window.location, document, logger); let _romePerfCachedEnabled = null; function _romePerfFreshBuffer() { return { meta: null, probe1: null, probe2: { rome: {}, observers: { created: 0 }, videojsCalls: { count: 0, totalMs: 0 } }, probe3: null, probe4: null, probe5: null }; } let _romePerfBuffer = _romePerfFreshBuffer(); let _romePerfInitialized = false; let _romePerfLongtaskTotalMs = 0; let _romePerfLcpTimestamp = null; let _romePerfMemoryAtDcl = null; let _romePerfVideojsCallTimes = []; let _romePerfObserverCreatedCount = 0; let _romePerfTtff = { loadedmetadataMs: null, playingMs: null, src: null }; let _romePerfClickProbeRan = false; const _romePerfWarnedKeys = new Set(); function _romePerfWarnOnce(key, msg) { if (_romePerfWarnedKeys.has(key)) return; _romePerfWarnedKeys.add(key); console.warn(msg); } function _romePerfReadScriptVersion() { return typeof GM_info !== "undefined" && GM_info.script ? GM_info.script.version : null; } function _romePerfSafeDisconnect(obs) { if (!obs) return; try { obs.disconnect(); } catch (_e) { } } function _romePerfComputeEnabled() { try { const params = new URLSearchParams(window.location.search); if (params.get("perf") === "1") return true; } catch (_e) { } if (typeof window !== "undefined" && window.__romePerfEnabled === true) { return true; } try { if (localStorage.getItem("romePerfEnabled") === "1") return true; } catch (_e) { } return false; } const RomePerf = { get enabled() { if (_romePerfCachedEnabled === null) { _romePerfCachedEnabled = _romePerfComputeEnabled(); } return _romePerfCachedEnabled; }, mark(name) { if (!this.enabled) return; try { performance.mark(`rome-perf-${name}`); } catch (_e) { _romePerfWarnOnce( "mark", "RomePerf: performance.mark unavailable; subsequent marks will silently no-op" ); } }, measure(name, startMark, endMark) { if (!this.enabled) return; try { performance.measure( `rome-perf-${name}`, `rome-perf-${startMark}`, `rome-perf-${endMark}` ); } catch (_e) { _romePerfWarnOnce( "measure", "RomePerf: performance.measure unavailable; subsequent measures will silently no-op" ); } }, _frameStats(durations) { if (durations.length === 0) { return { frames: 0, min: null, median: null, p95: null }; } const sorted = [...durations].sort((a, b) => a - b); const pick = (q) => sorted[Math.min(sorted.length - 1, Math.floor(q * sorted.length))]; return { frames: sorted.length, min: sorted[0], median: pick(0.5), p95: pick(0.95) }; }, _markStart(name) { const e = performance.getEntriesByName(`rome-perf-${name}`)[0]; return e ? e.startTime : null; }, _measureDuration(name) { const e = performance.getEntriesByName(`rome-perf-${name}`)[0]; return e && typeof e.duration === "number" ? e.duration : null; }, flush() { if (!this.enabled) return; const meta = { url: window.location.href, ts: ( new Date()).toISOString(), romeVersion: _romePerfReadScriptVersion() }; const out = { ..._romePerfBuffer, meta }; console.log(`[RomePerf] ${JSON.stringify(out)}`); _romePerfBuffer = _romePerfFreshBuffer(); }, async scrollProbe(durationMs = 5e3) { if (!this.enabled) { console.warn("RomePerf: scrollProbe called but harness disabled"); return null; } const startY = window.scrollY; const targetY = Math.max( document.documentElement.scrollHeight - window.innerHeight, startY ); const startTs = performance.now(); const endTs = startTs + durationMs; const frameTimes = []; let lastTs = startTs; return new Promise((resolve) => { const tick = (ts) => { const dt = ts - lastTs; if (lastTs !== startTs && !document.hidden) frameTimes.push(dt); lastTs = ts; const progress = Math.min(1, (ts - startTs) / durationMs); window.scrollTo({ top: startY + (targetY - startY) * progress, behavior: "auto" }); if (ts < endTs) { requestAnimationFrame(tick); } else { const stats = this._frameStats(frameTimes); const probe4 = { durationMs, scrolledFromY: startY, scrolledToY: window.scrollY, ...stats }; _romePerfBuffer.probe4 = probe4; this.flush(); resolve(probe4); } }; requestAnimationFrame(tick); }); }, async clickProbe(timeoutMs = 3e4) { if (!this.enabled) { console.warn("RomePerf: clickProbe called but harness disabled"); return null; } if (_romePerfClickProbeRan) { console.warn("RomePerf: clickProbe already ran this session"); return null; } _romePerfClickProbeRan = true; const candidates = [...document.querySelectorAll("video")].map((v) => ({ v, top: v.getBoundingClientRect().top })).filter((x) => x.top > 0).sort((a, b) => a.top - b.top); if (candidates.length === 0) { const probe5 = { status: "no_video", src: null }; _romePerfBuffer.probe5 = probe5; this.flush(); return probe5; } const topmost = candidates[0].v; const src = topmost.currentSrc || topmost.src; const readyStateAtClick = topmost.readyState; if (!topmost.paused && topmost.currentTime > 0) { const probe5 = { status: "already_playing", src, readyStateAtClick }; _romePerfBuffer.probe5 = probe5; this.flush(); return probe5; } return new Promise((resolve) => { let resolved = false; const finish = (probe5) => { if (resolved) return; resolved = true; topmost.removeEventListener("playing", onPlaying); _romePerfBuffer.probe5 = probe5; this.flush(); resolve(probe5); }; const clickStartMs = performance.now(); const onPlaying = () => { const playingMs = performance.now(); finish({ status: "ok", src, clickStartMs, playingMs, ttfp: playingMs - clickStartMs, readyStateAtClick, readyStateAtPlaying: topmost.readyState }); }; topmost.addEventListener("playing", onPlaying); try { topmost.dispatchEvent( new MouseEvent("click", { bubbles: true, cancelable: true, view: window }) ); } catch (_e) { try { topmost.play(); } catch (_e2) { } } setTimeout(() => { finish({ status: "timeout", src, clickStartMs, playingMs: null, ttfp: null, readyStateAtClick, readyStateAtPlaying: topmost.readyState }); }, timeoutMs); }); }, markVideojsCall(durationMs) { if (!this.enabled) return; _romePerfVideojsCallTimes.push(durationMs); }, markObserverCreated() { if (!this.enabled) return; _romePerfObserverCreatedCount += 1; }, _captureProbe2() { _romePerfBuffer.probe2 = { rome: { scriptStart: this._markStart("script-start"), bootstrapEnd: this._markStart("bootstrap-end"), bootstrap: this._measureDuration("bootstrap"), addProperID: this._measureDuration("addProperID"), videoCleanseReplace: this._measureDuration("videoCleanseReplace") }, observers: { created: _romePerfObserverCreatedCount }, videojsCalls: { count: _romePerfVideojsCallTimes.length, totalMs: _romePerfVideojsCallTimes.reduce((a, b) => a + b, 0), stats: this._frameStats(_romePerfVideojsCallTimes) } }; }, _captureProbe1() { let domContentLoaded = null; let loadEnd = null; let timingError = null; try { const navEntries = performance.getEntriesByType("navigation"); if (navEntries.length > 0) { domContentLoaded = navEntries[0].domContentLoadedEventEnd; loadEnd = navEntries[0].loadEventEnd; } else if (performance.timing) { const t = performance.timing; domContentLoaded = t.domContentLoadedEventEnd - t.navigationStart; loadEnd = t.loadEventEnd - t.navigationStart; } } catch (_e) { console.warn("RomePerf probe1: navigation timing read failed", _e); timingError = String(_e); } let memoryAtFlush = null; if (typeof performance !== "undefined" && performance.memory && typeof performance.memory.usedJSHeapSize === "number") { memoryAtFlush = performance.memory.usedJSHeapSize; } _romePerfBuffer.probe1 = { domContentLoaded, load: loadEnd, largestContentfulPaint: _romePerfLcpTimestamp, longtaskTotalMs: _romePerfLongtaskTotalMs, memoryAtDcl: _romePerfMemoryAtDcl, memoryAtFlush }; if (timingError !== null) { _romePerfBuffer.probe1.timingError = timingError; } }, _initTtff() { if (!this.enabled) return; setTimeout(() => { let topmost = null; try { const candidates = [...document.querySelectorAll("video")].map((v) => ({ v, top: v.getBoundingClientRect().top })).filter((x) => x.top > 0).sort((a, b) => a.top - b.top); if (candidates.length === 0) return; topmost = candidates[0].v; _romePerfTtff.src = topmost.currentSrc || topmost.src; } catch (_e) { console.warn("RomePerf TTFF: candidate selection failed", _e); return; } const stamp = () => performance.now(); const safeUnbind = (event, handler) => { try { topmost.removeEventListener(event, handler); } catch (_e) { console.warn(`RomePerf TTFF: removeEventListener failed for ${event}`, _e); } }; const onLoadedMeta = () => { if (_romePerfTtff.loadedmetadataMs === null) { _romePerfTtff.loadedmetadataMs = stamp(); } safeUnbind("loadedmetadata", onLoadedMeta); }; const onPlaying = () => { if (_romePerfTtff.playingMs === null) { _romePerfTtff.playingMs = stamp(); } safeUnbind("playing", onPlaying); }; try { topmost.addEventListener("loadedmetadata", onLoadedMeta); topmost.addEventListener("playing", onPlaying); if (topmost.readyState >= 1) onLoadedMeta(); if (!topmost.paused && topmost.currentTime > 0) onPlaying(); } catch (_e) { console.warn("RomePerf TTFF: listener wiring or synchronous fire failed", _e); } }, 1500); }, _captureProbe3() { _romePerfBuffer.probe3 = { src: _romePerfTtff.src, loadedmetadataMs: _romePerfTtff.loadedmetadataMs, playingMs: _romePerfTtff.playingMs }; }, _init() { if (!this.enabled || _romePerfInitialized) return; _romePerfInitialized = true; let longtaskObs = null; try { longtaskObs = new PerformanceObserver((list) => { for (const entry of list.getEntries()) { _romePerfLongtaskTotalMs += entry.duration; } }); longtaskObs.observe({ type: "longtask", buffered: true }); } catch (_e) { } let lcpObs = null; try { lcpObs = new PerformanceObserver((list) => { const entries = list.getEntries(); if (entries.length > 0) { _romePerfLcpTimestamp = entries[entries.length - 1].startTime; } }); lcpObs.observe({ type: "largest-contentful-paint", buffered: true }); } catch (_e) { } this._initTtff(); const recordMemAtDcl = () => { if (typeof performance !== "undefined" && performance.memory && typeof performance.memory.usedJSHeapSize === "number") { _romePerfMemoryAtDcl = performance.memory.usedJSHeapSize; } }; if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", recordMemAtDcl, { once: true }); } else { recordMemAtDcl(); } setTimeout(() => { _romePerfSafeDisconnect(longtaskObs); _romePerfSafeDisconnect(lcpObs); this._captureProbe1(); this._captureProbe2(); this._captureProbe3(); this.flush(); }, 5e3); try { const params = new URLSearchParams(window.location.search); if (params.get("autoclick") === "1") { setTimeout(() => { this.clickProbe(3e4); }, 6e3); } } catch (_e) { } } }; RomePerf._init(); if (RomePerf.enabled) { globalThis.RomePerf = RomePerf; } const multiVideo = createMultiVideoPlayback({ config: ROME_CONFIG, logger, perf: RomePerf }); function getVjsPlayer(vjsEl) { var _a; if (vjsEl.player) return vjsEl.player; try { const vjs = typeof videojs !== "undefined" ? videojs : (window == null ? void 0 : window.videojs) ?? (typeof _unsafeWindow !== "undefined" ? _unsafeWindow == null ? void 0 : _unsafeWindow.videojs : null); const all = ((_a = vjs == null ? void 0 : vjs.getAllPlayers) == null ? void 0 : _a.call(vjs)) ?? []; return all.find((p) => p && p.el() === vjsEl) ?? null; } catch (e) { return null; } } const libs = safeLibraryAccess(); if (!libs) { console.error("Rome: Critical library access failure - using minimal functionality"); return; } _GM_addStyle(` /* VJS Custom Controls */ .rome-vjs-controls { position: absolute; bottom: 0; left: 0; right: 0; z-index: 10; display: flex; align-items: center; gap: 8px; padding: 8px 12px 12px 12px; background: linear-gradient(transparent, rgba(0, 0, 0, 0.7)); pointer-events: none; } .rome-vjs-controls > * { pointer-events: auto; } .rome-vjs-play-btn { background: none; border: none; color: #fff; cursor: pointer; font-size: 16px; padding: 0; } .rome-vjs-progress { flex: 1; box-sizing: content-box; height: 4px; background: rgba(255, 255, 255, 0.3); border-radius: 2px; cursor: pointer; position: relative; transition: height 0.15s ease-in-out, padding 0.15s ease-in-out; align-self: center; padding: 8px 0; background-clip: content-box; } .video-js:hover .rome-vjs-progress { height: 28px; padding: 0; } .rome-vjs-fill { height: 100%; background: #eb6395; border-radius: 2px; width: 0%; pointer-events: none; } .rome-vjs-time { color: #fff; font-size: 12px; font-family: monospace; white-space: nowrap; } /* Rome Core Styles */ .inactive-gm { background: #a09f9d; } .active-gm { background: #eb6395 !important; } /* #togglePhotos, #sbsBtn: no explicit margin-right; natural spacing is sufficient. */ /* Side-by-side video layout */ .media-group.col-sm-6 { width: 50% !important; display: inline-block !important; vertical-align: top !important; padding: 5px !important; box-sizing: border-box !important; } @media (max-width: 768px) { .media-group.col-sm-6 { width: 100% !important; } } /* Rome Modal Styles */ .rome-modal { display: none; position: fixed; z-index: 99999; left: 0; top: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.6); backdrop-filter: blur(3px); } .rome-modal-content { background: linear-gradient(135deg, #2c2c2c 0%, #1a1a1a 100%); margin: 5% auto; padding: 0; border: none; border-radius: 12px; width: 90%; max-width: 600px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5); color: #fff; animation: modalSlideIn 0.3s ease-out; } @keyframes modalSlideIn { from { opacity: 0; transform: translateY(-50px); } to { opacity: 1; transform: translateY(0); } } .rome-modal-header { display: flex; justify-content: space-between; align-items: center; padding: 20px 25px; background: linear-gradient(135deg, #eb6395 0%, #d14d7a 100%); border-radius: 12px 12px 0 0; color: white; } .rome-modal-title { font-size: 18px; font-weight: bold; margin: 0; } .rome-close-modal { font-size: 28px; font-weight: bold; cursor: pointer; line-height: 1; padding: 0 5px; border-radius: 3px; transition: background 0.3s; } .rome-close-modal:hover { background: rgba(255, 255, 255, 0.2); } .rome-modal-body { padding: 25px; } .rome-settings-section { margin-bottom: 20px; } .rome-settings-section h4 { color: #eb6395; margin: 0 0 15px 0; font-size: 16px; border-bottom: 1px solid rgba(235, 99, 149, 0.3); padding-bottom: 8px; } /* Blocklist Styles */ .blocklist-display { background: rgba(0, 0, 0, 0.4); padding: 15px; border-radius: 8px; max-height: 300px; overflow-y: auto; margin-bottom: 15px; border: 1px solid rgba(235, 99, 149, 0.2); } .blocklist-display::-webkit-scrollbar { width: 8px; } .blocklist-display::-webkit-scrollbar-track { background: rgba(0, 0, 0, 0.2); border-radius: 4px; } .blocklist-display::-webkit-scrollbar-thumb { background: rgba(235, 99, 149, 0.6); border-radius: 4px; } .blocklist-display::-webkit-scrollbar-thumb:hover { background: rgba(235, 99, 149, 0.8); } .blocklist-item { display: flex; justify-content: space-between; align-items: center; padding: 8px 0; border-bottom: 1px solid rgba(255, 255, 255, 0.1); color: #fff; font-family: monospace; font-size: 13px; } .blocklist-item:last-child { border-bottom: none; } .blocklist-item span { flex: 1; word-break: break-word; margin-right: 10px; } .blocklist-item button { background: linear-gradient(135deg, #ff4757 0%, #ff3742 100%); color: white; border: none; padding: 4px 8px; border-radius: 4px; cursor: pointer; font-size: 11px; font-weight: bold; transition: all 0.3s; min-width: 60px; } .blocklist-item button:hover { background: linear-gradient(135deg, #ff6b7a 0%, #ff5252 100%); transform: translateY(-1px); box-shadow: 0 2px 8px rgba(255, 71, 87, 0.3); } /* Add Word Container */ .add-word-container { display: flex; gap: 10px; margin-top: 15px; } .add-word-container input { flex: 1; padding: 12px 15px; background: rgba(0, 0, 0, 0.3); border: 1px solid rgba(235, 99, 149, 0.3); border-radius: 6px; color: #fff; font-size: 14px; transition: border-color 0.3s; } .add-word-container input:focus { outline: none; border-color: rgba(235, 99, 149, 0.8); box-shadow: 0 0 0 2px rgba(235, 99, 149, 0.2); } .add-word-container input::placeholder { color: rgba(255, 255, 255, 0.5); } .add-word-container button { background: linear-gradient(135deg, #26de81 0%, #20bf6b 100%); color: white; border: none; padding: 12px 20px; border-radius: 6px; cursor: pointer; font-weight: bold; font-size: 14px; transition: all 0.3s; min-width: 80px; } .add-word-container button:hover { background: linear-gradient(135deg, #2ed573 0%, #26de81 100%); transform: translateY(-1px); box-shadow: 0 4px 12px rgba(38, 222, 129, 0.3); } .add-word-container button:disabled { background: #666; cursor: not-allowed; transform: none; box-shadow: none; } /* Navbar Button Styles */ .sp.no-select.rome-menu-btn { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; align-items: center; justify-content: center; text-decoration: none; white-space: nowrap; } /* Utility Classes */ .sp { -webkit-user-select: none; -ms-user-select: none; user-select: none; } .no-select { -webkit-user-select: none; -ms-user-select: none; user-select: none; } .no-select::selection, .no-select *::selection { background-color: transparent; } /* Quick Add Blocklist Styles */ .rome-quick-add { display: none; position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 99999; background: linear-gradient(135deg, #2c2c2c 0%, #1a1a1a 100%); border: 2px solid rgba(235, 99, 149, 0.6); border-radius: 12px; padding: 20px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5); min-width: 400px; max-width: 600px; backdrop-filter: blur(3px); } .rome-quick-add h3 { color: #eb6395; margin: 0 0 15px 0; font-size: 18px; font-weight: bold; text-align: center; } .rome-quick-add textarea { width: 100%; height: 150px; background: rgba(0, 0, 0, 0.4); border: 1px solid rgba(235, 99, 149, 0.3); border-radius: 8px; color: #fff; font-size: 14px; font-family: monospace; padding: 12px; resize: vertical; outline: none; transition: border-color 0.3s; } .rome-quick-add textarea:focus { border-color: rgba(235, 99, 149, 0.8); box-shadow: 0 0 0 2px rgba(235, 99, 149, 0.2); } .rome-quick-add textarea::placeholder { color: rgba(255, 255, 255, 0.5); } .rome-quick-add .instructions { color: #ccc; font-size: 12px; margin-top: 10px; text-align: center; } .rome-quick-add .instructions kbd { background: rgba(235, 99, 149, 0.2); border: 1px solid rgba(235, 99, 149, 0.4); border-radius: 3px; padding: 2px 6px; font-size: 11px; color: #eb6395; } /* Video rotation wrapper and custom controls */ .rome-video-wrapper { position: relative; overflow: hidden; background: #000; display: flex; align-items: center; justify-content: center; min-height: 100px; } .rome-video-wrapper video { transform-origin: center center; } .rome-custom-controls { display: none; position: absolute; bottom: 0; left: 0; right: 0; z-index: 10; background: linear-gradient(transparent, rgba(0, 0, 0, 0.7)); padding: 8px 12px 12px 12px; align-items: center; gap: 8px; } .rome-custom-controls.active { display: flex; } .rome-custom-controls button { background: none; border: none; color: #fff; cursor: pointer; font-size: 16px; } .rome-custom-controls .rome-progress-bar { flex: 1; box-sizing: content-box; height: 4px; background: rgba(255, 255, 255, 0.3); border-radius: 2px; cursor: pointer; position: relative; transition: height 0.15s ease-in-out, padding 0.15s ease-in-out; align-self: center; padding: 8px 0; background-clip: content-box; } .rome-video-wrapper.rome-bar-expanded .rome-progress-bar { height: 28px; padding: 0; } .rome-custom-controls .rome-progress-fill { height: 100%; background: #eb6395; border-radius: 2px; width: 0%; pointer-events: none; } .rome-custom-controls .rome-time-display { color: #fff; font-size: 12px; font-family: monospace; white-space: nowrap; } `); function watchAppendedPagesForDisclaimer() { return createDisclaimerWatcher({ selector: ROME_CONFIG.SELECTORS.DISCLAIMER, doc: document, root: document.body, dismiss: () => fetch("/user/disclaimer", { method: "POST" }), onLog: (...args) => logger.log(...args), onError: (...args) => console.error("[Rome] DisclaimerBypass", ...args) }); } function blockCollectionsModal() { const BLOCK_PATTERN = /\/collections\/modal\/save\//; const SAVE_BTN_RE = /^save-collection-/; function suppressDialogModal(saveBtn) { const dialogModal = document.querySelector("#dialogModal"); if (!dialogModal) return; let suppressed = false; function dismiss(modalObs2) { if (suppressed) return; suppressed = true; modalObs2.disconnect(); dialogModal.style.setProperty("display", "none", "important"); dialogModal.classList.remove("show", "in"); document.body.classList.remove("modal-open"); document.body.style.removeProperty("padding-right"); document.querySelectorAll(".modal-backdrop").forEach((el) => el.remove()); setTimeout(() => { dialogModal.style.removeProperty("display"); }, 500); if (saveBtn) { saveBtn.classList.add("album-unsave"); const svg = saveBtn.querySelector("svg"); if (svg) { svg.classList.remove("svg-far-fa-bookmark"); svg.classList.add("svg-fas-fa-bookmark", "pink"); const use = svg.querySelector("use"); if (use) { use.setAttribute("xlink:href", "#fas-fa-bookmark"); use.setAttribute("href", "#fas-fa-bookmark"); } } } } const modalObs = new MutationObserver(() => dismiss(modalObs)); modalObs.observe(dialogModal, { attributes: true, attributeFilter: ["style", "class"] }); const backdropObs = new MutationObserver((mutations) => { for (const m of mutations) { for (const node of m.addedNodes) { if (node.nodeType === Node.ELEMENT_NODE && node.classList.contains("modal-backdrop")) { node.remove(); } } } }); backdropObs.observe(document.body, { childList: true }); setTimeout(() => { modalObs.disconnect(); backdropObs.disconnect(); }, 1e3); } document.addEventListener( "click", (e) => { const btn = e.target.closest('[data-toggle="modal"], a, button'); if (!btn) return; const url = btn.dataset.remote || btn.getAttribute("href") || ""; if (BLOCK_PATTERN.test(url) && !btn.classList.contains("album-unsave")) { e.preventDefault(); e.stopPropagation(); const method = (btn.dataset.method || "GET").toUpperCase(); _unsafeWindow.fetch(url, { method }).then((r) => { if (!r.ok) console.error("[Rome] collection save/unsave HTTP error:", r.status, url); }).catch((err) => { console.warn("[Rome] collection save/unsave network error:", err); }); return; } if (SAVE_BTN_RE.test(btn.id || "") && !btn.classList.contains("album-unsave")) { suppressDialogModal(btn); } }, true ); function markPreSavedButtons() { document.querySelectorAll('[id^="save-collection-"]').forEach((btn) => { const svg = btn.querySelector("svg"); if (svg && svg.classList.contains("svg-fas-fa-bookmark")) { btn.classList.add("album-unsave"); } }); } markPreSavedButtons(); } function registerAlbumPageShortcuts() { document.addEventListener("keydown", (e) => { var _a, _b; const tag = (_a = e.target) == null ? void 0 : _a.tagName; if (tag === "INPUT" || tag === "TEXTAREA" || ((_b = e.target) == null ? void 0 : _b.isContentEditable)) return; if (e.ctrlKey || e.metaKey || e.altKey) return; const romeModal = document.getElementById("romeModal"); if (romeModal && romeModal.style.display === "block") return; const key = e.key.toLowerCase(); if (key === "w") { const likeBtn = document.querySelector("button.album-like, button.album-unlike"); if (!likeBtn) return; e.preventDefault(); likeBtn.click(); logger.log("AlbumShortcuts", "Like toggled (w)"); } else if (key === "e") { const saveBtn = document.querySelector('[id^="save-collection-"]'); if (!saveBtn) return; if (saveBtn.classList.contains("album-unsave")) { e.preventDefault(); logger.log("AlbumShortcuts", "Save ignored (already saved)"); return; } const albumId = saveBtn.id.slice("save-collection-".length); if (!albumId) return; e.preventDefault(); try { const $modalContent = $("#dialogModal .modal-content"); if ($modalContent.length === 0) { console.warn( "[AlbumShortcuts] dialogModal content not found; save aborted (albumId:", albumId, ")" ); return; } $modalContent.empty(); $modalContent.load( "/collections/modal/save/" + albumId, function(_resp, status) { if (status === "error") { console.warn("[AlbumShortcuts] Save load error (albumId:", albumId, ")"); return; } saveBtn.classList.add("album-unsave"); const svg = saveBtn.querySelector("svg"); if (svg) { svg.classList.remove("svg-far-fa-bookmark"); svg.classList.add("svg-fas-fa-bookmark", "pink"); const use = svg.querySelector("use"); if (use) { use.setAttribute("xlink:href", "#fas-fa-bookmark"); use.setAttribute("href", "#fas-fa-bookmark"); } } logger.log("AlbumShortcuts", "Save loaded (e)", { albumId }); } ); } catch (err) { console.warn("[AlbumShortcuts] Save failed:", err == null ? void 0 : err.message); } } }); } function initializeErome() { console.warn( `Rome v${GM_info.script.version} initializing for page type: ${currentPage}` ); const unifiedState = new EromeUnifiedState({ config: ROME_CONFIG, currentPage, libs }); let hybridFilter = null; switch (currentPage) { case "ALBUM": { try { const albumManager = new AlbumPageManager(unifiedState, { config: ROME_CONFIG, logger, multiVideo, createVjsObserverCallback, getVjsPlayer }); albumManager.ensureVjsCustomControls(); } catch (err) { console.error("Rome: AlbumPageManager init failed:", err); } try { registerAlbumPageShortcuts(); } catch (err) { console.error("Rome: registerAlbumPageShortcuts failed:", err); } break; } case "EXPLORE": try { hybridFilter = new HybridContentFilter(unifiedState.persistentStore, { config: ROME_CONFIG, logger, storage, checkMemoryUsage, createAlbumMutationObserver, wrap, notifyStorageFailure }); hybridFilter.initialize(); } catch (error) { console.error("Rome: Failed to initialize HybridContentFilter:", error); } try { const photoToggle = new PhotoOnlyAlbumToggle(unifiedState, { config: ROME_CONFIG, logger, currentPage }); if (hybridFilter) hybridFilter.setPhotoToggle(photoToggle); } catch (error) { console.error("Rome: Failed to initialize PhotoOnlyAlbumToggle:", error); } if (hybridFilter) { window.addEventListener("beforeunload", () => hybridFilter.cleanup(), { once: true }); } break; default: console.warn("Rome: No specific features for this page type"); } new WatchedTracker({ config: ROME_CONFIG, logger, storage, wrap, createAlbumMutationObserver, getVideoId, notifyStorageFailure }); const infyOverlayInstance = currentPage === "ALBUM" ? null : new InfyScrollOverlay({ config: ROME_CONFIG, logger, getValue: _GM_getValue, setValue: _GM_setValue }); if (!window.Rome) { window.Rome = { removeRegexPattern: hybridFilter ? (index) => hybridFilter.removePattern(index) : () => { }, setDebug: logger.setDebug, version: GM_info.script.version, performance: performance.memory ? { used: `${Math.round(performance.memory.usedJSHeapSize / 1024 / 1024)}MB`, limit: `${Math.round(performance.memory.jsHeapSizeLimit / 1024 / 1024)}MB` } : null }; if (window.Rome.performance) { Object.freeze(window.Rome.performance); } Object.freeze(window.Rome); } if (infyOverlayInstance) infyOverlayInstance.start(); } if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", watchAppendedPagesForDisclaimer); } else { watchAppendedPagesForDisclaimer(); } blockCollectionsModal(); if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", initializeErome); } else { setTimeout(initializeErome, 100); } _GM_registerMenuCommand("Rome Settings", () => { const modal = document.getElementById("romeModal"); if (modal) { modal.style.display = "block"; const debugToggle = document.getElementById("romeDebugToggle"); if (debugToggle) debugToggle.checked = ROME_CONFIG.DEBUG; } }); } main().catch((e) => { console.error("Rome: initialization failed", e); }); })();