Caveduck Modifier

修改Caveduck網站的樣式。

目前为 2024-11-30 提交的版本。查看 最新版本

// ==UserScript==
// @name			Caveduck Modifier
// @namespace		https://labs.muyi.tw/caveduck_modifier/
// @version			0.27.5
// @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;
	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 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_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);
			}, 200, 2);
			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 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_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 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_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 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');
				});
			}
		}
	}

	window.addEventListener('load', () => {
		setStylesheet();
		checkSettings();
		createSettingsUI();
		setTimeout(initializeObserver, 100);
	});
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址