您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Utility to enhance Vadapav experience
// ==UserScript== // @name Vadapav Utils // @namespace ff-utils@vadapav // @version 1.0 // @description Utility to enhance Vadapav experience // @author im.nbn // @match *://*.vadapav.mov/* // @license GPL-3.0-or-later // @grant none // ==/UserScript== (function () { 'use strict'; const defaultSettings = { openInIcon: '↗', copyIcon: '🔗', downloadIcon: '↓', checkIcon: '✔︎', urlOpenerPrefix: 'iina://open?url=', horizontalIcon: '↔', verticalIcon: '↕', autoOpenIn: false, linkSeparator: '\n', containerOrientation: 'vertical', }; let openInIcon = localStorage.getItem('openInIcon') || defaultSettings.openInIcon; let copyIcon = localStorage.getItem('copyIcon') || defaultSettings.copyIcon; let downloadIcon = localStorage.getItem('downloadIcon') || defaultSettings.downloadIcon; let urlOpenerPrefix = localStorage.getItem('urlOpenerPrefix') || defaultSettings.urlOpenerPrefix; let autoOpenIn = localStorage.getItem('autoOpenIn') === 'true'; let linkSeparator = localStorage.getItem('linkSeparator') || defaultSettings.linkSeparator; let containerOrientation = localStorage.getItem('containerOrientation') || defaultSettings.containerOrientation; let isOpenInMode = false; function toggleOpenerLinks(forceEnable) { isOpenInMode = forceEnable !== undefined ? forceEnable : !isOpenInMode; document.querySelectorAll('a.file-entry.wrap').forEach(anchor => { if (isOpenInMode) { if (!anchor.href.startsWith(urlOpenerPrefix)) { anchor.href = `${urlOpenerPrefix}${anchor.href}`; } } else { if (anchor.href.startsWith(urlOpenerPrefix)) { anchor.href = anchor.href.replace(urlOpenerPrefix, ''); } } }); openinButton.style.backgroundColor = isOpenInMode ? '#28a745' : '#666'; } function copyLinksToClipboard() { const links = Array.from(document.querySelectorAll('a.file-entry.wrap')) .map(anchor => anchor.href.replace(urlOpenerPrefix, '')) .join(linkSeparator); const textarea = document.createElement('textarea'); textarea.value = links; document.body.appendChild(textarea); textarea.select(); document.execCommand('copy'); document.body.removeChild(textarea); animateButton(copyButton, defaultSettings.checkIcon); } function downloadAllFiles() { document.querySelectorAll('a.file-entry.wrap').forEach(anchor => { const link = document.createElement('a'); link.href = anchor.href.replace(urlOpenerPrefix, ''); link.download = ''; document.body.appendChild(link); link.click(); document.body.removeChild(link); }); animateButton(downloadAllButton, defaultSettings.checkIcon); } function animateButton(button, symbol) { const originalContent = button.innerHTML; button.classList.add('rotate-icon'); setTimeout(() => { button.innerHTML = symbol; button.style.backgroundColor = '#28a745'; button.classList.remove('rotate-icon'); setTimeout(() => { button.innerHTML = originalContent; button.style.backgroundColor = '#666'; }, 1000); }, 500); } const style = document.createElement('style'); style.innerHTML = ` .button-bounce { transition: transform 0.2s ease-in-out; } .button-bounce:hover { transform: scale(1.1); } .rotate-icon { animation: rotate 0.5s linear; } @keyframes rotate { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } .draggable { cursor: move; } .overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; cursor: move; z-index: 1; display: none; } label { user-select: none; } `; document.head.appendChild(style); const container = createContainer('containerTop', 'containerLeft', '10000'); const buttonStyle = ` background-color: #666; color: white; border: none; padding: 10px; cursor: pointer; font-size: 16px; border-radius: 50%; width: 40px; height: 40px; display: flex; align-items: center; justify-content: center; transition: background-color 0.3s; `; const openinButton = createButton(openInIcon, buttonStyle, 'Toggle Opener', () => toggleOpenerLinks()); const copyButton = createButton(copyIcon, buttonStyle, 'Copy links to clipboard', copyLinksToClipboard); const downloadAllButton = createButton(downloadIcon, buttonStyle, 'Download all files', downloadAllFiles); const editButton = createButton('✎', buttonStyle, 'Edit settings', toggleSettings); container.append(openinButton, copyButton, downloadAllButton, editButton); document.body.appendChild(container); let settingsContainer; function toggleSettings() { if (settingsContainer) { document.body.removeChild(settingsContainer); settingsContainer = null; editButton.innerHTML = '✎'; } else { settingsContainer = createSettingsContainer(); document.body.appendChild(settingsContainer); editButton.innerHTML = '✖'; makeDraggable(settingsContainer, 'settingsContainerTop', 'settingsContainerLeft'); initializeSettingsContainerPosition(settingsContainer); } } function createButton(innerHTML, style, title, onClick) { const button = document.createElement('button'); button.innerHTML = innerHTML; button.style = style; button.classList.add('button-bounce'); button.title = title; button.onclick = onClick; return button; } function createContainer(topKey, leftKey, zIndex) { const container = document.createElement('div'); container.style.position = 'fixed'; container.style.top = localStorage.getItem(topKey) || '50px'; container.style.left = localStorage.getItem(leftKey) || 'calc(100% - 60px)'; container.style.zIndex = zIndex; container.style.backgroundColor = '#444'; container.style.border = '1px solid #555'; container.style.padding = '10px 15px'; container.style.boxShadow = '0px 2px 5px rgba(0, 0, 0, 0.5)'; container.style.borderRadius = '10px'; container.style.display = 'flex'; container.style.flexDirection = containerOrientation === 'vertical' ? 'column' : 'row'; container.style.gap = '10px'; container.classList.add('draggable'); makeDraggable(container, topKey, leftKey); return container; } function createSettingsContainer() { const settingsContainer = document.createElement('div'); settingsContainer.style.position = 'fixed'; settingsContainer.style.top = localStorage.getItem('settingsContainerTop') || '100px'; settingsContainer.style.left = localStorage.getItem('settingsContainerLeft') || 'calc(100% - 280px)'; settingsContainer.style.zIndex = '10001'; settingsContainer.style.backgroundColor = '#444'; settingsContainer.style.border = '1px solid #555'; settingsContainer.style.padding = '20px'; settingsContainer.style.boxShadow = '0px 2px 5px rgba(0, 0, 0, 0.5)'; settingsContainer.style.borderRadius = '10px'; settingsContainer.style.display = 'flex'; settingsContainer.style.flexDirection = 'column'; settingsContainer.style.gap = '10px'; const overlay = document.createElement('div'); overlay.className = 'overlay'; settingsContainer.appendChild(overlay); const inputStyle = ` background-color: white; border: 1px solid #ccc; padding: 8px; font-size: 14px; border-radius: 5px; width: 100%; box-sizing: border-box; `; settingsContainer.append( createInput('text', openInIcon, 'OpenIn Icon', inputStyle, val => openInIcon = val), createInput('text', copyIcon, 'Copy Icon', inputStyle, val => copyIcon = val), createInput('text', downloadIcon, 'Download Icon', inputStyle, val => downloadIcon = val), createInput('text', urlOpenerPrefix, 'iina://open?url=', inputStyle, val => urlOpenerPrefix = val), createAutoOpenInLabel(), createSeparatorLabel(inputStyle), createOrientationButton(buttonStyle), createSaveButton(buttonStyle) ); return settingsContainer; } function createInput(type, value, placeholder, style, onChange) { const input = document.createElement('input'); input.type = type; input.value = value; input.placeholder = placeholder; input.style = style; input.oninput = () => onChange(input.value); return input; } function createAutoOpenInLabel() { const autoOpenInLabel = document.createElement('label'); const autoOpenInCheckbox = document.createElement('input'); autoOpenInCheckbox.type = 'checkbox'; autoOpenInCheckbox.checked = autoOpenIn; autoOpenInLabel.style = ` display: flex; align-items: center; gap: 10px; `; autoOpenInLabel.appendChild(autoOpenInCheckbox); autoOpenInLabel.appendChild(document.createTextNode('Auto apply opener prefix')); autoOpenInCheckbox.onchange = () => { autoOpenIn = autoOpenInCheckbox.checked; localStorage.setItem('autoOpenIn', autoOpenIn); toggleOpenerLinks(autoOpenIn); }; return autoOpenInLabel; } function createSeparatorLabel(inputStyle) { const separatorLabel = document.createElement('label'); const separatorSelect = document.createElement('select'); const separatorOptions = [ { value: '\n', label: 'New Line (\\n)' }, { value: ',', label: 'Comma (,)' }, { value: ';', label: 'Semi-Colon (;)' }, { value: 'custom', label: 'Custom' } ]; separatorOptions.forEach(option => { const opt = document.createElement('option'); opt.value = option.value; opt.textContent = option.label; if (linkSeparator === option.value) { opt.selected = true; } separatorSelect.appendChild(opt); }); const customSeparatorInput = document.createElement('input'); customSeparatorInput.type = 'text'; customSeparatorInput.placeholder = 'Custom Separator'; customSeparatorInput.style = inputStyle; customSeparatorInput.value = linkSeparator; separatorSelect.onchange = () => { if (separatorSelect.value === 'custom') { customSeparatorInput.style.display = 'block'; linkSeparator = customSeparatorInput.value; } else { customSeparatorInput.style.display = 'none'; linkSeparator = separatorSelect.value; } localStorage.setItem('linkSeparator', linkSeparator); }; customSeparatorInput.oninput = () => { linkSeparator = customSeparatorInput.value; localStorage.setItem('linkSeparator', linkSeparator); }; if (linkSeparator !== '\n' && linkSeparator !== ',' && linkSeparator !== ';') { separatorSelect.value = 'custom'; customSeparatorInput.style.display = 'block'; } else { customSeparatorInput.style.display = 'none'; } separatorLabel.style = ` display: flex; flex-direction: column; align-items: flex-start; gap: 10px; `; separatorLabel.appendChild(document.createTextNode('Link Separator for download')); separatorLabel.appendChild(separatorSelect); separatorLabel.appendChild(customSeparatorInput); return separatorLabel; } function createOrientationButton(buttonStyle) { const toggleOrientationButton = createButton( containerOrientation === 'vertical' ? defaultSettings.verticalIcon : defaultSettings.horizontalIcon, buttonStyle, 'Toggle Orientation', () => { toggleOrientation(); toggleOrientationButton.innerHTML = containerOrientation === 'vertical' ? defaultSettings.verticalIcon : defaultSettings.horizontalIcon; } ); return toggleOrientationButton; } function createSaveButton(buttonStyle) { const saveButton = document.createElement('button'); saveButton.innerHTML = 'Save'; saveButton.style = ` background-color: #28a745; color: white; border: none; padding: 10px; cursor: pointer; font-size: 16px; border-radius: 5px; `; saveButton.onclick = () => { localStorage.setItem('openInIcon', openInIcon); localStorage.setItem('copyIcon', copyIcon); localStorage.setItem('downloadIcon', downloadIcon); localStorage.setItem('urlOpenerPrefix', urlOpenerPrefix); openinButton.innerHTML = openInIcon; copyButton.innerHTML = copyIcon; downloadAllButton.innerHTML = downloadIcon; document.body.removeChild(settingsContainer); settingsContainer = null; editButton.innerHTML = '✎'; }; return saveButton; } function toggleOrientation() { containerOrientation = containerOrientation === 'vertical' ? 'horizontal' : 'vertical'; container.style.flexDirection = containerOrientation === 'vertical' ? 'column' : 'row'; const containerRect = container.getBoundingClientRect(); if (containerRect.right > window.innerWidth) { container.style.left = (window.innerWidth - containerRect.width) + 'px'; } if (containerRect.bottom > window.innerHeight) { container.style.top = (window.innerHeight - containerRect.height) + 'px'; } localStorage.setItem('containerOrientation', containerOrientation); localStorage.setItem('containerTop', container.style.top); localStorage.setItem('containerLeft', container.style.left); } function makeDraggable(element, topKey, leftKey) { let offsetX = 0, offsetY = 0, initialMouseX = 0, initialMouseY = 0; element.onmousedown = function (e) { if (['INPUT', 'BUTTON', 'SELECT', 'TEXTAREA'].includes(e.target.tagName)) { return; } e.preventDefault(); initialMouseX = e.clientX; initialMouseY = e.clientY; offsetX = initialMouseX - element.getBoundingClientRect().left; offsetY = initialMouseY - element.getBoundingClientRect().top; document.onmousemove = function (e) { e.preventDefault(); const newMouseX = e.clientX; const newMouseY = e.clientY; let newTop = newMouseY - offsetY; let newLeft = newMouseX - offsetX; if (newTop < 0) newTop = 0; if (newLeft < 0) newLeft = 0; if (newTop + element.clientHeight > window.innerHeight) { newTop = window.innerHeight - element.clientHeight; } if (newLeft + element.clientWidth > window.innerWidth) { newLeft = window.innerWidth - element.clientWidth; } element.style.top = newTop + 'px'; element.style.left = newLeft + 'px'; localStorage.setItem(topKey, element.style.top); localStorage.setItem(leftKey, element.style.left); }; document.onmouseup = function () { document.onmouseup = null; document.onmousemove = null; }; }; } function initializeSettingsContainerPosition(settingsContainer) { const settingsLeftPercent = localStorage.getItem('settingsContainerLeftPercent') || 'calc(100% - 280px)'; const settingsTopPercent = localStorage.getItem('settingsContainerTopPercent') || '100px'; settingsContainer.style.left = `${settingsLeftPercent}%`; settingsContainer.style.top = `${settingsTopPercent}%`; } if (autoOpenIn) { toggleOpenerLinks(true); } window.addEventListener('resize', () => { const containerLeftPercent = localStorage.getItem('containerLeftPercent'); const containerTopPercent = localStorage.getItem('containerTopPercent'); if (containerLeftPercent !== null && containerTopPercent !== null) { container.style.left = `${containerLeftPercent}%`; container.style.top = `${containerTopPercent}%`; } const settingsLeftPercent = localStorage.getItem('settingsContainerLeftPercent'); const settingsTopPercent = localStorage.getItem('settingsContainerTopPercent'); if (settingsContainer && settingsLeftPercent !== null && settingsTopPercent !== null) { settingsContainer.style.left = `${settingsLeftPercent}%`; settingsContainer.style.top = `${settingsTopPercent}%`; } keepWithinBounds(container); if (settingsContainer) { keepWithinBounds(settingsContainer); } }); function keepWithinBounds(element) { const rect = element.getBoundingClientRect(); if (rect.right > window.innerWidth) { element.style.left = (window.innerWidth - rect.width) + 'px'; } if (rect.bottom > window.innerHeight) { element.style.top = (window.innerHeight - rect.height) + 'px'; } } container.addEventListener('mouseup', saveContainerPosition); if (settingsContainer) { settingsContainer.addEventListener('mouseup', saveSettingsContainerPosition); } function saveContainerPosition() { const containerRect = container.getBoundingClientRect(); const containerLeftPercent = (containerRect.left / window.innerWidth) * 100; const containerTopPercent = (containerRect.top / window.innerHeight) * 100; localStorage.setItem('containerLeftPercent', containerLeftPercent); localStorage.setItem('containerTopPercent', containerTopPercent); } function saveSettingsContainerPosition() { if (settingsContainer) { const settingsRect = settingsContainer.getBoundingClientRect(); const settingsLeftPercent = (settingsRect.left / window.innerWidth) * 100; const settingsTopPercent = (settingsRect.top / window.innerHeight) * 100; localStorage.setItem('settingsContainerLeftPercent', settingsLeftPercent); localStorage.setItem('settingsContainerTopPercent', settingsTopPercent); } } if (autoOpenIn) { toggleOpenerLinks(true); } makeDraggable(container, 'containerTop', 'containerLeft'); container.addEventListener('mouseup', saveContainerPosition); if (settingsContainer) { makeDraggable(settingsContainer, 'settingsContainerTop', 'settingsContainerLeft'); settingsContainer.addEventListener('mouseup', saveSettingsContainerPosition); } window.addEventListener('resize', () => { const containerLeftPercent = localStorage.getItem('containerLeftPercent'); const containerTopPercent = localStorage.getItem('containerTopPercent'); if (containerLeftPercent !== null && containerTopPercent !== null) { container.style.left = `${containerLeftPercent}%`; container.style.top = `${containerTopPercent}%`; } const settingsLeftPercent = localStorage.getItem('settingsContainerLeftPercent'); const settingsTopPercent = localStorage.getItem('settingsContainerTopPercent'); if (settingsContainer && settingsLeftPercent !== null && settingsTopPercent !== null) { settingsContainer.style.left = `${settingsLeftPercent}%`; settingsContainer.style.top = `${settingsTopPercent}%`; } keepWithinBounds(container); if (settingsContainer) { keepWithinBounds(settingsContainer); } }); function keepWithinBounds(element) { const rect = element.getBoundingClientRect(); if (rect.right > window.innerWidth) { element.style.left = (window.innerWidth - rect.width) + 'px'; } if (rect.bottom > window.innerHeight) { element.style.top = (window.innerHeight - rect.height) + 'px'; } } if (settingsContainer) { initializeSettingsContainerPosition(settingsContainer); } })();