您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
从 ChatGPT 导出你的 GPTs 数据
// ==UserScript== // @name ChatGPT GPTs Exporter // @name:zh-CN ChatGPT GPTs 导出工具 // @name:zh-TW ChatGPT GPTs 導出工具 // @namespace https://github.com/lroolle/chatgpt-degraded // @version 0.1.1 // @description Export your GPTs data from ChatGPT // @description:zh-CN 从 ChatGPT 导出你的 GPTs 数据 // @description:zh-TW 從 ChatGPT 導出你的 GPTs 數據 // @author lroolle // @license AGPL-3.0 // @match *://chat.openai.com/gpts/* // @match *://chatgpt.com/gpts/* // @grant GM_xmlhttpRequest // @grant GM_registerMenuCommand // @grant GM_setClipboard // @grant unsafeWindow // @run-at document-start // @icon data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2NCIgaGVpZ2h0PSI2NCIgdmlld0JveD0iMCAwIDY0IDY0Ij4KICA8ZGVmcz4KICAgIDxsaW5lYXJHcmFkaWVudCBpZD0iZ3JhZGllbnQiIHgxPSIwJSIgeTE9IjAlIiB4Mj0iMTAwJSIgeTI9IjEwMCUiPgogICAgICA8c3RvcCBvZmZzZXQ9IjAlIiBzdHlsZT0ic3RvcC1jb2xvcjojMmE5ZDhmO3N0b3Atb3BhY2l0eToxIi8+CiAgICAgIDxzdG9wIG9mZnNldD0iMTAwJSIgc3R5bGU9InN0b3AtY29sb3I6IzJhOWQ4ZjtzdG9wLW9wYWNpdHk6MC44Ii8+CiAgICA8L2xpbmVhckdyYWRpZW50PgogIDwvZGVmcz4KICA8Zz4KICAgIDxjaXJjbGUgY3g9IjMyIiBjeT0iMzIiIHI9IjI4IiBmaWxsPSJ1cmwoI2dyYWRpZW50KSIgc3Ryb2tlPSIjZmZmIiBzdHJva2Utd2lkdGg9IjEiLz4KPCEtLU91dGVyIGNpcmNsZSBtb2RpZmllZCB0byBsb29rIGxpa2UgIkMiLS0+CiAgICA8Y2lyY2xlIGN4PSIzMiIgY3k9IjMyIiByPSIyMCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZmZmIiBzdHJva2Utd2lkdGg9IjEiIHN0cm9rZS1kYXNoYXJyYXk9IjEyNSA1NSIgc3Ryb2tlLWRhc2hvZmZzZXQ9IjIwIi8+CiAgICA8Y2lyY2xlIGN4PSIzMiIgY3k9IjMyIiByPSIxMiIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZmZmIiBzdHJva2Utd2lkdGg9IjEiLz4KICAgIDxjaXJjbGUgY3g9IjMyIiBjeT0iMzIiIHI9IjQiIGZpbGw9IiNmZmYiLz4KICA8L2c+Cjwvc3ZnPg== // @homepageURL https://github.com/lroolle/chatgpt-degraded // @supportURL https://github.com/lroolle/chatgpt-degraded/issues // ==/UserScript== (function() { 'use strict'; // Store GPTs data let gptsData = []; let isExporting = false; // Storage key for persisted data const STORAGE_KEY = 'chatgpt_gpts_data'; // Load persisted data function loadPersistedData() { try { const stored = localStorage.getItem(STORAGE_KEY); return stored ? JSON.parse(stored) : []; } catch (error) { console.error('Error loading persisted GPTs data:', error); return []; } } // Save data to persistence function persistData(data) { try { // Create a map of existing data by ID const existingData = loadPersistedData(); const dataMap = new Map(existingData.map(item => [item.id, item])); // Update or add new data data.forEach(item => { dataMap.set(item.id, item); }); // Convert map back to array and save const mergedData = Array.from(dataMap.values()); localStorage.setItem(STORAGE_KEY, JSON.stringify(mergedData)); return mergedData; } catch (error) { console.error('Error persisting GPTs data:', error); return data; } } // Clear persisted data function clearPersistedData() { try { localStorage.removeItem(STORAGE_KEY); } catch (error) { console.error('Error clearing persisted GPTs data:', error); } } // i18n support const i18n = { 'en': { exportBtn: 'Export GPTs', exportJSON: 'Export as JSON', exportCSV: 'Export as CSV', clearData: 'Clear Data', exporting: 'Exporting...', copied: 'Copied to clipboard!', noData: 'No GPTs data found', error: 'Error exporting GPTs', dataCleared: 'Data cleared successfully', totalGPTs: 'Total GPTs: ' }, 'zh-CN': { exportBtn: '导出 GPTs', exportJSON: '导出为 JSON', exportCSV: '导出为 CSV', clearData: '清除数据', exporting: '导出中...', copied: '已复制到剪贴板!', noData: '未找到 GPTs 数据', error: '导出 GPTs 时出错', dataCleared: '数据已清除', totalGPTs: '总计 GPTs: ' }, 'zh-TW': { exportBtn: '導出 GPTs', exportJSON: '導出為 JSON', exportCSV: '導出為 CSV', clearData: '清除數據', exporting: '導出中...', copied: '已複製到剪貼板!', noData: '未找到 GPTs 數據', error: '導出 GPTs 時出錯', dataCleared: '數據已清除', totalGPTs: '總計 GPTs: ' } }; // Get user language const userLang = (navigator.language || 'en').toLowerCase(); const lang = i18n[userLang] ? userLang : userLang.startsWith('zh-tw') ? 'zh-TW' : userLang.startsWith('zh') ? 'zh-CN' : 'en'; const t = key => i18n[lang][key] || i18n.en[key]; // Intercept fetch requests const originalFetch = unsafeWindow.fetch; unsafeWindow.fetch = async function(resource, options) { const response = await originalFetch(resource, options); const url = typeof resource === 'string' ? resource : resource?.url; // Check if this is the GPTs list request if (url && url.includes('/public-api/gizmos/discovery/mine')) { try { const clonedResponse = response.clone(); const data = await clonedResponse.json(); if (data?.list?.items) { const newGPTsData = data.list.items.map(item => { const gpt = item.resource.gizmo; return { id: gpt.id, name: gpt.display.name || '', description: gpt.display.description || '', instructions: gpt.instructions || '', created_at: gpt.created_at, updated_at: gpt.updated_at, version: gpt.version, tools: item.resource.tools.map(tool => tool.type), prompt_starters: gpt.display.prompt_starters || [], share_recipient: gpt.share_recipient, num_interactions: gpt.num_interactions }; }); // Persist and update the data gptsData = persistData(newGPTsData); // Update the counter in UI updateGPTsCounter(gptsData.length); } } catch (error) { console.error('Error intercepting GPTs data:', error); } } return response; }; // Update GPTs counter in UI function updateGPTsCounter(count) { const counter = document.getElementById('gpts-counter'); if (counter) { counter.textContent = `${t('totalGPTs')}${count}`; } } // Convert GPTs data to CSV function convertToCSV(data) { const headers = [ 'ID', 'Name', 'Description', 'Instructions', 'Created At', 'Updated At', 'Version', 'Tools', 'Prompt Starters', 'Share Recipient', 'Interactions' ]; const rows = data.map(gpt => [ gpt.id, `"${(gpt.name || '').replace(/"/g, '""')}"`, `"${(gpt.description || '').replace(/"/g, '""')}"`, `"${(gpt.instructions || '').replace(/"/g, '""')}"`, gpt.created_at, gpt.updated_at, gpt.version, `"${(gpt.tools || []).join(', ')}"`, `"${(gpt.prompt_starters || []).join(', ').replace(/"/g, '""')}"`, gpt.share_recipient, gpt.num_interactions ]); return [headers.join(','), ...rows.map(row => row.join(','))].join('\n'); } // Export GPTs data function exportGPTs(format = 'json') { if (isExporting) return; isExporting = true; try { // Load all persisted data const allData = loadPersistedData(); if (!allData.length) { alert(t('noData')); isExporting = false; return; } let exportContent, filename, mimeType; if (format === 'csv') { exportContent = convertToCSV(allData); filename = `chatgpt-gpts-export-${new Date().toISOString().split('T')[0]}.csv`; mimeType = 'text/csv'; } else { const exportData = { exported_at: new Date().toISOString(), total_gpts: allData.length, gpts: allData }; exportContent = JSON.stringify(exportData, null, 2); filename = `chatgpt-gpts-export-${new Date().toISOString().split('T')[0]}.json`; mimeType = 'application/json'; } GM_setClipboard(exportContent); // Create and download file const blob = new Blob([exportContent], { type: mimeType }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = filename; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); alert(t('copied')); } catch (error) { console.error('Error exporting GPTs:', error); alert(t('error')); } finally { isExporting = false; } } // Register menu commands GM_registerMenuCommand(t('exportJSON'), () => exportGPTs('json')); GM_registerMenuCommand(t('exportCSV'), () => exportGPTs('csv')); // Add export button to UI function addExportButton() { const styles = document.createElement('style'); styles.textContent = ` .gpts-exporter { position: fixed; bottom: 20px; right: 20px; z-index: 10000; display: flex; flex-direction: column; gap: 8px; background: var(--surface-primary, rgba(255, 255, 255, 0.9)); padding: 12px; border-radius: 8px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); border: 1px solid var(--border-light, rgba(0, 0, 0, 0.1)); transition: transform 0.2s ease, opacity 0.2s ease; } .gpts-exporter:hover { transform: translateY(-2px); } .gpts-exporter.collapsed { transform: translateX(calc(100% + 20px)); } .gpts-exporter button { padding: 8px 16px; background-color: var(--success-color, #10a37f); color: #fff; border: none; border-radius: 4px; cursor: pointer; font-size: 14px; font-weight: 500; transition: all 0.2s ease; display: flex; align-items: center; justify-content: center; gap: 6px; min-width: 140px; } .gpts-exporter button:hover { opacity: 0.9; transform: translateY(-1px); } .gpts-exporter button:active { transform: translateY(0); } .gpts-exporter .toggle-btn { position: absolute; left: -32px; top: 50%; transform: translateY(-50%); width: 24px; height: 24px; background: var(--success-color, #10a37f); border: none; border-radius: 4px 0 0 4px; cursor: pointer; display: flex; align-items: center; justify-content: center; padding: 0; min-width: unset; } .gpts-exporter .toggle-btn svg { width: 16px; height: 16px; fill: #fff; transition: transform 0.2s ease; } .gpts-exporter.collapsed .toggle-btn svg { transform: rotate(180deg); } .gpts-exporter button.json-btn { background-color: var(--success-color, #10a37f); } .gpts-exporter button.csv-btn { background-color: var(--primary-color, #0ea5e9); } .gpts-exporter .counter { font-size: 12px; color: var(--text-secondary, #666); text-align: center; margin-bottom: 4px; } .gpts-exporter button.clear-btn { background-color: var(--error-color, #dc2626); font-size: 12px; padding: 4px 8px; } `; document.head.appendChild(styles); const container = document.createElement('div'); container.className = 'gpts-exporter'; // Add GPTs counter const counter = document.createElement('div'); counter.id = 'gpts-counter'; counter.className = 'counter'; counter.textContent = `${t('totalGPTs')}${loadPersistedData().length}`; const toggleBtn = document.createElement('button'); toggleBtn.className = 'toggle-btn'; toggleBtn.innerHTML = '<svg viewBox="0 0 24 24"><path d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z"/></svg>'; toggleBtn.addEventListener('click', () => { container.classList.toggle('collapsed'); }); const jsonBtn = document.createElement('button'); jsonBtn.className = 'json-btn'; jsonBtn.innerHTML = ` <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/> <polyline points="7 10 12 15 17 10"/> <line x1="12" y1="15" x2="12" y2="3"/> </svg> ${t('exportJSON')} `; jsonBtn.addEventListener('click', () => exportGPTs('json')); const csvBtn = document.createElement('button'); csvBtn.className = 'csv-btn'; csvBtn.innerHTML = ` <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/> <polyline points="14 2 14 8 20 8"/> <line x1="16" y1="13" x2="8" y2="13"/> <line x1="16" y1="17" x2="8" y2="17"/> <polyline points="10 9 9 9 8 9"/> </svg> ${t('exportCSV')} `; csvBtn.addEventListener('click', () => exportGPTs('csv')); // Add clear data button const clearBtn = document.createElement('button'); clearBtn.className = 'clear-btn'; clearBtn.textContent = t('clearData'); clearBtn.addEventListener('click', () => { if (confirm(t('clearData') + '?')) { clearPersistedData(); gptsData = []; updateGPTsCounter(0); alert(t('dataCleared')); } }); container.appendChild(counter); container.appendChild(toggleBtn); container.appendChild(jsonBtn); container.appendChild(csvBtn); container.appendChild(clearBtn); document.body.appendChild(container); } // Initialize if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', addExportButton); } else { addExportButton(); } })();