logo 摸鱼论坛beta

【油猴脚本】三款表情。

image.png
image.png
image.png

// ==UserScript==
// @name         moyu Custom Sticker Partition (V11 Batch Gen)
// @namespace    http://tampermonkey.net/
// @version      11.0
// @description  Adds custom stickers to moyu.
// @author       _RyF
// @match        https://mylt.net/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // =================================================================
    // 0. 🛠️ 序列生成助手 (无需修改,供下方配置调用)
    // =================================================================
    /**
     * 自动生成数字序列表情包
     * @param {string} baseUrl - 图片路径前缀 (例如 "https://example.com/bili/")
     * @param {number} start   - 开始数字 (例如 1)
     * @param {number} end     - 结束数字 (例如 50)
     * @param {string} suffix  - 后缀 (默认 ".png")
     * @param {number} pad     - 数字补零位数 (例如 2 则生成 01, 02... 默认 0 不补零)
     */
    const generateSequence = (baseUrl, start, end, suffix = ".png", pad = 0) => {
        const list = [];
        for (let i = start; i <= end; i++) {
            const numStr = i.toString().padStart(pad, '0');
            list.push({
                name: `${numStr}`, // 图片名称,用于 alt 属性
                url: `${baseUrl}${numStr}${suffix}`
            });
        }
        return list;
    };

    // =================================================================
    // 1. 🔧 用户配置区
    // =================================================================
    const config = [
        {
            tabName: "Bilibili",
            stickers: [
                // ✅ 使用 generateSequence 批量生成 1.png 到 50.png
                // 参数:(链接前缀, 开始数字, 结束数字, 后缀)
                ...generateSequence("https://assets.rainymoe.net/emoji/bilibili/", 1, 50, ".png"),

                // 您依然可以在这里混合添加单个特殊的
                // "https://other-site.com/special.gif"
            ]
        },
        {
            tabName: "猫猫",
            stickers: [
                ...generateSequence("https://assets.rainymoe.net/emoji/miya/", 1, 20, ".png"),
            ]
        },
        {
            tabName: "鼠鼠",
            stickers: [
                ...generateSequence("https://assets.rainymoe.net/emoji/shushu/", 1, 20, ".png"),
            ]
        },
    ];

    // =================================================================
    // 2. 🛠️ 核心工具与状态
    // =================================================================
    const SELECTORS = {
        tabBar: '.expression',
        contentBox: '.exp-container',
        tabItem: '.exp-item',
        customClass: 'ns-custom-element'
    };

    const REGISTRY = {
        tabs: [],
        panels: [],
        nativePanel: null,
        vueId: null,
        activeCustomIndex: -1,
        observer: null
    };

    function log(msg) {
        console.log(`[moyu Sticker V11] ${msg}`);
    }

    const isMobile = () => {
        return ('ontouchstart' in window) || (navigator.maxTouchPoints > 0) || /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
    };

    // 插入逻辑 (V10 智能追加 + V9 移动端优化)
    function insertSticker(text) {
        let cm = document.querySelector('.CodeMirror')?.CodeMirror;
        if (cm) {
            const doc = cm.getDoc();
            let cursor = doc.getCursor();

            // 智能追加:如果光标在开头且未聚焦,则追加到文末
            if (cursor.line === 0 && cursor.ch === 0 && doc.getValue().length > 0 && !cm.hasFocus()) {
                const lastLineIndex = doc.lineCount() - 1;
                const lastLineContent = doc.getLine(lastLineIndex);
                cursor = { line: lastLineIndex, ch: lastLineContent.length };
            }

            doc.replaceRange(` ${text} `, cursor);

            const newPos = { line: cursor.line, ch: cursor.ch + text.length + 2 };

            if (!isMobile()) {
                cm.focus();
            }
            doc.setCursor(newPos);

        } else {
            const ta = document.querySelector('textarea');
            if (ta) {
                if (ta.selectionStart === 0 && ta.selectionEnd === 0 && ta.value.length > 0) {
                     ta.selectionStart = ta.selectionEnd = ta.value.length;
                }
                ta.setRangeText(` ${text} `, ta.selectionStart, ta.selectionEnd, 'end');
                if (!isMobile()) {
                    ta.focus();
                }
            }
        }
    }

    function normalizeSticker(item) {
        if (typeof item === 'object' && item.url) {
            return { url: item.url, code: `![${item.name || ''}](${item.url})` };
        } else if (typeof item === 'string') {
            const match = item.match(/!\[(.*?)\]\((.*?)\)/);
            return match ? { url: match[2], code: item } : { url: item, code: `![](${item})` };
        }
        return null;
    }

    function getVueScopeId(element) {
        if (!element) return null;
        return element.getAttributeNames().find(name => name.startsWith('data-v-'));
    }

    // =================================================================
    // 3. 🧱 DOM 构建
    // =================================================================

    function createCustomPanel(groupConfig, index) {
        const panel = document.createElement('div');
        panel.className = `exp-container ${SELECTORS.customClass}`;
        panel.style.display = 'none';

        if (REGISTRY.vueId) panel.setAttribute(REGISTRY.vueId, '');

        groupConfig.stickers.forEach(rawItem => {
            const item = normalizeSticker(rawItem);
            if (!item) return;

            const img = document.createElement('img');
            img.src = item.url;
            img.className = 'sticker';
            img.loading = "lazy"; // 懒加载:节省流量
            img.alt = "sticker";
            img.title = item.code;
            img.style.cssText = 'cursor: pointer; -webkit-tap-highlight-color: transparent;';

            if (REGISTRY.vueId) img.setAttribute(REGISTRY.vueId, '');

            img.onclick = (e) => {
                e.stopPropagation();
                e.preventDefault();
                insertSticker(item.code);
            };
            panel.appendChild(img);
        });

        return panel;
    }

    function createCustomTab(groupConfig, index, referenceTab) {
        const tab = referenceTab.cloneNode(true);
        tab.innerText = groupConfig.tabName;
        tab.classList.remove('current-group');
        tab.classList.add(SELECTORS.customClass);
        tab.removeAttribute('id');

        tab.onclick = (e) => {
            e.stopPropagation();
            handleTabClick(index);
        };

        return tab;
    }

    // =================================================================
    // 4. 🎮 交互逻辑
    // =================================================================

    function handleTabClick(index) {
        if (REGISTRY.activeCustomIndex === index) {
            collapseAll();
        } else {
            activateCustomTab(index);
        }
    }

    function activateCustomTab(index) {
        REGISTRY.activeCustomIndex = index;

        if (REGISTRY.nativePanel) {
            REGISTRY.nativePanel.style.display = 'none';
            REGISTRY.nativePanel.classList.remove('open');
        }

        REGISTRY.panels.forEach(p => {
            if (p.id === index) {
                p.dom.style.display = 'block';
                p.dom.classList.add('open');
            } else {
                p.dom.style.display = 'none';
                p.dom.classList.remove('open');
            }
        });

        updateTabVisuals(index);
    }

    function collapseAll() {
        REGISTRY.activeCustomIndex = -1;

        REGISTRY.panels.forEach(p => {
            p.dom.style.display = 'none';
            p.dom.classList.remove('open');
        });

        // 还原原生面板状态 (V8 修复)
        if (REGISTRY.nativePanel) {
            REGISTRY.nativePanel.style.display = '';
            REGISTRY.nativePanel.classList.remove('open');
        }

        updateTabVisuals(-1);
    }

    function switchToNative() {
        if (REGISTRY.activeCustomIndex === -1) return;

        REGISTRY.activeCustomIndex = -1;

        REGISTRY.panels.forEach(p => {
            p.dom.style.display = 'none';
            p.dom.classList.remove('open');
        });

        if (REGISTRY.nativePanel) {
            REGISTRY.nativePanel.style.display = '';
            REGISTRY.nativePanel.classList.add('open');
        }

        updateTabVisuals(-1);
    }

    function updateTabVisuals(activeIndex) {
        REGISTRY.tabs.forEach(t => {
            if (t.id === activeIndex) {
                t.dom.classList.add('current-group');
            } else {
                t.dom.classList.remove('current-group');
            }
        });

        const tabBar = document.querySelector(SELECTORS.tabBar);
        if (tabBar && activeIndex !== -1) {
            Array.from(tabBar.children).forEach(el => {
                if (!el.classList.contains(SELECTORS.customClass)) {
                    el.classList.remove('current-group');
                }
            });
        }
    }

    // =================================================================
    // 5. 🚀 注入与监控
    // =================================================================

    function inject() {
        const tabBar = document.querySelector(SELECTORS.tabBar);
        const nativeContentBox = document.querySelector(SELECTORS.contentBox);

        if (!tabBar || !nativeContentBox || tabBar.querySelector(`.${SELECTORS.customClass}`)) return;

        log('检测到表情面板,开始注入...');

        REGISTRY.tabs = [];
        REGISTRY.panels = [];
        REGISTRY.nativePanel = nativeContentBox;
        REGISTRY.vueId = getVueScopeId(nativeContentBox);
        REGISTRY.activeCustomIndex = -1;

        const referenceTab = tabBar.querySelector(SELECTORS.tabItem);
        if (!referenceTab) return;

        Array.from(tabBar.children).forEach(nativeTab => {
            nativeTab.addEventListener('click', () => switchToNative());
        });

        config.forEach((groupConfig, index) => {
            const panel = createCustomPanel(groupConfig, index);
            nativeContentBox.parentNode.insertBefore(panel, nativeContentBox.nextSibling);
            REGISTRY.panels.push({ dom: panel, id: index });

            const tab = createCustomTab(groupConfig, index, referenceTab);
            tabBar.appendChild(tab);
            REGISTRY.tabs.push({ dom: tab, id: index });
        });
    }

    const observer = new MutationObserver((mutations) => {
        const tabBar = document.querySelector(SELECTORS.tabBar);
        if (tabBar && !tabBar.querySelector(`.${SELECTORS.customClass}`)) {
            inject();
        }
        if (!tabBar) {
             REGISTRY.activeCustomIndex = -1;
        }
    });

    observer.observe(document.body, {
        childList: true,
        subtree: true
    });

})();
  • 可以可以
    1

  • 可以的,给个鱼干
    1

  • 除了有点乱,其它的都不错

你好啊,陌生人!

我的朋友,看起来你是新来的,如果想参与到讨论中,点击下面的按钮!

📈Statistics📈

注册会员: 193
主题: 251
回复: 1093

所有版块