@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,115 @@
1
+ {
2
+ "version": 1,
3
+ "kind": "html",
4
+ "sourceId": "manmanbuy-search",
5
+ "_why_discount_tab": "慢慢买有两个搜索 tab:`c=discount`(优惠流,SSR 直出,免签名免登录)和 `c=search`(商品比价,客户端渲染、走 prom2Extra 签名 XHR)。比价 tab 要慢慢买登录态、否则 7700『需登录验证』,是浏览器 recipe(贵一级)。优惠流 tab 每一行本身就是『某平台对某商品的报价』,搜一个商品名即得多平台价格——正是比价意图,且是 Tier-2 裸 HTTP,所以走这条。要精确到同一 SKU 分组再考虑升级比价 tab。",
6
+ "request": {
7
+ "url": "https://s.manmanbuy.com/pc/search/result?keyword={keyword}&c=discount&pageId={pageId}",
8
+ "method": "GET"
9
+ },
10
+ "list": {
11
+ "_why_class_prefix": "class 名带 CSS-module 哈希后缀(DiscountItemPC_box__m9G3M),站点一次重构就换,所以一律用 `[class*=\"前缀__\"]` 属性包含选择器钉住语义前缀,不写死哈希。",
12
+ "selector": "[class*=\"DiscountItemPC_box__\"]",
13
+ "limit": 40,
14
+ "fields": {
15
+ "title": {
16
+ "selector": "[class*=\"DiscountItemPC_itemTitle__\"] a",
17
+ "text": true
18
+ },
19
+ "link": {
20
+ "selector": "[class*=\"DiscountItemPC_itemTitle__\"] a",
21
+ "attr": "href",
22
+ "resolve": true
23
+ },
24
+ "guid": {
25
+ "selector": "[class*=\"DiscountItemPC_itemTitle__\"] a",
26
+ "attr": "href",
27
+ "resolve": true
28
+ },
29
+ "description": {
30
+ "_why": "itemSubTitle 是价格行(含促销与国补口径),如 `7619.02元+179.98元淘金币(含国补)`。这是这条比价条目的核心值,落成正文。",
31
+ "selector": "[class*=\"DiscountItemPC_itemSubTitle__\"] a",
32
+ "text": true
33
+ },
34
+ "author": {
35
+ "_why": "平台/店铺名(京东自营 / 天猫旗舰店 / 淘宝 …)当作这条报价的来源署名——多行同一商品不同平台,author 一栏就把『谁家的价』摆出来了。",
36
+ "selector": "[class*=\"DiscountItemPC_itemMall__\"]",
37
+ "text": true
38
+ },
39
+ "image": {
40
+ "_why": "顶层 image 字段:defaultNormalizer 读的是 raw.image(不是 cover),配价格文本渲染成 gallery 卡(商品图 + 价格)。",
41
+ "selector": "[class*=\"DiscountItemPC_itemCover__\"] img",
42
+ "attr": "src",
43
+ "resolve": true
44
+ }
45
+ }
46
+ },
47
+ "pagination": {
48
+ "_why": "优惠流按 pageId 翻页(URL 里 {pageId} 占位由 runner 每页替换,不是自动追加)。maxPages:2 = 约 70 条,够一次比价搜索;两页间新增的优惠会让少量条目重叠,按 guid=优惠链接 dedup 兜掉。",
49
+ "mode": "increment",
50
+ "param": "pageId",
51
+ "start": 1,
52
+ "step": 1,
53
+ "maxPages": 2
54
+ },
55
+ "assert": [
56
+ {
57
+ "selector": "[class*=\"DiscountItemPC_box__\"]",
58
+ "desc": "慢慢买优惠搜索列表行存在"
59
+ }
60
+ ],
61
+ "meta": {
62
+ "normalizer": "rsshub",
63
+ "type": "post",
64
+ "description": "慢慢买 · 全网比价关键词搜索 —— 搜一个商品名,返回各电商平台(京东/天猫/淘宝/拼多多…)对它的报价与促销,一行一个平台报价",
65
+ "topics": [
66
+ "manmanbuy",
67
+ "慢慢买",
68
+ "比价",
69
+ "价格",
70
+ "优惠",
71
+ "促销",
72
+ "电商",
73
+ "购物",
74
+ "shopping",
75
+ "price",
76
+ "搜索",
77
+ "search"
78
+ ],
79
+ "categories": [
80
+ "shopping"
81
+ ],
82
+ "facility": {
83
+ "key": "manmanbuy",
84
+ "label": "慢慢买"
85
+ },
86
+ "capabilities": [
87
+ "search"
88
+ ],
89
+ "provides": [
90
+ "search-price"
91
+ ],
92
+ "key_param": "keyword",
93
+ "priority": 80,
94
+ "discoverable": true,
95
+ "cadence_hint_seconds": 3600,
96
+ "example_queries": [
97
+ "慢慢买搜 iPhone 17 Pro 256G",
98
+ "比价 索尼 WH-1000XM5"
99
+ ],
100
+ "auth": {
101
+ "type": "none"
102
+ },
103
+ "homepage": "manmanbuy.com",
104
+ "params_schema": {
105
+ "keyword": {
106
+ "type": "string",
107
+ "required": true,
108
+ "description": "商品搜索关键词(越具体越能对上同一商品,如带型号/容量/颜色)"
109
+ }
110
+ },
111
+ "radar": [
112
+ "s.manmanbuy.com/pc/search/result"
113
+ ]
114
+ }
115
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "@streamapp/manmanbuy",
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": "manmanbuy",
13
+ "schemaVersion": 1
14
+ }
15
+ }
@@ -0,0 +1,38 @@
1
+ # MinerU
2
+
3
+ Optional document/image → markdown backend (no credentials, no source).
4
+
5
+ A service-only plugin: it contributes a backend container (reached through the gateway at
6
+ `/_p/mineru`, or a remote relay when `mineru_url` is configured) but registers no source
7
+ adapter/normalizer. Self-built mirror of MinerU behind a thin FastAPI wrapper.
8
+
9
+ Local container = "free but slow" (CPU default, GPU when present); cloud relay (`mineru_url`) = the
10
+ paid fast lane. Both tiers run the SAME MinerU — cloud sells throughput, not a better model
11
+ (frontier VLMs are *less* faithful on documents than MinerU's pipeline). Output quality on the
12
+ 2026-07-30 run (see `backend.gpu` below for the setup): Chinese text accurate, tables reconstructed
13
+ as HTML, nothing invented.
14
+
15
+ ## backend
16
+
17
+ `gpu: true` —— MinerU's deterministic-first pipeline (layout → table-structure → formula → reading
18
+ order) runs best on a GPU; born-digital PDFs extract the text layer directly (no VLM rewrite → exact
19
+ numbers), the VLM only OCRs scanned/image regions.
20
+
21
+ Measured 2026-07-30 (RTX 4080 16GB, host 46GB RAM; input = one screenshot dense with Chinese text,
22
+ via `POST /_p/mineru/parse`). These are readings from that setup, not universal constants —
23
+ VRAM/RSS scale with input size and model backend:
24
+
25
+ - image on disk 9.64GB; model cache volume `stream_mineru-cache` 1.2GB
26
+ - idle RSS 335MiB (models load lazily — nothing resident until a request arrives)
27
+ - one-image parse: peak RSS 3.3GB (under a third of the `mem: 10G` cap)
28
+ - VRAM delta ~1.6GB (whole card 7066 → 8652 MiB)
29
+
30
+ With standby it costs 0 while unused.
31
+
32
+ - `env.MINERU_DEVICE` —— MinerU device autodetect; pin a backend/model here if needed.
33
+ - `volumes` —— persist the model + layout/table/formula weights so they don't re-download on restart.
34
+ - `mem: 10G` —— cap host RAM (model lives in VRAM; this bounds the python/pdf/image side). swap
35
+ disabled (memswap = mem) so a runaway OOM-kills fast rather than host swap-thrash.
36
+ - `standby.startTimeoutSeconds: 120` —— MinerU's model load (layout/table/formula weights) is heavier
37
+ than the other three backends' cold start; give it more runway before the standby-manager gives up
38
+ waiting for health.
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "@streamapp/mineru",
3
+ "version": "1.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "stream": {
7
+ "id": "mineru",
8
+ "name": "MinerU",
9
+ "tagline": "文档 / 图片 → Markdown",
10
+ "description": "把 PDF、扫描件、图片转成 Markdown:版面还原、表格转成 HTML、公式转成 LaTeX。取正文那条链路上负责「这份东西不是文字,是一张纸」的那一档。本机容器免费但慢(吃 GPU 更快),配了 mineru_url 就走云端快车道——两边跑的是同一个模型,云端卖的是速度不是效果。",
11
+ "backend": {
12
+ "image": "ghcr.io/jaggerh/mineru-server:latest",
13
+ "service": "mineru",
14
+ "port": 80,
15
+ "health": "/health",
16
+ "gpu": true,
17
+ "env": {
18
+ "MINERU_DEVICE": "auto"
19
+ },
20
+ "volumes": ["mineru-cache:/root/.cache"],
21
+ "mem": "10G",
22
+ "standby": {
23
+ "idleMinutes": 10,
24
+ "startTimeoutSeconds": 120
25
+ }
26
+ }
27
+ }
28
+ }
@@ -0,0 +1,14 @@
1
+ # PanSou
2
+
3
+ Search-only netdisk resource search (no credentials, no login).
4
+
5
+ ## backend
6
+
7
+ - `env.CHANNELS` —— Default Telegram channel pool: the TG channels pansou searches when no
8
+ per-request `channels` param is given. Curated from the upstream reference config. Add/remove
9
+ channels here; they take effect on next `docker compose up -d` (compose regenerated from this
10
+ descriptor).
11
+ - `env.ENABLED_PLUGINS` —— PanSou built-in indexer plugins to enable (non-TG web scrapers). Each is
12
+ a Go plugin in the pansou repo (`plugin/<name>/`).
13
+ - `standby.idleMinutes: 30` —— 冷查询型:调用零散、单次搜索几秒内完事,没有播放/挂载那种"随时要来"
14
+ 的时效压力——30 分钟闲置够覆盖同一会话内的连续几次搜索,又不会长期占着容器空转。
@@ -0,0 +1,11 @@
1
+ import type { ActivateFn } from '../../src/packages/activate.ts'
2
+ import { PansouAdapter } from './adapter.ts'
3
+ import { pansouNormalizer } from '../../src/content/pansou.ts'
4
+
5
+ /** 这个包贡献的东西:一个 adapter + 一个 normalizer。
6
+ * `config.url` 是宿主解析好的显式覆盖(未配则 undefined,adapter 自己惰性回落到
7
+ * PANSOU_URL env / plugin target)。 */
8
+ export const activate: ActivateFn = (ctx) => ({
9
+ adapters: { pansou: new PansouAdapter(ctx.config.url as string | undefined) },
10
+ normalizers: { pansou: pansouNormalizer },
11
+ })
@@ -0,0 +1,94 @@
1
+ import type { Adapter } from '../../src/adapters/types.ts'
2
+ import type { SourceManifest } from '../../src/manifest/types.ts'
3
+ import { pluginTarget } from '../../src/plugins/plugin-target.ts'
4
+ import { withAwake } from '../../src/plugins/standby/hook.ts'
5
+
6
+ /** server-side fetch base (NOT the client-facing gateway path): explicit ctor arg → PANSOU_URL
7
+ * env (dev override) → the bootstrap-injected plugin target (compose mode: container DNS;
8
+ * none: unset → ''). */
9
+ export function resolvePansouUrl(explicit?: string): string {
10
+ return explicit ?? process.env.PANSOU_URL ?? pluginTarget('pansou') ?? ''
11
+ }
12
+
13
+ /** netdisk types we render well — the default `cloud_types` filter. */
14
+ const DEFAULT_CLOUD_TYPES = ['baidu', 'quark', 'aliyun']
15
+ /** bias toward complete resources: drop obvious non-full junk (overridable). */
16
+ const DEFAULT_FILTER = { exclude: ['预告', '花絮', '试看', '样片', '片段', '主题曲'] }
17
+
18
+ /** a param that may arrive as string[] (bootstrap) or a comma string (HTTP query). */
19
+ function toArray(v: unknown): string[] | undefined {
20
+ if (Array.isArray(v)) return v.map(String).filter(Boolean)
21
+ if (typeof v === 'string' && v.trim()) return v.split(',').map((s) => s.trim()).filter(Boolean)
22
+ return undefined
23
+ }
24
+ /** a param that may arrive as an object (bootstrap) or a JSON string (HTTP query). */
25
+ function toObject(v: unknown): Record<string, unknown> | undefined {
26
+ if (v && typeof v === 'object') return v as Record<string, unknown>
27
+ if (typeof v === 'string' && v.trim()) {
28
+ try {
29
+ return JSON.parse(v) as Record<string, unknown>
30
+ } catch {
31
+ return undefined
32
+ }
33
+ }
34
+ return undefined
35
+ }
36
+
37
+ /**
38
+ * PanSou adapter — a thin HTTP client over a (Stream-managed or external) PanSou
39
+ * netdisk search service. Search-only, no credentials. Returns PanSou's `results`
40
+ * (one SearchResult per source message: title + content + images + a links[] of
41
+ * netdisk shares); the presenter maps each to a normalized link Content.
42
+ *
43
+ * PanSou's search domain is per-request, not baked into the container: `channels`,
44
+ * `cloud_types`, `filter` (include/exclude), `ext` (plugin params, e.g. title_en)
45
+ * and `src` are all forwarded when the caller provides them — so the AI/MCP can
46
+ * customize a search at call time. Defaults bias toward complete resources.
47
+ */
48
+ export class PansouAdapter implements Adapter {
49
+ readonly id = 'pansou'
50
+
51
+ constructor(private readonly explicitUrl?: string) {}
52
+
53
+ /** 惰性:host 档下 origin 是容器醒着时才存在的(standby Cell 缓存),构造期快照必得空串。
54
+ * 每次求值现解析;fetch 都在 withAwake 回调里,求值时容器已醒。compose 档恒定,无行为差。 */
55
+ private get baseUrl(): string {
56
+ return resolvePansouUrl(this.explicitUrl).replace(/\/$/, '')
57
+ }
58
+
59
+ async init(_env: Record<string, string>): Promise<void> {} // no credentials
60
+
61
+ async fetch(params: Record<string, unknown>, _manifest: SourceManifest): Promise<unknown[]> {
62
+ const kw = String(params.keyword ?? '')
63
+ if (!kw) throw new Error('[pansou] search needs `keyword`')
64
+ // res=results → the per-message SearchResult[] (title/content/images + links[]).
65
+ // Caller-supplied knobs win; otherwise default to render-able netdisks + a
66
+ // junk-excluding filter (lean toward 全集/合集 without nuking unlabeled packs).
67
+ const body: Record<string, unknown> = {
68
+ kw,
69
+ res: 'results',
70
+ cloud_types: toArray(params.cloud_types) ?? DEFAULT_CLOUD_TYPES,
71
+ filter: toObject(params.filter) ?? DEFAULT_FILTER,
72
+ }
73
+ const channels = toArray(params.channels)
74
+ if (channels) body.channels = channels
75
+ const ext = toObject(params.ext)
76
+ if (ext) body.ext = ext
77
+ if (params.src) body.src = String(params.src)
78
+
79
+ const r = await withAwake('pansou', () =>
80
+ fetch(`${this.baseUrl}/api/search`, {
81
+ method: 'POST',
82
+ headers: { 'content-type': 'application/json' },
83
+ body: JSON.stringify(body),
84
+ })
85
+ )
86
+ if (!r.ok) throw new Error(`[pansou] HTTP ${r.status}`)
87
+ // pansou nests the payload under `data`: { code, message, data: { total, results[] } }.
88
+ // (older builds returned a flat `results` — accept both so a version bump can't silently
89
+ // empty the source.)
90
+ const body2 = (await r.json()) as { results?: unknown[]; data?: { results?: unknown[] } }
91
+ const results = body2.data?.results ?? body2.results
92
+ return Array.isArray(results) ? results : []
93
+ }
94
+ }
@@ -0,0 +1,50 @@
1
+ - id: pansou-search
2
+ adapter: pansou
3
+ normalizer: pansou
4
+ title: 网盘搜索
5
+ description: 输入关键词搜索全网网盘(百度/阿里/夸克/天翼/迅雷等)的共享资源链接。支持网盘类型过滤与排除规则。
6
+ topics: [pansou, 网盘, 资源, 搜索, 百度网盘, 阿里云盘, 夸克网盘, 天翼云盘, netdisk, search, 影视, 剧集]
7
+ categories: [search, resource]
8
+ example_queries: [搜网盘资源, 海绵宝宝 网盘, 某电影网盘资源, 某剧全集 夸克]
9
+ capabilities: [search]
10
+ auth: { type: none }
11
+ cadence_hint_seconds: 3600
12
+ params_schema:
13
+ keyword:
14
+ type: string
15
+ required: true
16
+ description: 搜索关键词(必填),如电影名、剧名、资源名称。
17
+ cloud_types:
18
+ type: string
19
+ required: false
20
+ description: >-
21
+ 限定网盘类型,逗号分隔。可选值: baidu, aliyun, quark, tianyi, uc, 115, 123,
22
+ mobile, xunlei, pikpak, magnet, ed2k。默认 baidu,quark,aliyun。
23
+ filter:
24
+ type: string
25
+ required: false
26
+ description: >-
27
+ JSON 格式过滤规则。exclude 排除含指定关键词的结果,如 {"exclude":["预告","花絮"]}。
28
+ 默认排除预告/花絮/试看/样片/片段/主题曲。
29
+ channels:
30
+ type: string
31
+ required: false
32
+ description: >-
33
+ 指定搜索的 Telegram 频道列表,逗号分隔(如 yunpanx,Aliyun_4K_Movies)。
34
+ 不传则使用容器 CHANNELS 环境变量配置的默认频道池。
35
+ ext:
36
+ type: string
37
+ required: false
38
+ description: JSON 格式扩展参数,传给 PanSou 插件系统(如 title_en 供插件使用)。
39
+ src:
40
+ type: string
41
+ required: false
42
+ description: >-
43
+ 限定搜索来源,三选一:all(默认,两者都搜)、tg(只搜 Telegram 频道,走
44
+ t.me 公开网页预览)、plugin(只搜 ENABLED_PLUGINS 里的网站爬虫)。
45
+ fan_out:
46
+ dimension: channels
47
+ strategy: batch
48
+ batch_key: [cloud_types, filter, src]
49
+ provides: [search-download]
50
+ key_param: keyword
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "@streamapp/pansou",
3
+ "version": "1.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "stream": {
7
+ "id": "pansou",
8
+ "name": "PanSou",
9
+ "tagline": "网盘资源聚合搜索",
10
+ "description": "Telegram 频道 + 异步插件的网盘资源聚合搜索 API。支持百度网盘、阿里云盘、夸克网盘、 天翼云盘、UC 网盘、115 网盘、123 网盘、移动云盘、迅雷云盘、PikPak、磁力、ed2k 等 多种网盘类型,按类型分类、智能排序、二级缓存。搜索无需登录态。",
11
+ "repository": "https://github.com/fish2018/pansou",
12
+ "backend": {
13
+ "image": "ghcr.io/jaggerh/pansou:latest",
14
+ "port": 8888,
15
+ "health": "/api/health",
16
+ "env": {
17
+ "CHANNELS": "Aliyun_4K_Movies,BaiduCloudDisk,Oscar_4Kmovies,Quark_Movies, alyp_1,bdbdndn11,bsbdbfjfjff,cloudtianyi,dianyingshare, gotopan,hdhhd21,jdjdn1111,leoziyuan,oneonefivewpfx, peccxinpd,pikpakpan,qixingzhenren,sbsbsnsqq,shareAliyun, taoxgzy,tianyifc,tianyirigeng,tgsearchers3,txtyzy, tyypzhpd,tyysypzypd,ucquark,ucwpzy,wp123zy,xx123pan, ydypzyfx,yggpan,yingshifenxiang123,yp123pan,yunpan139, yunpan189,yunpanuc,yunpanx,yunpanxunlei,zaihuayun,zyfb123",
18
+ "ENABLED_PLUGINS": "labi,zhizhen,shandian,duoduo,muou"
19
+ },
20
+ "standby": {
21
+ "idleMinutes": 30
22
+ }
23
+ },
24
+ "normalizer": "pansou",
25
+ "code": { "entry": "./activate.ts", "adapters": ["pansou"], "normalizers": ["pansou"] }
26
+ }
27
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "@streamapp/qq",
3
+ "version": "1.0.0",
4
+ "author": "stream",
5
+ "keywords": [
6
+ "stream-recipe"
7
+ ],
8
+ "stream": {
9
+ "type": "recipe",
10
+ "name": "QQ",
11
+ "tagline": "给指定联系人发一条 QQ 消息",
12
+ "facility": "qq",
13
+ "schemaVersion": 1
14
+ }
15
+ }
@@ -0,0 +1,104 @@
1
+ {
2
+ "version": 1,
3
+ "kind": "desktop",
4
+ "sourceId": "qq-send",
5
+ "app": {
6
+ "process": "QQ.exe",
7
+ "title": "QQ"
8
+ },
9
+ "_why_title": "聊天窗口可以被拖出来独立成窗,那时 QQ.exe 会有多个窗口,不限定就会被拒绝(枚举里列候选让人选)。主窗口的标题恒为 `QQ`,包含匹配够用。",
10
+ "_a11y_activation": "QQ 是 Electron,**a11y 树是懒建的**:窗口不在前台时整棵树是空的(find 一律返回 0 条),前台之后才逐步长出来。回执里那面 `unbuilt` 旗只说明「这次读不可采信」,不会替你把窗口提上来——focus 仍然是这条 recipe 的第一步。所以第一步的 focus 不只是为了打字——它是这条 recipe 能看见任何东西的前提。实测 2026-08-27:后台时 role=Edit / role=Button / nameContains=搜索 全部空手而归,同一个 agent 同一时刻读 explorer.exe 的任务栏拿到 21 个控件(证明 agent 没问题,是 QQ 不给)。",
11
+ "steps": [
12
+ {
13
+ "_why": "复位。上一轮跑完会把界面留在「搜索模式 + 上个联系人的搜索词」里,而 `type` 是往输入框里**追加**、不是替换——不复位,第二轮的搜索词会变成「上个联系人本次联系人」,然后每一步都'成功'地跑在错的界面上。点左栏「消息」会同时清空搜索框并回到会话列表(实测)。首次运行时它本来就在这个状态,点一下无副作用,所以不标 optional。",
14
+ "kind": "invoke",
15
+ "query": { "role": "Button", "name": "消息" }
16
+ },
17
+ {
18
+ "_why": "抢前台。下面两处 `type` 都没有 `query`(QQ 的输入框不认 ValuePattern,见 _why_type_steals_screen),走的是键盘、打给'此刻的焦点窗口'——不抢屏就会打到用户正在用的那个程序里去。",
19
+ "kind": "focus"
20
+ },
21
+ { "kind": "wait", "ms": 400 },
22
+ {
23
+ "kind": "invoke",
24
+ "query": { "role": "ComboBox", "name": "搜索" }
25
+ },
26
+ {
27
+ "_why_no_query": "给了 `query` 也没用:实测这个搜索框和下面的消息输入框都拒绝 setValue,runner 会退回键盘。写上反而假装它能后台跑。",
28
+ "kind": "type",
29
+ "text": "{contact}"
30
+ },
31
+ {
32
+ "_why": "等搜索结果。这里没有'就是它出现了'的新控件可指——结果列表本来就在,只是内容会换,所以只能钝等。",
33
+ "kind": "wait",
34
+ "ms": 1200
35
+ },
36
+ {
37
+ "_why_fragile": "搜索结果里**至少两行**的名字都含 `{contact}`:真正的联系人行(名字形如 `<联系人> <签名或描述>`)和兜底的「进入全网搜索<联系人> 查找用户、群聊等」。这里靠的是**真联系人行排在前面**(实测树序如此,invoke 取第一个匹配)。这条是本 recipe 最脆的一步:QQ 哪天调换了这两行的顺序,消息就会发进一个全网搜索页而不是会话——而每一步依然'成功'。要更稳就得能表达「排除 nameContains=进入全网搜索」,desktop recipe 的 A11yQuery 今天没有否定条件。",
38
+ "kind": "invoke",
39
+ "query": { "role": "ListItem", "nameContains": "{contact}" }
40
+ },
41
+ {
42
+ "_why": "等会话面板渲染出来。消息输入框是这一刻才出现的。",
43
+ "kind": "wait",
44
+ "ms": 1500
45
+ },
46
+ {
47
+ "_why_name_is_contact": "消息输入框的 a11y name 就是会话名(实测「我的手机」的输入框 name = `我的手机`),所以这一步天然带着'我确实在跟他说话'的校验——会话没切过去的话这个 query 就找不到,整条 recipe 当场停,而不是把消息发给上一个会话。**只在「我的手机」这一个会话上验过**,其他会话是否同样以会话名命名输入框,未验。",
48
+ "kind": "invoke",
49
+ "query": { "role": "Edit", "name": "{contact}" }
50
+ },
51
+ {
52
+ "kind": "type",
53
+ "text": "{message}"
54
+ },
55
+ {
56
+ "_why_button_not_enter": "点「发送」按钮,不按回车:回车键在 QQ 里可以被用户设成换行,那样消息永远发不出去而每一步都'成功'。按钮有句柄、走 invoke,是确定的。",
57
+ "kind": "invoke",
58
+ "query": { "role": "Button", "name": "发送" }
59
+ },
60
+ {
61
+ "_why": "等消息进入会话流。observer 立刻读会读到发送前的状态。",
62
+ "kind": "wait",
63
+ "ms": 1200
64
+ }
65
+ ],
66
+ "observer": {
67
+ "itemQuery": { "role": "Text", "nameContains": "{message}" },
68
+ "fields": {
69
+ "content": { "read": "name" }
70
+ },
71
+ "dedupeBy": "content"
72
+ },
73
+ "_why_observer_reads_back_what_it_sent": "这条 recipe 是**动作**不是采集,本来无物可读。但 desktop runner 对'一条都没读到'的处置是 `blocked('drift')`(`src/replay/desktop-runner.ts:183`),而 browser recipe 那边表达动作用的 `allowEmpty` 在 desktop schema 里**不存在**。与其伪造一个恒真的 observer 去骗过这道闸,不如让它读回**刚发出去的那条消息**:发成功 → 读到 1 条 → 通过;没发出去(会话错了、按钮没点着、输入框没收下文字)→ 读不到 → 当场 blocked。于是那道闸从障碍变成了**送达确认**。代价是它会产出一条 item;这个源 `pick_in: []`、`discoverable: false`,不进任何选择面。",
74
+ "read": {
75
+ "dedupeBy": "content",
76
+ "targetCount": 1
77
+ },
78
+ "meta": {
79
+ "description": "给指定 QQ 联系人发一条消息:复位回消息列表 → 搜联系人 → 点进会话 → 写进输入框 → 点发送 → 读回刚发出去的那条当送达确认。桌面 recipe(a11y 词汇,Windows UIA 后端)。",
80
+ "action": true,
81
+ "effects": ["write"],
82
+ "_why_effects": "会真的发消息、写用户账户——安装 preview(src/replay/recipe-install.ts)靠这一格亮副作用牌,不声明就是让装它的人看不见这条会写东西。",
83
+ "_why_action": "这是动作 recipe(发消息,副作用不可撤回),不是采集——必须显式 opt-in 才能被 MCP 的 run_action_recipe 工具跑,见 src/replay/recipe.ts 的 RecipeMeta.action 头注。",
84
+ "_why_type_steals_screen": "**这条 recipe 会抢屏,不能当定时任务跑。** Telegram 那条能后台静默跑,是因为它的输入框认 ValuePattern(setValue 直接写进元素,不经键盘);QQ 的搜索框和消息输入框实测都不认,runner 只能退回键盘,而键盘打给'此刻的焦点窗口'——所以必须先 focus。半夜跑它 = 半夜把用户的屏幕抢过去打字。",
85
+ "discoverable": false,
86
+ "_why_pick_in": "两个选择面都不出现——它是动作,不是内容来源",
87
+ "pick_in": [],
88
+ "capabilities": [],
89
+ "member_timeout_ms": 25000,
90
+ "_why_member_timeout": "一趟要点 5 次、等 3 段(0.4+1.2+1.5+1.2s),加上 Electron 懒建 a11y 树的首次开销,实测约 8-10s。按流式路默认的 15s 会贴着边,照 telegram 那条的先例申报 25s。",
91
+ "params_schema": {
92
+ "contact": {
93
+ "type": "string",
94
+ "required": true,
95
+ "description": "发给谁。填搜索框里能命中他的名字(包含匹配即可),例如 `我的手机`。**别填得太短**:短词会同时命中一堆会话,而这一步取的是第一个匹配。"
96
+ },
97
+ "message": {
98
+ "type": "string",
99
+ "required": true,
100
+ "description": "消息正文。它同时被当作送达确认的判据(发完从会话里读回这段文字),所以别填一句会话里本来就常见的话——那样即使没发出去也能'读到'。"
101
+ }
102
+ }
103
+ }
104
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "@streamapp/quark",
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": "quark",
13
+ "schemaVersion": 1,
14
+ "cookieDomain": "quark.cn"
15
+ }
16
+ }
@@ -0,0 +1,40 @@
1
+ {
2
+ "version": 3,
3
+ "kind": "http",
4
+ "sourceId": "quark-share",
5
+ "acceptNonOk": true,
6
+ "output": "object",
7
+ "request": {
8
+ "url": "https://drive-h.quark.cn/1/clouddrive/share/sharepage/detail?pr=ucpro&fr=pc&uc_param_str=&ver=2&pwd_id={pwd_id}&pdir_fid=0&_page=1&_size=50",
9
+ "method": "GET",
10
+ "headers": { "referer": "https://pan.quark.cn/" }
11
+ },
12
+ "compute": {
13
+ "capabilities": [],
14
+ "prefetch": [
15
+ {
16
+ "as": "tok",
17
+ "request": {
18
+ "url": "https://drive-h.quark.cn/1/clouddrive/share/sharepage/token?pr=ucpro&fr=pc&uc_param_str=",
19
+ "method": "POST",
20
+ "headers": { "content-type": "application/json", "referer": "https://pan.quark.cn/" },
21
+ "body": "{\"pwd_id\":\"{pwd_id}\",\"passcode\":\"{passcode}\",\"support_visit_limit_private_share\":true}"
22
+ }
23
+ }
24
+ ],
25
+ "sign": "(() => { const t = input.pre.tok; const st = (t && t.data && t.data.stoken) || ''; return { url: 'https://drive-h.quark.cn/1/clouddrive/share/sharepage/detail?pr=ucpro&fr=pc&uc_param_str=&ver=2&pwd_id=' + encodeURIComponent(input.params.pwd_id) + '&stoken=' + encodeURIComponent(st) + '&pdir_fid=0&force=0&_page=1&_size=50&_fetch_total=1&_sort=file_type:asc,file_name:asc' }; })()",
26
+ "decode": "(() => { const t = input.pre.tok; if (!t || typeof t.code !== 'number') throw new Error('quark token: unreadable body'); if (t.code !== 0) { const s = t.status; if (typeof s === 'number' && s >= 400 && s < 500 && s !== 429) return { validity: 'not-usable', files: [], reason: String(t.message || ('code ' + t.code)) }; throw new Error('quark token status ' + s + ' code ' + t.code + ': ' + (t.message || '?')); } if (!t.data || !t.data.stoken) throw new Error('quark token: code 0 without stoken'); const b = input.body; const list = (b && b.data && b.data.list) || []; const files = list.map((f) => ({ name: String(f.file_name || ''), is_dir: !!f.dir, size: Number(f.size || 0) })); return files.length ? { validity: 'alive', files: files } : { validity: 'not-usable', files: [], reason: '分享里没有文件' }; })()"
27
+ },
28
+ "assert": [{ "path": "validity", "desc": "quark 探针判决必须带 validity" }],
29
+ "meta": {
30
+ "description": "夸克分享验活 + 列文件(probe recipe,只读,判决对象输出;转存见 quark-save)",
31
+ "discoverable": false,
32
+ "_why_pick_in": "只在 Provider 成员面挑得到,不在频道订阅面——netdisk-verify-quark 行的成员",
33
+ "pick_in": ["provider"],
34
+ "key_param": "pwd_id",
35
+ "params_schema": {
36
+ "pwd_id": { "type": "string", "required": true, "description": "夸克分享 id(pan.quark.cn/s/<pwd_id>)" },
37
+ "passcode": { "type": "string", "required": false, "default": "", "description": "分享提取码(如有)" }
38
+ }
39
+ }
40
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "@streamapp/reddit",
3
+ "version": "1.0.0",
4
+ "author": "stream",
5
+ "keywords": [
6
+ "stream-recipe"
7
+ ],
8
+ "stream": {
9
+ "type": "recipe",
10
+ "name": "Reddit",
11
+ "tagline": "subreddit 热门帖流",
12
+ "facility": "reddit",
13
+ "schemaVersion": 1
14
+ }
15
+ }