// ==UserScript== // @name 猫耳迎宾机器人 // @version 1.7.6.6 // @description 一个简单的迎宾机器人 // @author 洋子 // @match https://fm.missevan.com/live/* // @icon https://static.maoercdn.com/avatars/202408/25/2bf1715cfc845d8b4da511d8f5345fec210801.jpg?x-oss-process=style/avatar // @grant none // @namespace https://greasyfork.org/users/1357490 // @require https://cdn.jsdelivr.net/npm/pinyin-pro@3.18.2/dist/index.js // @downloadURL none // ==/UserScript== (function() { 'use strict'; // 从当前网址获取房间号 let roomId = window.location.pathname.split("/").pop(); // 欢迎用户列表 let welcomedUsers = []; // 存储已处理的消息 ID let processedMsgIds = new Set(); // 全局变量,用于存储歌曲列表 let songList = []; let isWelcomingEnabled = true; let isHistoryFetchingEnabled = true; // 打卡数据文件 const checkInDataKey = "checkInData"; // DP 字符数据文件 const dpLibraryKey = "dpLibrary"; // 欢迎词数据文件 const welcomeMessagesKey = "welcomeMessages"; // 字符编号 let nextDpId = parseInt(localStorage.getItem('nextDpId') || '0', 10); // 创建可隐藏的控制面板和按钮的容器 function createControlPanelContainer() { const container = document.createElement('div'); container.style.position = 'fixed'; container.style.top = '10px'; container.style.left = '10px'; // 从左侧展开 container.style.zIndex = '10000'; const panel = document.createElement('div'); panel.style.backgroundColor = '#f0f0f0'; panel.style.border = '1px solid #ccc'; panel.style.width = '400px'; // 调整宽度 panel.style.height = '300px'; // 调整高度 panel.style.display = 'none'; // 默认隐藏 panel.style.position = 'relative'; // 使面板在容器内定位 panel.style.display = 'flex'; // 使用flex布局 // 左侧菜单 const menu = document.createElement('div'); menu.style.width = '100px'; // 左侧菜单的宽度 menu.style.borderRight = '1px solid #ccc'; menu.style.padding = '10px'; menu.style.display = 'flex'; menu.style.flexDirection = 'column'; // 创建左侧菜单项 const createMenuButton = (text) => { const button = document.createElement('button'); button.textContent = text; button.style.marginBottom = '5px'; button.style.backgroundColor = 'transparent'; // 隐藏按钮背景 button.style.border = 'none'; // 隐藏按钮边框 button.style.cursor = 'pointer'; // 设置为手型指针 return button; }; const indexTab = createMenuButton('主 页'); const basicSettingsTab = createMenuButton('基本设置'); const dpSettingsTab = createMenuButton('灯牌设置'); const welcomeSettingsTab = createMenuButton('欢迎设置'); // 将菜单项添加到菜单容器 menu.appendChild(document.createElement('hr')); // 创建横线 menu.appendChild(indexTab); menu.appendChild(document.createElement('hr')); // 创建横线 menu.appendChild(basicSettingsTab); menu.appendChild(document.createElement('hr')); // 创建横线 menu.appendChild(dpSettingsTab); menu.appendChild(document.createElement('hr')); // 创建横线 menu.appendChild(welcomeSettingsTab); menu.appendChild(document.createElement('hr')); // 创建横线 // 右侧内容区 const contentArea = document.createElement('div'); contentArea.style.flex = '1'; contentArea.style.padding = '10px'; contentArea.style.overflowY = 'auto'; // 允许滚动 const indexContent = `
本脚本无偿提供给所有猫耳FM主播使用,内容不定时更新。
作者:S洋子 | Mid:3368718
发布地址/历史更新:点击跳转
`; const basicSettingsContent = `多行示例:
欢迎 {username}
拼音是 {pinyin}
加入时间是 {date}
/n/
单行行示例:
嗨 {username},拼音:{pinyin},今天是 {date},欢迎加入!/n/
用法:
每段欢迎词以/n/符号隔开
{username}代表要欢迎的用户名,{pinyin}代表用户名的拼音,{date}代表加入直播间的日期
注:/n/分隔符号不可少 {username} {pinyin} {date}一定要切换成英文输入法输入,或者直接复制
看不明白的可以直接复制上面的示例
`; // 默认显示主页内容 contentArea.innerHTML = indexContent; const resetButtonStyles = () => { indexTab.style.fontWeight = 'normal'; basicSettingsTab.style.fontWeight = 'normal'; dpSettingsTab.style.fontWeight = 'normal'; welcomeSettingsTab.style.fontWeight = 'normal'; }; // 切换到主页 indexTab.addEventListener('click', () => { contentArea.innerHTML = indexContent; resetButtonStyles(); indexTab.style.fontWeight = 'bold'; }); // 切换到基本设置 basicSettingsTab.addEventListener('click', () => { contentArea.innerHTML = basicSettingsContent; resetButtonStyles(); basicSettingsTab.style.fontWeight = 'bold'; document.getElementById('saveSettingsBtn').addEventListener('click', () => { roomId = document.getElementById('roomIdInput').value; isWelcomingEnabled = document.getElementById('welcomeToggle').checked; isHistoryFetchingEnabled = document.getElementById('historyToggle').checked; alert("设置已保存!"); }); }); // 切换到 DP 设置 dpSettingsTab.addEventListener('click', () => { contentArea.innerHTML = dpSettingsContent; resetButtonStyles(); dpSettingsTab.style.fontWeight = 'bold'; document.getElementById('saveDpBtn').addEventListener('click', () => { const dpId = parseInt(document.getElementById('dpId').value.trim(), 10); const dpChar = document.getElementById('dpInput').value.trim(); const dpDesc = document.getElementById('dpDesc').value.trim(); if (dpId >= 0 && dpChar && dpDesc) { dpLibrary.set(dpId, { char: dpChar, desc: dpDesc }); saveDpLibrary(); alert("DP 字符已保存!"); document.getElementById('dpId').value = ''; document.getElementById('dpInput').value = ''; document.getElementById('dpDesc').value = ''; } else { alert("请填写正确的 DP 编号、字符和描述!"); } }); }); // 加载用户自定义欢迎词并显示在输入框中 const loadCustomWelcomeMessages = () => { const messages = localStorage.getItem('customWelcomeMessages'); return messages ? messages.split('/n/') : []; }; // 在欢迎设置选项卡中添加事件监听 welcomeSettingsTab.addEventListener('click', () => { contentArea.innerHTML = welcomeSettingsContent; resetButtonStyles(); welcomeSettingsTab.style.fontWeight = 'bold'; // 加载并显示欢迎词 const customMessages = loadCustomWelcomeMessages(); document.getElementById('welcomeMessagesInput').value = customMessages.join('/n/'); // 保存按钮事件 document.getElementById('saveWelcomeMessagesBtn').addEventListener('click', () => { let newMessages = document.getElementById('welcomeMessagesInput').value; // 去掉每条消息前后的空格或多余的换行符 let cleanedMessages = newMessages.split('/n/').map(message => message.trim()); // 将清理后的欢迎词重新组合并保存 localStorage.setItem('customWelcomeMessages', cleanedMessages.join('/n/')); alert("欢迎词已保存!"); }); }); panel.appendChild(menu); panel.appendChild(contentArea); // 创建控制面板按钮 const toggleButton = document.createElement('button'); toggleButton.style.background = 'url(https://static.maoercdn.com/avatars/202408/25/2bf1715cfc845d8b4da511d8f5345fec210801.jpg?x-oss-process=style/avatar) no-repeat center center'; toggleButton.style.backgroundSize = 'cover'; toggleButton.style.border = 'none'; toggleButton.style.width = '50px'; toggleButton.style.height = '50px'; toggleButton.style.cursor = 'pointer'; toggleButton.textContent = ''; // 默认显示文字,便于访问性 container.appendChild(toggleButton); container.appendChild(panel); document.body.appendChild(container); // 切换控制面板显示 toggleButton.addEventListener('click', () => { panel.style.display = panel.style.display === 'none' ? 'flex' : 'none'; }); return container; } // 创建并添加控制面板容器到页面 createControlPanelContainer(); // 从 localStorage 读取打卡数据 function loadCheckInData() { const data = localStorage.getItem(checkInDataKey); return data ? JSON.parse(data) : {}; } // 保存打卡数据到 localStorage function saveCheckInData() { localStorage.setItem(checkInDataKey, JSON.stringify(Object.fromEntries(checkInData))); } // 初始化打卡数据 const checkInData = new Map(Object.entries(loadCheckInData())); // 从 localStorage 读取 DP 字符库 function loadDpLibrary() { const data = localStorage.getItem(dpLibraryKey); const dpLibraryData = data ? JSON.parse(data) : {}; // 恢复 DP 字符库 return new Map(Object.entries(dpLibraryData).map(([id, { char, desc }]) => [parseInt(id, 10), { char, desc }])); } // 保存 DP 字符库到 localStorage function saveDpLibrary() { // 将 dpLibrary 转换为对象 const dpLibraryData = Object.fromEntries(dpLibrary); localStorage.setItem(dpLibraryKey, JSON.stringify(dpLibraryData)); } // 初始化 DP 字符库 let dpLibrary = loadDpLibrary(); // 从 localStorage 读取欢迎词库 function loadWelcomeMessages() { const data = localStorage.getItem(welcomeMessagesKey); const welcomeMessagesData = data ? JSON.parse(data) : {}; // 恢复欢迎词 return welcomeMessagesData.messages || []; } // 保存欢迎词库到 localStorage function saveWelcomeMessages(welcomeMessages) { const welcomeMessagesObj = { messages: welcomeMessages }; localStorage.setItem(welcomeMessagesKey, JSON.stringify(welcomeMessagesObj)); } // 初始化欢迎词库 let welcomeMessages = loadWelcomeMessages(); // 过滤消息中的屏蔽词 function filterMessage(message, filterWords) { filterWords.forEach(word => { let regex = new RegExp(word, 'g'); message = message.replace(regex, '***'); }); return message; } // 屏蔽词数组示例 let filterWords = ["不良词1", "不良词2"]; // 生成UUID v4 function generateUUID() { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); return v.toString(16); }); } // 封装的发送消息函数 async function sendMessage(message, retries = 3) { let filteredMessage = filterMessage(message, filterWords); const maxLength = 200 // 每段消息的最大字符数 const url = 'https://fm.missevan.com/api/chatroom/message/send'; // 将消息分段 let messageChunks = []; for (let i = 0; i < filteredMessage.length; i += maxLength) { messageChunks.push(filteredMessage.slice(i, i + maxLength)); } const sendChunk = async (chunk) => { const data = { room_id: roomId, message: chunk, uuid: generateUUID() }; const options = { method: 'POST', headers: { 'Content-Type': 'application/json', 'user-agent': navigator.userAgent, 'cookie': document.cookie, 'origin': 'missevan.com', 'referer': `https://fm.missevan.com/live/${roomId}` }, body: JSON.stringify(data) }; for (let attempt = 1; attempt <= retries; attempt++) { try { const response = await fetch(url, options); const responseData = await response.json(); if (responseData.code === 0) { console.log('消息发送成功:', responseData); return true; // 成功退出 } else { console.error('发送失败:', responseData); if (attempt === retries) { console.error('达到最大重试次数,消息未发送'); } } } catch (error) { console.error('请求错误:', error); if (attempt === retries) { console.error('达到最大重试次数,消息未发送'); } } } return false; // 发送失败 }; // 按顺序发送每个分段 for (let chunk of messageChunks) { let success = await sendChunk(chunk); if (!success) { console.error('部分消息发送失败,停止发送剩余分段'); break; // 如果某个分段发送失败,停止发送 } } } // 预存的欢迎消息 const defaultWelcomeMessages = [ '|欢迎{username}\n|[拼音]:{pinyin}\n|日期:{date}', '欢迎{username}在{date}加入直播间!\n拼音:{pinyin},' ]; // 从 localStorage 读取用户自定义欢迎词 function loadCustomWelcomeMessages() { const customMessages = localStorage.getItem('customWelcomeMessages'); return customMessages ? customMessages.split('/n/') : []; } // 生成欢迎消息 function getWelcomeMessage(username, pinyinUsername, formattedDate) { const customMessages = loadCustomWelcomeMessages(); const messagesToUse = customMessages.length > 0 ? customMessages : defaultWelcomeMessages; const randomWelcomeMessage = messagesToUse[Math.floor(Math.random() * messagesToUse.length)]; return randomWelcomeMessage.replace('{username}', username) .replace('{pinyin}', pinyinUsername) .replace('{date}', formattedDate); } // 每秒检测用户加入 setInterval(() => { if (!isWelcomingEnabled) return; const appElement = document.querySelector('app'); if (appElement) { const appContent = appElement.innerHTML; const parser = new DOMParser(); const doc = parser.parseFromString(appContent, 'text/html'); const chatboxDiv = doc.querySelector('div#Room'); if (chatboxDiv) { const joinDivs = chatboxDiv.querySelectorAll('div.join-queue-effect.show.clickable'); if (joinDivs.length > 0) { joinDivs.forEach((userDiv) => { let usernameDiv = userDiv.querySelector('.username'); let username = usernameDiv ? usernameDiv.textContent.trim() : ''; if (username && !welcomedUsers.includes(username)) { welcomedUsers.push(username); let currentDate = new Date(); let formattedDate = currentDate.toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false }); let pinyinUsername = pinyinPro.pinyin(username, { style: pinyinPro.STYLE_NORMAL }); // 获取欢迎消息 let welcomeMessage = getWelcomeMessage(username, pinyinUsername, formattedDate); sendMessage(welcomeMessage); console.log(`用户 ${username} (${pinyinUsername}) 已加入并发送欢迎消息`); } }); } } else { console.log('未找到 id 为 ChatBox 的 div'); } } else { console.log('未找到