perf: 重构代码

- 分离动态筛选部分的网络请求和数据处理
- 优化部分ifelse判断
feat:
- 新增NOT_GO_LOTTERY环境变量
- 边转边存dyid
- 滤除的dyid也进行存储
fix: 修复部分bug
This commit is contained in:
shanmite 2021-07-17 17:59:56 +08:00
parent 266a657a8f
commit 18760ebaac
7 changed files with 214 additions and 165 deletions

View File

@ -5,14 +5,15 @@
*/
/**
* ## 账号相关参数
* ## 账号相关
* - `COOKIE` 是必填项
* - `NUMBER` 表示是第几个账号
* - `CLEAR` 是否启用清理功能
* - `ENABLE_MULTIPLE_ACCOUNT` 是否启用多账号
* - `MULTIPLE_ACCOUNT_PARM` 多账号参数(JSON格式)
* ## 调试相关
* - `LOTTERY_LOG_LEVEL` 输出日志等级 Error<Warn<Info<Debug 1<2<3<4
*
* - `NOT_GO_LOTTERY` 关闭抽奖行为 为空字符即关
* ## 多账号
* 1. ENABLE_MULTIPLE_ACCOUNT 的值改为true
* 2. 将账号信息依次填写于 multiple_account_parm , 参考例子类推
@ -26,7 +27,8 @@ const account_parm = {
CLEAR: true,
ENABLE_MULTIPLE_ACCOUNT: false,
MULTIPLE_ACCOUNT_PARM: "",
LOTTERY_LOG_LEVEL: 3
LOTTERY_LOG_LEVEL: 3,
NOT_GO_LOTTERY: ''
}
/**

View File

@ -35,6 +35,16 @@ const Base = {
})(params);
return isJSON ? isJSON : {}
},
/**
* @template T
* @param {Array<T>} iter
* @param {(value: T) => Promise<Boolean>} 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<void>}
*/
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) {

View File

@ -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: '获取开奖信息失败',

View File

@ -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<boolean>}
*/
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<number>}
*/
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<LotteryOptions[] | null>}
* @returns {Promise<LotteryOptions[]>}
*/
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<String, Boolean> */
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;
/* 评论 */

View File

@ -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 + ',', () => {

View File

@ -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,

View File

@ -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: ["脚本抽奖", "恭喜", "结果", "抽奖号", "钓鱼", "涨粉"],
/**
* 取关白名单