LotteryAutoScript/lib/Public.js
2021-07-29 12:57:14 +08:00

365 lines
13 KiB
JavaScript

const Util = require('./Util');
const BiliAPI = require('./BiliAPI');
const config = require("./config");
const MyStorage = require('./MyStorage');
const { log } = Util
/**
* 解析dynamic_detail_card
* 提取出的有用动态信息
* @typedef {object} UsefulDynamicInfo
* @property {number} uid
* @property {string} uname
* @property {number} createtime 10
* @property {string} rid_str
* @property {string} dynamic_id
* @property {number} type
* @property {string} description
* @property {boolean} hasOfficialLottery
* @property {Array<Object.<string,string|number>>} ctrl
*
* @property {number} origin_uid
* @property {string} origin_uname
* @property {string} origin_rid_str
* @property {string} origin_dynamic_id
* @property {number} orig_type
* @property {string} origin_description
* @property {boolean} origin_hasOfficialLottery
*
* @param {object} dynamic_detail_card
* @return {UsefulDynamicInfo}
*/
function parseDynamicCard(dynamic_detail_card) {
const { strToJson } = Util;
/**临时储存单个动态中的信息 */
let obj = {};
const { desc, card, extension, extend_json } = dynamic_detail_card
, { info } = desc.user_profile
, cardToJson = strToJson(card)
, { item } = cardToJson;
/* 转发者的UID */
obj.uid = info.uid;
/* 转发者的name */
obj.uname = info.uname;
/* 动态的ts10 */
obj.createtime = desc.timestamp
/* 动态类型 */
obj.type = desc.type
/* 用于发送评论 */
obj.rid_str = desc.rid_str.length > 12 ? desc.dynamic_id_str : desc.rid_str;
/* 源动态类型 */
obj.orig_type = desc.orig_type
/* 转发者的动态ID !!!!此为大数需使用字符串值,不然JSON.parse()会有丢失精度 */
obj.dynamic_id = desc.dynamic_id_str;
/* 定位@信息 */
obj.ctrl = (extend_json && strToJson(extend_json).ctrl) || [];
/* 是否有官方抽奖 */
obj.hasOfficialLottery = extension && extension.lott && true;
/* 转发者的描述 后两个分别是视频动态的描述和视频本身的描述*/
obj.description = (item && (item.content || item.description || cardToJson.dynamic || cardToJson.desc)) || '';
if (obj.type === 1) {
const { origin_extension, origin } = cardToJson
, originToJson = strToJson(origin)
, { user, item } = originToJson;
/* 被转发者的UID */
obj.origin_uid = desc.origin.uid;
/* 被转发者的rid(用于发评论) */
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;
/* 是否有官方抽奖 */
obj.origin_hasOfficialLottery = origin_extension && origin_extension.lott;
/* 被转发者的name */
obj.origin_uname = (user && (user.name || user.uname)) || '';
/* 被转发者的描述 */
obj.origin_description = (item && (item.content || item.description || originToJson.dynamic || originToJson.desc)) || '';
}
return obj
}
/**
* 处理来自个人动态或话题页面的一组动态数据
* @param {String} res
* @returns {{modifyDynamicResArray: UsefulDynamicInfo[], nextinfo: {has_more: number, next_offset: string}} | UsefulDynamicInfo |null}
*/
function modifyDynamicRes(res) {
const
{ data = {}, code } = Util.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(parseDynamicCard)
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 {number} hostuid 指定的用户UID
* @param {number} pages 读取页数
* @param {number} time 时延
* @param {string} [offset] 默认'0'
* @returns {Promise<{allModifyDynamicResArray: UsefulDynamicInfo[], offset: string}>} 获取前 `pages*12` 个动态信息
*/
async checkAllDynamic(hostuid, pages, time = 0, offset = '0') {
log.info('检查所有动态', `准备读取${pages}页动态`);
const { getOneDynamicInfoByUID } = BiliAPI,
/**
* 柯里化请求函数
*/
curriedGetOneDynamicInfoByUID = Util.curryify(getOneDynamicInfoByUID),
/**
* 储存了特定UID的请求函数
*/
hadUidGetOneDynamicInfoByUID = curriedGetOneDynamicInfoByUID(hostuid);
/**
* 储存所有经过整理后信息
* @type { UsefulDynamicInfo[] }
*/
let allModifyDynamicResArray = [];
for (let i = 0; i < pages; i++) {
log.info('检查所有动态', `正在读取其中第${i + 1}页动态`);
const
OneDynamicInfo = await hadUidGetOneDynamicInfoByUID(offset),
mDRdata = modifyDynamicRes(OneDynamicInfo);
if (mDRdata === null) {
break;
}
const
/**
* 一片动态
*/
mDRArry = mDRdata.modifyDynamicResArray,
nextinfo = mDRdata.nextinfo;
if (nextinfo.has_more === 0) {
offset = nextinfo.next_offset;
log.info('检查所有动态', `已经是最后一页了故无法读取更多`);
break;
} else {
/**合并 */
allModifyDynamicResArray.push.apply(allModifyDynamicResArray, mDRArry);
offset = nextinfo.next_offset;
}
await Util.delay(time);
}
log.info('检查所有动态', `${pages}页信息读取完成`)
return ({ allModifyDynamicResArray, offset });
}
/**
* 获取最新动态信息(转发子动态)
* 并初步整理
* @param {string} UID
* @returns {Promise<LotteryInfo[] | null>}
*/
async getLotteryInfoByUID(UID) {
log.info('获取动态', `开始获取用户${UID}的动态信息`);
const { allModifyDynamicResArray } = await this.checkAllDynamic(UID, config.uid_scan_page, config.search_wait);
if (!allModifyDynamicResArray.length) return null;
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 fomatdata;
}
/**
* 获取tag下的抽奖信息(转发母动态)
* 并初步整理
* @param {string} tag_name
* @returns {Promise<LotteryInfo[] | null>}
*/
async getLotteryInfoByTag(tag_name) {
const
tag_id = await BiliAPI.getTagIDByTagName(tag_name),
hotdy = await BiliAPI.getHotDynamicInfoByTagID(tag_id),
modDR = modifyDynamicRes(hotdy);
if (modDR === null) return null;
log.info('获取动态', `开始获取带话题#${tag_name}#的动态信息`);
log.info('获取动态', '成功获取热门动态');
/**
* 热门动态
*/
let mDRdata = modDR.modifyDynamicResArray;
let next_offset = modDR.nextinfo.next_offset;
for (let index = 0; index < config.tag_scan_page; index++) {
log.info('获取动态', `读取第${index + 1}页动态`);
const
newdy = await BiliAPI.getOneDynamicInfoByTag(tag_name, next_offset),
_modify = modifyDynamicRes(newdy);
if (_modify === null) return null;
mDRdata.push.apply(mDRdata, _modify.modifyDynamicResArray);
next_offset = _modify.nextinfo.next_offset;
await Util.delay(config.search_wait);
}
const fomatdata = mDRdata.map(o => {
return {
lottery_info_type: 'tag',
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('获取动态', `成功获取带话题#${tag_name}#的动态信息`);
return fomatdata
}
/**
* 从专栏中获取抽奖信息
* @param {string} key_words
* @returns {Promise<LotteryInfo[] | null>}
*/
async getLotteryInfoByArticle(key_words) {
log.info('获取动态', `开始获取含关键词${key_words}的专栏信息`);
const cvs = (await BiliAPI.searchArticlesByKeyword(key_words)).slice(0, config.article_scan_page);
let dyinfos = [];
for (const cv of cvs) {
const
content = await BiliAPI.getOneArticleByCv(cv),
dyids = content.match(/(?<=t.bilibili.com\/)[0-9]+/g) || [],
dyids_set = [...new Set(dyids)],
/**判断此专栏是否查看过的权重 */
weight = dyids_set.length / 2;
let { length } = dyids_set;
log.info('获取动态', `提取专栏(${cv})中提及的dyid(${length})`)
for (const dyid of dyids_set) {
let _weight = 0;
const isRelayed = await MyStorage.searchDyid(dyid);
if (isRelayed) {
_weight += 1;
}
if (_weight >= weight) {
log.info('获取动态', `1/2动态曾经转过,该专栏或已查看,故中止`)
dyinfos = []
break
}
if (dyid.length === Util.dyid_length) {
if (!isRelayed) {
log.info('获取动态', `查看动态(${dyid})的细节 (${length--})`)
const res = await BiliAPI.getOneDynamicByDyid(dyid)
, { code, data = {} } = Util.strToJson(res)
, { card } = data;
if (code !== 0) {
log.error('获取动态', '获取动态数据出错,可能是访问太频繁 \n' + res)
break
}
await Util.delay(2000)
if (card) {
dyinfos.push(parseDynamicCard(card));
}
} else {
log.info('获取动态', `动态(${dyid})已转发过 (${length--})`)
}
} else {
log.warn('获取动态', `动态(${dyid})无效 (${length--})`)
}
}
}
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
}
}
module.exports = Public;