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