- // ==UserScript==
- // @name Caveduck Modifier
- // @namespace https://labs.muyi.tw/caveduck_modifier/
- // @version 0.28
- // @description 修改Caveduck網站的樣式。
- // @license AGPL-3.0-or-later
- // @author 慕儀
- // @match *://caveduck.io/*
- // @grant GM_addStyle
- // @icon data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgdmlld0JveD0iMCAwIDEyODAgMTI4MCI+CiAgPGRlZnM+CiAgICA8c3R5bGU+CiAgICAgIC5jbHMtMSB7CiAgICAgICAgZmlsbDogI2ZmZjsKICAgICAgfQoKICAgICAgLmNscy0yIHsKICAgICAgICBmaWxsOiAjZjJiNDEyOwogICAgICB9CiAgICA8L3N0eWxlPgogIDwvZGVmcz4KICA8IS0tIEdlbmVyYXRvcjogQWRvYmUgSWxsdXN0cmF0b3IgMjguNy4xLCBTVkcgRXhwb3J0IFBsdWctSW4gLiBTVkcgVmVyc2lvbjogMS4yLjAgQnVpbGQgMTQyKSAgLS0+CiAgPGc+CiAgICA8ZyBpZD0iQ2F2ZWR1Y2siPgogICAgICA8ZyBpZD0iQ2F2ZWR1Y2stMiIgZGF0YS1uYW1lPSJDYXZlZHVjayI+CiAgICAgICAgPHBhdGggZD0iTTEwOTYuMSw0NTMuNWMzMDUuMiw0OTcuMywxNTIuNiw3NDYtNDU3LjgsNzQ2Uy0xMjQuNiw5NTAuOCwxODAuNiw0NTMuNWMzMDUuMi00OTcuMyw2MTAuMy00OTcuMyw5MTUuNSwwWiIvPgogICAgICAgIDxwYXRoIGNsYXNzPSJjbHMtMSIgZD0iTTExNDcuNiw0NTMuNWMyMDguOCwzNjEuNywxMDQuNCw1NDIuNS0zMTMuMiw1NDIuNXMtNTIyLTE4MC44LTMxMy4yLTU0Mi41YzIwOC44LTM2MS43LDQxNy42LTM2MS43LDYyNi41LDBaIi8+CiAgICAgICAgPHBhdGggY2xhc3M9ImNscy0yIiBkPSJNNjg0LDcyMS4yYzEwMC4yLDE3My42LDUwLjEsMjYwLjQtMTUwLjMsMjYwLjRzLTI1MC42LTg2LjgtMTUwLjMtMjYwLjRjMTAwLjItMTczLjYsMjAwLjUtMTczLjYsMzAwLjcsMFoiLz4KICAgICAgICA8cGF0aCBkPSJNODcxLjksNjEyLjdjMjUuMSw0My40LDEyLjUsNjUuMS0zNy42LDY1LjFzLTYyLjYtMjEuNy0zNy42LTY1LjFjMjUuMS00My40LDUwLjEtNDMuNCw3NS4yLDBaIi8+CiAgICAgIDwvZz4KICAgIDwvZz4KICA8L2c+Cjwvc3ZnPg==
- // ==/UserScript==
-
- (function () {
- 'use strict';
-
- let inLanguage;
- let debouncedAutoHeight;
- const $ = (selector) => document.querySelectorAll(selector);
- const $$ = (selector) => document.querySelector(selector);
- const tarAutoHeight = `prompt-input`;
- const tarAutoScrollHeight = `lorebook-data-input textarea, #charDesc`;
- const textReplaceSelector = `#chatMessages b:not([data-text-replaced]), #chatMessages p:not([data-text-replaced])`;
- const cURL = window.location.href;
- const muyiStyles = 'https://labs.muyi.tw/caveduck_modifier/style2.css?v=11312010702';
- const fontStyles = `
- user-input-form div[ng-repeat] textarea,
- #chatMessages b,
- #chatMessages p,
- form[ng-if~="!!chat.editMode"] textarea {
- font: normal clamp(16px, .95vw, 32px) / 1.75em var(--m_ff1);
- }
- #chatMessages b {
- font-family: var(--m_ff2);
- font-weight: 400;
- }
- form[ng-if~="!!chat.editMode"] textarea {
- font-size: var(--m_font-size);
- }
- user-input-form div[ng-repeat] textarea {
- font-size: var(--m_font-size);
- }
- `;
- const charMap = {
- '\\.{2,}': '⋯⋯',
- '⋯': '⋯⋯',
- '⋯{3,}': '⋯⋯',
- '!': '!',
- '\\?': '?',
- '~': '~',
- ';': ';',
- ':': ':',
- ',': ',',
- '\\.': '。',
- '\\(': '(',
- '\\)': ')'
- };
- const locale = {
- 'zh-hant': {
- cb_fontOverride: ['覆蓋字型', '作用頁:Talk<br>用慕儀喜歡的自訂字型取代預設字型。'],
- cb_shortButtons: ['快捷按鈕', '作用頁:Talk<br>將「我的資訊」與「使用者筆記」按鈕移到右側。'],
- cb_replaceText: ['取代符號', '作用頁:Talk<br>Claude 3 Haiku會使用錯誤的中文標點符號,這個功能可以修正它。'],
- cb_deskFix: ['桌面顯示修正', '作用頁:Talk<br>修正高解析度下的顯示體驗,讓對話畫面佔用全版,且圖片顯示區域更大。'],
- cb_mdFix: ['行動顯示修正', '作用頁:Talk<br>修正行動裝置的顯示問題。'],
- cb_hideLLM: ['隱藏低效LLM', '作用頁:Talk<br>隱藏表現與成本不合比例的LLM,避免誤選取。'],
- cb_autoHeight: ['編輯框自動高度', '作用頁:Edit Character、Lorebook、Custom prompt<br>每個項目使用卷軸十分愚蠢,勾選此項可以將其設為自動高度。'],
- toggleButton: '慕儀\n神器',
- reloadButton: '套用並重載',
- },
- 'en': {
- cb_fontOverride: ['Override Font', 'Active on: Talk<br>Replace default font with MuYi\'s preferred custom font.'],
- cb_shortButtons: ['Shortcut buttons', 'Active on: Talk<br>Move the "My Information" and "User Notes" buttons to the right side.'],
- cb_replaceText: ['Replace Symbols', 'Active on: Talk<br>Claude 3 Haiku uses incorrect Chinese punctuation. This feature fixes it.'],
- cb_deskFix: ['Desktop Display Fix', 'Active on: Talk<br>Fix display experience on high resolution, making the chat screen occupy the full screen and enlarging the image display area.'],
- cb_mdFix: ['Mobile Display Fix', 'Active on: Talk<br>Fix the display issues of mobile devices.'],
- cb_hideLLM: ['Hide unimportant LLMs', 'Active on: Talk<br>Hide LLMs whose performance does not correlate with their costs to avoid misselection.'],
- cb_autoHeight: ['Auto Height for Edit Box', 'Active on: Edit Character、Lorebook、Custom prompt<br>Using scrollbars for each item is stupid. Enable this to auto-height.'],
- toggleButton: 'MuYi\'s\nToolbox',
- reloadButton: 'Apply and reload',
- },
- };
- const settings = [
- {
- localeName: 'cb_fontOverride',
- key: 'sw_fontOverride'
- },
- {
- localeName: 'cb_shortButtons',
- key: 'sw_shortButtons'
- },
- {
- localeName: 'cb_replaceText',
- key: 'sw_replaceText'
- },
- {
- localeName: 'cb_deskFix',
- key: 'sw_deskFix'
- },
- {
- localeName: 'cb_hideLLM',
- key: 'sw_hideLLM'
- },
- {
- localeName: 'cb_mdFix',
- key: 'sw_mdFix'
- },
- {
- localeName: 'cb_autoHeight',
- key: 'sw_autoHeight'
- }
- ];
-
- const switches = {};
- settings.forEach(setting => {
- switches[setting.key] = JSON.parse(localStorage.getItem(`enable${setting.key.slice(3)}`) || 'false');
- });
-
- const domElements = {
- o_editMyInfoButton: 'button[ng-click="onEditMyInfoButtonClicked()"]',
- o_editUserNoteButton: 'button[ng-click="onEditUserNoteButtonClicked()"]',
- o_optionButton: '#optionButton',
- o_editButton: '#editButton',
- o_imgButton: '.hidden[ng-show~="backgroundImage"]'
- };
-
- const [
- o_editMyInfoButton,
- o_editUserNoteButton,
- o_optionButton,
- o_editButton,
- o_imgButton
- ] = Object.values(domElements).map(selector => $$(selector));
-
- // 添加自訂樣式
- function addCustomStyles() {
- GM_addStyle(fontStyles);
- console.log("Custom styles added.");
- }
-
- // 自動調整高度的核心函式
- function autoHeight(el) {
- el.style.height = 'auto';
- el.style.overflow = 'auto';
- }
-
- function autoScrollHeight(el) {
- autoHeight(el);
- el.style.height = `${el.scrollHeight}px`;
- }
-
- // 初始化符合條件的元素
- function initializeAutoHeight() {
- if (!debouncedAutoHeight) {
- debouncedAutoHeight = debounce(() => {
- $(tarAutoHeight).forEach(autoHeight);
- $(tarAutoScrollHeight).forEach(autoScrollHeight);
- }, 666, 3);
- debouncedAutoHeight();
- window.addEventListener('keydown', debouncedAutoHeight);
- window.addEventListener('click', debouncedAutoHeight);
- }
- }
-
-
- // 替換指定選擇符的內容
- function replaceTextContent() {
- const processedAttribute = "data-text-replaced"; // 標記屬性名稱
- const el = $(`${textReplaceSelector}:not([${processedAttribute}])`);
- el.forEach((el) => {
- let originalText = el.textContent;
- for (const [pattern, replacement] of Object.entries(charMap)) {
- originalText = originalText.replace(new RegExp(pattern, 'g'), replacement);
- }
- el.textContent = originalText;
- el.setAttribute(processedAttribute, ""); // 添加標記屬性
- });
- }
-
- // 延遲觸發的去抖函式
- function debounce(func, delay, repeat) {
- let timer = null;
- let count = 1;
- return () => {
- func();
- if (timer) clearInterval(timer);
- timer = setInterval(() => {
- func();
- count += 1;
- if (count >= repeat) {
- clearInterval(timer);
- }
- }, delay);
- };
- }
-
- // 啟動 MutationObserver
- function initializeObserver() {
- const observer = new MutationObserver(() => {
- mainAction();
- });
- observer.observe(document.body, { childList: true, subtree: true });
- console.log("MutationObserver initialized.");
- }
-
- // 檢查 inLanguage 並啟動必要功能
- function checkInLanguage() {
- const script = $$('script[type="application/ld+json"]');
- if (script) {
- try {
- const jsonData = JSON.parse(script.textContent);
- inLanguage = jsonData[0]?.inLanguage || '';
- } catch (error) {
- console.error("Failed to parse JSON:", error);
- }
- }
- }
-
- function createSettingsUI() {
- const lang = ['zh-hant', 'zh-hans'].includes(inLanguage) ? 'zh-hant' : 'en';
- const texts = locale[lang];
-
- // 創建核取方塊
- const createCheckbox = (setting) => {
- const container = document.createElement('div');
- const checkbox = document.createElement('input');
- const label = document.createElement('label');
- const desc = document.createElement('div');
- desc.className = 'desc';
-
- checkbox.type = 'checkbox';
- const storageKey = `enable${setting.key.slice(3)}`;
- checkbox.id = storageKey;
-
- const isChecked = JSON.parse(localStorage.getItem(storageKey) || 'false');
- checkbox.checked = isChecked;
- label.setAttribute('for', storageKey);
- label.textContent = texts[setting.localeName][0];
- desc.innerHTML = texts[setting.localeName][1];
- label.appendChild(desc);
- checkbox.addEventListener('change', () => {
- localStorage.setItem(storageKey, checkbox.checked);
- });
- container.appendChild(checkbox);
- container.appendChild(label);
- return container;
- };
-
- // 創建按鈕和設定視窗
- const mt = document.createElement('div');
- mt.id = 'mt';
-
- const toggleButton = document.createElement('button');
- toggleButton.className = 'button--red mt_toggleButton';
- toggleButton.textContent = texts.toggleButton;
- if (lang === 'zh-hant') toggleButton.style.fontSize = '1rem';
-
- const settingsPanel = document.createElement('div');
- settingsPanel.className = 'mt_fixed mt_settingsPanel';
- settingsPanel.style.display = 'none';
-
- // 添加核取方塊
- settings.forEach((setting) => {
- settingsPanel.appendChild(createCheckbox(setting));
- });
-
- // 重整按鈕
- const reloadButton = document.createElement('button');
- reloadButton.textContent = texts.reloadButton;
- reloadButton.className = 'button--red';
- reloadButton.addEventListener('click', () => location.reload());
- settingsPanel.appendChild(reloadButton);
-
- // 切換視窗顯示
- toggleButton.addEventListener('click', () => {
- settingsPanel.style.display = settingsPanel.style.display === 'none' ? 'block' : 'none';
- });
-
- // 添加到頁面
- document.body.appendChild(mt);
- mt.appendChild(toggleButton);
- document.body.appendChild(settingsPanel);
-
- // 快捷按鈕
- if (switches.sw_shortButtons && mURL('*/talk/*')) {
- ['👤', '📝'].forEach((text, index) => {
- const button = document.createElement('button');
- button.textContent = text;
- button.addEventListener('click', () => {
- o_optionButton.click();
- o_editButton.click();
- (index === 0 ? o_editMyInfoButton : o_editUserNoteButton).click();
- });
- mt.appendChild(button);
- });
- }
- }
-
- function checkSettings() {
- checkInLanguage();
- settings.forEach(setting => {
- switches[setting.switchVar] = JSON.parse(localStorage.getItem(setting.storageKey) || 'false');
- });
-
- if (switches.sw_fontOverride) addCustomStyles();
- mainAction();
- }
-
- function mURL(pattern) {
- const patternParts = pattern.split('*');
- let lastIndex = 0;
- for (let part of patternParts) {
- if (part === "") continue;
- const index = cURL.indexOf(part, lastIndex);
- if (index === -1) return false;
- lastIndex = index + part.length;
- }
- return true;
- }
-
- function setStylesheet() {
- const link = document.createElement("link");
- link.rel = 'stylesheet';
- link.href = muyiStyles;
- document.head.appendChild(link);
- }
-
- function mainAction() {
- if (switches.sw_autoHeight && (mURL('*/created-characters/*') || mURL('*/prompt-build-script/*') || mURL('*/lorebook-editor/*'))) initializeAutoHeight();
- if (['zh-hant', 'zh-hans', 'ja', 'ko'].includes(inLanguage) && (switches.sw_replaceText)) replaceTextContent();
- if (mURL('*/talk/*')) {
- if (switches.sw_mdFix) o_imgButton.classList.add('mt_fix');
- if (switches.sw_deskFix) {
- $('.container, .items-center.text-lg.relative, div.flex.overflow-hidden.flex-grow>div.flex[class*="md:w-[40%]"], div.flex.overflow-hidden.flex-grow>div.flex[class*="md:w-[60%]"]').forEach(el => {
- el.classList.add('mt_fix');
- });
- }
- if (switches.sw_hideLLM) {
- $(`
- input#gpt-4-turbo, label[for="gpt-4-turbo"],
- input#claude-v2, label[for="claude-v2"],
- input#claude-v3-opus, label[for="claude-v3-opus"],
- input#claude-v3-sonnet, label[for="claude-v3-sonnet"],
- input[id="claude-v3.5-sonnet"], label[for="claude-v3.5-sonnet"],
- input#gpt-4-turbo, label[for="gpt-4-turbo"],
- input#gpt-4-turbo, label[for="gpt-4-turbo"]
- `).forEach(el => {
- el.style.display = 'none';
- });
- }
- }
- }
-
- window.addEventListener('load', () => {
- setStylesheet();
- checkSettings();
- createSettingsUI();
- setTimeout(initializeObserver, 100);
- });
- })();