1
0

Neu: DoubleDouble Tools

This commit is contained in:
Akamaru
2025-12-05 21:28:00 +01:00
parent f7dd8150a8
commit 67bf469c5c
2 changed files with 613 additions and 0 deletions

View File

@@ -310,6 +310,25 @@ Stellt die Navigation-Links für "Abos" und "Verlauf" in der YouTube-Sidebar wie
---
### 17. `doubledouble-tools.user.js`
**[Installieren](https://git.ponywave.de/Akamaru/Userscripts/raw/branch/master/doubledouble-tools.user.js)**
**Beschreibung:**
Erweiterte Einstellungen und Features für DoubleDouble Music Downloader (doubledouble.top). Bietet eine zentrale Settings-Verwaltung und verhindert automatische Downloads.
**Funktionen:**
- Settings-Icon (⚙️) oben rechts öffnet Modal mit allen Einstellungen
- **Funktionale Settings:** Upload to external service (pixeldrain/litterbox/sendcm), Hide my download, Spotify Format (OGG/MP3), Add metadata
- **Kosmetische Settings:** Hide QQDL notice, Hide "Recently Downloaded" (blockiert auch `/recent` Requests), Hide Footer
- **Server-Wahl:** Auto-Redirect von doubledouble.top zu EU/US Server
- **Download-Link anzeigen:** Zeigt Modal mit Download-Link statt Auto-Download
- Modal mit drei Buttons: "Download Now", "Copy Link" (kopiert URL), "Close"
- Alle Einstellungen werden automatisch auf die Seiten-Checkboxen angewendet
- Persistent gespeichert mit GM_getValue/GM_setValue
- Injection in Page Context für zuverlässige window.open Interception
---
## Übersicht der enthaltenen UserStyles
### 1. `myanimelist-tweaks.user.css`

594
doubledouble-tools.user.js Normal file
View File

@@ -0,0 +1,594 @@
// ==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();
})();