@ginstone/nga-api
Version:
337 lines (336 loc) • 13.7 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.ReplyDocInfoRaw = exports.ReadDocBody = exports.TAB_PLACEHOLDER = void 0;
const Navigation_1 = require("../Navigation");
const NgaLinkType_1 = require("../../enums/NgaLinkType");
const StrUtils_1 = require("../../utils/StrUtils");
const WebUtils_1 = require("@ginstone/common-utils/dist/src/utils/WebUtils");
const UID_PATTERN = /__CURRENT_UID = parseInt\('(\d+)',/;
const CU_PATTERN = /__GP.init\('(.*)','(.*)','(.*)','(.*)','(.*)',''\)/;
const CUSTOM_LEVEL_PATTERN = /__CUSTOM_LEVEL=(.+)/;
const FID_PATTERN = /__CURRENT_FID.*?(-*\d+)/;
const TID_PATTERN = /__CURRENT_TID.*?(\d+)/;
const PAGE_PATTERN = /__PAGE = \{0:'.+?',1:(\d+),2:(\d+),3:(\d+)}/;
const USER_INFO_PATTERN = /commonui\.userInfo\.setAll\((.+)\)/;
const POST_ARG_PATTERN = /commonui.postArg.proc\((.+)\)if/;
const POST_ARG_SET_PATTERN = /commonui.postArg.setDefault\((.+)\)/;
const SCRIPT_PATTERN = /'(.*?)'/g;
const ALTER_INFO_PATTERN = /loadAlertInfo\('(.+?)'/;
const REPLY_TO_PATTERN = /pid=(\d+),\d+,\d+/;
const ATTACH_PATTERN = /attach\.load.+?(\[.+?])/;
const SP = "-";
const SP_1 = "|";
exports.TAB_PLACEHOLDER = "{制表符占位}";
/**
* read.php 解析网页得到的数据
*/
class ReadDocBody {
constructor(p) {
var _a;
const { html, doc } = p;
// 导航栏
const navigation = new Navigation_1.Navigation(doc);
// 当前用户
{
const matcher = UID_PATTERN.exec(html);
const cuMatcher = CU_PATTERN.exec(html);
const group_bit = cuMatcher ? Number(cuMatcher[1]) : 0;
const admincheck = cuMatcher ? Number(cuMatcher[2]) : 0;
const rvrc = cuMatcher ? Number(cuMatcher[3]) : 0;
const uid = matcher ? Number(matcher[1]) : 0;
this.__CU = { admincheck, group_bit, rvrc, uid, };
}
// 版面信息
{
const forumLink = navigation.findLast(NgaLinkType_1.NgaLinkTypeName.FORUM);
const colLink = navigation.findLast(NgaLinkType_1.NgaLinkTypeName.COL);
// 自定义声望等级
const customLevelMatcher = CUSTOM_LEVEL_PATTERN.exec(html);
const custom_level = customLevelMatcher ? customLevelMatcher[1] : undefined;
// 版面id
const fidMatcher = FID_PATTERN.exec(html);
console.log(fidMatcher);
const fid = fidMatcher ? Number(fidMatcher[1]) : undefined;
// 版面名称
const name = forumLink ? forumLink.title : undefined;
// 合集名称
const set_topic_subject = colLink ? colLink.title : undefined;
const set_topic_tid = colLink ? Number((_a = colLink.link.queryParam) === null || _a === void 0 ? void 0 : _a.get("stid")) : undefined;
// 合集id
this.__F = { custom_level, fid, name, set_topic_subject, set_topic_tid };
}
// 用户信息
{
const matcher = USER_INFO_PATTERN.exec(html);
if (matcher) {
const jsonString = matcher[1].trim().replace(/\t/g, exports.TAB_PLACEHOLDER);
try {
this.__U = matcher ? JSON.parse(jsonString) : {};
}
catch (e) {
console.warn(e);
console.log(jsonString);
}
}
}
// 主题信息
{
const topicLink = navigation.findLast(NgaLinkType_1.NgaLinkTypeName.TOPIC);
const tidMatcher = TID_PATTERN.exec(html);
console.log(tidMatcher);
const tid = tidMatcher ? Number(tidMatcher[1]) : undefined;
const subject = topicLink ? topicLink.title : undefined;
//todo
this.__T = {
tid,
fid: this.__F.fid,
subject,
};
}
// 回复信息
{
this.__R = [];
const replyTables = [...doc.getElementsByClassName("forumbox postbox")];
for (let i = 0; i < replyTables.length; i++) {
// 回复table 标签
const replyTable = replyTables[i];
// table标签后跟随的 script 标签
const script = replyTable.nextElementSibling;
const matcher = script ? POST_ARG_PATTERN.exec((0, StrUtils_1.clearLinkBreak)(script.innerHTML)) : undefined;
// 参数列表
if (matcher) {
let group = matcher[1].trim();
// 预处理
group = preHandle(group, SP);
const result = group.split(",")
.filter(i => !i.startsWith('$'))
.map(i => i.replace(/'/g, "").trim())
.map(i => {
if (i === 'null' || i === "") {
return undefined;
}
return i;
})
.filter(i => i !== 'null' && i !== "");
const reply = new ReplyDocInfoRaw(replyTable, result);
reply.tid = this.__T.tid;
// 解析投票数据
if (i === 0) {
const matcher = POST_ARG_SET_PATTERN.exec(html);
if (matcher) {
const params = preHandle(matcher[1], SP_1).split(",")
.map(i => i.replace(/\|/g, ","))
.map(i => i.replace(/'/g, ''));
if (params.length > 8) {
reply.vote = params[8];
}
}
}
this.__R.push(reply);
}
}
}
// 页信息
{
const matcher = PAGE_PATTERN.exec(html);
this.totalPage = matcher ? Number(matcher[1]) : 1;
this.__PAGE = matcher ? Number(matcher[2]) : 1;
this.__R__ROWS_PAGE = matcher ? Number(matcher[3]) : 20;
if (this.__PAGE === this.totalPage) {
// 如果当前是最后一页, 计算总回复数时采用保守估计
const numbers = new Array();
this.__R.map(i => i.lou).forEach(i => {
if (i != undefined)
numbers.push(i);
});
this.__ROWS = Math.max(...numbers) + 1;
}
else {
// 不是最后一页, 采用最大估计
this.__ROWS = this.totalPage * this.__R__ROWS_PAGE;
}
}
}
}
exports.ReadDocBody = ReadDocBody;
class ReplyDocInfoRaw {
// 补充字段
constructor(replyTable, result) {
// 解析script标签信息
//楼层
ReplyDocInfoRaw.handleResult(result, 0, (v) => this.lou = Number(v));
//回复id
ReplyDocInfoRaw.handleResult(result, 3, (v) => this.pid = Number(v));
//类型、状态
ReplyDocInfoRaw.handleResult(result, 4, (v) => this.type = Number(v));
// 作者id
ReplyDocInfoRaw.handleResult(result, 6, (v) => this.authorid = Number(v));
// 发布时间
ReplyDocInfoRaw.handleResult(result, 7, (v) => this.postdatetimestamp = Number(v));
// 推荐分 赞踩数
this.recommend = 0;
this.score = 0;
this.score_2 = 0;
ReplyDocInfoRaw.handleResult(result, 8, (v) => {
const split = v.split(SP);
this.recommend = Number(split[0]);
this.score = Number(split[1]);
this.score_2 = Number(split[2]);
});
// 客户端
ReplyDocInfoRaw.handleResult(result, 6, (v) => this.from_client = v);
// 礼物
if (result.length > 15 && result[15] && result[15] !== '0') {
this["14"] = {};
const arr = result[15].split(SP);
for (let i = 0; i < arr.length; i += 2) {
this["14"][arr[i]] = arr[i + 1];
}
}
// 解析 document信息
// html代码
const html = (0, StrUtils_1.clearLinkBreak)(replyTable.innerHTML).replace(/\t/g, "");
// 作者uid
handleElementById(replyTable, "postauthor" + this.lou, ele => this.authorid = parseUidFromATag(ele));
// 发布时间
handleElementById(replyTable, "postdate" + this.lou, ele => this.postdate = ele.innerHTML);
// 标题
handleElementById(replyTable, "postsubject" + this.lou, ele => this.subject = ele.innerHTML);
// 正文
handleElementById(replyTable, "postcontent" + this.lou, ele => this.content = ele.innerHTML);
// 改动信息
handleRegMatched(html, ALTER_INFO_PATTERN, matcher => this.alterinfo = matcher[1].trim());
// 回复或引用的id
handleRegMatched(this.content, REPLY_TO_PATTERN, matcher => this.reply_to = Number(matcher[1]));
// 贴条
handleElementById(replyTable, "comment_for_" + this.pid, ele => this.comment = parseComment(ele));
// 热评
handleElementById(replyTable, "hightlight_for_0", ele => this.hotreply = parseComment(ele));
// 附件
handleRegMatched(html, ATTACH_PATTERN, matcher => {
const group = (0, StrUtils_1.clearLinkBreak)(matcher[1])
.replace(/([,{])(\w+?):/g, "$1\"$2\":")
.replace(/'/g, "\"");
this.attachs = JSON.parse(group);
});
}
static handleResult(result, index, handle) {
if (result && result.length > index) {
const value = result[index];
if (value) {
handle(value);
}
}
}
}
exports.ReplyDocInfoRaw = ReplyDocInfoRaw;
/**
* 在指定父元素里查找指定id的子元素,如果找到调用回调方法
* @param e 父元素
* @param id id
* @param callback 回调方法
*/
const handleElementById = (e, id, callback) => {
const item = e.querySelector("#" + id);
if (item) {
callback(item);
}
};
/**
* 从字符串中查找匹配的字符串,如果匹配成功执行回调
* @param s 字符
* @param regExp 正则表达式
* @param callback 回调
*/
const handleRegMatched = (s, regExp, callback) => {
if (!s) {
return;
}
const matcher = regExp.exec(s);
if (matcher) {
callback((matcher));
}
};
/**
* 从a标签中解析uid
* @param e a标签
*/
const parseUidFromATag = (e) => {
const href = e.getAttribute("href");
if (href) {
const map = (0, WebUtils_1.parseQueryParam)(href);
const uid = map.get("uid");
if (uid) {
return Number(uid);
}
}
return undefined;
};
const parseComment = (root) => {
const comments = [...root.getElementsByClassName("comment_c left")];
return comments.map(comment => {
var _a;
const a = comment.querySelector(".posterinfo > a");
let res = {};
// 作者uid
if (a) {
res.authorid = parseUidFromATag(a);
const idSplit = (_a = a.getAttribute("id")) === null || _a === void 0 ? void 0 : _a.split("_");
res.pid = idSplit ? Number(idSplit[idSplit.length - 1]) : undefined;
// 回复时间
const replyTimeSpan = comment.querySelector("[title='reply time']");
if (replyTimeSpan) {
res.postdate = replyTimeSpan.innerHTML;
}
// 标题
handleElementById(comment, "postcommentsubject_" + res.pid, ele => res.subject = ele.innerHTML);
handleElementById(comment, "postcommentsubject__" + res.pid, ele => res.subject = ele.innerHTML);
// 正文
handleElementById(comment, "postcomment_" + res.pid, ele => res.content = ele.innerHTML);
handleElementById(comment, "postcomment__" + res.pid, ele => res.content = ele.innerHTML);
}
const tables = comment.getElementsByTagName("table");
if (tables && tables[0]) {
const script = tables[0].nextElementSibling;
if (script) {
let s = (0, StrUtils_1.clearLinkBreak)(script.innerHTML);
s = preHandle(s.substring(s.indexOf("(") + 1, s.lastIndexOf(")")), SP);
const list = s.split(",")
.map(i => i.replace(/'/g, "").trim())
.filter(i => !i.startsWith("_") && !i.startsWith("$"))
.map(item => {
if ("null" === item || "" === item) {
return undefined;
}
return item;
});
// 类型、状态
res.type = Number(list[5]);
// 时间戳
res.postdatetimestamp = Number(list[8]);
// 推荐分 赞踩
if (list[9]) {
const split = list[9].split(SP);
res.recommend = Number(split[0]);
res.score = Number(split[1]);
res.score_2 = Number(split[2]);
}
}
}
return res;
});
};
/**
* 参数列表预处理,将参数中的逗号替换为其他分隔符
* @param s 参数列表
* @param split 分隔符
* @return java.lang.String
* @since 2023/5/5 9:18
*/
const preHandle = (s, split) => {
let m;
while (m = SCRIPT_PATTERN.exec(s)) {
s = s.replace(m[0], m[0].replace(/,/g, split));
}
return s;
};