Skip to content

Instantly share code, notes, and snippets.

@robin-collins
Last active January 12, 2025 02:04
Show Gist options
  • Save robin-collins/65d4ebc68d7e961218a94d7537f16696 to your computer and use it in GitHub Desktop.
Save robin-collins/65d4ebc68d7e961218a94d7537f16696 to your computer and use it in GitHub Desktop.
Claude.ai-ChatDownloader - userscript to download claude.ai chats to a text file.
// ==UserScript==
// @name Claude.ai-ChatDownloader
// @namespace http://tampermonkey.net/
// @version 1.9
// @description Download all chats from Claude.ai as a single file
// @match https://claude.ai/*
// @match https://claude.ai/chats
// @match https://claude.ai/chat/*
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_download
// @downloadURL https://gist.githubusercontent.com/robin-collins/65d4ebc68d7e961218a94d7537f16696/raw/a7eb267fd358b00f58b4ccc75ad5f493c81d5cae/userscript.js
// @updateURL https://gist.githubusercontent.com/robin-collins/65d4ebc68d7e961218a94d7537f16696/raw/a7eb267fd358b00f58b4ccc75ad5f493c81d5cae/userscript.js
// ==/UserScript==
/* global $ */
(function() {
'use strict';
function getTextByClass(className) {
console.log('getTextByClass - starting');
var elements = document.getElementsByClassName(className);
var result = [];
for (var i = 0; i < elements.length; i++) {
// Clone the node to not affect the original element
var clone = elements[i].cloneNode(true);
// Remove all SVG and button elements
var unwantedElements = clone.querySelectorAll('svg, button');
for (var j = 0; j < unwantedElements.length; j++) {
unwantedElements[j].remove();
}
// Add cleaned text to the result
result.push(clone.innerText.trim());
}
return result.join("\n");
}
function nextPage() {
console.log('RC - function nextPage started');
var chatUrl = window.location.href;
// Get the chat data from Tampermonkey shared storage
var chatData = GM_getValue('chatData', {});
// Find the next chat to open
var chatUrls = Object.keys(chatData);
var currentIndex = chatUrls.indexOf(chatUrl);
var nextIndex = currentIndex + 1;
console.log('chat #'+currentIndex+' / '+chatUrls.length);
if (nextIndex < chatUrls.length) {
var nextChatUrl = chatUrls[nextIndex];
location.href=nextChatUrl;
} else {
// All chats have been scraped, create the download file
var allChatsText = '';
for (var url in chatData) {
var title = chatData[url];
var text = GM_getValue(url, '');
allChatsText += `==========================================================================================================================\n${url}\n\n== ${title} ==\n\n${text}\n\n`;
}
var blob = new Blob([allChatsText], { type: "text/plain;charset=utf-8" });
var filename = 'claude_ai_chats.txt';
GM_download({
url: URL.createObjectURL(blob),
name: filename,
saveAs: false
});
}
}
function downloadChatText() {
console.log('RC - function downloadChatText started');
var chatUrl = window.location.href;
var chatText = getTextByClass('contents');
// Store the chat text in Tampermonkey shared storage
GM_setValue(chatUrl, chatText);
}
function downloadAllChats() {
console.log('RC - function downloadAllChats - started');
var chatLinks = document.querySelectorAll('a[href^="/chat/"]');
var chatData = {};
// Collect chat titles and URLs
for (var i = 0; i < chatLinks.length; i++) {
var link = chatLinks[i];
var chatId = link.getAttribute('href').split('/').pop();
var chatUrl = `https://claude.ai/chat/${chatId}`;
var chatTitle = link.querySelector('span').innerText;
chatData[chatUrl] = chatTitle;
}
// Store chat data in Tampermonkey shared storage
GM_setValue('chatData', chatData);
// Open the first chat URL after a 30-second delay
var firstChatUrl = Object.keys(chatData)[0];
location.href=firstChatUrl;
}
function onDOMStable() {
console.log("DOM is stable now.");
// Your code to execute after DOM is stable
}
function observeDOMChanges(targetElement, callback, timeout = 5000) {
let timer = null;
let isStable = false;
const observer = new MutationObserver((mutations) => {
if (isStable) return; // Already stable, ignore further mutations
clearTimeout(timer); // Clear any pending timer
timer = setTimeout(() => {
isStable = true;
callback(); // Call the callback when the DOM is considered stable
observer.disconnect(); // Disconnect the observer
}, timeout); // Adjust timeout as needed
});
observer.observe(targetElement, { childList: true, subtree: true });
}
function checkForButton() {
const button = document.querySelector('button[aria-label="Send Message"]');
if (button) {
console.log("RC - Button found!");
// Your code to execute when the button is found
console.log('function downloadChatText about to run');
downloadChatText();
console.log('function nextPage about to run');
nextPage();
}
}
if (window.location.href === 'https://claude.ai/chats') {
console.log('RC - if window.location.href /chats/ - Creating download button');
// Create a download button
var downloadButton = document.createElement('button');
downloadButton.textContent = 'Download All Chats';
downloadButton.style.position = 'fixed';
downloadButton.style.top = '10px';
downloadButton.style.right = '10px';
downloadButton.style.zIndex = '9999';
downloadButton.addEventListener('click', downloadAllChats);
document.body.appendChild(downloadButton);
}
if (window.location.href.includes('/chat/')) {
console.log('RC - if window.location.href includes /chat/ - download and store text;');
console.log('RC - starting observer');
observeDOMChanges(document.body, checkForButton);
console.log('RC - finished observer');
}
// Your code here...
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment