您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Captures Claude chat conversation data and saves to local storage
- // ==UserScript==
- // @name Claude Chat Data Capture
- // @namespace http://tampermonkey.net/
- // @version 0.1
- // @description Captures Claude chat conversation data and saves to local storage
- // @author nickm8
- // @match https://claude.ai/chat/*
- // @grant none
- // @run-at document-start
- // @license MIT
- // ==/UserScript==
- (function() {
- 'use strict';
- // Debug logging
- const DEBUG = true;
- const log = {
- debug: (...args) => DEBUG && console.log('🔍 CAPTURE:', ...args),
- error: (...args) => console.error('❌ CAPTURE:', ...args),
- info: (...args) => console.log('ℹ️ CAPTURE:', ...args)
- };
- // Configuration
- const CONFIG = {
- urlPattern: 'chat_conversations/',
- ignorePatterns: ['chat_message_warning', 'latest'],
- storageKey: 'captured_chat_data',
- saveInterval: 1000,
- isEnabled: true
- };
- // Create toggle button
- function createToggleButton() {
- const button = document.createElement('button');
- button.innerHTML = '✔️';
- button.title = 'Toggle Chat Capture (Currently Active)';
- button.style.cssText = `
- position: fixed;
- right: 20px;
- bottom: 10px;
- width: 30px;
- height: 30px;
- border-radius: 50%;
- border: none;
- background: transparent;
- cursor: pointer;
- z-index: 9999;
- padding: 0;
- opacity: 0.5;
- transition: opacity 0.3s;
- font-size: 16px;
- display: flex;
- align-items: center;
- justify-content: center;
- `;
- button.addEventListener('mouseover', () => {
- button.style.opacity = '1';
- });
- button.addEventListener('mouseout', () => {
- button.style.opacity = '0.5';
- });
- button.addEventListener('click', () => {
- CONFIG.isEnabled = !CONFIG.isEnabled;
- button.innerHTML = CONFIG.isEnabled ? '✔️' : '❌';
- button.title = `Toggle Chat Capture (Currently ${CONFIG.isEnabled ? 'Active' : 'Inactive'})`;
- log.info(`Capture ${CONFIG.isEnabled ? 'enabled' : 'disabled'}`);
- });
- document.body.appendChild(button);
- }
- // State
- let capturedData = [];
- // Safe URL checker
- function extractUrl(request) {
- if (typeof request === 'string') return request;
- if (request instanceof URL) return request.href;
- if (request instanceof Request) return request.url;
- if (typeof request === 'object' && request.url) return request.url;
- return null;
- }
- function shouldCaptureUrl(request) {
- // Check if capturing is enabled
- if (!CONFIG.isEnabled) return false;
- try {
- const url = extractUrl(request);
- if (!url) {
- log.debug('Invalid URL format:', request);
- return false;
- }
- const shouldCapture = url.includes(CONFIG.urlPattern) &&
- !CONFIG.ignorePatterns.some(pattern => url.includes(pattern));
- log.debug(`URL: ${url}, Should capture: ${shouldCapture}`);
- return shouldCapture;
- } catch (error) {
- log.error('Error in shouldCaptureUrl:', error);
- return false;
- }
- }
- // Storage management
- function saveToStorage() {
- if (capturedData.length === 0) return;
- try {
- localStorage.setItem(CONFIG.storageKey, JSON.stringify(capturedData));
- log.info(`Saved ${capturedData.length} items to storage`);
- capturedData = [];
- } catch (error) {
- log.error('Storage save failed:', error);
- }
- }
- // Response processing
- async function processResponse(response, url) {
- try {
- const contentType = response.headers.get('content-type');
- if (!contentType?.toLowerCase().includes('application/json')) {
- log.debug('Not JSON content:', contentType);
- return;
- }
- const json = await response.json();
- capturedData.push({
- timestamp: new Date().toISOString(),
- url,
- data: json
- });
- log.debug('Captured new data:', url);
- } catch (error) {
- log.error('Error processing response:', error);
- }
- }
- // Fetch interceptor
- const originalFetch = window.fetch;
- window.fetch = async function(...args) {
- const response = await originalFetch.apply(this, args);
- try {
- if (shouldCaptureUrl(args[0])) {
- const url = extractUrl(args[0]);
- await processResponse(response.clone(), url);
- }
- } catch (error) {
- log.error('Fetch intercept error:', error);
- }
- return response;
- };
- // XHR interceptor
- const originalXHROpen = XMLHttpRequest.prototype.open;
- const originalXHRSend = XMLHttpRequest.prototype.send;
- XMLHttpRequest.prototype.open = function(...args) {
- this._url = args[1];
- return originalXHROpen.apply(this, args);
- };
- XMLHttpRequest.prototype.send = function(...args) {
- if (shouldCaptureUrl(this._url)) {
- this.addEventListener('load', function() {
- try {
- const contentType = this.getResponseHeader('content-type');
- if (contentType?.toLowerCase().includes('application/json')) {
- const json = JSON.parse(this.responseText);
- capturedData.push({
- timestamp: new Date().toISOString(),
- url: this._url,
- data: json
- });
- log.debug('Captured XHR data:', this._url);
- }
- } catch (error) {
- log.error('XHR process error:', error);
- }
- });
- }
- return originalXHRSend.apply(this, args);
- };
- // Save timer
- setInterval(saveToStorage, CONFIG.saveInterval);
- // Save on page unload
- window.addEventListener('beforeunload', saveToStorage);
- // Initialize
- log.info('Chat data capture initialized');
- // Wait for DOM to be ready before adding button
- if (document.readyState === 'loading') {
- document.addEventListener('DOMContentLoaded', createToggleButton);
- } else {
- createToggleButton();
- }
- // Expose debug helper
- window.getChatCaptures = () => {
- const data = localStorage.getItem(CONFIG.storageKey);
- return data ? JSON.parse(data) : [];
- };
- })();