feat(adapters): NexusPHP takelogin login, VIP and seeding page limit for user data
loginNexusPhp (captcha / two-factor / bad credentials / blocked / Cloudflare classification, TOTP), SiteUserInfo.vip via profile.vip_css or umt.vipLevels, maxSeedingPages option, MTorrent parser honours apiUrl; testkit mock NexusPHP site gains takelogin with login guards and user detail pages. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
5ee7ca4fee
commit
d6fca25679
10 files changed
+384
-8
No files matched your search
@@ -21,6 +21,7 @@
|
||||
* 3. 网络异常与非 200/500/403 状态:与 MP 一样返回空串继续,另把脱敏后的原因记在 `httpErrors`(入 raw)。
|
||||
* 4. 响应解码:按 Content-Type / `<meta charset>` 选 TextDecoder(MP `get_decoded_html_content` 用 chardet 探测)。
|
||||
*/
|
||||
import { load as loadCheerio } from "cheerio";
|
||||
import { redactText } from "../../rss/redact.ts";
|
||||
import { parseSizeMp } from "../spider/spider.ts";
|
||||
import { extractDomain, isLoggedIn, underChallenge } from "./common.ts";
|
||||
@@ -259,7 +260,8 @@ export abstract class SiteParserBase {
|
||||
),
|
||||
);
|
||||
let guard = 0;
|
||||
while (nextPage !== null && nextPage !== undefined && nextPage !== false && guard++ < MAX_PAGES) {
|
||||
const maxExtra = Math.max(0, (this._opts.maxSeedingPages ?? MAX_PAGES + 1) - 1);
|
||||
while (nextPage !== null && nextPage !== undefined && nextPage !== false && guard++ < maxExtra) {
|
||||
nextPage = await this._parseUserTorrentSeedingInfo(
|
||||
await this._getPageContent(
|
||||
urljoin(urljoin(this._baseUrl, this._torrentSeedingPage), nextPage),
|
||||
@@ -407,6 +409,23 @@ export abstract class SiteParserBase {
|
||||
}
|
||||
|
||||
/** MP `refresh_userdata` 里构造 `SiteUserData` 的部分 */
|
||||
/** VIP 判定(本项目扩展,见 SiteUserInfo.vip) */
|
||||
protected detectVip(): boolean | null {
|
||||
const css = this._opts.vipCss?.replace(/::.*$/, "").trim();
|
||||
const levels = this._opts.vipLevels?.filter(Boolean) ?? [];
|
||||
if (!css && !levels.length) return null;
|
||||
if (css && typeof this._indexHtml === "string" && this._indexHtml) {
|
||||
try {
|
||||
if (loadCheerio(this._indexHtml)(css).length > 0) return true;
|
||||
} catch {
|
||||
// 选择器不合法按未命中处理
|
||||
}
|
||||
}
|
||||
const level = (this.userLevel ?? "").toLowerCase();
|
||||
if (level && levels.some((l) => level.includes(l.toLowerCase()))) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
toUserInfo(): SiteUserInfo {
|
||||
const num = (v: unknown) => {
|
||||
const n = typeof v === "number" ? v : Number(v);
|
||||
@@ -435,6 +454,7 @@ export abstract class SiteParserBase {
|
||||
seedingInfo: this.seedingInfo.map(([a, b]) => [num(a), num(b)] as [number, number]),
|
||||
messageUnread: num(this.messageUnread),
|
||||
messageUnreadContents: messages,
|
||||
vip: this.detectVip(),
|
||||
errMsg: this.errMsg,
|
||||
raw: this.toDict(),
|
||||
};
|
||||
|
||||
@@ -48,7 +48,8 @@ export class MTorrentSiteUserInfo extends SiteParserBase {
|
||||
|
||||
protected _parseSitePage(_htmlText: string): void {
|
||||
// 更换 api 地址
|
||||
this._baseUrl = `https://api.${extractDomain(this._baseUrl)}`;
|
||||
// 本项目扩展:站点配置了独立 API 地址(site.config.apiBaseUrl)时用它
|
||||
this._baseUrl = this._apiUrl ? this._apiUrl.replace(/\/+$/, "") : `https://api.${extractDomain(this._baseUrl)}`;
|
||||
this._userTrafficPage = null;
|
||||
this._userDetailPage = null;
|
||||
this._userBasicPage = "api/member/profile";
|
||||
|
||||
@@ -69,6 +69,11 @@ export interface SiteUserInfo {
|
||||
messageUnreadContents: SiteUnreadMessage[];
|
||||
/** H&R 未达标数:MP 解析器不提供,保留字段供站点扩展 */
|
||||
hrCount?: number;
|
||||
/**
|
||||
* VIP(本项目扩展):定义 `umt.profile.vip_css` 在首页命中,或 `umt.vipLevels` 与等级匹配 → true;
|
||||
* 两者都没配置为 null(不显示)
|
||||
*/
|
||||
vip: boolean | null;
|
||||
/** 解析错误说明(MP `err_msg`,如"未检测到已登陆") */
|
||||
errMsg: string | null;
|
||||
raw: Record<string, unknown>;
|
||||
@@ -93,4 +98,10 @@ export interface SiteUserParserOptions {
|
||||
/** 共享站点限流器(与 list/search 共用) */
|
||||
limiter?: { acquire(signal?: AbortSignal): Promise<void> };
|
||||
signal?: AbortSignal;
|
||||
/** 做种分页上限(含第一页);缺省 50 */
|
||||
maxSeedingPages?: number;
|
||||
/** VIP 判定:首页选择器(定义 umt.profile.vip_css,可带 ::attr / ::text 后缀,判定时去掉) */
|
||||
vipCss?: string | null;
|
||||
/** VIP 判定:等级名列表(定义 umt.vipLevels),不区分大小写包含匹配 */
|
||||
vipLevels?: readonly string[] | null;
|
||||
}
|
||||
Reference in new issue
Block a user