@streamapp/stream 0.0.1

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.
Files changed (144) hide show
  1. package/README.md +24 -0
  2. package/bin/stream.mjs +89 -0
  3. package/package.json +34 -0
  4. package/resources/app/dist-panel/panel-boards.js +6 -0
  5. package/resources/app/dist-panel/panel-detail.css +1 -0
  6. package/resources/app/dist-panel/panel-detail.js +2141 -0
  7. package/resources/app/dist-panel/panel-manage.css +1 -0
  8. package/resources/app/dist-panel/panel-manage.js +2246 -0
  9. package/resources/app/dist-panel/panel-movie.css +1 -0
  10. package/resources/app/dist-panel/panel-movie.js +2268 -0
  11. package/resources/app/dist-panel/panel-research.css +1 -0
  12. package/resources/app/dist-panel/panel-research.js +316 -0
  13. package/resources/app/dist-panel/panel.css +1 -0
  14. package/resources/app/dist-panel/panel.js +463 -0
  15. package/resources/dsh-engine-lock/package-lock.json +9032 -0
  16. package/resources/extension/.output/chrome-mv3/assets/popup-C2NUZZQW.css +2 -0
  17. package/resources/extension/.output/chrome-mv3/background.js +1 -0
  18. package/resources/extension/.output/chrome-mv3/chunks/popup-BeBts9v2.js +50 -0
  19. package/resources/extension/.output/chrome-mv3/icon/128.png +0 -0
  20. package/resources/extension/.output/chrome-mv3/icon/16.png +0 -0
  21. package/resources/extension/.output/chrome-mv3/icon/32.png +0 -0
  22. package/resources/extension/.output/chrome-mv3/icon/48.png +0 -0
  23. package/resources/extension/.output/chrome-mv3/icon/96.png +0 -0
  24. package/resources/extension/.output/chrome-mv3/manifest.json +1 -0
  25. package/resources/extension/.output/chrome-mv3/popup.html +21 -0
  26. package/resources/packages/1lou/1lou-search.recipe.json +102 -0
  27. package/resources/packages/1lou/README.md +7 -0
  28. package/resources/packages/1lou/package.json +15 -0
  29. package/resources/packages/Douyin_TikTok_Download_API/README.md +23 -0
  30. package/resources/packages/Douyin_TikTok_Download_API/activate.ts +14 -0
  31. package/resources/packages/Douyin_TikTok_Download_API/adapter/adapter.ts +321 -0
  32. package/resources/packages/Douyin_TikTok_Download_API/adapter/danmaku.ts +21 -0
  33. package/resources/packages/Douyin_TikTok_Download_API/adapter/executor.ts +75 -0
  34. package/resources/packages/Douyin_TikTok_Download_API/adapter/normalize.ts +20 -0
  35. package/resources/packages/Douyin_TikTok_Download_API/manifests.yaml +521 -0
  36. package/resources/packages/Douyin_TikTok_Download_API/package.json +32 -0
  37. package/resources/packages/alist/README.md +14 -0
  38. package/resources/packages/alist/activate.ts +12 -0
  39. package/resources/packages/alist/adapter.ts +74 -0
  40. package/resources/packages/alist/manifests.yaml +76 -0
  41. package/resources/packages/alist/mounts.ts +120 -0
  42. package/resources/packages/alist/package.json +24 -0
  43. package/resources/packages/alist/presets.ts +53 -0
  44. package/resources/packages/alist/provision.ts +84 -0
  45. package/resources/packages/baidu/baidu-share.recipe.json +93 -0
  46. package/resources/packages/baidu/package.json +16 -0
  47. package/resources/packages/baidu-search/baidu-search.recipe.json +129 -0
  48. package/resources/packages/baidu-search/package.json +25 -0
  49. package/resources/packages/brave/brave-search.recipe.json +129 -0
  50. package/resources/packages/brave/package.json +25 -0
  51. package/resources/packages/browser/README.md +3 -0
  52. package/resources/packages/browser/adapter.ts +44 -0
  53. package/resources/packages/browser/manifests.yaml +20 -0
  54. package/resources/packages/browser/package.json +13 -0
  55. package/resources/packages/bt0/README.md +7 -0
  56. package/resources/packages/bt0/bt0-search.recipe.json +69 -0
  57. package/resources/packages/bt0/bt0-tlist.recipe.json +74 -0
  58. package/resources/packages/bt0/package.json +15 -0
  59. package/resources/packages/btbtla/README.md +6 -0
  60. package/resources/packages/btbtla/btbtla-detail.recipe.json +76 -0
  61. package/resources/packages/btbtla/btbtla-search.recipe.json +74 -0
  62. package/resources/packages/btbtla/package.json +15 -0
  63. package/resources/packages/builtin/README.md +8 -0
  64. package/resources/packages/builtin/manifests.yaml +487 -0
  65. package/resources/packages/builtin/package.json +14 -0
  66. package/resources/packages/douyin/README.md +39 -0
  67. package/resources/packages/douyin/douyin-collection.recipe.json +127 -0
  68. package/resources/packages/douyin/douyin-search.recipe.json +163 -0
  69. package/resources/packages/douyin/package.json +16 -0
  70. package/resources/packages/firecrawl/firecrawl-create-key.recipe.json +81 -0
  71. package/resources/packages/firecrawl/firecrawl-read-key.recipe.json +56 -0
  72. package/resources/packages/firecrawl/package.json +16 -0
  73. package/resources/packages/google/google-search.recipe.json +127 -0
  74. package/resources/packages/google/package.json +24 -0
  75. package/resources/packages/groq/groq-create-key.recipe.json +67 -0
  76. package/resources/packages/groq/package.json +16 -0
  77. package/resources/packages/huggingface/README.md +7 -0
  78. package/resources/packages/huggingface/huggingface-spaces.recipe.json +73 -0
  79. package/resources/packages/huggingface/package.json +15 -0
  80. package/resources/packages/imdb/imdb-chart.recipe.json +75 -0
  81. package/resources/packages/imdb/package.json +15 -0
  82. package/resources/packages/index.ts +15 -0
  83. package/resources/packages/iqiyi/README.md +10 -0
  84. package/resources/packages/iqiyi/iqiyi-cn-album.recipe.json +80 -0
  85. package/resources/packages/iqiyi/iqiyi-cn-search.recipe.json +71 -0
  86. package/resources/packages/iqiyi/package.json +15 -0
  87. package/resources/packages/lizhi/README.md +35 -0
  88. package/resources/packages/lizhi/lizhi-user.recipe.json +80 -0
  89. package/resources/packages/lizhi/package.json +15 -0
  90. package/resources/packages/manmanbuy/README.md +16 -0
  91. package/resources/packages/manmanbuy/manmanbuy-search.recipe.json +115 -0
  92. package/resources/packages/manmanbuy/package.json +15 -0
  93. package/resources/packages/mineru/README.md +38 -0
  94. package/resources/packages/mineru/package.json +28 -0
  95. package/resources/packages/pansou/README.md +14 -0
  96. package/resources/packages/pansou/activate.ts +11 -0
  97. package/resources/packages/pansou/adapter.ts +94 -0
  98. package/resources/packages/pansou/manifests.yaml +50 -0
  99. package/resources/packages/pansou/package.json +27 -0
  100. package/resources/packages/qq/package.json +15 -0
  101. package/resources/packages/qq/qq-send.recipe.json +104 -0
  102. package/resources/packages/quark/package.json +16 -0
  103. package/resources/packages/quark/quark-share.recipe.json +40 -0
  104. package/resources/packages/reddit/package.json +15 -0
  105. package/resources/packages/reddit/reddit-sub.recipe.json +77 -0
  106. package/resources/packages/replay/README.md +7 -0
  107. package/resources/packages/replay/package.json +13 -0
  108. package/resources/packages/rsshub/README.md +16 -0
  109. package/resources/packages/rsshub/manifests.yaml +107 -0
  110. package/resources/packages/rsshub/package.json +21 -0
  111. package/resources/packages/telegram/package.json +15 -0
  112. package/resources/packages/telegram/telegram-search.recipe.json +125 -0
  113. package/resources/packages/tencent/README.md +11 -0
  114. package/resources/packages/tencent/package.json +15 -0
  115. package/resources/packages/tencent/tencent-cn-episode.recipe.json +98 -0
  116. package/resources/packages/toubiec/README.md +38 -0
  117. package/resources/packages/toubiec/package.json +15 -0
  118. package/resources/packages/toubiec/toubiec-download.recipe.json +58 -0
  119. package/resources/packages/toubiec/toubiec-playlist.recipe.json +49 -0
  120. package/resources/packages/toubiec/toubiec-search.recipe.json +80 -0
  121. package/resources/packages/voiceprint/README.md +31 -0
  122. package/resources/packages/voiceprint/package.json +36 -0
  123. package/resources/packages/wikipedia/package.json +15 -0
  124. package/resources/packages/wikipedia/wikipedia-award-list.recipe.json +102 -0
  125. package/resources/packages/xhs/README.md +32 -0
  126. package/resources/packages/xhs/package.json +21 -0
  127. package/resources/packages/xhs/xhs-detail.recipe.json +129 -0
  128. package/resources/packages/xhs/xhs-home.recipe.json +144 -0
  129. package/resources/packages/xhs/xhs-like.recipe.json +96 -0
  130. package/resources/packages/xhs/xhs-search.recipe.json +138 -0
  131. package/resources/packages/xueqiu/README.md +9 -0
  132. package/resources/packages/xueqiu/package.json +16 -0
  133. package/resources/packages/xueqiu/xueqiu-detail.recipe.json +92 -0
  134. package/resources/packages/xueqiu/xueqiu-user.recipe.json +75 -0
  135. package/resources/packages/zhipu/package.json +16 -0
  136. package/resources/packages/zhipu/zhipu-create-key.recipe.json +74 -0
  137. package/resources/packages/zuna/README.md +31 -0
  138. package/resources/packages/zuna/package.json +15 -0
  139. package/resources/packages/zuna/zuna-album.recipe.json +46 -0
  140. package/resources/packages/zuna/zuna-download.recipe.json +53 -0
  141. package/resources/packages/zuna/zuna-playlist.recipe.json +46 -0
  142. package/resources/packages/zuna/zuna-search.recipe.json +96 -0
  143. package/resources/server.mjs +238876 -0
  144. package/resources/sidequest.jobs.js +1 -0
