{ "status": "success", "download_url": "https://chatgpt.com/backend-api/estuary/content?id=...", "metadata": null, "file_name": null, "creation_time": null, "no_auth_user_upload": null, "mime_type": null, "file_size_bytes": null}
// ==UserScript==// @name ChatGPT Download Helper// @namespace local// @version 2.0// @description Recovers file downloads when ChatGPT receives a download URL but does not trigger the browser download.// @match https://chatgpt.com/*// @match https://chat.openai.com/*// @grant none// ==/UserScript==(function () { 'use strict'; const LOG_PREFIX = '[ChatGPT Download Helper]'; const USER_GESTURE_WINDOW_MS = 8000; const handledDownloads = new Set(); let lastTrustedGestureAt = 0; let lastTrustedGestureLabel = ''; function log(...args) { console.log(LOG_PREFIX, ...args); } function debug(...args) { console.debug(LOG_PREFIX, ...args); } function normalizeUrl(url) { try { return new URL(url, location.href).toString(); } catch { return String(url || ''); } } function describeTarget(target) { const node = target && target.nodeType === Node.ELEMENT_NODE ? target.closest('button, a, [role="button"], li, div, span') : null; if (!node) return 'unknown'; const text = (node.innerText || node.textContent || '').trim().replace(/\s+/g, ' '); const aria = (node.getAttribute?.('aria-label') || '').trim(); const testId = (node.getAttribute?.('data-testid') || '').trim(); return [node.tagName?.toLowerCase(), text, aria, testId].filter(Boolean).join(' | ').slice(0, 160); } function markTrustedGesture(event) { if (!event.isTrusted) return; if (event.type === 'keydown' && event.key !== 'Enter' && event.key !== ' ') { return; } lastTrustedGestureAt = Date.now(); lastTrustedGestureLabel = describeTarget(event.target); debug('trusted gesture detected:', event.type, lastTrustedGestureLabel); } function hasRecentTrustedGesture() { return Date.now() - lastTrustedGestureAt <= USER_GESTURE_WINDOW_MS; } function isDownloadRequest(rawUrl) { try { const url = new URL(rawUrl, location.href); const path = url.pathname.toLowerCase(); if (url.searchParams.has('message_id') && path.includes('/download')) { return true; } if (path.includes('/interpreter/download')) { return true; } return false; } catch { return false; } } function extractDownloadUrl(payload, depth = 0) { if (!payload || depth > 3) return null; if (typeof payload.download_url === 'string' && payload.download_url) { return payload.download_url; } if (Array.isArray(payload)) { for (const item of payload) { const nested = extractDownloadUrl(item, depth + 1); if (nested) return nested; } return null; } if (typeof payload === 'object') { for (const value of Object.values(payload)) { const nested = extractDownloadUrl(value, depth + 1); if (nested) return nested; } } return null; } function triggerDownload(downloadUrl, sourceUrl) { const normalizedDownloadUrl = normalizeUrl(downloadUrl); if (!normalizedDownloadUrl) return; if (handledDownloads.has(normalizedDownloadUrl)) { debug('skip duplicate download:', normalizedDownloadUrl); return; } handledDownloads.add(normalizedDownloadUrl); const anchor = document.createElement('a'); anchor.href = normalizedDownloadUrl; anchor.rel = 'noopener noreferrer'; anchor.style.display = 'none'; document.body.appendChild(anchor); anchor.click(); anchor.remove(); log('download triggered:', { request: normalizeUrl(sourceUrl), download: normalizedDownloadUrl, gesture: lastTrustedGestureLabel || 'recent-user-action' }); } async function inspectDownloadResponse(response, requestUrl) { if (!hasRecentTrustedGesture()) { debug('ignored download response without a recent trusted gesture:', requestUrl); return; } const contentType = response.headers.get('content-type') || ''; if (!contentType.toLowerCase().includes('application/json')) { debug('ignored non-json download response:', requestUrl, contentType); return; } const payload = await response.clone().json().catch(() => null); const downloadUrl = extractDownloadUrl(payload); if (!downloadUrl) { debug('download response did not contain a download_url:', requestUrl, payload); return; } triggerDownload(downloadUrl, requestUrl); } document.addEventListener('pointerdown', markTrustedGesture, true); document.addEventListener('click', markTrustedGesture, true); document.addEventListener('keydown', markTrustedGesture, true); const originalFetch = window.fetch; window.fetch = async function (...args) { const response = await originalFetch.apply(this, args); try { const requestUrl = String(args[0]?.url || args[0] || ''); if (isDownloadRequest(requestUrl)) { inspectDownloadResponse(response, requestUrl).catch((error) => { debug('fetch inspection failed:', error); }); } } catch (error) { debug('fetch interception failed:', error); } return response; }; const originalOpen = XMLHttpRequest.prototype.open; const originalSend = XMLHttpRequest.prototype.send; XMLHttpRequest.prototype.open = function (method, url, ...rest) { this.__downloadHelperUrl = String(url || ''); return originalOpen.call(this, method, url, ...rest); }; XMLHttpRequest.prototype.send = function (...args) { if (isDownloadRequest(this.__downloadHelperUrl)) { this.addEventListener('load', () => { try { if (!hasRecentTrustedGesture()) { return; } const contentType = this.getResponseHeader('content-type') || ''; if (!contentType.toLowerCase().includes('application/json')) { return; } const payload = JSON.parse(this.responseText); const downloadUrl = extractDownloadUrl(payload); if (downloadUrl) { triggerDownload(downloadUrl, this.__downloadHelperUrl); } } catch (error) { debug('xhr inspection failed:', error); } }); } return originalSend.apply(this, args); }; log('enabled');})();