// ==UserScript== // @name /vt/ Soundpost Alternatives // @namespace https://tampermonkey.net/ // @version 2.7 // @description Fixed playback bug, removed forced muting, added draggable converter, added Catbox auto-embed, and fixed leftover (embed) text. // @match https://boards.4chan.org/vt/* // @match https://boards.4chan.org/wsg/* // @grant GM_setClipboard // @grant GM_getValue // @grant GM_setValue // @run-at document-start // ==/UserScript== (function () { 'use strict'; const BOARD = location.pathname.split('/')[1]; const IS_VT = BOARD === 'vt'; const IS_WSG = BOARD === 'wsg'; let settings = { autoEmbed: GM_getValue('autoEmbed', true), hideCrosslink: GM_getValue('hideCrosslink', true), autoEmbedCatbox: GM_getValue('autoEmbedCatbox', false), hideCatboxLink: GM_getValue('hideCatboxLink', false) }; function saveSettings() { GM_setValue('autoEmbed', settings.autoEmbed); GM_setValue('hideCrosslink', settings.hideCrosslink); GM_setValue('autoEmbedCatbox', settings.autoEmbedCatbox); GM_setValue('hideCatboxLink', settings.hideCatboxLink); } // ==================== /vt/ DRAGGABLE ARROW + PANEL ==================== if (IS_VT) { const arrow = document.createElement('div'); Object.assign(arrow.style, { position: 'fixed', right: '8px', top: '45%', background: 'rgba(0,0,0,0.65)', color: '#fff', padding: '14px 7px', fontSize: '20px', fontFamily: 'monospace', cursor: 'grab', zIndex: '99999', borderRadius: '8px 0 0 8px', opacity: '0.4', transition: 'opacity 0.2s', userSelect: 'none' }); arrow.innerHTML = '<'; arrow.title = 'VT-WSG Tools (drag or click)'; document.body.appendChild(arrow); let isDragging = false; let hasMoved = false; let startY = 0; arrow.addEventListener('mousedown', (e) => { isDragging = true; hasMoved = false; startY = e.clientY; arrow.style.transition = 'none'; arrow.style.cursor = 'grabbing'; const rect = arrow.getBoundingClientRect(); arrow.style.right = 'auto'; arrow.style.left = rect.left + 'px'; }); document.addEventListener('mousemove', (e) => { if (!isDragging) return; if (Math.abs(e.clientY - startY) > 8) hasMoved = true; let newTop = e.clientY - (arrow.offsetHeight / 2); newTop = Math.max(30, Math.min(window.innerHeight - 100, newTop)); arrow.style.top = newTop + 'px'; }); document.addEventListener('mouseup', () => { if (isDragging) { isDragging = false; arrow.style.transition = 'opacity 0.2s'; arrow.style.cursor = 'grab'; if (!hasMoved) togglePanel(); } }); function togglePanel() { panel.style.display = panel.style.display === 'block' ? 'none' : 'block'; arrow.style.opacity = panel.style.display === 'block' ? '1' : '0.4'; } const panel = document.createElement('div'); Object.assign(panel.style, { position: 'fixed', right: '70px', top: '35%', background: 'rgba(25,25,25,0.96)', color: '#eee', padding: '16px', borderRadius: '8px', zIndex: '100000', width: '275px', display: 'none', boxShadow: '0 5px 15px rgba(0,0,0,0.7)', fontFamily: 'sans-serif' }); panel.innerHTML = `

/vt/ Soundpost Alternatives


