@jackwener/opencli 0.9.6 → 1.0.0

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 (307) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.yml +83 -0
  2. package/.github/ISSUE_TEMPLATE/config.yml +8 -0
  3. package/.github/ISSUE_TEMPLATE/feature_request.yml +42 -0
  4. package/.github/ISSUE_TEMPLATE/new_site_adapter.yml +57 -0
  5. package/.github/dependabot.yml +27 -0
  6. package/.github/pull_request_template.md +24 -0
  7. package/.github/workflows/ci.yml +14 -8
  8. package/.github/workflows/e2e-headed.yml +6 -2
  9. package/.github/workflows/pkg-pr-new.yml +2 -2
  10. package/.github/workflows/release-please.yml +25 -0
  11. package/.github/workflows/release.yml +2 -2
  12. package/.github/workflows/security.yml +36 -0
  13. package/CDP.md +1 -1
  14. package/CDP.zh-CN.md +1 -1
  15. package/CLI-ELECTRON.md +89 -36
  16. package/CLI-EXPLORER.md +4 -4
  17. package/CONTRIBUTING.md +167 -0
  18. package/README.md +113 -89
  19. package/README.zh-CN.md +114 -91
  20. package/SKILL.md +10 -8
  21. package/TESTING.md +7 -7
  22. package/dist/browser/daemon-client.d.ts +37 -0
  23. package/dist/browser/daemon-client.js +82 -0
  24. package/dist/browser/discover.d.ts +11 -34
  25. package/dist/browser/discover.js +15 -190
  26. package/dist/browser/errors.d.ts +6 -20
  27. package/dist/browser/errors.js +24 -63
  28. package/dist/browser/index.d.ts +2 -11
  29. package/dist/browser/index.js +5 -11
  30. package/dist/browser/mcp.d.ts +9 -18
  31. package/dist/browser/mcp.js +70 -284
  32. package/dist/browser/page.d.ts +28 -6
  33. package/dist/browser/page.js +210 -85
  34. package/dist/browser.test.js +4 -202
  35. package/dist/build-manifest.d.ts +26 -0
  36. package/dist/build-manifest.js +132 -60
  37. package/dist/build-manifest.test.d.ts +1 -0
  38. package/dist/build-manifest.test.js +26 -0
  39. package/dist/cli-manifest.json +1582 -29
  40. package/dist/clis/bilibili/download.d.ts +10 -0
  41. package/dist/clis/bilibili/download.js +135 -0
  42. package/dist/clis/chatwise/ask.d.ts +1 -0
  43. package/dist/clis/chatwise/ask.js +76 -0
  44. package/dist/clis/chatwise/export.d.ts +1 -0
  45. package/dist/clis/chatwise/export.js +46 -0
  46. package/dist/clis/chatwise/history.d.ts +1 -0
  47. package/dist/clis/chatwise/history.js +43 -0
  48. package/dist/clis/chatwise/model.d.ts +1 -0
  49. package/dist/clis/chatwise/model.js +81 -0
  50. package/dist/clis/chatwise/new.d.ts +1 -0
  51. package/dist/clis/chatwise/new.js +18 -0
  52. package/dist/clis/chatwise/read.d.ts +1 -0
  53. package/dist/clis/chatwise/read.js +39 -0
  54. package/dist/clis/chatwise/screenshot.d.ts +1 -0
  55. package/dist/clis/chatwise/screenshot.js +27 -0
  56. package/dist/clis/chatwise/send.d.ts +1 -0
  57. package/dist/clis/chatwise/send.js +45 -0
  58. package/dist/clis/chatwise/status.d.ts +1 -0
  59. package/dist/clis/chatwise/status.js +22 -0
  60. package/dist/clis/discord-app/channels.d.ts +1 -0
  61. package/dist/clis/discord-app/channels.js +45 -0
  62. package/dist/clis/discord-app/members.d.ts +1 -0
  63. package/dist/clis/discord-app/members.js +38 -0
  64. package/dist/clis/discord-app/read.d.ts +1 -0
  65. package/dist/clis/discord-app/read.js +45 -0
  66. package/dist/clis/discord-app/search.d.ts +1 -0
  67. package/dist/clis/discord-app/search.js +56 -0
  68. package/dist/clis/discord-app/send.d.ts +1 -0
  69. package/dist/clis/discord-app/send.js +27 -0
  70. package/dist/clis/discord-app/servers.d.ts +1 -0
  71. package/dist/clis/discord-app/servers.js +36 -0
  72. package/dist/clis/discord-app/status.d.ts +1 -0
  73. package/dist/clis/discord-app/status.js +16 -0
  74. package/dist/clis/feishu/new.d.ts +1 -0
  75. package/dist/clis/feishu/new.js +27 -0
  76. package/dist/clis/feishu/read.d.ts +1 -0
  77. package/dist/clis/feishu/read.js +40 -0
  78. package/dist/clis/feishu/search.d.ts +1 -0
  79. package/dist/clis/feishu/search.js +30 -0
  80. package/dist/clis/feishu/send.d.ts +1 -0
  81. package/dist/clis/feishu/send.js +39 -0
  82. package/dist/clis/feishu/status.d.ts +1 -0
  83. package/dist/clis/feishu/status.js +28 -0
  84. package/dist/clis/grok/ask.d.ts +1 -0
  85. package/dist/clis/grok/ask.js +82 -0
  86. package/dist/clis/grok/debug.d.ts +1 -0
  87. package/dist/clis/grok/debug.js +45 -0
  88. package/dist/clis/jimeng/generate.yaml +84 -0
  89. package/dist/clis/jimeng/history.yaml +47 -0
  90. package/dist/clis/linux-do/categories.yaml +41 -0
  91. package/dist/clis/linux-do/category.yaml +49 -0
  92. package/dist/clis/linux-do/hot.yaml +50 -0
  93. package/dist/clis/linux-do/latest.yaml +40 -0
  94. package/dist/clis/linux-do/search.yaml +45 -0
  95. package/dist/clis/linux-do/topic.yaml +38 -0
  96. package/dist/clis/neteasemusic/like.d.ts +1 -0
  97. package/dist/clis/neteasemusic/like.js +25 -0
  98. package/dist/clis/neteasemusic/lyrics.d.ts +1 -0
  99. package/dist/clis/neteasemusic/lyrics.js +47 -0
  100. package/dist/clis/neteasemusic/next.d.ts +1 -0
  101. package/dist/clis/neteasemusic/next.js +26 -0
  102. package/dist/clis/neteasemusic/play.d.ts +1 -0
  103. package/dist/clis/neteasemusic/play.js +26 -0
  104. package/dist/clis/neteasemusic/playing.d.ts +1 -0
  105. package/dist/clis/neteasemusic/playing.js +59 -0
  106. package/dist/clis/neteasemusic/playlist.d.ts +1 -0
  107. package/dist/clis/neteasemusic/playlist.js +46 -0
  108. package/dist/clis/neteasemusic/prev.d.ts +1 -0
  109. package/dist/clis/neteasemusic/prev.js +25 -0
  110. package/dist/clis/neteasemusic/search.d.ts +1 -0
  111. package/dist/clis/neteasemusic/search.js +52 -0
  112. package/dist/clis/neteasemusic/status.d.ts +1 -0
  113. package/dist/clis/neteasemusic/status.js +16 -0
  114. package/dist/clis/neteasemusic/volume.d.ts +1 -0
  115. package/dist/clis/neteasemusic/volume.js +54 -0
  116. package/dist/clis/notion/export.d.ts +1 -0
  117. package/dist/clis/notion/export.js +31 -0
  118. package/dist/clis/notion/favorites.d.ts +1 -0
  119. package/dist/clis/notion/favorites.js +84 -0
  120. package/dist/clis/notion/new.d.ts +1 -0
  121. package/dist/clis/notion/new.js +34 -0
  122. package/dist/clis/notion/read.d.ts +1 -0
  123. package/dist/clis/notion/read.js +30 -0
  124. package/dist/clis/notion/search.d.ts +1 -0
  125. package/dist/clis/notion/search.js +46 -0
  126. package/dist/clis/notion/sidebar.d.ts +1 -0
  127. package/dist/clis/notion/sidebar.js +41 -0
  128. package/dist/clis/notion/status.d.ts +1 -0
  129. package/dist/clis/notion/status.js +16 -0
  130. package/dist/clis/notion/write.d.ts +1 -0
  131. package/dist/clis/notion/write.js +40 -0
  132. package/dist/clis/twitter/download.d.ts +8 -0
  133. package/dist/clis/twitter/download.js +204 -0
  134. package/dist/clis/wechat/chats.d.ts +1 -0
  135. package/dist/clis/wechat/chats.js +28 -0
  136. package/dist/clis/wechat/contacts.d.ts +1 -0
  137. package/dist/clis/wechat/contacts.js +28 -0
  138. package/dist/clis/wechat/read.d.ts +1 -0
  139. package/dist/clis/wechat/read.js +58 -0
  140. package/dist/clis/wechat/search.d.ts +1 -0
  141. package/dist/clis/wechat/search.js +31 -0
  142. package/dist/clis/wechat/send.d.ts +1 -0
  143. package/dist/clis/wechat/send.js +42 -0
  144. package/dist/clis/wechat/status.d.ts +1 -0
  145. package/dist/clis/wechat/status.js +29 -0
  146. package/dist/clis/xiaohongshu/creator-note-detail.d.ts +10 -0
  147. package/dist/clis/xiaohongshu/creator-note-detail.js +88 -0
  148. package/dist/clis/xiaohongshu/creator-notes.d.ts +11 -0
  149. package/dist/clis/xiaohongshu/creator-notes.js +109 -0
  150. package/dist/clis/xiaohongshu/creator-profile.d.ts +10 -0
  151. package/dist/clis/xiaohongshu/creator-profile.js +54 -0
  152. package/dist/clis/xiaohongshu/creator-stats.d.ts +10 -0
  153. package/dist/clis/xiaohongshu/creator-stats.js +74 -0
  154. package/dist/clis/xiaohongshu/download.d.ts +7 -0
  155. package/dist/clis/xiaohongshu/download.js +155 -0
  156. package/dist/clis/xiaohongshu/search.js +1 -1
  157. package/dist/clis/xiaohongshu/user-helpers.d.ts +15 -0
  158. package/dist/clis/xiaohongshu/user-helpers.js +67 -0
  159. package/dist/clis/xiaohongshu/user-helpers.test.d.ts +1 -0
  160. package/dist/clis/xiaohongshu/user-helpers.test.js +81 -0
  161. package/dist/clis/xiaohongshu/user.js +46 -29
  162. package/dist/clis/zhihu/download.d.ts +11 -0
  163. package/dist/clis/zhihu/download.js +186 -0
  164. package/dist/clis/zhihu/download.test.d.ts +1 -0
  165. package/dist/clis/zhihu/download.test.js +10 -0
  166. package/dist/daemon.d.ts +13 -0
  167. package/dist/daemon.js +187 -0
  168. package/dist/doctor.d.ts +27 -61
  169. package/dist/doctor.js +70 -601
  170. package/dist/doctor.test.js +30 -170
  171. package/dist/download/index.d.ts +79 -0
  172. package/dist/download/index.js +325 -0
  173. package/dist/download/progress.d.ts +36 -0
  174. package/dist/download/progress.js +111 -0
  175. package/dist/engine.test.js +15 -0
  176. package/dist/main.js +22 -28
  177. package/dist/pipeline/executor.test.js +1 -0
  178. package/dist/pipeline/registry.js +2 -0
  179. package/dist/pipeline/steps/browser.js +2 -2
  180. package/dist/pipeline/steps/download.d.ts +34 -0
  181. package/dist/pipeline/steps/download.js +251 -0
  182. package/dist/pipeline/steps/intercept.js +1 -2
  183. package/dist/pipeline/template.js +28 -0
  184. package/dist/setup.d.ts +6 -0
  185. package/dist/setup.js +46 -160
  186. package/dist/types.d.ts +6 -0
  187. package/extension/icons/icon-128.png +0 -0
  188. package/extension/icons/icon-16.png +0 -0
  189. package/extension/icons/icon-32.png +0 -0
  190. package/extension/icons/icon-48.png +0 -0
  191. package/extension/manifest.json +31 -0
  192. package/extension/package.json +16 -0
  193. package/extension/src/background.ts +293 -0
  194. package/extension/src/cdp.ts +125 -0
  195. package/extension/src/protocol.ts +57 -0
  196. package/extension/store-assets/screenshot-1280x800.png +0 -0
  197. package/extension/tsconfig.json +15 -0
  198. package/extension/vite.config.ts +18 -0
  199. package/package.json +8 -7
  200. package/scripts/test-site.mjs +70 -0
  201. package/src/browser/daemon-client.ts +113 -0
  202. package/src/browser/discover.ts +18 -216
  203. package/src/browser/errors.ts +30 -100
  204. package/src/browser/index.ts +6 -12
  205. package/src/browser/mcp.ts +78 -278
  206. package/src/browser/page.ts +222 -88
  207. package/src/browser.test.ts +3 -210
  208. package/src/build-manifest.test.ts +28 -0
  209. package/src/build-manifest.ts +147 -57
  210. package/src/clis/bilibili/download.ts +161 -0
  211. package/src/clis/chatgpt/README.md +1 -1
  212. package/src/clis/chatgpt/README.zh-CN.md +1 -1
  213. package/src/clis/chatwise/README.md +38 -0
  214. package/src/clis/chatwise/README.zh-CN.md +38 -0
  215. package/src/clis/chatwise/ask.ts +87 -0
  216. package/src/clis/chatwise/export.ts +51 -0
  217. package/src/clis/chatwise/history.ts +47 -0
  218. package/src/clis/chatwise/model.ts +87 -0
  219. package/src/clis/chatwise/new.ts +21 -0
  220. package/src/clis/chatwise/read.ts +42 -0
  221. package/src/clis/chatwise/screenshot.ts +33 -0
  222. package/src/clis/chatwise/send.ts +50 -0
  223. package/src/clis/chatwise/status.ts +25 -0
  224. package/src/clis/discord-app/README.md +28 -0
  225. package/src/clis/discord-app/README.zh-CN.md +28 -0
  226. package/src/clis/discord-app/channels.ts +48 -0
  227. package/src/clis/discord-app/members.ts +41 -0
  228. package/src/clis/discord-app/read.ts +49 -0
  229. package/src/clis/discord-app/search.ts +64 -0
  230. package/src/clis/discord-app/send.ts +32 -0
  231. package/src/clis/discord-app/servers.ts +39 -0
  232. package/src/clis/discord-app/status.ts +18 -0
  233. package/src/clis/feishu/README.md +20 -0
  234. package/src/clis/feishu/README.zh-CN.md +20 -0
  235. package/src/clis/feishu/new.ts +32 -0
  236. package/src/clis/feishu/read.ts +48 -0
  237. package/src/clis/feishu/search.ts +35 -0
  238. package/src/clis/feishu/send.ts +46 -0
  239. package/src/clis/feishu/status.ts +34 -0
  240. package/src/clis/grok/ask.ts +90 -0
  241. package/src/clis/grok/debug.ts +49 -0
  242. package/src/clis/jimeng/generate.yaml +84 -0
  243. package/src/clis/jimeng/history.yaml +47 -0
  244. package/src/clis/linux-do/categories.yaml +41 -0
  245. package/src/clis/linux-do/category.yaml +49 -0
  246. package/src/clis/linux-do/hot.yaml +50 -0
  247. package/src/clis/linux-do/latest.yaml +40 -0
  248. package/src/clis/linux-do/search.yaml +45 -0
  249. package/src/clis/linux-do/topic.yaml +38 -0
  250. package/src/clis/neteasemusic/README.md +31 -0
  251. package/src/clis/neteasemusic/README.zh-CN.md +31 -0
  252. package/src/clis/neteasemusic/like.ts +28 -0
  253. package/src/clis/neteasemusic/lyrics.ts +53 -0
  254. package/src/clis/neteasemusic/next.ts +30 -0
  255. package/src/clis/neteasemusic/play.ts +30 -0
  256. package/src/clis/neteasemusic/playing.ts +62 -0
  257. package/src/clis/neteasemusic/playlist.ts +51 -0
  258. package/src/clis/neteasemusic/prev.ts +29 -0
  259. package/src/clis/neteasemusic/search.ts +58 -0
  260. package/src/clis/neteasemusic/status.ts +18 -0
  261. package/src/clis/neteasemusic/volume.ts +61 -0
  262. package/src/clis/notion/README.md +29 -0
  263. package/src/clis/notion/README.zh-CN.md +29 -0
  264. package/src/clis/notion/export.ts +36 -0
  265. package/src/clis/notion/favorites.ts +87 -0
  266. package/src/clis/notion/new.ts +39 -0
  267. package/src/clis/notion/read.ts +33 -0
  268. package/src/clis/notion/search.ts +54 -0
  269. package/src/clis/notion/sidebar.ts +44 -0
  270. package/src/clis/notion/status.ts +18 -0
  271. package/src/clis/notion/write.ts +45 -0
  272. package/src/clis/twitter/download.ts +227 -0
  273. package/src/clis/wechat/README.md +28 -0
  274. package/src/clis/wechat/README.zh-CN.md +28 -0
  275. package/src/clis/wechat/chats.ts +33 -0
  276. package/src/clis/wechat/contacts.ts +33 -0
  277. package/src/clis/wechat/read.ts +72 -0
  278. package/src/clis/wechat/search.ts +36 -0
  279. package/src/clis/wechat/send.ts +49 -0
  280. package/src/clis/wechat/status.ts +35 -0
  281. package/src/clis/xiaohongshu/creator-note-detail.ts +95 -0
  282. package/src/clis/xiaohongshu/creator-notes.ts +116 -0
  283. package/src/clis/xiaohongshu/creator-profile.ts +60 -0
  284. package/src/clis/xiaohongshu/creator-stats.ts +81 -0
  285. package/src/clis/xiaohongshu/download.ts +173 -0
  286. package/src/clis/xiaohongshu/search.ts +1 -1
  287. package/src/clis/xiaohongshu/user-helpers.test.ts +106 -0
  288. package/src/clis/xiaohongshu/user-helpers.ts +85 -0
  289. package/src/clis/xiaohongshu/user.ts +52 -32
  290. package/src/clis/zhihu/download.test.ts +12 -0
  291. package/src/clis/zhihu/download.ts +223 -0
  292. package/src/daemon.ts +217 -0
  293. package/src/doctor.test.ts +32 -193
  294. package/src/doctor.ts +74 -668
  295. package/src/download/index.ts +395 -0
  296. package/src/download/progress.ts +125 -0
  297. package/src/engine.test.ts +17 -0
  298. package/src/main.ts +18 -26
  299. package/src/pipeline/executor.test.ts +1 -0
  300. package/src/pipeline/registry.ts +2 -0
  301. package/src/pipeline/steps/browser.ts +2 -2
  302. package/src/pipeline/steps/download.ts +310 -0
  303. package/src/pipeline/steps/intercept.ts +1 -2
  304. package/src/pipeline/template.ts +26 -0
  305. package/src/setup.ts +47 -183
  306. package/src/types.ts +1 -0
  307. package/tests/e2e/browser-auth.test.ts +25 -0
