@jackwener/opencli 1.6.1 → 1.6.2
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/CONTRIBUTING.md +1 -1
- package/README.md +27 -45
- package/README.zh-CN.md +32 -34
- package/autoresearch/browse-tasks.json +18 -20
- package/autoresearch/commands/debug.ts +163 -0
- package/autoresearch/commands/fix.ts +145 -0
- package/autoresearch/commands/plan.ts +88 -0
- package/autoresearch/commands/run.ts +138 -0
- package/autoresearch/config.ts +82 -0
- package/autoresearch/engine.ts +359 -0
- package/autoresearch/eval-all.ts +127 -0
- package/autoresearch/eval-browse.ts +1 -1
- package/autoresearch/eval-publish.ts +238 -0
- package/autoresearch/eval-save.ts +249 -0
- package/autoresearch/eval-skill.ts +14 -8
- package/autoresearch/eval-v2ex.ts +220 -0
- package/autoresearch/eval-zhihu.ts +230 -0
- package/autoresearch/logger.ts +69 -0
- package/autoresearch/presets/combined-reliability.ts +27 -0
- package/autoresearch/presets/index.ts +23 -0
- package/autoresearch/presets/operate-reliability.ts +24 -0
- package/autoresearch/presets/save-reliability.ts +26 -0
- package/autoresearch/presets/skill-quality.ts +20 -0
- package/autoresearch/presets/v2ex-reliability.ts +24 -0
- package/autoresearch/presets/zhihu-reliability.ts +25 -0
- package/autoresearch/publish-tasks.json +345 -0
- package/autoresearch/run-save.sh +11 -0
- package/autoresearch/save-adapters/xhs-explore-deep.ts +64 -0
- package/autoresearch/save-adapters/xhs-note-comments.ts +61 -0
- package/autoresearch/save-adapters/xhs-search-full.ts +62 -0
- package/autoresearch/save-adapters/zhihu-hot-detail.ts +52 -0
- package/autoresearch/save-adapters/zhihu-question-full.ts +57 -0
- package/autoresearch/save-adapters/zhihu-search-detail.ts +53 -0
- package/autoresearch/save-tasks.json +281 -0
- package/autoresearch/v2ex-tasks.json +899 -0
- package/autoresearch/zhihu-tasks.json +848 -0
- package/dist/browser/base-page.d.ts +4 -2
- package/dist/browser/base-page.js +37 -4
- package/dist/browser/bridge.js +10 -8
- package/dist/browser/cdp.js +2 -6
- package/dist/browser/daemon-client.d.ts +11 -1
- package/dist/browser/daemon-client.js +3 -0
- package/dist/browser/dom-helpers.d.ts +4 -2
- package/dist/browser/dom-helpers.js +42 -31
- package/dist/browser/dom-snapshot.js +23 -1
- package/dist/browser/page.d.ts +7 -2
- package/dist/browser/page.js +112 -30
- package/dist/browser.test.js +1 -1
- package/dist/build-manifest.d.ts +1 -0
- package/dist/build-manifest.js +1 -0
- package/dist/cli-manifest.json +1135 -184
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +48 -7
- package/dist/cli.test.d.ts +1 -0
- package/dist/cli.test.js +88 -0
- package/dist/clis/1688/item.d.ts +70 -0
- package/dist/clis/1688/item.js +187 -0
- package/dist/clis/1688/item.test.d.ts +1 -0
- package/dist/clis/1688/item.test.js +67 -0
- package/dist/clis/1688/search.d.ts +56 -0
- package/dist/clis/1688/search.js +309 -0
- package/dist/clis/1688/search.test.d.ts +1 -0
- package/dist/clis/1688/search.test.js +75 -0
- package/dist/clis/1688/shared.d.ts +112 -0
- package/dist/clis/1688/shared.js +514 -0
- package/dist/clis/1688/shared.test.d.ts +1 -0
- package/dist/clis/1688/shared.test.js +57 -0
- package/dist/clis/1688/store.d.ts +45 -0
- package/dist/clis/1688/store.js +226 -0
- package/dist/clis/1688/store.test.d.ts +1 -0
- package/dist/clis/1688/store.test.js +62 -0
- package/dist/clis/amazon/bestsellers.d.ts +0 -20
- package/dist/clis/amazon/bestsellers.js +6 -129
- package/dist/clis/amazon/bestsellers.test.js +12 -3
- package/dist/clis/amazon/movers-shakers.d.ts +1 -0
- package/dist/clis/amazon/movers-shakers.js +7 -0
- package/dist/clis/amazon/new-releases.d.ts +1 -0
- package/dist/clis/amazon/new-releases.js +7 -0
- package/dist/clis/amazon/rankings.d.ts +59 -0
- package/dist/clis/amazon/rankings.js +226 -0
- package/dist/clis/amazon/rankings.test.d.ts +1 -0
- package/dist/clis/amazon/rankings.test.js +41 -0
- package/dist/clis/amazon/shared.d.ts +11 -0
- package/dist/clis/amazon/shared.js +121 -11
- package/dist/clis/amazon/shared.test.js +11 -0
- package/dist/clis/bilibili/comments.js +2 -2
- package/dist/clis/bilibili/comments.test.js +3 -2
- package/dist/clis/bilibili/download.js +2 -1
- package/dist/clis/bilibili/subtitle.js +4 -3
- package/dist/clis/bilibili/subtitle.test.js +2 -1
- package/dist/clis/bilibili/utils.d.ts +5 -0
- package/dist/clis/bilibili/utils.js +30 -0
- package/dist/clis/bilibili/utils.test.d.ts +1 -0
- package/dist/clis/bilibili/utils.test.js +17 -0
- package/dist/clis/douban/marks.js +1 -1
- package/dist/clis/douban/subject.yaml +50 -19
- package/dist/clis/doubao/utils.js +32 -12
- package/dist/clis/douyin/_shared/browser-fetch.test.js +0 -1
- package/dist/clis/douyin/_shared/transcode.test.js +0 -2
- package/dist/clis/douyin/draft.test.js +0 -2
- package/dist/clis/facebook/search.test.js +0 -2
- package/dist/clis/gemini/ask.js +9 -3
- package/dist/clis/gemini/ask.test.d.ts +1 -0
- package/dist/clis/gemini/ask.test.js +100 -0
- package/dist/clis/gemini/reply-state.test.d.ts +1 -0
- package/dist/clis/gemini/reply-state.test.js +641 -0
- package/dist/clis/gemini/utils.d.ts +44 -1
- package/dist/clis/gemini/utils.js +528 -61
- package/dist/clis/gemini/utils.test.js +149 -2
- package/dist/clis/hupu/detail.d.ts +1 -0
- package/dist/clis/hupu/detail.js +72 -0
- package/dist/clis/hupu/hot.yaml +43 -0
- package/dist/clis/hupu/like.d.ts +1 -0
- package/dist/clis/hupu/like.js +75 -0
- package/dist/clis/hupu/reply.d.ts +1 -0
- package/dist/clis/hupu/reply.js +71 -0
- package/dist/clis/hupu/search.d.ts +1 -0
- package/dist/clis/hupu/search.js +59 -0
- package/dist/clis/hupu/unlike.d.ts +1 -0
- package/dist/clis/hupu/unlike.js +75 -0
- package/dist/clis/hupu/utils.d.ts +20 -0
- package/dist/clis/hupu/utils.js +319 -0
- package/dist/clis/instagram/_shared/private-publish.d.ts +138 -0
- package/dist/clis/instagram/_shared/private-publish.js +1030 -0
- package/dist/clis/instagram/_shared/private-publish.test.d.ts +1 -0
- package/dist/clis/instagram/_shared/private-publish.test.js +705 -0
- package/dist/clis/instagram/_shared/protocol-capture.d.ts +26 -0
- package/dist/clis/instagram/_shared/protocol-capture.js +282 -0
- package/dist/clis/instagram/_shared/protocol-capture.test.d.ts +1 -0
- package/dist/clis/instagram/_shared/protocol-capture.test.js +114 -0
- package/dist/clis/instagram/_shared/runtime-info.d.ts +9 -0
- package/dist/clis/instagram/_shared/runtime-info.js +81 -0
- package/dist/clis/instagram/note.d.ts +1 -0
- package/dist/clis/instagram/note.js +222 -0
- package/dist/clis/instagram/note.test.d.ts +1 -0
- package/dist/clis/instagram/note.test.js +81 -0
- package/dist/clis/instagram/post.d.ts +4 -0
- package/dist/clis/instagram/post.js +1496 -0
- package/dist/clis/instagram/post.test.d.ts +1 -0
- package/dist/clis/instagram/post.test.js +1647 -0
- package/dist/clis/instagram/reel.d.ts +1 -0
- package/dist/clis/instagram/reel.js +826 -0
- package/dist/clis/instagram/reel.test.d.ts +1 -0
- package/dist/clis/instagram/reel.test.js +167 -0
- package/dist/clis/instagram/story.d.ts +1 -0
- package/dist/clis/instagram/story.js +115 -0
- package/dist/clis/instagram/story.test.d.ts +1 -0
- package/dist/clis/instagram/story.test.js +167 -0
- package/dist/clis/sinafinance/stock-rank.d.ts +4 -0
- package/dist/clis/sinafinance/stock-rank.js +65 -0
- package/dist/clis/substack/utils.test.js +0 -2
- package/dist/clis/twitter/post.js +72 -45
- package/dist/clis/twitter/post.test.d.ts +1 -0
- package/dist/clis/twitter/post.test.js +116 -0
- package/dist/clis/twitter/reply.d.ts +12 -0
- package/dist/clis/twitter/reply.js +257 -35
- package/dist/clis/twitter/reply.test.d.ts +1 -0
- package/dist/clis/twitter/reply.test.js +151 -0
- package/dist/clis/xianyu/chat.d.ts +7 -0
- package/dist/clis/xianyu/chat.js +146 -0
- package/dist/clis/xianyu/chat.test.d.ts +1 -0
- package/dist/clis/xianyu/chat.test.js +15 -0
- package/dist/clis/xianyu/item.d.ts +7 -0
- package/dist/clis/xianyu/item.js +152 -0
- package/dist/clis/xianyu/item.test.d.ts +1 -0
- package/dist/clis/xianyu/item.test.js +56 -0
- package/dist/clis/xianyu/search.d.ts +10 -0
- package/dist/clis/xianyu/search.js +134 -0
- package/dist/clis/xianyu/search.test.d.ts +1 -0
- package/dist/clis/xianyu/search.test.js +17 -0
- package/dist/clis/xianyu/utils.d.ts +1 -0
- package/dist/clis/xianyu/utils.js +8 -0
- package/dist/clis/xiaoe/catalog.yaml +129 -0
- package/dist/clis/xiaoe/content.yaml +43 -0
- package/dist/clis/xiaoe/courses.yaml +73 -0
- package/dist/clis/xiaoe/detail.yaml +39 -0
- package/dist/clis/xiaoe/play-url.yaml +124 -0
- package/dist/clis/xiaohongshu/comments.test.js +0 -2
- package/dist/clis/xiaohongshu/creator-note-detail.test.js +0 -2
- package/dist/clis/xiaohongshu/creator-notes.test.js +0 -2
- package/dist/clis/xiaohongshu/download.test.js +0 -2
- package/dist/clis/xiaohongshu/note.test.js +0 -2
- package/dist/clis/xiaohongshu/publish.test.js +0 -2
- package/dist/clis/xiaohongshu/search.js +29 -20
- package/dist/clis/xiaohongshu/search.test.js +56 -48
- package/dist/clis/yuanbao/ask.d.ts +21 -0
- package/dist/clis/yuanbao/ask.js +427 -0
- package/dist/clis/yuanbao/ask.test.d.ts +1 -0
- package/dist/clis/yuanbao/ask.test.js +124 -0
- package/dist/clis/yuanbao/new.d.ts +1 -0
- package/dist/clis/yuanbao/new.js +70 -0
- package/dist/clis/yuanbao/new.test.d.ts +1 -0
- package/dist/clis/yuanbao/new.test.js +30 -0
- package/dist/clis/yuanbao/shared.d.ts +13 -0
- package/dist/clis/yuanbao/shared.js +49 -0
- package/dist/clis/zhihu/question.js +30 -19
- package/dist/clis/zhihu/question.test.js +34 -16
- package/dist/commanderAdapter.js +8 -4
- package/dist/commanderAdapter.test.js +42 -0
- package/dist/completion.js +3 -1
- package/dist/completion.test.d.ts +1 -0
- package/dist/completion.test.js +23 -0
- package/dist/doctor.js +1 -1
- package/dist/electron-apps.d.ts +2 -0
- package/dist/electron-apps.js +7 -1
- package/dist/errors.js +1 -1
- package/dist/execution.js +25 -35
- package/dist/explore.js +1 -1
- package/dist/launcher.d.ts +4 -0
- package/dist/launcher.js +64 -8
- package/dist/launcher.test.js +88 -7
- package/dist/output.d.ts +2 -0
- package/dist/output.js +10 -1
- package/dist/output.test.d.ts +0 -3
- package/dist/output.test.js +59 -92
- package/dist/pipeline/executor.test.js +0 -2
- package/dist/pipeline/steps/download.test.js +0 -2
- package/dist/registry.d.ts +2 -0
- package/dist/serialization.d.ts +1 -0
- package/dist/serialization.js +1 -0
- package/dist/types.d.ts +9 -2
- package/docs/.vitepress/config.mts +4 -0
- package/docs/adapters/browser/1688.md +52 -0
- package/docs/adapters/browser/36kr.md +2 -1
- package/docs/adapters/browser/doubao.md +5 -1
- package/docs/adapters/browser/hupu.md +53 -0
- package/docs/adapters/browser/sinafinance.md +32 -2
- package/docs/adapters/browser/weibo.md +6 -1
- package/docs/adapters/browser/wikipedia.md +2 -0
- package/docs/adapters/browser/xianyu.md +42 -0
- package/docs/adapters/browser/xiaoe.md +44 -0
- package/docs/adapters/browser/yuanbao.md +64 -0
- package/docs/adapters/index.md +14 -5
- package/docs/comparison.md +1 -1
- package/docs/developer/ai-workflow.md +2 -2
- package/docs/developer/contributing.md +1 -1
- package/docs/developer/testing.md +2 -0
- package/docs/guide/plugins.md +1 -0
- package/docs/guide/troubleshooting.md +11 -0
- package/docs/superpowers/specs/2026-04-03-v2ex-autoresearch-design.md +41 -0
- package/docs/zh/guide/plugins.md +1 -0
- package/extension/dist/background.js +1127 -0
- package/extension/src/background.test.ts +39 -0
- package/extension/src/background.ts +223 -34
- package/extension/src/cdp.ts +194 -4
- package/extension/src/protocol.ts +22 -1
- package/package.json +3 -2
- package/scripts/postinstall.js +1 -1
- package/skills/opencli-explorer/SKILL.md +1 -1
- package/skills/opencli-oneshot/SKILL.md +2 -2
- package/skills/opencli-operate/SKILL.md +120 -27
- package/skills/opencli-usage/SKILL.md +31 -20
- package/skills/opencli-usage/browser.md +114 -16
- package/skills/opencli-usage/public-api.md +32 -3
- package/skills/smart-search/SKILL.md +156 -0
- package/skills/smart-search/references/sources-ai.md +74 -0
- package/skills/smart-search/references/sources-info.md +43 -0
- package/skills/smart-search/references/sources-media.md +50 -0
- package/skills/smart-search/references/sources-other.md +42 -0
- package/skills/smart-search/references/sources-shopping.md +31 -0
- package/skills/smart-search/references/sources-social.md +51 -0
- package/skills/smart-search/references/sources-tech.md +42 -0
- package/skills/smart-search/references/sources-travel.md +20 -0
- package/src/browser/base-page.ts +41 -6
- package/src/browser/bridge.ts +11 -8
- package/src/browser/cdp.ts +1 -8
- package/src/browser/daemon-client.ts +11 -1
- package/src/browser/dom-helpers.ts +43 -31
- package/src/browser/dom-snapshot.ts +23 -1
- package/src/browser/page.ts +115 -31
- package/src/browser.test.ts +1 -1
- package/src/build-manifest.ts +2 -0
- package/src/cli.test.ts +133 -0
- package/src/cli.ts +73 -11
- package/src/clis/1688/item.test.ts +69 -0
- package/src/clis/1688/item.ts +282 -0
- package/src/clis/1688/search.test.ts +81 -0
- package/src/clis/1688/search.ts +402 -0
- package/src/clis/1688/shared.test.ts +75 -0
- package/src/clis/1688/shared.ts +623 -0
- package/src/clis/1688/store.test.ts +69 -0
- package/src/clis/1688/store.ts +300 -0
- package/src/clis/amazon/bestsellers.test.ts +12 -3
- package/src/clis/amazon/bestsellers.ts +6 -178
- package/src/clis/amazon/movers-shakers.ts +8 -0
- package/src/clis/amazon/new-releases.ts +8 -0
- package/src/clis/amazon/rankings.test.ts +47 -0
- package/src/clis/amazon/rankings.ts +312 -0
- package/src/clis/amazon/shared.test.ts +16 -0
- package/src/clis/amazon/shared.ts +134 -12
- package/src/clis/bilibili/comments.test.ts +4 -3
- package/src/clis/bilibili/comments.ts +2 -2
- package/src/clis/bilibili/download.ts +2 -1
- package/src/clis/bilibili/subtitle.test.ts +2 -1
- package/src/clis/bilibili/subtitle.ts +4 -3
- package/src/clis/bilibili/utils.test.ts +21 -0
- package/src/clis/bilibili/utils.ts +27 -0
- package/src/clis/douban/marks.ts +1 -1
- package/src/clis/douban/subject.yaml +50 -19
- package/src/clis/doubao/utils.ts +32 -12
- package/src/clis/douyin/_shared/browser-fetch.test.ts +0 -1
- package/src/clis/douyin/_shared/transcode.test.ts +0 -2
- package/src/clis/douyin/draft.test.ts +0 -2
- package/src/clis/facebook/search.test.ts +0 -2
- package/src/clis/gemini/ask.test.ts +116 -0
- package/src/clis/gemini/ask.ts +10 -3
- package/src/clis/gemini/reply-state.test.ts +708 -0
- package/src/clis/gemini/utils.test.ts +184 -2
- package/src/clis/gemini/utils.ts +588 -60
- package/src/clis/hupu/detail.ts +126 -0
- package/src/clis/hupu/hot.yaml +43 -0
- package/src/clis/hupu/like.ts +76 -0
- package/src/clis/hupu/reply.ts +76 -0
- package/src/clis/hupu/search.ts +95 -0
- package/src/clis/hupu/unlike.ts +76 -0
- package/src/clis/hupu/utils.ts +381 -0
- package/src/clis/instagram/_shared/private-publish.test.ts +827 -0
- package/src/clis/instagram/_shared/private-publish.ts +1303 -0
- package/src/clis/instagram/_shared/protocol-capture.test.ts +148 -0
- package/src/clis/instagram/_shared/protocol-capture.ts +321 -0
- package/src/clis/instagram/_shared/runtime-info.ts +91 -0
- package/src/clis/instagram/note.test.ts +96 -0
- package/src/clis/instagram/note.ts +254 -0
- package/src/clis/instagram/post.test.ts +1716 -0
- package/src/clis/instagram/post.ts +1620 -0
- package/src/clis/instagram/reel.test.ts +191 -0
- package/src/clis/instagram/reel.ts +886 -0
- package/src/clis/instagram/story.test.ts +191 -0
- package/src/clis/instagram/story.ts +151 -0
- package/src/clis/sinafinance/stock-rank.ts +68 -0
- package/src/clis/substack/utils.test.ts +0 -2
- package/src/clis/twitter/post.test.ts +157 -0
- package/src/clis/twitter/post.ts +82 -48
- package/src/clis/twitter/reply.test.ts +177 -0
- package/src/clis/twitter/reply.ts +285 -39
- package/src/clis/xianyu/chat.test.ts +20 -0
- package/src/clis/xianyu/chat.ts +175 -0
- package/src/clis/xianyu/item.test.ts +67 -0
- package/src/clis/xianyu/item.ts +172 -0
- package/src/clis/xianyu/search.test.ts +22 -0
- package/src/clis/xianyu/search.ts +151 -0
- package/src/clis/xianyu/utils.ts +9 -0
- package/src/clis/xiaoe/catalog.yaml +129 -0
- package/src/clis/xiaoe/content.yaml +43 -0
- package/src/clis/xiaoe/courses.yaml +73 -0
- package/src/clis/xiaoe/detail.yaml +39 -0
- package/src/clis/xiaoe/play-url.yaml +124 -0
- package/src/clis/xiaohongshu/comments.test.ts +0 -2
- package/src/clis/xiaohongshu/creator-note-detail.test.ts +0 -2
- package/src/clis/xiaohongshu/creator-notes.test.ts +0 -2
- package/src/clis/xiaohongshu/download.test.ts +0 -2
- package/src/clis/xiaohongshu/note.test.ts +0 -2
- package/src/clis/xiaohongshu/publish.test.ts +0 -2
- package/src/clis/xiaohongshu/search.test.ts +59 -48
- package/src/clis/xiaohongshu/search.ts +31 -21
- package/src/clis/yuanbao/ask.test.ts +156 -0
- package/src/clis/yuanbao/ask.ts +522 -0
- package/src/clis/yuanbao/new.test.ts +36 -0
- package/src/clis/yuanbao/new.ts +81 -0
- package/src/clis/yuanbao/shared.ts +57 -0
- package/src/clis/zhihu/question.test.ts +42 -17
- package/src/clis/zhihu/question.ts +31 -26
- package/src/commanderAdapter.test.ts +51 -0
- package/src/commanderAdapter.ts +8 -4
- package/src/completion.test.ts +30 -0
- package/src/completion.ts +3 -1
- package/src/doctor.ts +1 -1
- package/src/electron-apps.ts +9 -1
- package/src/errors.ts +1 -1
- package/src/execution.ts +26 -30
- package/src/explore.ts +1 -1
- package/src/launcher.test.ts +121 -7
- package/src/launcher.ts +87 -9
- package/src/output.test.ts +50 -90
- package/src/output.ts +10 -1
- package/src/pipeline/executor.test.ts +0 -2
- package/src/pipeline/steps/download.test.ts +0 -2
- package/src/registry.ts +2 -0
- package/src/serialization.ts +2 -0
- package/src/types.ts +9 -2
- package/tests/e2e/browser-auth.test.ts +9 -0
- package/CLI-EXPLORER.md +0 -724
- package/CLI-ONESHOT.md +0 -216
- package/SKILL.md +0 -59
|
@@ -7,6 +7,28 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { cli, Strategy } from '../../registry.js';
|
|
9
9
|
import { AuthRequiredError } from '../../errors.js';
|
|
10
|
+
/**
|
|
11
|
+
* Wait for search results or login wall using MutationObserver (max 5s).
|
|
12
|
+
* Returns 'content' if note items appeared, 'login_wall' if login gate
|
|
13
|
+
* detected, or 'timeout' if neither appeared within the deadline.
|
|
14
|
+
*/
|
|
15
|
+
const WAIT_FOR_CONTENT_JS = `
|
|
16
|
+
new Promise((resolve) => {
|
|
17
|
+
const detect = () => {
|
|
18
|
+
if (document.querySelector('section.note-item')) return 'content';
|
|
19
|
+
if (/登录后查看搜索结果/.test(document.body?.innerText || '')) return 'login_wall';
|
|
20
|
+
return null;
|
|
21
|
+
};
|
|
22
|
+
const found = detect();
|
|
23
|
+
if (found) return resolve(found);
|
|
24
|
+
const observer = new MutationObserver(() => {
|
|
25
|
+
const result = detect();
|
|
26
|
+
if (result) { observer.disconnect(); resolve(result); }
|
|
27
|
+
});
|
|
28
|
+
observer.observe(document.body, { childList: true, subtree: true });
|
|
29
|
+
setTimeout(() => { observer.disconnect(); resolve('timeout'); }, 5000);
|
|
30
|
+
})
|
|
31
|
+
`;
|
|
10
32
|
/**
|
|
11
33
|
* Extract approximate publish date from a Xiaohongshu note URL.
|
|
12
34
|
* XHS note IDs follow MongoDB ObjectID format where the first 8 hex
|
|
@@ -39,22 +61,17 @@ cli({
|
|
|
39
61
|
func: async (page, kwargs) => {
|
|
40
62
|
const keyword = encodeURIComponent(kwargs.query);
|
|
41
63
|
await page.goto(`https://www.xiaohongshu.com/search_result?keyword=${keyword}&source=web_search_result_notes`);
|
|
42
|
-
|
|
43
|
-
//
|
|
44
|
-
//
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
(() => /登录后查看搜索结果/.test(document.body?.innerText || ''))()
|
|
48
|
-
`);
|
|
49
|
-
if (loginCheck) {
|
|
64
|
+
// Wait for search results to render (or login wall to appear).
|
|
65
|
+
// Uses MutationObserver to resolve as soon as content appears,
|
|
66
|
+
// instead of a fixed delay + blind retry.
|
|
67
|
+
const waitResult = await page.evaluate(WAIT_FOR_CONTENT_JS);
|
|
68
|
+
if (waitResult === 'login_wall') {
|
|
50
69
|
throw new AuthRequiredError('www.xiaohongshu.com', 'Xiaohongshu search results are blocked behind a login wall');
|
|
51
70
|
}
|
|
52
71
|
// Scroll a couple of times to load more results
|
|
53
72
|
await page.autoScroll({ times: 2 });
|
|
54
73
|
const payload = await page.evaluate(`
|
|
55
74
|
(() => {
|
|
56
|
-
const loginWall = /登录后查看搜索结果/.test(document.body.innerText || '');
|
|
57
|
-
|
|
58
75
|
const normalizeUrl = (href) => {
|
|
59
76
|
if (!href) return '';
|
|
60
77
|
if (href.startsWith('http://') || href.startsWith('https://')) return href;
|
|
@@ -98,18 +115,10 @@ cli({
|
|
|
98
115
|
});
|
|
99
116
|
});
|
|
100
117
|
|
|
101
|
-
return
|
|
102
|
-
loginWall,
|
|
103
|
-
results,
|
|
104
|
-
};
|
|
118
|
+
return results;
|
|
105
119
|
})()
|
|
106
120
|
`);
|
|
107
|
-
|
|
108
|
-
return [];
|
|
109
|
-
if (payload.loginWall) {
|
|
110
|
-
throw new AuthRequiredError('www.xiaohongshu.com', 'Xiaohongshu search results are blocked behind a login wall');
|
|
111
|
-
}
|
|
112
|
-
const data = Array.isArray(payload.results) ? payload.results : [];
|
|
121
|
+
const data = Array.isArray(payload) ? payload : [];
|
|
113
122
|
return data
|
|
114
123
|
.filter((item) => item.title)
|
|
115
124
|
.slice(0, kwargs.limit)
|
|
@@ -17,8 +17,6 @@ function createPageMock(evaluateResults) {
|
|
|
17
17
|
getFormState: vi.fn().mockResolvedValue({ forms: [], orphanFields: [] }),
|
|
18
18
|
wait: vi.fn().mockResolvedValue(undefined),
|
|
19
19
|
tabs: vi.fn().mockResolvedValue([]),
|
|
20
|
-
closeTab: vi.fn().mockResolvedValue(undefined),
|
|
21
|
-
newTab: vi.fn().mockResolvedValue(undefined),
|
|
22
20
|
selectTab: vi.fn().mockResolvedValue(undefined),
|
|
23
21
|
networkRequests: vi.fn().mockResolvedValue([]),
|
|
24
22
|
consoleMessages: vi.fn().mockResolvedValue([]),
|
|
@@ -36,8 +34,8 @@ describe('xiaohongshu search', () => {
|
|
|
36
34
|
const cmd = getRegistry().get('xiaohongshu/search');
|
|
37
35
|
expect(cmd?.func).toBeTypeOf('function');
|
|
38
36
|
const page = createPageMock([
|
|
39
|
-
// First evaluate:
|
|
40
|
-
|
|
37
|
+
// First evaluate: MutationObserver wait (login wall detected)
|
|
38
|
+
'login_wall',
|
|
41
39
|
]);
|
|
42
40
|
await expect(cmd.func(page, { query: '特斯拉', limit: 5 })).rejects.toThrow('Xiaohongshu search results are blocked behind a login wall');
|
|
43
41
|
// autoScroll must NOT be called when a login wall is detected early
|
|
@@ -49,21 +47,18 @@ describe('xiaohongshu search', () => {
|
|
|
49
47
|
const detailUrl = 'https://www.xiaohongshu.com/search_result/68e90be80000000004022e66?xsec_token=test-token&xsec_source=';
|
|
50
48
|
const authorUrl = 'https://www.xiaohongshu.com/user/profile/635a9c720000000018028b40?xsec_token=user-token&xsec_source=pc_search';
|
|
51
49
|
const page = createPageMock([
|
|
52
|
-
// First evaluate:
|
|
53
|
-
|
|
54
|
-
// Second evaluate: main DOM extraction
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
},
|
|
65
|
-
],
|
|
66
|
-
},
|
|
50
|
+
// First evaluate: MutationObserver wait (content appeared)
|
|
51
|
+
'content',
|
|
52
|
+
// Second evaluate: main DOM extraction (returns array directly)
|
|
53
|
+
[
|
|
54
|
+
{
|
|
55
|
+
title: '某鱼买FSD被坑了4万',
|
|
56
|
+
author: '随风',
|
|
57
|
+
likes: '261',
|
|
58
|
+
url: detailUrl,
|
|
59
|
+
author_url: authorUrl,
|
|
60
|
+
},
|
|
61
|
+
],
|
|
67
62
|
]);
|
|
68
63
|
const result = await cmd.func(page, { query: '特斯拉', limit: 1 });
|
|
69
64
|
// Should only do one goto (the search page itself), no per-note detail navigation
|
|
@@ -84,41 +79,54 @@ describe('xiaohongshu search', () => {
|
|
|
84
79
|
const cmd = getRegistry().get('xiaohongshu/search');
|
|
85
80
|
expect(cmd?.func).toBeTypeOf('function');
|
|
86
81
|
const page = createPageMock([
|
|
87
|
-
// First evaluate:
|
|
88
|
-
|
|
89
|
-
// Second evaluate: main DOM extraction
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
},
|
|
114
|
-
],
|
|
115
|
-
},
|
|
82
|
+
// First evaluate: MutationObserver wait (content appeared)
|
|
83
|
+
'content',
|
|
84
|
+
// Second evaluate: main DOM extraction (returns array directly)
|
|
85
|
+
[
|
|
86
|
+
{
|
|
87
|
+
title: 'Result A',
|
|
88
|
+
author: 'UserA',
|
|
89
|
+
likes: '10',
|
|
90
|
+
url: 'https://www.xiaohongshu.com/search_result/aaa',
|
|
91
|
+
author_url: '',
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
title: '',
|
|
95
|
+
author: 'UserB',
|
|
96
|
+
likes: '5',
|
|
97
|
+
url: 'https://www.xiaohongshu.com/search_result/bbb',
|
|
98
|
+
author_url: '',
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
title: 'Result C',
|
|
102
|
+
author: 'UserC',
|
|
103
|
+
likes: '3',
|
|
104
|
+
url: 'https://www.xiaohongshu.com/search_result/ccc',
|
|
105
|
+
author_url: '',
|
|
106
|
+
},
|
|
107
|
+
],
|
|
116
108
|
]);
|
|
117
109
|
const result = (await cmd.func(page, { query: '测试', limit: 1 }));
|
|
118
110
|
// limit=1 should return only the first valid-titled result
|
|
119
111
|
expect(result).toHaveLength(1);
|
|
120
112
|
expect(result[0]).toMatchObject({ rank: 1, title: 'Result A' });
|
|
121
113
|
});
|
|
114
|
+
it('waits for content via MutationObserver before extracting', async () => {
|
|
115
|
+
const cmd = getRegistry().get('xiaohongshu/search');
|
|
116
|
+
expect(cmd?.func).toBeTypeOf('function');
|
|
117
|
+
const page = createPageMock([
|
|
118
|
+
// First evaluate: MutationObserver wait (content appeared)
|
|
119
|
+
'content',
|
|
120
|
+
// Second evaluate: extraction (returns empty array)
|
|
121
|
+
[],
|
|
122
|
+
]);
|
|
123
|
+
const result = (await cmd.func(page, { query: '测试等待', limit: 5 }));
|
|
124
|
+
expect(result).toHaveLength(0);
|
|
125
|
+
// Only one navigation, no retry
|
|
126
|
+
expect(page.goto).toHaveBeenCalledTimes(1);
|
|
127
|
+
// Two evaluate calls: wait + extraction
|
|
128
|
+
expect(page.evaluate).toHaveBeenCalledTimes(2);
|
|
129
|
+
});
|
|
122
130
|
});
|
|
123
131
|
describe('noteIdToDate (ObjectID timestamp parsing)', () => {
|
|
124
132
|
it('parses a known note ID to the correct China-timezone date', () => {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { isOnYuanbao } from './shared.js';
|
|
2
|
+
declare function normalizeBooleanFlag(value: unknown, fallback: boolean): boolean;
|
|
3
|
+
export declare function convertYuanbaoHtmlToMarkdown(value: string): string;
|
|
4
|
+
export declare function sanitizeYuanbaoResponseText(value: string, promptText: string): string;
|
|
5
|
+
export declare function collectYuanbaoTranscriptAdditions(beforeLines: string[], currentLines: string[], promptText: string): string;
|
|
6
|
+
export declare function pickLatestYuanbaoAssistantCandidate(messages: string[], baselineCount: number, promptText: string): string;
|
|
7
|
+
export declare function updateStableState(previousText: string, stableCount: number, nextText: string): {
|
|
8
|
+
previousText: string;
|
|
9
|
+
stableCount: number;
|
|
10
|
+
};
|
|
11
|
+
export declare const askCommand: import("../../registry.js").CliCommand;
|
|
12
|
+
export declare const __test__: {
|
|
13
|
+
collectYuanbaoTranscriptAdditions: typeof collectYuanbaoTranscriptAdditions;
|
|
14
|
+
convertYuanbaoHtmlToMarkdown: typeof convertYuanbaoHtmlToMarkdown;
|
|
15
|
+
isOnYuanbao: typeof isOnYuanbao;
|
|
16
|
+
normalizeBooleanFlag: typeof normalizeBooleanFlag;
|
|
17
|
+
pickLatestYuanbaoAssistantCandidate: typeof pickLatestYuanbaoAssistantCandidate;
|
|
18
|
+
sanitizeYuanbaoResponseText: typeof sanitizeYuanbaoResponseText;
|
|
19
|
+
updateStableState: typeof updateStableState;
|
|
20
|
+
};
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
import { cli, Strategy } from '../../registry.js';
|
|
2
|
+
import TurndownService from 'turndown';
|
|
3
|
+
import { CommandExecutionError, TimeoutError } from '../../errors.js';
|
|
4
|
+
import { YUANBAO_DOMAIN, IS_VISIBLE_JS, authRequired, isOnYuanbao, ensureYuanbaoPage, hasLoginGate } from './shared.js';
|
|
5
|
+
const YUANBAO_RESPONSE_POLL_INTERVAL_SECONDS = 2;
|
|
6
|
+
const YUANBAO_MIN_WAIT_MS = 8_000;
|
|
7
|
+
const YUANBAO_STABLE_POLLS_REQUIRED = 3;
|
|
8
|
+
function sendFailure(reason, detail) {
|
|
9
|
+
const suffix = detail ? ` Detail: ${detail}` : '';
|
|
10
|
+
return new CommandExecutionError(`${reason || 'Unknown Yuanbao send failure.'}${suffix}`, 'Make sure the Yuanbao chat composer is visible and ready before retrying.');
|
|
11
|
+
}
|
|
12
|
+
function normalizeText(value) {
|
|
13
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
14
|
+
}
|
|
15
|
+
function normalizeBooleanFlag(value, fallback) {
|
|
16
|
+
if (typeof value === 'boolean')
|
|
17
|
+
return value;
|
|
18
|
+
if (value == null || value === '')
|
|
19
|
+
return fallback;
|
|
20
|
+
const normalized = String(value).trim().toLowerCase();
|
|
21
|
+
return normalized === 'true' || normalized === '1' || normalized === 'yes' || normalized === 'on';
|
|
22
|
+
}
|
|
23
|
+
function createYuanbaoTurndown() {
|
|
24
|
+
const td = new TurndownService({
|
|
25
|
+
headingStyle: 'atx',
|
|
26
|
+
codeBlockStyle: 'fenced',
|
|
27
|
+
bulletListMarker: '-',
|
|
28
|
+
});
|
|
29
|
+
td.addRule('linebreak', {
|
|
30
|
+
filter: 'br',
|
|
31
|
+
replacement: () => '\n',
|
|
32
|
+
});
|
|
33
|
+
td.addRule('table', {
|
|
34
|
+
filter: 'table',
|
|
35
|
+
replacement: (content) => `\n\n${content}\n\n`,
|
|
36
|
+
});
|
|
37
|
+
td.addRule('tableSection', {
|
|
38
|
+
filter: ['thead', 'tbody', 'tfoot'],
|
|
39
|
+
replacement: (content) => content,
|
|
40
|
+
});
|
|
41
|
+
td.addRule('tableRow', {
|
|
42
|
+
filter: 'tr',
|
|
43
|
+
replacement: (content, node) => {
|
|
44
|
+
const element = node;
|
|
45
|
+
const cells = Array.from(element.children);
|
|
46
|
+
const isHeaderRow = element.parentElement?.tagName === 'THEAD'
|
|
47
|
+
|| (cells.length > 0 && cells.every((cell) => cell.tagName === 'TH'));
|
|
48
|
+
const row = `${content}\n`;
|
|
49
|
+
if (!isHeaderRow)
|
|
50
|
+
return row;
|
|
51
|
+
const separator = `| ${cells.map(() => '---').join(' | ')} |\n`;
|
|
52
|
+
return `${row}${separator}`;
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
td.addRule('tableCell', {
|
|
56
|
+
filter: ['th', 'td'],
|
|
57
|
+
replacement: (content, node) => {
|
|
58
|
+
const element = node;
|
|
59
|
+
const index = element.parentElement ? Array.from(element.parentElement.children).indexOf(element) : 0;
|
|
60
|
+
const prefix = index === 0 ? '| ' : ' ';
|
|
61
|
+
return `${prefix}${content.trim()} |`;
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
return td;
|
|
65
|
+
}
|
|
66
|
+
const yuanbaoTurndown = createYuanbaoTurndown();
|
|
67
|
+
export function convertYuanbaoHtmlToMarkdown(value) {
|
|
68
|
+
const markdown = yuanbaoTurndown.turndown(value || '');
|
|
69
|
+
return markdown
|
|
70
|
+
.replace(/\u00a0/g, ' ')
|
|
71
|
+
.replace(/\n{4,}/g, '\n\n\n')
|
|
72
|
+
.replace(/[ \t]+$/gm, '')
|
|
73
|
+
.trim();
|
|
74
|
+
}
|
|
75
|
+
export function sanitizeYuanbaoResponseText(value, promptText) {
|
|
76
|
+
let sanitized = value
|
|
77
|
+
.replace(/内容由AI生成,仅供参考/gi, '')
|
|
78
|
+
.replace(/重新回答/gi, '')
|
|
79
|
+
.trim();
|
|
80
|
+
if (/^(正在搜索资料|搜索资料中|正在思考|思考中)[.。…]*$/u.test(sanitized)) {
|
|
81
|
+
return '';
|
|
82
|
+
}
|
|
83
|
+
const prompt = promptText.trim();
|
|
84
|
+
if (!prompt)
|
|
85
|
+
return sanitized;
|
|
86
|
+
if (sanitized === prompt)
|
|
87
|
+
return '';
|
|
88
|
+
for (const separator of ['\n\n', '\n', '\r\n\r\n', '\r\n', ' ']) {
|
|
89
|
+
const prefix = `${prompt}${separator}`;
|
|
90
|
+
if (sanitized.startsWith(prefix)) {
|
|
91
|
+
sanitized = sanitized.slice(prefix.length).trim();
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return sanitized;
|
|
96
|
+
}
|
|
97
|
+
export function collectYuanbaoTranscriptAdditions(beforeLines, currentLines, promptText) {
|
|
98
|
+
const beforeSet = new Set(beforeLines);
|
|
99
|
+
const additions = currentLines
|
|
100
|
+
.filter((line) => !beforeSet.has(line))
|
|
101
|
+
.map((line) => sanitizeYuanbaoResponseText(line, promptText))
|
|
102
|
+
.filter((line) => line && line !== promptText);
|
|
103
|
+
return additions.join('\n').trim();
|
|
104
|
+
}
|
|
105
|
+
export function pickLatestYuanbaoAssistantCandidate(messages, baselineCount, promptText) {
|
|
106
|
+
const freshMessages = messages
|
|
107
|
+
.slice(Math.max(0, baselineCount))
|
|
108
|
+
.map((message) => sanitizeYuanbaoResponseText(message, promptText))
|
|
109
|
+
.filter(Boolean);
|
|
110
|
+
for (let i = freshMessages.length - 1; i >= 0; i -= 1) {
|
|
111
|
+
if (freshMessages[i] !== promptText.trim())
|
|
112
|
+
return freshMessages[i];
|
|
113
|
+
}
|
|
114
|
+
return '';
|
|
115
|
+
}
|
|
116
|
+
export function updateStableState(previousText, stableCount, nextText) {
|
|
117
|
+
if (!nextText)
|
|
118
|
+
return { previousText: '', stableCount: 0 };
|
|
119
|
+
if (nextText === previousText)
|
|
120
|
+
return { previousText, stableCount: stableCount + 1 };
|
|
121
|
+
return { previousText: nextText, stableCount: 0 };
|
|
122
|
+
}
|
|
123
|
+
function getTranscriptLinesScript() {
|
|
124
|
+
return `
|
|
125
|
+
(() => {
|
|
126
|
+
const clean = (value) => (value || '')
|
|
127
|
+
.replace(/\\u00a0/g, ' ')
|
|
128
|
+
.replace(/\\n{3,}/g, '\\n\\n')
|
|
129
|
+
.trim();
|
|
130
|
+
|
|
131
|
+
const root = (
|
|
132
|
+
document.querySelector('.agent-dialogue__content--common')
|
|
133
|
+
|| document.querySelector('.agent-dialogue__content')
|
|
134
|
+
|| document.querySelector('.agent-dialogue')
|
|
135
|
+
|| document.body
|
|
136
|
+
).cloneNode(true);
|
|
137
|
+
|
|
138
|
+
const removableSelectors = [
|
|
139
|
+
'.agent-dialogue__content--common__input',
|
|
140
|
+
'.agent-dialogue__tool',
|
|
141
|
+
'.agent-dialogue__content-copyright',
|
|
142
|
+
'.index_chatLandingBox__G7hAT',
|
|
143
|
+
'.index_chatLandingBoxMobile__J8i8v',
|
|
144
|
+
'.index_chatLandingHintList__M69Lr',
|
|
145
|
+
'.yb-nav',
|
|
146
|
+
'.agent-dialogue__content--common__input .ql-toolbar',
|
|
147
|
+
'.agent-dialogue__content--common__input .ql-container',
|
|
148
|
+
'.agent-dialogue__content--common__input .ql-editor',
|
|
149
|
+
'[role="dialog"]',
|
|
150
|
+
'iframe',
|
|
151
|
+
'button',
|
|
152
|
+
'script',
|
|
153
|
+
'style',
|
|
154
|
+
'noscript',
|
|
155
|
+
];
|
|
156
|
+
|
|
157
|
+
for (const selector of removableSelectors) {
|
|
158
|
+
root.querySelectorAll(selector).forEach((node) => node.remove());
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const stopLines = new Set([
|
|
162
|
+
'元宝',
|
|
163
|
+
'DeepSeek',
|
|
164
|
+
'深度思考',
|
|
165
|
+
'联网搜索',
|
|
166
|
+
'工具',
|
|
167
|
+
'登录',
|
|
168
|
+
'安装电脑版',
|
|
169
|
+
'内容由AI生成,仅供参考',
|
|
170
|
+
'有问题,尽管问,shift+enter换行',
|
|
171
|
+
'立即创建团队',
|
|
172
|
+
'微信',
|
|
173
|
+
'手机',
|
|
174
|
+
'QQ',
|
|
175
|
+
'微信扫码登录',
|
|
176
|
+
'扫码默认已阅读并同意',
|
|
177
|
+
'用户服务协议',
|
|
178
|
+
'隐私协议',
|
|
179
|
+
]);
|
|
180
|
+
|
|
181
|
+
const noisyPatterns = [
|
|
182
|
+
/^支持文件格式[::]/,
|
|
183
|
+
/^文件拖动到此处即可上传/,
|
|
184
|
+
/^下载元宝电脑版/,
|
|
185
|
+
];
|
|
186
|
+
|
|
187
|
+
return clean(root.innerText || root.textContent || '')
|
|
188
|
+
.split('\\n')
|
|
189
|
+
.map((line) => clean(line))
|
|
190
|
+
.filter((line) => line
|
|
191
|
+
&& line.length <= 4000
|
|
192
|
+
&& !stopLines.has(line)
|
|
193
|
+
&& !noisyPatterns.some((pattern) => pattern.test(line)));
|
|
194
|
+
})()
|
|
195
|
+
`;
|
|
196
|
+
}
|
|
197
|
+
async function getYuanbaoTranscriptLines(page) {
|
|
198
|
+
const result = await page.evaluate(getTranscriptLinesScript());
|
|
199
|
+
return Array.isArray(result) ? result.map(normalizeText).filter(Boolean) : [];
|
|
200
|
+
}
|
|
201
|
+
async function getYuanbaoAssistantMessages(page) {
|
|
202
|
+
const result = await page.evaluate(`(() => {
|
|
203
|
+
${IS_VISIBLE_JS}
|
|
204
|
+
|
|
205
|
+
const roots = Array.from(document.querySelectorAll('.agent-chat__list__item--ai'))
|
|
206
|
+
.filter((node) => isVisible(node));
|
|
207
|
+
|
|
208
|
+
return roots.map((root) => {
|
|
209
|
+
const doneContent = root.querySelector('.hyc-content-md-done');
|
|
210
|
+
const markdownContent = doneContent || root.querySelector('.hyc-content-md');
|
|
211
|
+
const speechContent = root.querySelector('.agent-chat__speech-text');
|
|
212
|
+
const bubbleContent = root.querySelector('.agent-chat__bubble__content');
|
|
213
|
+
const content = markdownContent || speechContent || bubbleContent;
|
|
214
|
+
|
|
215
|
+
if (content instanceof HTMLElement) {
|
|
216
|
+
return content.innerHTML || content.textContent || '';
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return root instanceof HTMLElement ? (root.innerHTML || root.textContent || '') : '';
|
|
220
|
+
}).filter(Boolean);
|
|
221
|
+
})()`);
|
|
222
|
+
return Array.isArray(result)
|
|
223
|
+
? result
|
|
224
|
+
.map((value) => convertYuanbaoHtmlToMarkdown(typeof value === 'string' ? value : ''))
|
|
225
|
+
.map(normalizeText)
|
|
226
|
+
.filter(Boolean)
|
|
227
|
+
: [];
|
|
228
|
+
}
|
|
229
|
+
async function getYuanbaoInternetSearchState(page) {
|
|
230
|
+
const result = await page.evaluate(`(() => {
|
|
231
|
+
${IS_VISIBLE_JS}
|
|
232
|
+
|
|
233
|
+
const button = Array.from(document.querySelectorAll('[dt-button-id="internet_search"]'))
|
|
234
|
+
.find((node) => isVisible(node));
|
|
235
|
+
|
|
236
|
+
if (!(button instanceof HTMLElement)) return { found: false, enabled: false };
|
|
237
|
+
|
|
238
|
+
const attr = button.getAttribute('dt-internet-search') || '';
|
|
239
|
+
const className = button.className || '';
|
|
240
|
+
return {
|
|
241
|
+
found: true,
|
|
242
|
+
enabled: attr === 'openInternetSearch' || className.includes('index_v2_active__'),
|
|
243
|
+
};
|
|
244
|
+
})()`);
|
|
245
|
+
return result;
|
|
246
|
+
}
|
|
247
|
+
async function setYuanbaoInternetSearch(page, enabled) {
|
|
248
|
+
const current = await getYuanbaoInternetSearchState(page);
|
|
249
|
+
if (!current.found || current.enabled === enabled)
|
|
250
|
+
return;
|
|
251
|
+
await page.evaluate(`(() => {
|
|
252
|
+
${IS_VISIBLE_JS}
|
|
253
|
+
|
|
254
|
+
const button = Array.from(document.querySelectorAll('[dt-button-id="internet_search"]'))
|
|
255
|
+
.find((node) => isVisible(node));
|
|
256
|
+
|
|
257
|
+
if (button instanceof HTMLElement) button.click();
|
|
258
|
+
})()`);
|
|
259
|
+
await page.wait(0.5);
|
|
260
|
+
}
|
|
261
|
+
async function getYuanbaoDeepThinkState(page) {
|
|
262
|
+
const result = await page.evaluate(`(() => {
|
|
263
|
+
${IS_VISIBLE_JS}
|
|
264
|
+
|
|
265
|
+
const button = Array.from(document.querySelectorAll('[dt-button-id="deep_think"]'))
|
|
266
|
+
.find((node) => isVisible(node));
|
|
267
|
+
|
|
268
|
+
if (!(button instanceof HTMLElement)) return { found: false, enabled: false };
|
|
269
|
+
|
|
270
|
+
const className = button.className || '';
|
|
271
|
+
return {
|
|
272
|
+
found: true,
|
|
273
|
+
enabled: className.includes('ThinkSelector_selected__'),
|
|
274
|
+
};
|
|
275
|
+
})()`);
|
|
276
|
+
return result;
|
|
277
|
+
}
|
|
278
|
+
async function setYuanbaoDeepThink(page, enabled) {
|
|
279
|
+
const current = await getYuanbaoDeepThinkState(page);
|
|
280
|
+
if (!current.found || current.enabled === enabled)
|
|
281
|
+
return;
|
|
282
|
+
await page.evaluate(`(() => {
|
|
283
|
+
${IS_VISIBLE_JS}
|
|
284
|
+
|
|
285
|
+
const button = Array.from(document.querySelectorAll('[dt-button-id="deep_think"]'))
|
|
286
|
+
.find((node) => isVisible(node));
|
|
287
|
+
|
|
288
|
+
if (button instanceof HTMLElement) button.click();
|
|
289
|
+
})()`);
|
|
290
|
+
await page.wait(0.5);
|
|
291
|
+
}
|
|
292
|
+
async function sendYuanbaoMessage(page, prompt) {
|
|
293
|
+
return await page.evaluate(`(async () => {
|
|
294
|
+
const waitFor = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
295
|
+
${IS_VISIBLE_JS}
|
|
296
|
+
|
|
297
|
+
const composer = Array.from(document.querySelectorAll('.ql-editor[contenteditable="true"], .ql-editor, [contenteditable="true"]'))
|
|
298
|
+
.find(isVisible);
|
|
299
|
+
|
|
300
|
+
if (!(composer instanceof HTMLElement)) {
|
|
301
|
+
return {
|
|
302
|
+
ok: false,
|
|
303
|
+
reason: 'Yuanbao composer was not found.',
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
try {
|
|
308
|
+
composer.focus();
|
|
309
|
+
const selection = window.getSelection();
|
|
310
|
+
const range = document.createRange();
|
|
311
|
+
range.selectNodeContents(composer);
|
|
312
|
+
range.collapse(false);
|
|
313
|
+
selection?.removeAllRanges();
|
|
314
|
+
selection?.addRange(range);
|
|
315
|
+
composer.textContent = '';
|
|
316
|
+
document.execCommand('insertText', false, ${JSON.stringify(prompt)});
|
|
317
|
+
composer.dispatchEvent(new InputEvent('input', { bubbles: true, data: ${JSON.stringify(prompt)}, inputType: 'insertText' }));
|
|
318
|
+
await waitFor(200);
|
|
319
|
+
} catch (error) {
|
|
320
|
+
return {
|
|
321
|
+
ok: false,
|
|
322
|
+
reason: 'Failed to insert the prompt into the Yuanbao composer.',
|
|
323
|
+
detail: error instanceof Error ? error.message : String(error),
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const submit = Array.from(document.querySelectorAll('a[class*="send-btn"], button[class*="send-btn"]'))
|
|
328
|
+
.find((node) => {
|
|
329
|
+
if (!(node instanceof HTMLElement) || !isVisible(node)) return false;
|
|
330
|
+
const className = node.className || '';
|
|
331
|
+
if (typeof className === 'string' && className.includes('disabled')) return false;
|
|
332
|
+
return true;
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
if (submit instanceof HTMLElement) {
|
|
336
|
+
submit.click();
|
|
337
|
+
return { ok: true, action: 'click' };
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
composer.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', code: 'Enter', keyCode: 13, bubbles: true }));
|
|
341
|
+
composer.dispatchEvent(new KeyboardEvent('keyup', { key: 'Enter', code: 'Enter', keyCode: 13, bubbles: true }));
|
|
342
|
+
return { ok: true, action: 'enter' };
|
|
343
|
+
})()`);
|
|
344
|
+
}
|
|
345
|
+
async function waitForYuanbaoResponse(page, baselineAssistantCount, beforeLines, prompt, timeoutSeconds) {
|
|
346
|
+
const startTime = Date.now();
|
|
347
|
+
let previousText = '';
|
|
348
|
+
let stableCount = 0;
|
|
349
|
+
let latestCandidate = '';
|
|
350
|
+
while (Date.now() - startTime < timeoutSeconds * 1000) {
|
|
351
|
+
await page.wait(YUANBAO_RESPONSE_POLL_INTERVAL_SECONDS);
|
|
352
|
+
if (await hasLoginGate(page))
|
|
353
|
+
return 'blocked';
|
|
354
|
+
const assistantMessages = await getYuanbaoAssistantMessages(page);
|
|
355
|
+
const assistantCandidate = pickLatestYuanbaoAssistantCandidate(assistantMessages, baselineAssistantCount, prompt);
|
|
356
|
+
const candidate = assistantCandidate || collectYuanbaoTranscriptAdditions(beforeLines, await getYuanbaoTranscriptLines(page), prompt);
|
|
357
|
+
if (!candidate)
|
|
358
|
+
continue;
|
|
359
|
+
latestCandidate = candidate;
|
|
360
|
+
const nextState = updateStableState(previousText, stableCount, candidate);
|
|
361
|
+
previousText = nextState.previousText;
|
|
362
|
+
stableCount = nextState.stableCount;
|
|
363
|
+
const waitedLongEnough = Date.now() - startTime >= YUANBAO_MIN_WAIT_MS;
|
|
364
|
+
if (waitedLongEnough && stableCount >= YUANBAO_STABLE_POLLS_REQUIRED)
|
|
365
|
+
return candidate;
|
|
366
|
+
}
|
|
367
|
+
return latestCandidate || null;
|
|
368
|
+
}
|
|
369
|
+
export const askCommand = cli({
|
|
370
|
+
site: 'yuanbao',
|
|
371
|
+
name: 'ask',
|
|
372
|
+
description: 'Send a prompt to Yuanbao web chat and wait for the assistant response',
|
|
373
|
+
domain: YUANBAO_DOMAIN,
|
|
374
|
+
strategy: Strategy.COOKIE,
|
|
375
|
+
browser: true,
|
|
376
|
+
navigateBefore: false,
|
|
377
|
+
defaultFormat: 'plain',
|
|
378
|
+
timeoutSeconds: 180,
|
|
379
|
+
args: [
|
|
380
|
+
{ name: 'prompt', required: true, positional: true, help: 'Prompt to send' },
|
|
381
|
+
{ name: 'timeout', required: false, help: 'Max seconds to wait (default: 60)', default: '60' },
|
|
382
|
+
{ name: 'search', type: 'boolean', required: false, help: 'Enable Yuanbao internet search (default: true)', default: true },
|
|
383
|
+
{ name: 'think', type: 'boolean', required: false, help: 'Enable Yuanbao deep thinking (default: false)', default: false },
|
|
384
|
+
],
|
|
385
|
+
columns: ['Role', 'Text'],
|
|
386
|
+
func: async (page, kwargs) => {
|
|
387
|
+
const prompt = kwargs.prompt;
|
|
388
|
+
const timeout = parseInt(kwargs.timeout, 10) || 60;
|
|
389
|
+
const useSearch = normalizeBooleanFlag(kwargs.search, true);
|
|
390
|
+
const useThink = normalizeBooleanFlag(kwargs.think, false);
|
|
391
|
+
await ensureYuanbaoPage(page);
|
|
392
|
+
if (await hasLoginGate(page)) {
|
|
393
|
+
throw authRequired('Yuanbao opened a login gate before sending the prompt.');
|
|
394
|
+
}
|
|
395
|
+
await setYuanbaoInternetSearch(page, useSearch);
|
|
396
|
+
await setYuanbaoDeepThink(page, useThink);
|
|
397
|
+
const beforeAssistantMessages = await getYuanbaoAssistantMessages(page);
|
|
398
|
+
const beforeLines = await getYuanbaoTranscriptLines(page);
|
|
399
|
+
const sendResult = await sendYuanbaoMessage(page, prompt);
|
|
400
|
+
if (!sendResult?.ok) {
|
|
401
|
+
if (await hasLoginGate(page)) {
|
|
402
|
+
throw authRequired('Yuanbao opened a login gate instead of accepting the prompt.');
|
|
403
|
+
}
|
|
404
|
+
throw sendFailure(sendResult?.reason, sendResult?.detail);
|
|
405
|
+
}
|
|
406
|
+
const response = await waitForYuanbaoResponse(page, beforeAssistantMessages.length, beforeLines, prompt, timeout);
|
|
407
|
+
if (response === 'blocked') {
|
|
408
|
+
throw authRequired('Yuanbao opened a login gate instead of returning a chat response.');
|
|
409
|
+
}
|
|
410
|
+
if (!response) {
|
|
411
|
+
throw new TimeoutError('yuanbao ask', timeout, 'No Yuanbao response was observed before the timeout. Retry with --timeout, and verify the current browser session is still interactive.');
|
|
412
|
+
}
|
|
413
|
+
return [
|
|
414
|
+
{ Role: 'User', Text: prompt },
|
|
415
|
+
{ Role: 'Assistant', Text: response },
|
|
416
|
+
];
|
|
417
|
+
},
|
|
418
|
+
});
|
|
419
|
+
export const __test__ = {
|
|
420
|
+
collectYuanbaoTranscriptAdditions,
|
|
421
|
+
convertYuanbaoHtmlToMarkdown,
|
|
422
|
+
isOnYuanbao,
|
|
423
|
+
normalizeBooleanFlag,
|
|
424
|
+
pickLatestYuanbaoAssistantCandidate,
|
|
425
|
+
sanitizeYuanbaoResponseText,
|
|
426
|
+
updateStableState,
|
|
427
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|