// ==UserScript== // @name DoubleDouble Tools // @namespace https://git.ponywave.de/Akamaru/Userscripts // @version 1.0 // @description Enhanced settings and features for DoubleDouble music downloader // @author Akamaru // @match https://doubledouble.top/* // @match https://eu.doubledouble.top/* // @match https://us.doubledouble.top/* // @icon https://www.google.com/s2/favicons?domain=eu.doubledouble.top&sz=32 // @grant GM_getValue // @grant GM_setValue // @grant GM_addStyle // @grant GM_setClipboard // @run-at document-start // @updateURL https://git.ponywave.de/Akamaru/Userscripts/raw/branch/master/doubledouble-tools.user.js // @downloadURL https://git.ponywave.de/Akamaru/Userscripts/raw/branch/master/doubledouble-tools.user.js // ==/UserScript== (function() { 'use strict'; console.log('[DoubleDouble Tools] Script loaded'); // Default settings const DEFAULT_SETTINGS = { uploadExternal: true, uploadService: 'pdrain', hideDownload: false, spotifyFormat: 'ogg', spotifyMetadata: true, hideQQDL: false, hideRecent: false, hideFooter: false, serverChoice: 'auto', // 'auto', 'eu', 'us' showDownloadLink: false }; // Settings management function getSettings() { try { const stored = GM_getValue('doubledouble_settings', '{}'); const settings = JSON.parse(stored); return { ...DEFAULT_SETTINGS, ...settings }; } catch (e) { console.error('[DoubleDouble Tools] Error loading settings:', e); return DEFAULT_SETTINGS; } } function saveSettings(settings) { GM_setValue('doubledouble_settings', JSON.stringify(settings)); } // Apply cosmetic settings (CSS) function applyCosmetics(settings) { let css = ''; if (settings.hideQQDL) { css += '.lucida-link { display: none !important; }\n'; } if (settings.hideRecent) { css += '#recently-downloaded { display: none !important; }\n'; } if (settings.hideFooter) { css += '.footer { display: none !important; }\n'; } // Settings icon styles css += ` #dd-tools-settings-icon { position: fixed; top: 20px; right: 20px; z-index: 10000; background: #333; color: white; border: none; border-radius: 50%; width: 40px; height: 40px; font-size: 20px; cursor: pointer; box-shadow: 0 2px 8px rgba(0,0,0,0.3); transition: background 0.2s; } #dd-tools-settings-icon:hover { background: #555; } #dd-tools-modal { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.7); z-index: 10001; justify-content: center; align-items: center; } #dd-tools-modal.active { display: flex; } #dd-tools-modal-content { background: #1a1a1a; color: white; padding: 30px; border-radius: 10px; max-width: 500px; width: 90%; max-height: 80vh; overflow-y: auto; box-shadow: 0 4px 20px rgba(0,0,0,0.5); } #dd-tools-modal h2 { margin-top: 0; margin-bottom: 20px; font-size: 24px; } .dd-tools-section { margin-bottom: 25px; padding-bottom: 20px; border-bottom: 1px solid #333; } .dd-tools-section:last-child { border-bottom: none; } .dd-tools-section h3 { margin-top: 0; margin-bottom: 15px; font-size: 16px; color: #aaa; text-transform: uppercase; letter-spacing: 1px; } .dd-tools-option { margin-bottom: 12px; display: flex; align-items: center; gap: 10px; } .dd-tools-option label { flex: 1; cursor: pointer; } .dd-tools-option input[type="checkbox"] { cursor: pointer; } .dd-tools-option select { background: #333; color: white; border: 1px solid #555; border-radius: 4px; padding: 5px 10px; cursor: pointer; } #dd-tools-close { background: #d9534f; color: white; border: none; border-radius: 5px; padding: 10px 20px; cursor: pointer; font-size: 14px; margin-top: 20px; transition: background 0.2s; } #dd-tools-close:hover { background: #c9302c; } #dd-tools-download-link-display { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: #1a1a1a; color: white; padding: 30px; border-radius: 10px; z-index: 10002; box-shadow: 0 4px 20px rgba(0,0,0,0.5); min-width: 400px; display: none; } #dd-tools-download-link-display.active { display: block; } #dd-tools-download-link-display h3 { margin-top: 0; } #dd-tools-download-link-display input { width: 100%; background: #333; color: white; border: 1px solid #555; border-radius: 4px; padding: 8px; margin: 10px 0; font-family: monospace; } #dd-tools-download-link-display button { background: #5cb85c; color: white; border: none; border-radius: 5px; padding: 10px 20px; cursor: pointer; margin-right: 10px; transition: background 0.2s; } #dd-tools-download-link-display button:hover { background: #449d44; } #dd-tools-copy-link { background: #f0ad4e !important; } #dd-tools-copy-link:hover { background: #ec971f !important; } #dd-tools-download-link-display button.secondary { background: #777; } #dd-tools-download-link-display button.secondary:hover { background: #555; } `; GM_addStyle(css); } // Server redirect function handleServerRedirect(settings) { if (window.location.hostname === 'doubledouble.top' && settings.serverChoice !== 'auto') { const newUrl = window.location.href.replace('doubledouble.top', `${settings.serverChoice}.doubledouble.top`); window.location.href = newUrl; } } // Block /recent requests function blockRecentRequests(settings) { if (!settings.hideRecent) return; // Intercept fetch const originalFetch = window.fetch; window.fetch = function(...args) { const url = args[0]; if (typeof url === 'string' && url.includes('/recent')) { console.log('[DoubleDouble Tools] Blocked fetch to /recent'); return Promise.resolve(new Response('[]', { status: 200, headers: { 'Content-Type': 'application/json' } })); } return originalFetch.apply(this, args); }; // Intercept XMLHttpRequest const originalOpen = XMLHttpRequest.prototype.open; XMLHttpRequest.prototype.open = function(method, url, ...rest) { if (typeof url === 'string' && url.includes('/recent')) { console.log('[DoubleDouble Tools] Blocked XMLHttpRequest to /recent'); // Replace with dummy request return originalOpen.call(this, method, 'data:text/plain,[]', ...rest); } return originalOpen.call(this, method, url, ...rest); }; } // Inject download interceptor into page context function interceptDownloads(settings) { if (!settings.showDownloadLink) return; const injectScript = () => { const script = document.createElement('script'); script.textContent = ` (function() { console.log('[DoubleDouble Tools] Injecting window.open interceptor into page context'); const originalWindowOpen = window.open; let isIntercepting = false; window.open = function(url, target, ...rest) { console.log('[DoubleDouble Tools] window.open called:', url, target); // Check if this is a download from statusHandoff (target="_self" and url starts with ./) if (!isIntercepting && target === '_self' && typeof url === 'string' && url.startsWith('./')) { console.log('[DoubleDouble Tools] Intercepted download:', url); isIntercepting = true; // Dispatch custom event to show modal const fullUrl = new URL(url, window.location.href).href; window.dispatchEvent(new CustomEvent('dd-tools-show-download', { detail: fullUrl })); setTimeout(() => { isIntercepting = false; }, 100); return null; } return originalWindowOpen.call(this, url, target, ...rest); }; console.log('[DoubleDouble Tools] window.open interceptor installed'); })(); `; (document.head || document.documentElement).appendChild(script); script.remove(); }; // Inject immediately or wait for head to exist if (document.head || document.documentElement) { injectScript(); } else { const observer = new MutationObserver(() => { if (document.head || document.documentElement) { injectScript(); observer.disconnect(); } }); observer.observe(document, { childList: true, subtree: true }); } // Listen for the custom event to show the modal window.addEventListener('dd-tools-show-download', (e) => { console.log('[DoubleDouble Tools] Showing download modal for:', e.detail); showDownloadLinkModal(e.detail); }); } // Show download link modal function showDownloadLinkModal(url) { const modal = document.getElementById('dd-tools-download-link-display'); const input = document.getElementById('dd-tools-download-url-input'); const fullUrl = new URL(url, window.location.href).href; input.value = fullUrl; modal.classList.add('active'); } // Copy link to clipboard function copyLinkToClipboard(url) { GM_setClipboard(url); // Visual feedback const button = document.getElementById('dd-tools-copy-link'); const originalText = button.textContent; button.textContent = 'Copied!'; button.style.background = '#5cb85c'; setTimeout(() => { button.textContent = originalText; button.style.background = ''; }, 2000); } // Apply functional settings to page controls function applyFunctionalSettings(settings) { const externalCheckbox = document.getElementById('external'); const siteSelect = document.getElementById('site'); const privateCheckbox = document.getElementById('private'); const formatSelect = document.getElementById('format'); const metadataCheckbox = document.getElementById('metadata'); if (externalCheckbox) externalCheckbox.checked = settings.uploadExternal; if (siteSelect) siteSelect.value = settings.uploadService; if (privateCheckbox) privateCheckbox.checked = settings.hideDownload; if (formatSelect) formatSelect.value = settings.spotifyFormat; if (metadataCheckbox) metadataCheckbox.checked = settings.spotifyMetadata; } // Create settings UI function createSettingsUI() { const settings = getSettings(); // Settings icon const icon = document.createElement('button'); icon.id = 'dd-tools-settings-icon'; icon.innerHTML = '⚙️'; icon.title = 'DoubleDouble Tools Settings'; icon.addEventListener('click', () => { document.getElementById('dd-tools-modal').classList.add('active'); }); document.body.appendChild(icon); // Settings modal const modal = document.createElement('div'); modal.id = 'dd-tools-modal'; modal.innerHTML = `

