LotteryAutoScript/lib/core/monitor.js
2025-08-04 21:53:09 +08:00

706 lines
27 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const { log, hasEnv, shuffle, getRandomOne,getAiContent, delay, try_for_each, retryfn, appendLotteryInfoFile } = require('../utils');
const { send } = require('../net/http');
const bili = require('../net/bili');
const { sendNotify } = require('../helper/notify');
const event_bus = require('../helper/event_bus');
const { randomDynamic } = require('../helper/randomDynamic');
const { Searcher } = require('./searcher');
const global_var = require('../data/global_var');
const config = require('../data/config');
const d_storage = require('../helper/d_storage');
/**
* 监视器
*/
class Monitor extends Searcher {
/**
* @constructor
* @param {[string, number | string]} lottery_param
*/
constructor(lottery_param) {
super();
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)],
['APIs', this.getLotteryInfoByAPI.bind(this)],
['TxT', this.getLotteryInfoByTxT.bind(this)]
]);
}
/**
* 初始化
*/
async init() {
if (config.model === '00') {
event_bus.emit('Turn_off_the_Monitor', '已关闭所有转发行为');
return;
}
if (!this.tagid
&& config.is_not_create_partition !== true) {
this.tagid = await bili.checkMyPartition(); /* 检查关注分区 */
if (!this.tagid) {
event_bus.emit('Turn_off_the_Monitor', '分区获取失败');
return;
}
}
/** 关注列表初始化 */
this.attentionList = await bili.getAttentionList(global_var.get('myUID'));
const status = await this.startLottery();
switch (status) {
case 0:
event_bus.emit('Turn_on_the_Monitor');
break;
case 1001:
event_bus.emit('Turn_off_the_Monitor', '评论失败');
break;
case 1010:
event_bus.emit('Turn_off_the_Monitor', '已掉号');
break;
case 1004:
event_bus.emit('Turn_off_the_Monitor', '需要输入验证码');
break;
case 2001:
event_bus.emit('Turn_off_the_Monitor', '关注出错');
break;
case 2004:
case 4005:
log.warn(`账号异常${status}`, `UID(${global_var.get('myUID')})异常号只会对部分UP出现异常`);
if (!config.is_exception) {
await sendNotify(
`[动态抽奖]账号异常${status}通知`,
`UID: ${global_var.get('myUID')}\n异常号只会对部分UP出现异常\n可在设置中令is_exception为true关闭此推送\n${log._cache.filter(it => /Error|\s抽奖信息\]/.test(it)).join('\n')}`
);
}
config.is_exception = true;
event_bus.emit('Turn_on_the_Monitor');
break;
case 2005:
log.warn('关注已达上限', `UID(${global_var.get('myUID')})关注已达上限,已临时进入只转已关注模式`);
if (!config.is_outof_maxfollow) {
await sendNotify(
'[动态抽奖]关注已达上限',
`UID: ${global_var.get('myUID')}\n关注已达上限,已临时进入只转已关注模式\n可在设置中令is_outof_maxfollow为true关闭此推送\n${log._cache.filter(it => /Error|\s抽奖信息\]/.test(it)).join('\n')}`
);
}
config.is_outof_maxfollow = true;
config.only_followed = true;
event_bus.emit('Turn_on_the_Monitor');
break;
case 5001:
event_bus.emit('Turn_off_the_Monitor', '转发失败');
break;
default:
event_bus.emit('Turn_off_the_Monitor', `??? 未知错误: ${status}`);
break;
}
}
/**
* 启动
* @returns {Promise<number>}
*/
async startLottery() {
const allLottery = await this.filterLotteryInfo()
, len = allLottery.length
, { create_dy, create_dy_mode, wait, filter_wait } = config;
log.info('筛选动态', `筛选完毕(${len})`);
if (len) {
let
status = 0,
is_exception = 0,
is_outof_maxfollow = 0,
relayed_nums = 0,
total_nums = 0;
for (const [index, lottery] of shuffle(allLottery).entries()) {
total_nums += 1;
let is_shutdown = false;
if (
is_outof_maxfollow
&& lottery.uid.length
&& (new RegExp(lottery.uid.join('|'))).test(this.attentionList)
) {
log.info('过滤', `已关注(${lottery.uid.join(',')})`);
continue;
}
if (lottery.isOfficialLottery) {
let { ts } = await bili.getLotteryNotice(lottery.dyid);
const ts_10 = Date.now() / 1000;
if (ts === -1) {
log.warn('过滤', '无法判断开奖时间');
await delay(filter_wait);
continue;
}
if (ts === -9999) {
log.info('过滤', '已撤销抽奖');
d_storage.updateDyid(lottery.dyid);
await delay(filter_wait);
continue;
}
if (ts < ts_10) {
log.info('过滤', '已过开奖时间');
d_storage.updateDyid(lottery.dyid);
await delay(filter_wait);
continue;
}
if (ts > ts_10 + config.maxday * 86400) {
log.info('过滤', '超过指定开奖时间');
d_storage.updateDyid(lottery.dyid);
await delay(filter_wait);
continue;
}
} else if (lottery.uid[0]) {
const { minfollower } = config;
if (minfollower > 0) {
const followerNum = await bili.getUserInfo(lottery.uid[0]);
if (followerNum === -1) {
log.warn('过滤', `粉丝数(${followerNum})获取失败`);
await delay(filter_wait);
continue;
}
if (followerNum < minfollower) {
log.info('过滤', `粉丝数(${followerNum})小于指定数量`);
d_storage.updateDyid(lottery.dyid);
await delay(filter_wait);
continue;
}
} else {
log.info('过滤', '不过滤粉丝数');
}
}
if (create_dy
&& create_dy_mode instanceof Array
&& index > 0
&& index % getRandomOne(create_dy_mode[0]) === 0
) {
const number = getRandomOne(create_dy_mode[1]) || 0;
randomDynamic(number);
}
status = await this.go(lottery);
switch (status) {
case 0:
relayed_nums += 1;
break;
case 1002:
case 1003:
case 1005:
case 1006:
case 1007:
case 1008:
case 1009:
case 1011:
case 2002:
case 2003:
case 3001:
case 4001:
case 4002:
case 4003:
case 4004:
case 5002:
case 5003:
case 5004:
case 5005:
status = 0;
break;
case 2004:
is_exception = 2004;
break;
case 4005:
is_exception = 4005;
break;
case 2005:
is_outof_maxfollow = 2005;
break;
case 1001:
case 1010:
case 1004:
case 2001:
case 5001:
is_shutdown = true;
break;
default:
break;
}
if (is_shutdown) break;
d_storage.updateDyid(lottery.dyid);
await delay(wait * (Math.random() + 0.5));
}
log.info('抽奖', `本轮共处理${total_nums}条,成功参与${relayed_nums}`);
return is_exception
|| is_outof_maxfollow
|| status;
} else {
log.info('抽奖', '无未转发抽奖');
return 0;
}
}
/**
* 抽奖配置
* @typedef {object} LotteryOptions
* @property {number[]} uid 用户标识
* @property {string} dyid 动态标识
* @property {boolean} isOfficialLottery 是否官方抽奖
* @property {string} relay_chat 转发词
* @property {string} ctrl 定位@
* @property {string} [rid] 评论标识
* @property {number} chat_type 评论类型
* @property {string} [chat] 评论词
*/
/**
* @returns {Promise<LotteryOptions[]>}
*/
async filterLotteryInfo() {
const { lottery_param, LotteryInfoMap, attentionList } = this;
/**
* @type {import("./searcher").LotteryInfo[]}
*/
let protoLotteryInfo = await LotteryInfoMap.get(lottery_param[0])(lottery_param[1]);
if (protoLotteryInfo === null)
return [];
log.info('筛选动态', `开始筛选(${protoLotteryInfo.length})`);
/** 所有抽奖信息 */
let alllotteryinfo = [];
const
{
check_if_duplicated, save_lottery_info_to_file,
set_lottery_info_url, disable_reserve_lottery,
is_not_relay_reserve_lottery,
reserve_lottery_wait, sneaktower, key_words,
model, chatmodel, chat: chats, relay: relays,
block_dynamic_type, max_create_time, is_imitator,
only_followed, at_users, blockword, blacklist,use_ai_comments
} = config,
now_ts = Date.now() / 1000;
/**
* @type {Map<String, Boolean>}
*/
let dyids_map = new Map();
/**去重 */
protoLotteryInfo = protoLotteryInfo.filter(({ dyid }) => {
if (dyids_map.has(dyid)) {
return false;
}
dyids_map.set(dyid, false);
return true;
});
log.info('筛选动态', `去重后(${protoLotteryInfo.length})`);
/**并发查询dyid */
if (check_if_duplicated >= 1) {
await Promise.all(
[...dyids_map.keys()]
.map(it => d_storage
.searchDyid(it)
.then(hasIt => dyids_map.set(it, hasIt))
)
);
log.info('筛选动态', '并发查询本地dyid完毕');
}
if (lottery_param[0] !== 'APIs' && save_lottery_info_to_file && protoLotteryInfo.length) {
log.info('保存抽奖信息', '保存开始');
await appendLotteryInfoFile(lottery_param[1].toString(), protoLotteryInfo);
}
if (lottery_param[0] !== 'APIs' && set_lottery_info_url && protoLotteryInfo.length) {
log.info('上传抽奖信息', '上传开始');
await new Promise((resolve) => {
send({
url: set_lottery_info_url,
method: 'POST',
headers: {
'content-type': 'application/json'
},
contents: protoLotteryInfo,
success: ({ body }) => {
log.info('发送获取到的动态数据', body);
resolve();
},
failure: err => {
log.error('发送获取到的动态数据', err);
resolve();
}
});
});
}
/* 检查动态是否满足要求 */
await try_for_each(protoLotteryInfo, async function (lottery_info) {
const {
lottery_info_type, is_liked,
uids, uname, dyid, reserve_id,
reserve_lottery_text,
is_charge_lottery,
create_time, chat_type,
ctrl, rid, des, type,
hasOfficialLottery
} = lottery_info;
log.debug('正在筛选的动态信息', lottery_info);
if (lottery_info_type.startsWith('sneak') && sneaktower) {
log.info('筛选动态', `偷塔模式不检查是否已转发(https://t.bilibili.com/${dyid})`);
} else {
/* 遇到转发过就退出 */
if (
((!check_if_duplicated || check_if_duplicated >= 2) && is_liked)
|| ((check_if_duplicated >= 1) && dyids_map.get(dyid))
) {
log.info('筛选动态', `已转发(https://t.bilibili.com/${dyid})`);
return false;
}
}
/* 超过指定时间退出 */
if (create_time && now_ts - create_time > max_create_time * 86400) {
log.info('筛选动态', `过时动态(https://t.bilibili.com/${dyid})`);
return false;
}
if (is_charge_lottery) {
log.info('筛选动态', `充电抽奖(https://t.bilibili.com/${dyid})`);
return false;
}
const
[m_uid, ori_uid] = uids,
mIsFollowed = !m_uid || (new RegExp(m_uid)).test(attentionList),
oriIsFollowed = !ori_uid || (new RegExp(ori_uid)).test(attentionList),
/**判断是转发源动态还是现动态 实际发奖人*/
[real_uid, realIsFollowed] = lottery_info_type === 'uid'
? [ori_uid, oriIsFollowed]
: [m_uid, mIsFollowed],
description = des.split(/\/\/@.*?:/)[0],
needAt = /(?:@|艾特)[^@|(艾特)]*?好友/.test(description),
needTopic = [...new Set(description.match(/(?<=[带加]上?(?:话题|tag).*)#.+?#|(?<=[带加])上?#.+?#(?=话题|tag)/ig) || [])].join(' '),
isRelayDynamic = type === 1,
has_key_words = key_words.every(it => new RegExp(it).test(description)),
isBlock = blockword.length && new RegExp(blockword.join('|')).test(description + reserve_lottery_text),
isLottery =
(is_imitator && lottery_info_type === 'uid' && model !== '00')
|| (hasOfficialLottery && model[0] === '1')
|| (!hasOfficialLottery && model[1] === '1' && has_key_words),
isSendChat =
(hasOfficialLottery && chatmodel[0] === '1')
|| (!hasOfficialLottery && chatmodel[1] === '1'),
keys = [dyid, m_uid, ori_uid];
log.debug('筛选动态', { real_uid, mIsFollowed, oriIsFollowed, realIsFollowed, needAt, needTopic, type, isRelayDynamic, key_words, has_key_words, blockword, isBlock, isLottery, isSendChat });
if (
blacklist.split(',').some(id => keys.some(key => {
if (key + '' === id) {
log.info('筛选动态', `黑名单匹配(${id})(https://t.bilibili.com/${dyid})`);
return true;
} else {
return false;
}
}))
) {
return false;
}
if (block_dynamic_type.includes(type)) {
log.warn('筛选动态', `屏蔽动态类型 ${type}`);
return false;
}
/**屏蔽词 */
if (isBlock) {
log.info('筛选动态', `包含屏蔽词(https://t.bilibili.com/${dyid})`);
return false;
}
if (reserve_id) {
if (disable_reserve_lottery) {
log.info('已关闭预约抽奖功能');
} else {
log.info('预约抽奖', '开始');
log.info('预约抽奖', `奖品: ${reserve_lottery_text}`);
if (hasEnv('NOT_GO_LOTTERY')) {
log.info('NOT_GO_LOTTERY', 'ON');
} else {
await delay(reserve_lottery_wait);
await bili.reserve_lottery(reserve_id);
}
}
if (is_not_relay_reserve_lottery === true) {
log.info('预约抽奖', '已关闭预约抽奖转关功能');
return false;
}
}
if (!hasOfficialLottery && model[1] === '1' && !has_key_words && description) {
log.warn('筛选动态', `无关键词动态的描述: ${description}\n\n考虑是否修改设置key_words:\n${key_words.join('\n且满足: ')}`);
}
/**若勾选只转已关注 */
if (only_followed
&& (!mIsFollowed || !oriIsFollowed)
) {
log.info('筛选动态', `只转已关注(https://t.bilibili.com/${dyid})`);
return false;
}
if (isLottery) {
let onelotteryinfo = {};
onelotteryinfo.isOfficialLottery = hasOfficialLottery;
/**初始化待关注列表 */
onelotteryinfo.uid = [];
if (!realIsFollowed) {
onelotteryinfo.uid.push(real_uid);
}
onelotteryinfo.dyid = dyid;
let
/**转发评语 */
RandomStr = (getRandomOne(relays) || '!!!')
.replace(/\$\{uname\}/g, uname),
/**控制字段 */
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 (isRelayDynamic) {
/* 转发内容长度+'//'+'@'+用户名+':'+源内容 */
const addlength = RandomStr.length + 2 + uname.length + 1 + 1;
onelotteryinfo.relay_chat = RandomStr + `//@${uname}:` + des;
new_ctrl.push({
data: String(real_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 (!oriIsFollowed) {
onelotteryinfo.uid.push(ori_uid);
}
} else {
onelotteryinfo.relay_chat = RandomStr;
}
onelotteryinfo.ctrl = JSON.stringify(new_ctrl);
/* 根据动态的类型决定评论的类型 */
onelotteryinfo.chat_type = chat_type;
/* 是否评论 */
if (isSendChat) {
onelotteryinfo.rid = rid;
if (use_ai_comments) {
try {
log.info('开始获取Ai评论', `(https://t.bilibili.com/${dyid})`);
onelotteryinfo.chat = await getAiContent(lottery_info.des);
//(getRandomOne(chats) || '!!!').replace(/\$\{uname\}/g, uname);
log.info('Ai评论内容', `${onelotteryinfo.chat}`);
} catch (e) {
log.error('获取AI评论失败使用随机评论', e);
onelotteryinfo.chat = (getRandomOne(chats) || '!!!').replace(/\$\{uname\}/g, uname);
}
} else {
onelotteryinfo.chat = (getRandomOne(chats) || '!!!').replace(/\$\{uname\}/g, uname);
}
}
alllotteryinfo.push(onelotteryinfo);
} else {
log.info('筛选动态', `非抽奖动态(https://t.bilibili.com/${dyid})`);
}
});
return alllotteryinfo;
}
/**
* 关注转发评论
* @param {LotteryOptions} option
* @returns {Promise<number>}
* - 成功 0
* - 评论 未知错误 1001
* - 评论 原动态已删除 1002
* - 评论 评论区已关闭 1003
* - 评论 需要输入验证码 1004
* - 评论 已被对方拉入黑名单 1005
* - 评论 黑名单用户无法互动 1006
* - 评论 UP主已关闭评论区 1007
* - 评论 内容包含敏感信息 1008
* - 评论 重复评论 1009
* - 评论 帐号未登录 1010
* - 评论 关注UP主7天以上的人可发评论 1011
* - 关注 未知错误 2001
* - 关注 您已被对方拉入黑名单 2002
* - 关注 黑名单用户无法关注 2003
* - 关注 账号异常 2004
* - 关注 关注已达上限 2005
* - 分区 移动失败 3001
* - 点赞 未知错误 4001
* - 点赞 点赞异常 4002
* - 点赞 点赞频繁 4003
* - 点赞 已赞过 4004
* - 点赞 账号异常,点赞失败 4005
* - 转发 未知错误 5001
* - 转发 该动态不能转发分享 5002
* - 转发 请求数据发生错误,请刷新或稍后重试 5003
* - 转发 操作太频繁了,请稍后重试 5004
* - 转发 源动态禁止转发 5005
*/
async go(option) {
log.debug('正在转发的动态信息', option);
if (hasEnv('NOT_GO_LOTTERY')) {
log.info('NOT_GO_LOTTERY', 'ON');
return 0;
}
let
status = 0,
{ uid, dyid, chat_type, rid, relay_chat, ctrl, chat } = option,
{ check_if_duplicated, is_copy_chat, copy_blockword, is_repost_then_chat, is_not_create_partition } = config;
/* 评论 */
if (rid && chat_type) {
if (is_copy_chat) {
const copy_chat = getRandomOne(
(await bili.getChat(rid, chat_type))
.filter(it => !(new RegExp(copy_blockword.join('|')).test(it[1])))
) || [';;;;;;;;;', chat || '!!!'];
chat = copy_chat[1]
.replace(
new RegExp(copy_chat[0], 'g'),
global_var.get('myUNAME') || '');
} else {
if (is_repost_then_chat) {
chat = chat + relay_chat;
}
}
status = await retryfn(
6,
[4],
() => bili.sendChatWithOcr(
rid,
chat,
chat_type
)
);
if (status === 8 ||
status === 9) {
status = await bili.sendChatWithOcr(
rid,
'[doge][doge][doge][doge][doge]',
chat_type
);
}
if (status) {
log.warn('抽奖信息', `dyid: ${dyid}, rid: ${rid}, chat_type: ${chat_type}`);
return 1000 + status;
}
}
/* 关注 */
if (uid.length) {
await try_for_each(uid, async (u) => {
status = await bili.autoAttention(u);
if (status === 6) {
status = 0;
return false;
}
if (status) {
log.warn('抽奖信息', `dyid: ${dyid}, uid: ${u}`);
return true;
} else {
if (is_not_create_partition !== true) {
if (await bili.movePartition(u, this.tagid)) {
log.warn('抽奖信息', `dyid: ${dyid}, uid: ${u} tagid: ${this.tagid}`);
/* 3000系错误 */
status = 1001;
return true;
} else {
return false;
}
} else {
return false;
}
}
});
if (status) return 2000 + status;
}
/* 点赞 */
if (!check_if_duplicated || check_if_duplicated === 3) {
status = await retryfn(
5,
[3],
() => bili.autolike(dyid)
);
if (status) {
log.warn('抽奖信息', `dyid: ${dyid}`);
return 4000 + status;
}
}
/* 转发 */
if (dyid) {
status = await retryfn(
5,
[3, 4],
() => bili.autoRelay(
global_var.get('myUID'),
dyid,
relay_chat,
ctrl)
);
if (status) {
log.warn('抽奖信息', `dyid: ${dyid}`);
return 5000 + status;
}
}
return status;
}
}
module.exports = { Monitor };