

// ==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: `` };
} else if (typeof item === 'string') {
const match = item.match(/!\[(.*?)\]\((.*?)\)/);
return match ? { url: match[2], code: item } : { url: item, code: `` };
}
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
});
})(); -
可以可以
-
可以的,给个鱼干
-
除了有点乱,其它的都不错