@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 +19 -0
- package/dist/cmd-manifest.json +2 -2
- package/dist/register-subreddit-assets.js +1 -1
- package/dist/register-subreddit-assets.js.map +1 -1
- package/package.json +1 -1
- package/skills/integrity.json +14 -13
- package/skills/manifest.json +1 -1
- package/skills/reddit-content-rewrite/SKILL.md +1 -1
- package/skills/reddit-content-rewrite/prompts.md +2 -2
- package/skills/reddit-content-rewrite/sop-reference.md +18 -123
- package/skills/reddit-ext-post-audit/references/ext-post-audit.md +6 -7
- package/skills/reddit-matrix.lock.json +3 -3
- package/skills/reddit-voc-volume/SKILL.md +13 -9
- package/skills/reddit-voc-volume/references/arctic-shift.md +1 -1
- package/skills/reddit-voc-volume/requirements.txt +1 -4
- package/skills/reddit-voc-volume/scripts/reddit_voc_volume.py +22 -234
- package/skills/reddit-voc-volume/scripts/setup_env.sh +3 -40
- package/skills/reddit-voc-volume/tests/test_links_fallback.py +7 -7
- package/skills/reddit-voc-volume/tests/test_no_direct_reddit.py +315 -0
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
|
package/dist/cmd-manifest.json
CHANGED
|
@@ -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 — 手动刷新该板块的页面周指标(入队抓取)
|
|
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":"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"}
|
|
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
package/skills/integrity.json
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 1,
|
|
3
3
|
"algorithm": "sha256",
|
|
4
|
-
"fileCount":
|
|
4
|
+
"fileCount": 1658,
|
|
5
5
|
"files": {
|
|
6
6
|
"README.md": "5246d4ae2712e3e656b101041929183d7ae7f8ea487dacf5854187826ab7c335",
|
|
7
|
-
"manifest.json": "
|
|
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": "
|
|
13
|
+
"reddit-content-rewrite/SKILL.md": "7f7e98ca336eda259ad6cd01a36a85dcd6ad2ab40c4895e0df843eab1e0ab4e7",
|
|
14
14
|
"reddit-content-rewrite/matrix-contract.md": "6af594450dc0f900070ae33cc6b9e437bca86e9500475d26b6f97ffba9b9db64",
|
|
15
|
-
"reddit-content-rewrite/prompts.md": "
|
|
16
|
-
"reddit-content-rewrite/sop-reference.md": "
|
|
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": "
|
|
29
|
-
"reddit-matrix.lock.json": "
|
|
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": "
|
|
68
|
+
"reddit-voc-volume/SKILL.md": "bca757c89b75ebc37e83f40087eafed9d068f133be6e403ebb86e45acc04c75f",
|
|
69
69
|
"reddit-voc-volume/matrix-contract.md": "5676c3a847c35062e8432538e0beb98015dfae4a8144da9d437080211a384b11",
|
|
70
|
-
"reddit-voc-volume/references/arctic-shift.md": "
|
|
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": "
|
|
74
|
-
"reddit-voc-volume/scripts/reddit_voc_volume.py": "
|
|
75
|
-
"reddit-voc-volume/scripts/setup_env.sh": "
|
|
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": "
|
|
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",
|
package/skills/manifest.json
CHANGED
|
@@ -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
|
|
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
|
|
232
|
-
`
|
|
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
|
-
###
|
|
129
|
-
|
|
130
|
-
`reddit-voc-volume`
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
-
|
|
137
|
-
-
|
|
138
|
-
-
|
|
139
|
-
|
|
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
|
-
-
|
|
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
|
-
#
|
|
40
|
-
#
|
|
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
|
-
>
|
|
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": "
|
|
25
|
+
"contentSha256": "b42bfea525bd11646d6c15ae081d00137c114835611d01b77354698d4b05f00e"
|
|
26
26
|
},
|
|
27
27
|
"reddit-delivery-check": {
|
|
28
28
|
"contentSha256": "9231ff9ae5d950bc2fb87e45bee05e793b3ff9c37bee45e8444059a8b698e4a9"
|
|
29
29
|
},
|
|
30
30
|
"reddit-voc-volume": {
|
|
31
|
-
"contentSha256": "
|
|
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": "
|
|
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
|
|
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
|
-
|
|
404
|
-
|
|
405
|
-
`
|
|
406
|
-
`
|
|
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
|
-
"
|
|
477
|
-
"
|
|
478
|
-
"
|
|
479
|
-
"
|
|
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
|
|
18
|
+
不需要 cookie 或指纹伪装,且**正文不截断**。
|
|
19
19
|
|
|
20
20
|
## 覆盖范围(实测,非文档转述)
|
|
21
21
|
|
|
@@ -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
|
-
|
|
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":
|
|
4697
|
-
"cookie_source":
|
|
4698
|
-
"cookie_cache_reused":
|
|
4699
|
-
"impersonation":
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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
|
|
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
|
-
|
|
40
|
-
|
|
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
|
-
|
|
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
|
|
28
|
+
def test_arctic_miss_uses_rendered_page_without_direct_network_calls(self) -> None:
|
|
29
29
|
with (
|
|
30
|
-
mock.patch.object(voc.
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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()
|