Enhanced Character.AI Text Color and Font

Enhanced script to change text colors, fonts, and sizes with a modern UI using Tailwind CSS. Now includes more font choices and highlights chosen words.

目前為 2024-06-27 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name        Enhanced Character.AI Text Color and Font
// @namespace   EnhancedCharacterAITextColorFont
// @match       https://*.character.ai/*
// @grant       none
// @license     MIT
// @version     1.2
// @description Enhanced script to change text colors, fonts, and sizes with a modern UI using Tailwind CSS. Now includes more font choices and highlights chosen words.
// @icon        https://i.imgur.com/ynjBqKW.png
// ==/UserScript==

(function () {
  // Add Tailwind CSS to the document
  const tailwindCSS = document.createElement('link');
  tailwindCSS.href = 'https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.2.19/tailwind.min.css';
  tailwindCSS.rel = 'stylesheet';
  document.head.appendChild(tailwindCSS);

  // Add Google Fonts
  const googleFonts = document.createElement('link');
  googleFonts.href = 'https://fonts.googleapis.com/css2?family=Noto+Sans&family=Roboto&family=Arial&family=Courier+New&family=Times+New+Roman&family=Verdana&display=swap';
  googleFonts.rel = 'stylesheet';
  document.head.appendChild(googleFonts);

  var plaintextColor = localStorage.getItem('plaintext_color') || '#958C7F';
  var fontFamily = localStorage.getItem('font_family') || 'Noto Sans, sans-serif';
  var fontSize = localStorage.getItem('font_size') || '16px';

  var css = `
    p, .swiper-no-swiping div {
      color: ${plaintextColor} !important;
      font-family: ${fontFamily} !important;
      font-size: ${fontSize} !important;
    }
  `;

  var head = document.getElementsByTagName("head")[0];
  var style = document.createElement("style");
  style.setAttribute("type", "text/css");
  style.innerHTML = css;
  head.appendChild(style);
})();

