OpenAI Censor

Encode/decode words from the given file on the ChatGPT page to hide sensitive info.

  1. // ==UserScript==
  2. // @name OpenAI Censor
  3. // @namespace http://tampermonkey.net/
  4. // @version 2024-06-19
  5. // @description Encode/decode words from the given file on the ChatGPT page to hide sensitive info.
  6. // @author Screeneroner
  7. // @license GPL v3
  8. // @homepageURL https://github.com/screeneroner/OpenAI-Censor
  9. // @match https://chat.openai.com/*
  10. // @match https://chatgpt.com/*
  11. // @icon https://seeklogo.com/images/C/chatgpt-logo-4AE4C3B8A4-seeklogo.com.png
  12. // @grant none
  13. // ==/UserScript==
  14.  
  15. /*——————————————————————————————————————————————————————————————————————————————————————————————————
  16.  
  17. This code is free software: you can redistribute it and/or modify it under the terms of the
  18. version 3 GNU General Public License as published by the Free Software Foundation.
  19.  
  20. This code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY without even
  21. the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  22. See the GNU General Public License for more details (https://www.gnu.org/licenses/gpl-3.0.html)
  23.  
  24. WARNING TO USERS AND MODIFIERS
  25.  
  26. This script contains "Buy me a coffee" links to honor the author's hard work and dedication in creating
  27. all features present in this code. Removing or altering these links not only violates the GPL license
  28. but also disregards the significant effort put into making this script valuable for the community.
  29.  
  30. If you find value in this script and would like to show appreciation to the author,
  31. kindly consider visiting the site below and treating the author to a few cups of coffee:
  32.  
  33. https://www.buymeacoffee.com/screeneroner
  34.  
  35. Your honor and gratitude is greatly appreciated.
  36.  
  37. ——————————————————————————————————————————————————————————————————————————————————————————————————*/
  38.  
  39. /*——————————————————————————————————————————————————————————————————————————————————————————————————
  40.  
  41. ## Three can keep a secret if two are dead
  42.  
  43. AI is a powerful tool that may use your inputs to "improve models," "enhance your experience," and
  44. other similar purposes. This means YOUR TEXTS MUST BE STORED SOMEWHERE, in a location that you do
  45. not have access to verify or delete them. Even if you accidentally send your ABSOLUTELY PRIVATE
  46. information, it could be stored. To prevent even a theoretical possibility of disclosing your own
  47. secrets or confidential information about clients and/or customers, you SHOULD NOT SHARE IT AT ALL!
  48. However, for example, if you are using AI to correct grammar, wording, and tone of your letters,
  49. you must use the exact text in the AI prompts.
  50.  
  51. ## Hide it to keep it
  52.  
  53. The solution for this dichotomy is straightforward - just replace real words with their 'anonymized'
  54. analogs! Instead of calling your client by its real name 'Super Secret IT Company LTD.', use
  55. 'Client Company Name' in the prompt. There are two main problems with this approach. The first is
  56. that there may be many places in your text where you need to make these changes. It can take a lot
  57. of time to replace them all, and you may miss some entries, leaving them unhidden. The second
  58. problem is that the response from the AI will contain these 'anonymized' words, and you will need
  59. to replace them back to get 'unhidden' text.
  60.  
  61. This script will handle the above-described routine for you! You should prepare the encrypting file
  62. in a zebra-like format. The first row contains the real word(s), and the second row contains its
  63. anonymized version. Like this:
  64. ```
  65. Apple
  66. Client Company
  67. Steve Jobs
  68. John Smith
  69. ```
  70. The script will inject the transcoding codes into the OpenAI ChatGPT web page along with three
  71. buttons below the entry field: Choose File, Unhide, and Hide. The first button will load the
  72. 'encrypting' file (you may use several ones). When you press the Hide button, it will scan the
  73. entire page (including the prompt entry text area) and replace real words with anonymized ones.
  74. From this moment, you can safely send your texts to the AI. Once you get a response and press the
  75. Unhide button, the script will do a reverse translation, and you will see the original texts and
  76. words ready to be copy-pasted.
  77.  
  78. The important point here is that the AI operates with anonymized text only. The real, de-anonymized
  79. texts remain in the operational memory of your local computer and become unhidden only when you
  80. load the proper encoding file and press the Unhide button!
  81.  
  82. ## How to use this script
  83.  
  84. 1. Download and install a browser add-on like Tampermonkey (or similar)
  85. 2. Install this OpenAI Censor script according to the add-on's script installation procedure.
  86. 3. Prepare and save the encoding file with pairs of rows that contain real and encoded text.
  87. 4. Open the ChatGPT web page. You should see a new row with the script buttons under the input area.
  88. 5. Press the Choose File button and load your encryption file.
  89. 6. Type your prompt.
  90. 7. Press the Hide button and verify that all real words were anonymized.
  91. 8. Send the anonymized prompt to the AI and get its response.
  92. 9. Press the Unhide button to replace anonymized words with the originals and get the original texts.
  93. 10. Profit!
  94.  
  95. ——————————————————————————————————————————————————————————————————————————————————————————————————*/
  96.  
  97. // VERSION CHANGES
  98. // 2024-06-15 Initial release
  99. // 2024-06-19 Fixed bug when encoding data was not cleared on re-reading (changed) encoding file.
  100.  
  101. (function() {
  102. 'use strict';
  103.  
  104. const placeholders = {};
  105. const instructionText = "WARNING! Tokens in ««»», represent some hidden words and in your responses they should be used as-is without any changes inside the ««»»!";
  106.  
  107. const replace = (text, dict, reverse = false) => {
  108. if (typeof text !== 'string') return text;
  109. if (reverse) {
  110. for (let i = 1; i < dict.length; i += 2) {
  111. const pattern = `««${dict[i]}»»`;
  112. const re = new RegExp(pattern, 'g');
  113. text = text.replace(re, dict[i - 1]);
  114. }
  115. } else {
  116. for (let i = 0; i < dict.length; i += 2) {
  117. const pattern = dict[i];
  118. const replacement = `««${dict[i + 1]}»»`;
  119. const re = new RegExp(pattern, 'g');
  120. text = text.replace(re, replacement);
  121. }
  122. }
  123. return text;
  124. };
  125.  
  126. const processTextNodes = (node, dict, reverse = false) => {
  127. if (node.nodeType === Node.TEXT_NODE) {
  128. node.nodeValue = replace(node.nodeValue, dict, reverse);
  129. } else if (node.nodeType === Node.ELEMENT_NODE) {
  130. if (node.tagName !== 'INPUT' && node.tagName !== 'TEXTAREA' && node.tagName !== 'SELECT') {
  131. node.childNodes.forEach(child => processTextNodes(child, dict, reverse));
  132. } else if (node.tagName === 'INPUT' && node.type !== 'file') {
  133. node.value = replace(node.value, dict, reverse);
  134. } else if (node.tagName === 'TEXTAREA') {
  135. node.value = replace(node.value, dict, reverse);
  136. }
  137. }
  138. };
  139.  
  140. const createButton = (text, onClick, bgColor) => {
  141. const btn = document.createElement('button');
  142. btn.textContent = text;
  143. Object.assign(btn.style, { width:'100px', display:'inline-block', padding:'2px 4px',
  144. color:'#fff', backgroundColor:bgColor, borderColor:bgColor, borderRadius:'3px', height:'100%' });
  145. btn.onclick = (event) => { event.preventDefault(); onClick(); };
  146. return btn;
  147. };
  148.  
  149. const removeInstructionTextGlobally = () => {
  150. const replaceInstructionText = (node) => {
  151. if (node.nodeType === Node.TEXT_NODE)
  152. node.nodeValue = node.nodeValue.replace(new RegExp(instructionText, 'g'), '');
  153. else if (node.nodeType === Node.ELEMENT_NODE) {
  154. node.childNodes.forEach(replaceInstructionText);
  155. if (node.tagName === 'TEXTAREA') {
  156. node.value = node.value.replace(new RegExp(instructionText, 'g'), '');
  157. }
  158. }
  159. };
  160. replaceInstructionText(document.body);
  161. };
  162.  
  163. const addInstructionText = () => {
  164. const promptTextarea = document.getElementById('prompt-textarea');
  165. if (promptTextarea && !promptTextarea.value.includes(instructionText)) {
  166. promptTextarea.value += ` ${instructionText}`;
  167. }
  168. };
  169.  
  170. const addContainer = () => {
  171. const existingContainer = document.getElementById('encoding-container');
  172. if (existingContainer) return;
  173.  
  174. const container = document.createElement('div');
  175. container.id = 'encoding-container';
  176. Object.assign(container.style, { display:'flex', flexDirection:'row', gap:'6px', margin:'6px', width:'100%', justifyContent:'center', alignItems: 'stretch', scale: '0.8' });
  177.  
  178. // Create Buy me a coffee button
  179. const bmcContainer = document.createElement('div');
  180. bmcContainer.innerHTML = `
  181. <a href="https://www.buymeacoffee.com/screeneroner" target="_blank">
  182. <img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" style="height: 28px !important;margin-right: 8px" >
  183. </a>
  184. `;
  185.  
  186. // Create file input
  187. const fileInput = document.createElement('input');
  188. fileInput.type = 'file';
  189. fileInput.accept = '.txt';
  190.  
  191. fileInput.onchange = event => {
  192. const file = event.target.files[0];
  193. if (file) {
  194. const reader = new FileReader();
  195. reader.onload = e => {
  196. try {
  197. const lines = e.target.result.split('\n').map(line => line.trim()).filter(line => line.length > 0);
  198. Object.keys(placeholders).forEach(key => delete placeholders[key]); // Clear existing placeholders
  199. for (let i = 0; i < lines.length; i++) {
  200. placeholders[i] = lines[i];
  201. }
  202. } catch (err) {
  203. alert('Error reading or parsing file.');
  204. }
  205. };
  206. reader.readAsText(file);
  207. }
  208. };
  209.  
  210. container.append(
  211. bmcContainer,
  212. fileInput,
  213. createButton('UNHIDE', () => { removeInstructionTextGlobally(); processTextNodes(document.body, Object.values(placeholders), true); }, '#d9534f'),
  214. createButton('HIDE', () => { processTextNodes(document.body, Object.values(placeholders), false); addInstructionText(); }, '#5cb85c')
  215. );
  216.  
  217. const promptTextarea = document.getElementById('prompt-textarea');
  218. if (promptTextarea) {
  219. promptTextarea.parentNode.insertBefore(container, promptTextarea.nextSibling);
  220. } else {
  221. document.body.appendChild(container);
  222. }
  223. };
  224.  
  225. setInterval(addContainer, 5000);
  226.  
  227. })();