UNPKG

@ginstone/nga-api

Version:

337 lines (336 loc) 13.7 kB
"use strict"; 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; };