// ==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); })();