Files
self-scripts/feisay.js
2024-08-16 16:50:34 +08:00

415 lines
17 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// ==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';
// 获取页面的 <meta name="theme-color"> 标签
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: '🍭 <LINUX DO> 总会会长 🍭', 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 = `<img src="${randomUser.avatar}" alt="" width="36" height="36" class="avatar" />&nbsp;`;
// 添加名称,总是带颜色
userHtml += isModerator ? `[color=#00aeff]**${randomUser.name}**[/color]&nbsp;` : `${randomUser.name}&nbsp;`;
// 如果是版主,添加版主图标
if (isModerator) {
userHtml += `<img width="11" height="11" src="upload://dYktfGjcK2IRp3Sg7kj7wnG7vpm.png" class=".topic-meta-data .user-status-message img.emoji">&nbsp;&nbsp;`;
}
// 如果用户名和名称不同,则添加用户名
userHtml += randomUser.name !== randomUser.username ? `${randomUser.username}&nbsp;&nbsp;` : '';
// 添加描述和表情
userHtml += `[color=#919191]${randomUser.description}[/color]&nbsp;&nbsp;<img width="14" height="14" src="upload://eHs0oDRBVpTk7yaAQJ7zxxDnDrv.png" class=".topic-meta-data .user-status-message img.emoji">\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`;
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 = `<strong>${getUsername()}</strong>`;
if (config.enableSignature && !text.includes(signature)) {
text = `${text}\n\n---\n\n<div style="text-align:right">— 来自${getEmotionalState()}${signature}</div>`;
}
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 = ' <svg class="fa d-icon d-icon-discourse-other-tab svg-icon svg-string" xmlns="http://www.w3.org/2000/svg"><use href="#discourse-other-tab"></use></svg>';
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);
})();