feat(adapters): port MoviePilot open site framework (7 spiders, 20 user parsers) + MovieClaw v0.27.0 site definitions + mp-sync tools (W1-3c)
- sites/spiders: Haidan/HDDolby/RousiPro/SunnyPT/TNode/TorrentLeech/Yema (+mTorrent wrapper), specialized SiteAdapter, indirect download resolved at download time - sites/user-parsers: SiteParserBase + all 20 MP SiteSchema parsers (lxml XPath via xpath+xmldom), refreshSiteUserData with schema fallback - sites/definitions: zod SiteDefinition schemas, 23 MovieClaw v0.27.0 (MIT) definitions via scripts/mp-sync/movieclaw-import.mjs, registry getSiteDefinition/listSiteDefinitions, NOTICE - scripts/mp-sync: ledger.json (upstream sha256), check.mjs, build-ledger.mjs; docs/porting/mp-sync-ledger.md - license-and-provenance: add copied-mit kind, re-anchor MovieClaw rows to v0.27.0 Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
cf85481d95
commit
d9f2af2f3c
166 files changed
+16577
-186
No files matched your search
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* CHDBits(彩虹岛)列表页 fixture:按 MovieClaw v0.27.0 chdbits.yaml 选择器描述的标准 NexusPHP 结构自行编写
|
||||
* (tr > td > table.torrentname、font.subtitle、img.pro_*、td.embedded > span[title] 促销截止),**未对照真实页面**。
|
||||
*/
|
||||
export const CHDBITS_LIST_PAGE = `<!DOCTYPE html><html><head><title>CHDBits :: 种子</title></head><body>
|
||||
<table id="info_block"><tr><td><a href="userdetails.php?id=42" class="VIP_Name">carol</a> <a href="logout.php">退出</a>
|
||||
<font class="color_uploaded">上传量:</font> 25.052 TB <font class="color_downloaded">下载量:</font> 1.2 TB</td></tr></table>
|
||||
<table class="torrents" cellspacing="0" cellpadding="5" width="100%">
|
||||
<tr><td class="colhead">类型</td><td class="colhead">标题</td><td class="colhead">评论</td><td class="colhead">时间</td><td class="colhead">大小</td><td class="colhead">种子</td><td class="colhead">下载</td><td class="colhead">完成</td><td class="colhead">发布者</td></tr>
|
||||
<tr>
|
||||
<td class="rowfollow nowrap"><a href="?cat=402"><img class="c_tvseries" src="pic/cattrans.gif" alt="TV Series"/></a></td>
|
||||
<td class="rowfollow"><table class="torrentname" width="100%"><tr>
|
||||
<td class="embedded"><a title="The.Last.of.Us.S02.2025.1080p.BluRay.x264.DTS-HD.MA.5.1-CHD" href="details.php?id=555001&hit=1"><b>The.Last.of.Us.S02.2025.1080p.BluRay...</b></a>
|
||||
<img class="pro_free2up" src="pic/trans.gif" alt="2X Free"/> <span title="2026-09-30 12:00:00">[限时]</span><br/><font class="subtitle">最后生还者 第二季 <span class="tags">中字</span></font></td>
|
||||
<td class="embedded" width="20"><a href="download.php?id=555001"><img class="download" alt="download"/></a></td>
|
||||
</tr></table></td>
|
||||
<td class="rowfollow">3</td>
|
||||
<td class="rowfollow nowrap"><span title="2026-09-25 08:00:00">2天</span></td>
|
||||
<td class="rowfollow">45.20<br/>GB</td>
|
||||
<td class="rowfollow"><b><a href="details.php?id=555001#peers">42</a></b></td>
|
||||
<td class="rowfollow">3</td>
|
||||
<td class="rowfollow"><a href="viewsnatches.php?id=555001"><b>100</b></a></td>
|
||||
<td class="rowfollow"><a href="userdetails.php?id=1">uploader</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="rowfollow nowrap"><a href="?cat=401"><img class="c_movie" src="pic/cattrans.gif" alt="Movies"/></a></td>
|
||||
<td class="rowfollow"><table class="torrentname" width="100%"><tr>
|
||||
<td class="embedded"><a title="Oppenheimer.2023.2160p.UHD.BluRay.REMUX-CHD" href="details.php?id=555002&hit=1"><b>Oppenheimer.2023...</b></a><br/><font class="subtitle">奥本海默</font></td>
|
||||
<td class="embedded" width="20"><a href="download.php?id=555002"><img class="download" alt="download"/></a></td>
|
||||
</tr></table></td>
|
||||
<td class="rowfollow">0</td>
|
||||
<td class="rowfollow nowrap"><span title="2026-09-01 10:00:00">26天</span></td>
|
||||
<td class="rowfollow">80.00<br/>GB</td>
|
||||
<td class="rowfollow">7</td>
|
||||
<td class="rowfollow">0</td>
|
||||
<td class="rowfollow">55</td>
|
||||
<td class="rowfollow"><a href="userdetails.php?id=1">uploader</a></td>
|
||||
</tr>
|
||||
</table></body></html>`;
|
||||
|
||||
export const CHDBITS_LOGIN_PAGE = `<!DOCTYPE html><html><head><title>CHDBits :: 登录</title></head><body>
|
||||
<form method="post" action="takelogin.php"><input name="username"/><input type="password" name="password"/></form>
|
||||
<p>未登录!</p></body></html>`;
|
||||
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* HHClub(憨憨)列表/详情页 fixture:按 MovieClaw v0.27.0 hhanclub.yaml 选择器所描述的结构自行编写
|
||||
* (div 卡片模板 + promotion-tag-* + torrent-info-hr + Tailwind 详情页),**未对照真实页面**。
|
||||
*/
|
||||
export const HHANCLUB_LIST_PAGE = `<!DOCTYPE html><html><head><title>HHCLUB :: 种子</title></head><body>
|
||||
<div id="user-info-panel"><a href="userdetails.php?id=7" class="EliteUser_Name">bob</a> <a href="logout.php">退出</a></div>
|
||||
<div class="torrent-table">
|
||||
<div class="torrent-table-sub-info">
|
||||
<div class="torrent-cat"><a href="?cat[]=403"><img src="pic/cattrans.gif" alt="综艺"/></a></div>
|
||||
<div class="torrent-title"><a class="torrent-info-text-name" href="details.php?id=90001&hit=1">Go.Fighting.S10E01.2026.2160p.WEB-DL.H265.AAC-HHWEB</a>
|
||||
<span class="promotion-tag promotion-tag-free">免费</span></div>
|
||||
<div class="torrent-info-text-small_name">极限挑战 第十季 第1期</div>
|
||||
<div class="torrent-manage"><a href="download.php?id=90001">下载</a></div>
|
||||
<div class="torrent-info-text-added"><span title="2026-09-20 20:30:00">6天23时</span></div>
|
||||
<div class="torrent-info-text-size">6.12 GB</div>
|
||||
<div class="torrent-info-text-seeders">88</div>
|
||||
<div class="torrent-info-text-leechers">5</div>
|
||||
<div class="torrent-info-text-finished">1,024</div>
|
||||
<div class="torrent-info-text-author">anon</div>
|
||||
<div class="torrent-info-hr">30</div>
|
||||
</div>
|
||||
<div class="torrent-table-sub-info">
|
||||
<div class="torrent-cat"><a href="?cat[]=401"><img src="pic/cattrans.gif" alt="电影"/></a></div>
|
||||
<div class="torrent-title"><a class="torrent-info-text-name" href="details.php?id=90002&hit=1">Dune.Part.Two.2024.2160p.UHD.BluRay.REMUX.DV.HDR.HEVC.TrueHD.7.1.Atmos-HHWEB</a>
|
||||
<span class="promotion-tag promotion-tag-50pctdown2up">50%</span></div>
|
||||
<div class="torrent-info-text-small_name">沙丘2</div>
|
||||
<div class="torrent-manage"><a href="download.php?id=90002">下载</a></div>
|
||||
<div class="torrent-info-text-added">2026-09-01 08:00:00</div>
|
||||
<div class="torrent-info-text-size">78.3 GB</div>
|
||||
<div class="torrent-info-text-seeders">12</div>
|
||||
<div class="torrent-info-text-leechers">0</div>
|
||||
<div class="torrent-info-text-finished">300</div>
|
||||
<div class="torrent-info-text-author">anon</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pagination"><a href="?page=0">1-100</a><a href="?page=3">301-400</a></div>
|
||||
</body></html>`;
|
||||
|
||||
export const HHANCLUB_DETAIL_PAGE = `<!DOCTYPE html><html><head><title>HHCLUB :: 种子详情</title></head><body>
|
||||
<a href="logout.php">退出</a>
|
||||
<div class="grid">
|
||||
<div class="font-bold leading-6">标题</div><div class="font-bold leading-6">Dune.Part.Two.2024.2160p.UHD.BluRay.REMUX-HHWEB</div>
|
||||
<div class="font-bold leading-6">副标题</div><div class="font-bold leading-6">沙丘2 | 中字</div>
|
||||
<div><span class="font-bold">大小:</span><span>78.30 GB</span></div>
|
||||
<div><a href="download.php?id=90002">下载种子</a></div>
|
||||
<div id="kdescr"><a href="https://www.imdb.com/title/tt15239678/">IMDb</a> <a href="https://movie.douban.com/subject/35575567/">豆瓣</a> 简介……</div>
|
||||
</div></body></html>`;
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from "./chdbits.ts";
|
||||
export * from "./hhanclub.ts";
|
||||
@@ -0,0 +1,48 @@
|
||||
/** 海胆 `torrents.php?isapi=1` 响应样例(自行编写,字段名按 MP haidan.py 读取的键;不代表真实数据)。 */
|
||||
export const HAIDAN_SEARCH_OK = {
|
||||
code: 0,
|
||||
msg: "success",
|
||||
data: {
|
||||
"30001": {
|
||||
group_id: 9001,
|
||||
category: "401",
|
||||
name: "Dune.Part.Two.2024.2160p.UHD.BluRay.REMUX.HDR.HEVC.Atmos-HDH",
|
||||
small_descr: "沙丘2 | 4K 原盘 REMUX",
|
||||
url: "https://www.haidan.video/download.php?id=30001&passkey=HDPASSKEY123",
|
||||
added: 1726790400,
|
||||
size: "76543210987",
|
||||
seeders: "88",
|
||||
leechers: "3",
|
||||
times_completed: "412",
|
||||
sp_state: "2",
|
||||
},
|
||||
"30002": {
|
||||
group_id: 9002,
|
||||
category: "402",
|
||||
name: "Shogun.2024.S01.1080p.WEB-DL.DDP5.1.H.264-HDH",
|
||||
small_descr: "幕府将军 第一季",
|
||||
url: "https://www.haidan.video/download.php?id=30002&passkey=HDPASSKEY123",
|
||||
added: "2026-09-20 10:00:00",
|
||||
size: 21474836480,
|
||||
seeders: 12,
|
||||
leechers: 0,
|
||||
times_completed: 30,
|
||||
sp_state: "6",
|
||||
},
|
||||
"30003": {
|
||||
group_id: 9003,
|
||||
category: "406",
|
||||
name: "Some.Album.2024.FLAC",
|
||||
small_descr: "",
|
||||
url: "https://www.haidan.video/download.php?id=30003&passkey=HDPASSKEY123",
|
||||
added: 1726790400,
|
||||
size: 1073741824,
|
||||
seeders: 5,
|
||||
leechers: 1,
|
||||
times_completed: 2,
|
||||
sp_state: "1",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const HAIDAN_SEARCH_ERROR = { code: 1, msg: "请先登录" };
|
||||
@@ -0,0 +1,53 @@
|
||||
/** HDDolby `/api/v1/torrent/search` 响应样例(自行编写,结构按 MP hddolby.py 注释示例)。 */
|
||||
export const HDDOLBY_SEARCH_OK = {
|
||||
data: [
|
||||
{
|
||||
id: 120202,
|
||||
promotion_time_type: 2,
|
||||
promotion_until: "2026-09-30 12:00:00",
|
||||
category: 402,
|
||||
leechers: 0,
|
||||
seeders: 1,
|
||||
name: "[DBY] Lost S06 2010 Complete 1080p Netflix WEB-DL AVC DDP5.1-DBTV",
|
||||
small_descr: "lost ",
|
||||
times_completed: 0,
|
||||
size: 33665425886,
|
||||
added: "2025-02-18 19:47:56",
|
||||
url: 0,
|
||||
hr: 1,
|
||||
downhash: "DOWNHASHSECRET",
|
||||
tags: "gf;zz;xx",
|
||||
},
|
||||
{
|
||||
id: 120203,
|
||||
promotion_time_type: 6,
|
||||
promotion_until: "0000-00-00 00:00:00",
|
||||
category: 401,
|
||||
leechers: 2,
|
||||
seeders: 40,
|
||||
name: "Movie.2024.2160p.WEB-DL.DV.HDR.H.265-DBTV",
|
||||
small_descr: "电影",
|
||||
times_completed: 99,
|
||||
size: 12345678901,
|
||||
added: "2026-09-01 08:00:00",
|
||||
hr: 0,
|
||||
downhash: "DOWNHASHSECRET2",
|
||||
tags: "",
|
||||
},
|
||||
{
|
||||
id: 120204,
|
||||
promotion_time_type: 0,
|
||||
category: 406,
|
||||
name: "Concert.2023.1080p",
|
||||
size: 1000,
|
||||
seeders: 1,
|
||||
leechers: 0,
|
||||
times_completed: 0,
|
||||
added: "2026-09-01 08:00:00",
|
||||
downhash: "X",
|
||||
tags: null,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const HDDOLBY_SEARCH_ERROR = { error: { message: "invalid api key" } };
|
||||
@@ -0,0 +1,7 @@
|
||||
export * from "./haidan.ts";
|
||||
export * from "./hddolby.ts";
|
||||
export * from "./rousi.ts";
|
||||
export * from "./sunnypt.ts";
|
||||
export * from "./tnode.ts";
|
||||
export * from "./torrentleech.ts";
|
||||
export * from "./yema.ts";
|
||||
@@ -0,0 +1,45 @@
|
||||
/** Rousi.pro `/api/v1/torrents` 响应样例(自行编写,字段名按 MP rousi.py)。 */
|
||||
export const ROUSI_SEARCH_OK = {
|
||||
code: 0,
|
||||
data: {
|
||||
torrents: [
|
||||
{
|
||||
id: 501,
|
||||
title: "The.Bear.S03.2160p.DSNP.WEB-DL.DDP5.1.HDR.H.265",
|
||||
subtitle: "熊家餐馆 第三季",
|
||||
category: { slug: "tv", name: "TV" },
|
||||
size: "50000000000",
|
||||
seeders: 20,
|
||||
leechers: 2,
|
||||
downloads: 77,
|
||||
created_at: "2026-09-20T02:00:00Z",
|
||||
promotion: { is_active: true, down_multiplier: 0, up_multiplier: 2, until: "2026-09-28T00:00:00+08:00" },
|
||||
},
|
||||
{
|
||||
id: 502,
|
||||
title: "Oppenheimer.2023.1080p.BluRay.x264",
|
||||
subtitle: "奥本海默",
|
||||
category: "movie",
|
||||
size: 15000000000,
|
||||
seeders: 50,
|
||||
leechers: 0,
|
||||
downloads: 300,
|
||||
created_at: "2026-09-19 10:00:00",
|
||||
promotion: { is_active: false, down_multiplier: 0.5 },
|
||||
},
|
||||
{
|
||||
id: 503,
|
||||
title: "Variety.Show.E01",
|
||||
category: { slug: "variety" },
|
||||
size: 1,
|
||||
seeders: 1,
|
||||
leechers: 0,
|
||||
downloads: 0,
|
||||
created_at: "2026-09-19 10:00:00",
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export const ROUSI_SEARCH_ERROR = { code: 403, message: "permission denied: torrent:read" };
|
||||
export const ROUSI_DETAIL_OK = { code: 0, data: { download_url: "https://dl.rousi.invalid/t/501?cap=CAPSECRET" } };
|
||||
@@ -0,0 +1,55 @@
|
||||
/** SunnyPT MoviePilot API 响应样例(自行编写,字段名按 MP sunnypt.py)。 */
|
||||
export const SUNNYPT_CATEGORIES_OK = {
|
||||
code: 0,
|
||||
data: [
|
||||
{ id: 401, name: "电影", media_types: ["movie"] },
|
||||
{ id: 402, name: "剧集", media_types: ["tv"] },
|
||||
{ id: 405, name: "动漫", media_types: ["movie", "tv"] },
|
||||
{ id: 406, name: "音乐", media_types: ["music"] },
|
||||
],
|
||||
};
|
||||
|
||||
export const SUNNYPT_SEARCH_OK = {
|
||||
code: "0",
|
||||
data: {
|
||||
items: [
|
||||
{
|
||||
id: 7001,
|
||||
title: "Arcane.S02.2160p.NF.WEB-DL.DDP5.1.Atmos.DV.H.265",
|
||||
subtitle: "英雄联盟:双城之战 第二季",
|
||||
media_type: "tv",
|
||||
category_id: 402,
|
||||
promotion: { is_active: true, down_multiplier: 0, up_multiplier: 1, until: "2026-09-30 23:59:59" },
|
||||
created_at: "2026-09-18 12:00:00",
|
||||
size: 60000000000,
|
||||
seeders: 30,
|
||||
leechers: 1,
|
||||
completed: 150,
|
||||
details_url: "https://sunnypt.top/details.php?id=7001",
|
||||
imdb_id: "tt11126994",
|
||||
tags: ["官方", "中字"],
|
||||
hit_and_run: true,
|
||||
},
|
||||
{
|
||||
id: 7002,
|
||||
title: "Inception.2010.1080p.BluRay",
|
||||
media_type: "movie",
|
||||
category_id: 401,
|
||||
promotion: { is_active: false, down_multiplier: 0 },
|
||||
created_at: "2026-09-18 12:00:00",
|
||||
size: "9000000000",
|
||||
seeders: 3,
|
||||
leechers: 0,
|
||||
completed: 10,
|
||||
details_url: "https://sunnypt.top/details.php?id=7002",
|
||||
hit_and_run: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export const SUNNYPT_SEARCH_ERROR = { code: 40101, msg: "API Key 无效" };
|
||||
export const SUNNYPT_DOWNLOAD_TOKEN_OK = {
|
||||
code: "0",
|
||||
data: { download_url: "/api/v1/mp/torrents/7001/download?token=TOK" },
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
/** TNode 首页(含 CSRF meta)与 `api/torrent/advancedSearch` 响应样例(自行编写)。 */
|
||||
export const TNODE_HOME_HTML = `<!DOCTYPE html><html><head><meta name="x-csrf-token" content="CSRF-TOKEN-XYZ"><title>TNode</title></head><body>ok</body></html>`;
|
||||
export const TNODE_HOME_NO_TOKEN = `<!DOCTYPE html><html><head><title>登录</title></head><body><form><input type="password"></form></body></html>`;
|
||||
|
||||
export const TNODE_SEARCH_OK = {
|
||||
status: 200,
|
||||
data: {
|
||||
total: 2,
|
||||
torrents: [
|
||||
{
|
||||
id: 3301,
|
||||
title: "Evangelion.1.11.2007.1080p.BluRay.x264.FLAC",
|
||||
subtitle: "新世纪福音战士新剧场版:序",
|
||||
upload_time: 1726790400,
|
||||
size: 9876543210,
|
||||
seeding: 15,
|
||||
leeching: 1,
|
||||
complete: 60,
|
||||
downloadRate: 0,
|
||||
uploadRate: 1,
|
||||
imdb: "tt0923811",
|
||||
},
|
||||
{
|
||||
id: 3302,
|
||||
title: "Mushishi.S01.1080p.BluRay",
|
||||
subtitle: "虫师",
|
||||
upload_time: 1726794000,
|
||||
size: 40000000000,
|
||||
seeding: 8,
|
||||
leeching: 0,
|
||||
complete: 20,
|
||||
downloadRate: 0.5,
|
||||
uploadRate: 2,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
/** TorrentLeech `torrents/browse/list/...` 响应样例(自行编写,字段名按 MP torrentleech.py)。 */
|
||||
export const TORRENTLEECH_SEARCH_OK = {
|
||||
numFound: 2,
|
||||
torrentList: [
|
||||
{
|
||||
fid: 240001,
|
||||
filename: "The.Matrix.1999.2160p.UHD.BluRay.x265-TL.torrent",
|
||||
name: "The.Matrix.1999.2160p.UHD.BluRay.x265-TL",
|
||||
addedTimestamp: "2026-09-20 08:00:00",
|
||||
size: 25000000000,
|
||||
seeders: 120,
|
||||
leechers: 4,
|
||||
completed: 999,
|
||||
download_multiplier: 0,
|
||||
imdbID: "tt0133093",
|
||||
categoryID: 47,
|
||||
},
|
||||
{
|
||||
fid: 240002,
|
||||
filename: "Show.S01E01.1080p.WEB.h264.torrent",
|
||||
name: "Show.S01E01.1080p.WEB.h264",
|
||||
addedTimestamp: "2026-09-20 09:00:00",
|
||||
size: 2000000000,
|
||||
seeders: 30,
|
||||
leechers: 1,
|
||||
completed: 100,
|
||||
download_multiplier: 1,
|
||||
categoryID: 32,
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,46 @@
|
||||
/** YemaPT 开放 API 响应样例(自行编写,字段名按 MP yema.py)。 */
|
||||
export const YEMA_SEARCH_MOVIE = {
|
||||
success: true,
|
||||
data: [
|
||||
{
|
||||
id: 88001,
|
||||
showName: "Past.Lives.2023.1080p.WEB-DL.DDP5.1.H.264",
|
||||
shortDesc: "过往人生",
|
||||
categoryId: 4,
|
||||
tagList: ["3", "6", "99"],
|
||||
listingTime: "2026-09-21 09:30:00",
|
||||
fileSize: 8000000000,
|
||||
seedNum: 25,
|
||||
leechNum: 2,
|
||||
completedNum: 80,
|
||||
downloadPromotion: "free",
|
||||
uploadPromotion: "double_upload",
|
||||
downloadPromotionEndTime: "2026-09-29 00:00:00",
|
||||
hrPunishEnable: true,
|
||||
},
|
||||
{ id: "bad-id", showName: "非整数 ID 被跳过" },
|
||||
],
|
||||
};
|
||||
|
||||
export const YEMA_SEARCH_TV_A = {
|
||||
success: true,
|
||||
data: [
|
||||
{
|
||||
id: 88002,
|
||||
showName: "Show.S01.1080p",
|
||||
categoryId: 5,
|
||||
tagList: [],
|
||||
listingTime: "2026-09-21 09:30:00",
|
||||
fileSize: 1,
|
||||
seedNum: 1,
|
||||
leechNum: 0,
|
||||
completedNum: 0,
|
||||
downloadPromotion: "half",
|
||||
uploadPromotion: "one_half",
|
||||
hrPunishEnable: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const YEMA_SEARCH_ERROR = { success: false, errorMessage: "AuthKey 无效" };
|
||||
export const YEMA_DOWNLOAD_KEY_OK = { success: true, data: "DLKEY-123" };
|
||||
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* 极速之星(BitPT)用户信息 fixture(自行编写,不代表真实页面)。
|
||||
* 对应解析器:adapters/src/sites/user-parsers/bitpt.ts(MP bitpt.py)。
|
||||
*/
|
||||
export const BITPT_USER_INDEX = `<html><body><div id="userinfotop"><a href="userdetails.php?uid=55">gina</a></div></body></html>`;
|
||||
|
||||
export const BITPT_USER_DETAILS = `<html><body><table class="frmtable">
|
||||
<tr><td>UID</td><td>55</td></tr>
|
||||
<tr><td>用户名</td><td>gina (在线)</td></tr>
|
||||
<tr><td>用户级别</td><td>Elite</td></tr>
|
||||
<tr><td>注册时间</td><td>2021-06-07 08:09:10</td></tr>
|
||||
<tr><td>上传流量</td><td>3.00 TB</td></tr>
|
||||
<tr><td>下载流量</td><td>1.00 TB</td></tr>
|
||||
<tr><td>共享率</td><td>3.000</td></tr>
|
||||
<tr><td>星辰</td><td>当前100 累计2048.5</td></tr>
|
||||
</table></body></html>`;
|
||||
|
||||
export const BITPT_USER_SEEDING_P1 = `<table class="torrenttable"><tr><td>标题</td><td class="r">大小</td></tr>
|
||||
<tr class="btr"><td>A</td><td class="r"><a>5.00 GB</a></td></tr>
|
||||
<tr class="btr"><td>B</td><td class="r">1.00 GB</td></tr>
|
||||
<tr class="other"><td>C</td><td class="r">9.00 GB</td></tr>
|
||||
</table>`;
|
||||
|
||||
export const BITPT_USER_SEEDING_EMPTY = `<table class="torrenttable"><tr><td>标题</td></tr></table>`;
|
||||
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Discuz!X PT 插件用户信息 fixture(自行编写,不代表真实页面)。
|
||||
* 对应解析器:adapters/src/sites/user-parsers/discuz.ts(MP discuz.py)。
|
||||
*/
|
||||
export const DISCUZ_USER_INDEX = `<html><body>
|
||||
<a href="home.php?mod=space&uid=42">frank<img src="a.png"/></a>
|
||||
<a href="member.php?mod=logging&action=logout">退出</a>
|
||||
</body></html>`;
|
||||
|
||||
export const DISCUZ_USER_SPACE = `<html><body><ul>
|
||||
<li><em>注册时间</em>2018-02-03 04:05</li>
|
||||
<li>分享率 (1.5)</li>
|
||||
<li><em>积分</em>520</li>
|
||||
<li><em>上传量</em>1 TB / 2.00 TB</li>
|
||||
<li><em>下载量</em>10 GB / 500.00 GB</li>
|
||||
</ul>
|
||||
<a href="home.php?mod=spacecp&ac=usergroup">用户组</a> <a href="home.php?mod=spacecp&ac=usergroup&gid=3">Lv3</a>
|
||||
</body></html>`;
|
||||
|
||||
export const DISCUZ_USER_SEEDING = `<table><tr><td>标题</td><td>分类</td><td><img class="size" alt="size"/></td><td><img class="seeders" alt="seeders"/></td></tr>
|
||||
<tr><td>A</td><td>电影</td><td>8.00 GB</td><td>6</td></tr></table>`;
|
||||
@@ -0,0 +1,16 @@
|
||||
/** FileList 用户信息 fixture(自行编写的最小结构,不代表真实站点;对应 adapters/src/sites/user-parsers/file-list.ts)。 */
|
||||
export const FILELIST_USER_INDEX = `<html><body><a href="userdetails.php?id=555">erin</a> <a href="logout.php">Logout</a></body></html>`;
|
||||
|
||||
export const FILELIST_USER_DETAIL = `<html><body><table>
|
||||
<tr><td>Uploaded</td><td>3.00 TB</td></tr>
|
||||
<tr><td>Downloaded</td><td>1.00 TB</td></tr>
|
||||
<tr><td>Share ratio</td><td>3.00</td></tr>
|
||||
<tr><td>Seed bonus</td><td><b>Seeding:</b><span>42</span><b>Size:</b><span>2.50 TB</span></td></tr>
|
||||
<tr><td>Class</td><td> Elite User </td></tr>
|
||||
<tr><td>Join date</td><td>2017-07-08 09:10:11 (8 years ago)</td></tr>
|
||||
</table><a href="shop.php">1,000.5</a></body></html>`;
|
||||
|
||||
export const FILELIST_USER_SEEDING = `<html><body><table>
|
||||
<tr><td>c1</td><td>c2</td><td>c3</td><td>c4</td><td>c5</td><td>Size</td><td>Seeders</td></tr>
|
||||
<tr><td>1</td><td>2</td><td>3</td><td>4</td><td>5</td><td>1.00 GB</td><td>4</td></tr>
|
||||
</table></body></html>`;
|
||||
@@ -0,0 +1,21 @@
|
||||
/** Gazelle 用户信息 fixture(自行编写的最小结构,不代表真实站点;对应 adapters/src/sites/user-parsers/gazelle.ts)。 */
|
||||
export const GAZELLE_USER_INDEX = `<html><body><div id="userinfo"><ul>
|
||||
<li id="nav_userinfo"><a href="user.php?id=4242" class="username">bob</a></li>
|
||||
<li><a href="logout.php?auth=x">Logout</a></li>
|
||||
<li id="stats_seeding">Up: <span>120.50 GB</span></li>
|
||||
<li id="stats_leeching">Down: <span>40.25 GB</span></li>
|
||||
<li><a href="bonus.php" data-tooltip="Bonus points: 1,234.5">Bonus</a></li>
|
||||
</ul></div></body></html>`;
|
||||
|
||||
export const GAZELLE_USER_DETAIL = `<html><body><div class="box_userinfo_stats"><ul>
|
||||
<li>用户等级: Power User</li>
|
||||
<li>加入时间 <span>2019-05-06 07:08:09</span></li>
|
||||
</ul></div></body></html>`;
|
||||
|
||||
export const GAZELLE_USER_SEEDING = `<html><body><table id="torrent_table">
|
||||
<tr><td>Name</td><td>Files</td><td>Time</td><td>Size</td><td>Snatches</td><td>Seeders</td><td>Leechers</td></tr>
|
||||
<tr><td>A</td><td>1</td><td>1d</td><td>10.00 GB</td><td>3</td><td>7</td><td>0</td></tr>
|
||||
<tr><td>B</td><td>2</td><td>2d</td><td>1.00 GB</td><td>1</td><td>2</td><td>0</td></tr>
|
||||
</table></body></html>`;
|
||||
|
||||
export const GAZELLE_LOGIN_PAGE = `<html><body><form action="login.php"><input type="password" name="password"/></form></body></html>`;
|
||||
@@ -0,0 +1,26 @@
|
||||
/** HDDolby 用户信息 fixture(自行编写的最小结构,不代表真实站点;对应 adapters/src/sites/user-parsers/hddolby.ts)。 */
|
||||
export const HDDOLBY_USER_DATA = {
|
||||
status: 0,
|
||||
data: [
|
||||
{
|
||||
id: "1",
|
||||
username: "ivy",
|
||||
added: "2019-03-03 15:30:36",
|
||||
class: "6",
|
||||
uploaded: "852071699418",
|
||||
downloaded: "1885536536",
|
||||
seedbonus: "99774.5",
|
||||
unread_messages: "2",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const HDDOLBY_USER_PEERS = {
|
||||
status: 0,
|
||||
data: [
|
||||
{ size: 1000, seeders: 4 },
|
||||
{ size: 500, seeders: 0 },
|
||||
],
|
||||
};
|
||||
|
||||
export const HDDOLBY_USER_ERROR = { status: 1, error: { message: "invalid api key" } };
|
||||
@@ -0,0 +1,20 @@
|
||||
export * from "./bitpt.ts";
|
||||
export * from "./discuz.ts";
|
||||
export * from "./file-list.ts";
|
||||
export * from "./gazelle.ts";
|
||||
export * from "./hddolby.ts";
|
||||
export * from "./ipt.ts";
|
||||
export * from "./mtorrent.ts";
|
||||
export * from "./nexus-audiences.ts";
|
||||
export * from "./nexus-hhanclub.ts";
|
||||
export * from "./nexus-php.ts";
|
||||
export * from "./nexus-project.ts";
|
||||
export * from "./nexus-rabbit.ts";
|
||||
export * from "./rousi.ts";
|
||||
export * from "./small-horse.ts";
|
||||
export * from "./sunnypt.ts";
|
||||
export * from "./tnode.ts";
|
||||
export * from "./torrent-leech.ts";
|
||||
export * from "./unit3d.ts";
|
||||
export * from "./yema.ts";
|
||||
export * from "./zhixing.ts";
|
||||
@@ -0,0 +1,12 @@
|
||||
/** IPTorrents 用户信息 fixture(自行编写的最小结构,不代表真实站点;对应 adapters/src/sites/user-parsers/ipt.ts)。 */
|
||||
export const IPT_USER_INDEX = `<html><body><a href="/logout.php">Logout</a>
|
||||
<a href="/u/777"><b>dave</b></a>
|
||||
<div class="stats"><div><div>
|
||||
<span>2.500</span><span>1.20 TB</span><span>480.00 GB</span>
|
||||
<a href="/a0">x</a><a href="/a1">y</a><a href="/peers?u=777">15<i></i>3</a><a href="/bonus">4321.5</a>
|
||||
</div></div></div></body></html>`;
|
||||
|
||||
export const IPT_USER_DETAIL = `<html><body><table>
|
||||
<tr><th>Class</th><td>Power User</td></tr>
|
||||
<tr><th>Join date</th><td>2018-01-02 03:04:05 (7 years ago)</td></tr>
|
||||
</table></body></html>`;
|
||||
@@ -0,0 +1,34 @@
|
||||
/** M-Team 用户信息 fixture(自行编写的最小结构,不代表真实站点;对应 adapters/src/sites/user-parsers/mtorrent.ts)。 */
|
||||
export const MTORRENT_USER_PROFILE = {
|
||||
code: "0",
|
||||
message: "SUCCESS",
|
||||
data: {
|
||||
id: "31337",
|
||||
username: "henry",
|
||||
role: "6",
|
||||
memberStatus: { createdDate: "2020-10-11 12:13:14" },
|
||||
memberCount: { uploaded: "3000", downloaded: "1000", shareRate: "3.000", bonus: "4567.8" },
|
||||
},
|
||||
};
|
||||
|
||||
export const MTORRENT_USER_SEEDING_PAGE1 = {
|
||||
code: "0",
|
||||
data: { data: [{ torrent: { size: "100", source: "5" } }, { torrent: { size: "200", source: "2" } }] },
|
||||
};
|
||||
export const MTORRENT_USER_SEEDING_PAGE2 = {
|
||||
code: "0",
|
||||
data: { data: [{ torrent: { size: "300", source: "1" } }] },
|
||||
};
|
||||
export const MTORRENT_USER_PEER_STATUS = { code: "0", data: { seeder: "3", leecher: "0" } };
|
||||
|
||||
export const MTORRENT_USER_MESSAGES = {
|
||||
code: "0",
|
||||
data: {
|
||||
data: [
|
||||
{ id: "m1", unread: true, title: "欢迎", createdDate: "2026-09-01 10:00:00", context: "hello" },
|
||||
{ id: "m2", unread: false, title: "旧消息", createdDate: "2026-08-01 10:00:00", context: "old" },
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export const MTORRENT_USER_KEY_INVALID = { code: "1", message: "key無效" };
|
||||
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* 观众(Audiences)新版 userbar 用户信息 fixture(自行编写的最小结构,不代表真实页面)。
|
||||
* 对应解析器:adapters/src/sites/user-parsers/nexus-audiences.ts(MP nexus_audiences.py)。
|
||||
*/
|
||||
export const AUDIENCES_USER_INDEX = `<html><body><div class="site-userbar">
|
||||
<a class="site-userbar__user" href="userdetails.php?id=233" data-uploader-url="userdetails.php?id=233" data-uploader-label="bob" data-uploader-badge="Elite User" data-uploader-stats='[{"label":"上传量:","tone":"uploaded","value":"2.00 TB"},{"label":"下载量","tone":"downloaded","value":"100.00 GB"},{"label":"爆米花","tone":"bonus","value":"8,888.5"},{"label":"活跃","tone":"active","value":"↑ 30 / ↓ 1"}]'>bob</a>
|
||||
<span class="site-userbar__compact-metric site-userbar__compact-metric--ratio"><span>分享率</span><span>20.48</span></span>
|
||||
<a class="site-userbar__compact-tool site-userbar__compact-tool--has-unread" href="messages.php" title="收件箱 1749/2">信</a>
|
||||
<a href="logout.php">退出</a>
|
||||
</div></body></html>`;
|
||||
|
||||
export const AUDIENCES_USER_DETAILS = `<html><body><table>
|
||||
<tr><td class="rowhead">加入日期</td><td class="rowfollow">2020-05-06 07:08:09 (5年前)</td></tr>
|
||||
</table></body></html>`;
|
||||
|
||||
export const AUDIENCES_USER_SEEDING = `<table class="table table-bordered"><tr><td>类型</td><td>数量</td><td>体积</td></tr>
|
||||
<tr><td>电影</td><td>20</td><td>2.00 TB</td></tr>
|
||||
<tr><td>Total</td><td>31</td><td>3.50 TB</td></tr></table>`;
|
||||
|
||||
export const AUDIENCES_USER_MAILBOX = `<html><body><div class="pm-list">
|
||||
<div class="pm-item-row is-unread"><a href="messageviewer.php?viewmessage=1&id=901">看</a>
|
||||
<span class="pm-item__subject">种子被删除</span><span class="pm-item__time">2026-09-01 10:00:00</span><span class="pm-item__preview">你的种子 X 已被删除</span></div>
|
||||
<div class="pm-item-row is-unread"><a href="messageviewer.php?viewmessage=1&id=902">看</a>
|
||||
<span class="pm-item__subject">欢迎</span><span class="pm-item__time">2026-09-02 11:00:00</span><span class="pm-item__preview">欢迎加入观众</span></div>
|
||||
<div class="pm-item-row"><a href="messageviewer.php?viewmessage=1&id=800">已读</a></div>
|
||||
</div><a href="logout.php">退出</a></body></html>`;
|
||||
|
||||
export const AUDIENCES_USER_MESSAGE_901 = `<html><body><div class="pm-view">
|
||||
<h2 class="pm-hero__title">种子被删除</h2>
|
||||
<div class="pm-view__meta"><span class="pm-view__label">日期</span><span class="pm-view__value">2026-09-01 10:00:00</span></div>
|
||||
<div class="pm-view__body">你的种子 X 因重复已被删除。</div></div></body></html>`;
|
||||
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* 憨憨(HHClub)用户信息 fixture(自行编写的最小结构,仿 #user-info-panel 布局;不代表真实页面)。
|
||||
* 对应解析器:adapters/src/sites/user-parsers/nexus-hhanclub.ts(MP nexus_hhanclub.py)。
|
||||
*/
|
||||
export const HHCLUB_USER_INDEX = `<html><body>
|
||||
<a href="userdetails.php?id=4567"><b class="Power_Name">hana</b></a> <a href="logout.php">登出</a>
|
||||
<div id="user-info-panel">
|
||||
<div>hana</div>
|
||||
<div>
|
||||
<div><div><div>[分享率]: 3.5</div></div></div>
|
||||
<div><div>a</div><div>b</div><div>c</div><div>[上传量]: 1.20 TB</div><div>[下载量]: 300.00 GB</div></div>
|
||||
</div>
|
||||
</div>
|
||||
<a href="mybonus.php">魔力值: 6,789.0</a>
|
||||
</body></html>`;
|
||||
|
||||
export const HHCLUB_USER_DETAILS = `<html><body>
|
||||
<div><span>加入日期</span><span><span title="2022-02-02 02:02:02">3 年前</span></span></div>
|
||||
<b class="Power_Name">Power User</b>
|
||||
</body></html>`;
|
||||
|
||||
/** 已登录但 #user-info-panel 缺失(改版):MP 在 xpath(...)[0] 抛 IndexError */
|
||||
export const HHCLUB_USER_INDEX_NO_PANEL = `<html><body><a href="userdetails.php?id=4567">hana</a> <a href="logout.php">登出</a></body></html>`;
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* NexusPHP 用户信息页 fixture(自行编写的最小结构,仿 NexusPHP 1.x 默认模板;不代表任何真实站点)。
|
||||
* 对应解析器:adapters/src/sites/user-parsers/nexus-php.ts(MP nexus_php.py)。
|
||||
*/
|
||||
export const NEXUS_PHP_INDEX = `<!DOCTYPE html><html><head><title>Demo NexusPHP</title></head><body>
|
||||
<table id="info_block"><tr><td><span class="medium">欢迎回来, <a href="userdetails.php?id=10086" class="User_Name"><b>alice</b></a>
|
||||
[<a href="logout.php">退出</a>] <a href="mybonus.php">[使用]</a>: 12,345.6
|
||||
<font class="color_ratio">分享率:</font> 3.217
|
||||
<font class="color_uploaded">上传量:</font> 1.50 TB
|
||||
<font class="color_downloaded">下载量:</font> 477.30 GB
|
||||
<font class="color_active">当前活动:</font><img class="arrowup" alt="Torrents seeding" />12 <img class="arrowdown" alt="Torrents leeching" />2
|
||||
</span></td>
|
||||
<td><a href="messages.php"><img alt="inbox" /></a> 信息箱 3</td></tr></table>
|
||||
</body></html>`;
|
||||
|
||||
export const NEXUS_PHP_USERDETAILS = `<html><body><table>
|
||||
<tr><td class="rowhead">加入日期</td><td class="rowfollow">2021-03-04 05:06:07 (4年前)</td></tr>
|
||||
<tr><td class="rowhead">等级</td><td class="rowfollow"><img alt="Veteran User" title="Veteran User" src="pic/veteran.gif"/></td></tr>
|
||||
<tr><td class="rowhead">魔力值</td><td class="rowfollow">12345.6</td></tr>
|
||||
</table></body></html>`;
|
||||
|
||||
export const NEXUS_PHP_SEEDING = `<table class="torrents" border="1"><tr><td class="colhead">类型</td><td class="colhead">标题</td><td class="colhead"><img class="size" src="pic/trans.gif" alt="size" /></td><td class="colhead"><img class="seeders" src="pic/trans.gif" alt="seeders" /></td></tr>
|
||||
<tr><td>电影</td><td><a href="details.php?id=1">A</a></td><td>10.00 GB</td><td><b><a href="#">5</a></b></td></tr>
|
||||
<tr><td>剧集</td><td><a href="details.php?id=2">B</a></td><td>1.5 TB</td><td><b><a href="#">12</a></b></td></tr>
|
||||
</table>`;
|
||||
|
||||
export const NEXUS_PHP_LOGIN_PAGE = `<html><body><form action="takelogin.php" method="post"><input name="username"/><input type="password" name="password"/></form></body></html>`;
|
||||
|
||||
export const NEXUS_PHP_CF_CHALLENGE = `<html><head><title>Just a moment...</title></head><body><div id="cf-please-wait"></div></body></html>`;
|
||||
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* NexusProject 变种(star-space 风格 p_user/user_detail.php?uid=)用户信息 fixture(自行编写,不代表真实页面)。
|
||||
* 对应解析器:adapters/src/sites/user-parsers/nexus-project.ts(MP nexus_project.py)。
|
||||
*/
|
||||
export const NEXUS_PROJECT_USER_INDEX = `<html><body>
|
||||
<a href="p_user/user_detail.php?uid=77"><b>carol</b></a> <a href="logout.php">退出</a>
|
||||
<span id="user_info">上传:445.11 G</span><span id="user_info_no_hover">下载:29.61 G</span>
|
||||
</body></html>`;
|
||||
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* NexusRabbit(layui-hares 模板)用户信息 fixture(自行编写,不代表真实页面)。
|
||||
* 对应解析器:adapters/src/sites/user-parsers/nexus-rabbit.ts(MP nexus_rabbit.py)。
|
||||
*/
|
||||
export const RABBIT_USER_INDEX = `<html><body>
|
||||
<a href="user.php?id=5">dave</a> <a href="logout.php">退出</a>
|
||||
<div class="bonus"><div>奶糖余额</div><div> 1,024.5 </div></div>
|
||||
</body></html>`;
|
||||
|
||||
export const RABBIT_USER_DETAILS = `<html><body><div class="layui-hares-user-info-right">
|
||||
<span>用户名:<a href="user.php?id=5"><span> dave </span></a></span>
|
||||
<span>等级:<b>Power User</b></span>
|
||||
<span>注册日期:2022-01-02 03:04:05</span>
|
||||
<span>上传量:1.50 TB</span><span>下载量:500.00 GB</span>
|
||||
<span>分享率:<em>3.072</em></span>
|
||||
</div></body></html>`;
|
||||
|
||||
export const RABBIT_USER_SEEDING_JSON = {
|
||||
data: [
|
||||
{ seeders: "3", size: "1.00 GB" },
|
||||
{ seeders: 7, size: "2.50 GB" },
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
/** Rousi.pro 用户信息 fixture(自行编写的最小结构,不代表真实站点;对应 adapters/src/sites/user-parsers/rousi.ts)。 */
|
||||
export const ROUSI_USER_PROFILE = {
|
||||
code: 0,
|
||||
message: "success",
|
||||
data: {
|
||||
id: 1,
|
||||
username: "jack",
|
||||
level_text: "Lv.5",
|
||||
registered_at: "2024-01-01T00:00:00Z",
|
||||
uploaded: 1073741824,
|
||||
downloaded: 536870912,
|
||||
ratio: 2.0,
|
||||
karma: 1000.5,
|
||||
seeding_leeching_data: {
|
||||
seeding_count: 10,
|
||||
seeding_size: 10737418240,
|
||||
leeching_count: 2,
|
||||
leeching_size: 2147483648,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const ROUSI_USER_ERROR = { code: 401, message: "API Key 无效" };
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Small Horse(Gazelle 变种)用户信息 fixture(自行编写,不代表真实页面)。
|
||||
* 对应解析器:adapters/src/sites/user-parsers/small-horse.ts(MP small_horse.py)。
|
||||
*/
|
||||
export const SMALL_HORSE_USER_INDEX = `<html><body><a href="user.php?id=9">eve</a> <a href="logout.php?auth=x">Logout</a></body></html>`;
|
||||
|
||||
export const SMALL_HORSE_USER_PAGE = `<html><body>
|
||||
<ul class="stats nobullet"><li>Profile</li></ul>
|
||||
<ul class="stats nobullet">
|
||||
<li>Joined: <span title="x">2019-01-01 00:00:00</span></li>
|
||||
<li>Last seen: now</li>
|
||||
<li>Uploaded: 1.00 TB</li>
|
||||
<li>Downloaded: 200.00 GB</li>
|
||||
<li>Ratio: <span class="r20">5.12</span></li>
|
||||
<li>Bonus: 1234.5</li>
|
||||
</ul>
|
||||
<ul class="stats nobullet"><li>Percentile</li></ul>
|
||||
<ul class="stats nobullet"><li>Class: Power User</li></ul>
|
||||
<ul class="stats nobullet"><li>a</li><li>b</li><li>c</li><li>d</li><li>e</li><li>f</li><li>Leeching: 3</li></ul>
|
||||
</body></html>`;
|
||||
|
||||
const row = (size: string, seeders: number) =>
|
||||
`<tr><td>1</td><td>2</td><td>3</td><td>4</td><td>5</td><td>${size}</td><td>7</td><td>${seeders}</td></tr>`;
|
||||
|
||||
export const SMALL_HORSE_USER_SEEDING_P1 = `<table id="torrent_table"><tr><td>h</td></tr>${row("10.00 GB", 4)}${row("5.00 GB", 2)}</table>
|
||||
<ul class="pagination"><li class="active">1</li><li>2</li><li>Next</li></ul>`;
|
||||
|
||||
export const SMALL_HORSE_USER_SEEDING_P2 = `<table id="torrent_table"><tr><td>h</td></tr>${row("1.00 GB", 9)}</table>
|
||||
<ul class="pagination"><li>1</li><li class="active">2</li></ul>`;
|
||||
@@ -0,0 +1,31 @@
|
||||
/** SunnyPT 用户信息 fixture(自行编写的最小结构,不代表真实站点;对应 adapters/src/sites/user-parsers/sunnypt.ts)。 */
|
||||
export const SUNNYPT_USER_PROFILE = {
|
||||
code: 0,
|
||||
msg: "ok",
|
||||
data: {
|
||||
id: 12,
|
||||
username: "kate",
|
||||
level: "Elite User",
|
||||
registered_at: "2023-04-05 06:07:08",
|
||||
uploaded: 4000,
|
||||
downloaded: 2000,
|
||||
ratio: 2,
|
||||
bonus: 88.8,
|
||||
seeding_count: 7,
|
||||
seeding_size: 7000,
|
||||
leeching_count: 1,
|
||||
leeching_size: 100,
|
||||
unread_messages: 1,
|
||||
},
|
||||
};
|
||||
|
||||
export const SUNNYPT_USER_MESSAGES = {
|
||||
code: 0,
|
||||
data: {
|
||||
unread_count: 1,
|
||||
has_more: false,
|
||||
items: [{ id: 5, unread: true, title: "通知", content: "内容", created_at: "2026-09-20 08:00:00" }],
|
||||
},
|
||||
};
|
||||
|
||||
export const SUNNYPT_USER_ERROR = { code: 403, msg: "API Key 无效" };
|
||||
@@ -0,0 +1,28 @@
|
||||
/** TNode 用户信息 fixture(自行编写的最小结构,不代表真实站点;对应 adapters/src/sites/user-parsers/tnode.ts)。 */
|
||||
export const TNODE_USER_INDEX = `<html><head><meta name="x-csrf-token" content="csrf-abc"></head><body></body></html>`;
|
||||
|
||||
export const TNODE_USER_MAIN_INFO = {
|
||||
status: 200,
|
||||
data: {
|
||||
id: 99,
|
||||
username: "gina",
|
||||
class: { name: "Elite" },
|
||||
regTime: "2022-01-02 03:04:05",
|
||||
upload: 2000,
|
||||
download: 1000,
|
||||
bonus: 12.5,
|
||||
unreadAdmin: 1,
|
||||
unreadInbox: 2,
|
||||
unreadSystem: 0,
|
||||
},
|
||||
};
|
||||
|
||||
export const TNODE_USER_SEEDING = {
|
||||
status: 200,
|
||||
data: {
|
||||
torrents: [
|
||||
{ size: 100, seeding: 3 },
|
||||
{ size: 50, seeding: 1 },
|
||||
],
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
/** TorrentLeech 用户信息 fixture(自行编写的最小结构,不代表真实站点;对应 adapters/src/sites/user-parsers/torrent-leech.ts)。 */
|
||||
export const TORRENTLEECH_USER_INDEX = `<html><body><span class="centerTopBar"><a onclick="location.href='/profile/frank/view'">frank</a></span>
|
||||
<a href="/user/account/logout">Logout</a></body></html>`;
|
||||
|
||||
export const TORRENTLEECH_USER_PROFILE = `<html><body><div class="profile-username">frank</div>
|
||||
<div class="profile-uploaded"><span>5.00 TB</span></div>
|
||||
<div class="profile-downloaded"><span>1.00 TB</span></div>
|
||||
<div class="profile-ratio"><span>5.00</span></div>
|
||||
<table class="profileViewTable"><tr><td>Class</td><td>VIP</td></tr><tr><td>Registration date</td><td>2016-06-07 08:09:10</td></tr></table>
|
||||
<span class="total-TL-points">98765.4</span></body></html>`;
|
||||
|
||||
export const TORRENTLEECH_USER_SEEDING = `<html><body><table><tbody>
|
||||
<tr><td>A</td><td>2.00 GB</td><td>3</td><td>4</td><td>5</td><td>6</td><td>11</td></tr>
|
||||
</tbody></table></body></html>`;
|
||||
@@ -0,0 +1,13 @@
|
||||
/** UNIT3D 用户信息 fixture(自行编写的最小结构,不代表真实站点;对应 adapters/src/sites/user-parsers/unit3d.ts)。 */
|
||||
export const UNIT3D_USER_INDEX = `<html><body><nav>
|
||||
<a href="https://pt.example.com/users/carol/settings">Settings</a>
|
||||
<form action="https://pt.example.com/logout" method="POST"><button>Logout</button></form>
|
||||
<a href="https://pt.example.com/users/carol/bonus/earnings"><i></i> 5,678.9</a>
|
||||
<ul class="ratio-bar"><li>上传量: 2.00 TiB</li><li>下载量: 500.00 GiB</li><li>分享率: 4.1</li></ul>
|
||||
</nav></body></html>`;
|
||||
|
||||
export const UNIT3D_USER_DETAIL = `<html><body><div class="content"><span class="badge-user">Elite</span>
|
||||
<h4>Registration date 2020-02-03 04:05:06</h4></div></body></html>`;
|
||||
|
||||
export const UNIT3D_USER_SEEDING = `<html><body><table><thead><tr><th class="name">Name</th><th class="seeders">Seeders</th><th class="size">Size</th></tr></thead>
|
||||
<tbody><tr><td>X</td><td>9</td><td>3.00 GiB</td></tr><tr><td>Y</td><td>1</td><td>1.00 GiB</td></tr></tbody></table></body></html>`;
|
||||
@@ -0,0 +1,15 @@
|
||||
/** YemaPT 用户信息 fixture(自行编写的最小结构,不代表真实站点;对应 adapters/src/sites/user-parsers/yema.ts)。 */
|
||||
export const YEMA_USER_BASIC = {
|
||||
success: true,
|
||||
data: {
|
||||
id: 321,
|
||||
name: "leo",
|
||||
level: 3,
|
||||
registerTime: "2024-05-06 07:08:09",
|
||||
promotionUploadSize: 3000,
|
||||
promotionDownloadSize: 1000,
|
||||
bonus: 12.3,
|
||||
},
|
||||
};
|
||||
|
||||
export const YEMA_USER_ERROR = { success: false, errorMessage: "AuthKey 无效" };
|
||||
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* 知行(Zhixing)用户信息 fixture(自行编写,不代表真实页面)。
|
||||
* 对应解析器:adapters/src/sites/user-parsers/zhixing.ts(MP zhixing.py)。
|
||||
*/
|
||||
export const ZHIXING_USER_INDEX = `<html><body><div id="um"><a href="/user/321/">zed</a></div></body></html>`;
|
||||
|
||||
export const ZHIXING_USER_BASIC = `<html><body><div class="user-details-tabs">
|
||||
<p>UID:321</p><p>用户名:zed</p><p>用户组:Lv2 (查看权限)</p>
|
||||
<p>注册时间:2023-01-01 10:00:00 (2年前)</p>
|
||||
<p>上传流量:1.5 TB</p><p>下载流量:123456</p>
|
||||
<p>共享率:12.5</p><p>保种积分:88.8</p>
|
||||
<p>当前保种数量:7</p><p>当前保种容量:70 GB</p>
|
||||
</div></body></html>`;
|
||||
|
||||
export const ZHIXING_USER_SEEDING_P1 = `<table>
|
||||
<tr id="t1"><td>A</td><td class="r"><a>10 GB</a></td></tr>
|
||||
<tr id="t2"><td>B</td><td class="r">20 GB</td></tr>
|
||||
</table>`;
|
||||
|
||||
export const ZHIXING_USER_SEEDING_EMPTY = `<table><tr><td>没有种子</td></tr></table>`;
|
||||
|
||||
export const ZHIXING_LOGIN_PAGE = `<html><body><form><input type="password"/></form></body></html>`;
|
||||
@@ -1,10 +1,20 @@
|
||||
export * from "./fixtures/definitions/index.ts";
|
||||
export * from "./fixtures/spiders/index.ts";
|
||||
export * from "./fixtures/user-parsers/index.ts";
|
||||
export {
|
||||
createMockFetch,
|
||||
type MockFetch,
|
||||
type MockFetchRequest,
|
||||
type MockRouteHandler,
|
||||
type MockRouteResponse,
|
||||
} from "./mock-fetch.ts";
|
||||
export {
|
||||
createMockMteamApi,
|
||||
type MockMteamApi,
|
||||
type MockMteamFailMode,
|
||||
type MockMteamOptions,
|
||||
} from "./mock-mteam";
|
||||
export { createMockNexusPhpSite, type MockNexusPhpOptions, type MockNexusPhpSite } from "./mock-nexusphp";
|
||||
export { type MockMteamTorrent, MTEAM_FIXTURE_TORRENTS } from "./mteam-fixtures";
|
||||
export { AUDIENCES_FIXTURE_RELEASES, type MockNexusPromo, type MockNexusRelease } from "./nexusphp-fixtures";
|
||||
export { buildFakeTorrent, type FakeTorrent } from "./torrent";
|
||||
} from "./mock-mteam.ts";
|
||||
export { createMockNexusPhpSite, type MockNexusPhpOptions, type MockNexusPhpSite } from "./mock-nexusphp.ts";
|
||||
export { type MockMteamTorrent, MTEAM_FIXTURE_TORRENTS } from "./mteam-fixtures.ts";
|
||||
export { AUDIENCES_FIXTURE_RELEASES, type MockNexusPromo, type MockNexusRelease } from "./nexusphp-fixtures.ts";
|
||||
export { buildFakeTorrent, type FakeTorrent } from "./torrent.ts";
|
||||
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* 路由表式 fetch 替身(站点用户信息解析器 / API spider 测试用)。
|
||||
* 路由键:`METHOD path?query`(query 可省略,省略时匹配任意 query;METHOD 省略为任意方法);
|
||||
* 值为响应描述或函数。未命中返回 404。记录全部请求(含头与 body)供断言。
|
||||
*/
|
||||
|
||||
export interface MockRouteResponse {
|
||||
status?: number;
|
||||
body?: string | object;
|
||||
contentType?: string;
|
||||
headers?: Record<string, string>;
|
||||
}
|
||||
|
||||
export type MockRouteHandler = MockRouteResponse | ((req: MockFetchRequest) => MockRouteResponse);
|
||||
|
||||
export interface MockFetchRequest {
|
||||
method: string;
|
||||
url: URL;
|
||||
headers: Record<string, string>;
|
||||
body: string | null;
|
||||
}
|
||||
|
||||
export interface MockFetch {
|
||||
fetch: typeof globalThis.fetch;
|
||||
requests: MockFetchRequest[];
|
||||
/** 追加/覆盖路由 */
|
||||
route(key: string, handler: MockRouteHandler): void;
|
||||
}
|
||||
|
||||
function match(key: string, req: MockFetchRequest): boolean {
|
||||
let method: string | null = null;
|
||||
let target = key;
|
||||
const sp = key.indexOf(" ");
|
||||
if (sp > 0 && /^[A-Z]+$/.test(key.slice(0, sp))) {
|
||||
method = key.slice(0, sp);
|
||||
target = key.slice(sp + 1);
|
||||
}
|
||||
if (method && method !== req.method) return false;
|
||||
// 可带主机:"https://api.x.com/path"
|
||||
let host: string | null = null;
|
||||
if (/^https?:\/\//.test(target)) {
|
||||
const u = new URL(target);
|
||||
host = u.host;
|
||||
target = u.pathname + u.search;
|
||||
}
|
||||
if (host && host !== req.url.host) return false;
|
||||
const [path, query] = target.split("?");
|
||||
if (path !== req.url.pathname) return false;
|
||||
if (query === undefined) return true;
|
||||
const want = new URLSearchParams(query);
|
||||
for (const [k, v] of want) if (req.url.searchParams.get(k) !== v) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
export function createMockFetch(routes: Record<string, MockRouteHandler> = {}): MockFetch {
|
||||
const table = new Map<string, MockRouteHandler>(Object.entries(routes));
|
||||
const requests: MockFetchRequest[] = [];
|
||||
const fetchImpl = async (input: string | URL | Request, init?: RequestInit): Promise<Response> => {
|
||||
const url = new URL(typeof input === "string" ? input : input instanceof URL ? input.href : input.url);
|
||||
const headers: Record<string, string> = {};
|
||||
new Headers(init?.headers).forEach((v, k) => {
|
||||
headers[k] = v;
|
||||
});
|
||||
const req: MockFetchRequest = {
|
||||
method: (init?.method ?? "GET").toUpperCase(),
|
||||
url,
|
||||
headers,
|
||||
body: typeof init?.body === "string" ? init.body : null,
|
||||
};
|
||||
requests.push(req);
|
||||
// 后加入的路由优先,便于测试覆盖默认路由;同时更具体(带 query)的键优先
|
||||
const keys = [...table.keys()].reverse().sort((a, b) => Number(b.includes("?")) - Number(a.includes("?")));
|
||||
for (const key of keys) {
|
||||
if (!match(key, req)) continue;
|
||||
const h = table.get(key)!;
|
||||
const r = typeof h === "function" ? h(req) : h;
|
||||
const isJson = r.body !== undefined && typeof r.body !== "string";
|
||||
const body = r.body === undefined ? "" : isJson ? JSON.stringify(r.body) : (r.body as string);
|
||||
return new Response(body, {
|
||||
status: r.status ?? 200,
|
||||
headers: {
|
||||
"content-type": r.contentType ?? (isJson ? "application/json" : "text/html; charset=utf-8"),
|
||||
...(r.headers ?? {}),
|
||||
},
|
||||
});
|
||||
}
|
||||
return new Response("not found", { status: 404 });
|
||||
};
|
||||
return {
|
||||
fetch: fetchImpl as typeof globalThis.fetch,
|
||||
requests,
|
||||
route(key, handler) {
|
||||
table.delete(key);
|
||||
table.set(key, handler);
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
import { randomBytes } from "node:crypto";
|
||||
import type { ServerResponse } from "node:http";
|
||||
import { type StartedServer, startHttpServer } from "../rss/server";
|
||||
import { type MockMteamTorrent, MTEAM_FIXTURE_TORRENTS } from "./mteam-fixtures";
|
||||
import { buildFakeTorrent } from "./torrent";
|
||||
import { type StartedServer, startHttpServer } from "../rss/server.ts";
|
||||
import { type MockMteamTorrent, MTEAM_FIXTURE_TORRENTS } from "./mteam-fixtures.ts";
|
||||
import { buildFakeTorrent } from "./torrent.ts";
|
||||
|
||||
export type MockMteamFailMode = "banned_key" | "rate_limit" | "http_429" | "non_json" | "no_dl_token";
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { IncomingMessage, ServerResponse } from "node:http";
|
||||
import { type StartedServer, startHttpServer } from "../rss/server";
|
||||
import { AUDIENCES_FIXTURE_RELEASES, type MockNexusRelease } from "./nexusphp-fixtures";
|
||||
import { buildFakeTorrent } from "./torrent";
|
||||
import { type StartedServer, startHttpServer } from "../rss/server.ts";
|
||||
import { AUDIENCES_FIXTURE_RELEASES, type MockNexusRelease } from "./nexusphp-fixtures.ts";
|
||||
import { buildFakeTorrent } from "./torrent.ts";
|
||||
|
||||
export interface MockNexusPhpOptions {
|
||||
/** 登录态 Cookie;请求 Cookie 头需包含它 */
|
||||
|
||||
Reference in new issue
Block a user