function changeColors() {
  const pTags = document.getElementsByTagName("p");
  for (let i = 0; i < pTags.length; i++) {
    const pTag = pTags[i];
    if (
      pTag.dataset.colorChanged === "true" ||
      pTag.querySelector("code") ||
      pTag.querySelector("img")
    ) {
      continue;
    }
    let text = pTag.innerHTML;

    const aTags = pTag.getElementsByTagName("a");
    for (let j = 0; j < aTags.length; j++) {
      const aTag = aTags[j];
      text = text.replace(aTag.outerHTML, "REPLACE_ME_" + j);
    }

    text = text.replace(/(["“”«»].*?["“”«»])/g, `<span style="color: ${localStorage.getItem('quotationmarks_color') || '#FFFFFF'}">$1</span>`);
    text = text.replace(/<em>(.*?)<\/em>/g,`<span style="color: ${localStorage.getItem('italic_color') || '#E0DF7F'}; font-style: italic;">$1</span>`);

    var wordlist_cc = JSON.parse(localStorage.getItem('wordlist_cc')) || [];
    if (wordlist_cc.length > 0) {
      var wordRegex = new RegExp('\\b(' + wordlist_cc.map(word => word.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|') + ')\\b', 'gi');
      text = text.replace(wordRegex, `<span style="color: ${localStorage.getItem('custom_color') || '#FFFFFF'}">$1</span>`);
    }

    for (let j = 0; j < aTags.length; j++) {
      const aTag = aTags[j];
      text = text.replace("REPLACE_ME_" + j, aTag.outerHTML);
    }

    pTag.innerHTML = text;
    pTag.dataset.colorChanged = "true";
  }

  console.log("Changed colors");
}

const observer = new MutationObserver(changeColors);
observer.observe(document, { subtree: true, childList: true });
changeColors();

function createButton(symbol, onClick, extraClasses = '') {
  const button = document.createElement('button');
  button.innerHTML = symbol;
  button.className = `relative bg-none border-none text-lg cursor-pointer ${extraClasses}`;
  button.addEventListener('click', onClick);
  return button;
}

function createColorPanel() {
  const panel = document.createElement('div');
  panel.id = 'colorPanel';
  panel.className = 'fixed top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 bg-gray-800 rounded-lg p-6 shadow-lg text-white z-50';

  const categories = ['italic', 'quotationmarks', 'plaintext', 'custom'];
  const colorPickers = {};

  categories.forEach(category => {
    const colorPicker = document.createElement('input');
    colorPicker.type = 'color';
    const storedColor = localStorage.getItem(`${category}_color`);
    if (storedColor) {
      colorPicker.value = storedColor;
    }

    colorPickers[category] = colorPicker;

    const colorDiv = document.createElement('div');
    colorDiv.className = 'relative w-5 h-5 ml-2 mt-1 bg-current inline-block mr-2 cursor-pointer border border-black';
    colorDiv.style.backgroundColor = colorPicker.value;

    colorDiv.addEventListener('click', function () {
      colorPicker.click();
    });

    colorPicker.addEventListener('input', function () {
      colorDiv.style.backgroundColor = colorPicker.value;
    });

    const label = document.createElement('label');
    label.className = 'inline-block w-32';
    label.appendChild(document.createTextNode(`${category}: `));

    const resetButton = createButton('↺', function () {
      colorPicker.value = getDefaultColor(category);
      colorDiv.style.backgroundColor = colorPicker.value;
    }, 'relative top-1');

    const containerDiv = document.createElement('div');
    containerDiv.appendChild(label);
    containerDiv.appendChild(colorDiv);
    containerDiv.appendChild(resetButton);

    panel.appendChild(containerDiv);
    panel.appendChild(document.createElement('br'));
  });

  // Font selector
  const fontSelector = document.createElement('select');
  fontSelector.className = 'mt-4 bg-gray-700 border-none text-white rounded p-2';
  const fonts = ['Noto Sans', 'Roboto', 'Arial', 'Courier New', 'Times New Roman', 'Verdana'];
  fonts.forEach(font => {
    const option = document.createElement('option');
    option.value = font;
    option.textContent = font;
    fontSelector.appendChild(option);
  });
  fontSelector.value = localStorage.getItem('font_family') || 'Noto Sans';

  const fontSizeInput = document.createElement('input');
  fontSizeInput.type = 'number';
  fontSizeInput.className = 'mt-4 bg-gray-700 border-none text-white rounded p-2 w-20';
  fontSizeInput.value = localStorage.getItem('font_size') ? parseInt(localStorage.getItem('font_size')) : 16;

  panel.appendChild(document.createTextNode('Font: '));
  panel.appendChild(fontSelector);
  panel.appendChild(document.createElement('br'));
  panel.appendChild(document.createTextNode('Font Size: '));
  panel.appendChild(fontSizeInput);
  panel.appendChild(document.createElement('br'));

  // Custom word list input
  const wordListInput = document.createElement('input');
  wordListInput.type = 'text';
  wordListInput.placeholder = 'Separate words with commas';
  wordListInput.className = 'mt-4 bg-gray-700 border-none text-white rounded p-2 w-full';
  panel.appendChild(wordListInput);
  panel.appendChild(document.createElement('br'));

  const wordListContainer = document.createElement('div');
  wordListContainer.className = 'flex flex-wrap mt-4';
  panel.appendChild(wordListContainer);

  const wordListArray = JSON.parse(localStorage.getItem('wordlist_cc')) || [];

  function createWordButton(word) {
    const wordButton = createButton(`${word} ✕`, function() {
      const index = wordListArray.indexOf(word);
      if (index !== -1) {
        wordListArray.splice(index, 1);
        updateWordListButtons();
      }
    }, 'bg-blue-500 hover:bg-blue-600 text-white rounded px-2 py-1 m-1');

    return wordButton;
  }

  function updateWordListButtons() {
    wordListContainer.innerHTML = '';
    wordListArray.forEach(word => {
      const wordButton = createWordButton(word);
      wordListContainer.appendChild(wordButton);
    });
  }

  const addWordsButton = document.createElement('button');
  addWordsButton.textContent = 'Add';
  addWordsButton.className = 'bg-green-500 hover:bg-green-600 text-white rounded px-4 py-2 mt-4';
  addWordsButton.addEventListener('click', function() {
    const newWords = wordListInput.value.split(',').map(word => word.trim()).filter(word => word);
    newWords.forEach(word => {
      if (!wordListArray.includes(word)) {
        wordListArray.push(word);
      }
    });
    localStorage.setItem('wordlist_cc', JSON.stringify(wordListArray));
    updateWordListButtons();
    wordListInput.value = '';
  });

  panel.appendChild(addWordsButton);

  // Update buttons initially
  updateWordListButtons();

  const buttonContainer = document.createElement('div');
  buttonContainer.className = 'mt-6 flex justify-between';

  const okButton = document.createElement('button');
  okButton.textContent = 'Confirm';
  okButton.className = 'w-24 h-10 rounded bg-green-500 hover:bg-green-600 text-white';
  okButton.addEventListener('click', function () {
    categories.forEach(category => {
      const oldValue = localStorage.getItem(`${category}_color`);
      const newValue = colorPickers[category].value;

      if (oldValue !== newValue) {
        localStorage.setItem(`${category}_color`, newValue);
        if (category === 'plaintext') {
          window.location.reload();
        }
      }
    });

    localStorage.setItem('font_family', fontSelector.value);
    localStorage.setItem('font_size', fontSizeInput.value + 'px');
    localStorage.setItem('wordlist_cc', JSON.stringify(wordListArray));
    window.location.reload();
  });

  const cancelButton = document.createElement('button');
  cancelButton.textContent = 'Cancel';
  cancelButton.className = 'w-24 h-10 rounded bg-red-500 hover:bg-red-600 text-white';
  cancelButton.addEventListener('click', function () {
    panel.remove();
  });

  buttonContainer.appendChild(okButton);
  buttonContainer.appendChild(cancelButton);
  panel.appendChild(buttonContainer);

  document.body.appendChild(panel);
}

function getDefaultColor(category) {
  const defaultColors = {
    'italic': '#E0DF7F',
    'quotationmarks': '#FFFFFF',
    'plaintext': '#958C7F',
    'custom': '#E0DF7F'
  };
  return defaultColors[category];
}

const mainButton = createButton('', function () {
  const colorPanelExists = document.getElementById('colorPanel');
  if (!colorPanelExists) {
    createColorPanel();
  }
});

mainButton.style.backgroundImage = "url('https://i.imgur.com/yBgJ3za.png')";
mainButton.style.backgroundSize = "cover";
mainButton.style.top = "0px";
mainButton.style.width = "22px";
mainButton.style.height = "22px";

function insertMainButton() {
  const targetSelector = '.chat2 > div:nth-child(1) > div:nth-child(1) > div:nth-child(3) > div:nth-child(1)';
  const targetPanel1 = document.querySelector(targetSelector);

  if (targetPanel1) {
    if (!document.querySelector('.color-palette-button')) {
      mainButton.classList.add('color-palette-button');
      targetPanel1.insertBefore(mainButton, targetPanel1.firstChild);
    }
  } else {
    const observer = new MutationObserver(() => {
      const updatedTargetPanel = document.querySelector(targetSelector);
      if (updatedTargetPanel) {
        mainButton.classList.add('color-palette-button');
        updatedTargetPanel.insertBefore(mainButton, updatedTargetPanel.firstChild);
        observer.disconnect();
      }
    });

    observer.observe(document.body, { subtree: true, childList: true });
    console.error('Target panel not found. Waiting for changes...');
  }
}

setInterval(insertMainButton, 1000);
insertMainButton();