595 lines
22 KiB
JavaScript
595 lines
22 KiB
JavaScript
// ==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 = `
|
|
<div id="dd-tools-modal-content">
|
|
<h2>DoubleDouble Tools Settings</h2>
|
|
|
|
<div class="dd-tools-section">
|
|
<h3>Functional Settings</h3>
|
|
|
|
<div class="dd-tools-option">
|
|
<input type="checkbox" id="dd-setting-upload-external" ${settings.uploadExternal ? 'checked' : ''}>
|
|
<label for="dd-setting-upload-external">Upload to external service</label>
|
|
</div>
|
|
|
|
<div class="dd-tools-option">
|
|
<label for="dd-setting-upload-service">Upload service:</label>
|
|
<select id="dd-setting-upload-service">
|
|
<option value="pdrain" ${settings.uploadService === 'pdrain' ? 'selected' : ''}>pixeldrain.com</option>
|
|
<option value="litterbox" ${settings.uploadService === 'litterbox' ? 'selected' : ''}>litterbox.catbox.moe</option>
|
|
<option value="sendcm" ${settings.uploadService === 'sendcm' ? 'selected' : ''}>send.cm</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="dd-tools-option">
|
|
<input type="checkbox" id="dd-setting-hide-download" ${settings.hideDownload ? 'checked' : ''}>
|
|
<label for="dd-setting-hide-download">Hide my download from "Recently Downloaded"</label>
|
|
</div>
|
|
|
|
<div class="dd-tools-option">
|
|
<label for="dd-setting-spotify-format">Spotify format:</label>
|
|
<select id="dd-setting-spotify-format">
|
|
<option value="ogg" ${settings.spotifyFormat === 'ogg' ? 'selected' : ''}>OGG 320kbps</option>
|
|
<option value="mp3" ${settings.spotifyFormat === 'mp3' ? 'selected' : ''}>MP3 320kbps</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="dd-tools-option">
|
|
<input type="checkbox" id="dd-setting-spotify-metadata" ${settings.spotifyMetadata ? 'checked' : ''}>
|
|
<label for="dd-setting-spotify-metadata">Add metadata (Spotify)</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="dd-tools-section">
|
|
<h3>Cosmetic Settings</h3>
|
|
|
|
<div class="dd-tools-option">
|
|
<input type="checkbox" id="dd-setting-hide-qqdl" ${settings.hideQQDL ? 'checked' : ''}>
|
|
<label for="dd-setting-hide-qqdl">Hide QQDL notice</label>
|
|
</div>
|
|
|
|
<div class="dd-tools-option">
|
|
<input type="checkbox" id="dd-setting-hide-recent" ${settings.hideRecent ? 'checked' : ''}>
|
|
<label for="dd-setting-hide-recent">Hide "Recently Downloaded" (blocks requests)</label>
|
|
</div>
|
|
|
|
<div class="dd-tools-option">
|
|
<input type="checkbox" id="dd-setting-hide-footer" ${settings.hideFooter ? 'checked' : ''}>
|
|
<label for="dd-setting-hide-footer">Hide footer</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="dd-tools-section">
|
|
<h3>Advanced Settings</h3>
|
|
|
|
<div class="dd-tools-option">
|
|
<label for="dd-setting-server-choice">Server choice:</label>
|
|
<select id="dd-setting-server-choice">
|
|
<option value="auto" ${settings.serverChoice === 'auto' ? 'selected' : ''}>Auto (no redirect)</option>
|
|
<option value="eu" ${settings.serverChoice === 'eu' ? 'selected' : ''}>EU Server</option>
|
|
<option value="us" ${settings.serverChoice === 'us' ? 'selected' : ''}>US Server</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="dd-tools-option">
|
|
<input type="checkbox" id="dd-setting-show-download-link" ${settings.showDownloadLink ? 'checked' : ''}>
|
|
<label for="dd-setting-show-download-link">Show download link instead of auto-download</label>
|
|
</div>
|
|
</div>
|
|
|
|
<button id="dd-tools-close">Close & Save</button>
|
|
</div>
|
|
`;
|
|
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 = `
|
|
<h3>Download Ready</h3>
|
|
<p>Your download link:</p>
|
|
<input type="text" id="dd-tools-download-url-input" readonly>
|
|
<div>
|
|
<button id="dd-tools-download-now">Download Now</button>
|
|
<button id="dd-tools-copy-link">Copy Link</button>
|
|
<button id="dd-tools-download-close" class="secondary">Close</button>
|
|
</div>
|
|
`;
|
|
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();
|
|
})();
|