From cbce4b113daa464b6c6ac02f519b49457332d50f Mon Sep 17 00:00:00 2001 From: shanmite Date: Tue, 27 Jul 2021 21:48:12 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E4=BB=8E=E4=B8=93?= =?UTF-8?q?=E6=A0=8F=E8=8E=B7=E5=8F=96=E6=8A=BD=E5=A5=96=E7=9A=84=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/BiliAPI.js | 43 +++++++++ lib/Monitor.js | 31 +++--- lib/Public.js | 201 +++++++++++++++++++++++++-------------- lib/lottery-in-nodejs.js | 4 +- lib/setVariable.js | 10 +- my_config.example.js | 21 +++- script/pkg/pkg.ps1 | 1 - 7 files changed, 218 insertions(+), 93 deletions(-) diff --git a/lib/BiliAPI.js b/lib/BiliAPI.js index a68f7cc..759d31e 100644 --- a/lib/BiliAPI.js +++ b/lib/BiliAPI.js @@ -321,6 +321,49 @@ const BiliAPI = { }); }); }, + /** + * 搜索专栏 + * @param {string} keyword + * @return {Promise>} + */ + searchArticlesByKeyword(keyword) { + return new Promise((resolve) => { + Ajax.get({ + url: 'https://api.bilibili.com/x/web-interface/search/type', + queryStringsObj: { + keyword, + page: 1, + order: 'pubdate', + search_type: 'article' + }, + success: responseText => { + const res = JSON.parse(responseText); + if (res.code === 0) { + log.info('搜索专栏', '成功 关键词: ' + keyword) + resolve(res.data.result.map(it => it.id)) + } else { + log.error('搜索专栏', '失败 原因:\n' + responseText) + resolve([]) + } + } + }) + }); + }, + /** + * 获取专栏内容 + * @param {number} cv + * @returns {Promise} + */ + getOneArticleByCv(cv) { + return new Promise((resolve) => { + Ajax.get({ + url: `https://www.bilibili.com/read/cv${cv}`, + success: responseText => { + resolve(responseText) + } + }) + }); + }, /** * 获取粉丝数 * @param {number} uid diff --git a/lib/Monitor.js b/lib/Monitor.js index e13ac2f..4c93377 100644 --- a/lib/Monitor.js +++ b/lib/Monitor.js @@ -14,13 +14,18 @@ const { log, hasEnv } = Util; class Monitor extends Public { /** * @constructor - * @param {number | string} param + * @param {[string, number | string]} lottery_param */ - constructor(param) { + constructor(lottery_param) { super(); - typeof param === 'number' ? this.UID = param : this.tag_name = param; + this.lottery_param = lottery_param this.tagid = config.partition_id; /* tagid初始化 */ this.attentionList = ''; /* 转为字符串的所有关注的up主uid */ + this.LotteryInfoMap = new Map([ + ['UIDs', this.getLotteryInfoByUID.bind(this)], + ['TAGs', this.getLotteryInfoByTag.bind(this)], + ['Articles', this.getLotteryInfoByArticle.bind(this)], + ]); } /** * 初始化 @@ -37,7 +42,8 @@ class Monitor extends Public { return } } - this.attentionList = await BiliAPI.getAttentionList(GlobalVar.get("myUID")); /* 获取关注列表 */ + /** 关注列表初始化 */ + this.attentionList = await BiliAPI.getAttentionList(GlobalVar.get("myUID")); switch (await this.startLottery()) { case 0: eventBus.emit('Turn_on_the_Monitor') @@ -142,10 +148,8 @@ class Monitor extends Public { * @returns {Promise} */ async filterLotteryInfo() { - let self = this, - protoLotteryInfo = typeof self.UID === 'number' - ? await self.getLotteryInfoByUID(self.UID) - : await self.getLotteryInfoByTag(self.tag_name); + const { lottery_param, LotteryInfoMap, attentionList } = this; + let protoLotteryInfo = await LotteryInfoMap.get(lottery_param[0])(lottery_param[1]); if (protoLotteryInfo === null) return []; @@ -188,9 +192,10 @@ class Monitor extends Public { /* 遇到转发过就退出 */ if (dyids_map.get(dyid)) return false; - /**判断是转发源动态还是现动态 */ - const uid = lottery_info_type === 'tag' ? uids[0] : uids[1] - , isFollowed = (new RegExp(uid)).test(self.attentionList) + const + /**判断是转发源动态还是现动态 */ + uid = lottery_info_type === ('tag' || 'article') ? uids[0] : uids[1] + , isFollowed = (new RegExp(uid)).test(attentionList) , description = typeof des === 'string' ? des : '' , needAt = /(?:@|艾特)[^@|(艾特)]*?好友/.test(description) , needTopic = (/(?<=[带加上](?:话题|tag))#.*#/i.exec(description) || [])[0] @@ -199,7 +204,7 @@ class Monitor extends Public { , 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') + (is_imitator && lottery_info_type === 'uid' && model !== '00') || (hasOfficialLottery && model[0] === '1') || (!hasOfficialLottery && model[1] === '1' && !isTwoLevelDynamic && has_key_words) , isSendChat = @@ -274,7 +279,7 @@ class Monitor extends Public { item.location += addlength; return item; }).forEach(it => new_ctrl.push(it)) - if (!(new RegExp(uids[1])).test(self.attentionList)) + if (!(new RegExp(uids[1])).test(attentionList)) onelotteryinfo.uid.push(uids[1]); } else { onelotteryinfo.relay_chat = RandomStr; diff --git a/lib/Public.js b/lib/Public.js index 4aad784..4a06b9e 100644 --- a/lib/Public.js +++ b/lib/Public.js @@ -77,25 +77,81 @@ function parseDynamicCard(dynamic_detail_card) { return obj } +/** + * 处理来自个人动态或话题页面的一组动态数据 + * @param {String} res + * @returns {{modifyDynamicResArray: UsefulDynamicInfo[], nextinfo: {has_more: number, next_offset: string}} | UsefulDynamicInfo |null} + */ +function modifyDynamicRes(res) { + const + strToJson = Util.strToJson + , { data, code } = strToJson(res) + , { cards = [], has_more, offset } = data; + + if (code !== 0) { + log.error('处理动态数据', '获取动态数据出错,可能是访问太频繁 \n' + res); + return null; + } + + if (!cards.length) { + log.warn('处理动态数据', '未找到任何动态信息') + } + + const + /** + * 字符串offset防止损失精度 + */ + next = { + has_more, + next_offset: typeof offset === 'string' + ? offset + : /(?<=next_offset":)[0-9]+/.exec(res)[0] + }, + /** + * 储存获取到的一组动态中的信息 + */ + array = next.has_more === 0 + ? [] + : cards.map(onecard => parseDynamicCard(onecard)) + + log.info('处理动态数据', `动态数据读取完毕(${cards.length})(${next.has_more})`); + + return { + modifyDynamicResArray: array, + nextinfo: next + } +} + /** * 基础功能 */ class Public { constructor() { } /** - * 检查所有的动态信息 + * 整理后的抽奖信息 + * @typedef {object} LotteryInfo + * @property {string} lottery_info_type + * @property {number[]} uids `[uid,ouid]` + * @property {string} uname + * @property {Array<{}>} ctrl + * @property {string} dyid + * @property {string} rid + * @property {string} des + * @property {number} type + * @property {boolean} hasOfficialLottery 是否官方 + */ + /** + * 检查指定用户的所有的动态信息 * @param {string} UID 指定的用户UID * @param {number} pages 读取页数 * @param {number} time 时延 * @param {string} [offset] 默认'0' - * @returns {Promise<{allModifyDynamicResArray: UsefulDynamicInfo[];offset: string}>} 获取前 `pages*12` 个动态信息 + * @returns {Promise<{allModifyDynamicResArray: UsefulDynamicInfo[], offset: string}>} 获取前 `pages*12` 个动态信息 */ async checkAllDynamic(hostuid, pages, time = 0, offset = '0') { log.info('检查所有动态', `准备读取${pages}页动态`); - const - { modifyDynamicRes } = this, - { getOneDynamicInfoByUID } = BiliAPI, + const { getOneDynamicInfoByUID } = BiliAPI, /** * 柯里化请求函数 */ @@ -148,57 +204,34 @@ class Public { return ({ allModifyDynamicResArray, offset }); } /** - * 互动抽奖 - * 处理来自动态页面的数据 - * @param {String} res - * @returns {{modifyDynamicResArray: UsefulDynamicInfo[];nextinfo: {has_more: number;next_offset: string;};} | null} + * 获取最新动态信息(转发子动态) + * 并初步整理 + * @param {string} UID + * @returns {Promise} */ - modifyDynamicRes(res) { - const - strToJson = Util.strToJson - , { data, code } = strToJson(res) - , { cards = [] } = data; + async getLotteryInfoByUID(UID) { + log.info('获取动态', `开始获取用户${UID}的动态信息`); + const { allModifyDynamicResArray } = await this.checkAllDynamic(UID, config.uid_scan_page, config.search_wait); - if (code !== 0) { - log.error('处理动态数据', '获取动态数据出错,可能是访问太频繁 \n' + res); - return null; - } - const - /** - * 字符串offset防止损失精度 - */ - next = { - has_more: data.has_more, - next_offset: typeof data.offset === 'string' - ? data.offset - : /(?<=next_offset":)[0-9]*/.exec(res)[0] - }, - /** - * 储存获取到的一组动态中的信息 - */ - array = next.has_more === 0 - ? [] - : cards.map(onecard => parseDynamicCard(onecard)) + if (!allModifyDynamicResArray.length) return null; - log.info('处理动态数据', `动态数据读取完毕(${cards.length})(${next.has_more})`); + const fomatdata = allModifyDynamicResArray.map(o => { + return { + lottery_info_type: 'uid', + uids: [o.uid, o.origin_uid], + uname: o.origin_uname, + ctrl: [], + dyid: o.origin_dynamic_id, + rid: o.origin_rid_str, + des: o.origin_description, + type: o.orig_type, + hasOfficialLottery: o.origin_hasOfficialLottery + } + }).filter(a => a.type != 0) + log.info('获取动态', `成功获取用户${UID}的动态信息`); - return { - modifyDynamicResArray: array, - nextinfo: next - }; + return fomatdata; } - /** - * @typedef {object} LotteryInfo - * @property {string} lottery_info_type - * @property {number[]} uids `[uid,ouid]` - * @property {string} uname - * @property {Array<{}>} ctrl - * @property {string} dyid - * @property {string} rid - * @property {string} des - * @property {number} type - * @property {boolean} hasOfficialLottery 是否官方 - */ /** * 获取tag下的抽奖信息(转发母动态) * 并初步整理 @@ -207,7 +240,6 @@ class Public { */ async getLotteryInfoByTag(tag_name) { const - { modifyDynamicRes } = this, tag_id = await BiliAPI.getTagIDByTagName(tag_name), hotdy = await BiliAPI.getHotDynamicInfoByTagID(tag_id), modDR = modifyDynamicRes(hotdy); @@ -223,7 +255,7 @@ class Public { let mDRdata = modDR.modifyDynamicResArray; let next_offset = modDR.nextinfo.next_offset; - for (let index = 0; index < config.scan_page_num; index++) { + for (let index = 0; index < config.tag_scan_page; index++) { log.info('获取动态', `读取第${index + 1}页动态`); const newdy = await BiliAPI.getOneDynamicInfoByTag(tag_name, next_offset), @@ -254,33 +286,54 @@ class Public { return fomatdata } /** - * 获取最新动态信息(转发子动态) - * 并初步整理 - * @param {string} UID + * 从专栏中获取抽奖信息 + * @param {string} key_words * @returns {Promise} */ - async getLotteryInfoByUID(UID) { - log.info('获取动态', `开始获取用户${UID}的动态信息`); - const { allModifyDynamicResArray } = await this.checkAllDynamic(UID, config.scan_page_num, config.search_wait); + async getLotteryInfoByArticle(key_words) { + log.info('获取动态', `开始获取含关键词${key_words}的专栏信息`); + const cvs = (await BiliAPI.searchArticlesByKeyword(key_words)).slice(0, config.article_scan_page); - if (!allModifyDynamicResArray.length) return null; + let dyinfos = []; + for (const cv of cvs) { + const content = await BiliAPI.getOneArticleByCv(cv) + , dyids = content.match(/(?<=t.bilibili.com\/)[0-9]+/g) || []; + let { length } = dyids; + log.info('获取动态', `提取专栏中提及的dyid(${length})`) + for (const dyid of dyids) { + log.info('获取动态', `查看动态(${dyid})的细节 (${length--})`) + const res = await BiliAPI.getOneDynamicByDyid(dyid) + , { code, data } = Util.strToJson(res) + , { card } = data; - const fomatdata = allModifyDynamicResArray.map(o => { - return { - lottery_info_type: 'uid', - uids: [o.uid, o.origin_uid], - uname: o.origin_uname, - ctrl: [], - dyid: o.origin_dynamic_id, - rid: o.origin_rid_str, - des: o.origin_description, - type: o.orig_type, - hasOfficialLottery: o.origin_hasOfficialLottery + if (code !== 0) { + log.error('获取动态', '获取动态数据出错,可能是访问太频繁 \n' + res) + break + } + + await Util.delay(2000) + + if (card) { + dyinfos.push(parseDynamicCard(card)); + } } - }).filter(a => a.type != 0) - log.info('获取动态', `成功获取用户${UID}的动态信息`); + } + const fomatdata = dyinfos.map(o => { + return { + lottery_info_type: 'article', + uids: [o.uid, o.origin_uid], + uname: o.uname, + ctrl: o.ctrl, + dyid: o.dynamic_id, + rid: o.rid_str, + des: o.description, + type: o.type, + hasOfficialLottery: o.hasOfficialLottery + }; + }) + log.info('获取动态', `成功获取含关键词${key_words}的专栏信息`); - return fomatdata; + return fomatdata } } diff --git a/lib/lottery-in-nodejs.js b/lib/lottery-in-nodejs.js index f17fe52..2e30a6e 100644 --- a/lib/lottery-in-nodejs.js +++ b/lib/lottery-in-nodejs.js @@ -47,8 +47,8 @@ function start() { return; } const lottery = lotterys[times.next()]; - const nlottery = Number(lottery); - await (new Monitor(isNaN(nlottery) ? lottery : nlottery)).init(); + + await (new Monitor(lottery)).init(); }); eventBus.on('Turn_off_the_Monitor', async (msg) => { await createRandomDynamic(config.create_dy_num); diff --git a/lib/setVariable.js b/lib/setVariable.js index 8d8ae58..a1ee1ae 100644 --- a/lib/setVariable.js +++ b/lib/setVariable.js @@ -15,12 +15,20 @@ async function setVariable(cookie, n) { config.updata(process.env.NUMBER); GlobalVar.set('cookie', cookie); + cookie.split(/\s*;\s*/).forEach(item => { const _item = item.split('='); if (key_map.has(_item[0])) GlobalVar.set(key_map.get(_item[0]), _item[1]); }); - GlobalVar.set('Lottery', [...config.UIDs, ...config.TAGs]); + + const { UIDs = [], TAGs = [], Articles = [] } = config; + GlobalVar.set('Lottery', [ + ...UIDs.map(it => ['UIDs', it]), + ...TAGs.map(it => ['TAGs', it]), + ...Articles.map(it => ['Articles', it]) + ]); + GlobalVar.set('remoteconfig', await Util.getRemoteConfig()); } await Util.createDir('dyids'); diff --git a/my_config.example.js b/my_config.example.js index 3feed9d..13a056b 100644 --- a/my_config.example.js +++ b/my_config.example.js @@ -12,6 +12,13 @@ module.exports = { 241675899 ], + /** + * 监视的专栏关键词 + */ + Articles: [ + '抽奖合集' + ], + /** * 监视的tag */ @@ -53,9 +60,19 @@ module.exports = { is_imitator: false, /** - * - 在uid或tag里检索的页数 + * - 在uid里检索的页数 */ - scan_page_num: 3, + uid_scan_page: 3, + + /** + * - 在tag里检索的页数 + */ + tag_scan_page: 3, + + /** + * - 获取专栏数量 + */ + article_scan_page: 3, /** * - 开奖时间距离现在的最大天数 diff --git a/script/pkg/pkg.ps1 b/script/pkg/pkg.ps1 index bbe0201..b7a2cd0 100644 --- a/script/pkg/pkg.ps1 +++ b/script/pkg/pkg.ps1 @@ -17,7 +17,6 @@ Copy-Item -Path $TEMPLATE_CONFIG_FILE -Destination $TARGET_DIR -Force Set-Location -Path $TARGET_DIR -# 重命名文件 Move-Item -Path $TEMPLATE_ENV_FILE -Destination $ENV_FILE -Force Move-Item -Path $TEMPLATE_CONFIG_FILE -Destination $CONFIG_FILE -Force