Skip to content

Instantly share code, notes, and snippets.

@wjd3
Last active September 27, 2024 12:30
Show Gist options
  • Save wjd3/42c270fdb0e34783c04000488354a6be to your computer and use it in GitHub Desktop.
Save wjd3/42c270fdb0e34783c04000488354a6be to your computer and use it in GitHub Desktop.
// 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