// ==UserScript== // @name 猫耳迎宾机器人 // @version 1.7.3 // @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'; // 显示提示信息 alert(`猫耳迎宾机器人已启动!\n查看具体功能请输入“帮助”访问网址查看。`); // 从当前网址获取房间号 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"; // 创建可隐藏的控制面板和按钮的容器 function createControlPanelContainer() { const container = document.createElement('div'); container.style.position = 'fixed'; container.style.top = '10px'; container.style.right = '10px'; container.style.zIndex = '10000'; container.style.cursor = 'move'; // 使容器可以拖动 const panel = document.createElement('div'); panel.style.backgroundColor = '#f0f0f0'; panel.style.border = '1px solid #ccc'; panel.style.padding = '10px'; panel.style.display = 'none'; // 默认隐藏 panel.style.position = 'relative'; // 使面板在容器内定位 panel.innerHTML = `
`; // 创建控制面板按钮 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); // 保存设置按钮点击事件 document.getElementById('saveSettingsBtn').addEventListener('click', () => { roomId = document.getElementById('roomIdInput').value; isWelcomingEnabled = document.getElementById('welcomeToggle').checked; isHistoryFetchingEnabled = document.getElementById('historyToggle').checked; alert("设置已保存!"); }); // 文件选择事件处理 document.getElementById('fileInput').addEventListener('change', (event) => { const file = event.target.files[0]; if (file) { const reader = new FileReader(); reader.onload = (e) => { const fileContent = e.target.result; console.log('文件内容:', fileContent); // 在这里处理文件内容 }; reader.readAsText(file); } }); // 切换控制面板显示 toggleButton.addEventListener('click', () => { if (panel.style.display === 'none') { panel.style.display = 'block'; toggleButton.textContent = ''; } else { panel.style.display = 'none'; toggleButton.textContent = ''; } }); // 添加拖动功能 makeDraggable(container); return container; } // 使容器可以拖动 function makeDraggable(element) { let isDragging = false; let offsetX, offsetY; element.addEventListener('mousedown', (e) => { isDragging = true; offsetX = e.clientX - parseInt(window.getComputedStyle(element).left, 10); offsetY = e.clientY - parseInt(window.getComputedStyle(element).top, 10); element.style.cursor = 'move'; }); window.addEventListener('mousemove', (e) => { if (isDragging) { element.style.left = `${e.clientX - offsetX}px`; element.style.top = `${e.clientY - offsetY}px`; } }); window.addEventListener('mouseup', () => { isDragging = false; element.style.cursor = 'default'; }); } // 创建并添加控制面板容器到页面 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())); // 过滤消息中的屏蔽词 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) { let filteredMessage = filterMessage(message, filterWords); const url = 'https://fm.missevan.com/api/chatroom/message/send'; const data = { room_id: roomId, message: filteredMessage, 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) }; try { const response = await fetch(url, options); const responseData = await response.json(); if (responseData.code === 0) { console.log('消息发送成功:', responseData); } else { console.error('发送失败:', responseData); } } catch (error) { console.error('请求错误:', error); } } // 每秒检测新用户加入 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#ChatBox'); 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 date = new Date().toISOString().slice(0, 19).replace('T', ' '); let pinyinUsername = pinyinPro.pinyin(username, { style: pinyinPro.STYLE_NORMAL }); let welcomeMessage = `| 欢迎${username}\n| 拼音: ${pinyinUsername}\n| 日期: ${date}`; sendMessage(welcomeMessage); console.log(`用户 ${username} (${pinyinUsername}) 已加入并发送欢迎消息`); } }); } } else { console.log('未找到 id 为 ChatBox 的 div'); } } else { console.log('未找到 标签'); } }, 1000); // 每秒检测一次 // 每秒获取一次历史消息并检查内容 setInterval(async function() { if (!isHistoryFetchingEnabled) return; const url = `https://fm.missevan.com/api/v2/chatroom/history/message?room_id=${roomId}`; try { const response = await fetch(url); const data = await response.json(); if (data.code === 0 && data.info && data.info.history) { const currentTimestamp = Date.now(); // 获取当前时间戳 data.info.history.forEach(async messageData => { const { msg_id, create_time, message, user } = messageData; const timeDiff = currentTimestamp - create_time; // 检查时间差,若在 1 秒内则处理 if (timeDiff <= 1000 && !processedMsgIds.has(msg_id)) { // 检查是否匹配到“我是谁”信息 if (message === "我是谁") { const userId = user.user_id; const username = user.username; const level = user.titles.find(title => title.type === 'level')?.level; console.log('匹配到“我是谁”信息:'); console.log('用户ID:', userId); console.log('用户名:', username); console.log('等级:', level); // 将用户ID、用户名和等级传递给fetchPageData函数 await fetchPageData(userId, username, level); } // 检查用户是否输入了 "直播间" 命令 if (message === "直播间") { console.log('用户请求直播间信息'); // 调用 zhibojian 函数并传递 roomId await zhibojian(roomId); } //点歌功能 if (message.startsWith("点歌 ")) { const songName = message.substring(3).trim(); requestSong('add', songName); } else if (message.startsWith("删歌 ")) { const songName = message.substring(3).trim(); requestSong('delete', songName); } else if (message === "歌单") { requestSong('show'); } // 打卡功能 if (message === "dd" || message === "打卡") { const username = user.username; const currentDate = new Date(); const checkInTime = currentDate.toLocaleString(); // 打卡时间 // 从打卡数据中获取用户信息 if (checkInData.has(username)) { const userData = checkInData.get(username); // 解析最后打卡时间 const lastCheckInDate = new Date(userData.lastCheckInTime); // 比较日期(只比较年月日) const lastCheckInDay = lastCheckInDate.toISOString().split('T')[0]; const currentDay = currentDate.toISOString().split('T')[0]; if (lastCheckInDay === currentDay) { // 用户今天已经打过卡 await sendMessage(`用户 ${username},今天已经打过卡了!`); } else { // 用户未打过卡,添加新打卡记录 userData.count += 1; userData.lastCheckInTime = checkInTime; checkInData.set(username, userData); // 构建打卡完成消息 let message = `打卡成功!\n\n| 用户名: ${username}\n| 打卡次数: ${userData.count}\n| 打卡时间: ${userData.lastCheckInTime}`; // 调用 sendMessage 函数发送消息 await sendMessage(message); // 保存打卡数据至 localStorage saveCheckInData(); } } else { // 用户不在打卡数据中,初始化打卡记录 checkInData.set(username, { count: 1, lastCheckInTime: checkInTime }); // 构建打卡完成消息 let message = `打卡成功!\n\n| 用户名: ${username}\n| 打卡次数: 1\n| 打卡时间: ${checkInTime}`; // 调用 sendMessage 函数发送消息 await sendMessage(message); // 保存打卡数据至 localStorage saveCheckInData(); } } // 查看所有打卡数据功能 if (message === "查看打卡数据") { if (checkInData.size > 0) { // 构建查看打卡数据的消息 let message = `所有用户的打卡数据:\n\n`; checkInData.forEach((userData, username) => { message += `| 用户名: ${username}\n| 打卡次数: ${userData.count}\n| 最后打卡时间: ${userData.lastCheckInTime}\n\n`; }); // 调用 sendMessage 函数发送所有打卡数据 await sendMessage(message); } else { // 如果没有任何打卡数据 await sendMessage(`没有任何打卡数据。`); } } //帮助 if (message === "帮助") { sendMessage(`请访问:https://greasyfork.org/zh-CN/scripts/505115-%E7%8C%AB%E8%80%B3%E8%BF%8E%E5%AE%BE%E6%9C%BA%E5%99%A8%E4%BA%BA/versions`); } processedMsgIds.add(msg_id); // 将处理过的消息 ID 添加到集合中 } }); } else { console.error('无法获取消息历史:', data); } } catch (error) { console.error('请求失败:', error); } }, 1000); // 每秒请求一次 // 修改fetchPageData函数以接受userId、username和level参数 async function fetchPageData(userId, username, level) { try { // 使用模板字符串动态插入 userId const url = `https://www.missevan.com/${userId}/`; console.log('Fetching URL:', url); const response = await fetch(url); const html = await response.text(); // 创建一个 DOMParser 来解析 HTML const parser = new DOMParser(); const doc = parser.parseFromString(html, 'text/html'); // 提取信息 const botText = doc.querySelector('#t_u_n_a')?.textContent.trim(); const followCount = doc.querySelector('.home-follow span')?.textContent.trim(); const fanCount = doc.querySelector('.home-fans span')?.textContent.trim(); // 提取注册日期,使用正则从 span 中提取日期部分 const timeSpan = doc.querySelector('.time')?.textContent.trim(); const dateMatch = timeSpan?.match(/注册于 (\d{4}-\d{2}-\d{2})/); const date = dateMatch ? dateMatch[1] : '未找到日期'; // 提取鱼干数量 const fishCount = doc.querySelector('.fish')?.textContent.trim(); // 格式化消息内容 let message = `|用户名: ${username}\n|等级: ${level}\n|个性签名: ${botText}\n|关注人数: ${followCount}\n|粉丝人数: ${fanCount}\n|鱼干: ${fishCount}`; // 调用 sendMessage 函数发送消息 await sendMessage(message); } catch (error) { console.error('网页抓取失败:', error); } } async function zhibojian(roomid) { const url = `https://fm.missevan.com/api/v2/live/${roomid}`; try { // 发送请求并获取响应 const response = await fetch(url); const data = await response.json(); // 提取房间信息 const room_info = data['info']['room']; const room_name = room_info['name']; const room_id = room_info['room_id']; const accumulation = room_info['statistics']['accumulation']; const online = room_info['statistics']['online']; const attention_count = room_info['statistics']['attention_count']; const vip_status = room_info['statistics']['vip']; const score = room_info['statistics']['score']; const open_time_timestamp = room_info['status']['open_time']; // 将时间戳转换为 Date 对象 const open_time = new Date(open_time_timestamp); // 获取当前时间 const current_time = new Date(); // 计算时间差 const time_diff = current_time - open_time; // 计算天、小时、分钟和秒数 const days = Math.floor(time_diff / (1000 * 60 * 60 * 24)); const hours = Math.floor((time_diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); const minutes = Math.floor((time_diff % (1000 * 60 * 60)) / (1000 * 60)); const seconds = Math.floor((time_diff % (1000 * 60)) / 1000); // 格式化开播时间和时长 const open_time_str = `${open_time.getFullYear()}-${(open_time.getMonth() + 1).toString().padStart(2, '0')}-${open_time.getDate().toString().padStart(2, '0')} ${open_time.getHours().toString().padStart(2, '0')}:${open_time.getMinutes().toString().padStart(2, '0')}`; const time_diff_str = `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`; // 发送消息 const message = `|房间名称: ${room_name}\n|房间ID: ${room_id}\n|在线人数: ${online}\n|累积人数: ${accumulation}\n|关注人数: ${attention_count}\n|贵族人数: ${vip_status}\n|热度: ${score}\n|开播时间: ${open_time_str}\n|开播时长: ${time_diff_str}`; // 调用 sendMessage 函数发送消息 await sendMessage(message); } catch (error) { console.error('Error fetching room data:', error); } } // 点歌功能实现 function requestSong(action, songName = null) { try { if (action === 'add' && songName) { // 添加歌曲 if (!songList.includes(songName)) { songList.push(songName); sendMessage(`已添加歌曲: ${songName}`); } else { sendMessage(`歌曲: ${songName} 已在列表中`); } } else if (action === 'delete' && songName) { // 删除歌曲 const index = songList.indexOf(songName); if (index !== -1) { songList.splice(index, 1); sendMessage(`已删除歌曲: ${songName}`); } else { sendMessage(`歌曲: ${songName} 不在列表中`); } } else if (action === 'show') { // 显示歌曲列表 if (songList.length > 0) { const songListStr = songList.join('\n'); sendMessage(`<歌曲列表>:\n${songListStr}`); } else { sendMessage("歌曲列表为空"); } } } catch (error) { console.error(`点歌功能发生异常: ${error}`); } } })();