DoubleDouble Tools Settings

Functional Settings

Cosmetic Settings

Advanced Settings

`; document.body.appendChild(modal); // Close modal on background click modal.addEventListener('click', (e) => { if (e.target === modal) { saveSettingsFromUI(); modal.classList.remove('active'); } }); // Close button document.getElementById('dd-tools-close').addEventListener('click', () => { saveSettingsFromUI(); modal.classList.remove('active'); // Reload to apply settings location.reload(); }); // Download link display modal const downloadModal = document.createElement('div'); downloadModal.id = 'dd-tools-download-link-display'; downloadModal.innerHTML = `

Download Ready

Your download link:

`; document.body.appendChild(downloadModal); // Download button document.getElementById('dd-tools-download-now').addEventListener('click', () => { const url = document.getElementById('dd-tools-download-url-input').value; window.location.href = url; downloadModal.classList.remove('active'); }); // Copy link button document.getElementById('dd-tools-copy-link').addEventListener('click', () => { const url = document.getElementById('dd-tools-download-url-input').value; copyLinkToClipboard(url); }); // Close download modal document.getElementById('dd-tools-download-close').addEventListener('click', () => { downloadModal.classList.remove('active'); }); } // Save settings from UI function saveSettingsFromUI() { const newSettings = { uploadExternal: document.getElementById('dd-setting-upload-external').checked, uploadService: document.getElementById('dd-setting-upload-service').value, hideDownload: document.getElementById('dd-setting-hide-download').checked, spotifyFormat: document.getElementById('dd-setting-spotify-format').value, spotifyMetadata: document.getElementById('dd-setting-spotify-metadata').checked, hideQQDL: document.getElementById('dd-setting-hide-qqdl').checked, hideRecent: document.getElementById('dd-setting-hide-recent').checked, hideFooter: document.getElementById('dd-setting-hide-footer').checked, serverChoice: document.getElementById('dd-setting-server-choice').value, showDownloadLink: document.getElementById('dd-setting-show-download-link').checked }; saveSettings(newSettings); } // Initialize function init() { try { const settings = getSettings(); // Apply settings that need to run early (before DOM is ready) handleServerRedirect(settings); blockRecentRequests(settings); interceptDownloads(settings); applyCosmetics(settings); // Wait for DOM to be ready before creating UI if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => { try { createSettingsUI(); applyFunctionalSettings(settings); } catch (e) { console.error('[DoubleDouble Tools] Error creating UI:', e); } }); } else { createSettingsUI(); applyFunctionalSettings(settings); } } catch (e) { console.error('[DoubleDouble Tools] Error during initialization:', e); } } init(); })();