/** * SPDX-License-Identifier: GPL-3.0-only * 移植自 MoviePilot(https://github.com/jxxghp/MoviePilot,GPLv3),本文件按 GPLv3 条款提供。 * @mp-source app/modules/indexer/__init__.py @ e6820a8 * * 站点用户信息解析器注册表与刷新入口。 * - `IndexerModule.__init__` 里 `ModuleHelper.load('app.modules.indexer.parser', filter=有 schema)` → `USER_PARSER_CLASSES` * (按模块文件名字母序,与 pkgutil 遍历顺序一致;回退时按此顺序尝试) * - `IndexerModule.refresh_userdata` → `refreshSiteUserData`:按站点声明的 schema 取解析器;解析后 userid 为空且非公开站时, * 依次尝试其他 schema(兼容 schema 标注错误/变种站),其他 schema 的异常只记录不抛出。 * * 与 MP 一致:声明 schema 解析器的异常原样抛出(由调用方记录),只有回退尝试的异常被吞掉。 * 差异:结果不含凭据;`fetchMessages` 默认 false(MP 由全局 SITE_MESSAGE 控制)。 */ import type { SiteParserBase, SiteParserClass } from "./base.ts"; import { BitptSiteUserInfo } from "./bitpt.ts"; import { extractDomain } from "./common.ts"; import { DiscuzUserInfo } from "./discuz.ts"; import { FileListSiteUserInfo } from "./file-list.ts"; import { GazelleSiteUserInfo } from "./gazelle.ts"; import { HDDolbySiteUserInfo } from "./hddolby.ts"; import { IptSiteUserInfo } from "./ipt.ts"; import { MTorrentSiteUserInfo } from "./mtorrent.ts"; import { NexusAudiencesSiteUserInfo } from "./nexus-audiences.ts"; import { NexusHhanclubSiteUserInfo } from "./nexus-hhanclub.ts"; import { NexusPhpSiteUserInfo } from "./nexus-php.ts"; import { NexusProjectSiteUserInfo } from "./nexus-project.ts"; import { NexusRabbitSiteUserInfo } from "./nexus-rabbit.ts"; import { RousiSiteUserInfo } from "./rousi.ts"; import { SmallHorseSiteUserInfo } from "./small-horse.ts"; import { SunnyPTSiteUserInfo } from "./sunnypt.ts"; import { TNodeSiteUserInfo } from "./tnode.ts"; import { TorrentLeechSiteUserInfo } from "./torrent-leech.ts"; import type { SiteSchemaValue, SiteUserInfo, SiteUserParserOptions } from "./types.ts"; import { Unit3dSiteUserInfo } from "./unit3d.ts"; import { YemaSiteUserInfo } from "./yema.ts"; import { ZhixingSiteUserInfo } from "./zhixing.ts"; /** 全部解析器(模块字母序:bitpt, discuz, file_list, gazelle, hddolby, ipt, mtorrent, nexus_*, rousi, …) */ export const USER_PARSER_CLASSES: readonly SiteParserClass[] = [ BitptSiteUserInfo, DiscuzUserInfo, FileListSiteUserInfo, GazelleSiteUserInfo, HDDolbySiteUserInfo, IptSiteUserInfo, MTorrentSiteUserInfo, NexusAudiencesSiteUserInfo, NexusHhanclubSiteUserInfo, NexusPhpSiteUserInfo, NexusProjectSiteUserInfo, NexusRabbitSiteUserInfo, RousiSiteUserInfo, SmallHorseSiteUserInfo, SunnyPTSiteUserInfo, TNodeSiteUserInfo, TorrentLeechSiteUserInfo, Unit3dSiteUserInfo, YemaSiteUserInfo, ZhixingSiteUserInfo, ] as unknown as SiteParserClass[]; export function getUserParserClass(schema: string | null | undefined): SiteParserClass | undefined { if (!schema) return undefined; return USER_PARSER_CLASSES.find((c) => c.schema === schema); } /** refresh_userdata 的站点入参(MP site dict 子集) */ export interface SiteUserDataRequest extends Omit { name: string; /** 站点声明的解析 schema(SiteSchema 值) */ schema?: string | null; /** 公开站:不报"未找到解析器",也不做 schema 回退 */ public?: boolean; } /** MP `refresh_userdata`:未找到解析器返回 null */ export async function refreshSiteUserData(site: SiteUserDataRequest): Promise { const make = (schema: string | null | undefined): SiteParserBase | null => { const cls = getUserParserClass(schema); if (!cls) return null; const { name, schema: _s, public: _p, ...rest } = site; return new cls({ ...rest, siteName: name }); }; let obj = make(site.schema); if (!obj) return null; try { await obj.parse(); if (!obj.userid && !site.public) { const tried = new Set([String(site.schema)]); for (const cls of USER_PARSER_CLASSES) { const value = cls.schema as SiteSchemaValue | null; if (!value || tried.has(value)) continue; tried.add(value); const alt = make(value); if (!alt) continue; try { await alt.parse(); } catch { continue; } if (alt.userid) { obj = alt; break; } } } const info = obj.toUserInfo(); info.domain = extractDomain(site.url) || null; return info; } finally { obj.clear(); } }