diff --git a/feisay.js b/feisay.js new file mode 100644 index 0000000..2d3c3bc --- /dev/null +++ b/feisay.js @@ -0,0 +1,415 @@ +// ==UserScript== +// @name 斐说 +// @namespace http://tampermonkey.net/ +// @version 1.0 +// @description 斐说,支持 Ctrl+Enter 快捷键,可配置随机用户、末尾签名,自定义用户列表和情绪状态,并解决8字符困扰 +// @match https://linux.do/* +// @icon https://cdn.linux.do/uploads/default/original/3X/9/d/9dd49731091ce8656e94433a26a3ef36062b3994.png +// @grant GM_setValue +// @grant GM_getValue +// ==/UserScript== + +(function () { + 'use strict'; + const MIN_REQUIRED_CHARS = 8; + const ZERO_WIDTH_SPACE = '\u200B'; + + // 获取页面的 标签 + const themeColorMeta = document.querySelector('meta[name="theme-color"]'); + let themeColor = '#ffffff'; // 默认白色背景 + let invertedColor = '#000000'; // 默认黑色字体 + + if (themeColorMeta) { + themeColor = themeColorMeta.getAttribute('content'); + invertedColor = invertColor(themeColor); // 计算相反颜色 + } + + // 设置样式变量 + const style = document.createElement('style'); + style.textContent = ` + :root { + --panel-bg: ${themeColor}; + --panel-text: ${invertedColor}; + --panel-border: ${invertedColor}; + --button-bg: ${invertedColor}; + --button-text: ${themeColor}; + --button-hover-bg: ${invertColor(invertedColor)}; + }`; + document.head.appendChild(style); + + function invertColor(hex) { + // 去掉前面的“#”字符 + hex = hex.replace('#', ''); + + // 如果输入的是3位的hex值,转换为6位的 + if (hex.length === 3) { + hex = hex.split('').map(c => c + c).join(''); + } + + // 计算相反的颜色 + const r = (255 - parseInt(hex.slice(0, 2), 16)).toString(16).padStart(2, '0'); + const g = (255 - parseInt(hex.slice(2, 4), 16)).toString(16).padStart(2, '0'); + const b = (255 - parseInt(hex.slice(4, 6), 16)).toString(16).padStart(2, '0'); + + return `#${r}${g}${b}`; + } + + const defaultAnyoneSay = [ + { name: '慕思斐', username: 'Musifei', avatar: '/user_avatar/linux.do/musifei/96/8107_2.png', description: '「手持烟火以谋生 心怀诗意以谋爱」', isModerator: true, moderatorCategory: '' }, + { name: 'Neo', username: 'Neo', avatar: '/user_avatar/linux.do/neo/96/12_2.png', description: '岂曰无衣?', isModerator: true, moderatorCategory: '' }, + { name: 'King-Huiwen-of-Qin', username: 'King-Huiwen-of-Qin', avatar: '/user_avatar/linux.do/king-huiwen-of-qin/96/4672_2.png', description: '🍭 总会会长 🍭', isModerator: true, moderatorCategory: '' }, + { name: '神墨网络', username: 'SMNET', avatar: '/user_avatar/linux.do/smnet/96/95935_2.png', description: '🐾 护猫使者 🐾', isModerator: true, moderatorCategory: '' } + ]; + + const defaultEmotionalState = [ + '容光焕发', '无精打采', '精神抖擞', '奄奄一息', '气宇轩昂', + '疲惫不堪', '落落大方', '愁眉苦脸', '满面春风', '心力交瘁', + '风度翩翩', '萎靡不振', '温文尔雅', '怏怏不乐', '精神焕发', + '人见人爱,花见花开,车见车爆胎' + ]; + + let config = { + enableRandomUser: GM_getValue('enableRandomUser', true), + enableSignature: GM_getValue('enableSignature', true), + enableMinCharCompletion: GM_getValue('enableMinCharCompletion', true), + closeConfigAfterSave: GM_getValue('closeConfigAfterSave', true), + anyoneSay: updateOldConfig(GM_getValue('anyoneSay', defaultAnyoneSay)), + emotionalState: GM_getValue('emotionalState', defaultEmotionalState) + }; + + const randInt = (start, end) => { + return Math.floor(Math.random() * (end - start + 1)) + start; + }; + + const getEmotionalState = () => { + return config.emotionalState[randInt(0, config.emotionalState.length - 1)]; + }; + + function updateOldConfig(oldConfig) { + return oldConfig.map(user => ({ + ...user, + isModerator: user.isModerator || false, + moderatorCategory: user.moderatorCategory || '' + })); + } + + // 获取预加载数据 + function getPreloadedData() { + const preloadedDataElement = document.querySelector("#data-preloaded"); + if (!preloadedDataElement) { + throw new Error("Preloaded data element not found"); + } + const preloadedData = preloadedDataElement.getAttribute("data-preloaded"); + return JSON.parse(preloadedData); + }; + // 获取用户名 + function getUsername() { + const preloadedData = getPreloadedData(); + const preloadedCurrentUserData = JSON.parse(preloadedData.currentUser); + return preloadedCurrentUserData.username; + } + + function getCategoryNames() { + const categories = []; + + // 第一种方式 + const titleWrapper = document.getElementsByClassName('title-wrapper')[0]; + if (titleWrapper && titleWrapper.children[1]) { + for (let child of titleWrapper.children[1].children) { + if (child.children[0]) { + categories.push(child.children[0].innerText.trim()); + } + } + } + + // 第二种方式 + const topicCategory = document.getElementsByClassName('topic-category')[0]; + if (topicCategory) { + categories.push(...topicCategory.innerText.split('\n').map(cat => cat.trim())); + } + + return categories; + } + + function isSecretGarden() { + const categories = getCategoryNames(); + return categories.includes("秘密花园"); + } + + function isCategoryMatch(userCategory, pageCategories) { + if (!userCategory) return true; // 如果用户没有指定类别,则认为匹配 + return pageCategories.some(category => category === userCategory); + } + + function getRandomUser() { + const randomUser = config.anyoneSay[Math.floor(Math.random() * config.anyoneSay.length)]; + const pageCategories = getCategoryNames(); + const isModerator = randomUser.isModerator && isCategoryMatch(randomUser.moderatorCategory, pageCategories); + + let userHtml = ` `; + + // 添加名称,总是带颜色 + userHtml += isModerator ? `[color=#00aeff]**${randomUser.name}**[/color] ` : `${randomUser.name} `; + + // 如果是版主,添加版主图标 + if (isModerator) { + userHtml += `  `; + } + + // 如果用户名和名称不同,则添加用户名 + userHtml += randomUser.name !== randomUser.username ? `${randomUser.username}  ` : ''; + + // 添加描述和表情 + userHtml += `[color=#919191]${randomUser.description}[/color]  \n           `; + + return userHtml; + } + + function handleClick(event) { + if (event.target && event.target.closest('button.create')) { + processTextarea(); + } + } + + function handleKeydown(event) { + if (event.ctrlKey && event.key === 'Enter') { + processTextarea(); + } + } + + function processTextarea() { + if (!isSecretGarden()) { return; } + let textarea = document.querySelector('#reply-control textarea'); + let text = textarea.value.trim(); + let originalLength = text.length; + + if (text.length !== 0) { + if (config.enableRandomUser && !text.includes("eHs0oDRBVpTk7yaAQJ7zxxDnDrv.png")) { + text = getRandomUser() + text; + } + let signature = `${getUsername()}`; + if (config.enableSignature && !text.includes(signature)) { + text = `${text}\n\n---\n\n
— 来自${getEmotionalState()}的 ${signature}
`; + } + if (config.enableMinCharCompletion && originalLength < MIN_REQUIRED_CHARS) { + // 通过添加零宽字符来补足长度到最小要求 + while (text.length < MIN_REQUIRED_CHARS) { + text += ZERO_WIDTH_SPACE; + } + } + + textarea.value = text; + + // 创建并触发 input 事件 + const inputEvent = new Event('input', { + bubbles: true, + cancelable: true + }); + // 触发事件 + textarea.dispatchEvent(inputEvent); + } + } + + function createConfigPanel() { + const panel = document.createElement('div'); + panel.id = 'feisay-config-panel'; + panel.style.position = 'fixed'; + panel.style.top = '80px'; + panel.style.right = '360px'; + panel.style.padding = '20px'; + panel.style.border = `1px solid var(--panel-border)`; + panel.style.borderRadius = '8px'; + panel.style.zIndex = '10000'; + panel.style.width = '300px'; + panel.style.maxHeight = '80%'; + panel.style.overflowY = 'auto'; + panel.style.display = 'none'; // 默认隐藏面板 + panel.style.backgroundColor = 'var(--panel-bg)'; + panel.style.color = 'var(--panel-text)'; + panel.style.boxShadow = '0 4px 6px rgba(0, 0, 0, 0.1)'; + + const title = document.createElement('h3'); + title.textContent = '斐说配置'; + title.style.marginTop = '0'; + title.style.marginBottom = '15px'; + panel.appendChild(title); + + const createCheckbox = (id, text, checked) => { + const label = document.createElement('label'); + label.style.display = 'flex'; + label.style.alignItems = 'center'; + label.style.marginBottom = '10px'; + label.style.cursor = 'pointer'; + + const checkbox = document.createElement('input'); + checkbox.type = 'checkbox'; + checkbox.id = id; + checkbox.checked = checked; + checkbox.style.marginRight = '10px'; + checkbox.addEventListener('change', (e) => { + config[id] = e.target.checked; + GM_setValue(id, config[id]); + }); + + label.appendChild(checkbox); + label.appendChild(document.createTextNode(text)); + + return label; + }; + + panel.appendChild(createCheckbox('enableRandomUser', '启用随机用户头像和名称', config.enableRandomUser)); + panel.appendChild(createCheckbox('enableSignature', '启用末尾签名', config.enableSignature)); + panel.appendChild(createCheckbox('enableMinCharCompletion', '启用最小字符数补全', config.enableMinCharCompletion)); + panel.appendChild(createCheckbox('closeConfigAfterSave', '保存后自动关闭配置页面', config.closeConfigAfterSave)); + + const createTextArea = (id, value, placeholder) => { + const container = document.createElement('div'); + container.style.marginBottom = '15px'; + + const label = document.createElement('label'); + label.textContent = placeholder; + label.style.display = 'block'; + label.style.marginBottom = '5px'; + container.appendChild(label); + + const textarea = document.createElement('textarea'); + textarea.id = id; + textarea.value = JSON.stringify(value, null, 2); + textarea.rows = 5; + textarea.style.width = '100%'; + textarea.style.padding = '5px'; + textarea.style.border = '1px solid var(--panel-border)'; + textarea.style.borderRadius = '4px'; + textarea.style.backgroundColor = 'var(--panel-bg)'; + textarea.style.color = 'var(--panel-text)'; + container.appendChild(textarea); + + return container; + }; + + panel.appendChild(createTextArea('anyoneSay', config.anyoneSay, '自定义用户列表:')); + panel.appendChild(createTextArea('emotionalState', config.emotionalState, '自定义情绪状态:')); + + const buttonContainer = document.createElement('div'); + buttonContainer.style.display = 'flex'; + buttonContainer.style.justifyContent = 'space-between'; + + const createButton = (text, onClick, primary = false) => { + const button = document.createElement('button'); + button.textContent = text; + button.style.padding = '8px 16px'; + button.style.border = 'none'; + button.style.borderRadius = '4px'; + button.style.cursor = 'pointer'; + button.style.backgroundColor = primary ? '#0078d7' : '#f0f0f0'; + button.style.color = primary ? '#ffffff' : '#333333'; + button.addEventListener('click', onClick); + return button; + }; + + const saveButton = createButton('保存设置', () => { + try { + const newAnyoneSay = JSON.parse(document.getElementById('anyoneSay').value); + config.anyoneSay = updateOldConfig(newAnyoneSay); + config.emotionalState = JSON.parse(document.getElementById('emotionalState').value); + GM_setValue('anyoneSay', config.anyoneSay); + GM_setValue('emotionalState', config.emotionalState); + if (config.closeConfigAfterSave) { + panel.style.display = 'none'; + } + } catch (error) { + alert('保存失败,请检查输入格式是否正确'); + } + }, true); + + const closeButton = createButton('关闭', () => { + panel.style.display = 'none'; + }); + + buttonContainer.appendChild(saveButton); + buttonContainer.appendChild(closeButton); + panel.appendChild(buttonContainer); + + document.body.appendChild(panel); + return panel; + } + + function toggleConfigPanel() { + const panel = document.getElementById('feisay-config-panel') || createConfigPanel(); + panel.style.display = panel.style.display === 'none' ? 'block' : 'none'; + } + + function createConfigButton() { + const toolbar = document.querySelector('.d-editor-button-bar'); + if (!toolbar || document.querySelector('.feisay-config')) return; + + const configButton = document.createElement('button'); + configButton.className = 'btn btn-flat btn-icon no-text user-menu-tab active feisay-config'; + configButton.title = '斐说配置'; + configButton.innerHTML = ' '; + configButton.onclick = toggleConfigPanel; + + toolbar.appendChild(configButton); + } + + function watchReplyControl() { + const replyControl = document.getElementById('reply-control'); + if (!replyControl) return; + + const observer = new MutationObserver((mutations) => { + mutations.forEach((mutation) => { + if (mutation.type === 'attributes' && mutation.attributeName === 'class') { + if (replyControl.classList.contains('closed')) { + const panel = document.getElementById('feisay-config-panel'); + if (panel) { + panel.style.display = 'none'; + } + } else { + // 当 reply-control 重新打开时,尝试添加配置按钮 + setTimeout(() => { + if (!document.querySelector('.feisay-config')) { + createConfigButton(); + } + }, 500); // 给予一些时间让编辑器完全加载 + } + } + }); + }); + + observer.observe(replyControl, { attributes: true }); + } + + function watchForEditor() { + const observer = new MutationObserver((mutations) => { + mutations.forEach((mutation) => { + if (mutation.type === 'childList') { + const addedNodes = mutation.addedNodes; + for (let node of addedNodes) { + if (node.nodeType === Node.ELEMENT_NODE && node.classList.contains('d-editor')) { + if (!document.querySelector('.feisay-config')) { + createConfigButton(); + } + return; + } + } + } + }); + }); + + observer.observe(document.body, { childList: true, subtree: true }); + } + + function init() { + const container = document.getElementById("reply-control"); + container.addEventListener('click', handleClick, true); + document.addEventListener('keydown', handleKeydown, true); + if (!document.querySelector('.feisay-config')) { + createConfigButton(); + } + watchReplyControl(); + watchForEditor(); + } + + // 初始化 + setTimeout(() => init(), 1000); +})(); \ No newline at end of file