Last active
September 27, 2024 12:30
-
-
Save wjd3/42c270fdb0e34783c04000488354a6be to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// I made this script because the ChatGPT Team plan does not allow you to export your data like you can with a Free or Plus account. | |
// The script can be used with any type of ChatGPT account (Free, Plus, or Team). | |
// Run the script in the console while you are logged into chatgpt.com via the browser. | |
// This code has been tested to work in Chromium-based browsers (Chrome, Chromium, Brave, etc.) as of September 2024. | |
// Function to download data as a JSON file | |
function downloadJSON(filename, jsonData) { | |
const jsonString = JSON.stringify(jsonData, null, 2) // Convert data to a formatted JSON string | |
const element = document.createElement('a') | |
element.setAttribute( | |
'href', | |
'data:application/json;charset=utf-8,' + encodeURIComponent(jsonString) | |
) | |
element.setAttribute('download', filename) | |
element.style.display = 'none' | |
document.body.appendChild(element) | |
element.click() | |
document.body.removeChild(element) | |
} | |
// Function to collect chat messages | |
function collectChatMessages() { | |
const messages = document.querySelectorAll('[data-testid^="conversation-turn-"]') // Select conversation turns | |
const chatHistory = [] | |
messages.forEach((message) => { | |
const messageElement = message.querySelector('[data-message-author-role]') | |
if (messageElement) { | |
// Use the value of the data-message-author-role attribute as the key | |
const role = messageElement.getAttribute('data-message-author-role') | |
const text = messageElement.innerText | |
chatHistory.push({ [role]: text }) | |
} | |
}) | |
return chatHistory | |
} | |
// Function to sanitize text to be friendly for a macOS file name | |
function sanitizeFilename(text) { | |
return text.replace(/[^a-zA-Z0-9-_]/g, '_').substring(0, 50) // Replace invalid characters and limit length | |
} | |
// Function to download current chat messages if available | |
async function downloadCurrentChat() { | |
const chatHistory = collectChatMessages() | |
if (chatHistory.length > 0) { | |
// Extracting chat identifier from URL | |
const chatId = window.location.pathname.split('/').pop() || 'current_chat' | |
// Find the currently selected link in the nav based on /c/{uuid} format | |
const selectedLink = document.querySelector(`a[href="/c/${chatId}"]`) | |
let chatName = 'chat' // Default if no name found | |
// Get the friendly name from the link text, sanitize it for macOS filenames | |
if (selectedLink && selectedLink.innerText) { | |
chatName = sanitizeFilename(selectedLink.innerText) | |
} | |
// Fetch chat details from the backend API in order to store the chat "time created" and "time updated" (optional) | |
const AUTH_TOKEN = '' | |
if (AUTH_TOKEN) { | |
try { | |
const response = await fetch(`https://chatgpt.com/backend-api/conversation/${chatId}`, { | |
method: 'GET', | |
credentials: 'include', // Include cookies from the request's origin | |
headers: { | |
Authorization: `Bearer ${AUTH_TOKEN}` // Replace with the actual token | |
} | |
}) | |
if (response.ok) { | |
const chatData = await response.json() | |
const { create_time, update_time } = chatData | |
// Include the additional data to the chat history | |
chatHistory.push({ create_time, update_time }) | |
} else { | |
console.error('Failed to fetch chat details:', response.statusText || response.status) | |
} | |
} catch (error) { | |
console.error('Error fetching chat details:', error) | |
} | |
// Create the filename using chat name and chatId | |
const filename = `${chatName}_${chatId}.json` | |
downloadJSON(filename, chatHistory) | |
} | |
} | |
} | |
// Function to navigate to the first chat link | |
async function goToFirstChat(loadTime = 2000) { | |
// Select all nav links with href matching the /c/{uuid} format | |
const chatLinks = document.querySelectorAll('a[href^="/c/"]') | |
// Check if there are any chat links | |
if (chatLinks.length > 0) { | |
// Click on the first link | |
chatLinks[0].click() | |
// Wait for the chat to load | |
await new Promise((resolve) => setTimeout(resolve, loadTime)) // Adjust time as needed | |
} | |
} | |
// Function to iterate through each link in the nav with format /c/{uuid} | |
async function iterateAndDownloadChats(loadTime = 2000) { | |
// Select all nav links with href matching the /c/{uuid} format | |
const chatLinks = document.querySelectorAll('a[href^="/c/"]') | |
for (const link of chatLinks) { | |
// Click on the link | |
link.click() | |
// Wait for chat to load before collecting messages | |
await new Promise((resolve) => setTimeout(resolve, loadTime)) // Adjust time as needed | |
// Download current chat messages | |
await downloadCurrentChat() | |
} | |
} | |
// Start by navigating to the first chat link | |
async function startDownloadingChats( | |
// goToFirst: A boolean that determines whether to navigate to the first chat link before downloading chats. Set this to `false` if you want to start downloading from the conversation link you have selected. | |
// loadTime: The time in milliseconds to wait for each chat to load before attempting to download messages. Use a higher number if you have slow internet. | |
{ goToFirst, loadTime } = { goToFirst: true, loadTime: 2000 } | |
) { | |
if (goToFirst) { | |
await goToFirstChat(loadTime) | |
} | |
// Then iterate through all chats in the nav and download each | |
await iterateAndDownloadChats(loadTime) | |
} | |
// Begin the process | |
await startDownloadingChats({}) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment