diff --git a/.dsh/skills/ptd-site-definition/SKILL.md b/.dsh/skills/ptd-site-definition/SKILL.md new file mode 100644 index 000000000..c51ef43de --- /dev/null +++ b/.dsh/skills/ptd-site-definition/SKILL.md @@ -0,0 +1,85 @@ +--- +name: ptd-site-definition +description: 为 PT-depiler 仓库新增或修改 PT/BT 站点定义文件(src/packages/site/definitions/*.ts)时使用。覆盖:判定站点所属引擎、编写 siteMetadata(id/urls/version/category/search/userInfo/levelRequirements)、编写选择器与 filters、覆写引擎类方法、以及提交前的类型检查与静态校验。Use when adding support for a new tracker site, editing an existing site definition, adapting a NexusPHP / Unit3D / Gazelle / GazelleJSONAPI / AvistazNetwork / Luminance / Rartracker / TCG site, or when the task mentions definitions/、schemas/、siteMetadata、search selectors、userInfo process。 +--- + +# PT-depiler 站点定义(site definition)编写 + +目标产物是 `src/packages/site/definitions/.ts`:一个文件定义一个站点,导出 `siteMetadata: ISiteMetadata`,按需再导出一个覆写引擎行为的 `default class`。本 skill 只讲「如何写这个文件」;修改 `types/` 公共类型或 `schemas/` 引擎实现属于另一层次的工作,除非用户明确要求,不要顺手改动。 + +## 先理解加载机制(决定了哪些东西必须写、哪些不用写) + +- `src/packages/site/index.ts:21-29` 用 `import.meta.glob` 自动收集 `schemas/*.ts` 与 `definitions/*.ts`。**新增定义文件不需要注册到任何列表**,文件名(去扩展名)就是 siteId。 +- 实例构造顺序见 `index.ts:79-102`: + 1. definition 导出了 `default class` → 直接用它; + 2. 否则取 `siteMetadata.schema` 指向的 schema 类; + 3. `schema` 值在 `schemas/` 里没有对应实现 → 按 `type` 回落 `AbstractPrivateSite`(private)或 `AbstractBittorrentSite`(public)。 +- `index.ts:41-42`:`siteMetadata.schema` 缺省时同样按 `type` 回落,所以**不写 `schema` 也合法**。 +- **`SchemaMetadata` 不会被自动合并**:`index.ts:100-101` 从 schema 模块只取 `default class`。definition 必须自己写 `...SchemaMetadata` 才能拿到引擎默认值(如 `search`/`userInfo`/`detail`),否则该站点的这些配置就是空的。 +- `index.ts:53-56`:`urls` / `legacyUrls` 会做 rot13 还原后再使用。 +- `index.ts:59-60`:`tags` 缺省为 `[]`;`timezoneOffset` 缺省为 `NexusPHP` 站点 `+0800`、其余 `+0000`。 + +## 工作流 + +1. **判定引擎**:先确定目标站点由哪个 schema 承载(NexusPHP / Unit3D / Gazelle / GazelleJSONAPI / AvistazNetwork / Luminance / Rartracker / TCG / 仅基类)。判定依据写在各个引擎文档的「判定站点是否属于该引擎」一节。 +2. **读该引擎的 reference**:见下方「按需读取」。**不要凭记忆套模板**,不同 schema 的 `SchemaMetadata` 默认值差异很大。 +3. **收集事实**:category 的 value→name、搜索入口、选择器、等级要求必须来自真实页面、接口响应或站点规则页。拿不到就明确写出假设并向用户确认,**严禁为了让文件"看起来完整"而编造数值、选择器或接口路径**。 +4. **写文件**:以同引擎既有定义文件为骨架(`...SchemaMetadata` 展开 + 覆写差异项),只写与默认值不同的部分。 +5. **校验**:跑 `pnpm check`(`vue-tsc --noEmit`,定义文件在 tsconfig include 内)与静态检查脚本(见下),必要时跑 `pnpm format`。 +6. **交付**:说明改了什么、依据是什么、哪些值来自真实站点、哪些是待确认的假设。 + +## 硬性规则 + +- **id = 文件名**,必须匹配 `/[0-9a-z]+/`(无大写、无特殊字符,见 `types/site.ts:36-44`)。若 Jackett 有同名站点配置,优先与其命名一致。 +- **version**:必填数字,既有定义全部显式声明(2026-09 的 master 上 301 个定义文件无一例外)。`types/site.ts:46-54` 的约定是「模板类 `-1`;`NexusPHP` / `Unit3D` / `Gazelle` / `GazelleJSONAPI` / `AvistaZ` 系 `0` 且无需递增;其余站点 >0」。 + 但仓库现状与文字约定不一致:引擎类定义普遍写 `1`(2026-09 统计:NexusPHP 164 个定义里 158 个写 `1`、Unit3D 35 个里 31 个、GazelleJSONAPI 14 个里 12 个),模板类站点也常写 `1`。 + 实际做法:**跟随同引擎既有文件的取值**(绝大多数是 `1`),同一站点内保持一致;修改既有站点时不要顺手改这个字段(除非该站点本来就用 `YYYYMMDD` 式自定义版本号)。 +- **urls**:第一个是默认使用地址;支持 https 就优先 https;敏感站点网址可写成 rot13(`uggc://` / `uggcf://`,类型见 `types/base.ts:11-21`,还原见 `utils/html.ts:49`)。 +- **legacyUrls**:更换域名时**不要**删 `urls` 里的旧地址,把它移入 `legacyUrls`;若该域名曾出现在 ptpp 中而未出现在 `urls` 中,必须补进 `legacyUrls`(`types/site.ts:88-96`)。 +- **favicon**(`types/site.ts:98-106`):可写完整 `https://.../favicon.ico`、`./.png`(`public/icons/site/` 下的文件)或 `data:image/...`;若 `public/icons/site/` 已存在 `{id}.{png,ico,svg}` 则无需声明。 +- **isDead**:只在站点彻底死亡、无恢复可能时写 `isDead: true`;临时不可用应交给用户配置,不要写进 metadata(`types/site.ts:108-115`)。 +- **prettier**:printWidth 120、分号、trailing comma all(根 `package.json` 的 prettier 配置)。 +- 若同时修改了公共类型/schema,必须同步检查其余定义文件是否受影响(`definitions/` 下 300+ 个定义文件、10 个 schema)。 + +## 按需读取 + +| 场景 | 读取 | +| --- | --- | +| 从零新增一个站点、想知道完整流程与交付前检查 | [references/workflow.md](references/workflow.md) | +| 不确定 `ISiteMetadata` 各字段的含义、默认值与约束 | [references/metadata-fields.md](references/metadata-fields.md) | +| 写 selectors、filters、category、searchEntry、userInfo process | [references/selectors-and-filters.md](references/selectors-and-filters.md) | +| 排查"搜不到/解析为空/提示未登录/结果字段错误" | [references/debugging.md](references/debugging.md) | +| 目标站点是 NexusPHP 系(国内 HDSky/OurBits 类结构) | [references/engines/nexusphp.md](references/engines/nexusphp.md) | +| 目标站点是 Unit3D 系(Laravel + API) | [references/engines/unit3d.md](references/engines/unit3d.md) | +| 目标站点是 Gazelle 系(音乐 tracker,网页解析) | [references/engines/gazelle.md](references/engines/gazelle.md) | +| 目标站点是 Gazelle 的 JSON API 变体 | [references/engines/gazelle-json-api.md](references/engines/gazelle-json-api.md) | +| 目标站点属于 AvistaZ 网络(AvistaZ/AsiaTorrents/CinemaZ/PrivateHD 等) | [references/engines/avistaz-network.md](references/engines/avistaz-network.md) | +| 目标站点是 Luminance 引擎 | [references/engines/luminance.md](references/engines/luminance.md) | +| 目标站点是 Rartracker 引擎 | [references/engines/rartracker.md](references/engines/rartracker.md) | +| 目标站点是 TCG 引擎 | [references/engines/tcg.md](references/engines/tcg.md) | +| 私有站但没有现成引擎,直接用私有站模板 | [references/engines/abstract-private-site.md](references/engines/abstract-private-site.md) | +| 公共 BT 站(type: "public")与基类提供的通用能力 | [references/engines/abstract-bittorrent-site.md](references/engines/abstract-bittorrent-site.md) | + +## 静态校验 + +写完定义文件后运行(无需安装依赖): + +```bash +node .dsh/skills/ptd-site-definition/scripts/check-site-definition.mjs src/packages/site/definitions/.ts +node .dsh/skills/ptd-site-definition/scripts/check-site-definition.mjs # 不带参数 = 全量扫描 +``` + +脚本检查:文件名与 `id` 是否一致、`id` 是否合法、必填字段是否齐全、`type` 取值、`urls` 是否非空且可 rot13 还原、`timezoneOffset` 格式、`category` 的 key 是否重复、`levelRequirements` 的 id 是否重复或非递增;并对 `schema` 命名、version 口径、明文 http 给出提示。脚本只做静态结构检查,**不能替代** `pnpm check` 与真实站点上的运行验证。 + +## 交付前检查清单 + +- [ ] `id` 与文件名一致,且只含小写字母与数字 +- [ ] `version` 与同引擎既有文件取值一致(新增站点通常为 `1`) +- [ ] `urls` 至少一项,首个为默认地址,可用 https 就用 https;旧域名进了 `legacyUrls` +- [ ] `type` 与 `schema` 匹配;`schema` 若填的是 `schemas/` 里没有的名字,则该文件必须导出 `default class` 承载它 +- [ ] 展开了 `...SchemaMetadata`(而不是手抄引擎默认值),只额外写与默认不同的部分 +- [ ] `category` 的每个 key 在站点内唯一,value 与站点真实分类一致 +- [ ] 所有选择器/接口路径都有真实依据,没有猜测值 +- [ ] `node .dsh/skills/ptd-site-definition/scripts/check-site-definition.mjs ` 通过 +- [ ] `pnpm check` 通过 +- [ ] 若覆写了类方法,确认签名与基类一致(`override` 关键字、参数个数与类型) diff --git a/.dsh/skills/ptd-site-definition/references/debugging.md b/.dsh/skills/ptd-site-definition/references/debugging.md new file mode 100644 index 000000000..bb36f578e --- /dev/null +++ b/.dsh/skills/ptd-site-definition/references/debugging.md @@ -0,0 +1,62 @@ +# 调试与常见故障 + +## 状态码先看这个 + +搜索结果是 `ISearchResult`(`types/search.ts:295-304`),`status` 取值见 `types/base.ts:29-39`。基类的映射逻辑在 `AbstractBittorrentSite.ts:294-311`: + +| 现象 | 来源 | +| --- | --- | +| 正常运行 | `success` | +| 站点被禁用/跳过 | `passParse` | +| 列表选择器未定义 | 抛 `列表选择器未定义` → `parseError`(`AbstractBittorrentSite.ts:504-506`) | +| 选择器没命中任何行 | `NoTorrentsError` → `noResults`(`AbstractBittorrentSite.ts:543-546`) | +| 响应被 Cloudflare 拦 | `CFBlockedError` → `CFBlocked`(`AbstractBittorrentSite.ts:152-154`) | +| 判定为未登录 | `NeedLoginError` → `needLogin`(`AbstractBittorrentSite.ts:156-159`) | +| 其他(4xx/5xx、单行解析抛错) | `parseError`(`AbstractBittorrentSite.ts:161-164, 548-555`) | + +`statusMsg` 以 `i18n.` 开头时会被前端按对应 i18n key 展示(`types/search.ts:297-302`)。 + +注意:**单行解析抛错会中断整次搜索**(`AbstractBittorrentSite.ts:551-554` 会 `console.error` 后继续抛),所以"搜到一半全空"通常是某一行里某个 selector 或 filter 抛异常。 + +## 四步定位法 + +1. **请求对不对**:看控制台里 `[Site] start search with requestConfig:`(`AbstractBittorrentSite.ts:291`)。没有这条说明在入口就被跳过了(`allowSearch` 为假、`enabled: false`、或关键词被 `skipNonLatinCharacters`/`skipWhiteSpacePlaceholder` 拦掉,见 `AbstractBittorrentSite.ts:182-218`)。 +2. **响应形态对不对**:HTML 站点必须让 `responseType` 为 `document`(缺省垫片就是 document);JSON API 站点必须在 `requestConfig` 里显式写 `responseType: "json"`,否则 `doc` 是字符串,所有 `get()` 路径都会取空。 +3. **行选择器命中没有**:`rows.selector` 为空 → `noResults`。先在浏览器 Console 里用与 Sizzle 相同的语法手动验证选择器,再看是否需要 `rows.merge`(一个种子跨多行)或 `rows.filter`。 +4. **单行字段对不对**:逐字段核对,重点看 `title`/`size`/`time`/`seeders`/`link`。可临时加 `filters: [{ name: "dump" }]` 把中间值打出来(`utils/filter.ts:196-204`)。 + +## 高频坑 + +- **`url` 与 `link` 写反**:`url` 是详情页,`link` 是种子下载链接(`types/torrent.ts:26-34`)。写反的表现是"能搜到但下载失败"或"详情页打开的是种子文件"。 +- **数字被自动转换**:取到的字符串若整体匹配 `/^-?\d+$/` 会被转成 number(`AbstractBittorrentSite.ts:433-441`),`0012` 会变成 `12`。 +- **取不到值时静默变空串**:query 为 `undefined` 时回落到 `elementQuery.text ?? ""`(`AbstractBittorrentSite.ts:429-430`),所以选择器写错不一定报错,只是字段为空。不要用"字段为空"判断"站点不支持该字段",要看源码里的 `text: "N/A"` 约定(`types/search.ts:254-256`)。 +- **`:self` 只对"当前元素"生效**:在 HTML 上下文里它跳过 Sizzle 直接取当前元素;在 JSON 上下文里它是顶层对象(`AbstractBittorrentSite.ts:380, 402`)。写 `":self"` 时后面的 `attr`/`data`/`case` 才有意义。 +- **JSON 站点误用 CSS 选择器**:非 Document 上下文下 `selector` 走 `es-toolkit/compat` 的 `get` 路径(`AbstractBittorrentSite.ts:401-403`),例如 `data.0.attributes.name`。 +- **时间差一小时/时区错位**:检查 `timezoneOffset`;不写时 NexusPHP 站点按 `+0800`、其余 `+0000`(`index.ts:60`)。 +- **私有没有登录态**:插件请求带的是浏览器 cookie;如果站点需要额外的 API key,用 `userInputSettingMeta` + `userConfig.inputSetting` 表达(`types/site.ts:378-382`)。 +- **相对链接漏了 `fixLink`**:直接给 `url`/`link` 赋相对路径时,基类会自动补全(`AbstractBittorrentSite.ts:321-338, 750-752`);但 `@warning`(`AbstractBittorrentSite.ts:315-320`)指出**不要在 `filters` 里调用 `fixLink`**。 +- **列表页与详情页被互相误判**:`list[].urlPattern` 自动生成后可能把详情页也匹配进来,用 `excludeUrlPattern` 排除(`types/site.ts:170-177`)。 + +## 常用调试手段 + +```ts +// 1. 打印最终请求配置 +search: { + requestConfig: { url: "/torrents.php" }, + requestConfigTransformer: (config) => { console.log(config); return config; }, +} + +// 2. 打印某个字段的中间值 +title: { selector: "a.title", filters: [{ name: "dump" }] } +``` + +- 开发模式下解析异常会额外 `console.error`(`AbstractBittorrentSite.ts:299-301`、`AbstractPrivateSite.ts:201-203`)。 +- 搜索入口本身会打印站点名与关键词(`AbstractBittorrentSite.ts:175`)。 +- 站点定义里需要长期保留的日志按仓库习惯写成 ``console.debug(`[PTD] site '${this.name}' ...`)``(例如 `definitions/audiobookbay.ts:202`)。 + +## 覆写类方法时的自检 + +- 覆写必须带 `override` 关键字(`tsconfig.json` 开了 `noImplicitOverride`),`pnpm check` 会检查。 +- 覆写解析方法时,**先调用 super 再改**通常更安全(例如 `definitions/hdsky.ts:372-394` 先 `super.parseTorrentRowForTags(...)`)。 +- 覆写 `getTorrentDownloadLink` 时记得处理"链接已存在但可能过期"的情况,并在无法取得时回落到 `super`(`definitions/hdsky.ts:337-370`)。 +- 覆写 `guessUserLevelId` / `getUserInfoResult` 时注意它们与 `levelRequirements` 的既有约定(`AbstractPrivateSite.ts:194-220`)。 diff --git a/.dsh/skills/ptd-site-definition/references/engines/abstract-bittorrent-site.md b/.dsh/skills/ptd-site-definition/references/engines/abstract-bittorrent-site.md new file mode 100644 index 000000000..5cb42c579 --- /dev/null +++ b/.dsh/skills/ptd-site-definition/references/engines/abstract-bittorrent-site.md @@ -0,0 +1,221 @@ +# AbstractBittorrentSite 站点定义规范 + +`src/packages/site/schemas/AbstractBittorrentSite.ts` 是所有站点模板的根基类(`AbstractPrivateSite`、`NexusPHP`、`Unit3D`、`Gazelle` 等全部直接或间接继承它),同时也是公共 BT 站点(`type: "public"`)在未指定 `schema` 时的**回落基类**(`index.ts:41-42`、`index.ts:97`)。它自带一整套「按 `siteMetadata.search / list / detail` 发请求 → 用选择器解析 → 组装 `ITorrent`」的通用流程,因此公共 BT 站通常**只写 metadata、不导出 `default class`**;只有当需要在流程中间插入逻辑(补下载链接、读页面内 token、改写列表页结构)时才导出 `default class Xxx extends BittorrentSite`。 + +下文行号若未特别注明,均指 `src/packages/site/schemas/AbstractBittorrentSite.ts`(该文件实际 791 行)。 + +## 什么情况下落到这个基类 + +三种来源,优先级从高到低(`index.ts:79-102`): + +1. **definition 导出了 `default class`** → 直接使用该 class(`index.ts:81-84`),此时 `siteMetadata.schema` 被忽略。这也是 `nyaa`、`ehentai`、`nekobt`、`exttorrents`、`audiobookbay` 等公共站的做法。 +2. **未导出 class,且 `schema` 在 `schemas/` 中有同名实现** → 用该 schema 类(`index.ts:93-101`)。 +3. **未导出 class,且 `schema` 缺省或没有对应实现** → 按 `type` 回落:`private` → `AbstractPrivateSite`,`public` → `AbstractBittorrentSite`(`index.ts:41-42`、`index.ts:96-98`)。 + +因此对公共 BT 站而言: + +- 写 `type: "public"` 且**不写** `schema`,就是"隐式回落"到本基类。现有 15 个 public 定义全部属于这种写法,**没有一个声明 `schema`**。 +- 显式写 `schema: "AbstractBittorrentSite"` 效果相同,但仓库现状没有先例,不必画蛇添足。 +- 如果给 public 站写了一个 `schemas/` 里不存在的 `schema` 名(例如复制 private 站模板留下 `schema: "NexusPHP"` 却没改),`index.ts:96-98` 会静默回落,**不会报错**——写错引擎时不会有任何提示,必须自己核对。 + +与 `AbstractPrivateSite` 的分工(`AbstractPrivateSite.ts:26` 继承自本基类): + +| 能力 | AbstractBittorrentSite | AbstractPrivateSite | +| --- | --- | --- | +| 搜索列表 / 列表页 / 详情页解析 | 有(本文件) | 继承 | +| 下载链接生成 `getTorrentDownloadLink` | 有(764-778) | 继承 | +| 登录态判定 `loggedCheck` | **恒返回 true**(98-100) | 覆写为按状态码/URL/refresh 头/选择器判定(50-107),消费 `metadata.noLoginAssert` | +| `allowQueryUserInfo` getter | **不存在** | 有(27-29) | +| `getUserInfoResult` 用户信息抓取 | **不存在** | 有(113-215),消费 `metadata.userInfo` / `levelRequirements` | +| `SchemaMetadata` 默认值 | `{ version: -1, search: {} }`(39-42) | `{ version: -1, search: {}, userInfo: {} }`(`AbstractPrivateSite.ts:20-24`) | + +**怎么判断一个新站该写 `public` 还是 `private`**:`type` 只有这两个取值(`types/site.ts:75`)。要登录才能搜索、需要抓用户信息/等级/保号数据的站是 `private`(并应优先考虑 NexusPHP / Unit3D / Gazelle 等现成引擎);无需登录即可检索、没有账号体系的站是 `public`。`type` 还会顺带决定两件事:缺省 `schema`(`index.ts:41-42`)与 `userConfig.allowQueryUserInfo` 的默认值(`index.ts:105-106`,依据 metadata 里是否存在 `userInfo`)。把 public 站写成 `private` 会引入一整套无用的 `noLoginAssert` 判定,反之则会让 public 站失去应有的解析能力。 + +> 两个 `SchemaMetadata` 都不会被框架自动合并:`index.ts:100-101` 只取 schema 模块的 `default` 类,`getDefinedSiteMetadata`(`index.ts:47-63`)只读 definition 自己的 `siteMetadata`。它们的用途是给 definition 里手写 `...SchemaMetadata` 展开用;对 public 站来说展开它只等于 `{ version: -1, search: {} }`,实际意义不大(15 个 public 定义一个都没展开它)。 + +## 它提供的通用能力 + +只列对写 definition 有直接影响的: + +- **`request(axiosConfig, checkLogin = true)`(119-167)**:所有请求的统一入口。垫片为 `baseURL = this.url`、`url = "/"`、`timeout = userConfig.timeout ?? 30e3`(121-123);发请求前 `await sleepAction(metadata.requestDelay ?? 0)`(126);`responseType: "document"` 时自动把 Cloudflare 的 `span.__cf_email__` 还原成明文邮箱(133-146);命中 Cloudflare 拦截抛 `CFBlockedError`(152-154);`loggedCheck` 不通过抛 `NeedLoginError`(157-159,基类恒为 true);`status >= 400` 抛 `Network Error: `(162-164)。注意异常被 catch 后仍会取 `error.response` 继续走上述判定(147-150)。 +- **`getSearchResult(keywords?, searchEntry = {})`(174-313)**:搜索总入口。负责 searchEntry 合并、关键词与高级搜索词解析、requestConfig 组装、调用 `request` 与 `transformSearchPage`,并把异常映射为 `EResultParseStatus.{CFBlocked, needLogin, noResults, parseError}`(304-310)。 +- **`transformSearchPage(doc, searchConfig)`(502-558)**:把列表页/接口响应转成 `ITorrent[]`。`searchEntry.selectors.rows` 未定义时抛 `Error("列表选择器未定义")`(504-506);DOM 结果可用 `rows.merge`(多行合并为一颗种子)或 `rows.filter`,JSON 结果只能用 `rows.filter`(512-541);结果为空抛 `NoTorrentsError`(544-546);逐行调用 `parseWholeTorrentFromRow`(548-555)。 +- **`transformListPage(doc)`(651-710)**:content-script 在列表页调用。按 `metadata.list[*].urlPattern` 选中一条并覆盖 `search.selectors`(656-665,命中即 `break`),再推断当前关键词(668-697),最后委托给 `transformSearchPage`。 +- **`transformDetailPage(doc)`(716-755)**:content-script 在详情页调用。用 `metadata.detail.selectors` 取值(721-722),并内置 url / id / title 的回落规则。 +- **`getFieldData` / `getFieldsData`(347-444)**:选择器求值核心,解释 `IElementQuery` 的全部字段(见「常见坑」)。 +- **`runQueryFilters(query, filters)`(446-460)**:执行 `filters`,既支持函数也支持 `{ name, args }`;只有 `filterNames`(`utils/filter.ts:207`)里存在的名字才生效,**拼错名字会被静默忽略**(451-456)。 +- **`fixLink(uri, requestConfig)`(321-338)**:把 `//host/x`、`/x`、`x` 形式的相对链接按请求地址补全;`magnet:` 原样返回。 +- **`parseTorrentRowForTags(torrent, row, searchConfig)`(615-637)**:仅当 `searchEntry.selectors.tags` 存在时才写入 `torrent.tags`(620);对 DOM 用 `Sizzle(selector, row).length > 0` 判断,对 JSON 用 `get(row, selector)` 判断(623-631)。 +- **`parseWholeTorrentFromRow(torrent, row, searchConfig)`(560-613)**:逐字段求值 + 通用补全(`site`、`id`、`fixLink`、体积/计数转数字、`timezoneOffset` 时间换算,590-608),最后交给 `fixParsedTorrent`(611)。 +- **`fixParsedTorrent(torrent, row, searchConfig)`(639-645)**:默认直接返回入参,是留给子类的空挂钩。 +- **`getTorrentDownloadLink(torrent)`(764-778)**:`torrent.link` 为空且配置了 `detail.selectors.link` 时,用 `detail.requestConfig` 垫片请求详情页并取值(765-770);随后若用户配置了 `downloadLinkAppendix` 就拼到链接尾部(772-775)。 +- **`getTorrentDownloadRequestConfig(torrent)`(784-790)**:先取下载链接,再与 `download.requestConfig` 合并出最终 axios 配置。 +- **`userInfo` 相关:基类没有。** `allowQueryUserInfo`、`getUserInfoResult`、`guessUserLevelId` 都只存在于 `AbstractPrivateSite`(27-29、113-215、218-220)。调用方 `offscreen/utils/userInfo.ts:59-61` 用 `if (site.allowQueryUserInfo)` 判断,纯 `BittorrentSite` 实例上该属性为 `undefined`,因此 public 站**永远不会进入用户信息抓取**。 + +## siteMetadata 中由基类解释的字段 + +| 字段 | 谁在读它 | 说明 | +| --- | --- | --- | +| `type` | `index.ts:41-42`、`index.ts:97` | 基类自己**不读**,只用于决定缺省 `schema` 与实例类型兜底 | +| `schema` | `index.ts:94-98` | 同上;public 站一般省略 | +| `id` | 590、717、`storeRuntimeSettings` / `retrieveRuntimeSettings` 108-117 | 也被要求等于文件名(`index.ts:22-29` 用文件名做 key) | +| `name` | `get name()` 74-76 | `userConfig.merge?.name ?? metadata.name` | +| `urls` | `get url()` 78-80、`request` 121、`fixLink` 328 | 只取 **第一个**作为默认地址;`urls`/`legacyUrls` 会先经 rot13 还原(`index.ts:53-56`) | +| `legacyUrls` | `index.ts:54-56`;旧地址迁移提示见 `entries/options/stores/metadata.ts:405-407` | 基类解析流程不使用 | +| `favicon` | `utils/favicon.ts:200-252`(尤其 212 行优先使用定义值) | 不在基类里消费 | +| `category` | 基类**不消费**;由 `SetSearchSolution/utils.ts:35-80` 生成搜索入口请求配置 | 详见「搜索与列表页」 | +| `requestDelay` | 126(全站统一延迟) | definition 中写的是毫秒;与 `search.requestDelay`(287-289)**叠加** | +| `officialGroupPattern` | `offscreen/utils/search.ts:28-29` | 仅在用户开启 `autoDetectOfficialGroupFromTitle` 时用于自动打「官方」标签 | +| `search` | 174-313 全流程 | 关键词路径、请求垫片、选择器 | +| `searchEntry` | 191-198(按 `id` 合并后再与 `search` 合并) | 多搜索入口才需要 | +| `list` | 651-665 | content-script 判定列表页 + 覆盖选择器 | +| `detail` | 716-755(详情页解析)、765-770(补下载链接) | `urlPattern` 只能手写,不能推断(`types/site.ts:210-212`) | +| `download.requestConfig` | 784-790 | 与 `{ baseURL: this.url, url: 下载链接, method: "GET", timeout }` 合并 | +| `download.interval` | `get downloadInterval()` 90-92 | `userConfig.downloadInterval ?? metadata.download?.interval ?? 0`(单位:秒) | +| `noLoginAssert` | 基类**不消费**(`loggedCheck` 恒 true,98-100);仅 `AbstractPrivateSite.ts:31-101` 使用 | public 站写它无效(且 `types/site.ts:260-266` 也声明"仅对 PrivateSite 模板生效") | +| `userInfo` / `levelRequirements` | 基类**不消费** | public 站写了也不会被读取,除非自己导出 class 实现 `getUserInfoResult` | +| `userInputSettingMeta` | `offscreen/utils/site.ts:35-36` 生成用户输入项 | 基类不参与 | + +## 最小可用定义骨架 + +```ts +import { type ISiteMetadata } from "../types"; + +export const siteMetadata: ISiteMetadata = { + version: 1, // 现有 15 个 public 定义全部写 1(不是 SchemaMetadata 里的 -1) + id: "example", // 必须等于文件名 example.ts + name: "Example", + type: "public", // 不写 schema,隐式回落到 AbstractBittorrentSite + urls: ["https://example.com/"], // 第一个是默认使用地址 + + search: { + keywordPath: "params.q", // 默认值就是 params.keywords(267) + requestConfig: { url: "/search", responseType: "document" }, + selectors: { + rows: { selector: "table.torrents > tbody > tr" }, // 必需,否则 "列表选择器未定义" + id: { selector: "a[href*='/view/']", attr: "href" }, // 省略时由 url/link 推断(591) + title: { selector: "a[href*='/view/']" }, + url: { selector: "a[href*='/view/']", attr: "href" }, // 详情页链接 + link: { selector: "a[href^='magnet:']", attr: "href" }, // 下载链接 + time: { selector: "td:nth-child(3)", filters: [{ name: "parseFuzzyTime" }] }, + size: { selector: "td:nth-child(4)", filters: [{ name: "parseSize" }] }, + seeders: { selector: "td:nth-child(5)" }, + leechers: { selector: "td:nth-child(6)" }, + }, + }, +}; +``` + +## 搜索与列表页 + +**requestConfig 垫片**(226-229):先取 `{ url: "/", responseType: "document", params: {}, data: {} }`,再用 `searchEntry.requestConfig`(含从 `search` 合并来的部分)覆盖。`baseURL` 不在这里写,由 `request` 补成 `this.url`(121)。JSON 接口的站点在 `requestConfig.responseType` 里改成 `"json"`,并把 `selectors` 换成 JSON 路径(范例 `definitions/torrentscsv.ts:14-37`:`rows: { selector: "torrents" }`、`id: { selector: "id" }`,对 JSON 走 `get(row, selector)`,见 402)。 + +**关键词**(266-268):`set(requestConfig, searchEntry.keywordPath || "params.keywords", keywords)`。`keywordPath` 按点号路径解释(`es-toolkit` 的 `set`,类型见 `types/search.ts:100`),如 `params.q`、`params.search`、`data.keywords`。 + +**高级搜索词**(231-263):形如 `imdb|tt17097088`、`douban|35131346`。前缀先被剥掉(240),再查 `searchEntry.advanceKeywordParams[<前缀>]`:未声明时 `imdb` 视同 `{ enabled: true }` 并回落成普通关键词搜索,**其它前缀(douban/bangumi/anidb/tmdb/tvdb/mal 等)直接标记为不搜并返回**(244-257)。声明为 `false` 或 `{ enabled: false }` 同样跳过。命中后先合 `requestConfig`(272-274)、再套 `requestConfigTransformer`(276-279)。 + +**搜索入口合并**(188-205):传入 `searchEntry.id` 时先与 `metadata.searchEntry[id]` 深合并(192);随后只要 `searchEntry` 为空或没显式写 `merge: false`,就继续与 `metadata.search` 深合并(196-198);`enabled === false` 的入口直接返回 `passParse`。`nyaa` 用它做"表站/里站"两个入口(`definitions/nyaa.ts:137-151`)。 + +**selectors 字段解析**:见「常见坑」中的 `IElementQuery` 语义。可用的种子键为 `ITorrent` 里除 `site`/`tags` 外的字段 + `rows`(`types/search.ts:115-141`);`parseWholeTorrentFromRow` 遍历的是 `selectors` 的键与 `defaultTorrentSelectorKey`(44-61)的并集(575)。 + +**`list.urlPattern` 自动推断**:`metadata.list` 为空时,content-script 用 `uniq([search.requestConfig.url, ...searchEntry[*].requestConfig.url])` 作为列表页正则(`entries/content-script/app/utils.ts:44-52`);一旦 `metadata.list` 非空,就**只**用其中各条 `urlPattern` 的并集(46)。`list[*].urlPattern` 缺省则为空数组、不匹配任何页面。匹配前会先用 `excludeUrlPattern` 排除(54-59)。`transformListPage` 侧则只认 `list`(656-665),与上面的推断互补。 + +**category 与 cross**:基类不读 `category`;它由 `SetSearchSolution/utils.ts:39-80` 生成请求配置——有 `generateRequestConfig` 就用它(49-50);否则键为 `#url` 时直接写入 `requestConfig.url`(53-54),其余写进 `requestConfig..`(56-77),`cross.mode` 决定 `brackets`(数组)/`comma`(逗号串)/`append`(`key{v}: 1`)/`appendQuote`(`key[v]: 1`),`custom` 必须在 `generateRequestConfig` 里自己实现(`types/search.ts:222-241`)。`cross: false` 与不写等价(单选)。 + +**列表页专属字段**:`list[*].selectors` 可额外定义 `keywords`(`types/site.ts:199`),命中后用于回填搜索框;没写时 `transformListPage` 按 `keywordPath` 推断输入框选择器,再退化为从 URL 的 `params.`、`search`、`keywords`、`keyword`、`q` 里解析(668-697)。 + +`list` 与 `rows` 的常见写法: + +```ts +list: [ + { + urlPattern: ["/search", "/top100"], // 命中即 break,注意与其它条目的重叠(659-665) + mergeSearchSelectors: true, // 默认 true;接口型站点写 false 以摆脱 search.selectors + selectors: { keywords: { selector: 'input[name="q"]', attr: "value" } }, + }, +], + +search: { + selectors: { + // 一个种子被相邻多个 tr 表示时,merge 会把它们包进一个 div(520-535) + // 之后子选择器要写成 "tr:nth-child(1) xxx" 这种形式 + rows: { selector: "table > tbody > tr", merge: 2 }, + }, +}, +``` + +## 详情页与下载链接 + +`transformDetailPage(doc)`(716-755)的解析顺序: + +1. 克隆文档,用 `metadata.detail.selectors` 逐字段取值并合并进 `{ site }`(718-722)。 +2. `url` 缺失 → `doc.URL || location.href`(725-727)。 +3. `id` 缺失 → 从 url 查询串的 `tid`、`id` 依次取;仍无则 `id = url`(730-744)。 +4. `title` 缺失 → `html > body > title`(746-748)。 +5. `link` 存在 → 以文档 URL 为基准 `fixLink`(750-752)。 + +`getTorrentDownloadLink(torrent)`(764-778)只在 **`torrent.link` 为空** 且配置了 `detail.selectors.link` 时才发请求:请求配置是 `toMerged({ responseType: "document", url: torrent.url }, metadata.detail?.requestConfig ?? {})`(766-768),即"以种子的详情页 url 为基础,用 `detail.requestConfig` 打补丁"。取到的值直接写入 `torrent.link`(769)。之后若 `userConfig.downloadLinkAppendix` 非空则追加到尾部(772-775)。`getTorrentDownloadRequestConfig` 再把它合成 `{ baseURL: this.url, url, method: "GET", timeout }` 并与 `download.requestConfig` 合并(784-790)。 + +因此"搜索页拿不到下载链接"的站点有两种写法:配置 `detail.selectors.link` + 可选 `detail.requestConfig`(如 `1337x.ts:255-262`),或在 class 里覆写 `getTorrentDownloadLink`(如 `nyaa.ts:176-186`)。 + +```ts +detail: { + urlPattern: ["/torrent/\\d+"], // 无法自动推断,必须显式声明(types/site.ts:210-212) + // 垫片为 { responseType: "document", url: torrent.url }(766-768) + requestConfig: { responseType: "document" }, + selectors: { + title: { selector: "h1.card-title" }, + link: { selector: ["a[href^='magnet:?xt=']", "a[href$='.torrent']"], attr: "href" }, + }, +}, + +download: { + // 与 { baseURL: this.url, url: 下载链接, method: "GET", timeout } 合并(786-789) + requestConfig: { method: "POST" }, +}, +``` + +## 常被覆写的类方法 + +| 方法(签名见源码) | 何时覆写 | 行号 / 范例 | +| --- | --- | --- | +| `public async getTorrentDownloadLink(torrent: ITorrent): Promise` | 下载链接需要二次跳转、需要拼接 id、或需要页面内 token 时。常见写法是 `const link = await super.getTorrentDownloadLink(torrent)` 后判断并补全 | 764-778;`definitions/nyaa.ts:177-185`、`definitions/nekobt.ts:210-218`、`definitions/ehentai.ts:42-45` | +| `public async getTorrentDownloadRequestConfig(torrent: ITorrent): Promise` | 下载需要 POST、额外 header 等 `download.requestConfig` 表达不了的配置 | 784-790;`definitions/gtnet.ts:216` | +| `public async transformSearchPage(doc, searchConfig): Promise` | 列表页需要二次加工:从页面脚本取 token、按接口结构重组行。建议先 `await super.transformSearchPage(...)` 再加工 | 502-558;`definitions/exttorrents.ts:201-215`、`definitions/audiobookbay.ts:184` | +| `public async transformListPage(doc: Document)` | 列表页结构特殊,默认的"list 覆盖 selectors"不够用 | 651-710 | +| `public async transformDetailPage(doc: Document): Promise` | 详情页需要额外字段或改写 id 推断 | 716-755;`definitions/exttorrents.ts:217-226` | +| `protected parseTorrentRowForTags(torrent, row, searchConfig)` | 需要按行内容动态打标签(JSON 接口站点尤其常见) | 615-637;`definitions/nekobt.ts:195-208` | +| `protected fixParsedTorrent(torrent, row, searchConfig)` | 解析收尾时统一修正字段 | 639-645 | +| `protected fixLink(uri, requestConfig): string` | 站点的相对链接规则异于 `new URL(uri, requestUrl)` | 321-338 | +| `protected getFieldData(element, elementQuery)` | 需要一种全新的取值方式(极少用) | 366-444 | +| `public async request(axiosConfig, checkLogin = true)` | 需要统一注入 header / 处理特殊响应 | 119-167 | + +覆写时保留签名并加 `override`(`pnpm check` 会校验);`protected` 成员在 definition 的子类里同样可覆写,但要从 `protected` 提升为 `public` 时必须显式写 `public`。 + +## 范例定义 + +- `definitions/torrentscsv.ts` — 最简的"纯 metadata + JSON 接口"样例:`responseType: "json"`、JSON 路径选择器、用 filter 把 infohash 拼成 magnet,另附一条 `mergeSearchSelectors: false` 的 `list` 规则。 +- `definitions/nyaa.ts` — 综合度最高:`category` 里用 `generateRequestConfig` 切表站/里站、`searchEntry` 两个入口、`detail.selectors.id` 用 `:self` + `elementProcess`、并覆写 `getTorrentDownloadLink` 补 `/download/{id}.torrent`。 +- `definitions/1337x.ts` — 演示 `requestConfigTransformer` 把 `params` 重写成 REST 路径(关键词/分类/排序都进 URL 路径),以及 `detail.selectors.link` 双候选(magnet 与 itorrents)。 +- `definitions/ehentai.ts` — 最短的覆写范例:`getTorrentDownloadLink` 里再发一次请求,从下载页取真实 `.torrent` 链接。 +- `definitions/exttorrents.ts` — 需要页面内 token 的完整改造:同时覆写 `transformSearchPage`(注入 token)、`transformDetailPage`(解析 id 与 magnet)、`getTorrentDownloadLink`,并用 `detail.selectors.link: { text: "N/A" }` 占位。 +- `definitions/nekobt.ts` — 覆写 `parseTorrentRowForTags` 处理非 DOM 行对象(`{ level: number }`)的样例。 + +## 常见坑 + +1. **`url` 与 `link` 的含义与 ptpp 相反**:`url` 是详情页链接、`link` 是下载链接(`types/torrent.ts:26-34`)。写反会导致详情页/下载全错,且不会有任何报错。 +2. **`id` 会被自动推断,但推断源是 `url || link`**(591,`tryToNumber` 包裹)。若 `url` 是一整条长链接,`id` 就会退化成该链接字符串;`transformDetailPage` 里的推断规则不同(优先 `tid`/`id` 查询参数,最后才是 url,730-744)。需要稳定数字 id 就显式写 `selectors.id`。 +3. **选择器数组是"首个命中即停"**:数组内逐个尝试,只有 `typeof query !== "undefined"` 才跳出(372-411)。空字符串(`textContent` 为空)经 `|| query` 处理后仍是 `undefined`,会继续试下一个选择器,这是 `nyaa.ts:105-127`、`1337x.ts:259` 能写多候选的前提。 +4. **`:self` 是特殊选择器**:DOM 场景表示元素自身(用于 `tr` 自身的属性),JSON 场景表示根对象(380、402)。`nyaa.ts:163-170` 用 `:self` + `elementProcess` 从 `Document.URL` 里正则取 id。 +5. **取值有隐式回落与类型转换**(429-441):最终 `query ??= elementQuery.text ?? ""`;字符串会 `trim()`,且只有形如 `^-?\d+$` 的整串数字才转成 `int`——`"1,234"`、`"1.2 GB"` 都不会自动变数字,必须配 `filters`(如 `parseNumber`、`parseSize`)。 +6. **`elementProcess > case > data > attr > innerText`** 是有优先级的互斥链(383-399,注释见 `types/search.ts:265-281`);写了 `elementProcess` 就不要再依赖 `filters`(`types/search.ts:273`)。 +7. **`filters` 名字拼错会被静默忽略**(451-456 只对 `filterNames` 中的名字生效);`switchFilters` 的键是"最终命中的那条 selector 字符串",不是数组下标(421-422)。 +8. **`rows.merge` 只在 DOM 返回时生效**(515-541),JSON 返回只能靠 `rows.filter`;`filter` 存在时 `merge` 被完全忽略(515-517)。 +9. **`list` 只命中第一条即 `break`**(659-665):规则顺序与 `urlPattern` 重叠会互相遮挡;`mergeSearchSelectors: false`(对接口型站点常用,如 `torrentscsv.ts:42`)会让该条的 `selectors` 独立,不再继承 `search.selectors`。 +10. **`detail.selectors.link` 只在 `torrent.link` 为空时被使用**(765):如果搜索页已经解析出了 `link`(哪怕是个中间页链接),基类不会再去详情页取真正链接——这种情况必须覆写 `getTorrentDownloadLink`。 +11. **`downloadLinkAppendix` 会被重复追加**:它直接改写 `torrent.link`(772-775),而 `getTorrentDownloadRequestConfig` 内部又调用一次 `getTorrentDownloadLink`(785)。调用方 `offscreen/utils/download.ts:118-121` 正是先调 `getTorrentDownloadLink(torrent)` 再调 `getTorrentDownloadRequestConfig(torrent)`(同一对象),`userConfig.downloadLinkAppendix` 非空时会被拼两次。改这块逻辑时需注意。 +12. **`types/site.ts:246-248` 的注释(`toMerged({ url: torrent.link, method: 'get' }, ...)`)与实现不一致**:实际实现是 `getTorrentDownloadRequestConfig`(784-790),先经 `getTorrentDownloadLink` 补链接,且垫片含 `baseURL: this.url` 与 `timeout`。以源码为准。 +13. **public 站写 `userInfo` / `levelRequirements` / `noLoginAssert` 不生效**:基类既没有 `getUserInfoResult`(只在 `AbstractPrivateSite.ts:113` 与各 schema 中),`loggedCheck` 也恒为 `true`(98-100)。现有 15 个 public 定义没有一个写这三项。 +14. **`metadata.requestDelay` 与 `search.requestDelay` 会叠加**(126 与 287-289),两个都设时要按总和评估限速。 +15. **`urls[0]` 是默认地址**(78-80),更换域名必须新地址放前面、旧地址进 `legacyUrls`(`types/site.ts:88-96`);两者都会在加载时做 rot13 还原(`index.ts:53-56`),所以敏感站点可以直接写 `uggcf://` 形式。 +16. **`SchemaMetadata` 不会被自动合并**(`index.ts:100-101` 只用 `default` 类),definition 里写的 `search` 是**整体对象**,不做与 schema 默认值的深合并——写 `search.selectors` 时要么写全,要么自己手动展开。 diff --git a/.dsh/skills/ptd-site-definition/references/engines/abstract-private-site.md b/.dsh/skills/ptd-site-definition/references/engines/abstract-private-site.md new file mode 100644 index 000000000..0be779697 --- /dev/null +++ b/.dsh/skills/ptd-site-definition/references/engines/abstract-private-site.md @@ -0,0 +1,187 @@ +# AbstractPrivateSite 站点定义规范 + +`AbstractPrivateSite`(`schemas/AbstractPrivateSite.ts:1-2` 注释:「所有PT站点的基类」)是私有站默认模板,继承 `AbstractBittorrentSite`。它的定位是**兜底基类**:当 `siteMetadata.type === "private"` 且 schema 缺省或指向不存在的 schema 时,站点实例就落在它身上(`index.ts:41-42`、`index.ts:96-98`)。 + +因此「只写 `siteMetadata`、不导出 `default class`」时,站点的全部能力由它决定,主要只有三块: + +1. **未登录判定**:`loggedCheck`(`schemas/AbstractPrivateSite.ts:50-107`)按状态码 / responseURL / refresh 头 / matchSelectors / 响应体做断言; +2. **用户信息采集**:`getUserInfoResult`(113-215)按 `userInfo.process` 逐步抓取、按 `pickLast` 复用缓存、按 `parseUserInfoFor{Field}` 做特化; +3. **等级换算**:`guessUserLevelId`(218-220)。 + +它**不提供任何搜索或用户信息的默认选择器**(`SchemaMetadata` 只有 `version` 与两个空对象),所以使用它的站点要把 `search.selectors`、`userInfo.process` 等整段自己写。 + +下文行号若未特别说明,均指 `src/packages/site/schemas/AbstractPrivateSite.ts`(该文件实际 221 行)。 + +## 判定站点是否属于该引擎 + +先明确实例化顺序(`index.ts:79-102`):**definition 导出了 `default class` → 直接用它;否则才看 `siteMetadata.schema`;`schema` 值在 `schemas/` 下没有实现时再按 `type` 回落**(private → `AbstractPrivateSite`)。所以「是否属于本模板」和「schema 字段写不写」是两个问题。 + +**省略 `schema` 字段**(合法,`index.ts:41-42` 会补成 `"AbstractPrivateSite"`)适用于: + +- definition 导出了 `default class`——此时 schema 字段对实例化毫无影响,仓库中 `definitions/hdbits.ts`、`definitions/gtnet.ts`、`definitions/gtru.ts`、`definitions/qingwa.ts` 就是这样(文件里没有 `schema` 行、有 default class;其中部分是通过 `...SchemaMetadata` 带入 `schema` 的); +- 站点就是普通私有站,选择器与流程全部自己写、不依赖任何现成引擎,且不需要在文件里强调这一点(`definitions/ncore.ts`、`definitions/bitpt.ts`、`definitions/cinemageddon.ts`)。 + +**显式写 `schema: "AbstractPrivateSite"`** 适用于: + +- 文件里不导出 default class,但希望读者一眼看出「这个站走通用私有站模板」;仓库现有 15 个文件这么写(`definitions/milkie.ts:25`、`definitions/starspace.ts:29`、`definitions/torrentleech.ts`、`definitions/bakabt.ts`、`definitions/karagarga.ts`、`definitions/retrotoon.ts`、`definitions/pussytorrents.ts`、`definitions/hdroute.ts`、`definitions/pornolab.ts`、`definitions/rutracker.ts`、`definitions/yzyy.ts`、`definitions/audionews.ts`、`definitions/iptorrents.ts`、`definitions/pornbits.ts`、`definitions/speedapp.ts`); +- definition 导出了 `default class` 且该 class 继承的就是 `PrivateSite`(`definitions/milkie.ts:3` + `124`、`definitions/bakabt.ts:4` + `239`)——此时显式写只是声明意图,与省略的实例化结果完全相同。 + +**必须注意的反向情形**:`schema` 字段还会影响时区缺省(`index.ts:60`:只有 `schema === "NexusPHP"` 才给 `"+0800"`,其余一律 `"+0000"`)。注意这一步作用在 definition 最终导出的对象上——`...SchemaMetadata` 展开本身就会带入 `schema`(`schemas/NexusPHP.ts:348`)与 `timezoneOffset`(`schemas/NexusPHP.ts:350`),所以展开过引擎默认值的文件不会踩到这个缺省。仓库中所有「default class 继承某引擎」的 definition 都展开了 `...SchemaMetadata`,因此该陷阱目前没有真实案例。**结论:default class 继承某个引擎时,展开该引擎的 `SchemaMetadata`、显式写 `schema`、显式写 `timezoneOffset` 三者至少要有其一。** + +易混淆情况: + +- **站点其实是 NexusPHP / Unit3D / Gazelle 等现成引擎**:手写 `schema: "AbstractPrivateSite"` 会让站点退化成通用模板,白白丢掉引擎的默认选择器与流程。判定依据见各引擎文档;`iptorrents`、`pornolab`、`speedapp` 等站点确实不属于任何现有 schema,才用本模板。 +- **把 `type: "public"` 的 BT 站写成 private**:会拿到 `loggedCheck` 的未登录断言与 `userInfo` 流程(虽然没人调用),公开站应走 `AbstractBittorrentSite`(`index.ts:41-42`、97)。 + +## SchemaMetadata 默认值 + +`schemas/AbstractPrivateSite.ts:20-24` 导出的 `SchemaMetadata: Partial`: + +| 字段路径 | 默认值 | 行号 | definition 是否通常需要覆盖 | +| --- | --- | --- | --- | +| `version` | `-1` | 21 | 不展开它,definition 自行写 `1` 及以上(见「常见坑」) | +| `search` | `{}` | 22 | — 空对象,等于没有默认值 | +| `userInfo` | `{}` | 23 | — 空对象,等于没有默认值 | + +**没有任何 definition 从本 schema 导入 `SchemaMetadata`**(从 `../schemas/` 导入 `SchemaMetadata` 的定义文件全部指向 NexusPHP / Unit3D / Gazelle / GazelleJSONAPI / Luminance / Rartracker / TCG 等)。所以在本模板下不存在「展开默认值再覆写差异项」的写法,definition 是整段自写 metadata。 + +本模板同样**不提供**:`search.keywordPath`、`search.requestConfig`、`search.selectors`、`search.advanceKeywordParams`、`detail`、`list`、`searchEntry`、`category`、`levelRequirements`、`noLoginAssert`、`userInputSettingMeta`、`userInfo.pickLast/process/selectors/requestDelay/donorConfig`。 + +缺省值只由加载层补:`tags → []`、`timezoneOffset → "+0000"`(`index.ts:59-60`),`schema → "AbstractPrivateSite"`(`index.ts:41-42`),`userConfig.allowSearch` / `allowQueryUserInfo` 按 metadata 是否非空推导(`index.ts:65-70`、`105-107`)。 + +## 最小可用定义骨架 + +只写 metadata、不导出 class;私有站靠 `type` 回落本模板(对照 `definitions/ncore.ts`): + +```ts +import type { ISiteMetadata } from "../types"; + +export const siteMetadata: ISiteMetadata = { + id: "example", // 必须与文件名 example.ts 一致 + version: 1, + name: "Example", + timezoneOffset: "+0800", // 缺省 "+0000"(index.ts:60),按站点实际时区写 + + type: "private", // 不写 schema:私有站缺省回落 AbstractPrivateSite + urls: ["https://example.org/"], + + userInfo: { + pickLast: ["id", "name"], + process: [ + { + requestConfig: { url: "/profile.php" }, // responseType 缺省 document + selectors: { + id: { selector: "input[data-uid]", attr: "data-uid" }, + name: { selector: "#profile-name" }, + uploaded: { selector: "#uploaded", filters: [{ name: "parseSize" }] }, + joinTime: { selector: "#join-date", filters: [{ name: "parseTime" }] }, + }, + }, + ], + }, + + levelRequirements: [{ id: 1, name: "User" }, { id: 2, name: "Power User", interval: "P4W", uploaded: "50GB" }], +}; +``` + +需要覆写引擎行为时再加 `export default class Example extends PrivateSite {}`,导入路径见 `definitions/milkie.ts:3`(`../schemas/AbstractPrivateSite`,有的文件带 `.ts` 后缀)。 + +## 搜索配置要点 + +本模板不覆写 `getSearchResult` / `transformSearchPage` / `transformListPage`,搜索完全走 `AbstractBittorrentSite`,因此要点都在基类: + +- **请求配置生成顺序**(`schemas/AbstractBittorrentSite.ts:188-229`):`searchEntry.id` → 与 `metadata.searchEntry[id]` 合并 → 未显式 `merge: false` 时再与 `metadata.search` 合并;最后套在垫片 `{ url: "/", responseType: "document", params: {}, data: {} }` 之上。所以**不写 `responseType` 就是 `document`**,JSON 接口必须显式写 `responseType: "json"`(或像 `definitions/milkie.ts:130` 那样在 `request` 里统一改)。 +- **关键词**:`set(requestConfig, searchEntry.keywordPath || "params.keywords", keywords)`(`schemas/AbstractBittorrentSite.ts:266-268`)。本模板下 `keywordPath` 没有默认值之外的约定,直接按站点参数名写 `"params.query"`(`definitions/milkie.ts:39`)即可。 +- **`selectors.rows` 必填**:缺失时 `transformSearchPage` 直接 `throw Error("列表选择器未定义")`(`schemas/AbstractBittorrentSite.ts:504-506`),表现为搜索状态 `parseError`。行选择器在 JSON 响应下用 `get(context, selector)`,`:self` 表示响应根(479-485、512)。 +- **单行内可覆写的字段**:`id, title, subTitle, url, link, time, size, author, seeders, leechers, completed, comments, category, tags, progress, status`(`schemas/AbstractBittorrentSite.ts:44-61` 的 `defaultTorrentSelectorKey`);每个字段都可以用同名类方法 `parseTorrentRowFor{Key}` 取代 selector(575-587)。 +- **category 分类**:metadata 顶层的 `category: ISearchCategories[]` 决定搜索时怎么下发分类(`types/search.ts:202-248`)。本模板的站点形态各异,三种写法都能在仓库里找到范例:`cross: { mode: "comma" }`(`definitions/milkie.ts:29-36`)、`cross: { mode: "append" }`(`definitions/retrotoon.ts:73-80`)、以及用特殊 key `"#url"` 直接切搜索入口(`definitions/starspace.ts:36-45`,语义见 `types/search.ts:214-217`)。分类 value 必须来自站点真实参数。 +- **`list[]`**:只在「浏览器直接打开列表页时也要被识别」时写,且 `urlPattern` 命中即 `break`(`schemas/AbstractBittorrentSite.ts:659-665`)。用 AJAX 取数据的站要把 `mergeSearchSelectors` 设为 `false`(`definitions/milkie.ts:69-86`)。 +- **`detail`**:用于详情页解析与「搜索页拿不到 link」时的补链接(`schemas/AbstractBittorrentSite.ts:716-778`)。`urlPattern` 必须显式声明(`types/site.ts:207-214`)。 +- **高级搜索词**:`advanceKeywordParams` 同样没有默认值。未声明时 `imdb|xxx` 会被视同 `{ enabled: true }`(只去掉前缀后按普通文本搜索),其余前缀(douban / tmdb / tvdb 等)一律跳过并返回 `i18n.noAdvanceParams`;声明为 `false` 则连 imdb 也跳过(`schemas/AbstractBittorrentSite.ts:237-262`、`types/search.ts:102-113`)。`requestConfigTransformer` 可在请求发出前最后改写配置(`schemas/AbstractBittorrentSite.ts:281-284`)。 +- **`searchEntry`**:多搜索入口时才需要(`types/site.ts:139-144`);本模板下的站点多数不需要。 +- **`noLoginAssert`**:这是本模板站点的常用补丁——当站点未登录时返回 200 而非跳转时,用它兜底(生效条件见「用户信息」末段)。范例:`definitions/retrotoon.ts:68-71`(`urlPatterns` + `matchSelectors`)、`definitions/torrentleech.ts:316-318`(只给 `matchSelectors`)、`definitions/karagarga.ts:115`、`definitions/hdroute.ts:246`、`definitions/pussytorrents.ts:312`、`definitions/yzyy.ts:339`、`definitions/bitpt.ts:260`。 + +## 用户信息(userInfo) + +不导出 default class 时,这一段的行为完全由 `AbstractPrivateSite.getUserInfoResult`(113-215)决定。逐步说明: + +1. **入口守卫**(120-123):`!allowQueryUserInfo || !metadata.userInfo?.process` 时直接返回 `status = passParse`,不做任何请求。`allowQueryUserInfo` 要求站点在线、`metadata.userInfo` 存在、且用户配置没有显式关闭(27-29);加载层还会把 `userConfig.allowQueryUserInfo` 的缺省值设为「`userInfo` 是否非空对象」(`index.ts:65-70`、`105-107`)。**没有 `userInfo` 段或没有 `process` 的站点不会报错,只是没有个人数据。** +2. **`pickLast`**(126-128):把上一次结果里的字段并入本次结果,避免重复请求。仓库中的实际取值:`["id"]`(`definitions/ncore.ts:14`)、`["name", "joinTime"]`(`definitions/milkie.ts:97`)、`["id", "name"]`(`definitions/torrentleech.ts`、`definitions/gtnet.ts`、`definitions/starspace.ts`)、`["id", "name", "joinTime"]`(`definitions/karagarga.ts`)。字段允许范围由 `TUserInfoParseKey` 约束(`types/site.ts:24`)。 +3. **步骤跳过判定**(131-141):某一步能提供的字段是 `[...(fields ?? []), ...Object.keys(selectors ?? {})]`,若这些字段**全部**已在结果里,该步骤直接跳过。这是「已取到的字段不重复请求」的机制,也意味着**不能假设某一步一定会发请求**。 +4. **请求配置**(144-147):垫片是 `{ url: "/", params: {}, data: {}, responseType: "document" }`,与 `process[n].requestConfig` 做 `toMerged`。JSON 接口要在该步骤显式写 `responseType: "json"`。 +5. **`assertion`**(148-165):`{ id: "url" }` 表示把 URL 字符串里的 `$id$` 替换成已取到的 id;`{ id: "params.uid" }` 表示当 `params.uid` 不存在时直接 `set` 上去(本模板下 `definitions/milkie.ts` 这类单步站点不用,多步站点常用,如 `schemas/TCG.ts:72-74` 的 `/userdetails.php` + `{ id: "params.id" }`)。**断言字段缺失会 `throw`(160-163),整次采集以 `parseError` 结束且后续步骤不再执行。** +6. **`requestConfigTransformer`**(167-170)与 **`requestDelay`**(172-173,回落到 `userInfo.requestDelay`):需要按已取字段改写请求、或给单步限速时使用。 +7. **字段取值顺序**(177-191): + ① 同名类方法 `parseUserInfoFor{PascalKey}(flushUserInfo, dataDocument, requestConfig)`(动态派发见 178-181); + ② 本步骤的 `selectors[key]`; + ③ 全局回落 `userInfo.selectors[key]`; + ④ 三者都没有时**静默什么都不做**——`throw` 已被注释掉(186-189)。 +8. **等级**(194-197、218-220):仅当 `metadata.levelRequirements` 存在、`flushUserInfo.levelName` 有值、且 `levelId` 未定义时,调用 `guessUserLevelId`(`utils/level.ts:300-310`)。所以**给了 `levelRequirements` 却没取到 `levelName`,等级不会被推出来**;取到 `levelId` 的站点则直接使用。 +9. **错误分类**(199-212):`CFBlockedError → CFBlocked`、`NeedLoginError → needLogin`、其余 → `parseError`;正常结束为 `success`。 + +未登录判定(本模板的能力之一): + +- 默认值来自 `metadata.noLoginAssert`,缺省是状态码 `[401, 403, 502, 504]`(31-40)、URL 模式 `[/doLogin|login|verify|checkpoint|returnto/gi]`(42-44)、refresh 头模式同 URL 模式(46-48)、`matchSelectors: []`、`checkResponseContent: false`(83、93)。 +- `noLoginAssert` 的每一项都可以单独设成 `false` 关闭,但**不能整体关闭**;要整体关闭只能改 `type` 或覆写 `loggedCheck`(`types/site.ts:259-266`)。 +- **覆写了 `loggedCheck` 的 definition 会让 `noLoginAssert` 失效**(`types/site.ts:263`),本模板下如 `definitions/hdbits.ts:331-341`、`definitions/beyondhd.ts:615`、`definitions/sunnypt.ts:164`。 +- `noLoginAssert.checkResponseContent` 开启后会对响应文本做「过短且含 login / auth_form / not authorized」的判定(93-101)。 + +`donorConfig` 在本模板中没有引擎侧效果:`AbstractPrivateSite` 不读取它,`bonusPerHourMultiplier` 只在 `schemas/NexusPHP.ts:868-870` 生效,`isAccountKept` 仅被前端读取(`src/entries/options/views/Overview/MyData/UserLevelRequirementsTd.vue:56-57`)。 + +## 凭据、运行态与下载链路 + +本模板站点的结构千差万别,凭据与下载这几条路径常常要自己接: + +- **需要用户输入的凭据**(API token、额外的用户名/passkey):用 `userInputSettingMeta` 声明表单项(`types/site.ts:378-382`),运行时从 `this.userConfig.inputSetting.` 读取(`types/site.ts:435-438`)。范例:`definitions/milkie.ts:114-121` 声明 `token`,`definitions/milkie.ts:135` 在 `request` 里注入 `x-milkie-auth` 头;`definitions/hdbits.ts:311-314` 声明 `username`/`passkey`,`definitions/hdbits.ts:324-325` 放进 POST data。 +- **跨调用复用的一次性凭据**:`storeRuntimeSettings(key, value)` / `retrieveRuntimeSettings(key)`(`schemas/AbstractBittorrentSite.ts:108-117`)把值持久化到扩展存储,避免每次搜索都重新取 token。范例:`definitions/zhuque.ts:299-324` 缓存并回读 `csrfToken`。 +- **下载链接**:`getTorrentDownloadLink` 会先用 `detail.selectors.link` 补(`schemas/AbstractBittorrentSite.ts:764-770`),再追加用户的 `userConfig.downloadLinkAppendix`(772-775)。 +- **下载请求**:`getTorrentDownloadRequestConfig` 把 `metadata.download.requestConfig` 合并到 `{ baseURL: this.url, url: torrentDownloadLink, method: "GET", timeout }` 之上(`schemas/AbstractBittorrentSite.ts:784-790`,字段定义见 `types/site.ts:244-258`);`download.interval` 会被提升为批量下载间隔(`types/site.ts:252-257`)。下载要 POST 或带一次性 token 时覆写该方法(`definitions/gtnet.ts:216-251`)。 +- **请求延迟**:站点级 `requestDelay` 在 `request()` 里统一等待(`schemas/AbstractBittorrentSite.ts:126`),`search.requestDelay` 与 `userInfo.process[n].requestDelay` 是叠加关系(`types/site.ts:119-124`、`339-342`)。 +- **开关语义**:没有 `search` 配置时 `allowSearch` 为 false(`schemas/AbstractBittorrentSite.ts:86-88`、`index.ts:105`);站点 `isDead` 或用户把 `isOffline` 打开后 `isOnline` 为 false,进而 `allowQueryUserInfo` 也为 false(`schemas/AbstractPrivateSite.ts:27-29`)。写 `isDead: true` 的站点只需保留身份字段即可(`definitions/baconbits.ts`、`definitions/sdbits.ts`)。 + +## 常被覆写的类方法 + +| 方法(签名见源码) | 何时覆写 | 行号 | +| --- | --- | --- | +| `protected override loggedCheck(res: AxiosResponse): boolean` | 未登录时返回 200 + JSON 错误体/登录框;默认只处理状态码、responseURL、refresh 头、matchSelectors 与响应文本 | 50-107;范例 `definitions/hdbits.ts:331-341`、`definitions/beyondhd.ts:615`、`definitions/fsm.ts:277`、`definitions/sunnypt.ts:164` | +| `public override async getUserInfoResult(lastUserInfo = {}): Promise` | 需要在默认流程之外再补请求/汇总(先用 `super` 拿基础数据再加工) | 113-215;范例 `definitions/torrentleech.ts:362-383`、`definitions/karagarga.ts:293`、`definitions/zhixing.ts:208`、`definitions/zhuque.ts:299`、`definitions/beyondhd.ts:532` | +| `protected async parseUserInfoFor{Field}(flushUserInfo, dataDocument, requestConfig)` | 某字段的取值需要单独发请求或跨行汇总。方法名按字段 PascalCase 动态派发,因此不需要注册;调用点在流程内,实际定义通常只声明第一个参数 | 动态派发 177-191;范例 `definitions/torrentleech.ts:400-430`、`definitions/zhixing.ts:175` | +| `protected override guessUserLevelId(userInfo: IUserInfo): TLevelId` | 站点等级无法用名称/条件表推出(如按用户组 id 映射) | 218-220;范例 `definitions/52pt.ts:245`、`definitions/speedapp.ts:541` | +| `public override async request(axiosConfig, checkLogin = true)` | 统一注入 token / 自定义请求头,或统一改 `responseType`(基类方法) | `schemas/AbstractBittorrentSite.ts:119-167`;范例 `definitions/milkie.ts:125-139`、`definitions/hdbits.ts:318-329`、`definitions/yemapt.ts:357`、`definitions/zhuque.ts:252` | +| `public override async transformSearchPage(doc, searchConfig): Promise` | 需要对整个列表做后处理(补分类、去重、改写字段) | `schemas/AbstractBittorrentSite.ts:502-558`;范例 `definitions/bakabt.ts:240-256`、`definitions/speedapp.ts:554` | +| `protected override parseTorrentRowFor{TorrentKey}(torrent, row, searchConfig)` | 逐字段覆盖:`Link`(拼带凭据的下载链接)、`Tags`(打标签)、`Time`(特殊时间格式)等 | 调度见 `schemas/AbstractBittorrentSite.ts:575-587`;范例 `definitions/milkie.ts:141-144`(Link)、`definitions/hdbits.ts:343-350`(Link)、`definitions/torrentleech.ts:385-397`(Tags) | +| `public override async getTorrentDownloadLink(torrent: ITorrent): Promise` | 搜索页拿不到 link、需要在详情页或接口补 | `schemas/AbstractBittorrentSite.ts:764-778`;范例 `definitions/bakabt.ts:258-262`、`definitions/pornolab.ts:264`、`definitions/yzyy.ts:354`、`definitions/speedapp.ts:605` | +| `public override async getTorrentDownloadRequestConfig(torrent: ITorrent): Promise` | 下载需要 POST / 特殊 header / 一次性 token | `schemas/AbstractBittorrentSite.ts:784-790`;范例 `definitions/gtnet.ts:216-251`、`definitions/yemapt.ts:398` | +| `public override async transformDetailPage(doc: Document): Promise` | content-script 的详情页结构与默认推断不符 | `schemas/AbstractBittorrentSite.ts:716-755`;范例 `definitions/hdroute.ts:291`、`definitions/exttorrents.ts:217` | +| `protected override async parseWholeTorrentFromRow(torrent, row, searchConfig)` | 整行解析逻辑需要改写(不只是某个字段) | `schemas/AbstractBittorrentSite.ts:560-613`;范例 `definitions/hdroute.ts:253` | +| `protected async storeRuntimeSettings(key, value)` / `retrieveRuntimeSettings(key)` | 需要把一次性凭据(csrfToken 等)持久化跨调用复用 | `schemas/AbstractBittorrentSite.ts:108-117`;范例 `definitions/zhuque.ts:299-324` | +| `protected fixParsedTorrent(torrent, row, searchConfig)` | 基类预留的统一后处理钩子;**当前 definitions 中无覆写实例** | `schemas/AbstractBittorrentSite.ts:639-645` | + +覆写时保持签名一致并加 `override` 关键字(`pnpm check` / `vue-tsc --noEmit` 会校验)。 + +## 范例定义 + +- `definitions/milkie.ts` — 显式 `schema: "AbstractPrivateSite"` + `default class extends PrivateSite`:用 `userInputSettingMeta` 收 token(114-121)、在 `request` 里注入请求头(125-139)、`parseTorrentRowForLink` 拼下载 key(141-144),并完整自写 `search`/`list`/`detail`。 +- `definitions/ncore.ts` — 最小可用形态:不写 schema、不导出 class,只靠 `userInfo.process`(13-63)与 `levelRequirements`(65-93)走默认模板。 +- `definitions/torrentleech.ts` — `getUserInfoResult` 里 `super` 之后追加两个 `parseUserInfoFor*` 步骤(362-430),并自定义 `noLoginAssert`(316-318)。 +- `definitions/bakabt.ts` — `transformSearchPage` 后处理(用上一行分类补齐 stub 行)与 `getTorrentDownloadLink` 修正相对链接(239-263)。 +- `definitions/bitpt.ts` / `definitions/hdbits.ts` — 同为「不写 schema 字段」的两种走向:前者不导出 class,`category`/`search`/`detail`/`userInfo`/`levelRequirements` 全部手写(73-75、114、244、264、348);后者导出 default class,`request` 注入 api 凭据、`loggedCheck` 校验接口 status、`parseTorrentRowForLink`/`ForTags` 逐字段覆盖(317-377)。 + +站点已死亡时只留身份字段也是合法定义:`definitions/baconbits.ts`、`definitions/sdbits.ts`、`definitions/pornbits.ts`。 + +## 常见坑 + +1. **不写 `schema` 不等于使用本模板**:只要导出了 `default class`,无论 `schema` 写什么(甚至不写)都用那个 class(`index.ts:81-84`、93-102)。`definitions/qingwa.ts` 继承 `NexusPHP` 而完全不写 `schema`,实例行为仍由 NexusPHP 决定。 +2. **`schema` 字段会影响时区缺省,但要看最终对象**:`index.ts:60` 只认字面量 `"NexusPHP"` 给 `"+0800"`,其余一律 `"+0000"`。该判断作用在 definition 导出的最终 metadata 上,而 `...SchemaMetadata` 会同时带入 `schema: "NexusPHP"`(`schemas/NexusPHP.ts:348`)与 `timezoneOffset: "+0800"`(`schemas/NexusPHP.ts:350`)——`definitions/qingwa.ts:343` 就是这种写法(文件里没有 `schema` 行,但展开后 `schema` 为 `"NexusPHP"`,时区也是 `+0800`)。只有「既不展开引擎 `SchemaMetadata`、又不写 `schema`」时才吃 `"+0000"` 缺省。 +3. **忘记 `type: "private"`**:`index.ts:41-42` 的类型判断落空后会把 `schema` 补成 `"AbstractBittorrentSite"`,站点将失去个人信息的采集入口(`AbstractBittorrentSite` 没有 `getUserInfoResult`)。 +4. **`version` 不要写 `-1`**:`SchemaMetadata`(若展开)给的是 `-1`,但仓库中所有本模板定义写的是大于 0 的数(多数 `1`,`definitions/gtorg.ts`/`definitions/gtru.ts`/`definitions/audionews.ts`/`definitions/speedapp.ts`/`definitions/starspace.ts` 写 `2`,`definitions/yzyy.ts` 写日期 `20260813`)。本模板定义通常根本不展开 `SchemaMetadata`。 +5. **`process` 里 `fields` 写了却没给 selector 或 `parseUserInfoFor*`,字段会静默丢失**:`schemas/AbstractPrivateSite.ts:186-189` 的 `throw` 是被注释掉的,不会报错,只是数据一直为空。排查「某字段永远是空」时先看这一条。 +6. **`assertion` 缺字段会中断整次采集**(160-163):例如第二步依赖第一步的 `id`,而第一步没写 `id` 选择器,结果直接 `parseError`,后面的步骤一次都不会执行。 +7. **`pickLast` 只复用上一次缓存**(126-128):首次采集时缓存为空,不能假设 `pickLast` 的字段一定存在;依赖它们的 `assertion` 会因此抛错。 +8. **步骤可能被整体跳过**(131-141):判定用的是 `fields ∪ selectors` 的**全部**字段是否已存在。把 `id` 既写进第一步的 `selectors`、又写进 `pickLast`,第二次采集时第一步可能直接不发请求——如果依赖该请求的副作用(如刷新 token),要改用 `getUserInfoResult` 覆写。 +9. **`responseType` 默认是 `document`**(144-147):JSON 接口必须在每个步骤的 `requestConfig` 里写 `responseType: "json"`,或像 `definitions/milkie.ts:130` 那样在 `request` 中统一改。 +10. **`noLoginAssert` 只在未覆写 `loggedCheck` 时生效**(`types/site.ts:263`),且不能整体禁用(265);覆写 `loggedCheck` 时要自己处理未登录情形,否则会退化成「把登录页当作正常页面解析」。 +11. **`search.selectors.rows` 缺失会直接抛错**(`schemas/AbstractBittorrentSite.ts:504-506`):本模板不提供任何搜索默认值,新建定义时最容易漏的就是 `rows`。 +12. **`userInfo.selectors` 是全局回落、优先级低于步骤选择器**(183):把字段写在这一层可以让多个步骤共用,但要清楚它不会覆盖步骤里已有的同名选择器。 +13. **`donorConfig` 在本模板无效**:只有 `schemas/NexusPHP.ts:868-870` 消费 `bonusPerHourMultiplier`,纯本模板站点写它没有引擎侧效果。 +14. **`levelName` 与 `levelRequirements` 必须配套**(194-197):只写等级表而没取到 `levelName` 时等级不会显示;匹配规则是「等级表 name 包含页面 levelName」(`utils/level.ts:302-310`),文案不一致同样匹配不上。 diff --git a/.dsh/skills/ptd-site-definition/references/engines/avistaz-network.md b/.dsh/skills/ptd-site-definition/references/engines/avistaz-network.md new file mode 100644 index 000000000..6425629c8 --- /dev/null +++ b/.dsh/skills/ptd-site-definition/references/engines/avistaz-network.md @@ -0,0 +1,155 @@ +# AvistazNetwork 站点定义规范 + +AvistaZ Network(AvistaZ / CinemaZ / PrivateHD / ExoticaZ / AnimeZ 等)是同一套私有 tracker 软件的多站点网络:搜索统一走站点自带的 Jackett API(JSON + Bearer token),网页部分(列表页、下载历史页、资料页)结构也高度一致。引擎实现在 `src/packages/site/schemas/AvistazNetwork.ts`(**实际 838 行**),默认类继承 `AbstractPrivateSite`(`:517`)。因此定义文件的工作量很小:展开 `...SchemaMetadata` 覆写站点差异,需要站点专属标签时再导出一个 `extends AvistazNetwork` 的 `default class`(现有 5 个定义全部导出了 default class)。 + +下文行号若未特别说明,均指 `schemas/AvistazNetwork.ts`。 + +## 判定站点是否属于该引擎 + +正向特征(有其一即可进一步验证): + +- 站点自述属于 AvistaZ Network(`definitions/exoticaz.ts:38`、`definitions/privatehd.ts:37`),或由 avistaz.to 家族运营。 +- 搜索走 `/api/v1/jackett/torrents`(JSON),鉴权走 `/api/v1/jackett/auth`(POST 提交 `username`/`password`/`pid`)(`:683-697`、`:316-320`)。 +- 种子详情页 URL 为 `/torrent/{id}`,下载链接含 `/download/torrent/`(`:246-263`)。 +- 资料页 `/profile/{name}`,另有 `/profile/{name}/active`(做种列表)与 `/profile/{name}/bonus`(时魔)(`:601`、`:637`、`:667`)。 +- Bootstrap + FontAwesome 类名体系:`.ratio-bar`、`span.badge-extra.fa-database` / `fa-arrow-up` / `fa-arrow-down` / `fa-check`、分类图标 `i[data-original-title]`(`:96-102`、`:296-300`、`:410-423`)。 +- 账号配置需要三项:用户名、密码、PID(`:493-514`)。 + +易混淆情况: + +- **Gazelle 系**:同为英文影视站,但路径是 `torrents.php?id=`,没有 Jackett API。 +- **Unit3D 系**:类名是 `torrent-search--list__*` / `ratio-bar__*`,且下载链接为 `/torrents/download/{id}`。 +- **AnimeZ**:资料页已换成 datagrid 模板(用 `.datagrid-item`/`.datagrid-title`/`.datagrid-content` 取值,`definitions/animez.ts:23-32`),列表页字段名也不同,但仍在网络内——不要因为页面模板不同就改判引擎,改的是 `search.selectors` 与 userInfo 选择器。 +- **同网络内各站的编号表不通用**:AvistaZ 的 res 是 `1:SD/2:720p/3:1080p/6:2160p`(`definitions/avistaz.ts:11-18`),ExoticaZ 是 `1:240p…9:VR 360°`(`definitions/exoticaz.ts:19-29`),PrivateHD 又是另一套(`definitions/privatehd.ts:10-16`)。分类编号必须逐站核对。 + +## SchemaMetadata 默认值 + +`schemas/AvistazNetwork.ts:304-515` 导出的 `SchemaMetadata`(类型为 `Pick`): + +| 字段路径 | 默认值 | 行号 | definition 是否通常需要覆盖 | +| --- | --- | --- | --- | +| `version` | `0` | 308 | 现状 5 个定义都显式写 `1`(animez 为 `3`) | +| `schema` | `"AvistazNetwork"` | 309 | 不用改 | +| `type` | `"private"` | 310 | 不用改 | +| `timezoneOffset` | `"-0400"` | 312 | 需要:5 个定义全部覆写为 `"+0100"` | +| `search.keywordPath` | `"params.search"` | 315 | 不用改 | +| `search.requestConfig` | `url: "/api/v1/jackett/torrents"`、`responseType: "json"`、`params: { in: 1, limit: 50 }` | 316-320 | 一般不改;只改 `limit` 时要展开 `params`(见常见坑 3) | +| `search.advanceKeywordParams` | `imdb` / `tvdb` / `tmdb` 三项,均把 `params.search` 改名为 `params.{imdb,tvdb,tmdb}` | 321-349 | 站点不支持时显式关掉(`definitions/animez.ts:96-100`、`definitions/exoticaz.ts:101-105`) | +| `search.selectors` | 17 个键(含 `rows`;JSON 取值路径),见「搜索配置要点」 | 350-380 | 站点 API 字段名不同时覆写(animez 改 `title`/`category`) | +| `list` | `[listTorrentPageMetadata, listHistoryPageMetadata]`:`/torrents` 与 `/profile/(.+)/history`,两者均 `mergeSearchSelectors: false` | 241-271、274-302、383 | 页面 DOM 变了才改(exoticaz 整段重写) | +| `detail.urlPattern` | `["/torrent/"]` | 386 | 不用改 | +| `detail.selectors` | `id`(`:self` + 正则)、`title`、`link` | 387-398 | 少数站改 `title`(exoticaz) | +| `userInfo.pickLast` | `["name"]` | 402 | 一般不改 | +| `userInfo.selectors` | 14 个字段,见「用户信息」 | 403-490 | 资料页模板不同的站要改(animez 覆写 10 个) | +| `userInfo.process` | **未提供** | — | 定义 `process` 会整体改走 `AbstractPrivateSite` 流程(`:544-547`) | +| `userInfo.requestDelay` | **未提供** | — | 站点限速时补;它是 `sleepAction` 的依据(`:599`、`:616`、`:634`) | +| `userInfo.donorConfig` | **未提供**,本引擎也不消费 | — | 写了无效(见常见坑 9) | +| `noLoginAssert` | **未提供** | — | 一般不需要;默认值来自 `AbstractPrivateSite.ts:31-48` | +| `category` | **未提供** | — | 需要:不写就没有分类筛选 | +| `levelRequirements` | **未提供** | — | 需要:不写则不会有 `levelId`(`:586-588`) | +| `userInputSettingMeta` | username / password / pid,三项均 `required: true` | 493-514 | 现状都写 `[...SchemaMetadata.userInputSettingMeta!]` 显式展开 | +| `searchEntry` | **未提供** | — | 只有 exoticaz 使用(见「搜索配置要点」) | + +另**未提供**:`search.requestDelay`、`detail.requestConfig`、`download`、`officialGroupPattern`。 + +### 为网络中的新站点写定义时要改哪些字段 + +1. **站点身份**:`id`(= 文件名)、`name`、`aka`、`description`、`tags`、`urls`(敏感站点写 rot13)、`version: 1`。 +2. **`timezoneOffset`**:默认是 `-0400`,网络内现有定义都按目标站写成 `"+0100"`。 +3. **`category`**:至少给「分类」与「促销」两组;`key` 必须用站点 API 真实参数名,编号表逐站核对。促销组直接复用导出的 `avzNetDiscountMap`(`:195-199`)。 +4. **`levelRequirements`**:等级名各站不同(AvistaZ/PrivateHD/ExoticaZ 用同一套 `Leech/Newbie/Member/V.I.P./…`,AnimeZ 另有 `Power User`、`Staff` 等)。等级 `name` 必须与页面显示文本一致(`types/userinfo.ts:73-77`)。 +5. **`userInputSettingMeta`**:写 `[...SchemaMetadata.userInputSettingMeta!]` 即可,三项默认值已够用。 +6. **API 字段名不同时**:覆写 `search.selectors`(AnimeZ 把 `title` 改成 `release_title`、`category` 改成 `format`,`definitions/animez.ts:101-106`),并关掉不支持的高级搜索词。 +7. **资料页模板不同时**:覆写 `userInfo.selectors`,并在 default class 里覆写 `getBaseInfoFromSite` / `getExtendInfoFromProfile`(`definitions/animez.ts:242-274`)。 +8. **列表页 DOM 不同时**:整段覆写 `list` 的两条规则,并保持 `mergeSearchSelectors: false`(`definitions/exoticaz.ts:112-181`)。 +9. **站点专属标签**:default class 覆写 `parseTorrentRowForTags`(`definitions/avistaz.ts:140-168`)。 + +## 最小可用定义骨架 + +```ts +import { type ISiteMetadata } from "../types"; +import AvistazNetwork, { SchemaMetadata, avzNetDiscountMap } from "../schemas/AvistazNetwork.ts"; + +const categoryMap: Record = { 0: "All", 1: "Movies", 2: "TV" }; +const opts = (map: Record) => Object.entries(map).map(([value, name]) => ({ name, value: +value })); +const cross = { mode: "appendQuote" } as const; // 多选:生成 params.category = { "1": 1 } + +export const siteMetadata: ISiteMetadata = { + ...SchemaMetadata, + version: 1, + id: "example", // 必须与文件名 example.ts 一致 + name: "Example", + tags: ["电影"], + timezoneOffset: "+0100", + type: "private", + schema: "AvistazNetwork", + urls: ["uggcf://rknzcyr.gb/"], // rot13,加载时会还原 + category: [ + { name: "分类", key: "category", keyPath: "params", options: opts(categoryMap), cross }, + { name: "促销", key: "discount", keyPath: "params", options: opts(avzNetDiscountMap), cross }, + ], + levelRequirements: [{ id: 1, name: "Leech" }, { id: 2, name: "Member" }], + userInputSettingMeta: [...SchemaMetadata.userInputSettingMeta!], +}; +``` + +## 搜索配置要点 + +- **默认请求**:`GET /api/v1/jackett/torrents`,`responseType: "json"`,固定参数 `in: 1`、`limit: 50`(最大 50 条结果,源码注释 `:319`);关键词写入 `params.search`(`:315`)。 +- **鉴权由 `request()` 自动接管**:URL 严格等于 `/api/v1/jackett/auth` 时改成 POST 并注入 `username`/`password`/`pid`;URL 以 `/api/v1/jackett/torrents` 开头时改成 GET 并加 `Authorization: Bearer `(`:683-706`)。token 存在 `runtimeSettings` 的 `authToken`/`authExpiry` 里复用(`:763-813`)。**这两条判断都与 URL 字符串强耦合**:把 `search.requestConfig.url` 改到别的路径,鉴权头就不会再注入。 +- **高级搜索词**:默认可直接用 `imdb|tt…`、`tvdb|…`、`tmdb|…`,三者的 `requestConfigTransformer` 只是把 `params.search` 改名(`:321-349`)。站点不支持时要显式写 `{ enabled: false }` 或 `false`。 +- **默认覆盖的种子字段**(JSON 路径):`rows: data`、`id: id`、`title: file_name`、`subTitle: {text: ""}`、`url: url`、`link: download`、`category`(取 `category` 对象 `Object.values()[0]`)、`time: created_at`(`parseTime`)、`size: file_size`、`author: {text: ""}`、`seeders: seed`、`leechers: leech`、`completed: completed`、`comments: {text: "N/A"}`、`progress: {text: 0}`、`status: {text: unknown}`、`ext_imdb: movie_tv.imdb`(`extImdbId`)(`:350-380`)。 +- **覆写 `search.selectors` 时**:必须展开 `...SchemaMetadata.search!.selectors!`,否则会丢掉其余字段(`definitions/animez.ts:101-106`)。 +- **category 与 cross**:`keyPath: "params"` + `cross: { mode: "appendQuote" }` 会生成 `requestConfig.params.category = { "1": 1 }`(`SetSearchSolution/utils.ts:66-68`),由 axios 序列化后交给 Jackett API。单值开关不写 `cross`(`definitions/avistaz.ts:73-84` 的「特殊」组)。ExoticaZ 的「类型」组用 `cross: { mode: "append", key: "" }` 生成 `params.{value}=1`(`definitions/exoticaz.ts:91-96`)。 +- **`list`**:两条默认规则都用于 content-script 的网页解析,均显式 `mergeSearchSelectors: false`(`:243`、`:276`),因为搜索走 JSON API、DOM 选择器不能当垫片——自定义 `list` 时必须照做。 +- **`searchEntry`**:SchemaMetadata 不定义;回落规则见 `entries/options/stores/metadata.ts:192`(默认 `{ default: {} }`)。网络内只有 ExoticaZ 声明了一个默认关闭的入口 `area_adult`(`definitions/exoticaz.ts:108-110`),同时声明了名为「搜索入口」的分类项(key `entry`,`generateRequestConfig` 返回空 params,`definitions/exoticaz.ts:50-60`)。 +- **`detail`**:默认只解析 `title`、`link`,`id` 的 `elementProcess` 用 `:self` 的 URL 匹配 `/detail/(\d+)/`(`:390-394`)——与 `detail.urlPattern` 的 `/torrent/` 并不匹配,实际通常返回整条 URL;站点若有更可靠的 id 来源,直接在定义里覆写。 + +## 用户信息(userInfo) + +- **默认不抓取用户数据**。`enableAvistazUserInfoFetching` 只在构建期 `VITE_ENABLE_AVISTAZ_USER_INFO_FETCHING === "true"` 时为真(`:22`;类型声明 `src/vite-env.d.ts:4`)。关闭时 `getUserInfoResult` 直接返回 `status: passParse`,只有 `name`(取自 `userConfig.inputSetting.username`)与 `levelName: "应站点要求,不启用用户数据获取"`(`:530-537`)。这是站点方的要求(源码注释 `:518-522`),**不要在定义里绕过**。 +- **启用后的流程**(`:523-594`):`allowQueryUserInfo` 为假 → `passParse`(`:539-542`);`userInfo.process` 是数组 → 交给 `AbstractPrivateSite.getUserInfoResult`(`:544-547`);否则依次 `getBaseInfoFromSite(userName)` → `getExtendInfoFromProfile(userName)`,只要前面拿到过资料页字段,再执行 `getUserSeedingTorrents` 与 `getUserBonusPerHour`(每步都用 `mergeUserInfo` 包裹,单步失败不影响整体,`:184-193`)。 +- **fields 三批**:`name/levelName/uploaded/downloaded/ratio/bonus`(`:605-612`)→ `joinTime/lastAccessAt/uploads/snatches/seeding/leeching/hnrUnsatisfied`(`:622-630`)→ `seedingSize`(`:633-660`)、`bonusPerHour`(`:662-677`)。 +- **selectors 默认覆盖 14 个字段**:`name`(资料表 "Username" 或 `.ratio-bar` 徽章)、`levelName`("Rank")、`uploaded`/`downloaded`/`ratio`/`bonus`(资料表或 `.ratio-bar` 值)、`bonusPerHour`(积分页表格/标题里的 `points per hour`)、`joinTime`("Joined",`dd MMM yyyy hh:mm a`)、`lastAccessAt`("Last Access")、`uploads`("Uploads")、`snatches`("Downloads")、`seeding`("Seeds"/"Seeding")、`leeching`("Leeching"/"Peers")、`hnrUnsatisfied`("Hit & Run")(`:403-490`)。**未覆盖**:`id`、`messageCount`、`invites`、`isDonor`、`posts`、`trueRatio`、`seedingTime`。 +- **`pickLast` 默认 `["name"]`**(`:402`):命中缓存时不再重复解析用户名。覆写 `userInfo` 时必须展开 `...SchemaMetadata.userInfo!`,否则会连带丢掉 `pickLast`(animez 显式重写了 `pickLast`,`definitions/animez.ts:110-112`)。 +- **`noLoginAssert` 未提供**,且两个 Jackett API 请求**不经过 `super.request`**(`:708-710`),因此 `loggedCheck` 系列断言不适用于搜索接口(源码注释 `:735`)。 +- **`donorConfig` 无效**:本引擎不读取它,`getUserBonusPerHour` 直接采用页面值(`:662-677`)。仓库中只有 `NexusPHP.ts:868-871` 会应用 `bonusPerHourMultiplier`。 +- **`levelRequirements` 与 `levelId`**:仅当 `levelRequirements` 与 `levelName` 同时存在、且 `levelId` 缺失时才调用 `guessUserLevelId`(`:586-588`、`AbstractPrivateSite.ts:218-220`)。因此不写 `levelRequirements` 的站点(如 `definitions/cinemaz.ts`)不会得到 `levelId`。 + +## 常被覆写的类方法 + +| 方法 | 何时覆写 | 行号 / 范例 | +| --- | --- | --- | +| `public override async getUserInfoResult(lastUserInfo = {})` | 需要改整套抓取流程或状态判定;5 个现有定义都没覆写 | 523-594 | +| `protected async getBaseInfoFromSite(userName)` | 基础资料页不是 `/profile/{name}`,或字段分组不同 | 596-613;`definitions/animez.ts:242-262` | +| `protected async getExtendInfoFromProfile(userName)` | 同上,抓 `joinTime`/`lastAccessAt` 等第二批字段 | 615-631;`definitions/animez.ts:264-274` | +| `protected async getUserSeedingTorrents(userName)` | 需改做种量统计口径(默认翻 `/profile/{name}/active`,最多 100 页,`getActivePageCount` 上限 100) | 633-660 | +| `protected async getUserBonusPerHour(userName)` | 时魔页面不是 `/profile/{name}/bonus` | 662-677 | +| `public override async request(axiosConfig, checkLogin = true)` | 需改 API 鉴权/重试;注意只对两个 Jackett 路径生效,其余仍走 `super.request` | 679-760 | +| `public async getAuthToken(lastUserInfo = {})` | token 的缓存键或过期策略变化 | 763-813 | +| `protected override parseTorrentRowForTags(torrent, row, searchConfig)` | 需要增加折扣外的站点标签;默认实现是**覆盖** `tags`(`:835`),子类必须先 `super` 再 push | 815-837;`definitions/avistaz.ts:140-168` | + +覆写时保持签名一致并加 `override` 关键字(`pnpm check` 会校验)。可用类型:`IAvzNetRawTorrent`(`:201-238`)。 + +## 范例定义 + +- `definitions/avistaz.ts` — 网络内最完整的模板:分类/分辨率/剧集包/促销/特殊五组 `category`、完整 `levelRequirements`、default class 追加「完结/中配/中字」。 +- `definitions/animez.ts` — API 字段名不同的站:覆写 `search.selectors.title`/`category`、关掉三个高级搜索词、按 datagrid 资料页覆写 `userInfo.selectors` 与两个 `get*Info*` 方法、在 `parseTorrentRowForTags` 里改 `title`/`category`。 +- `definitions/exoticaz.ts` — 列表页 DOM 与 AvistaZ 不同:整段覆写两条 `list`、覆写 `detail.selectors.title`,并用 `searchEntry` 声明默认关闭的成人入口。 +- `definitions/privatehd.ts` — 与 AvistaZ 同构但编号表不同、带 `favicon` 与 `collaborator` 的标准写法。 +- `definitions/cinemaz.ts` — 最小形态:只写 metadata + 一个追加标签的 default class,没有 `levelRequirements`(因此没有 `levelId`)。 + +## 常见坑 + +1. **默认构建下 userInfo 选择器完全不生效**:`VITE_ENABLE_AVISTAZ_USER_INFO_FETCHING` 未开启时 `getUserInfoResult` 在 `:530-537` 就返回了,网络上任何一处 `userInfo.selectors` 覆写都不会被执行。用户报「个人信息只有用户名」属于预期行为。 +2. **定义 `userInfo.process` 会切换实现**:`:544-547` 判定到数组就走 `AbstractPrivateSite` 的多步流程,与网络默认流程完全不同;两条路径的字段来源不一致。 +3. **只改 `limit` 会丢掉 `in: 1`**:animez 写的是 `params: { limit: 50 }`(`definitions/animez.ts:92-95`),在 `...requestConfig` 之后整段替换了 `params`。要保留默认参数必须显式写 `params: { ...SchemaMetadata.search!.requestConfig!.params, limit: 50 }`。 +4. **改 `search.requestConfig.url` 会破坏鉴权**:`request()` 用字符串比较/前缀匹配判定 API(`:683-684`),换路径后既不注入 Bearer token,也不会把 401/403 走 token 刷新重试(`:736-749`)。 +5. **Jackett API 的 400/404/422 被当作「无结果」**:`:751-754` 把它们转成 `NoTorrentsError`,排查「搜不到」时不要只看 HTTP 状态码。 +6. **列表页 id 只取一位数字**:`definitions` 中 `/\/torrent\/(\d)/`(`:253`)只捕获 1 位,而历史页用 `\d+`(`:286`)。种子 id 超过一位时列表页会解析出错误 id(疑似历史遗留 bug,照抄时请改成 `\d+`)。 +7. **`detail.selectors.id` 与 `urlPattern` 不一致**:`elementProcess` 匹配 `/detail/(\d+)/`(`:390-394`),但 `detail.urlPattern` 是 `/torrent/`(`:386`),通常匹配失败并回落到整条 URL。 +8. **`parseTorrentRowForTags` 覆盖而非追加**:默认实现直接 `torrent.tags = tags`(`:835`),子类必须先取 `super` 的结果再 push(`definitions/avistaz.ts:146-147`)。 +9. **`donorConfig` 在本引擎无效果**:只有 `NexusPHP.ts:868-871` 消费 `bonusPerHourMultiplier`。 +10. **`SchemaMetadata.userInfo` 是浅合并**(同其它引擎):`userInfo: { selectors: {...} }` 会丢掉 `pickLast`;正确写法是 `...SchemaMetadata.userInfo!` 后再展开 `selectors`。 +11. **分类对象取值只取第一个**:JSON 里 `category` 是 `{[key]: string}`(`:209-211`),默认选择器取 `Object.values()[0]`(`:357-366`);AnimeZ 直接换成 `format` 字段(`definitions/animez.ts:104`)。 +12. **`userInputSettingMeta` 的 hint 文本拼接缺空格**(`:497`、`:503`、`:509-510`),是既有瑕疵,新增定义时别照抄这个拼接方式。 diff --git a/.dsh/skills/ptd-site-definition/references/engines/gazelle-json-api.md b/.dsh/skills/ptd-site-definition/references/engines/gazelle-json-api.md new file mode 100644 index 000000000..307802288 --- /dev/null +++ b/.dsh/skills/ptd-site-definition/references/engines/gazelle-json-api.md @@ -0,0 +1,137 @@ +# GazelleJSONAPI 站点定义规范 + +`GazelleJSONAPI` 服务于**提供完整 JSON API 的 Gazelle 站**——搜索与用户信息都走 `/ajax.php?action=…`,而不是解析 HTML 表格。`schemas/Gazelle.ts:205-208` 的注释把这作为选型规则:能拿到完整 JSON 就用本 schema,否则用 `Gazelle`。 + +它与 `Gazelle` 共用基类 `GazelleBase`(`schemas/Gazelle.ts:375`,提供 `getSeedingSize`、`getUserTorrentList`、`getTorrentDownloadLinkFactory`),但搜索与用户信息的实现完全独立(`schemas/GazelleJSONAPI.ts:300`);本文件 519 行,下文行号未特别说明时均指该文件。 + +## 判定站点是否属于该引擎 + +- 请求 `/ajax.php?action=browse`(搜索)、`action=index`(账号概览)、`action=user&id=`(用户详情)后返回 JSON(226-236、303-316、488-493)。 +- 返回体统一为 `{ status: "success", response: {...} }`,搜索结果是 `response.results`(157、400-402)。 +- 下载链接形如 `torrents.php?torrentid=`,由 `GazelleBase.getTorrentDownloadLinkFactory("torrentid")` 补成 `torrents.php?action=download&id=`(419-422、`schemas/Gazelle.ts:436-455`)。 +- 页面仍是 Gazelle 体系(`torrents.php`、`user.php`),但列表页 HTML 不参与解析。 + +易混淆情况: + +- **Gazelle**:`/ajax.php` 不可用或返回 HTML 的站点用 `schema: "Gazelle"`(见 [gazelle.md](gazelle.md))。两者的选择器**不能互抄**:本 schema 的 `userInfo.selectors` 是 JSON 路径(`response.userstats.uploaded`),Gazelle 的是 DOM 选择器。 +- **Luminance**:同样是 `GazelleBase` 子类,但走 `/torrents.php?action=advanced` 的 HTML 解析。 +- **NexusPHP 的 JSON 接口站**(如 zhuyue/rousipro):路径是 `/api/...`,返回结构不同,不能套用本 schema。 + +## SchemaMetadata 默认值 + +`schemas/GazelleJSONAPI.ts:226-298` 导出的 `SchemaMetadata`——**只有 `version`、`search`、`userInfo` 三项**: + +| 字段路径 | 默认值 | 行号 | definition 是否通常需要覆盖 | +| --- | --- | --- | --- | +| `version` | `0` | 227 | 现状全部显式写 `1`(`definitions/redacted.ts`、`definitions/orpheus.ts` 等) | +| `search.keywordPath` | `"params.searchstr"` | 229 | 不需要 | +| `search.requestConfig` | `url: "/ajax.php"`、`responseType: "json"`、`params.action: "browse"` | 230-236 | 一般不需要;追加筛选参数时展开 `params` | +| `search.selectors` | **未提供,也不需要** | — | 不要写 DOM 选择器(见「常见坑」1) | +| `userInfo.pickLast` | **未提供** | — | 需要时可补,减少重复请求 | +| `userInfo.process` | **未提供,且写了也不生效** | — | 见「常见坑」2 | +| `userInfo.selectors` | 16 个字段的 JSON 路径 | 239-296 | 站点接口字段不同时逐个覆写 | +| `userInfo.requestDelay` | **未提供** | — | 需要限速时补(只作用于 `getUserExtendInfo`,489) | +| `category`、`levelRequirements`、`list`、`detail`、`noLoginAssert`、`donorConfig`、`userInputSettingMeta`、`searchEntry`、`timezoneOffset` | **未提供** | — | `category`(搜索筛选)与 `levelRequirements` 需自备;时区缺省 `+0000`(`index.ts:60`) | + +`userInfo.selectors` 的字段与来源接口(239-296): + +- `action=index`:`id`、`name`、`messageCount`、`uploaded`、`downloaded`、`ratio`、`levelName`、`bonus`、`bonusPerHour`、`seedingSize`; +- `action=user&id=`:`joinTime`、`seeding`、`uploads`、`perfectFlacs`、`groups`、`invited`、`lastAccessAt`。 + +注意 `seedingSize` 的路径注释为 `// GazellePW`(269),`bonusPerHour` 允许回落到 `seedingBonusPointsPerHour`(266)——不同 Gazelle 分支的字段并不完全一致。 + +## 最小可用定义骨架 + +```ts +import type { ISiteMetadata } from "../types"; +import GazelleJSONAPI, { SchemaMetadata } from "../schemas/GazelleJSONAPI.ts"; + +export const siteMetadata: ISiteMetadata = { + ...SchemaMetadata, + + version: 1, + id: "example", // 必须与文件名一致 + name: "Example", + tags: ["音乐"], + + type: "private", + schema: "GazelleJSONAPI", + urls: ["uggcf://rknzcyr.pbz/"], // 需要时可写 rot13 + + // 搜索筛选:JSON 接口的参数名按站点实测填写 + category: [ + { + name: "类别", + key: "filter_cat", + options: [{ name: "Music", value: 1 }], + cross: { mode: "appendQuote" }, + }, + ], + + levelRequirements: [{ id: 1, name: "User", privilege: "..." }], +}; + +export default class Example extends GazelleJSONAPI {} +``` + +## 搜索配置要点 + +- **请求**:`GET /ajax.php?action=browse`,`responseType: "json"`(230-236);关键词写入 `params.searchstr`(229,注入逻辑见 `schemas/AbstractBittorrentSite.ts:266-268`)。 +- **`selectors` 不参与解析**:`transformSearchPage`(394-417)直接读 `doc.response.results`,逐项判断该行是否含 `torrents` 键——含则是「种子组」(`transformGroupTorrent`,358-393),否则是「单种」(`transformUnGroupTorrent`,329-357)。给 `search.selectors` 写 CSS/JSON 路径都不会被使用。 +- **分类筛选**:按接口实际参数名写 `metadata.category` 的 `key`(常见 `filter_cat`),多选形式(`appendQuote` → `filter_cat[1]=1`)与站点一致即可,生成规则见 `types/search.ts:202-248`。 +- **空结果的两种来源**:接口 `status !== "success"`(如鉴权失败)时**静默返回空数组**(400-414),不会抛错;真正的「无匹配」也返回空数组。排查时先看网络面板里 `action=browse` 的响应体。 +- **`list` / `detail` 未提供**:搜索走 JSON,浏览器里直接打开列表页时没有可复用的选择器;需要 content-script 支持(列表页批量下载、详情页下载按钮)的站点要自己补。 + +## 用户信息(userInfo) + +本 schema 覆写了 `getUserInfoResult`(424-469),**不经过 `AbstractPrivateSite` 的 `userInfo.process` 流程**: + +1. 入口守卫:`allowQueryUserInfo` 为假时直接 `passParse`(431-434)。 +2. `getUserBaseInfo()` 请求 `action=index`,取 `id`、`name`、`messageCount`、`uploaded`、`downloaded`、`ratio`、`levelName`、`bonus`、`bonusPerHour`、`seedingSize`(471-486)。 +3. 有 `id` 时 `getUserExtendInfo(id)` 请求 `action=user&id=`,取 `joinTime`、`seeding`、`uploads`、`perfectFlacs`、`groups`、`invited`、`lastAccessAt`(488-504);该步骤前会按 `userInfo.requestDelay` 等待(489)。 +4. `seedingSize` 仍为空时,调用基类 `getSeedingSize(id)` 翻页抓做种列表累加(444-449、`schemas/Gazelle.ts:377-426`)。 +5. `cleanupUserInfo`(506-518):`bonus`/`bonusPerHour` 为空时填 `"N/A"`,`perfectFlacs` 为空时删除该键。 +6. 有 `levelName` 但无 `levelId` 时按 `levelRequirements` 反推(455-457、`schemas/AbstractPrivateSite.ts:194-197`)。 +7. 异常映射:`NeedLoginError` → `needLogin`,其余 → `parseError`(460-466)。 + +由于字段全靠 `userInfo.selectors` 的 JSON 路径(`getFieldsData`,474-485、495-503),站点接口改名时只改对应 selector 即可;字段缺失不会报错,只会被 `cleanupUserInfo` 兜底。 + +## 常被覆写的类方法 + +| 方法(签名见源码) | 何时覆写 | 行号 | +| --- | --- | --- | +| `protected async requestApi(action, params)` | 需要给所有 API 请求统一加参数/请求头(如 `api_key`)时 | 303-311 | +| `protected async getAuthKey()` | `authkey`/`passkey` 不在 `action=index` 响应里时(默认带缓存) | 318-327 | +| `protected async transformUnGroupTorrent(group)` | 单种行的字段映射/标签不同 | 329-357 | +| `protected async transformGroupTorrent(group, torrent)` | 组内种子的字段映射不同(组字段 + 种子字段合并) | 358-393 | +| `public override async transformSearchPage(doc, searchConfig)` | 接口返回结构不同(如结果不在 `response.results`)时整体替换 | 394-417 | +| `public override async getTorrentDownloadLink(torrent)` | 下载链接参数名不是 `torrentid` 时 | 419-422 | +| `protected async getUserBaseInfo()` / `getUserExtendInfo(userId)` | 接口字段分组变化、或需要追加第三个接口时 | 471-486 / 488-504 | +| `public override async getUserInfoResult(lastUserInfo)` | 需要改动整体流程(如改为走 `process`)时,注意保持 `status`/`updateAt`/`site` 结构 | 424-469 | +| `protected cleanupUserInfo(flushUserInfo)` | 需要清理/兜底更多字段时 | 506-518 | +| `protected async getSeedingSize(userId, sizeIndex = 0)`(基类) | 做种体积接口不同或想跳过翻页 | `schemas/Gazelle.ts:377-426` | +| `protected async request(axiosConfig, checkLogin = true)`(基类) | 需要注入自定义请求头(API key 等) | `schemas/AbstractBittorrentSite.ts:119-167` | + +覆写时保持签名一致并加 `override`(`pnpm check` 会校验)。 + +## 范例定义 + +- `definitions/ianon.ts`、`definitions/snakepop.ts` — 最小形态:身份字段 + `isDead: true`,不展开 `SchemaMetadata`。 +- `definitions/redacted.ts` — 音乐站的标准适配:`category` 筛选 + `levelRequirements`,覆写 `transformUnGroupTorrent`(`definitions/redacted.ts:173`)、`transformGroupTorrent`(181)、`getSeedingSize`(189)。 +- `definitions/orpheus.ts` — 与 redacted 同源的站,可作为「站点参数差异」的对照(覆写 1 处)。 +- `definitions/dicmusic.ts` — 中文音乐站,覆写 `getSeedingSize`(`definitions/dicmusic.ts:365`)与 `transformGroupTorrent`(380)。 +- `definitions/gazellegames.ts` — 游戏站,覆写点最多(6 处),示范组/单种映射与用户信息方法的实际用法。 +- `definitions/greatposterwall.ts`、`definitions/secretcinema.ts` — 中等规模的完整定义,`category` 与 `levelRequirements` 齐全。 + +## 常见坑 + +1. **不要写 `search.selectors`**:`SchemaMetadata` 没有该字段,解析也完全不看它(394-417)。写了不会报错,但会给人「已经适配过」的错觉。 +2. **`userInfo.process` 写了无效**:本类自定义了 `getUserInfoResult`(424-469),不会走 `AbstractPrivateSite` 的 process 循环。要改流程只能覆写 `getUserBaseInfo` / `getUserExtendInfo` / `getUserInfoResult`。 +3. **接口失败是静默的**:`doc.status !== "success"` 时直接返回空数组(400-414),搜索会显示「无结果」而不是报错。鉴权失效(缺少 cookie/api_key)时表现相同,排查要靠网络面板。 +4. **字段缺失不会报错**:`getFieldsData` 取不到就给 `undefined`(474-485),最终由 `cleanupUserInfo` 兜底成 `"N/A"` 或删除(506-518)。看到 `N/A` 要回到接口响应里核对字段名。 +5. **不同分支字段名不一致**:`seedingSize` 标注为 GazellePW 特有(269)、`bonusPerHour` 有回落路径(266)。跨站复用定义前先比对响应体。 +6. **`getAuthKey` 只缓存成功结果**(318-327):`action=index` 失败会再次请求;`authkey`/`passkey` 缺失时下载链接拿不到。 +7. **`pickLast` 未提供**:每次查询用户信息都会重新请求 `index` 与 `user` 两个接口;用户量大或站点限速严格时建议补 `pickLast: ["id"]`。 +8. **`requestDelay` 只作用于扩展信息步骤**(489):想限制搜索频率要另外配 `search.requestDelay` 或站点级 `requestDelay`(`types/site.ts:119-124`)。 +9. **时区缺省 `+0000`**(`index.ts:60`):Gazelle 站的 `joinTime` / `lastAccessAt` 由 `parseTime` 解析,时区不符会整体偏移,必要时显式写 `timezoneOffset`。 +10. **`version` 要显式覆盖**:`SchemaMetadata` 是 `0`(227),仓库现状写 `1`。 diff --git a/.dsh/skills/ptd-site-definition/references/engines/gazelle.md b/.dsh/skills/ptd-site-definition/references/engines/gazelle.md new file mode 100644 index 000000000..c753f6ac6 --- /dev/null +++ b/.dsh/skills/ptd-site-definition/references/engines/gazelle.md @@ -0,0 +1,138 @@ +# Gazelle 站点定义规范 + +Gazelle 是一套广泛用于音乐/影视 PT 站的开源程序,页面以 HTML 表格呈现,本仓库由 `src/packages/site/schemas/Gazelle.ts`(709 行)承载。`schemas/Gazelle.ts:205-208` 的注释明确了分工:**站点支持 JSON API 且数据完整时应当用 `GazelleJSONAPI`**,其余走本 schema。 + +用户信息与搜索都依赖 Gazelle 的经典页面结构(`table.torrent_table` 列表、`/user.php?id=` 资料页、`.box` 统计块),因此定义文件通常只做「补 `category` + 补 `levelRequirements` + 少量选择器覆写」。 + +下文行号未特别说明时均指 `src/packages/site/schemas/Gazelle.ts`。 + +## 判定站点是否属于该引擎 + +- 种子列表页是 `table.torrent_table`(`schemas/Gazelle.ts:109`),行分两类:种子组行带 `group` / `group_redline`,单种行带 `torrent` / `torrent_redline`(`schemas/Gazelle.ts:459-464`)。 +- 下载链接形如 `torrents.php?action=download&id=`(`schemas/Gazelle.ts:244`);详情页为 `torrents.php?id=` 或 `torrents.php?torrentid=`(`schemas/Gazelle.ts:241`)。 +- 排序链接带 `order_by=time|size|seeders|leechers|snatched`(`schemas/Gazelle.ts:466-474`)。 +- 用户页 `/user.php?id=`、首页 `/index.php`,统计值分布在 `.box` 区块中(`schemas/Gazelle.ts:189`、`269-295`)。 +- 站点支持 `torrents.php?action=…` 之外还提供 `/ajax.php` 时,**先判断是不是 `GazelleJSONAPI`**(见下条),不要默认用本 schema。 + +易混淆情况: + +- **GazelleJSONAPI**:请求 `/ajax.php?action=browse` 返回 JSON 的站点,应使用 `schema: "GazelleJSONAPI"`(见 [gazelle-json-api.md](gazelle-json-api.md))。二者共用 `GazelleBase`(`schemas/Gazelle.ts:375`),但搜索与用户信息的实现完全不同。 +- **Luminance**:也是 `GazelleBase` 的子类,但列表页用 `/torrents.php?action=advanced` 且列索引推导方式不同,属于独立 schema(见 [luminance.md](luminance.md))。 +- **NexusPHP / Unit3D**:中文站与 Laravel 站的表格结构和类名体系完全不同,不适用本 schema。 + +## SchemaMetadata 默认值 + +`schemas/Gazelle.ts:209-373` 导出的 `SchemaMetadata: Partial`: + +| 字段路径 | 默认值 | 行号 | definition 是否通常需要覆盖 | +| --- | --- | --- | --- | +| `version` | `0` | 210 | 现状全部显式写 `1`(`definitions/anthelion.ts`、`definitions/passthepopcorn.ts` 等) | +| `search.keywordPath` | `"params.searchstr"` | 212 | 不需要 | +| `search.requestConfig` | `url: "/torrents.php"`、`responseType: "document"`、`params.searchsubmit: 1` | 213-217 | 搜索入口不同的站用 `searchEntry` 表达 | +| `search.selectors.rows` | `table.torrent_table tr:gt(0)` | 109、220 | 换表格类名时要改 | +| `search.selectors.id` | 优先从下载链接取,再回落到 `torrents.php?id=`,filter `querystring(torrentid,id)` | 221-226 | 一般不需要(这样拿到的是种子 id 而不是组 id) | +| `search.selectors.title` | `:self` + `GazelleUtils.genTitleElementProcess()` | 227-230 | 标题结构特别时才改 | +| `search.selectors.subTitle` | `.tags` / `> td:has(a[...]) a:not(span a):last`,配 `switchFilters` 区分单种行与组内行 | 231-239 | 视站点 | +| `search.selectors.url` | `a[href*='torrents.php?id=']` 等 | 240-243 | 一般不须改 | +| `search.selectors.link` | `a[href*='torrents.php?action=download']:first` | 244 | 一般不须改 | +| `search.selectors.category` | **被 TODO 注释掉,未提供** | 245 | **必须自己补**(否则列表页没有分类) | +| `search.selectors.time` | `baseTimeSelector`(`` 的 title) | 110-131、246 | 视站点 | +| `search.selectors.progress` / `status` | `{ text: 0 }` / `{ text: ETorrentStatus.unknown }` | 247-248 | 需要进度/状态时自补 | +| `search.selectors.tags` | 仅 `strong:contains('Freeleech!')` → Free | 249 | 常追加站点自有标签 | +| `search.selectors.seeders/leechers/completed/size` | **未提供 selector**,由 `transformSearchPage` 按表头自动推导(见下节) | 219、466-474 | 表头结构不同时覆写 `guessSearchFieldIndexConfig` | +| `list` | 三条:`commonPagesList`、`detailPageList`、`top10PageList`(分别导出在 137、141、165) | 253-263 | 需要额外列表页时追加 | +| `userInfo.pickLast` | `["id"]` | 266 | 一般不需要 | +| `userInfo.process` | 两步:`/index.php` 取 `id` → `/user.php?id=` 取 11 个字段 | 267-296 | 资料页路径不同时改 `requestConfig` | +| `userInfo.selectors` | 10 个字段:`id`、`name`、`messageCount`、`uploaded`、`downloaded`、`ratio`、`levelName`、`bonus`、`joinTime`、`lastAccessAt`、`uploads`(用 `genStatBoxSelector` + `BoxName` 拼 `.box` 选择器) | 297-371 | 站点改版时逐个覆写 | +| `detail` | **未提供** | — | 需要 content-script 的详情页适配时自补 | +| `category`、`levelRequirements`、`noLoginAssert`、`donorConfig`、`userInputSettingMeta`、`searchEntry`、`timezoneOffset` | **未提供** | — | `category` 与 `levelRequirements` 必需;时区缺省为 `+0000`(`index.ts:60`) | + +## 最小可用定义骨架 + +```ts +import type { ISiteMetadata } from "../types"; +import Gazelle, { SchemaMetadata } from "../schemas/Gazelle.ts"; + +export const siteMetadata: ISiteMetadata = { + ...SchemaMetadata, + + version: 1, + id: "example", // 必须与文件名一致 + name: "Example", + tags: ["音乐"], + + type: "private", + schema: "Gazelle", + urls: ["https://example.com/"], + + category: [ + { + name: "类别", + key: "filter_cat", + options: [{ name: "Music", value: 1 }], // 必须来自站点真实分类 + cross: { mode: "appendQuote" }, // Gazelle 常用 filter_cat[1]=1 + }, + ], + + levelRequirements: [{ id: 1, name: "User", privilege: "..." }], +}; + +export default class Example extends Gazelle {} +``` + +## 搜索配置要点 + +- **请求**:`GET /torrents.php`、`responseType: "document"`、`params.searchsubmit = 1`(213-217);关键词写入 `params.searchstr`(212)。 +- **列索引自动推导**:`seeders`、`leechers`、`completed`、`size`、`time` 的取值位置不是写死的,而是 `transformSearchPage`(476-587)调用 `guessSearchFieldIndexConfig()`(466-474)扫描表头单元格,命中 `a[href*='order_by=seeders']`、`img[src*='seeders']` 一类的特征后生成 `> td:nth-child(n)`。站点表头结构不同时,覆写 `guessSearchFieldIndexConfig()`(范例 `definitions/nebulance.ts`、`definitions/anthelion.ts`)。 +- **种子组与单种行**:Gazelle 列表里一个「种子组」下可能有多个单种,行 class 决定分组(459-464);`transformSearchPage` 会先解析组信息再展开组内种子(588-680)。站点改了行 class 名,必须覆写 `torrentClasses`。 +- **`subTitle` 的双形态**:默认用 `switchFilters` 对「单种行」直接取 `.tags`、对「组内行」用 `GazelleUtils.extractTags` 提取属性(231-239),改这里要同时顾及两种行。 +- **`category` 必须自补**:源码里是 `// TODO category: {}`(245),站点需要自己给 `category` 选择器与 `metadata.category` 搜索分类。Gazelle 的搜索分类参数通常是 `filter_cat`,多选写法 `cross: { mode: "appendQuote" }`(`filter_cat[1]=1`)。 +- **`list`**:默认三条覆盖常规列表页、详情页(`torrents.php?id=` 的组页)与 Top10(253-263,定义在 137/141/165)。新增特殊列表页时追加条目即可。 + +## 用户信息(userInfo) + +默认两步(267-296): + +1. `GET /index.php`(document),只取 `id`(从 `a.username[href*='user.php']` 的 `id` 查询参数,299-303)。 +2. `GET /user.php?id=$id$`(`assertion: { id: "params.id" }`),取 `name`、`messageCount`、`uploaded`、`downloaded`、`ratio`、`levelName`、`bonus`、`joinTime`、`lastAccessAt`、`seeding`、`seedingSize`、`uploads`。 + +- 选择器用 `genStatBoxSelector(BoxName.stats|personal|community, "<文案>")` 拼 `.box` 内的位置(189、337-370),站点改文案时替换对应项即可。 +- `messageCount` 用 `elementProcess` 同时解析传统 `#alerts` 与弹窗 `.noty-notification`(307-335)。 +- **`seedingSize` 默认没有 selector**:`Gazelle.getUserInfoResult` 在拿到 `id` 后若 `seedingSize` 仍为空,会调用 `getSeedingSize(id)` 翻页抓 `/torrents.php?userid=&type=seeding` 累加体积(686-692、377-426、427-434)。这一步请求量大,站点若能直接给出该值,写 selector 更划算。 +- `levelName` 拿到但 `levelId` 缺失时,由 `levelRequirements` 反推(`schemas/AbstractPrivateSite.ts:194-197`)。 +- `donorConfig` 在本 schema 未被消费(`bonusPerHourMultiplier` 只在 `schemas/NexusPHP.ts:866-873` 生效)。 + +## 常被覆写的类方法 + +| 方法(签名见源码) | 何时覆写 | 行号 | +| --- | --- | --- | +| `protected get torrentClasses(): Record<"group" \| "unGroupTorrent", string[]>` | 列表行 class 名与默认不同时 | 459-464 | +| `protected guessSearchFieldIndexConfig(): Record` | 表头没有 `order_by=` 排序链接或图标特征时 | 466-474 | +| `public override async transformSearchPage(doc, searchConfig)` | 行列结构整体不同时(尽量先用 selector 解决) | 476-587 | +| `protected getTorrentGroupInfo(group, searchConfig)` | 种子组的公共字段(标题、分类、标签)取法不同 | 588-596 | +| `protected async transformGroupTorrents(...)` / `transformUnGroupTorrent(...)` | 组内种子/单种的字段映射不同 | 597-680 | +| `public override async getTorrentDownloadLink(torrent)` | 下载链接不是 `torrents.php?action=download&id=` 时;默认实现走 `getTorrentDownloadLinkFactory("torrentid")`(436-455) | 681-684 | +| `public override async getUserInfoResult(lastUserInfo)` | 需要改动整体抓取流程(默认 = `super` + 补 `seedingSize`) | 686-692 | +| `protected async getSeedingSize(userId, sizeIndex = 0)` | 做种体积页面结构不同、或想跳过翻页 | 377-426 | +| `protected async getUserTorrentList(userId, page, type)` | 做种列表路径/参数不同 | 427-434 | + +## 范例定义 + +- `definitions/awesomehd.ts`、`definitions/gfxpeers.ts` — 最小形态:只写身份字段 + `isDead: true`,不展开 `SchemaMetadata`(关站后无需解析能力)。 +- `definitions/simurg.ts` — 只写 `category` 与 `levelRequirements`、不覆写任何方法的典型「页面结构标准」站。 +- `definitions/jpopsuki.ts` — 音乐站,覆写三处引擎钩子:`guessSearchFieldIndexConfig`(`definitions/jpopsuki.ts:253`)、`getTorrentGroupInfo`(264)、`getUserTorrentList`(268)。 +- `definitions/anthelion.ts` — 覆写 `transformSearchPage`(`definitions/anthelion.ts:332`)与 `getTorrentGroupInfo`(346),示范组信息结构不同时的处理。 +- `definitions/nebulance.ts` — 用 `groupPageSelectors` / `categorySelector` 常量自定义列表页与分类选择器(`definitions/nebulance.ts:318-341`)。 +- `definitions/passthepopcorn.ts` — 电影站:`category` 项较多,覆写 `transformSearchPage`(`definitions/passthepopcorn.ts:367`)适配自身列表结构。 + +## 常见坑 + +1. **忘了补 `category`**:`SchemaMetadata` 里是 TODO 注释(245),不补则搜索分类为空、列表页也拿不到分类。 +2. **`seeders`/`leechers`/`completed`/`size` 不是 selector**:它们由表头推导(219、466-474)。只改 CSS 选择器而不改 `guessSearchFieldIndexConfig`,这些字段会一直是空。 +3. **`id` 优先取下载链接**:这是为了避免把「种子组 id」当成「种子 id」(221-226)。覆写 `id` 选择器时要保留这个语义。 +4. **`progress` / `status` 默认恒为 0 / unknown**(247-248):站点没有做种进度信息时不要强行补,写了反而会误导用户。 +5. **`...SchemaMetadata` 是浅展开**:覆写 `search.selectors` 时要连 `...SchemaMetadata.search!.selectors` 一起展开,否则 `title`/`subTitle`/`url`/`link` 全部丢失(范例见各定义中的 `...(SchemaMetadata.search?.selectors ?? {})` 写法)。 +6. **`userInfo` 同理**:只想改一个选择器时用 `...SchemaMetadata.userInfo!` + `selectors: { ...SchemaMetadata.userInfo!.selectors, xxx }`,否则会连两步 `process` 一起丢掉。 +7. **时区缺省是 `+0000`**:`index.ts:60` 只给 `schema === "NexusPHP"` 特殊待遇;Gazelle 站的发布时间多为站点本地时区,需要显式写 `timezoneOffset`(如 `-0500`)。 +8. **`getSeedingSize` 会翻页请求**(377-426):做种数量很多的站首次采集较慢,且请求路径基于 `/torrents.php`,站点改版后该值会静默变空。 +9. **`version` 要显式覆盖**:`SchemaMetadata` 是 `0`(210),仓库现状写 `1`。 diff --git a/.dsh/skills/ptd-site-definition/references/engines/luminance.md b/.dsh/skills/ptd-site-definition/references/engines/luminance.md new file mode 100644 index 000000000..34e6748c7 --- /dev/null +++ b/.dsh/skills/ptd-site-definition/references/engines/luminance.md @@ -0,0 +1,150 @@ +# Luminance 站点定义规范 + +Luminance 是 Gazelle 的一个分支(Empornium / MoreThanTV / PixelCove 等站使用的源码),列表页与资料页的类名、列布局都是自研的。引擎实现在 `src/packages/site/schemas/Luminance.ts`(**实际 304 行**):`export default class Luminance extends GazelleBase`(`:219`),而 `GazelleBase` 定义在 `schemas/Gazelle.ts:375`。注意它**只继承 `GazelleBase` 的三个工具方法**(`getSeedingSize` / `getUserTorrentList` / `getTorrentDownloadLinkFactory`),并不继承 `Gazelle.ts` 里默认类 `Gazelle` 的种子组解析逻辑——Luminance 的 `transformSearchPage` 里 `super.transformSearchPage(...)`(`:278`)最终落到 `AbstractBittorrentSite.transformSearchPage`(`schemas/AbstractBittorrentSite.ts:502`),即「一行 = 一个种子」的通用解析。 + +因此定义文件通常是「展开 `...SchemaMetadata` + 覆写 `category` 选择器 + 补 `levelRequirements`」;8 个现有定义中只有 `definitions/morethantv.ts` 导出了 default class。 + +下文行号若未特别说明,均指 `schemas/Luminance.ts`。 + +## 判定站点是否属于该引擎 + +正向特征(有其一即可进一步验证): + +- 列表页为 `/torrents.php`,默认用高级搜索接口 `?action=advanced`(`:10-16`)。 +- 分类筛选参数是 `filter_cat[n]`(`definitions/cathoderaytube.ts:31-33`、`definitions/cgpeers.ts:41-45`、`definitions/empornium.ts:71-76`、`definitions/kufirc.ts:19-21`、`definitions/pixelcove.ts:64-66`),部分站还用 `filter_freeleech`(`definitions/happyfappy.ts:69-73`)。 +- 列表页存在 `table#torrent_table`,且末表的首行是表头(`:240`、`:250`)。 +- 用户资料页 `/user.php?id={id}`(`:87-93`),首页有 `a.username` 且 href 带 `id`(`:114-118`)。 +- 图标类名:`span.icon_disk_seed` / `span.icon_disk_leech` / `span.icon_disk_grabbed`(`:50-52`),Free 与 2xUp 用 `span.icon[title*='Freeleech']` / `img[alt='DoubleSeed']`(`:66`、`:71`)。 +- 资料页出现 `ul.stats > li`、`div[id='bonusdiv']`、`div[id='bonuslog']`(`:121`、`:145`、`:164`)。 +- 站点源码基于 Empornium/Luminance(描述中常直接写明)。 + +易混淆情况: + +- **Gazelle / GazelleJSONAPI**:Gazelle 的列表是「种子组行 + 组内种子行」(`Gazelle.ts:459-580`),分类从中部 `.cats_col` 自动取得;Luminance 走通用单行解析,**分类选择器在源码里被注释掉了**(`:45`),必须自己补。 +- **MoreThanTV**:虽然 `schema: "Luminance"`,搜索已切到 `/api/torznab`(XML)(`definitions/morethantv.ts:58-108`)。判引擎要看站点源码而不是搜索接口。 +- **同一引擎内分类列结构不同**:有的站分类在 `td.cats_col > div[title] > a` 的 href 里(cathoderaytube),有的只能读 `div` 的 `title` 属性(happyfappy / kufirc)。写选择器前先看真实页面。 +- **`pornbay`** 只是保留 `schema: "Luminance"` 并标记 `isDead` 的极简定义,`...SchemaMetadata` 被注释掉(`definitions/pornbay.ts:2`、`:5`),不要把它当模板。 +- 现有 8 个 Luminance 定义里,`definitions/pornbay.ts:18` 与 `definitions/morethantv.ts:45` 已 `isDead: true`;它们仍能反映 schema 的用法,但不要据此推断站点当前行为。 + +## SchemaMetadata 默认值 + +`schemas/Luminance.ts:7-217` 导出的 `SchemaMetadata: Partial`: + +| 字段路径 | 默认值 | 行号 | definition 是否通常需要覆盖 | +| --- | --- | --- | --- | +| `version` | `0` | 8 | 现状定义写 `1`,改版过的站写 `2`(cgpeers / happyfappy / morethantv) | +| `search.requestConfig` | `url: "/torrents.php"`、`responseType: "document"`、`params: { action: "advanced" }` | 10-16 | 换搜索接口或改每页数量时覆写(happyfappy 加 `perPage: 100`,cgpeers 改 `url: "/torrent/browse"`) | +| `search.keywordPath` | `"params.title"` | 17 | 一般不改;站点走 torznab 时改成 `params.q`(morethantv) | +| `search.selectors.rows` | `table#torrent_table:last tr:gt(0)` | 19 | 一般不改;`transformSearchPage` 只在未定义时兜底(`:243-247`) | +| `search.selectors.id/title/url/link` | `a[href*='torrents.php?id=']`(id 用正则抽数字)、`a[href*='torrents.php?action=download']` | 20-25、42-43 | 站点链接形式不同才改(cgpeers 用 `a[href*='torrent/download/']`) | +| `search.selectors.subTitle` | 汇总 `div.tags a[href]` 文本,`", "` 连接 | 26-41 | 一般不改 | +| `search.selectors.time` | `span.time[title]` 的 `title`,`parseTime` | 44 | 一般不改 | +| `search.selectors.category` | **未提供**(源码注释掉) | 45 | **需要**:现有定义全部自补 | +| `search.selectors.status` / `progress` | 由下载链接内 `span` 的类名映射:`icon_disk_seed`→seeding、`icon_disk_leech`→downloading、`icon_disk_grabbed`→inactive;progress 只有 seed 为 100 | 46-61 | 站点图标类名不同就整套重写(empornium `:114-122`、cgpeers `:81-96`) | +| `search.selectors.tags` | 两项:`Free`(blue)、`2xUp`(lime) | 62-74 | 一般不改 | +| `userInfo.pickLast` | `["id"]` | 79 | 一般不改 | +| `userInfo.process` | 两步:先 `/` 取 `id`,再 `/user.php`(`assertion: { id: "params.id" }`)取 14 个字段 | 80-111 | 资料页路径不是 `/user.php?id=` 时改(cgpeers 改 `/user/$id$`) | +| `userInfo.selectors` | 15 个字段,见「用户信息」 | 112-193 | 站点改版时覆写;`category`/`levelName` 之外的字段基本能用 | +| `userInfo.requestDelay` | **未提供** | — | 需要限速时补 | +| `userInfo.donorConfig` | **未提供**,本引擎也不消费 | — | 写了无效(只有 `NexusPHP.ts:868-871` 消费) | +| `list` | 一条:`urlPattern: ["/torrents\\.php(?!\\?id=\\d+$)"]`,无 `selectors`(继承 `search.selectors`)、无 `mergeSearchSelectors`(默认 `true`) | 196-200 | 一般不改;另有特殊列表页时追加条目 | +| `detail.urlPattern` | `["/torrents\\.php\\?id=\\d+"]` | 203 | 一般不需要 | +| `detail.selectors` | `title`(`#content > .details > h2`,回落 `table.torrent_table tr[id] strong`)、`id`、`link` | 204-215 | 详情页模板不同才改(cgpeers) | +| `timezoneOffset` | **未提供**(`index.ts:60` 回落到 `"+0000"`) | — | 站点时区非 UTC 时必须写(empornium 写 `"-1100"`) | +| `category` / `levelRequirements` / `noLoginAssert` / `userInputSettingMeta` / `searchEntry` / `download` / `detail.requestConfig` | **未提供** | — | `category` 与 `levelRequirements` 通常需要 | + +## 最小可用定义骨架 + +```ts +import { type ISiteMetadata } from "../types"; +import { SchemaMetadata } from "../schemas/Luminance"; +import { buildCategoryOptionsFromDict } from "../utils"; +const categoryMap: Record = { 1: "Movies", 2: "TV" }; +const catOpts = buildCategoryOptionsFromDict(categoryMap); +const toCategoryName = (q: string) => categoryMap[Number(q.match(/filter_cat\[(\d+)\]=/)?.[1])] ?? ""; + +export const siteMetadata: ISiteMetadata = { + ...SchemaMetadata, + version: 1, + id: "example", // 必须与文件名 example.ts 一致 + name: "Example", + tags: ["综合"], + timezoneOffset: "+0000", + type: "private", + schema: "Luminance", + urls: ["uggcf://rknzcyr.bet/"], + category: [ + { name: "类别", key: "filter_cat", options: catOpts, cross: { mode: "appendQuote" } }, + { name: "优惠", key: "filter_freeleech", options: [{ name: "Free", value: 1 }] }, + ], + search: { + ...SchemaMetadata.search, + selectors: { + ...SchemaMetadata.search!.selectors, + category: { selector: "td.cats_col > div[title] > a", attr: "href", filters: [toCategoryName] }, + }, + }, + levelRequirements: [{ id: 1, name: "User" }, { id: 2, name: "Member", uploaded: "50GB", ratio: 1 }], +}; +``` + +## 搜索配置要点 + +- **默认请求**:`GET /torrents.php?action=advanced`,`responseType: "document"`,关键词写入 `params.title`(`:10-17`)。定义里加 `params` 时要展开默认值(happyfappy 只加 `perPage: 100` 并展开 `requestConfig`,`definitions/happyfappy.ts:78-83`)。 +- **高级搜索词**:`keywordPath` 是 `params.title`,站点的「全文搜索」参数是 `searchtext`,所以几乎每个定义都定义一个高级搜索词把 `params.title` 改名为 `params.searchtext`——有的是 `imdb`(cathoderaytube)、有的是自定义的 `terms`(happyfappy / kufirc / empornium)。按 `types/search.ts:106-113` 的约定,除 `imdb` 外的关键词必须显式声明,不支持时写 `imdb: false`(cgpeers `:60-62`、empornium `:89`)。 +- **默认覆盖的种子字段**:`rows`、`id`、`title`、`subTitle`、`url`、`link`、`time`、`status`、`progress`、`tags`(`:18-75`)——**没有 `category`**,也**没有 `size`/`seeders`/`leechers`/`completed`/`comments`/`author`**。 +- **列索引由类方法自动推导**:`guessSearchFieldIndexConfig()`(`:220-229`)把表头单元格匹配到 `size`/`seeders`/`leechers`/`completed`/`comments`/`author`,`transformSearchPage` 再据此生成 `> td:eq(N)` 选择器(`:250-274`)。因此覆盖 `search.selectors` 时不要手写这几列,除非站点表头无法识别。 +- **覆写 `search.selectors` 必须展开默认值**:`...SchemaMetadata.search!.selectors`,同时外层还要 `...SchemaMetadata.search`(`definitions/cgpeers.ts:54-97`、`definitions/empornium.ts:86-123`、`definitions/happyfappy.ts:76-78`)。 +- **category 写法**:分类筛选统一用 `key: "filter_cat"` + `cross: { mode: "appendQuote" }`,生成 `requestConfig.params.filter_cat = { "1": 1 }`(`SetSearchSolution/utils.ts:66-68`),由 axios 序列化。优惠项可用两种写法:`{ key: "filter_freeleech" }`(不写 `cross`,`definitions/cathoderaytube.ts:36-39`)或 `{ key: "free", cross: { mode: "append", key: "" } }`(生成 `params.filter_freeleech = 1`,`definitions/happyfappy.ts:68-73`)。标签组合筛选用 `cross: { mode: "custom" }` + `generateRequestConfig` 拼 `taglist=a+b`(`definitions/happyfappy.ts:43-67`、`definitions/kufirc.ts:80-102`)。 +- **`searchEntry`**:SchemaMetadata 未提供,现有 Luminance 定义也都没用;默认搜索入口直接用 `search.requestConfig`(`types/site.ts:139-144`)。`list` 里的 `keywords` 选择器也不需要写,会按 `keywordPath` 推断。 +- **`detail`**:默认已能解析 `title`/`id`/`link`,一般不用改;cgpeers 因为详情页是另一套模板而覆写了三个字段(`definitions/cgpeers.ts:147-155`),覆写时同样要展开默认值。 + +## 用户信息(userInfo) + +- **两步 process**(`:80-111`):第 1 步请求 `/`(`responseType: "document"`)只取 `id`;第 2 步请求 `/user.php`,用 `assertion: { id: "params.id" }` 把 id 写进查询参数,取 `name`、`joinTime`、`lastAccessAt`、`uploaded`、`downloaded`、`levelName`、`bonus`、`ratio`、`uploads`、`bonusPerHour`、`seeding`、`seedingSize`、`messageCount`、`posts`。资料页路径不同的站只需重写第 2 步的 `requestConfig.url` 与 `assertion`(cgpeers 改成 `/user/$id$` + `assertion: { id: "url" }`,`definitions/cgpeers.ts:100-127`)。 +- **selectors 默认覆盖 15 个字段**(`:112-193`):`id`、`name`、`joinTime`、`lastAccessAt`、`uploaded`、`downloaded`、`levelName`、`bonus`、`ratio`、`uploads`、`bonusPerHour`、`seeding`、`seedingSize`、`messageCount`、`posts`。**未覆盖**:`snatches`、`leeching`、`invites`、`avatar`、`isDonor`、`hnrUnsatisfied`、`trueRatio`、`seedingTime`。 +- **`seedingSize` 走动态钩子**:`AbstractPrivateSite.getUserInfoResult` 会用 `parseUserInfoFor${pascalCase(field)}` 找同名方法(`AbstractPrivateSite.ts:177-181`),因此 `process` 里的 `seedingSize` 会命中 `parseUserInfoForSeedingSize`(`:286-303`):先按 `userInfo.selectors.seedingSize`(`ul.stats > li:contains('Seeding Size:')`,`:183`)直读,读不到再回落到 `GazelleBase.getSeedingSize(userId)` 翻做种列表累加(`Gazelle.ts:377-423`,请求 `/torrents.php?userid=&page=&type=seeding`)。**改字段名会连带改掉这个钩子名**。 +- **`pickLast` 默认 `["id"]`**(`:79`):缓存里拿到 id 时第 1 步会被跳过(`AbstractPrivateSite.ts:138-141`)。覆写 `userInfo` 时要展开 `...SchemaMetadata.userInfo!` 才能保留它。 +- **`ratio` 用 `-1` 表示无限**(`:150-156`):源码注释说明 `Infinity` 无法通过 `sendMessage` 传递,前端另有处理;自定义 ratio 选择器时应沿用 `-1`。 +- **`bonusPerHour` 是从 `div#bonuslog` 里算出来的**(`:162-173`):取日志中含 `hrs` 的第一行,按 `| N credits |` 除以 24。站点日志格式不同就得重写该字段。 +- **`noLoginAssert` 未提供**:默认值来自 `AbstractPrivateSite.ts:31-48`(401/403/502/504 + `login` 系列 URL 模式);需要额外兜底时在定义里补。 +- **`donorConfig` 无效**:本引擎不读取它(只有 `NexusPHP.ts:868-871` 会应用 `bonusPerHourMultiplier`)。 +- **`levelRequirements`**:本引擎不提供默认值,必须自己写(`id` 递增 + `name` 必须与站点 `levelName` 文本一致 + `interval`/`uploaded`/`ratio`/`uploads`/`posts` 等),否则不会有 `levelId`(`AbstractPrivateSite.ts:194-197`)。 + +## 常被覆写的类方法 + +| 方法 | 何时覆写 | 行号 / 范例 | +| --- | --- | --- | +| `protected guessSearchFieldIndexConfig(): Record` | 站点表头无法用默认锚点识别(`order_by=size`、`td:contains('Size')` 等)时改锚点 | 220-229(Gazelle 版本在 `Gazelle.ts:466-474`) | +| `public override async transformSearchPage(doc, searchConfig)` | 需要改自动列选择器逻辑,或在解析前修正 DOM;默认只对 `doc instanceof Document` 生效 | 231-279 | +| `public override async getTorrentDownloadLink(torrent)` | 下载链接的参数名不是 `id`(MoreThanTV 是 `torrentid`) | 281-284;`definitions/morethantv.ts:229-233` | +| `protected async parseUserInfoForSeedingSize(flushUserInfo, dataDocument)` | 做种量来源变化(如改走别的接口) | 286-303 | +| `protected async getSeedingSize(userId, sizeIndex = 0)`(继承自 `GazelleBase`) | 做种列表页不是 `/torrents.php?userid=&page=&type=seeding` | `Gazelle.ts:377-423` | +| `protected async getUserTorrentList(userId, page = 1, type = "seeding")`(继承自 `GazelleBase`) | 同上 | `Gazelle.ts:427-434` | +| `protected getTorrentDownloadLinkFactory(torrentIdParam)`(继承自 `GazelleBase`) | 不是覆写点,而是自定义 `getTorrentDownloadLink` 时的复用工具 | `Gazelle.ts:436-455` | + +覆写时保持签名一致并加 `override` 关键字(`pnpm check` 会校验)。 + +## 范例定义 + +- `definitions/cathoderaytube.ts` — 最小标准写法:`filter_cat` + `filter_freeleech` 分类、从 `td.cats_col > div[title] > a` 的 href 反解分类名、`imdb` 高级搜索词改写为 `searchtext`。 +- `definitions/cgpeers.ts` — 改版站:搜索地址换成 `/torrent/browse`、整段覆写 `search.selectors`(含 `status`/`progress`)、`userInfo.process` 改成 `/user/$id$`、覆写 `detail.selectors`。 +- `definitions/happyfappy.ts` — 用 `buildCategoryOptionsFromList` + `cross: { mode: "custom" }` + `generateRequestConfig` 实现 `taglist=a+b` 组合筛选;`perPage: 100`;自定义 `terms` 高级搜索词。 +- `definitions/empornium.ts` — 站点图标类名不同,整套重写 `status` 的 `case` 映射;`advanceKeywordParams.imdb: false` 并新增 `terms`。 +- `definitions/morethantv.ts` — 唯一导出 default class 的定义:搜索整体切到 `/api/torznab` XML(`skipNonLatinCharacters: true`、`keywordPath: "params.q"`、手写全部选择器),并覆写 `getTorrentDownloadLink`。 +- 反例:`definitions/pornbay.ts` — 注释掉 `...SchemaMetadata` 后仅剩 `isDead`,不是可参考的骨架。 + +## 常见坑 + +1. **`category` 默认没有选择器**:`:45` 明确注释掉了,不补 `search.selectors.category` 就永远没有分类。 +2. **列索引不考虑 colspan**:Luminance 用 `elementIndex` 直接生成 `> td:eq(N)`(`:250-273`),而 Gazelle 会累加 `colSpan`(`Gazelle.ts:490-503`)。列表页有合并单元格的站点会整体错位,需要覆写 `transformSearchPage`。 +3. **列选择器自动生成依赖 `table#torrent_table` 的表头**:`:240`、`:250` 取的是 `table#torrent_table:last tr:first > td`,XML/JSON 接口的响应里没有这些表头,自动生成会静默失效——morethantv 因此手写了 `rows`/`id`/`title`/`link`/`size`/`seeders` 等全部字段(`definitions/morethantv.ts:85-107`)。 +4. **`rows` 已定义就不再兜底**:`:243-247` 只在 `searchEntry.selectors.rows` 为空时才生成 `table#torrent_table:last tr:gt(0)`。 +5. **覆盖 `search.selectors` 必须展开默认值**:否则会丢掉 `rows`/`title`/`url`/`link`/`time`/`status`/`progress`/`tags`(`definitions/cgpeers.ts:63-97` 是正确写法)。 +6. **只写 `userInfo: { selectors: {...} }` 会丢 `pickLast` 与 `process`**:标准写法是先 `...SchemaMetadata.userInfo!`(`definitions/cgpeers.ts:100-129`)。 +7. **`tags` 默认选择器是网页类名**:`span.icon[title*='Freeleech']` / `img[alt='DoubleSeed']`(`:66`、`:71`),对没有这些图标的接口返回不会命中,需要整段重写(morethantv 用 `[name="downloadvolumefactor"][value="0"]`,`definitions/morethantv.ts:102`)。 +8. **`status`/`progress` 用 `case` + `Sizzle.matchesSelector` 判定**(`:46-61`):`case` 的键是 CSS 选择器,站点换图标类名后不会报错,只会全部落到默认值 `unknown` / `0`。 +9. **`timezoneOffset` 没有默认值**:`index.ts:60` 对非 NexusPHP 站点回落 `"+0000"`;有些定义仍显式写 `"+0000"`(冗余),Empornium 写 `"-1100"`(`definitions/empornium.ts:63`)——时区错了会让 `time`/`lastAccessAt` 偏移。 +10. **下载链接补全依赖参数名**:默认 `getTorrentDownloadLinkFactory("id")`(`:281-284`)只处理 `?id=`;链接形如 `torrents.php?torrentid=123` 的站必须覆写(morethantv),否则得到的不是下载链接。 +11. **`list` 只有一条且没有 selectors**:`mergeSearchSelectors` 默认 `true`(`AbstractBittorrentSite.ts:660`),列表页解析直接用 `search.selectors`;新增列表页条目时注意 `urlPattern` 的先后顺序(`transformListPage` 命中即 `break`,`AbstractBittorrentSite.ts:661-664`)。 +12. **`pornbay` 的写法不要模仿**:`definitions/pornbay.ts:2`、`:5` 把 `...SchemaMetadata` 注释掉了,该站点没有任何 `search`/`userInfo` 配置,仅靠 `isDead: true` 存在;同时它仍然声明 `schema: "Luminance"`。 diff --git a/.dsh/skills/ptd-site-definition/references/engines/nexusphp.md b/.dsh/skills/ptd-site-definition/references/engines/nexusphp.md new file mode 100644 index 000000000..25bfd411e --- /dev/null +++ b/.dsh/skills/ptd-site-definition/references/engines/nexusphp.md @@ -0,0 +1,177 @@ +# NexusPHP 站点定义规范 + +`schemas/NexusPHP.ts` 是 NexusPHP(xiaomlove/nexusphp 及其大量二次开发)站点的引擎模板,`schemas/NexusPHP.ts:343-771` 导出的 `SchemaMetadata` 已写好搜索页、详情页、用户页的整套默认选择器与抓取流程。只要目标站点的页面结构属于 NexusPHP 家族,就使用 `schema: "NexusPHP"` 并只覆写差异项,不要退回 `AbstractPrivateSite` 从零写。 + +前置事实:`index.ts:93-102` 从 schema 模块取走的**只有 `default class`**,`SchemaMetadata` 永远不会被自动合并。definition 必须自己 `...SchemaMetadata` 展开;不展开则 search / userInfo / list / detail 全为空。仓库内确实存在不展开的 NexusPHP 定义(`definitions/whupt.ts:3-16`、`definitions/yingk.ts:3-17`),但它们都是 `isDead: true` 的站点。 + +## 判定站点是否属于该引擎 + +可观测特征(每一项都能在上面列出的默认选择器里找到依据): + +- 种子列表页是 `table.torrents` 表格(`schemas/NexusPHP.ts:799`),行内下载链接为 `a[href*="download.php?id="]` 且内含 `img[alt="download"]`(`:87`)。 +- 页面 URL 形态固定:详情页 `/details.php?id=`、用户页 `/userdetails.php?id=`、魔力页 `/mybonus.php`、H&R 页 `/myhr.php`(`:735`、`:757`、`:683`)。 +- 种子行内优惠图标使用固定 class:`img.pro_free`、`img.pro_free2up`、`img.pro_2up`、`img.pro_50pctdown`、`img.hitandrun`(`:477-483`)。 +- 用户信息表是 `td.rowhead`(字段名)/ `td.rowfollow`(值)两列结构(`:568-571`、`:631-634`)。 +- 页面顶部存在 `#info_block` 容器,其中有指向 `userdetails.php?id=` 的链接(`:71`)与 `myhr.php` 链接(`:683`)。 +- 做种数、做种量、发布数走 AJAX 接口 `/getusertorrentlistajax.php?userid=&type=seeding|uploaded`(`:888-894`、`:943-945`)。 +- 详情页标题为 `

`,或 `` 形如 `… "种子名" - Powered by NexusPHP`(`:499`、`:512-521`)。 + +易混淆情况: + +- 二次开发站点会替换默认结构类名(Audiences 用 BEM:`table.torrents-table`、`.site-userbar__compact-metric--uploaded`,见 `definitions/audiences.ts:17-25`、`:115-125`),引擎判定不变,但默认 selector 大量失效。 +- 无 `default class` 导出时,`schema` 值必须能在 `schemas/` 找到实现,否则按 `type` 回落 `AbstractPrivateSite` / `AbstractBittorrentSite`(`index.ts:96-98`);写了 `default class` 则 `schema` 不参与实例化,但类型上仍是必填字段(`index.ts:16-19`)。 + +## SchemaMetadata 默认值 + +字段路径 → 默认值 → 源码行 → definition 是否通常需要覆盖(`schemas/NexusPHP.ts`): + +| 字段 | 默认值 | 行号 | 是否需覆盖 | +| --- | --- | --- | --- | +| `version` | `0` | `:347` | 否;仓库内 NexusPHP 定义普遍自行写 `version: 1`(`definitions/alingpt.ts:6`、`definitions/ourbits.ts:15`),约定见 `types/site.ts:46-54` | +| `schema` / `type` | `"NexusPHP"` / `"private"` | `:348-349` | 否,直接沿用 | +| `timezoneOffset` | `"+0800"` | `:350` | 仅在站点非 +0800 时改;`index.ts:60` 对 `schema === "NexusPHP"` 也会兜底 +0800 | +| `search.keywordPath` | `"params.search"` | `:352` | 否(注意不是 `params.keywords`) | +| `search.requestConfig` | `{ url: "/torrents.php", params: { notnewword: 1 } }` | `:353-356` | 搜索入口不同的站点用 `searchEntry` 表达,不直接改这里 | +| `search.advanceKeywordParams` | 仅 `imdb` → `params.search_area = 4` | `:357-364` | 需要 `douban` 等前缀时追加(`definitions/ourbits.ts:164-172`) | +| `search.selectors` | 12 个字段:`link` `url` `id` `title` `subTitle` `progress` `status` `category` `time` `ext_douban` `ext_imdb` `tags` | `:365-485` | 只覆写与站点不符的项 | +| `list` | `[{ urlPattern: ["/torrents.php", "/special.php"] }]` | `:488-492` | 入口不同则增删(`definitions/tjupt.ts:149-154`、`definitions/zrpt.ts:192-196`) | +| `detail.urlPattern` | `["/details.php"]` | `:494-495` | 否 | +| `detail.selectors.title` | `h1#top` / `html > body > title` + `switchFilters` | `:498-523` | 极少 | +| `detail.selectors.link` | 4 个 `download.php` 选择器(downhash / passkey 优先) | `:524-533` | 链接带签名或需 POST 时覆写(`definitions/hdsky.ts:324-332`) | +| `userInfo.pickLast` | `["id"]` | `:542` | 否 | +| `userInfo.selectors` | 16 个字段,见「用户信息」一节 | `:543-728` | 只覆写与站点不符的项 | +| `userInfo.process` | 3 步:`/index.php` → `/userdetails.php` → `/mybonus.php` | `:729-760` | 站点接口不同则增删(`definitions/byrbt.ts:119-125`) | +| `userInfo.donorConfig` | `{ isAccountKept: false, bonusPerHourMultiplier: 2 }` | `:766-769` | selector 已能取到加倍后时魔时改为 `1`(`definitions/baozi.ts:127-130`) | + +**没有默认值、必须由 definition 提供的字段**(`SchemaMetadata` 的 `Pick` 列表见 `:343-346`):`id`、`name`、`urls`、`category`、`levelRequirements`;`officialGroupPattern`、`noLoginAssert`、`requestDelay`、`download` 等按需补充。 + +模式中导出但**不含在 `SchemaMetadata` 内**的等级数据:`xiaomloveDefaultUserLevelRequirements`(`:191-333`)与 `defaultUserLevelRequirements`(`:335-337`,对前者 `omit` 一个字段,内容相同)。它们只是可复用的素材,不会自动生效。 + +## 最小可用定义骨架 + +```ts +import { type ISiteMetadata } from "../types"; +import { CategoryInclbookmarked, CategoryIncldead, CategorySpstate, SchemaMetadata } from "../schemas/NexusPHP"; + +export const siteMetadata: ISiteMetadata = { + ...SchemaMetadata, // 必须:默认 search / userInfo / list / detail 只在此合并 + version: 1, + id: "example", // 必须与文件名一致,且只含 [0-9a-z] + name: "Example", + type: "private", + schema: "NexusPHP", + urls: ["https://example.com/"], + + category: [ + { name: "分类", key: "cat", options: [{ name: "Movies", value: 401 }], cross: { mode: "append" } }, + CategoryIncldead, + CategorySpstate, + CategoryInclbookmarked, + ], + + officialGroupPattern: [/-EXAMPLE$/i], + + levelRequirements: [{ id: 1, name: "User", privilege: "新用户的默认级别。" }], +}; +``` + +`id` 规则见 `types/site.ts:36-44`;`category` 与 `levelRequirements` 缺失时功能不完整:没有 `levelRequirements` 就不会计算 `levelId`(`schemas/AbstractPrivateSite.ts:194-197`)。 + +## 搜索配置要点 + +**请求配置**。默认 `url: "/torrents.php"`、`params: { notnewword: 1 }`、关键字写入 `params.search`(`:352-356`)。合并顺序是「垫片 → searchEntry → search.requestConfig → 高级搜索词配置 → 关键字 → transformer」,后一步覆盖前一步(`schemas/AbstractBittorrentSite.ts:226-284`)。 + +**默认 selector 覆盖的种子字段**:`link`(`:367`,`download.php` 且带下载图标)、`url`(`:368`,由 link 的 `id` 拼 `/details.php?id=`)、`id`(`:375`)、`title`(`:379`,取 `title` 属性或文本)、`subTitle`(`:386`)、`progress`(`:391`)、`status`(`:399`)、`category`(`:416`)、`time`(`:431`)、`ext_douban`(`:452`)、`ext_imdb`(`:464`)、`tags`(`:476-484`)。 + +**未在 selector 中声明、由行结构自动推导的字段**:`rows` 与表头字段。`transformSearchPage` 在返回 Document 时(`:797-842`): + +- 若未定义 `selectors.rows`,生成 `table.torrents:last > tbody > tr`;表格无 `<thead>` 时追加 `:gt(0)` 跳过首行(`:799-809`)。 +- 扫描表头单元格,命中 `/(cat|类型|類型|分类|分類|Тип)/i` 的列绑定为 `category`,命中 `guessSearchFieldIndexConfig()` 中图标选择器的列绑定为 `author` / `comments` / `completed` / `leechers` / `seeders` / `size` / `time`,生成 `> td:eq(<列号>)`(`:812-841`,配置见 `:774-784`)。 + +因此当站点没有分类图标、或表头结构与 NexusPHP 默认不同时,应当显式写 `rows`(`definitions/byrbt.ts:58`)或覆写 `guessSearchFieldIndexConfig`(`definitions/byrbt.ts:249-259`)。 + +**category 分类写法**。`SchemaMetadata` 不提供 `category`,定义必须自行给出。模板导出了三个通用常量直接复用:`CategoryIncldead`(`:28-37`,key `incldead`)、`CategorySpstate`(`:39-53`,key `spstate`)、`CategoryInclbookmarked`(`:55-64`,key `inclbookmarked`),三者均 `cross: false`。 + +自定义分类的生成规则(`types/search.ts:202-248` 定义,`entries/options/views/Settings/SetSearchSolution/utils.ts:39-80` 实现): + +- 不写 `cross` → 单选值直接放进 `params.<key>`; +- `cross: { mode: "append" }` → `params.<key><value> = 1`,NexusPHP 的 `cat401=1` 形态(`definitions/hdsky.ts:158`、`definitions/audiences.ts:62`); +- `cross: { mode: "brackets" }` → `params.<key> = [v1, v2]`(`definitions/ourbits.ts:45`); +- `cross: { mode: "comma" }` / `appendQuote` → 逗号串 / `key[v]=1`; +- `cross.key` 可改写外层字段名,多个分类块可共用一个站点参数(`definitions/ggpt.ts:47`、`:62`); +- `key: "#url"` 是特殊值,直接把值写进 `requestConfig.url`,用于区分 `/torrents.php` 与 `/special.php` 两个入口(`entries/options/views/Settings/SetSearchSolution/utils.ts:53-54`,用例 `definitions/ggpt.ts:26-32`)。 + +**searchEntry**。站点有多个搜索入口且结果不同时使用,每个入口是一个 `ISearchEntryRequestConfig`,默认与 `search` 合并(`types/search.ts:144-156`、`schemas/AbstractBittorrentSite.ts:190-205`): + +```ts +searchEntry: { + area_normal: { name: "种子区", requestConfig: { url: "/torrents.php" } }, + area_special: { name: "9KG专区", enabled: false, requestConfig: { url: "/special.php" } }, +}, +``` + +**officialGroupPattern**。类型是 `TPatterns`(正则或字符串),用于按标题自动打「官方」标签;只有当用户开启 `searchEntity.autoDetectOfficialGroupFromTitle` 时才生效,且字符串会被 `new RegExp(pattern, "i")` 处理,所以写字符串等于写正则源码(`entries/offscreen/utils/search.ts:28-39`;字符串写法见 `definitions/hdpt.ts:19`)。纯文本组名建议还是写成正则(`definitions/zmpt.ts:107`)。 + +## 用户信息(userInfo) + +**process 三步**(`:729-760`),每一步的 `fields` 只列出该步要抓的字段,已有值会自动跳过(`schemas/AbstractPrivateSite.ts:131-141`): + +1. `GET /index.php`(document),只取 `id`。 +2. `GET /userdetails.php`,`assertion: { id: "params.id" }` —— 断言把上一步的 id 写进请求参数;断言字段缺失会抛错(`schemas/AbstractPrivateSite.ts:148-165`)。该步取 `name`、`messageCount`、`uploaded`、`trueUploaded`、`downloaded`、`trueDownloaded`、`levelName`、`bonus`、`seedingBonus`、`joinTime`、`seeding`、`seedingSize`、`hnrUnsatisfied`、`hnrPreWarning`、`lastAccessAt`、`isDonor`。 +3. `GET /mybonus.php`(document),取 `bonusPerHour`、`seedingBonusPerHour`。 + +站点魔力页路径不同时,常见做法是过滤掉第 3 步再补一步(`definitions/byrbt.ts:119-125`、`definitions/tjupt.ts:174-180`)。 + +**selectors 默认覆盖的字段**:`id`(`:545`,用 `baseUserIdSelector`,优先 `#info_block a[href*='userdetails.php'][href*='id=']`,见 `:66-75`)、`name`(`:552`)、`messageCount`(`:555`)、`uploaded`(`:565`)、`trueUploaded`(`:580`)、`downloaded`(`:597`)、`trueDownloaded`(`:612`)、`levelName`(`:629`,读等级图标的 `title`)、`isDonor`(`:637`)、`bonus`(`:642`,`createUserBonusSelectorFn`,`:91-120`)、`seedingBonus`(`:670`)、`joinTime`(`:671`)、`hnrPreWarning`(`:680`)、`hnrUnsatisfied`(`:691`)、`bonusPerHour`(`:702`)、`lastAccessAt`(`:712`)。 + +`seeding` / `seedingSize` 默认没有 selector(`:721-727` 注释说明):`process` 三步执行完后,若两者仍为 `undefined`,`getUserInfoResult` 会用 `parseUserInfoForSeedingStatus` 请求 `/getusertorrentlistajax.php` 计算;`uploads` 未定义时同样用 `parseUserInfoForUploads` 补(`:848-876`)。也可以直接给 `seeding` / `seedingSize` 写 selector 来跳过这次 AJAX(`definitions/audiences.ts:318-325` 展示了自定义 selector 的写法)。 + +**pickLast**。默认 `["id"]`(`:542`),表示 `id` 从本地历史缓存取,减少请求;`name` 刻意不在其中,因为部分站点允许改名(`:538-541`)。 + +**noLoginAssert**。NexusPHP 的 `SchemaMetadata` **不提供**该字段,默认登录检查来自 `AbstractPrivateSite`:状态码 `[401, 403, 502, 504]`、响应 URL 匹配 `/doLogin|login|verify|checkpoint|returnto/gi`、`refresh` 头重定向(`schemas/AbstractPrivateSite.ts:31-107`)。需要额外断言时在定义里补,例如 `definitions/hddolby.ts:202-204` 的 `matchSelectors: ["script:contains('take2fa.php?returnto=')"]`。 + +**donorConfig**。默认 `isAccountKept: false`、`bonusPerHourMultiplier: 2`(`:766-769`)。生效条件是 `isDonor === true` 且 `bonusPerHour` 是 number,此时乘系数(`:867-873`)。若 selector 已经能直接选出加倍后的时魔,必须把系数改回 `1`,写法为 `{ ...SchemaMetadata.userInfo!.donorConfig, bonusPerHourMultiplier: 1 }`(`definitions/baozi.ts:127-130`、`definitions/52movie.ts:120-123`)。 + +**等级处理**。`levelRequirements` 不在 `SchemaMetadata` 内,定义必须提供;`levelId` 由 `guessUserLevelId` 依据 `levelName`(可配合 `nameAka`)匹配 `levelRequirements` 得出(`schemas/AbstractPrivateSite.ts:194-197`、`:218-220`;字段定义见 `types/userinfo.ts:73-91`)。确实需要直接指定 id 时也可以在 `userInfo.selectors` 中给 `levelId` 写选择器(`definitions/zrpt.ts:203-212`)。 + +## 常被覆写的类方法 + +`schemas/NexusPHP.ts:773-1052` 的 `default class NexusPHP extends PrivateSite`。definition 用 `export default class X extends NexusPHP` 覆写;下表只列 NexusPHP 真实定义、且在 NexusPHP 定义文件中确有真实用例的覆写点: + +| 方法签名 | 何时覆写 | 源码行 / 用例 | +| --- | --- | --- | +| `protected guessSearchFieldIndexConfig(): Record<string, string[]>` | 列表表头不是 NPHP 默认的 `a[href*="sort=9"]` / `img.comments` 等 | `:774-784`;`definitions/byrbt.ts:249-259`(改用 `div.icons.*`) | +| `protected get customTagsLocaterSelector(): string` | 自定义 tag 不在 `table.torrentname` 内 | `:786-788`;`definitions/cspt.ts:318` | +| `public override async transformSearchPage(doc, searchConfig): Promise<ITorrent[]>` | 需要改行解析或表头推导逻辑(多数情况应先用 selector 解决;NexusPHP 定义中未见覆写) | `:790-846` | +| `protected async requestUserSeedingPage(userId, type): Promise<string \| null>` | 站点 AJAX 路径不同、或需要额外请求头 | `:888-894`;`definitions/audiences.ts:456-465`(补 `Referer`) | +| `protected async parseUserInfoForSeedingStatus(flushUserInfo)` | 做种/做种量表格结构与默认不同 | `:896-941`;`definitions/audiences.ts:468-495`、`definitions/keepfrds.ts:402` | +| `protected async parseUserInfoForUploads(flushUserInfo)` | 发布数不在默认文案/表格里 | `:943-968`;`definitions/tjupt.ts:346`、`definitions/keepfrds.ts:408` | +| `protected override parseTorrentRowForTags(torrent, row, searchConfig)` | 站点自定义 tag 的元素结构不同 | `:970-1012`;`definitions/hdsky.ts:372-394`(`span.optiontag`) | +| `public override async getTorrentDownloadLink(torrent): Promise<string>` | 下载链接需要凭据、有时间限制或需改写 | `:1014-1052`;`definitions/hdsky.ts:338-370`、`definitions/yzyy.ts:354` | +| `protected guessUserLevelId(userInfo): TLevelId` | 等级判定有额外规则(继承自 `AbstractPrivateSite`) | `schemas/AbstractPrivateSite.ts:218-220`;`definitions/hdsky.ts:396-414`、`definitions/52pt.ts:245` | + +## 范例定义 + +- `definitions/alingpt.ts` — 最小可用形态:`...SchemaMetadata` + urls + 自定义 `category` + 抄改的 `levelRequirements`,没有覆写任何方法。 +- `definitions/ggpt.ts` — 用 `key: "#url"` 区分 `/torrents.php` 与 `/special.php` 两个搜索入口,并用 `searchEntry` 声明入口,`cross.key` 让两个分类块共用 `cat` 参数。 +- `definitions/byrbt.ts` — 覆盖 `rows` 与 `id`/`url`/`link`/`category`/`status` 选择器、过滤掉默认 `/mybonus.php` 步骤换成 `/bonus.php`,并覆写 `guessSearchFieldIndexConfig` 适配 `div.icons.*` 表头。 +- `definitions/ourbits.ts` — `cross: { mode: "brackets" }` 的多维分类、追加 `douban` 高级搜索词、覆写 `progress`/`status`/`tags` 选择器与 `hnr*` 选择器。 +- `definitions/hdsky.ts` — 三个方法级覆写:`getTorrentDownloadLink`(passkey/sign 两种链接形态与 10 分钟有效期)、`parseTorrentRowForTags`(`span.optiontag`)、`guessUserLevelId`(按 `joinTime` 切换新旧两套等级表),并覆写 `detail.selectors.link`。 +- `definitions/audiences.ts` — 二次开发改版站点的完整适配:重写 `link`/`rows`/`time`/`progress`/`status`/`tags` 与大量用户字段选择器,并覆写 `requestUserSeedingPage` 与 `parseUserInfoForSeedingStatus`。 + +## 常见坑 + +1. **忘了 `...SchemaMetadata`**:`index.ts:93-102` 只取 schema 的 `default class`,不会合并 `SchemaMetadata`;不展开则完全没有 search / userInfo 配置。 +2. **`id` 与文件名不一致或含大写**:`id` 必须等于文件名(去扩展名)且匹配 `/[0-9a-z]+/`(`types/site.ts:36-44`)。 +3. **不给 `levelRequirements`**:它不在 `SchemaMetadata` 里,缺失时 `levelId` 不会计算(`schemas/AbstractPrivateSite.ts:194-197`)。 +4. **`rows` 自动推断失效**:默认依赖 `table.torrents:last`,且无 `<thead>` 时用 `:gt(0)` 跳过首行(`:799-809`)。表格类名或结构不同必须显式写 `rows`(`definitions/byrbt.ts:58`、`definitions/audiences.ts:117-119`)。 +5. **`progress` / `status` 默认取法特殊**:默认从标题元素父级的 `div[title="<status> <progress>"]` 解析(`:122-131`、`:391-415`)。站点改用 `div.progressBar` 一类结构时必须覆写(`definitions/ourbits.ts:182-212`、`definitions/hdsky.ts:300-320`)。 +6. **`category` 默认读行内第一个 `a` 的图片 title/alt**(`:416-430`),没有分类图标时会一律得到 `"Other"`。 +7. **`time` 覆盖时不要丢掉 `elementProcess`**:默认实现依赖 `<br>` 分隔与时间串格式(`:431-450`),只换 selector 的写法见 `definitions/audiences.ts:163-166`。 +8. **`tags` 用追加而不是替换**:默认 7 项探测固定 class(`:476-484`),站点自定义标签应 `...SchemaMetadata.search!.selectors!.tags!` 后再补(`definitions/audiences.ts:181-205`)。 +9. **`donorConfig.bonusPerHourMultiplier` 忘改**:默认 2,若 selector 已经取到加倍后的时魔,会再乘一次(`:766-769`、`:867-873`)。 +10. **`keywordPath` 写错**:默认是 `params.search`,不是 `params.keywords`(`:352`);`params.search` 与 `search_area` 配合使用(高级搜索词改写见 `:359-363`)。 +11. **高级搜索词只默认支持 `imdb`**:未声明 `douban` 时 `douban|xxx` 会被直接跳过(`schemas/AbstractBittorrentSite.ts:243-257`),需要自己在 `advanceKeywordParams` 里加(`definitions/ourbits.ts:164-172`)。 +12. **`list` 默认只认两个入口**:`/torrents.php` 与 `/special.php`(`:488-492`);入口不同要追加(`definitions/tjupt.ts:149-154`),要去掉 `/special.php` 需整体重写(`definitions/zrpt.ts:192-196`)。 +13. **H&R 字段默认只读 `#info_block` 的 `myhr.php` 链接**(`:680-700`),站点若在别处展示必须覆写(`definitions/ourbits.ts:236-250`)。 +14. **`timezoneOffset` 只在非 +0800 时改**:模板已给 `+0800`(`:350`),`index.ts:60` 对 NexusPHP 还会兜底一次。 diff --git a/.dsh/skills/ptd-site-definition/references/engines/rartracker.md b/.dsh/skills/ptd-site-definition/references/engines/rartracker.md new file mode 100644 index 000000000..2a9c45769 --- /dev/null +++ b/.dsh/skills/ptd-site-definition/references/engines/rartracker.md @@ -0,0 +1,137 @@ +# Rartracker 站点定义规范 + +Rartracker(`https://github.com/swetorrentking/rartracker`)是一套小型开源 BT/PT 站程序,前端为 AngularJS SPA,页面数据通过 `/api/v1/` 下的 JSON 接口异步加载(`schemas/Rartracker.ts:1-5`)。本仓库由 `src/packages/site/schemas/Rartracker.ts` 承载它:该 schema 已给出搜索、用户信息的 JSON 选择器,并导出一个 `SchemaMetadata`。 + +因此**Rartracker 站点只需在 `definitions/<id>.ts` 中展开 `...SchemaMetadata` 再覆写差异项**;种子下载链接由 schema 的类方法用 passkey 现拼(`schemas/Rartracker.ts:102-106`),definition 一般不必再处理。只有分叉站的字段名/接口路径不同,或需要给站点打标签、补 content-script 的列表页/详情页时才需要写额外配置。 + +下文行号若未特别说明,均指 `src/packages/site/schemas/Rartracker.ts`(该文件实际 107 行)。 + +## 判定站点是否属于该引擎 + +正向特征(有其一即可进一步验证): + +- 存在 JSON 接口 `/api/v1/status`(`schemas/Rartracker.ts:12`)与 `/api/v1/torrents`(23-24);打开浏览器控制台可在网络面板看到这两个请求。 +- 页面是 AngularJS 风格的自定义元素,如 `torrents-table[torrents]`、`torrent[viewing-torrent]`(`definitions/digitalcore.ts:8`、`definitions/digitalcore.ts:180`)。 +- 种子详情页形如 `/torrent/{id}/`(`schemas/Rartracker.ts:41`),用户页形如 `/api/v1/users/{id}`(72-73)。 +- 上游程序的 README/页脚写着 rartracker;已知基于它的站只有 DigitalCore 与 Superbits(仓库内 `schema: "Rartracker"` 的定义仅这两个)。 + +易混淆情况: + +- **TCG**:同为「PHP 自研小站」,但 TCG 是 `browse.php` + HTML 表格(`schemas/TCG.ts:10`、`schemas/TCG.ts:25`),没有 `/api/v1/` 接口。 +- **Unit3D / NexusPHP 的 JSON 变体**:也可能返回 JSON,但路径是 `/api/torrents/filter` 或 `torrents.php`,且不存在 `/api/v1/status`。 +- **分叉站的字段名会变**:`completed` 在 DigitalCore 是 `times_completed`(`definitions/digitalcore.ts:135`),在 Superbits 是 `timesCompleted`(`definitions/superbits.ts:77`);`section`、`limit` 等查询参数也可能被改。**不要照抄另一个 Rartracker 站的选择器**。 +- 部分分叉站把 `/api/v1/torrents` 的响应包了一层(顶层不是数组)。此时 `rows: { selector: ":self" }` 会选到对象而不是行数组,必须换成具体字段路径,做法参考 `definitions/milkie.ts:50`(`rows: { selector: "torrents" }`)这类 JSON 站点。 + +## SchemaMetadata 默认值 + +`schemas/Rartracker.ts:16-88` 导出的 `SchemaMetadata: Partial<ISiteMetadata>`;另导出 `statusRequestConfig`(11-14),供类方法与 definition 复用。 + +| 字段路径 | 默认值 | 行号 | definition 是否通常需要覆盖 | +| --- | --- | --- | --- | +| `version` | `0` | 17 | 现状都显式写 `1`(`definitions/digitalcore.ts:91`、`definitions/superbits.ts:26`) | +| `schema` | `"Rartracker"` | 18 | 展开 `...SchemaMetadata` 已带上;现有定义仍在 `type` 旁重复写一遍(`definitions/digitalcore.ts:99`) | +| `type` | `"private"` | 19 | 不需要 | +| `search.keywordPath` | `"params.searchText"` | 22 | 分叉站改了参数名时才改 | +| `search.requestConfig` | `url: "/api/v1/torrents"`、`responseType: "json"`、`params: { limit: 100, index: 0, section: "all", extendedSearch: false, watchview: false }` | 23-33 | 需要:补站点特有参数(排序、`dead`、`page` 等) | +| `search.advanceKeywordParams.imdb` | `{ enabled: true }`(只去除 `imdb\|` 前缀,不改写请求) | 34-36 | 一般不需要;要提取 imdb id 时自行加 `ext_imdb` selector | +| `search.selectors.rows` | `{ selector: ":self" }` | 38 | 响应包了一层时才改 | +| `search.selectors.id/title/url` | `id` / `name` / `id` + filter 拼 `/torrent/{id}/` | 39-41 | 分叉站字段名不同时改 | +| `search.selectors.time/size/seeders/leechers/comments/category` | `added` / `size` / `seeders` / `leechers` / `comments` / `category` | 42-47 | `completed`、`subTitle`、`tags`、`ext_imdb` 等要自己补 | +| `userInfo.pickLast` | `["id", "joinTime"]` | 52 | 需要:常补 `name`(`definitions/digitalcore.ts:187`) | +| `userInfo.process[0]` | `statusRequestConfig` + `id/name/uploaded/downloaded/messageCount/bonus/seedingSize` | 54-69 | 需要:追加 `uploads`、`seeding`、`levelId/levelName` 等 | +| `userInfo.process[1]` | `url: "/api/v1/users/$id$"`、`assertion: { id: "url" }` + `joinTime/lastAccessAt/uploaded/downloaded/trueDownloaded/bonus` | 70-85 | 需要:追加站点等级、真实上传量等 | +| `userInfo.requestDelay`、`userInfo.selectors`、`userInfo.donorConfig` | **未提供** | — | 需要限速时补(`types/site.ts:355-358`) | +| `category`、`levelRequirements`、`list`、`detail`、`searchEntry`、`noLoginAssert`、`userInputSettingMeta` | **未提供** | — | 前四项按站点补(见下);后两项一般不需要 | + +## 最小可用定义骨架 + +```ts +import type { ISiteMetadata } from "../types"; +import Rartracker, { SchemaMetadata } from "../schemas/Rartracker"; + +export const siteMetadata: ISiteMetadata = { + ...SchemaMetadata, + + version: 1, + id: "example", // 必须与文件名 example.ts 一致 + name: "Example", + tags: ["综合"], + timezoneOffset: "+0200", // 缺省为 "+0000"(index.ts:60),按站点实际时区补 + + type: "private", + schema: "Rartracker", + + urls: ["https://example.org/"], + + category: [ + { + name: "类别", + key: "categories", // 站点接口接收的分类参数名 + options: [{ name: "Movies", value: 1 }], // 必须来自站点真实分类 + cross: { mode: "brackets" }, // 多选形式按接口实测 + }, + ], + + levelRequirements: [{ id: 1, name: "User", privilege: "..." }], +}; + +export default class Example extends Rartracker {} +``` + +`default class` 只有在该分叉站覆写了引擎行为时才需要;不写也能正常工作(`index.ts:93-102` 会用 `schema` 指向的 `Rartracker` 类)。 + +## 搜索配置要点 + +- **请求基础**:URL 由 schema 给定为 `/api/v1/torrents`,`responseType: "json"`(23-25);未声明 `method`,因此走 axios 默认的 GET。搜索的通用垫片是 `{ url: "/", responseType: "document", params: {}, data: {} }`(`schemas/AbstractBittorrentSite.ts:226-229`),schema 在此之上覆盖。 +- **关键词位置**:`keywordPath: "params.searchText"`(22);`getSearchResult` 用 `set(requestConfig, searchEntry.keywordPath || "params.keywords", keywords)` 注入(`schemas/AbstractBittorrentSite.ts:266-268`)。覆写 `keywordPath` 时只改路径字符串即可。 +- **高级搜索词**:`advanceKeywordParams: { imdb: { enabled: true } }`(34-36)只表示「`imdb|tt123` 不跳过」,前缀被去掉后仍按普通文本搜索(`schemas/AbstractBittorrentSite.ts:238-262`)。要真正拿到 imdb id,需自行加 `ext_imdb` selector(`definitions/digitalcore.ts:148`)。 +- **selectors 覆盖的种子字段**:`id, title, url, time, size, seeders, leechers, comments, category`(38-48)。**没有 `link`**:下载链接由类方法的 `parseTorrentRowForLink` 用 passkey 拼(102-106)。`completed`/`subTitle`/`tags`/`ext_imdb` 都由 definition 自行追加。 +- **JSON 行选择器**:`rows: { selector: ":self" }` 在非 Document 响应下代表响应根(`schemas/AbstractBittorrentSite.ts:479-485`、512);如果接口把数组包在 `{ torrents: [...] }` 之类的外层里,`:self` 会选到对象,必须把 selector 改成具体字段路径。 +- **覆写 requestConfig 要保留默认 params**:只写自己的参数会把 `limit/index/section/...` 丢掉,标准写法是逐层展开——`definitions/digitalcore.ts:122-131`、`definitions/superbits.ts:59-73`。 +- **category 分类**:种子的 `category` 取到的是站点数字分类 id(47),definition 用 `filters` 映射成名字(`definitions/digitalcore.ts:136-139`);搜索用的分类配置写在 metadata 顶层的 `category`,写法见 `types/search.ts:202-248`。两个范例都用了字典 + `buildCategoryOptionsFromDict`(`utils/helper.ts:36`),但多选形式不同:DigitalCore 用 `cross: { mode: "brackets" }`(`definitions/digitalcore.ts:103-118`),Superbits 用 `cross: { mode: "append" }`(`definitions/superbits.ts:40-55`)——**按接口实测结果选,不要照抄**。 +- **`list` / `detail` / `searchEntry`**:schema 均未提供。由于搜索走 JSON 接口,浏览器里直接打开列表页时插件无法复用同一套选择器,需要 content-script 适配的站(如 DigitalCore 的 HTML 列表页)要自己补 `list[]` 与 `detail`(`definitions/digitalcore.ts:152-184`),并把 `mergeSearchSelectors` 设为 `false`。 +- **请求延迟**:schema 未设置 `search.requestDelay`;站点级 `requestDelay`(`types/site.ts:119-124`)在 `request()` 内生效(`schemas/AbstractBittorrentSite.ts:126`)。 + +## 用户信息(userInfo) + +两个步骤,全部走 `/api/v1/` JSON: + +1. `process[0]` 请求 `/api/v1/status`(复用导出的 `statusRequestConfig`,11-14),取 `user.id`、`user.username`、`user.uploaded`、`user.downloaded`、`user.newMessages`、`user.bonuspoang`、`user.currentGbSeed`(57-68)。最后一项用 `filters: [(q) => q * GB]` 换算成字节,`GB` 来自 `utils/filesize.ts:8`。 +2. `process[1]` 请求 `/api/v1/users/$id$`,`assertion: { id: "url" }`(72-76):`AbstractPrivateSite.getUserInfoResult` 会把 URL 里的 `$id$` 替换成上一步拿到的 id(`schemas/AbstractPrivateSite.ts:148-165`);取 `added`、`last_access`、`uploaded`、`downloaded`、`downloaded_real`、`bonuspoang`(77-84)。 + +其余行为全部继承 `AbstractPrivateSite`,要点: + +- `pickLast` 默认 `["id", "joinTime"]`(52):命中历史缓存时该字段不会重复请求(`schemas/AbstractPrivateSite.ts:126-128`)。DigitalCore 补成 `["id", "name", "joinTime"]`(`definitions/digitalcore.ts:187`),Superbits 直接展开默认值再改 process(`definitions/superbits.ts:82-83`)。 +- 某一步的 `fields ∪ Object.keys(selectors)` 若已全部存在,该步骤整体跳过(`schemas/AbstractPrivateSite.ts:131-141`)——所以「上一步已经取到的字段」不要再在下一步写一次选择器。 +- 断言字段缺失会抛错,该次采集直接以 `parseError` 结束(`schemas/AbstractPrivateSite.ts:160-163`、200-206)。 +- `donorConfig` 在 schema 中未提供,Rartracker 路径也不会消费它;`bonusPerHourMultiplier` 仅在 `schemas/NexusPHP.ts:868-870` 生效。 +- **等级要自己写**:`SchemaMetadata` 没有 `levelRequirements`。站点等级在 process[1] 里以 `levelId` / `levelName` 的形式取(`definitions/digitalcore.ts:202-207` 用 `class` 字段 + 字典 filter);只有当 `levelName` 有值且 `levelId` 缺失时,`AbstractPrivateSite.ts:194-197` 才会用 `guessUserLevelId` 反推(`utils/level.ts:300-310`)。两个范例都写全了 `levelRequirements`(`definitions/digitalcore.ts:236-279`、`definitions/superbits.ts:106-133`)。 +- 追加第三个步骤很常见(如 bonus 日志接口),照 `definitions/digitalcore.ts:209-232` 的写法加 `requestConfig` + `assertion` 即可。 + +## 常被覆写的类方法 + +| 方法(签名见源码) | 何时覆写 | 行号 | +| --- | --- | --- | +| `protected async parseTorrentRowForLink(torrent: Partial<ITorrent>): Promise<Partial<ITorrent>>` | 下载链接的拼法不同(路径、附加参数)时;默认实现是 `/api/v1/torrents/download/{id}/{passkey}`。覆写时可继续用父类私有方法 `getPassKey()` 拿 passkey | 102-106 | +| `private async getPassKey(): Promise<string>` | passkey 不在 `/api/v1/status` 的 `user.passkey` 里时;默认实现带缓存,只请求一次 | 94-100 | +| `protected override parseTorrentRowForTags(torrent, row, searchConfig)` | 分叉站在 JSON 行对象上有自己的优惠/语言标记时。注意 row 是普通对象而非 Element,用 `row.frileech === 1` 这类判断,不要用 Sizzle | 基类实现 `schemas/AbstractBittorrentSite.ts:615-637`;范例 `definitions/superbits.ts:138-154` | +| `public override async request<T>(axiosConfig, checkLogin = true)` | 需要给所有 API 请求统一加 header / 改 `responseType` 时(基类方法,不是本 schema 定义) | `schemas/AbstractBittorrentSite.ts:119-167`;同类写法见 `definitions/milkie.ts:125-139`、`definitions/hdbits.ts:318-329` | +| `protected override loggedCheck(res)` | 分叉站未登录时返回的是 HTTP 200 + JSON 错误体时(基类默认只处理状态码/跳转/refresh 头)。Rartracker 路径默认走 `AbstractPrivateSite` 的实现 | `schemas/AbstractPrivateSite.ts:50-107`;范例 `definitions/hdbits.ts:331-341` | + +覆写时保持签名一致并加 `override` 关键字(`pnpm check` 会校验:`vue-tsc --noEmit`)。 + +## 范例定义 + +- `definitions/digitalcore.ts` — 最完整的范例:展开 `...SchemaMetadata` 后补搜索参数、`category` 映射与标签、`ext_imdb`、两级 userInfo 追加、第三级 bonus 日志接口、完整 `levelRequirements`,并额外补 `list[]` / `detail` 供 content-script 使用;`default class` 为空壳。 +- `definitions/superbits.ts` — 差异化范例:`category` 用 `cross: { mode: "append" }`、`completed` 字段名不同、覆写 `parseTorrentRowForTags` 在 JSON 行上打 Free/SweSub/Pack 标签。 + +## 常见坑 + +1. **分叉字段名不一致**:`completed` 一处叫 `times_completed`(`definitions/digitalcore.ts:135`)、一处叫 `timesCompleted`(`definitions/superbits.ts:77`)。选择器必须对着真实接口响应写。 +2. **`...SchemaMetadata` 是浅展开**:覆写 `search.requestConfig.params` 时要连同 `...SchemaMetadata.search!.requestConfig!.params` 一起展开,否则丢 `limit/index/section` 等默认值(`definitions/digitalcore.ts:122-131`)。覆写 `userInfo.process[n]` 同理,要 `...SchemaMetadata.userInfo!.process![n]`(`definitions/digitalcore.ts:190-196`)。 +3. **`rows: ":self"` 只对「响应根就是行数组」成立**:响应多包一层会解析失败或抛错(`schemas/AbstractBittorrentSite.ts:479-485`、512)。 +4. **`imdb` 高级搜索词不等于支持 imdb 检索**:`{ enabled: true }` 只是不跳过(34-36 + `schemas/AbstractBittorrentSite.ts:238-262`),请求里不会自动带 imdb 参数;要展示 imdb id 得自己加 `ext_imdb` selector。 +5. **`SchemaMetadata` 不含 `link` 选择器**:搜索结果的下载链接由 `parseTorrentRowForLink` 拼(102-106)。覆写该方法时不要忘了 passkey,否则下载链接失效。 +6. **`version` 要显式覆盖**:`SchemaMetadata` 写的是 `0`(17),现有定义都写 `1`(`definitions/digitalcore.ts:91`、`definitions/superbits.ts:26`)。按 `types/site.ts:46-54` 的约定 Rartracker 不在「不需要更新版本号」的名单内。 +7. **时区缺省是 `+0000`**:`index.ts:60` 只给 `schema === "NexusPHP"` 特殊待遇;DigitalCore 写 `+0200`、Superbits 写 `+0100`,漏写会让 `added` 时间偏移(`schemas/AbstractBittorrentSite.ts:606-608`)。 +8. **`noLoginAssert` 只在未覆写 `loggedCheck` 时生效**(`types/site.ts:259-266`);Rartracker 路径使用 `AbstractPrivateSite.ts:31-48` 的默认值(401/403/502/504 + `/doLogin|login|verify|checkpoint|returnto/gi`)。若某站未登录也返回 200,需要显式补 `noLoginAssert.matchSelectors` 或覆写 `loggedCheck`。 diff --git a/.dsh/skills/ptd-site-definition/references/engines/tcg.md b/.dsh/skills/ptd-site-definition/references/engines/tcg.md new file mode 100644 index 000000000..4dd9b8a21 --- /dev/null +++ b/.dsh/skills/ptd-site-definition/references/engines/tcg.md @@ -0,0 +1,162 @@ +# TCG 站点定义规范 + +TCG 是一套英文学习/电子书类小站共用的 PHP tracker 模板(Jackett 中对应 thegeeks / thevault 两个站点定义,见 `definitions/thegeeks.ts:2`、`definitions/thevault.ts:2`)。本仓库由 `src/packages/site/schemas/TCG.ts` 承载它:该 schema 已给出 `browse.php` 列表页、`details.php` 详情页与两步式用户信息的选择器,并导出一个 `SchemaMetadata`。 + +因此 **TCG 站点只需在 `definitions/<id>.ts` 中展开 `...SchemaMetadata` 再覆写分类映射与等级**,不需要导出 `default class`(现有两个 TCG 定义都没有)。schema 末尾的类体是空的(`schemas/TCG.ts:133`:`export default class TCG extends PrivateSite {}`),站点能力完全由 `AbstractPrivateSite` 提供——需要覆写类方法时请先读 `references/engines/abstract-private-site.md`。 + +下文行号若未特别说明,均指 `src/packages/site/schemas/TCG.ts`(该文件实际 133 行)。 + +## 判定站点是否属于该引擎 + +正向特征(有其一即可进一步验证): + +- 搜索页是 `/browse.php`,分类链接形如 `browse.php?cat=NNN`,详情页 `details.php?id=`,下载链接 `download.php/...`(10、27-29、51)。 +- 用户信息页是 `/main.php` + `/userdetails.php?id=`(62-73),等级显示在 `td.rowhead:contains('Class') + td`(106)。 +- 列表表格行是 `tr.ttable`,且行内带有 `a[href^='browse.php?cat=']`(25)。 +- 同一套模板的站还有 TCG 联盟的其他站(Jackett 里同批定义),它们的选择器基本一致。 + +易混淆情况: + +- **NexusPHP**:同样有 `details.php?id=`、`userdetails.php?id=`,但 NexusPHP 的列表页通常是 `torrents.php` / `table.torrents`,用户信息结构是 `td.rowhead` 的成套文案(见 `schemas/NexusPHP.ts`)。若站点搜索页是 `torrents.php` 而不是 `browse.php`,基本可判定为 NexusPHP。 +- **Rartracker**:同为自研小站,但 Rartracker 走 `/api/v1/` JSON(`schemas/Rartracker.ts:12`、23-24),没有 `browse.php`。 +- **TCG 分叉站改了列顺序**:schema 的时间/大小/完成数/做种/下载/评论全部写死 `td:nth-child(N)`(30-38),分叉站一旦增删列就必须整段重写 `search.selectors`,照抄会静默解析出错误字段(不会报错,只是数值对不上)。 +- **搜索结果里混入特殊行**:schema 的 `rows` 选择器已经排除了 `*TCG*` 与蓝色字体的禁转/免费种子(23-26,注释说明来自 Jackett 的实测);换站时要确认这套排除规则仍然适用。 + +## SchemaMetadata 默认值 + +`schemas/TCG.ts:5-131` 导出的 `SchemaMetadata: Partial<ISiteMetadata>`: + +| 字段路径 | 默认值 | 行号 | definition 是否通常需要覆盖 | +| --- | --- | --- | --- | +| `version` | `0` | 6 | 现状都显式写 `1`(`definitions/thegeeks.ts:97`、`definitions/thevault.ts:63`) | +| `type` | **未提供** | — | **必须自己写 `type: "private"`**(见「常见坑」) | +| `schema` | **未提供** | — | **必须自己写 `schema: "TCG"`** | +| `search.keywordPath` | `"params.search"` | 8 | 不需要 | +| `search.requestConfig` | `url: "/browse.php"`、`responseType: "document"`、`params: { incldead: 1, titleonly: 1, nonboolean: 0 }` | 9-17 | 一般不需要(`titleonly: 1` 表示只搜标题,`nonboolean: 0` 表示精确搜索) | +| `search.advanceKeywordParams.imdb` | `false`(`imdb\|xxx` 搜索会被跳过) | 18-20 | 不需要;要支持时改写成 `{ enabled: true }` | +| `search.selectors.rows` | `table[border='0'] > tbody > tr.ttable:has(a[href^='browse.php?cat=']):not(:has(font[color='blue'])):not(:contains('*TCG*'))` | 22-26 | 分叉站改版时才改 | +| `search.selectors.title/url/link` | 都取自 `a[href^='details.php?id=']`(title 用 `attr: "title"`、url 用 `attr: "href"`)与 `a[href^='download.php/']` | 27-29 | 一般不需要 | +| `search.selectors.time` | `td:nth-child(6)` + `parseTime`,参数 `["yyyy-MM-ddHH:mm:ss"]` | 30 | 分叉站列顺序不同时改 | +| `search.selectors.size` | `td:nth-child(7)` + `parseSize` | 31 | 同上 | +| `search.selectors.completed` | `td:nth-child(8)`,先把 `Never` 换成 `0` 再 `parseNumber` | 32-35 | 同上 | +| `search.selectors.seeders/leechers/comments` | `td:nth-child(9)` / `td:nth-child(10)` / `td:nth-child(5)` | 36-38 | 同上 | +| `search.selectors.subTitle` | `span > em` | 39 | 一般不需要 | +| `search.selectors.tags` | 仅一个 `Neutral`(`font[color='green']:contains('NEUTRAL')`,绿色) | 40-46 | 常追加站点自有标签 | +| `search.selectors.id` | **未提供**,由 url 或 link 反推(`schemas/AbstractBittorrentSite.ts:591`) | — | 一般不需要 | +| `search.selectors.category` | **未提供**,需要自己从 `a[href^='browse.php?cat=']` 解析 | — | **需要**(两个范例都写了) | +| `detail.urlPattern` | `["/details.php"]` | 51 | 不需要 | +| `detail.selectors.title/link` | `div > h1` / `a[href^='download.php/']` | 53-54 | 一般不需要 | +| `userInfo.pickLast` | `["id", "name", "joinTime"]` | 59 | 不需要 | +| `userInfo.process[0]` | `/main.php`,取 `id`(从 userdetails 链接 querystring)与 `name` | 62-71 | 一般不需要 | +| `userInfo.process[1]` | `/userdetails.php` + `assertion: { id: "params.id" }`,取 13 个字段 | 72-128 | 追加字段时展开该步骤 | +| `userInfo.selectors`、`userInfo.requestDelay`、`userInfo.donorConfig` | **未提供** | — | 限速 / 捐赠倍率需求时才补 | +| `noLoginAssert` | **未提供** | — | 一般不需要(默认值见 `schemas/AbstractPrivateSite.ts:31-48`) | +| `category`、`levelRequirements`、`list`、`searchEntry`、`userInputSettingMeta` | **未提供** | — | 前两项需要自备(见下) | + +## 最小可用定义骨架 + +```ts +import type { ISiteMetadata } from "../types.ts"; +import { SchemaMetadata } from "../schemas/TCG.ts"; +import { definedFilters, buildCategoryOptionsFromDict } from "../utils.ts"; + +const categoryMap: Record<number, string> = { 1: "Example Category" }; + +export const siteMetadata: ISiteMetadata = { + ...SchemaMetadata, + version: 1, + id: "example", // 必须与文件名 example.ts 一致 + name: "Example", + tags: ["学习"], + type: "private", // TCG 的 SchemaMetadata 不提供 type/schema,必须自己写 + schema: "TCG", + urls: ["https://example.club/"], + + category: [ + { name: "类别", key: "category", options: buildCategoryOptionsFromDict(categoryMap), cross: { mode: "append", key: "c" } }, + { name: "种子状态", key: "incldead", options: [{ name: "Active", value: 0 }] }, + ], + + search: { + ...SchemaMetadata.search, + selectors: { + ...SchemaMetadata.search!.selectors, // 必须展开,否则丢掉 rows/title/url/... 全部默认选择器 + category: { selector: "a[href^='browse.php?cat=']", attr: "href", filters: [(q: string) => categoryMap[Number(definedFilters.querystring(q, ["cat"]))]] }, + }, + }, +}; +``` + +`levelRequirements` 与 `timezoneOffset`(缺省 `"+0000"`,`index.ts:60`)按站点补;`schema: "TCG"` 会让站点实例落到 `schemas/TCG.ts` 的空类上(`index.ts:96-101`),行为等价于 `AbstractPrivateSite`。 + +## 搜索配置要点 + +- **请求基础**:`GET /browse.php`,`responseType: "document"`,固定参数 `incldead=1&titleonly=1&nonboolean=0`(10-16);关键词写入 `params.search`(8 + `schemas/AbstractBittorrentSite.ts:266-268`)。两个现有定义都没有覆写 `search.requestConfig`,直接沿用默认值。 +- **分类参数**:`thegeeks.ts:110-116` 与 `thevault.ts:76-82` 把站点分类写成 `key: "category"` + `cross: { mode: "append", key: "c" }`,实际生成 `c<catId>=1` 形式(`types/search.ts:226-240`);`incldead`、`nonboolean` 用单值即可(`types/search.ts:202-248`)。分类 value 必须与站点真实 cat id 一致。 +- **selectors 依赖 Sizzle 扩展伪类**:默认 `rows` 里的 `:has()` / `:contains()`(24-25)、`userInfo` 里的 `:contains()`(77、85、94、97)与 `:first`(65、69)都不是标准 CSS 选择器,只有在 Sizzle 下才成立——本仓库的解析路径正是 Sizzle(`schemas/AbstractBittorrentSite.ts:380`、488),所以能正常工作;但**不要把这些选择器复制到 `document.querySelectorAll` 或其他工具里**。 +- **种子字段**:默认覆盖 `title/url/link/time/size/completed/seeders/leechers/comments/subTitle/tags`(27-46),`id` 由 url 或 link 反推(`schemas/AbstractBittorrentSite.ts:591`)。搜索页已能拿到下载链接(29),因此通常不必再走详情页。 +- **category 映射**:种子的 `category` 需要自己写 selector,两个范例的写法一致——取 `a[href^='browse.php?cat=']` 的 href,再用 `definedFilters.querystring(query, ["cat"])` 取 id,最后查字典(`definitions/thegeeks.ts:141-150`、`definitions/thevault.ts:107-116`;filter 实现见 `utils/filter.ts:29-39`)。覆写 `search.selectors` 时务必展开 `...SchemaMetadata.search!.selectors`,否则会丢掉上面整组默认选择器。 +- **高级搜索词**:`advanceKeywordParams: { imdb: false }`(18-20)。显式 `false` 表示 `imdb|xxx` 形式的查询会被跳过并返回 `i18n.noAdvanceParams`(`schemas/AbstractBittorrentSite.ts:244-257`),而不是「回落到普通关键词」。 +- **`detail`**:给的是 `urlPattern: ["/details.php"]` 与 `title`/`link` 两个选择器(50-56),服务于 content-script 的详情页解析(`schemas/AbstractBittorrentSite.ts:716-755`)与 `getTorrentDownloadLink` 的补链接分支(764-778)。`urlPattern` 以 `new RegExp(pattern, "i").test(location.href)` 做子串匹配,因此 `"/details.php"` 足以命中带 query 的详情页。 +- **`list` / `searchEntry` 未提供**:TCG 站点是服务端渲染的表格页,`search` 的选择器可直接复用到列表页,无需额外的 `list[]`。 +- **请求延迟**:schema 未设置 `search.requestDelay`;需要限速时用站点级 `requestDelay`(`types/site.ts:119-124`,在 `schemas/AbstractBittorrentSite.ts:126` 生效)。 + +## 用户信息(userInfo) + +两个步骤,均返回 Document: + +1. `process[0]` 请求 `/main.php`(62-63),从 `a[href*='userdetails.php?id=']:first` 的 href 中取 `id`(经 `querystring` filter 取 `id` 参数)与 `name`(64-70)。**这一步依赖登录后的 `main.php` 上存在指向自己资料的链接**。 +2. `process[1]` 请求 `/userdetails.php`,`assertion: { id: "params.id" }`(72-74):因为请求配置里没有 `params.id`,框架会直接把上一步的 id `set` 到 `params.id`(`schemas/AbstractPrivateSite.ts:151-159`),即最终请求 `/userdetails.php?id=<id>`。 + +第 2 步覆盖的字段(76-127): + +| 字段 | 取法要点 | 行号 | +| --- | --- | --- | +| `joinTime` / `lastAccessAt` | `td:contains('Join date'/'Last seen') + td:first`,先用 `split("(", 0)` + `trim` 去掉尾部的 "(10 months ago)",再 `parseTime` | 76-88 | +| `messageCount` | 站内信链接后的 `b`,`parseNumber` | 89-92 | +| `uploaded` / `downloaded` | `td.rowhead:contains('Uploaded'/'Downloaded') + td`,同样先切掉括号内容再 `parseSize` | 93-100 | +| `ratio` | 站点用 `---` 表示无分享率,映射为 `-1` | 101-104 | +| `seeding` | `img[title='seeders'] + span:first` | 105 | +| `levelName` | `td.rowhead:contains('Class') + td` | 106 | +| `bonus` / `bonusPerHour` | 用 `{ text: "N/A" }` 占位,表示站点不提供该数据 | 107-112 | +| `seedingSize` | selector 指向做种表 `div#kd1 > table.details > tbody`,用 `elementProcess` 遍历各行累加第 4 列的 `parseSize` | 113-126 | + +其余要点: + +- `pickLast` 默认 `["id", "name", "joinTime"]`(59):命中缓存时这三个字段不再重复请求;两个现有定义都未改动它。 +- `bonusPerHour` 给的是 `"N/A"`,`donorConfig.bonusPerHourMultiplier` 也未被 `SchemaMetadata` 提供,TCG 路径不会做时魔倍率修正(该逻辑只在 `schemas/NexusPHP.ts:868-870`)。若站点实际有魔力值,需自行覆写 `bonus` / `bonusPerHour` 的 selector。 +- **等级要自己写**:`levelRequirements` 不在 `SchemaMetadata` 里,而 `levelName` 由第 2 步直接取出(106),因此只在 definition 里补齐等级表,`levelId` 就会由 `guessUserLevelId` 按名称反推(`schemas/AbstractPrivateSite.ts:194-197`、`utils/level.ts:300-310`)。注意 `guessUserLevelId` 的匹配是「站点等级表里的 name 包含页面取到的 levelName」,因此 `name` 文案要尽量与页面一致。 +- `noLoginAssert` 未定义,走 `schemas/AbstractPrivateSite.ts:31-48` 的默认值(401/403/502/504 + URL 模式匹配);TCG 站点未登录时通常会 302 到登录页,命中默认 URL 模式即可。 +- 两个现有定义的 `levelRequirements`(`definitions/thegeeks.ts:154-176`、`definitions/thevault.ts:120-142`)完全一致:User / Power User / Extreme User 三级,只写了 `interval` 与 `uploaded`。 + +## 常被覆写的类方法 + +`schemas/TCG.ts:133` 是一个空类(`export default class TCG extends PrivateSite {}`),**本 schema 自身没有定义任何可覆写的方法**。TCG 定义若需要覆写,等价于覆写 `AbstractPrivateSite` / `AbstractBittorrentSite` 的钩子(导入写法:`import PrivateSite from "../schemas/AbstractPrivateSite"`,见 `definitions/milkie.ts:3`),常见的有: + +| 方法(签名见源码) | 何时覆写 | 行号 | +| --- | --- | --- | +| `protected override loggedCheck(res: AxiosResponse): boolean` | 未登录时返回 200 而非跳转(基类默认检查状态码、responseURL、refresh 头) | `schemas/AbstractPrivateSite.ts:50-107`;范例 `definitions/hdbits.ts:331-341` | +| `public override async getUserInfoResult(lastUserInfo = {})` | 用户信息需要额外页面/接口补充时 | `schemas/AbstractPrivateSite.ts:113-215`;范例 `definitions/torrentleech.ts:362-383` | +| `protected async parseUserInfoFor{Field}(flushUserInfo)` | 某字段需要在抓取流程内做额外请求(方法名按字段 PascalCase 动态派发) | `schemas/AbstractPrivateSite.ts:177-191`;范例 `definitions/torrentleech.ts:400-430` | +| `public override async getTorrentDownloadLink(torrent: ITorrent)` | 搜索页的 `download.php/...` 链接需要补参数或改走接口 | `schemas/AbstractBittorrentSite.ts:764-778`;范例 `definitions/pornolab.ts:264` | +| `public override async request<T>(axiosConfig, checkLogin = true)` | 需要统一加 header / 改 `responseType` | `schemas/AbstractBittorrentSite.ts:119-167`;范例 `definitions/milkie.ts:125-139` | +| `protected override parseTorrentRowForTags(torrent, row, searchConfig)` | 要在默认 `Neutral` 标签之外追加优惠/免费标签 | `schemas/AbstractBittorrentSite.ts:615-637`;范例 `definitions/torrentleech.ts:385-397` | + +若覆写,请同样写出 `export default class Example extends TCG {}`(导入 `../schemas/TCG`)或直接继承 `PrivateSite`;仅当确有必要时才加类,否则保持与其他 TCG 定义一致的无类写法。 + +## 范例定义 + +- `definitions/thegeeks.ts` — 标准写法:展开 `...SchemaMetadata`,补 `category`(category/incldead/nonboolean 三组)、覆写 `search.selectors.category` 做 id→名称映射、自备 `levelRequirements`;无 `default class`。 +- `definitions/thevault.ts` — 与 thegeeks 同构的另一个站,可用来对照「同一模板下只有 categoryMap 与 `urls`/`name` 不同」的最小差异。 + +## 常见坑 + +1. **`SchemaMetadata` 没有 `type` 和 `schema`**:不写 `type: "private"` 时,`index.ts:41-42` 的类型判断失败会把 `schema` 补成 `AbstractBittorrentSite`,站点将不会做登录检查、也不会采集用户信息。TCG 定义必须自己写这两行(`definitions/thegeeks.ts:105-106`)。 +2. **只写 `userInfo: { ... }` 会整段替换默认流程**:TCG 的默认用户信息全在 `process` 里,覆写字段时应展开 `process[0]` / `process[1]` 后**只加不改**,否则会丢掉 `joinTime`、`uploaded` 等既有选择器(对比 `definitions/digitalcore.ts:188-208` 的展开写法)。 +3. **`search.selectors` 是整组替换**:只写 `category` 而不展开 `...SchemaMetadata.search!.selectors`,会丢掉 `rows/title/url/link/time/size/...`,搜索直接解析不出结果(正确写法见 `definitions/thegeeks.ts:139-151`)。 +4. **列位置写死在 `td:nth-child(N)`**(30-38):分叉站增删列后这些数字全部要重排,且错位后不会报错,只会出现「大小/做种数明显不对」。 +5. **选择器含 Sizzle 专有伪类**(`:has`、`:contains`、`:first`):见「搜索配置要点」,换到非 Sizzle 环境会直接抛异常或匹配为空。 +6. **`completed` 的 `Never` 需要预处理**(32-35):跳过 `replace("Never", "0")` 会让 `parseNumber("Never")` 得到 0 或 NaN 类结果,表现为完成数异常。 +7. **`bonus`/`bonusPerHour` 默认是 `"N/A"`**(107-112):如果站点其实提供魔力值,必须换成真实 selector,否则前端会一直显示 N/A。 +8. **`version` 要显式覆盖**:`SchemaMetadata` 写的是 `0`(6),现有定义都写 `1`(`definitions/thegeeks.ts:97`);按 `types/site.ts:46-54` 的约定 TCG 不在「不需要更新版本号」名单内。 +9. **`detail.urlPattern` 只影响 content-script**:`["/details.php"]`(51)用于判定「当前页是不是详情页」,与搜索请求无关;站点详情页路径不同时要同步改(`types/site.ts:207-214`)。 diff --git a/.dsh/skills/ptd-site-definition/references/engines/unit3d.md b/.dsh/skills/ptd-site-definition/references/engines/unit3d.md new file mode 100644 index 000000000..9ada0446a --- /dev/null +++ b/.dsh/skills/ptd-site-definition/references/engines/unit3d.md @@ -0,0 +1,161 @@ +# Unit3D 站点定义规范 + +Unit3D(UNIT3D-Community-Edition,Laravel + Blade)是近年新建 PT 站最常用的开源引擎,各站的列表页、详情页、用户资料页结构高度一致。本仓库由 `src/packages/site/schemas/Unit3D.ts` 承载它:该 schema 已提供搜索列表页、详情页、用户信息的默认选择器,并导出一个 `SchemaMetadata`。因此**绝大多数 Unit3D 站点只需在 `definitions/<id>.ts` 中展开 `...SchemaMetadata` 再覆写差异项,不需要导出 `default class`**(现有 35 个 Unit3D 定义中只有 2 个导出了 default class)。 + +下文行号若未特别说明,均指 `src/packages/site/schemas/Unit3D.ts`(该文件实际 666 行)。 + +## 判定站点是否属于该引擎 + +正向特征(有其一即可进一步验证): + +- 种子列表页在 `/torrents/`(少数站改为自建 JSON 接口,如 huno 的 `/api/torrents/filter`,`definitions/huno.ts:179`),下载链接形如 `/torrents/download/{id}`、`/download/{id}`、`/download_check/{id}`(`schemas/Unit3D.ts:140-144`)。 +- 页面存在 Unit3D 专有类名:`torrent-search--list__results`、`torrent-search--list__name`、`torrent-search--list__seeders/leechers/completed/size/category/uploader`、`ratio-bar__uploaded/downloaded/ratio/points/seeding/leeching`、`badge-user`、`panelV2`(`schemas/Unit3D.ts:103-205`、`405-520`)。 +- 图标类名成体系:`i.fa-arrow-circle-up`(做种)、`i.fa-thumbs-down`(撤种)、`i.torrent-icons__freeleech`、`i.torrent-icons__double-upload`、`i.torrent-icons__internal`、`i.fa-thumbtack`(`schemas/Unit3D.ts:213-288`)。 +- 用户等级显示为 `span.badge-user`(或 `a.user-tag__link`),资料页路径为 `/users/{name}`,另有 `/users/{name}/earnings`(`schemas/Unit3D.ts:457-462`、`635`、`649`)。 +- 站点描述中直接写明 "POWERED BY UNIT3D"(`definitions/huno.ts:112`)。 + +易混淆情况: + +- **Gazelle / GazelleJSONAPI**:同为英文影视/音乐站,但路径为 `torrents.php?id=` 或 JSON API,且没有 `torrent-search--list__*` 这组类名。 +- **NexusPHP**:中文站通常走 `details.php?id=`、`torrents.php`,类名体系完全不同。 +- **其它 Laravel 模板**:可能同样使用 Bootstrap 的 `badge`、`table-responsive`,务必以 `torrent-search--list__*` / `ratio-bar__*` 是否出现为准,不要仅凭「Laravel + `/torrents/`」判定。 +- **Unit3D 站点自研前端**:仍属本引擎,但默认选择器会失效(Livewire 卡片布局见 `definitions/nordicbytes.ts:135-137`),需要整段重写 `search.selectors`。 + +## SchemaMetadata 默认值 + +`schemas/Unit3D.ts:70-523` 导出的 `SchemaMetadata: Partial<ISiteMetadata>`: + +| 字段路径 | 默认值 | 行号 | definition 是否通常需要覆盖 | +| --- | --- | --- | --- | +| `version` | `0` | 71 | 现状全部显式写 `1`(见「常见坑」) | +| `timezoneOffset` | `"+0000"` | 72 | 一般不需要(个别站如 monikadesign 写 `+0800`) | +| `search.keywordPath` | `"params.name"` | 74 | 不需要 | +| `search.requestConfig` | `url: "/torrents/"`、`responseType: "document"`、`params.perPage: 100` | 75-81 | 默认不需要;JSON API 站必须改(含 `responseType`) | +| `search.advanceKeywordParams.imdb` | 把 `params.name` 改写为 `params.imdbId` | 83-91 | 不需要;新增其它高级搜索词时展开 | +| `search.advanceKeywordParams.tmdb` | 把 `params.name` 改写为 `params.tmdbId` | 92-100 | 同上 | +| `search.selectors.rows` | 新版 `div.torrent-search--list__results > table:first > tbody > tr` 与旧版 `div.table-responsive table > tbody > tr` 两套,外加只保留含 `/torrents/{id}` 链接行的 `filter` | 103-118 | 仅改版站 / JSON API 站需要 | +| `search.selectors.id/title/url` | `a.view-torrent`、`a.torrent-search--list__name` | 124-138 | 视站点 | +| `search.selectors.subTitle` | `{ text: "" }`(Unit3D 列表页没有副标题设计) | 132-134 | 有副标题的站要覆写(`definitions/monikadesign.ts:187`) | +| `search.selectors.link` | `a[href*='/download/']`、`a[href*='/download_check/']`,并把 `download_check` 归一为 `download` | 140-144 | 视站点 | +| `search.selectors.time` | `time` 标签;有 `title` 用 `parseValidTimeString(title)`,否则用 `parseTimeToLiveToDate(text)` | 146-158 | 视站点 | +| `search.selectors.size/author/category/seeders/leechers/completed/comments` | 见源码;`category` 优先取 `data-original-title` 去掉末尾的 "Torrent" 词,回落到 `alt` | 160-211 | 视站点 | +| `search.selectors.status/progress` | 由 `i.torrent-icons` 的类名映射 `ETorrentStatus`,默认 `unknown` / `0` | 213-229 | 视站点 | +| `search.selectors.tags` | 11 个标签:Free、2xUp、75%、50%、25%、置顶、可退款、Internal、个人发布、Highspeed、Trump | 230-288 | 常追加站点自有标签 | +| `list[0]` | `urlPattern` 匹配 `/torrents` 根路径,`excludeUrlPattern` 排除 `?view=card` / `?view=grouped` / `?view=poster` | 292-296 | 不需要 | +| `list[1]` | `/torrents/similar/`,`mergeSearchSelectors: false`,自带 `rows/id/title/url/link` | 297-325 | 不需要 | +| `detail.urlPattern` | `/torrents/\d+` | 329 | 不需要 | +| `detail.selectors` | `id`(先 URL 后页面链接)、`title`(`h1.torrent__name`,回落 `html > body > title` 并做切分)、`link` | 330-379 | 不需要 | +| `userInfo.pickLast` | `["name"]` | 383 | 一般不需要 | +| `userInfo.selectors` | 18 个字段,见下节 | 384-521 | 站点改版时逐个覆写 | +| `userInfo.process` | **未提供** | — | 未提供即走 Unit3D 自带的抓取流程;只有 JSON API / 多步抓取才需要定义 | +| `userInfo.requestDelay` | **未提供** | — | 需要限速时补 | +| `userInfo.donorConfig` | **未提供**,且 Unit3D 的 `getUserInfoResult` 不消费该配置 | — | 在 Unit3D 定义里写它无效(见「常见坑」) | +| `noLoginAssert` | **未提供** | — | 一般不需要;兜底来自 `AbstractPrivateSite.ts:31-48`,需要时按 `definitions/huno.ts:269-272` 追加 URL 模式 | +| `category` | **未提供** | — | 需要:活跃定义几乎都显式声明(未声明的站将没有分类筛选) | +| `levelRequirements` | **未提供** | — | 需要:不写则无法把 `levelName` 映射为 `levelId` | +| `userInputSettingMeta` | **未提供** | — | 仅站点要求 API token 时(`definitions/huno.ts:124-131`) | +| `searchEntry` | **未提供** | — | 35 个 Unit3D 定义均未使用;只有多搜索入口才需要(`types/site.ts:139-144`) | + +## 最小可用定义骨架 + +```ts +import { type ISiteMetadata } from "../types"; +import { CategoryFree, SchemaMetadata } from "../schemas/Unit3D.ts"; + +export const siteMetadata: ISiteMetadata = { + ...SchemaMetadata, + + version: 1, + id: "example", // 必须与文件名 example.ts 一致 + name: "Example", + tags: ["影视", "综合"], + + type: "private", + schema: "Unit3D", + + urls: ["https://example.cc/"], // 敏感站点写 rot13:"uggcf://rknzcyr.pp/" + + category: [ + { + name: "类别", + key: "categoryIds", // Unit3D 分类参数名,站点间不同(categoryIds / categories / types / resolutions...) + options: [{ name: "Movie", value: 1 }], // 必须来自站点真实分类 + cross: { mode: "brackets" }, // &categoryIds[0]=1&categoryIds[1]=2 + }, + CategoryFree, // schemas/Unit3D.ts:43-68 导出的通用「优惠」分类(custom 模式) + ], + + // 站点要求 API token 时才需要(见 definitions/huno.ts:124-131 + huno.ts:525-537 注入请求头) + // userInputSettingMeta: [{ name: "token", label: "Token", hint: "...", required: true }], +}; +``` + +## 搜索配置要点 + +- **默认请求**:`GET /torrents/`,`responseType: "document"`,`params.perPage = 100`(75-81);关键词写入 `params.name`(74)。个别站会按自身每页数量调整该项,如 nordicbytes 改成 25(`definitions/nordicbytes.ts:131`)。 +- **高级搜索词**:`advanceKeywordParams` 已内置 `imdb`、`tmdb`(83-100),二者都把 `params.name` 改名为 `params.imdbId` / `params.tmdbId`。要新增(如 bangumi)时展开该对象:`definitions/monikadesign.ts:172-184` 把 name 改写为 `params.bgmId`。若站点用 JSON 接口,需像 huno 一样只保留 `imdb`(`definitions/huno.ts:185-195`)。 +- **默认覆盖的种子字段**:`id, title, subTitle, url, link, time, size, author, category, seeders, leechers, completed, comments, status, progress, tags`(102-289)。覆写 `search.selectors` 时若只写部分字段,需要展开 `...SchemaMetadata.search.selectors` 保留其余字段(`definitions/aither.ts:86-101`)。注意 `tags` 是数组,展开时要连同数组一起处理。 +- **category 写法**:每个大类是一个 `ISearchCategories`(`types/search.ts:202-248`)。Unit3D 站的分类 key 通常是 `categoryIds` / `types` / `resolutions` / `genres` / `free`,多选用 `cross: { mode: "brackets" }`(`definitions/blutopia.ts:100`、`definitions/monikadesign.ts:35`、`definitions/huno.ts:139`),追加式单值参数用 `cross: { mode: "append", key: "" }`(`definitions/monikadesign.ts:142`)。优惠分类用 `cross: { mode: "custom" }` + `generateRequestConfig`,直接复用导出的 `CategoryFree`(`schemas/Unit3D.ts:43-68`)或照 `definitions/huno.ts:155-172` 自行实现。category → requestConfig 的合并发生在 `src/entries/options/views/Settings/SetSearchSolution/utils.ts:39-89`。 +- **`searchEntry`**:Unit3D 定义中无先例,默认搜索入口用 `search.requestConfig` 即可(`types/site.ts:139-144`)。列表中 `keywords` 选择器也不需要写:`AbstractBittorrentSite.transformListPage` 会从 `keywordPath` 推断(`types/site.ts:191-198`)。 +- **`list`**:默认两条已覆盖普通列表页与 `/torrents/similar/`;站点若另有一批特殊列表页(如 monikadesign 的 `/torrents/airing/`),只需在数组尾部追加 `{ urlPattern: [...] }`(`definitions/monikadesign.ts:227-232`)。 +- **`detail`**:默认已能解析 id/title/link,一般不用动;替换下载链接的补全逻辑由 class 方法负责(见下两节)。 + +## 用户信息(userInfo) + +Unit3D 覆写了 `getUserInfoResult`(564-612),行为与 `AbstractPrivateSite` 不同: + +1. **定义了 `userInfo.process` 时**,直接交给 `AbstractPrivateSite.getUserInfoResult`(577-579),按 process 逐步抓取。 +2. **未定义 `process` 时**(默认路径)依次请求: + - 若 `lastUserInfo.name` 不存在,用 `getUserNameFromSite()` 访问 `/`,按 `selectors.name` 取用户名(587-589、614-629); + - `getUserInfoFromDetailsPage(name)` 请求 `/users/{name}`,用 `selectors` 除去 `name` 之外的全部字段(593、631-644); + - 若 `levelRequirements` 与 `levelName` 存在但 `levelId` 缺失,调用 `guessUserLevelId`(596-598、`AbstractPrivateSite.ts:218-220`); + - `getUserBonusPerHour(name)` 请求 `/users/{name}/earnings`(600、646-655); + - 只有 `NeedLoginError` 会被映射为 `needLogin`,其它异常统一 `parseError`(603-609)。 + +默认选择器覆盖的字段(384-521):`name`(取 `/users/*/settings` 链接中的用户名)、`id`、`uploaded`、`downloaded`、`ratio`、`trueRatio`、`bonus`、`seeding`、`leeching`、`seedingSize`、`averageSeedingTime`、`levelName`、`messageCount`、`uploads`、`joinTime`、`lastAccessAt`、`invites`、`bonusPerHour`。 +**未覆盖**:`hnrUnsatisfied`、`hnrPreWarning`、`avatar`、`isDonor`、`posts`、`snatches`(`definitions/monikadesign.ts:254` 的 TODO 也记录了这一点),需要时自行补 selector 或自定义 process。 + +其余要点: + +- `pickLast` 默认 `["name"]`(383);改用 process 时可按 huno 写 `["name", "id"]`(`definitions/huno.ts:275`)。 +- 多数站点是「`dl/dt/dd` 或 `td:contains(...)` 文本匹配」,源码用导出的 `userInfoTrans`(32-41)拼选择器;站点改版后按同样思路替换:`definitions/aither.ts:13-14` 用文案拼 `span.user-profile-card__meta-item-title:contains('x') + span.user-profile-card__meta-item-value`。 +- `messageCount` 的 `elementProcess` 恒返回 `11`(468),只是为了让未读红点显示出来,不是真实数量;huno 覆写它从 `fa-square-N` 反推(`definitions/huno.ts:318-334`)。 +- `noLoginAssert` 在 Unit3D 中未定义;需要额外兜底时在 definition 里补 `urlPatterns`(`definitions/huno.ts:269-272`),默认值见 `AbstractPrivateSite.ts:43`(`/doLogin|login|verify|checkpoint|returnto/gi`)。 +- `donorConfig` 只在 NexusPHP 中被消费(`NexusPHP.ts:866-873`),Unit3D 路径不会应用 `bonusPerHourMultiplier`;Unit3D 站点若时魔需要倍率,应直接在 `bonusPerHour` 的 selector/filter 中处理(如 `definitions/monikadesign.ts:247-250` 覆写 `getUserBonusPerHour`)。 +- `levelRequirements` 用 `id`(递增)+ `name`(**必须与站点 `levelName` 文本一致**,`types/userinfo.ts:73-77`)+ 需求字段(`uploaded`、`interval`、`averageSeedingTime`、`seedingSize`、`uploads`、`alternative` 等,`types/userinfo.ts:18-69`)。Unit3D 站常见「上传量 + 等待时间」与「做种量 + 平均做种时间」两类,用 `groupType: "user"` 区分保号组(`definitions/blutopia.ts:61-69`)。 + +## 常被覆写的类方法 + +| 方法(签名见源码) | 何时覆写 | 行号 | +| --- | --- | --- | +| `protected parseTorrentRowForTags(torrent, row, searchConfig)` | 需要在默认标签之外增删标签。默认实现会把同一个种子的多个优惠标签裁剪为只保留最高折扣(Free > 75% > 50% > 25%) | 527-562;范例 `definitions/huno.ts:405` | +| `public getUserInfoResult(lastUserInfo = {})` | 抓取流程与默认三页不同(每站自定义路径 / 字段顺序)时整体替换;注意保持 `status`/`updateAt`/`site` 返回结构 | 564-612 | +| `protected getUserNameFromSite()` | 首页无法用默认 `selectors.name` 取到用户名时 | 614-629 | +| `protected getUserInfoFromDetailsPage(userName)` | 用户资料页路径不是 `/users/{name}` 时 | 631-644 | +| `protected getUserBonusPerHour(name)` | 时魔不在 `/users/{name}/earnings` 时(monikadesign 改为 `/users/{name}/bonus/transactions/create`) | 646-655;范例 `definitions/monikadesign.ts:318` | +| `public getTorrentDownloadLink(torrent)` | 默认实现只把 `/torrents/{id}` 形式补成 `/torrents/download/{id}`;若站点需要先跳详情页再取真实链接,覆写并可按需直接调用 `AbstractBittorrentSite.prototype.getTorrentDownloadLink` 绕过 Unit3D 逻辑 | 657-665;范例 `definitions/monikadesign.ts:329-342` | +| `public request(axiosConfig, checkLogin = true)`(基类方法) | 站点要求注入 `X-Api-Token` 等自定义请求头 | 范例 `definitions/huno.ts:525-537` | + +覆写时保持签名一致并加 `override` 关键字(`pnpm check` 会校验)。 + +## 范例定义 + +- `definitions/blutopia.ts` — 最典型的「只写 metadata」范例:`category`(categoryIds/types/resolutions + `CategoryFree`)、完整 `levelRequirements`、追加 H&R 标签。 +- `definitions/aither.ts` — 站点改版后只覆写 `userInfo.selectors`(用导出的 `userInfoTrans` 拼接新文案选择器)与 `category`,其余沿用默认。 +- `definitions/monikadesign.ts` — 同时示范:自研列表页 selectors、`bangumi` 高级搜索词、覆写 `getUserBonusPerHour` 与 `getTorrentDownloadLink`。 +- `definitions/huno.ts` — 改用 JSON API 的完整改造:整段替换 `search.selectors`、定义 `userInfo.process`、`userInputSettingMeta` 存 token、覆写 `request` 注入请求头、覆写 `parseTorrentRowForTags` 打标签。 +- `definitions/nordicbytes.ts` — Livewire 卡片布局 + `userInfo.process` 两步抓取(首页统计 + 资料页徽章)。 +- 极简样板:`definitions/hdpost.ts`、`definitions/dxdhd.ts`(仅 `id`/`name`/`urls`/`isDead`,不展开 `SchemaMetadata`)。 + +## 常见坑 + +1. **`version` 与类型注释不一致**:`Unit3D.ts:71` 声明 `version: 0`,`types/site.ts:46-54` 也说 Unit3D 架构「不需要更新版本号」,但仓库中 35 个 Unit3D 定义全部显式写了大于 `0` 的版本号(绝大多数 `1`,`asiancinema`、`huno`、`lst`、`nordicbytes` 为 `2`)。新增定义请跟随现状写 `1`。 +2. **JSON API 站不能只改 `requestConfig`**:默认 `rows/id/title/...` 全是 DOM 选择器,把 `responseType` 改成 `json` 后必须整段替换 `search.selectors`(`definitions/huno.ts:197-266`)。 +3. **`userInfo` 是浅合并**:只写 `userInfo: { selectors: { ... } }` 会丢掉默认的 `pickLast` 与其它字段,标准写法是 `...SchemaMetadata.userInfo!` 后再展开 `selectors`(`definitions/seedpool.ts:37-40`、`definitions/aither.ts:104-107`)。 +4. **`tags: [{ selector: "*" }]` 恒成立**:`AbstractBittorrentSite.ts:620-632` 用 `Sizzle(selector, row).length > 0` 判断,`"*"` 对任意 DOM 行都为真,因此 `definitions/blutopia.ts:143-147`、`definitions/aither.ts:95-99` 追加的 `H&R` 实际会命中所有行;照抄该写法前请确认这是站点真实语义。 +5. **`donorConfig` 在 Unit3D 无效**:只有 `NexusPHP.ts:866-873` 会应用 `bonusPerHourMultiplier`,Unit3D 的 `getUserInfoResult` 不含该逻辑。 +6. **`SchemaMetadata.noLoginAssert` 不存在**:`definitions/huno.ts:270` 展开的是 `undefined`(JS 展开 `undefined` 合法但不产生任何键),真正的默认值来自 `AbstractPrivateSite.ts:43-48`。 +7. **`subTitle` 默认是空字符串**:`Unit3D.ts:132-134` 用 `{ text: "" }` 明确表明列表页没有副标题;要展示副标题必须自己给 `selector`(`definitions/monikadesign.ts:187`)。 +8. **`comments` 空值处理**:默认靠 `parseNumber` filter 把空值变成 `0`(208-211);JSON API 站若接口不返回评论数,写 `comments: { text: 0 }` 更稳妥(`definitions/huno.ts:235`)。 +9. **`list` 只匹配第一条命中的规则**:`transformListPage` 命中即 `break`(`AbstractBittorrentSite.ts:659-665`),新增 `list` 条目要注意顺序与 `urlPattern` 的重叠。 +10. **`id` 必须与文件名一致且只含小写字母数字**(`index.ts:22`、`types/site.ts:36-44`);`urls` 支持 rot13,加载时会统一还原(`index.ts:53-56`)。 diff --git a/.dsh/skills/ptd-site-definition/references/metadata-fields.md b/.dsh/skills/ptd-site-definition/references/metadata-fields.md new file mode 100644 index 000000000..9ee409c34 --- /dev/null +++ b/.dsh/skills/ptd-site-definition/references/metadata-fields.md @@ -0,0 +1,110 @@ +# ISiteMetadata 字段速查 + +来源:`src/packages/site/types/site.ts`(字段与注释的唯一权威)。本文件是索引与约束汇总,写定义时以源码注释为准。 + +一个站点定义文件的最小形态是: + +```ts +import type { ISiteMetadata } from "../types"; + +export const siteMetadata: ISiteMetadata = { + version: 1, + id: "example", + name: "Example", + type: "private", + schema: "NexusPHP", // 可省略,省略时按 type 回落(index.ts:41-42) + urls: ["https://example.com/"], +}; +``` + +导出名必须是 `siteMetadata`(`index.ts:37-44` 读取该字段)。 + +## 身份与版本 + +| 字段 | 类型 | 必填 | 约束 | +| --- | --- | --- | --- | +| `id` | string | ✅ | 必须等于文件名(无扩展名),匹配 `/[0-9a-z]+/`;Jackett 有同名站点时优先与其一致(`types/site.ts:36-44`) | +| `version` | number | ✅ | 必填数字,既有定义全部显式声明。`types/site.ts:46-54` 约定:模板类 `-1`、NexusPHP/Unit3D/Gazelle/GazelleJSONAPI/AvistaZ 系 `0`、其他 >0;但仓库现状是引擎类定义普遍写 `1`。实际做法是跟随同引擎既有文件取值,同一站点内保持一致 | +| `name` | string | ✅ | 站点显示名 | +| `aka` | string[] | | 站点别名,用于搜索/匹配 | +| `description` | string \| string[] | | 站点说明 | +| `tags` | string[] | | 站点标签;缺省为 `[]`(`index.ts:59`) | +| `timezoneOffset` | `` `${"UTC"|""}${"+"|"-"}${number}` `` | | 用于发布时间解析;缺省 NexusPHP `+0800`、其余 `+0000`(`index.ts:60`) | +| `collaborator` | readonly string[] | | 该站点解决方案的协作者,`readonly` | + +## 类型与构造 + +| 字段 | 说明 | +| --- | --- | +| `type` | 必填,`"private"` \| `"public"`;决定 schema 缺省回落方向,也影响未登录断言等私有站行为 | +| `schema` | 可省略。取值见 `types/site.ts:11-22` 的 `SiteSchema`。缺省或取值在 `schemas/` 中无实现时按 `type` 回落(`index.ts:41-42, 93-102`) | + +若定义文件导出了 `default class`,实例化时**直接使用该类**,`schema` 字段不再决定基类(`index.ts:79-84`)。此时仍建议保留能表达意图的 `schema` 值。 + +## 地址与图标 + +| 字段 | 约束 | +| --- | --- | +| `urls` | 必填,至少一项;第一项为默认使用地址;支持 https 就用 https;敏感站点可写 rot13(`uggc://` / `uggcf://`),读取时由 `restoreSecureLink` 还原(`index.ts:53`,`utils/html.ts:49`) | +| `legacyUrls` | 历史域名。换域名时不要删 `urls` 中的旧值,而应移入此处;曾出现在 ptpp 中但未出现在 `urls` 的域名必须补进来(`types/site.ts:88-96`) | +| `favicon` | 三种写法:完整 favicon URL、`./<name>.png`(指向 `public/icons/site/`)、`data:image/...`;若 `public/icons/site/` 已有 `{id}.{png,ico,svg}` 可不写(`types/site.ts:98-106`) | +| `isDead` | 只在站点彻底死亡时写 `true`;写 `true` 后插件不再展示该站点、停止搜索与用户信息(`types/site.ts:108-115`)。临时故障不要用这个字段 | + +## 搜索相关 + +| 字段 | 说明 | +| --- | --- | +| `category` | `ISearchCategories[]`,自定义搜索方案的分类。同一站点内各分类的 `key` 不能重复;`#url` 是特殊 key,表示直接替换请求 url(`types/search.ts:202-248`) | +| `requestDelay` | 站点全局请求延迟(毫秒)。该值作用于 `AbstractBittorrentSite.request`;若同时定义了 `search` / `searchEntry[*]` / `userInfo.process[*]` 的 `requestDelay`,两者**叠加**(`types/site.ts:119-124`) | +| `officialGroupPattern` | `TPattern[]`;搜索标题命中时,用户开启对应功能后会打上「官方」标签(`types/site.ts:126-130`) | +| `search` | `ISearchConfig`,见 [selectors-and-filters.md](selectors-and-filters.md) | +| `searchEntry` | `Record<string, ISearchEntryRequestConfig>`;仅在「站点有多个搜索入口」或「单次搜索需多页」时定义(`types/site.ts:139-144`) | +| `list` | 种子列表页(content-script 用)。`urlPattern` 不写时会由 `search.requestConfig.url` 与各 `searchEntry[*].requestConfig.url` 自动生成(`types/site.ts:155-200`) | +| `detail` | 种子详情页。`urlPattern` **无法自动生成**,需显式声明或依赖 schema(`types/site.ts:207-242`) | +| `download` | `requestConfig` 在 `getTorrentDownloadRequestConfig`(`schemas/AbstractBittorrentSite.ts:784-790`)中以 `toMerged({ baseURL: this.url, url: <下载链接>, method: "GET", timeout }, download.requestConfig)` 合并(`types/site.ts:246-248` 的注释写的是简化的 `{ url: torrent.link, method: "get" }`,与实现略有出入);`interval`(秒)会提升为 `ISiteUserConfig.downloadInterval`(`types/site.ts:252-257`) | + +## 未登录判定(private 站点) + +`noLoginAssert` 仅对基于私有站模板且未改写 `loggedCheck` 的站点生效;每一项都可单独置 `false` 禁用,但不支持整体禁用(`types/site.ts:260-299`): + +| 项 | 默认值(`AbstractPrivateSite.ts:31-48, 83-101`) | +| --- | --- | +| `httpStatusCodes` | `[401, 403, 502, 504]` | +| `urlPatterns` | `[/doLogin|login|verify|checkpoint|returnto/gi]` | +| `refreshHeaderPattern` | 缺省等于 `urlPatterns`;`urlPatterns` 为 `false` 时该项也为 `false` | +| `matchSelectors` | 缺省空;命中即判定未登录(Document 用 Sizzle,JSON 用 `has`) | +| `checkResponseContent` | 缺省 `false`;开启后响应为空、或长度 < 800 且含 `login`/`auth_form`/`not authorized` 时判定未登录 | + +## 用户信息(private 站点) + +`userInfo` 仅对基于私有站模板且未改写 `getUserInfoResult` 的站点生效(`types/site.ts:301-371`): + +| 字段 | 说明 | +| --- | --- | +| `pickLast` | 从上次缓存结果中直接继承的字段,用于减少请求 | +| `process` | 有序步骤数组;**第一步不应有 `assertion`**,后续步骤可用。每步可含 `requestConfig`、`assertion`、`requestConfigTransformer`、`requestDelay`、`fields`、`selectors` | +| `process[*].fields` / `selectors` | 共同决定该步骤要取哪些字段:实际字段为 `[...fields, ...Object.keys(selectors)]`(`AbstractPrivateSite.ts:133-136`);字段已存在则跳过该步骤 | +| `process[*].assertion` | `{ 字段: 'url' \| 'params.uid' \| ... }`;值为 `url` 时在 url 中替换 `$字段$`,否则按路径 set 进请求配置;缺字段会抛错(`AbstractPrivateSite.ts:148-165`) | +| `requestDelay` | process 未定义时的回落值 | +| `donorConfig` | 捐赠者(黄星)特权:`isAccountKept`、`bonusPerHourMultiplier`(能直接用 selector 选出正确时魔时该系数应为 1) | +| `selectors` | 所有步骤共用的字段选择器 | + +约定:**schema 用 `fields` 声明默认能取的字段,definition 用 `userInfo.selectors` 覆写具体选择器**,这样站点侧改版只需改选择器(`types/site.ts:344-352`)。 + +## 用户等级 + +`levelRequirements: ILevelRequirement[]`(类型见 `types/userinfo.ts:73-91`): + +- `id` 为递增等级序列,不可重复;`name` 需与解析出的 `levelName` 一致,可用 `nameAka` 补充别名(i18n 或站点改名场景)。 +- `groupType` 缺省 `"user"`;`vip` 为 100-199、`manager` 为 200-299(常量见 `utils/level.ts:11-12`)。 +- `isKept: true` 表示该等级符合保号要求;**大于该等级的 user 组别都要声明**,否则默认视为不符合保级要求;vip/manager 组别不需要该字段。 +- 需求项支持 `isoDuration`(如 `P5W`)、`TSize`(如 `"200GB"`)与数值;`ratio` 支持 `[min, max]` 区间。可选要求写在 `alternative` 中。 +- **不要**写 `bonusNeededInterval` / `passTime` 这类由 `levelRequirementUnMet` 计算出的派生字段(`types/userinfo.ts:44-57`)。 + +## 需要用户额外输入 + +`userInputSettingMeta: ISiteUserInputMeta[]`:每项 `{ name, label, hint, required }`,`name` 是存入 `userConfig.inputSetting` 的键(`types/site.ts:26-31, 378-382`)。典型用途是需要 API key 或额外 ID 的站点。 + +## 与用户配置的边界 + +`ISiteUserConfig`(`types/site.ts:392-454`)是**用户侧**数据(url、超时、下载间隔、分组、`inputSetting`、`merge` 等),不是站点定义该写的内容。定义文件唯一能"提升"到用户配置的是 `download.interval`。用户可通过 `merge` 覆盖 metadata 任意字段(例如自定义 `levelRequirements` 或启停某个 `searchEntry`)。 diff --git a/.dsh/skills/ptd-site-definition/references/selectors-and-filters.md b/.dsh/skills/ptd-site-definition/references/selectors-and-filters.md new file mode 100644 index 000000000..78fccfb5b --- /dev/null +++ b/.dsh/skills/ptd-site-definition/references/selectors-and-filters.md @@ -0,0 +1,343 @@ +# 选择器、filters 与搜索配置 + +写站点定义时绝大多数工作都落在这一层。权威源码:`types/search.ts`(类型与语义注释)、`utils/filter.ts`(可用 filters)、`schemas/AbstractBittorrentSite.ts`(实际解释逻辑)。 + +## IElementQuery:单字段如何取值 + +一个字段就是一个 `IElementQuery` 对象(`types/search.ts:250-293`)。解释逻辑全在 `AbstractBittorrentSite.getFieldData`(`schemas/AbstractBittorrentSite.ts:366-444`),核心事实是:**`elementProcess` / `case` / `data` / `attr` / 文本 这五种取值方式是互斥的**(源码是 `else if` 链,383-399),命中一种就不再往下走;而 `selector` 数组是"多候选"(依次尝试)。 + +### 取值流程 + +```text +selector 数组依次尝试 ──► 命中元素(":self" = 当前元素 / JSON 根) + │ + ├─ elementProcess 存在 → 它的返回值(返回 undefined 视为未命中,继续下一个 selector) + ├─ case 存在 → 元素自身匹配到的第一个 CSS 选择器所对应的值 + ├─ data 存在 → element.dataset[data] (即 data-* 属性) + ├─ attr 存在 → element.getAttribute(attr) + └─ 都没有 → innerText ?? textContent(换行替换为空格) + + ├─ 有 selector 命中:字符串 trim() → 应用 switchFilters[命中的 selector],否则 filters + └─ 全部未命中:直接使用 text 回落值(**不经过 filters**) + + └─ 最后归一化:整体匹配 /^-?\d+$/ 的字符串 → number;NaN → 0 +``` + +### 什么时候用哪一种 + +| 方式 | 适用场景 | 取到的值 | 注意 | +| --- | --- | --- | --- | +| `elementProcess(el)` | 一个字段需要**组合多个来源、清洗或条件判断**:标题优先取 `title` 属性再回落到文本;状态要同时看 class 与进度 | 函数返回值,类型不限(string / number / 枚举) | 返回 `undefined` 会被当作"该选择器未命中",继续尝试数组里的下一个;返回 `null` 会走到 `text` 回落 | +| `case: { … }` | 字段值是**固定枚举**,由元素自身特征决定(class、属性),不需要处理文本 | `case` 里第一个命中键对应的值(类型自由,字符串仍会在末尾被 `trim()`) | 键是 CSS 选择器,用 `Sizzle.matchesSelector` 匹配**选中的那个元素自身**(不是它的子元素)。值建议直接写 `ETorrentStatus.*` | +| `data: "cat"` | 值挂在 `data-*` 上(现代前端最常见) | `element.dataset.cat`,即 `data-cat="…"` | 属性名转驼峰:`data-category-id` → `data: "categoryId"` | +| `attr: "href"` | 值在普通属性上:`href`、`title`、`style`、`id` | `getAttribute()` 的字符串 | 名为 `url` / `link` 的字段,相对链接会被基类自动补全(`schemas/AbstractBittorrentSite.ts:592-593`);其它字段不会。**不要在 filters 里调用 `fixLink`**(315-320 的 `@warning`) | +| 都不写 | 值就是元素文本 | `innerText ?? textContent`,换行→空格,随后 `trim()` | 取的是**整个子树**的文本:`<a title="X"><b>X</b><span>中字</span></a>` 会得到 `"X 中字"`。要把这类混入的元素剔除,正是用 `elementProcess` 的典型场景(`schemas/NexusPHP.ts:386-390` 的 `subTitleRemoveExtraElement`) | + +除此之外还有两个"结构级"字段: + +- `selector`:`string` 时是 CSS 选择器;`string[]` 时**依次尝试**,第一个取到非 `undefined` 的生效(`AbstractBittorrentSite.ts:372-411`)。写多个候选是为了兼容站点改版,例如 NexusPHP 的标题选择器列了 4 个(`schemas/NexusPHP.ts:77-84`)。 +- `filters`:在取值**之后**统一处理(413-426)。`switchFilters` 按"最终命中的那个 selector"选 filters,优先级高于 `filters`;`selector` 是数组时常用它区分结构不同的行(例如 Gazelle 的 `subTitle`,`schemas/Gazelle.ts:231-239`)。 +- `text`:所有 selector 都没命中时的回落值;约定 `"N/A"` 表示源站不提供该信息(`types/search.ts:254-256`)。 + +### HTML 示例 + +下面是一段 NexusPHP 风格的种子行(**示意结构**,各选择器取自本仓库的真实配置),上下文就是 `search.selectors.rows` 选出的这一个 `<tr>`: + +```html +<table class="torrents"> + <tbody> + <tr id="12345" data-cat="Movies"> + <td class="rowfollow"> + <a href="details.php?id=12345&hit=1" title="Some.Movie.2024.1080p"><b>Some.Movie.2024.1080p</b><span class="optiontag">中字</span></a> + </td> + <td class="rowfollow"><a href="download.php?id=12345&passkey=abc"><img alt="download"></a></td> + <td class="rowfollow"><div class="progressseeding" style="width:100%"></div></td> + <td class="rowfollow">4.37 GB</td> + </tr> + </tbody> +</table> +``` + +同一行上五种取法的写法与结果: + +```ts +selectors: { + // attr + filters:链接在 href 上,再从中抽出 id + id: { + selector: 'a[href*="download.php?id="]', // NexusPHP.ts:86-89 + attr: "href", // → "download.php?id=12345&passkey=abc" + filters: [{ name: "querystring", args: ["id"] }], // → "12345" + }, + + // elementProcess:title 属性比 innerText 干净,且要防止取到空值 + title: { + selector: ["a[href^='details.php?id='][title]"], // NexusPHP.ts:77-84 + elementProcess: (el) => (el.getAttribute("title") || el.textContent || "").trim(), + // → "Some.Movie.2024.1080p" + // 若只取文本(不写 elementProcess),会得到 "Some.Movie.2024.1080p 中字" + }, + + // case:状态由元素自身的 class 决定,不需要读文本 + status: { + text: ETorrentStatus.unknown, + selector: ['div[class^="progress"]'], + case: { + ".progressseeding": ETorrentStatus.seeding, // → ETorrentStatus.seeding + ".progressdownloading": ETorrentStatus.downloading, + ".progressfinished": ETorrentStatus.completed, + }, + }, // 写法参考 definitions/hdsky.ts:311-320 + + // 默认文本:取第 4 个 td 的文本 + size: { selector: "> td:nth-child(4)", filters: [{ name: "parseSize" }] }, // "4.37 GB" → 对应字节数 + + // data:读当前元素(:self)的 data-cat + category: { selector: ":self", data: "cat" }, // → "Movies"(写法参考 definitions/animebytes.ts:267) +} +``` + +几个容易错的点: + +- `:self` 表示"当前上下文元素本身"。在 `rows` 里就是这一行;在 `userInfo` 步骤里是该步骤的响应文档;在 `detail` 里是整个 `Document`(因此在 `detail.selectors` 里写 `:self` 通常没有意义,会拿到整页文本);在 JSON 里是当前对象/根(见下节)。 +- `case` 的键匹配的是**选中元素自身**:`selector: 'div[class^="progress"]'` 配 `case: { ".progressseeding": … }` 能命中;写成 `.progressseeding span` 就永远不命中。 +- 文本取值会把换行变成空格(397-399),所以 `<td>` 里的多行内容不会带 `\n`。 +- 字符串会 `trim()`,且**整体匹配 `/^-?\d+$/` 时被转成 number**(433-441):`0012` → `12`。需要保留前导零时用 `text` 或自定义 filter 兜住。这一步在**所有**取值方式之后执行,`case`/`data`/`attr` 取到的字符串同样会被 trim。 +- `text` 回落值**不经过 filters**(filters 只在"有 selector 命中"时应用,413-426),所以别指望用 filter 处理回落值。 + +### HTML 下 `rows.merge`:一个种子占多行时 + +有些站(尤其公开 BT 站)把**一个种子的信息拆在连续的多行 `<tr>` 里**:第一行放标题与下载链接,第二行放评论数、发布日期、体积、发布者。如果直接按行解析,每个 `<tr>` 都会被当成一个独立种子,结果是同一个种子出现两条残缺记录。 + +`rows.merge: N` 就是为这种情况准备的:**把选中的行按位置每 N 行合并成"一行"**,再交给行解析。 + +源码行为(`schemas/AbstractBittorrentSite.ts:514-536`): + +```ts +// 只在 Document 响应、且没有 rows.filter 时走到这里 +const rowMergeDeep = rowsSelector.merge || 1; // 默认 1,即不合并 +if (trs.length > 0 && rowMergeDeep > 1) { + const newTrs: Element[] = []; + chunk(trs, rowMergeDeep).forEach((chunkTr) => { // 按位置切组 + const wrapperDiv = doc.createElement("div"); // 每组包进一个新建的 <div> + chunkTr.forEach((tr) => wrapperDiv.appendChild(tr)); + newTrs.push(wrapperDiv); + }); + trs = newTrs; +} +``` + +三个前置条件(缺一不生效): + +1. **只在 HTML(Document)响应下生效**:JSON 分支没有这段逻辑(537-541),`merge` 对接口站无意义。 +2. **只在没有定义 `rows.filter` 时生效**:`filter` 优先,写了 `filter` 就直接走它,`merge` 被完全忽略(515-517 与 522-535 是 if/else 的两支)。 +3. **值必须大于 1**:不写或写 `1` 等于不合并(522)。 + +合并之后,行内子选择器的上下文变成了那个包装 `<div>`,于是有两种写法: + +- **不区分行**:直接写 `td.desc-bot` 这类选择器,Sizzle 会在整组里取第一个匹配的元素。真实用例 `definitions/tokyotosho.ts:60`(`rows: { selector: "table.listing tr.category_0", merge: 2 }`,其余子选择器仍是不带前缀的 `td.desc-top` / `td.desc-bot` / `td.stats`)。 +- **区分第几行**:`tr:nth-child(1) xxx` / `tr:nth-child(2) xxx`——源码注释里明确推荐这种写法(518-521),组内出现同名 class 时用它精确定位。 + +HTML 示例(两行一个种子): + +```html +<table class="listing"> + <tbody> + <tr class="category_0"><!-- 第 1 行 --> + <td class="desc-top"><a href="details.php?id=1" type="application/x-bittorrent">Some Thing</a></td> + </tr> + <tr class="category_0"><!-- 第 2 行 --> + <td class="desc-bot">Comment: 不错 | Date: 2025-01-02 03:04 UTC | Size: 1.2 GB | Submitter: alice</td> + </tr> + </tbody> +</table> +``` + +```ts +selectors: { + rows: { selector: "table.listing tr.category_0", merge: 2 }, // 每 2 行合并为一行 + title: { selector: "td.desc-top a" }, // 自动落在第 1 行 + size: { + selector: "tr:nth-child(2) td.desc-bot", // 明确取第 2 行 + filters: [{ name: "parseSize" }], + }, +} +``` + +注意事项: + +- 分组是**纯位置配对**,不会校验这 N 行是否真的属于同一种子。因此 `rows.selector` 必须选到每个种子的**全部行**(而不是只选每个种子的第一行),否则会把不同种子的行配成一组;若选出的行数又不是 N 的整数倍,最后一组是残缺组(`chunk` 保留余数),该行通常只能解析出部分字段甚至空值。行与行之间存在更强的对应关系(行号、隐藏字段、成对标记等)时,用 `rows.filter` 自行分组更可靠,也更易读。 +- `appendChild` 是**移动**而不是复制,原本的表格结构会被改动。搜索场景下 `doc` 来自请求响应或文档副本,没有副作用;列表页场景调用方传的也是副本(`entries/content-script/app/pages/SiteListPage.vue:27` 用 `document.cloneNode(true)`),不会影响用户正在看的页面。 +- 列表页同样支持:`list[].selectors.rows` 最终走的是同一个 `transformSearchPage`(`schemas/AbstractBittorrentSite.ts:651-706`)。 +- `merge` 与 `filter` 都只能二选一,且都只作用于 `rows`;`types/search.ts:116-126` 的注释里也是这样约定的。 + +### 响应是 JSON 时怎么处理 + +当请求配置里写了 `responseType: "json"`(`search.requestConfig`、`detail.requestConfig`、`userInfo.process[n].requestConfig` 任一处),传入 `getFieldData` 的就是普通对象/数组而不是 `Document`,走的是另一条分支(`AbstractBittorrentSite.ts:401-403`): + +```ts +// JSON 分支的实际逻辑 +query = usedSelector === ":self" ? element : get(element, usedSelector); +``` + +规则: + +1. **`selector` 变成取值路径**,用 `es-toolkit/compat` 的 `get` 解析,支持点号与下标:`response.userstats.uploaded`、`data.0.attributes.name`。 +2. **`elementProcess` / `case` / `data` / `attr` 全部失效**(它们在 `element instanceof Node` 分支里),写了不会被调用——JSON 站点不要写这些字段。 +3. **`filters` / `switchFilters` 仍然生效**(414-426 在分支之外),`parseTime`、`parseSize`、`parseNumber` 等照常用。 +4. **`:self` = 当前对象本身**:在 `rows` 里表示"响应根",在行内表示"这一行的对象"。 +5. 路径取不到 → `undefined` → 继续尝试 `selector` 数组里的下一个;全部未命中 → 回落 `text ?? ""`(同样不经过 filters)。 +6. 取值后的归一化与 HTML 分支完全一致(字符串 trim、纯整数转 number、NaN → 0)。 + +示例(GazelleJSONAPI 的真实选择器,`schemas/GazelleJSONAPI.ts:239-296`)——响应: + +```json +{ + "status": "success", + "response": { + "id": 9527, + "username": "alice", + "notifications": { "messages": 3 }, + "userstats": { "uploaded": 123456789, "ratio": 1.5, "class": "Power User" }, + "stats": { "joinedDate": "2021-01-01 12:00:00" } + } +} +``` + +配置: + +```ts +userInfo: { + selectors: { + id: { selector: ["response.id"] }, // → 9527 + name: { selector: ["response.username"] }, // → "alice" + messageCount: { selector: ["response.notifications.messages"] }, // → 3 + uploaded: { selector: ["response.userstats.uploaded"] }, // → 123456789 + levelName: { selector: ["response.userstats.class"] }, // → "Power User" + joinTime: { + selector: ["response.stats.joinedDate"], + filters: [{ name: "parseTime" }], // → 毫秒时间戳 + }, + }, +} +``` + +**JSON 下的 `rows`**:`search.selectors.rows.selector` 同样按路径解析(`AbstractBittorrentSite.ts:478-494`、512),所以: + +```ts +search: { + requestConfig: { url: "/api/v1/torrents", responseType: "json" }, + selectors: { + rows: { selector: ":self" }, // 响应本身就是 [{...}, {...}] 时用 ":self" + id: { selector: "id" }, + title: { selector: "name" }, + link: { selector: "downloadUrl" }, + }, +} +``` + +- 响应被包了一层(如 `{ "torrents": [ … ] }`)时,`:self` 会选到那个**对象**而不是数组:对象没有 `length`,随后的 `for…of` 会抛错,整次搜索以 `parseError` 结束。此时必须改成 `rows: { selector: "torrents" }`(Rartracker 的文档里专门记了这条:`references/engines/rartracker.md` 的常见坑 3)。 +- 行内字段用**相对路径**(`id`、`name`、`attributes.name`),不是 CSS 选择器。 +- `rows.filter` 在 JSON 下同样生效(538-540);`rows.merge` 只在 Document 响应里实现,JSON 站点写了也不会被处理——多行合并的完整语义见上文「HTML 下 `rows.merge`」。 + +两个高频故障: + +- **忘记 `responseType: "json"`**:`doc` 是字符串,所有 `get()` 都取不到值,字段全空且不报错。接口站排查时先确认这一项。 +- **照抄 HTML 选择器**:`selector: "a.title"` 在 JSON 下会被当成"取对象的 `a.title` 属性路径",结果是 `undefined`。 + +## 可用 filters + +`utils/filter.ts:20-205` 定义,`runQueryFilters` 支持 `{ name, args }` 对象形式与直接函数(`AbstractBittorrentSite.ts:446-460`): + +| filter | args | 作用 | +| --- | --- | --- | +| `querystring` | `["id"]` 可多个 | 从 URL 取查询参数,返回第一个命中的值 | +| `prepend` / `append` | `["xxx"]` | 前置/后置拼接 | +| `replace` | `["pattern", "replacement"]` | 字符串或正则替换 | +| `split` | `[",", 1]` | 按分隔符切分取第 n 段 | +| `trim` / `toLower` / `toUpper` | | 常规字符串处理 | +| `parseNumber` | | 从含杂字符的文本中抽数字 | +| `parseSize` | | 抽取体积并转为 Byte 数 | +| `parseTime` | `["yyyy-MM-dd HH:mm:ss"]` | 按指定格式解析时间戳(毫秒) | +| `parseTTL` | | 解析 `1天2小时前` / `2 months ago` 类相对时间 | +| `parseDuration` | | 解析为秒数 | +| `parseFuzzyTime` | 可选格式 | 先按格式解析,失败再按 TTL 解析 | +| `extImdbId` / `extDoubanId` / `extAnidbId` / `extBangumiId` / `extTvmazeId` | | 抽取外部资源 id,写入 `ext_*` 字段 | +| `dump` | | 打印当前值并原样返回,仅用于调试 | + +自定义处理直接写函数即可(`filters: [(v) => ...]`)。时间字段建议优先用 `parseFuzzyTime`,站点改版时更稳。 + +## 种子字段 + +`selectors` 的键对应 `ITorrent` 字段(`types/torrent.ts:19-53`),常用:`id`、`title`、`subTitle`、`url`(详情页)、`link`(**下载链接**)、`time`、`size`、`seeders`、`leechers`、`completed`、`comments`、`category`、`progress`、`status`、`tags`。 + +最容易写反的一对:`url` 是详情页链接,`link` 是种子下载链接(`types/torrent.ts:26-34`)。解析时若 `link` 缺失,插件会走 `getTorrentDownloadLink` 补全(见下)。 + +`tags` 的写法是数组,每项 `{ name, selector, color? }`(`types/search.ts:133-141`);name 命中 `utils/tags.ts:13-55` 的预定义名或其 aka(不区分大小写)时,会被替换成统一的 name 与颜色,`color` 取 Vuetify 调色板名。 + +## search 配置 + +```ts +search: { + requestConfig: { url: "/torrents.php", params: { ... } }, + keywordPath: "params.search", // 缺省 params.keywords(AbstractBittorrentSite.ts:267) + advanceKeywordParams: { imdb: { requestConfig: { url: "/torrents.php", params: { imdb: "tt" } } } }, + selectors: { + rows: { selector: "table.torrents > tbody > tr", merge: 2 }, + title: { selector: "a.torrent-name" }, + link: { selector: "a[href*='download']", attr: "href" }, + }, +} +``` + +- 请求配置的生成顺序见 `types/search.ts:20-44` 的注释:默认垫片(`AbstractBittorrentSite.ts:120-123` 补 `baseURL`/`url`/`timeout`)→ `searchEntry` 合并 → `search.requestConfig` → 高级搜索词 → 关键词 → `requestConfigTransformer`。中间每一步都会**覆盖**前面同名字段。 +- `rows.filter` 可对取到的行集合做自定义处理;`rows.merge` 用于"多行表示一个种子"的情况,仅在 Document 响应下生效(`types/search.ts:116-126`,`AbstractBittorrentSite.ts:502-543`)。 +- 高级搜索词形如 `imdb|tt17097088`。`imdb` 未声明时视为启用,其他类型默认禁用(`types/search.ts:102-113`)。 +- `skipNonLatinCharacters`:站点只支持拉丁字符查询时置 `true`;`skipWhiteSpacePlaceholder`:跳过纯空白查询(`types/search.ts:60-88`)。 +- POST 请求如需表单编码,记得显式设置 `Content-Type`(`types/search.ts:37-42`)。 + +## category(搜索分类) + +```ts +category: [{ + name: "类别", + key: "cat", + options: [{ value: 401, name: "Movies/电影" }], + cross: { mode: "append" }, // 允许多选时的拼装方式 +}] +``` + +`cross.mode` 的语义表见 `types/search.ts:222-241`:`brackets` → `cat[]=v1&cat[]=v2`、`comma` → `cat=v1,v2`、`append` → `cat4010=1&cat4011=1`、`appendQuote` → `cat[4010]=1&cat[4011]=1`;`cross.key` 可覆盖外层 key。特殊 key `#url` 会直接替换请求 url。 + +`utils/helper.ts:17-61` 提供 `buildCategoryOptionsFromList` / `buildCategoryOptionsFromDict` 生成 options,避免手写长列表。 + +## searchEntry(多搜索入口) + +`searchEntry` 是 `Record<id, ISearchEntryRequestConfig>`,用于"站点有多个搜索入口且结果不同"或"单次搜索多页"(`types/site.ts:139-144`)。合并规则见 `AbstractBittorrentSite.ts:191-198`:先按 `id` 合并 `metadata.searchEntry[id]`,再(若 `merge !== false`)与 `metadata.search` 合并;`enabled: false` 的入口会被跳过(`AbstractBittorrentSite.ts:201`)。 + +## list(种子列表页)与 detail(详情页) + +- `list[].urlPattern` 不写时会由 `search.requestConfig.url` 与各 `searchEntry[*].requestConfig.url` 自动生成;`excludeUrlPattern` 用于排除被误判为列表页的详情页(`types/site.ts:155-200`)。 +- `list[].selectors` 缺省以 `search.selectors` 为垫片;对 API 型站点要显式写 `mergeSearchSelectors: false`(`types/site.ts:179-183`)。 +- `list[].selectors.keywords` 用于回填页面上的搜索词;未设置时会按 `keywordPath` 推断(`params.x` → `input[name="x"]`,`data.x` → `form[method="post" i] input[name="x"]`),再退化为从 URL 的 `search`/`keywords`/`keyword`/`q` 参数中读取(`types/site.ts:191-197`,`AbstractBittorrentSite.ts:687-695`)。 +- `detail.urlPattern` **必须显式声明**(schema 通常已给)。解析时基类有兜底:未定义 `url` → 取 `doc.URL`;未定义 `id` → 从 URL 的 `tid`/`id` 参数推断,再退化为整个 url;未定义 `title` → `html > body > title`(`AbstractBittorrentSite.ts:716-748`)。 + +## 下载链接 + +`getTorrentDownloadLink`(`AbstractBittorrentSite.ts:764-778`)的逻辑:搜索页已解析出 `link` 就直接用;否则若配置了 `detail.selectors.link`,会请求 `detail.requestConfig`(默认 `{ responseType: "document", url: torrent.url }`)后从详情页取 `link`;最后追加用户配置的 `downloadLinkAppendix`。需要更复杂的下载逻辑(如动态 token、限时签名)时才覆写该方法或其 `getTorrentDownloadRequestConfig`(`AbstractBittorrentSite.ts:784-790`,示例见 `definitions/hdsky.ts:337-370`)。 + +## userInfo 的取字段流程 + +`AbstractPrivateSite.getUserInfoResult`(`AbstractPrivateSite.ts:113-215`)按 `process` 顺序执行,每一步: + +1. 该步字段 = `fields ∪ keys(selectors)`;若这些字段在已有结果中**全部存在**则跳过该步(`AbstractPrivateSite.ts:133-141`)。 +2. 处理 `assertion`:url 中替换 `$字段$`,其余路径 `set` 进请求配置;字段缺失直接抛错(`AbstractPrivateSite.ts:148-165`)。 +3. 跑 `requestConfigTransformer`,等待 `requestDelay`,发请求(`AbstractPrivateSite.ts:168-175`)。 +4. 逐字段解析:若子类存在 `parseUserInfoFor<字段名>`(PascalCase)方法则调用它,否则用该步 `selectors[字段]`,再回落到 `userInfo.selectors[字段]`(`AbstractPrivateSite.ts:177-191`)。**这就是定义文件里既能写选择器、也能写 `public parseUserInfoForXxx()` 的原因。** + +`levelName` 拿到但 `levelId` 缺失时,会用 `levelRequirements` 推断(`AbstractPrivateSite.ts:194-197`,`utils/level.ts:300`)。 + +## 调试用法 + +- `filters: [{ name: "dump" }]`:把中间值打到控制台。 +- `requestConfigTransformer: (config) => { console.log(config); return config; }`:打印最终请求配置(`types/search.ts:51-58`)。 +- 运行时 `getSearchResult` 会打印站点名与关键词(`AbstractBittorrentSite.ts:175`)。 diff --git a/.dsh/skills/ptd-site-definition/references/workflow.md b/.dsh/skills/ptd-site-definition/references/workflow.md new file mode 100644 index 000000000..2ac682966 --- /dev/null +++ b/.dsh/skills/ptd-site-definition/references/workflow.md @@ -0,0 +1,127 @@ +# 新增 / 修改站点定义的完整流程 + +## 0. 前置确认 + +- **站点是否已被支持**:查 `src/packages/site/definitions/` 是否有同名文件;注意域名可能已在别的文件的 `legacyUrls` 里(换域名场景)。已支持就改为修改既有文件,不要新建。 +- **站点是否还活着**:彻底死亡才写 `isDead: true`。 +- **是不是"换域名"**:旧域名要从 `urls` 移到 `legacyUrls`,不要删除。 + +查重命令: + +```bash +ls src/packages/site/definitions/ | grep -i <关键词> +grep -rn "uggcf://\|https://" src/packages/site/definitions/ | grep -i <域名> +``` + +## 1. 收集事实(工作量的主要来源) + +| 要写进 metadata 的内容 | 从哪里拿 | +| --- | --- | +| `id` / `name` / `aka` | 站点自身;Jackett 同名站点配置可作命名参考 | +| `type` | PT 私有站 = `private`;公开 BT 站 = `public` | +| `urls` / `legacyUrls` | 站点实际地址;旧地址查历史 | +| `schema` | 见第 2 步 | +| `timezoneOffset` | 站点显示时间的时区;不写则按引擎缺省 | +| `favicon` | 站点 favicon URL;或往 `public/icons/site/` 放 `{id}.png` | +| `category` | 搜索页的分类下拉/表单(value 必须是站点真实值) | +| `officialGroupPattern` | 站点的官组命名规则(标题里的组名特征) | +| `levelRequirements` | 站点规则页/等级页;含各等级的门槛与保号要求 | +| `userInputSettingMeta` | 需要用户额外提供的值(API key、uid 等) | +| `noLoginAssert` | 未登录时站点返回的状态码/跳转/特征串 | +| `search` / `userInfo` | 搜索接口与个人页的 DOM 或 API 响应 | + +**拿不到的值不要猜**:留空或使用 schema 默认值,并在交付说明里列出"待确认项"。编造 category value 或选择器会让站点静默失效,比不做更糟。 + +若站点信息来自 PT-Plugin-Plus 的既有配置,按仓库惯例在文件头标注来源(参考 `definitions/cinemageddon.ts:1-4`、`definitions/hdroute.ts:1-3` 的 `@PTPPDefinitions` 注释)。 + +## 2. 判定引擎 + +`schema` 的合法取值见 `types/site.ts:11-22`。判定方法: + +```bash +# 看同引擎的既有站点都长什么样 +grep -rln 'schema: "<Engine>"' src/packages/site/definitions/ | head +``` + +各引擎的可观测判定特征写在各引擎文档的「判定站点是否属于该引擎」一节,先读它再动手。判定不出来时用 `type` + 基类(private → `AbstractPrivateSite`,public → `AbstractBittorrentSite`)也可以先跑起来,但会失去引擎提供的默认选择器与用户信息流程,通常不是终点。 + +## 3. 选骨架 + +挑一个**同引擎、同类型、结构最接近**的既有定义作为起点(同语种站点更好),然后: + +- 展开引擎的默认 metadata:`...SchemaMetadata`(若该 schema 导出了它,例如 `definitions/hdsky.ts:122`)。 +- 只写与默认值不同的部分。整段复制默认值会让后续引擎升级难以追踪。 +- 只有需要覆写行为时才 `export default class X extends <Schema> { ... }`(`index.ts:79-84`)。 + +## 4. 写文件 + +路径:`src/packages/site/definitions/<id>.ts`,文件名必须等于 `id`。 + +最小骨架(private + 引擎模板): + +```ts +import type { ISiteMetadata } from "../types"; +import NexusPHP, { SchemaMetadata } from "../schemas/NexusPHP.ts"; + +export const siteMetadata: ISiteMetadata = { + ...SchemaMetadata, // 必须显式展开:引擎默认值不会自动合并(index.ts:100-101) + + version: 1, // 跟随同引擎既有文件取值,仓库现状多为 1 + id: "example", + name: "Example", + aka: ["EX"], + description: "一句话说明", + tags: ["影视"], + + type: "private", + schema: "NexusPHP", + urls: ["https://example.com/"], + + category: [ /* ... */ ], + // search / userInfo / levelRequirements 只写与引擎默认不同的部分 +}; + +// 需要覆写引擎行为时才写 +export default class Example extends NexusPHP {} +``` + +建议按既有定义的字段顺序排列(id/name/aka/description/tags → type/schema/urls → category → search → detail → userInfo → levelRequirements),diff 更易读。 + +## 5. 校验 + +```bash +# 1) 静态结构检查(本 skill 自带,无依赖) +node .dsh/skills/ptd-site-definition/scripts/check-site-definition.mjs src/packages/site/definitions/<id>.ts + +# 2) 类型检查(定义文件在 tsconfig include 内,错误会在此暴露) +pnpm check + +# 3) 格式化 +pnpm format +``` + +`pnpm check` 是 `vue-tsc --noEmit`(根 `package.json:31`)。有覆写类方法时,类型检查能抓出签名不匹配、缺 `override` 等问题。 + +## 6. 真机验证 + +```bash +pnpm dev # 开发模式,产物输出到 dist-chrome(vite.config.ts:48) +pnpm build:dist # Chrome 构建,同样输出 dist-chrome +pnpm build:dist-firefox # Firefox 构建,输出 dist-firefox +``` + +在浏览器「加载已解压的扩展程序」中指向对应输出目录,然后至少验证: + +1. 站点能正常添加、URL 可用; +2. 搜索返回结果,标题/大小/时间/做种数等字段解析正确(不是空值或错位); +3. 种子下载链接可用(不是 404 或未登录页); +4. 私有站能取到用户信息与等级; +5. 控制台无解析异常(搜索入口会打印站点名与关键词,见 `AbstractBittorrentSite.ts:175`)。 + +排查手段见 [debugging.md](debugging.md)。 + +## 7. 交付 + +- 提交信息沿用仓库习惯:新增站点 `feat(pkg/site): 适配<站点名>站点 (#PR)`,单站点修复 `fix(pkg/site/<id>): <说明> (#PR)`。 +- 在交付说明中写明:判定为哪个引擎、依据是什么、哪些值来自真实站点、哪些是假设或待确认。 +- 只提交定义文件相关的改动;不要顺带格式化无关文件或提交构建产物(`dist-*` 已在 `.gitignore` 中)。 diff --git a/.dsh/skills/ptd-site-definition/scripts/check-site-definition.mjs b/.dsh/skills/ptd-site-definition/scripts/check-site-definition.mjs new file mode 100644 index 000000000..1e2552c46 --- /dev/null +++ b/.dsh/skills/ptd-site-definition/scripts/check-site-definition.mjs @@ -0,0 +1,542 @@ +#!/usr/bin/env node +/** + * PT-depiler 站点定义静态检查(无第三方依赖) + * + * 用法: + * node .dsh/skills/ptd-site-definition/scripts/check-site-definition.mjs # 检查全部 + * node .dsh/skills/ptd-site-definition/scripts/check-site-definition.mjs src/packages/site/definitions/hdsky.ts + * + * 只做结构层面的静态检查,不发起网络请求,也不能替代 `pnpm check` 与真机验证。 + * 规则来源:src/packages/site/index.ts、types/site.ts、types/base.ts。 + */ +import { existsSync, readFileSync, readdirSync } from "node:fs"; +import { basename, join, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; + +const ROOT = resolve(fileURLToPath(new URL("../../../../", import.meta.url))); +const DEFINITIONS_DIR = join(ROOT, "src/packages/site/definitions"); +const SCHEMAS_DIR = join(ROOT, "src/packages/site/schemas"); + +/** 模板类固定版本的引擎(types/site.ts:46-54) */ +const ENGINE_VERSION_RULE = { + AbstractBittorrentSite: -1, + AbstractPrivateSite: -1, + NexusPHP: 0, + Unit3D: 0, + Gazelle: 0, + GazelleJSONAPI: 0, + AvistazNetwork: 0, +}; + +/** definition 中 default class 对模板类的别名写法 */ +const CLASS_ALIAS = { + PrivateSite: "AbstractPrivateSite", + BittorrentSite: "AbstractBittorrentSite", +}; + +const ID_PATTERN = /^[0-9a-z]+$/; + +/* ---------------------------------- 词法扫描 --------------------------------- */ + +function skipString(text, i) { + const quote = text[i]; + i++; + while (i < text.length) { + const ch = text[i]; + if (ch === "\\") { + i += 2; + continue; + } + if (ch === quote) return i + 1; + if (quote === "`" && ch === "$" && text[i + 1] === "{") { + const end = scanBalanced(text, i + 1); + i = end < 0 ? text.length : end; + continue; + } + i++; + } + return i; +} + +/** 判断下标 i 处的 `/` 是否可能是正则字面量的起点(启发式:看前一个非空白字符) */ +function regexCanStart(text, i) { + let j = i - 1; + while (j >= 0 && /\s/.test(text[j])) j--; + if (j < 0) return true; + return "([{,:;=!&|?+-*%<>~^".includes(text[j]); +} + +/** i 指向正则起始的 `/`,返回其结束后的下标;不像正则则原样返回 i */ +function skipRegex(text, i) { + if (!regexCanStart(text, i)) return i; + let j = i + 1; + let inClass = false; + while (j < text.length) { + const ch = text[j]; + if (ch === "\\") { + j += 2; + continue; + } + if (ch === "\n") return i; // 正则不跨行,说明判断有误 + if (ch === "[") inClass = true; + else if (ch === "]") inClass = false; + else if (ch === "/" && !inClass) return j + 1; + j++; + } + return i; +} + +/** 判断“可能是注释/正则”的分支,统一在扫描器中调用 */ +function skipSlashConstruct(text, i) { + const next = text[i + 1]; + if (next === "/") { + const nl = text.indexOf("\n", i); + return { end: nl < 0 ? text.length : nl, kind: "line-comment" }; + } + if (next === "*") { + const end = text.indexOf("*/", i); + return { end: end < 0 ? text.length : end + 2, kind: "block-comment" }; + } + const regexEnd = skipRegex(text, i); + return regexEnd === i ? undefined : { end: regexEnd, kind: "regex" }; +} + +/** openIdx 指向 `{` / `[` / `(`,返回配对结束符之后的下标;未闭合返回 -1 */ +function scanBalanced(text, openIdx) { + let depth = 0; + let i = openIdx; + while (i < text.length) { + const ch = text[i]; + if (ch === '"' || ch === "'" || ch === "`") { + i = skipString(text, i); + continue; + } + if (ch === "/") { + const skipped = skipSlashConstruct(text, i); + if (skipped) { + i = skipped.end; + continue; + } + } + if (ch === "{" || ch === "[" || ch === "(") depth++; + else if (ch === "}" || ch === "]" || ch === ")") { + depth--; + if (depth === 0) return i + 1; + } + i++; + } + return -1; +} + +/** 把 from..to 之间按顶层逗号切段(忽略字符串与嵌套括号内的逗号) */ +function splitTopLevel(text, from, to) { + const segments = []; + let depth = 0; + let start = from; + let i = from; + while (i < to) { + const ch = text[i]; + if (ch === '"' || ch === "'" || ch === "`") { + i = skipString(text, i); + continue; + } + if (ch === "/") { + const skipped = skipSlashConstruct(text, i); + if (skipped) { + i = skipped.end; + continue; + } + } + if (ch === "{" || ch === "[" || ch === "(") depth++; + else if (ch === "}" || ch === "]" || ch === ")") depth--; + else if (ch === "," && depth === 0) { + if (text.slice(start, i).trim()) segments.push(text.slice(start, i)); + start = i + 1; + } + i++; + } + if (text.slice(start, to).trim()) segments.push(text.slice(start, to)); + return segments; +} + +/** 取整个字面量的内部文本(不含首尾括号),入参为字面量起始下标 */ +function literalInner(text, openIdx) { + const end = scanBalanced(text, openIdx); + if (end < 0) return undefined; + return { inner: text.slice(openIdx + 1, end - 1), end }; +} + +/** 去掉注释(字符串字面量内部原样保留) */ +function stripComments(text) { + let out = ""; + let i = 0; + while (i < text.length) { + const ch = text[i]; + if (ch === '"' || ch === "'" || ch === "`") { + const end = skipString(text, i); + out += text.slice(i, end); + i = end; + continue; + } + if (ch === "/") { + const skipped = skipSlashConstruct(text, i); + if (skipped) { + if (skipped.kind === "regex") out += text.slice(i, skipped.end); // 正则原样保留 + if (skipped.end <= i) break; + i = skipped.end; + continue; + } + } + out += ch; + i++; + } + return out; +} + +/** 把对象字面量内部文本解析为 key → 原始值文本 */ +function objectEntryMap(inner) { + const map = new Map(); + const clean = stripComments(inner); + for (const segment of splitTopLevel(clean, 0, clean.length)) { + const colon = findTopLevelColon(segment); + if (colon < 0) continue; + const rawKey = segment.slice(0, colon).trim(); + const key = rawKey.replace(/^["'`]|["'`]$/g, ""); + map.set(key, segment.slice(colon + 1).trim()); + } + return map; +} + +function findTopLevelColon(text) { + let depth = 0; + let i = 0; + while (i < text.length) { + const ch = text[i]; + if (ch === '"' || ch === "'" || ch === "`") { + i = skipString(text, i); + continue; + } + if (ch === "{" || ch === "[" || ch === "(") depth++; + else if (ch === "}" || ch === "]" || ch === ")") depth--; + else if (ch === ":" && depth === 0) return i; + i++; + } + return -1; +} + +function unescapeLiteral(text) { + return text.replace(/\\(u\{[0-9a-fA-F]+\}|u[0-9a-fA-F]{4}|x[0-9a-fA-F]{2}|.)/g, (_, esc) => { + const map = { n: "\n", r: "\r", t: "\t", b: "\b", f: "\f", v: "\v", "0": "\0" }; + if (esc[0] === "u" || esc[0] === "x") return String.fromCodePoint(parseInt(esc.replace(/^u\{?|^x|\}$/g, ""), 16)); + return map[esc] ?? esc; + }); +} + +function asString(raw) { + if (typeof raw !== "string") return undefined; + const t = stripComments(raw).trim(); + const quote = t[0]; + if ((quote !== '"' && quote !== "'") || t.at(-1) !== quote) return undefined; + return unescapeLiteral(t.slice(1, -1)); +} + +function asNumber(raw) { + if (typeof raw !== "string") return undefined; + const t = stripComments(raw).trim(); + return /^-?\d+(\.\d+)?$/.test(t) ? Number(t) : undefined; +} + +function asArrayElements(raw) { + if (typeof raw !== "string") return undefined; + const t = stripComments(raw).trim(); + if (t[0] !== "[") return undefined; + const lit = literalInner(t, 0); + if (!lit) return undefined; + return splitTopLevel(lit.inner, 0, lit.inner.length); +} + +function objectOf(raw) { + if (typeof raw !== "string") return undefined; + const t = stripComments(raw).trim(); + if (t[0] !== "{") return undefined; + const lit = literalInner(t, 0); + return lit ? objectEntryMap(lit.inner) : undefined; +} + +/** 数组元素是否为“引用的常量/展开”,这类元素不是对象字面量,属正常写法 */ +function isReferenceElement(raw) { + const t = stripComments(raw ?? "").trim(); + return !t.startsWith("{"); // 展开、标识符、函数调用等表达式一律交给运行时 +} + +/** 对象字面量是否含展开(`{ ...CategorySpstate, cross: {...} }`),其 key/name 来自被展开对象 */ +function objectHasSpread(raw) { + const t = stripComments(raw ?? "").trim(); + if (t[0] !== "{") return false; + const lit = literalInner(t, 0); + if (!lit) return false; + return splitTopLevel(lit.inner, 0, lit.inner.length).some((segment) => segment.trim().startsWith("...")); +} + +/** 兜底边界:第一个位于第 0 列的 `};` / `} as ...`(返回 `}` 之后的下标) */ +function findColumnZeroClose(text, from) { + const re = /^\}\s*(?:;|as\b)/gm; + re.lastIndex = from; + const match = re.exec(text); + return match ? match.index + 1 : undefined; +} + +/** rot13:用于校验 urls 中“被保护”的地址(types/base.ts:11-21) */ +function rot13(input) { + return input.replace(/[a-zA-Z]/g, (ch) => { + const base = ch <= "Z" ? 65 : 97; + return String.fromCharCode(((ch.charCodeAt(0) - base + 13) % 26) + base); + }); +} + +function isHttpUrl(url) { + return /^https?:\/\/[^\s/]+/i.test(url); +} + +/* ---------------------------------- 单文件检查 --------------------------------- */ + +function checkFile(filePath, seenIds) { + const errors = []; + const warnings = []; + const notes = []; + const fileName = basename(filePath); + const expectedId = fileName.replace(/\.ts$/, ""); + const text = readFileSync(filePath, "utf8"); + const knownSchemas = new Set(readdirSync(SCHEMAS_DIR).filter((f) => f.endsWith(".ts")).map((f) => f.slice(0, -3))); + + const decl = /export\s+const\s+siteMetadata[\s\S]{0,200}?=\s*\{/.exec(text); + if (!decl) { + errors.push("未找到 `export const siteMetadata = { ... }`(index.ts:37-44 要求导出该字段)"); + return { errors, warnings, notes }; + } + const braceIdx = decl.index + decl[0].length - 1; + // 括号扫描器偶发被正则/注释带偏,用第 0 列的 `};` 兜底(metadata 对象的嵌套层级都带缩进) + const scanEnd = scanBalanced(text, braceIdx); + const lineEnd = findColumnZeroClose(text, braceIdx); + let end = scanEnd; + if (lineEnd !== undefined && (scanEnd < 0 || scanEnd > lineEnd)) end = lineEnd; + if (end === undefined || end <= braceIdx) { + errors.push("siteMetadata 对象字面量未闭合,无法解析"); + return { errors, warnings, notes }; + } + const meta = objectEntryMap(text.slice(braceIdx + 1, end - 1)); + + // 1. id + const id = asString(meta.get("id")); + if (id === undefined) errors.push("缺少 `id` 或它不是字符串字面量"); + else { + if (id !== expectedId) errors.push(`id "${id}" 与文件名 "${expectedId}" 不一致(index.ts 以文件名作为 siteId)`); + if (!ID_PATTERN.test(id)) errors.push(`id "${id}" 不符合 /^[0-9a-z]+$/(types/site.ts:36-44)`); + if (seenIds?.has(id)) errors.push(`id "${id}" 与其他定义文件重复:${seenIds.get(id)}`); + else seenIds?.set(id, fileName); + } + + // 2. 必填字段 + for (const [field, desc] of [ + ["version", "数字"], + ["name", "字符串"], + ["type", "字符串"], + ]) { + if (!meta.has(field)) errors.push(`缺少必填字段 \`${field}\`(应为${desc})`); + } + if (!meta.has("urls")) errors.push("缺少必填字段 `urls`(types/site.ts:86)"); + + const type = asString(meta.get("type")); + if (type !== undefined && type !== "private" && type !== "public") { + errors.push(`type 取值 "${type}" 非法,只能是 "private" 或 "public"`); + } + if (type === undefined && meta.has("type")) errors.push("type 不是字符串字面量"); + + // 3. schema 与 default class + const schema = asString(meta.get("schema")); + const classDecl = /export\s+default\s+class\s+[A-Za-z_$][\w$]*\s+extends\s+([A-Za-z_$][\w$.]*)/.exec(text); + const extendsRaw = classDecl?.[1]; + const extendsTarget = extendsRaw ? (CLASS_ALIAS[extendsRaw] ?? extendsRaw) : undefined; + + if (schema !== undefined && !knownSchemas.has(schema)) { + if (classDecl) { + notes.push( + `schema "${schema}" 不是 schemas/ 下的引擎,属该站点自定义命名;因已导出 default class,实例化以该类为准(index.ts:79-84)`, + ); + } else { + warnings.push( + `schema "${schema}" 不在 schemas/ 中且未导出 default class,实例化会按 type 回落 ${type === "public" ? "AbstractBittorrentSite" : "AbstractPrivateSite"}(index.ts:93-102)`, + ); + } + } + if (!meta.has("schema") && !classDecl) { + notes.push( + `未声明 schema 且未导出 default class,将按 type 回落 ${type === "public" ? "AbstractBittorrentSite" : "AbstractPrivateSite"}`, + ); + } + if (extendsRaw && !knownSchemas.has(extendsTarget)) { + warnings.push(`default class extends "${extendsRaw}",该目标不在 schemas/ 中`); + } + // schema 明确写了 schemas/ 中存在的引擎、但 default class 继承别的类时,才视为不一致; + // 若 schema 是站点自定义命名(schemas/ 中不存在),definition 继承模板类属于正常写法。 + if (extendsRaw && schema !== undefined && knownSchemas.has(schema) && extendsTarget !== schema) { + warnings.push(`schema 字段为 "${schema}",但 default class extends "${extendsRaw}"(实例化时以 default class 为准)`); + } + + // 4. version + // 注意:types/site.ts:46-54 的约定与仓库现状不一致(引擎类定义普遍写 1 而非 0), + // 因此此处只提示、不报错,避免对既有文件产生大面积噪音。 + const version = asNumber(meta.get("version")); + if (version === undefined) { + if (meta.has("version")) errors.push("version 不是数字字面量"); + } else { + const engine = schema ?? extendsTarget ?? (type === "public" ? "AbstractBittorrentSite" : "AbstractPrivateSite"); + const expected = ENGINE_VERSION_RULE[engine]; + if (expected !== undefined && version !== expected) { + notes.push( + `version=${version},types/site.ts:46-54 约定 "${engine}" 应为 ${expected}(仓库现状多写 1,保持站点内一致即可)`, + ); + } else if (expected === undefined && version <= 0) { + warnings.push(`version=${version},非模板类站点应为大于 0 的数字(types/site.ts:52-53)`); + } + } + + // 5. urls / legacyUrls + const urlsRaw = meta.get("urls"); + const urls = asArrayElements(urlsRaw); + if (urlsRaw !== undefined) { + if (!urls) errors.push("urls 不是数组字面量"); + else if (urls.length === 0) errors.push("urls 为空数组"); + else { + urls.forEach((element, index) => { + const value = asString(element); + if (value === undefined) { + errors.push(`urls[${index}] 不是字符串字面量`); + return; + } + const decoded = isHttpUrl(value) ? value : rot13(value); + if (!isHttpUrl(decoded)) { + errors.push(`urls[${index}] = "${value}" 不是 http(s) 地址,rot13 后也不是`); + } else if (/^http:\/\//i.test(decoded)) { + warnings.push(`urls[${index}] 使用明文 http,建议优先 https:${decoded}`); + } + }); + } + } + const legacy = asArrayElements(meta.get("legacyUrls")); + if (legacy && urls) { + const urlSet = new Set(urls.map((u) => asString(u)).filter(Boolean)); + legacy.forEach((element, index) => { + const value = asString(element); + if (value === undefined) errors.push(`legacyUrls[${index}] 不是字符串字面量`); + else if (urlSet.has(value)) warnings.push(`legacyUrls[${index}] 与 urls 重复:${value}`); + }); + } + + // 6. timezoneOffset + const tz = asString(meta.get("timezoneOffset")); + if (tz !== undefined && !/^(UTC)?[+-]\d{4}$/.test(tz)) { + errors.push(`timezoneOffset "${tz}" 格式非法,应为 +0800 / -0500 / UTC+0800 之类(utils/datetime.ts:3)`); + } + + // 7. category:key 必须唯一(types/search.ts:208-217) + const categoryElements = asArrayElements(meta.get("category")); + if (categoryElements) { + const keys = []; + categoryElements.forEach((element, index) => { + if (isReferenceElement(element)) return; // 例如 CategoryIncldead / ...sharedCategory + const entry = objectOf(element); + if (!entry) { + errors.push(`category[${index}] 不是对象字面量,也不是可识别的常量引用`); + return; + } + const key = asString(entry.get("key")); + if (key === undefined) { + if (!objectHasSpread(element)) errors.push(`category[${index}] 缺少字符串类型的 key(types/search.ts:208-217)`); + } else keys.push(key); + if (!entry.has("options") && !objectHasSpread(element)) { + warnings.push(`category[${index}](key=${key ?? "?"})缺少 options`); + } + }); + const duplicated = keys.filter((key, index) => keys.indexOf(key) !== index); + if (duplicated.length > 0) errors.push(`category 的 key 重复:${[...new Set(duplicated)].join(", ")}`); + } + + // 8. levelRequirements:id 递增且唯一(types/userinfo.ts:73-74) + const levelElements = asArrayElements(meta.get("levelRequirements")); + if (levelElements) { + const ids = []; + levelElements.forEach((element, index) => { + if (isReferenceElement(element)) return; + const entry = objectOf(element); + if (!entry) { + errors.push(`levelRequirements[${index}] 不是对象字面量,也不是可识别的常量引用`); + return; + } + const levelId = asNumber(entry.get("id")); + if (levelId === undefined) { + if (!objectHasSpread(element)) errors.push(`levelRequirements[${index}] 缺少数字类型的 id`); + } else ids.push(levelId); + if (!entry.has("name") && !objectHasSpread(element)) { + warnings.push(`levelRequirements[${index}](id=${levelId ?? "?"})缺少 name`); + } + }); + const duplicated = ids.filter((v, index) => ids.indexOf(v) !== index); + if (duplicated.length > 0) { + errors.push(`levelRequirements 的 id 重复:${[...new Set(duplicated)].join(", ")}`); + } else { + for (let i = 1; i < ids.length; i++) { + if (ids[i] <= ids[i - 1]) { + warnings.push(`levelRequirements 的 id 非严格递增:${ids[i - 1]} → ${ids[i]}(在数组第 ${i} 项)`); + break; + } + } + } + } + + // 9. isDead 提示 + if (meta.get("isDead")?.trim() === "true") { + notes.push("isDead: true —— 按 types/site.ts:108-115 建议注释或删除其后的全部配置项"); + } + + return { errors, warnings, notes }; +} + +/* ---------------------------------- 入口 --------------------------------- */ + +const args = process.argv.slice(2).filter((arg) => !arg.startsWith("--")); +let files; +if (args.length > 0) { + files = args.map((arg) => resolve(process.cwd(), arg)); +} else { + files = readdirSync(DEFINITIONS_DIR) + .filter((f) => f.endsWith(".ts")) + .map((f) => join(DEFINITIONS_DIR, f)); +} + +const seenIds = new Map(); +let errorCount = 0; +let warningCount = 0; + +for (const file of files) { + if (!existsSync(file)) { + console.log(`ERROR ${file}\n - 文件不存在`); + errorCount++; + continue; + } + const { errors, warnings, notes } = checkFile(file, seenIds); + const label = basename(file); + if (errors.length === 0 && warnings.length === 0) { + console.log(`OK ${label}${notes.length ? ` (${notes.length} 条提示)` : ""}`); + } else { + console.log(`${errors.length > 0 ? "ERROR " : "WARN "} ${label}`); + } + for (const error of errors) console.log(` ✖ ${error}`); + for (const warning of warnings) console.log(` ⚠ ${warning}`); + for (const note of notes) console.log(` · ${note}`); + errorCount += errors.length; + warningCount += warnings.length; +} + +console.log(`\n检查 ${files.length} 个文件:${errorCount} 个错误,${warningCount} 个警告`); +if (errorCount > 0) process.exitCode = 1; diff --git a/src/packages/site/definitions/13city.ts b/src/packages/site/definitions/13city.ts index 979e511bb..08cc12b6b 100755 --- a/src/packages/site/definitions/13city.ts +++ b/src/packages/site/definitions/13city.ts @@ -1,6 +1,7 @@ /** * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/13city.yml * @JackettIssue https://github.com/Jackett/Jackett/issues/16052 + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/13city.json */ import { type ISiteMetadata } from "../types"; import { CategoryIncldead, CategorySpstate, CategoryInclbookmarked, SchemaMetadata } from "../schemas/NexusPHP"; diff --git a/src/packages/site/definitions/1ptba.ts b/src/packages/site/definitions/1ptba.ts index 7a831cec7..9deb150e2 100644 --- a/src/packages/site/definitions/1ptba.ts +++ b/src/packages/site/definitions/1ptba.ts @@ -1,6 +1,8 @@ /** * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/1ptbar.yml * @JackettIssue https://github.com/Jackett/Jackett/issues/9598 + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/1ptba.com/config.json + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/1ptba.json */ import { type ISiteMetadata } from "../types"; import { CategoryInclbookmarked, CategoryIncldead, CategorySpstate, SchemaMetadata } from "../schemas/NexusPHP.ts"; diff --git a/src/packages/site/definitions/2xfree.ts b/src/packages/site/definitions/2xfree.ts index 71f6e9fd6..3c4a50939 100644 --- a/src/packages/site/definitions/2xfree.ts +++ b/src/packages/site/definitions/2xfree.ts @@ -1,5 +1,6 @@ /** * @JackettIssue https://github.com/Jackett/Jackett/issues/13943 + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/pt.2xfree.org/config.json */ import type { ISiteMetadata } from "../types"; diff --git a/src/packages/site/definitions/52movie.ts b/src/packages/site/definitions/52movie.ts index 405026685..3f9fae74c 100644 --- a/src/packages/site/definitions/52movie.ts +++ b/src/packages/site/definitions/52movie.ts @@ -1,3 +1,6 @@ +/** + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/52movie.json + */ import type { ISiteMetadata } from "../types"; import { CategoryInclbookmarked, CategoryIncldead, CategorySpstate, SchemaMetadata } from "../schemas/NexusPHP"; diff --git a/src/packages/site/definitions/52pt.ts b/src/packages/site/definitions/52pt.ts index da135ef58..7c6865b20 100644 --- a/src/packages/site/definitions/52pt.ts +++ b/src/packages/site/definitions/52pt.ts @@ -1,6 +1,8 @@ /** * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/52pt.yml * @JackettIssue https://github.com/Jackett/Jackett/issues/6763 + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/52pt.site/config.json + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/52pt.json */ import type { ISiteMetadata, IUserInfo } from "../types"; import { CategoryInclbookmarked, CategoryIncldead, CategorySpstate, SchemaMetadata } from "../schemas/NexusPHP"; diff --git a/src/packages/site/definitions/afun.ts b/src/packages/site/definitions/afun.ts index ca210498b..cd12fcb64 100644 --- a/src/packages/site/definitions/afun.ts +++ b/src/packages/site/definitions/afun.ts @@ -1,6 +1,7 @@ /** * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/afun.yml * @JackettIssue https://github.com/Jackett/Jackett/issues/15940 + * @PTMDefinitions https://github.com/JustLookAtNow/pt_mate/blob/master/assets/sites/afun.json */ import { type ISiteMetadata } from "../types"; // import { CategoryInclbookmarked, CategoryIncldead, CategorySpstate, SchemaMetadata } from "../schemas/NexusPHP"; diff --git a/src/packages/site/definitions/agsvpt.ts b/src/packages/site/definitions/agsvpt.ts index 3952e7c5b..a62ad3a8e 100644 --- a/src/packages/site/definitions/agsvpt.ts +++ b/src/packages/site/definitions/agsvpt.ts @@ -1,6 +1,8 @@ /** * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/agsvpt.yml * @JackettIssue https://github.com/Jackett/Jackett/issues/14946 + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/agsvpt.json + * @PTMDefinitions https://github.com/JustLookAtNow/pt_mate/blob/master/assets/sites/agsvpt.json */ import { type ISiteMetadata } from "../types"; import { CategoryInclbookmarked, CategoryIncldead, CategorySpstate, SchemaMetadata } from "../schemas/NexusPHP"; diff --git a/src/packages/site/definitions/aither.ts b/src/packages/site/definitions/aither.ts index 6db3c0ea3..46a6ca393 100644 --- a/src/packages/site/definitions/aither.ts +++ b/src/packages/site/definitions/aither.ts @@ -1,3 +1,8 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/aither-api.yml + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/aither.cc/config.json + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/aither.json + */ import { type ISiteMetadata } from "../types"; import { CategoryFree, SchemaMetadata, userInfoTrans } from "../schemas/Unit3D.ts"; import { buildCategoryOptionsFromDict } from "../utils.ts"; diff --git a/src/packages/site/definitions/alingpt.ts b/src/packages/site/definitions/alingpt.ts index 8d32764ec..7d710cfd6 100644 --- a/src/packages/site/definitions/alingpt.ts +++ b/src/packages/site/definitions/alingpt.ts @@ -1,3 +1,7 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/alingpt.yml + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/aling.json + */ import { type ISiteMetadata } from "../types"; import { SchemaMetadata } from "../schemas/NexusPHP"; diff --git a/src/packages/site/definitions/alpharatio.ts b/src/packages/site/definitions/alpharatio.ts index 29f5bde1d..a5889715d 100644 --- a/src/packages/site/definitions/alpharatio.ts +++ b/src/packages/site/definitions/alpharatio.ts @@ -1,5 +1,6 @@ /** * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Indexers/Definitions/AlphaRatio.cs + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/alpharatio.cc/config.json */ import type { ISiteMetadata, ITorrent, IUserInfo, TSearchRequestConfigTransformer } from "../types"; import GazelleJSONAPI, { SchemaMetadata, torrentBrowseResult } from "../schemas/GazelleJSONAPI"; diff --git a/src/packages/site/definitions/animebytes.ts b/src/packages/site/definitions/animebytes.ts index f7b2e4d6a..6bf7b13cd 100644 --- a/src/packages/site/definitions/animebytes.ts +++ b/src/packages/site/definitions/animebytes.ts @@ -1,6 +1,7 @@ /** * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Indexers/Definitions/AnimeBytes.cs * @JackettIssue https://github.com/Jackett/Jackett/issues/16062 + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/animebytes.tv/config.json */ import type { ISearchInput, ISiteMetadata, ITorrent } from "../types"; import { AxiosRequestConfig, AxiosResponse } from "axios"; diff --git a/src/packages/site/definitions/animetosho.ts b/src/packages/site/definitions/animetosho.ts index 311243d64..17ac940f6 100644 --- a/src/packages/site/definitions/animetosho.ts +++ b/src/packages/site/definitions/animetosho.ts @@ -1,5 +1,5 @@ /** - * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Indexers/Definitions/Feeds/AnimeTosho.cs + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/animetosho-xyz.yml */ import { type ISiteMetadata } from "../types"; import { format, subDays } from "date-fns"; diff --git a/src/packages/site/definitions/animez.ts b/src/packages/site/definitions/animez.ts index 0efefe0ef..2928f94fc 100644 --- a/src/packages/site/definitions/animez.ts +++ b/src/packages/site/definitions/animez.ts @@ -1,3 +1,6 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Indexers/Definitions/AnimeZ.cs + */ import { ISearchInput, ISiteMetadata, ITorrent, ITorrentTag, IUserInfo } from "../types"; import AvistazNetwork, { SchemaMetadata, IAvzNetRawTorrent } from "../schemas/AvistazNetwork.ts"; import { definedFilters } from "../utils/filter.ts"; diff --git a/src/packages/site/definitions/anthelion.ts b/src/packages/site/definitions/anthelion.ts index 8cb40a9ef..0445eb013 100644 --- a/src/packages/site/definitions/anthelion.ts +++ b/src/packages/site/definitions/anthelion.ts @@ -1,3 +1,7 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/anthelion-api.yml + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/anthelion.me/config.json + */ import Gazelle, { SchemaMetadata, GazelleUtils, commonPagesList, detailPageList } from "../schemas/Gazelle.ts"; import { ISiteMetadata, ITorrent, ISearchInput, ETorrentStatus } from "../types.ts"; import { buildCategoryOptionsFromList } from "../utils.ts"; diff --git a/src/packages/site/definitions/asiancinema.ts b/src/packages/site/definitions/asiancinema.ts index 3af0dbbdd..31246a5ca 100644 --- a/src/packages/site/definitions/asiancinema.ts +++ b/src/packages/site/definitions/asiancinema.ts @@ -1,5 +1,6 @@ /** * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/asiancinema.yml + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/asiancinema.me/config.json */ import { SchemaMetadata, CategoryFree } from "../schemas/Unit3D.ts"; import { type ISiteMetadata } from "../types"; diff --git a/src/packages/site/definitions/audiences.ts b/src/packages/site/definitions/audiences.ts index 21de34336..a29a42a5d 100644 --- a/src/packages/site/definitions/audiences.ts +++ b/src/packages/site/definitions/audiences.ts @@ -1,6 +1,9 @@ /** * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/audiences.yml * @JackettIssue https://github.com/Jackett/Jackett/issues/13156 + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/audiences.me/config.json + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/audiences.json + * @PTMDefinitions https://github.com/JustLookAtNow/pt_mate/blob/master/assets/sites/audiences.json */ import Sizzle from "sizzle"; import { mergeWith } from "es-toolkit"; diff --git a/src/packages/site/definitions/avistaz.ts b/src/packages/site/definitions/avistaz.ts index 235ca0908..ba53ced4e 100644 --- a/src/packages/site/definitions/avistaz.ts +++ b/src/packages/site/definitions/avistaz.ts @@ -1,3 +1,6 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Indexers/Definitions/AvistaZ.cs + */ import { ISiteMetadata, ISearchInput, IAdvancedSearchRequestConfig, ITorrent, ITorrentTag } from "../types"; import AvistazNetwork, { SchemaMetadata, avzNetDiscountMap, IAvzNetRawTorrent } from "../schemas/AvistazNetwork.ts"; diff --git a/src/packages/site/definitions/azusa.ts b/src/packages/site/definitions/azusa.ts index ea6119690..b6d0446eb 100644 --- a/src/packages/site/definitions/azusa.ts +++ b/src/packages/site/definitions/azusa.ts @@ -1,6 +1,8 @@ /** * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/azusa.yml * @JackettIssue https://github.com/Jackett/Jackett/issues/14478 + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/azusa.wiki/config.json + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/azusa.json */ import { type ISiteMetadata } from "../types"; import { CategoryInclbookmarked, CategoryIncldead, CategorySpstate, SchemaMetadata } from "../schemas/NexusPHP.ts"; diff --git a/src/packages/site/definitions/baconbits.ts b/src/packages/site/definitions/baconbits.ts index 1a6a551bb..025a38465 100644 --- a/src/packages/site/definitions/baconbits.ts +++ b/src/packages/site/definitions/baconbits.ts @@ -1,5 +1,6 @@ /** * @JackettIssue https://github.com/Jackett/Jackett/issues/13211 + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/baconbits.org/config.json */ import { type ISiteMetadata } from "../types"; diff --git a/src/packages/site/definitions/baozi.ts b/src/packages/site/definitions/baozi.ts index 985179278..2e02e3b45 100644 --- a/src/packages/site/definitions/baozi.ts +++ b/src/packages/site/definitions/baozi.ts @@ -1,3 +1,7 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/baozipt.yml + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/baozi.json + */ import type { ISiteMetadata, IUserInfo } from "../types"; import NexusPHP, { CategoryInclbookmarked, diff --git a/src/packages/site/definitions/beitai.ts b/src/packages/site/definitions/beitai.ts index dd6bfb08a..a75a17985 100644 --- a/src/packages/site/definitions/beitai.ts +++ b/src/packages/site/definitions/beitai.ts @@ -1,5 +1,7 @@ /** * @JackettIssue https://github.com/Jackett/Jackett/issues/6764 + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/beitai.pt/config.json + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/beitai.json */ import type { ISiteMetadata } from "../types"; diff --git a/src/packages/site/definitions/beyondhd.ts b/src/packages/site/definitions/beyondhd.ts index 538a918cd..9fb207334 100644 --- a/src/packages/site/definitions/beyondhd.ts +++ b/src/packages/site/definitions/beyondhd.ts @@ -1,3 +1,6 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Indexers/Definitions/BeyondHDAPI.cs + */ import { omit, toMerged } from "es-toolkit"; import type { AxiosRequestConfig, AxiosResponse } from "axios"; diff --git a/src/packages/site/definitions/bitporn.ts b/src/packages/site/definitions/bitporn.ts index 1b87c9691..2a9d3b32d 100644 --- a/src/packages/site/definitions/bitporn.ts +++ b/src/packages/site/definitions/bitporn.ts @@ -1,5 +1,7 @@ /** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/bitporn.yml * @JackettIssue https://github.com/Jackett/Jackett/issues/14816 + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/bitporn.json */ import { type ISiteMetadata } from "../types"; import { SchemaMetadata } from "../schemas/Unit3D"; diff --git a/src/packages/site/definitions/bitpt.ts b/src/packages/site/definitions/bitpt.ts index a2f8d9d31..db533fc90 100644 --- a/src/packages/site/definitions/bitpt.ts +++ b/src/packages/site/definitions/bitpt.ts @@ -1,3 +1,6 @@ +/** + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/bitpt.cn/config.json + */ import { type ISiteMetadata } from "../types"; import { GB, TB } from "../utils"; diff --git a/src/packages/site/definitions/blutopia.ts b/src/packages/site/definitions/blutopia.ts index ab07b2781..0fe063d54 100644 --- a/src/packages/site/definitions/blutopia.ts +++ b/src/packages/site/definitions/blutopia.ts @@ -1,3 +1,8 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/blutopia-api.yml + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/blutopia.cc/config.json + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/blutopia.json + */ import { type ISiteMetadata } from "../types"; import { CategoryFree, SchemaMetadata } from "../schemas/Unit3D.ts"; diff --git a/src/packages/site/definitions/broadcasthenet.ts b/src/packages/site/definitions/broadcasthenet.ts index c381a2642..bdb823202 100644 --- a/src/packages/site/definitions/broadcasthenet.ts +++ b/src/packages/site/definitions/broadcasthenet.ts @@ -1,3 +1,6 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Indexers/Definitions/BroadcasTheNet.cs + */ import type { ISiteMetadata, ISearchResult, ISearchEntryRequestConfig, ITorrent } from "../types.ts"; import { SchemaMetadata } from "../schemas/Gazelle.ts"; import Gazelle from "../schemas/Gazelle.ts"; diff --git a/src/packages/site/definitions/brokenstones.ts b/src/packages/site/definitions/brokenstones.ts index bf6eacc34..7a467684e 100644 --- a/src/packages/site/definitions/brokenstones.ts +++ b/src/packages/site/definitions/brokenstones.ts @@ -1,3 +1,7 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Indexers/Definitions/BrokenStones.cs + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/brokenstones.is/config.json + */ import { ISearchInput, ISiteMetadata, ITorrent } from "../types.ts"; import GazelleJSONAPI, { SchemaMetadata } from "../schemas/GazelleJSONAPI.ts"; import { SchemaMetadata as GazelleSchemaMetadata, GazelleUtils } from "../schemas/Gazelle.ts"; diff --git a/src/packages/site/definitions/btetree.ts b/src/packages/site/definitions/btetree.ts index b388664e7..c20682fea 100644 --- a/src/packages/site/definitions/btetree.ts +++ b/src/packages/site/definitions/btetree.ts @@ -1,3 +1,6 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/btetree.yml + */ import type { ISearchInput, ISiteMetadata, ITorrent } from "../types"; import BittorrentSite from "../schemas/AbstractBittorrentSite.ts"; import { parseValidTimeString } from "../utils/datetime.ts"; diff --git a/src/packages/site/definitions/btschool.ts b/src/packages/site/definitions/btschool.ts index e5d6758d6..2987c65e2 100644 --- a/src/packages/site/definitions/btschool.ts +++ b/src/packages/site/definitions/btschool.ts @@ -1,6 +1,8 @@ /** * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/btschool.yml * @JackettIssue https://github.com/Jackett/Jackett/issues/5761 + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/btschool.json + * @PTMDefinitions https://github.com/JustLookAtNow/pt_mate/blob/master/assets/sites/btschool.json */ import { ETorrentStatus, type ISiteMetadata } from "../types"; import { parseSizeString, rot13 } from "../utils"; diff --git a/src/packages/site/definitions/byrbt.ts b/src/packages/site/definitions/byrbt.ts index 669f38e89..b9eb354af 100644 --- a/src/packages/site/definitions/byrbt.ts +++ b/src/packages/site/definitions/byrbt.ts @@ -1,6 +1,8 @@ /** * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/byrbt.yml * @JackettIssue https://github.com/Jackett/Jackett/issues/12540 + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/byr.pt/config.json + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/byr.json */ import { ETorrentStatus, type ILevelRequirement, type ISiteMetadata } from "../types"; import NexusPHP, { diff --git a/src/packages/site/definitions/capybarabr.ts b/src/packages/site/definitions/capybarabr.ts index 0479cdb29..d69958278 100644 --- a/src/packages/site/definitions/capybarabr.ts +++ b/src/packages/site/definitions/capybarabr.ts @@ -1,3 +1,6 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/capybarabr.yml + */ import { type ISiteMetadata } from "../types"; import { SchemaMetadata, userInfoTrans } from "../schemas/Unit3D.ts"; diff --git a/src/packages/site/definitions/carpt.ts b/src/packages/site/definitions/carpt.ts index 1adde4f65..14233266f 100644 --- a/src/packages/site/definitions/carpt.ts +++ b/src/packages/site/definitions/carpt.ts @@ -1,6 +1,8 @@ /** * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/carpt.yml * @JackettIssue https://github.com/Jackett/Jackett/issues/13388 + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/carpt.net/config.json + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/carpt.json */ import { type ISiteMetadata } from "../types"; import { CategoryInclbookmarked, CategoryIncldead, CategorySpstate, SchemaMetadata } from "../schemas/NexusPHP.ts"; diff --git a/src/packages/site/definitions/cathoderaytube.ts b/src/packages/site/definitions/cathoderaytube.ts index e5e821947..561461d90 100644 --- a/src/packages/site/definitions/cathoderaytube.ts +++ b/src/packages/site/definitions/cathoderaytube.ts @@ -1,3 +1,6 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/cathoderaytube.yml + */ import { type ISiteMetadata } from "../types"; import { SchemaMetadata } from "../schemas/Luminance"; import { buildCategoryOptionsFromDict } from "../utils"; diff --git a/src/packages/site/definitions/cbg.ts b/src/packages/site/definitions/cbg.ts index 357461a31..79a061330 100644 --- a/src/packages/site/definitions/cbg.ts +++ b/src/packages/site/definitions/cbg.ts @@ -1,3 +1,8 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/cangbaoge.yml + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/cangbaoge.json + * @PTMDefinitions https://github.com/JustLookAtNow/pt_mate/blob/master/assets/sites/cbg.json + */ import { type ISiteMetadata } from "../types"; import { CategoryInclbookmarked, CategoryIncldead, CategorySpstate, SchemaMetadata } from "../schemas/NexusPHP.ts"; diff --git a/src/packages/site/definitions/ccfbits.ts b/src/packages/site/definitions/ccfbits.ts index affdaa18d..353d3f930 100644 --- a/src/packages/site/definitions/ccfbits.ts +++ b/src/packages/site/definitions/ccfbits.ts @@ -1,3 +1,6 @@ +/** + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/ccfbits.org/config.json + */ import type { ISiteMetadata } from "../types"; export const siteMetadata: ISiteMetadata = { diff --git a/src/packages/site/definitions/cdfile.ts b/src/packages/site/definitions/cdfile.ts index bc630cb50..05e0cc414 100644 --- a/src/packages/site/definitions/cdfile.ts +++ b/src/packages/site/definitions/cdfile.ts @@ -1,5 +1,6 @@ /** * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/cdfile.yml + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/cdfile.json */ import { type ISiteMetadata } from "../types"; import { diff --git a/src/packages/site/definitions/cgpeers.ts b/src/packages/site/definitions/cgpeers.ts index 78022a94e..782790db3 100644 --- a/src/packages/site/definitions/cgpeers.ts +++ b/src/packages/site/definitions/cgpeers.ts @@ -1,3 +1,7 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/cgpeers.yml + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/cgpeers.com/config.json + */ import { type ISiteMetadata, ETorrentStatus } from "../types"; import { SchemaMetadata } from "../schemas/Luminance"; import { buildCategoryOptionsFromDict } from "../utils"; diff --git a/src/packages/site/definitions/chdbits.ts b/src/packages/site/definitions/chdbits.ts index 15d6c3b4b..5e6fbbb66 100644 --- a/src/packages/site/definitions/chdbits.ts +++ b/src/packages/site/definitions/chdbits.ts @@ -1,5 +1,6 @@ /** * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/chdbits.yml + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/chdbits.co/config.json */ import { ETorrentStatus, type ISiteMetadata } from "../types"; import { CategoryInclbookmarked, CategoryIncldead, CategorySpstate, SchemaMetadata } from "../schemas/NexusPHP"; diff --git a/src/packages/site/definitions/cinematik.ts b/src/packages/site/definitions/cinematik.ts index c5f9cc6b2..837ee8761 100644 --- a/src/packages/site/definitions/cinematik.ts +++ b/src/packages/site/definitions/cinematik.ts @@ -1,3 +1,8 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/cinematik.yml + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/cinematik.net/config.json + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/cinematik.json + */ import { type ISiteMetadata } from "../types"; import { CategoryFree, SchemaMetadata } from "../schemas/Unit3D.ts"; diff --git a/src/packages/site/definitions/cinemaz.ts b/src/packages/site/definitions/cinemaz.ts index dd482d798..e2de8634d 100644 --- a/src/packages/site/definitions/cinemaz.ts +++ b/src/packages/site/definitions/cinemaz.ts @@ -1,3 +1,6 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Indexers/Definitions/CinemaZ.cs + */ import { ISiteMetadata, ISearchInput, ITorrent, ITorrentTag } from "../types"; import AvistazNetwork, { SchemaMetadata, avzNetDiscountMap, IAvzNetRawTorrent } from "../schemas/AvistazNetwork.ts"; diff --git a/src/packages/site/definitions/clearjav.ts b/src/packages/site/definitions/clearjav.ts index 10babc8ab..7a8a40089 100644 --- a/src/packages/site/definitions/clearjav.ts +++ b/src/packages/site/definitions/clearjav.ts @@ -1,3 +1,7 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/clearjav-api.yml + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/clearjav.json + */ import { type ISiteMetadata } from "../types"; import { CategoryFree, SchemaMetadata } from "../schemas/Unit3D"; diff --git a/src/packages/site/definitions/cnlang.ts b/src/packages/site/definitions/cnlang.ts index c57161358..2b93107e0 100644 --- a/src/packages/site/definitions/cnlang.ts +++ b/src/packages/site/definitions/cnlang.ts @@ -1,3 +1,6 @@ +/** + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/cnlang.org/config.json + */ import { type ISiteMetadata } from "../types"; export const siteMetadata: ISiteMetadata = { diff --git a/src/packages/site/definitions/crabpt.ts b/src/packages/site/definitions/crabpt.ts index 2c15c8b2d..4c597a38e 100644 --- a/src/packages/site/definitions/crabpt.ts +++ b/src/packages/site/definitions/crabpt.ts @@ -1,5 +1,6 @@ /** * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/crabpt.yml + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/crabpt.json */ import { type ISiteMetadata } from "../types"; import { diff --git a/src/packages/site/definitions/cspt.ts b/src/packages/site/definitions/cspt.ts index ee5939ae7..88c7c7537 100644 --- a/src/packages/site/definitions/cspt.ts +++ b/src/packages/site/definitions/cspt.ts @@ -1,3 +1,8 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/cspt.yml + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/cspt.json + * @PTMDefinitions https://github.com/JustLookAtNow/pt_mate/blob/master/assets/sites/cspt.json + */ import { ETorrentStatus, type ISiteMetadata } from "../types"; import NexusPHP, { CategoryInclbookmarked, diff --git a/src/packages/site/definitions/cyanbug.ts b/src/packages/site/definitions/cyanbug.ts index 6829c2f3f..d5acfb6f3 100644 --- a/src/packages/site/definitions/cyanbug.ts +++ b/src/packages/site/definitions/cyanbug.ts @@ -1,3 +1,8 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/cyanbug.yml + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/cyanbug.net/config.json + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/cyanbug.json + */ import { type ISiteMetadata } from "../types"; import { SchemaMetadata, xiaomloveDefaultUserLevelRequirements } from "../schemas/NexusPHP"; diff --git a/src/packages/site/definitions/dajiao.ts b/src/packages/site/definitions/dajiao.ts index 17f7df06e..879ff3c61 100644 --- a/src/packages/site/definitions/dajiao.ts +++ b/src/packages/site/definitions/dajiao.ts @@ -1,3 +1,7 @@ +/** + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/dajiao.cyou/config.json + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/dajiao.json + */ import type { ISiteMetadata } from "../types"; export const siteMetadata: ISiteMetadata = { diff --git a/src/packages/site/definitions/darkpeers.ts b/src/packages/site/definitions/darkpeers.ts index ae514c5d1..a9bc256a6 100644 --- a/src/packages/site/definitions/darkpeers.ts +++ b/src/packages/site/definitions/darkpeers.ts @@ -1,3 +1,6 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/darkpeers.yml + */ import type { ISiteMetadata } from "../types"; import { CategoryFree, SchemaMetadata } from "../schemas/Unit3D.ts"; import { buildCategoryOptionsFromDict } from "../utils.ts"; diff --git a/src/packages/site/definitions/daxiangjiao.ts b/src/packages/site/definitions/daxiangjiao.ts index c41066389..1c23d818a 100644 --- a/src/packages/site/definitions/daxiangjiao.ts +++ b/src/packages/site/definitions/daxiangjiao.ts @@ -1,3 +1,7 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/daxiangjiao.yml + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/daxiangjiao.json + */ import type { ISiteMetadata } from "../types"; import { CategoryInclbookmarked, diff --git a/src/packages/site/definitions/desigaane.ts b/src/packages/site/definitions/desigaane.ts index 55e9876e1..3ce3444e4 100644 --- a/src/packages/site/definitions/desigaane.ts +++ b/src/packages/site/definitions/desigaane.ts @@ -1,3 +1,6 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Indexers/Definitions/DesiGaane.cs + */ import { type ISiteMetadata } from "../types"; import { SchemaMetadata } from "../schemas/GazelleJSONAPI.ts"; diff --git a/src/packages/site/definitions/dicmusic.ts b/src/packages/site/definitions/dicmusic.ts index 7f5546e1c..0e48a8758 100644 --- a/src/packages/site/definitions/dicmusic.ts +++ b/src/packages/site/definitions/dicmusic.ts @@ -1,3 +1,8 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Indexers/Definitions/DICMusic.cs + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/dicmusic.com/config.json + * @PTMDefinitions https://github.com/JustLookAtNow/pt_mate/blob/master/assets/sites/dicmusic.json + */ import { buildCategoryOptionsFromList } from "../utils"; import { type IUserInfo, type ITorrent, type ISiteMetadata } from "../types"; import GazelleJSONAPI, { diff --git a/src/packages/site/definitions/digitalcore.ts b/src/packages/site/definitions/digitalcore.ts index 9ca4eebba..a8f406893 100644 --- a/src/packages/site/definitions/digitalcore.ts +++ b/src/packages/site/definitions/digitalcore.ts @@ -1,3 +1,6 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/digitalcore-api.yml + */ import type { ISiteMetadata } from "../types"; import { get } from "es-toolkit/compat"; import { buildCategoryOptionsFromDict } from "../utils"; diff --git a/src/packages/site/definitions/discfan.ts b/src/packages/site/definitions/discfan.ts index f8de88e86..ff3e95ba4 100644 --- a/src/packages/site/definitions/discfan.ts +++ b/src/packages/site/definitions/discfan.ts @@ -1,3 +1,8 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/discfan.yml + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/discfan.net/config.json + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/discfan.json + */ import { type ISiteMetadata } from "../types"; import { SchemaMetadata } from "../schemas/NexusPHP"; diff --git a/src/packages/site/definitions/dmhy.ts b/src/packages/site/definitions/dmhy.ts index ed1071c6e..d51c7dd88 100644 --- a/src/packages/site/definitions/dmhy.ts +++ b/src/packages/site/definitions/dmhy.ts @@ -1,3 +1,7 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/dmhy.yml + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/bt_dmhy.json + */ import { type ISiteMetadata } from "../types"; export const siteMetadata: ISiteMetadata = { diff --git a/src/packages/site/definitions/dragonhd.ts b/src/packages/site/definitions/dragonhd.ts index 0b560285b..68449852c 100644 --- a/src/packages/site/definitions/dragonhd.ts +++ b/src/packages/site/definitions/dragonhd.ts @@ -1,3 +1,6 @@ +/** + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/dragonhd.json + */ import { type ISiteMetadata } from "../types"; import { CategoryInclbookmarked, CategoryIncldead, CategorySpstate, SchemaMetadata } from "../schemas/NexusPHP.ts"; diff --git a/src/packages/site/definitions/dubhe.ts b/src/packages/site/definitions/dubhe.ts index f21194887..f56305535 100644 --- a/src/packages/site/definitions/dubhe.ts +++ b/src/packages/site/definitions/dubhe.ts @@ -1,3 +1,8 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/dubhe.yml + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/dubhe.json + * @PTMDefinitions https://github.com/JustLookAtNow/pt_mate/blob/master/assets/sites/dubhe.json + */ import type { ISiteMetadata } from "../types"; import { SchemaMetadata } from "../schemas/NexusPHP"; import { userInfoWithInvitesInUserDetailsPage } from "./kunlun.ts"; diff --git a/src/packages/site/definitions/ecustpt.ts b/src/packages/site/definitions/ecustpt.ts index cefa5fb05..99f4e2123 100644 --- a/src/packages/site/definitions/ecustpt.ts +++ b/src/packages/site/definitions/ecustpt.ts @@ -1,3 +1,6 @@ +/** + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/ecustpt.json + */ import type { ISiteMetadata } from "../types"; export const siteMetadata: ISiteMetadata = { diff --git a/src/packages/site/definitions/ehentai.ts b/src/packages/site/definitions/ehentai.ts index 5995eb95a..e38922983 100644 --- a/src/packages/site/definitions/ehentai.ts +++ b/src/packages/site/definitions/ehentai.ts @@ -1,3 +1,6 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/ehentai.yml + */ import BittorrentSite from "../schemas/AbstractBittorrentSite"; import { ITorrent, type ISiteMetadata } from "../types"; diff --git a/src/packages/site/definitions/empornium.ts b/src/packages/site/definitions/empornium.ts index 8fd960f91..6a0c7b3aa 100644 --- a/src/packages/site/definitions/empornium.ts +++ b/src/packages/site/definitions/empornium.ts @@ -1,3 +1,8 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/empornium.yml + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/empornium.sx/config.json + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/empornium.json + */ import { type ISiteMetadata, ETorrentStatus } from "../types"; import { SchemaMetadata } from "../schemas/Luminance"; import { buildCategoryOptionsFromDict } from "../utils"; diff --git a/src/packages/site/definitions/exoticaz.ts b/src/packages/site/definitions/exoticaz.ts index 3ab58f2b0..b11a6ae9b 100644 --- a/src/packages/site/definitions/exoticaz.ts +++ b/src/packages/site/definitions/exoticaz.ts @@ -1,3 +1,7 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Indexers/Definitions/ExoticaZ.cs + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/exoticaz.json + */ import { ISiteMetadata, ISearchInput, IAdvancedSearchRequestConfig, ITorrent, ITorrentTag } from "../types"; import AvistazNetwork, { SchemaMetadata, diff --git a/src/packages/site/definitions/fearnopeer.ts b/src/packages/site/definitions/fearnopeer.ts index afd53f150..9a670a217 100644 --- a/src/packages/site/definitions/fearnopeer.ts +++ b/src/packages/site/definitions/fearnopeer.ts @@ -1,3 +1,6 @@ +/** + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/fearnopee.json + */ import { type ISiteMetadata } from "../types"; // import { SchemaMetadata } from "../schemas/Unit3D.ts"; // import { buildCategoryOptionsFromDict, buildCategoryOptionsFromList } from "../utils"; diff --git a/src/packages/site/definitions/filelist.ts b/src/packages/site/definitions/filelist.ts index cb5247028..3c2001e1c 100644 --- a/src/packages/site/definitions/filelist.ts +++ b/src/packages/site/definitions/filelist.ts @@ -1,3 +1,8 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Indexers/Definitions/FileList.cs + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/filelist.io/config.json + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/filelist.json + */ import { set } from "es-toolkit/compat"; import PrivateSite from "../schemas/AbstractPrivateSite.ts"; diff --git a/src/packages/site/definitions/filept.ts b/src/packages/site/definitions/filept.ts index da086cd97..4903f4a3c 100644 --- a/src/packages/site/definitions/filept.ts +++ b/src/packages/site/definitions/filept.ts @@ -1,3 +1,6 @@ +/** + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/filept.com/config.json + */ import { type ISiteMetadata } from "../types"; export const siteMetadata: ISiteMetadata = { diff --git a/src/packages/site/definitions/freefarm.ts b/src/packages/site/definitions/freefarm.ts index fadd1244a..36541fb77 100644 --- a/src/packages/site/definitions/freefarm.ts +++ b/src/packages/site/definitions/freefarm.ts @@ -1,3 +1,7 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/freefarm.yml + * @PTMDefinitions https://github.com/JustLookAtNow/pt_mate/blob/master/assets/sites/freefarm.json + */ import { type ISiteMetadata } from "../types"; import { CategoryInclbookmarked, CategoryIncldead, CategorySpstate, SchemaMetadata } from "../schemas/NexusPHP.ts"; diff --git a/src/packages/site/definitions/gainbound.ts b/src/packages/site/definitions/gainbound.ts index 8900fda30..726ea7d9b 100644 --- a/src/packages/site/definitions/gainbound.ts +++ b/src/packages/site/definitions/gainbound.ts @@ -1,3 +1,6 @@ +/** + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/gainbound.net/config.json + */ import { type ISiteMetadata } from "../types"; export const siteMetadata: ISiteMetadata = { diff --git a/src/packages/site/definitions/gazellegames.ts b/src/packages/site/definitions/gazellegames.ts index c2015c115..7ef8421d5 100644 --- a/src/packages/site/definitions/gazellegames.ts +++ b/src/packages/site/definitions/gazellegames.ts @@ -1,3 +1,7 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Indexers/Definitions/GazelleGamesAPI.cs + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/gazellegames.net/config.json + */ import type { ISiteMetadata, ISearchInput, diff --git a/src/packages/site/definitions/gfxpeers.ts b/src/packages/site/definitions/gfxpeers.ts index f0ed84004..d46974b88 100644 --- a/src/packages/site/definitions/gfxpeers.ts +++ b/src/packages/site/definitions/gfxpeers.ts @@ -1,5 +1,6 @@ /** * @JackettIssue https://github.com/Jackett/Jackett/issues/10161 + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/gfxpeers.net/config.json */ import type { ISiteMetadata } from "../types"; diff --git a/src/packages/site/definitions/ggpt.ts b/src/packages/site/definitions/ggpt.ts index 6fedc68b9..78268abb8 100644 --- a/src/packages/site/definitions/ggpt.ts +++ b/src/packages/site/definitions/ggpt.ts @@ -1,5 +1,7 @@ /** * FIXME 由 resource/sites/www.gamegamept.com/config.json 自动转换而来 + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/ggpt.yml + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/ggpt.json */ import { type ISiteMetadata } from "../types"; import { CategoryInclbookmarked, CategoryIncldead, CategorySpstate, SchemaMetadata } from "../schemas/NexusPHP"; diff --git a/src/packages/site/definitions/greatposterwall.ts b/src/packages/site/definitions/greatposterwall.ts index 1346accc1..f947299ed 100644 --- a/src/packages/site/definitions/greatposterwall.ts +++ b/src/packages/site/definitions/greatposterwall.ts @@ -1,3 +1,7 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Indexers/Definitions/GreatPosterWall.cs + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/greatposterwall.com/config.json + */ import { buildCategoryOptionsFromList, parseTimeWithZone, extractContent } from "../utils"; import { ISiteMetadata, ITorrent, ITorrentTag, IUserInfo } from "../types"; import GazelleJSONAPI, { groupBrowseResult, groupTorrent, SchemaMetadata } from "../schemas/GazelleJSONAPI.ts"; diff --git a/src/packages/site/definitions/gtnet.ts b/src/packages/site/definitions/gtnet.ts index 7c0c71d6c..62857e228 100644 --- a/src/packages/site/definitions/gtnet.ts +++ b/src/packages/site/definitions/gtnet.ts @@ -1,3 +1,6 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/gay-torrents.yml + */ import type { AxiosRequestConfig } from "axios"; import type { ISiteMetadata, ITorrent, ISearchResult, ISearchEntryRequestConfig } from "../types"; import PrivateSite from "../schemas/AbstractPrivateSite"; diff --git a/src/packages/site/definitions/gtorg.ts b/src/packages/site/definitions/gtorg.ts index 8ac9cc44c..081e854db 100644 --- a/src/packages/site/definitions/gtorg.ts +++ b/src/packages/site/definitions/gtorg.ts @@ -1,3 +1,6 @@ +/** + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/gay-torrents.org/config.json + */ import { ISiteMetadata } from "../types"; export const siteMetadata: ISiteMetadata = { diff --git a/src/packages/site/definitions/haidan.ts b/src/packages/site/definitions/haidan.ts index da490c6d7..f30950d18 100644 --- a/src/packages/site/definitions/haidan.ts +++ b/src/packages/site/definitions/haidan.ts @@ -1,3 +1,8 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/haidan.yml + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/haidan.video/config.json + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/haidan.json + */ import { type ISiteMetadata } from "../types"; import { CategoryInclbookmarked, CategoryIncldead, CategorySpstate, SchemaMetadata } from "../schemas/NexusPHP.ts"; import { userInfoWithInvitesInUserDetailsPage } from "./kunlun.ts"; diff --git a/src/packages/site/definitions/happyfappy.ts b/src/packages/site/definitions/happyfappy.ts index fef66b39a..73e49b1d8 100644 --- a/src/packages/site/definitions/happyfappy.ts +++ b/src/packages/site/definitions/happyfappy.ts @@ -1,3 +1,7 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/happyfappy.yml + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/happyfappy.json + */ import { type ISiteMetadata } from "../types"; import { SchemaMetadata } from "../schemas/Luminance"; import { buildCategoryOptionsFromList } from "../utils"; diff --git a/src/packages/site/definitions/hares.ts b/src/packages/site/definitions/hares.ts index f4b7f7ed8..b41367545 100644 --- a/src/packages/site/definitions/hares.ts +++ b/src/packages/site/definitions/hares.ts @@ -1,3 +1,6 @@ +/** + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/club.hares.top/config.json + */ import type { ISiteMetadata } from "../types"; export const siteMetadata: ISiteMetadata = { diff --git a/src/packages/site/definitions/hd4fans.ts b/src/packages/site/definitions/hd4fans.ts index a92791f9c..fb56b86c8 100644 --- a/src/packages/site/definitions/hd4fans.ts +++ b/src/packages/site/definitions/hd4fans.ts @@ -1,3 +1,6 @@ +/** + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/pt.hd4fans.org/config.json + */ import type { ISiteMetadata } from "../types"; export const siteMetadata: ISiteMetadata = { diff --git a/src/packages/site/definitions/hdarea.ts b/src/packages/site/definitions/hdarea.ts index 9e027038c..681eca52c 100644 --- a/src/packages/site/definitions/hdarea.ts +++ b/src/packages/site/definitions/hdarea.ts @@ -1,3 +1,7 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/hdarea.yml + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/hdarea.co/config.json + */ import type { ISearchInput, ISiteMetadata, ITorrent, ITorrentTag, IUserInfo } from "../types"; import NexusPHP, { CategoryInclbookmarked, diff --git a/src/packages/site/definitions/hdatmos.ts b/src/packages/site/definitions/hdatmos.ts index 2616c1005..02a6f3664 100644 --- a/src/packages/site/definitions/hdatmos.ts +++ b/src/packages/site/definitions/hdatmos.ts @@ -1,3 +1,7 @@ +/** + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/hdatmos.club/config.json + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/hdatmos.json + */ import type { ISiteMetadata } from "../types"; export const siteMetadata: ISiteMetadata = { diff --git a/src/packages/site/definitions/hdbao.ts b/src/packages/site/definitions/hdbao.ts index 04fe05868..3cb7ea52b 100644 --- a/src/packages/site/definitions/hdbao.ts +++ b/src/packages/site/definitions/hdbao.ts @@ -1,3 +1,7 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/hdbao.yml + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/hdbao.json + */ import { type ISiteMetadata } from "../types"; import { CategoryInclbookmarked, CategoryIncldead, CategorySpstate, SchemaMetadata } from "../schemas/NexusPHP"; diff --git a/src/packages/site/definitions/hdbits.ts b/src/packages/site/definitions/hdbits.ts index 1f42921cb..4ebebc0a2 100644 --- a/src/packages/site/definitions/hdbits.ts +++ b/src/packages/site/definitions/hdbits.ts @@ -1,3 +1,8 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Indexers/Definitions/HDBitsApi.cs + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/hdbits.org/config.json + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/hdbits.json + */ import { ETorrentStatus, ISearchCategories, ISearchInput, ISiteMetadata, ITorrent } from "../types"; import PrivateSite from "../schemas/AbstractPrivateSite"; import { set } from "es-toolkit/compat"; diff --git a/src/packages/site/definitions/hdchina.ts b/src/packages/site/definitions/hdchina.ts index 055ea1651..9c6d34a5a 100644 --- a/src/packages/site/definitions/hdchina.ts +++ b/src/packages/site/definitions/hdchina.ts @@ -1,3 +1,6 @@ +/** + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/hdchina.org/config.json + */ import { type ISiteMetadata } from "../types"; export const siteMetadata: ISiteMetadata = { diff --git a/src/packages/site/definitions/hdcity.ts b/src/packages/site/definitions/hdcity.ts index 3d8acd8d0..9963f2f34 100644 --- a/src/packages/site/definitions/hdcity.ts +++ b/src/packages/site/definitions/hdcity.ts @@ -1,3 +1,7 @@ +/** + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/hdcity.city/config.json + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/hdcity.json + */ import { type ILevelRequirement, ISearchInput, type ISiteMetadata, ITorrent, ITorrentTag } from "../types"; import NexusPHP, { CategoryInclbookmarked, diff --git a/src/packages/site/definitions/hdclone.ts b/src/packages/site/definitions/hdclone.ts index 03c7d0193..64574f466 100644 --- a/src/packages/site/definitions/hdclone.ts +++ b/src/packages/site/definitions/hdclone.ts @@ -1,3 +1,7 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/hdclone.yml + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/hdclone.json + */ import type { ISiteMetadata } from "../types"; import { CategoryInclbookmarked, CategoryIncldead, CategorySpstate, SchemaMetadata } from "../schemas/NexusPHP"; diff --git a/src/packages/site/definitions/hddolby.ts b/src/packages/site/definitions/hddolby.ts index ea2859eb8..78f1ab0e5 100644 --- a/src/packages/site/definitions/hddolby.ts +++ b/src/packages/site/definitions/hddolby.ts @@ -1,3 +1,9 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/hddolby.yml + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/hddolby.com/config.json + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/hddolby.json + * @PTMDefinitions https://github.com/JustLookAtNow/pt_mate/blob/master/assets/sites/hddolby.json + */ import { type ISiteMetadata } from "../types"; import { CategoryInclbookmarked, CategoryIncldead, CategorySpstate, SchemaMetadata } from "../schemas/NexusPHP.ts"; import { allCustomTags, allTagSelectors, selectorSearchProgress, selectorSearchStatus } from "./hdhome.ts"; diff --git a/src/packages/site/definitions/hdfans.ts b/src/packages/site/definitions/hdfans.ts index 40bb2d6e8..6cd3d8fad 100644 --- a/src/packages/site/definitions/hdfans.ts +++ b/src/packages/site/definitions/hdfans.ts @@ -1,3 +1,9 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/hdfans.yml + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/hdfans.org/config.json + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/hdfans.json + * @PTMDefinitions https://github.com/JustLookAtNow/pt_mate/blob/master/assets/sites/hdfans.json + */ import { type ISiteMetadata } from "../types"; import { parseSectionedHitAndRunElement, SchemaMetadata } from "../schemas/NexusPHP"; diff --git a/src/packages/site/definitions/hdhome.ts b/src/packages/site/definitions/hdhome.ts index 56f00e182..8dfbe6152 100644 --- a/src/packages/site/definitions/hdhome.ts +++ b/src/packages/site/definitions/hdhome.ts @@ -1,3 +1,8 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/hdhome.yml + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/hdhome.org/config.json + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/hd_home.json + */ import { ETorrentStatus, type IElementQuery, type ISiteMetadata } from "../types"; import { CategoryInclbookmarked, diff --git a/src/packages/site/definitions/hdkylin.ts b/src/packages/site/definitions/hdkylin.ts index 91aa4dc87..bf8568b0f 100644 --- a/src/packages/site/definitions/hdkylin.ts +++ b/src/packages/site/definitions/hdkylin.ts @@ -1,3 +1,8 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/hdkylin.yml + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/hdkyl.json + * @PTMDefinitions https://github.com/JustLookAtNow/pt_mate/blob/master/assets/sites/hdkyl.json + */ import { type ISiteMetadata } from "../types"; import { GB, TB } from "../utils"; import { CategoryIncldead, CategorySpstate, CategoryInclbookmarked, SchemaMetadata } from "../schemas/NexusPHP.ts"; diff --git a/src/packages/site/definitions/hdmayi.ts b/src/packages/site/definitions/hdmayi.ts index 69697ab3a..dcf867d30 100644 --- a/src/packages/site/definitions/hdmayi.ts +++ b/src/packages/site/definitions/hdmayi.ts @@ -1,3 +1,6 @@ +/** + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/hdmayi.com/config.json + */ import type { ISiteMetadata } from "../types"; export const siteMetadata: ISiteMetadata = { diff --git a/src/packages/site/definitions/hdpost.ts b/src/packages/site/definitions/hdpost.ts index a0d914da4..26862222f 100644 --- a/src/packages/site/definitions/hdpost.ts +++ b/src/packages/site/definitions/hdpost.ts @@ -1,3 +1,7 @@ +/** + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/pt.hdpost.top/config.json + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/hdpost.json + */ import type { ISiteMetadata } from "../types"; export const siteMetadata: ISiteMetadata = { diff --git a/src/packages/site/definitions/hdpt.ts b/src/packages/site/definitions/hdpt.ts index 280dce787..055b66acc 100644 --- a/src/packages/site/definitions/hdpt.ts +++ b/src/packages/site/definitions/hdpt.ts @@ -1,3 +1,7 @@ +/** + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/hdpt.xyz/config.json + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/hdpt.json + */ import type { ISiteMetadata } from "../types"; export const siteMetadata: ISiteMetadata = { diff --git a/src/packages/site/definitions/hdroute.ts b/src/packages/site/definitions/hdroute.ts index cb41d0977..b644b0784 100644 --- a/src/packages/site/definitions/hdroute.ts +++ b/src/packages/site/definitions/hdroute.ts @@ -1,5 +1,8 @@ /** - * @PTPPDefinitions HDRoute 完全自定义采集配置 + * HDRoute 完全自定义采集配置 + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/hdroute.yml + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/hdroute.org/config.json + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/hdroute.json */ import PrivateSite from "../schemas/AbstractPrivateSite"; import { type ISiteMetadata, type ITorrent, type ISearchInput } from "../types"; diff --git a/src/packages/site/definitions/hdsky.ts b/src/packages/site/definitions/hdsky.ts index 82c1f0e20..15d924c7c 100644 --- a/src/packages/site/definitions/hdsky.ts +++ b/src/packages/site/definitions/hdsky.ts @@ -1,3 +1,8 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/hdsky.yml + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/hdsky.me/config.json + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/hdsky.json + */ import urlJoin from "url-join"; import { omit } from "es-toolkit"; diff --git a/src/packages/site/definitions/hdtime.ts b/src/packages/site/definitions/hdtime.ts index 48c3a914a..63d493828 100644 --- a/src/packages/site/definitions/hdtime.ts +++ b/src/packages/site/definitions/hdtime.ts @@ -1,3 +1,8 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/hdtime.yml + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/hdtime.org/config.json + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/hd_time.json + */ import { type ISiteMetadata } from "../types"; import { SchemaMetadata } from "../schemas/NexusPHP.ts"; diff --git a/src/packages/site/definitions/hdupt.ts b/src/packages/site/definitions/hdupt.ts index 4e79732e4..3810c2ca6 100644 --- a/src/packages/site/definitions/hdupt.ts +++ b/src/packages/site/definitions/hdupt.ts @@ -1,3 +1,8 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/hdu.yml + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/pt.hdupt.com/config.json + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/hdu.json + */ import { ETorrentStatus, type ISiteMetadata } from "../types"; import { CategoryInclbookmarked, CategoryIncldead, SchemaMetadata } from "../schemas/NexusPHP.ts"; diff --git a/src/packages/site/definitions/hdvideo.ts b/src/packages/site/definitions/hdvideo.ts index c4fe295c1..a8314c4a9 100644 --- a/src/packages/site/definitions/hdvideo.ts +++ b/src/packages/site/definitions/hdvideo.ts @@ -1,3 +1,8 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/hdvideo.yml + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/hdvideo.one/config.json + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/hdvideo.json + */ import type { ISiteMetadata } from "../types"; import { SchemaMetadata } from "../schemas/NexusPHP"; diff --git a/src/packages/site/definitions/hdzone.ts b/src/packages/site/definitions/hdzone.ts index 6af8b3fe1..4a8eff6d5 100644 --- a/src/packages/site/definitions/hdzone.ts +++ b/src/packages/site/definitions/hdzone.ts @@ -1,3 +1,7 @@ +/** + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/hdzone.me/config.json + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/hd_zone.json + */ import type { ISiteMetadata } from "../types"; export const siteMetadata: ISiteMetadata = { diff --git a/src/packages/site/definitions/hhanclub.ts b/src/packages/site/definitions/hhanclub.ts index 4c3ad9bf7..a9ecb9eab 100644 --- a/src/packages/site/definitions/hhanclub.ts +++ b/src/packages/site/definitions/hhanclub.ts @@ -1,3 +1,9 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/hhanclub.yml + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/hhanclub.top/config.json + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/hhanclub.json + * @PTMDefinitions https://github.com/JustLookAtNow/pt_mate/blob/master/assets/sites/hhanclub.json + */ import { ETorrentStatus, type ISiteMetadata, IUserInfo } from "../types"; import NexusPHP, { CategoryInclbookmarked, diff --git a/src/packages/site/definitions/hitpt.ts b/src/packages/site/definitions/hitpt.ts index c2f0daaf9..de02249e8 100644 --- a/src/packages/site/definitions/hitpt.ts +++ b/src/packages/site/definitions/hitpt.ts @@ -1,3 +1,7 @@ +/** + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/hitpt.com/config.json + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/hitpt.json + */ import { type ISiteMetadata } from "../types"; import { CategoryInclbookmarked, CategoryIncldead, CategorySpstate, SchemaMetadata } from "../schemas/NexusPHP.ts"; diff --git a/src/packages/site/definitions/hspt.ts b/src/packages/site/definitions/hspt.ts index b61d4868d..da3304b43 100644 --- a/src/packages/site/definitions/hspt.ts +++ b/src/packages/site/definitions/hspt.ts @@ -1,3 +1,6 @@ +/** + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/hspt.json + */ import type { ISiteMetadata } from "../types"; export const siteMetadata: ISiteMetadata = { diff --git a/src/packages/site/definitions/htpt.ts b/src/packages/site/definitions/htpt.ts index 95ab01ea2..39e5bf445 100644 --- a/src/packages/site/definitions/htpt.ts +++ b/src/packages/site/definitions/htpt.ts @@ -1,3 +1,8 @@ +/** + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/htpt.cc/config.json + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/htpt.json + * @PTMDefinitions https://github.com/JustLookAtNow/pt_mate/blob/master/assets/sites/htpt.json + */ import { type ISiteMetadata } from "../types"; import { CategoryInclbookmarked, CategoryIncldead, CategorySpstate, SchemaMetadata } from "../schemas/NexusPHP.ts"; diff --git a/src/packages/site/definitions/hudbt.ts b/src/packages/site/definitions/hudbt.ts index dd8ab534a..1b3edef1e 100644 --- a/src/packages/site/definitions/hudbt.ts +++ b/src/packages/site/definitions/hudbt.ts @@ -1,3 +1,8 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/hudbt.yml + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/hudbt.hust.edu.cn/config.json + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/butterfly.json + */ import { type ISiteMetadata } from "../types"; import { CategoryInclbookmarked, CategorySpstate, SchemaMetadata } from "../schemas/NexusPHP.ts"; diff --git a/src/packages/site/definitions/hxpt.ts b/src/packages/site/definitions/hxpt.ts index f04ff94dd..0eff903eb 100644 --- a/src/packages/site/definitions/hxpt.ts +++ b/src/packages/site/definitions/hxpt.ts @@ -1,3 +1,8 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/hxpt.yml + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/hxpt.json + * @PTMDefinitions https://github.com/JustLookAtNow/pt_mate/blob/master/assets/sites/hxpt.json + */ import type { ISiteMetadata } from "../types"; import { CategoryInclbookmarked, CategoryIncldead, CategorySpstate, SchemaMetadata } from "../schemas/NexusPHP"; diff --git a/src/packages/site/definitions/icc2022.ts b/src/packages/site/definitions/icc2022.ts index 8efabe5fd..a7bbd351b 100644 --- a/src/packages/site/definitions/icc2022.ts +++ b/src/packages/site/definitions/icc2022.ts @@ -1,5 +1,7 @@ /** * 由 resource/sites/www.icc2022.com/config.json 自动转换而来 + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/icc2022.com/config.json + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/icc2022.json */ import { type ISiteMetadata } from "../types"; // import { CategoryInclbookmarked, CategoryIncldead, CategorySpstate, SchemaMetadata } from "../schemas/NexusPHP"; diff --git a/src/packages/site/definitions/ihdbits.ts b/src/packages/site/definitions/ihdbits.ts index 1f26574ee..fa383a8e5 100644 --- a/src/packages/site/definitions/ihdbits.ts +++ b/src/packages/site/definitions/ihdbits.ts @@ -1,3 +1,6 @@ +/** + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/ihdbits.me/config.json + */ import type { ISiteMetadata } from "../types"; export const siteMetadata: ISiteMetadata = { diff --git a/src/packages/site/definitions/ilolicon.ts b/src/packages/site/definitions/ilolicon.ts index 27a06055a..c3014fd19 100644 --- a/src/packages/site/definitions/ilolicon.ts +++ b/src/packages/site/definitions/ilolicon.ts @@ -1,3 +1,6 @@ +/** + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/ilolicon.json + */ import type { ISiteMetadata } from "../types"; import NexusPHP, { CategoryInclbookmarked, diff --git a/src/packages/site/definitions/iptorrents.ts b/src/packages/site/definitions/iptorrents.ts index 9e732ffc2..5abb6bb86 100644 --- a/src/packages/site/definitions/iptorrents.ts +++ b/src/packages/site/definitions/iptorrents.ts @@ -1,3 +1,8 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Indexers/Definitions/IPTorrents.cs + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/iptorrents.com/config.json + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/ipt.json + */ import { type IAdvancedSearchRequestConfig, type ISearchCategories, diff --git a/src/packages/site/definitions/itzmx.ts b/src/packages/site/definitions/itzmx.ts index b169bc0ed..9e1377d87 100644 --- a/src/packages/site/definitions/itzmx.ts +++ b/src/packages/site/definitions/itzmx.ts @@ -1,5 +1,6 @@ /** * FIXME 由 resource/sites/pt.itzmx.com/config.json 自动转换而来 + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/ptitzmx.yml */ import { type ISiteMetadata } from "../types"; import { CategoryInclbookmarked, CategoryIncldead, CategorySpstate, SchemaMetadata } from "../schemas/NexusPHP"; diff --git a/src/packages/site/definitions/joyhd.ts b/src/packages/site/definitions/joyhd.ts index ed6009819..52fe9b29e 100644 --- a/src/packages/site/definitions/joyhd.ts +++ b/src/packages/site/definitions/joyhd.ts @@ -1,3 +1,7 @@ +/** + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/joyhd.net/config.json + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/joyhd.json + */ import { type ISiteMetadata } from "../types"; // import { CategoryInclbookmarked, CategorySpstate, SchemaMetadata } from "../schemas/NexusPHP.ts"; diff --git a/src/packages/site/definitions/jpopsuki.ts b/src/packages/site/definitions/jpopsuki.ts index b5699659a..a9b1718da 100644 --- a/src/packages/site/definitions/jpopsuki.ts +++ b/src/packages/site/definitions/jpopsuki.ts @@ -1,3 +1,9 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/jpopsuki.yml + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/jpopsuki.eu/config.json + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/jpop.json + * @PTMDefinitions https://github.com/JustLookAtNow/pt_mate/blob/master/assets/sites/jpopsuki.json + */ import { ISiteMetadata, ITorrent, IUserInfo, ISearchInput } from "../types"; import Gazelle, { SchemaMetadata, diff --git a/src/packages/site/definitions/jptv4us.ts b/src/packages/site/definitions/jptv4us.ts index 04aa6e73f..ec69da1b2 100644 --- a/src/packages/site/definitions/jptv4us.ts +++ b/src/packages/site/definitions/jptv4us.ts @@ -1,3 +1,6 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/jptv4us-api.yml + */ import { type ISiteMetadata } from "../types.ts"; import { SchemaMetadata } from "../schemas/Unit3D.ts"; import { buildCategoryOptionsFromList } from "../utils"; diff --git a/src/packages/site/definitions/jptvclub.ts b/src/packages/site/definitions/jptvclub.ts index 9634f885b..87e67874f 100644 --- a/src/packages/site/definitions/jptvclub.ts +++ b/src/packages/site/definitions/jptvclub.ts @@ -1,3 +1,6 @@ +/** + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/jptv.club/config.json + */ import type { ISiteMetadata } from "../types"; export const siteMetadata: ISiteMetadata = { diff --git a/src/packages/site/definitions/kamept.ts b/src/packages/site/definitions/kamept.ts index 2a60fd9fb..ca6e7dc86 100644 --- a/src/packages/site/definitions/kamept.ts +++ b/src/packages/site/definitions/kamept.ts @@ -1,3 +1,8 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/kamept.yml + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/kamept.com/config.json + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/kamept.json + */ import type { ISiteMetadata } from "../types"; import { CategoryInclbookmarked, diff --git a/src/packages/site/definitions/karagarga.ts b/src/packages/site/definitions/karagarga.ts index a1ef53854..2ca592870 100644 --- a/src/packages/site/definitions/karagarga.ts +++ b/src/packages/site/definitions/karagarga.ts @@ -1,3 +1,8 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/karagarga.yml + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/karagarga.in/config.json + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/karagarga.json + */ import PrivateSite from "../schemas/AbstractPrivateSite.ts"; import type { ISearchInput, ISiteMetadata, ITorrent, ITorrentTag, IUserInfo } from "../types"; import { parseSizeString, parseValidTimeString } from "../utils"; diff --git a/src/packages/site/definitions/keepfrds.ts b/src/packages/site/definitions/keepfrds.ts index 024901a07..4b328f9e0 100644 --- a/src/packages/site/definitions/keepfrds.ts +++ b/src/packages/site/definitions/keepfrds.ts @@ -1,3 +1,9 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/keepfriends.yml + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/pt.keepfrds.com/config.json + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/keepfrds.json + * @PTMDefinitions https://github.com/JustLookAtNow/pt_mate/blob/master/assets/sites/frds.json + */ import { type ISiteMetadata, type IUserInfo } from "../types"; import NexusPHP, { CategoryInclbookmarked, diff --git a/src/packages/site/definitions/kelu.ts b/src/packages/site/definitions/kelu.ts index 606e051a4..770c2f435 100644 --- a/src/packages/site/definitions/kelu.ts +++ b/src/packages/site/definitions/kelu.ts @@ -1,3 +1,7 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/kelu.yml + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/kelu.json + */ import type { ISiteMetadata } from "../types"; import { SchemaMetadata } from "../schemas/NexusPHP"; diff --git a/src/packages/site/definitions/kufei.ts b/src/packages/site/definitions/kufei.ts index 535a19297..da0cd9e39 100644 --- a/src/packages/site/definitions/kufei.ts +++ b/src/packages/site/definitions/kufei.ts @@ -1,5 +1,7 @@ /** * FIXME 由 resource/sites/kufei.org/config.json 自动转换而来 + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/kufei.yml + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/kufei.json */ import { type ISiteMetadata } from "../types"; import { CategoryInclbookmarked, CategoryIncldead, CategorySpstate, SchemaMetadata } from "../schemas/NexusPHP"; diff --git a/src/packages/site/definitions/kufirc.ts b/src/packages/site/definitions/kufirc.ts index cba98dd97..198e3b228 100644 --- a/src/packages/site/definitions/kufirc.ts +++ b/src/packages/site/definitions/kufirc.ts @@ -1,3 +1,7 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/kufirc.yml + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/kufirc.json + */ import { type ISiteMetadata } from "../types"; import { SchemaMetadata } from "../schemas/Luminance"; import { buildCategoryOptionsFromList } from "../utils"; diff --git a/src/packages/site/definitions/kunlun.ts b/src/packages/site/definitions/kunlun.ts index 497c4c31f..1f03d5bf5 100644 --- a/src/packages/site/definitions/kunlun.ts +++ b/src/packages/site/definitions/kunlun.ts @@ -1,3 +1,6 @@ +/** + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/yhpp.json + */ import type { ISiteMetadata } from "../types"; import { SchemaMetadata } from "../schemas/NexusPHP"; diff --git a/src/packages/site/definitions/lajidui.ts b/src/packages/site/definitions/lajidui.ts index 0f2d296a5..73805503b 100644 --- a/src/packages/site/definitions/lajidui.ts +++ b/src/packages/site/definitions/lajidui.ts @@ -1,3 +1,8 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/lajidui.yml + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/lajidui.json + * @PTMDefinitions https://github.com/JustLookAtNow/pt_mate/blob/master/assets/sites/lajidui.json + */ import type { ISiteMetadata } from "../types"; import { SchemaMetadata } from "../schemas/NexusPHP"; diff --git a/src/packages/site/definitions/lemonhd.ts b/src/packages/site/definitions/lemonhd.ts index a1f4dd206..f943b9ff8 100644 --- a/src/packages/site/definitions/lemonhd.ts +++ b/src/packages/site/definitions/lemonhd.ts @@ -1,3 +1,7 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/lemonhd-net.yml + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/lemonhd_club.json + */ import type { ISiteMetadata } from "../types"; //import { CategoryInclbookmarked, CategoryIncldead, CategorySpstate, SchemaMetadata } from "../schemas/NexusPHP"; diff --git a/src/packages/site/definitions/longpt.ts b/src/packages/site/definitions/longpt.ts index 557bdd837..e7bd72d74 100644 --- a/src/packages/site/definitions/longpt.ts +++ b/src/packages/site/definitions/longpt.ts @@ -1,3 +1,7 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/longpt.yml + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/longpt.json + */ import type { ISiteMetadata } from "../types"; import { CategoryInclbookmarked, CategoryIncldead, CategorySpstate, SchemaMetadata } from "../schemas/NexusPHP"; diff --git a/src/packages/site/definitions/lst.ts b/src/packages/site/definitions/lst.ts index 3f382e339..80b697cf6 100644 --- a/src/packages/site/definitions/lst.ts +++ b/src/packages/site/definitions/lst.ts @@ -1,3 +1,7 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/lst.yml + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/lst.json + */ import { type ISiteMetadata } from "../types"; import { CategoryFree, SchemaMetadata, userInfoTrans } from "../schemas/Unit3D.ts"; diff --git a/src/packages/site/definitions/luckpt.ts b/src/packages/site/definitions/luckpt.ts index 0c2ae2843..cb08c77fc 100644 --- a/src/packages/site/definitions/luckpt.ts +++ b/src/packages/site/definitions/luckpt.ts @@ -1,3 +1,8 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/luckpt.yml + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/luckpt.json + * @PTMDefinitions https://github.com/JustLookAtNow/pt_mate/blob/master/assets/sites/luckpt.json + */ import type { ISiteMetadata } from "../types"; import { SchemaMetadata } from "../schemas/NexusPHP"; diff --git a/src/packages/site/definitions/luminarr.ts b/src/packages/site/definitions/luminarr.ts index b4a90d189..6c109413e 100644 --- a/src/packages/site/definitions/luminarr.ts +++ b/src/packages/site/definitions/luminarr.ts @@ -1,3 +1,7 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/luminarr-api.yml + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/luminarr.json + */ import { type ISiteMetadata } from "../types"; import { CategoryFree, SchemaMetadata } from "../schemas/Unit3D"; import { buildCategoryOptionsFromDict } from "../utils"; diff --git a/src/packages/site/definitions/march.ts b/src/packages/site/definitions/march.ts index 052dfe40a..eb14fc281 100644 --- a/src/packages/site/definitions/march.ts +++ b/src/packages/site/definitions/march.ts @@ -1,3 +1,7 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/march.yml + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/march.json + */ import { type ISiteMetadata } from "../types"; import { SchemaMetadata } from "../schemas/NexusPHP"; diff --git a/src/packages/site/definitions/midnightscene.ts b/src/packages/site/definitions/midnightscene.ts index 8a39e5484..fffc33177 100644 --- a/src/packages/site/definitions/midnightscene.ts +++ b/src/packages/site/definitions/midnightscene.ts @@ -1,3 +1,6 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/midnightscene.yml + */ import type { ISiteMetadata } from "../types"; import { CategoryFree, SchemaMetadata } from "../schemas/Unit3D.ts"; import { buildCategoryOptionsFromDict } from "../utils.ts"; diff --git a/src/packages/site/definitions/milkie.ts b/src/packages/site/definitions/milkie.ts index 430af987c..398afb03f 100644 --- a/src/packages/site/definitions/milkie.ts +++ b/src/packages/site/definitions/milkie.ts @@ -1,3 +1,6 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/milkie.yml + */ import type { ISiteMetadata, ITorrent } from "../types"; import { AxiosRequestConfig, AxiosResponse } from "axios"; import PrivateSite from "../schemas/AbstractPrivateSite"; diff --git a/src/packages/site/definitions/momentpt.ts b/src/packages/site/definitions/momentpt.ts index 4c0e4a346..0b00e7d81 100644 --- a/src/packages/site/definitions/momentpt.ts +++ b/src/packages/site/definitions/momentpt.ts @@ -1,6 +1,9 @@ /** * 新站点模板 - NP架构 * 基于NexusPHP架构的通用站点配置 + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/momentpt.yml + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/momentpt.json + * @PTMDefinitions https://github.com/JustLookAtNow/pt_mate/blob/master/assets/sites/momentpt.json */ import { type ISiteMetadata } from "../types"; import { CategoryInclbookmarked, CategoryIncldead, CategorySpstate, SchemaMetadata } from "../schemas/NexusPHP"; diff --git a/src/packages/site/definitions/monikadesign.ts b/src/packages/site/definitions/monikadesign.ts index d6c80e29a..bf4a23e23 100644 --- a/src/packages/site/definitions/monikadesign.ts +++ b/src/packages/site/definitions/monikadesign.ts @@ -1,3 +1,9 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/monikadesign-api.yml + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/monikadesign.uk/config.json + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/monikadesign.json + * @PTMDefinitions https://github.com/JustLookAtNow/pt_mate/blob/master/assets/sites/monikadesign.json + */ import { ETorrentStatus, type ISiteMetadata, type ITorrent } from "../types"; import Unit3D, { SchemaMetadata } from "../schemas/Unit3D.ts"; import AbstractBittorrentSite from "../schemas/AbstractBittorrentSite"; diff --git a/src/packages/site/definitions/morethantv.ts b/src/packages/site/definitions/morethantv.ts index e6aa5a4e8..192d30d46 100644 --- a/src/packages/site/definitions/morethantv.ts +++ b/src/packages/site/definitions/morethantv.ts @@ -1,3 +1,6 @@ +/** + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/morethantv.me/config.json + */ import { ISearchCategoryOptions, type ISiteMetadata, type ITorrent } from "../types"; import Luminance, { SchemaMetadata } from "../schemas/Luminance"; diff --git a/src/packages/site/definitions/mteam.ts b/src/packages/site/definitions/mteam.ts index f17769586..752804cde 100644 --- a/src/packages/site/definitions/mteam.ts +++ b/src/packages/site/definitions/mteam.ts @@ -1,3 +1,8 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Indexers/Definitions/MTeamTp.cs + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/mteam.json + * @PTMDefinitions https://github.com/JustLookAtNow/pt_mate/blob/master/assets/sites/mteam.json + */ import type { AxiosRequestConfig, AxiosResponse } from "axios"; import { set } from "es-toolkit/compat"; import { build as buildDouban } from "@ptd/social/entity/douban.ts"; diff --git a/src/packages/site/definitions/musopia.ts b/src/packages/site/definitions/musopia.ts index 774660fbc..bdfc9b258 100644 --- a/src/packages/site/definitions/musopia.ts +++ b/src/packages/site/definitions/musopia.ts @@ -1,3 +1,6 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/musopia.yml + */ import type { ISiteMetadata } from "../types"; import { CategoryInclbookmarked, CategoryIncldead, CategorySpstate, SchemaMetadata } from "../schemas/NexusPHP"; diff --git a/src/packages/site/definitions/muxuege.ts b/src/packages/site/definitions/muxuege.ts index 1adab8ce1..4658741ae 100644 --- a/src/packages/site/definitions/muxuege.ts +++ b/src/packages/site/definitions/muxuege.ts @@ -1,3 +1,7 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/muxuege.yml + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/muxuege.json + */ import type { ISiteMetadata } from "../types"; import { CategoryInclbookmarked, CategoryIncldead, CategorySpstate, SchemaMetadata } from "../schemas/NexusPHP"; diff --git a/src/packages/site/definitions/myanonamouse.ts b/src/packages/site/definitions/myanonamouse.ts index a5ff355e0..7395c7edd 100644 --- a/src/packages/site/definitions/myanonamouse.ts +++ b/src/packages/site/definitions/myanonamouse.ts @@ -1,3 +1,7 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Indexers/Definitions/MyAnonamouse.cs + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/myanonamouse.net/config.json + */ import { type ISiteMetadata } from "../types"; const categoryOptions = [ diff --git a/src/packages/site/definitions/mypt.ts b/src/packages/site/definitions/mypt.ts index 770f57399..10fb5f66b 100644 --- a/src/packages/site/definitions/mypt.ts +++ b/src/packages/site/definitions/mypt.ts @@ -1,3 +1,6 @@ +/** + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/mypt.json + */ import { type ISiteMetadata } from "../types"; import { SchemaMetadata } from "../schemas/NexusPHP"; diff --git a/src/packages/site/definitions/nanyangpt.ts b/src/packages/site/definitions/nanyangpt.ts index 85b21eb4c..b57eca089 100644 --- a/src/packages/site/definitions/nanyangpt.ts +++ b/src/packages/site/definitions/nanyangpt.ts @@ -1,3 +1,8 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/nanyangpt.yml + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/nanyangpt.com/config.json + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/nanyangpt.json + */ import { ETorrentStatus, type ISiteMetadata } from "../types"; import { CategoryInclbookmarked, CategorySpstate, SchemaMetadata } from "../schemas/NexusPHP.ts"; diff --git a/src/packages/site/definitions/ncore.ts b/src/packages/site/definitions/ncore.ts index f5a29e3f8..edd9bcd2d 100644 --- a/src/packages/site/definitions/ncore.ts +++ b/src/packages/site/definitions/ncore.ts @@ -1,3 +1,7 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/ncore.yml + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/ncore.json + */ import type { ISiteMetadata } from "../types"; import { parseTimeToLiveToDate } from "../utils/datetime.ts"; diff --git a/src/packages/site/definitions/nebulance.ts b/src/packages/site/definitions/nebulance.ts index 2f1a33526..9af7a4cec 100644 --- a/src/packages/site/definitions/nebulance.ts +++ b/src/packages/site/definitions/nebulance.ts @@ -1,3 +1,7 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Indexers/Definitions/NebulanceAPI.cs + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/nebulance.io/config.json + */ import { ISiteMetadata, ITorrent, ISearchInput, ETorrentStatus } from "../types"; import Sizzle from "sizzle"; import Gazelle, { SchemaMetadata, top10PageList } from "../schemas/Gazelle.ts"; diff --git a/src/packages/site/definitions/nekobt.ts b/src/packages/site/definitions/nekobt.ts index 56f9cf3e3..5051e962f 100644 --- a/src/packages/site/definitions/nekobt.ts +++ b/src/packages/site/definitions/nekobt.ts @@ -1,3 +1,6 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/nekobt.yml + */ import type { ISearchConfig, ISiteMetadata, ITorrent, ITorrentTag } from "../types"; import BittorrentSite from "../schemas/AbstractBittorrentSite"; diff --git a/src/packages/site/definitions/neu6.ts b/src/packages/site/definitions/neu6.ts index 693af0aab..c8d6bc1ad 100644 --- a/src/packages/site/definitions/neu6.ts +++ b/src/packages/site/definitions/neu6.ts @@ -1,3 +1,6 @@ +/** + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/bt.neu6.edu.cn/config.json + */ import type { ISiteMetadata } from "../types"; export const siteMetadata: ISiteMetadata = { diff --git a/src/packages/site/definitions/nicept.ts b/src/packages/site/definitions/nicept.ts index 6079163a0..183915b53 100644 --- a/src/packages/site/definitions/nicept.ts +++ b/src/packages/site/definitions/nicept.ts @@ -1,3 +1,9 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/nicept.yml + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/nicept.net/config.json + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/nicept.json + * @PTMDefinitions https://github.com/JustLookAtNow/pt_mate/blob/master/assets/sites/nicept.json + */ import type { ISiteMetadata } from "../types"; import { SchemaMetadata, xiaomloveDefaultUserLevelRequirements } from "../schemas/NexusPHP"; diff --git a/src/packages/site/definitions/njtupt.ts b/src/packages/site/definitions/njtupt.ts index 8652700e1..ea550feb3 100644 --- a/src/packages/site/definitions/njtupt.ts +++ b/src/packages/site/definitions/njtupt.ts @@ -1,3 +1,7 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/njtupt.yml + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/njtupt.json + */ import type { ISiteMetadata } from "../types"; import { CategoryInclbookmarked, diff --git a/src/packages/site/definitions/nordicbytes.ts b/src/packages/site/definitions/nordicbytes.ts index e79853bbd..9f83f14af 100644 --- a/src/packages/site/definitions/nordicbytes.ts +++ b/src/packages/site/definitions/nordicbytes.ts @@ -1,3 +1,6 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/nordicbytes.yml + */ import { type ISiteMetadata } from "../types"; import { SchemaMetadata } from "../schemas/Unit3D.ts"; import { buildCategoryOptionsFromDict } from "../utils.ts"; diff --git a/src/packages/site/definitions/nordicquality.ts b/src/packages/site/definitions/nordicquality.ts index 4b530b268..cee7932e8 100644 --- a/src/packages/site/definitions/nordicquality.ts +++ b/src/packages/site/definitions/nordicquality.ts @@ -1,3 +1,6 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/nordicquality.yml + */ import { type ISiteMetadata } from "../types"; import { SchemaMetadata } from "../schemas/Unit3D.ts"; diff --git a/src/packages/site/definitions/novahd.ts b/src/packages/site/definitions/novahd.ts index 7aaa0912a..70341dcf2 100644 --- a/src/packages/site/definitions/novahd.ts +++ b/src/packages/site/definitions/novahd.ts @@ -1,3 +1,7 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/novahd.yml + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/novahd.json + */ import type { ISiteMetadata } from "../types"; import { CategoryInclbookmarked, CategoryIncldead, CategorySpstate, SchemaMetadata } from "../schemas/NexusPHP"; diff --git a/src/packages/site/definitions/npupt.ts b/src/packages/site/definitions/npupt.ts index de8594bf2..2c948bfce 100644 --- a/src/packages/site/definitions/npupt.ts +++ b/src/packages/site/definitions/npupt.ts @@ -1,3 +1,6 @@ +/** + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/npupt.com/config.json + */ import type { ISiteMetadata } from "../types"; export const siteMetadata: ISiteMetadata = { diff --git a/src/packages/site/definitions/nyaa.ts b/src/packages/site/definitions/nyaa.ts index 3fe07c5fa..431b09643 100644 --- a/src/packages/site/definitions/nyaa.ts +++ b/src/packages/site/definitions/nyaa.ts @@ -1,3 +1,6 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/nyaasi.yml + */ import { type ISiteMetadata, type ITorrent } from "../types"; import BittorrentSite from "../schemas/AbstractBittorrentSite"; import { rot13 } from "../utils"; diff --git a/src/packages/site/definitions/okpt.ts b/src/packages/site/definitions/okpt.ts index 9c1b5cd96..23d3b6ff2 100644 --- a/src/packages/site/definitions/okpt.ts +++ b/src/packages/site/definitions/okpt.ts @@ -1,3 +1,8 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/okpt.yml + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/okpt.net/config.json + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/okpt.json + */ import { type ISiteMetadata } from "../types"; import { CategoryInclbookmarked, CategoryIncldead, CategorySpstate, SchemaMetadata } from "../schemas/NexusPHP.ts"; diff --git a/src/packages/site/definitions/oldtoonsworld.ts b/src/packages/site/definitions/oldtoonsworld.ts index 8cf420d13..e4eb8cb1b 100644 --- a/src/packages/site/definitions/oldtoonsworld.ts +++ b/src/packages/site/definitions/oldtoonsworld.ts @@ -1,3 +1,7 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/oldtoonsworld.yml + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/oldtoons.json + */ import { type ISiteMetadata } from "../types"; import { SchemaMetadata } from "../schemas/Unit3D.ts"; diff --git a/src/packages/site/definitions/onlyencodes.ts b/src/packages/site/definitions/onlyencodes.ts index c08eaf5ab..ae126a651 100644 --- a/src/packages/site/definitions/onlyencodes.ts +++ b/src/packages/site/definitions/onlyencodes.ts @@ -1,3 +1,6 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/onlyencodes-api.yml + */ import type { ISiteMetadata } from "../types"; import { CategoryFree, SchemaMetadata } from "../schemas/Unit3D.ts"; import { buildCategoryOptionsFromDict } from "../utils.ts"; diff --git a/src/packages/site/definitions/opencd.ts b/src/packages/site/definitions/opencd.ts index 60266bff7..44dd6e746 100644 --- a/src/packages/site/definitions/opencd.ts +++ b/src/packages/site/definitions/opencd.ts @@ -1,3 +1,7 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/opencd.yml + * @PTMDefinitions https://github.com/JustLookAtNow/pt_mate/blob/master/assets/sites/opencd.json + */ import { ETorrentStatus, type IElementQuery, type ITorrent, type ISiteMetadata } from "../types"; import NexusPHP, { CategoryInclbookmarked, diff --git a/src/packages/site/definitions/orpheus.ts b/src/packages/site/definitions/orpheus.ts index a65ace08c..56d7e1369 100644 --- a/src/packages/site/definitions/orpheus.ts +++ b/src/packages/site/definitions/orpheus.ts @@ -1,3 +1,7 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Indexers/Definitions/Orpheus.cs + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/orpheus.network/config.json + */ import type { ISiteMetadata, IUserInfo } from "../types"; import GazelleJSONAPI, { SchemaMetadata } from "../schemas/GazelleJSONAPI.ts"; diff --git a/src/packages/site/definitions/oshenpt.ts b/src/packages/site/definitions/oshenpt.ts index 90e4ae52d..971541c1d 100644 --- a/src/packages/site/definitions/oshenpt.ts +++ b/src/packages/site/definitions/oshenpt.ts @@ -1,5 +1,7 @@ /** * FIXME 由 resource/sites/www.oshen.win/config.json 自动转换而来 + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/oshenpt.yml + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/oshenpt.json */ import { type ISiteMetadata } from "../types"; import { CategoryInclbookmarked, CategoryIncldead, CategorySpstate, SchemaMetadata } from "../schemas/NexusPHP"; diff --git a/src/packages/site/definitions/ourbits.ts b/src/packages/site/definitions/ourbits.ts index c33c361f9..7f7b3b29b 100644 --- a/src/packages/site/definitions/ourbits.ts +++ b/src/packages/site/definitions/ourbits.ts @@ -1,3 +1,9 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/ourbits.yml + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/ourbits.club/config.json + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/ourbits.json + * @PTMDefinitions https://github.com/JustLookAtNow/pt_mate/blob/master/assets/sites/ourbits.json + */ import { set } from "es-toolkit/compat"; import { ETorrentStatus, type ISiteMetadata } from "../types"; import { GB, TB } from "../utils"; diff --git a/src/packages/site/definitions/pandapt.ts b/src/packages/site/definitions/pandapt.ts index 6e226d881..1edbbb65e 100644 --- a/src/packages/site/definitions/pandapt.ts +++ b/src/packages/site/definitions/pandapt.ts @@ -1,3 +1,7 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/panda.yml + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/pandapt.json + */ import { type ISiteMetadata } from "../types"; import { CategoryInclbookmarked, CategoryIncldead, CategorySpstate, SchemaMetadata } from "../schemas/NexusPHP.ts"; diff --git a/src/packages/site/definitions/passthepopcorn.ts b/src/packages/site/definitions/passthepopcorn.ts index e3674afa6..84d7c066e 100644 --- a/src/packages/site/definitions/passthepopcorn.ts +++ b/src/packages/site/definitions/passthepopcorn.ts @@ -1,3 +1,7 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Indexers/Definitions/PassThePopcorn.cs + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/passthepopcorn.me/config.json + */ import Sizzle from "sizzle"; import Gazelle, { SchemaMetadata } from "../schemas/Gazelle.ts"; import { parseValidTimeString, parseSizeString, buildCategoryOptionsFromList } from "../utils"; diff --git a/src/packages/site/definitions/piggo.ts b/src/packages/site/definitions/piggo.ts index f13fc7295..d5b35464f 100644 --- a/src/packages/site/definitions/piggo.ts +++ b/src/packages/site/definitions/piggo.ts @@ -1,3 +1,8 @@ +/** + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/piggo.me/config.json + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/piggo.json + * @PTMDefinitions https://github.com/JustLookAtNow/pt_mate/blob/master/assets/sites/piggo.json + */ import type { ISiteMetadata } from "../types"; import { parseSectionedHitAndRunElement, SchemaMetadata } from "../schemas/NexusPHP"; diff --git a/src/packages/site/definitions/pixelcove.ts b/src/packages/site/definitions/pixelcove.ts index 3520c5bd9..6fb662763 100644 --- a/src/packages/site/definitions/pixelcove.ts +++ b/src/packages/site/definitions/pixelcove.ts @@ -1,3 +1,6 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/pixelcove.yml + */ import { type ISiteMetadata } from "../types"; import { SchemaMetadata } from "../schemas/Luminance"; import { buildCategoryOptionsFromDict } from "../utils"; diff --git a/src/packages/site/definitions/playletpt.ts b/src/packages/site/definitions/playletpt.ts index bbed8a7bf..d81ac15b8 100644 --- a/src/packages/site/definitions/playletpt.ts +++ b/src/packages/site/definitions/playletpt.ts @@ -1,5 +1,7 @@ /** * FIXME 由 resource/sites/playletpt.xyz/config.json 自动转换而来 + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/playletpt.yml + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/playletpt.json */ import { type ISiteMetadata } from "../types"; import { CategoryInclbookmarked, CategoryIncldead, CategorySpstate, SchemaMetadata } from "../schemas/NexusPHP"; diff --git a/src/packages/site/definitions/portugas.ts b/src/packages/site/definitions/portugas.ts index 5c2c91005..b6e015fd4 100644 --- a/src/packages/site/definitions/portugas.ts +++ b/src/packages/site/definitions/portugas.ts @@ -1,3 +1,7 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/portugas-api.yml + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/portugas.json + */ import { type ISiteMetadata } from "../types"; import { CategoryFree, SchemaMetadata, userInfoTrans } from "../schemas/Unit3D.ts"; import { buildCategoryOptionsFromDict } from "../utils.ts"; diff --git a/src/packages/site/definitions/privatehd.ts b/src/packages/site/definitions/privatehd.ts index 2d567992c..930c3c9a7 100644 --- a/src/packages/site/definitions/privatehd.ts +++ b/src/packages/site/definitions/privatehd.ts @@ -1,3 +1,6 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Indexers/Definitions/PrivateHD.cs + */ import { ISiteMetadata, ISearchInput, ITorrent, ITorrentTag } from "../types"; import AvistazNetwork, { SchemaMetadata, IAvzNetRawTorrent } from "../schemas/AvistazNetwork.ts"; diff --git a/src/packages/site/definitions/ptcafe.ts b/src/packages/site/definitions/ptcafe.ts index 0300b0b69..cca789e17 100644 --- a/src/packages/site/definitions/ptcafe.ts +++ b/src/packages/site/definitions/ptcafe.ts @@ -1,3 +1,7 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/ptcafe.yml + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/ptcafe.json + */ import { type ISiteMetadata } from "../types"; import { CategoryInclbookmarked, CategoryIncldead, CategorySpstate, SchemaMetadata } from "../schemas/NexusPHP.ts"; diff --git a/src/packages/site/definitions/ptchina.ts b/src/packages/site/definitions/ptchina.ts index 0d93e4046..56af1ded9 100644 --- a/src/packages/site/definitions/ptchina.ts +++ b/src/packages/site/definitions/ptchina.ts @@ -1,3 +1,7 @@ +/** + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/ptchina.org/config.json + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/ptchina.json + */ import { type ISiteMetadata } from "../types"; // import { CategoryInclbookmarked, CategoryIncldead, CategorySpstate, SchemaMetadata } from "../schemas/NexusPHP.ts"; diff --git a/src/packages/site/definitions/pter.ts b/src/packages/site/definitions/pter.ts index c67fd54ff..262eb482d 100644 --- a/src/packages/site/definitions/pter.ts +++ b/src/packages/site/definitions/pter.ts @@ -1,3 +1,8 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/pterclub.yml + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/pterclub.com/config.json + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/pterclub.json + */ import { set } from "es-toolkit/compat"; import Sizzle from "sizzle"; import { diff --git a/src/packages/site/definitions/ptfans.ts b/src/packages/site/definitions/ptfans.ts index 4ac92afb4..b624975d3 100644 --- a/src/packages/site/definitions/ptfans.ts +++ b/src/packages/site/definitions/ptfans.ts @@ -1,3 +1,8 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/ptfans.yml + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/ptfans.json + * @PTMDefinitions https://github.com/JustLookAtNow/pt_mate/blob/master/assets/sites/ptfans.json + */ import type { ISiteMetadata } from "../types"; import { CategoryInclbookmarked, diff --git a/src/packages/site/definitions/ptgtk.ts b/src/packages/site/definitions/ptgtk.ts index 0d3147098..27314b0fa 100644 --- a/src/packages/site/definitions/ptgtk.ts +++ b/src/packages/site/definitions/ptgtk.ts @@ -1,5 +1,8 @@ /** * FIXME 由 resource/sites/pt.gtk.pw/config.json 自动转换而来 + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/ptgtk.yml + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/ptgtk.json + * @PTMDefinitions https://github.com/JustLookAtNow/pt_mate/blob/master/assets/sites/ptgtk.json */ import { type ISiteMetadata } from "../types"; import { CategoryInclbookmarked, CategoryIncldead, CategorySpstate, SchemaMetadata } from "../schemas/NexusPHP"; diff --git a/src/packages/site/definitions/pthome.ts b/src/packages/site/definitions/pthome.ts index 86e6367f9..d2f541c1f 100644 --- a/src/packages/site/definitions/pthome.ts +++ b/src/packages/site/definitions/pthome.ts @@ -1,3 +1,8 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/pthome.yml + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/pthome.net/config.json + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/pthome.json + */ import type { ISiteMetadata } from "../types"; import { SchemaMetadata, subTitleRemoveExtraElement } from "../schemas/NexusPHP"; diff --git a/src/packages/site/definitions/ptlao.ts b/src/packages/site/definitions/ptlao.ts index f88819b1e..1fe618811 100644 --- a/src/packages/site/definitions/ptlao.ts +++ b/src/packages/site/definitions/ptlao.ts @@ -1,3 +1,6 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/ptlao.yml + */ import { type ISiteMetadata } from "../types"; import { SchemaMetadata } from "../schemas/NexusPHP"; diff --git a/src/packages/site/definitions/ptlgs.ts b/src/packages/site/definitions/ptlgs.ts index c34ec90df..a68563812 100644 --- a/src/packages/site/definitions/ptlgs.ts +++ b/src/packages/site/definitions/ptlgs.ts @@ -1,3 +1,7 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/ptlgs.yml + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/ptlgs.json + */ import { type ISiteMetadata } from "../types"; import { CategoryInclbookmarked, CategoryIncldead, CategorySpstate, SchemaMetadata } from "../schemas/NexusPHP"; diff --git a/src/packages/site/definitions/ptsbao.ts b/src/packages/site/definitions/ptsbao.ts index ef8c643cb..b500b7c3e 100644 --- a/src/packages/site/definitions/ptsbao.ts +++ b/src/packages/site/definitions/ptsbao.ts @@ -1,3 +1,8 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/ptsbao.yml + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/ptsbao.club/config.json + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/ptsbao.json + */ import type { ISiteMetadata } from "../types"; import { SchemaMetadata } from "../schemas/NexusPHP"; diff --git a/src/packages/site/definitions/ptskit.ts b/src/packages/site/definitions/ptskit.ts index 70549836f..93871314d 100644 --- a/src/packages/site/definitions/ptskit.ts +++ b/src/packages/site/definitions/ptskit.ts @@ -1,3 +1,8 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/ptskit.yml + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/ptskit.json + * @PTMDefinitions https://github.com/JustLookAtNow/pt_mate/blob/master/assets/sites/ptskit.json + */ import { type ISiteMetadata } from "../types"; import { CategoryInclbookmarked, CategoryIncldead, CategorySpstate, SchemaMetadata } from "../schemas/NexusPHP.ts"; diff --git a/src/packages/site/definitions/pttime.ts b/src/packages/site/definitions/pttime.ts index 28c7028f6..e9244f6b3 100644 --- a/src/packages/site/definitions/pttime.ts +++ b/src/packages/site/definitions/pttime.ts @@ -1,3 +1,9 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/pttime.yml + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/pttime.org/config.json + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/pttime.json + * @PTMDefinitions https://github.com/JustLookAtNow/pt_mate/blob/master/assets/sites/pttime.json + */ import { ISiteMetadata, IUserInfo } from "../types"; import NexusPHP, { CategoryIncldead, CategorySpstate, SchemaMetadata } from "../schemas/NexusPHP"; import { createDocument, parseSizeString } from "../utils"; diff --git a/src/packages/site/definitions/ptvicomo.ts b/src/packages/site/definitions/ptvicomo.ts index eb0ddf34e..e84ddca9d 100644 --- a/src/packages/site/definitions/ptvicomo.ts +++ b/src/packages/site/definitions/ptvicomo.ts @@ -1,5 +1,6 @@ /** * 由 resource/sites/ptvicomo.net/config.json 自动转换而来 + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/ptvicomo.json */ import { type ISiteMetadata } from "../types"; // import { CategoryInclbookmarked, CategoryIncldead, CategorySpstate, SchemaMetadata } from "../schemas/NexusPHP"; diff --git a/src/packages/site/definitions/ptzone.ts b/src/packages/site/definitions/ptzone.ts index 360fdde1c..c0cebd100 100644 --- a/src/packages/site/definitions/ptzone.ts +++ b/src/packages/site/definitions/ptzone.ts @@ -1,3 +1,9 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/ptzone.yml + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/ptzone.xyz/config.json + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/ptzone.json + * @PTMDefinitions https://github.com/JustLookAtNow/pt_mate/blob/master/assets/sites/ptzone.json + */ import type { ISiteMetadata } from "../types"; import { SchemaMetadata, xiaomloveDefaultUserLevelRequirements } from "../schemas/NexusPHP"; diff --git a/src/packages/site/definitions/pussytorrents.ts b/src/packages/site/definitions/pussytorrents.ts index 62e4d21c8..7377a8d37 100644 --- a/src/packages/site/definitions/pussytorrents.ts +++ b/src/packages/site/definitions/pussytorrents.ts @@ -1,3 +1,7 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/pussytorrents.yml + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/pussytorrents.org/config.json + */ import type { AxiosRequestConfig, AxiosResponse } from "axios"; import type { ISearchInput, ISiteMetadata, ITorrent, IUserInfo } from "../types"; import { EResultParseStatus } from "../types"; diff --git a/src/packages/site/definitions/qingwa.ts b/src/packages/site/definitions/qingwa.ts index 5cc42a078..0532901e5 100644 --- a/src/packages/site/definitions/qingwa.ts +++ b/src/packages/site/definitions/qingwa.ts @@ -1,3 +1,8 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/qingwa.yml + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/qingwapt.json + * @PTMDefinitions https://github.com/JustLookAtNow/pt_mate/blob/master/assets/sites/qingwapt.json + */ import { mergeWith, toMerged } from "es-toolkit"; import { set } from "es-toolkit/compat"; diff --git a/src/packages/site/definitions/railgunpt.ts b/src/packages/site/definitions/railgunpt.ts index 3a2f789d5..cbc7fdee6 100644 --- a/src/packages/site/definitions/railgunpt.ts +++ b/src/packages/site/definitions/railgunpt.ts @@ -1,3 +1,6 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/railgunpt.yml + */ import type { ISiteMetadata } from "../types"; import { CategoryInclbookmarked, CategoryIncldead, CategorySpstate, SchemaMetadata } from "../schemas/NexusPHP"; diff --git a/src/packages/site/definitions/raingfh.ts b/src/packages/site/definitions/raingfh.ts index b0f09f17a..50a5b54e8 100644 --- a/src/packages/site/definitions/raingfh.ts +++ b/src/packages/site/definitions/raingfh.ts @@ -1,5 +1,6 @@ /** * FIXME 由 resource/sites/raingfh.top/config.json 自动转换而来 + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/raingfh.json */ import { type ISiteMetadata } from "../types"; import { CategoryInclbookmarked, CategoryIncldead, CategorySpstate, SchemaMetadata } from "../schemas/NexusPHP"; diff --git a/src/packages/site/definitions/redacted.ts b/src/packages/site/definitions/redacted.ts index 15dc5801b..18eead0d5 100644 --- a/src/packages/site/definitions/redacted.ts +++ b/src/packages/site/definitions/redacted.ts @@ -1,3 +1,7 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Indexers/Definitions/Redacted.cs + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/redacted.ch/config.json + */ import { ITorrent, IUserInfo, type ISiteMetadata } from "../types"; import GazelleJSONAPI, { jsonResponse, diff --git a/src/packages/site/definitions/reelflix.ts b/src/packages/site/definitions/reelflix.ts index 3af542e4d..57de4e3aa 100644 --- a/src/packages/site/definitions/reelflix.ts +++ b/src/packages/site/definitions/reelflix.ts @@ -1,3 +1,7 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/reelflix-api.yml + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/reelflix.json + */ import type { ISiteMetadata } from "../types"; import { CategoryFree, SchemaMetadata } from "../schemas/Unit3D.ts"; diff --git a/src/packages/site/definitions/retroflix.ts b/src/packages/site/definitions/retroflix.ts index 18785bce9..dee111322 100644 --- a/src/packages/site/definitions/retroflix.ts +++ b/src/packages/site/definitions/retroflix.ts @@ -1,3 +1,6 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Indexers/Definitions/RetroFlix.cs + */ import { set } from "es-toolkit/compat"; import { type ISiteMetadata } from "../types.ts"; diff --git a/src/packages/site/definitions/retrotoon.ts b/src/packages/site/definitions/retrotoon.ts index 134f8ac50..185a38385 100644 --- a/src/packages/site/definitions/retrotoon.ts +++ b/src/packages/site/definitions/retrotoon.ts @@ -1,3 +1,6 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/retrotoon.yml + */ import type { ISiteMetadata } from "../types"; import { buildCategoryOptionsFromDict, parseSizeString, parseValidTimeString } from "../utils"; diff --git a/src/packages/site/definitions/rousi.ts b/src/packages/site/definitions/rousi.ts index a250bf1b4..61a9aa52d 100644 --- a/src/packages/site/definitions/rousi.ts +++ b/src/packages/site/definitions/rousi.ts @@ -1,3 +1,8 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/rousi-pro.yml + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/rousi.json + * @PTMDefinitions https://github.com/JustLookAtNow/pt_mate/blob/master/assets/sites/rousi.json + */ import type { ISiteMetadata } from "../types"; // import { CategoryIncldead, CategorySpstate, CategoryInclbookmarked, SchemaMetadata } from "../schemas/NexusPHP.ts"; diff --git a/src/packages/site/definitions/rousipro.ts b/src/packages/site/definitions/rousipro.ts index c71626e6b..fae264fa5 100644 --- a/src/packages/site/definitions/rousipro.ts +++ b/src/packages/site/definitions/rousipro.ts @@ -1,3 +1,7 @@ +/** + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/rousi.json + * @PTMDefinitions https://github.com/JustLookAtNow/pt_mate/blob/master/assets/sites/rousipro.json + */ import type { ISearchInput, ISiteMetadata, ITorrent, ITorrentTag } from "../types"; import type { AxiosRequestConfig, AxiosResponse } from "axios"; diff --git a/src/packages/site/definitions/rs.ts b/src/packages/site/definitions/rs.ts index 58577e966..8000b18e0 100644 --- a/src/packages/site/definitions/rs.ts +++ b/src/packages/site/definitions/rs.ts @@ -1,3 +1,6 @@ +/** + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/resource.xidian.edu.cn/config.json + */ import { type ISiteMetadata } from "../types"; import { CategoryInclbookmarked, CategoryIncldead, CategorySpstate, SchemaMetadata } from "../schemas/NexusPHP"; diff --git a/src/packages/site/definitions/sanpro.ts b/src/packages/site/definitions/sanpro.ts index 398f21865..9bf169b7b 100644 --- a/src/packages/site/definitions/sanpro.ts +++ b/src/packages/site/definitions/sanpro.ts @@ -1,3 +1,6 @@ +/** + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/sanpro.json + */ import type { ISiteMetadata } from "../types"; export const siteMetadata: ISiteMetadata = { diff --git a/src/packages/site/definitions/sbpt.ts b/src/packages/site/definitions/sbpt.ts index bc017c8aa..bc9e07c10 100644 --- a/src/packages/site/definitions/sbpt.ts +++ b/src/packages/site/definitions/sbpt.ts @@ -1,3 +1,7 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/sbpt.yml + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/sbpt.json + */ import { type ISiteMetadata } from "../types"; import { CategoryInclbookmarked, diff --git a/src/packages/site/definitions/sdbits.ts b/src/packages/site/definitions/sdbits.ts index 935c9d23f..af183c6b6 100644 --- a/src/packages/site/definitions/sdbits.ts +++ b/src/packages/site/definitions/sdbits.ts @@ -1,5 +1,6 @@ /** * @JackettIssue https://github.com/Jackett/Jackett/issues/771 + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/sdbits.org/config.json */ import type { ISiteMetadata } from "../types"; diff --git a/src/packages/site/definitions/secretcinema.ts b/src/packages/site/definitions/secretcinema.ts index 27f7f5945..b4b66f17e 100644 --- a/src/packages/site/definitions/secretcinema.ts +++ b/src/packages/site/definitions/secretcinema.ts @@ -1,3 +1,6 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Indexers/Definitions/SecretCinema.cs + */ import type { ISiteMetadata, ITorrent, IUserInfo, ISearchInput } from "../types"; import { parseTimeWithZone, diff --git a/src/packages/site/definitions/seedpool.ts b/src/packages/site/definitions/seedpool.ts index a28b43a30..5aa585610 100644 --- a/src/packages/site/definitions/seedpool.ts +++ b/src/packages/site/definitions/seedpool.ts @@ -1,3 +1,6 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/seedpool-api.yml + */ import { type ISiteMetadata } from "../types"; import { CategoryFree, SchemaMetadata } from "../schemas/Unit3D.ts"; import { buildCategoryOptionsFromDict } from "../utils.ts"; diff --git a/src/packages/site/definitions/sewerpt.ts b/src/packages/site/definitions/sewerpt.ts index 7d26475e1..fc946404f 100644 --- a/src/packages/site/definitions/sewerpt.ts +++ b/src/packages/site/definitions/sewerpt.ts @@ -1,5 +1,7 @@ /** * FIXME 由 resource/sites/sewerpt.com/config.json 自动转换而来 + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/sewerpt.yml + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/sewerpt.json */ import { type ISiteMetadata } from "../types"; import { CategoryInclbookmarked, CategoryIncldead, CategorySpstate, SchemaMetadata } from "../schemas/NexusPHP"; diff --git a/src/packages/site/definitions/shareisland.ts b/src/packages/site/definitions/shareisland.ts index f8e8fbd2d..03d274a9e 100644 --- a/src/packages/site/definitions/shareisland.ts +++ b/src/packages/site/definitions/shareisland.ts @@ -1,3 +1,6 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/shareisland-api.yml + */ import { type ISiteMetadata } from "../types"; import { SchemaMetadata } from "../schemas/Unit3D.ts"; diff --git a/src/packages/site/definitions/simurg.ts b/src/packages/site/definitions/simurg.ts index 2769ac3c1..7e560b169 100644 --- a/src/packages/site/definitions/simurg.ts +++ b/src/packages/site/definitions/simurg.ts @@ -1,3 +1,6 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Indexers/Definitions/Simurg.cs + */ import type { ISiteMetadata } from "../types"; import { SchemaMetadata } from "../schemas/Gazelle.ts"; diff --git a/src/packages/site/definitions/siqi.ts b/src/packages/site/definitions/siqi.ts index 2297a8c49..16d9e75ff 100644 --- a/src/packages/site/definitions/siqi.ts +++ b/src/packages/site/definitions/siqi.ts @@ -1,3 +1,7 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/siqi.yml + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/siqi.json + */ import type { ISiteMetadata } from "../types"; import { CategoryInclbookmarked, CategoryIncldead, CategorySpstate, SchemaMetadata } from "../schemas/NexusPHP"; diff --git a/src/packages/site/definitions/sjtu.ts b/src/packages/site/definitions/sjtu.ts index 5ab3646f6..568fefc7a 100644 --- a/src/packages/site/definitions/sjtu.ts +++ b/src/packages/site/definitions/sjtu.ts @@ -1,3 +1,8 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/putao.yml + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/pt.sjtu.edu.cn/config.json + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/putao.json + */ import { mergeWith } from "es-toolkit"; import { ETorrentStatus, type ISiteMetadata, type IUserInfo } from "../types"; import { parseSizeString, createDocument } from "../utils"; diff --git a/src/packages/site/definitions/soulvoice.ts b/src/packages/site/definitions/soulvoice.ts index 51352d756..6951c01c2 100644 --- a/src/packages/site/definitions/soulvoice.ts +++ b/src/packages/site/definitions/soulvoice.ts @@ -1,3 +1,7 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/soulvoice.yml + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/soulvoice.json + */ import type { ISiteMetadata } from "../types"; import { SchemaMetadata } from "../schemas/NexusPHP"; diff --git a/src/packages/site/definitions/speedapp.ts b/src/packages/site/definitions/speedapp.ts index 83133f1e8..59fd2e150 100644 --- a/src/packages/site/definitions/speedapp.ts +++ b/src/packages/site/definitions/speedapp.ts @@ -1,3 +1,7 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Indexers/Definitions/SpeedApp.cs + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/speedapp.io/config.json + */ import type { ISiteMetadata, ISearchEntryRequestConfig, diff --git a/src/packages/site/definitions/sportscult.ts b/src/packages/site/definitions/sportscult.ts index 0fdbbb391..7b6268b32 100644 --- a/src/packages/site/definitions/sportscult.ts +++ b/src/packages/site/definitions/sportscult.ts @@ -1,6 +1,7 @@ /** * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/sportscult.yml * @JackettIssue https://github.com/Jackett/Jackett/issues/1330 + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/sportscult.org/config.json */ import type { ISiteMetadata, IUserInfo } from "../types.ts"; import PrivateSite from "../schemas/AbstractPrivateSite.ts"; diff --git a/src/packages/site/definitions/springsunday.ts b/src/packages/site/definitions/springsunday.ts index 26370c673..644059e21 100644 --- a/src/packages/site/definitions/springsunday.ts +++ b/src/packages/site/definitions/springsunday.ts @@ -1,3 +1,7 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/springsunday.yml + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/springsunday.net/config.json + */ import { set } from "es-toolkit/compat"; import { diff --git a/src/packages/site/definitions/sugoimusic.ts b/src/packages/site/definitions/sugoimusic.ts index 9d0e6f221..d24cb1381 100644 --- a/src/packages/site/definitions/sugoimusic.ts +++ b/src/packages/site/definitions/sugoimusic.ts @@ -1,3 +1,6 @@ +/** + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/sugoimusic.me/config.json + */ import { type ISiteMetadata } from "../types"; // import { SchemaMetadata } from "../schemas/GazelleJSONAPI.ts"; diff --git a/src/packages/site/definitions/sunnypt.ts b/src/packages/site/definitions/sunnypt.ts index 3f261247c..033454cc0 100644 --- a/src/packages/site/definitions/sunnypt.ts +++ b/src/packages/site/definitions/sunnypt.ts @@ -1,3 +1,6 @@ +/** + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/sunny.json + */ import { type AxiosRequestConfig, type AxiosResponse } from "axios"; import { ISearchInput, type ISiteMetadata, type ITorrent } from "../types"; diff --git a/src/packages/site/definitions/superbits.ts b/src/packages/site/definitions/superbits.ts index 01782caea..fc38e6981 100644 --- a/src/packages/site/definitions/superbits.ts +++ b/src/packages/site/definitions/superbits.ts @@ -1,5 +1,6 @@ /** * Superbits - 基于 rartracker 的瑞典 BT 站点 + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/superbits.yml */ import type { ISiteMetadata, ITorrent, ISearchInput, ITorrentTag } from "../types"; import Rartracker from "../schemas/Rartracker"; diff --git a/src/packages/site/definitions/tangpt.ts b/src/packages/site/definitions/tangpt.ts index 0ba658ff1..85d2466ae 100644 --- a/src/packages/site/definitions/tangpt.ts +++ b/src/packages/site/definitions/tangpt.ts @@ -1,3 +1,7 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/tangpt.yml + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/tangpt.json + */ import type { ISiteMetadata } from "../types"; import { CategoryInclbookmarked, CategoryIncldead, CategorySpstate, SchemaMetadata } from "../schemas/NexusPHP"; diff --git a/src/packages/site/definitions/tccf.ts b/src/packages/site/definitions/tccf.ts index f883a44a8..5377fe73b 100644 --- a/src/packages/site/definitions/tccf.ts +++ b/src/packages/site/definitions/tccf.ts @@ -1,3 +1,6 @@ +/** + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/tccf.json + */ import { ETorrentStatus, type ISiteMetadata } from "../types"; import { CategoryInclbookmarked, CategoryIncldead, CategorySpstate, SchemaMetadata } from "../schemas/NexusPHP.ts"; diff --git a/src/packages/site/definitions/tey.ts b/src/packages/site/definitions/tey.ts index df0eec763..946b56a26 100644 --- a/src/packages/site/definitions/tey.ts +++ b/src/packages/site/definitions/tey.ts @@ -1,3 +1,6 @@ +/** + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/tey.json + */ import { type ISiteMetadata } from "../types"; import { CategoryInclbookmarked, CategoryIncldead, CategorySpstate, SchemaMetadata } from "../schemas/NexusPHP.ts"; diff --git a/src/packages/site/definitions/thegeeks.ts b/src/packages/site/definitions/thegeeks.ts index 3cea19902..174e92f6d 100644 --- a/src/packages/site/definitions/thegeeks.ts +++ b/src/packages/site/definitions/thegeeks.ts @@ -1,6 +1,7 @@ /** * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/thegeeks.yml * @JackettIssue https://github.com/Jackett/Jackett/issues/1302 + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/thegeeks.click/config.json */ import type { ISiteMetadata } from "../types.ts"; import { SchemaMetadata } from "../schemas/TCG.ts"; diff --git a/src/packages/site/definitions/tjupt.ts b/src/packages/site/definitions/tjupt.ts index 8e1206a64..8ebe5a60b 100644 --- a/src/packages/site/definitions/tjupt.ts +++ b/src/packages/site/definitions/tjupt.ts @@ -1,3 +1,8 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/tjupt.yml + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/tjupt.org/config.json + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/tjupt.json + */ import { set } from "es-toolkit/compat"; import Sizzle from "sizzle"; diff --git a/src/packages/site/definitions/tlfbits.ts b/src/packages/site/definitions/tlfbits.ts index c70587734..c3ad02c90 100644 --- a/src/packages/site/definitions/tlfbits.ts +++ b/src/packages/site/definitions/tlfbits.ts @@ -1,3 +1,6 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/tlfbits.yml + */ import { ETorrentStatus, type ISiteMetadata } from "../types"; import { CategoryInclbookmarked, CategoryIncldead, CategorySpstate, SchemaMetadata } from "../schemas/NexusPHP.ts"; diff --git a/src/packages/site/definitions/tmpt.ts b/src/packages/site/definitions/tmpt.ts index 39ef63ce0..4cef7a656 100644 --- a/src/packages/site/definitions/tmpt.ts +++ b/src/packages/site/definitions/tmpt.ts @@ -1,3 +1,6 @@ +/** + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/tmpt.json + */ import type { ISiteMetadata } from "../types"; import { /* SchemaMetadata, */ xiaomloveDefaultUserLevelRequirements } from "../schemas/NexusPHP"; diff --git a/src/packages/site/definitions/tokyopt.ts b/src/packages/site/definitions/tokyopt.ts index f62203c3d..dcf37b615 100644 --- a/src/packages/site/definitions/tokyopt.ts +++ b/src/packages/site/definitions/tokyopt.ts @@ -1,3 +1,6 @@ +/** + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/tokyopt.json + */ import type { ISiteMetadata } from "../types"; //import { CategoryInclbookmarked, CategoryIncldead, CategorySpstate, SchemaMetadata } from "../schemas/NexusPHP"; diff --git a/src/packages/site/definitions/tokyotosho.ts b/src/packages/site/definitions/tokyotosho.ts index 3553be805..27539bd1a 100644 --- a/src/packages/site/definitions/tokyotosho.ts +++ b/src/packages/site/definitions/tokyotosho.ts @@ -1,3 +1,6 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/tokyotosho.yml + */ import type { ISiteMetadata } from "../types"; import { buildCategoryOptionsFromDict } from "../utils"; diff --git a/src/packages/site/definitions/torrenthub.ts b/src/packages/site/definitions/torrenthub.ts index b45a62736..87d858497 100644 --- a/src/packages/site/definitions/torrenthub.ts +++ b/src/packages/site/definitions/torrenthub.ts @@ -1,3 +1,6 @@ +/** + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/torrenthub.json + */ import type { ISiteMetadata } from "../types"; // import { SchemaMetadata } from "../schemas/NexusPHP"; diff --git a/src/packages/site/definitions/torrentleech.ts b/src/packages/site/definitions/torrentleech.ts index f5b174b59..a6626cef1 100644 --- a/src/packages/site/definitions/torrentleech.ts +++ b/src/packages/site/definitions/torrentleech.ts @@ -1,3 +1,7 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/torrentleech.yml + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/torrentleech.org/config.json + */ import urlJoin from "url-join"; import Sizzle from "sizzle"; import { mergeWith } from "es-toolkit"; diff --git a/src/packages/site/definitions/torrentscsv.ts b/src/packages/site/definitions/torrentscsv.ts index 05f7c39df..ef7112bdf 100644 --- a/src/packages/site/definitions/torrentscsv.ts +++ b/src/packages/site/definitions/torrentscsv.ts @@ -1,3 +1,6 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Indexers/Definitions/TorrentsCSV.cs + */ import type { ISiteMetadata } from "../types"; export const siteMetadata: ISiteMetadata = { diff --git a/src/packages/site/definitions/torrentseeds.ts b/src/packages/site/definitions/torrentseeds.ts index 08a113e7d..2f507de6d 100644 --- a/src/packages/site/definitions/torrentseeds.ts +++ b/src/packages/site/definitions/torrentseeds.ts @@ -1,5 +1,6 @@ /** * @JackettIssue https://github.com/Jackett/Jackett/issues/4842 + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/torrentseeds.org/config.json */ import type { ISiteMetadata } from "../types"; diff --git a/src/packages/site/definitions/tosky.ts b/src/packages/site/definitions/tosky.ts index 9bfe1e7bb..e3ace4296 100644 --- a/src/packages/site/definitions/tosky.ts +++ b/src/packages/site/definitions/tosky.ts @@ -1,3 +1,6 @@ +/** + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/tosky.json + */ import type { ISiteMetadata } from "../types"; export const siteMetadata: ISiteMetadata = { diff --git a/src/packages/site/definitions/totheglory.ts b/src/packages/site/definitions/totheglory.ts index fb94a944f..b9f21ef61 100644 --- a/src/packages/site/definitions/totheglory.ts +++ b/src/packages/site/definitions/totheglory.ts @@ -1,3 +1,9 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/totheglory.yml + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/totheglory.im/config.json + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/ttg.json + * @PTMDefinitions https://github.com/JustLookAtNow/pt_mate/blob/master/assets/sites/ttg.json + */ import Sizzle from "sizzle"; import { set } from "es-toolkit/compat"; diff --git a/src/packages/site/definitions/tu88.ts b/src/packages/site/definitions/tu88.ts index d9ed6582d..c2d8236e2 100644 --- a/src/packages/site/definitions/tu88.ts +++ b/src/packages/site/definitions/tu88.ts @@ -1,3 +1,6 @@ +/** + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/tu88.json + */ import type { ISiteMetadata } from "../types"; import { CategoryInclbookmarked, CategoryIncldead, CategorySpstate, SchemaMetadata } from "../schemas/NexusPHP"; diff --git a/src/packages/site/definitions/u2.ts b/src/packages/site/definitions/u2.ts index ef9286e2d..5aa044777 100644 --- a/src/packages/site/definitions/u2.ts +++ b/src/packages/site/definitions/u2.ts @@ -1,3 +1,9 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/u2.yml + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/u2.dmhy.org/config.json + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/u2.json + * @PTMDefinitions https://github.com/JustLookAtNow/pt_mate/blob/master/assets/sites/u2share.json + */ import { ETorrentStatus, type ISiteMetadata } from "../types"; import { CategoryInclbookmarked, CategoryIncldead, SchemaMetadata } from "../schemas/NexusPHP"; import { set } from "es-toolkit/compat"; diff --git a/src/packages/site/definitions/ubits.ts b/src/packages/site/definitions/ubits.ts index f27cd25b5..150e1a3ff 100644 --- a/src/packages/site/definitions/ubits.ts +++ b/src/packages/site/definitions/ubits.ts @@ -1,3 +1,9 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/ubits.yml + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/ubits.club/config.json + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/ubits.json + * @PTMDefinitions https://github.com/JustLookAtNow/pt_mate/blob/master/assets/sites/ubits.json + */ import { set } from "es-toolkit/compat"; import type { IAdvancedSearchRequestConfig, ISiteMetadata, TSelectSearchCategoryValue } from "../types"; import { GB, TB } from "../utils"; diff --git a/src/packages/site/definitions/uhdbits.ts b/src/packages/site/definitions/uhdbits.ts index e2e0bc135..47f6397f5 100644 --- a/src/packages/site/definitions/uhdbits.ts +++ b/src/packages/site/definitions/uhdbits.ts @@ -1,3 +1,6 @@ +/** + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/uhdbits.org/config.json + */ // import Sizzle from "sizzle"; import type { ISiteMetadata, diff --git a/src/packages/site/definitions/ultrahd.ts b/src/packages/site/definitions/ultrahd.ts index 7ec86cc0f..592825986 100644 --- a/src/packages/site/definitions/ultrahd.ts +++ b/src/packages/site/definitions/ultrahd.ts @@ -1,3 +1,8 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/ultrahd.yml + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/ultrahd.net/config.json + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/ultrahd.json + */ import { type ISiteMetadata } from "../types"; import { CategoryInclbookmarked, CategoryIncldead, CategorySpstate, SchemaMetadata } from "../schemas/NexusPHP.ts"; diff --git a/src/packages/site/definitions/uploadcx.ts b/src/packages/site/definitions/uploadcx.ts index 18e3204ab..86e906c62 100644 --- a/src/packages/site/definitions/uploadcx.ts +++ b/src/packages/site/definitions/uploadcx.ts @@ -1,3 +1,6 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/uploadcx.yml + */ import { type ISiteMetadata } from "../types"; import { CategoryFree, SchemaMetadata } from "../schemas/Unit3D.ts"; import { buildCategoryOptionsFromDict } from "../utils.ts"; diff --git a/src/packages/site/definitions/vclib.ts b/src/packages/site/definitions/vclib.ts index 31f3cdbc8..cd7f5bda1 100644 --- a/src/packages/site/definitions/vclib.ts +++ b/src/packages/site/definitions/vclib.ts @@ -1,3 +1,6 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/vc-lib.yml + */ import { type ISiteMetadata } from "../types"; import { SchemaMetadata } from "../schemas/NexusPHP.ts"; diff --git a/src/packages/site/definitions/wintersakura.ts b/src/packages/site/definitions/wintersakura.ts index d8f3caf47..090969324 100644 --- a/src/packages/site/definitions/wintersakura.ts +++ b/src/packages/site/definitions/wintersakura.ts @@ -1,3 +1,8 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/wintersakura.yml + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/wintersakura.net/config.json + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/wintersakura.json + */ import { type ISiteMetadata } from "../types"; import { CategoryInclbookmarked, CategoryIncldead, CategorySpstate, SchemaMetadata } from "../schemas/NexusPHP.ts"; diff --git a/src/packages/site/definitions/wukongwendao.ts b/src/packages/site/definitions/wukongwendao.ts index dbc31b9d2..3875d5730 100644 --- a/src/packages/site/definitions/wukongwendao.ts +++ b/src/packages/site/definitions/wukongwendao.ts @@ -1,3 +1,6 @@ +/** + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/wukongwendao.json + */ import type { ISiteMetadata } from "../types"; export const siteMetadata: ISiteMetadata = { diff --git a/src/packages/site/definitions/xauat6.ts b/src/packages/site/definitions/xauat6.ts index 4164c18c2..453a10798 100644 --- a/src/packages/site/definitions/xauat6.ts +++ b/src/packages/site/definitions/xauat6.ts @@ -1,3 +1,6 @@ +/** + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/pt.xauat6.edu.cn/config.json + */ import { type ISiteMetadata } from "../types"; // import { SchemaMetadata } from "../schemas/NexusPHP"; diff --git a/src/packages/site/definitions/xingtan.ts b/src/packages/site/definitions/xingtan.ts index 5d3f60d47..a08584e09 100644 --- a/src/packages/site/definitions/xingtan.ts +++ b/src/packages/site/definitions/xingtan.ts @@ -1,3 +1,9 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/xingtan.yml + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/xingtan.one/config.json + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/xingtan.json + * @PTMDefinitions https://github.com/JustLookAtNow/pt_mate/blob/master/assets/sites/xingtan.json + */ import type { ISiteMetadata } from "../types"; import { CategoryInclbookmarked, CategoryIncldead, CategorySpstate, SchemaMetadata } from "../schemas/NexusPHP"; diff --git a/src/packages/site/definitions/xingwan.ts b/src/packages/site/definitions/xingwan.ts index 50a6b5021..41f206fc7 100644 --- a/src/packages/site/definitions/xingwan.ts +++ b/src/packages/site/definitions/xingwan.ts @@ -1,3 +1,7 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/xingwan.yml + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/xingwan.json + */ import type { ISiteMetadata } from "../types"; import { CategoryInclbookmarked, diff --git a/src/packages/site/definitions/xingyunge.ts b/src/packages/site/definitions/xingyunge.ts index 0b37dcbea..4a1cd9969 100644 --- a/src/packages/site/definitions/xingyunge.ts +++ b/src/packages/site/definitions/xingyunge.ts @@ -1,3 +1,8 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/xingyung.yml + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/xingyunge.json + * @PTMDefinitions https://github.com/JustLookAtNow/pt_mate/blob/master/assets/sites/xingyunge.json + */ import type { ISiteMetadata } from "../types"; import { CategoryInclbookmarked, CategoryIncldead, CategorySpstate, SchemaMetadata } from "../schemas/NexusPHP"; diff --git a/src/packages/site/definitions/ydypt.ts b/src/packages/site/definitions/ydypt.ts index 56b0dabce..b8d965fcf 100644 --- a/src/packages/site/definitions/ydypt.ts +++ b/src/packages/site/definitions/ydypt.ts @@ -1,3 +1,6 @@ +/** + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/pt.hdbd.us/config.json + */ import type { ISiteMetadata } from "../types"; export const siteMetadata: ISiteMetadata = { diff --git a/src/packages/site/definitions/yemapt.ts b/src/packages/site/definitions/yemapt.ts index f8dad54c6..829e8a61a 100644 --- a/src/packages/site/definitions/yemapt.ts +++ b/src/packages/site/definitions/yemapt.ts @@ -1,3 +1,6 @@ +/** + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/yemapt.json + */ import type { AxiosRequestConfig, AxiosResponse } from "axios"; import PrivateSite from "../schemas/AbstractPrivateSite.ts"; diff --git a/src/packages/site/definitions/yinguskg.ts b/src/packages/site/definitions/yinguskg.ts index d6d92acfb..4952f986d 100644 --- a/src/packages/site/definitions/yinguskg.ts +++ b/src/packages/site/definitions/yinguskg.ts @@ -1,3 +1,7 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/ptying.yml + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/yinghua.json + */ import type { ISiteMetadata } from "../types"; import { CategoryInclbookmarked, diff --git a/src/packages/site/definitions/yuscene.ts b/src/packages/site/definitions/yuscene.ts index 48cea44e9..f4c8b9bd5 100644 --- a/src/packages/site/definitions/yuscene.ts +++ b/src/packages/site/definitions/yuscene.ts @@ -1,3 +1,6 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/yuscene-api.yml + */ import type { ISiteMetadata } from "../types"; import { CategoryFree, SchemaMetadata } from "../schemas/Unit3D.ts"; import { buildCategoryOptionsFromDict, parseValidTimeString } from "../utils.ts"; diff --git a/src/packages/site/definitions/zhixing.ts b/src/packages/site/definitions/zhixing.ts index 7ce45747b..0453a7f06 100644 --- a/src/packages/site/definitions/zhixing.ts +++ b/src/packages/site/definitions/zhixing.ts @@ -1,3 +1,6 @@ +/** + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/pt.zhixing.bjtu.edu.cn/config.json + */ import urlJoin from "url-join"; import { createDocument } from "../utils"; diff --git a/src/packages/site/definitions/zhuque.ts b/src/packages/site/definitions/zhuque.ts index f691bb58e..b2c23d926 100644 --- a/src/packages/site/definitions/zhuque.ts +++ b/src/packages/site/definitions/zhuque.ts @@ -1,3 +1,7 @@ +/** + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/zhuque.in/config.json + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/zhuque.json + */ import type { AxiosRequestConfig, AxiosResponse } from "axios"; import { diff --git a/src/packages/site/definitions/zmpt.ts b/src/packages/site/definitions/zmpt.ts index fe23a15da..75390a760 100644 --- a/src/packages/site/definitions/zmpt.ts +++ b/src/packages/site/definitions/zmpt.ts @@ -1,3 +1,9 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/zmpt.yml + * @PTPPDefinitions https://github.com/pt-plugins/PT-Plugin-Plus/blob/dev/resource/sites/zmpt.cc/config.json + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/zmpt.json + * @PTMDefinitions https://github.com/JustLookAtNow/pt_mate/blob/master/assets/sites/zmpt.json + */ import { type ISiteMetadata } from "../types"; import { CategoryInclbookmarked, CategoryIncldead, CategorySpstate, SchemaMetadata } from "../schemas/NexusPHP.ts"; diff --git a/src/packages/site/definitions/zrpt.ts b/src/packages/site/definitions/zrpt.ts index b1c86ad8c..4f0009d1c 100644 --- a/src/packages/site/definitions/zrpt.ts +++ b/src/packages/site/definitions/zrpt.ts @@ -1,3 +1,7 @@ +/** + * @JackettDefinitions https://github.com/Jackett/Jackett/blob/master/src/Jackett.Common/Definitions/zrpt.yml + * @PDSDefinitions https://github.com/mantou568/pre-dessert-sites/blob/main/site_config/sites/zrpt.json + */ import type { ISiteMetadata, ITorrentTag } from "../types"; import { CategoryInclbookmarked, CategoryIncldead, CategorySpstate, SchemaMetadata } from "../schemas/NexusPHP";