Luogu Chat Better!

· · 科技·工程

前言

在洛谷私信发一大段消息的时候,由于无法自动聚焦输入框,经常要点击,极大地影响了我们水谷的速度,所以做了这个插件(

目前有自动聚焦输入框、更改自己的消息颜色和私信图片显示功能。

源码

将代码放到油猴里就行了。

整个脚本最前端,有一块代码:

const COLOR_CONFIG = {
    enableOwnMessageColor: true,// 是否启用自定义颜色
    ownMessageBgColor: "#d9f0ff",// 自己消息框的背景色
    ownMessageTextColor: "#000000" // 自己消息的文字颜色
};

你可以修改参数的值来实现不同的效果。

:::info[Luogu Chat Better!]{open}

// ==UserScript==
// @name         Luogu Chat Better!
// @namespace    http://tampermonkey.net/
// @version      3.1
// @description  洛谷私信 自动聚焦输入框 + 图片显示 + 颜色修改
// @author       Forever_Rain
// @icon         https://cdn.luogu.com.cn/upload/usericon/3.png
// @match        https://www.luogu.com.cn/chat*
// @match        https://www.luogu.com.cn/chat/*
// @grant        none
// ==/UserScript==

/* 用户自定义颜色配置 */
const COLOR_CONFIG = {
    enableOwnMessageColor: true,// 是否启用自定义颜色
    ownMessageBgColor: "#d9f0ff",// 自己消息框的背景色
    ownMessageTextColor: "#000000" // 自己消息的文字颜色
};
/* 用户自设部分 */

// 自动聚焦输入框
(function() {
    'use strict';
    const inputSelectors = [
        'textarea.lg-message-input',
        'textarea[name="content"]',
        '.chat-input textarea',
        '.message-editor textarea',
        'div[contenteditable="true"]',
        'textarea[placeholder*="私信"]',
        'textarea[placeholder*="消息"]',
        'textarea.lfe-form-sz-middle',
        '.input-wrap textarea',
        '#chat-input textarea'
    ];
    const sendButtonSelectors = [
        'button[type="submit"]',
        'button.send-btn',
        'button:has(span:contains("发送"))',
        '.send-button',
        '.chat-send-btn'
    ];
    function getInputElement() {
        for (const selector of inputSelectors) {
            const element = document.querySelector(selector);
            if (element && element.offsetParent !== null) { // 确保可见
                return element;
            }
        }
        const allTextareas = document.querySelectorAll('textarea');
        for (const ta of allTextareas) {
            if (ta.offsetParent !== null && (ta.placeholder?.includes('私信') || ta.placeholder?.includes('消息') || ta.closest('.chat-area'))) {
                return ta;
            }
        }
        const editableDivs = document.querySelectorAll('[contenteditable="true"]');
        for (const div of editableDivs) {
            if (div.offsetParent !== null && div.closest('.chat-area')) {
                return div;
            }
        }
        return null;
    }
    function focusInput(retries = 3) {// 重试:3次
        const input = getInputElement();
        if (input) {
            input.focus();
            if (input.isContentEditable) {
                const range = document.createRange();
                const sel = window.getSelection();
                range.selectNodeContents(input);
                range.collapse(false); // 折叠到末尾
                sel.removeAllRanges();
                sel.addRange(range);
            }
        } else if (retries > 0) {
            setTimeout(() => focusInput(retries - 1), 100);// 间隔0.1s
        }
    }
    function isSendButton(el) {
        if (!el) return false;
        const tag = el.tagName.toLowerCase();
        if (tag === 'button' || tag === 'input' && (el.type === 'submit' || el.type === 'button')) {
            const text = el.innerText || el.value || '';
            if (text.includes('发送') || text.includes('Send')) return true;
            if (el.classList && (el.classList.contains('send-btn') || el.classList.contains('chat-send-btn'))) return true;
            if (el.getAttribute('aria-label') === '发送') return true;
        }
        if (el.querySelector && el.querySelector('svg[data-icon="paper-plane"], svg[data-icon="send"]')) return true;
        return false;
    }
    function onDocumentClick(e) {
        let target = e.target;
        while (target && target !== document.body) {
            if (isSendButton(target)) {
                setTimeout(focusInput, 120);
                break;
            }
            target = target.parentElement;
        }
    }
    function onKeyDown(e) {
        // 检测 Ctrl+Enter
        if (e.ctrlKey && e.key === 'Enter') {
            const activeEl = document.activeElement;
            if (activeEl && (activeEl.tagName === 'TEXTAREA' || activeEl.isContentEditable)) {
                setTimeout(focusInput, 100);
            }
        }
    }
    document.addEventListener('click', onDocumentClick, true);
    document.addEventListener('keydown', onKeyDown);
    const observer = new MutationObserver((mutations) => {
        for (const mutation of mutations) {
            if (mutation.type === 'childList' && mutation.addedNodes.length) {
                const activeTag = document.activeElement?.tagName;
                if (activeTag !== 'TEXTAREA' && !document.activeElement?.isContentEditable) {
                    const input = getInputElement();
                    if (input && document.hasFocus()) {
                        setTimeout(() => {
                            if (document.activeElement !== input) {
                                input.focus();
                            }
                        }, 200);
                    }
                }
                break;
            }
        }
    });
    observer.observe(document.body, { childList: true, subtree: true });

    // 页面初始加载后,如果已有输入框且未聚焦,尝试聚焦一次(可选)
    window.addEventListener('load', () => {
        setTimeout(focusInput, 500);
    });
})();

// 图片显示
(function() {
    'use strict';
    // 要搜索的 class 列表
    const searchClasses = ['message'];
    function replacePics(element) {
        if (!element) return;
        const walker = document.createTreeWalker(element, NodeFilter.SHOW_ELEMENT, {
            acceptNode: function(node) {
                if (node.classList && searchClasses.some(className => node.classList == className)) {
                    return NodeFilter.FILTER_ACCEPT;
                }
                return NodeFilter.FILTER_SKIP;
            }
        }, false);

        let node;
        while (node = walker.nextNode()) {
            var a = node.innerHTML.indexOf("![");
            while(a != -1){
                var b = node.innerHTML.indexOf("](", a);
                if(b == -1) break;
                var c = node.innerHTML.indexOf(")", b);
                if(c == -1) break;
                var link = node.innerHTML.substr(b + 2, c - b - 2);
                var text = node.innerHTML.substr(a + 2, b - a - 2);
                if(text == '') text = link;
                var regex = node.innerHTML.substr(a, c - a + 1);
                node.innerHTML = node.innerHTML.replace(regex, `<img style = "max-width: 516px;" src="${link}" alt="${text}">`);
                a = node.innerHTML.indexOf("![")
            }
        }
    }
    replacePics(document.body);
    // 监听DOM
    const observer = new MutationObserver(mutations => {
        mutations.forEach(mutation => {
            if (mutation.type === 'childList') {
                mutation.addedNodes.forEach(node => {
                    if (node.nodeType === Node.ELEMENT_NODE) {
                        replacePics(node);
                    }
                });
            }
        });
    });
    observer.observe(document.body, { childList: true, subtree: true });
    (function() {
    'use strict';
    // 读取
    const enable = COLOR_CONFIG.enableOwnMessageColor;
    if (!enable) {
        return;
    }
    const bgColor = COLOR_CONFIG.ownMessageBgColor;
    const textColor = COLOR_CONFIG.ownMessageTextColor;
    // 校验
    const isValidColor = (color) => /^#[0-9A-Fa-f]{6}$/.test(color);
    const finalBg = isValidColor(bgColor) ? bgColor : "#d9f0ff";
    const finalText = isValidColor(textColor) ? textColor : "#000000";
    // 注入自定义样式
    const styleId = "luogu-own-message-color-style";
    if (!document.getElementById(styleId)) {
        const style = document.createElement("style");
        style.id = styleId;
        style.textContent = `
            /* 自己发送的消息气泡背景 & 文字颜色 */
            .message-block.right .message {
                background-color: ${finalBg} !important;
                color: ${finalText} !important;
            }
            /* 调整气泡圆角等保持美观,不覆盖原有设计 */
            .message-block.right .message {
                border-radius: 12px !important;
                padding: 6px 12px !important;
            }
        `;
        document.head.appendChild(style);
    }
})();
})();

:::

代码使用AI进行了可读性调整