@@ -0,0 +1,96 @@
1
+ {
2
+ "version": 1,
3
+ "kind": "browser",
4
+ "sourceId": "xhs-like",
5
+ "cookieDomain": "xiaohongshu.com",
6
+ "entryUrl": "https://www.xiaohongshu.com/explore",
7
+ "entryWait": "domcontentloaded",
8
+ "rideCurrentPage": true,
9
+ "loginCheck": {
10
+ "loggedIn": ".main-container .user",
11
+ "wall": ".login-modal.reds-modal-open"
12
+ },
13
+ "session": {
14
+ "facility": "xhs",
15
+ "lifecycle": "persistent",
16
+ "visibility": "unattended"
17
+ },
18
+ "_why": "互动写入(点赞/收藏),不是采集。这是 knowingly 放宽『recipe 只读』边界(Gap C,见 docs/superpowers/specs/2026-07-18-xhs-like-collect-design.md §2)——若日后收紧信任模型,这份 recipe 是回补清单第一条。复用 evaluate step:targetCount:1 + 无 cursorField => 只发一次 in-page eval。call 在登录页里揪出站点自身的签名请求 fn(按语义名 postApiSnsWebV1Note{Like,Dislike,Collect,Uncollect} 扫模块,不硬编模块 id/导出字母),按 action 发对应端点。body key 各 action 不同(实测:like/unlike=note_oid、collect=note_id、uncollect=note_ids 逗号串),context-free(无需打开笔记)。业务/参数/风控错都会 reject => EvaluateDriftError => 端点失败 => 前端回滚。响应形状不统一,故只回 {noteId, action, ok},计数前端乐观。",
19
+ "steps": [
20
+ {
21
+ "kind": "evaluate",
22
+ "itemsAt": "items",
23
+ "pageSize": 1,
24
+ "maxPages": 1,
25
+ "call": "async (cursor, num, params) => { const g='webpackChunkxhs_pc_web'; let req; window[g].push([[Math.random()],{},r=>req=r]); let m; for (const id of Object.keys(req.m)) { try { if (req.m[id].toString().includes('postApiSnsWebV1NoteLike(')) { m=req(id); break; } } catch(e){} } if(!m) throw new Error('xhs interaction module not found'); const pick=(needle)=>{ for (const k of Object.keys(m)) { try { if (typeof m[k]==='function' && m[k].toString().includes(needle+'(')) return m[k]; } catch(e){} } return null; }; const id=params.noteId; const action=params.action; const table={ like:{fn:'postApiSnsWebV1NoteLike',body:{note_oid:id}}, unlike:{fn:'postApiSnsWebV1NoteDislike',body:{note_oid:id}}, collect:{fn:'postApiSnsWebV1NoteCollect',body:{note_id:id}}, uncollect:{fn:'postApiSnsWebV1NoteUncollect',body:{note_ids:id}} }; const spec=table[action]; if(!spec) throw new Error('unknown xhs interaction action: '+action); const fn=pick(spec.fn); if(!fn) throw new Error('xhs interaction fn not found: '+spec.fn); await fn(spec.body); return { items: [ { noteId:id, action:action, ok:true } ] }; }"
26
+ }
27
+ ],
28
+ "observers": [],
29
+ "output": {
30
+ "itemsAt": "items",
31
+ "dedupeBy": "noteId",
32
+ "targetCount": 1,
33
+ "mapping": {
34
+ "noteId": "noteId",
35
+ "action": "action",
36
+ "ok": "ok"
37
+ }
38
+ },
39
+ "policy": {
40
+ "maxTaskMs": 20000
41
+ },
42
+ "meta": {
43
+ "auth": {
44
+ "type": "session",
45
+ "facility": "xhs",
46
+ "login": "qr",
47
+ "loginUrl": "https://www.xiaohongshu.com/explore",
48
+ "qrSelector": ".login-container img.qrcode-img, .qrcode img",
49
+ "cookieDomain": "xiaohongshu.com",
50
+ "sessionCookies": [
51
+ "web_session"
52
+ ]
53
+ },
54
+ "effects": [
55
+ "write"
56
+ ],
57
+ "action": true,
58
+ "_why_action": "写用户账户(点赞/收藏),meta.action:true 让它落进 ActionRecipeBlockedError 那道闸——模型经 stream_read/定时调度不能碰它。它仍能被前端『点赞』按钮正常调用:那条路(POST /api/discover/interact → harvest.xhsInteract → config.readSource('xhs-like', ..., { userInitiated: true }))显式声明了 userInitiated,闸认的是这个标记,不是 kind。",
59
+ "normalizer": "xhs",
60
+ "type": "post",
61
+ "title": "互动(点赞/收藏)",
62
+ "description": "小红书笔记点赞/收藏/取消 · 登录态 tab 内调站点签名接口写入",
63
+ "topics": [
64
+ "xhs",
65
+ "xiaohongshu",
66
+ "social"
67
+ ],
68
+ "categories": [
69
+ "social-media"
70
+ ],
71
+ "facility": {
72
+ "key": "xhs",
73
+ "label": "小红书"
74
+ },
75
+ "capabilities": [
76
+ "anchor"
77
+ ],
78
+ "cadence_hint_seconds": 1800,
79
+ "discoverable": false,
80
+ "_why_pick_in": "两个选择面都不出现——点赞/收藏的写操作,由前端动作直调",
81
+ "pick_in": [],
82
+ "homepage": "xiaohongshu.com",
83
+ "params_schema": {
84
+ "noteId": {
85
+ "type": "string",
86
+ "required": true,
87
+ "description": "笔记 id"
88
+ },
89
+ "action": {
90
+ "type": "string",
91
+ "required": true,
92
+ "description": "like | unlike | collect | uncollect"
93
+ }
94
+ }
95
+ }
96
+ }
@@ -0,0 +1,138 @@
1
+ {
2
+ "version": 4,
3
+ "kind": "browser",
4
+ "sourceId": "xhs-search",
5
+ "cookieDomain": "xiaohongshu.com",
6
+ "entryUrl": "https://www.xiaohongshu.com/search_result?keyword={keyword}",
7
+ "entryWait": "load",
8
+ "loginCheck": {
9
+ "loggedIn": ".main-container .user",
10
+ "wall": ".login-modal.reds-modal-open"
11
+ },
12
+ "_why_one_shot": "搜索没有常驻的必要(用户拍板 2026-08-24):搜完 tab 留着只是占用户浏览器。one-shot = 跑完 release 即关。代价看 _why_ledger——账本随标签走,关了就丢,xhs-detail 落到它自己的 fallbackUrl(直达笔记页,xsec_token 随搜索条目带着),慢一点但功能不缺。",
13
+ "session": {
14
+ "facility": "xhs",
15
+ "lifecycle": "one-shot",
16
+ "visibility": "unattended"
17
+ },
18
+ "_why_unattended": "后台档(unattended):**不抢屏**。用户在 Stream 里点一下,屏幕就该留在 Stream —— 浏览器蹦到面前再缩回去,比它省下的那点时间烦得多。\n\n「前台」和「能不能跑」本来就不是一回事。可信输入不是靠占前台换来的,是靠 `Emulation.setFocusEmulationEnabled`(在 `browser-ext.ts` 的 launch 一处开启,那里有对照数字)——**它给的是「页面被当成有焦点」,不是帧**,别把两件事说成一件。开了之后,隐藏标签一次可信点击 **162–185ms**,比它当活动标签时的 204–257ms 还快;不开则是 **39.8–41.6s**。\n\n本条另有一层:它是用户按下搜索才跑的,跑完结果要回到 Stream 里看——全程没有任何一刻需要用户盯着浏览器。",
19
+ "_why_ledger": "搜索结果这一批就是 xhs 的账本:搜出来的顺序 = 页面上卡片的铺排顺序,idField 与 xhs-detail locate 的 identityParam(noteId) 同字段。**one-shot 之下账本随标签一起关**——所以 detail 的常态就是它自己的 fallbackUrl(直达笔记页);这份 ledger 声明保留,是给「同一次运行内」的 step 用坐标系,不再承诺跨运行存活。",
20
+ "ledger": {
21
+ "idField": "noteId"
22
+ },
23
+ "steps": [
24
+ {
25
+ "kind": "scroll",
26
+ "dwell_s": [
27
+ 0.7,
28
+ 1
29
+ ],
30
+ "maxTimes": 20,
31
+ "noProgressStop": 3
32
+ }
33
+ ],
34
+ "observers": [
35
+ {
36
+ "_why": "搜索结果走站点自己的 XHR /api/sns/web/v1/search/notes(真实抓包见 src/replay/fixtures/xhs-feed.ts)——像 homefeed 一样拦接口读,不再 type/submit + 滚 40 屏抠 DOM。runner 因为存在 network observer 会在 observer 挂好之后强制重新 goto entryUrl(recipe-runner capturesLoad),所以带 keyword 的首屏那次 search/notes 请求也被这个 observer 看到。响应是 snake_case,与 homefeed network 分支同 note_card 形状,沿用其已验证映射。搜索 token 配 pc_search 源。",
37
+ "kind": "network",
38
+ "urlPattern": "*/api/sns/web/v2/search/notes*",
39
+ "windowMs": 300000,
40
+ "maxBodyBytes": 8000000,
41
+ "input": {
42
+ "itemsAt": "data.items",
43
+ "dedupeBy": "id",
44
+ "targetCount": 400,
45
+ "mapping": {
46
+ "noteId": "id",
47
+ "guid": "id",
48
+ "title": "note_card.display_title",
49
+ "author": "note_card.user.nick_name",
50
+ "author_avatar": "note_card.user.avatar",
51
+ "likes": "note_card.interact_info.liked_count",
52
+ "cover": "note_card.cover.url_default",
53
+ "note_type": "note_card.type",
54
+ "xsec_token": "xsec_token",
55
+ "link": "https://www.xiaohongshu.com/explore/{id}?xsec_token={xsec_token}&xsec_source=pc_search"
56
+ },
57
+ "assert": [
58
+ {
59
+ "path": "data.items",
60
+ "desc": "xhs search/notes items"
61
+ }
62
+ ]
63
+ }
64
+ }
65
+ ],
66
+ "output": {
67
+ "itemsAt": "items",
68
+ "dedupeBy": "guid",
69
+ "_why_targetCount": "**这个数才是停止条件**,不是上面 observer 的 400(那只是累积上限)。实测:60 时滚 2-3 次、9.3s 出 63 条,scroll 阶段 7025ms 里绝大部分是 dwell。降到 30 少滚一轮,是省时间最便宜的一刀——比砍 dwell 零风险(dwell 是拟人化的承重件)。",
70
+ "targetCount": 30,
71
+ "mapping": {
72
+ "noteId": "noteId",
73
+ "guid": "guid",
74
+ "title": "title",
75
+ "author": "author",
76
+ "author_avatar": "author_avatar",
77
+ "likes": "likes",
78
+ "cover": "cover",
79
+ "note_type": "note_type",
80
+ "xsec_token": "xsec_token",
81
+ "link": "link"
82
+ }
83
+ },
84
+ "policy": {
85
+ "minActionIntervalMs": 900,
86
+ "maxTaskMs": 300000
87
+ },
88
+ "meta": {
89
+ "_why_uses": "同 xhs-home:搜索结果里的每一条,点开取正文/图集/评论走的是共用的 xhs-detail(src/content/enrich/index.ts)。detail 漂了,本源采集照常成功、每一条却都点不开——不申报就没有任何一处会把它算进受影响的源。",
90
+ "uses": [
91
+ "xhs-detail"
92
+ ],
93
+ "auth": {
94
+ "type": "session",
95
+ "facility": "xhs",
96
+ "login": "qr",
97
+ "loginUrl": "https://www.xiaohongshu.com/explore",
98
+ "qrSelector": ".login-container img.qrcode-img, .qrcode img",
99
+ "cookieDomain": "xiaohongshu.com",
100
+ "sessionCookies": [
101
+ "web_session"
102
+ ]
103
+ },
104
+ "normalizer": "xhs",
105
+ "type": "post",
106
+ "description": "小红书 — 关键词搜索(登录态浏览器采集,模拟人工输入+滚动)",
107
+ "topics": [
108
+ "xhs",
109
+ "xiaohongshu",
110
+ "social"
111
+ ],
112
+ "categories": [
113
+ "social-media"
114
+ ],
115
+ "facility": {
116
+ "key": "xhs",
117
+ "label": "小红书"
118
+ },
119
+ "capabilities": [
120
+ "search"
121
+ ],
122
+ "provides": [
123
+ "search-content"
124
+ ],
125
+ "key_param": "keyword",
126
+ "priority": 80,
127
+ "discoverable": true,
128
+ "cadence_hint_seconds": 1800,
129
+ "homepage": "xiaohongshu.com",
130
+ "params_schema": {
131
+ "keyword": {
132
+ "type": "string",
133
+ "required": true,
134
+ "description": "搜索关键词"
135
+ }
136
+ }
137
+ }
138
+ }
@@ -0,0 +1,9 @@
1
+
2
+ ## 说明(迁自 manifests.yaml 注释)
3
+
4
+ 雪球 用户动态 (user_timeline) — replay source(跑在用户自己的 Chrome 上)。
5
+
6
+ xueqiu-detail(单条状态详情,enrich)——雪球 user_timeline 把被转发/被回复原帖的 description
7
+ 服务端截断("...":实测 statuses/show.json 与详情页裸 HTML 都被 WAF 拦成挑战页,哪怕带着有效
8
+ 登录 cookie 裸 fetch 也过不去——只有真实浏览器跑一遍它的混淆 JS 才能过,故 kind:browser,不是
9
+ kind:http)。on-open 懒加载,不进推荐/时间线,故 discoverable:false。
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "@streamapp/xueqiu",
3
+ "version": "1.0.0",
4
+ "author": "stream",
5
+ "keywords": [
6
+ "stream-recipe"
7
+ ],
8
+ "stream": {
9
+ "type": "recipe",
10
+ "name": "雪球",
11
+ "tagline": "某个用户的动态时间线",
12
+ "facility": "xueqiu",
13
+ "schemaVersion": 1,
14
+ "cookieDomain": "xueqiu.com"
15
+ }
16
+ }
@@ -0,0 +1,92 @@
1
+ {
2
+ "version": 2,
3
+ "kind": "browser",
4
+ "sourceId": "xueqiu-detail",
5
+ "_why_browser": "statuses/show.json 和状态详情页的裸 HTML 都被雪球的 Aliyun WAF 拦成挑战页——即便带着 broker 发的有效登录 cookie 也过不去(实测:user_timeline.json 同一份 cookie 裸 fetch 能过,这两个不能)。WAF 要求真的跑一遍它的混淆 JS,只有真实浏览器能过,所以这条只能走 kind:browser,在用户自己的 Chrome 里读渲染完的 DOM。",
6
+ "entryUrl": "{permalink}",
7
+ "entryWait": "domcontentloaded",
8
+ "loginCheck": {
9
+ "loggedIn": ".avatar",
10
+ "_why_wall": "雪球把登录弹窗的 DOM 壳子(.modal__login)常驻页面、inline style=\"display: none\" 隐藏,不是打开时才插入——exists() 只查 querySelector 存在性不看可见性,裸类名一律假阳性(实测已登录页面上照样命中 4 个)。:not([style*='display: none']) 排除隐藏态那份壳子,只在它真被打开(inline style 改掉 none)时才算 WALLED。",
11
+ "wall": ".modal__login:not([style*='display: none'])"
12
+ },
13
+ "session": {
14
+ "facility": "xueqiu",
15
+ "lifecycle": "one-shot",
16
+ "visibility": "unattended"
17
+ },
18
+ "steps": [],
19
+ "observers": [
20
+ {
21
+ "kind": "dom",
22
+ "itemSelector": "#app",
23
+ "trigger": "entry",
24
+ "fields": {
25
+ "id": {
26
+ "selector": "a.time",
27
+ "attr": "href",
28
+ "extract": "(\\d+)$"
29
+ },
30
+ "text": {
31
+ "selector": ".article__bd__detail"
32
+ }
33
+ }
34
+ }
35
+ ],
36
+ "output": {
37
+ "itemsAt": "items",
38
+ "dedupeBy": "id",
39
+ "targetCount": 1,
40
+ "mapping": {
41
+ "id": "id",
42
+ "text": "text"
43
+ },
44
+ "assert": [
45
+ {
46
+ "path": "items.0.text",
47
+ "desc": "xueqiu status detail page rendered its body text"
48
+ }
49
+ ]
50
+ },
51
+ "policy": {
52
+ "maxTaskMs": 20000
53
+ },
54
+ "meta": {
55
+ "type": "post",
56
+ "title": "状态详情(enrich)",
57
+ "description": "雪球单条状态详情 · 补全被截断的原帖正文",
58
+ "topics": [
59
+ "xueqiu",
60
+ "finance"
61
+ ],
62
+ "categories": [
63
+ "finance"
64
+ ],
65
+ "facility": {
66
+ "key": "xueqiu",
67
+ "label": "雪球"
68
+ },
69
+ "capabilities": [
70
+ "anchor"
71
+ ],
72
+ "priority": 80,
73
+ "discoverable": false,
74
+ "_why_pick_in": "两个选择面都不出现——打开一条状态时由 enrich 直调",
75
+ "pick_in": [],
76
+ "cadence_hint_seconds": 1800,
77
+ "auth": {
78
+ "type": "session",
79
+ "facility": "xueqiu",
80
+ "login": "cookie",
81
+ "cookieDomain": "xueqiu.com"
82
+ },
83
+ "homepage": "xueqiu.com",
84
+ "params_schema": {
85
+ "permalink": {
86
+ "type": "string",
87
+ "required": true,
88
+ "description": "状态详情页完整 URL(quoted.permalink,https://xueqiu.com/<uid>/<statusId>)"
89
+ }
90
+ }
91
+ }
92
+ }
@@ -0,0 +1,75 @@
1
+ {
2
+ "version": 2,
3
+ "kind": "fetch",
4
+ "sourceId": "xueqiu-user",
5
+ "cookieDomain": "xueqiu.com",
6
+ "entryUrl": "https://xueqiu.com/u/{id}",
7
+ "entryWait": "load",
8
+ "_history_transport": "考古结论(2026-07-28):42e1c9fb 当初把它 opt-out 回 cloak,**不是技术原因**——那天用户正在把 xhs 迁 cloak,原话是「行,把 xueqiu 那个也改了,我一起测,雪球还是 cdp,改 cloak」,纯粹为了一次测两条 cloak 路径。同一会话里唯一的实际抱怨(「cdp 一直新开页面」)是当年采集标签落在用户当前窗口的毛病,background 档出现后不成立。cloak 整体退役(42f707d0)后 transport 字段已不存在——只有一个浏览器:用户自己的 Chrome。留这段是防止将来有人再想给它「换个浏览器」。",
9
+ "request": {
10
+ "url": "https://xueqiu.com/v4/statuses/user_timeline.json?user_id={id}&page={page}",
11
+ "method": "GET"
12
+ },
13
+ "pagination": {
14
+ "mode": "increment",
15
+ "param": "page",
16
+ "start": 1,
17
+ "step": 1,
18
+ "itemsAt": "statuses",
19
+ "maxPages": 2
20
+ },
21
+ "assert": [
22
+ {
23
+ "path": "statuses",
24
+ "desc": "user_timeline returned a statuses[] array"
25
+ }
26
+ ],
27
+ "mapping": {
28
+ "guid": "id",
29
+ "title": "title",
30
+ "description": "description",
31
+ "link": "target",
32
+ "author": "user.screen_name",
33
+ "pubDate": "created_at",
34
+ "quoteText": "retweeted_status.description",
35
+ "quoteAuthor": "retweeted_status.user.screen_name",
36
+ "quotePermalink": "retweeted_status.target"
37
+ },
38
+ "meta": {
39
+ "normalizer": "xueqiu",
40
+ "type": "post",
41
+ "description": "雪球 — 用户动态",
42
+ "topics": [
43
+ "xueqiu",
44
+ "finance"
45
+ ],
46
+ "categories": [
47
+ "finance"
48
+ ],
49
+ "facility": {
50
+ "key": "xueqiu",
51
+ "label": "雪球"
52
+ },
53
+ "capabilities": [
54
+ "timeline"
55
+ ],
56
+ "key_param": "id",
57
+ "priority": 90,
58
+ "discoverable": true,
59
+ "cadence_hint_seconds": 1800,
60
+ "auth": {
61
+ "type": "none"
62
+ },
63
+ "homepage": "xueqiu.com",
64
+ "params_schema": {
65
+ "id": {
66
+ "type": "string",
67
+ "required": true,
68
+ "description": "用户 id(雪球用户主页 URL xueqiu.com/u/<id> 中的数字)"
69
+ }
70
+ },
71
+ "radar": [
72
+ "xueqiu.com/u/:id"
73
+ ]
74
+ }
75
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "@streamapp/zhipu",
3
+ "version": "1.0.0",
4
+ "author": "stream",
5
+ "keywords": [
6
+ "stream-recipe"
7
+ ],
8
+ "stream": {
9
+ "type": "recipe",
10
+ "name": "智谱 BigModel",
11
+ "tagline": "在 bigmodel.cn 自助建 API key",
12
+ "facility": "zhipu",
13
+ "schemaVersion": 1,
14
+ "cookieDomain": "bigmodel.cn"
15
+ }
16
+ }
@@ -0,0 +1,74 @@
1
+ {
2
+ "version": 1,
3
+ "kind": "browser",
4
+ "sourceId": "zhipu-create-key",
5
+ "cookieDomain": "bigmodel.cn",
6
+ "entryUrl": "https://bigmodel.cn/apikey/platform",
7
+ "entryWait": "load",
8
+ "loginCheck": {
9
+ "loggedIn": ".api-keys .header-right button",
10
+ "wall": "a[href*='/login']"
11
+ },
12
+ "session": {
13
+ "facility": "zhipu",
14
+ "lifecycle": "one-shot",
15
+ "visibility": "interactive"
16
+ },
17
+ "_selectors_verified": "2026-07-30 活体重定于 bigmodel.cn/apikey/platform(Element-UI,全站无 data-testid,只能靠语义祖先收敛)。这一页有两处「第一个命中就是错的」:(1) button.el-button--primary 命中 2 个,第一个是页面预渲染、不可见的「确 定」——所以开弹窗必须走 .api-keys .header-right 这个祖先;(2) 弹窗里 .el-message-box .el-button--default 命中 2 个,第一个是「取消」——确定键靠 --primary 区分。别用 data-v-* 属性做选择器:那是 Vue scoped-style 的构建哈希,站方重新发布就变。",
18
+ "_why_last_child": "新建的 key **追加在表尾**(列表按创建时间升序,实测两行:默认项目 2026-06-18 在前,新建的在后)。所以复制图标定位在 tr:last-child——不是第一行。",
19
+ "steps": [
20
+ {
21
+ "kind": "click",
22
+ "selector": ".api-keys .header-right button",
23
+ "expect": { "selector": ".el-message-box input.el-input__inner", "timeout": 10000 }
24
+ },
25
+ { "kind": "type", "selector": ".el-message-box input.el-input__inner", "text": "{name}" },
26
+ {
27
+ "_why_countIncreases": "这一步的判据只能是「表格多了一行」。第一版写的是「最后一行有复制图标」——**恒真**:列表里本来就有旧 key,那图标一直都在。于是「确定」根本没建成也照样判过,下一步点到旧那行的复制按钮上,失败最后以两步之外的「抽取命中 0 处」露面。四步全绿、结果全错。",
28
+ "kind": "click",
29
+ "selector": ".el-message-box__btns button.el-button--primary",
30
+ "expect": { "selector": ".el-table__body tr", "countIncreases": true, "errorSurface": ".el-message, .el-notification", "timeout": 15000 }
31
+ },
32
+ {
33
+ "_why": "明文只有这一下能拿到:列表接口返回的是**服务端掩码**(页面上永远是 be11...dE4X),点复制才会打 /api_keys/copy/<id> 单独取一次明文塞进剪贴板。实测点击前后 performance 资源记录只多出这一条请求——所以观察口就钉在它上面。",
34
+ "kind": "click",
35
+ "selector": ".el-table__body tr:last-child i.icon-wdapp_copy"
36
+ }
37
+ ],
38
+ "_why_no_observers": "本 recipe 一条 item 都不产,全部产出就是那把 key。凭证的捕获**不用**在这里声明 observer——引擎按 extract.from.network 的 glob 自己挂一份只捕获不累积的。曾经在这里挂过一个,结果它进了 items 管线、抓到的响应被空 output 判成 malformed → drift,drift 又盖住了 extract 的真实结论。",
39
+ "observers": [],
40
+ "output": { "itemsAt": "", "dedupeBy": "", "targetCount": 0, "mapping": {} },
41
+ "allowEmpty": true,
42
+ "extract": {
43
+ "field": "apiKey",
44
+ "pattern": "[0-9a-f]{32}\\.[A-Za-z0-9]{16}",
45
+ "from": { "network": "*/api_keys/copy/*" },
46
+ "timeout": 20000
47
+ },
48
+ "meta": {
49
+ "description": "在 bigmodel.cn 自助建一把 API key,并把只经剪贴板露面的明文落进 runtime_config(不产 item)",
50
+ "discoverable": false,
51
+ "_why_pick_in": "两个选择面都不出现——配置流程:用户在场自助建 key",
52
+ "pick_in": [],
53
+ "capabilities": ["anchor"],
54
+ "facility": { "key": "zhipu", "label": "智谱 BigModel" },
55
+ "params_schema": {
56
+ "name": {
57
+ "type": "string",
58
+ "required": true,
59
+ "description": "key 名。带一段随机后缀(如 stream-auto-7f3a)——建 key 不幂等,重跑会堆出一排看不出区别的同名 key"
60
+ }
61
+ },
62
+ "runtime_config": {
63
+ "ref": "zhipu",
64
+ "fields": {
65
+ "apiKey": {
66
+ "type": "secret",
67
+ "label": "智谱 API Key",
68
+ "description": "总结梯子与 OCR 梯子的智谱成员用它(GLM-4.7-Flash / GLM-4V-Flash 永久免费档)。可以手填,也可以让本 recipe 自己去建一把填进来。",
69
+ "helpUrl": "https://bigmodel.cn/apikey/platform"
70
+ }
71
+ }
72
+ }
73
+ }
74
+ }
@@ -0,0 +1,31 @@
1
+ ## 说明
2
+
3
+ music.znnu.com — 一个第三方网易云解析站。它的 API 对请求签名(HMAC-SHA256)、对响应加密
4
+ (AES-256-GCM),recipe 把这两段作为 PURE compute 片段带着,在零能力的 isolated-vm 堆里跑。
5
+ 它**不是平台**,所以不碰红线(碰红线的是 iqiyi)。
6
+
7
+ ## 四份 recipe
8
+
9
+ 四份都是 `kind:'http'` + compute 钩子(沙箱里签名 + 解密),一次请求拿一份结果:
10
+
11
+ | recipe | 干什么 | 谁在用 |
12
+ |---|---|---|
13
+ | `zuna-search` | 关键词 → 歌曲 | `music-search` 行的并发成员(与 `toubiec-search` 并列) |
14
+ | `zuna-download` | song id → 可播放/可下载地址 | `netease-track` 梯子(radar `music.163.com/song`,排在 toubiec 之后) |
15
+ | `zuna-playlist` | 歌单 id → 整份曲目名单 | radar `music.163.com/playlist` |
16
+ | `zuna-album` | 专辑 id → 整张曲目名单 | radar `music.163.com/album` |
17
+
18
+ 除 `zuna-download` 外,结果 item 的 link 都回到 `music.163.com/song?id=`——每首歌的真实文件
19
+ 地址在播放/下载那一刻由 `netease-track` 现解,名单本身不预解析。
20
+
21
+ ## 上游现状:整站 500
22
+
23
+ 2026-08-25 实测:`/api/song`、`/api/search`、`/api/playlist` 一律 `500 服务器内部错误`,每发
24
+ 约 15s,**在 music.znnu.com 自己的页面上点「开始解析」拿到的也是同一个 500**。
25
+
26
+ **别去改我们这一侧。** 逐项核过:前端 bundle 里的 HMAC 密钥就是 recipe 里那把
27
+ (`a09d0f37…`),签名算法、参数集、请求头一字不差,签名是**过的**——过不了才回 401。
28
+ 这是上游自己的故障,等它修。同一条梯子上 toubiec 是健康的那一档。
29
+
30
+ `zuna-album` 是照 `zuna-playlist` 的形状对称写出来的(参数取自它前端 `/api/album` 的调用点),
31
+ **在上游恢复之前没能跑通过一次**——它是这四份里唯一没有活体证据的。
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "@streamapp/zuna",
3
+ "version": "1.0.0",
4
+ "author": "stream",
5
+ "keywords": [
6
+ "stream-recipe"
7
+ ],
8
+ "stream": {
9
+ "type": "recipe",
10
+ "name": "Zuna",
11
+ "tagline": "聚合搜索网易云歌曲",
12
+ "facility": "zuna",
13
+ "schemaVersion": 1
14
+ }
15
+ }
@@ -0,0 +1,46 @@
1
+ {
2
+ "version": 1,
3
+ "kind": "http",
4
+ "sourceId": "zuna-album",
5
+ "request": { "url": "https://music.znnu.com/api/album", "method": "POST" },
6
+ "compute": {
7
+ "capabilities": ["hmacSha256", "aesGcmDecrypt"],
8
+ "prefetch": [
9
+ { "as": "keyResp", "request": { "url": "https://music.znnu.com/api/key", "method": "GET", "headers": { "X-Referer": "musicParser" } } },
10
+ { "as": "ipResp", "request": { "url": "https://music.znnu.com/api/ip", "method": "GET" } }
11
+ ],
12
+ "sign": "(() => { const p = { act: 'album', id: input.params.id, rawInput: input.params.id, ip: input.pre.ipResp.ip || '' }; let s = input.now + 'music.znnu.com'; for (const k of Object.keys(p).sort()) s += k + '=' + p[k]; const signature = hmacSha256('a09d0f3700a279584e1515354fbe08a7ee1c617f919543142fa625b82f1b5ad0', s); const enc = (o) => Object.keys(o).map(k => encodeURIComponent(k) + '=' + encodeURIComponent(o[k])).join('&'); return { body: enc(Object.assign({}, p, { signature, timestamp: String(input.now), domain: 'music.znnu.com' })), headers: { 'content-type': 'application/x-www-form-urlencoded', 'X-Referer': 'musicParser', 'X-Key-Token': input.pre.keyResp.data.keyToken } }; })()",
13
+ "decode": "(() => { const d = input.body && input.body.data; if (!d || d.enc !== 1) return input.body; return JSON.parse(aesGcmDecrypt({ key: input.pre.keyResp.data.key, iv: d.iv, ciphertext: d.ciphertext, tag: d.tag })); })()"
14
+ },
15
+ "pagination": { "mode": "increment", "param": "page", "start": 1, "step": 1, "itemsAt": "songs", "maxPages": 1 },
16
+ "assert": [{ "path": "songs", "desc": "decrypted zuna album has songs[] (one request, full tracklist)" }],
17
+ "mapping": {
18
+ "guid": "id",
19
+ "title": "name",
20
+ "author": "artists",
21
+ "link": "https://music.163.com/song?id={id}",
22
+ "description": "album",
23
+ "image": "picUrl"
24
+ },
25
+ "meta": {
26
+ "normalizer": "rsshub",
27
+ "type": "post",
28
+ "title": "专辑曲目",
29
+ "description": "Zuna 网易云解析 — 专辑曲目(整张一次取回;每首歌的文件地址在播放/下载时由 netease-track 现解)",
30
+ "topics": ["zuna", "网易云", "音乐", "专辑", "music", "netease"],
31
+ "categories": ["music"],
32
+ "facility": { "key": "zuna", "label": "Zuna" },
33
+ "capabilities": ["timeline"],
34
+ "mode": "collection",
35
+ "key_param": "id",
36
+ "priority": 90,
37
+ "discoverable": true,
38
+ "cadence_hint_seconds": 86400,
39
+ "auth": { "type": "none" },
40
+ "homepage": "music.znnu.com",
41
+ "params_schema": {
42
+ "id": { "type": "string", "required": true, "description": "网易云专辑 ID(取自 music.163.com/album?id=…)" }
43
+ },
44
+ "radar": ["music.163.com/album"]
45
+ }
46
+ }