feat(sites): site requests follow browser UA, smooth UA switch, CF cookies browser-only (BR-4)
- every outbound site request (adapter list/search/detail/download/health, RSS, login, user data) carries the account UA via SourcesRuntime.siteFetch; no more Deno default UA - priority: account manual > pending > global setting > browser identity (fallback: same-shape Windows Firefox UA); settings UA default is empty - migration 0056: existing cookie/password accounts keep their old UA as ua_pending until re-login or browser cookie write-back switches cookie+UA in one transaction; browser_provider.identity caches the browser identity - cf_clearance/__cf_bm stripped on browser write-back (names kept only) - browser sessions get per-site egress (same proxy as HTTP, or direct) - site account page shows UA source; raw UA in tech info Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
ebd07c9230
commit
adc853aa64
21 files changed
+955
-72
No files matched your search
@@ -254,8 +254,15 @@ export const MetadataSettings = z.object({
|
||||
});
|
||||
export type MetadataSettings = z.infer<typeof MetadataSettings>;
|
||||
|
||||
/** 固定桌面 Chrome UA(账号未设 UA 时使用) */
|
||||
export const DEFAULT_SITE_USER_AGENT =
|
||||
/**
|
||||
* 读不到浏览器身份(没有浏览器服务 / 旧镜像 / 暂时连不上)时站点请求用的 UA:与浏览器身份同型的 Windows Firefox
|
||||
* (camofox 1.17.0 = Camoufox 152;升级浏览器镜像时同步改这里,BR-4)。
|
||||
*/
|
||||
export const FALLBACK_SITE_USER_AGENT =
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:152.0) Gecko/20100101 Firefox/152.0";
|
||||
|
||||
/** BR-4 之前的缺省 UA(Windows Chrome 140):迁移时据此把存量账号标"待切换",设置里的旧缺省值清空改为跟随浏览器 */
|
||||
export const LEGACY_SITE_USER_AGENT =
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36";
|
||||
|
||||
/** 站点默认(site-center §8):站点行的限流 / 快照周期为空时继承;站点和凭证在站点页管理 */
|
||||
@@ -268,8 +275,10 @@ export const SitesSettings = z.object({
|
||||
.int()
|
||||
.min(300)
|
||||
.max(7 * 24 * 3600),
|
||||
/** 账号未设 UA 时使用 */
|
||||
userAgent: z.string().min(1).max(500),
|
||||
/**
|
||||
* 全局 UA 覆盖:空串 = 跟随浏览器(缺省,BR-4);填了则账号没有手动 UA 时都用它(浏览器仍是自己的 UA,两端会不一致)
|
||||
*/
|
||||
userAgent: z.string().trim().max(500),
|
||||
/** 站点开"使用代理"时生效;null = 不用代理 */
|
||||
proxyUrl: z.url().nullable(),
|
||||
/** 用户数据做种分页上限 */
|
||||
@@ -645,7 +654,7 @@ export const DEFAULT_SETTINGS: GlobalSettings = {
|
||||
sites: {
|
||||
defaultRateLimit: { requestsPerMinute: 20, concurrency: 2 },
|
||||
defaultSnapshotIntervalSec: 3600,
|
||||
userAgent: DEFAULT_SITE_USER_AGENT,
|
||||
userAgent: "",
|
||||
proxyUrl: null,
|
||||
userDataMaxSeedingPages: 10,
|
||||
defaultSnapshotMaxPages: 1,
|
||||
|
||||
@@ -252,8 +252,17 @@ export const SiteAccount = EntityBase.extend({
|
||||
userInfo: JsonObject,
|
||||
/** 账密登录名 */
|
||||
loginName: z.string().nullable(),
|
||||
/** 空 = 全局默认 UA(settings.sites.userAgent) */
|
||||
/** 账号手动填的 UA;空 = 不覆盖(见 userAgentSource / effectiveUserAgent) */
|
||||
userAgent: z.string().nullable(),
|
||||
/**
|
||||
* 当前生效 UA 的来源(BR-4):browser = 跟随浏览器;manual = 账号手动填写;global = 设置里的全局 UA;
|
||||
* pending = 待切换(沿用取得当前 Cookie 时的旧 UA,下次重新登录或浏览器回写 Cookie 时换成浏览器 UA)
|
||||
*/
|
||||
userAgentSource: z.enum(["browser", "manual", "global", "pending"]),
|
||||
/** 当前站点请求实际发送的 UA(技术信息) */
|
||||
effectiveUserAgent: z.string(),
|
||||
/** 仅浏览器用、没有回写给站点请求的 Cookie 名(Cloudflare 验证 Cookie,不含值) */
|
||||
browserOnlyCookies: z.array(z.string()),
|
||||
/** 最近一次登录 / 校验成功(Cookie、API Key 校验成功也算) */
|
||||
lastLoginAt: Timestamp.nullable(),
|
||||
lastError: SiteAccountError.nullable(),
|
||||
|
||||
@@ -83,8 +83,9 @@ const ROWS: Record<string, { row: Obj; dbOnly?: string[]; apiOnly?: string[] }>
|
||||
SiteUserData: { row: C.SiteUserData },
|
||||
SiteAccount: {
|
||||
row: C.SiteAccount,
|
||||
dbOnly: ["totpSecretRef", "sessionSecretRef", "passkeySecretRef"],
|
||||
apiOnly: ["hasTotp", "hasSession", "hasPasskey"],
|
||||
// BR-4:ua_pending 只给解析后的 userAgentSource / effectiveUserAgent;browserOnlyCookies 取自 user_info
|
||||
dbOnly: ["totpSecretRef", "sessionSecretRef", "passkeySecretRef", "uaPending"],
|
||||
apiOnly: ["hasTotp", "hasSession", "hasPasskey", "userAgentSource", "effectiveUserAgent", "browserOnlyCookies"],
|
||||
},
|
||||
SourceRelease: { row: C.SourceRelease },
|
||||
Subscription: { row: C.Subscription, apiOnly: ["mediaEntityTitle"] },
|
||||
@@ -100,7 +101,8 @@ const ROWS: Record<string, { row: Obj; dbOnly?: string[]; apiOnly?: string[] }>
|
||||
ResolutionAttempt: { row: C.ResolutionAttempt },
|
||||
SiteSnapshot: { row: C.SiteSnapshot },
|
||||
// W4-C 浏览器实体(0023)
|
||||
BrowserProvider: { row: C.BrowserProviderRow },
|
||||
// BR-4:identity 是浏览器固定身份的缓存(站点请求 UA 用),不进 API
|
||||
BrowserProvider: { row: C.BrowserProviderRow, dbOnly: ["identity"] },
|
||||
BrowserSession: { row: C.BrowserSession },
|
||||
BrowserTab: { row: C.BrowserTab },
|
||||
BrowserControlLease: { row: C.BrowserControlLease },
|
||||
|
||||
Reference in new issue
Block a user