This commit is contained in:
2026-09-09 22:12:38 +02:00
parent f87ac267f7
commit 43ff87ed62
2 changed files with 87 additions and 27 deletions
+84 -25
View File
@@ -25,11 +25,29 @@
const isMobile = /Mobi/i.test(navigator.userAgent); const isMobile = /Mobi/i.test(navigator.userAgent);
function isItemPage() { 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; const path = window.location.pathname;
// Strictly match item pages (e.g., /123, /user/name/123) and exclude grids/specials 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);
const isItem = (path.match(/^\/\d+/) || path.split('/').some(s => /^\d+$/.test(s))) && !path.match(/\/p\//); if (isForbidden) return false;
const isForbidden = path === '/upload' || path.startsWith('/admin') || path.startsWith('/mod');
return isItem && !isForbidden; 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 ---------- // ---------- Settings / Config ----------
@@ -235,11 +253,12 @@
applyConfigToCanvas(); applyConfigToCanvas();
function drawFrame() { function drawFrame(force = false) {
if (!enabled || video.paused || video.ended) return; if (!enabled || (!force && (video.paused || video.ended))) return;
try { try {
const w = canvas.width; const w = canvas.width;
const h = canvas.height; const h = canvas.height;
if (!w || !h) return;
ctx.drawImage(video, 0, 0, w, h); ctx.drawImage(video, 0, 0, w, h);
// If advanced palette reduction is disabled, skip quantization // If advanced palette reduction is disabled, skip quantization
@@ -249,7 +268,7 @@
ctx.putImageData(frame, 0, 0); ctx.putImageData(frame, 0, 0);
} }
} catch (e) { } catch (e) {
// ignore if (window.f0ckDebug) window.f0ckDebug("[flash_yank] drawFrame error:", e);
} }
} }
@@ -271,6 +290,8 @@
enabled = true; enabled = true;
canvas.style.display = 'block'; canvas.style.display = 'block';
video.style.visibility = 'hidden'; // Keep layout space! video.style.visibility = 'hidden'; // Keep layout space!
applyConfigToCanvas();
drawFrame(true);
if (!video.paused && !video.ended) startLoop(); if (!video.paused && !video.ended) startLoop();
} }
@@ -296,9 +317,20 @@
stopLoop(); stopLoop();
applyConfigToCanvas(); applyConfigToCanvas();
if (wasEnabled) { if (wasEnabled) {
drawFrame(true);
if (!video.paused && !video.ended) {
startLoop(); startLoop();
} }
} }
}
const handleFrameUpdate = () => {
if (enabled) drawFrame(true);
};
const handleMetaLoaded = () => {
applyConfigToCanvas();
if (enabled) drawFrame(true);
};
function destroy() { function destroy() {
disable(); disable();
@@ -306,12 +338,17 @@
video.removeEventListener('play', startLoop); video.removeEventListener('play', startLoop);
video.removeEventListener('pause', stopLoop); video.removeEventListener('pause', stopLoop);
video.removeEventListener('ended', stopLoop); video.removeEventListener('ended', stopLoop);
video.removeEventListener('seeked', handleFrameUpdate);
video.removeEventListener('loadeddata', handleMetaLoaded);
video.removeEventListener('loadedmetadata', handleMetaLoaded);
} }
video.addEventListener('play', startLoop); video.addEventListener('play', startLoop);
video.addEventListener('pause', stopLoop); video.addEventListener('pause', stopLoop);
video.addEventListener('ended', 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 }; return { enable, disable, toggle, isEnabled, destroy, onConfigChanged };
} }
@@ -319,13 +356,18 @@
function setupVideo(video) { function setupVideo(video) {
if (!video) return; 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 (!isItemPage()) {
if (ui) ui.wrapper.style.display = 'none'; if (ui) ui.wrapper.style.display = 'none';
return; return;
} }
// Prioritize the main item player (id="my-video" or class "viewer") // 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 (video.dataset.flashFilterAttached === '1') {
// If already attached, ensure its currentController is restored if it's the primary one // If already attached, ensure its currentController is restored if it's the primary one
@@ -524,8 +566,11 @@
const bottom = rect.bottom + HOVER_MARGIN; const bottom = rect.bottom + HOVER_MARGIN;
if (e.clientX < left || e.clientX > right || e.clientY < top || e.clientY > bottom) { if (e.clientX < left || e.clientX > right || e.clientY < top || e.clientY > bottom) {
const overTrigger = e.target.closest && (e.target.closest('#toggleswf') || e.target === floatingBadge);
if (!overTrigger) {
hidePanel(); hidePanel();
} }
}
}); });
const title = panel.querySelector('#w0bm-title'); const title = panel.querySelector('#w0bm-title');
@@ -556,20 +601,24 @@
settings.enabled = !settings.enabled; settings.enabled = !settings.enabled;
saveSettings(); saveSettings();
// If a specific controller was the target (e.g. clicked inside a player), use it. let controller = targetController || currentController;
// Otherwise use the global currentController (main player). if (!controller) {
const controller = targetController || currentController; 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 (controller) {
if (settings.enabled) controller.enable(); if (settings.enabled) controller.enable();
else controller.disable(); 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(); 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 // 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 // If clicked a button inside a player, try to get THAT player's controller
let targetCtrl = null; let targetCtrl = null;
if (swfBtn) { if (swfBtn) {
const player = swfBtn.closest('.v0ck'); const player = swfBtn.closest('.v0ck') || swfBtn.closest('.media-object') || document.querySelector('.v0ck');
const vid = player ? player.querySelector('video') : null; const vid = player ? player.querySelector('video') : (document.getElementById('my-video') || document.querySelector('video'));
if (vid && vid.__flashFilterController) { if (vid) {
if (!vid.__flashFilterController) {
setupVideo(vid);
}
targetCtrl = vid.__flashFilterController; targetCtrl = vid.__flashFilterController;
} }
} }
toggleEnabledFromUI(targetCtrl); toggleEnabledFromUI(targetCtrl);
// On mobile, explicitly show panel on click/tap // Explicitly show options panel on click for both mobile and desktop
if (isMobile) { if (swfBtn || floatingBadge) {
handleBadgeHover(swfBtn || floatingBadge); handleBadgeHover(swfBtn || floatingBadge);
} }
} }
@@ -632,7 +684,7 @@
ui.slider.disabled = !isEnabled; ui.slider.disabled = !isEnabled;
// Visual state: strike-through when disabled // 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 // 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). // with the primary player present but no in-player SWF button (e.g. v0ck not loaded).
@@ -646,7 +698,7 @@
if (!b) return; if (!b) return;
b.style.textDecoration = isEnabled ? 'none' : 'line-through'; b.style.textDecoration = isEnabled ? 'none' : 'line-through';
b.style.opacity = isEnabled ? '1' : '0.6'; 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.color = isEnabled ? 'var(--accent, #9f0)' : '#fff';
b.style.fontWeight = isEnabled ? 'bold' : 'normal'; b.style.fontWeight = isEnabled ? 'bold' : 'normal';
} }
@@ -667,6 +719,13 @@
const tag = document.activeElement?.tagName?.toLowerCase(); const tag = document.activeElement?.tagName?.toLowerCase();
if (tag === 'input' || tag === 'textarea' || document.activeElement?.isContentEditable) return; 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; if (!currentController) return;
settings.enabled = !settings.enabled; settings.enabled = !settings.enabled;
@@ -677,7 +736,7 @@
updateUIFromSettings(); updateUIFromSettings();
if (typeof window.flashMessage === 'function') { 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');
} }
}); });
} }
+1
View File
@@ -622,6 +622,7 @@ process.on('uncaughtException', err => {
app.use(async (req, res) => { app.use(async (req, res) => {
if (req.url?.pathname?.endsWith('.map')) { if (req.url?.pathname?.endsWith('.map')) {
res.writeHead(404, { 'Content-Type': 'text/plain' }).end('Not Found'); res.writeHead(404, { 'Content-Type': 'text/plain' }).end('Not Found');
req.url.pathname = '/handled_map_bypass';
} }
}); });