- // ==UserScript==
- // @name OpenAI Censor
- // @namespace http://tampermonkey.net/
- // @version 2024-06-19
- // @description Encode/decode words from the given file on the ChatGPT page to hide sensitive info.
- // @author Screeneroner
- // @license GPL v3
- // @homepageURL https://github.com/screeneroner/OpenAI-Censor
- // @match https://chat.openai.com/*
- // @match https://chatgpt.com/*
- // @icon https://seeklogo.com/images/C/chatgpt-logo-4AE4C3B8A4-seeklogo.com.png
- // @grant none
- // ==/UserScript==
-
- /*——————————————————————————————————————————————————————————————————————————————————————————————————
-
- This code is free software: you can redistribute it and/or modify it under the terms of the
- version 3 GNU General Public License as published by the Free Software Foundation.
-
- This code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY without even
- the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details (https://www.gnu.org/licenses/gpl-3.0.html)
-
- WARNING TO USERS AND MODIFIERS
-
- This script contains "Buy me a coffee" links to honor the author's hard work and dedication in creating
- all features present in this code. Removing or altering these links not only violates the GPL license
- but also disregards the significant effort put into making this script valuable for the community.
-
- If you find value in this script and would like to show appreciation to the author,
- kindly consider visiting the site below and treating the author to a few cups of coffee:
-
- https://www.buymeacoffee.com/screeneroner
-
- Your honor and gratitude is greatly appreciated.
-
- ——————————————————————————————————————————————————————————————————————————————————————————————————*/
-
- /*——————————————————————————————————————————————————————————————————————————————————————————————————
-
- ## Three can keep a secret if two are dead
-
- AI is a powerful tool that may use your inputs to "improve models," "enhance your experience," and
- other similar purposes. This means YOUR TEXTS MUST BE STORED SOMEWHERE, in a location that you do
- not have access to verify or delete them. Even if you accidentally send your ABSOLUTELY PRIVATE
- information, it could be stored. To prevent even a theoretical possibility of disclosing your own
- secrets or confidential information about clients and/or customers, you SHOULD NOT SHARE IT AT ALL!
- However, for example, if you are using AI to correct grammar, wording, and tone of your letters,
- you must use the exact text in the AI prompts.
-
- ## Hide it to keep it
-
- The solution for this dichotomy is straightforward - just replace real words with their 'anonymized'
- analogs! Instead of calling your client by its real name 'Super Secret IT Company LTD.', use
- 'Client Company Name' in the prompt. There are two main problems with this approach. The first is
- that there may be many places in your text where you need to make these changes. It can take a lot
- of time to replace them all, and you may miss some entries, leaving them unhidden. The second
- problem is that the response from the AI will contain these 'anonymized' words, and you will need
- to replace them back to get 'unhidden' text.
-
- This script will handle the above-described routine for you! You should prepare the encrypting file
- in a zebra-like format. The first row contains the real word(s), and the second row contains its
- anonymized version. Like this:
- ```
- Apple
- Client Company
- Steve Jobs
- John Smith
- ```
- The script will inject the transcoding codes into the OpenAI ChatGPT web page along with three
- buttons below the entry field: Choose File, Unhide, and Hide. The first button will load the
- 'encrypting' file (you may use several ones). When you press the Hide button, it will scan the
- entire page (including the prompt entry text area) and replace real words with anonymized ones.
- From this moment, you can safely send your texts to the AI. Once you get a response and press the
- Unhide button, the script will do a reverse translation, and you will see the original texts and
- words ready to be copy-pasted.
-
- The important point here is that the AI operates with anonymized text only. The real, de-anonymized
- texts remain in the operational memory of your local computer and become unhidden only when you
- load the proper encoding file and press the Unhide button!
-
- ## How to use this script
-
- 1. Download and install a browser add-on like Tampermonkey (or similar)
- 2. Install this OpenAI Censor script according to the add-on's script installation procedure.
- 3. Prepare and save the encoding file with pairs of rows that contain real and encoded text.
- 4. Open the ChatGPT web page. You should see a new row with the script buttons under the input area.
- 5. Press the Choose File button and load your encryption file.
- 6. Type your prompt.
- 7. Press the Hide button and verify that all real words were anonymized.
- 8. Send the anonymized prompt to the AI and get its response.
- 9. Press the Unhide button to replace anonymized words with the originals and get the original texts.
- 10. Profit!
-
- ——————————————————————————————————————————————————————————————————————————————————————————————————*/
-
- // VERSION CHANGES
- // 2024-06-15 Initial release
- // 2024-06-19 Fixed bug when encoding data was not cleared on re-reading (changed) encoding file.
-
- (function() {
- 'use strict';
-
- const placeholders = {};
- const instructionText = "WARNING! Tokens in ««»», represent some hidden words and in your responses they should be used as-is without any changes inside the ««»»!";
-
- const replace = (text, dict, reverse = false) => {
- if (typeof text !== 'string') return text;
- if (reverse) {
- for (let i = 1; i < dict.length; i += 2) {
- const pattern = `««${dict[i]}»»`;
- const re = new RegExp(pattern, 'g');
- text = text.replace(re, dict[i - 1]);
- }
- } else {
- for (let i = 0; i < dict.length; i += 2) {
- const pattern = dict[i];
- const replacement = `««${dict[i + 1]}»»`;
- const re = new RegExp(pattern, 'g');
- text = text.replace(re, replacement);
- }
- }
- return text;
- };
-
- const processTextNodes = (node, dict, reverse = false) => {
- if (node.nodeType === Node.TEXT_NODE) {
- node.nodeValue = replace(node.nodeValue, dict, reverse);
- } else if (node.nodeType === Node.ELEMENT_NODE) {
- if (node.tagName !== 'INPUT' && node.tagName !== 'TEXTAREA' && node.tagName !== 'SELECT') {
- node.childNodes.forEach(child => processTextNodes(child, dict, reverse));
- } else if (node.tagName === 'INPUT' && node.type !== 'file') {
- node.value = replace(node.value, dict, reverse);
- } else if (node.tagName === 'TEXTAREA') {
- node.value = replace(node.value, dict, reverse);
- }
- }
- };
-
- const createButton = (text, onClick, bgColor) => {
- const btn = document.createElement('button');
- btn.textContent = text;
- Object.assign(btn.style, { width:'100px', display:'inline-block', padding:'2px 4px',
- color:'#fff', backgroundColor:bgColor, borderColor:bgColor, borderRadius:'3px', height:'100%' });
- btn.onclick = (event) => { event.preventDefault(); onClick(); };
- return btn;
- };
-
- const removeInstructionTextGlobally = () => {
- const replaceInstructionText = (node) => {
- if (node.nodeType === Node.TEXT_NODE)
- node.nodeValue = node.nodeValue.replace(new RegExp(instructionText, 'g'), '');
- else if (node.nodeType === Node.ELEMENT_NODE) {
- node.childNodes.forEach(replaceInstructionText);
- if (node.tagName === 'TEXTAREA') {
- node.value = node.value.replace(new RegExp(instructionText, 'g'), '');
- }
- }
- };
- replaceInstructionText(document.body);
- };
-
- const addInstructionText = () => {
- const promptTextarea = document.getElementById('prompt-textarea');
- if (promptTextarea && !promptTextarea.value.includes(instructionText)) {
- promptTextarea.value += ` ${instructionText}`;
- }
- };
-
- const addContainer = () => {
- const existingContainer = document.getElementById('encoding-container');
- if (existingContainer) return;
-
- const container = document.createElement('div');
- container.id = 'encoding-container';
- Object.assign(container.style, { display:'flex', flexDirection:'row', gap:'6px', margin:'6px', width:'100%', justifyContent:'center', alignItems: 'stretch', scale: '0.8' });
-
- // Create Buy me a coffee button
- const bmcContainer = document.createElement('div');
- bmcContainer.innerHTML = `
- <a href="https://www.buymeacoffee.com/screeneroner" target="_blank">
- <img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" style="height: 28px !important;margin-right: 8px" >
- </a>
- `;
-
- // Create file input
- const fileInput = document.createElement('input');
- fileInput.type = 'file';
- fileInput.accept = '.txt';
-
- fileInput.onchange = event => {
- const file = event.target.files[0];
- if (file) {
- const reader = new FileReader();
- reader.onload = e => {
- try {
- const lines = e.target.result.split('\n').map(line => line.trim()).filter(line => line.length > 0);
- Object.keys(placeholders).forEach(key => delete placeholders[key]); // Clear existing placeholders
- for (let i = 0; i < lines.length; i++) {
- placeholders[i] = lines[i];
- }
- } catch (err) {
- alert('Error reading or parsing file.');
- }
- };
- reader.readAsText(file);
- }
- };
-
- container.append(
- bmcContainer,
- fileInput,
- createButton('UNHIDE', () => { removeInstructionTextGlobally(); processTextNodes(document.body, Object.values(placeholders), true); }, '#d9534f'),
- createButton('HIDE', () => { processTextNodes(document.body, Object.values(placeholders), false); addInstructionText(); }, '#5cb85c')
- );
-
- const promptTextarea = document.getElementById('prompt-textarea');
- if (promptTextarea) {
- promptTextarea.parentNode.insertBefore(container, promptTextarea.nextSibling);
- } else {
- document.body.appendChild(container);
- }
- };
-
- setInterval(addContainer, 5000);
-
- })();