@jackwener/opencli 1.7.12 → 1.7.13
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 +8 -7
- package/README.zh-CN.md +9 -8
- package/cli-manifest.json +12194 -6843
- package/clis/1point3acres/digest.js +35 -0
- package/clis/1point3acres/forum.js +51 -0
- package/clis/1point3acres/forums.js +44 -0
- package/clis/1point3acres/hot.js +35 -0
- package/clis/1point3acres/latest.js +35 -0
- package/clis/1point3acres/notifications.js +64 -0
- package/clis/1point3acres/search.js +71 -0
- package/clis/1point3acres/thread.js +117 -0
- package/clis/1point3acres/user.js +77 -0
- package/clis/1point3acres/utils.js +247 -0
- package/clis/_shared/desktop-commands.js +4 -0
- package/clis/aibase/news.js +110 -0
- package/clis/aibase/news.test.js +59 -0
- package/clis/amazon/discussion.test.js +1 -28
- package/clis/antigravity/watch.js +3 -2
- package/clis/arxiv/author.js +44 -0
- package/clis/baidu-scholar/search.js +0 -1
- package/clis/bbc/topic.js +57 -0
- package/clis/bbc/utils.js +79 -0
- package/clis/chaoxing/assignments.js +1 -1
- package/clis/chaoxing/exams.js +1 -1
- package/clis/chatgpt/ask.js +57 -0
- package/clis/chatgpt/commands.test.js +45 -0
- package/clis/chatgpt/detail.js +46 -0
- package/clis/chatgpt/history.js +39 -0
- package/clis/chatgpt/image.js +12 -11
- package/clis/chatgpt/image.test.js +23 -0
- package/clis/chatgpt/new.js +25 -0
- package/clis/chatgpt/read.js +43 -0
- package/clis/chatgpt/send.js +46 -0
- package/clis/chatgpt/status.js +29 -0
- package/clis/chatgpt/utils.js +294 -4
- package/clis/chatgpt/utils.test.js +13 -0
- package/clis/chatgpt-app/ask.js +6 -3
- package/clis/chatwise/ask.js +16 -43
- package/clis/chatwise/composer.test.js +186 -0
- package/clis/chatwise/send.js +2 -24
- package/clis/chatwise/utils.js +143 -0
- package/clis/claude/ask.js +1 -1
- package/clis/claude/detail.js +1 -0
- package/clis/claude/history.js +1 -0
- package/clis/claude/new.js +1 -0
- package/clis/claude/read.js +1 -0
- package/clis/claude/send.js +1 -0
- package/clis/claude/status.js +1 -0
- package/clis/codex/ask.js +15 -9
- package/clis/codex/history.js +16 -33
- package/clis/codex/projects.js +28 -0
- package/clis/codex/read.js +10 -4
- package/clis/codex/send.js +10 -3
- package/clis/codex/sidebar.js +356 -0
- package/clis/codex/sidebar.test.js +329 -0
- package/clis/coingecko/categories.js +75 -0
- package/clis/coingecko/coin.js +107 -0
- package/clis/coingecko/coingecko.test.js +109 -0
- package/clis/coingecko/derivatives.js +84 -0
- package/clis/coingecko/exchanges.js +74 -0
- package/clis/coingecko/global.js +71 -0
- package/clis/coingecko/top.js +64 -0
- package/clis/coingecko/trending.js +55 -0
- package/clis/coupang/add-to-cart.js +21 -13
- package/clis/coupang/coupang.test.js +159 -0
- package/clis/coupang/product.js +257 -0
- package/clis/coupang/search.js +38 -16
- package/clis/coupang/utils.js +55 -1
- package/clis/crates/crate.js +62 -0
- package/clis/crates/search.js +44 -0
- package/clis/crates/utils.js +72 -0
- package/clis/ctrip/ctrip.test.js +234 -0
- package/clis/ctrip/hotel-suggest.js +45 -0
- package/clis/ctrip/search.js +22 -68
- package/clis/ctrip/utils.js +175 -0
- package/clis/cursor/ask.js +6 -3
- package/clis/dblp/author.js +133 -0
- package/clis/dblp/venue.js +64 -0
- package/clis/deepseek/ask.js +12 -7
- package/clis/deepseek/ask.test.js +13 -13
- package/clis/deepseek/detail.js +38 -0
- package/clis/deepseek/detail.test.js +81 -0
- package/clis/deepseek/history.js +1 -0
- package/clis/deepseek/new.js +1 -0
- package/clis/deepseek/read.js +1 -0
- package/clis/deepseek/send.js +140 -0
- package/clis/deepseek/send.test.js +107 -0
- package/clis/deepseek/status.js +1 -0
- package/clis/deepseek/utils.js +66 -0
- package/clis/deepseek/utils.test.js +107 -1
- package/clis/defillama/defillama.test.js +99 -0
- package/clis/defillama/protocol.js +84 -0
- package/clis/defillama/protocols.js +55 -0
- package/clis/defillama/utils.js +99 -0
- package/clis/devto/latest.js +74 -0
- package/clis/dockerhub/image.js +52 -0
- package/clis/dockerhub/search.js +47 -0
- package/clis/dockerhub/utils.js +100 -0
- package/clis/doubao/ask.js +7 -3
- package/clis/doubao/detail.js +1 -0
- package/clis/doubao/history.js +1 -0
- package/clis/doubao/meeting-summary.js +1 -0
- package/clis/doubao/meeting-transcript.js +1 -0
- package/clis/doubao/new.js +1 -0
- package/clis/doubao/read.js +1 -0
- package/clis/doubao/send.js +1 -0
- package/clis/doubao/status.js +1 -0
- package/clis/douyin/draft.test.js +1 -30
- package/clis/endoflife/endoflife.test.js +51 -0
- package/clis/endoflife/product.js +55 -0
- package/clis/endoflife/utils.js +89 -0
- package/clis/facebook/__fixtures__/notifications-page.html +13 -0
- package/clis/facebook/notifications.js +326 -30
- package/clis/facebook/notifications.test.js +458 -0
- package/clis/flathub/app.js +71 -0
- package/clis/flathub/flathub.test.js +90 -0
- package/clis/flathub/search.js +80 -0
- package/clis/flathub/utils.js +114 -0
- package/clis/gemini/ask.js +7 -3
- package/clis/gemini/ask.test.js +2 -2
- package/clis/gemini/deep-research-result.js +6 -2
- package/clis/gemini/deep-research-result.test.js +15 -14
- package/clis/gemini/deep-research.js +8 -4
- package/clis/gemini/deep-research.test.js +15 -18
- package/clis/gemini/image.js +7 -2
- package/clis/gemini/new.js +1 -0
- package/clis/gemini/utils.js +0 -4
- package/clis/google-scholar/cite.js +0 -1
- package/clis/google-scholar/profile.js +0 -1
- package/clis/google-scholar/search.js +0 -1
- package/clis/goproxy/goproxy.test.js +103 -0
- package/clis/goproxy/module.js +47 -0
- package/clis/goproxy/utils.js +165 -0
- package/clis/goproxy/versions.js +59 -0
- package/clis/gov-law/recent.js +0 -1
- package/clis/gov-law/search.js +0 -1
- package/clis/gov-policy/__fixtures__/recent.html +16 -0
- package/clis/gov-policy/__fixtures__/search.html +41 -0
- package/clis/gov-policy/gov-policy.test.js +224 -0
- package/clis/gov-policy/recent.js +66 -24
- package/clis/gov-policy/search.js +65 -23
- package/clis/gov-policy/utils.js +54 -0
- package/clis/grok/ask.js +49 -265
- package/clis/grok/ask.test.js +21 -46
- package/clis/grok/detail.js +60 -0
- package/clis/grok/history.js +48 -0
- package/clis/grok/{image.ts → image.js} +56 -70
- package/clis/grok/image.test.ts +20 -0
- package/clis/grok/new.js +20 -0
- package/clis/grok/read.js +39 -0
- package/clis/grok/send.js +50 -0
- package/clis/grok/status.js +41 -0
- package/clis/grok/utils.js +326 -0
- package/clis/grok/utils.test.js +103 -0
- package/clis/hf/datasets.js +88 -0
- package/clis/hf/hf.test.js +16 -0
- package/clis/hf/models.js +91 -0
- package/clis/hf/paper.js +79 -0
- package/clis/hf/spaces.js +101 -0
- package/clis/hf/top.js +1 -0
- package/clis/homebrew/cask.js +39 -0
- package/clis/homebrew/formula.js +41 -0
- package/clis/homebrew/popular.js +54 -0
- package/clis/homebrew/utils.js +100 -0
- package/clis/hupu/__fixtures__/hot-home.html +64 -0
- package/clis/hupu/detail.js +0 -1
- package/clis/hupu/hot.js +156 -35
- package/clis/hupu/hot.test.js +224 -0
- package/clis/hupu/search.js +0 -1
- package/clis/instagram/note.js +1 -1
- package/clis/instagram/note.test.js +1 -29
- package/clis/instagram/post.js +1 -1
- package/clis/instagram/post.test.js +1 -1
- package/clis/instagram/reel.js +1 -1
- package/clis/instagram/story.js +1 -1
- package/clis/instagram/story.test.js +1 -34
- package/clis/jd/commands.test.js +1 -24
- package/clis/lichess/lichess.test.js +85 -0
- package/clis/lichess/top.js +46 -0
- package/clis/lichess/user.js +91 -0
- package/clis/lichess/utils.js +97 -0
- package/clis/linkedin/search.js +107 -10
- package/clis/linkedin/search.test.js +222 -0
- package/clis/linux-do/feed.js +2 -5
- package/clis/linux-do/feed.test.js +35 -0
- package/clis/lobsters/domain.js +92 -0
- package/clis/maven/artifact.js +49 -0
- package/clis/maven/search.js +51 -0
- package/clis/maven/utils.js +110 -0
- package/clis/mdn/search.js +97 -0
- package/clis/medium/tag.js +135 -0
- package/clis/npm/downloads.js +59 -0
- package/clis/npm/package.js +70 -0
- package/clis/npm/search.js +49 -0
- package/clis/npm/utils.js +76 -0
- package/clis/nuget/nuget.test.js +111 -0
- package/clis/nuget/package.js +101 -0
- package/clis/nuget/search.js +69 -0
- package/clis/nuget/utils.js +87 -0
- package/clis/nvd/cve.js +121 -0
- package/clis/oeis/oeis.test.js +88 -0
- package/clis/oeis/search.js +63 -0
- package/clis/oeis/sequence.js +71 -0
- package/clis/oeis/utils.js +88 -0
- package/clis/openalex/search.js +69 -0
- package/clis/openalex/utils.js +160 -0
- package/clis/openalex/work.js +65 -0
- package/clis/openfda/drug-label.js +74 -0
- package/clis/openfda/food-recall.js +65 -0
- package/clis/openfda/openfda.test.js +114 -0
- package/clis/openfda/utils.js +67 -0
- package/clis/osv/osv.test.js +97 -0
- package/clis/osv/query.js +72 -0
- package/clis/osv/utils.js +169 -0
- package/clis/osv/vulnerability.js +54 -0
- package/clis/packagist/package.js +49 -0
- package/clis/packagist/search.js +43 -0
- package/clis/packagist/utils.js +113 -0
- package/clis/paperreview/feedback.js +1 -1
- package/clis/paperreview/review.js +1 -1
- package/clis/paperreview/submit.js +1 -1
- package/clis/pixiv/download.test.js +1 -1
- package/clis/pixiv/illusts.test.js +1 -1
- package/clis/pixiv/search.test.js +1 -1
- package/clis/pubmed/article.js +50 -0
- package/clis/pubmed/author.js +64 -0
- package/clis/pubmed/citations.js +36 -0
- package/clis/pubmed/pubmed.test.js +276 -0
- package/clis/pubmed/related.js +45 -0
- package/clis/pubmed/search.js +75 -0
- package/clis/pubmed/utils.js +309 -0
- package/clis/pypi/downloads.js +66 -0
- package/clis/pypi/package.js +79 -0
- package/clis/pypi/utils.js +55 -0
- package/clis/quark/mv.js +1 -1
- package/clis/quark/save.js +1 -1
- package/clis/qwen/ask.js +85 -0
- package/clis/qwen/detail.js +62 -0
- package/clis/qwen/history.js +61 -0
- package/clis/qwen/image.js +179 -0
- package/clis/qwen/new.js +23 -0
- package/clis/qwen/read.js +41 -0
- package/clis/qwen/send.js +55 -0
- package/clis/qwen/status.js +37 -0
- package/clis/qwen/utils.js +409 -0
- package/clis/qwen/utils.test.js +45 -0
- package/clis/rest-countries/country.js +65 -0
- package/clis/rest-countries/region.js +64 -0
- package/clis/rest-countries/rest-countries.test.js +83 -0
- package/clis/rest-countries/utils.js +126 -0
- package/clis/reuters/article-detail.js +53 -0
- package/clis/reuters/reuters.test.js +299 -0
- package/clis/reuters/search.js +45 -34
- package/clis/reuters/utils.js +159 -0
- package/clis/rfc/rfc.js +52 -0
- package/clis/rfc/rfc.test.js +74 -0
- package/clis/rfc/utils.js +72 -0
- package/clis/rubygems/gem.js +42 -0
- package/clis/rubygems/search.js +47 -0
- package/clis/rubygems/utils.js +86 -0
- package/clis/stackoverflow/related.js +66 -0
- package/clis/stackoverflow/stackoverflow.test.js +58 -0
- package/clis/stackoverflow/tag.js +60 -0
- package/clis/stackoverflow/user.js +50 -0
- package/clis/stackoverflow/utils.js +118 -0
- package/clis/steam/app.js +67 -0
- package/clis/steam/search.js +58 -0
- package/clis/steam/steam.test.js +46 -0
- package/clis/steam/utils.js +107 -0
- package/clis/taobao/commands.test.js +1 -24
- package/clis/test-utils.js +61 -0
- package/clis/tieba/hot.js +0 -1
- package/clis/tiktok/comment.js +128 -41
- package/clis/tiktok/creator-videos.js +270 -0
- package/clis/tiktok/creator-videos.test.js +113 -0
- package/clis/tiktok/explore.js +137 -29
- package/clis/tiktok/follow.js +115 -33
- package/clis/tiktok/following.js +157 -36
- package/clis/tiktok/friends.js +139 -37
- package/clis/tiktok/live.js +137 -41
- package/clis/tiktok/notifications.js +141 -38
- package/clis/tiktok/refactor.test.js +389 -0
- package/clis/tiktok/unfollow.js +124 -38
- package/clis/tiktok/user.js +203 -29
- package/clis/tiktok/utils.js +505 -0
- package/clis/tiktok/write-refactor.test.js +370 -0
- package/clis/toutiao/articles.js +36 -62
- package/clis/toutiao/hot.js +63 -0
- package/clis/toutiao/toutiao.test.js +378 -0
- package/clis/toutiao/utils.js +161 -0
- package/clis/tvmaze/search.js +61 -0
- package/clis/tvmaze/show.js +60 -0
- package/clis/tvmaze/tvmaze.test.js +93 -0
- package/clis/tvmaze/utils.js +110 -0
- package/clis/twitter/accept.js +1 -1
- package/clis/twitter/followers.js +134 -69
- package/clis/twitter/reply-dm.js +1 -1
- package/clis/twitter/reply.test.js +1 -29
- package/clis/uisdc/news.js +105 -0
- package/clis/uisdc/news.test.js +66 -0
- package/clis/wanfang/search.js +0 -1
- package/clis/web/read.js +47 -17
- package/clis/web/read.test.js +101 -1
- package/clis/weixin/create-draft.js +1 -1
- package/clis/weixin/drafts.js +1 -1
- package/clis/weixin/drafts.test.js +5 -1
- package/clis/weixin/search.js +157 -0
- package/clis/weixin/search.test.js +227 -0
- package/clis/wikidata/entity.js +60 -0
- package/clis/wikidata/search.js +50 -0
- package/clis/wikidata/utils.js +117 -0
- package/clis/wikidata/wikidata.test.js +83 -0
- package/clis/wikipedia/page.js +95 -0
- package/clis/wttr/current.js +63 -0
- package/clis/wttr/forecast.js +71 -0
- package/clis/wttr/utils.js +50 -0
- package/clis/wttr/wttr.test.js +84 -0
- package/clis/xianyu/chat.js +16 -4
- package/clis/xianyu/chat.test.js +64 -0
- package/clis/xianyu/publish.js +485 -0
- package/clis/xianyu/publish.test.js +220 -0
- package/clis/xiaoe/catalog.js +105 -40
- package/clis/xiaoe/content.js +164 -29
- package/clis/xiaoe/courses.js +86 -29
- package/clis/xiaoe/xiaoe.test.js +486 -0
- package/clis/xiaohongshu/creator-notes-summary.js +1 -1
- package/clis/xiaohongshu/publish.js +16 -3
- package/clis/xiaohongshu/publish.test.js +46 -1
- package/clis/youtube/transcript.js +13 -19
- package/clis/youtube/transcript.test.js +17 -0
- package/clis/yuanbao/ask.js +17 -66
- package/clis/yuanbao/ask.test.js +5 -5
- package/clis/yuanbao/detail.js +65 -0
- package/clis/yuanbao/history.js +51 -0
- package/clis/yuanbao/new.js +1 -0
- package/clis/yuanbao/read.js +38 -0
- package/clis/yuanbao/send.js +57 -0
- package/clis/yuanbao/shared.js +297 -5
- package/clis/yuanbao/shared.test.js +80 -0
- package/clis/yuanbao/status.js +44 -0
- package/clis/zlibrary/commands.test.js +1 -11
- package/dist/src/browser/base-page.d.ts +9 -0
- package/dist/src/browser/base-page.js +44 -1
- package/dist/src/browser/base-page.test.js +66 -0
- package/dist/src/browser/cdp.d.ts +1 -0
- package/dist/src/browser/cdp.js +51 -9
- package/dist/src/browser/daemon-client.d.ts +4 -0
- package/dist/src/browser/errors.js +1 -1
- package/dist/src/browser/page.d.ts +1 -1
- package/dist/src/browser/page.js +3 -1
- package/dist/src/browser/page.test.js +29 -0
- package/dist/src/browser/target-errors.d.ts +2 -1
- package/dist/src/browser/target-errors.js +1 -0
- package/dist/src/browser/target-resolver.d.ts +25 -0
- package/dist/src/browser/target-resolver.js +43 -0
- package/dist/src/build-manifest.js +9 -4
- package/dist/src/build-manifest.test.js +2 -8
- package/dist/src/capabilityRouting.d.ts +16 -1
- package/dist/src/capabilityRouting.js +24 -1
- package/dist/src/capabilityRouting.test.js +19 -1
- package/dist/src/cli.js +76 -11
- package/dist/src/cli.test.js +150 -0
- package/dist/src/commanderAdapter.js +0 -5
- package/dist/src/commanderAdapter.test.js +0 -1
- package/dist/src/discovery.js +2 -5
- package/dist/src/errors.js +1 -1
- package/dist/src/execution.d.ts +1 -1
- package/dist/src/execution.js +111 -27
- package/dist/src/execution.test.js +326 -17
- package/dist/src/help.d.ts +23 -2
- package/dist/src/help.js +41 -19
- package/dist/src/help.test.d.ts +1 -0
- package/dist/src/help.test.js +54 -0
- package/dist/src/main.js +14 -1
- package/dist/src/manifest-types.d.ts +5 -3
- package/dist/src/pipeline/executor.js +1 -1
- package/dist/src/pipeline/executor.test.js +8 -0
- package/dist/src/pipeline/registry.d.ts +9 -0
- package/dist/src/pipeline/registry.js +13 -1
- package/dist/src/pipeline/steps/browser.d.ts +1 -0
- package/dist/src/pipeline/steps/browser.js +10 -0
- package/dist/src/pipeline/steps/download.test.js +1 -0
- package/dist/src/registry-api.d.ts +1 -1
- package/dist/src/registry.d.ts +12 -11
- package/dist/src/registry.js +16 -6
- package/dist/src/registry.test.js +2 -2
- package/dist/src/runtime.d.ts +2 -1
- package/dist/src/runtime.js +1 -1
- package/dist/src/serialization.d.ts +2 -2
- package/dist/src/serialization.js +4 -6
- package/dist/src/serialization.test.js +17 -0
- package/dist/src/types.d.ts +17 -0
- package/dist/src/validate.js +15 -11
- package/dist/src/validate.test.d.ts +9 -0
- package/dist/src/validate.test.js +90 -0
- package/package.json +1 -1
- package/scripts/fetch-adapters.js +1 -1
- package/scripts/typed-error-lint-baseline.json +5 -77
- package/clis/ctrip/search.test.js +0 -64
- package/clis/gov-policy/commands.test.js +0 -27
- package/clis/linux-do/category.js +0 -37
- package/clis/linux-do/hot.js +0 -26
- package/clis/linux-do/latest.js +0 -19
- package/clis/pixiv/test-utils.js +0 -23
- package/clis/toutiao/articles.test.js +0 -30
- package/dist/src/analysis.d.ts +0 -40
- package/dist/src/analysis.js +0 -172
|
@@ -0,0 +1,485 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { ArgumentError, AuthRequiredError, CommandExecutionError } from '@jackwener/opencli/errors';
|
|
4
|
+
import { cli, Strategy } from '@jackwener/opencli/registry';
|
|
5
|
+
|
|
6
|
+
const SUPPORTED_IMAGE_EXTENSIONS = new Set(['.jpg', '.jpeg', '.png', '.webp']);
|
|
7
|
+
const MAX_IMAGES = 9;
|
|
8
|
+
const CONDITION_CHOICES = ['全新', '几乎全新', '轻微使用', '明显使用', '老旧'];
|
|
9
|
+
|
|
10
|
+
function buildPublishUrl() {
|
|
11
|
+
return 'https://www.goofish.com/publish';
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async function getCurrentPageUrl(page) {
|
|
15
|
+
if (page.getCurrentUrl) {
|
|
16
|
+
try {
|
|
17
|
+
const currentUrl = await page.getCurrentUrl();
|
|
18
|
+
if (currentUrl) return currentUrl;
|
|
19
|
+
} catch {
|
|
20
|
+
// Best-effort URL is only used for operator diagnostics after submit.
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return buildPublishUrl();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function requireText(value, label) {
|
|
27
|
+
const text = String(value ?? '').replace(/\s+/g, ' ').trim();
|
|
28
|
+
if (!text) {
|
|
29
|
+
throw new ArgumentError(`xianyu publish ${label} cannot be empty`);
|
|
30
|
+
}
|
|
31
|
+
return text;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function parsePositivePrice(value, label) {
|
|
35
|
+
if (value == null || String(value).trim() === '') {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
const text = String(value).trim();
|
|
39
|
+
if (!/^\d+(?:\.\d{1,2})?$/.test(text)) {
|
|
40
|
+
throw new ArgumentError(`xianyu publish ${label} must be a positive price with at most 2 decimals`);
|
|
41
|
+
}
|
|
42
|
+
const price = Number(text);
|
|
43
|
+
if (!Number.isFinite(price) || price <= 0) {
|
|
44
|
+
throw new ArgumentError(`xianyu publish ${label} must be a positive price`);
|
|
45
|
+
}
|
|
46
|
+
return text;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function validateCondition(value) {
|
|
50
|
+
const condition = requireText(value, 'condition');
|
|
51
|
+
if (!CONDITION_CHOICES.includes(condition)) {
|
|
52
|
+
throw new ArgumentError(`xianyu publish condition must be one of: ${CONDITION_CHOICES.join(', ')}`);
|
|
53
|
+
}
|
|
54
|
+
return condition;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function validateImagePaths(raw) {
|
|
58
|
+
if (!raw) return [];
|
|
59
|
+
const paths = String(raw).split(',').map((item) => item.trim()).filter(Boolean);
|
|
60
|
+
if (paths.length === 0) return [];
|
|
61
|
+
if (paths.length > MAX_IMAGES) {
|
|
62
|
+
throw new ArgumentError(`xianyu publish images supports at most ${MAX_IMAGES} files`);
|
|
63
|
+
}
|
|
64
|
+
return paths.map((item) => {
|
|
65
|
+
const absPath = path.resolve(item);
|
|
66
|
+
const ext = path.extname(absPath).toLowerCase();
|
|
67
|
+
if (!SUPPORTED_IMAGE_EXTENSIONS.has(ext)) {
|
|
68
|
+
throw new ArgumentError(`Unsupported image format "${ext}". Supported: jpg, jpeg, png, webp`);
|
|
69
|
+
}
|
|
70
|
+
const stat = fs.statSync(absPath, { throwIfNoEntry: false });
|
|
71
|
+
if (!stat || !stat.isFile()) {
|
|
72
|
+
throw new ArgumentError(`Not a valid image file: ${absPath}`);
|
|
73
|
+
}
|
|
74
|
+
return absPath;
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function normalizePublishArgs(kwargs) {
|
|
79
|
+
const price = parsePositivePrice(kwargs.price, 'price');
|
|
80
|
+
if (price == null) {
|
|
81
|
+
throw new ArgumentError('xianyu publish price cannot be empty');
|
|
82
|
+
}
|
|
83
|
+
const normalized = {};
|
|
84
|
+
normalized.title = requireText(kwargs.title, 'title');
|
|
85
|
+
normalized.description = requireText(kwargs.description, 'description');
|
|
86
|
+
normalized.price = price;
|
|
87
|
+
normalized.condition = validateCondition(kwargs.condition);
|
|
88
|
+
normalized.category = requireText(kwargs.category, 'category');
|
|
89
|
+
normalized.original_price = parsePositivePrice(kwargs.original_price, 'original_price');
|
|
90
|
+
normalized.location = kwargs.location ? requireText(kwargs.location, 'location') : '';
|
|
91
|
+
normalized.images = validateImagePaths(kwargs.images);
|
|
92
|
+
return normalized;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ===== 表单填充 evaluate scripts =====
|
|
96
|
+
|
|
97
|
+
function buildFillFormEvaluate(data) {
|
|
98
|
+
return `
|
|
99
|
+
(() => {
|
|
100
|
+
const clean = (value) => String(value ?? '').replace(/\\s+/g, ' ').trim();
|
|
101
|
+
|
|
102
|
+
const filled = [];
|
|
103
|
+
const missing = [];
|
|
104
|
+
|
|
105
|
+
// 1. 填标题
|
|
106
|
+
const titleInput = document.querySelector('input[id*="title"], input[placeholder*="标题"], textarea[id*="title"], [class*="titleInput"]');
|
|
107
|
+
if (titleInput) {
|
|
108
|
+
const setter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value')?.set
|
|
109
|
+
|| Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, 'value')?.set;
|
|
110
|
+
if (setter) {
|
|
111
|
+
titleInput.focus();
|
|
112
|
+
setter.call(titleInput, ${JSON.stringify(data.title)});
|
|
113
|
+
titleInput.dispatchEvent(new Event('input', { bubbles: true }));
|
|
114
|
+
titleInput.dispatchEvent(new Event('change', { bubbles: true }));
|
|
115
|
+
filled.push('title');
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (!filled.includes('title')) missing.push('title');
|
|
119
|
+
|
|
120
|
+
// 2. 填描述
|
|
121
|
+
const descInput = document.querySelector('textarea[id*="desc"], textarea[id*="description"], [class*="descInput"], [class*="description"]');
|
|
122
|
+
if (descInput) {
|
|
123
|
+
const setter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, 'value')?.set;
|
|
124
|
+
if (setter) {
|
|
125
|
+
descInput.focus();
|
|
126
|
+
setter.call(descInput, ${JSON.stringify(data.description)});
|
|
127
|
+
descInput.dispatchEvent(new Event('input', { bubbles: true }));
|
|
128
|
+
descInput.dispatchEvent(new Event('change', { bubbles: true }));
|
|
129
|
+
filled.push('description');
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (!filled.includes('description')) missing.push('description');
|
|
133
|
+
|
|
134
|
+
// 3. 填价格
|
|
135
|
+
const priceInput = document.querySelector('input[id*="price"], input[placeholder*="价"], input[class*="price"]');
|
|
136
|
+
if (priceInput) {
|
|
137
|
+
const setter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value')?.set;
|
|
138
|
+
if (setter) {
|
|
139
|
+
priceInput.focus();
|
|
140
|
+
setter.call(priceInput, ${JSON.stringify(String(data.price))});
|
|
141
|
+
priceInput.dispatchEvent(new Event('input', { bubbles: true }));
|
|
142
|
+
priceInput.dispatchEvent(new Event('change', { bubbles: true }));
|
|
143
|
+
filled.push('price');
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if (!filled.includes('price')) missing.push('price');
|
|
147
|
+
|
|
148
|
+
// 4. 填原价(可选)
|
|
149
|
+
${data.original_price ? `
|
|
150
|
+
const originalPriceInput = document.querySelector('input[id*="original"], input[placeholder*="原价"], input[class*="original"]');
|
|
151
|
+
if (originalPriceInput) {
|
|
152
|
+
const setter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value')?.set;
|
|
153
|
+
if (setter) {
|
|
154
|
+
originalPriceInput.focus();
|
|
155
|
+
setter.call(originalPriceInput, ${JSON.stringify(String(data.original_price))});
|
|
156
|
+
originalPriceInput.dispatchEvent(new Event('input', { bubbles: true }));
|
|
157
|
+
originalPriceInput.dispatchEvent(new Event('change', { bubbles: true }));
|
|
158
|
+
filled.push('original_price');
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
` : ''}
|
|
162
|
+
|
|
163
|
+
// 5. 填地址(可选)
|
|
164
|
+
${data.location ? `
|
|
165
|
+
const locationInput = document.querySelector('input[id*="location"], input[placeholder*="地"], input[class*="location"]');
|
|
166
|
+
if (locationInput) {
|
|
167
|
+
const setter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value')?.set;
|
|
168
|
+
if (setter) {
|
|
169
|
+
locationInput.focus();
|
|
170
|
+
setter.call(locationInput, ${JSON.stringify(data.location)});
|
|
171
|
+
locationInput.dispatchEvent(new Event('input', { bubbles: true }));
|
|
172
|
+
locationInput.dispatchEvent(new Event('change', { bubbles: true }));
|
|
173
|
+
filled.push('location');
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
` : ''}
|
|
177
|
+
|
|
178
|
+
// 6. 选择成色(点击对应按钮)
|
|
179
|
+
if (${JSON.stringify(data.condition)}) {
|
|
180
|
+
const condition = ${JSON.stringify(data.condition)};
|
|
181
|
+
const conditionMap = {
|
|
182
|
+
'全新': ['全新', '全新未使用', 'new'],
|
|
183
|
+
'几乎全新': ['几乎全新', '几乎全新无瑕疵', 'like-new'],
|
|
184
|
+
'轻微使用': ['轻微使用', '轻微使用痕迹'],
|
|
185
|
+
'明显使用': ['明显使用', '有明显使用痕迹'],
|
|
186
|
+
'老旧': ['老旧', '年代久远', '二手'],
|
|
187
|
+
};
|
|
188
|
+
const keywords = conditionMap[condition] || [condition];
|
|
189
|
+
const allButtons = Array.from(document.querySelectorAll('button, [class*="tag"], [class*="condition"], [class*="level"], [role="button"]'));
|
|
190
|
+
const matchBtn = allButtons.find((el) => {
|
|
191
|
+
const text = clean(el.textContent || '');
|
|
192
|
+
return keywords.some((kw) => text === kw || text.includes(kw));
|
|
193
|
+
});
|
|
194
|
+
if (matchBtn) {
|
|
195
|
+
matchBtn.click();
|
|
196
|
+
filled.push('condition');
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if (!filled.includes('condition')) missing.push('condition');
|
|
200
|
+
|
|
201
|
+
return { ok: missing.length === 0, filled, missing };
|
|
202
|
+
})()
|
|
203
|
+
`;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function buildSelectCategoryEvaluate(categoryName) {
|
|
207
|
+
return `
|
|
208
|
+
(async () => {
|
|
209
|
+
const clean = (value) => String(value ?? '').replace(/\\s+/g, ' ').trim();
|
|
210
|
+
|
|
211
|
+
// 点击分类选择器
|
|
212
|
+
const categoryTrigger = Array.from(document.querySelectorAll('button, [class*="trigger"], [class*="selector"], [role="button"]'))
|
|
213
|
+
.find((el) => /分类|category|类目/.test(el.textContent || ''))
|
|
214
|
+
|| document.querySelector('[class*="category"], [class*="categorySelector"]');
|
|
215
|
+
|
|
216
|
+
if (categoryTrigger) {
|
|
217
|
+
categoryTrigger.click();
|
|
218
|
+
} else {
|
|
219
|
+
return { ok: false, reason: 'category-trigger-not-found' };
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// 等待分类弹窗/面板出现
|
|
223
|
+
const waitFor = async (predicate, timeoutMs = 5000) => {
|
|
224
|
+
const start = Date.now();
|
|
225
|
+
while (Date.now() - start < timeoutMs) {
|
|
226
|
+
if (predicate()) return true;
|
|
227
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
228
|
+
}
|
|
229
|
+
return false;
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
// 在分类弹窗中搜索并点击
|
|
233
|
+
const searchKeyword = ${JSON.stringify(categoryName)};
|
|
234
|
+
const hasMatch = await waitFor(() => {
|
|
235
|
+
const allNodes = Array.from(document.querySelectorAll('button, [class*="item"], [class*="node"], [role="option"]'));
|
|
236
|
+
return allNodes.some((el) => clean(el.textContent || '').includes(searchKeyword));
|
|
237
|
+
}, 5000);
|
|
238
|
+
|
|
239
|
+
if (!hasMatch) {
|
|
240
|
+
return { ok: false, reason: 'category-not-found' };
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const allNodes = Array.from(document.querySelectorAll('button, [class*="item"], [class*="node"], [role="option"]'));
|
|
244
|
+
const matchNode = allNodes.find((el) => clean(el.textContent || '').includes(searchKeyword));
|
|
245
|
+
if (matchNode) {
|
|
246
|
+
matchNode.click();
|
|
247
|
+
return { ok: true };
|
|
248
|
+
}
|
|
249
|
+
return { ok: false, reason: 'category-match-failed' };
|
|
250
|
+
})()
|
|
251
|
+
`;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function buildFindFileInputSelectorEvaluate() {
|
|
255
|
+
return `
|
|
256
|
+
(() => {
|
|
257
|
+
// 找图片上传相关的 file input
|
|
258
|
+
const fileInput = document.querySelector('input[type="file"]');
|
|
259
|
+
if (!fileInput) return { ok: false, reason: 'no-file-input' };
|
|
260
|
+
|
|
261
|
+
// 获取 selector 来唯一标识这个 input
|
|
262
|
+
const escapeAttr = (value) => String(value).replace(/["\\\\]/g, '\\\\$&');
|
|
263
|
+
const selector = fileInput.id ? '[id="' + escapeAttr(fileInput.id) + '"]'
|
|
264
|
+
: fileInput.name ? '[name="' + fileInput.name + '"]'
|
|
265
|
+
: fileInput.className ? 'input.' + fileInput.className.split(' ').join('.')
|
|
266
|
+
: 'input[type="file"]';
|
|
267
|
+
|
|
268
|
+
return { ok: true, selector, hasMultiple: fileInput.multiple };
|
|
269
|
+
})()
|
|
270
|
+
`;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function buildSubmitEvaluate() {
|
|
274
|
+
return `
|
|
275
|
+
(() => {
|
|
276
|
+
const clean = (value) => String(value ?? '').replace(/\\s+/g, ' ').trim();
|
|
277
|
+
|
|
278
|
+
// 找发布按钮
|
|
279
|
+
const submitBtn = Array.from(document.querySelectorAll('button'))
|
|
280
|
+
.find((btn) => {
|
|
281
|
+
const text = clean(btn.textContent || '');
|
|
282
|
+
return /发布|提交|上架|确认/.test(text) && !/取消/.test(text);
|
|
283
|
+
})
|
|
284
|
+
|| document.querySelector('[class*="publish"], [class*="submit"], [class*="confirm"]');
|
|
285
|
+
|
|
286
|
+
if (!submitBtn || submitBtn.disabled) {
|
|
287
|
+
return { ok: false, reason: 'submit-button-not-found-or-disabled' };
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
submitBtn.click();
|
|
291
|
+
return { ok: true };
|
|
292
|
+
})()
|
|
293
|
+
`;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function buildDetectSuccessEvaluate() {
|
|
297
|
+
return `
|
|
298
|
+
(() => {
|
|
299
|
+
const clean = (value) => String(value ?? '').replace(/\\s+/g, ' ').trim();
|
|
300
|
+
const bodyText = document.body?.innerText || '';
|
|
301
|
+
const url = window.location.href || '';
|
|
302
|
+
|
|
303
|
+
// 成功标志:URL 变为商品详情页
|
|
304
|
+
if (/item\\?id=\\d+/.test(url)) {
|
|
305
|
+
const match = url.match(/item\\?id=(\\d+)/);
|
|
306
|
+
return { status: 'published', item_id: match ? match[1] : '', url, message: '发布成功' };
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// 成功标志:页面出现"发布成功"
|
|
310
|
+
if (/发布成功|上架成功|发布完成/.test(bodyText)) {
|
|
311
|
+
const idMatch = url.match(/item\\?id=(\\d+)/) || bodyText.match(/id[::]?\\s*(\\d{10,})/);
|
|
312
|
+
return { status: 'published', item_id: idMatch ? (idMatch[1] || idMatch[0]) : '', url, message: '发布成功' };
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// 失败标志
|
|
316
|
+
const errMatch = Array.from(document.querySelectorAll('[class*="error"], [class*="fail"]'))
|
|
317
|
+
.map((el) => clean(el.textContent || ''))
|
|
318
|
+
.filter(Boolean);
|
|
319
|
+
if (errMatch.length || /发布失败|上架失败|异常|错误|违规/.test(bodyText)) {
|
|
320
|
+
return { status: 'failed', message: errMatch.join(' | ') || 'publish-failed' };
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
return { ok: false, reason: 'unknown-state' };
|
|
324
|
+
})()
|
|
325
|
+
`;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function buildExtractPageStateEvaluate() {
|
|
329
|
+
return `
|
|
330
|
+
(() => {
|
|
331
|
+
const clean = (value) => String(value ?? '').replace(/\\s+/g, ' ').trim();
|
|
332
|
+
const bodyText = document.body?.innerText || '';
|
|
333
|
+
const url = window.location.href || '';
|
|
334
|
+
|
|
335
|
+
const requiresAuth = /请先登录|登录后/.test(bodyText);
|
|
336
|
+
const hasPublishForm = /发布闲置|发布宝贝|闲置描述|标题|价格|成色|分类/.test(bodyText);
|
|
337
|
+
const hasCategorySelect = /选择分类|选择类目|分类选择/.test(bodyText);
|
|
338
|
+
const hasImageUpload = /上传图片|添加图片|photo|图片/.test(bodyText);
|
|
339
|
+
|
|
340
|
+
// 找各字段
|
|
341
|
+
const titleInput = !!document.querySelector('input[id*="title"], input[placeholder*="标题"], textarea[id*="title"]');
|
|
342
|
+
const descInput = !!document.querySelector('textarea[id*="desc"], textarea[id*="description"]');
|
|
343
|
+
const priceInput = !!document.querySelector('input[id*="price"], input[placeholder*="价"]');
|
|
344
|
+
const conditionSelect = !!document.querySelector('[class*="condition"], [class*="level"], button[class*="tag"]');
|
|
345
|
+
const submitBtn = Array.from(document.querySelectorAll('button'))
|
|
346
|
+
.find((btn) => /发布|提交|上架/.test(clean(btn.textContent || '')));
|
|
347
|
+
|
|
348
|
+
return {
|
|
349
|
+
requiresAuth,
|
|
350
|
+
hasPublishForm,
|
|
351
|
+
hasCategorySelect,
|
|
352
|
+
hasImageUpload,
|
|
353
|
+
titleInput,
|
|
354
|
+
descInput,
|
|
355
|
+
priceInput,
|
|
356
|
+
conditionSelect,
|
|
357
|
+
submitBtn: !!submitBtn,
|
|
358
|
+
pageUrl: url,
|
|
359
|
+
bodySnippet: bodyText.slice(0, 500),
|
|
360
|
+
};
|
|
361
|
+
})()
|
|
362
|
+
`;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// ===== CLI definition =====
|
|
366
|
+
|
|
367
|
+
export const publishCommand = cli({
|
|
368
|
+
site: 'xianyu',
|
|
369
|
+
name: 'publish',
|
|
370
|
+
access: 'write',
|
|
371
|
+
description: '发布闲鱼宝贝(需先在浏览器中登录闲鱼)',
|
|
372
|
+
domain: 'www.goofish.com',
|
|
373
|
+
strategy: Strategy.COOKIE,
|
|
374
|
+
navigateBefore: false,
|
|
375
|
+
browser: true,
|
|
376
|
+
args: [
|
|
377
|
+
{ name: 'title', required: true, positional: true, help: '商品标题' },
|
|
378
|
+
{ name: 'description', required: true, positional: true, help: '商品描述/详情' },
|
|
379
|
+
{ name: 'price', required: true, positional: true, type: 'float', help: '出售价格(元)' },
|
|
380
|
+
{ name: 'condition', required: true, positional: true, help: '成色:全新 / 几乎全新 / 轻微使用 / 明显使用 / 老旧' },
|
|
381
|
+
{ name: 'category', required: true, positional: true, help: '商品分类关键词(如:手机、衣服、图书)' },
|
|
382
|
+
{ name: 'original_price', type: 'float', help: '原价(选填,用于显示折扣)' },
|
|
383
|
+
{ name: 'location', help: '所在地区(选填,如:杭州)' },
|
|
384
|
+
{ name: 'images', help: '本地图片路径,多张用逗号分隔(选填,如:/tmp/a.jpg,/tmp/b.jpg)' },
|
|
385
|
+
],
|
|
386
|
+
columns: ['status', 'item_id', 'title', 'price', 'condition', 'url', 'message'],
|
|
387
|
+
func: async (page, kwargs) => {
|
|
388
|
+
const data = normalizePublishArgs(kwargs);
|
|
389
|
+
// 1. 导航到发布页
|
|
390
|
+
await page.goto(buildPublishUrl());
|
|
391
|
+
await page.wait(3);
|
|
392
|
+
|
|
393
|
+
// 2. 检查登录状态
|
|
394
|
+
const initState = await page.evaluate(buildExtractPageStateEvaluate());
|
|
395
|
+
if (initState?.requiresAuth) {
|
|
396
|
+
throw new AuthRequiredError('www.goofish.com', '发布闲鱼需要先登录,请在 Chrome 中打开 goofish.com 并完成登录');
|
|
397
|
+
}
|
|
398
|
+
if (!initState?.hasPublishForm) {
|
|
399
|
+
throw new CommandExecutionError('Xianyu publish form was not detected', 'Confirm goofish.com is logged in and the publish page finished loading.');
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// 3. 选择分类(先于其他字段,因为分类可能影响表单结构)
|
|
403
|
+
const categoryResult = await page.evaluate(buildSelectCategoryEvaluate(data.category));
|
|
404
|
+
if (!categoryResult?.ok) {
|
|
405
|
+
throw new CommandExecutionError(`Xianyu category selection failed: ${categoryResult?.reason || 'unknown-reason'}`);
|
|
406
|
+
}
|
|
407
|
+
await page.wait(1.5);
|
|
408
|
+
|
|
409
|
+
// 4. 填充表单
|
|
410
|
+
const fillResult = await page.evaluate(buildFillFormEvaluate(data));
|
|
411
|
+
if (!fillResult?.ok) {
|
|
412
|
+
const missing = Array.isArray(fillResult?.missing) ? fillResult.missing.join(', ') : 'unknown';
|
|
413
|
+
throw new CommandExecutionError(`Xianyu publish form fill failed; missing fields: ${missing}`);
|
|
414
|
+
}
|
|
415
|
+
await page.wait(1);
|
|
416
|
+
|
|
417
|
+
// 5. 上传图片(如果有)
|
|
418
|
+
if (data.images.length > 0) {
|
|
419
|
+
if (!page.setFileInput) {
|
|
420
|
+
throw new CommandExecutionError('Xianyu publish requires Browser Bridge file upload support', 'Use a browser mode that supports setFileInput.');
|
|
421
|
+
}
|
|
422
|
+
const fileInput = await page.evaluate(buildFindFileInputSelectorEvaluate());
|
|
423
|
+
if (!fileInput?.ok) {
|
|
424
|
+
throw new CommandExecutionError(`Xianyu image upload input was not found: ${fileInput?.reason || 'unknown-reason'}`);
|
|
425
|
+
}
|
|
426
|
+
try {
|
|
427
|
+
await page.setFileInput(data.images, fileInput.selector || 'input[type="file"]');
|
|
428
|
+
await page.wait(3); // 等待图片上传处理
|
|
429
|
+
} catch (err) {
|
|
430
|
+
throw new CommandExecutionError(`Xianyu image upload failed: ${err?.message || err}`);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// 6. 点击发布按钮
|
|
435
|
+
const submitResult = await page.evaluate(buildSubmitEvaluate());
|
|
436
|
+
if (!submitResult?.ok) {
|
|
437
|
+
throw new CommandExecutionError(`Xianyu publish submit failed: ${submitResult?.reason || 'unknown-reason'}`);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// 7. 等待发布结果(最多 15 秒轮询)
|
|
441
|
+
await page.wait(2);
|
|
442
|
+
let itemId = '';
|
|
443
|
+
let finalUrl = await getCurrentPageUrl(page);
|
|
444
|
+
let failReason = '';
|
|
445
|
+
|
|
446
|
+
for (let i = 0; i < 10; i++) {
|
|
447
|
+
await page.wait(1.5);
|
|
448
|
+
const result = await page.evaluate(buildDetectSuccessEvaluate());
|
|
449
|
+
finalUrl = await getCurrentPageUrl(page);
|
|
450
|
+
|
|
451
|
+
if (result?.status === 'published') {
|
|
452
|
+
itemId = String(result.item_id || '').replace(/\D/g, '');
|
|
453
|
+
return [{
|
|
454
|
+
status: 'published',
|
|
455
|
+
item_id: itemId,
|
|
456
|
+
title: data.title.slice(0, 50),
|
|
457
|
+
price: `¥${data.price}`,
|
|
458
|
+
condition: data.condition,
|
|
459
|
+
url: result.url || finalUrl,
|
|
460
|
+
message: '发布成功',
|
|
461
|
+
}];
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
if (result?.status === 'failed') {
|
|
465
|
+
failReason = result.message || '发布失败';
|
|
466
|
+
break;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
throw new CommandExecutionError(failReason || 'Xianyu publish result was not confirmed before timeout', `Open ${finalUrl} and verify whether the listing was published.`);
|
|
471
|
+
},
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
export const __test__ = {
|
|
475
|
+
CONDITION_CHOICES,
|
|
476
|
+
MAX_IMAGES,
|
|
477
|
+
validateImagePaths,
|
|
478
|
+
normalizePublishArgs,
|
|
479
|
+
buildPublishUrl,
|
|
480
|
+
getCurrentPageUrl,
|
|
481
|
+
buildFillFormEvaluate,
|
|
482
|
+
buildSelectCategoryEvaluate,
|
|
483
|
+
buildFindFileInputSelectorEvaluate,
|
|
484
|
+
buildDetectSuccessEvaluate,
|
|
485
|
+
};
|