From 43ff87ed62a22c692fb111aae775da1efc947cd6 Mon Sep 17 00:00:00 2001 From: Kibi Kelburton Date: Wed, 9 Sep 2026 22:12:38 +0200 Subject: [PATCH] kjh --- public/s/js/flash_yank.js | 113 +++++++++++++++++++++++++++++--------- src/index.mjs | 1 + 2 files changed, 87 insertions(+), 27 deletions(-) diff --git a/public/s/js/flash_yank.js b/public/s/js/flash_yank.js index 890c0db..7875be1 100644 --- a/public/s/js/flash_yank.js +++ b/public/s/js/flash_yank.js @@ -25,11 +25,29 @@ const isMobile = /Mobi/i.test(navigator.userAgent); function isItemPage() { + // Fast DOM check: item view or primary media element present + if (document.getElementById('my-video') || document.querySelector('#main.item-view, .item-view, .item-layout-container, .item-main-content')) { + return true; + } + const path = window.location.pathname; - // Strictly match item pages (e.g., /123, /user/name/123) and exclude grids/specials - const isItem = (path.match(/^\/\d+/) || path.split('/').some(s => /^\d+$/.test(s))) && !path.match(/\/p\//); - const isForbidden = path === '/upload' || path.startsWith('/admin') || path.startsWith('/mod'); - return isItem && !isForbidden; + const isForbidden = /^\/(s|b|t|ca|a|login|register|settings|about|terms|rules|api|logout|auth|admin|mod|comments|notifications|feed|upload|tags|halls|ranking|abyss|random)(\/|$)/.test(path); + if (isForbidden) return false; + + const segments = path.split('/').filter(Boolean); + if (segments.length === 0) return false; + + // Path ends in /p/123 -> pagination grid, not single item + const last = segments[segments.length - 1]; + if (/^\d+$/.test(last) && segments.length >= 2 && segments[segments.length - 2] === 'p') { + return false; + } + if (['p', 'tags', 'halls', 'ranking', 'abyss', 'uploads', 'favs', 'f0cks'].includes(last)) { + return false; + } + + // Single item path: e.g. /123, /ZLHmYNnj-kK, /tag/foo/ZLHmYNnj-kK, /h/bar/123, etc. + return segments.some(s => /^[a-zA-Z0-9_-]{11}$/.test(s) || /^\d+$/.test(s)); } // ---------- Settings / Config ---------- @@ -235,11 +253,12 @@ applyConfigToCanvas(); - function drawFrame() { - if (!enabled || video.paused || video.ended) return; + function drawFrame(force = false) { + if (!enabled || (!force && (video.paused || video.ended))) return; try { const w = canvas.width; const h = canvas.height; + if (!w || !h) return; ctx.drawImage(video, 0, 0, w, h); // If advanced palette reduction is disabled, skip quantization @@ -249,7 +268,7 @@ ctx.putImageData(frame, 0, 0); } } catch (e) { - // ignore + if (window.f0ckDebug) window.f0ckDebug("[flash_yank] drawFrame error:", e); } } @@ -271,6 +290,8 @@ enabled = true; canvas.style.display = 'block'; video.style.visibility = 'hidden'; // Keep layout space! + applyConfigToCanvas(); + drawFrame(true); if (!video.paused && !video.ended) startLoop(); } @@ -296,22 +317,38 @@ stopLoop(); applyConfigToCanvas(); if (wasEnabled) { - startLoop(); + drawFrame(true); + if (!video.paused && !video.ended) { + startLoop(); + } } } + const handleFrameUpdate = () => { + if (enabled) drawFrame(true); + }; + const handleMetaLoaded = () => { + applyConfigToCanvas(); + if (enabled) drawFrame(true); + }; + function destroy() { disable(); canvas.remove(); video.removeEventListener('play', startLoop); video.removeEventListener('pause', stopLoop); video.removeEventListener('ended', stopLoop); + video.removeEventListener('seeked', handleFrameUpdate); + video.removeEventListener('loadeddata', handleMetaLoaded); + video.removeEventListener('loadedmetadata', handleMetaLoaded); } video.addEventListener('play', startLoop); video.addEventListener('pause', stopLoop); video.addEventListener('ended', stopLoop); - video.addEventListener('loadedmetadata', applyConfigToCanvas); // Recalculate when metadata allows + video.addEventListener('seeked', handleFrameUpdate); + video.addEventListener('loadeddata', handleMetaLoaded); + video.addEventListener('loadedmetadata', handleMetaLoaded); return { enable, disable, toggle, isEnabled, destroy, onConfigChanged }; } @@ -319,13 +356,18 @@ function setupVideo(video) { if (!video) return; + // Ignore sidebar sticker / emoji preview / modal videos + if (video.closest && video.closest('.sidebar-activity, .global-sidebar-right, .emoji-preview, .modal')) { + return; + } + if (!isItemPage()) { if (ui) ui.wrapper.style.display = 'none'; return; } // Prioritize the main item player (id="my-video" or class "viewer") - const isPrimary = video.id === 'my-video' || video.classList.contains('viewer') || video.classList.contains('v0ck_video'); + const isPrimary = video.id === 'my-video' || video.classList.contains('viewer') || video.classList.contains('v0ck_video') || (video.closest && !!video.closest('.media-object, .v0ck')); if (video.dataset.flashFilterAttached === '1') { // If already attached, ensure its currentController is restored if it's the primary one @@ -524,7 +566,10 @@ const bottom = rect.bottom + HOVER_MARGIN; if (e.clientX < left || e.clientX > right || e.clientY < top || e.clientY > bottom) { - hidePanel(); + const overTrigger = e.target.closest && (e.target.closest('#toggleswf') || e.target === floatingBadge); + if (!overTrigger) { + hidePanel(); + } } }); @@ -556,20 +601,24 @@ settings.enabled = !settings.enabled; saveSettings(); - // If a specific controller was the target (e.g. clicked inside a player), use it. - // Otherwise use the global currentController (main player). - const controller = targetController || currentController; + let controller = targetController || currentController; + if (!controller) { + const mainVid = document.getElementById('my-video') || document.querySelector('video.v0ck_video, video.viewer, video'); + if (mainVid) { + if (!mainVid.__flashFilterController) setupVideo(mainVid); + controller = mainVid.__flashFilterController; + } + } if (controller) { if (settings.enabled) controller.enable(); else controller.disable(); } - // For global consistency, if settings.enabled changed, we might want to toggle ALL? - // But per user request, we focus on the item player. - // If there's another video that isn't the currentController, it won't toggle here, - // but the hotkey and UI rely on currentController. - updateUIFromSettings(); + + if (typeof window.flashMessage === 'function') { + window.flashMessage(`Flash Yank ${settings.enabled ? 'enabled' : 'disabled'}`, 2000, settings.enabled ? 'success' : 'info'); + } } // Click SWF badge or title to toggle filter enabled/disabled @@ -581,15 +630,18 @@ // If clicked a button inside a player, try to get THAT player's controller let targetCtrl = null; if (swfBtn) { - const player = swfBtn.closest('.v0ck'); - const vid = player ? player.querySelector('video') : null; - if (vid && vid.__flashFilterController) { + const player = swfBtn.closest('.v0ck') || swfBtn.closest('.media-object') || document.querySelector('.v0ck'); + const vid = player ? player.querySelector('video') : (document.getElementById('my-video') || document.querySelector('video')); + if (vid) { + if (!vid.__flashFilterController) { + setupVideo(vid); + } targetCtrl = vid.__flashFilterController; } } toggleEnabledFromUI(targetCtrl); - // On mobile, explicitly show panel on click/tap - if (isMobile) { + // Explicitly show options panel on click for both mobile and desktop + if (swfBtn || floatingBadge) { handleBadgeHover(swfBtn || floatingBadge); } } @@ -632,7 +684,7 @@ ui.slider.disabled = !isEnabled; // Visual state: strike-through when disabled - const swfButtons = Array.from(document.querySelectorAll('.v0ck_menu_item')).filter(b => b.textContent.trim() === 'SWF'); + const swfButtons = Array.from(document.querySelectorAll('#toggleswf, .v0ck_menu_item')).filter(b => b.id === 'toggleswf' || b.textContent.trim() === 'SWF'); // Handle floating badge visibility — only show as fallback when on an item page // with the primary player present but no in-player SWF button (e.g. v0ck not loaded). @@ -646,7 +698,7 @@ if (!b) return; b.style.textDecoration = isEnabled ? 'none' : 'line-through'; b.style.opacity = isEnabled ? '1' : '0.6'; - if (b.classList.contains('v0ck_menu_item')) { + if (b.classList.contains('v0ck_menu_item') || b.id === 'toggleswf') { b.style.color = isEnabled ? 'var(--accent, #9f0)' : '#fff'; b.style.fontWeight = isEnabled ? 'bold' : 'normal'; } @@ -667,6 +719,13 @@ const tag = document.activeElement?.tagName?.toLowerCase(); if (tag === 'input' || tag === 'textarea' || document.activeElement?.isContentEditable) return; + if (!currentController) { + const mainVid = document.getElementById('my-video') || document.querySelector('video.v0ck_video, video.viewer, video'); + if (mainVid) { + if (!mainVid.__flashFilterController) setupVideo(mainVid); + currentController = mainVid.__flashFilterController; + } + } if (!currentController) return; settings.enabled = !settings.enabled; @@ -677,7 +736,7 @@ updateUIFromSettings(); if (typeof window.flashMessage === 'function') { - window.flashMessage(`Flash Yank ${settings.enabled ? 'enabled' : 'disabled'}`, 2000, settings.enabled ? 'success' : 'success'); + window.flashMessage(`Flash Yank ${settings.enabled ? 'enabled' : 'disabled'}`, 2000, settings.enabled ? 'success' : 'info'); } }); } diff --git a/src/index.mjs b/src/index.mjs index 494086d..2939bee 100644 --- a/src/index.mjs +++ b/src/index.mjs @@ -622,6 +622,7 @@ process.on('uncaughtException', err => { app.use(async (req, res) => { if (req.url?.pathname?.endsWith('.map')) { res.writeHead(404, { 'Content-Type': 'text/plain' }).end('Not Found'); + req.url.pathname = '/handled_map_bypass'; } });