diff --git a/README.md b/README.md index 1890c5c..16b37d2 100644 --- a/README.md +++ b/README.md @@ -138,6 +138,22 @@ Ersetzt Ländercodes mit echten Flaggen-Emojis unter Windows/Chromium. Windows z --- +### 8. `get-chatgpt-audio.user.js` + +**Beschreibung:** +Lädt ChatGPT Audio-Antworten herunter. Wenn du auf "Vorlesen" klickst, erscheint automatisch ein Popup zum Download der Audio-Datei. + +**Funktionen:** +- Automatisches Abfangen von ChatGPT Audio-Synthesize-Anfragen +- Popup erscheint 2 Sekunden nach Klick auf "Vorlesen" +- Automatische Dark/Light Mode-Anpassung basierend auf System-Theme +- Download als `.aac` Datei mit eindeutiger Message-ID +- Zeigt Audiogröße im Popup an +- Automatische Bereinigung alter Audio-Blobs nach 1 Stunde +- Kein CSP-Problem durch direktes Blob-Caching + +--- + ## Übersicht der enthaltenen UserStyles ### 1. `myanimelist-tweaks.user.css` diff --git a/get-chatgpt-audio.user.js b/get-chatgpt-audio.user.js new file mode 100644 index 0000000..67e5dd1 --- /dev/null +++ b/get-chatgpt-audio.user.js @@ -0,0 +1,224 @@ +// ==UserScript== +// @name ChatGPT Audio Downloader +// @namespace https://git.ponywave.de/Akamaru/Userscripts +// @version 1.0 +// @description Lädt ChatGPT Audio-Antworten herunter +// @author Akamaru +// @match https://chatgpt.com/* +// @match https://chat.openai.com/* +// @grant none +// @run-at document-start +// @updateURL https://git.ponywave.de/Akamaru/Userscripts/raw/branch/master/get-chatgpt-audio.user.js +// @downloadURL https://git.ponywave.de/Akamaru/Userscripts/raw/branch/master/get-chatgpt-audio.user.js +// ==/UserScript== + +(function() { + 'use strict'; + + console.log('[ChatGPT Audio] Skript geladen'); + + // Cache für Audio-Blobs + const audioCache = new Map(); + + // Fange fetch-Aufrufe ab (vor ChatGPT-Initialisierung) + const originalFetch = window.fetch; + window.fetch = async function(...args) { + const url = typeof args[0] === 'string' ? args[0] : args[0]?.url; + + // Prüfe ob es ein Audio-Synthesize-Request ist + if (url && (url.includes('synthesize') || url.includes('/backend-api/synthesize'))) { + console.log('[ChatGPT Audio] ===== SYNTHESIZE-ANFRAGE ERKANNT ====='); + console.log('[ChatGPT Audio] URL:', url); + + const response = await originalFetch.apply(this, args); + console.log('[ChatGPT Audio] Antwort erhalten:', response.status, response.type); + + // Klone Response um sie mehrfach zu lesen + const responseClone = response.clone(); + + try { + const blob = await responseClone.blob(); + console.log('[ChatGPT Audio] Blob erfasst! Größe:', blob.size, 'Typ:', blob.type); + + // Extrahiere message_id aus URL + const urlObj = new URL(url, window.location.origin); + const messageId = urlObj.searchParams.get('message_id'); + console.log('[ChatGPT Audio] Message-ID:', messageId); + + if (blob.size > 0) { + // Speichere das echte Blob! + audioCache.set(messageId || 'latest', { + blob: blob, + messageId: messageId, + timestamp: Date.now(), + url: url + }); + console.log('[ChatGPT Audio] ✓ Audio im Cache gespeichert! Message-ID:', messageId, 'Größe:', blob.size); + + // Zeige Popup nach 2 Sekunden automatisch + setTimeout(() => { + console.log('[ChatGPT Audio] Zeige Download-Popup...'); + zeigeDownloadPopup(messageId || 'latest'); + }, 2000); + } + } catch (err) { + console.error('[ChatGPT Audio] Fehler beim Verarbeiten des Blobs:', err); + } + + return response; + } + + return originalFetch.apply(this, args); + }; + + console.log('[ChatGPT Audio] Fetch-Interceptor installiert'); + + // Zeige Download-Popup + function zeigeDownloadPopup(messageId) { + console.log('[ChatGPT Audio] Zeige Download-Popup für Nachricht:', messageId); + + // Prüfe ob document.body existiert + if (!document.body) { + console.warn('[ChatGPT Audio] document.body noch nicht bereit, warte...'); + setTimeout(() => zeigeDownloadPopup(messageId), 100); + return; + } + + // Hole gecachtes Audio + const audioData = audioCache.get(messageId); + if (!audioData || !audioData.blob) { + console.error('[ChatGPT Audio] Keine Audiodaten im Cache für Nachricht:', messageId); + console.log('[ChatGPT Audio] Cache-Inhalt:', Array.from(audioCache.keys())); + return; + } + + console.log('[ChatGPT Audio] Gecachtes Audio gefunden, Blob-Größe:', audioData.blob.size); + + // Entferne altes Popup falls vorhanden + const altesPopup = document.getElementById('chatgpt-audio-download-popup'); + if (altesPopup) { + altesPopup.remove(); + } + + const altesOverlay = document.getElementById('chatgpt-audio-download-overlay'); + if (altesOverlay) { + altesOverlay.remove(); + } + + console.log('[ChatGPT Audio] Erstelle Popup-Elemente...'); + + // Erkenne Dark Mode + const isDarkMode = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches; + const bgColor = isDarkMode ? '#2f2f2f' : '#ffffff'; + const textColor = isDarkMode ? '#ececec' : '#000000'; + const borderColor = isDarkMode ? '#565656' : '#ddd'; + const secondaryTextColor = isDarkMode ? '#b4b4b4' : '#666'; + const cancelBtnBg = isDarkMode ? '#424242' : '#ffffff'; + const cancelBtnBorder = isDarkMode ? '#565656' : '#ddd'; + const cancelBtnText = isDarkMode ? '#ececec' : '#000000'; + + // Erstelle Popup + const popup = document.createElement('div'); + popup.id = 'chatgpt-audio-download-popup'; + popup.style.cssText = ` + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background: ${bgColor}; + border: 1px solid ${borderColor}; + border-radius: 12px; + padding: 24px; + box-shadow: 0 4px 24px rgba(0,0,0,0.3); + z-index: 10000; + min-width: 300px; + color: ${textColor}; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; + `; + + popup.innerHTML = ` +

