Character.AI Text Color

Changes the color of all text except the text within "Quotation Marks"

当前为 2023-11-14 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Character.AI Text Color
  3. // @namespace Character.AI Text Color by Vishanka
  4. // @match https://*.character.ai/*
  5. // @grant none
  6. // @license MIT
  7. // @version 2.1
  8. // @author Vishanka via chatGPT
  9. // @description Changes the color of all text except the text within "Quotation Marks"
  10. // @icon https://i.imgur.com/ynjBqKW.png
  11. // ==/UserScript==
  12.  
  13.  
  14. (function () {
  15. var plaintextColor = localStorage.getItem('plaintext_color');
  16.  
  17. // Default color if 'plaintext_color' is not set
  18. var defaultColor = '#958C7F';
  19.  
  20. // Use the retrieved color or default color
  21. var color = plaintextColor || defaultColor;
  22.  
  23. // Create the CSS style
  24. var css =
  25. "p, .swiper-no-swiping div { color: " + color + " !important; font-family: 'Noto Sans', sans-serif !important; }";
  26.  
  27. var head = document.getElementsByTagName("head")[0];
  28. var style = document.createElement("style");
  29. style.setAttribute("type", "text/css");
  30. style.innerHTML = css;
  31. head.appendChild(style);
  32.  
  33.  
  34.  
  35. })();
  36.  
  37. function changeColors() {
  38. const pTags = document.getElementsByTagName("p");
  39. for (let i = 0; i < pTags.length; i++) {
  40. const pTag = pTags[i];
  41. if (
  42. pTag.dataset.colorChanged === "true" ||
  43. pTag.querySelector("code") ||
  44. pTag.querySelector("img")
  45. ) {
  46. continue;
  47. }
  48. let text = pTag.innerHTML;
  49.  
  50. const aTags = pTag.getElementsByTagName("a"); // Get all <a> tags within the <p> tag
  51.  
  52. // Remove the <a> tags temporarily
  53. for (let j = 0; j < aTags.length; j++) {
  54. const aTag = aTags[j];
  55. text = text.replace(aTag.outerHTML, "REPLACE_ME_" + j); // Use a placeholder to be able to restore the links later
  56. }
  57.  
  58. //Changes Text within Quotation Marks to white
  59. text = text.replace(/(["“”«»].*?["“”«»])/g, `<span style="color: ${localStorage.getItem('quotationmarks_color') || '#FFFFFF'}">$1</span>`);
  60. //Changes Text within Quotation Marks and a comma at the end to orange
  61. text = text.replace(/(["“”«»][^"]*?,["“”«»])/g, '<span style="color: #E0DF7F">$1</span>');
  62. // Changes Italic Text Color
  63. text = text.replace(/<em>(.*?)<\/em>/g,`<span style="color: ${localStorage.getItem('italic_color') || '#E0DF7F'}; font-style: italic;">$1</span>`);
  64.  
  65. var wordlist_cc = JSON.parse(localStorage.getItem('wordlist_cc')) || [];
  66.  
  67. // Check if the wordlist is not empty before creating the regex
  68. if (wordlist_cc.length > 0) {
  69. // Escape special characters in each word and join them with the "|" operator
  70. var wordRegex = new RegExp('\\b(' + wordlist_cc.map(word => word.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|') + ')\\b', 'gi');
  71.  
  72. // Replace matching words in the text
  73. text = text.replace(wordRegex, `<span style="color: ${localStorage.getItem('custom_color') || '#FFFFFF'}">$1</span>`);
  74. }
  75.  
  76.  
  77.  
  78. // Restore the <a> tags
  79. for (let j = 0; j < aTags.length; j++) {
  80. const aTag = aTags[j];
  81. text = text.replace("REPLACE_ME_" + j, aTag.outerHTML);
  82. }
  83.  
  84. pTag.innerHTML = text;
  85. pTag.dataset.colorChanged = "true";
  86. }
  87.  
  88. console.log("Changed colors");
  89. }
  90.  
  91. const observer = new MutationObserver(changeColors);
  92. observer.observe(document, { subtree: true, childList: true });
  93. changeColors();
  94.  
  95.  
  96.  
  97. function createButton(symbol, onClick) {
  98. const colorpalettebutton = document.createElement('button');
  99. colorpalettebutton.innerHTML = symbol;
  100. colorpalettebutton.style.position = 'relative';
  101. colorpalettebutton.style.background = 'none';
  102. colorpalettebutton.style.border = 'none';
  103. colorpalettebutton.style.fontSize = '18px';
  104. colorpalettebutton.style.top = '-5px';
  105. colorpalettebutton.style.cursor = 'pointer';
  106. colorpalettebutton.addEventListener('click', onClick);
  107. return colorpalettebutton;
  108. }
  109.  
  110. // Function to create the color selector panel
  111. function createColorPanel() {
  112. const panel = document.createElement('div');
  113. panel.id = 'colorPanel';
  114. panel.style.position = 'fixed';
  115. panel.style.top = '50%';
  116. panel.style.left = '50%';
  117. panel.style.transform = 'translate(-50%, -50%)';
  118. panel.style.backgroundColor = 'rgba(0, 0, 0, 0.95)';
  119. panel.style.border = 'none';
  120. panel.style.borderRadius = '5px';
  121. panel.style.padding = '20px';
  122. // panel.style.border = '2px solid #000';
  123. panel.style.zIndex = '9999';
  124.  
  125. const categories = ['italic', 'quotationmarks', 'plaintext', 'custom'];
  126.  
  127. const colorPickers = {};
  128.  
  129. // Set a fixed width for the labels
  130. const labelWidth = '150px';
  131.  
  132. categories.forEach(category => {
  133. const colorPicker = document.createElement('input');
  134. colorPicker.type = 'color';
  135.  
  136. // Retrieve stored color from local storage
  137. const storedColor = localStorage.getItem(`${category}_color`);
  138. if (storedColor) {
  139. colorPicker.value = storedColor;
  140. }
  141.  
  142. colorPickers[category] = colorPicker;
  143.  
  144. // Create a div to hold color picker
  145. const colorDiv = document.createElement('div');
  146. colorDiv.style.position = 'relative';
  147. colorDiv.style.width = '20px';
  148. colorDiv.style.height = '20px';
  149. colorDiv.style.marginLeft = '10px';
  150. colorDiv.style.top = '5px';
  151. colorDiv.style.backgroundColor = colorPicker.value;
  152. colorDiv.style.display = 'inline-block';
  153. colorDiv.style.marginRight = '10px';
  154. colorDiv.style.cursor = 'pointer';
  155. colorDiv.style.border = '1px solid black';
  156.  
  157.  
  158. // Event listener to open color picker when the color square is clicked
  159. colorDiv.addEventListener('click', function () {
  160. colorPicker.click();
  161. });
  162.  
  163. // Event listener to update the color div when the color changes
  164. colorPicker.addEventListener('input', function () {
  165. colorDiv.style.backgroundColor = colorPicker.value;
  166. });
  167.  
  168. const label = document.createElement('label');
  169. label.style.width = labelWidth; // Set fixed width for the label
  170. label.appendChild(document.createTextNode(`${category}: `));
  171.  
  172. // Reset button for each color picker
  173. const resetButton = createButton('↺', function () {
  174. colorPicker.value = getDefaultColor(category);
  175. colorDiv.style.backgroundColor = colorPicker.value;
  176. });
  177. resetButton.style.position = 'relative';
  178. resetButton.style.top = '1px';
  179. // Create a div to hold label, color picker, and reset button
  180. const containerDiv = document.createElement('div');
  181. containerDiv.appendChild(label);
  182. containerDiv.appendChild(colorDiv);
  183. containerDiv.appendChild(resetButton);
  184.  
  185. panel.appendChild(containerDiv);
  186. panel.appendChild(document.createElement('br'));
  187. });
  188.  
  189. // Custom word list input
  190. const wordListInput = document.createElement('input');
  191. wordListInput.type = 'text';
  192. wordListInput.placeholder = 'Separate words with commas';
  193. wordListInput.style.width = '250px';
  194. wordListInput.style.height = '35px';
  195. wordListInput.style.borderRadius = '3px';
  196. wordListInput.style.marginBottom = '10px';
  197. panel.appendChild(wordListInput);
  198. panel.appendChild(document.createElement('br'));
  199.  
  200. const wordListContainer = document.createElement('div');
  201. wordListContainer.style.display = 'flex';
  202. wordListContainer.style.flexWrap = 'wrap';
  203. wordListContainer.style.maxWidth = '300px'; // Set a fixed maximum width for the container
  204.  
  205. // Display custom word list buttons
  206. const wordListArray = JSON.parse(localStorage.getItem('wordlist_cc')) || [];
  207. const wordListButtons = [];
  208.  
  209. function createWordButton(word) {
  210. const wordButton = createButton(`${word} 🞮`, function() {
  211. // Remove the word from the list and update the panel
  212. const index = wordListArray.indexOf(word);
  213. if (index !== -1) {
  214. wordListArray.splice(index, 1);
  215. updateWordListButtons();
  216. }
  217. });
  218.  
  219. // Word Buttons
  220. wordButton.style.borderRadius = '3px';
  221. wordButton.style.border = 'none';
  222. wordButton.style.backgroundColor = 'lightsteelblue';
  223. wordButton.style.marginBottom = '5px';
  224. wordButton.style.marginRight = '5px';
  225. wordButton.style.fontSize = '16px';
  226. wordButton.classList.add('word-button');
  227. return wordButton;
  228. }
  229.  
  230. function updateWordListButtons() {
  231. wordListContainer.innerHTML = ''; // Clear the container
  232. wordListArray.forEach(word => {
  233. const wordButton = createWordButton(word);
  234. wordListContainer.appendChild(wordButton);
  235. });
  236. }
  237.  
  238. // Append wordListContainer to the panel
  239.  
  240.  
  241.  
  242. updateWordListButtons();
  243.  
  244. // Add Words button
  245. const addWordsButton = document.createElement('button');
  246. addWordsButton.textContent = 'Add';
  247. addWordsButton.style.marginTop = '-8px';
  248. addWordsButton.style.marginLeft = '5px';
  249. addWordsButton.style.borderRadius = '3px';
  250. addWordsButton.style.border = 'none';
  251. addWordsButton.style.backgroundColor = 'lightsteelblue';
  252. addWordsButton.addEventListener('click', function() {
  253. // Get the input value, split into words, and add to wordListArray
  254. const wordListValue = wordListInput.value;
  255. const newWords = wordListValue.split(',').map(word => word.trim());
  256. wordListArray.push(...newWords);
  257.  
  258. // Update the word list buttons in the panel
  259. updateWordListButtons();
  260. });
  261.  
  262. // Create a div to group the input and button on the same line
  263. const inputButtonContainer = document.createElement('div');
  264. inputButtonContainer.style.display = 'flex';
  265. inputButtonContainer.style.alignItems = 'center';
  266.  
  267. inputButtonContainer.appendChild(wordListInput);
  268. inputButtonContainer.appendChild(addWordsButton);
  269.  
  270. // Append the container to the panel
  271. panel.appendChild(inputButtonContainer);
  272. panel.appendChild(wordListContainer);
  273. // Create initial word list buttons
  274. updateWordListButtons();
  275.  
  276.  
  277. // OK button
  278. const okButton = document.createElement('button');
  279. okButton.textContent = 'Confirm';
  280. okButton.style.marginTop = '-20px';
  281. okButton.style.width = '75px';
  282. okButton.style.height = '35px';
  283. okButton.style.marginRight = '5px';
  284. okButton.style.borderRadius = '3px';
  285. okButton.style.border = 'none';
  286. okButton.style.backgroundColor = 'lightsteelblue';
  287. okButton.style.position = 'relative';
  288. okButton.style.left = '24%';
  289. //okButton.style.transform = 'translateX(-50%)';
  290. okButton.addEventListener('click', function() {
  291. // Save selected colors to local storage
  292. categories.forEach(category => {
  293. const oldValue = localStorage.getItem(`${category}_color`);
  294. const newValue = colorPickers[category].value;
  295.  
  296. if (oldValue !== newValue) {
  297. localStorage.setItem(`${category}_color`, newValue);
  298.  
  299. // If 'plaintext' color is changed, auto-reload the page
  300. if (category === 'plaintext') {
  301. window.location.reload();
  302. }
  303. }
  304. });
  305.  
  306. // Save custom word list to local storage
  307. const wordListValue = wordListInput.value;
  308. const newWords = wordListValue.split(',').map(word => word.trim().toLowerCase()).filter(word => word !== ''); // Convert to lowercase and remove empty entries
  309. const uniqueNewWords = Array.from(new Set(newWords)); // Remove duplicates
  310.  
  311. // Check for existing words and add only new ones
  312. uniqueNewWords.forEach(newWord => {
  313. if (!wordListArray.includes(newWord)) {
  314. wordListArray.push(newWord);
  315. }
  316. });
  317.  
  318. localStorage.setItem('wordlist_cc', JSON.stringify(wordListArray));
  319.  
  320. updateWordListButtons();
  321.  
  322. // Close the panel
  323. panel.remove();
  324. });
  325.  
  326. // Cancel button
  327. const cancelButton = document.createElement('button');
  328. cancelButton.textContent = 'Cancel';
  329. cancelButton.style.marginTop = '-20px';
  330. cancelButton.style.borderRadius = '3px';
  331. cancelButton.style.width = '75px';
  332. cancelButton.style.marginLeft = '5px';
  333. cancelButton.style.height = '35px';
  334. cancelButton.style.border = 'none';
  335. cancelButton.style.backgroundColor = 'darkgrey';
  336. cancelButton.style.position = 'relative';
  337. cancelButton.style.left = '25%';
  338. cancelButton.addEventListener('click', function() {
  339. // Close the panel without saving
  340. panel.remove();
  341. });
  342.  
  343. panel.appendChild(document.createElement('br'));
  344. panel.appendChild(okButton);
  345. panel.appendChild(cancelButton);
  346.  
  347. document.body.appendChild(panel);
  348. }
  349.  
  350.  
  351.  
  352. // Function to get the default color for a category
  353. function getDefaultColor(category) {
  354. const defaultColors = {
  355. 'italic': '#E0DF7F',
  356. 'quotationmarks': '#FFFFFF',
  357. 'plaintext': '#958C7F',
  358. 'custom': '#E0DF7F'
  359. };
  360. return defaultColors[category];
  361. }
  362.  
  363. const mainButton = createButton('', function() {
  364. const colorPanelExists = document.getElementById('colorPanel');
  365. if (!colorPanelExists) {
  366. createColorPanel();
  367. }
  368. });
  369.  
  370. // Set the background image of the button to the provided image
  371. mainButton.style.backgroundImage = "url('https://i.imgur.com/yBgJ3za.png')";
  372. mainButton.style.backgroundSize = "cover";
  373. mainButton.style.top = "0px";
  374. mainButton.style.width = "22px"; // Adjust the width and height as needed
  375. mainButton.style.height = "22px"; // Adjust the width and height as needed
  376.  
  377.  
  378. const targetSelector = '.chat2 > div:nth-child(1) > div:nth-child(1) > div:nth-child(3) > div:nth-child(1)';
  379. const targetPanel1 = document.querySelector(targetSelector);
  380.  
  381. if (targetPanel1) {
  382. targetPanel1.insertBefore(mainButton, targetPanel1.firstChild);
  383. } else {
  384. // If the target panel is not found, set up a MutationObserver to keep checking for it.
  385. const observer = new MutationObserver(() => {
  386. const updatedTargetPanel = document.querySelector(targetSelector);
  387. if (updatedTargetPanel) {
  388. // Once the target panel is available, insert the mainButton and disconnect the observer.
  389. updatedTargetPanel.insertBefore(mainButton, updatedTargetPanel.firstChild);
  390. observer.disconnect();
  391. }
  392. });
  393.  
  394. // Set up the observer to watch for changes in the parent of the target panel or higher up in the DOM.
  395. observer.observe(document.body, { subtree: true, childList: true });
  396.  
  397. console.error('Target panel not found. Waiting for changes...');
  398. }