koishi-plugin-batchkick
Version:
批量踢人插件,支持通过QQ群管理接口批量踢出群成员
264 lines (263 loc) • 12.6 kB
JavaScript
;
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)}`;
}
});
}