UNPKG

koishi-plugin-bilibili-notify

Version:
1,184 lines (1,183 loc) 89.1 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const jsx_runtime_1 = require("@satorijs/element/jsx-runtime"); /* eslint-disable @typescript-eslint/ban-types */ /* eslint-disable @typescript-eslint/no-namespace */ /* eslint-disable @typescript-eslint/no-explicit-any */ const koishi_1 = require("koishi"); // 导入qrcode const qrcode_1 = __importDefault(require("qrcode")); var LiveType; (function (LiveType) { LiveType[LiveType["NotLiveBroadcast"] = 0] = "NotLiveBroadcast"; LiveType[LiveType["StartBroadcasting"] = 1] = "StartBroadcasting"; LiveType[LiveType["LiveBroadcast"] = 2] = "LiveBroadcast"; LiveType[LiveType["StopBroadcast"] = 3] = "StopBroadcast"; })(LiveType || (LiveType = {})); class ComRegister { static inject = ['ba', 'gi', 'database', 'sm']; qqRelatedBotList = ['qq', 'onebot', 'red', 'satori', 'chronocat']; logger; config; loginTimer; num = 0; rebootCount = 0; subNotifier; subManager = []; // 检查登录数据库是否有数据 loginDBData; // 机器人实例 privateBot; // 动态销毁函数 dynamicDispose; // 发送消息方式 sendMsgFunc; // 构造函数 constructor(ctx, config) { // 初始化 this.init(ctx, config); // 注册指令 const statusCom = ctx.command('status', '插件状态相关指令', { permissions: ['authority:5'] }); statusCom.subcommand('.dyn', '查看动态监测运行状态') .usage('查看动态监测运行状态') .example('status dyn') .action(() => { if (this.dynamicDispose) { return '动态监测正在运行'; } else { return '动态监测未运行'; } }); statusCom.subcommand('.sm', '查看订阅管理对象') .usage('查看订阅管理对象') .example('status sm') .action(async () => { this.logger.info(this.subManager); return '查看控制台'; }); statusCom .subcommand('.bot', '查询当前拥有的机器人信息', { hidden: true }) .usage('查询当前拥有的机器人信息') .example('status bot 查询当前拥有的机器人信息') .action(() => { this.logger.info('开始输出BOT信息'); ctx.bots.forEach(bot => { this.logger.info('--------------------------------'); this.logger.info('平台:' + bot.platform); this.logger.info('名称:' + bot.user.name); this.logger.info('--------------------------------'); }); }); statusCom .subcommand('.env', '查询当前环境的信息', { hidden: true }) .usage('查询当前环境的信息') .example('status env 查询当前环境的信息') .action(async ({ session }) => { await session.send(`Guild ID:${session.event.guild.id}`); await session.send(`Channel ID: ${session.event.channel.id}`); }); const biliCom = ctx.command('bili', 'bili-notify插件相关指令', { permissions: ['authority:3'] }); biliCom.subcommand('.login', '登录B站之后才可以进行之后的操作') .usage('使用二维码登录,登录B站之后才可以进行之后的操作') .example('bili login') .action(async ({ session }) => { this.logger.info('调用bili login指令'); // 获取二维码 let content; try { content = await ctx.ba.getLoginQRCode(); } catch (e) { return 'bili login getLoginQRCode() 本次网络请求失败'; } // 判断是否出问题 if (content.code !== 0) return await session.send('出问题咯,请联系管理员解决'); // 生成二维码 qrcode_1.default.toBuffer(content.data.url, { errorCorrectionLevel: 'H', // 错误更正水平 type: 'png', // 输出类型 margin: 1, // 边距大小 color: { dark: '#000000', // 二维码颜色 light: '#FFFFFF' // 背景颜色 } }, async (err, buffer) => { if (err) return await session.send('二维码生成出错,请重新尝试'); await session.send(koishi_1.h.image(buffer, 'image/png')); }); // 检查之前是否存在登录定时器 if (this.loginTimer) this.loginTimer(); // 设置flag let flag = true; // 发起登录请求检查登录状态 this.loginTimer = ctx.setInterval(async () => { try { // 判断上一个循环是否完成 if (!flag) return; flag = false; // 获取登录信息 let loginContent; try { loginContent = await ctx.ba.getLoginStatus(content.data.qrcode_key); } catch (e) { this.logger.error(e); return; } if (loginContent.code !== 0) { this.loginTimer(); return await session.send('登录失败请重试'); } if (loginContent.data.code === 86038) { this.loginTimer(); return await session.send('二维码已失效,请重新登录'); } if (loginContent.data.code === 0) { // 登录成功 const encryptedCookies = ctx.ba.encrypt(ctx.ba.getCookies()); const encryptedRefreshToken = ctx.ba.encrypt(loginContent.data.refresh_token); await ctx.database.upsert('loginBili', [{ id: 1, bili_cookies: encryptedCookies, bili_refresh_token: encryptedRefreshToken }]); // 销毁定时器 this.loginTimer(); // 订阅之前的订阅 await this.loadSubFromDatabase(ctx); // 清除控制台通知 ctx.ba.disposeNotifier(); // 发送成功登录推送 await session.send('登录成功'); // bili show await session.execute('bili show'); // 开启cookies刷新检测 ctx.ba.enableRefreshCookiesDetect(); return; } } finally { flag = true; } }, 1000); }); biliCom .subcommand('.unsub <uid:string>', '取消订阅UP主动态、直播或全部') .usage('取消订阅,加-l为取消直播订阅,加-d为取消动态订阅,什么都不加则为全部取消') .option('live', '-l') .option('dynamic', '-d') .example('bili unsub 用户UID -ld') .action(async ({ session, options }, uid) => { this.logger.info('调用bili.unsub指令'); // 若用户UID为空则直接返回 if (!uid) return '用户UID不能为空'; // -d -l两个选项不能同时存在 if (options.dynamic && options.live) return '需要取消订阅该UP主请直接使用指令bili unsub 用户UID'; // 定义是否存在 let exist; await Promise.all(this.subManager.map(async (sub, i) => { if (sub.uid === uid) { // 取消单个订阅 if (options.live || options.dynamic) { if (options.live) await session.send(this.unsubSingle(ctx, sub.roomId, 0)); /* 0为取消订阅Live */ if (options.dynamic) await session.send(this.unsubSingle(ctx, sub.uid, 1)); /* 1为取消订阅Dynamic */ // 将存在flag设置为true exist = true; // 结束循环 return; } // 取消全部订阅 执行dispose方法,销毁定时器 if (sub.live) this.subManager[i].liveDispose(); // 从数据库中删除订阅 await ctx.database.remove('bilibili', { uid: this.subManager[i].uid }); // 将该订阅对象从订阅管理对象中移除 this.subManager.splice(i, 1); // 将订阅对象移出订阅关注组 const removeUserFromGroupData = await ctx.ba.removeUserFromGroup(sub.uid); // 判断是否移出成功 22105关注对象为自己 if (removeUserFromGroupData.code !== 0 && removeUserFromGroupData.code !== 22105) { // 移出失败 await session.send('取消订阅对象失败,请稍后重试'); // 将存在flag设置为true exist = true; // 结束循环 return; } // id-- this.num--; // 判断是否还有动态订阅 this.checkIfUserIsTheLastOneWhoSubDyn(); // 发送成功通知 await session.send('已取消订阅该用户'); // 更新控制台提示 this.updateSubNotifier(ctx); // 将存在flag设置为true exist = true; } })); // 未订阅该用户,无需取消订阅 if (!exist) await session.send('未订阅该用户,无需取消订阅'); }); biliCom .subcommand('.show', '展示订阅对象') .usage('展示订阅对象') .example('bili show') .action(() => { const subTable = this.subShow(); return subTable; }); biliCom .subcommand('.sub <mid:string> [...groupId:string]', '订阅用户动态和直播通知') .option('multiplatform', '-m <value:string>', { type: /^(?:-?[A-Za-z0-9]+@?(?:,-?[A-Za-z0-9]+@?)*\.[A-Za-z0-9]+)(?:;(?:-?[A-Za-z0-9]+@?(?:,-?[A-Za-z0-9]+@?)*\.[A-Za-z0-9]+))*$/ }) .option('live', '-l') .option('dynamic', '-d') .option('atAll', '-a') .usage('订阅用户动态和直播通知,若需要订阅直播请加上-l,需要订阅动态则加上-d') .example('bili sub 1194210119 目标群号或频道号 -l -d 订阅UID为1194210119的UP主的动态和直播') .action(async ({ session, options }, mid, ...groupId) => { this.logger.info('调用bili.sub指令'); // 先判断是否订阅直播,再判断是否解锁订阅限制,最后判断直播订阅是否已超三个 if (options.live && !this.config.unlockSubLimits && (this.subManager.reduce((acc, cur) => acc + (cur.live ? 1 : 0), 0) >= 3)) { return '直播订阅已达上限,请取消部分直播订阅后再进行订阅'; } // 检查是否登录 if (!(await this.checkIfIsLogin(ctx))) { // 未登录直接返回 return '请使用指令bili login登录后再进行订阅操作'; } // 检查必选参数是否已填 if (!mid) return '请输入用户uid'; // 订阅对象 const subUserData = await this.subUserInBili(ctx, mid); // 判断是否订阅对象存在 if (!subUserData.flag) return '订阅对象失败,请稍后重试!'; // 定义目标变量 let target = []; // 判断是否使用了多群组推送 if (groupId.length > 0) { // 定义channelIdArr const channelIdArr = []; // 遍历输入的群组 groupId.forEach(group => { channelIdArr.push({ channelId: group, dynamic: true, live: true, atAll: options.atAll }); }); target.push({ channelIdArr, platform: session.event.platform }); } else { // 判断是否使用多平台功能 if (options.multiplatform) { // 分割字符串,赋值给target target = this.splitMultiPlatformStr(options.multiplatform); } // 判断是否使用了多平台 if (target.length > 0) { for (const [index, { channelIdArr, platform }] of target.entries()) { if (channelIdArr.length > 0) { // 输入了推送群号或频道号 // 拿到对应的bot const bot = this.getBot(ctx, platform); // 判断是否配置了对应平台的机器人 if (!ctx.bots.some(bot => bot.platform === platform)) { // 发送提示消息 await session.send('您未配置对应平台的机器人,不能在该平台进行订阅操作'); // 直接返回 return; } // 判断是否需要加入的群全部推送 if (channelIdArr[0].channelId !== 'all') { // 定义满足条件的群组数组 const targetArr = []; // 获取机器人加入的群组 const guildList = await bot.getGuildList(); // 遍历target数组 for (const channelId of channelIdArr) { // 定义是否加入群组标志 let flag = false; // 遍历群组 for (const guild of guildList.data) { // 获取频道列表 const channelList = await bot.getChannelList(guild.id); // 判断机器人是否加入群聊或频道 if (channelList.data.some(channel => channel.id === channelId.channelId)) { // 加入群聊或频道 targetArr.push(channelId); // 设置标志位为true flag = true; // 结束循环 break; } } if (!flag) { // 不满足条件发送错误提示 await session.send(`您的机器未加入${channelId.channelId},无法对该群或频道进行推送`); } } // 判断targetArr是否为空 if (target.length === 0) { // 为空则默认为当前环境 target = [{ channelIdArr: [{ channelId: session.event.channel.id, dynamic: true, live: true, atAll: options.atAll ? options.atAll : false }], platform: session.event.platform }]; // 没有满足条件的群组或频道 await session.send('没有满足条件的群组或频道,默认订阅到当前聊天环境'); } // 将符合条件的群组添加到target中 target[index].channelIdArr = targetArr; } // 如果为all则全部推送,不需要进行处理 } else { // 未填写群号或频道号,默认为当前环境 target = [{ channelIdArr: [{ channelId: session.event.channel.id, dynamic: true, live: true, atAll: options.atAll ? options.atAll : false }], platform: session.event.platform }]; // 发送提示消息 await session.send('没有填写群号或频道号,默认订阅到当前聊天环境'); } } } else { // 用户直接订阅,将当前环境赋值给target target = [{ channelIdArr: [{ channelId: session.event.channel.id, dynamic: true, live: true, atAll: options.atAll ? options.atAll : false }], platform: session.event.platform }]; } } // 定义外围变量 let content; try { // 获取用户信息 content = await ctx.ba.getUserInfo(mid); } catch (e) { // 返回错误信息 return 'bili sub getUserInfo() 发生了错误,错误为:' + e.message; } // 判断是否成功获取用户信息 if (content.code !== 0) { // 定义错误消息 let msg; // 判断错误代码 switch (content.code) { case -400: msg = '请求错误'; break; case -403: msg = '访问权限不足,请尝试重新登录'; break; case -404: msg = '用户不存在'; break; case -352: msg = '风控校验失败,请尝试更换UA'; break; default: msg = '未知错误,错误信息:' + content.message; break; } // 返回错误信息 return msg; } // 获取data const { data } = content; // 判断是否需要订阅直播和动态 const [liveMsg, dynamicMsg] = await this.checkIfNeedSub(options.live, options.dynamic, session, data.live_room); // 判断是否未订阅任何消息 if (!liveMsg && !dynamicMsg) return '您未订阅该UP的任何消息'; // 获取到对应的订阅对象 const subUser = this.subManager.find(sub => sub.uid === mid); // 判断要订阅的用户是否已经存在于订阅管理对象中 if (subUser) { // 已存在,判断是否重复订阅直播通知 if (liveMsg && subUser.live) { return '已订阅该用户直播通知,请勿重复订阅'; } // 已存在,判断是否重复订阅动态通知 if (dynamicMsg && subUser.dynamic) { return '已订阅该用户动态通知,请勿重复订阅'; } } // 获取直播房间号 const roomId = data.live_room?.roomid.toString(); // 获取用户信息 let userData; try { const { data } = await ctx.ba.getMasterInfo(mid); userData = data; } catch (e) { this.logger.error('bili sub指令 getMasterInfo() 发生了错误,错误为:' + e.message); return '订阅出错啦,请重试'; } // 定义live销毁函数 let liveDispose; // 订阅直播 if (liveMsg) { // 开始循环检测 liveDispose = ctx.setInterval(this.liveDetect(ctx, roomId, target), config.liveLoopTime * 1000); // 发送订阅消息通知 await session.send(`订阅${userData.info.uname}直播通知`); } // 订阅动态 if (dynamicMsg) { // 判断是否开启动态监测 if (!this.dynamicDispose) { this.enableDynamicDetect(ctx); } // 发送订阅消息通知 await session.send(`订阅${userData.info.uname}动态通知`); } // 保存到数据库中 const sub = await ctx.database.create('bilibili', { uid: mid, room_id: roomId, dynamic: dynamicMsg ? 1 : 0, live: liveMsg ? 1 : 0, target: JSON.stringify(target), platform: session.event.platform, time: new Date() }); // 订阅数+1 this.num++; // 保存新订阅对象 this.subManager.push({ id: sub.id, uid: mid, roomId, target, platform: session.event.platform, live: liveMsg, dynamic: dynamicMsg, liveDispose }); // 新增订阅展示到控制台 this.updateSubNotifier(ctx); }); biliCom .subcommand('.status <roomId:string>', '查询主播当前直播状态', { hidden: true }) .usage('查询主播当前直播状态') .example('bili status 732') .action(async ({ session }, roomId) => { this.logger.info('调用bili.status指令'); if (!roomId) return session.send('请输入房间号!'); let content; try { content = await ctx.ba.getLiveRoomInfo(roomId); } catch (e) { return 'bili status指令 getLiveRoomInfo() 发生了错误,错误为:' + e.message; } const { data } = content; let userData; try { const { data: userInfo } = await ctx.ba.getMasterInfo(data.uid); userData = userInfo; } catch (e) { return 'bili status指令 getMasterInfo() 发生了错误,错误为:' + e.message; } // B站出问题了 if (content.code !== 0) { if (content.msg === '未找到该房间') { session.send('未找到该房间'); } else { session.send('未知错误,错误信息为:' + content.message); } return; } const { pic, buffer } = await ctx.gi.generateLiveImg(data, userData.info.uname, userData.info.face, data.live_status !== 1 ? LiveType.NotLiveBroadcast : LiveType.LiveBroadcast); // pic 存在,使用的是render模式 if (pic) return pic; // pic不存在,说明使用的是page模式 await session.send(koishi_1.h.image(buffer, 'image/png')); }); biliCom .subcommand('.private', '向主人账号发送一条测试消息', { hidden: true }) .usage('向主人账号发送一条测试消息') .example('bili private 向主人账号发送一条测试消息') .action(async ({ session }) => { // 发送消息 await this.sendPrivateMsg('Hello World'); // 发送提示 await session.send('已发送消息,如未收到则说明您的机器人不支持发送私聊消息或您的信息填写有误'); }); } async init(ctx, config) { // 设置logger this.logger = ctx.logger('cr'); // 将config设置给类属性 this.config = config; // 拿到私人机器人实例 this.privateBot = ctx.bots.find(bot => bot.platform === config.master.platform); if (!this.privateBot) { ctx.notifier.create({ content: '您未配置私人机器人,将无法向您推送机器人状态!' }); this.logger.error('您未配置私人机器人,将无法向您推送机器人状态!'); } // 检查登录数据库是否有数据 this.loginDBData = (await ctx.database.get('loginBili', 1, ['dynamic_group_id']))[0]; // 从配置获取订阅 config.sub && await this.loadSubFromConfig(ctx, config.sub); // 从数据库获取订阅 await this.loadSubFromDatabase(ctx); // 判断消息发送方式 if (config.automaticResend) { this.sendMsgFunc = async (bot, channelId, content) => { // 多次尝试发送消息 const attempts = 3; for (let i = 0; i < attempts; i++) { try { // 发送消息 await bot.sendMessage(channelId, content); // 防止消息发送速度过快被忽略 await ctx.sleep(500); // 成功发送消息,跳出循环 break; } catch (e) { if (i === attempts - 1) { // 已尝试三次 this.logger.error(`发送群组ID:${channelId}消息失败!原因: ` + e.message); console.log(e); this.sendPrivateMsg(`发送群组ID:${channelId}消息失败,请查看日志`); } } } }; } else { this.sendMsgFunc = async (bot, guild, content) => { try { // 发送消息 await bot.sendMessage(guild, content); } catch (e) { this.logger.error(`发送群组ID:${guild}消息失败!原因: ` + e.message); await this.sendPrivateMsg(`发送群组ID:${guild}消息失败,请查看日志`); } }; } // 检查是否需要动态监测 this.checkIfDynamicDetectIsNeeded(ctx); // 在控制台中显示订阅对象 this.updateSubNotifier(ctx); } splitMultiPlatformStr(str) { return str.split(';').map(cv => cv.split('.')).map(([idStr, platform]) => { const channelIdArr = idStr.split(',').map(id => { const atAll = /@$/.test(id); // 使用正则表达式检查 id 是否以 @ 结尾 const channelId = atAll ? id.slice(0, -1) : id; // 去除末尾的 @ return { channelId, dynamic: true, live: true, atAll }; }); return { channelIdArr, platform }; }); } getBot(ctx, pf) { return ctx.bots.find(bot => bot.platform === pf); } async sendPrivateMsg(content) { if (this.config.master.enable) { if (this.config.master.masterAccountGuildId) { // 向机器人主人发送消息 await this.privateBot.sendPrivateMessage(this.config.master.masterAccount, content, this.config.master.masterAccountGuildId); } else { // 向机器人主人发送消息 await this.privateBot.sendPrivateMessage(this.config.master.masterAccount, content); } } } async sendPrivateMsgAndRebootService(ctx) { // 判断重启次数是否超过三次 if (this.rebootCount >= 3) { // logger this.logger.error('已重启插件三次,请检查机器人状态后使用指令 sys start 启动插件'); // 重启失败,发送消息 await this.sendPrivateMsg('已重启插件三次,请检查机器人状态后使用指令 sys start 启动插件'); // 关闭插件 await ctx.sm.disposePlugin(); // 结束 return; } // 重启次数+1 this.rebootCount++; // logger this.logger.info('插件出现未知错误,正在重启插件'); // 重启插件 const flag = await ctx.sm.restartPlugin(); // 判断是否重启成功 if (flag) { this.logger.info('重启插件成功'); } else { // logger this.logger.error('重启插件失败,请检查机器人状态后使用指令 sys start 启动插件'); // 重启失败,发送消息 await this.sendPrivateMsg('重启插件失败,请检查机器人状态后使用指令 sys start 启动插件'); // 关闭插件 await ctx.sm.disposePlugin(); } } async sendPrivateMsgAndStopService(ctx) { // 发送消息 await this.sendPrivateMsg('插件发生未知错误,请检查机器人状态后使用指令 sys start 启动插件'); // logger this.logger.error('插件发生未知错误,请检查机器人状态后使用指令 sys start 启动插件'); // 关闭插件 await ctx.sm.disposePlugin(); // 结束 return; } async sendMsg(ctx, targets, content, live) { for (const target of targets) { // 获取机器人实例 const bot = this.getBot(ctx, target.platform); // 定义需要发送的数组 let sendArr = []; // 判断是否需要推送所有机器人加入的群 if (target.channelIdArr[0].channelId === 'all') { // 获取所有guild for (const guild of (await bot.getGuildList()).data) { sendArr.push({ channelId: guild.id, dynamic: target.channelIdArr[0].dynamic, live: target.channelIdArr[0].live, atAll: target.channelIdArr[0].atAll }); } } else { sendArr = target.channelIdArr; } // 判断是否是直播开播推送,如果是则需要进一步判断是否需要艾特群体成员 if (live) { // 直播开播推送,判断是否需要艾特全体成员 for (const channel of sendArr) { // 判断是否需要推送直播消息 if (channel.live) { await this.sendMsgFunc(bot, channel.channelId, content); } // 判断是否需要艾特全体成员 if (channel.atAll) { await this.sendMsgFunc(bot, channel.channelId, (0, jsx_runtime_1.jsx)("at", { type: "all" })); } } } else { for (const channel of sendArr) { // 判断是否需要推送动态消息和直播消息 if (channel.dynamic || channel.live) { await this.sendMsgFunc(bot, channel.channelId, content); } } } } } dynamicDetect(ctx) { // 检测初始化变量 let detectSetup = true; // 更新基线 let updateBaseline; // 第一条动态的动态ID let dynamicIdStr; // 相当于锁的作用,防止上一个循环没处理完 let flag = true; // 返回一个闭包函数 return async () => { // 判断上一个循环是否完成 if (!flag) return; flag = false; // 无论是否执行成功都要释放锁 try { // 检测启动初始化 if (detectSetup) { // 获取动态信息 const data = await ctx.ba.getAllDynamic(); // 判断获取动态信息是否成功 if (data.code !== 0) return; // 设置更新基线 updateBaseline = data.data.update_baseline; // 设置初始化为false detectSetup = false; // 初始化完成 return; } // 获取用户所有动态数据 let updateNum; let content; try { // 查询是否有新动态 const data = await ctx.ba.hasNewDynamic(updateBaseline); updateNum = data.data.update_num; // 没有新动态或获取动态信息失败直接返回 if (updateNum <= 0 || data.code !== 0) return; // 获取动态内容 content = await ctx.ba.getAllDynamic(updateBaseline); } catch (e) { return this.logger.error('dynamicDetect getUserSpaceDynamic() 发生了错误,错误为:' + e.message); } // 判断获取动态内容是否成功 if (content.code !== 0) { switch (content.code) { case -101: { // 账号未登录 // 输出日志 this.logger.error('账号未登录,插件已停止工作,请登录后,输入指令 sys start 启动插件'); // 发送私聊消息 await this.sendPrivateMsg('账号未登录,插件已停止工作,请登录后,输入指令 sys start 启动插件'); // 停止服务 await ctx.sm.disposePlugin(); // 结束循环 break; } case -352: { // 风控 // 输出日志 this.logger.error('账号被风控,插件已停止工作,请确认风控解除后,输入指令 sys start 启动插件'); // 发送私聊消息 await this.sendPrivateMsg('账号被风控,插件已停止工作,请确认风控解除后,输入指令 sys start 启动插件'); // 停止服务 await ctx.sm.disposePlugin(); // 结束循环 break; } case 4101128: case 4101129: { // 获取动态信息错误 // 输出日志 this.logger.error('获取动态信息错误,错误码为:' + content.code + ',错误为:' + content.message); // 发送私聊消息 await this.sendPrivateMsg('获取动态信息错误,错误码为:' + content.code + ',错误为:' + content.message); // 未知错误 // 结束循环 break; } default: { // 未知错误 // 发送私聊消息 await this.sendPrivateMsg('获取动态信息错误,错误码为:' + content.code + ',错误为:' + content.message); // 未知错误 // 结束循环 break; } } } // 获取数据内容 const data = content.data; // 更新基线 updateBaseline = data.update_baseline; // 有新动态内容 const items = data.items; // 检查更新的动态 for (let num = updateNum - 1; num >= 0; num--) { // 没有动态内容则直接跳过 if (!items[num]) continue; // 从动态数据中取出UP主名称、UID和动态ID const upUID = items[num].modules.module_author.mid; // 寻找关注的UP主的动态 this.subManager.forEach(async (sub) => { // 判断是否是订阅的UP主 if (sub.dynamic && sub.uid == upUID) { // 订阅该UP主,推送该动态 // 判断更新动态是否为1条 if (updateNum === 1) { // 判断dynamicIdStr是否有值,是否与当前动态ID一致 if (dynamicIdStr && dynamicIdStr === items[num].id_str) { // 重复动态,不再推送,直接返回 return; } // 存储该动态ID dynamicIdStr = items[num].id_str; } // 定义变量 let pic; let buffer; // 从动态数据中取出UP主名称和动态ID const upName = items[num].modules.module_author.name; const dynamicId = items[num].id_str; // 推送该条动态 const attempts = 3; for (let i = 0; i < attempts; i++) { // 获取动态推送图片 try { // 渲染图片 const { pic: gimgPic, buffer: gimgBuffer } = await ctx.gi.generateDynamicImg(items[num]); // 赋值 pic = gimgPic; buffer = gimgBuffer; // 成功则跳出循环 break; } catch (e) { // 直播开播动态,不做处理 if (e.message === '直播开播动态,不做处理') return; if (e.message === '出现关键词,屏蔽该动态') { // 如果需要发送才发送 if (this.config.filter.notify) { await this.sendMsg(ctx, sub.target, `${upName}发布了一条含有屏蔽关键字的动态`); } return; } if (e.message === '已屏蔽转发动态') { if (this.config.filter.notify) { await this.sendMsg(ctx, sub.target, `${upName}发布了一条转发动态,已屏蔽`); } return; } // 未知错误 if (i === attempts - 1) { this.logger.error('dynamicDetect generateDynamicImg() 推送卡片发送失败,原因:' + e.message); // 发送私聊消息并重启服务 return await this.sendPrivateMsgAndStopService(ctx); } } } // 判断是否需要发送URL const dUrl = this.config.dynamicUrl ? `${upName}发布了一条动态:https://t.bilibili.com/${dynamicId}` : ''; // 如果pic存在,则直接返回pic if (pic) { this.logger.info('推送动态中,使用render模式'); // pic存在,使用的是render模式 await this.sendMsg(ctx, sub.target, pic + (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: dUrl })); } else if (buffer) { this.logger.info('推送动态中,使用page模式'); // pic不存在,说明使用的是page模式 await this.sendMsg(ctx, sub.target, (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [koishi_1.h.image(buffer, 'image/png'), dUrl] })); } else { this.logger.info(items[num].modules.module_author.name + '发布了一条动态,但是推送失败'); } } }); } } finally { flag = true; } }; } debug_dynamicDetect(ctx) { // 检测初始化变量 let detectSetup = true; // 更新基线 let updateBaseline; // 第一条动态的动态ID let dynamicIdStr; // 相当于锁的作用,防止上一个循环没处理完 let flag = true; // 返回一个闭包函数 return async () => { // 判断上一个循环是否完成 if (!flag) return; flag = false; // 无论是否执行成功都要释放锁 try { console.log(`初始化状态:${detectSetup}`); // 检测启动初始化 if (detectSetup) { // 获取动态信息 const data = await ctx.ba.getAllDynamic(); // 判断获取动态信息是否成功 if (data.code !== 0) return; console.log(`更新基线:${data.data.update_baseline}`); // 设置更新基线 updateBaseline = data.data.update_baseline; // 设置初始化为false detectSetup = false; // 初始化完成 return; } // 获取用户所有动态数据 let updateNum; let content; try { // 查询是否有新动态 const data = await ctx.ba.hasNewDynamic(updateBaseline); updateNum = data.data.update_num; console.log(`获取是否有新动态:`); console.log(data); // 没有新动态或获取动态信息失败直接返回 if (updateNum <= 0 || data.code !== 0) return; // 获取动态内容 content = await ctx.ba.getAllDynamic(updateBaseline); console.log('获取动态内容:'); console.log(content.data.items[0]); } catch (e) { return this.logger.error('dynamicDetect getUserSpaceDynamic() 发生了错误,错误为:' + e.message); } // 判断获取动态内容是否成功 if (content.code !== 0) { switch (content.code) { case -101: { // 账号未登录 // 输出日志 this.logger.error('账号未登录,插件已停止工作,请登录后,输入指令 sys start 启动插件'); // 发送私聊消息 await this.sendPrivateMsg('账号未登录,插件已停止工作,请登录后,输入指令 sys start 启动插件'); // 停止服务 await ctx.sm.disposePlugin(); // 结束循环 break; } case -352: { // 风控 // 输出日志 this.logger.error('账号被风控,插件已停止工作,请确认风控解除后,输入指令 sys start 启动插件'); // 发送私聊消息 await this.sendPrivateMsg('账号被风控,插件已停止工作,请确认风控解除后,输入指令 sys start 启动插件'); // 停止服务 await ctx.sm.disposePlugin(); // 结束循环 break; } case 4101128: case 4101129: { // 获取动态信息错误 // 输出日志 this.logger.error('获取动态信息错误,错误码为:' + content.code + ',错误为:' + content.message); // 发送私聊消息 await this.sendPrivateMsg('获取动态信息错误,错误码为:' + content.code + ',错误为:' + content.message); // 未知错误 // 结束循环 break; } default: { // 未知错误 // 发送私聊消息 await this.sendPrivateMsg('获取动态信息错误,错误码为:' + content.code + ',错误为:' + content.message); // 未知错误 // 结束循环 break; } } } // 获取数据内容 const data = content.data; // 更新基线 updateBaseline = data.update_baseline; console.log(`更新基线:${updateBaseline}`); // 有新动态内容 const items = data.items; // 检查更新的动态 for (let num = updateNum - 1; num >= 0; num--) { // 有更新动态 console.log('有更新动态'); // 没有动态内容则直接跳过 if (!items[num]) continue; // 从动态数据中取出UP主名称、UID和动态ID const upName = content.data.items[num].modules.module_author.name; const upUID = items[num].modules.module_author.mid; const dynamicId = content.data.items[num].id_str; console.log(`寻找关注的UP主,当前动态UP主:${upName},UID:${upUID},动态ID:${dynamicId}`); // 寻找关注的UP主的动态 this.subManager.forEach(async (sub) => { console.log(`当前订阅UP主:${sub.uid}`); // 判断是否是订阅的UP主 if (sub.dynamic && sub.uid == upUID) { // 订阅该UP主,推送该动态 // 判断更新动态是否为1条 if (updateNum === 1) { // 判断dynamicIdStr是否有值,是否与当前动态ID一致 if (dynamicIdStr && dynamicIdStr === items[num].id_str) { // 重复动态,不再推送,直接返回 return; } // 存储该动态ID dynamicIdStr = items[num].id_str; } // 定义变量 let pic; let buffer; // 从动态数据中取出UP主名称和动态ID const upName = items[num].modules.module_author.name; const dynamicId = items[num].id_str; console.log(`UP主名称:${upName},动态ID:${dynamicId}`); // 推送该条动态 const attempts = 3; for (let i = 0; i < attempts; i++) { // 获取动态推送图片 try { // 渲染图片 const { pic: gimgPic, buffer: gimgBuffer } = await ctx.gi.generateDynamicImg(items[num]); // 赋值 pic = gimgPic; buffer = gimgBuffer; // 成功则跳出循环 break; } catch (e) { // 直播开播动态,不做处理 if (e.message === '直播开播动态,不做处理') return; if (e.message === '出现关键词,屏蔽该动态') { // 如果需要发送才发送 if (this.config.filter.notify) { await this.sendMsg(ctx, sub.target, `${upName}发布了一条含有屏蔽关键字的动态`); } return; } if (e.message === '已屏蔽转发动态') { if (this.config.filter.notify) { await this.sendMsg(ctx, sub.target, `${upName}发布了一条转发动态,已屏蔽`); } return; } // 未知错误 if (i === attempts - 1) { this.logger.error('dynamicDetect generateDynamicImg() 推送卡片发送失败,原因:' + e.message); // 发送私聊消息并重启服务 return await this.sendPrivateMsgAndStopService(ctx); } } } // 判断是否需要发送URL const dUrl = this.config.dynamicUrl ? `${upName}发布了一条动态:https://t.bilibili.com/${dynamicId}` : ''; // 如果pic存在,则直接返回pic if (pic) { this.logger.info('推送动态中,使用render模式'); // pic存在,使用的是render模式 await this.sendMsg(ctx, sub.target, pic + (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: dUrl })); } else if (buffer) { this.logger.info('推送动态中,使用page模式'); // pic不存在,说明使用的是page模式 await this.sendMsg(ctx, sub.target, (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [koishi_1.h.image(buffer, 'image/png'), dUrl] })); } else { this.logger.info(items[num].modules.module_author.name + '发布了一条动态,但是推送失败'); } } }); } } finally { flag = true; } }; } liveDetect(ctx, roomId, target) { let firstSubscription = true; let timer = 0; let open = false; let liveTime; let username; let userface; // 相当于锁的作用,防止上一个循环没处理完 let flag = true; // 定义发送直播通知卡片方法 const sendLiveNotifyCard = async (data, liveType, liveNotifyMsg) => { // 定义变量 let pic; let buffer; // 多次尝试生成图片 const attempts = 3; for (let i = 0; i < attempts; i++) { try { // 获取直播通知卡片 const { pic: picv, buffer: bufferv } = await ctx.gi.generateLiveImg(data, username, userface, liveType); // 赋值 pic = picv; buffer = bufferv; // 成功则跳出循环 break; } catch (e) { if (i === attempts - 1) { // 已尝试三次 this.logger.error('liveDetect generateLiveImg() 推送卡片生成失败,原因:' + e.message); // 发送私聊消息并重启服务 return await this.sendPrivateMsgAndStopService(ctx); } } } // 推送直播信息 // pic 存在,使用的是render模式 if (pic) { // 只有在开播时才艾特全体成员 if (liveType === LiveType.StartBroadcasting) { return await this.sendMsg(ctx, target, pic + (liveNotifyMsg ?? ''), true); } // 正常不需要艾特全体成员 return await this.sendMsg(ctx, target, pic + (liveNotifyMsg ?? '')); } // pic不存在,说明使用的是page模式 const msg = (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [koishi_1.h.image(buffer, 'image/png'), liveNotifyMsg || ''] }); // 只有在开播时才艾特全体成员 if (liveType === LiveType.StartBroadcasting) { return await this.sendMsg(ctx, target, msg, true); } // 正常不需要艾特全体成员 return await this.sendMsg(ctx, target, msg); }; // 定义获取主播信息方法 let useMasterInfo; if (this.config.changeMasterInfoApi) { useMasterInfo = async (uid) => { const { data } = await ctx.ba.getUserInfo(uid); username = data.name; userface = data.face; }; } else { useMasterInfo = async (uid) => { const { data: { info } } = await ctx.ba.getMasterInfo(uid); username = info.uname; userface = info.face; }; } return async () => { // 如果flag为false则说明前面的代码还未执行完,则直接返回 if (!flag) return; flag = false; // 无论是否执行成功都要释放锁 try { // 发送请求检测直播状态 let content; const attempts = 3; for (let i = 0; i < attempts; i++) { try { // 发送请求获取room信息 content = await ctx.ba.getLiveRoomInfo(roomId); // 成功则跳出循环 break; } catch (e) { this.logger.error('liveDetect getLiveRoomInfo 发生了错误,错误为:' + e.message); if (i === attempts - 1) { // 已尝试三次 // 发送私聊消息并重启服务 return await this.sendPrivateMsgAndStopService(ctx); } } } const { data } = content; // 判断是否是第一次订阅 if (firstSubscription) { firstSubscription = false; // 获取主播信息 const attempts = 3; for (let i = 0; i < attempts; i++) { try { // 发送请求获取主播信息 await useMasterInfo(data.uid); // 成功则跳出循环 break;