// ==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}`);
}
}
})();