Audio herunterladen

+

Audiogröße: ${(audioData.blob.size / 1024).toFixed(1)} KB

+
+ + +
+ `; + + // Overlay (Hintergrund) + const overlay = document.createElement('div'); + overlay.id = 'chatgpt-audio-download-overlay'; + overlay.style.cssText = ` + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0,0,0,0.5); + z-index: 9999; + `; + + document.body.appendChild(overlay); + document.body.appendChild(popup); + + console.log('[ChatGPT Audio] Popup angezeigt'); + + // Event-Listener für Abbrechen-Button + document.getElementById('download-cancel-btn').addEventListener('click', () => { + popup.remove(); + overlay.remove(); + }); + + // Event-Listener für Download-Button + document.getElementById('download-confirm-btn').addEventListener('click', () => { + try { + console.log('[ChatGPT Audio] Download-Button geklickt'); + + // Verwende das bereits gecachte Blob direkt! + const blob = audioData.blob; + const fileName = `chatgpt-audio-${messageId || Date.now()}.aac`; + + console.log('[ChatGPT Audio] Erstelle Download-Link für Blob, Größe:', blob.size); + + // Erstelle temporäre Download-URL + const downloadUrl = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = downloadUrl; + a.download = fileName; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + + // Aufräumen + setTimeout(() => URL.revokeObjectURL(downloadUrl), 100); + console.log('[ChatGPT Audio] ✓ Download gestartet:', fileName); + + popup.remove(); + overlay.remove(); + } catch (error) { + console.error('[ChatGPT Audio] Download-Fehler:', error); + alert('Fehler beim Download: ' + error.message); + } + }); + + // Klick auf Overlay schließt Popup + overlay.addEventListener('click', () => { + popup.remove(); + overlay.remove(); + }); + } + + // Cleanup alter Audio-Blobs (nach 1 Stunde) + setInterval(() => { + const now = Date.now(); + const oneHour = 60 * 60 * 1000; + + for (const [messageId, audioData] of audioCache.entries()) { + if (now - audioData.timestamp > oneHour) { + audioCache.delete(messageId); + console.log('[ChatGPT Audio] Altes Audio bereinigt:', messageId); + } + } + }, 10 * 60 * 1000); // Alle 10 Minuten prüfen + +})();