@geoly-ai/social-hub-cli 0.3.47 → 0.3.48

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,25 @@ All notable changes to `@geoly-ai/social-hub-cli` are documented in this file.
4
4
 
5
5
  Release workflow: `pnpm release:cli` → `pnpm publish:cli`.
6
6
 
7
+ ## [0.3.48] - 2026-11-05
8
+
9
+ ### Added
10
+
11
+ - per-subreddit daily real fetches 1 -> 3 (catch-up 1 + manual 2) and clearer refresh rejections
12
+
13
+ ### Fixed
14
+
15
+ - keep --via-curl as deprecated no-op; fix EPA/sop docs; harden static scan
16
+
17
+ ### Changed
18
+
19
+ - remove dead reddit.com direct-fetch code (curl_cffi/cookies/--via-curl)
20
+
21
+ ### Documentation
22
+
23
+ - revert prettier-only churn on reddit-\* md; relock matrix
24
+ - drop curl_cffi/cookie guidance in ext-post-audit and content-rewrite; prettier
25
+
7
26
  ## [0.3.47] - 2026-10-22
8
27
 
9
28
  ### Added
@@ -1,6 +1,6 @@
1
1
  {
2
- "generatedAt": "2026-10-22T03:31:57.523Z",
3
- "cliVersion": "0.3.47",
2
+ "generatedAt": "2026-11-05T10:26:57.105Z",
3
+ "cliVersion": "0.3.48",
4
4
  "commands": [
5
5
  "accounts",
6
6
  "accounts brand-bindings",
@@ -200,7 +200,7 @@ export function registerSubredditAssetCommands(program) {
200
200
  });
201
201
  asset
202
202
  .command("refresh-weekly-stats")
203
- .description("POST .../:id/weekly-stats/refresh — 手动刷新该板块的页面周指标(入队抓取)。每板块每业务日最多真实抓一次,当日已抓返 409 并带 nextAllowedAt;仅 active 资产")
203
+ .description("POST .../:id/weekly-stats/refresh — 手动刷新该板块的页面周指标(入队抓取)。每板块每业务日最多手动真实抓 2 次(与每日补跑 1 次互不占用),已有抓取在途或手动次数用完均返 409 并带 nextAllowedAt(次数用完的码为 manual_refresh_limit_reached);仅 active 资产")
204
204
  .requiredOption("--id <uuid>", "owned_subreddits.id")
205
205
  .option("--dry-run", "预览", false)
206
206
  .option("--apply", "执行写入", false)
@@ -1 +1 @@
1
- {"version":3,"file":"register-subreddit-assets.js","sourceRoot":"","sources":["../src/register-subreddit-assets.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,EAAE,WAAW,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAChE,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAE7D;;;;;;;;;;;;;;;GAeG;AAEH,MAAM,IAAI,GAAG,6BAA6B,CAAC;AAC3C,MAAM,UAAU,GAAG,iCAAiC,CAAC;AAErD,uDAAuD;AACvD,MAAM,UAAU,wBAAwB,CACtC,GAAuB;IAEvB,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IACxC,MAAM,KAAK,GAAG,GAAG;SACd,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;SAC1B,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACrC,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;AACvC,CAAC;AAED,MAAM,UAAU,8BAA8B,CAAC,OAAgB;IAC7D,MAAM,KAAK,GAAG,OAAO;SAClB,OAAO,CAAC,iBAAiB,CAAC;SAC1B,WAAW,CACV,uDAAuD,CACxD,CAAC;IAEJ,2EAA2E;IAE3E,KAAK;SACF,OAAO,CAAC,MAAM,CAAC;SACf,WAAW,CACV,wDAAwD,CACzD;SACA,MAAM,CAAC,oBAAoB,EAAE,0BAA0B,CAAC;SACxD,MAAM,CACL,sBAAsB,EACtB,sDAAsD,CACvD;SACA,MAAM,CAAC,mBAAmB,EAAE,aAAa,CAAC;SAC1C,MAAM,CAAC,aAAa,EAAE,eAAe,CAAC;SACtC,MAAM,CAAC,cAAc,EAAE,IAAI,CAAC;SAC5B,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;QACrB,MAAM,oBAAoB,CAAC,KAAK,IAAI,EAAE;YACpC,MAAM,mBAAmB,CACvB;gBACE,OAAO,EAAE,sBAAsB;gBAC/B,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;aACxD,EACD,KAAK,IAAI,EAAE;gBACT,MAAM,MAAM,GAAG,IAAI,eAAe,EAAE,CAAC;gBACrC,IAAI,IAAI,CAAC,WAAW;oBAClB,MAAM,CAAC,GAAG,CAAC,aAAa,EAAE,IAAI,CAAC,WAAqB,CAAC,CAAC;gBACxD,IAAI,IAAI,CAAC,aAAa;oBACpB,MAAM,CAAC,GAAG,CAAC,eAAe,EAAE,IAAI,CAAC,aAAuB,CAAC,CAAC;gBAC5D,IAAI,IAAI,CAAC,OAAO;oBAAE,MAAM,CAAC,GAAG,CAAC,SAAS,EAAE,IAAI,CAAC,OAAiB,CAAC,CAAC;gBAChE,IAAI,IAAI,CAAC,KAAK;oBAAE,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,KAAe,CAAC,CAAC;gBAC1D,IAAI,IAAI,CAAC,MAAM;oBAAE,MAAM,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,CAAC,MAAgB,CAAC,CAAC;gBAC7D,MAAM,EAAE,GAAG,MAAM,CAAC,QAAQ,EAAE,CAAC;gBAC7B,WAAW,CAAC,MAAM,UAAU,CAAC,GAAG,IAAI,GAAG,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;YAChE,CAAC,CACF,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEL,KAAK;SACF,OAAO,CAAC,KAAK,CAAC;SACd,WAAW,CAAC,8CAA8C,CAAC;SAC3D,cAAc,CAAC,aAAa,EAAE,qBAAqB,CAAC;SACpD,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;QACrB,MAAM,oBAAoB,CAAC,KAAK,IAAI,EAAE;YACpC,MAAM,mBAAmB,CACvB;gBACE,OAAO,EAAE,qBAAqB;gBAC9B,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;aACxD,EACD,KAAK,IAAI,EAAE;gBACT,WAAW,CACT,MAAM,UAAU,CACd,GAAG,IAAI,IAAI,kBAAkB,CAAC,IAAI,CAAC,EAAY,CAAC,EAAE,CACnD,CACF,CAAC;YACJ,CAAC,CACF,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEL,KAAK;SACF,OAAO,CAAC,cAAc,CAAC;SACvB,WAAW,CACV,2HAA2H,CAC5H;SACA,cAAc,CAAC,aAAa,EAAE,qBAAqB,CAAC;SACpD,MAAM,CAAC,aAAa,EAAE,kBAAkB,CAAC;SACzC,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;QACrB,MAAM,oBAAoB,CAAC,KAAK,IAAI,EAAE;YACpC,MAAM,mBAAmB,CACvB;gBACE,OAAO,EAAE,8BAA8B;gBACvC,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;aACxD,EACD,KAAK,IAAI,EAAE;gBACT,MAAM,MAAM,GAAG,IAAI,eAAe,EAAE,CAAC;gBACrC,IAAI,IAAI,CAAC,KAAK;oBAAE,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,KAAe,CAAC,CAAC;gBAC1D,MAAM,EAAE,GAAG,MAAM,CAAC,QAAQ,EAAE,CAAC;gBAC7B,WAAW,CACT,MAAM,UAAU,CACd,GAAG,IAAI,IAAI,kBAAkB,CAAC,IAAI,CAAC,EAAY,CAAC,gBAC9C,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,EAClB,EAAE,CACH,CACF,CAAC;YACJ,CAAC,CACF,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEL,sEAAsE;IAEtE,KAAK;SACF,OAAO,CAAC,oBAAoB,CAAC;SAC7B,WAAW,CACV,yEAAyE,CAC1E;SACA,cAAc,CACb,oBAAoB,EACpB,sBAAsB,CACvB;SACA,MAAM,CAAC,oBAAoB,EAAE,QAAQ,CAAC;SACtC,MAAM,CAAC,WAAW,EAAE,IAAI,EAAE,KAAK,CAAC;SAChC,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,CAAC;SAChC,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;QACrB,MAAM,oBAAoB,CAAC,KAAK,IAAI,EAAE;YACpC,MAAM,mBAAmB,CACvB;gBACE,OAAO,EAAE,oCAAoC;gBAC7C,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;gBACzD,QAAQ,EAAE,IAAI;aACf,EACD,KAAK,IAAI,EAAE;gBACT,MAAM,IAAI,GAA4B,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC;gBAC1D,IAAI,IAAI,CAAC,WAAW;oBAAE,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,WAAW,CAAC;gBAC1D,IACE,CAAC,mBAAmB,CAAC,IAAI,EAAE;oBACzB,OAAO,EAAE,oCAAoC;oBAC7C,MAAM,EAAE,OAAO;oBACf,OAAO,EAAE,IAAI;iBACd,CAAC,EACF,CAAC;oBACD,OAAO;gBACT,CAAC;gBACD,WAAW,CACT,MAAM,UAAU,CAAC,GAAG,IAAI,aAAa,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CACjE,CAAC;YACJ,CAAC,CACF,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEL,wEAAwE;IAExE,KAAK;SACF,OAAO,CAAC,UAAU,CAAC;SACnB,WAAW,CACV,uEAAuE,CACxE;SACA,cAAc,CACb,oBAAoB,EACpB,sBAAsB,CACvB;SACA,MAAM,CAAC,oBAAoB,EAAE,QAAQ,CAAC;SACtC,MAAM,CACL,mBAAmB,EACnB,kCAAkC,CACnC;SACA,MAAM,CAAC,oBAAoB,EAAE,6BAA6B,CAAC;SAC3D,MAAM,CAAC,aAAa,EAAE,IAAI,CAAC;SAC3B,MAAM,CAAC,WAAW,EAAE,IAAI,EAAE,KAAK,CAAC;SAChC,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,CAAC;SAChC,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;QACrB,MAAM,oBAAoB,CAAC,KAAK,IAAI,EAAE;YACpC,MAAM,mBAAmB,CACvB;gBACE,OAAO,EAAE,0BAA0B;gBACnC,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;gBACzD,QAAQ,EAAE,IAAI;aACf,EACD,KAAK,IAAI,EAAE;gBACT,MAAM,IAAI,GAA4B,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC;gBAC1D,IAAI,IAAI,CAAC,WAAW;oBAAE,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,WAAW,CAAC;gBAC1D,IAAI,IAAI,CAAC,OAAO;oBAAE,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;gBAC9C,IAAI,IAAI,CAAC,WAAW;oBAAE,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,WAAW,CAAC;gBAC1D,IAAI,IAAI,CAAC,KAAK;oBAAE,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;gBACxC,IACE,CAAC,mBAAmB,CAAC,IAAI,EAAE;oBACzB,OAAO,EAAE,0BAA0B;oBACnC,MAAM,EAAE,OAAO;oBACf,OAAO,EAAE,IAAI;iBACd,CAAC,EACF,CAAC;oBACD,OAAO;gBACT,CAAC;gBACD,WAAW,CAAC,MAAM,UAAU,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;YAChE,CAAC,CACF,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEL,KAAK;SACF,OAAO,CAAC,YAAY,CAAC;SACrB,WAAW,CACV,6EAA6E,CAC9E;SACA,cAAc,CAAC,aAAa,EAAE,qBAAqB,CAAC;SACpD,cAAc,CAAC,oBAAoB,EAAE,0BAA0B,CAAC;SAChE,cAAc,CAAC,mBAAmB,EAAE,aAAa,CAAC;SAClD,MAAM,CAAC,cAAc,EAAE,WAAW,CAAC;SACnC,MAAM,CAAC,WAAW,EAAE,IAAI,EAAE,KAAK,CAAC;SAChC,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,CAAC;SAChC,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;QACrB,MAAM,oBAAoB,CAAC,KAAK,IAAI,EAAE;YACpC,MAAM,mBAAmB,CACvB;gBACE,OAAO,EAAE,4BAA4B;gBACrC,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;gBACzD,QAAQ,EAAE,IAAI;aACf,EACD,KAAK,IAAI,EAAE;gBACT,MAAM,IAAI,GAA4B;oBACpC,WAAW,EAAE,IAAI,CAAC,WAAW;oBAC7B,UAAU,EAAE,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC;iBACpC,CAAC;gBACF,IAAI,IAAI,CAAC,MAAM;oBAAE,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;gBAC3C,IACE,CAAC,mBAAmB,CAAC,IAAI,EAAE;oBACzB,OAAO,EAAE,4BAA4B;oBACrC,MAAM,EAAE,OAAO;oBACf,OAAO,EAAE,IAAI;iBACd,CAAC,EACF,CAAC;oBACD,OAAO;gBACT,CAAC;gBACD,WAAW,CACT,MAAM,UAAU,CACd,GAAG,IAAI,IAAI,kBAAkB,CAAC,IAAI,CAAC,EAAY,CAAC,SAAS,EACzD,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,CAC1B,CACF,CAAC;YACJ,CAAC,CACF,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEL,KAAK;SACF,OAAO,CAAC,sBAAsB,CAAC;SAC/B,WAAW,CACV,+GAA+G,CAChH;SACA,cAAc,CAAC,aAAa,EAAE,qBAAqB,CAAC;SACpD,MAAM,CAAC,WAAW,EAAE,IAAI,EAAE,KAAK,CAAC;SAChC,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,CAAC;SAChC,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;QACrB,MAAM,oBAAoB,CAAC,KAAK,IAAI,EAAE;YACpC,MAAM,mBAAmB,CACvB;gBACE,OAAO,EAAE,sCAAsC;gBAC/C,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;gBACzD,QAAQ,EAAE,IAAI;aACf,EACD,KAAK,IAAI,EAAE;gBACT,IACE,CAAC,mBAAmB,CAAC,IAAI,EAAE;oBACzB,OAAO,EAAE,sCAAsC;oBAC/C,MAAM,EAAE,OAAO;oBACf,OAAO,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE;iBACzB,CAAC,EACF,CAAC;oBACD,OAAO;gBACT,CAAC;gBACD,WAAW,CACT,MAAM,UAAU,CACd,GAAG,IAAI,IAAI,kBAAkB,CAC3B,IAAI,CAAC,EAAY,CAClB,uBAAuB,EACxB,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,EAAE,CAC7B,CACF,CAAC;YACJ,CAAC,CACF,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEL,sEAAsE;IAEtE,MAAM,GAAG,GAAG,KAAK;SACd,OAAO,CAAC,WAAW,CAAC;SACpB,WAAW,CAAC,oCAAoC,CAAC,CAAC;IAErD,GAAG;SACA,OAAO,CAAC,KAAK,CAAC;SACd,WAAW,CACV,qFAAqF,CACtF;SACA,cAAc,CAAC,6BAA6B,EAAE,qBAAqB,CAAC;SACpE,cAAc,CAAC,4BAA4B,EAAE,kBAAkB,CAAC;SAChE,MAAM,CAAC,eAAe,EAAE,aAAa,EAAE,KAAK,CAAC;SAC7C,MAAM,CACL,+BAA+B,EAC/B,kCAAkC,CACnC;SACA,MAAM,CAAC,aAAa,EAAE,IAAI,CAAC;SAC3B,MAAM,CAAC,WAAW,EAAE,IAAI,EAAE,KAAK,CAAC;SAChC,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,CAAC;SAChC,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;QACrB,MAAM,oBAAoB,CAAC,KAAK,IAAI,EAAE;YACpC,MAAM,mBAAmB,CACvB;gBACE,OAAO,EAAE,+BAA+B;gBACxC,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;gBACzD,QAAQ,EAAE,IAAI;aACf,EACD,KAAK,IAAI,EAAE;gBACT,MAAM,IAAI,GAA4B;oBACpC,eAAe,EAAE,IAAI,CAAC,eAAe;iBACtC,CAAC;gBACF,IAAI,IAAI,CAAC,UAAU;oBAAE,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC;gBAC5C,MAAM,KAAK,GAAG,wBAAwB,CACpC,IAAI,CAAC,mBAAyC,CAC/C,CAAC;gBACF,IAAI,KAAK,KAAK,SAAS;oBAAE,IAAI,CAAC,mBAAmB,GAAG,KAAK,CAAC;gBAC1D,IAAI,IAAI,CAAC,KAAK;oBAAE,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;gBACxC,IACE,CAAC,mBAAmB,CAAC,IAAI,EAAE;oBACzB,OAAO,EAAE,+BAA+B;oBACxC,MAAM,EAAE,OAAO;oBACf,OAAO,EAAE,IAAI;iBACd,CAAC,EACF,CAAC;oBACD,OAAO;gBACT,CAAC;gBACD,WAAW,CACT,MAAM,UAAU,CACd,GAAG,IAAI,IAAI,kBAAkB,CAC3B,IAAI,CAAC,gBAA0B,CAChC,aAAa,EACd,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,CACzB,CACF,CAAC;YACJ,CAAC,CACF,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEL,GAAG;SACA,OAAO,CAAC,QAAQ,CAAC;SACjB,WAAW,CACV,+DAA+D,CAChE;SACA,cAAc,CAAC,qBAAqB,EAAE,iCAAiC,CAAC;SACxE,cAAc,CAAC,mBAAmB,EAAE,aAAa,CAAC;SAClD,MAAM,CAAC,cAAc,EAAE,WAAW,CAAC;SACnC,MAAM,CAAC,WAAW,EAAE,IAAI,EAAE,KAAK,CAAC;SAChC,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,CAAC;SAChC,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;QACrB,MAAM,oBAAoB,CAAC,KAAK,IAAI,EAAE;YACpC,MAAM,mBAAmB,CACvB;gBACE,OAAO,EAAE,kCAAkC;gBAC3C,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;gBACzD,QAAQ,EAAE,IAAI;aACf,EACD,KAAK,IAAI,EAAE;gBACT,MAAM,IAAI,GAA4B;oBACpC,UAAU,EAAE,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC;iBACpC,CAAC;gBACF,IAAI,IAAI,CAAC,MAAM;oBAAE,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;gBAC3C,IACE,CAAC,mBAAmB,CAAC,IAAI,EAAE;oBACzB,OAAO,EAAE,kCAAkC;oBAC3C,MAAM,EAAE,OAAO;oBACf,OAAO,EAAE,IAAI;iBACd,CAAC,EACF,CAAC;oBACD,OAAO;gBACT,CAAC;gBACD,WAAW,CACT,MAAM,UAAU,CACd,GAAG,IAAI,eAAe,kBAAkB,CACtC,IAAI,CAAC,SAAmB,CACzB,SAAS,EACV,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,CACzB,CACF,CAAC;YACJ,CAAC,CACF,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEL,GAAG;SACA,OAAO,CAAC,SAAS,CAAC;SAClB,WAAW,CACV,qEAAqE,CACtE;SACA,cAAc,CAAC,qBAAqB,EAAE,oBAAoB,CAAC;SAC3D,cAAc,CAAC,mBAAmB,EAAE,aAAa,CAAC;SAClD,cAAc,CAAC,iCAAiC,EAAE,UAAU,CAAC;SAC7D,MAAM,CAAC,+BAA+B,EAAE,cAAc,CAAC;SACvD,MAAM,CAAC,cAAc,EAAE,WAAW,CAAC;SACnC,MAAM,CAAC,WAAW,EAAE,IAAI,EAAE,KAAK,CAAC;SAChC,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,CAAC;SAChC,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;QACrB,MAAM,oBAAoB,CAAC,KAAK,IAAI,EAAE;YACpC,MAAM,mBAAmB,CACvB;gBACE,OAAO,EAAE,mCAAmC;gBAC5C,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;gBACzD,QAAQ,EAAE,IAAI;aACf,EACD,KAAK,IAAI,EAAE;gBACT,MAAM,IAAI,GAA4B;oBACpC,SAAS,EAAE,IAAI,CAAC,SAAS;oBACzB,UAAU,EAAE,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC;oBACnC,mBAAmB,EAAE,IAAI,CAAC,mBAAmB;iBAC9C,CAAC;gBACF,MAAM,KAAK,GAAG,wBAAwB,CACpC,IAAI,CAAC,mBAAyC,CAC/C,CAAC;gBACF,IAAI,KAAK,KAAK,SAAS;oBAAE,IAAI,CAAC,mBAAmB,GAAG,KAAK,CAAC;gBAC1D,IAAI,IAAI,CAAC,MAAM;oBAAE,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;gBAC3C,IACE,CAAC,mBAAmB,CAAC,IAAI,EAAE;oBACzB,OAAO,EAAE,mCAAmC;oBAC5C,MAAM,EAAE,OAAO;oBACf,OAAO,EAAE,IAAI;iBACd,CAAC,EACF,CAAC;oBACD,OAAO;gBACT,CAAC;gBACD,WAAW,CACT,MAAM,UAAU,CAAC,GAAG,IAAI,qBAAqB,EAAE;oBAC7C,MAAM,EAAE,MAAM;oBACd,IAAI;iBACL,CAAC,CACH,CAAC;YACJ,CAAC,CACF,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEL,0EAA0E;IAE1E,MAAM,UAAU,GAAG,KAAK;SACrB,OAAO,CAAC,YAAY,CAAC;SACrB,WAAW,CACV,2DAA2D,CAC5D,CAAC;IAEJ,UAAU;SACP,OAAO,CAAC,KAAK,CAAC;SACd,WAAW,CACV,yDAAyD,CAC1D;SACA,MAAM,CAAC,KAAK,IAAI,EAAE;QACjB,MAAM,oBAAoB,CAAC,KAAK,IAAI,EAAE;YACpC,MAAM,mBAAmB,CACvB;gBACE,OAAO,EAAE,gCAAgC;gBACzC,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;aACxD,EACD,KAAK,IAAI,EAAE;gBACT,WAAW,CAAC,MAAM,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC;YAC5C,CAAC,CACF,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEL,UAAU;SACP,OAAO,CAAC,KAAK,CAAC;SACd,WAAW,CACV,kFAAkF,CACnF;SACA,cAAc,CAAC,YAAY,EAAE,wBAAwB,CAAC;SACtD,cAAc,CACb,mBAAmB,EACnB,mCAAmC,CACpC;SACA,MAAM,CAAC,YAAY,EAAE,WAAW,CAAC;SACjC,MAAM,CAAC,WAAW,EAAE,IAAI,EAAE,KAAK,CAAC;SAChC,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,CAAC;SAChC,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;QACrB,MAAM,oBAAoB,CAAC,KAAK,IAAI,EAAE;YACpC,MAAM,mBAAmB,CACvB;gBACE,OAAO,EAAE,gCAAgC;gBACzC,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;gBACzD,QAAQ,EAAE,IAAI;aACf,EACD,KAAK,IAAI,EAAE;gBACT,MAAM,IAAI,GAA4B;oBACpC,IAAI,EAAE,IAAI,CAAC,IAAI;oBACf,UAAU,EAAE,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC;iBACpC,CAAC;gBACF,IAAI,IAAI,CAAC,IAAI;oBAAE,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;gBACrC,IACE,CAAC,mBAAmB,CAAC,IAAI,EAAE;oBACzB,OAAO,EAAE,gCAAgC;oBACzC,oCAAoC;oBACpC,MAAM,EAAE,OAAO;oBACf,OAAO,EAAE,IAAI;iBACd,CAAC,EACF,CAAC;oBACD,OAAO;gBACT,CAAC;gBACD,WAAW,CACT,MAAM,UAAU,CAAC,UAAU,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CACxD,CAAC;YACJ,CAAC,CACF,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACP,CAAC"}
1
+ {"version":3,"file":"register-subreddit-assets.js","sourceRoot":"","sources":["../src/register-subreddit-assets.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geoly-ai/social-hub-cli",
3
- "version": "0.3.47",
3
+ "version": "0.3.48",
4
4
  "type": "module",
5
5
  "description": "social-hub CLI for Social Ops Hub",
6
6
  "repository": {
@@ -1,19 +1,19 @@
1
1
  {
2
2
  "version": 1,
3
3
  "algorithm": "sha256",
4
- "fileCount": 1657,
4
+ "fileCount": 1658,
5
5
  "files": {
6
6
  "README.md": "5246d4ae2712e3e656b101041929183d7ae7f8ea487dacf5854187826ab7c335",
7
- "manifest.json": "f470227ddcda7c66342429a7b22b573fc438c86e1571aee8894fa8e256851209",
7
+ "manifest.json": "739ba1b87c8ed725d487d2881b925f3315c867d878c5fb056cddb5956cd97eca",
8
8
  "reddit-answers-research/SKILL.md": "8936215363b5b222f4010fd18349cb2c56b473ce659bfeb21116ad5448faa640",
9
9
  "reddit-answers-research/references/browser-playbook.md": "a8f61d3d43ca93d46dadc8a092a515ddcd32df12fb1d08d334cce2e02709d915",
10
10
  "reddit-answers-research/references/report-template.md": "9aa9acf555ba8b2ca503bdbdf5750a5f53c66e19989bf6807fa6200c7317e306",
11
11
  "reddit-brand-risk-response/SKILL.md": "590ff3254f3c027bd300f422ef5f9c36b05ef0e1cc470655f5705855911b54bf",
12
12
  "reddit-brand-risk-response/agents/openai.yaml": "00aff3fc6333d4a99b8bb51c282a02f5a10d67bf1279bbdf6e5bd51384e30c67",
13
- "reddit-content-rewrite/SKILL.md": "5fc998f119e94d82e51779043e9df765de514824eee343384f91d0eb7310bbcd",
13
+ "reddit-content-rewrite/SKILL.md": "7f7e98ca336eda259ad6cd01a36a85dcd6ad2ab40c4895e0df843eab1e0ab4e7",
14
14
  "reddit-content-rewrite/matrix-contract.md": "6af594450dc0f900070ae33cc6b9e437bca86e9500475d26b6f97ffba9b9db64",
15
- "reddit-content-rewrite/prompts.md": "b2f7195bd6a484c7fca3832948fc05d766c6411338021bf64a4fbe09989eed27",
16
- "reddit-content-rewrite/sop-reference.md": "dec597ddbc448211dd6c40bd0c087a703b734c315bbc5e9aac9cfb8d0b53fc4e",
15
+ "reddit-content-rewrite/prompts.md": "0eb54fb4ca346843b6a7cbac43e3176da40a08dd36b0c35b1fdb55468a8275f3",
16
+ "reddit-content-rewrite/sop-reference.md": "3640269dca6bab003f405f14f7e311fce5b495d5b045ff5ab64914432737c7ea",
17
17
  "reddit-content-writing/SKILL.md": "9ed5571500faa3511c0caf29845ef279cf7106ca236c2b749a3457eceb2f0d1b",
18
18
  "reddit-content-writing/references/comp-mention-strategy.md": "c6855848468379ee3a5618307a82f4ae08263ff397cbd66772f00a9b4411aa58",
19
19
  "reddit-content-writing/references/pgc-writing-strategy.md": "63cc15a7734eb0f00e5854f0d6dbffe51db23262c281ea963bef9afa0c92ebe5",
@@ -25,8 +25,8 @@
25
25
  "reddit-delivery-check/SKILL.md": "7a645cadecfc6f6224a5fe963c6027bd5e1700d6b29a57fef13784a028806b65",
26
26
  "reddit-delivery-check/references/phase5-delivery.md": "50abd9f6d1a433d52d915221aca17f4f279d9de62190442437c9b92056efcd8a",
27
27
  "reddit-ext-post-audit/SKILL.md": "3cd4310d810eabb58c64116aafa6e373a2da49dc5dfaeaea96f3d1c8af629341",
28
- "reddit-ext-post-audit/references/ext-post-audit.md": "0d2fc69ca38b84520409cf866284803ceacbceb82dcdea0df3ed52ad8183a2b6",
29
- "reddit-matrix.lock.json": "5e76c65a6236c9455d6af8b423647ab3f2094127f1b9fc3e5c5fd808175796c4",
28
+ "reddit-ext-post-audit/references/ext-post-audit.md": "c1f3dfc838f2645d259ea185117e68813315ba2f15e13f3805f9125f0e50fe54",
29
+ "reddit-matrix.lock.json": "0ac0b9e2ae27508fed6dcd36257d5f9de55d2aea31b4613b9dbe258a22d2e921",
30
30
  "reddit-monthly-iteration/SKILL.md": "cb13d1bd821c3361c3bc557beedd78f17d6278cdfbe4dd171d1a6d3b1aa19069",
31
31
  "reddit-monthly-iteration/references/monthly-iteration.md": "fcaf88824e1f7cf80a446919edbcb3685954d31c5606d53836e00589e9524524",
32
32
  "reddit-phase-brief/SKILL.md": "7b4ba2b0aab929ef01c6b07ccbc390f744e08a1acc03d04a969d296a77c2f8f8",
@@ -65,14 +65,14 @@
65
65
  "reddit-subreddit-compliance/SKILL.md": "12333387bf618e3b1304f5b21a32712a3101c80bcf3daac18754b0bff4129cd5",
66
66
  "reddit-subreddit-compliance/matrix-contract.md": "94cc3eab9133354c58283b4f11f72fc3fdd6e95b9b40b6d250dda86581e452ce",
67
67
  "reddit-subreddit-compliance/references/output-templates.md": "589f44c9544c72e72dcfe8c1a06ec67690214964d0394c652781e469fb9b2fe1",
68
- "reddit-voc-volume/SKILL.md": "edbe500db0397240867a8d7a44b1bb973dcb6865a8b82ee5ba98ce43f973db87",
68
+ "reddit-voc-volume/SKILL.md": "bca757c89b75ebc37e83f40087eafed9d068f133be6e403ebb86e45acc04c75f",
69
69
  "reddit-voc-volume/matrix-contract.md": "5676c3a847c35062e8432538e0beb98015dfae4a8144da9d437080211a384b11",
70
- "reddit-voc-volume/references/arctic-shift.md": "c4cec5a64b72e9d528a085d92330833afd175a673310c63968d3b0d4b60f4131",
70
+ "reddit-voc-volume/references/arctic-shift.md": "3a302d39c702e61ad633602bc5ca8ab901d4a6b1bc8b77e5ede5ce7da797bf96",
71
71
  "reddit-voc-volume/references/browser-fallback.md": "aa68e38b2fb3026034b8df0e2fcfa950f23b6b235b23df66f68062771f1531c9",
72
72
  "reddit-voc-volume/references/hub-first.md": "36d178bc848ab2a507afdb1feacdf22e67ae775b8d221d50cb700ee2d088d547",
73
- "reddit-voc-volume/requirements.txt": "91f21d1e6a42dc29db733a5914288ba3f74f3d65d33d49a966fdb60eebd435cc",
74
- "reddit-voc-volume/scripts/reddit_voc_volume.py": "daf5df32c632b139c2944748b2b31610bd42ba2a578c8aa6a16659e674d4ecda",
75
- "reddit-voc-volume/scripts/setup_env.sh": "2e9de90f1a8985953d161beda490c34b4cb5867a2d2d217766d97fd6fe373ec4",
73
+ "reddit-voc-volume/requirements.txt": "43b1afb7a184c45af13a495f32ff3966b83d91f0726c2fc6827d760086ffae61",
74
+ "reddit-voc-volume/scripts/reddit_voc_volume.py": "0864fc03b7f8e3edcdd782768f5831347c3a86235d34cc3760295b99b2cbe88d",
75
+ "reddit-voc-volume/scripts/setup_env.sh": "da02fd1ac7d914602a321a34ba403dfea30078e94df5f7b466464c0502f81c30",
76
76
  "reddit-voc-volume/scripts/setup_firecrawl_service.py": "8d25d38761d9db169bf403a49afdc7892bc5df09910d6d44a073082fbc29967c",
77
77
  "reddit-voc-volume/services/firecrawl/.gitattributes": "1a1dbe176bc233b499d35a57db7513f2941c99ab9759f177830c9149be99005b",
78
78
  "reddit-voc-volume/services/firecrawl/.github/CODEOWNERS": "e9035efad8c4d98efb06728ba1efce400e61c4a0e399d085c8001c6f887a875c",
@@ -1633,7 +1633,8 @@
1633
1633
  "reddit-voc-volume/services/firecrawl/img/firecrawl_logo_v1.png": "40b85912f49f4a1b45f42b85bbb39150fb7c0823c1c4ecadaa82860a988fa884",
1634
1634
  "reddit-voc-volume/services/firecrawl/img/open-source-cloud.png": "3d49d98b06dc91e829c9ea2410ad10aaddc0b8b7d7e2a133e061daaff6429895",
1635
1635
  "reddit-voc-volume/tests/test_archived_comments.py": "81fd6da65498f5cca81a10bae28bd6a9164efee1be4545dcab8d99984780a54e",
1636
- "reddit-voc-volume/tests/test_links_fallback.py": "0b6756cbef92567c680fe56525f89ca791502d6173414f833b50e305ef56c65a",
1636
+ "reddit-voc-volume/tests/test_links_fallback.py": "0f9e91e4f7721d6c8f63dc2e98f20d0aeaa82d497a780054ae0096150ac8d861",
1637
+ "reddit-voc-volume/tests/test_no_direct_reddit.py": "5f6764a92f0579eb3322ffcdf29db5351a4d5c38278ab923a1439b5e0a9be45a",
1637
1638
  "social-hub-accounts/SKILL.md": "096f7969f572a0cecbf244d814ba43422b8b556a98b751dc1ece7f9066864e4d",
1638
1639
  "social-hub-admin/SKILL.md": "bce961e34268c736df5bc92e5b9edfdd98393d19dc2122f388112e4250e1102d",
1639
1640
  "social-hub-calendar-directives/SKILL.md": "27f82cd33c14bc6a51282f59560930f1600d48cb7df1a308505c457b2777ad6f",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 1,
3
3
  "package": "social-ops-hub",
4
- "cliVersion": "0.3.47",
4
+ "cliVersion": "0.3.48",
5
5
  "repository": "social-ops-hub",
6
6
  "publish": {
7
7
  "registry": "https://skill.sh",
@@ -312,7 +312,7 @@ ExplicitStructureSpec:
312
312
  **Subreddit 信息获取优先级(不要乱写抓取脚本)**
313
313
 
314
314
  1. 优先使用用户提供的 subreddit 规则、热门帖链接、截图、已有样本或具体发帖要求
315
- 2. 如果用户要求“参考真实样本 / 抓一下 / 看这个板块风格”,按 [sop-reference.md](sop-reference.md) 的“Reddit 样本采集流程”执行;帖子级采样优先用 `/reddit-voc-volume`,评论采样才使用 SOP 中的 `curl_cffi + 完整请求头 + cookie` 低频流程
315
+ 2. 如果用户要求“参考真实样本 / 抓一下 / 看这个板块风格”,按 [sop-reference.md](sop-reference.md) 的“Reddit 样本采集流程”执行;帖子级采样优先用 `/reddit-voc-volume`,评论采样用 `reddit-voc-volume comments`(Arctic 历史评论),当下证据走 Hub-First 或 `social-hub scrape`
316
316
  3. 如果只是普通改写任务,且没有明确要求抓样本,不要临时编写新爬虫、不要并发抓取、不要绕过 403/429、不要抓私密或登录墙内容
317
317
  4. 如果板块规则可能决定能不能发,但没有足够信息,先问用户补充规则/热门帖样本,或在输出前给一句简短 Note 说明假设
318
318
 
@@ -228,8 +228,8 @@ baseline 是分布证据,不是归一化目标:不要求内容向样本中
228
228
  - 只有用户明确要求"参考真实样本 / 抓一下 / 看这个板块风格"时,才按
229
229
  `sop-reference.md` 的 Reddit 样本采集流程取样。该文件已清理为纯操作流程,
230
230
  其 legacy 评分维度、语料锚点与成品示例帖已在 v2.12.25 删除,不要凭记忆重建
231
- - 帖子级采样优先用 `/reddit-voc-volume`;评论级采样才使用 SOP 中的
232
- `curl_cffi + 完整请求头 + cookie` 低频流程
231
+ - 帖子级采样优先用 `/reddit-voc-volume`;评论级采样用
232
+ `reddit-voc-volume comments`(Arctic 历史评论),当下证据走 Hub-First 或 `social-hub scrape`
233
233
  - 普通改写任务不要临时写新爬虫、不要并发抓取、不要绕过 403/429、
234
234
  不要抓私密或登录墙内容
235
235
  - 板块规则要求补充事实性上下文(specs、routine、地区、报价明细、时间跨度等)
@@ -125,130 +125,25 @@ py -3 $VocScript subreddit `
125
125
  受控值一律取 `../reddit-strategy-shared/references/content-diversity-contract.md`
126
126
  的枚举,不要自造标签——换标签的伪差异是明确禁止项。
127
127
 
128
- ### 评论样本:用 curl_cffi + 完整请求头 + cookie
129
-
130
- `reddit-voc-volume` 默认只返回帖子级字段,不返回评论正文。评论样本需要对公开 permalink 的 `.json` 端点做低频读取。
131
-
132
- 必须使用:
133
-
134
- - `curl_cffi.requests.Session(impersonate="chrome131")`
135
- - 完整 Chrome 请求头
136
- - 浏览器 cookie(`browser_cookie3` 或 `REDDIT_COOKIES_JSON`)
137
- - 先 warm `https://www.reddit.com/`
138
- - 每个帖子请求之间 sleep 2–6 秒
139
- - 遇 403/429 立即停止,不要扩量
140
-
141
- 可复用脚本:
142
-
143
- ```python
144
- import importlib.util
145
- import json
146
- import time
147
- from pathlib import Path
148
-
149
- from curl_cffi import requests
150
-
151
- voc_script = Path.home() / ".cursor/skills/reddit-voc-volume/scripts/reddit_voc_volume.py"
152
- spec = importlib.util.spec_from_file_location("reddit_voc_volume", voc_script)
153
- rv = importlib.util.module_from_spec(spec)
154
- spec.loader.exec_module(rv)
155
-
156
- UA = (
157
- "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 "
158
- "(KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
159
- )
160
-
161
- HEADERS = {
162
- "User-Agent": UA,
163
- "Accept": "application/json, text/plain, */*",
164
- "Accept-Language": "en-US,en;q=0.9",
165
- "Accept-Encoding": "gzip, deflate, br, zstd",
166
- "Referer": "https://www.reddit.com/",
167
- "Origin": "https://www.reddit.com",
168
- "Connection": "keep-alive",
169
- "DNT": "1",
170
- "Sec-CH-UA": '"Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24"',
171
- "Sec-CH-UA-Mobile": "?0",
172
- "Sec-CH-UA-Platform": '"macOS"',
173
- "Sec-Fetch-Dest": "empty",
174
- "Sec-Fetch-Mode": "cors",
175
- "Sec-Fetch-Site": "same-origin",
176
- "Priority": "u=1, i",
177
- }
178
-
179
- def comment_json_url(permalink: str) -> str:
180
- return permalink.rstrip("/") + ".json?raw_json=1&limit=20&sort=top&threaded=true"
181
-
182
- def walk_comments(children, out, depth=0):
183
- for child in children:
184
- if child.get("kind") != "t1":
185
- continue
186
- data = child.get("data") or {}
187
- body = (data.get("body") or "").strip()
188
- if body and body not in ("[deleted]", "[removed]"):
189
- out.append({
190
- "body": body[:1000],
191
- "score": data.get("score"),
192
- "depth": depth,
193
- "author": data.get("author"),
194
- "permalink": "https://www.reddit.com" + (data.get("permalink") or ""),
195
- })
196
- replies = data.get("replies")
197
- if isinstance(replies, dict):
198
- walk_comments(((replies.get("data") or {}).get("children") or []), out, depth + 1)
199
-
200
- store = rv.CookieStore.get()
201
- cookies = store.cookies
202
- proxies = rv._proxies_dict()
203
- session = requests.Session(impersonate=rv.IMPERSONATE)
204
-
205
- # Warm same-origin session. Cookie is required for many public comment JSON pages.
206
- session.get(
207
- "https://www.reddit.com/",
208
- headers=HEADERS,
209
- cookies=cookies or None,
210
- proxies=proxies or None,
211
- timeout=30,
212
- )
213
-
214
- permalinks = [
215
- # 填入本次要采样的公开帖 permalink;不要在本文件里固化真实帖子标题
216
- "<https://www.reddit.com/r/<subreddit>/comments/<id>/<slug>/>",
217
- ]
218
-
219
- samples = []
220
- for permalink in permalinks:
221
- headers = dict(HEADERS)
222
- headers["Referer"] = permalink
223
- resp = session.get(
224
- comment_json_url(permalink),
225
- headers=headers,
226
- cookies=cookies or None,
227
- proxies=proxies or None,
228
- timeout=30,
229
- )
230
- if resp.status_code in (403, 429):
231
- break
232
- resp.raise_for_status()
233
- data = resp.json()
234
- post = (((data[0].get("data") or {}).get("children") or [{}])[0].get("data") or {})
235
- comments = []
236
- if len(data) > 1:
237
- walk_comments((data[1].get("data") or {}).get("children") or [], comments)
238
- samples.append({
239
- "post_title": post.get("title"),
240
- "subreddit": post.get("subreddit"),
241
- "url": permalink,
242
- "comments": comments[:12],
243
- })
244
- time.sleep(2.5)
245
-
246
- Path("/tmp/reddit_comment_samples.json").write_text(
247
- json.dumps(samples, ensure_ascii=False, indent=2),
248
- encoding="utf-8",
249
- )
128
+ ### 评论样本:历史评论走 Arctic,当下证据走 Hub
129
+
130
+ `reddit-voc-volume` 的 `links` / `keyword` 默认只返回帖子级字段,不返回评论正文。评论样本按下面路径取,
131
+ **不要自写 reddit.com `.json` 抓取脚本**(`curl_cffi` / cookie / 指纹伪装均已移除;`.json` 对住宅代理同样 403):
132
+
133
+ - 历史评论 / 评论树:`reddit-voc-volume comments <帖子 URL 或 ID>`,直连 Arctic Shift 归档,正文不截断,
134
+ 默认 tree,另有 `--source search`(`--body` / `--author` / `--after` / `--before` / `--parent-id`)
135
+ 与 `--ids` 批查。归档评论 score 非实时,只做分布统计,不当实时数据用。
136
+ - 当下证据(刚发布、Arctic 尚未收录的帖子):走 Hub-First(`reddit-voc-volume` 的 Hub 派发),
137
+ 或用 `social-hub scrape` 抓公开渲染页;本机 Firecrawl 作为再下一级。
138
+ - `comments` 子命令没有 `--sleep-sec`;多帖采样时,两次 `comments` 调用之间手动等待至少 2 秒(脚本常量 `ARCTIC_MIN_SLEEP_SEC`)。任何一步遇 403/429/验证码立即停止,不要扩量。
139
+
140
+ ```bash
141
+ VOC=~/.cursor/skills/reddit-voc-volume/scripts/reddit_voc_volume.py
142
+ python3 "$VOC" comments "https://www.reddit.com/r/<subreddit>/comments/<id>/<slug>/" --pretty
250
143
  ```
251
144
 
145
+ 输出 JSON 的评论条目按「评论功能」做分类计数(见下节),不落盘原文库。
146
+
252
147
  ### 评论样本提炼维度
253
148
 
254
149
  不要把评论样本整段塞进模板。按“评论功能”提炼:
@@ -272,7 +167,7 @@ Path("/tmp/reddit_comment_samples.json").write_text(
272
167
 
273
168
  - 只读公开帖子和公开评论。
274
169
  - 不尝试绕过私密版、登录墙、验证码或 403/429。
275
- - Cookie 只用于减少公开页误判,不输出 cookie 值。
170
+ - 不使用 cookie 或登录态;不直连 reddit.com。
276
171
  - 单次采样控制在少量请求;不要并发刷评论页。
277
172
  - 采样结果仅用于分布统计与 `SampleSetID` baseline manifest,不用于复制原文,
278
173
  也不用于提炼跨板块统一风格。
@@ -36,30 +36,29 @@ Step 6 ── EPA-5:淘汰行补充备选 URL → 重新从 EPA-0 开始
36
36
  **执行:** 从执行表中提取当批全部外部评论行的 Reddit URL,调用 `reddit-voc-volume` skill 的 `links` 模式:
37
37
 
38
38
  ```bash
39
- # macOS 可选:Anker 仓库包装脚本(自动 REDDIT_USE_BROWSER_COOKIES=1)
40
- # <Anker项目根>/Tools/reddit-voc/run_reddit_voc.sh links "URL1" "URL2" --pretty
41
-
42
- # 推荐:直接调用本 skill 脚本(须 export REDDIT_USE_BROWSER_COOKIES=1)
43
- export REDDIT_USE_BROWSER_COOKIES=1
39
+ # 直接调用本 skill 脚本;不需要 cookie 或任何浏览器环境变量。
40
+ # 显式加 --fallback auto:Arctic 未命中时依次尝试 Social Hub 渲染页、本机 Firecrawl。
44
41
  python3 ~/.cursor/skills/reddit-voc-volume/scripts/reddit_voc_volume.py links \
45
42
  "https://www.reddit.com/r/.../comments/xxx/..." \
46
43
  "https://www.reddit.com/r/.../comments/yyy/..." \
44
+ --fallback auto \
47
45
  --pretty
48
46
  ```
49
47
 
50
48
  Windows PowerShell:
51
49
 
52
50
  ```powershell
53
- $env:REDDIT_USE_BROWSER_COOKIES = "1"
54
51
  $SkillsRoot = Join-Path $HOME ".cursor\skills"
55
52
  $VocScript = Join-Path $SkillsRoot "reddit-voc-volume\scripts\reddit_voc_volume.py"
56
53
  py -3 $VocScript links `
57
54
  "https://www.reddit.com/r/.../comments/xxx/..." `
58
55
  "https://www.reddit.com/r/.../comments/yyy/..." `
56
+ --fallback auto `
59
57
  --pretty
60
58
  ```
61
59
 
62
- > **Cookie 自检:** `request_identity.used_cookies` 须为 true;JSON 连续 403/429 时读已安装 `reddit-voc-volume/references/browser-fallback.md`。
60
+ > **取数自检:** 脚本自 v2.12.28 起不再直连 reddit.com,也不读 cookie;`request_identity`(含 `used_cookies`、`fetch_mode`)只是兼容字段,`used_cookies` 恒为 false,**不能**拿来判定成败。改查:顶层 `ok` 为 true、`errors` 为空。
61
+ > 注意区分两层来源:顶层 `provenance` 是「采集编排来源」(`links` 模式下恒为 `emergency_local`,不能据此判断数据从哪来);**具体数据来源**看每条 `items[].source_fetch`:`arctic_shift` 表示 Arctic 归档命中,`social_hub_scrape` / `firecrawl` 表示 Arctic 未命中、改走渲染页证据(`item_type` 为 `firecrawl_page`)。若未加 `--fallback auto` 且 Arctic 未命中、结果为空或 `errors` 非空,加该参数重跑。Arctic 与渲染页都取不到时,读已安装 `reddit-voc-volume/references/browser-fallback.md`。
63
62
 
64
63
  **从返回 JSON 提取以下字段(每条 URL 对应一个 item):**
65
64
 
@@ -22,13 +22,13 @@
22
22
  "contentSha256": "08023a5dfa6cc2f3ffc3f1a551479c645ddcfc8cc775f88cd2d245c067829482"
23
23
  },
24
24
  "reddit-content-rewrite": {
25
- "contentSha256": "2f9dfdbb2410d060f755860b57ebc7c6776f4df343990029a8770855ecd33005"
25
+ "contentSha256": "b42bfea525bd11646d6c15ae081d00137c114835611d01b77354698d4b05f00e"
26
26
  },
27
27
  "reddit-delivery-check": {
28
28
  "contentSha256": "9231ff9ae5d950bc2fb87e45bee05e793b3ff9c37bee45e8444059a8b698e4a9"
29
29
  },
30
30
  "reddit-voc-volume": {
31
- "contentSha256": "7b068ab2fb50627978ec54bea3c76d12333b5438dd40f16204d1982ae5e9d95c"
31
+ "contentSha256": "f6b81203fb6892d367ec95f2e29caa21cad657559636f59248975a604353e725"
32
32
  },
33
33
  "reddit-brand-risk-response": {
34
34
  "contentSha256": "dbba80345ebe68818a25e7604dfaa6951c6bf0e4d785d5013be7a06b22ec5b7c"
@@ -43,7 +43,7 @@
43
43
  "contentSha256": "808659c48e546e8ce35e32ec42c68d35e3f843ec41d4974ea8eabd1da1dac7ce"
44
44
  },
45
45
  "reddit-ext-post-audit": {
46
- "contentSha256": "7ebe1b407c1897a6ba7620c6ae6ec16d206b3a7b0be3d1d30618705d58492214"
46
+ "contentSha256": "f14b469768ebf1dd2687b4e9c26f66fd141889b45710e3adee7b015ebdf35f64"
47
47
  }
48
48
  }
49
49
  }
@@ -47,7 +47,7 @@ description: |
47
47
 
48
48
 
49
49
  Python 3.10+;网络可访问 **arctic-shift.photon-reddit.com**(历史/评论/版规/wiki/about)与 Social Hub / 本机 Firecrawl(渲染页证据);建议配置 HTTPS_PROXY。
50
- **本机不直连 reddit.com**:渲染页由 Hub / Firecrawl 代为访问。`curl`、`curl_cffi`、`browser_cookie3` 与 `--via-curl` 均为 v2.12.28 前的遗留物,当前代码路径用不到(见下节「请求身份」)。
50
+ **本机不直连 reddit.com**:渲染页由 Hub / Firecrawl 代为访问。脚本只依赖 Python 标准库,不需要 `curl`、`curl_cffi`、`browser_cookie3`;v2.12.28 前的这些直连实现的直连实现已删除;`--via-curl` 仅作为废弃空参数保留一个发布周期(见下节「请求身份」)。
51
51
 
52
52
  ## 何时用 / 不用
53
53
 
@@ -400,10 +400,12 @@ v2.12.23 沿用 v2.12.12 引入的安全默认采样:`limit=10 / sleep=6`,
400
400
  渲染页证据走 Social Hub / Firecrawl。**2026-09-18 实测:`.json` 对住宅代理同样 403**,
401
401
  Reddit 侧封控已不是「换个指纹/加 cookie 就能过」的问题,别再把它当可选通道。
402
402
 
403
- 遗留物盘点:`RedditHttpClient` / `CookieStore` 仍有定义,但主流程不再实例化客户端,也不会读取浏览器 cookie;
404
- `--via-curl` 是保留的空开关;`fetch_via_curl_cffi` / `http_get_json_via_curl` 只有定义没有调用。
405
- `requirements.txt` 的 `curl_cffi`、`browser_cookie3` 当前路径用不到。输出信封里的
406
- `request_identity.*` 是兼容字段,**不表示真的打过 reddit.com**。
403
+ 遗留代码已删除:`RedditHttpClient` / `CookieStore` / `http_get_json_via_curl` / `fetch_via_curl_cffi` 与
404
+ `curl_cffi`、`browser_cookie3` 依赖均不复存在,`--via-curl`(keyword / subreddit / stickies / links)只剩废弃空参数:传入只向 stderr 打 `DEPRECATED` 提示,不影响取数与退出码,**下一版移除**,请勿再传。
405
+ 输出信封里的 `request_identity` 仍保留以兼容下游:`used_cookies` 恒为 `false`、`cookie_source` 恒为 `null`、
406
+ `cookie_cache_reused` 恒为 `false`、`impersonation` 恒为 `null`,`fetch_mode` 目前恒为 `"none"`
407
+ (脚本没有直连 reddit.com 的取数方式可记录);`proxy_used` 反映是否解析到 HTTPS 代理。
408
+ 这些字段**不表示真的打过 reddit.com**。浏览器兜底由 agent 手工回填时才会写 `fetch_mode: "browser_fallback"`。
407
409
 
408
410
  `links` 模式四种目标现在全部走 Arctic:帖子 permalink → `arctic_post_by_permalink`,
409
411
  about / rules / wiki → 对应的 `arctic_*_as_reddit_payload`;取不到才按 `--fallback` 走 Hub / Firecrawl 渲染页。
@@ -473,10 +475,12 @@ Social Hub / Firecrawl 渲染页。
473
475
  "rate_limited": false
474
476
  },
475
477
  "request_identity": {
476
- "fetch_mode": "curl_cffi",
477
- "used_cookies": true,
478
- "cookie_source": "browser",
479
- "impersonation": "chrome131",
478
+ "user_agent_kind": "chrome",
479
+ "fetch_mode": "none",
480
+ "used_cookies": false,
481
+ "cookie_source": null,
482
+ "cookie_cache_reused": false,
483
+ "impersonation": null,
480
484
  "proxy_used": true
481
485
  },
482
486
  "notes": [],
@@ -15,7 +15,7 @@ Pushshift 停摆后的接续者。**无需 auth**,免费公开服务,作者
15
15
  v2.12.26 修掉正文截断后,baseline 卡的长度维度只接受未截断全文。但多数网络
16
16
  环境下 reddit.com 的 `.json` 直采已被拦截(实测本机:`curl (56) CONNECT tunnel
17
17
  failed 403`,去掉代理直连亦为 `403 Blocked`)。Arctic Shift 不走 reddit.com,
18
- 不需要 cookie / curl_cffi 伪装,且**正文不截断**。
18
+ 不需要 cookie 或指纹伪装,且**正文不截断**。
19
19
 
20
20
  ## 覆盖范围(实测,非文档转述)
21
21
 
@@ -1,5 +1,2 @@
1
1
  # Reddit VOC volume — Python dependencies
2
- # 以下两项仅供未调用的旧 Reddit HTTP 函数使用。主流程不实例化旧客户端,
3
- # 不读取浏览器 cookie;不安装也不影响当前模式。
4
- curl_cffi>=0.7.0
5
- browser_cookie3>=0.19.0
2
+ # 无第三方 Python 依赖:脚本只用标准库(Arctic / Social Hub / Firecrawl 均走 urllib 或 CLI)。
@@ -154,7 +154,19 @@ DEFAULT_UA = (
154
154
  "(KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 reddit-voc-volume/1.0"
155
155
  )
156
156
 
157
- IMPERSONATE = "chrome131"
157
+
158
+ class _DeprecatedViaCurl(argparse.Action):
159
+ """`--via-curl` 自 v2.12.28 起无效果;保留一个发布周期,仅向 stderr 提示,下一版移除。"""
160
+
161
+ def __init__(self, option_strings: Any, dest: str, **kwargs: Any) -> None:
162
+ kwargs["nargs"] = 0
163
+ kwargs.setdefault("default", False)
164
+ kwargs.setdefault("help", "DEPRECATED:已无效果(v2.12.28 起不直连 reddit),下一版移除")
165
+ super().__init__(option_strings, dest, **kwargs)
166
+
167
+ def __call__(self, parser: Any, namespace: Any, values: Any, option_string: Any = None) -> None:
168
+ print("DEPRECATED: --via-curl 已无效果(v2.12.28 起不直连 reddit)", file=sys.stderr)
169
+ setattr(namespace, self.dest, True)
158
170
 
159
171
 
160
172
  def utc_now_iso() -> str:
@@ -177,22 +189,6 @@ def sleep_with_jitter(base_sec: float) -> float:
177
189
  return jitter
178
190
 
179
191
 
180
- def _looks_like_html(text: str) -> bool:
181
- s = (text or "").lstrip()
182
- return s.startswith("<") or "<html" in s[:200].lower() or "theme-beta" in s[:500]
183
-
184
-
185
- def _loads_json_or_raise(text: str, *, url: str) -> Any:
186
- if _looks_like_html(text):
187
- raise ValueError(
188
- "reddit_returned_html_not_json (likely 403/block; try proxy or browser fallback)"
189
- )
190
- try:
191
- return json.loads(text)
192
- except json.JSONDecodeError as e:
193
- raise ValueError(f"invalid_json: {e}") from e
194
-
195
-
196
192
  def _loads_json_response(text: str) -> Any:
197
193
  try:
198
194
  return json.loads(text)
@@ -243,114 +239,11 @@ def resolve_proxy_url() -> str:
243
239
  return _macos_system_proxy_url()
244
240
 
245
241
 
246
- def _proxies_dict() -> dict[str, str]:
247
- p = resolve_proxy_url()
248
- return {"http": p, "https": p} if p else {}
249
-
250
-
251
- def _curl_proxy_args() -> list[str]:
252
- proxy = resolve_proxy_url()
253
- return ["--proxy", proxy] if proxy else []
254
-
255
-
256
242
  def _env_truthy(name: str) -> bool:
257
243
  raw = (os.environ.get(name) or "").strip().lower()
258
244
  return raw in ("1", "true", "yes", "on")
259
245
 
260
246
 
261
- def _use_browser_cookies() -> bool:
262
- raw = (os.environ.get("REDDIT_USE_BROWSER_COOKIES") or "1").strip().lower()
263
- return raw not in ("0", "false", "no", "off")
264
-
265
-
266
- class CookieStore:
267
- """Load reddit.com cookies once per process and reuse for all requests."""
268
-
269
- _instance: CookieStore | None = None
270
-
271
- def __init__(self) -> None:
272
- self._cookies: dict[str, str] = {}
273
- self.source: str | None = None
274
- self.loaded = False
275
-
276
- @classmethod
277
- def get(cls) -> CookieStore:
278
- if cls._instance is None:
279
- cls._instance = cls()
280
- cls._instance.load_once()
281
- return cls._instance
282
-
283
- def load_once(self) -> dict[str, str]:
284
- if self.loaded:
285
- return self._cookies
286
- self.loaded = True
287
- raw = (os.environ.get("REDDIT_COOKIES_JSON") or "").strip()
288
- if raw:
289
- try:
290
- obj = json.loads(raw)
291
- if isinstance(obj, dict):
292
- self._cookies = {str(k): str(v) for k, v in obj.items() if k and v is not None}
293
- self.source = "env_json"
294
- return self._cookies
295
- except json.JSONDecodeError:
296
- pass
297
- if not _use_browser_cookies():
298
- self.source = None
299
- return self._cookies
300
- try:
301
- import browser_cookie3 # type: ignore
302
- except ImportError:
303
- self.source = None
304
- return self._cookies
305
- for name in ("chrome", "safari", "firefox"):
306
- fn = getattr(browser_cookie3, name, None)
307
- if not callable(fn):
308
- continue
309
- try:
310
- jar = fn(domain_name="reddit.com")
311
- cookies = {c.name: c.value for c in jar if c.name and c.value}
312
- if cookies:
313
- self._cookies = cookies
314
- self.source = "browser"
315
- # Share with child processes in same sync job (e.g. feishu sync)
316
- os.environ.setdefault("REDDIT_COOKIES_JSON", json.dumps(cookies))
317
- return self._cookies
318
- except Exception:
319
- continue
320
- self.source = None
321
- return self._cookies
322
-
323
- @property
324
- def cookies(self) -> dict[str, str]:
325
- return self._cookies
326
-
327
-
328
- def _load_browser_cookies() -> dict[str, str]:
329
- return CookieStore.get().cookies
330
-
331
-
332
- def _browser_headers(user_agent: str) -> dict[str, str]:
333
- return {
334
- "User-Agent": user_agent,
335
- "Accept": "application/json, text/plain, */*",
336
- "Accept-Language": "en-US,en;q=0.9",
337
- "Referer": "https://www.reddit.com/",
338
- "Origin": "https://www.reddit.com",
339
- "Sec-Fetch-Dest": "empty",
340
- "Sec-Fetch-Mode": "cors",
341
- "Sec-Fetch-Site": "same-origin",
342
- }
343
-
344
-
345
- def _curl_cffi_available() -> bool:
346
- try:
347
- import curl_cffi # noqa: F401
348
-
349
- return True
350
- except ImportError:
351
- return False
352
-
353
-
354
247
  class RequestTracker:
355
248
  def __init__(self) -> None:
356
249
  self.count = 0
@@ -358,9 +251,6 @@ class RequestTracker:
358
251
  self.rate_limited = False
359
252
  self.truncated = False
360
253
  self.last_fetch_mode = "none"
361
- self.used_cookies = False
362
- self.cookie_source: str | None = None
363
- self.cookie_cache_reused = False
364
254
 
365
255
  def bump(self) -> None:
366
256
  self.count += 1
@@ -368,108 +258,6 @@ class RequestTracker:
368
258
  self.truncated = True
369
259
  raise RuntimeError("max_http_requests_exceeded")
370
260
 
371
- def record_identity(self, mode: str, store: CookieStore) -> None:
372
- self.last_fetch_mode = mode
373
- if store.cookies:
374
- self.used_cookies = True
375
- self.cookie_source = store.source
376
- if store.loaded and self.count > 1:
377
- self.cookie_cache_reused = True
378
-
379
-
380
- class RedditHttpClient:
381
- """Reuse cookies + curl_cffi session within a single CLI run."""
382
-
383
- def __init__(self, user_agent: str, *, force_curl: bool = False) -> None:
384
- self.user_agent = user_agent
385
- self.force_curl = force_curl
386
- self.cookie_store = CookieStore.get()
387
- self._cffi_session: Any = None
388
- self._warmed = False
389
-
390
- def _ensure_cffi_session(self) -> Any:
391
- if self._cffi_session is None:
392
- from curl_cffi import requests as cffi_requests
393
-
394
- self._cffi_session = cffi_requests.Session(impersonate=IMPERSONATE)
395
- if not self._warmed:
396
- headers = _browser_headers(self.user_agent)
397
- proxies = _proxies_dict()
398
- cookies = self.cookie_store.cookies
399
- try:
400
- self._cffi_session.get(
401
- "https://www.reddit.com/",
402
- headers=headers,
403
- proxies=proxies or None,
404
- cookies=cookies or None,
405
- timeout=30.0,
406
- )
407
- except Exception:
408
- pass
409
- self._warmed = True
410
- return self._cffi_session
411
-
412
- def fetch_via_curl_cffi(self, url: str, timeout: float = 30.0) -> tuple[Any, str]:
413
- session = self._ensure_cffi_session()
414
- headers = _browser_headers(self.user_agent)
415
- proxies = _proxies_dict()
416
- cookies = self.cookie_store.cookies
417
- resp = session.get(
418
- url,
419
- headers=headers,
420
- proxies=proxies or None,
421
- cookies=cookies or None,
422
- timeout=timeout,
423
- )
424
- if resp.status_code == 429:
425
- raise RuntimeError("http_status:429")
426
- if resp.status_code == 403:
427
- raise RuntimeError("http_status:403")
428
- if resp.status_code >= 400:
429
- raise RuntimeError(f"http_status:{resp.status_code}")
430
- return _loads_json_or_raise(resp.text, url=url), "curl_cffi"
431
-
432
-
433
- def http_get_json_via_curl(url: str, user_agent: str, timeout: int = 30) -> tuple[Any, str]:
434
- curl = shutil.which("curl")
435
- if not curl:
436
- raise RuntimeError("curl not found in PATH")
437
- cmd = [
438
- curl,
439
- "-sS",
440
- "-L",
441
- "--max-time",
442
- str(int(timeout)),
443
- "-A",
444
- user_agent,
445
- "-H",
446
- "Accept: application/json, text/plain, */*",
447
- "-H",
448
- "Accept-Language: en-US,en;q=0.9",
449
- "-H",
450
- "Referer: https://www.reddit.com/",
451
- *_curl_proxy_args(),
452
- url,
453
- ]
454
- proc = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout + 5)
455
- if proc.returncode != 0:
456
- err = (proc.stderr or proc.stdout or "curl failed").strip()
457
- if "429" in err:
458
- raise RuntimeError("http_status:429")
459
- raise RuntimeError(err)
460
- return _loads_json_or_raise(proc.stdout, url=url), "curl"
461
-
462
-
463
- def http_get_json_urllib(url: str, user_agent: str, timeout: float = 25.0) -> tuple[Any, str]:
464
- req = urllib.request.Request(url, headers=_browser_headers(user_agent), method="GET")
465
- try:
466
- with urllib.request.urlopen(req, timeout=timeout) as resp:
467
- charset = resp.headers.get_content_charset() or "utf-8"
468
- raw = resp.read().decode(charset)
469
- except urllib.error.HTTPError as e:
470
- raise RuntimeError(f"http_status:{e.code}") from e
471
- return _loads_json_or_raise(raw, url=url), "urllib"
472
-
473
261
 
474
262
  def normalize_firecrawl_base_url(base_url: str | None = None) -> str:
475
263
  raw = (base_url or os.environ.get("FIRECRAWL_BASE_URL") or DEFAULT_FIRECRAWL_BASE_URL).strip()
@@ -4693,10 +4481,10 @@ def build_output(
4693
4481
  "request_identity": {
4694
4482
  "user_agent_kind": "chrome",
4695
4483
  "fetch_mode": tracker.last_fetch_mode,
4696
- "used_cookies": tracker.used_cookies,
4697
- "cookie_source": tracker.cookie_source,
4698
- "cookie_cache_reused": tracker.cookie_cache_reused,
4699
- "impersonation": IMPERSONATE if tracker.last_fetch_mode == "curl_cffi" else None,
4484
+ "used_cookies": False,
4485
+ "cookie_source": None,
4486
+ "cookie_cache_reused": False,
4487
+ "impersonation": None,
4700
4488
  "proxy_used": bool(proxy),
4701
4489
  },
4702
4490
  "notes": notes,
@@ -4833,7 +4621,7 @@ def main() -> int:
4833
4621
  pk.add_argument("--max-pages", type=int, default=DEFAULT_MAX_PAGES)
4834
4622
  pk.add_argument("--sleep-sec", type=float, default=DEFAULT_SLEEP_SEC)
4835
4623
  pk.add_argument("--user-agent", default=DEFAULT_UA)
4836
- pk.add_argument("--via-curl", action="store_true")
4624
+ pk.add_argument("--via-curl", action=_DeprecatedViaCurl)
4837
4625
  pk.add_argument("--pretty", action="store_true")
4838
4626
  add_hub_voc_options(pk)
4839
4627
 
@@ -4846,7 +4634,7 @@ def main() -> int:
4846
4634
  ps.add_argument("--max-pages", type=int, default=DEFAULT_MAX_PAGES)
4847
4635
  ps.add_argument("--sleep-sec", type=float, default=DEFAULT_SLEEP_SEC)
4848
4636
  ps.add_argument("--user-agent", default=DEFAULT_UA)
4849
- ps.add_argument("--via-curl", action="store_true")
4637
+ ps.add_argument("--via-curl", action=_DeprecatedViaCurl)
4850
4638
  ps.add_argument("--pretty", action="store_true")
4851
4639
  add_hub_voc_options(ps)
4852
4640
 
@@ -4855,7 +4643,7 @@ def main() -> int:
4855
4643
  pst.add_argument("--limit", type=int, default=10, help="Hot listing size per subreddit before filtering stickied posts")
4856
4644
  pst.add_argument("--sleep-sec", type=float, default=DEFAULT_SLEEP_SEC)
4857
4645
  pst.add_argument("--user-agent", default=DEFAULT_UA)
4858
- pst.add_argument("--via-curl", action="store_true")
4646
+ pst.add_argument("--via-curl", action=_DeprecatedViaCurl)
4859
4647
  pst.add_argument("--pretty", action="store_true")
4860
4648
  add_hub_voc_options(pst)
4861
4649
 
@@ -4922,7 +4710,7 @@ def main() -> int:
4922
4710
  pl.add_argument("--file", "-f", help="File with one URL per line")
4923
4711
  pl.add_argument("--sleep-sec", type=float, default=DEFAULT_SLEEP_SEC)
4924
4712
  pl.add_argument("--user-agent", default=DEFAULT_UA)
4925
- pl.add_argument("--via-curl", action="store_true")
4713
+ pl.add_argument("--via-curl", action=_DeprecatedViaCurl)
4926
4714
  pl.add_argument(
4927
4715
  "--fallback",
4928
4716
  choices=["none", "firecrawl", "social-hub", "auto"],
@@ -5363,7 +5151,7 @@ def main() -> int:
5363
5151
  errors = list(acquisition.get("errors") or [])
5364
5152
  if acquisition.get("should_local"):
5365
5153
  # Emergency Acquisition 缺省走 Arctic Shift 归档 API。
5366
- # 相对 .json 直采:正文不截断、不需要 cookie/curl 伪装、不受 Reddit 反爬拦截;
5154
+ # 相对 .json 直采:正文不截断、不受 Reddit 反爬拦截;
5367
5155
  # 代价是 score 有约 1/3 为陈旧值(已逐条标 score_status),且无 live 语义。
5368
5156
  items = []
5369
5157
  for idx, sub in enumerate(subreddits or [None]):
@@ -36,44 +36,8 @@ find_python() {
36
36
  return 1
37
37
  }
38
38
 
39
- ensure_curl() {
40
- if command -v curl >/dev/null 2>&1; then
41
- echo " curl: $(command -v curl)"
42
- return 0
43
- fi
44
- echo " curl: not found — attempting install..."
45
- if [[ "$SKIP_BREW" != 1 ]] && command -v brew >/dev/null 2>&1; then
46
- brew install curl || true
47
- fi
48
- if [[ "$SKIP_APT" != 1 ]] && command -v apt-get >/dev/null 2>&1; then
49
- if [[ "$ASSUME_YES" == 1 ]]; then
50
- sudo apt-get update -qq && sudo apt-get install -y curl || true
51
- else
52
- echo " Run: sudo apt-get install -y curl (or re-run with --yes if sudo is OK)"
53
- fi
54
- fi
55
- if command -v curl >/dev/null 2>&1; then
56
- echo " curl: installed at $(command -v curl)"
57
- return 0
58
- fi
59
- echo " curl: still missing (urllib/curl_cffi may still work; install curl manually for fallback)"
60
- return 0
61
- }
62
-
63
- ensure_pip_packages() {
64
- local py="$1"
65
- echo " pip packages: checking curl_cffi, browser_cookie3..."
66
- if "$py" -c "import curl_cffi, browser_cookie3" 2>/dev/null; then
67
- echo " pip packages: OK"
68
- return 0
69
- fi
70
- echo " pip packages: installing from ${REQ}..."
71
- "$py" -m pip install --upgrade pip >/dev/null 2>&1 || true
72
- if ! "$py" -m pip install -r "$REQ"; then
73
- echo " pip install failed — try: $py -m pip install -r $REQ" >&2
74
- return 1
75
- fi
76
- echo " pip packages: installed"
39
+ report_pip_packages() {
40
+ echo " pip packages: none required (stdlib only; see ${REQ})"
77
41
  }
78
42
 
79
43
  main() {
@@ -84,8 +48,7 @@ main() {
84
48
  exit 1
85
49
  fi
86
50
  echo " python: $PYTHON ($("$PYTHON" --version 2>&1))"
87
- ensure_curl
88
- ensure_pip_packages "$PYTHON"
51
+ report_pip_packages
89
52
  echo "=== setup complete ==="
90
53
  }
91
54
 
@@ -25,9 +25,9 @@ class LinksFallbackTests(unittest.TestCase):
25
25
  code = voc.main()
26
26
  return code, json.loads(stdout.getvalue())
27
27
 
28
- def test_arctic_miss_uses_rendered_page_without_loading_browser_cookies(self) -> None:
28
+ def test_arctic_miss_uses_rendered_page_without_direct_network_calls(self) -> None:
29
29
  with (
30
- mock.patch.object(voc.CookieStore, "get", side_effect=AssertionError("browser cookies loaded")) as cookies,
30
+ mock.patch.object(voc.urllib.request, "urlopen", side_effect=AssertionError("direct network call")) as urlopen,
31
31
  mock.patch.object(voc, "arctic_post_by_permalink", return_value=None) as archive,
32
32
  mock.patch.object(voc, "fetch_page_scrape", return_value={"markdown": "Public page"}) as page,
33
33
  mock.patch.object(voc, "collect_subreddit_metrics", return_value=[]),
@@ -42,11 +42,11 @@ class LinksFallbackTests(unittest.TestCase):
42
42
  self.assertEqual(out["errors"], [])
43
43
  archive.assert_called_once()
44
44
  self.assertEqual(page.call_args.kwargs["provider"], "social-hub")
45
- cookies.assert_not_called()
45
+ urlopen.assert_not_called()
46
46
 
47
47
  def test_arctic_miss_without_fallback_reports_archive_not_json(self) -> None:
48
48
  with (
49
- mock.patch.object(voc.CookieStore, "get", side_effect=AssertionError("browser cookies loaded")) as cookies,
49
+ mock.patch.object(voc.urllib.request, "urlopen", side_effect=AssertionError("direct network call")) as urlopen,
50
50
  mock.patch.object(voc, "arctic_post_by_permalink", side_effect=RuntimeError("archive unavailable")),
51
51
  mock.patch.object(voc, "collect_subreddit_metrics", return_value=[]),
52
52
  mock.patch.object(voc, "hub_writeback_items", return_value={"status": "not_applicable"}),
@@ -58,7 +58,7 @@ class LinksFallbackTests(unittest.TestCase):
58
58
  self.assertFalse(out["ok"])
59
59
  self.assertTrue(any("archive unavailable" in error for error in out["errors"]))
60
60
  self.assertTrue(any("Arctic lookup failed" in note for note in out["notes"]))
61
- cookies.assert_not_called()
61
+ urlopen.assert_not_called()
62
62
 
63
63
  def test_auto_fallback_tries_firecrawl_after_social_hub_failure(self) -> None:
64
64
  def page_result(_url: str, *, provider: str, **_kwargs: object) -> dict:
@@ -67,7 +67,7 @@ class LinksFallbackTests(unittest.TestCase):
67
67
  return {"markdown": "Public page"}
68
68
 
69
69
  with (
70
- mock.patch.object(voc.CookieStore, "get", side_effect=AssertionError("browser cookies loaded")) as cookies,
70
+ mock.patch.object(voc.urllib.request, "urlopen", side_effect=AssertionError("direct network call")) as urlopen,
71
71
  mock.patch.object(voc, "arctic_post_by_permalink", return_value=None),
72
72
  mock.patch.object(voc, "fetch_page_scrape", side_effect=page_result) as page,
73
73
  mock.patch.object(voc, "social_hub_unavailable_hint", return_value="Hub unavailable"),
@@ -80,7 +80,7 @@ class LinksFallbackTests(unittest.TestCase):
80
80
  self.assertEqual(code, 0)
81
81
  self.assertEqual(out["items"][0]["page_provider"], "firecrawl")
82
82
  self.assertEqual([call.kwargs["provider"] for call in page.call_args_list], ["social-hub", "firecrawl"])
83
- cookies.assert_not_called()
83
+ urlopen.assert_not_called()
84
84
 
85
85
 
86
86
  if __name__ == "__main__":
@@ -0,0 +1,315 @@
1
+ """v2.12.28+ 契约:脚本不得直连 reddit.com,`request_identity` 输出形状保持兼容。
2
+
3
+ 静态扫描 + 输出契约;全部离线。成对对照组保证检查器本身不会静默变空。
4
+
5
+ 注意:静态部分是**启发式防回归**,不是「无直连」的完整证明——它只看 urllib 请求调用的实参
6
+ (字面量、同函数内的局部赋值、模块级常量)是否指向 reddit.com 主机,动态拼接的 URL 抓不到。
7
+ 运行时的兜底是 test_links_fallback / 本文件中把 `urlopen` 打成「调用即失败」的用例。
8
+ """
9
+ from __future__ import annotations
10
+
11
+ import ast
12
+ import contextlib
13
+ import importlib.util
14
+ import io
15
+ import json
16
+ from pathlib import Path
17
+ import re
18
+ import sys
19
+ import unittest
20
+ from unittest import mock
21
+
22
+
23
+ SCRIPT = Path(__file__).resolve().parents[1] / "scripts" / "reddit_voc_volume.py"
24
+ SPEC = importlib.util.spec_from_file_location("reddit_voc_no_direct_under_test", SCRIPT)
25
+ assert SPEC is not None and SPEC.loader is not None
26
+ voc = importlib.util.module_from_spec(SPEC)
27
+ SPEC.loader.exec_module(voc)
28
+
29
+ FORBIDDEN_TOKENS = (
30
+ "curl_cffi",
31
+ "browser_cookie3",
32
+ "http_get_json",
33
+ "cffi_requests",
34
+ "RedditHttpClient",
35
+ "CookieStore",
36
+ "IMPERSONATE",
37
+ )
38
+ # 精确匹配 reddit.com 主机:不误报 photon-reddit.com、reddit.com.example.org。
39
+ REDDIT_URL_RE = re.compile(r"^https?://(?:[\w-]+\.)*reddit\.com(?:[:/?#]|$)")
40
+ # urllib 请求调用;subprocess 仅在命令里出现 curl/wget 时才算 HTTP 请求。
41
+ URLLIB_CALLS = {"urlopen", "Request", "firecrawl_urlopen"}
42
+ SUBPROCESS_CALLS = {"run", "Popen", "check_output"}
43
+ HTTP_CLIENT_BINARIES = {"curl", "wget"}
44
+ POST_URL = "https://www.reddit.com/r/example/comments/abc123/slug/"
45
+ IDENTITY_KEYS = {
46
+ "user_agent_kind",
47
+ "fetch_mode",
48
+ "used_cookies",
49
+ "cookie_source",
50
+ "cookie_cache_reused",
51
+ "impersonation",
52
+ "proxy_used",
53
+ }
54
+
55
+
56
+ def _strings(node: ast.AST) -> list[str]:
57
+ return [n.value for n in ast.walk(node) if isinstance(n, ast.Constant) and isinstance(n.value, str)]
58
+
59
+
60
+ def _has_reddit_url(node: ast.AST) -> bool:
61
+ return any(REDDIT_URL_RE.match(s) for s in _strings(node))
62
+
63
+
64
+ def _call_name(call: ast.Call) -> str:
65
+ f = call.func
66
+ if isinstance(f, ast.Attribute):
67
+ return f.attr
68
+ if isinstance(f, ast.Name):
69
+ return f.id
70
+ return ""
71
+
72
+
73
+ def _names_bound_to_reddit_url(scope: ast.AST) -> set[str]:
74
+ out: set[str] = set()
75
+ for n in ast.walk(scope):
76
+ if isinstance(n, ast.Assign) and _has_reddit_url(n.value):
77
+ out.update(t.id for t in n.targets if isinstance(t, ast.Name))
78
+ return out
79
+
80
+
81
+ def _is_http_request(call: ast.Call) -> bool:
82
+ name = _call_name(call)
83
+ if name in URLLIB_CALLS:
84
+ return True
85
+ if name in SUBPROCESS_CALLS:
86
+ return any(s in HTTP_CLIENT_BINARIES for s in _strings(call))
87
+ return False
88
+
89
+
90
+ def functions_requesting_reddit(source: str) -> tuple[list[str], list[str]]:
91
+ """返回 (发起 HTTP 请求的函数名, 其中请求实参指向 reddit.com 的函数名)。
92
+
93
+ 只检查请求调用的实参:reddit URL 作为 permalink / 展示字段,或放进发给 Hub 的 payload,
94
+ 都不在请求实参里,不算违规。
95
+ """
96
+ tree = ast.parse(source)
97
+ module_consts = _names_bound_to_reddit_url(ast.Module(body=[n for n in tree.body if isinstance(n, ast.Assign)], type_ignores=[]))
98
+ requesting: list[str] = []
99
+ offending: list[str] = []
100
+ for fn in ast.walk(tree):
101
+ if not isinstance(fn, (ast.FunctionDef, ast.AsyncFunctionDef)):
102
+ continue
103
+ calls = [n for n in ast.walk(fn) if isinstance(n, ast.Call) and _is_http_request(n)]
104
+ if not calls:
105
+ continue
106
+ requesting.append(fn.name)
107
+ reddit_names = module_consts | _names_bound_to_reddit_url(fn)
108
+ for call in calls:
109
+ arg_nodes = [*call.args, *(kw.value for kw in call.keywords)]
110
+ used = {n.id for a in arg_nodes for n in ast.walk(a) if isinstance(n, ast.Name)}
111
+ if any(_has_reddit_url(a) for a in arg_nodes) or used & reddit_names:
112
+ offending.append(fn.name)
113
+ break
114
+ return requesting, offending
115
+
116
+
117
+ class StaticScanTests(unittest.TestCase):
118
+ @classmethod
119
+ def setUpClass(cls) -> None:
120
+ cls.source = SCRIPT.read_text(encoding="utf-8")
121
+
122
+ def test_legacy_direct_fetch_tokens_are_gone(self) -> None:
123
+ for token in FORBIDDEN_TOKENS:
124
+ with self.subTest(token=token):
125
+ self.assertNotIn(token, self.source)
126
+
127
+ def test_no_function_that_sends_requests_references_reddit_com(self) -> None:
128
+ requesting, offending = functions_requesting_reddit(self.source)
129
+ # 对照:真实脚本里确实有发请求的函数(Arctic / Firecrawl / Hub),扫描器不是空转。
130
+ self.assertIn("arctic_get", requesting)
131
+ self.assertGreaterEqual(len(requesting), 3)
132
+ self.assertEqual(offending, [])
133
+
134
+ def test_scanner_control_flags_a_direct_reddit_request(self) -> None:
135
+ bad_literal = (
136
+ "import urllib.request\n"
137
+ "def fetch():\n"
138
+ " return urllib.request.urlopen('https://www.reddit.com/r/x/.json')\n"
139
+ )
140
+ bad_module_const = (
141
+ "import urllib.request\n"
142
+ "REDDIT_URL = 'https://old.reddit.com/r/x/'\n"
143
+ "def fetch():\n"
144
+ " req = urllib.request.Request(REDDIT_URL)\n"
145
+ " return urllib.request.urlopen(req)\n"
146
+ )
147
+ bad_local_var = (
148
+ "import urllib.request\n"
149
+ "def fetch(sub):\n"
150
+ " url = f'https://www.reddit.com/r/{sub}/about.json'\n"
151
+ " return urllib.request.urlopen(url)\n"
152
+ )
153
+ bad_curl = (
154
+ "import subprocess\n"
155
+ "def fetch():\n"
156
+ " return subprocess.run(['curl', 'https://www.reddit.com/.json'])\n"
157
+ )
158
+ for name, src in (
159
+ ("literal", bad_literal),
160
+ ("module_const", bad_module_const),
161
+ ("local_var", bad_local_var),
162
+ ("curl", bad_curl),
163
+ ):
164
+ with self.subTest(case=name):
165
+ self.assertEqual(functions_requesting_reddit(src)[1], ["fetch"])
166
+
167
+ def test_scanner_control_does_not_flag_display_payload_or_lookalike_hosts(self) -> None:
168
+ good = (
169
+ "import subprocess, urllib.request\n"
170
+ "REDDIT_URL = 'https://www.reddit.com/'\n"
171
+ "def fetch():\n"
172
+ " return urllib.request.urlopen('https://arctic-shift.photon-reddit.com/api')\n"
173
+ "def lookalike():\n"
174
+ " return urllib.request.urlopen('https://reddit.com.example.org/x')\n"
175
+ "def permalink(sub):\n"
176
+ " return f'https://www.reddit.com/r/{sub}/'\n"
177
+ "def display(post):\n"
178
+ " post['url'] = REDDIT_URL + 'r/x'\n"
179
+ " return urllib.request.urlopen('https://arctic-shift.photon-reddit.com/api')\n"
180
+ "def to_hub(url):\n"
181
+ " payload = {'url': 'https://www.reddit.com/r/x/', 'target': REDDIT_URL}\n"
182
+ " return subprocess.run(['social-hub', 'scrape', '--json', str(payload)])\n"
183
+ )
184
+ requesting, offending = functions_requesting_reddit(good)
185
+ # 检查器确实扫到了这些请求函数,不是因为扫描为空才没有误报。
186
+ self.assertTrue({"fetch", "lookalike", "display"} <= set(requesting))
187
+ self.assertEqual(offending, [])
188
+
189
+ def test_requirements_have_no_legacy_packages(self) -> None:
190
+ req = (SCRIPT.parents[1] / "requirements.txt").read_text(encoding="utf-8")
191
+ lines = [ln.strip() for ln in req.splitlines() if ln.strip() and not ln.strip().startswith("#")]
192
+ self.assertEqual(lines, [])
193
+
194
+ def test_via_curl_is_deprecated_noop_on_all_four_subcommands(self) -> None:
195
+ cmds = {
196
+ "keyword": ["keyword", "battery"],
197
+ "subreddit": ["subreddit", "battery", "-r", "example"],
198
+ "stickies": ["stickies", "-r", "example"],
199
+ "links": ["links", POST_URL],
200
+ }
201
+ parser_help = io.StringIO()
202
+ for mode, argv in cmds.items():
203
+ with self.subTest(mode=mode):
204
+ stderr = io.StringIO()
205
+ with (
206
+ mock.patch.object(sys, "argv", [str(SCRIPT), *argv, "--via-curl", "--help"]),
207
+ contextlib.redirect_stderr(stderr),
208
+ contextlib.redirect_stdout(parser_help),
209
+ self.assertRaises(SystemExit) as ctx,
210
+ ):
211
+ voc.main()
212
+ self.assertEqual(ctx.exception.code, 0) # --help 正常退出,说明参数已注册
213
+ self.assertIn("DEPRECATED", parser_help.getvalue())
214
+
215
+ def test_links_with_via_curl_warns_and_keeps_output_shape(self) -> None:
216
+ def run(extra: list[str]) -> tuple[int, dict, str]:
217
+ stdout, stderr = io.StringIO(), io.StringIO()
218
+ with (
219
+ mock.patch.object(voc.urllib.request, "urlopen", side_effect=AssertionError("direct network call")),
220
+ mock.patch.object(voc, "resolve_proxy_url", return_value=""),
221
+ mock.patch.object(voc, "arctic_post_by_permalink", return_value=None),
222
+ mock.patch.object(voc, "fetch_page_scrape", return_value={"markdown": "Public page"}),
223
+ mock.patch.object(voc, "collect_subreddit_metrics", return_value=[]),
224
+ mock.patch.object(voc, "hub_writeback_items", return_value={"status": "pending"}),
225
+ mock.patch.object(voc, "sleep_with_jitter", return_value=0.0),
226
+ mock.patch.object(sys, "argv", [str(SCRIPT), "links", POST_URL, "--fallback", "auto", "--sleep-sec", "0", *extra]),
227
+ contextlib.redirect_stdout(stdout),
228
+ contextlib.redirect_stderr(stderr),
229
+ ):
230
+ code = voc.main()
231
+ return code, json.loads(stdout.getvalue()), stderr.getvalue()
232
+
233
+ code_a, out_a, err_a = run([])
234
+ code_b, out_b, err_b = run(["--via-curl"])
235
+ self.assertEqual((code_a, code_b), (0, 0))
236
+ self.assertNotIn("DEPRECATED", err_a) # 对照:不传则无告警
237
+ self.assertIn("DEPRECATED: --via-curl 已无效果(v2.12.28 起不直连 reddit)", err_b)
238
+ self.assertEqual(set(out_a), set(out_b))
239
+ self.assertEqual(out_a["request_identity"], out_b["request_identity"])
240
+
241
+
242
+ class RequestIdentityContractTests(unittest.TestCase):
243
+ def build(self, proxy: str, mode: str | None = None) -> dict:
244
+ tracker = voc.RequestTracker()
245
+ if mode is not None:
246
+ tracker.last_fetch_mode = mode
247
+ with mock.patch.object(voc, "resolve_proxy_url", return_value=proxy):
248
+ return voc.build_output(
249
+ ok=True,
250
+ mode="keyword",
251
+ query="q",
252
+ source_urls=[],
253
+ items=[],
254
+ subreddit_metrics=[],
255
+ requested_limit=10,
256
+ effective_limit=10,
257
+ limit_truncated=False,
258
+ sleep_sec=6.0,
259
+ max_pages=1,
260
+ tracker=tracker,
261
+ notes=[],
262
+ errors=[],
263
+ )
264
+
265
+ def test_request_identity_shape_and_fixed_values(self) -> None:
266
+ identity = self.build("")["request_identity"]
267
+ self.assertEqual(set(identity), IDENTITY_KEYS)
268
+ self.assertEqual(
269
+ identity,
270
+ {
271
+ "user_agent_kind": "chrome",
272
+ "fetch_mode": "none",
273
+ "used_cookies": False,
274
+ "cookie_source": None,
275
+ "cookie_cache_reused": False,
276
+ "impersonation": None,
277
+ "proxy_used": False,
278
+ },
279
+ )
280
+
281
+ def test_proxy_used_follows_resolved_proxy(self) -> None:
282
+ # 对照组:proxy_used 是唯一随环境变化的键,防止断言被写死成常量。
283
+ self.assertIs(self.build("http://127.0.0.1:7890")["request_identity"]["proxy_used"], True)
284
+ self.assertIs(self.build("")["request_identity"]["proxy_used"], False)
285
+
286
+ def test_fetch_mode_reports_tracker_mode_but_never_curl_identity(self) -> None:
287
+ identity = self.build("", mode="browser_fallback")["request_identity"]
288
+ self.assertEqual(identity["fetch_mode"], "browser_fallback")
289
+ self.assertIsNone(identity["impersonation"])
290
+ self.assertIs(identity["used_cookies"], False)
291
+
292
+ def test_links_run_emits_same_identity_without_network(self) -> None:
293
+ stdout = io.StringIO()
294
+ with (
295
+ mock.patch.object(voc.urllib.request, "urlopen", side_effect=AssertionError("direct network call")) as urlopen,
296
+ mock.patch.object(voc, "resolve_proxy_url", return_value=""),
297
+ mock.patch.object(voc, "arctic_post_by_permalink", return_value=None),
298
+ mock.patch.object(voc, "fetch_page_scrape", return_value={"markdown": "Public page"}),
299
+ mock.patch.object(voc, "collect_subreddit_metrics", return_value=[]),
300
+ mock.patch.object(voc, "hub_writeback_items", return_value={"status": "pending"}),
301
+ mock.patch.object(voc, "sleep_with_jitter", return_value=0.0),
302
+ mock.patch.object(sys, "argv", [str(SCRIPT), "links", POST_URL, "--fallback", "auto", "--sleep-sec", "0"]),
303
+ contextlib.redirect_stdout(stdout),
304
+ ):
305
+ code = voc.main()
306
+ out = json.loads(stdout.getvalue())
307
+ self.assertEqual(code, 0)
308
+ self.assertEqual(set(out["request_identity"]), IDENTITY_KEYS)
309
+ self.assertEqual(out["request_identity"]["fetch_mode"], "none")
310
+ self.assertIs(out["request_identity"]["used_cookies"], False)
311
+ urlopen.assert_not_called()
312
+
313
+
314
+ if __name__ == "__main__":
315
+ unittest.main()