From 18760ebaaca50e1e2b0cf3f958209c43b7ab1c1b Mon Sep 17 00:00:00 2001 From: shanmite Date: Sat, 17 Jul 2021 17:59:56 +0800 Subject: [PATCH] =?UTF-8?q?perf:=20=20=E9=87=8D=E6=9E=84=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=20-=20=E5=88=86=E7=A6=BB=E5=8A=A8=E6=80=81=E7=AD=9B=E9=80=89?= =?UTF-8?q?=E9=83=A8=E5=88=86=E7=9A=84=E7=BD=91=E7=BB=9C=E8=AF=B7=E6=B1=82?= =?UTF-8?q?=E5=92=8C=E6=95=B0=E6=8D=AE=E5=A4=84=E7=90=86=20-=20=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E9=83=A8=E5=88=86ifelse=E5=88=A4=E6=96=AD=20feat:=20-?= =?UTF-8?q?=20=E6=96=B0=E5=A2=9ENOT=5FGO=5FLOTTERY=E7=8E=AF=E5=A2=83?= =?UTF-8?q?=E5=8F=98=E9=87=8F=20-=20=E8=BE=B9=E8=BD=AC=E8=BE=B9=E5=AD=98dy?= =?UTF-8?q?id=20-=20=E6=BB=A4=E9=99=A4=E7=9A=84dyid=E4=B9=9F=E8=BF=9B?= =?UTF-8?q?=E8=A1=8C=E5=AD=98=E5=82=A8=20fix:=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E9=83=A8=E5=88=86bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- env.example.js | 8 +- lib/Base.js | 16 ++- lib/BiliAPI.js | 4 +- lib/Monitor.js | 314 +++++++++++++++++++++++++------------------ lib/MyStorage.js | 2 +- lib/Public.js | 24 +--- my_config.example.js | 11 +- 7 files changed, 214 insertions(+), 165 deletions(-) diff --git a/env.example.js b/env.example.js index 1211394..bb6517b 100644 --- a/env.example.js +++ b/env.example.js @@ -5,14 +5,15 @@ */ /** - * ## 账号相关参数 + * ## 账号相关 * - `COOKIE` 是必填项 * - `NUMBER` 表示是第几个账号 * - `CLEAR` 是否启用清理功能 * - `ENABLE_MULTIPLE_ACCOUNT` 是否启用多账号 * - `MULTIPLE_ACCOUNT_PARM` 多账号参数(JSON格式) + * ## 调试相关 * - `LOTTERY_LOG_LEVEL` 输出日志等级 Error} iter + * @param {(value: T) => Promise} fn 返回true整体退出 + */ + async try_for_each(iter, fn) { + for (const item of iter) { + if (await fn(item)) break + } + }, /** * 函数柯里化 * @param {function} func @@ -58,6 +68,7 @@ const Base = { * @returns {Promise} */ delay(time) { + Base.log.info('时延', `${time}ms`); return new Promise(resolve => { setTimeout(() => { resolve(); @@ -93,6 +104,9 @@ const Base = { } return RandomOne }, + hasEnv(env_name) { + return process.env[env_name] ? true : false; + }, /**日志 */ log: { level: 0, @@ -113,7 +127,7 @@ const Base = { debug(context, msg) { if (this.level > 3) { if (msg instanceof Object) msg = JSON.stringify(msg, null, 4); - this.proPrint([`[${Date()}]`, chalk.grey("[Debug]"), chalk.hex('#FFA500')(`[${context}]`), '\n', chalk.hex('#0070BB')(`[${msg}]`)]) + this.proPrint([`[${Date()}]`, chalk.grey("[Debug]"), chalk.hex('#FFA500')(`[${context}]`), chalk.hex('#0070BB')(`[\n${msg}\n]`)]) } }, info(context, msg) { diff --git a/lib/BiliAPI.js b/lib/BiliAPI.js index eedbb2a..ce85b4e 100644 --- a/lib/BiliAPI.js +++ b/lib/BiliAPI.js @@ -334,8 +334,8 @@ const BiliAPI = { success: responseText => { const res = strToJson(responseText); if (res.code === 0) { - resolve(res.data.follower); log.info('获取粉丝数', 'ok'); + resolve(res.data.follower); } else { log.error('获取粉丝数', `出错 可能是访问过频繁\n${responseText}`); resolve(-1); @@ -389,7 +389,7 @@ const BiliAPI = { isMe: isMe }); } else { - log.info('获取开奖信息', `失败\n${responseText}`); + log.error('获取开奖信息', `失败\n${responseText}`); resolve({ ts: -1, text: '获取开奖信息失败', diff --git a/lib/Monitor.js b/lib/Monitor.js index 18cc2ac..1bcc5d6 100644 --- a/lib/Monitor.js +++ b/lib/Monitor.js @@ -6,7 +6,7 @@ const Public = require('./Public'); const GlobalVar = require("./GlobalVar"); const config = require("./config"); const MyStorage = require('./MyStorage'); -const { log } = Base; +const { log, hasEnv } = Base; /** * 监视器 @@ -38,31 +38,7 @@ class Monitor extends Public { } } this.attentionList = await BiliAPI.getAttentionList(GlobalVar.get("myUID")); /* 获取关注列表 */ - await this.startLottery(); - } - /** - * 启动 - * @returns {Promise} - */ - async startLottery() { - const allLottery = await this.filterLotteryInfo(); - let status = 1; - if (allLottery instanceof Array) { - if (allLottery.length) { - let dyids = []; - for (const Lottery of allLottery) { - status = await this.go(Lottery); - if (status % 2) break - dyids.push(Lottery.dyid); - } - if (dyids.length) MyStorage.updateDyid(dyids.toString()) - log.info('抽奖', '开始转发下一组动态'); - } else { - status = 0; - log.info('抽奖', '无未转发抽奖'); - } - } - switch (status) { + switch (await this.startLottery()) { case 0: eventBus.emit('Turn_on_the_Monitor') break; @@ -85,26 +61,81 @@ class Monitor extends Public { case 41: eventBus.emit('Turn_off_the_Monitor', '转发失败') break + case 51: + eventBus.emit('Turn_off_the_Monitor', '获取开奖时间失败') + break + case 61: + eventBus.emit('Turn_off_the_Monitor', '获取关注数失败') + break default: - eventBus.emit('Turn_off_the_Monitor', '出错 allLottery: ' + allLottery) + eventBus.emit('Turn_off_the_Monitor', '未知错误') break; } } + /** + * 启动 + * @returns {Promise} + */ + async startLottery() { + const allLottery = await this.filterLotteryInfo() + , len = allLottery.length; + + log.info('筛选动态', `筛选完毕(${len})`); + + if (len) { + for (const Lottery of allLottery) { + let status = 0; + if (Lottery.isOfficialLottery) { + let { ts } = await BiliAPI.getLotteryNotice(Lottery.dyid); + if (ts < 0) { + return 51 + } + if (ts > (Date.now() / 1000) + config.maxday * 864e5) { + log.info('过滤', '开奖时间晚于指定时间') + MyStorage.updateDyid(Lottery.dyid) + continue + } + } else { + const followerNum = await BiliAPI.getUserInfo(Lottery.uid[0]); + if (followerNum < 0) { + return 61 + } + if (followerNum < config.minfollower) { + log.info('过滤', `粉丝数(${followerNum})小于指定数量`) + MyStorage.updateDyid(Lottery.dyid) + continue + } + } + status = await this.go(Lottery) + if (status % 2 !== 0) { + return status + } + MyStorage.updateDyid(Lottery.dyid); + } + log.info('抽奖', '开始转发下一组动态'); + return 0 + } else { + log.info('抽奖', '无未转发抽奖'); + return 0 + } + } + /** * 抽奖配置 * @typedef {object} LotteryOptions * @property {number[]} uid 用户标识 * @property {string} dyid 动态标识 + * @property {boolean} isOfficialLottery 是否官方抽奖 * @property {number} type 动态类型 * @property {string} relay_chat 动态类型 * @property {string} ctrl 定位@ * @property {string} rid 评论类型 */ /** - * @returns {Promise} + * @returns {Promise} */ async filterLotteryInfo() { - const self = this, + let self = this, protoLotteryInfo = typeof self.UID === 'number' ? await self.getLotteryInfoByUID(self.UID) : await self.getLotteryInfoByTag(self.tag_name); @@ -112,128 +143,151 @@ class Monitor extends Public { if (protoLotteryInfo === null) return []; + log.info('筛选动态', `开始筛选(${protoLotteryInfo.length})`); + log.debug('未进行筛选的动态信息', protoLotteryInfo); + /** 所有抽奖信息 */ let alllotteryinfo = []; - const { key_words, model, chatmodel, is_imitator, maxday: _maxday, minfollower, skip_official_verify, only_followed, at_users, blockword, blacklist } = config; - const maxday = _maxday * 86400 - let dyids_set = new Set(); - log.info('筛选动态', '正在筛选'); - for (const info of protoLotteryInfo) { - const { lottery_info_type, uids, uname, dyid, official_verify, ctrl, befilter, rid, des, type, hasOfficialLottery } = info; + const { key_words, model, chatmodel, is_imitator, only_followed, at_users, blockword, blacklist } = config; + /**Map */ + let dyids_map = new Map(); - /**dyid去重 */ - if (dyids_set.has(dyid)) continue; - dyids_set.add(dyid); + /**去重 */ + protoLotteryInfo = protoLotteryInfo.filter(({ dyid }) => { + if (dyids_map.has(dyid)) { + return false + } + dyids_map.set(dyid, false); + return true + }); + + /**并发查询dyid */ + await Promise.all( + [...dyids_map.keys()] + .map(it => MyStorage + .searchDyid(it) + .then(hasIt => dyids_map.set(it, hasIt)) + ) + ) + + /* 检查动态是否满足要求 */ + await Base.try_for_each(protoLotteryInfo, async function ({ + lottery_info_type, uids, + uname, dyid, + ctrl, rid, des, type, + hasOfficialLottery + }) { + /* 遇到转发过就退出 */ + if (dyids_map.get(dyid)) return false; /**判断是转发源动态还是现动态 */ - const uid = lottery_info_type === 'tag' ? uids[0] : uids[1]; + const uid = lottery_info_type === 'tag' ? uids[0] : uids[1] + , isFollowed = (new RegExp(uid)).test(self.attentionList) + , description = typeof des === 'string' ? des : '' + , needAt = /(?:@|艾特)[^@|(艾特)]*?好友/.test(description) + , needTopic = (/(?<=[带加上](?:话题|tag))#.*#/i.exec(description) || [])[0] + , isRelayDynamic = type === 1 + , isTwoLevelDynamic = /\/\/@/.test(description) + , has_key_words = key_words.every(it => new RegExp(it).test(description)) + , isBlock = new RegExp(blockword.join('|')).test(description) + , isLottery = + (lottery_info_type === 'uid' && is_imitator && model !== '00') + || (hasOfficialLottery && model[0] === '1') + || (!hasOfficialLottery && model[1] === '1' && !isTwoLevelDynamic && has_key_words) + , isSendChat = + (is_imitator && lottery_info_type === 'uid' && chatmodel !== '00') + || (hasOfficialLottery && chatmodel[0] === '1') + || (!hasOfficialLottery && chatmodel[1] === '1'); - const now_ts_10 = Date.now() / 1000; - let onelotteryinfo = {}; - let isLottery = false; - let isSendChat = false; - let isBlock = false; - let ts = 0; - const description = typeof des === 'string' ? des : ''; - for (let index = 0; index < blockword.length; index++) { - const word = blockword[index]; - const reg = new RegExp(word); - isBlock = reg.test(description) ? true : false; - if (isBlock) break; - } - if (isBlock) continue; - const needAt = /(?:@|艾特)[^@|(艾特)]*?好友/.test(description); - const needTopic = (/(?<=[带加上](?:话题|tag))#.*#/i.exec(description) || [])[0]; - const isTwoLevelRelay = /\/\/@/.test(description); + /**屏蔽词 */ + if (isBlock) return false; - /**是否包含关键词 */ - const has_key_words = key_words.map(it => new RegExp(it)).every(it => it.test(description)) + /**若勾选只转已关注 */ + if (only_followed && !isFollowed) return false; + + /* 获取黑名单并去重合并 */ + const { blacklist: remote_blacklist } = GlobalVar.get("remoteconfig") + , new_blacklist = remote_blacklist + ? [...new Set([...blacklist.split(','), ...remote_blacklist.split(',')])].join() + : blacklist; + if ((new RegExp(dyid + '|' + uid)).test(new_blacklist)) return false; - if (lottery_info_type === 'uid' && is_imitator) { - isLottery = true; - isSendChat = chatmodel[1] === '1' || chatmodel[1] === '1'; - } else if (hasOfficialLottery && model[0] === '1') { - ({ ts } = await BiliAPI.getLotteryNotice(dyid)); - if (ts < 0) { alllotteryinfo = null; break } - isLottery = ts > now_ts_10 && ts < now_ts_10 + maxday; - isSendChat = chatmodel[0] === '1'; - } else if (!hasOfficialLottery && model[1] === '1' && has_key_words && !isTwoLevelRelay) { - ({ ts } = Base.getLotteryNotice(description)); - if (official_verify && skip_official_verify) { - isLottery = ts === 0 || (ts > now_ts_10 && ts < now_ts_10 + maxday); - } else { - const followerNum = await BiliAPI.getUserInfo(uid); - if (followerNum < 0) { alllotteryinfo = null; break } - if (followerNum < minfollower) continue; - isLottery = !befilter && (ts === 0 || (ts > now_ts_10 && ts < now_ts_10 + maxday)); - } - isSendChat = chatmodel[1] === '1'; - } if (isLottery) { - /* 判断是否关注过 */ - const isFollowed = (new RegExp(uid)).test(self.attentionList); - if (only_followed && !isFollowed) continue; - /* 判断是否转发过 */ - const isRelayed = await MyStorage.searchDyid(dyid); - /* 获取黑名单并去重合并 */ - const { blacklist: remote_blacklist } = GlobalVar.get("remoteconfig"); - const new_blacklist = remote_blacklist ? - Array.from(new Set([...blacklist.split(','), ...remote_blacklist.split(',')])).toString() : blacklist; - /* 进行判断 */ - if ((new RegExp(dyid + '|' + uid)).test(new_blacklist)) continue; - onelotteryinfo.uid = [] /**初始化待关注列表 */ - if (!isFollowed) onelotteryinfo.uid.push(uid); - if (!isRelayed) { - onelotteryinfo.dyid = dyid; - let RandomStr = Base.getRandomOne(config.relay); - let new_ctrl = []; - if (needTopic) { - RandomStr += needTopic - } - if (needAt) { - at_users.forEach(it => { - new_ctrl.push({ - data: String(it[1]), - location: RandomStr.length, - length: it[0].length + 1, - type: 1 - }) - RandomStr += '@' + it[0] - }) - } - if (type === 1) { - /* 转发内容长度+'//'+'@'+用户名+':'+源内容 */ - const addlength = RandomStr.length + 2 + uname.length + 1 + 1; - onelotteryinfo.relay_chat = RandomStr + `//@${uname}:` + des; + let onelotteryinfo = {}; + + onelotteryinfo.isOfficialLottery = hasOfficialLottery; + + /**初始化待关注列表 */ + onelotteryinfo.uid = [] + + if (!isFollowed) { + onelotteryinfo.uid.push(uid); + } + + onelotteryinfo.dyid = dyid; + + let + /**转发评语 */ + RandomStr = Base.getRandomOne(config.relay), + /**控制字段 */ + new_ctrl = []; + + /* 是否需要带话题 */ + if (needTopic) { + RandomStr += needTopic + } + + /* 是否需要@ */ + if (needAt) { + at_users.forEach(it => { new_ctrl.push({ - data: String(uid), - location: RandomStr.length + 2, - length: uname.length + 1, + data: String(it[1]), + location: RandomStr.length, + length: it[0].length + 1, type: 1 }) - ctrl.map(item => { - item.location += addlength; - return item; - }).forEach(it => new_ctrl.push(it)) - if (!(new RegExp(uids[1])).test(self.attentionList)) - onelotteryinfo.uid.push(uids[1]); - } else { - onelotteryinfo.relay_chat = RandomStr; - } - onelotteryinfo.ctrl = JSON.stringify(new_ctrl); + RandomStr += '@' + it[0] + }) } + + /* 是否是转发的动态 */ + if (isRelayDynamic) { + /* 转发内容长度+'//'+'@'+用户名+':'+源内容 */ + const addlength = RandomStr.length + 2 + uname.length + 1 + 1; + onelotteryinfo.relay_chat = RandomStr + `//@${uname}:` + des; + new_ctrl.push({ + data: String(uid), + location: RandomStr.length + 2, + length: uname.length + 1, + type: 1 + }) + ctrl.map(item => { + item.location += addlength; + return item; + }).forEach(it => new_ctrl.push(it)) + if (!(new RegExp(uids[1])).test(self.attentionList)) + onelotteryinfo.uid.push(uids[1]); + } else { + onelotteryinfo.relay_chat = RandomStr; + } + + onelotteryinfo.ctrl = JSON.stringify(new_ctrl); + /* 根据动态的类型决定评论的类型 */ onelotteryinfo.type = type === 2 ? 11 : type === 4 || type === 1 ? 17 : type === 8 ? 1 : 0; + /* 是否评论 */ if (isSendChat) onelotteryinfo.rid = rid; - if (onelotteryinfo.dyid) alllotteryinfo.push(onelotteryinfo); + + alllotteryinfo.push(onelotteryinfo); } - } + }) + return alllotteryinfo; } /** @@ -249,6 +303,10 @@ class Monitor extends Public { */ async go(option) { log.debug('正在转发的动态信息', option); + if (hasEnv('NOT_GO_LOTTERY')) { + log.info('NOT_GO_LOTTERY', 'ON'); + return 0 + } let status = 1; const { uid, dyid, type, rid, relay_chat, ctrl } = option; /* 评论 */ diff --git a/lib/MyStorage.js b/lib/MyStorage.js index f9e530b..1248a3c 100644 --- a/lib/MyStorage.js +++ b/lib/MyStorage.js @@ -31,7 +31,7 @@ const MyStorage = { * @param {string} dyid */ updateDyid: (dyid) => { - log.info('更新dyid', '写入已转发过的动态信息'); + log.info('更新dyid', `写入${dyid}`); return new Promise((resolve) => { const ws = Base.writeDyidFile(Number(process.env.NUMBER)); ws.write(dyid + ',', () => { diff --git a/lib/Public.js b/lib/Public.js index 441473e..fe81496 100644 --- a/lib/Public.js +++ b/lib/Public.js @@ -12,7 +12,6 @@ class Public { * @typedef {object} UsefulDynamicInfo * @property {number} uid * @property {string} uname - * @property {boolean} official_verify * @property {number} createtime 10 * @property {string} rid_str * @property {string} dynamic_id @@ -23,7 +22,6 @@ class Public { * * @property {number} origin_uid * @property {string} origin_uname - * @property {boolean} origin_official_verify * @property {string} origin_rid_str * @property {string} origin_dynamic_id * @property {number} orig_type @@ -115,15 +113,12 @@ class Public { /**临时储存单个动态中的信息 */ let obj = {}; const { desc, card } = onecard - , { info, card: user_profile_card } = desc.user_profile - , { official_verify } = user_profile_card + , { info } = desc.user_profile , cardToJson = strToJson(card); /* 转发者的UID */ obj.uid = info.uid; /* 转发者的name */ obj.uname = info.uname; - /* 是否官方号 */ - obj.official_verify = official_verify.type > -1 ? true : false; /* 动态的ts10 */ obj.createtime = desc.timestamp /* 动态类型 */ @@ -149,15 +144,7 @@ class Public { obj.origin_rid_str = desc.origin.rid_str.length > 12 ? desc.origin.dynamic_id_str : desc.origin.rid_str; /* 被转发者的动态的ID !!!!此为大数需使用字符串值,不然JSON.parse()会有丢失精度 */ obj.origin_dynamic_id = desc.orig_dy_id_str; - const { origin_extension, origin_user } = cardToJson; - try { - /* 是否官方号 */ - obj.origin_official_verify = typeof origin_user === 'undefined' ? - false : origin_user.card.official_verify.type < 0 ? - false : true; - } catch (_) { - obj.origin_official_verify = false; - } + const { origin_extension } = cardToJson; /* 是否有官方抽奖 */ obj.origin_hasOfficialLottery = typeof origin_extension === 'undefined' ? false : typeof origin_extension.lott === 'undefined' ? @@ -187,8 +174,6 @@ class Public { * @property {string} uname * @property {Array<{}>} ctrl * @property {string} dyid - * @property {boolean} befilter - * @property {boolean} official_verify 官方认证 * @property {string} rid * @property {string} des * @property {number} type @@ -220,15 +205,12 @@ class Public { await Base.delay(config.search_wait); } const fomatdata = mDRdata.map(o => { - const hasOrigin = o.type === 1; return { lottery_info_type: 'tag', uids: [o.uid, o.origin_uid], uname: o.uname, ctrl: o.ctrl, dyid: o.dynamic_id, - official_verify: o.official_verify, - befilter: hasOrigin, rid: o.rid_str, des: o.description, type: o.type, @@ -255,8 +237,6 @@ class Public { uname: o.origin_uname, ctrl: [], dyid: o.origin_dynamic_id, - official_verify: o.origin_official_verify, - befilter: false, rid: o.origin_rid_str, des: o.origin_description, type: o.orig_type, diff --git a/my_config.example.js b/my_config.example.js index d7517eb..bae60d1 100644 --- a/my_config.example.js +++ b/my_config.example.js @@ -17,9 +17,9 @@ module.exports = { */ TAGs: [ '互动抽奖', - '抽奖', '转发抽奖', '动态抽奖', + '抽奖', ], /** @@ -76,7 +76,7 @@ module.exports = { * - 单位毫秒 * - 上下浮动50% */ - wait: 100 * 1000, + wait: 30 * 1000, /** * - 检索动态间隔 @@ -89,11 +89,6 @@ module.exports = { */ minfollower: 1000, - /** - * 粉丝数限制是否跳过有官方认证的up - */ - skip_official_verify: true, - /** * - 只转发已关注的 */ @@ -135,7 +130,7 @@ module.exports = { /** * 屏蔽词 */ - blockword: ["脚本抽奖", "恭喜", "结果", "抽奖号", "钓鱼"], + blockword: ["脚本抽奖", "恭喜", "结果", "抽奖号", "钓鱼", "涨粉"], /** * 取关白名单