OpenAI Chat Synthesize Interceptor with Advanced Icon Controls

Capture ChatGPT Synthesize API responses and add advanced playback controls with icons. Forked by Baldoe13 on community.openai.com. Original by Winkelmann.

// ==UserScript==
// @name         OpenAI Chat Synthesize Interceptor with Advanced Icon Controls
// @version      0.7
// @description  Capture ChatGPT Synthesize API responses and add advanced playback controls with icons. Forked by Baldoe13 on community.openai.com. Original by Winkelmann.
// @author       93andresen
// @match        https://chat.openai.com/*
// @match        https://chatgpt.com/*
// @grant        none
// @license      MIT
// @namespace    https://github.com/93andresen/Userscripts
// ==/UserScript==

(function () {
    'use strict';
    let audio = null;  // Global audio element
    let playPauseBtn;  // Global play/pause button

    const originalFetch = window.fetch;
    window.fetch = async function (url, options) {
        const response = await originalFetch.apply(this, arguments);
        if (url.startsWith('https://chat.openai.com/backend-api/synthesize')) {
            console.log('Intercepted Synthesize API request for URL:', url);
            try {
                const clone = response.clone();
                const arrayBuffer = await clone.arrayBuffer();
                const aacFile = new Blob([arrayBuffer], { type: 'audio/aac' });
                const fileUrl = URL.createObjectURL(aacFile);
                if (audio) {
                    audio.src = fileUrl;
                } else {
                    audio = new Audio(fileUrl);
                    createControls();
                }
                audio.play().catch(err => {
                    console.error('Audio playback error:', err);
                    playPauseBtn.innerHTML = '▶️'; // Change to Play icon on error
                }).then(() => {
                    if (audio) {
                        playPauseBtn.innerHTML = '⏸'; // Change to Pause icon
                        showControlsTemporarily(); // Show controls when audio starts playing
                    }
                });
            } catch (err) {
                console.error('Error processing audio:', err);
            }
        }
        return response;
    };

    function createControls() {
        const controlsDiv = document.createElement('div');
        controlsDiv.id = 'audioControls';
        controlsDiv.style.position = 'fixed';
        controlsDiv.style.top = '15px';
        controlsDiv.style.left = '50%';
        controlsDiv.style.transform = 'translateX(-50%)';
        controlsDiv.style.zIndex = '10000';
        controlsDiv.style.display = 'flex';
        controlsDiv.style.gap = '10px';
        controlsDiv.style.opacity = '0'; // Initially invisible
        controlsDiv.style.transition = 'opacity 0.5s ease'; // Smooth transition for opacity

        controlsDiv.onmouseover = function() {
            controlsDiv.style.opacity = '1';
        };

        controlsDiv.onmouseout = function() {
            controlsDiv.style.opacity = '0';
        };

        const startOverBtn = createButton('⏮', 'Start over', () => {
            audio.currentTime = 0;
            audio.play();
            playPauseBtn.innerHTML = '⏸';
        });

        playPauseBtn = createButton('▶️', 'Play/Pause', () => {
            if (audio.paused || audio.ended) {
                audio.play();
                playPauseBtn.innerHTML = '⏸';
            } else {
                audio.pause();
                playPauseBtn.innerHTML = '▶️';
            }
        });

        const rewindBtn = createButton('⏪', 'Rewind 10 seconds', () => {
            audio.currentTime = Math.max(0, audio.currentTime - 10);
            if (audio.paused) {
                audio.play();
                playPauseBtn.innerHTML = '⏸';
            }
        });

        const stopBtn = createButton('⏹', 'Stop', () => {
            audio.pause();
            audio.currentTime = 0;
            playPauseBtn.innerHTML = '▶️';
        });

        controlsDiv.appendChild(startOverBtn);
        controlsDiv.appendChild(rewindBtn);
        controlsDiv.appendChild(playPauseBtn);
        controlsDiv.appendChild(stopBtn);

        document.body.appendChild(controlsDiv);
        showControlsTemporarily();
    }

    function createButton(icon, title, action) {
        const button = document.createElement('button');
        button.innerHTML = icon;
        button.title = title;
        button.style.cursor = 'pointer';
        button.style.background = 'none';
        button.style.border = 'none';
        button.onclick = action;
        return button;
    }

    function showControlsTemporarily() {
        const controlsDiv = document.getElementById('audioControls');
        if (controlsDiv) {
            controlsDiv.style.opacity = '1';
            setTimeout(() => {
                if (!controlsDiv.matches(':hover')) {
                    controlsDiv.style.opacity = '0';
                }
            }, 2000);
        }
    }
})();