Last active
January 12, 2025 02:04
-
-
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.
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
// ==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