您需要先安装一个扩展,例如 篡改猴、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();
- }
- })();