feat(browser): unify site credentials with browser profiles (BR-2)
- Inject the site account Cookie (manual + auto-login session, session wins)
into the camofox profile before the first tab opens; password accounts
without a session run refresh-login first; API Key sites are skipped.
- Cookie expired after injection -> credential.status=expired; cookie
accounts go to waiting/login_required ("log in once, applies site-wide").
- Write back site-domain cookies from the browser on handover, session close,
every 10 min of activity, and from refresh-login when the stored cookie is
invalid (verified before saving). user_info.cookieOrigin records
source=browser; source shown as manual/login/browser in the origin view.
- New camofox plugin umt-cookies (GET/POST /umt/sessions/:userId/cookies
under the access key); profile dir 0700/files 0600 in the desktop image.
- Single entry: sources-identity/service/credentials.ts.
- Real check: HHClub cookie injected into a fresh profile -> home page
logged in (logout link present), 5 cookies read back.
Requires rebuilding umt-browser-desktop (docker compose build camofox &&
docker compose up -d camofox) to take effect.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
901b01d1a1
commit
28974d1e1a
58 files changed
+2843
-1112
No files matched your search
@@ -1,10 +1,4 @@
|
||||
import {
|
||||
CHDBITS_LIST_PAGE,
|
||||
CHDBITS_LOGIN_PAGE,
|
||||
HHANCLUB_DETAIL_PAGE,
|
||||
HHANCLUB_LIST_PAGE,
|
||||
HHANCLUB_LIST_PAGE_2026,
|
||||
} from "@umt/testkit";
|
||||
import { CHDBITS_LIST_PAGE, CHDBITS_LOGIN_PAGE, HHANCLUB_DETAIL_PAGE, HHANCLUB_LIST_PAGE } from "@umt/testkit";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { getSiteDefinition, listSiteDefinitions } from "../registry.ts";
|
||||
import { parseDetailPage } from "../spider/detail.ts";
|
||||
@@ -108,44 +102,6 @@ describe("HHClub 定义(div 卡片模板)", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("2026-09-29 实测结构:去掉新种标记「[新]」,短剧 412、完成数 0、无促销、时间按 +08:00、下载链接 passkey 脱敏", () => {
|
||||
const [a, b] = parse(HHANCLUB_LIST_PAGE_2026);
|
||||
expect(a).toMatchObject({
|
||||
siteReleaseId: "91001",
|
||||
title: "示例短剧 1080p WEB-DL H265 AAC-HHWEB",
|
||||
subtitle: "示例短剧 | 全30集 | 1080p",
|
||||
category: { id: "412", name: "短剧" },
|
||||
sizeBytes: Math.round(185.38 * 1024 ** 2),
|
||||
seeders: 1,
|
||||
leechers: 58,
|
||||
snatched: 0,
|
||||
promotion: { type: "none", downloadFactor: 1, uploadFactor: 1, expiresAt: null },
|
||||
hr: false,
|
||||
uploadedAt: "2026-09-29T06:07:03.000Z",
|
||||
detailUrl: "https://hhanclub.net/details.php?id=91001&hit=1",
|
||||
});
|
||||
expect(a!.downloadUrl).not.toContain("0000000000000000");
|
||||
expect(b).toMatchObject({
|
||||
title: "Example Show S01 2026 2160p WEB-DL H.264 AAC-HHWEB",
|
||||
category: { id: "402", name: "电视剧" },
|
||||
snatched: 187,
|
||||
uploadedAt: "2026-09-29T04:03:28.000Z",
|
||||
});
|
||||
});
|
||||
|
||||
it("站点访问默认:快照只抓 1 页、健康检测 6 小时、详情缓存 24 小时、搜索并发 1;3 秒 1 个令牌、突发 2", () => {
|
||||
expect(hhanclubDefinition.umt.defaults).toEqual({
|
||||
snapshotIntervalSec: 3600,
|
||||
snapshotMaxPages: 1,
|
||||
healthCheckIntervalSec: 21600,
|
||||
userDataIntervalSec: 21600,
|
||||
searchConcurrency: 1,
|
||||
detailCacheSec: 86400,
|
||||
});
|
||||
expect(hhanclubDefinition.umt.rateLimit).toEqual({ minIntervalMs: 3000, burst: 2 });
|
||||
expect(hhanclubDefinition.umt.vipLevels).toContain("贵宾");
|
||||
});
|
||||
|
||||
it("浏览从 page=0 起(MovieClaw page_offset -1);mtype=tv 选 402/403/407/412", () => {
|
||||
expect(new SiteSpider(hhanclubDefinition, { page: 0 }).getSearchUrl()).toBe(
|
||||
"https://hhanclub.net/torrents.php?page=0",
|
||||
|
||||
@@ -86,17 +86,7 @@
|
||||
"attribute": "title"
|
||||
},
|
||||
"title": {
|
||||
"text": "{% if fields['title_optional'] %}{{ fields['title_optional'] }}{% else %}{{ fields['title_default'] }}{% endif %}",
|
||||
"filters": [
|
||||
{
|
||||
"name": "replace",
|
||||
"args": ["[新]", ""]
|
||||
},
|
||||
{
|
||||
"name": "replace",
|
||||
"args": ["(新)", ""]
|
||||
}
|
||||
]
|
||||
"text": "{% if fields['title_optional'] %}{{ fields['title_optional'] }}{% else %}{{ fields['title_default'] }}{% endif %}"
|
||||
},
|
||||
"details": {
|
||||
"selector": "div.torrent-title a[href*='details.php']",
|
||||
@@ -185,13 +175,13 @@
|
||||
}
|
||||
},
|
||||
"umt": {
|
||||
"version": "[email protected]+umt.1",
|
||||
"version": "[email protected]",
|
||||
"kind": "nexusphp",
|
||||
"displayName": "憨憨",
|
||||
"timezone": "+08:00",
|
||||
"rateLimit": {
|
||||
"minIntervalMs": 3000,
|
||||
"burst": 2
|
||||
"burst": 1
|
||||
},
|
||||
"capabilities": {
|
||||
"list": true,
|
||||
@@ -259,15 +249,6 @@
|
||||
"douban": "a[href*='douban.com']::attr(href)",
|
||||
"fileList": "td.rowfollow > ul > li::text"
|
||||
}
|
||||
},
|
||||
"vipLevels": ["贵宾", "VIP"],
|
||||
"defaults": {
|
||||
"snapshotIntervalSec": 3600,
|
||||
"snapshotMaxPages": 1,
|
||||
"healthCheckIntervalSec": 21600,
|
||||
"userDataIntervalSec": 21600,
|
||||
"searchConcurrency": 1,
|
||||
"detailCacheSec": 86400
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -74,14 +74,6 @@ const CapabilitiesSchema = z.strictObject({
|
||||
download: z.boolean(),
|
||||
rss: z.boolean(),
|
||||
});
|
||||
const AccessDefaultsSchema = z.strictObject({
|
||||
snapshotIntervalSec: z.number().int().min(300).optional(),
|
||||
snapshotMaxPages: z.number().int().min(1).max(10).optional(),
|
||||
healthCheckIntervalSec: z.number().int().min(300).optional(),
|
||||
userDataIntervalSec: z.number().int().min(300).optional(),
|
||||
searchConcurrency: z.number().int().min(1).max(4).optional(),
|
||||
detailCacheSec: z.number().int().min(0).optional(),
|
||||
});
|
||||
const RateLimitSchema = z.strictObject({ minIntervalMs: z.number().int().min(0), burst: z.number().int().min(1) });
|
||||
const TimezoneSchema = z.string().regex(/^[+-]\d{2}:\d{2}$/, "时区须为 ±HH:MM 偏移");
|
||||
const PairSchema = z.tuple([z.string(), z.string()]);
|
||||
@@ -95,8 +87,6 @@ const UmtNexusSchema = z.strictObject({
|
||||
authSupported: z.array(z.enum(["cookie", "credential", "apikey"])).optional(),
|
||||
timezone: TimezoneSchema,
|
||||
rateLimit: RateLimitSchema,
|
||||
/** 站点级访问默认(与 settings.sites 取更保守的一方) */
|
||||
defaults: AccessDefaultsSchema.optional(),
|
||||
capabilities: CapabilitiesSchema,
|
||||
categoryNames: z.record(z.string(), z.string()).optional(),
|
||||
loginSuccess: z.string().optional(),
|
||||
|
||||
Reference in new issue
Block a user