ChatGPTKeeper

Introducing ChatGPTKeeper, your shield against unwanted data usage in ChatGPT sessions. While official history functionalities often use your data to train models, ChatGPTKeeper stands as a bastion of privacy, utilizing a local Redis server to securely save and retrieve your chat history locally.

当前为 2023-09-15 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name ChatGPTKeeper
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.2
  5. // @description Introducing ChatGPTKeeper, your shield against unwanted data usage in ChatGPT sessions. While official history functionalities often use your data to train models, ChatGPTKeeper stands as a bastion of privacy, utilizing a local Redis server to securely save and retrieve your chat history locally.
  6. // @author maple
  7. // @match https://chat.openai.com/*
  8. // @grant GM_xmlhttpRequest
  9. // @license GPL-3.0-or-later
  10. // ==/UserScript==
  11.  
  12. (function() {
  13. 'use strict';
  14.  
  15. const WEBREDIS_ENDPOINT = "http://127.0.0.1:7379";
  16. var redisname = "";
  17. var thisInHistory = false;
  18. var HistoryPrefix = "HISTORY";
  19. var PromptPrefix = "USERPROMPT";
  20.  
  21. /*---------------------------------History-------------------------------------*/
  22. function load(key, callback) {
  23. key = encodeURIComponent(key);
  24. GM_xmlhttpRequest({
  25. method: "GET",
  26. url: `${WEBREDIS_ENDPOINT}/GET/${key}`,
  27. onload: function(response) {
  28. callback(null, JSON.parse(response.responseText));
  29. },
  30. onerror: function(error) {
  31. callback(error, null);
  32. }
  33. });
  34. }
  35.  
  36. function save(nameprefix, data, callback) {
  37. var strdata;
  38. var dname;
  39. var currentTimestamp = "";
  40. if (Array.isArray(data)) {// history
  41. strdata = JSON.stringify(data.map(function(element) {
  42. return element.innerHTML;
  43. }));
  44. dname = data[0].innerText.substring(0, 50).trim();
  45. var date = new Date();
  46. currentTimestamp = date.toLocaleString().substring(0, 19);
  47. } else {//prompt
  48. strdata = JSON.stringify(data);
  49. dname = data.substring(0, 50).trim();
  50. }
  51. if (strdata && strdata.length < 3){
  52. return;
  53. }
  54. if (redisname == ""){
  55. redisname = encodeURIComponent(nameprefix + currentTimestamp + "\n" + dname);
  56. console.log(redisname);
  57. }
  58. GM_xmlhttpRequest({
  59. method: "GET",
  60. url: `${WEBREDIS_ENDPOINT}/SET/${redisname}/${encodeURIComponent(strdata)}`,
  61. onload: function(response) {
  62. console.log(response);
  63. callback(null, response.responseText);
  64. },
  65. onerror: function(error) {
  66. console.log(error);
  67. callback(error, null);
  68. }
  69. });
  70. }
  71.  
  72. function remove(key, callback) {
  73. key = encodeURIComponent(key);
  74. GM_xmlhttpRequest({
  75. method: "GET",
  76. url: `${WEBREDIS_ENDPOINT}/DEL/${key}`,
  77. onload: function(response) {
  78. callback(null, JSON.parse(response.responseText));
  79. },
  80. onerror: function(error) {
  81. callback(error, null);
  82. }
  83. });
  84. }
  85.  
  86.  
  87. function getAllRedisKeys(callback) {
  88. GM_xmlhttpRequest({
  89. method: "GET",
  90. url: `${WEBREDIS_ENDPOINT}/KEYS/*`, // Update this to your actual endpoint
  91. onload: function(response) {
  92. if (response.responseText == undefined){
  93. redisError();
  94. return;
  95. }
  96. callback(null, JSON.parse(response.responseText));
  97. },
  98. onerror: function(error) {
  99. callback(error, null);
  100. }
  101. });
  102. }
  103.  
  104.  
  105. function getCurrentDialogContent() {
  106. // Get all div elements with the specified class
  107. const divsWithSpecificClass = document.querySelectorAll('div.flex.flex-grow.flex-col.gap-3.max-w-full');
  108. // Create an array to store the text content of each div
  109. const divTexts = [];
  110.  
  111. // Loop through the NodeList and get the text content of each div
  112. divsWithSpecificClass.forEach(div => {
  113. var textContent = [];
  114. /*div.querySelectorAll('*').forEach(child => {
  115. if (child.children.length === 0) {
  116. textContent.push(child.textContent);
  117. if(child.tagName === 'P' || child.tagName === 'DIV') {
  118. textContent.push("\n");
  119. }
  120. }
  121. });
  122. divTexts.push(textContent.join(""));*/
  123. //divTexts.push(div.innerText);
  124. divTexts.push(div);
  125. });
  126.  
  127. // Return the array of text contents
  128. return divTexts;
  129. }
  130.  
  131. function sleep(ms) {
  132. return new Promise(resolve => setTimeout(resolve, ms));
  133. }
  134.  
  135. function showHistory(dataList) {
  136. var targetDiv = document.querySelector('div.flex-1.overflow-hidden > div > div > div');
  137.  
  138. /*while(targetDiv.firstChild) {
  139. targetDiv.removeChild(targetDiv.firstChild);
  140. }*/
  141.  
  142. dataList.forEach(data => {
  143. var newDiv = document.createElement('div');
  144. newDiv.textContent = data;
  145. targetDiv.appendChild(newDiv);
  146. });
  147. }
  148.  
  149. function makeAGroup(name, keys, elementfilter, clickcallback){
  150. const ul = document.createElement('ul');
  151. ul.style.overflowY = 'auto';
  152. ul.style.maxHeight = '500px';
  153. var eid = "myUl" + name
  154. ul.id = eid; // Setting an ID to the ul element
  155. var h2 = document.createElement('h2');
  156. h2.innerText = name;
  157. h2.style.color = 'white';
  158. h2.style.textAlign = "center";
  159. ul.append(h2);
  160. for (let i = 0; i < keys.length; i++) {
  161. const li = document.createElement('li');
  162. if(!keys[i].startsWith(elementfilter)){
  163. continue;
  164. }
  165. li.textContent = keys[i].substring(elementfilter.length, keys[i].length);
  166. li.style.color = 'white';
  167. // Apply CSS styles for the rounded rectangle
  168. li.style.border = '1px solid white'; // Add a border
  169. li.style.borderRadius = '10px'; // Adjust the horizontal and vertical radii to create rounded corners
  170. li.style.padding = '5px'; // Add some padding to make it visually appealing
  171. li.style.position = 'relative';
  172. li.addEventListener('mouseenter', function() {
  173. li.style.backgroundColor = 'grey'; // Change to your desired background color
  174. });
  175.  
  176. li.addEventListener('mouseleave', function() {
  177. li.style.backgroundColor = ''; // Reset to the original background color
  178. });
  179. li.addEventListener('click', (event) => {clickcallback(event, keys[i]);});
  180.  
  181. // add close
  182. // Create close button
  183. const closeButton = document.createElement('span');
  184. closeButton.textContent = '✖';
  185. closeButton.style.position = 'absolute';
  186. closeButton.style.top = '5px';
  187. closeButton.style.right = '5px';
  188. closeButton.style.color = 'white';
  189. closeButton.style.cursor = 'pointer'; // Set cursor to pointer to indicate it's clickable
  190.  
  191. // Add event listener for the close button
  192. closeButton.addEventListener('click', async (event) => {
  193. // Your callback function here
  194. event.stopPropagation();
  195. remove(keys[i], function (){});
  196. await sleep(500);
  197. InitPanel();
  198. });
  199.  
  200. // Add close button to li
  201. li.appendChild(closeButton);
  202. ul.appendChild(li);
  203. }
  204. return ul;
  205. }
  206.  
  207.  
  208. async function InitPanel() {
  209.  
  210.  
  211.  
  212.  
  213. getAllRedisKeys(function(error, data) {
  214. if (error) {
  215. redisError();
  216. console.error('An error occurred:', error);
  217. return;
  218. }
  219.  
  220. const ol = document.querySelectorAll('ol')[2];
  221. var div = document.querySelectorAll('div.flex-shrink-0.overflow-x-hidden')[0];
  222. const ulExisting = document.getElementById('myUlHistory');
  223. if (ulExisting) {
  224. div.removeChild(ulExisting, function (){});
  225. }
  226. if (data.KEYS.length == 0){
  227. redisError();
  228. }
  229. var ul = makeAGroup("History", data.KEYS.sort().reverse(), HistoryPrefix, function(event, key) {
  230. //console.log('Item clicked:', data.KEYS[i]);
  231. // Load data after saving
  232. load(key, function(err, data) {
  233. if (err) return console.error(err);
  234. var myList = JSON.parse(data.GET);
  235. if (Array.isArray(myList)){
  236. for (let i = 0; i < myList.length; i++) {
  237. if (i % 2 == 0) {
  238. myList[i] = "👨: " + myList[i].replace(/\n/g, '<br>');
  239. } else {
  240. myList[i] = "🤖: " + myList[i].replace(/\n/g, '<br>');
  241. }
  242. }
  243. showHistoryLog(myList.join("<br>"));
  244. }
  245. });
  246. });
  247. div.prepend(ul);
  248.  
  249. /*---Prompt---*/
  250. var ulPrompt = document.getElementById('myUlPrompt');
  251. if (ulPrompt) {
  252. div.removeChild(ulPrompt);
  253. }
  254. var prompt = makeAGroup("Prompt", data.KEYS.sort().reverse(), PromptPrefix, function(event, key) {
  255. //console.log('Item clicked:', data.KEYS[i]);
  256. // Load data after saving
  257. load(key, function(err, data) {
  258. if (err) return console.error(err);
  259. var prompt = JSON.parse(data.GET);
  260. var textarea = document.getElementById('prompt-textarea');
  261. textarea.value = prompt;
  262. });
  263. });
  264. div.prepend(prompt);
  265. if (!ulPrompt) {
  266. var button = document.createElement('button');
  267. button.innerText = 'Save Message As Prompt';
  268. button.style.color = 'white';
  269. button.style.position = 'relative';
  270. button.style.textAlign = 'center';
  271. button.style.border = '1px solid white';
  272. button.style.backgroundColor = '#268BD2';
  273. var bottomdiv = document.querySelectorAll('div.relative.pb-3.pt-2.text-center.text-xs.text-gray-600')[0];
  274. bottomdiv.appendChild(button);
  275.  
  276. button.addEventListener('click', function() {
  277. var textarea = document.getElementById('prompt-textarea');
  278. save(PromptPrefix, textarea.textContent, function(err, response) {
  279. if (err) return console.error(err);
  280. });
  281. InitPanel();
  282. });
  283. }
  284.  
  285.  
  286.  
  287.  
  288.  
  289. });
  290. /*Remote Offical*/
  291. await sleep(2000);
  292. const olElements = document.querySelectorAll('ol');
  293. olElements.forEach(ol => {
  294. // First remove all existing children
  295. while (ol.firstChild) {
  296. ol.removeChild(ol.firstChild);
  297. }
  298. });
  299.  
  300. }
  301.  
  302. function redisError(){
  303. var div = document.querySelectorAll('div.flex-shrink-0.overflow-x-hidden')[0];
  304. const ul = document.createElement('ul');
  305. div.prepend(ul);
  306. const li = document.createElement('li');
  307. li.textContent = "There is no record. Please verify if webdis AND redis-server has been started! Just run `webdis.sh start` to start.";
  308. li.style.color = 'white';
  309. ul.appendChild(li);
  310. }
  311.  
  312. function showHistoryLog(text) {
  313. // Check if the div with a specific id already exists
  314. var existingDiv = document.getElementById('historyLog');
  315.  
  316. if (existingDiv) {
  317. // If the div exists, update the messageSpan's HTML content
  318. var messageSpan = existingDiv.querySelector('.message-span');
  319. if (messageSpan) {
  320. messageSpan.innerHTML = text;
  321. }
  322. existingDiv.style.display = '';
  323. } else {
  324. // If the div doesn't exist, create a new div and append it to the body
  325. var hoverBox = document.createElement('div');
  326. hoverBox.id = 'historyLog'; // Set a unique id for the div
  327. hoverBox.style.position = 'fixed';
  328. hoverBox.style.top = '50%';
  329. hoverBox.style.left = '50%';
  330. hoverBox.style.transform = 'translate(-50%, -50%)';
  331. hoverBox.style.zIndex = '10000';
  332. hoverBox.style.padding = '10px';
  333. hoverBox.style.width = '1000px';
  334. hoverBox.style.height = '800px';
  335. hoverBox.style.backgroundColor = 'white';
  336. hoverBox.style.border = '1px solid black';
  337. hoverBox.style.borderRadius = '5px';
  338. hoverBox.style.boxShadow = '0px 0px 10px rgba(0, 0, 0, 0.5)';
  339. hoverBox.style.overflow = 'hidden'; // Hide content overflow
  340.  
  341. // Create a container div for the content and close button
  342. var contentContainer = document.createElement('div');
  343. contentContainer.style.overflowY = 'auto'; // Make content scrollable
  344. //contentContainer.style.resize = 'both'; // Enable resizing
  345. contentContainer.style.height = 'calc(100% - 40px)'; // Adjust height for close button
  346.  
  347. // Create a span element to hold the message
  348. var messageSpan = document.createElement('span');
  349. messageSpan.innerHTML = text;
  350. messageSpan.className = 'message-span'; // Add a class for easy selection
  351. messageSpan.style.display = 'block';
  352. messageSpan.style.marginTop = '20px';
  353.  
  354. // Create a button element to close the hover box
  355. var closeButton = document.createElement('button');
  356. closeButton.textContent = '✖';
  357. closeButton.style.position = 'absolute';
  358. closeButton.style.top = '10px';
  359. closeButton.style.right = '10px';
  360. closeButton.addEventListener('click', function () {
  361. hoverBox.style.display = 'none';
  362. });
  363.  
  364. // Add the message span and close button to the content container
  365. contentContainer.appendChild(messageSpan);
  366. contentContainer.appendChild(closeButton);
  367.  
  368. // Add the content container to the hover box
  369. hoverBox.appendChild(contentContainer);
  370.  
  371. document.addEventListener('click', function (event) {
  372. if (!hoverBox.contains(event.target) && event.target !== hoverBox) {
  373. hoverBox.style.display = 'none';
  374. }
  375. });
  376.  
  377. // Add the hover box to the body of the document
  378. document.body.appendChild(hoverBox);
  379. }
  380. }
  381.  
  382.  
  383.  
  384.  
  385.  
  386.  
  387.  
  388. InitPanel();
  389.  
  390.  
  391.  
  392.  
  393. document.addEventListener('keydown', async function(event) {
  394. if (event.key === 'Enter' || (event.metaKey && event.key === 'r')) {
  395. // Usage examples
  396. while(true){
  397. var thisdialog = getCurrentDialogContent();
  398. save(HistoryPrefix, thisdialog, function(err, response) {
  399. if (err) return console.error(err);
  400. console.log('Save response:', response);
  401. if(!thisInHistory){
  402. InitPanel();
  403. thisInHistory = true;
  404. }
  405. });
  406. const divElement = document.querySelector('div.flex.items-center.md\\:items-end');
  407. if (divElement == undefined || divElement.textContent == undefined || divElement.textContent != 'Stop generating'){
  408. break;
  409. }
  410. await sleep(2000);
  411. }
  412. }
  413. if (event.key === 'Escape') {
  414. var existingDiv = document.getElementById('historyLog');
  415. if (existingDiv) {
  416. existingDiv.style.display = 'none';
  417. }
  418. }
  419. });
  420.  
  421.  
  422.  
  423.  
  424. })();