Drag arrow anywhere.
Videos start paused.
`; document.body.appendChild(panel); setTimeout(() => { const cbEmbed = panel.querySelector('#cb-embed'); const cbHide = panel.querySelector('#cb-hide'); const cbCatboxEmbed = panel.querySelector('#cb-catbox-embed'); const cbCatboxHide = panel.querySelector('#cb-catbox-hide'); const postBtn = panel.querySelector('#btn-post'); cbEmbed.checked = settings.autoEmbed; cbHide.checked = settings.hideCrosslink; cbCatboxEmbed.checked = settings.autoEmbedCatbox; cbCatboxHide.checked = settings.hideCatboxLink; cbEmbed.addEventListener('change', () => { settings.autoEmbed = cbEmbed.checked; saveSettings(); }); cbHide.addEventListener('change', () => { settings.hideCrosslink = cbHide.checked; saveSettings(); document.querySelectorAll('.quotelink[data-embedded]').forEach(link => { link.style.display = settings.hideCrosslink ? 'none' : ''; }); }); cbCatboxEmbed.addEventListener('change', () => { settings.autoEmbedCatbox = cbCatboxEmbed.checked; saveSettings(); }); // Updated toggle logic to also hide/show the adjacent (embed) text if it exists cbCatboxHide.addEventListener('change', () => { settings.hideCatboxLink = cbCatboxHide.checked; saveSettings(); document.querySelectorAll('a[href*="catbox.moe"][data-embedded]').forEach(link => { link.style.display = settings.hideCatboxLink ? 'none' : ''; let embedSibling = link.nextElementSibling; if (embedSibling && embedSibling.classList.contains('catbox-native-embed')) { embedSibling.style.display = settings.hideCatboxLink ? 'none' : ''; } }); }); postBtn.addEventListener('click', () => window.location.href = "https://boards.4chan.org/wsg/catalog#s=gvy"); }, 200); } // ==================== /wsg/ DRAGGABLE & COLLAPSIBLE BOX ==================== if (IS_WSG) { function addCrossButtons() { document.querySelectorAll('.postContainer:not(.cross-processed)').forEach(post => { const postId = post.id.replace(/\D/g, ''); if (!postId) return; const postno = post.querySelector('.postno, .postNum'); if (!postno) return; const btn = document.createElement('span'); btn.textContent = ' [Cross]'; btn.style.cssText = 'color:#0a0;cursor:pointer;font-size:0.85em;'; btn.onclick = () => { GM_setClipboard(`>>>/wsg/${postId}`); btn.textContent = ' [Copied!]'; setTimeout(() => btn.textContent = ' [Cross]', 1500); }; postno.appendChild(btn); post.classList.add('cross-processed'); }); } new MutationObserver(addCrossButtons).observe(document.body, {childList:true, subtree:true}); window.addEventListener('load', addCrossButtons); const box = document.createElement('div'); Object.assign(box.style, { position: 'fixed', bottom: '18px', right: '18px', background: 'rgba(0,0,0,0.85)', border: '1px solid #0a0', borderRadius: '8px', width: '230px', zIndex: '99999', color: '#ddd', fontSize: '13px', fontFamily: 'sans-serif', overflow: 'hidden', boxShadow: '0 4px 10px rgba(0,0,0,0.5)' }); box.innerHTML = `
Cross-Board [-]
`; document.body.appendChild(box); const header = box.querySelector('#converter-header'); const body = box.querySelector('#converter-body'); const toggleBtn = box.querySelector('#converter-toggle'); const input = box.querySelector('#input'); const status = box.querySelector('#status'); // Collapsing Logic toggleBtn.addEventListener('click', (e) => { e.stopPropagation(); if (body.style.display === 'none') { body.style.display = 'block'; toggleBtn.textContent = '[-]'; header.style.borderBottom = '1px solid #0a0'; } else { body.style.display = 'none'; toggleBtn.textContent = '[+]'; header.style.borderBottom = 'none'; } }); // Dragging Logic let isBoxDragging = false; let boxStartX, boxStartY, initialMouseX, initialMouseY; header.addEventListener('mousedown', (e) => { if (e.target === toggleBtn) return; isBoxDragging = true; header.style.cursor = 'grabbing'; const rect = box.getBoundingClientRect(); boxStartX = rect.left; boxStartY = rect.top; initialMouseX = e.clientX; initialMouseY = e.clientY; box.style.bottom = 'auto'; box.style.right = 'auto'; box.style.left = boxStartX + 'px'; box.style.top = boxStartY + 'px'; }); document.addEventListener('mousemove', (e) => { if (!isBoxDragging) return; e.preventDefault(); const dx = e.clientX - initialMouseX; const dy = e.clientY - initialMouseY; box.style.left = (boxStartX + dx) + 'px'; box.style.top = (boxStartY + dy) + 'px'; }); document.addEventListener('mouseup', () => { if (isBoxDragging) { isBoxDragging = false; header.style.cursor = 'grab'; } }); // Conversion Logic function convert() { const val = input.value.trim(); const match = val.match(/>>?(\d+)/); if (!match) { status.innerHTML = 'Enter something like >>12345678'; return; } const cross = `>>>/wsg/${match[1]}`; GM_setClipboard(cross); status.innerHTML = `[OK] Copied: ${cross}`; setTimeout(() => status.textContent = '', 2800); } input.addEventListener('keypress', e => { if (e.key === 'Enter') convert(); }); input.addEventListener('input', () => { if (input.value.trim().length > 6) convert(); }); } // ==================== AUTO-EMBED VIDEOS ON /vt/ ==================== if (IS_VT) { // Helper function for the clickable expanding video behavior function bindVideoClickBehavior(video) { let expanded = false; video.addEventListener('click', (e) => { const rect = video.getBoundingClientRect(); const clickY = e.clientY - rect.top; if (expanded && clickY > rect.height - 40) return; e.preventDefault(); e.stopImmediatePropagation(); expanded = !expanded; if (expanded) { video.style.maxWidth = '88vw'; video.style.maxHeight = '75vh'; let playPromise = video.play(); if (playPromise !== undefined) { playPromise.catch(err => console.debug('Playback interrupted:', err)); } } else { video.style.maxWidth = '340px'; video.style.maxHeight = ''; video.pause(); } }); } async function embedVideo(link) { if (link.dataset.embedded === 'true') return; link.dataset.embedded = 'true'; if (link.nextElementSibling && link.nextElementSibling.tagName === 'VIDEO') return; try { let href = link.href.startsWith('//') ? 'https:' + link.href : link.href; const match = href.match(/\/wsg\/(?:thread|res)\/(\d+)/); if (!match) return; const threadId = match[1]; const postMatch = href.match(/#p?(\d+)/) || link.textContent.match(/(\d+)/); const postId = postMatch ? postMatch[1] : threadId; const json = await fetch(`https://a.4cdn.org/wsg/thread/${threadId}.json`).then(r => r.json()); const post = json.posts.find(p => String(p.no) === postId); if (!post || !['.webm','.mp4','.gif'].includes(post.ext || '')) return; const videoUrl = `https://i.4cdn.org/wsg/${post.tim}${post.ext}`; const video = document.createElement('video'); Object.assign(video, { src: videoUrl, controls: true, loop: true, style: 'max-width: 340px; margin: 8px 0; display:block; border:1px solid #444; border-radius:4px; cursor:pointer;' }); video.pause(); bindVideoClickBehavior(video); link.after(video); if (settings.hideCrosslink) { link.style.display = 'none'; } } catch (e) { console.debug('Embed skipped', e); } } async function embedCatbox(link) { if (link.dataset.embedded === 'true') return; if (!link.href.match(/\.(webm|mp4|gif)$/i)) return; link.dataset.embedded = 'true'; if (link.nextElementSibling && link.nextElementSibling.tagName === 'VIDEO') return; try { const video = document.createElement('video'); Object.assign(video, { src: link.href, controls: true, loop: true, style: 'max-width: 340px; margin: 8px 0; display:block; border:1px solid #444; border-radius:4px; cursor:pointer;' }); video.pause(); bindVideoClickBehavior(video); // Check for a native or 4chanX (embed) button directly following the link let embedSibling = link.nextElementSibling; if (embedSibling && embedSibling.tagName === 'A' && embedSibling.textContent.toLowerCase().includes('embed')) { embedSibling.classList.add('catbox-native-embed'); embedSibling.after(video); // Place the video after the embed text } else { link.after(video); } if (settings.hideCatboxLink) { link.style.display = 'none'; if (embedSibling && embedSibling.classList.contains('catbox-native-embed')) { embedSibling.style.display = 'none'; } } } catch (e) { console.debug('Catbox embed skipped', e); } } function scan() { if (settings.autoEmbed) { document.querySelectorAll('.quotelink[href*="wsg"]:not([data-embedded])').forEach(link => { embedVideo(link); }); } if (settings.autoEmbedCatbox) { document.querySelectorAll('a[href*="catbox.moe"]:not([data-embedded])').forEach(link => { embedCatbox(link); }); } } new MutationObserver(scan).observe(document.body, {childList:true, subtree:true}); window.addEventListener('load', scan); } console.log('%cVT-WSG Crosslinker v2.7 loaded on /' + BOARD, 'color:#0a0;font-weight:bold'); })();