Claude Chat Data Capture

Captures Claude chat conversation data and saves to local storage

  1. // ==UserScript==
  2. // @name Claude Chat Data Capture
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.1
  5. // @description Captures Claude chat conversation data and saves to local storage
  6. // @author nickm8
  7. // @match https://claude.ai/chat/*
  8. // @grant none
  9. // @run-at document-start
  10. // @license MIT
  11. // ==/UserScript==
  12.  
  13. (function() {
  14. 'use strict';
  15.  
  16. // Debug logging
  17. const DEBUG = true;
  18. const log = {
  19. debug: (...args) => DEBUG && console.log('🔍 CAPTURE:', ...args),
  20. error: (...args) => console.error('❌ CAPTURE:', ...args),
  21. info: (...args) => console.log('ℹ️ CAPTURE:', ...args)
  22. };
  23.  
  24. // Configuration
  25. const CONFIG = {
  26. urlPattern: 'chat_conversations/',
  27. ignorePatterns: ['chat_message_warning', 'latest'],
  28. storageKey: 'captured_chat_data',
  29. saveInterval: 1000,
  30. isEnabled: true
  31. };
  32.  
  33. // Create toggle button
  34. function createToggleButton() {
  35. const button = document.createElement('button');
  36. button.innerHTML = '✔️';
  37. button.title = 'Toggle Chat Capture (Currently Active)';
  38. button.style.cssText = `
  39. position: fixed;
  40. right: 20px;
  41. bottom: 10px;
  42. width: 30px;
  43. height: 30px;
  44. border-radius: 50%;
  45. border: none;
  46. background: transparent;
  47. cursor: pointer;
  48. z-index: 9999;
  49. padding: 0;
  50. opacity: 0.5;
  51. transition: opacity 0.3s;
  52. font-size: 16px;
  53. display: flex;
  54. align-items: center;
  55. justify-content: center;
  56. `;
  57.  
  58. button.addEventListener('mouseover', () => {
  59. button.style.opacity = '1';
  60. });
  61.  
  62. button.addEventListener('mouseout', () => {
  63. button.style.opacity = '0.5';
  64. });
  65.  
  66. button.addEventListener('click', () => {
  67. CONFIG.isEnabled = !CONFIG.isEnabled;
  68. button.innerHTML = CONFIG.isEnabled ? '✔️' : '❌';
  69. button.title = `Toggle Chat Capture (Currently ${CONFIG.isEnabled ? 'Active' : 'Inactive'})`;
  70. log.info(`Capture ${CONFIG.isEnabled ? 'enabled' : 'disabled'}`);
  71. });
  72.  
  73. document.body.appendChild(button);
  74. }
  75.  
  76. // State
  77. let capturedData = [];
  78.  
  79. // Safe URL checker
  80. function extractUrl(request) {
  81. if (typeof request === 'string') return request;
  82. if (request instanceof URL) return request.href;
  83. if (request instanceof Request) return request.url;
  84. if (typeof request === 'object' && request.url) return request.url;
  85. return null;
  86. }
  87.  
  88. function shouldCaptureUrl(request) {
  89. // Check if capturing is enabled
  90. if (!CONFIG.isEnabled) return false;
  91. try {
  92. const url = extractUrl(request);
  93. if (!url) {
  94. log.debug('Invalid URL format:', request);
  95. return false;
  96. }
  97.  
  98. const shouldCapture = url.includes(CONFIG.urlPattern) &&
  99. !CONFIG.ignorePatterns.some(pattern => url.includes(pattern));
  100. log.debug(`URL: ${url}, Should capture: ${shouldCapture}`);
  101. return shouldCapture;
  102. } catch (error) {
  103. log.error('Error in shouldCaptureUrl:', error);
  104. return false;
  105. }
  106. }
  107.  
  108. // Storage management
  109. function saveToStorage() {
  110. if (capturedData.length === 0) return;
  111.  
  112. try {
  113. localStorage.setItem(CONFIG.storageKey, JSON.stringify(capturedData));
  114. log.info(`Saved ${capturedData.length} items to storage`);
  115. capturedData = [];
  116. } catch (error) {
  117. log.error('Storage save failed:', error);
  118. }
  119. }
  120.  
  121. // Response processing
  122. async function processResponse(response, url) {
  123. try {
  124. const contentType = response.headers.get('content-type');
  125. if (!contentType?.toLowerCase().includes('application/json')) {
  126. log.debug('Not JSON content:', contentType);
  127. return;
  128. }
  129.  
  130. const json = await response.json();
  131. capturedData.push({
  132. timestamp: new Date().toISOString(),
  133. url,
  134. data: json
  135. });
  136.  
  137. log.debug('Captured new data:', url);
  138. } catch (error) {
  139. log.error('Error processing response:', error);
  140. }
  141. }
  142.  
  143. // Fetch interceptor
  144. const originalFetch = window.fetch;
  145. window.fetch = async function(...args) {
  146. const response = await originalFetch.apply(this, args);
  147. try {
  148. if (shouldCaptureUrl(args[0])) {
  149. const url = extractUrl(args[0]);
  150. await processResponse(response.clone(), url);
  151. }
  152. } catch (error) {
  153. log.error('Fetch intercept error:', error);
  154. }
  155.  
  156. return response;
  157. };
  158.  
  159. // XHR interceptor
  160. const originalXHROpen = XMLHttpRequest.prototype.open;
  161. const originalXHRSend = XMLHttpRequest.prototype.send;
  162.  
  163. XMLHttpRequest.prototype.open = function(...args) {
  164. this._url = args[1];
  165. return originalXHROpen.apply(this, args);
  166. };
  167.  
  168. XMLHttpRequest.prototype.send = function(...args) {
  169. if (shouldCaptureUrl(this._url)) {
  170. this.addEventListener('load', function() {
  171. try {
  172. const contentType = this.getResponseHeader('content-type');
  173. if (contentType?.toLowerCase().includes('application/json')) {
  174. const json = JSON.parse(this.responseText);
  175. capturedData.push({
  176. timestamp: new Date().toISOString(),
  177. url: this._url,
  178. data: json
  179. });
  180. log.debug('Captured XHR data:', this._url);
  181. }
  182. } catch (error) {
  183. log.error('XHR process error:', error);
  184. }
  185. });
  186. }
  187. return originalXHRSend.apply(this, args);
  188. };
  189.  
  190. // Save timer
  191. setInterval(saveToStorage, CONFIG.saveInterval);
  192.  
  193. // Save on page unload
  194. window.addEventListener('beforeunload', saveToStorage);
  195.  
  196. // Initialize
  197. log.info('Chat data capture initialized');
  198. // Wait for DOM to be ready before adding button
  199. if (document.readyState === 'loading') {
  200. document.addEventListener('DOMContentLoaded', createToggleButton);
  201. } else {
  202. createToggleButton();
  203. }
  204. // Expose debug helper
  205. window.getChatCaptures = () => {
  206. const data = localStorage.getItem(CONFIG.storageKey);
  207. return data ? JSON.parse(data) : [];
  208. };
  209. })();