UNPKG

koishi-plugin-batchkick

Version:

批量踢人插件,支持通过QQ群管理接口批量踢出群成员

264 lines (263 loc) 12.6 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Config = exports.name = void 0; exports.apply = apply; const koishi_1 = require("koishi"); const axios_1 = __importDefault(require("axios")); exports.name = 'batchkick'; exports.Config = koishi_1.Schema.object({}); function apply(ctx, config) { // 计算BKN的函数 function getBkn(skey) { let hash = 5381; for (let i = 0; i < skey.length; ++i) { hash += (hash << 5) + skey.charCodeAt(i); } return hash & 0x7fffffff; } // 从cookies中提取skey function extractSkey(cookies) { const match = cookies.match(/skey=([^;]+)/); return match ? match[1] : null; } ctx.command('批量踢人 <groupId:number> <userIds:text>', '批量踢出指定群聊的成员') .alias('batch-kick') .example('批量踢人 123456789 742235059,3140284655,2715937649') .action(async ({ session }, groupId, userIds) => { if (!groupId) { return '请提供群号'; } if (!userIds) { return '请提供要踢出的用户QQ号列表,用逗号分隔\n例如:批量踢人 123456789 742235059,3140284655,2715937649'; } if (!session?.bot) { return '无法获取机器人实例'; } try { // 获取qun.qq.com的凭证 const credentials = await session.bot.internal.getCredentials('qun.qq.com'); if (!credentials || !credentials.cookies) { return '无法获取qun.qq.com的凭证,请确保机器人已登录QQ'; } // 提取skey const skey = extractSkey(credentials.cookies); if (!skey) { return '无法从cookies中提取skey'; } // 计算bkn const bkn = getBkn(skey); // 处理用户ID列表 const userIdList = userIds.split(',').map(id => id.trim()).filter(id => id); if (userIdList.length === 0) { return '用户ID列表为空'; } // 获取群成员列表进行验证 let validUserIds = []; try { const groupMembers = await session.bot.getGuildMemberList(groupId.toString()); const memberIds = new Set(groupMembers.data.map(member => member.user?.id).filter(id => id)); // 过滤出在群内的用户ID validUserIds = userIdList.filter(userId => memberIds.has(userId)); const invalidUserIds = userIdList.filter(userId => !memberIds.has(userId)); ctx.logger.info('群成员验证结果:', { 总数: userIdList.length, 有效: validUserIds.length, 无效: invalidUserIds.length, 无效用户: invalidUserIds }); if (invalidUserIds.length > 0) { session.send(`⚠️ 以下用户不在群内,将被跳过:${invalidUserIds.join(', ')}`); } if (validUserIds.length === 0) { return '❌ 没有找到任何在群内的有效用户'; } session.send(`✅ 验证完成,将踢出 ${validUserIds.length} 个用户`); } catch (memberError) { ctx.logger.warn('获取群成员列表失败,跳过验证:', memberError); session.send('⚠️ 无法获取群成员列表,跳过验证步骤'); validUserIds = userIdList; } // 构建ul参数 - 使用管道符分隔用户ID const ulParam = validUserIds.join('|'); // 构建请求参数 - 使用管道符让URLSearchParams自动编码为%7C const postData = new URLSearchParams({ gc: groupId.toString(), ul: ulParam, flag: '0', bkn: bkn.toString() }); // 记录关键信息到日志 ctx.logger.info('批量踢人请求:', { 群号: groupId, 用户数量: validUserIds.length, bkn: bkn, 负载: postData.toString() }); // 发送踢人请求 const response = await axios_1.default.post('https://qun.qq.com/cgi-bin/qun_mgr/delete_group_member', postData.toString(), { headers: { 'Accept': 'application/json, text/javascript, */*; q=0.01', 'Accept-Encoding': 'gzip, deflate, br, zstd', 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive', 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', 'Cookie': credentials.cookies, 'Host': 'qun.qq.com', 'Origin': 'https://qun.qq.com', 'Pragma': 'no-cache', 'Referer': 'https://qun.qq.com/member.html', 'Sec-Fetch-Dest': 'empty', 'Sec-Fetch-Mode': 'cors', 'Sec-Fetch-Site': 'same-origin', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 Edg/138.0.0.0', 'X-Requested-With': 'XMLHttpRequest', 'sec-ch-ua': '"Not)A;Brand";v="8", "Chromium";v="138", "Microsoft Edge";v="138"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': '"Windows"' }, timeout: 10000 }); // 处理响应 ctx.logger.info('批量踢人响应:', response.data); if (response.status === 200) { const result = response.data; return [ `✅ 批量踢人操作已执行`, `群号:${groupId}`, `目标用户:${validUserIds.join(', ')}`, `响应状态:${response.status}`, `响应数据:${JSON.stringify(result)}` ].join('\n'); } else { return `❌ 批量踢人失败,HTTP状态码:${response.status}`; } } catch (error) { ctx.logger.error('批量踢人失败:', error); if (error && typeof error === 'object' && 'response' in error) { const axiosError = error; ctx.logger.error('批量踢人请求失败:', axiosError.response?.data); return `❌ 批量踢人失败:${axiosError.response?.status} ${axiosError.response?.statusText || axiosError.message}`; } return `❌ 批量踢人失败:${error instanceof Error ? error.message : String(error)}`; } }); ctx.command('通知 <groupId:number> [...args:text]', '在指定群聊中发送通知并批量艾特用户') .alias('notify') .example('通知 123456789 请注意查看群公告 742235059,3140284655,2715937649') .action(async ({ session }, groupId, ...args) => { if (!groupId) { return '请提供群号'; } if (!args || args.length === 0) { return '请提供通知内容和用户ID列表\n例如:通知 123456789 请注意查看群公告 742235059,3140284655,2715937649'; } // 将所有参数合并为一个字符串,然后分离消息和用户ID const allArgs = args.join(' '); // 查找最后一个包含逗号的部分作为用户ID列表 const lastCommaIndex = allArgs.lastIndexOf(','); if (lastCommaIndex === -1) { return '请提供要艾特的用户QQ号列表,用逗号分隔\n例如:通知 123456789 请注意查看群公告 742235059,3140284655,2715937649'; } // 从最后一个逗号向前查找,找到用户ID列表的开始位置 let userIdsStart = lastCommaIndex; while (userIdsStart > 0 && !/\s/.test(allArgs[userIdsStart - 1])) { userIdsStart--; } // 继续向前查找,跳过数字和逗号,找到消息内容的结束位置 while (userIdsStart > 0 && /[\d,]/.test(allArgs[userIdsStart - 1])) { userIdsStart--; } if (userIdsStart === 0) { return '无法解析消息内容和用户ID列表,请检查格式\n例如:通知 123456789 请注意查看群公告 742235059,3140284655,2715937649'; } const message = allArgs.substring(0, userIdsStart).trim(); const userIds = allArgs.substring(userIdsStart).trim(); if (!message) { return '请提供通知内容'; } if (!userIds) { return '请提供要艾特的用户QQ号列表,用逗号分隔\n例如:通知 123456789 请注意查看群公告 742235059,3140284655,2715937649'; } if (!session?.bot) { return '无法获取机器人实例'; } try { // 处理用户ID列表 const userIdList = userIds.split(',').map(id => id.trim()).filter(id => id); if (userIdList.length === 0) { return '用户ID列表为空'; } // 构建消息内容:通知内容 + 换行 + 艾特所有用户 const atElements = userIdList.map(userId => koishi_1.h.at(userId)); const messageContent = [ message, ...atElements ]; // 发送消息到指定群聊 try { await session.bot.sendMessage(`${groupId}`, messageContent); return [ `批量通知已发送`, `群号:${groupId}`, `通知内容:${message}`, `艾特用户:${userIdList.join(', ')}`, `艾特用户数:${userIdList.length}` ].join('\n'); } catch (sendError) { ctx.logger.error('发送消息失败:', sendError); // 检查是否是权限问题 if (sendError && typeof sendError === 'object' && 'message' in sendError) { const errorMsg = sendError.message; if (errorMsg.includes('1200')) { return `发送失败:机器人可能没有权限发送消息到群 ${groupId},或者群号不存在`; } if (errorMsg.includes('retcode')) { return `发送失败:OneBot API错误 - ${errorMsg}`; } } return `发送批量通知失败:${sendError instanceof Error ? sendError.message : String(sendError)}`; } } catch (error) { ctx.logger.error('批量通知处理失败:', error); return `批量通知处理失败:${error instanceof Error ? error.message : String(error)}`; } }); ctx.command('测试通知 <groupId:number>', '测试是否能向指定群聊发送消息') .alias('test-notify') .example('测试通知 123456789') .action(async ({ session }, groupId) => { if (!groupId) { return '请提供群号'; } if (!session?.bot) { return '无法获取机器人实例'; } try { // 发送简单的测试消息 await session.bot.sendMessage(`${groupId}`, '这是一条测试消息'); return `测试消息已成功发送到群 ${groupId}`; } catch (error) { ctx.logger.error('测试发送失败:', error); if (error && typeof error === 'object' && 'message' in error) { const errorMsg = error.message; if (errorMsg.includes('1200')) { return `测试失败:机器人可能没有权限发送消息到群 ${groupId},或者群号不存在`; } if (errorMsg.includes('retcode')) { return `测试失败:OneBot API错误 - ${errorMsg}`; } } return `测试发送失败:${error instanceof Error ? error.message : String(error)}`; } }); }