您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds "Export Chat" buttons to Claude.ai
- // ==UserScript==
- // @name Claude.ai ShareGPT Exporter
- // @description Adds "Export Chat" buttons to Claude.ai
- // @version 1.0
- // @author EndlessReform
- // @namespace https://github.com/EndlessReform/claude-sharegpt-exporter
- // @match https://claude.ai/*
- // @grant GM_xmlhttpRequest
- // @grant GM_download
- // @grant GM_setValue
- // @grant GM_getValue
- // @license MIT
- // ==/UserScript==
- /*
- NOTES:
- - This project is a fork of GeoAnima's fork of "Export Claude.Ai" (https://github.com/TheAlanK/export-claude), licensed under the MIT license.
- - The "Export All Chats" option can only be accessed from the https://claude.ai/chats URL.
- */
- (function () {
- "use strict";
- const API_BASE_URL = "https://claude.ai/api";
- // Function to make API requests
- function apiRequest(method, endpoint, data = null, headers = {}) {
- return new Promise((resolve, reject) => {
- GM_xmlhttpRequest({
- method: method,
- url: `${API_BASE_URL}${endpoint}`,
- headers: {
- "Content-Type": "application/json",
- ...headers,
- },
- data: data ? JSON.stringify(data) : null,
- onload: (response) => {
- if (response.status >= 200 && response.status < 300) {
- resolve(JSON.parse(response.responseText));
- } else {
- reject(
- new Error(
- `API request failed with status ${response.status}: ${response.responseText}`
- )
- );
- }
- },
- onerror: (error) => {
- reject(error);
- },
- });
- });
- }
- // Function to get the organization ID
- async function getOrganizationId() {
- let orgId = GM_getValue("orgId");
- if (typeof orgId === "undefined") {
- const organizations = await apiRequest("GET", "/organizations");
- const new_id = organizations[0].uuid;
- GM_setValue("orgId", new_id);
- return new_id;
- } else {
- return orgId;
- }
- }
- // Function to get all conversations
- async function getAllConversations(orgId) {
- return await apiRequest(
- "GET",
- `/organizations/${orgId}/chat_conversations`
- );
- }
- // Function to get conversation history
- async function getConversationHistory(orgId, chatId) {
- return await apiRequest(
- "GET",
- `/organizations/${orgId}/chat_conversations/${chatId}`
- );
- }
- // Function to download data as a file
- function downloadData(data, filename) {
- return new Promise((resolve, reject) => {
- let content = JSON.stringify(data, null, 2);
- const blob = new Blob([content], { type: "text/plain" });
- const url = URL.createObjectURL(blob);
- const a = document.createElement("a");
- a.href = url;
- a.download = filename;
- a.style.display = "none";
- document.body.appendChild(a);
- a.click();
- setTimeout(() => {
- document.body.removeChild(a);
- URL.revokeObjectURL(url);
- resolve();
- }, 100);
- });
- }
- function transformChatToConversation(input) {
- const { chat_messages, current_leaf_message_uuid } = input;
- // Map to store messages by their uuid for easy lookup
- const messagesMap = new Map();
- chat_messages.forEach((message) => messagesMap.set(message.uuid, message));
- // Traverse back from the leaf to the root
- let currentMessage = messagesMap.get(current_leaf_message_uuid);
- const conversation = [];
- while (
- currentMessage &&
- currentMessage.parent_message_uuid !==
- "00000000-0000-4000-8000-000000000000"
- ) {
- conversation.unshift({
- from:
- currentMessage.sender === "assistant" ? "gpt" : currentMessage.sender,
- value: currentMessage.text,
- });
- currentMessage = messagesMap.get(currentMessage.parent_message_uuid);
- }
- // Add the root message
- if (currentMessage) {
- conversation.unshift({
- from:
- currentMessage.sender === "assistant" ? "gpt" : currentMessage.sender,
- value: currentMessage.text,
- });
- }
- return { conversations: conversation };
- }
- // Function to export a single chat
- async function exportChat(orgId, chatId, showAlert = true) {
- try {
- const originalChatData = await getConversationHistory(orgId, chatId);
- const chatData = transformChatToConversation(originalChatData);
- const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
- const filename = `${originalChatData.name}_${timestamp}.json`;
- await downloadData(chatData, filename);
- } catch (error) {
- alert("Error exporting chat. Please try again later.");
- }
- }
- // Function to export all chats
- async function exportAllChats(format) {
- try {
- const orgId = await getOrganizationId();
- const conversations = await getAllConversations(orgId);
- for (const conversation of conversations) {
- await exportChat(orgId, conversation.uuid, format, false);
- }
- } catch (error) {
- console.error(error);
- alert("Error exporting all chats: see browser console for details");
- }
- }
- // Function to create a button
- function createButton(text, onClick) {
- const style = document.createElement("style");
- // Add the CSS rules to the style element
- style.innerHTML = `
- #gm-export {
- position: fixed;
- top: 10px;
- right: 100px;
- padding: 6px 12px;
- color: hsl(var(--text-400) / var(--tw-text-opacity));
- border-radius: 0.375rem;
- cursor: pointer;
- font-size: 16px;
- z-index: 9999;
- border: 1px solid hsl(var(--bg-400));
- box-sizing: border-box;
- }
- #gm-export:hover {
- background-color: hsl(var(--bg-400));
- }
- `;
- // Add the style element to the document head
- document.head.appendChild(style);
- const button = document.createElement("button");
- button.textContent = text;
- button.id = "gm-export";
- button.addEventListener("click", onClick);
- document.body.appendChild(button);
- }
- // Function to remove existing export buttons
- function removeExportButtons() {
- const existingButton = document.getElementById("gm-export");
- if (existingButton) {
- existingButton.parentNode.removeChild(existingButton);
- }
- }
- // Function to initialize the export functionality
- async function initExportFunctionality() {
- removeExportButtons();
- const currentUrl = window.location.href;
- if (currentUrl.includes("/chat/")) {
- const urlParts = currentUrl.split("/");
- const chatId = urlParts[urlParts.length - 1];
- const orgId = await getOrganizationId();
- createButton("Export Chat", async () => {
- await exportChat(orgId, chatId);
- });
- } else if (currentUrl.includes("/chats")) {
- createButton("Export All Chats", async () => {
- const format = prompt("Enter the export format (json or txt):", "json");
- if (format === "json" || format === "txt") {
- await exportAllChats(format);
- } else {
- alert('Invalid export format. Please enter either "json" or "txt".');
- }
- });
- }
- }
- // Function to observe changes in the URL
- function observeUrlChanges(callback) {
- let lastUrl = location.href;
- const observer = new MutationObserver(() => {
- const url = location.href;
- if (url !== lastUrl) {
- lastUrl = url;
- callback();
- }
- });
- const config = { subtree: true, childList: true };
- observer.observe(document, config);
- }
- // Function to observe changes in the DOM
- function observeDOMChanges(selector, callback) {
- const observer = new MutationObserver((mutations) => {
- const element = document.querySelector(selector);
- if (element) {
- if (document.readyState === "complete") {
- observer.disconnect();
- callback();
- }
- }
- });
- observer.observe(document.documentElement, {
- childList: true,
- subtree: true,
- });
- }
- // Function to initialize the script
- async function init() {
- await initExportFunctionality();
- // Observe URL changes and reinitialize export functionality
- observeUrlChanges(async () => {
- await initExportFunctionality();
- });
- }
- // Wait for the desired element to be present in the DOM before initializing the script
- observeDOMChanges(".grecaptcha-badge", init);
- })();