@jackwener/opencli 1.7.7 → 1.7.9
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/README.md +49 -14
- package/README.zh-CN.md +30 -10
- package/cli-manifest.json +782 -55
- package/clis/36kr/news.js +1 -1
- package/clis/amazon/discussion.js +37 -6
- package/clis/amazon/discussion.test.js +147 -32
- package/clis/apple-podcasts/commands.test.js +4 -4
- package/clis/apple-podcasts/episodes.js +1 -1
- package/clis/apple-podcasts/search.js +1 -1
- package/clis/apple-podcasts/top.js +1 -1
- package/clis/arxiv/paper.js +1 -1
- package/clis/arxiv/search.js +1 -1
- package/clis/band/mentions.js +3 -3
- package/clis/bbc/news.js +1 -1
- package/clis/bilibili/subtitle.js +2 -2
- package/clis/bloomberg/businessweek.js +1 -1
- package/clis/bloomberg/economics.js +1 -1
- package/clis/bloomberg/industries.js +1 -1
- package/clis/bloomberg/main.js +1 -1
- package/clis/bloomberg/markets.js +1 -1
- package/clis/bloomberg/opinions.js +1 -1
- package/clis/bloomberg/politics.js +1 -1
- package/clis/bloomberg/tech.js +1 -1
- package/clis/boss/search.js +49 -8
- package/clis/boss/search.test.js +78 -0
- package/clis/boss/send.js +3 -3
- package/clis/chatgpt/image.js +37 -8
- package/clis/chatgpt/image.test.js +92 -0
- package/clis/chatgpt/utils.js +39 -6
- package/clis/chatgpt/utils.test.js +63 -0
- package/clis/chatgpt-app/ask.js +4 -20
- package/clis/chatgpt-app/ax.js +135 -2
- package/clis/chatgpt-app/ax.test.js +35 -0
- package/clis/chatgpt-app/model.js +1 -1
- package/clis/chatgpt-app/new.js +1 -1
- package/clis/chatgpt-app/read.js +1 -1
- package/clis/chatgpt-app/send.js +3 -22
- package/clis/chatgpt-app/status.js +1 -1
- package/clis/chatwise/ask.js +2 -2
- package/clis/chatwise/model.js +2 -2
- package/clis/chatwise/send.js +2 -2
- package/clis/claude/ask.js +128 -0
- package/clis/claude/ask.test.js +338 -0
- package/clis/claude/commands.test.js +118 -0
- package/clis/claude/detail.js +29 -0
- package/clis/claude/history.js +31 -0
- package/clis/claude/new.js +21 -0
- package/clis/claude/read.js +24 -0
- package/clis/claude/send.js +41 -0
- package/clis/claude/status.js +24 -0
- package/clis/claude/utils.js +440 -0
- package/clis/claude/utils.test.js +148 -0
- package/clis/codex/ask.js +2 -2
- package/clis/codex/send.js +2 -2
- package/clis/ctrip/search.js +1 -1
- package/clis/ctrip/search.test.js +4 -4
- package/clis/cursor/ask.js +2 -2
- package/clis/cursor/composer.js +2 -2
- package/clis/cursor/send.js +2 -2
- package/clis/deepseek/ask.js +49 -10
- package/clis/deepseek/ask.test.js +150 -3
- package/clis/deepseek/utils.js +60 -22
- package/clis/deepseek/utils.test.js +124 -5
- package/clis/doubao/utils.js +53 -11
- package/clis/doubao/utils.test.js +22 -2
- package/clis/eastmoney/announcement.js +1 -1
- package/clis/eastmoney/convertible.js +1 -1
- package/clis/eastmoney/etf.js +1 -1
- package/clis/eastmoney/holders.js +1 -1
- package/clis/eastmoney/index-board.js +1 -1
- package/clis/eastmoney/kline.js +1 -1
- package/clis/eastmoney/kuaixun.js +1 -1
- package/clis/eastmoney/longhu.js +1 -1
- package/clis/eastmoney/money-flow.js +1 -1
- package/clis/eastmoney/northbound.js +1 -1
- package/clis/eastmoney/quote.js +1 -1
- package/clis/eastmoney/rank.js +1 -1
- package/clis/eastmoney/sectors.js +1 -1
- package/clis/facebook/marketplace-inbox.js +83 -0
- package/clis/facebook/marketplace-listings.js +83 -0
- package/clis/facebook/marketplace.test.js +91 -0
- package/clis/google/news.js +1 -1
- package/clis/google/suggest.js +1 -1
- package/clis/google/trends.js +1 -1
- package/clis/google-scholar/cite.js +74 -0
- package/clis/google-scholar/cite.test.js +47 -0
- package/clis/google-scholar/profile.js +92 -0
- package/clis/google-scholar/profile.test.js +49 -0
- package/clis/google-scholar/search.js +1 -1
- package/clis/google-scholar/search.test.js +15 -0
- package/clis/hf/top.js +1 -1
- package/clis/jd/item.js +679 -47
- package/clis/jd/item.test.js +318 -7
- package/clis/jd/item.test.ts +517 -0
- package/clis/lesswrong/comments.js +1 -1
- package/clis/lesswrong/curated.js +1 -1
- package/clis/lesswrong/frontpage.js +1 -1
- package/clis/lesswrong/new.js +1 -1
- package/clis/lesswrong/read.js +1 -1
- package/clis/lesswrong/sequences.js +1 -1
- package/clis/lesswrong/shortform.js +1 -1
- package/clis/lesswrong/tag.js +1 -1
- package/clis/lesswrong/tags.js +1 -1
- package/clis/lesswrong/top-month.js +1 -1
- package/clis/lesswrong/top-week.js +1 -1
- package/clis/lesswrong/top-year.js +1 -1
- package/clis/lesswrong/top.js +1 -1
- package/clis/lesswrong/user-posts.js +1 -1
- package/clis/lesswrong/user.js +1 -1
- package/clis/paperreview/commands.test.js +6 -6
- package/clis/paperreview/feedback.js +1 -1
- package/clis/paperreview/review.js +1 -1
- package/clis/paperreview/submit.js +1 -1
- package/clis/powerchina/search.js +250 -0
- package/clis/powerchina/search.test.js +67 -0
- package/clis/producthunt/posts.js +1 -1
- package/clis/producthunt/today.js +1 -1
- package/clis/sinablog/search.js +1 -1
- package/clis/sinafinance/news.js +1 -1
- package/clis/sinafinance/stock.js +6 -3
- package/clis/sinafinance/stock.test.js +59 -0
- package/clis/spotify/spotify.js +6 -6
- package/clis/substack/search.js +1 -1
- package/clis/toutiao/articles.js +80 -0
- package/clis/toutiao/articles.test.js +30 -0
- package/clis/twitter/followers.js +2 -2
- package/clis/twitter/following.js +224 -73
- package/clis/twitter/following.test.js +277 -0
- package/clis/twitter/post.js +184 -47
- package/clis/twitter/post.test.js +114 -34
- package/clis/uiverse/_shared.js +63 -4
- package/clis/uiverse/_shared.test.js +7 -0
- package/clis/uiverse/code.js +1 -0
- package/clis/uiverse/navigation.test.js +12 -0
- package/clis/uiverse/preview.js +1 -0
- package/clis/web/read.js +319 -81
- package/clis/web/read.test.js +221 -5
- package/clis/weibo/favorites.js +169 -0
- package/clis/weibo/favorites.test.js +114 -0
- package/clis/weibo/publish.js +282 -0
- package/clis/weibo/publish.test.js +183 -0
- package/clis/weixin/create-draft.js +225 -0
- package/clis/weixin/drafts.js +65 -0
- package/clis/weixin/drafts.test.js +65 -0
- package/clis/weread/ranking.js +1 -1
- package/clis/weread/search-regression.test.js +8 -8
- package/clis/weread/search.js +1 -1
- package/clis/wikipedia/random.js +1 -1
- package/clis/wikipedia/search.js +1 -1
- package/clis/wikipedia/summary.js +1 -1
- package/clis/wikipedia/trending.js +1 -1
- package/clis/xianyu/chat.js +3 -3
- package/clis/xianyu/item.js +2 -2
- package/clis/xianyu/item.test.js +3 -3
- package/clis/xiaohongshu/search.js +17 -2
- package/clis/xiaohongshu/search.test.js +37 -1
- package/clis/xiaoyuzhou/download.js +1 -1
- package/clis/xiaoyuzhou/download.test.js +3 -3
- package/clis/xiaoyuzhou/episode.js +1 -1
- package/clis/xiaoyuzhou/podcast-episodes.js +1 -1
- package/clis/xiaoyuzhou/podcast-episodes.test.js +2 -2
- package/clis/xiaoyuzhou/podcast.js +1 -1
- package/clis/xiaoyuzhou/transcript.js +1 -1
- package/clis/xiaoyuzhou/transcript.test.js +5 -5
- package/clis/yollomi/models.js +1 -1
- package/clis/youtube/channel.js +24 -1
- package/clis/youtube/channel.test.js +59 -0
- package/clis/zhihu/answer.js +21 -162
- package/clis/zhihu/answer.test.js +26 -53
- package/clis/zhihu/collection.js +197 -0
- package/clis/zhihu/collection.test.js +290 -0
- package/clis/zhihu/collections.js +127 -0
- package/clis/zhihu/collections.test.js +182 -0
- package/clis/zhihu/comment.js +24 -305
- package/clis/zhihu/comment.test.js +31 -35
- package/clis/zhihu/favorite.js +44 -182
- package/clis/zhihu/favorite.test.js +30 -167
- package/clis/zhihu/follow.js +25 -56
- package/clis/zhihu/follow.test.js +20 -23
- package/clis/zhihu/like.js +22 -67
- package/clis/zhihu/like.test.js +19 -42
- package/clis/zhihu/search.js +3 -2
- package/clis/zhihu/write-shared.js +8 -1
- package/clis/zhihu/write-shared.test.js +1 -0
- package/clis/zlibrary/commands.test.js +75 -0
- package/clis/zlibrary/info.js +47 -0
- package/clis/zlibrary/search.js +46 -0
- package/clis/zlibrary/utils.js +136 -0
- package/dist/src/adapter-source.d.ts +11 -0
- package/dist/src/adapter-source.js +24 -0
- package/dist/src/adapter-source.test.js +29 -0
- package/dist/src/browser/base-page.d.ts +3 -1
- package/dist/src/browser/base-page.js +76 -1
- package/dist/src/browser/base-page.test.d.ts +1 -0
- package/dist/src/browser/base-page.test.js +74 -0
- package/dist/src/browser/bridge.d.ts +1 -0
- package/dist/src/browser/bridge.js +36 -9
- package/dist/src/browser/cdp.d.ts +1 -0
- package/dist/src/browser/cdp.js +3 -3
- package/dist/src/browser/daemon-client.d.ts +38 -4
- package/dist/src/browser/daemon-client.js +24 -7
- package/dist/src/browser/daemon-client.test.js +49 -0
- package/dist/src/browser/errors.js +3 -0
- package/dist/src/browser/errors.test.js +3 -0
- package/dist/src/browser/network-cache.d.ts +1 -0
- package/dist/src/browser/page.d.ts +3 -1
- package/dist/src/browser/page.js +10 -2
- package/dist/src/browser/profile.d.ts +14 -0
- package/dist/src/browser/profile.js +85 -0
- package/dist/src/build-manifest.d.ts +2 -0
- package/dist/src/build-manifest.js +13 -3
- package/dist/src/build-manifest.test.js +20 -2
- package/dist/src/cli.d.ts +6 -0
- package/dist/src/cli.js +462 -32
- package/dist/src/cli.test.js +209 -2
- package/dist/src/commanderAdapter.js +29 -9
- package/dist/src/commanderAdapter.test.js +78 -2
- package/dist/src/commands/daemon.js +6 -0
- package/dist/src/completion-shared.js +1 -2
- package/dist/src/completion.test.js +3 -2
- package/dist/src/daemon.js +125 -41
- package/dist/src/doctor.d.ts +4 -6
- package/dist/src/doctor.js +80 -22
- package/dist/src/doctor.test.js +82 -0
- package/dist/src/engine.test.js +6 -5
- package/dist/src/errors.d.ts +14 -8
- package/dist/src/errors.js +36 -30
- package/dist/src/errors.test.js +5 -5
- package/dist/src/execution.d.ts +4 -0
- package/dist/src/execution.js +173 -25
- package/dist/src/execution.test.js +171 -1
- package/dist/src/main.js +10 -0
- package/dist/src/observation/artifact.d.ts +16 -0
- package/dist/src/observation/artifact.js +260 -0
- package/dist/src/observation/artifact.test.d.ts +1 -0
- package/dist/src/observation/artifact.test.js +121 -0
- package/dist/src/observation/events.d.ts +89 -0
- package/dist/src/observation/events.js +1 -0
- package/dist/src/observation/index.d.ts +7 -0
- package/dist/src/observation/index.js +7 -0
- package/dist/src/observation/manager.d.ts +9 -0
- package/dist/src/observation/manager.js +27 -0
- package/dist/src/observation/manager.test.d.ts +1 -0
- package/dist/src/observation/manager.test.js +13 -0
- package/dist/src/observation/redaction.d.ts +11 -0
- package/dist/src/observation/redaction.js +81 -0
- package/dist/src/observation/redaction.test.d.ts +1 -0
- package/dist/src/observation/redaction.test.js +32 -0
- package/dist/src/observation/retention.d.ts +32 -0
- package/dist/src/observation/retention.js +160 -0
- package/dist/src/observation/retention.test.d.ts +1 -0
- package/dist/src/observation/retention.test.js +118 -0
- package/dist/src/observation/ring-buffer.d.ts +22 -0
- package/dist/src/observation/ring-buffer.js +45 -0
- package/dist/src/observation/ring-buffer.test.d.ts +1 -0
- package/dist/src/observation/ring-buffer.test.js +22 -0
- package/dist/src/observation/session.d.ts +25 -0
- package/dist/src/observation/session.js +50 -0
- package/dist/src/pipeline/executor.test.js +1 -0
- package/dist/src/pipeline/steps/download.test.js +1 -0
- package/dist/src/pipeline/steps/fetch.js +1 -21
- package/dist/src/pipeline/steps/fetch.test.js +6 -12
- package/dist/src/plugin-scaffold.js +1 -1
- package/dist/src/plugin-scaffold.test.js +1 -1
- package/dist/src/registry.d.ts +40 -9
- package/dist/src/registry.js +3 -1
- package/dist/src/runtime-detect.d.ts +10 -0
- package/dist/src/runtime-detect.js +19 -0
- package/dist/src/runtime-detect.test.js +12 -1
- package/dist/src/runtime.d.ts +2 -0
- package/dist/src/runtime.js +1 -0
- package/dist/src/types.d.ts +22 -0
- package/dist/src/update-check.d.ts +31 -1
- package/dist/src/update-check.js +62 -16
- package/dist/src/update-check.test.js +86 -1
- package/package.json +1 -1
- package/dist/src/diagnostic.d.ts +0 -63
- package/dist/src/diagnostic.js +0 -292
- package/dist/src/diagnostic.test.js +0 -302
- /package/dist/src/{diagnostic.test.d.ts → adapter-source.test.d.ts} +0 -0
package/clis/doubao/utils.js
CHANGED
|
@@ -63,7 +63,7 @@ function getTranscriptLinesScript() {
|
|
|
63
63
|
const stopLines = new Set([
|
|
64
64
|
'豆包',
|
|
65
65
|
'新对话',
|
|
66
|
-
'内容由豆包 AI
|
|
66
|
+
'内容由豆包 AI 生成,请仔细甄别',
|
|
67
67
|
'AI 创作',
|
|
68
68
|
'云盘',
|
|
69
69
|
'更多',
|
|
@@ -75,6 +75,8 @@ function getTranscriptLinesScript() {
|
|
|
75
75
|
'PPT 生成',
|
|
76
76
|
'图像生成',
|
|
77
77
|
'帮我写作',
|
|
78
|
+
'请仔细甄别',
|
|
79
|
+
'下载电脑版',
|
|
78
80
|
]);
|
|
79
81
|
|
|
80
82
|
const noisyPatterns = [
|
|
@@ -88,7 +90,7 @@ function getTranscriptLinesScript() {
|
|
|
88
90
|
|
|
89
91
|
const transcriptText = clean(root.innerText || root.textContent || '')
|
|
90
92
|
.replace(/新对话/g, '\\n')
|
|
91
|
-
.replace(/内容由豆包 AI
|
|
93
|
+
.replace(/内容由豆包 AI 生成,请仔细甄别/g, '\\n')
|
|
92
94
|
.replace(/在此处拖放文件/g, '\\n')
|
|
93
95
|
.replace(/文件数量:[^\\n]*/g, '')
|
|
94
96
|
.replace(/文件类型:[^\\n]*/g, '');
|
|
@@ -144,12 +146,20 @@ function getTurnsScript() {
|
|
|
144
146
|
if (
|
|
145
147
|
root.matches('[data-testid="send_message"], [class*="send-message"]')
|
|
146
148
|
|| root.querySelector('[data-testid="send_message"], [class*="send-message"]')
|
|
149
|
+
|| root.matches('[class*="bg-g-send-msg-bubble"]')
|
|
150
|
+
||
|
|
151
|
+
root.querySelector('[class*="bg-g-send-msg-bubble"]')
|
|
152
|
+
|| root.querySelector('[data-foundation-type="send-message-action-bar"]')
|
|
147
153
|
) {
|
|
148
154
|
return 'User';
|
|
149
155
|
}
|
|
150
156
|
if (
|
|
151
157
|
root.matches('[data-testid="receive_message"], [data-testid*="receive_message"], [class*="receive-message"]')
|
|
152
158
|
|| root.querySelector('[data-testid="receive_message"], [data-testid*="receive_message"], [class*="receive-message"]')
|
|
159
|
+
|| root.matches('[class*="bg-g-receive-msg-bubble"]')
|
|
160
|
+
||
|
|
161
|
+
root.querySelector('[class*="bg-g-receive-msg-bubble"]')
|
|
162
|
+
|| root.querySelector('[data-foundation-type="receive-message-action-bar"]')
|
|
153
163
|
) {
|
|
154
164
|
return 'Assistant';
|
|
155
165
|
}
|
|
@@ -163,6 +173,10 @@ function getTurnsScript() {
|
|
|
163
173
|
'[data-testid*="message_content"]',
|
|
164
174
|
'[class*="message-text"]',
|
|
165
175
|
'[class*="message-content"]',
|
|
176
|
+
'[class*="bg-g-send-msg-bubble"]',
|
|
177
|
+
'[class*="bg-g-receive-msg-bubble"]',
|
|
178
|
+
'.flow-markdown-body',
|
|
179
|
+
'[class*="bubble"]',
|
|
166
180
|
];
|
|
167
181
|
const messageImageSelector = messageTextSelectors.map((s) => s + ' img').join(', ');
|
|
168
182
|
|
|
@@ -205,14 +219,30 @@ function getTurnsScript() {
|
|
|
205
219
|
return text ? text + '\\n' + imageLines.join('\\n') : imageLines.join('\\n');
|
|
206
220
|
};
|
|
207
221
|
|
|
208
|
-
const messageList = document.querySelector('[data-testid="message-list"]');
|
|
222
|
+
const messageList = document.querySelector('[class*="message-list-S2Fv2S"], .container-PvPoAn, .scroll-view-OEiNXD, [data-testid="message-list"]');
|
|
209
223
|
if (!messageList) return [];
|
|
210
224
|
|
|
211
|
-
const
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
225
|
+
const itemSelectors = [
|
|
226
|
+
'[class*="item-kDun2N"]',
|
|
227
|
+
'[data-testid="union_message"]',
|
|
228
|
+
'[data-testid="message-block-container"]',
|
|
229
|
+
'[data-message-id]',
|
|
230
|
+
'[class*="bg-g-send-msg-bubble"]',
|
|
231
|
+
'[class*="bg-g-receive-msg-bubble"]',
|
|
232
|
+
];
|
|
233
|
+
|
|
234
|
+
const allRoots = [];
|
|
235
|
+
const seen = new Set();
|
|
236
|
+
for (const sel of itemSelectors) {
|
|
237
|
+
messageList.querySelectorAll(sel).forEach((el) => {
|
|
238
|
+
if (!seen.has(el)) {
|
|
239
|
+
seen.add(el);
|
|
240
|
+
allRoots.push(el);
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
const roots = allRoots
|
|
245
|
+
.filter((el) => isVisible(el) && !el.closest('script, style, noscript'))
|
|
216
246
|
.filter((el, index, items) => !items.some((other, otherIndex) => otherIndex !== index && other.contains(el)));
|
|
217
247
|
|
|
218
248
|
const turns = roots
|
|
@@ -230,11 +260,11 @@ function getTurnsScript() {
|
|
|
230
260
|
});
|
|
231
261
|
|
|
232
262
|
const deduped = [];
|
|
233
|
-
const
|
|
263
|
+
const dedupedSeen = new Set();
|
|
234
264
|
for (const turn of turns) {
|
|
235
265
|
const key = turn.role + '::' + turn.text;
|
|
236
|
-
if (
|
|
237
|
-
|
|
266
|
+
if (dedupedSeen.has(key)) continue;
|
|
267
|
+
dedupedSeen.add(key);
|
|
238
268
|
deduped.push({ Role: turn.role, Text: turn.text });
|
|
239
269
|
}
|
|
240
270
|
|
|
@@ -421,6 +451,16 @@ function clickSendButtonScript() {
|
|
|
421
451
|
return `
|
|
422
452
|
(() => {
|
|
423
453
|
${buildDoubaoComposerLocatorScript()}
|
|
454
|
+
const directSendButton = document.querySelector('button#flow-end-msg-send');
|
|
455
|
+
if (directSendButton instanceof HTMLElement && isVisible(directSendButton)) {
|
|
456
|
+
const disabled = directSendButton.getAttribute('disabled') !== null
|
|
457
|
+
|| directSendButton.getAttribute('aria-disabled') === 'true';
|
|
458
|
+
if (!disabled) {
|
|
459
|
+
directSendButton.click();
|
|
460
|
+
return true;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
424
464
|
const composer = findComposer();
|
|
425
465
|
if (!(composer instanceof HTMLElement)) return false;
|
|
426
466
|
|
|
@@ -1076,6 +1116,8 @@ export const __test__ = {
|
|
|
1076
1116
|
clickSendButtonScript,
|
|
1077
1117
|
composerStateScript,
|
|
1078
1118
|
detectDoubaoVerificationScript,
|
|
1119
|
+
getTurnsScript,
|
|
1120
|
+
getTranscriptLinesScript,
|
|
1079
1121
|
};
|
|
1080
1122
|
export async function startNewDoubaoChat(page) {
|
|
1081
1123
|
await ensureDoubaoChatPage(page);
|
|
@@ -144,6 +144,25 @@ describe('doubao send strategy', () => {
|
|
|
144
144
|
await expect(sendDoubaoMessage(page, '你好')).rejects.toBeInstanceOf(CommandExecutionError);
|
|
145
145
|
});
|
|
146
146
|
});
|
|
147
|
+
describe('doubao receive strategy', () => {
|
|
148
|
+
it('keeps both the new skin selectors and the older structural fallbacks in the turns script', () => {
|
|
149
|
+
const turnsScript = __test__.getTurnsScript();
|
|
150
|
+
expect(turnsScript).toContain('[class*="message-list-S2Fv2S"]');
|
|
151
|
+
expect(turnsScript).toContain('.container-PvPoAn');
|
|
152
|
+
expect(turnsScript).toContain('[data-testid="message-list"]');
|
|
153
|
+
expect(turnsScript).toContain('[class*="bg-g-receive-msg-bubble"]');
|
|
154
|
+
expect(turnsScript).toContain('[data-testid="receive_message"]');
|
|
155
|
+
expect(turnsScript).toContain('[data-foundation-type="receive-message-action-bar"]');
|
|
156
|
+
expect(turnsScript).toContain('[data-testid="union_message"]');
|
|
157
|
+
expect(turnsScript).toContain('[data-testid="message-block-container"]');
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('extends transcript-noise cleanup for the current zh-CN chrome copy', () => {
|
|
161
|
+
const transcriptScript = __test__.getTranscriptLinesScript();
|
|
162
|
+
expect(transcriptScript).toContain('请仔细甄别');
|
|
163
|
+
expect(transcriptScript).toContain('下载电脑版');
|
|
164
|
+
});
|
|
165
|
+
});
|
|
147
166
|
describe('collectDoubaoTranscriptAdditions', () => {
|
|
148
167
|
it('ignores landing-page capability chips that are not assistant content', () => {
|
|
149
168
|
const before = ['older'];
|
|
@@ -202,9 +221,10 @@ describe('collectDoubaoTranscriptAdditions', () => {
|
|
|
202
221
|
expect(collectDoubaoTranscriptAdditions(before, current, '测试一下,只回复OK', (value) => value.replace('测试一下,只回复OK', '').trim())).toBe('');
|
|
203
222
|
});
|
|
204
223
|
it('treats only the exact landing-page chip string as UI noise', () => {
|
|
205
|
-
expect(__test__.clickSendButtonScript()).
|
|
224
|
+
expect(__test__.clickSendButtonScript()).toContain("button#flow-end-msg-send");
|
|
225
|
+
expect(__test__.clickSendButtonScript()).toContain("getAttribute('disabled') !== null");
|
|
226
|
+
expect(__test__.clickSendButtonScript()).toContain("getAttribute('aria-disabled') === 'true'");
|
|
206
227
|
expect(__test__.clickSendButtonScript()).toContain('bestScore >= 200');
|
|
207
|
-
expect(__test__.clickSendButtonScript()).not.toContain("|| !!button.closest('.chat-input-button')");
|
|
208
228
|
expect(__test__.clickSendButtonScript()).toContain("button.getAttribute('type') === 'submit') score += 1200");
|
|
209
229
|
expect(__test__.composerStateScript()).toContain("(composer.innerText || '').trim() || (composer.textContent || '').trim()");
|
|
210
230
|
expect(__test__.detectDoubaoVerificationScript()).not.toContain('document.body?.innerText');
|
|
@@ -18,7 +18,7 @@ cli({
|
|
|
18
18
|
{ name: 'limit', type: 'int', default: 20, help: '返回数量 (max 100)' },
|
|
19
19
|
],
|
|
20
20
|
columns: ['time', 'code', 'name', 'title', 'category', 'url'],
|
|
21
|
-
func: async (
|
|
21
|
+
func: async (args) => {
|
|
22
22
|
const market = String(args.market ?? 'SHA,SZA,BJA').trim() || 'SHA,SZA,BJA';
|
|
23
23
|
const limit = Math.max(1, Math.min(Number(args.limit) || 20, 100));
|
|
24
24
|
|
|
@@ -28,7 +28,7 @@ cli({
|
|
|
28
28
|
{ name: 'limit', type: 'int', default: 20, help: '返回数量 (max 100)' },
|
|
29
29
|
],
|
|
30
30
|
columns: ['rank', 'bondCode', 'bondName', 'bondPrice', 'bondChangePct', 'stockCode', 'stockName', 'stockPrice', 'stockChangePct', 'convPrice', 'convValue', 'convPremiumPct', 'remainingYears', 'ytm', 'listDate'],
|
|
31
|
-
func: async (
|
|
31
|
+
func: async (args) => {
|
|
32
32
|
const sortKey = String(args.sort ?? 'turnover').toLowerCase();
|
|
33
33
|
const sort = SORTS[sortKey];
|
|
34
34
|
if (!sort) throw new CliError('INVALID_ARGUMENT', `Unknown sort "${sortKey}". Valid: ${Object.keys(SORTS).join(', ')}`);
|
package/clis/eastmoney/etf.js
CHANGED
|
@@ -26,7 +26,7 @@ cli({
|
|
|
26
26
|
{ name: 'limit', type: 'int', default: 20, help: '返回数量 (max 100)' },
|
|
27
27
|
],
|
|
28
28
|
columns: ['rank', 'code', 'name', 'price', 'changePercent', 'change', 'turnover', 'volume', 'turnoverRate'],
|
|
29
|
-
func: async (
|
|
29
|
+
func: async (args) => {
|
|
30
30
|
const sortKey = String(args.sort ?? 'turnover').toLowerCase();
|
|
31
31
|
const sort = SORTS[sortKey];
|
|
32
32
|
if (!sort) throw new CliError('INVALID_ARGUMENT', `Unknown sort "${sortKey}". Valid: ${Object.keys(SORTS).join(', ')}`);
|
|
@@ -37,7 +37,7 @@ cli({
|
|
|
37
37
|
{ name: 'limit', type: 'int', default: 10, help: '返回股东数(默认十大流通股东)' },
|
|
38
38
|
],
|
|
39
39
|
columns: ['rank', 'reportDate', 'name', 'holdNum', 'floatRatio', 'change'],
|
|
40
|
-
func: async (
|
|
40
|
+
func: async (args) => {
|
|
41
41
|
/** @type {string} */
|
|
42
42
|
let secucode;
|
|
43
43
|
try { secucode = toSecucode(args.symbol); }
|
|
@@ -47,7 +47,7 @@ cli({
|
|
|
47
47
|
},
|
|
48
48
|
],
|
|
49
49
|
columns: ['code', 'name', 'price', 'changePercent', 'change', 'open', 'high', 'low', 'prevClose'],
|
|
50
|
-
func: async (
|
|
50
|
+
func: async (args) => {
|
|
51
51
|
const group = String(args.group ?? 'main').toLowerCase();
|
|
52
52
|
/** @type {[string,string][]} */
|
|
53
53
|
let entries;
|
package/clis/eastmoney/kline.js
CHANGED
|
@@ -36,7 +36,7 @@ cli({
|
|
|
36
36
|
{ name: 'limit', type: 'int', default: 30, help: '返回最近 N 根(末尾)' },
|
|
37
37
|
],
|
|
38
38
|
columns: ['date', 'open', 'close', 'high', 'low', 'volume', 'turnover', 'amplitude', 'changePercent', 'change', 'turnoverRate'],
|
|
39
|
-
func: async (
|
|
39
|
+
func: async (args) => {
|
|
40
40
|
const secid = resolveSecid(args.symbol);
|
|
41
41
|
const periodKey = String(args.period ?? 'day').toLowerCase();
|
|
42
42
|
const klt = PERIOD_MAP[periodKey];
|
|
@@ -26,7 +26,7 @@ cli({
|
|
|
26
26
|
{ name: 'limit', type: 'int', default: 20, help: '返回数量 (max 100)' },
|
|
27
27
|
],
|
|
28
28
|
columns: ['time', 'title', 'summary', 'stocks'],
|
|
29
|
-
func: async (
|
|
29
|
+
func: async (args) => {
|
|
30
30
|
const column = String(args.column ?? '102').trim();
|
|
31
31
|
const limit = Math.max(1, Math.min(Number(args.limit) || 20, 100));
|
|
32
32
|
|
package/clis/eastmoney/longhu.js
CHANGED
|
@@ -26,7 +26,7 @@ cli({
|
|
|
26
26
|
{ name: 'limit', type: 'int', default: 20, help: '返回数量 (max 100)' },
|
|
27
27
|
],
|
|
28
28
|
columns: ['tradeDate', 'code', 'name', 'closePrice', 'changeRate', 'boardAmt', 'buyAmt', 'sellAmt', 'netAmt', 'turnover', 'dealRatio', 'market', 'reason'],
|
|
29
|
-
func: async (
|
|
29
|
+
func: async (args) => {
|
|
30
30
|
const sinceDate = String(args.date || '').trim() || defaultTradeDate();
|
|
31
31
|
const limit = Math.max(1, Math.min(Number(args.limit) || 20, 100));
|
|
32
32
|
|
|
@@ -28,7 +28,7 @@ cli({
|
|
|
28
28
|
{ name: 'limit', type: 'int', default: 20, help: '返回数量 (max 100)' },
|
|
29
29
|
],
|
|
30
30
|
columns: ['rank', 'code', 'name', 'price', 'changePercent', 'mainNet', 'mainNetRatio', 'superNet', 'bigNet', 'mediumNet', 'smallNet'],
|
|
31
|
-
func: async (
|
|
31
|
+
func: async (args) => {
|
|
32
32
|
const rangeKey = String(args.range ?? 'today').toLowerCase();
|
|
33
33
|
const range = RANGES[rangeKey];
|
|
34
34
|
if (!range) {
|
|
@@ -19,7 +19,7 @@ cli({
|
|
|
19
19
|
{ name: 'limit', type: 'int', default: 10, help: '返回最近 N 分钟' },
|
|
20
20
|
],
|
|
21
21
|
columns: ['time', 'cumulativeNetYi', 'minuteNetYi', 'totalNetYi'],
|
|
22
|
-
func: async (
|
|
22
|
+
func: async (args) => {
|
|
23
23
|
const dir = String(args.direction ?? 'north').toLowerCase();
|
|
24
24
|
if (!['north', 'south', 'n', 's'].includes(dir)) {
|
|
25
25
|
throw new CliError('INVALID_ARGUMENT', `Unknown direction "${dir}". Valid: north / south`);
|
package/clis/eastmoney/quote.js
CHANGED
|
@@ -57,7 +57,7 @@ cli({
|
|
|
57
57
|
'turnoverRate', 'amplitude', 'peDynamic', 'priceBook',
|
|
58
58
|
'marketCap', 'floatMarketCap',
|
|
59
59
|
],
|
|
60
|
-
func: async (
|
|
60
|
+
func: async (args) => {
|
|
61
61
|
const raw = splitSymbols(args.symbols);
|
|
62
62
|
if (raw.length === 0) {
|
|
63
63
|
throw new CliError('INVALID_ARGUMENT', 'At least one symbol is required');
|
package/clis/eastmoney/rank.js
CHANGED
|
@@ -43,7 +43,7 @@ cli({
|
|
|
43
43
|
{ name: 'limit', type: 'int', default: 20, help: '返回数量 (max 100)' },
|
|
44
44
|
],
|
|
45
45
|
columns: ['rank', 'code', 'name', 'price', 'changePercent', 'change', 'turnover', 'volume', 'turnoverRate', 'peDynamic', 'marketCap'],
|
|
46
|
-
func: async (
|
|
46
|
+
func: async (args) => {
|
|
47
47
|
const market = String(args.market ?? 'hs-a').toLowerCase();
|
|
48
48
|
const sortKey = String(args.sort ?? 'change').toLowerCase();
|
|
49
49
|
const limit = Math.max(1, Math.min(Number(args.limit) || 20, 100));
|
|
@@ -33,7 +33,7 @@ cli({
|
|
|
33
33
|
{ name: 'limit', type: 'int', default: 20, help: '返回数量 (max 100)' },
|
|
34
34
|
],
|
|
35
35
|
columns: ['rank', 'code', 'name', 'price', 'changePercent', 'mainNet', 'leadStock', 'leadChangePercent', 'upCount', 'downCount'],
|
|
36
|
-
func: async (
|
|
36
|
+
func: async (args) => {
|
|
37
37
|
const typeKey = String(args.type ?? 'industry').toLowerCase();
|
|
38
38
|
const fs = SECTOR_TYPES[typeKey];
|
|
39
39
|
if (!fs) throw new CliError('INVALID_ARGUMENT', `Unknown sector type "${typeKey}". Valid: ${Object.keys(SECTOR_TYPES).join(', ')}`);
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { cli, Strategy } from '@jackwener/opencli/registry';
|
|
2
|
+
import { ArgumentError, AuthRequiredError, CommandExecutionError, EmptyResultError } from '@jackwener/opencli/errors';
|
|
3
|
+
|
|
4
|
+
function normalizeLimit(value) {
|
|
5
|
+
const limit = Number(value ?? 20);
|
|
6
|
+
if (!Number.isInteger(limit) || limit <= 0) {
|
|
7
|
+
throw new ArgumentError('facebook marketplace-inbox --limit must be a positive integer');
|
|
8
|
+
}
|
|
9
|
+
return Math.min(limit, 100);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
cli({
|
|
13
|
+
site: 'facebook',
|
|
14
|
+
name: 'marketplace-inbox',
|
|
15
|
+
description: 'List recent Facebook Marketplace buyer/seller conversations',
|
|
16
|
+
domain: 'www.facebook.com',
|
|
17
|
+
strategy: Strategy.COOKIE,
|
|
18
|
+
args: [
|
|
19
|
+
{ name: 'limit', type: 'int', default: 20, help: 'Number of conversations to return' },
|
|
20
|
+
],
|
|
21
|
+
columns: ['index', 'buyer', 'listing', 'snippet', 'time', 'unread'],
|
|
22
|
+
func: async (page, args) => {
|
|
23
|
+
if (!page) throw new CommandExecutionError('Browser session required for facebook marketplace-inbox');
|
|
24
|
+
const limit = normalizeLimit(args.limit);
|
|
25
|
+
await page.goto('https://www.facebook.com/marketplace/inbox/');
|
|
26
|
+
await page.wait(4);
|
|
27
|
+
|
|
28
|
+
const result = await page.evaluate(String.raw`(() => {
|
|
29
|
+
const clean = (s) => String(s || '').replace(/[\u00a0\u202f]/g, ' ').replace(/\s+/g, ' ').trim();
|
|
30
|
+
const timeRe = /^(?:\d{1,2}:\d{2}\s?(?:AM|PM|am|pm|上午|下午)?|Mon|Tue|Wed|Thu|Fri|Sat|Sun|Today|Yesterday|\d+[mhdw]|\d+\s*(?:min|h|d|w))$/;
|
|
31
|
+
const text = document.body?.innerText || '';
|
|
32
|
+
if (/log in|sign in/i.test(text) && !/Marketplace/i.test(text)) {
|
|
33
|
+
return { authRequired: true, rows: [] };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const lines = text.split(/\n+/).map(clean).filter(Boolean);
|
|
37
|
+
const out = [];
|
|
38
|
+
const seen = new Set();
|
|
39
|
+
const skipBuyer = /^(Marketplace|Browse all|Notifications|Inbox|Marketplace access|Buying|Selling|Create new listing|Create multiple listings|Location|Categories|Vehicles|Property Rentals|All|Pending payment|Paid|To be shipped|Shipped|Cash on delivery|Completed|Filter by label)$/i;
|
|
40
|
+
for (let i = 0; i < lines.length - 2; i += 1) {
|
|
41
|
+
const buyer = lines[i];
|
|
42
|
+
const meta = lines[i + 1];
|
|
43
|
+
if (skipBuyer.test(buyer) || !/^·\s+/.test(meta)) continue;
|
|
44
|
+
const listing = meta.replace(/^·\s*/, '');
|
|
45
|
+
if (!listing || /^Within\b/i.test(listing)) continue;
|
|
46
|
+
const snippet = lines[i + 2] || '';
|
|
47
|
+
const time = timeRe.test(lines[i + 3] || '') ? lines[i + 3] : '';
|
|
48
|
+
const key = buyer + '|' + listing;
|
|
49
|
+
if (seen.has(key)) continue;
|
|
50
|
+
seen.add(key);
|
|
51
|
+
const nearby = lines.slice(Math.max(0, i - 2), i + 5).join(' ');
|
|
52
|
+
out.push({
|
|
53
|
+
buyer,
|
|
54
|
+
listing,
|
|
55
|
+
snippet,
|
|
56
|
+
time,
|
|
57
|
+
unread: /Unread/i.test(nearby),
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
return { authRequired: false, rows: out };
|
|
61
|
+
})()`);
|
|
62
|
+
|
|
63
|
+
if (result?.authRequired) {
|
|
64
|
+
throw new AuthRequiredError('facebook.com', 'Facebook Marketplace inbox requires an active signed-in Facebook session.');
|
|
65
|
+
}
|
|
66
|
+
const items = Array.isArray(result?.rows) ? result.rows : [];
|
|
67
|
+
if (items.length === 0) {
|
|
68
|
+
throw new EmptyResultError('facebook marketplace-inbox', 'No Marketplace inbox conversations were visible. Check that Marketplace inbox is available for this account.');
|
|
69
|
+
}
|
|
70
|
+
return items.slice(0, limit).map((item, index) => ({
|
|
71
|
+
index: index + 1,
|
|
72
|
+
buyer: item.buyer || '',
|
|
73
|
+
listing: item.listing || '',
|
|
74
|
+
snippet: item.snippet || '',
|
|
75
|
+
time: item.time || '',
|
|
76
|
+
unread: Boolean(item.unread),
|
|
77
|
+
}));
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
export const __test__ = {
|
|
82
|
+
normalizeLimit,
|
|
83
|
+
};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { cli, Strategy } from '@jackwener/opencli/registry';
|
|
2
|
+
import { ArgumentError, AuthRequiredError, CommandExecutionError, EmptyResultError } from '@jackwener/opencli/errors';
|
|
3
|
+
|
|
4
|
+
function normalizeLimit(value) {
|
|
5
|
+
const limit = Number(value ?? 20);
|
|
6
|
+
if (!Number.isInteger(limit) || limit <= 0) {
|
|
7
|
+
throw new ArgumentError('facebook marketplace-listings --limit must be a positive integer');
|
|
8
|
+
}
|
|
9
|
+
return Math.min(limit, 100);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
cli({
|
|
13
|
+
site: 'facebook',
|
|
14
|
+
name: 'marketplace-listings',
|
|
15
|
+
description: 'List your Facebook Marketplace seller listings',
|
|
16
|
+
domain: 'www.facebook.com',
|
|
17
|
+
strategy: Strategy.COOKIE,
|
|
18
|
+
args: [
|
|
19
|
+
{ name: 'limit', type: 'int', default: 20, help: 'Number of listings to return' },
|
|
20
|
+
],
|
|
21
|
+
columns: ['index', 'title', 'price', 'status', 'listed', 'clicks', 'actions'],
|
|
22
|
+
func: async (page, args) => {
|
|
23
|
+
if (!page) throw new CommandExecutionError('Browser session required for facebook marketplace-listings');
|
|
24
|
+
const limit = normalizeLimit(args.limit);
|
|
25
|
+
await page.goto('https://www.facebook.com/marketplace/you/selling/');
|
|
26
|
+
await page.wait(4);
|
|
27
|
+
|
|
28
|
+
const result = await page.evaluate(String.raw`(() => {
|
|
29
|
+
const clean = (s) => String(s || '').replace(/\u00a0/g, ' ').replace(/\s+/g, ' ').trim();
|
|
30
|
+
const allText = document.body?.innerText || '';
|
|
31
|
+
if (/log in|sign in/i.test(allText) && !/Marketplace/i.test(allText)) {
|
|
32
|
+
return { authRequired: true, rows: [] };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const lines = allText.split(/\n+/).map(clean).filter(Boolean);
|
|
36
|
+
const seen = new Set();
|
|
37
|
+
const out = [];
|
|
38
|
+
for (let i = 1; i < lines.length; i += 1) {
|
|
39
|
+
if (!/^(?:CA\$|\$)\s*\d+/.test(lines[i])) continue;
|
|
40
|
+
const title = lines[i - 1];
|
|
41
|
+
if (!title || /^(Hide|All listings|Needs attention|Marketplace|Selling)$/i.test(title)) continue;
|
|
42
|
+
if (seen.has(title)) continue;
|
|
43
|
+
seen.add(title);
|
|
44
|
+
const windowLines = lines.slice(i, i + 12);
|
|
45
|
+
const status = windowLines.find((line) => /^(Active|Sold|Pending|Draft)$/i.test(line)) || '';
|
|
46
|
+
const listed = windowLines.find((line) => /Listed on\b/i.test(line))?.replace(/^·\s*/, '') || '';
|
|
47
|
+
const clickLine = windowLines.find((line) => /clicks? on listing/i.test(line)) || '';
|
|
48
|
+
const clickMatch = clickLine.match(/([\d,.]+)\s+clicks? on listing/i);
|
|
49
|
+
const actions = windowLines.filter((line) => /^(Mark as sold|Mark as available|Relist this item|Share|Boost listing)$/i.test(line));
|
|
50
|
+
out.push({
|
|
51
|
+
title,
|
|
52
|
+
price: lines[i],
|
|
53
|
+
status,
|
|
54
|
+
listed,
|
|
55
|
+
clicks: clickMatch ? clickMatch[1] : '',
|
|
56
|
+
actions,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
return { authRequired: false, rows: out };
|
|
60
|
+
})()`);
|
|
61
|
+
|
|
62
|
+
if (result?.authRequired) {
|
|
63
|
+
throw new AuthRequiredError('facebook.com', 'Facebook Marketplace seller listings require an active signed-in Facebook session.');
|
|
64
|
+
}
|
|
65
|
+
const items = Array.isArray(result?.rows) ? result.rows : [];
|
|
66
|
+
if (items.length === 0) {
|
|
67
|
+
throw new EmptyResultError('facebook marketplace-listings', 'No seller listings were visible. Check that Marketplace selling is available for this account.');
|
|
68
|
+
}
|
|
69
|
+
return items.slice(0, limit).map((item, index) => ({
|
|
70
|
+
index: index + 1,
|
|
71
|
+
title: item.title || '',
|
|
72
|
+
price: item.price || '',
|
|
73
|
+
status: item.status || '',
|
|
74
|
+
listed: item.listed || '',
|
|
75
|
+
clicks: item.clicks || '',
|
|
76
|
+
actions: Array.isArray(item.actions) ? item.actions.join(', ') : String(item.actions || ''),
|
|
77
|
+
}));
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
export const __test__ = {
|
|
82
|
+
normalizeLimit,
|
|
83
|
+
};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { getRegistry } from '@jackwener/opencli/registry';
|
|
3
|
+
import { ArgumentError, AuthRequiredError, EmptyResultError } from '@jackwener/opencli/errors';
|
|
4
|
+
import './marketplace-listings.js';
|
|
5
|
+
import './marketplace-inbox.js';
|
|
6
|
+
|
|
7
|
+
function makePage(overrides = {}) {
|
|
8
|
+
return {
|
|
9
|
+
goto: vi.fn().mockResolvedValue(undefined),
|
|
10
|
+
wait: vi.fn().mockResolvedValue(undefined),
|
|
11
|
+
evaluate: vi.fn().mockResolvedValue([]),
|
|
12
|
+
...overrides,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
describe('facebook marketplace read commands', () => {
|
|
17
|
+
it('marketplace-listings navigates to selling page and returns limited listing rows', async () => {
|
|
18
|
+
const command = getRegistry().get('facebook/marketplace-listings');
|
|
19
|
+
expect(command).toBeDefined();
|
|
20
|
+
const page = makePage({
|
|
21
|
+
evaluate: vi.fn().mockResolvedValue({
|
|
22
|
+
authRequired: false,
|
|
23
|
+
rows: [
|
|
24
|
+
{ title: 'Black electric standing desk', price: 'CA$80', status: 'Active', listed: 'Listed on 4/26', clicks: '87', actions: ['Mark as sold', 'Share'] },
|
|
25
|
+
{ title: 'Large gray corduroy beanbag chair', price: 'CA$30', status: 'Sold', listed: 'Listed on 4/26', clicks: '52', actions: ['Mark as available', 'Relist this item'] },
|
|
26
|
+
],
|
|
27
|
+
}),
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const rows = await command.func(page, { limit: 1 });
|
|
31
|
+
|
|
32
|
+
expect(page.goto).toHaveBeenCalledWith('https://www.facebook.com/marketplace/you/selling/');
|
|
33
|
+
expect(page.wait).toHaveBeenCalledWith(4);
|
|
34
|
+
expect(rows).toEqual([
|
|
35
|
+
{
|
|
36
|
+
index: 1,
|
|
37
|
+
title: 'Black electric standing desk',
|
|
38
|
+
price: 'CA$80',
|
|
39
|
+
status: 'Active',
|
|
40
|
+
listed: 'Listed on 4/26',
|
|
41
|
+
clicks: '87',
|
|
42
|
+
actions: 'Mark as sold, Share',
|
|
43
|
+
},
|
|
44
|
+
]);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('marketplace-inbox navigates to inbox and returns recent buyer conversations', async () => {
|
|
48
|
+
const command = getRegistry().get('facebook/marketplace-inbox');
|
|
49
|
+
expect(command).toBeDefined();
|
|
50
|
+
const page = makePage({
|
|
51
|
+
evaluate: vi.fn().mockResolvedValue({
|
|
52
|
+
authRequired: false,
|
|
53
|
+
rows: [
|
|
54
|
+
{ buyer: 'Kulwant', listing: 'White 3-tier rolling utility cart', snippet: 'Can I pick up today?', time: '3:43 PM', unread: true },
|
|
55
|
+
{ buyer: 'Gabriel', listing: 'Black electric standing desk', snippet: 'Yes, still available.', time: '12:17 PM', unread: false },
|
|
56
|
+
],
|
|
57
|
+
}),
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const rows = await command.func(page, { limit: 2 });
|
|
61
|
+
|
|
62
|
+
expect(page.goto).toHaveBeenCalledWith('https://www.facebook.com/marketplace/inbox/');
|
|
63
|
+
expect(page.wait).toHaveBeenCalledWith(4);
|
|
64
|
+
expect(rows).toEqual([
|
|
65
|
+
{ index: 1, buyer: 'Kulwant', listing: 'White 3-tier rolling utility cart', snippet: 'Can I pick up today?', time: '3:43 PM', unread: true },
|
|
66
|
+
{ index: 2, buyer: 'Gabriel', listing: 'Black electric standing desk', snippet: 'Yes, still available.', time: '12:17 PM', unread: false },
|
|
67
|
+
]);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('throws EmptyResultError when Marketplace returns no inbox rows', async () => {
|
|
71
|
+
const command = getRegistry().get('facebook/marketplace-inbox');
|
|
72
|
+
const page = makePage({ evaluate: vi.fn().mockResolvedValue({ authRequired: false, rows: [] }) });
|
|
73
|
+
|
|
74
|
+
await expect(command.func(page, { limit: 5 })).rejects.toThrow(EmptyResultError);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('throws AuthRequiredError when Marketplace returns a login page', async () => {
|
|
78
|
+
const command = getRegistry().get('facebook/marketplace-listings');
|
|
79
|
+
const page = makePage({ evaluate: vi.fn().mockResolvedValue({ authRequired: true, rows: [] }) });
|
|
80
|
+
|
|
81
|
+
await expect(command.func(page, { limit: 5 })).rejects.toThrow(AuthRequiredError);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('throws ArgumentError for invalid limits', async () => {
|
|
85
|
+
const command = getRegistry().get('facebook/marketplace-listings');
|
|
86
|
+
const page = makePage();
|
|
87
|
+
|
|
88
|
+
await expect(command.func(page, { limit: 0 })).rejects.toThrow(ArgumentError);
|
|
89
|
+
expect(page.goto).not.toHaveBeenCalled();
|
|
90
|
+
});
|
|
91
|
+
});
|
package/clis/google/news.js
CHANGED
|
@@ -18,7 +18,7 @@ cli({
|
|
|
18
18
|
{ name: 'region', default: 'US', help: 'Region code (e.g. US, CN)' },
|
|
19
19
|
],
|
|
20
20
|
columns: ['title', 'source', 'date', 'url'],
|
|
21
|
-
func: async (
|
|
21
|
+
func: async (args) => {
|
|
22
22
|
const limit = Math.max(1, Math.min(Number(args.limit), 100));
|
|
23
23
|
const lang = encodeURIComponent(args.lang);
|
|
24
24
|
const region = encodeURIComponent(args.region);
|
package/clis/google/suggest.js
CHANGED
|
@@ -15,7 +15,7 @@ cli({
|
|
|
15
15
|
{ name: 'lang', default: 'zh-CN', help: 'Language code' },
|
|
16
16
|
],
|
|
17
17
|
columns: ['suggestion'],
|
|
18
|
-
func: async (
|
|
18
|
+
func: async (args) => {
|
|
19
19
|
const keyword = encodeURIComponent(args.keyword);
|
|
20
20
|
const lang = encodeURIComponent(args.lang);
|
|
21
21
|
const url = `https://suggestqueries.google.com/complete/search?client=firefox&q=${keyword}&hl=${lang}`;
|
package/clis/google/trends.js
CHANGED
|
@@ -16,7 +16,7 @@ cli({
|
|
|
16
16
|
{ name: 'limit', type: 'int', default: 20, help: 'Number of results' },
|
|
17
17
|
],
|
|
18
18
|
columns: ['title', 'traffic', 'date'],
|
|
19
|
-
func: async (
|
|
19
|
+
func: async (args) => {
|
|
20
20
|
const limit = Math.max(1, Math.min(Number(args.limit), 100));
|
|
21
21
|
const region = encodeURIComponent(args.region);
|
|
22
22
|
const url = `https://trends.google.com/trending/rss?geo=${region}`;
|