@@ -0,0 +1,47 @@
1
+ site: jimeng
2
+ name: history
3
+ description: 即梦AI 查看最近生成的作品
4
+ domain: jimeng.jianying.com
5
+ strategy: cookie
6
+ browser: true
7
+
8
+ args:
9
+ limit:
10
+ type: int
11
+ default: 5
12
+
13
+ columns: [prompt, model, status, image_url, created_at]
14
+
15
+ pipeline:
16
+ - navigate: https://jimeng.jianying.com/ai-tool/generate?type=image&workspace=0
17
+ - wait: 3
18
+ - evaluate: |
19
+ (async () => {
20
+ const limit = ${{ args.limit }};
21
+ const res = await fetch('/mweb/v1/get_history?aid=513695&device_platform=web&region=cn&da_version=3.3.11&web_version=7.5.0&aigc_features=app_lip_sync', {
22
+ method: 'POST',
23
+ credentials: 'include',
24
+ headers: { 'Content-Type': 'application/json' },
25
+ body: JSON.stringify({ cursor: '', count: limit, need_page_item: true, need_aigc_data: true, aigc_mode_list: ['workbench'] })
26
+ });
27
+ const data = await res.json();
28
+ const items = data?.data?.history_list || [];
29
+ return items.slice(0, limit).map(item => {
30
+ const params = item.aigc_image_params?.text2image_params || {};
31
+ const images = item.image?.large_images || [];
32
+ return {
33
+ prompt: params.prompt || item.common_attr?.title || 'N/A',
34
+ model: params.model_config?.model_name || 'unknown',
35
+ status: item.common_attr?.status === 102 ? 'completed' : 'pending',
36
+ image_url: images[0]?.image_url || '',
37
+ created_at: new Date((item.common_attr?.create_time || 0) * 1000).toLocaleString('zh-CN'),
38
+ };
39
+ });
40
+ })()
41
+ - map:
42
+ prompt: ${{ item.prompt }}
43
+ model: ${{ item.model }}
44
+ status: ${{ item.status }}
45
+ image_url: ${{ item.image_url }}
46
+ created_at: ${{ item.created_at }}
47
+ - limit: ${{ args.limit }}
@@ -0,0 +1,41 @@
1
+ site: linux-do
2
+ name: categories
3
+ description: linux.do 分类列表
4
+ domain: linux.do
5
+ browser: true
6
+
7
+ args:
8
+ limit:
9
+ type: int
10
+ default: 20
11
+ description: Number of categories
12
+
13
+ pipeline:
14
+ - navigate: https://linux.do
15
+
16
+ - evaluate: |
17
+ (async () => {
18
+ const res = await fetch('/categories.json', { credentials: 'include' });
19
+ if (!res.ok) throw new Error('HTTP ' + res.status + ' - 请先登录 linux.do');
20
+ let data;
21
+ try { data = await res.json(); } catch { throw new Error('响应不是有效 JSON - 请先登录 linux.do'); }
22
+ const cats = data?.category_list?.categories || [];
23
+ return cats.slice(0, ${{ args.limit }}).map(c => ({
24
+ name: c.name,
25
+ slug: c.slug,
26
+ id: c.id,
27
+ topics: c.topic_count,
28
+ description: (c.description_text || '').slice(0, 80),
29
+ }));
30
+ })()
31
+
32
+ - map:
33
+ name: ${{ item.name }}
34
+ slug: ${{ item.slug }}
35
+ id: ${{ item.id }}
36
+ topics: ${{ item.topics }}
37
+ description: ${{ item.description }}
38
+
39
+ - limit: ${{ args.limit }}
40
+
41
+ columns: [name, slug, id, topics, description]
@@ -0,0 +1,49 @@
1
+ site: linux-do
2
+ name: category
3
+ description: linux.do 分类内话题
4
+ domain: linux.do
5
+ browser: true
6
+
7
+ args:
8
+ slug:
9
+ type: str
10
+ required: true
11
+ description: Category slug (use 'categories' command to find)
12
+ id:
13
+ type: int
14
+ required: true
15
+ description: Category ID (use 'categories' command to find)
16
+ limit:
17
+ type: int
18
+ default: 20
19
+ description: Number of topics
20
+
21
+ pipeline:
22
+ - navigate: https://linux.do
23
+
24
+ - evaluate: |
25
+ (async () => {
26
+ const slug = ${{ args.slug | json }};
27
+ const res = await fetch('/c/' + encodeURIComponent(slug) + '/${{ args.id }}.json', { credentials: 'include' });
28
+ if (!res.ok) throw new Error('HTTP ' + res.status + ' - 请先登录 linux.do');
29
+ let data;
30
+ try { data = await res.json(); } catch { throw new Error('响应不是有效 JSON - 请先登录 linux.do'); }
31
+ const topics = data?.topic_list?.topics || [];
32
+ return topics.slice(0, ${{ args.limit }}).map(t => ({
33
+ title: t.title,
34
+ replies: (t.posts_count || 1) - 1,
35
+ views: t.views,
36
+ likes: t.like_count,
37
+ }));
38
+ })()
39
+
40
+ - map:
41
+ rank: ${{ index + 1 }}
42
+ title: ${{ item.title }}
43
+ replies: ${{ item.replies }}
44
+ views: ${{ item.views }}
45
+ likes: ${{ item.likes }}
46
+
47
+ - limit: ${{ args.limit }}
48
+
49
+ columns: [rank, title, replies, views, likes]
@@ -0,0 +1,50 @@
1
+ site: linux-do
2
+ name: hot
3
+ description: linux.do 热门话题
4
+ domain: linux.do
5
+ browser: true
6
+
7
+ args:
8
+ limit:
9
+ type: int
10
+ default: 20
11
+ description: Number of topics
12
+ period:
13
+ type: str
14
+ default: weekly
15
+ description: Time period
16
+ choices: [all, daily, weekly, monthly, yearly]
17
+
18
+ pipeline:
19
+ - navigate: https://linux.do
20
+
21
+ - evaluate: |
22
+ (async () => {
23
+ const period = ${{ args.period | json }};
24
+ const res = await fetch('/top.json?period=' + encodeURIComponent(period), { credentials: 'include' });
25
+ if (!res.ok) throw new Error('HTTP ' + res.status + ' - 请先登录 linux.do');
26
+ let data;
27
+ try { data = await res.json(); } catch { throw new Error('响应不是有效 JSON - 请先登录 linux.do'); }
28
+ const topics = data?.topic_list?.topics || [];
29
+ const cats = data?.topic_list?.categories || data?.categories || [];
30
+ const catMap = Object.fromEntries(cats.map(c => [c.id, c.name]));
31
+ return topics.slice(0, ${{ args.limit }}).map(t => ({
32
+ title: t.title,
33
+ replies: (t.posts_count || 1) - 1,
34
+ views: t.views,
35
+ likes: t.like_count,
36
+ category: catMap[t.category_id] || String(t.category_id),
37
+ }));
38
+ })()
39
+
40
+ - map:
41
+ rank: ${{ index + 1 }}
42
+ title: ${{ item.title }}
43
+ replies: ${{ item.replies }}
44
+ views: ${{ item.views }}
45
+ likes: ${{ item.likes }}
46
+ category: ${{ item.category }}
47
+
48
+ - limit: ${{ args.limit }}
49
+
50
+ columns: [rank, title, replies, views, likes, category]
@@ -0,0 +1,40 @@
1
+ site: linux-do
2
+ name: latest
3
+ description: linux.do 最新话题
4
+ domain: linux.do
5
+ browser: true
6
+
7
+ args:
8
+ limit:
9
+ type: int
10
+ default: 20
11
+ description: Number of topics
12
+
13
+ pipeline:
14
+ - navigate: https://linux.do
15
+
16
+ - evaluate: |
17
+ (async () => {
18
+ const res = await fetch('/latest.json', { credentials: 'include' });
19
+ if (!res.ok) throw new Error('HTTP ' + res.status + ' - 请先登录 linux.do');
20
+ let data;
21
+ try { data = await res.json(); } catch { throw new Error('响应不是有效 JSON - 请先登录 linux.do'); }
22
+ const topics = data?.topic_list?.topics || [];
23
+ return topics.slice(0, ${{ args.limit }}).map(t => ({
24
+ title: t.title,
25
+ replies: (t.posts_count || 1) - 1,
26
+ views: t.views,
27
+ likes: t.like_count,
28
+ }));
29
+ })()
30
+
31
+ - map:
32
+ rank: ${{ index + 1 }}
33
+ title: ${{ item.title }}
34
+ replies: ${{ item.replies }}
35
+ views: ${{ item.views }}
36
+ likes: ${{ item.likes }}
37
+
38
+ - limit: ${{ args.limit }}
39
+
40
+ columns: [rank, title, replies, views, likes]
@@ -0,0 +1,45 @@
1
+ site: linux-do
2
+ name: search
3
+ description: 搜索 linux.do
4
+ domain: linux.do
5
+ browser: true
6
+
7
+ args:
8
+ keyword:
9
+ type: str
10
+ required: true
11
+ description: Search keyword
12
+ limit:
13
+ type: int
14
+ default: 20
15
+ description: Number of results
16
+
17
+ pipeline:
18
+ - navigate: https://linux.do
19
+
20
+ - evaluate: |
21
+ (async () => {
22
+ const keyword = ${{ args.keyword | json }};
23
+ const res = await fetch('/search.json?q=' + encodeURIComponent(keyword), { credentials: 'include' });
24
+ if (!res.ok) throw new Error('HTTP ' + res.status + ' - 请先登录 linux.do');
25
+ let data;
26
+ try { data = await res.json(); } catch { throw new Error('响应不是有效 JSON - 请先登录 linux.do'); }
27
+ const topics = data?.topics || [];
28
+ return topics.slice(0, ${{ args.limit }}).map(t => ({
29
+ title: t.title,
30
+ views: t.views,
31
+ likes: t.like_count,
32
+ replies: (t.posts_count || 1) - 1,
33
+ }));
34
+ })()
35
+
36
+ - map:
37
+ rank: ${{ index + 1 }}
38
+ title: ${{ item.title }}
39
+ views: ${{ item.views }}
40
+ likes: ${{ item.likes }}
41
+ replies: ${{ item.replies }}
42
+
43
+ - limit: ${{ args.limit }}
44
+
45
+ columns: [rank, title, views, likes, replies]
@@ -0,0 +1,38 @@
1
+ site: linux-do
2
+ name: topic
3
+ description: linux.do 帖子详情和回复(首页)
4
+ domain: linux.do
5
+ browser: true
6
+
7
+ args:
8
+ id:
9
+ type: int
10
+ required: true
11
+ description: Topic ID
12
+
13
+ pipeline:
14
+ - navigate: https://linux.do
15
+
16
+ - evaluate: |
17
+ (async () => {
18
+ const res = await fetch('/t/${{ args.id }}.json', { credentials: 'include' });
19
+ if (!res.ok) throw new Error('HTTP ' + res.status + ' - 请先登录 linux.do');
20
+ let data;
21
+ try { data = await res.json(); } catch { throw new Error('响应不是有效 JSON - 请先登录 linux.do'); }
22
+ const strip = (html) => (html || '').replace(/<br\s*\/?>/gi, ' ').replace(/<\/(p|div|li|blockquote|h[1-6])>/gi, ' ').replace(/<[^>]+>/g, '').replace(/&nbsp;/g, ' ').replace(/&amp;/g, '&').replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&quot;/g, '"').replace(/&#(?:(\d+)|x([0-9a-fA-F]+));/g, (_, dec, hex) => { try { return String.fromCodePoint(dec !== undefined ? Number(dec) : parseInt(hex, 16)); } catch { return ''; } }).replace(/\s+/g, ' ').trim();
23
+ const posts = data?.post_stream?.posts || [];
24
+ return posts.map(p => ({
25
+ author: p.username,
26
+ content: strip(p.cooked).slice(0, 200),
27
+ likes: p.like_count,
28
+ created_at: p.created_at,
29
+ }));
30
+ })()
31
+
32
+ - map:
33
+ author: ${{ item.author }}
34
+ content: ${{ item.content }}
35
+ likes: ${{ item.likes }}
36
+ created_at: ${{ item.created_at }}
37
+
38
+ columns: [author, content, likes, created_at]
@@ -0,0 +1 @@
1
+ export declare const likeCommand: import("../../registry.js").CliCommand;
@@ -0,0 +1,25 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ export const likeCommand = cli({
3
+ site: 'neteasemusic',
4
+ name: 'like',
5
+ description: 'Like/unlike the currently playing song',
6
+ domain: 'localhost',
7
+ strategy: Strategy.UI,
8
+ browser: true,
9
+ args: [],
10
+ columns: ['Status'],
11
+ func: async (page) => {
12
+ const result = await page.evaluate(`
13
+ (function() {
14
+ // The like/heart button in the player bar
15
+ const btn = document.querySelector('.m-playbar .icn-love, .m-playbar [class*="like"], .m-player [class*="love"], [data-action="like"]');
16
+ if (!btn) return 'Like button not found';
17
+
18
+ const wasLiked = btn.classList.contains('loved') || btn.classList.contains('active') || btn.getAttribute('data-liked') === 'true';
19
+ btn.click();
20
+ return wasLiked ? 'Unliked' : 'Liked';
21
+ })()
22
+ `);
23
+ return [{ Status: result }];
24
+ },
25
+ });
@@ -0,0 +1 @@
1
+ export declare const lyricsCommand: import("../../registry.js").CliCommand;
@@ -0,0 +1,47 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ export const lyricsCommand = cli({
3
+ site: 'neteasemusic',
4
+ name: 'lyrics',
5
+ description: 'Get the lyrics of the currently playing song',
6
+ domain: 'localhost',
7
+ strategy: Strategy.UI,
8
+ browser: true,
9
+ args: [],
10
+ columns: ['Line'],
11
+ func: async (page) => {
12
+ // Try to open lyrics panel if not visible
13
+ await page.evaluate(`
14
+ (function() {
15
+ const btn = document.querySelector('.m-playbar .icn-lyric, [class*="lyric-btn"], [data-action="lyric"]');
16
+ if (btn) btn.click();
17
+ })()
18
+ `);
19
+ await page.wait(1);
20
+ const lyrics = await page.evaluate(`
21
+ (function() {
22
+ // Look for lyrics container
23
+ const selectors = [
24
+ '.m-lyric p, .m-lyric [class*="line"]',
25
+ '[class*="lyric-content"] p',
26
+ '.listlyric li',
27
+ '[class*="lyric"] [class*="line"]',
28
+ '.j-lyric p',
29
+ ];
30
+
31
+ for (const sel of selectors) {
32
+ const nodes = document.querySelectorAll(sel);
33
+ if (nodes.length > 0) {
34
+ return Array.from(nodes).map(n => (n.textContent || '').trim()).filter(l => l.length > 0);
35
+ }
36
+ }
37
+
38
+ // Fallback: try the body text for any lyrics-like content
39
+ return [];
40
+ })()
41
+ `);
42
+ if (lyrics.length === 0) {
43
+ return [{ Line: 'No lyrics found. Try opening the lyrics panel first.' }];
44
+ }
45
+ return lyrics.map((line) => ({ Line: line }));
46
+ },
47
+ });
@@ -0,0 +1 @@
1
+ export declare const nextCommand: import("../../registry.js").CliCommand;
@@ -0,0 +1,26 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ export const nextCommand = cli({
3
+ site: 'neteasemusic',
4
+ name: 'next',
5
+ description: 'Skip to the next song',
6
+ domain: 'localhost',
7
+ strategy: Strategy.UI,
8
+ browser: true,
9
+ args: [],
10
+ columns: ['Status'],
11
+ func: async (page) => {
12
+ const clicked = await page.evaluate(`
13
+ (function() {
14
+ const btn = document.querySelector('.m-playbar .btnfwd, .m-playbar [class*="next"], .m-player .btn-next, [data-action="next"]');
15
+ if (btn) { btn.click(); return true; }
16
+ return false;
17
+ })()
18
+ `);
19
+ if (!clicked) {
20
+ // Fallback: Ctrl+Right is common next-track shortcut
21
+ await page.pressKey('Control+ArrowRight');
22
+ }
23
+ await page.wait(1);
24
+ return [{ Status: 'Skipped to next song' }];
25
+ },
26
+ });
@@ -0,0 +1 @@
1
+ export declare const playCommand: import("../../registry.js").CliCommand;
@@ -0,0 +1,26 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ export const playCommand = cli({
3
+ site: 'neteasemusic',
4
+ name: 'play',
5
+ description: 'Toggle play/pause for the current song',
6
+ domain: 'localhost',
7
+ strategy: Strategy.UI,
8
+ browser: true,
9
+ args: [],
10
+ columns: ['Status'],
11
+ func: async (page) => {
12
+ // Click the play/pause button or use Space key
13
+ const clicked = await page.evaluate(`
14
+ (function() {
15
+ const btn = document.querySelector('.m-playbar .btnp, .m-playbar [class*="play"], .m-player .btn-play, [data-action="play"]');
16
+ if (btn) { btn.click(); return true; }
17
+ return false;
18
+ })()
19
+ `);
20
+ if (!clicked) {
21
+ // Fallback: use Space key which is the universal play/pause shortcut
22
+ await page.pressKey('Space');
23
+ }
24
+ return [{ Status: 'Play/Pause toggled' }];
25
+ },
26
+ });
@@ -0,0 +1 @@
1
+ export declare const playingCommand: import("../../registry.js").CliCommand;
@@ -0,0 +1,59 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ export const playingCommand = cli({
3
+ site: 'neteasemusic',
4
+ name: 'playing',
5
+ description: 'Get the currently playing song info',
6
+ domain: 'localhost',
7
+ strategy: Strategy.UI,
8
+ browser: true,
9
+ args: [],
10
+ columns: ['Title', 'Artist', 'Album', 'Duration', 'Progress'],
11
+ func: async (page) => {
12
+ const info = await page.evaluate(`
13
+ (function() {
14
+ // NeteaseMusic player bar is at the bottom
15
+ const selectors = {
16
+ title: '.m-playbar .j-song .name, .m-playbar .song .name, [class*="playing"] .name, .m-player .name',
17
+ artist: '.m-playbar .j-song .by, .m-playbar .song .by, [class*="playing"] .artist, .m-player .by',
18
+ album: '.m-playbar .j-song .album, [class*="playing"] .album',
19
+ time: '.m-playbar .j-dur, .m-playbar .time, .m-player .time',
20
+ progress: '.m-playbar .barbg .rng, .m-playbar [role="progressbar"], .m-player [class*="progress"]',
21
+ };
22
+
23
+ function getText(sel) {
24
+ for (const s of sel.split(',')) {
25
+ const el = document.querySelector(s.trim());
26
+ if (el) return (el.textContent || el.innerText || '').trim();
27
+ }
28
+ return '';
29
+ }
30
+
31
+ const title = getText(selectors.title);
32
+ const artist = getText(selectors.artist);
33
+ const album = getText(selectors.album);
34
+ const time = getText(selectors.time);
35
+
36
+ // Try to get playback progress from the progress bar width
37
+ let progress = '';
38
+ const bar = document.querySelector('.m-playbar .barbg .rng, [class*="progress"] [class*="played"]');
39
+ if (bar) {
40
+ const style = bar.getAttribute('style') || '';
41
+ const match = style.match(/width:\\s*(\\d+\\.?\\d*)%/);
42
+ if (match) progress = match[1] + '%';
43
+ }
44
+
45
+ if (!title) {
46
+ // Fallback: try document title which often contains "songName - NeteaseMusic"
47
+ const docTitle = document.title;
48
+ if (docTitle && !docTitle.includes('NeteaseMusic')) {
49
+ return { Title: docTitle, Artist: '', Album: '', Duration: '', Progress: '' };
50
+ }
51
+ return { Title: 'No song playing', Artist: '—', Album: '—', Duration: '—', Progress: '—' };
52
+ }
53
+
54
+ return { Title: title, Artist: artist, Album: album, Duration: time, Progress: progress };
55
+ })()
56
+ `);
57
+ return [info];
58
+ },
59
+ });
@@ -0,0 +1 @@
1
+ export declare const playlistCommand: import("../../registry.js").CliCommand;
@@ -0,0 +1,46 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ export const playlistCommand = cli({
3
+ site: 'neteasemusic',
4
+ name: 'playlist',
5
+ description: 'Show the current playback queue / playlist',
6
+ domain: 'localhost',
7
+ strategy: Strategy.UI,
8
+ browser: true,
9
+ args: [],
10
+ columns: ['Index', 'Title', 'Artist'],
11
+ func: async (page) => {
12
+ // Open the playlist panel (usually a button at the bottom bar)
13
+ await page.evaluate(`
14
+ (function() {
15
+ const btn = document.querySelector('.m-playbar .icn-list, .m-playbar [class*="playlist"], [data-action="playlist"], .m-playbar .btnlist');
16
+ if (btn) btn.click();
17
+ })()
18
+ `);
19
+ await page.wait(1);
20
+ const items = await page.evaluate(`
21
+ (function() {
22
+ const results = [];
23
+ // Playlist panel items
24
+ const rows = document.querySelectorAll('.m-playlist li, [class*="playlist-panel"] li, .listlyric li, .j-playlist li');
25
+
26
+ rows.forEach((row, i) => {
27
+ const nameEl = row.querySelector('.name, [class*="name"], a, span:first-child');
28
+ const artistEl = row.querySelector('.by, [class*="artist"], .ar');
29
+
30
+ const title = nameEl ? (nameEl.getAttribute('title') || nameEl.textContent || '').trim() : (row.textContent || '').trim();
31
+ const artist = artistEl ? (artistEl.textContent || '').trim() : '';
32
+
33
+ if (title && title.length > 0) {
34
+ results.push({ Index: i + 1, Title: title.substring(0, 80), Artist: artist });
35
+ }
36
+ });
37
+
38
+ return results;
39
+ })()
40
+ `);
41
+ if (items.length === 0) {
42
+ return [{ Index: 0, Title: 'Playlist is empty or panel not open', Artist: '—' }];
43
+ }
44
+ return items;
45
+ },
46
+ });
@@ -0,0 +1 @@
1
+ export declare const prevCommand: import("../../registry.js").CliCommand;
@@ -0,0 +1,25 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ export const prevCommand = cli({
3
+ site: 'neteasemusic',
4
+ name: 'prev',
5
+ description: 'Go back to the previous song',
6
+ domain: 'localhost',
7
+ strategy: Strategy.UI,
8
+ browser: true,
9
+ args: [],
10
+ columns: ['Status'],
11
+ func: async (page) => {
12
+ const clicked = await page.evaluate(`
13
+ (function() {
14
+ const btn = document.querySelector('.m-playbar .btnbak, .m-playbar [class*="prev"], .m-player .btn-prev, [data-action="prev"]');
15
+ if (btn) { btn.click(); return true; }
16
+ return false;
17
+ })()
18
+ `);
19
+ if (!clicked) {
20
+ await page.pressKey('Control+ArrowLeft');
21
+ }
22
+ await page.wait(1);
23
+ return [{ Status: 'Went to previous song' }];
24
+ },
25
+ });
@@ -0,0 +1 @@
1
+ export declare const searchCommand: import("../../registry.js").CliCommand;
@@ -0,0 +1,52 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ export const searchCommand = cli({
3
+ site: 'neteasemusic',
4
+ name: 'search',
5
+ description: 'Search for songs, artists, albums, or playlists',
6
+ domain: 'localhost',
7
+ strategy: Strategy.UI,
8
+ browser: true,
9
+ args: [{ name: 'query', required: true, positional: true, help: 'Search query' }],
10
+ columns: ['Index', 'Title', 'Artist'],
11
+ func: async (page, kwargs) => {
12
+ const query = kwargs.query;
13
+ // Focus and fill the search box
14
+ await page.evaluate(`
15
+ (function(q) {
16
+ const input = document.querySelector('.m-search input, #srch, [class*="search"] input, input[type="search"]');
17
+ if (!input) throw new Error('Search input not found');
18
+ input.focus();
19
+ const setter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set;
20
+ setter.call(input, q);
21
+ input.dispatchEvent(new Event('input', { bubbles: true }));
22
+ })(${JSON.stringify(query)})
23
+ `);
24
+ await page.pressKey('Enter');
25
+ await page.wait(2);
26
+ // Scrape results
27
+ const results = await page.evaluate(`
28
+ (function() {
29
+ const items = [];
30
+ // Song list items in search results
31
+ const rows = document.querySelectorAll('.srchsongst li, .m-table tbody tr, [class*="songlist"] [class*="item"], table tbody tr');
32
+
33
+ rows.forEach((row, i) => {
34
+ if (i >= 20) return;
35
+ const nameEl = row.querySelector('.sn, .name a, [class*="songName"], td:nth-child(2) a, b[title]');
36
+ const artistEl = row.querySelector('.ar, .artist, [class*="artist"], td:nth-child(4) a, td:nth-child(3) a');
37
+
38
+ const title = nameEl ? (nameEl.getAttribute('title') || nameEl.textContent || '').trim() : '';
39
+ const artist = artistEl ? (artistEl.getAttribute('title') || artistEl.textContent || '').trim() : '';
40
+
41
+ if (title) items.push({ Index: i + 1, Title: title, Artist: artist });
42
+ });
43
+
44
+ return items;
45
+ })()
46
+ `);
47
+ if (results.length === 0) {
48
+ return [{ Index: 0, Title: `No results for "${query}"`, Artist: '—' }];
49
+ }
50
+ return results;
51
+ },
52
+ });
@@ -0,0 +1 @@
1
+ export declare const statusCommand: import("../../registry.js").CliCommand;