@jackwener/opencli 1.5.6 → 1.5.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -0
- package/README.md +4 -2
- package/README.zh-CN.md +4 -1
- package/SKILL.md +879 -0
- package/dist/browser/cdp.d.ts +1 -0
- package/dist/browser/cdp.js +30 -27
- package/dist/browser/daemon-client.d.ts +7 -1
- package/dist/browser/daemon-client.js +3 -0
- package/dist/browser/dom-helpers.js +1 -0
- package/dist/browser/dom-helpers.test.js +14 -1
- package/dist/browser/mcp.js +18 -13
- package/dist/browser/page.js +22 -2
- package/dist/browser/page.test.d.ts +1 -0
- package/dist/browser/page.test.js +44 -0
- package/dist/browser/stealth.js +198 -0
- package/dist/browser/stealth.test.d.ts +1 -0
- package/dist/browser/stealth.test.js +134 -0
- package/dist/browser.test.js +1 -1
- package/dist/build-manifest.d.ts +1 -0
- package/dist/build-manifest.js +5 -1
- package/dist/build-manifest.test.js +2 -0
- package/dist/cli-manifest.json +544 -137
- package/dist/cli.js +20 -3
- package/dist/clis/antigravity/serve.d.ts +1 -1
- package/dist/clis/antigravity/serve.js +5 -8
- package/dist/clis/bilibili/subtitle.js +4 -0
- package/dist/clis/bilibili/subtitle.test.d.ts +1 -0
- package/dist/clis/bilibili/subtitle.test.js +48 -0
- package/dist/clis/chatwise/ask.js +0 -2
- package/dist/clis/chatwise/export.js +0 -2
- package/dist/clis/chatwise/history.js +0 -2
- package/dist/clis/chatwise/model.js +0 -2
- package/dist/clis/chatwise/new.js +1 -2
- package/dist/clis/chatwise/read.js +0 -2
- package/dist/clis/chatwise/screenshot.js +1 -2
- package/dist/clis/chatwise/send.js +0 -2
- package/dist/clis/chatwise/status.js +1 -2
- package/dist/clis/ctrip/search.d.ts +13 -0
- package/dist/clis/ctrip/search.js +73 -48
- package/dist/clis/ctrip/search.test.d.ts +1 -0
- package/dist/clis/ctrip/search.test.js +64 -0
- package/dist/clis/douyin/_shared/sts2.js +8 -2
- package/dist/clis/douyin/_shared/sts2.test.d.ts +1 -0
- package/dist/clis/douyin/_shared/sts2.test.js +27 -0
- package/dist/clis/douyin/activities.js +4 -2
- package/dist/clis/douyin/activities.test.js +34 -1
- package/dist/clis/douyin/collections.js +1 -1
- package/dist/clis/douyin/collections.test.js +24 -2
- package/dist/clis/douyin/draft.d.ts +8 -11
- package/dist/clis/douyin/draft.js +302 -185
- package/dist/clis/douyin/draft.test.d.ts +1 -1
- package/dist/clis/douyin/draft.test.js +357 -2
- package/dist/clis/douyin/hashtag.js +9 -2
- package/dist/clis/douyin/hashtag.test.js +35 -2
- package/dist/clis/douyin/profile.js +1 -1
- package/dist/clis/douyin/profile.test.js +36 -1
- package/dist/clis/douyin/videos.js +22 -5
- package/dist/clis/douyin/videos.test.js +45 -2
- package/dist/clis/facebook/search.test.d.ts +5 -0
- package/dist/clis/facebook/search.test.js +60 -0
- package/dist/clis/facebook/search.yaml +4 -3
- package/dist/clis/instagram/download.d.ts +16 -0
- package/dist/clis/instagram/download.js +225 -0
- package/dist/clis/instagram/download.test.d.ts +1 -0
- package/dist/clis/instagram/download.test.js +118 -0
- package/dist/clis/notebooklm/bind-current.d.ts +1 -0
- package/dist/clis/notebooklm/bind-current.js +29 -0
- package/dist/clis/notebooklm/bind-current.test.d.ts +1 -0
- package/dist/clis/notebooklm/bind-current.test.js +35 -0
- package/dist/clis/notebooklm/binding.test.d.ts +1 -0
- package/dist/clis/notebooklm/binding.test.js +44 -0
- package/dist/clis/notebooklm/compat.test.d.ts +3 -0
- package/dist/clis/notebooklm/compat.test.js +16 -0
- package/dist/clis/notebooklm/current.d.ts +1 -0
- package/dist/clis/notebooklm/current.js +28 -0
- package/dist/clis/notebooklm/get.d.ts +1 -0
- package/dist/clis/notebooklm/get.js +37 -0
- package/dist/clis/notebooklm/history.d.ts +1 -0
- package/dist/clis/notebooklm/history.js +25 -0
- package/dist/clis/notebooklm/history.test.d.ts +1 -0
- package/dist/clis/notebooklm/history.test.js +58 -0
- package/dist/clis/notebooklm/list.d.ts +1 -0
- package/dist/clis/notebooklm/list.js +35 -0
- package/dist/clis/notebooklm/note-list.d.ts +1 -0
- package/dist/clis/notebooklm/note-list.js +28 -0
- package/dist/clis/notebooklm/note-list.test.d.ts +1 -0
- package/dist/clis/notebooklm/note-list.test.js +56 -0
- package/dist/clis/notebooklm/notes-get.d.ts +1 -0
- package/dist/clis/notebooklm/notes-get.js +47 -0
- package/dist/clis/notebooklm/notes-get.test.d.ts +1 -0
- package/dist/clis/notebooklm/notes-get.test.js +72 -0
- package/dist/clis/notebooklm/rpc.d.ts +36 -0
- package/dist/clis/notebooklm/rpc.js +189 -0
- package/dist/clis/notebooklm/rpc.test.d.ts +1 -0
- package/dist/clis/notebooklm/rpc.test.js +105 -0
- package/dist/clis/notebooklm/shared.d.ts +87 -0
- package/dist/clis/notebooklm/shared.js +3 -0
- package/dist/clis/notebooklm/source-fulltext.d.ts +1 -0
- package/dist/clis/notebooklm/source-fulltext.js +44 -0
- package/dist/clis/notebooklm/source-fulltext.test.d.ts +1 -0
- package/dist/clis/notebooklm/source-fulltext.test.js +106 -0
- package/dist/clis/notebooklm/source-get.d.ts +1 -0
- package/dist/clis/notebooklm/source-get.js +40 -0
- package/dist/clis/notebooklm/source-get.test.d.ts +1 -0
- package/dist/clis/notebooklm/source-get.test.js +84 -0
- package/dist/clis/notebooklm/source-guide.d.ts +1 -0
- package/dist/clis/notebooklm/source-guide.js +44 -0
- package/dist/clis/notebooklm/source-guide.test.d.ts +1 -0
- package/dist/clis/notebooklm/source-guide.test.js +104 -0
- package/dist/clis/notebooklm/source-list.d.ts +1 -0
- package/dist/clis/notebooklm/source-list.js +30 -0
- package/dist/clis/notebooklm/status.d.ts +1 -0
- package/dist/clis/notebooklm/status.js +31 -0
- package/dist/clis/notebooklm/summary.d.ts +1 -0
- package/dist/clis/notebooklm/summary.js +30 -0
- package/dist/clis/notebooklm/summary.test.d.ts +1 -0
- package/dist/clis/notebooklm/summary.test.js +78 -0
- package/dist/clis/notebooklm/utils.d.ts +37 -0
- package/dist/clis/notebooklm/utils.js +739 -0
- package/dist/clis/notebooklm/utils.test.d.ts +1 -0
- package/dist/clis/notebooklm/utils.test.js +390 -0
- package/dist/clis/substack/utils.d.ts +4 -0
- package/dist/clis/substack/utils.js +8 -2
- package/dist/clis/substack/utils.test.d.ts +1 -0
- package/dist/clis/substack/utils.test.js +46 -0
- package/dist/clis/v2ex/hot.yaml +4 -1
- package/dist/clis/v2ex/latest.yaml +4 -1
- package/dist/clis/v2ex/topic.yaml +6 -1
- package/dist/clis/weixin/download.d.ts +9 -0
- package/dist/clis/weixin/download.js +76 -6
- package/dist/clis/weread/book.js +108 -2
- package/dist/clis/weread/commands.test.js +262 -152
- package/dist/clis/weread/utils.d.ts +10 -0
- package/dist/clis/weread/utils.js +27 -7
- package/dist/clis/xiaohongshu/comments.d.ts +3 -0
- package/dist/clis/xiaohongshu/comments.js +76 -17
- package/dist/clis/xiaohongshu/comments.test.js +70 -9
- package/dist/clis/xiaohongshu/download.d.ts +4 -1
- package/dist/clis/xiaohongshu/download.js +83 -22
- package/dist/clis/xiaohongshu/download.test.d.ts +1 -0
- package/dist/clis/xiaohongshu/download.test.js +75 -0
- package/dist/clis/xiaohongshu/note-helpers.d.ts +12 -0
- package/dist/clis/xiaohongshu/note-helpers.js +23 -0
- package/dist/clis/xiaohongshu/note.d.ts +7 -0
- package/dist/clis/xiaohongshu/note.js +76 -0
- package/dist/clis/xiaohongshu/note.test.d.ts +1 -0
- package/dist/clis/xiaohongshu/note.test.js +136 -0
- package/dist/clis/xiaohongshu/search.js +9 -0
- package/dist/clis/xiaohongshu/search.test.js +10 -4
- package/dist/clis/youtube/search.js +57 -17
- package/dist/clis/zhihu/question.js +19 -17
- package/dist/clis/zhihu/question.test.d.ts +1 -0
- package/dist/clis/zhihu/question.test.js +54 -0
- package/dist/commanderAdapter.js +9 -0
- package/dist/commanderAdapter.test.js +25 -0
- package/dist/commands/daemon.d.ts +9 -0
- package/dist/commands/daemon.js +124 -0
- package/dist/commands/daemon.test.d.ts +1 -0
- package/dist/commands/daemon.test.js +185 -0
- package/dist/completion.js +3 -1
- package/dist/constants.d.ts +2 -0
- package/dist/constants.js +2 -0
- package/dist/daemon.d.ts +1 -1
- package/dist/daemon.js +25 -14
- package/dist/daemon.test.d.ts +1 -0
- package/dist/daemon.test.js +65 -0
- package/dist/discovery.d.ts +9 -0
- package/dist/discovery.js +47 -2
- package/dist/electron-apps.d.ts +29 -0
- package/dist/electron-apps.js +65 -0
- package/dist/electron-apps.test.d.ts +1 -0
- package/dist/electron-apps.test.js +43 -0
- package/dist/engine.test.js +41 -9
- package/dist/execution.js +20 -16
- package/dist/idle-manager.d.ts +19 -0
- package/dist/idle-manager.js +54 -0
- package/dist/launcher.d.ts +36 -0
- package/dist/launcher.js +152 -0
- package/dist/launcher.test.d.ts +1 -0
- package/dist/launcher.test.js +57 -0
- package/dist/main.js +3 -3
- package/dist/registry.d.ts +1 -0
- package/dist/registry.js +31 -3
- package/dist/registry.test.js +13 -0
- package/dist/runtime.d.ts +5 -3
- package/dist/runtime.js +12 -5
- package/dist/serialization.d.ts +1 -0
- package/dist/serialization.js +3 -0
- package/dist/serialization.test.js +17 -1
- package/dist/tui.d.ts +7 -0
- package/dist/tui.js +52 -0
- package/dist/tui.test.d.ts +1 -0
- package/dist/tui.test.js +19 -0
- package/dist/weixin-download.test.js +14 -0
- package/docs/.vitepress/config.mts +1 -0
- package/docs/adapters/browser/notebooklm.md +69 -0
- package/docs/adapters/browser/xiaohongshu.md +19 -10
- package/docs/adapters/index.md +67 -66
- package/docs/guide/browser-bridge.md +12 -0
- package/docs/guide/troubleshooting.md +9 -4
- package/docs/superpowers/plans/2026-03-31-daemon-lifecycle-redesign.md +857 -0
- package/docs/superpowers/specs/2026-03-31-daemon-lifecycle-redesign.md +208 -0
- package/docs/zh/guide/browser-bridge.md +12 -0
- package/extension/dist/background.js +794 -513
- package/extension/src/background.test.ts +202 -2
- package/extension/src/background.ts +174 -10
- package/extension/src/cdp.ts +12 -0
- package/extension/src/protocol.ts +7 -5
- package/package.json +1 -1
- package/src/browser/cdp.ts +24 -17
- package/src/browser/daemon-client.ts +7 -1
- package/src/browser/dom-helpers.test.ts +15 -1
- package/src/browser/dom-helpers.ts +1 -0
- package/src/browser/mcp.ts +18 -13
- package/src/browser/page.test.ts +58 -0
- package/src/browser/page.ts +18 -2
- package/src/browser/stealth.test.ts +153 -0
- package/src/browser/stealth.ts +198 -0
- package/src/browser.test.ts +1 -1
- package/src/build-manifest.test.ts +2 -0
- package/src/build-manifest.ts +6 -1
- package/src/cli.ts +21 -3
- package/src/clis/antigravity/SKILL.md +3 -12
- package/src/clis/antigravity/serve.ts +5 -10
- package/src/clis/bilibili/subtitle.test.ts +60 -0
- package/src/clis/bilibili/subtitle.ts +4 -0
- package/src/clis/chatwise/ask.ts +0 -2
- package/src/clis/chatwise/export.ts +0 -2
- package/src/clis/chatwise/history.ts +0 -2
- package/src/clis/chatwise/model.ts +0 -2
- package/src/clis/chatwise/new.ts +1 -2
- package/src/clis/chatwise/read.ts +0 -2
- package/src/clis/chatwise/screenshot.ts +1 -2
- package/src/clis/chatwise/send.ts +0 -2
- package/src/clis/chatwise/status.ts +1 -2
- package/src/clis/ctrip/search.test.ts +73 -0
- package/src/clis/ctrip/search.ts +97 -47
- package/src/clis/douyin/_shared/sts2.test.ts +31 -0
- package/src/clis/douyin/_shared/sts2.ts +11 -3
- package/src/clis/douyin/activities.test.ts +41 -1
- package/src/clis/douyin/activities.ts +12 -3
- package/src/clis/douyin/collections.test.ts +35 -2
- package/src/clis/douyin/collections.ts +1 -1
- package/src/clis/douyin/draft.test.ts +444 -2
- package/src/clis/douyin/draft.ts +382 -218
- package/src/clis/douyin/hashtag.test.ts +42 -2
- package/src/clis/douyin/hashtag.ts +11 -3
- package/src/clis/douyin/profile.test.ts +43 -1
- package/src/clis/douyin/profile.ts +9 -2
- package/src/clis/douyin/videos.test.ts +52 -2
- package/src/clis/douyin/videos.ts +49 -15
- package/src/clis/facebook/search.test.ts +70 -0
- package/src/clis/facebook/search.yaml +4 -3
- package/src/clis/instagram/download.test.ts +159 -0
- package/src/clis/instagram/download.ts +286 -0
- package/src/clis/notebooklm/bind-current.test.ts +43 -0
- package/src/clis/notebooklm/bind-current.ts +36 -0
- package/src/clis/notebooklm/binding.test.ts +53 -0
- package/src/clis/notebooklm/compat.test.ts +19 -0
- package/src/clis/notebooklm/current.ts +38 -0
- package/src/clis/notebooklm/get.ts +53 -0
- package/src/clis/notebooklm/history.test.ts +70 -0
- package/src/clis/notebooklm/history.ts +36 -0
- package/src/clis/notebooklm/list.ts +40 -0
- package/src/clis/notebooklm/note-list.test.ts +64 -0
- package/src/clis/notebooklm/note-list.ts +42 -0
- package/src/clis/notebooklm/notes-get.test.ts +88 -0
- package/src/clis/notebooklm/notes-get.ts +67 -0
- package/src/clis/notebooklm/rpc.test.ts +126 -0
- package/src/clis/notebooklm/rpc.ts +286 -0
- package/src/clis/notebooklm/shared.ts +98 -0
- package/src/clis/notebooklm/source-fulltext.test.ts +123 -0
- package/src/clis/notebooklm/source-fulltext.ts +69 -0
- package/src/clis/notebooklm/source-get.test.ts +100 -0
- package/src/clis/notebooklm/source-get.ts +60 -0
- package/src/clis/notebooklm/source-guide.test.ts +121 -0
- package/src/clis/notebooklm/source-guide.ts +69 -0
- package/src/clis/notebooklm/source-list.ts +45 -0
- package/src/clis/notebooklm/status.ts +34 -0
- package/src/clis/notebooklm/summary.test.ts +94 -0
- package/src/clis/notebooklm/summary.ts +45 -0
- package/src/clis/notebooklm/utils.test.ts +446 -0
- package/src/clis/notebooklm/utils.ts +893 -0
- package/src/clis/substack/utils.test.ts +54 -0
- package/src/clis/substack/utils.ts +10 -2
- package/src/clis/v2ex/hot.yaml +4 -1
- package/src/clis/v2ex/latest.yaml +4 -1
- package/src/clis/v2ex/topic.yaml +6 -1
- package/src/clis/weixin/download.ts +95 -6
- package/src/clis/weread/book.ts +142 -2
- package/src/clis/weread/commands.test.ts +314 -154
- package/src/clis/weread/utils.ts +33 -4
- package/src/clis/xiaohongshu/comments.test.ts +85 -9
- package/src/clis/xiaohongshu/comments.ts +76 -17
- package/src/clis/xiaohongshu/download.test.ts +96 -0
- package/src/clis/xiaohongshu/download.ts +83 -22
- package/src/clis/xiaohongshu/note-helpers.ts +25 -0
- package/src/clis/xiaohongshu/note.test.ts +164 -0
- package/src/clis/xiaohongshu/note.ts +86 -0
- package/src/clis/xiaohongshu/search.test.ts +11 -4
- package/src/clis/xiaohongshu/search.ts +13 -0
- package/src/clis/youtube/search.ts +57 -17
- package/src/clis/zhihu/question.test.ts +71 -0
- package/src/clis/zhihu/question.ts +27 -15
- package/src/commanderAdapter.test.ts +30 -0
- package/src/commanderAdapter.ts +7 -0
- package/src/commands/daemon.test.ts +238 -0
- package/src/commands/daemon.ts +135 -0
- package/src/completion.ts +2 -1
- package/src/constants.ts +3 -0
- package/src/daemon.test.ts +88 -0
- package/src/daemon.ts +26 -14
- package/src/discovery.ts +52 -2
- package/src/electron-apps.test.ts +50 -0
- package/src/electron-apps.ts +89 -0
- package/src/engine.test.ts +45 -9
- package/src/execution.ts +24 -19
- package/src/idle-manager.ts +60 -0
- package/src/launcher.test.ts +67 -0
- package/src/launcher.ts +185 -0
- package/src/main.ts +3 -2
- package/src/registry.test.ts +15 -0
- package/src/registry.ts +32 -3
- package/src/runtime.ts +13 -7
- package/src/serialization.test.ts +19 -1
- package/src/serialization.ts +2 -0
- package/src/tui.test.ts +23 -0
- package/src/tui.ts +65 -0
- package/src/weixin-download.test.ts +27 -0
- package/tests/e2e/browser-public-extended.test.ts +6 -2
- package/chatwise-opencli.ps1 +0 -82
- package/dist/clis/chatwise/shared.d.ts +0 -2
- package/dist/clis/chatwise/shared.js +0 -6
- package/src/clis/chatwise/shared.ts +0 -8
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { buildNotebooklmRpcBody, classifyNotebooklmPage, extractNotebooklmHistoryPreview, extractNotebooklmRpcResult, getNotebooklmPageState, normalizeNotebooklmTitle, parseNotebooklmHistoryThreadIdsResult, parseNotebooklmIdFromUrl, parseNotebooklmListResult, parseNotebooklmNoteListRawRows, parseNotebooklmNotebookDetailResult, parseNotebooklmSourceFulltextResult, parseNotebooklmSourceGuideResult, parseNotebooklmSourceListResult, } from './utils.js';
|
|
3
|
+
describe('notebooklm utils', () => {
|
|
4
|
+
it('parses notebook id from a notebook url', () => {
|
|
5
|
+
expect(parseNotebooklmIdFromUrl('https://notebooklm.google.com/notebook/abc-123')).toBe('abc-123');
|
|
6
|
+
});
|
|
7
|
+
it('returns empty string when notebook id is absent', () => {
|
|
8
|
+
expect(parseNotebooklmIdFromUrl('https://notebooklm.google.com/')).toBe('');
|
|
9
|
+
});
|
|
10
|
+
it('classifies notebook pages correctly', () => {
|
|
11
|
+
expect(classifyNotebooklmPage('https://notebooklm.google.com/notebook/demo-id')).toBe('notebook');
|
|
12
|
+
expect(classifyNotebooklmPage('https://notebooklm.google.com/')).toBe('home');
|
|
13
|
+
expect(classifyNotebooklmPage('https://example.com/notebook/demo-id')).toBe('unknown');
|
|
14
|
+
});
|
|
15
|
+
it('normalizes notebook titles', () => {
|
|
16
|
+
expect(normalizeNotebooklmTitle(' Demo Notebook ')).toBe('Demo Notebook');
|
|
17
|
+
expect(normalizeNotebooklmTitle('', 'Untitled')).toBe('Untitled');
|
|
18
|
+
});
|
|
19
|
+
it('builds the notebooklm rpc request body with csrf token', () => {
|
|
20
|
+
const body = buildNotebooklmRpcBody('wXbhsf', [null, 1, null, [2]], 'csrf123');
|
|
21
|
+
expect(body).toContain('f.req=');
|
|
22
|
+
expect(body).toContain('at=csrf123');
|
|
23
|
+
expect(body.endsWith('&')).toBe(true);
|
|
24
|
+
expect(decodeURIComponent(body)).toContain('"[null,1,null,[2]]"');
|
|
25
|
+
});
|
|
26
|
+
it('extracts notebooklm rpc payload from chunked batchexecute response', () => {
|
|
27
|
+
const raw = ')]}\'\n107\n[["wrb.fr","wXbhsf","[[[\\"Notebook One\\",null,\\"nb1\\",null,null,[null,false,null,null,null,[1704067200]]]]]"]]';
|
|
28
|
+
const result = extractNotebooklmRpcResult(raw, 'wXbhsf');
|
|
29
|
+
expect(Array.isArray(result)).toBe(true);
|
|
30
|
+
expect(result[0]).toBeDefined();
|
|
31
|
+
});
|
|
32
|
+
it('parses notebook rows from notebooklm rpc payload', () => {
|
|
33
|
+
const rows = parseNotebooklmListResult([
|
|
34
|
+
[
|
|
35
|
+
['Notebook One', null, 'nb1', null, null, [null, false, null, null, null, [1704067200]]],
|
|
36
|
+
],
|
|
37
|
+
]);
|
|
38
|
+
expect(rows).toEqual([
|
|
39
|
+
{
|
|
40
|
+
id: 'nb1',
|
|
41
|
+
title: 'Notebook One',
|
|
42
|
+
url: 'https://notebooklm.google.com/notebook/nb1',
|
|
43
|
+
source: 'rpc',
|
|
44
|
+
is_owner: true,
|
|
45
|
+
created_at: '2024-01-01T00:00:00.000Z',
|
|
46
|
+
},
|
|
47
|
+
]);
|
|
48
|
+
});
|
|
49
|
+
it('parses notebook metadata from notebook detail rpc payload', () => {
|
|
50
|
+
const notebook = parseNotebooklmNotebookDetailResult([
|
|
51
|
+
'Browser Automation',
|
|
52
|
+
[
|
|
53
|
+
[
|
|
54
|
+
[['src1']],
|
|
55
|
+
'Pasted text',
|
|
56
|
+
[null, 359, [1774872183, 855096000], ['doc1', [1774872183, 356519000]], 8, null, 1, null, null, null, null, null, null, null, [1774872185, 395271000]],
|
|
57
|
+
[null, 2],
|
|
58
|
+
],
|
|
59
|
+
],
|
|
60
|
+
'nb-demo',
|
|
61
|
+
'🕸️',
|
|
62
|
+
null,
|
|
63
|
+
[1, false, true, null, null, [1774889558, 348721000], 1, false, [1774872161, 361922000], null, null, null, false, true, 1, false, null, true, 1],
|
|
64
|
+
]);
|
|
65
|
+
expect(notebook).toEqual({
|
|
66
|
+
id: 'nb-demo',
|
|
67
|
+
title: 'Browser Automation',
|
|
68
|
+
url: 'https://notebooklm.google.com/notebook/nb-demo',
|
|
69
|
+
source: 'rpc',
|
|
70
|
+
emoji: '🕸️',
|
|
71
|
+
source_count: 1,
|
|
72
|
+
is_owner: true,
|
|
73
|
+
created_at: '2026-03-30T12:02:41.361Z',
|
|
74
|
+
updated_at: '2026-03-30T16:52:38.348Z',
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
it('parses notebook metadata when detail rpc wraps the payload in a singleton envelope', () => {
|
|
78
|
+
const notebook = parseNotebooklmNotebookDetailResult([
|
|
79
|
+
[
|
|
80
|
+
'Browser Automation',
|
|
81
|
+
[
|
|
82
|
+
[
|
|
83
|
+
[['src1']],
|
|
84
|
+
'Pasted text',
|
|
85
|
+
[null, 359, [1774872183, 855096000], ['doc1', [1774872183, 356519000]], 8, null, 1, null, null, null, null, null, null, null, [1774872185, 395271000]],
|
|
86
|
+
[null, 2],
|
|
87
|
+
],
|
|
88
|
+
],
|
|
89
|
+
'nb-demo',
|
|
90
|
+
'🕸️',
|
|
91
|
+
null,
|
|
92
|
+
[1, false, true, null, null, [1774889558, 348721000], 1, false, [1774872161, 361922000], null, null, null, false, true, 1, false, null, true, 1],
|
|
93
|
+
],
|
|
94
|
+
]);
|
|
95
|
+
expect(notebook).toEqual({
|
|
96
|
+
id: 'nb-demo',
|
|
97
|
+
title: 'Browser Automation',
|
|
98
|
+
url: 'https://notebooklm.google.com/notebook/nb-demo',
|
|
99
|
+
source: 'rpc',
|
|
100
|
+
emoji: '🕸️',
|
|
101
|
+
source_count: 1,
|
|
102
|
+
is_owner: true,
|
|
103
|
+
created_at: '2026-03-30T12:02:41.361Z',
|
|
104
|
+
updated_at: '2026-03-30T16:52:38.348Z',
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
it('parses sources from notebook detail rpc payload', () => {
|
|
108
|
+
const rows = parseNotebooklmSourceListResult([
|
|
109
|
+
'Browser Automation',
|
|
110
|
+
[
|
|
111
|
+
[
|
|
112
|
+
[['src1']],
|
|
113
|
+
'Pasted text',
|
|
114
|
+
[null, 359, [1774872183, 855096000], ['doc1', [1774872183, 356519000]], 8, null, 1, null, null, null, null, null, null, null, [1774872185, 395271000]],
|
|
115
|
+
[null, 2],
|
|
116
|
+
],
|
|
117
|
+
],
|
|
118
|
+
'nb-demo',
|
|
119
|
+
'🕸️',
|
|
120
|
+
null,
|
|
121
|
+
[1, false, true, null, null, [1774889558, 348721000], 1, false, [1774872161, 361922000], null, null, null, false, true, 1, false, null, true, 1],
|
|
122
|
+
]);
|
|
123
|
+
expect(rows).toEqual([
|
|
124
|
+
{
|
|
125
|
+
id: 'src1',
|
|
126
|
+
notebook_id: 'nb-demo',
|
|
127
|
+
title: 'Pasted text',
|
|
128
|
+
type: 'pasted-text',
|
|
129
|
+
type_code: 8,
|
|
130
|
+
size: 359,
|
|
131
|
+
created_at: '2026-03-30T12:03:03.855Z',
|
|
132
|
+
updated_at: '2026-03-30T12:03:05.395Z',
|
|
133
|
+
url: 'https://notebooklm.google.com/notebook/nb-demo',
|
|
134
|
+
source: 'rpc',
|
|
135
|
+
},
|
|
136
|
+
]);
|
|
137
|
+
});
|
|
138
|
+
it('parses sources when detail rpc wraps the payload in a singleton envelope', () => {
|
|
139
|
+
const rows = parseNotebooklmSourceListResult([
|
|
140
|
+
[
|
|
141
|
+
'Browser Automation',
|
|
142
|
+
[
|
|
143
|
+
[
|
|
144
|
+
[['src1']],
|
|
145
|
+
'Pasted text',
|
|
146
|
+
[null, 359, [1774872183, 855096000], ['doc1', [1774872183, 356519000]], 8, null, 1, null, null, null, null, null, null, null, [1774872185, 395271000]],
|
|
147
|
+
[null, 2],
|
|
148
|
+
],
|
|
149
|
+
],
|
|
150
|
+
'nb-demo',
|
|
151
|
+
'🕸️',
|
|
152
|
+
null,
|
|
153
|
+
[1, false, true, null, null, [1774889558, 348721000], 1, false, [1774872161, 361922000], null, null, null, false, true, 1, false, null, true, 1],
|
|
154
|
+
],
|
|
155
|
+
]);
|
|
156
|
+
expect(rows).toEqual([
|
|
157
|
+
{
|
|
158
|
+
id: 'src1',
|
|
159
|
+
notebook_id: 'nb-demo',
|
|
160
|
+
title: 'Pasted text',
|
|
161
|
+
type: 'pasted-text',
|
|
162
|
+
type_code: 8,
|
|
163
|
+
size: 359,
|
|
164
|
+
created_at: '2026-03-30T12:03:03.855Z',
|
|
165
|
+
updated_at: '2026-03-30T12:03:05.395Z',
|
|
166
|
+
url: 'https://notebooklm.google.com/notebook/nb-demo',
|
|
167
|
+
source: 'rpc',
|
|
168
|
+
},
|
|
169
|
+
]);
|
|
170
|
+
});
|
|
171
|
+
it('parses sources when the source id container is only wrapped once', () => {
|
|
172
|
+
const rows = parseNotebooklmSourceListResult([
|
|
173
|
+
[
|
|
174
|
+
'Browser Automation',
|
|
175
|
+
[
|
|
176
|
+
[
|
|
177
|
+
['src-live'],
|
|
178
|
+
'Pasted text',
|
|
179
|
+
[null, 359, [1774872183, 855096000], ['doc1', [1774872183, 356519000]], 8, null, 1, null, null, null, null, null, null, null, [1774872185, 395271000]],
|
|
180
|
+
[null, 2],
|
|
181
|
+
],
|
|
182
|
+
],
|
|
183
|
+
'nb-demo',
|
|
184
|
+
'🕸️',
|
|
185
|
+
null,
|
|
186
|
+
[1, false, true, null, null, [1774889558, 348721000], 1, false, [1774872161, 361922000], null, null, null, false, true, 1, false, null, true, 1],
|
|
187
|
+
],
|
|
188
|
+
]);
|
|
189
|
+
expect(rows).toEqual([
|
|
190
|
+
{
|
|
191
|
+
id: 'src-live',
|
|
192
|
+
notebook_id: 'nb-demo',
|
|
193
|
+
title: 'Pasted text',
|
|
194
|
+
type: 'pasted-text',
|
|
195
|
+
type_code: 8,
|
|
196
|
+
size: 359,
|
|
197
|
+
created_at: '2026-03-30T12:03:03.855Z',
|
|
198
|
+
updated_at: '2026-03-30T12:03:05.395Z',
|
|
199
|
+
url: 'https://notebooklm.google.com/notebook/nb-demo',
|
|
200
|
+
source: 'rpc',
|
|
201
|
+
},
|
|
202
|
+
]);
|
|
203
|
+
});
|
|
204
|
+
it('parses source type from metadata slot instead of the stale entry[3] envelope', () => {
|
|
205
|
+
const rows = parseNotebooklmSourceListResult([
|
|
206
|
+
[
|
|
207
|
+
'Browser Automation',
|
|
208
|
+
[
|
|
209
|
+
[
|
|
210
|
+
['src-pdf'],
|
|
211
|
+
'Manual.pdf',
|
|
212
|
+
[null, 18940, [1774872183, 855096000], ['doc1', [1774872183, 356519000]], 3, null, 1, null, null, null, null, null, null, null, [1774872185, 395271000]],
|
|
213
|
+
[null, 2],
|
|
214
|
+
],
|
|
215
|
+
[
|
|
216
|
+
['src-web'],
|
|
217
|
+
'Example Site',
|
|
218
|
+
[null, 131, [1774872183, 855096000], ['doc2', [1774872183, 356519000]], 5, ['https://example.com'], 1, null, null, null, null, null, null, null, [1774872185, 395271000]],
|
|
219
|
+
[null, 2],
|
|
220
|
+
],
|
|
221
|
+
[
|
|
222
|
+
['src-yt'],
|
|
223
|
+
'Video Source',
|
|
224
|
+
[null, 11958, [1774872183, 855096000], ['doc3', [1774872183, 356519000]], 9, ['https://youtu.be/demo', 'demo', 'Uploader'], 1, null, null, null, null, null, null, null, [1774872185, 395271000]],
|
|
225
|
+
[null, 2],
|
|
226
|
+
],
|
|
227
|
+
],
|
|
228
|
+
'nb-demo',
|
|
229
|
+
'🕸️',
|
|
230
|
+
null,
|
|
231
|
+
[1, false, true, null, null, [1774889558, 348721000], 1, false, [1774872161, 361922000], null, null, null, false, true, 1, false, null, true, 1],
|
|
232
|
+
],
|
|
233
|
+
]);
|
|
234
|
+
expect(rows).toEqual([
|
|
235
|
+
expect.objectContaining({
|
|
236
|
+
id: 'src-pdf',
|
|
237
|
+
type: 'pdf',
|
|
238
|
+
type_code: 3,
|
|
239
|
+
}),
|
|
240
|
+
expect.objectContaining({
|
|
241
|
+
id: 'src-web',
|
|
242
|
+
type: 'web',
|
|
243
|
+
type_code: 5,
|
|
244
|
+
}),
|
|
245
|
+
expect.objectContaining({
|
|
246
|
+
id: 'src-yt',
|
|
247
|
+
type: 'youtube',
|
|
248
|
+
type_code: 9,
|
|
249
|
+
}),
|
|
250
|
+
]);
|
|
251
|
+
});
|
|
252
|
+
it('parses notebook history thread ids from hPTbtc payload', () => {
|
|
253
|
+
const threadIds = parseNotebooklmHistoryThreadIdsResult([
|
|
254
|
+
[[['28e0f2cb-4591-45a3-a661-7653666f7c78']]],
|
|
255
|
+
]);
|
|
256
|
+
expect(threadIds).toEqual(['28e0f2cb-4591-45a3-a661-7653666f7c78']);
|
|
257
|
+
});
|
|
258
|
+
it('extracts a notebook history preview from khqZz payload', () => {
|
|
259
|
+
const preview = extractNotebooklmHistoryPreview([
|
|
260
|
+
[
|
|
261
|
+
['28e0f2cb-4591-45a3-a661-7653666f7c78'],
|
|
262
|
+
[null, 'Summarize this notebook'],
|
|
263
|
+
],
|
|
264
|
+
]);
|
|
265
|
+
expect(preview).toBe('Summarize this notebook');
|
|
266
|
+
});
|
|
267
|
+
it('parses notebook notes from studio note rows', () => {
|
|
268
|
+
const rows = parseNotebooklmNoteListRawRows([
|
|
269
|
+
{
|
|
270
|
+
title: '新建笔记',
|
|
271
|
+
text: 'sticky_note_2 新建笔记 6 分钟前 more_vert',
|
|
272
|
+
},
|
|
273
|
+
], 'nb-demo', 'https://notebooklm.google.com/notebook/nb-demo');
|
|
274
|
+
expect(rows).toEqual([
|
|
275
|
+
{
|
|
276
|
+
notebook_id: 'nb-demo',
|
|
277
|
+
title: '新建笔记',
|
|
278
|
+
created_at: '6 分钟前',
|
|
279
|
+
url: 'https://notebooklm.google.com/notebook/nb-demo',
|
|
280
|
+
source: 'studio-list',
|
|
281
|
+
},
|
|
282
|
+
]);
|
|
283
|
+
});
|
|
284
|
+
it('parses source fulltext from hizoJc payload', () => {
|
|
285
|
+
const row = parseNotebooklmSourceFulltextResult([
|
|
286
|
+
[
|
|
287
|
+
[['src-1']],
|
|
288
|
+
'粘贴的文字',
|
|
289
|
+
[null, 359, [1774872183, 855096000], null, 8, null, 1, ['https://example.com/source']],
|
|
290
|
+
[null, 2],
|
|
291
|
+
],
|
|
292
|
+
null,
|
|
293
|
+
null,
|
|
294
|
+
[
|
|
295
|
+
[
|
|
296
|
+
[
|
|
297
|
+
[0, 5, [[[0, 5, ['第一段']]]]],
|
|
298
|
+
[5, 10, [[[5, 10, ['第二段']]]]],
|
|
299
|
+
],
|
|
300
|
+
],
|
|
301
|
+
],
|
|
302
|
+
], 'nb-demo', 'https://notebooklm.google.com/notebook/nb-demo');
|
|
303
|
+
expect(row).toEqual({
|
|
304
|
+
source_id: 'src-1',
|
|
305
|
+
notebook_id: 'nb-demo',
|
|
306
|
+
title: '粘贴的文字',
|
|
307
|
+
kind: 'pasted-text',
|
|
308
|
+
content: '第一段\n第二段',
|
|
309
|
+
char_count: 7,
|
|
310
|
+
url: 'https://example.com/source',
|
|
311
|
+
source: 'rpc',
|
|
312
|
+
});
|
|
313
|
+
});
|
|
314
|
+
it('parses source guide from tr032e payloads with either null or source-id envelope in slot 0', () => {
|
|
315
|
+
const source = {
|
|
316
|
+
id: 'src-yt',
|
|
317
|
+
notebook_id: 'nb-demo',
|
|
318
|
+
title: 'Video Source',
|
|
319
|
+
type: 'youtube',
|
|
320
|
+
};
|
|
321
|
+
expect(parseNotebooklmSourceGuideResult([
|
|
322
|
+
[
|
|
323
|
+
[
|
|
324
|
+
null,
|
|
325
|
+
['Guide summary'],
|
|
326
|
+
[['AI', 'agents']],
|
|
327
|
+
[],
|
|
328
|
+
],
|
|
329
|
+
],
|
|
330
|
+
], source)).toEqual({
|
|
331
|
+
source_id: 'src-yt',
|
|
332
|
+
notebook_id: 'nb-demo',
|
|
333
|
+
title: 'Video Source',
|
|
334
|
+
type: 'youtube',
|
|
335
|
+
summary: 'Guide summary',
|
|
336
|
+
keywords: ['AI', 'agents'],
|
|
337
|
+
source: 'rpc',
|
|
338
|
+
});
|
|
339
|
+
expect(parseNotebooklmSourceGuideResult([
|
|
340
|
+
[
|
|
341
|
+
[
|
|
342
|
+
[['src-yt']],
|
|
343
|
+
['Guide summary'],
|
|
344
|
+
[['AI', 'agents']],
|
|
345
|
+
[],
|
|
346
|
+
],
|
|
347
|
+
],
|
|
348
|
+
], source)).toEqual({
|
|
349
|
+
source_id: 'src-yt',
|
|
350
|
+
notebook_id: 'nb-demo',
|
|
351
|
+
title: 'Video Source',
|
|
352
|
+
type: 'youtube',
|
|
353
|
+
summary: 'Guide summary',
|
|
354
|
+
keywords: ['AI', 'agents'],
|
|
355
|
+
source: 'rpc',
|
|
356
|
+
});
|
|
357
|
+
});
|
|
358
|
+
it('prefers real NotebookLM page tokens over login text heuristics', async () => {
|
|
359
|
+
let call = 0;
|
|
360
|
+
const page = {
|
|
361
|
+
evaluate: async () => {
|
|
362
|
+
call += 1;
|
|
363
|
+
if (call === 1) {
|
|
364
|
+
return {
|
|
365
|
+
url: 'https://notebooklm.google.com/notebook/nb-demo',
|
|
366
|
+
title: 'Demo Notebook - NotebookLM',
|
|
367
|
+
hostname: 'notebooklm.google.com',
|
|
368
|
+
kind: 'notebook',
|
|
369
|
+
notebookId: 'nb-demo',
|
|
370
|
+
loginRequired: true,
|
|
371
|
+
notebookCount: 0,
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
return {
|
|
375
|
+
html: '<html>"SNlM0e":"csrf-123","FdrFJe":"sess-456"</html>',
|
|
376
|
+
sourcePath: '/notebook/nb-demo',
|
|
377
|
+
};
|
|
378
|
+
},
|
|
379
|
+
};
|
|
380
|
+
await expect(getNotebooklmPageState(page)).resolves.toEqual({
|
|
381
|
+
url: 'https://notebooklm.google.com/notebook/nb-demo',
|
|
382
|
+
title: 'Demo Notebook - NotebookLM',
|
|
383
|
+
hostname: 'notebooklm.google.com',
|
|
384
|
+
kind: 'notebook',
|
|
385
|
+
notebookId: 'nb-demo',
|
|
386
|
+
loginRequired: false,
|
|
387
|
+
notebookCount: 0,
|
|
388
|
+
});
|
|
389
|
+
});
|
|
390
|
+
});
|
|
@@ -2,3 +2,7 @@ import type { IPage } from '../../types.js';
|
|
|
2
2
|
export declare function buildSubstackBrowseUrl(category?: string): string;
|
|
3
3
|
export declare function loadSubstackFeed(page: IPage, url: string, limit: number): Promise<any[]>;
|
|
4
4
|
export declare function loadSubstackArchive(page: IPage, baseUrl: string, limit: number): Promise<any[]>;
|
|
5
|
+
export declare const __test__: {
|
|
6
|
+
FEED_POST_LINK_SELECTOR: string;
|
|
7
|
+
ARCHIVE_POST_LINK_SELECTOR: string;
|
|
8
|
+
};
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { CommandExecutionError } from '../../errors.js';
|
|
2
|
+
const FEED_POST_LINK_SELECTOR = 'a[href*="/home/post/"], a[href*="/p/"]';
|
|
3
|
+
const ARCHIVE_POST_LINK_SELECTOR = 'a[href*="/p/"]';
|
|
2
4
|
export function buildSubstackBrowseUrl(category) {
|
|
3
5
|
if (!category || category === 'all')
|
|
4
6
|
return 'https://substack.com/';
|
|
@@ -9,7 +11,7 @@ export async function loadSubstackFeed(page, url, limit) {
|
|
|
9
11
|
if (!page)
|
|
10
12
|
throw new CommandExecutionError('Browser session required for substack feed');
|
|
11
13
|
await page.goto(url);
|
|
12
|
-
await page.wait({ selector:
|
|
14
|
+
await page.wait({ selector: FEED_POST_LINK_SELECTOR, timeout: 5 });
|
|
13
15
|
const data = await page.evaluate(`
|
|
14
16
|
(async () => {
|
|
15
17
|
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
@@ -77,7 +79,7 @@ export async function loadSubstackArchive(page, baseUrl, limit) {
|
|
|
77
79
|
if (!page)
|
|
78
80
|
throw new CommandExecutionError('Browser session required for substack archive');
|
|
79
81
|
await page.goto(`${baseUrl}/archive`);
|
|
80
|
-
await page.wait({ selector:
|
|
82
|
+
await page.wait({ selector: ARCHIVE_POST_LINK_SELECTOR, timeout: 5 });
|
|
81
83
|
const data = await page.evaluate(`
|
|
82
84
|
(async () => {
|
|
83
85
|
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
@@ -128,3 +130,7 @@ export async function loadSubstackArchive(page, baseUrl, limit) {
|
|
|
128
130
|
`);
|
|
129
131
|
return Array.isArray(data) ? data : [];
|
|
130
132
|
}
|
|
133
|
+
export const __test__ = {
|
|
134
|
+
FEED_POST_LINK_SELECTOR,
|
|
135
|
+
ARCHIVE_POST_LINK_SELECTOR,
|
|
136
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { __test__, loadSubstackArchive, loadSubstackFeed } from './utils.js';
|
|
3
|
+
function createPageMock(evaluateResult) {
|
|
4
|
+
return {
|
|
5
|
+
goto: vi.fn().mockResolvedValue(undefined),
|
|
6
|
+
evaluate: vi.fn().mockResolvedValue(evaluateResult),
|
|
7
|
+
snapshot: vi.fn().mockResolvedValue(undefined),
|
|
8
|
+
click: vi.fn().mockResolvedValue(undefined),
|
|
9
|
+
typeText: vi.fn().mockResolvedValue(undefined),
|
|
10
|
+
pressKey: vi.fn().mockResolvedValue(undefined),
|
|
11
|
+
scrollTo: vi.fn().mockResolvedValue(undefined),
|
|
12
|
+
getFormState: vi.fn().mockResolvedValue({}),
|
|
13
|
+
wait: vi.fn().mockResolvedValue(undefined),
|
|
14
|
+
tabs: vi.fn().mockResolvedValue([]),
|
|
15
|
+
closeTab: vi.fn().mockResolvedValue(undefined),
|
|
16
|
+
newTab: vi.fn().mockResolvedValue(undefined),
|
|
17
|
+
selectTab: vi.fn().mockResolvedValue(undefined),
|
|
18
|
+
networkRequests: vi.fn().mockResolvedValue([]),
|
|
19
|
+
consoleMessages: vi.fn().mockResolvedValue([]),
|
|
20
|
+
scroll: vi.fn().mockResolvedValue(undefined),
|
|
21
|
+
autoScroll: vi.fn().mockResolvedValue(undefined),
|
|
22
|
+
installInterceptor: vi.fn().mockResolvedValue(undefined),
|
|
23
|
+
getInterceptedRequests: vi.fn().mockResolvedValue([]),
|
|
24
|
+
getCookies: vi.fn().mockResolvedValue([]),
|
|
25
|
+
screenshot: vi.fn().mockResolvedValue(''),
|
|
26
|
+
waitForCapture: vi.fn().mockResolvedValue(undefined),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
describe('substack utils wait selectors', () => {
|
|
30
|
+
it('waits for both feed link shapes before scraping the feed', async () => {
|
|
31
|
+
const page = createPageMock([]);
|
|
32
|
+
await loadSubstackFeed(page, 'https://substack.com/', 5);
|
|
33
|
+
expect(page.wait).toHaveBeenCalledWith({
|
|
34
|
+
selector: __test__.FEED_POST_LINK_SELECTOR,
|
|
35
|
+
timeout: 5,
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
it('waits for archive post links before scraping archive pages', async () => {
|
|
39
|
+
const page = createPageMock([]);
|
|
40
|
+
await loadSubstackArchive(page, 'https://example.substack.com', 5);
|
|
41
|
+
expect(page.wait).toHaveBeenCalledWith({
|
|
42
|
+
selector: __test__.ARCHIVE_POST_LINK_SELECTOR,
|
|
43
|
+
timeout: 5,
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
});
|
package/dist/clis/v2ex/hot.yaml
CHANGED
|
@@ -16,10 +16,13 @@ pipeline:
|
|
|
16
16
|
url: https://www.v2ex.com/api/topics/hot.json
|
|
17
17
|
|
|
18
18
|
- map:
|
|
19
|
+
id: ${{ item.id }}
|
|
19
20
|
rank: ${{ index + 1 }}
|
|
20
21
|
title: ${{ item.title }}
|
|
22
|
+
node: ${{ item.node.title }}
|
|
21
23
|
replies: ${{ item.replies }}
|
|
24
|
+
url: ${{ item.url }}
|
|
22
25
|
|
|
23
26
|
- limit: ${{ args.limit }}
|
|
24
27
|
|
|
25
|
-
columns: [rank, title, replies]
|
|
28
|
+
columns: [id, rank, title, node, replies, url]
|
|
@@ -16,10 +16,13 @@ pipeline:
|
|
|
16
16
|
url: https://www.v2ex.com/api/topics/latest.json
|
|
17
17
|
|
|
18
18
|
- map:
|
|
19
|
+
id: ${{ item.id }}
|
|
19
20
|
rank: ${{ index + 1 }}
|
|
20
21
|
title: ${{ item.title }}
|
|
22
|
+
node: ${{ item.node.title }}
|
|
21
23
|
replies: ${{ item.replies }}
|
|
24
|
+
url: ${{ item.url }}
|
|
22
25
|
|
|
23
26
|
- limit: ${{ args.limit }}
|
|
24
27
|
|
|
25
|
-
columns: [rank, title, replies]
|
|
28
|
+
columns: [id, rank, title, node, replies, url]
|
|
@@ -19,10 +19,15 @@ pipeline:
|
|
|
19
19
|
id: ${{ args.id }}
|
|
20
20
|
|
|
21
21
|
- map:
|
|
22
|
+
id: ${{ item.id }}
|
|
22
23
|
title: ${{ item.title }}
|
|
24
|
+
content: ${{ item.content }}
|
|
25
|
+
member: ${{ item.member.username }}
|
|
26
|
+
created: ${{ item.created }}
|
|
27
|
+
node: ${{ item.node.title }}
|
|
23
28
|
replies: ${{ item.replies }}
|
|
24
29
|
url: ${{ item.url }}
|
|
25
30
|
|
|
26
31
|
- limit: 1
|
|
27
32
|
|
|
28
|
-
columns: [title, replies, url]
|
|
33
|
+
columns: [id, title, content, member, created, node, replies, url]
|
|
@@ -23,7 +23,16 @@ export declare function extractWechatCreateTimeValue(htmlStr: string): string;
|
|
|
23
23
|
* Extract the publish time from DOM text first, then fall back to numeric create_time values.
|
|
24
24
|
*/
|
|
25
25
|
export declare function extractWechatPublishTime(publishTimeText: string | null | undefined, htmlStr: string): string;
|
|
26
|
+
/**
|
|
27
|
+
* Detect WeChat anti-bot / verification gate pages before we try to parse the article.
|
|
28
|
+
*/
|
|
29
|
+
export declare function detectWechatAccessIssue(pageText: string | null | undefined, htmlStr: string): string;
|
|
30
|
+
export declare function pickFirstWechatMetaText(...candidates: Array<string | null | undefined>): string;
|
|
26
31
|
/**
|
|
27
32
|
* Build a self-contained helper for execution inside page.evaluate().
|
|
28
33
|
*/
|
|
29
34
|
export declare function buildExtractWechatPublishTimeJs(): string;
|
|
35
|
+
/**
|
|
36
|
+
* Build a self-contained access-issue detector for execution inside page.evaluate().
|
|
37
|
+
*/
|
|
38
|
+
export declare function buildDetectWechatAccessIssueJs(): string;
|
|
@@ -89,6 +89,28 @@ export function extractWechatPublishTime(publishTimeText, htmlStr) {
|
|
|
89
89
|
return '';
|
|
90
90
|
return formatWechatTimestamp(rawCreateTime);
|
|
91
91
|
}
|
|
92
|
+
/**
|
|
93
|
+
* Detect WeChat anti-bot / verification gate pages before we try to parse the article.
|
|
94
|
+
*/
|
|
95
|
+
export function detectWechatAccessIssue(pageText, htmlStr) {
|
|
96
|
+
const normalizedText = (pageText || '').replace(/\s+/g, ' ').trim();
|
|
97
|
+
if (/环境异常/.test(normalizedText) &&
|
|
98
|
+
/(完成验证后即可继续访问|去验证)/.test(normalizedText)) {
|
|
99
|
+
return 'environment verification required';
|
|
100
|
+
}
|
|
101
|
+
if (/secitptpage\/verify\.html/.test(htmlStr) || /id=["']js_verify["']/.test(htmlStr)) {
|
|
102
|
+
return 'environment verification required';
|
|
103
|
+
}
|
|
104
|
+
return '';
|
|
105
|
+
}
|
|
106
|
+
export function pickFirstWechatMetaText(...candidates) {
|
|
107
|
+
for (const candidate of candidates) {
|
|
108
|
+
const normalized = (candidate || '').replace(/\s+/g, ' ').trim();
|
|
109
|
+
if (normalized && normalized !== 'Name cleared')
|
|
110
|
+
return normalized;
|
|
111
|
+
}
|
|
112
|
+
return '';
|
|
113
|
+
}
|
|
92
114
|
/**
|
|
93
115
|
* Build a self-contained helper for execution inside page.evaluate().
|
|
94
116
|
*/
|
|
@@ -127,6 +149,22 @@ export function buildExtractWechatPublishTimeJs() {
|
|
|
127
149
|
return formatWechatTimestamp(rawCreateTime);
|
|
128
150
|
}.toString()})`;
|
|
129
151
|
}
|
|
152
|
+
/**
|
|
153
|
+
* Build a self-contained access-issue detector for execution inside page.evaluate().
|
|
154
|
+
*/
|
|
155
|
+
export function buildDetectWechatAccessIssueJs() {
|
|
156
|
+
return `(${function detectWechatAccessIssueInPage(pageText, htmlStr) {
|
|
157
|
+
const normalizedText = (pageText || '').replace(/\s+/g, ' ').trim();
|
|
158
|
+
if (/环境异常/.test(normalizedText) &&
|
|
159
|
+
/(完成验证后即可继续访问|去验证)/.test(normalizedText)) {
|
|
160
|
+
return 'environment verification required';
|
|
161
|
+
}
|
|
162
|
+
if (/secitptpage\/verify\.html/.test(htmlStr) || /id=["']js_verify["']/.test(htmlStr)) {
|
|
163
|
+
return 'environment verification required';
|
|
164
|
+
}
|
|
165
|
+
return '';
|
|
166
|
+
}.toString()})`;
|
|
167
|
+
}
|
|
130
168
|
// ============================================================
|
|
131
169
|
// CLI Registration
|
|
132
170
|
// ============================================================
|
|
@@ -158,18 +196,34 @@ cli({
|
|
|
158
196
|
title: '',
|
|
159
197
|
author: '',
|
|
160
198
|
publishTime: '',
|
|
199
|
+
errorHint: '',
|
|
161
200
|
contentHtml: '',
|
|
162
201
|
codeBlocks: [],
|
|
163
202
|
imageUrls: []
|
|
164
203
|
};
|
|
165
204
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
205
|
+
const pickFirstText = (...selectors) => {
|
|
206
|
+
for (const selector of selectors) {
|
|
207
|
+
const text = document.querySelector(selector)?.textContent?.replace(/\\s+/g, ' ').trim() || '';
|
|
208
|
+
if (text && text !== 'Name cleared') return text;
|
|
209
|
+
}
|
|
210
|
+
return '';
|
|
211
|
+
};
|
|
169
212
|
|
|
170
|
-
//
|
|
171
|
-
|
|
172
|
-
|
|
213
|
+
// WeChat has multiple article templates. Newer pages use #js_text_title.
|
|
214
|
+
result.title = pickFirstText(
|
|
215
|
+
'#activity-name',
|
|
216
|
+
'#js_text_title',
|
|
217
|
+
'.rich_media_title',
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
result.author = pickFirstText(
|
|
221
|
+
'#js_name',
|
|
222
|
+
'.wx_follow_nickname',
|
|
223
|
+
'#profileBt .profile_nickname',
|
|
224
|
+
'.rich_media_meta.rich_media_meta_nickname',
|
|
225
|
+
'.rich_media_meta_nickname',
|
|
226
|
+
);
|
|
173
227
|
|
|
174
228
|
// Publish time: prefer the rendered DOM text, then fall back to numeric create_time values.
|
|
175
229
|
const publishTimeEl = document.querySelector('#publish_time');
|
|
@@ -179,6 +233,13 @@ cli({
|
|
|
179
233
|
document.documentElement.innerHTML,
|
|
180
234
|
);
|
|
181
235
|
|
|
236
|
+
const detectWechatAccessIssue = ${buildDetectWechatAccessIssueJs()};
|
|
237
|
+
result.errorHint = detectWechatAccessIssue(
|
|
238
|
+
document.body ? document.body.innerText : '',
|
|
239
|
+
document.documentElement.innerHTML,
|
|
240
|
+
);
|
|
241
|
+
if (result.errorHint) return result;
|
|
242
|
+
|
|
182
243
|
// Content processing
|
|
183
244
|
const contentEl = document.querySelector('#js_content');
|
|
184
245
|
if (!contentEl) return result;
|
|
@@ -229,6 +290,15 @@ cli({
|
|
|
229
290
|
return result;
|
|
230
291
|
})()
|
|
231
292
|
`);
|
|
293
|
+
if (data?.errorHint === 'environment verification required') {
|
|
294
|
+
return [{
|
|
295
|
+
title: 'Error',
|
|
296
|
+
author: '-',
|
|
297
|
+
publish_time: '-',
|
|
298
|
+
status: 'failed — verification required in WeChat browser page',
|
|
299
|
+
size: '-',
|
|
300
|
+
}];
|
|
301
|
+
}
|
|
232
302
|
return downloadArticle({
|
|
233
303
|
title: data?.title || '',
|
|
234
304
|
author: data?.author,
|