socialdatax-skills 0.2.5 → 0.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -5
- package/cli.mjs +625 -27
- package/package.json +1 -1
- package/skills/media-comments/SKILL.md +14 -7
- package/skills/media-detail/SKILL.md +4 -2
- package/skills/media-search/SKILL.md +20 -6
- package/skills/media-user-posts/SKILL.md +12 -4
- package/skills/socialdatax-content-research-assistant/SKILL.md +17 -2
package/README.md
CHANGED
|
@@ -48,7 +48,7 @@ Common search phrases for this skill package:
|
|
|
48
48
|
- Kuaishou current platform MCP registry name: `com.52choujiang/kuaishou-insights`
|
|
49
49
|
- Kuaishou future platform MCP registry name: `com.socialdatax/kuaishou-insights`
|
|
50
50
|
- Unified MCP registry name: none; this package installs skills and calls explicit platform services.
|
|
51
|
-
- Current public capability version: `0.2.
|
|
51
|
+
- Current public capability version: `0.2.7`
|
|
52
52
|
|
|
53
53
|
## Direct CLI
|
|
54
54
|
|
|
@@ -61,6 +61,7 @@ npx -y socialdatax-skills@latest xhs search --keyword "露营桌" --pretty
|
|
|
61
61
|
npx -y socialdatax-skills@latest xhs hot-search --pretty
|
|
62
62
|
npx -y socialdatax-skills@latest xhs detail --note-id "<note_id>" --pretty
|
|
63
63
|
npx -y socialdatax-skills@latest xhs comments --note-id "<note_id>" --pretty
|
|
64
|
+
npx -y socialdatax-skills@latest xhs comments --note-id "<note_id>" --all --include-replies --pretty
|
|
64
65
|
npx -y socialdatax-skills@latest xhs sub-comments --note-id "<note_id>" --comment-id "<comment_id>" --pretty
|
|
65
66
|
npx -y socialdatax-skills@latest xhs user-info --user-id "<user_id>" --pretty
|
|
66
67
|
npx -y socialdatax-skills@latest xhs user-posts --user-id "<user_id>" --pretty
|
|
@@ -68,6 +69,7 @@ npx -y socialdatax-skills@latest douyin hot-search --pretty
|
|
|
68
69
|
npx -y socialdatax-skills@latest douyin search --keyword "露营桌" --pretty
|
|
69
70
|
npx -y socialdatax-skills@latest douyin detail --aweme-id "<aweme_id>" --pretty
|
|
70
71
|
npx -y socialdatax-skills@latest douyin comments --aweme-id "<aweme_id>" --pretty
|
|
72
|
+
npx -y socialdatax-skills@latest douyin comments --aweme-id "<aweme_id>" --all --include-replies --pretty
|
|
71
73
|
npx -y socialdatax-skills@latest douyin replies --aweme-id "<aweme_id>" --comment-id "<comment_id>" --pretty
|
|
72
74
|
npx -y socialdatax-skills@latest douyin user-info --sec-user-id "<sec_user_id>" --pretty
|
|
73
75
|
npx -y socialdatax-skills@latest douyin user-info --profile-url "<profile_url_or_share_text>" --pretty
|
|
@@ -80,6 +82,7 @@ npx -y socialdatax-skills@latest kuaishou search --keyword "露营桌" --pretty
|
|
|
80
82
|
npx -y socialdatax-skills@latest kuaishou detail --photo-id "<photo_id>" --pretty
|
|
81
83
|
npx -y socialdatax-skills@latest kuaishou detail --url "<kuaishou_content_url_or_share_text>" --pretty
|
|
82
84
|
npx -y socialdatax-skills@latest kuaishou comments --photo-id "<photo_id>" --pretty
|
|
85
|
+
npx -y socialdatax-skills@latest kuaishou comments --photo-id "<photo_id>" --all --include-replies --pretty
|
|
83
86
|
npx -y socialdatax-skills@latest kuaishou comments --url "<kuaishou_content_url_or_share_text>" --pretty
|
|
84
87
|
npx -y socialdatax-skills@latest kuaishou replies --photo-id "<photo_id>" --comment-id "<comment_id>" --pretty
|
|
85
88
|
npx -y socialdatax-skills@latest kuaishou user-info --user-id "<user_id>" --pretty
|
|
@@ -90,6 +93,16 @@ npx -y socialdatax-skills@latest kuaishou user-posts --profile-url "<profile_url
|
|
|
90
93
|
|
|
91
94
|
The direct CLI prints a JSON envelope with `platform`, `tool`, `arguments`, and `data`.
|
|
92
95
|
|
|
96
|
+
For list-style commands, use `--pages <n>` to fetch N pages from the current starting point and `--max-items <n>` to cap the merged `data.items` output. Search commands support `--pages` but not `--all`, because search has no stable complete-result boundary.
|
|
97
|
+
|
|
98
|
+
For comments, replies, creator content lists, and Douyin creator series, use `--all` to continue until the returned `next_page_token` is empty. `--all` has no default item or page cap; add `--max-items <n>` or use `--pages <n>` when you want a bounded run.
|
|
99
|
+
|
|
100
|
+
For first-level comments, add `--include-replies` to fetch the second-level replies under each returned first-level comment. Multi-page output keeps `data.items` as the merged first-level list; each item gets `replies`, `replies_page_count`, and `replies_next_page_token`.
|
|
101
|
+
|
|
102
|
+
For commands that accept `--page-token`, continue only with the complete returned `next_page_token` from the same pagination chain. Do not modify, truncate, redact, mask, omit, normalize, rebuild, generate, or replace the middle with ellipses.
|
|
103
|
+
|
|
104
|
+
XHS search uses numeric `--page`; Douyin and Kuaishou search do not accept `--page`. For Douyin and Kuaishou search, omit `--page-token` on the first request and pass the complete returned `next_page_token` only when continuing the same search chain.
|
|
105
|
+
|
|
93
106
|
Kuaishou search uses `--keyword` and optional `--page-token`; it does not accept Douyin semantic filters.
|
|
94
107
|
|
|
95
108
|
Douyin search filters use semantic values: `--sort-type` supports `general`,
|
|
@@ -234,7 +247,7 @@ Current Douyin workflows include:
|
|
|
234
247
|
- Search related Douyin works by keyword.
|
|
235
248
|
- Resolve a Douyin content page link, short link, share text, or aweme_id into structured work details.
|
|
236
249
|
- Fetch paginated first-level comments for comment analysis.
|
|
237
|
-
- Fetch paginated replies under a first-level comment.
|
|
250
|
+
- Fetch paginated replies under a first-level comment; pass both `aweme_id` and `comment_id`, and use `page_token` to continue pagination.
|
|
238
251
|
- Continue Douyin comment and reply pagination only when `next_page_token` is non-empty; an empty string means there are no more comments or replies to request.
|
|
239
252
|
- Read creator profile data from a profile link, short link, share text, or sec_user_id.
|
|
240
253
|
- Fetch paginated creator work lists from a sec_user_id, profile link, short link, or share text.
|
|
@@ -261,12 +274,12 @@ Current Douyin workflows include:
|
|
|
261
274
|
| Tool | Public purpose |
|
|
262
275
|
| --- | --- |
|
|
263
276
|
| `douyin_get_hot_search_list` | Fetch the current Douyin main hot search list. |
|
|
264
|
-
| `douyin_search_videos` | Search Douyin works by keyword with optional
|
|
277
|
+
| `douyin_search_videos` | Search Douyin works by keyword with optional `page_token` continuation and filters; do not pass `page`. |
|
|
265
278
|
| `douyin_get_video_detail_by_aweme_id` | Fetch structured Douyin work details when the caller already has an aweme_id. |
|
|
266
279
|
| `douyin_get_video_detail_by_url` | Resolve a Douyin content page link, short link, or share text into structured Douyin work details. |
|
|
267
280
|
| `douyin_get_video_comments_by_aweme_id` | Fetch paginated first-level comments when the caller already has an aweme_id. |
|
|
268
281
|
| `douyin_get_video_comments_by_url` | Fetch paginated first-level comments directly from a Douyin content page URL, short link, or share text. |
|
|
269
|
-
| `douyin_get_video_comment_replies_by_comment_id` | Fetch paginated replies under a first-level Douyin comment
|
|
282
|
+
| `douyin_get_video_comment_replies_by_comment_id` | Fetch paginated replies under a first-level Douyin comment; pass both aweme_id and comment_id, and use page_token to continue pagination. |
|
|
270
283
|
| `douyin_get_user_info_by_sec_user_id` | Fetch creator profile data when the caller already has a sec_user_id. |
|
|
271
284
|
| `douyin_get_user_info_by_profile_url` | Resolve a profile link, short link, or share text into creator profile data. |
|
|
272
285
|
| `douyin_get_user_posted_videos_by_sec_user_id` | Fetch a paginated list of works published by a creator when the caller already has a sec_user_id. |
|
|
@@ -279,7 +292,7 @@ Current Douyin workflows include:
|
|
|
279
292
|
| Tool | Public purpose |
|
|
280
293
|
| --- | --- |
|
|
281
294
|
| `kuaishou_get_hot_search_list` | Get the current Kuaishou / 快手 short-video hot list. |
|
|
282
|
-
| `kuaishou_search_videos` | Search Kuaishou works by natural-language keyword with optional
|
|
295
|
+
| `kuaishou_search_videos` | Search Kuaishou works by natural-language keyword with optional `page_token` continuation; do not pass `page`. |
|
|
283
296
|
| `kuaishou_get_video_detail_by_photo_id` | Fetch structured work details when the caller already has a photo_id. |
|
|
284
297
|
| `kuaishou_get_video_detail_by_url` | Resolve a Kuaishou work page link, short link, or share text into structured work details. |
|
|
285
298
|
| `kuaishou_get_video_comments_by_photo_id` | Fetch paginated first-level comments when the caller already has a photo_id. |
|
package/cli.mjs
CHANGED
|
@@ -7,7 +7,7 @@ import { dirname, join, resolve } from "node:path";
|
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
8
|
|
|
9
9
|
const PACKAGE_NAME = "socialdatax-skills";
|
|
10
|
-
const PACKAGE_VERSION = "0.2.
|
|
10
|
+
const PACKAGE_VERSION = "0.2.7";
|
|
11
11
|
const PACKAGE_SPEC = `${PACKAGE_NAME}@latest`;
|
|
12
12
|
const LOG_PREFIX = `[${PACKAGE_NAME}]`;
|
|
13
13
|
const MIN_NODE_VERSION = "20.18.1";
|
|
@@ -50,17 +50,53 @@ const AVAILABLE_SKILLS = [
|
|
|
50
50
|
},
|
|
51
51
|
];
|
|
52
52
|
const AVAILABLE_SKILL_NAMES = AVAILABLE_SKILLS.map((skill) => skill.name);
|
|
53
|
-
const BOOLEAN_OPTIONS = new Set([
|
|
53
|
+
const BOOLEAN_OPTIONS = new Set([
|
|
54
|
+
"all",
|
|
55
|
+
"dryRun",
|
|
56
|
+
"force",
|
|
57
|
+
"includeReplies",
|
|
58
|
+
"json",
|
|
59
|
+
"pretty",
|
|
60
|
+
]);
|
|
61
|
+
const DIRECT_BOOLEAN_OPTIONS = new Set(["all", "includeReplies", "pretty"]);
|
|
54
62
|
const INSTALL_TARGETS = ["openclaw", "hermes", "agents", "codex", "claude-code", "claude"];
|
|
55
63
|
const VALID_SCOPES = ["user", "workspace", "shared"];
|
|
56
64
|
const XHS_DIRECT_ACTION_OPTIONS = {
|
|
57
65
|
"hot-search": ["pretty"],
|
|
58
|
-
search: [
|
|
66
|
+
search: [
|
|
67
|
+
"keyword",
|
|
68
|
+
"page",
|
|
69
|
+
"pages",
|
|
70
|
+
"all",
|
|
71
|
+
"maxItems",
|
|
72
|
+
"sortType",
|
|
73
|
+
"noteType",
|
|
74
|
+
"publishTimeRange",
|
|
75
|
+
"pretty",
|
|
76
|
+
],
|
|
59
77
|
detail: ["noteId", "url", "pretty"],
|
|
60
|
-
comments: [
|
|
61
|
-
|
|
78
|
+
comments: [
|
|
79
|
+
"noteId",
|
|
80
|
+
"url",
|
|
81
|
+
"pageToken",
|
|
82
|
+
"pages",
|
|
83
|
+
"all",
|
|
84
|
+
"maxItems",
|
|
85
|
+
"includeReplies",
|
|
86
|
+
"pretty",
|
|
87
|
+
],
|
|
88
|
+
"sub-comments": [
|
|
89
|
+
"noteId",
|
|
90
|
+
"commentId",
|
|
91
|
+
"pageToken",
|
|
92
|
+
"pages",
|
|
93
|
+
"all",
|
|
94
|
+
"maxItems",
|
|
95
|
+
"includeReplies",
|
|
96
|
+
"pretty",
|
|
97
|
+
],
|
|
62
98
|
"user-info": ["userId", "profileUrl", "pretty"],
|
|
63
|
-
"user-posts": ["userId", "profileUrl", "pageToken", "pretty"],
|
|
99
|
+
"user-posts": ["userId", "profileUrl", "pageToken", "pages", "all", "maxItems", "pretty"],
|
|
64
100
|
};
|
|
65
101
|
const XHS_DIRECT_ACTION_NAMES = Object.keys(XHS_DIRECT_ACTION_OPTIONS).join(", ");
|
|
66
102
|
const XHS_OPTION_DISPLAY_NAMES = {
|
|
@@ -73,6 +109,10 @@ const XHS_OPTION_DISPLAY_NAMES = {
|
|
|
73
109
|
noteId: "--note-id",
|
|
74
110
|
commentId: "--comment-id",
|
|
75
111
|
pageToken: "--page-token",
|
|
112
|
+
pages: "--pages",
|
|
113
|
+
maxItems: "--max-items",
|
|
114
|
+
all: "--all",
|
|
115
|
+
includeReplies: "--include-replies",
|
|
76
116
|
profileUrl: "--profile-url",
|
|
77
117
|
userId: "--user-id",
|
|
78
118
|
};
|
|
@@ -93,6 +133,9 @@ const DOUYIN_DIRECT_ACTION_OPTIONS = {
|
|
|
93
133
|
search: [
|
|
94
134
|
"keyword",
|
|
95
135
|
"pageToken",
|
|
136
|
+
"pages",
|
|
137
|
+
"all",
|
|
138
|
+
"maxItems",
|
|
96
139
|
"sortType",
|
|
97
140
|
"publishTimeRange",
|
|
98
141
|
"durationRange",
|
|
@@ -100,11 +143,29 @@ const DOUYIN_DIRECT_ACTION_OPTIONS = {
|
|
|
100
143
|
"pretty",
|
|
101
144
|
],
|
|
102
145
|
detail: ["awemeId", "url", "pretty"],
|
|
103
|
-
comments: [
|
|
104
|
-
|
|
146
|
+
comments: [
|
|
147
|
+
"awemeId",
|
|
148
|
+
"url",
|
|
149
|
+
"pageToken",
|
|
150
|
+
"pages",
|
|
151
|
+
"all",
|
|
152
|
+
"maxItems",
|
|
153
|
+
"includeReplies",
|
|
154
|
+
"pretty",
|
|
155
|
+
],
|
|
156
|
+
replies: [
|
|
157
|
+
"awemeId",
|
|
158
|
+
"commentId",
|
|
159
|
+
"pageToken",
|
|
160
|
+
"pages",
|
|
161
|
+
"all",
|
|
162
|
+
"maxItems",
|
|
163
|
+
"includeReplies",
|
|
164
|
+
"pretty",
|
|
165
|
+
],
|
|
105
166
|
"user-info": ["secUserId", "profileUrl", "pretty"],
|
|
106
|
-
"user-posts": ["secUserId", "profileUrl", "pageToken", "pretty"],
|
|
107
|
-
"user-series": ["secUserId", "profileUrl", "pageToken", "pretty"],
|
|
167
|
+
"user-posts": ["secUserId", "profileUrl", "pageToken", "pages", "all", "maxItems", "pretty"],
|
|
168
|
+
"user-series": ["secUserId", "profileUrl", "pageToken", "pages", "all", "maxItems", "pretty"],
|
|
108
169
|
};
|
|
109
170
|
const DOUYIN_DIRECT_ACTION_NAMES = Object.keys(DOUYIN_DIRECT_ACTION_OPTIONS).join(", ");
|
|
110
171
|
const DOUYIN_SEARCH_SORT_TYPES = ["general", "time_descending", "like_count_descending"];
|
|
@@ -119,6 +180,10 @@ const DOUYIN_SEARCH_CONTENT_TYPES = ["all", "video", "image"];
|
|
|
119
180
|
const DOUYIN_OPTION_DISPLAY_NAMES = {
|
|
120
181
|
keyword: "--keyword",
|
|
121
182
|
pageToken: "--page-token",
|
|
183
|
+
pages: "--pages",
|
|
184
|
+
maxItems: "--max-items",
|
|
185
|
+
all: "--all",
|
|
186
|
+
includeReplies: "--include-replies",
|
|
122
187
|
sortType: "--sort-type",
|
|
123
188
|
publishTimeRange: "--publish-time-range",
|
|
124
189
|
durationRange: "--duration-range",
|
|
@@ -131,17 +196,39 @@ const DOUYIN_OPTION_DISPLAY_NAMES = {
|
|
|
131
196
|
};
|
|
132
197
|
const KUAISHOU_DIRECT_ACTION_OPTIONS = {
|
|
133
198
|
"hot-search": ["pretty"],
|
|
134
|
-
search: ["keyword", "pageToken", "pretty"],
|
|
199
|
+
search: ["keyword", "pageToken", "pages", "all", "maxItems", "pretty"],
|
|
135
200
|
detail: ["photoId", "url", "pretty"],
|
|
136
|
-
comments: [
|
|
137
|
-
|
|
201
|
+
comments: [
|
|
202
|
+
"photoId",
|
|
203
|
+
"url",
|
|
204
|
+
"pageToken",
|
|
205
|
+
"pages",
|
|
206
|
+
"all",
|
|
207
|
+
"maxItems",
|
|
208
|
+
"includeReplies",
|
|
209
|
+
"pretty",
|
|
210
|
+
],
|
|
211
|
+
replies: [
|
|
212
|
+
"photoId",
|
|
213
|
+
"commentId",
|
|
214
|
+
"pageToken",
|
|
215
|
+
"pages",
|
|
216
|
+
"all",
|
|
217
|
+
"maxItems",
|
|
218
|
+
"includeReplies",
|
|
219
|
+
"pretty",
|
|
220
|
+
],
|
|
138
221
|
"user-info": ["userId", "profileUrl", "pretty"],
|
|
139
|
-
"user-posts": ["userId", "profileUrl", "pageToken", "pretty"],
|
|
222
|
+
"user-posts": ["userId", "profileUrl", "pageToken", "pages", "all", "maxItems", "pretty"],
|
|
140
223
|
};
|
|
141
224
|
const KUAISHOU_DIRECT_ACTION_NAMES = Object.keys(KUAISHOU_DIRECT_ACTION_OPTIONS).join(", ");
|
|
142
225
|
const KUAISHOU_OPTION_DISPLAY_NAMES = {
|
|
143
226
|
keyword: "--keyword",
|
|
144
227
|
pageToken: "--page-token",
|
|
228
|
+
pages: "--pages",
|
|
229
|
+
maxItems: "--max-items",
|
|
230
|
+
all: "--all",
|
|
231
|
+
includeReplies: "--include-replies",
|
|
145
232
|
url: "--url",
|
|
146
233
|
profileUrl: "--profile-url",
|
|
147
234
|
photoId: "--photo-id",
|
|
@@ -252,7 +339,7 @@ const PLATFORMS = {
|
|
|
252
339
|
},
|
|
253
340
|
{
|
|
254
341
|
name: "douyin_get_video_comment_replies_by_comment_id",
|
|
255
|
-
description: "Fetch paginated replies under a first-level Douyin comment
|
|
342
|
+
description: "Fetch paginated replies under a first-level Douyin comment; pass both aweme_id and comment_id, and use page_token to continue pagination.",
|
|
256
343
|
},
|
|
257
344
|
{
|
|
258
345
|
name: "douyin_get_user_info_by_sec_user_id",
|
|
@@ -280,7 +367,8 @@ const PLATFORMS = {
|
|
|
280
367
|
},
|
|
281
368
|
{
|
|
282
369
|
name: "douyin_search_videos",
|
|
283
|
-
description:
|
|
370
|
+
description:
|
|
371
|
+
"Search Douyin works by keyword with optional page_token continuation and filters; do not pass page.",
|
|
284
372
|
},
|
|
285
373
|
],
|
|
286
374
|
},
|
|
@@ -304,7 +392,8 @@ const PLATFORMS = {
|
|
|
304
392
|
},
|
|
305
393
|
{
|
|
306
394
|
name: "kuaishou_search_videos",
|
|
307
|
-
description:
|
|
395
|
+
description:
|
|
396
|
+
"Search Kuaishou works by natural-language keyword with optional page_token continuation; do not pass page.",
|
|
308
397
|
},
|
|
309
398
|
{
|
|
310
399
|
name: "kuaishou_get_video_detail_by_photo_id",
|
|
@@ -471,9 +560,9 @@ function validateXhsDirectActionOptions(action, options) {
|
|
|
471
560
|
}
|
|
472
561
|
|
|
473
562
|
validateKnownOptions(options, allowedOptions);
|
|
474
|
-
|
|
563
|
+
validateDirectPaginationOptions("xhs", action, options);
|
|
475
564
|
for (const key of allowedOptions) {
|
|
476
|
-
if (key
|
|
565
|
+
if (!DIRECT_BOOLEAN_OPTIONS.has(key)) {
|
|
477
566
|
requireOptionValue(options, key, XHS_OPTION_DISPLAY_NAMES[key]);
|
|
478
567
|
}
|
|
479
568
|
}
|
|
@@ -486,9 +575,9 @@ function validateDouyinDirectActionOptions(action, options) {
|
|
|
486
575
|
}
|
|
487
576
|
|
|
488
577
|
validateKnownOptions(options, allowedOptions);
|
|
489
|
-
|
|
578
|
+
validateDirectPaginationOptions("douyin", action, options);
|
|
490
579
|
for (const key of allowedOptions) {
|
|
491
|
-
if (key
|
|
580
|
+
if (!DIRECT_BOOLEAN_OPTIONS.has(key)) {
|
|
492
581
|
requireOptionValue(options, key, DOUYIN_OPTION_DISPLAY_NAMES[key]);
|
|
493
582
|
}
|
|
494
583
|
}
|
|
@@ -501,14 +590,35 @@ function validateKuaishouDirectActionOptions(action, options) {
|
|
|
501
590
|
}
|
|
502
591
|
|
|
503
592
|
validateKnownOptions(options, allowedOptions);
|
|
504
|
-
|
|
593
|
+
validateDirectPaginationOptions("kuaishou", action, options);
|
|
505
594
|
for (const key of allowedOptions) {
|
|
506
|
-
if (key
|
|
595
|
+
if (!DIRECT_BOOLEAN_OPTIONS.has(key)) {
|
|
507
596
|
requireOptionValue(options, key, KUAISHOU_OPTION_DISPLAY_NAMES[key]);
|
|
508
597
|
}
|
|
509
598
|
}
|
|
510
599
|
}
|
|
511
600
|
|
|
601
|
+
function validateDirectPaginationOptions(platformId, action, options) {
|
|
602
|
+
validateFlagOption(options, "pretty", "--pretty");
|
|
603
|
+
validateFlagOption(options, "all", "--all");
|
|
604
|
+
validateFlagOption(options, "includeReplies", "--include-replies");
|
|
605
|
+
if (options.all && options.pages !== undefined) {
|
|
606
|
+
throw new Error("Use only one of --all or --pages.");
|
|
607
|
+
}
|
|
608
|
+
if (options.pages !== undefined) {
|
|
609
|
+
parsePositiveIntegerOption(options.pages, "--pages");
|
|
610
|
+
}
|
|
611
|
+
if (options.maxItems !== undefined) {
|
|
612
|
+
parsePositiveIntegerOption(options.maxItems, "--max-items");
|
|
613
|
+
}
|
|
614
|
+
if (options.all && action === "search") {
|
|
615
|
+
throw new Error(`--all is not supported for ${platformId} search. Use --pages instead.`);
|
|
616
|
+
}
|
|
617
|
+
if (options.includeReplies && action !== "comments") {
|
|
618
|
+
throw new Error(`--include-replies is only supported for ${platformId} comments.`);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
|
|
512
622
|
function parsePositiveIntegerOption(value, displayName) {
|
|
513
623
|
const text = String(value);
|
|
514
624
|
if (!/^\d+$/.test(text)) {
|
|
@@ -992,6 +1102,9 @@ function printHelp() {
|
|
|
992
1102
|
console.log(` npx -y ${PACKAGE_SPEC} xhs comments --note-id "<note_id>" --pretty`);
|
|
993
1103
|
console.log(" Call the XHS comments tool directly and print JSON.");
|
|
994
1104
|
console.log("");
|
|
1105
|
+
console.log(` npx -y ${PACKAGE_SPEC} xhs comments --note-id "<note_id>" --all --include-replies --pretty`);
|
|
1106
|
+
console.log(" Fetch all XHS first-level comments and nested replies.");
|
|
1107
|
+
console.log("");
|
|
995
1108
|
console.log(
|
|
996
1109
|
` npx -y ${PACKAGE_SPEC} xhs sub-comments --note-id "<note_id>" --comment-id "<comment_id>" --pretty`
|
|
997
1110
|
);
|
|
@@ -1015,10 +1128,13 @@ function printHelp() {
|
|
|
1015
1128
|
console.log(` npx -y ${PACKAGE_SPEC} douyin comments --aweme-id "<aweme_id>" --pretty`);
|
|
1016
1129
|
console.log(" Call the Douyin work comments tool directly and print JSON.");
|
|
1017
1130
|
console.log("");
|
|
1131
|
+
console.log(` npx -y ${PACKAGE_SPEC} douyin comments --aweme-id "<aweme_id>" --all --include-replies --pretty`);
|
|
1132
|
+
console.log(" Fetch all Douyin first-level comments and nested replies.");
|
|
1133
|
+
console.log("");
|
|
1018
1134
|
console.log(
|
|
1019
1135
|
` npx -y ${PACKAGE_SPEC} douyin replies --aweme-id "<aweme_id>" --comment-id "<comment_id>" --pretty`
|
|
1020
1136
|
);
|
|
1021
|
-
console.log(" Call the Douyin comment replies tool
|
|
1137
|
+
console.log(" Call the Douyin comment replies tool with aweme_id and comment_id; use page_token for pagination.");
|
|
1022
1138
|
console.log("");
|
|
1023
1139
|
console.log(` npx -y ${PACKAGE_SPEC} douyin user-info --sec-user-id "<sec_user_id>" --pretty`);
|
|
1024
1140
|
console.log(" Call the Douyin creator profile tool directly and print JSON.");
|
|
@@ -1053,6 +1169,9 @@ function printHelp() {
|
|
|
1053
1169
|
console.log(` npx -y ${PACKAGE_SPEC} kuaishou comments --photo-id "<photo_id>" --pretty`);
|
|
1054
1170
|
console.log(" Call the Kuaishou work comments tool directly and print JSON.");
|
|
1055
1171
|
console.log("");
|
|
1172
|
+
console.log(` npx -y ${PACKAGE_SPEC} kuaishou comments --photo-id "<photo_id>" --all --include-replies --pretty`);
|
|
1173
|
+
console.log(" Fetch all Kuaishou first-level comments and nested replies.");
|
|
1174
|
+
console.log("");
|
|
1056
1175
|
console.log(` npx -y ${PACKAGE_SPEC} kuaishou comments --url "<kuaishou_content_url_or_share_text>" --pretty`);
|
|
1057
1176
|
console.log(" Call the Kuaishou work comments tool from a work link or share text.");
|
|
1058
1177
|
console.log("");
|
|
@@ -1148,6 +1267,15 @@ function printHelp() {
|
|
|
1148
1267
|
console.log(" --user-id <user_id>");
|
|
1149
1268
|
console.log(" --sec-user-id <sec_user_id>");
|
|
1150
1269
|
console.log(" --page <number>");
|
|
1270
|
+
console.log(" XHS search only. Douyin and Kuaishou search do not accept --page.");
|
|
1271
|
+
console.log(" --pages <number>");
|
|
1272
|
+
console.log(" Fetch and merge N pages from the current starting point for search, comments, replies, creator content lists, and creator series.");
|
|
1273
|
+
console.log(" --all");
|
|
1274
|
+
console.log(" Continue comments, replies, creator content lists, and creator series until next_page_token is empty; not supported for search.");
|
|
1275
|
+
console.log(" --max-items <number>");
|
|
1276
|
+
console.log(" Stop after collecting this many primary items.");
|
|
1277
|
+
console.log(" --include-replies");
|
|
1278
|
+
console.log(" For comments commands, also fetch nested second-level replies for each returned first-level comment.");
|
|
1151
1279
|
console.log(" --sort-type <general|time_descending|like_count_descending|comment_count_descending|collect_count_descending>");
|
|
1152
1280
|
console.log(" XHS sort meanings: general=default, time_descending=newest, like_count_descending=most liked, comment_count_descending=most commented, collect_count_descending=most collected.");
|
|
1153
1281
|
console.log(" --note-type <all|image|video> XHS search note type filter; default is all.");
|
|
@@ -1162,6 +1290,7 @@ function printHelp() {
|
|
|
1162
1290
|
console.log(" --content-type <all|video|image>");
|
|
1163
1291
|
console.log(" Douyin content type filter; omit for all content types.");
|
|
1164
1292
|
console.log(" --page-token <token>");
|
|
1293
|
+
console.log(" Continue token-paginated commands with the complete returned next_page_token. For Douyin and Kuaishou search, omit it on the first request.");
|
|
1165
1294
|
console.log(" --pretty Pretty-print direct CLI JSON output.");
|
|
1166
1295
|
console.log(" --json Print doctor output as JSON.");
|
|
1167
1296
|
console.log(" --target <openclaw|hermes|agents|codex|claude-code|claude>");
|
|
@@ -1210,7 +1339,9 @@ async function runXhsDirectCommand(args) {
|
|
|
1210
1339
|
validateXhsDirectActionOptions(action, options);
|
|
1211
1340
|
|
|
1212
1341
|
const operation = buildXhsOperation(action, options);
|
|
1213
|
-
const data =
|
|
1342
|
+
const data = shouldUsePaginatedDirectOutput(options)
|
|
1343
|
+
? await callPaginatedDirectOperation(operation, options)
|
|
1344
|
+
: await callDirectOperation(operation);
|
|
1214
1345
|
const envelope = {
|
|
1215
1346
|
platform: operation.platform.id,
|
|
1216
1347
|
tool: operation.tool,
|
|
@@ -1235,7 +1366,9 @@ async function runDouyinDirectCommand(args) {
|
|
|
1235
1366
|
validateDouyinDirectActionOptions(action, options);
|
|
1236
1367
|
|
|
1237
1368
|
const operation = buildDouyinOperation(action, options);
|
|
1238
|
-
const data =
|
|
1369
|
+
const data = shouldUsePaginatedDirectOutput(options)
|
|
1370
|
+
? await callPaginatedDirectOperation(operation, options)
|
|
1371
|
+
: await callDirectOperation(operation);
|
|
1239
1372
|
const envelope = {
|
|
1240
1373
|
platform: operation.platform.id,
|
|
1241
1374
|
tool: operation.tool,
|
|
@@ -1260,7 +1393,9 @@ async function runKuaishouDirectCommand(args) {
|
|
|
1260
1393
|
validateKuaishouDirectActionOptions(action, options);
|
|
1261
1394
|
|
|
1262
1395
|
const operation = buildKuaishouOperation(action, options);
|
|
1263
|
-
const data =
|
|
1396
|
+
const data = shouldUsePaginatedDirectOutput(options)
|
|
1397
|
+
? await callPaginatedDirectOperation(operation, options)
|
|
1398
|
+
: await callDirectOperation(operation);
|
|
1264
1399
|
const envelope = {
|
|
1265
1400
|
platform: operation.platform.id,
|
|
1266
1401
|
tool: operation.tool,
|
|
@@ -1773,6 +1908,469 @@ async function callDirectOperation(operation) {
|
|
|
1773
1908
|
}
|
|
1774
1909
|
}
|
|
1775
1910
|
|
|
1911
|
+
function shouldUsePaginatedDirectOutput(options) {
|
|
1912
|
+
return Boolean(
|
|
1913
|
+
options.all ||
|
|
1914
|
+
options.pages !== undefined ||
|
|
1915
|
+
options.maxItems !== undefined ||
|
|
1916
|
+
options.includeReplies
|
|
1917
|
+
);
|
|
1918
|
+
}
|
|
1919
|
+
|
|
1920
|
+
async function callPaginatedDirectOperation(operation, options) {
|
|
1921
|
+
const pagination = parseDirectPaginationOptions(options);
|
|
1922
|
+
return collectPaginatedDirectData(operation, pagination, {
|
|
1923
|
+
includeReplies: Boolean(options.includeReplies),
|
|
1924
|
+
});
|
|
1925
|
+
}
|
|
1926
|
+
|
|
1927
|
+
function parseDirectPaginationOptions(options) {
|
|
1928
|
+
return {
|
|
1929
|
+
all: Boolean(options.all),
|
|
1930
|
+
pages:
|
|
1931
|
+
options.pages === undefined
|
|
1932
|
+
? undefined
|
|
1933
|
+
: parsePositiveIntegerOption(options.pages, "--pages"),
|
|
1934
|
+
maxItems:
|
|
1935
|
+
options.maxItems === undefined
|
|
1936
|
+
? undefined
|
|
1937
|
+
: parsePositiveIntegerOption(options.maxItems, "--max-items"),
|
|
1938
|
+
};
|
|
1939
|
+
}
|
|
1940
|
+
|
|
1941
|
+
async function collectPaginatedDirectData(
|
|
1942
|
+
operation,
|
|
1943
|
+
pagination,
|
|
1944
|
+
{ includeReplies = false } = {}
|
|
1945
|
+
) {
|
|
1946
|
+
const pageLimit = pagination.all ? Number.POSITIVE_INFINITY : pagination.pages || 1;
|
|
1947
|
+
const collectedItems = [];
|
|
1948
|
+
const itemDedupeState = createPaginatedItemDedupeState();
|
|
1949
|
+
const seenNextMarkers = initialPaginationMarkers(operation);
|
|
1950
|
+
let pageCount = 0;
|
|
1951
|
+
let lastPageData;
|
|
1952
|
+
let nextMarker;
|
|
1953
|
+
let parentContextData = {};
|
|
1954
|
+
let currentOperation = cloneDirectOperation(operation);
|
|
1955
|
+
|
|
1956
|
+
while (pageCount < pageLimit) {
|
|
1957
|
+
const pageData = await callDirectOperation(currentOperation);
|
|
1958
|
+
pageCount += 1;
|
|
1959
|
+
lastPageData = pageData;
|
|
1960
|
+
parentContextData = mergeParentContextData(parentContextData, pageData);
|
|
1961
|
+
|
|
1962
|
+
const pageItems = uniquePaginatedPageItems(
|
|
1963
|
+
operation,
|
|
1964
|
+
directPageItems(pageData),
|
|
1965
|
+
itemDedupeState
|
|
1966
|
+
);
|
|
1967
|
+
const candidateItems = itemsForRemainingLimit(
|
|
1968
|
+
pageItems,
|
|
1969
|
+
collectedItems.length,
|
|
1970
|
+
pagination.maxItems
|
|
1971
|
+
);
|
|
1972
|
+
const decoratedItems =
|
|
1973
|
+
includeReplies && operation.operation === "comments"
|
|
1974
|
+
? await attachRepliesToCommentItems(
|
|
1975
|
+
currentOperation,
|
|
1976
|
+
candidateItems,
|
|
1977
|
+
parentContextData
|
|
1978
|
+
)
|
|
1979
|
+
: candidateItems;
|
|
1980
|
+
appendItemsWithLimit(collectedItems, decoratedItems, pagination.maxItems);
|
|
1981
|
+
|
|
1982
|
+
nextMarker = readNextPageMarker(currentOperation, pageData);
|
|
1983
|
+
const markerKey = nextMarker === undefined ? undefined : String(nextMarker);
|
|
1984
|
+
const markerRepeated =
|
|
1985
|
+
markerKey !== undefined && seenNextMarkers.has(markerKey);
|
|
1986
|
+
if (
|
|
1987
|
+
!nextMarker ||
|
|
1988
|
+
reachedMaxItems(collectedItems, pagination.maxItems) ||
|
|
1989
|
+
pageCount >= pageLimit
|
|
1990
|
+
) {
|
|
1991
|
+
if (markerRepeated) {
|
|
1992
|
+
nextMarker = undefined;
|
|
1993
|
+
}
|
|
1994
|
+
break;
|
|
1995
|
+
}
|
|
1996
|
+
if (markerRepeated) {
|
|
1997
|
+
throw new Error(
|
|
1998
|
+
`Pagination stopped because ${nextMarkerName(currentOperation)} repeated.`
|
|
1999
|
+
);
|
|
2000
|
+
}
|
|
2001
|
+
seenNextMarkers.add(markerKey);
|
|
2002
|
+
currentOperation = operationWithNextPageMarker(currentOperation, nextMarker);
|
|
2003
|
+
}
|
|
2004
|
+
|
|
2005
|
+
return buildPaginatedData({
|
|
2006
|
+
operation,
|
|
2007
|
+
lastPageData,
|
|
2008
|
+
parentContextData,
|
|
2009
|
+
items: collectedItems,
|
|
2010
|
+
pageCount,
|
|
2011
|
+
nextMarker,
|
|
2012
|
+
});
|
|
2013
|
+
}
|
|
2014
|
+
|
|
2015
|
+
function cloneDirectOperation(operation) {
|
|
2016
|
+
return {
|
|
2017
|
+
...operation,
|
|
2018
|
+
arguments: { ...operation.arguments },
|
|
2019
|
+
};
|
|
2020
|
+
}
|
|
2021
|
+
|
|
2022
|
+
function directPageItems(pageData) {
|
|
2023
|
+
return Array.isArray(pageData?.items) ? pageData.items : [];
|
|
2024
|
+
}
|
|
2025
|
+
|
|
2026
|
+
function createPaginatedItemDedupeState() {
|
|
2027
|
+
return { seenKeys: new Set() };
|
|
2028
|
+
}
|
|
2029
|
+
|
|
2030
|
+
function uniquePaginatedPageItems(operation, items, itemDedupeState) {
|
|
2031
|
+
const uniqueItems = [];
|
|
2032
|
+
for (const item of items) {
|
|
2033
|
+
const dedupeKeys = paginatedItemDedupeKeys(operation, item);
|
|
2034
|
+
if (dedupeKeys.length === 0) {
|
|
2035
|
+
uniqueItems.push(item);
|
|
2036
|
+
continue;
|
|
2037
|
+
}
|
|
2038
|
+
if (dedupeKeys.some((key) => itemDedupeState.seenKeys.has(key))) {
|
|
2039
|
+
recordPaginatedItemDedupeKeys(itemDedupeState, dedupeKeys);
|
|
2040
|
+
continue;
|
|
2041
|
+
}
|
|
2042
|
+
recordPaginatedItemDedupeKeys(itemDedupeState, dedupeKeys);
|
|
2043
|
+
uniqueItems.push(item);
|
|
2044
|
+
}
|
|
2045
|
+
return uniqueItems;
|
|
2046
|
+
}
|
|
2047
|
+
|
|
2048
|
+
function recordPaginatedItemDedupeKeys(itemDedupeState, dedupeKeys) {
|
|
2049
|
+
for (const key of dedupeKeys) {
|
|
2050
|
+
itemDedupeState.seenKeys.add(key);
|
|
2051
|
+
}
|
|
2052
|
+
}
|
|
2053
|
+
|
|
2054
|
+
function paginatedItemDedupeKeys(operation, item) {
|
|
2055
|
+
if (shouldDeduplicateCommentItems(operation)) {
|
|
2056
|
+
const commentId = itemStringField(item, "comment_id");
|
|
2057
|
+
return commentId ? [`comment:${commentId}`] : [];
|
|
2058
|
+
}
|
|
2059
|
+
if (!shouldDeduplicateContentItems(operation)) {
|
|
2060
|
+
return [];
|
|
2061
|
+
}
|
|
2062
|
+
const keys = [];
|
|
2063
|
+
const itemId = contentItemId(operation, item);
|
|
2064
|
+
if (itemId) {
|
|
2065
|
+
keys.push(`${operation.operation}:${operation.platform.id}:id:${itemId}`);
|
|
2066
|
+
}
|
|
2067
|
+
const xhsFingerprint = xhsContentItemFingerprint(operation, item);
|
|
2068
|
+
if (xhsFingerprint) {
|
|
2069
|
+
keys.push(`${operation.operation}:xhs:fingerprint:${xhsFingerprint}`);
|
|
2070
|
+
}
|
|
2071
|
+
return keys;
|
|
2072
|
+
}
|
|
2073
|
+
|
|
2074
|
+
function shouldDeduplicateCommentItems(operation) {
|
|
2075
|
+
return ["comments", "replies", "sub-comments"].includes(operation.operation);
|
|
2076
|
+
}
|
|
2077
|
+
|
|
2078
|
+
function shouldDeduplicateContentItems(operation) {
|
|
2079
|
+
return ["search", "user-posts", "user-series"].includes(operation.operation);
|
|
2080
|
+
}
|
|
2081
|
+
|
|
2082
|
+
function contentItemId(operation, item) {
|
|
2083
|
+
switch (operation.platform.id) {
|
|
2084
|
+
case "xhs":
|
|
2085
|
+
return itemStringField(item, "note_id");
|
|
2086
|
+
case "douyin":
|
|
2087
|
+
if (operation.operation === "user-series") {
|
|
2088
|
+
return itemStringField(item, "series_id");
|
|
2089
|
+
}
|
|
2090
|
+
return itemStringField(item, "aweme_id");
|
|
2091
|
+
case "kuaishou":
|
|
2092
|
+
return itemStringField(item, "photo_id");
|
|
2093
|
+
default:
|
|
2094
|
+
return undefined;
|
|
2095
|
+
}
|
|
2096
|
+
}
|
|
2097
|
+
|
|
2098
|
+
function xhsContentItemFingerprint(operation, item) {
|
|
2099
|
+
if (
|
|
2100
|
+
operation.platform.id !== "xhs" ||
|
|
2101
|
+
!shouldDeduplicateContentItems(operation)
|
|
2102
|
+
) {
|
|
2103
|
+
return undefined;
|
|
2104
|
+
}
|
|
2105
|
+
const authorId = itemStringField(item?.author, "user_id");
|
|
2106
|
+
const publishTime = itemStringField(item, "publish_time");
|
|
2107
|
+
const publishTimeNumber = Number(publishTime);
|
|
2108
|
+
const coverToken = xhsImageUrlToken(item?.cover_image_url);
|
|
2109
|
+
if (
|
|
2110
|
+
!authorId ||
|
|
2111
|
+
!publishTime ||
|
|
2112
|
+
!Number.isFinite(publishTimeNumber) ||
|
|
2113
|
+
publishTimeNumber <= 0 ||
|
|
2114
|
+
!coverToken
|
|
2115
|
+
) {
|
|
2116
|
+
return undefined;
|
|
2117
|
+
}
|
|
2118
|
+
return `${authorId}\t${publishTime}\t${coverToken}`;
|
|
2119
|
+
}
|
|
2120
|
+
|
|
2121
|
+
function xhsImageUrlToken(imageUrl) {
|
|
2122
|
+
const urlValue = stringValue(imageUrl);
|
|
2123
|
+
if (!urlValue) {
|
|
2124
|
+
return undefined;
|
|
2125
|
+
}
|
|
2126
|
+
let pathname;
|
|
2127
|
+
try {
|
|
2128
|
+
pathname = new URL(urlValue).pathname;
|
|
2129
|
+
} catch {
|
|
2130
|
+
return undefined;
|
|
2131
|
+
}
|
|
2132
|
+
let token = pathname.replace(/^\/+/, "");
|
|
2133
|
+
const webpicContentIndex = token.indexOf("/c/");
|
|
2134
|
+
if (webpicContentIndex !== -1) {
|
|
2135
|
+
token = token.slice(webpicContentIndex + 3);
|
|
2136
|
+
}
|
|
2137
|
+
token = token.split("!", 1)[0];
|
|
2138
|
+
return token || undefined;
|
|
2139
|
+
}
|
|
2140
|
+
|
|
2141
|
+
function itemStringField(item, field) {
|
|
2142
|
+
if (!item || typeof item !== "object") {
|
|
2143
|
+
return undefined;
|
|
2144
|
+
}
|
|
2145
|
+
return stringValue(item[field]);
|
|
2146
|
+
}
|
|
2147
|
+
|
|
2148
|
+
function stringValue(value) {
|
|
2149
|
+
if (value === undefined || value === null || value === "") {
|
|
2150
|
+
return undefined;
|
|
2151
|
+
}
|
|
2152
|
+
return String(value);
|
|
2153
|
+
}
|
|
2154
|
+
|
|
2155
|
+
function mergeParentContextData(current, pageData) {
|
|
2156
|
+
return {
|
|
2157
|
+
...current,
|
|
2158
|
+
...pickDefinedFields(pageData, ["note_id", "aweme_id", "photo_id"]),
|
|
2159
|
+
};
|
|
2160
|
+
}
|
|
2161
|
+
|
|
2162
|
+
function pickDefinedFields(source, fields) {
|
|
2163
|
+
if (!source || typeof source !== "object") {
|
|
2164
|
+
return {};
|
|
2165
|
+
}
|
|
2166
|
+
const picked = {};
|
|
2167
|
+
for (const field of fields) {
|
|
2168
|
+
if (source[field] !== undefined && source[field] !== null && source[field] !== "") {
|
|
2169
|
+
picked[field] = source[field];
|
|
2170
|
+
}
|
|
2171
|
+
}
|
|
2172
|
+
return picked;
|
|
2173
|
+
}
|
|
2174
|
+
|
|
2175
|
+
function itemsForRemainingLimit(items, collectedItemCount, maxItems) {
|
|
2176
|
+
if (maxItems === undefined) {
|
|
2177
|
+
return items;
|
|
2178
|
+
}
|
|
2179
|
+
const remaining = maxItems - collectedItemCount;
|
|
2180
|
+
return remaining > 0 ? items.slice(0, remaining) : [];
|
|
2181
|
+
}
|
|
2182
|
+
|
|
2183
|
+
function appendItemsWithLimit(target, source, maxItems) {
|
|
2184
|
+
if (maxItems === undefined) {
|
|
2185
|
+
target.push(...source);
|
|
2186
|
+
return;
|
|
2187
|
+
}
|
|
2188
|
+
const remaining = maxItems - target.length;
|
|
2189
|
+
if (remaining <= 0) {
|
|
2190
|
+
return;
|
|
2191
|
+
}
|
|
2192
|
+
target.push(...source.slice(0, remaining));
|
|
2193
|
+
}
|
|
2194
|
+
|
|
2195
|
+
function reachedMaxItems(items, maxItems) {
|
|
2196
|
+
return maxItems !== undefined && items.length >= maxItems;
|
|
2197
|
+
}
|
|
2198
|
+
|
|
2199
|
+
function readNextPageMarker(operation, pageData) {
|
|
2200
|
+
if (operation.platform.id === "xhs" && operation.operation === "search") {
|
|
2201
|
+
const page = pageData?.next_page;
|
|
2202
|
+
return page === undefined || page === null || page === "" ? undefined : page;
|
|
2203
|
+
}
|
|
2204
|
+
const token = pageData?.next_page_token;
|
|
2205
|
+
return typeof token === "string" && token ? token : undefined;
|
|
2206
|
+
}
|
|
2207
|
+
|
|
2208
|
+
function nextMarkerName(operation) {
|
|
2209
|
+
return operation.platform.id === "xhs" && operation.operation === "search"
|
|
2210
|
+
? "next_page"
|
|
2211
|
+
: "next_page_token";
|
|
2212
|
+
}
|
|
2213
|
+
|
|
2214
|
+
function initialPaginationMarkers(operation) {
|
|
2215
|
+
const marker = currentPageMarker(operation);
|
|
2216
|
+
return marker === undefined ? new Set() : new Set([String(marker)]);
|
|
2217
|
+
}
|
|
2218
|
+
|
|
2219
|
+
function currentPageMarker(operation) {
|
|
2220
|
+
if (operation.platform.id === "xhs" && operation.operation === "search") {
|
|
2221
|
+
return operation.arguments.page;
|
|
2222
|
+
}
|
|
2223
|
+
return operation.arguments.page_token;
|
|
2224
|
+
}
|
|
2225
|
+
|
|
2226
|
+
function operationWithNextPageMarker(operation, nextMarker) {
|
|
2227
|
+
const nextOperation = cloneDirectOperation(operation);
|
|
2228
|
+
if (operation.platform.id === "xhs" && operation.operation === "search") {
|
|
2229
|
+
nextOperation.arguments.page = nextMarker;
|
|
2230
|
+
} else {
|
|
2231
|
+
nextOperation.arguments.page_token = nextMarker;
|
|
2232
|
+
}
|
|
2233
|
+
return nextOperation;
|
|
2234
|
+
}
|
|
2235
|
+
|
|
2236
|
+
function buildPaginatedData({
|
|
2237
|
+
operation,
|
|
2238
|
+
lastPageData,
|
|
2239
|
+
parentContextData,
|
|
2240
|
+
items,
|
|
2241
|
+
pageCount,
|
|
2242
|
+
nextMarker,
|
|
2243
|
+
}) {
|
|
2244
|
+
const data = {
|
|
2245
|
+
...(lastPageData && typeof lastPageData === "object" ? lastPageData : {}),
|
|
2246
|
+
...parentContextData,
|
|
2247
|
+
items,
|
|
2248
|
+
page_count: pageCount,
|
|
2249
|
+
item_count: items.length,
|
|
2250
|
+
};
|
|
2251
|
+
if (operation.platform.id === "xhs" && operation.operation === "search") {
|
|
2252
|
+
data.next_page = nextMarker ?? null;
|
|
2253
|
+
} else {
|
|
2254
|
+
data.next_page_token = nextMarker || "";
|
|
2255
|
+
}
|
|
2256
|
+
return data;
|
|
2257
|
+
}
|
|
2258
|
+
|
|
2259
|
+
async function attachRepliesToCommentItems(operation, items, pageData) {
|
|
2260
|
+
const decorated = [];
|
|
2261
|
+
for (const item of items) {
|
|
2262
|
+
const comment = { ...item };
|
|
2263
|
+
if (commentMayHaveReplies(comment)) {
|
|
2264
|
+
const repliesOperation = buildRepliesOperationForComment(
|
|
2265
|
+
operation.platform,
|
|
2266
|
+
comment,
|
|
2267
|
+
operation.arguments,
|
|
2268
|
+
pageData
|
|
2269
|
+
);
|
|
2270
|
+
const repliesData = await collectPaginatedDirectData(
|
|
2271
|
+
repliesOperation,
|
|
2272
|
+
{ all: true, pages: undefined, maxItems: undefined },
|
|
2273
|
+
{ includeReplies: false }
|
|
2274
|
+
);
|
|
2275
|
+
comment.replies = repliesData.items;
|
|
2276
|
+
comment.replies_page_count = repliesData.page_count;
|
|
2277
|
+
comment.replies_next_page_token = repliesData.next_page_token || "";
|
|
2278
|
+
} else {
|
|
2279
|
+
comment.replies = [];
|
|
2280
|
+
comment.replies_page_count = 0;
|
|
2281
|
+
comment.replies_next_page_token = "";
|
|
2282
|
+
}
|
|
2283
|
+
decorated.push(comment);
|
|
2284
|
+
}
|
|
2285
|
+
return decorated;
|
|
2286
|
+
}
|
|
2287
|
+
|
|
2288
|
+
function commentMayHaveReplies(comment) {
|
|
2289
|
+
if (typeof comment.reply_count === "number") {
|
|
2290
|
+
return comment.reply_count > 0;
|
|
2291
|
+
}
|
|
2292
|
+
const replyCount = parseNonNegativeIntegerString(comment.reply_count);
|
|
2293
|
+
if (replyCount !== undefined) {
|
|
2294
|
+
return replyCount > 0;
|
|
2295
|
+
}
|
|
2296
|
+
if (typeof comment.has_replies === "boolean") {
|
|
2297
|
+
return comment.has_replies;
|
|
2298
|
+
}
|
|
2299
|
+
return true;
|
|
2300
|
+
}
|
|
2301
|
+
|
|
2302
|
+
function parseNonNegativeIntegerString(value) {
|
|
2303
|
+
if (typeof value !== "string" || !/^\d+$/.test(value)) {
|
|
2304
|
+
return undefined;
|
|
2305
|
+
}
|
|
2306
|
+
const parsed = Number(value);
|
|
2307
|
+
return Number.isSafeInteger(parsed) ? parsed : undefined;
|
|
2308
|
+
}
|
|
2309
|
+
|
|
2310
|
+
function buildRepliesOperationForComment(platform, comment, parentArguments, parentData) {
|
|
2311
|
+
if (!comment.comment_id) {
|
|
2312
|
+
throw new Error("Cannot fetch comment replies because comment_id is missing.");
|
|
2313
|
+
}
|
|
2314
|
+
switch (platform.id) {
|
|
2315
|
+
case "xhs":
|
|
2316
|
+
{
|
|
2317
|
+
const noteId = comment.note_id || parentArguments.note_id || parentData?.note_id;
|
|
2318
|
+
if (!noteId) {
|
|
2319
|
+
throw new Error("Cannot fetch XHS comment replies because note_id is missing.");
|
|
2320
|
+
}
|
|
2321
|
+
return buildDirectOperation(
|
|
2322
|
+
"sub-comments",
|
|
2323
|
+
{
|
|
2324
|
+
tool: "xhs_get_note_sub_comments_by_comment_id",
|
|
2325
|
+
toolArguments: {
|
|
2326
|
+
note_id: noteId,
|
|
2327
|
+
comment_id: comment.comment_id,
|
|
2328
|
+
},
|
|
2329
|
+
},
|
|
2330
|
+
platform
|
|
2331
|
+
);
|
|
2332
|
+
}
|
|
2333
|
+
case "douyin":
|
|
2334
|
+
{
|
|
2335
|
+
const awemeId = comment.aweme_id || parentArguments.aweme_id || parentData?.aweme_id;
|
|
2336
|
+
if (!awemeId) {
|
|
2337
|
+
throw new Error("Cannot fetch Douyin comment replies because aweme_id is missing.");
|
|
2338
|
+
}
|
|
2339
|
+
return buildDirectOperation(
|
|
2340
|
+
"replies",
|
|
2341
|
+
{
|
|
2342
|
+
tool: "douyin_get_video_comment_replies_by_comment_id",
|
|
2343
|
+
toolArguments: {
|
|
2344
|
+
aweme_id: awemeId,
|
|
2345
|
+
comment_id: comment.comment_id,
|
|
2346
|
+
},
|
|
2347
|
+
},
|
|
2348
|
+
platform
|
|
2349
|
+
);
|
|
2350
|
+
}
|
|
2351
|
+
case "kuaishou":
|
|
2352
|
+
{
|
|
2353
|
+
const photoId = comment.photo_id || parentArguments.photo_id || parentData?.photo_id;
|
|
2354
|
+
if (!photoId) {
|
|
2355
|
+
throw new Error("Cannot fetch Kuaishou comment replies because photo_id is missing.");
|
|
2356
|
+
}
|
|
2357
|
+
return buildDirectOperation(
|
|
2358
|
+
"replies",
|
|
2359
|
+
{
|
|
2360
|
+
tool: "kuaishou_get_video_comment_replies_by_comment_id",
|
|
2361
|
+
toolArguments: {
|
|
2362
|
+
photo_id: photoId,
|
|
2363
|
+
comment_id: comment.comment_id,
|
|
2364
|
+
},
|
|
2365
|
+
},
|
|
2366
|
+
platform
|
|
2367
|
+
);
|
|
2368
|
+
}
|
|
2369
|
+
default:
|
|
2370
|
+
throw new Error(`Unsupported comment reply platform: ${platform.id}.`);
|
|
2371
|
+
}
|
|
2372
|
+
}
|
|
2373
|
+
|
|
1776
2374
|
async function callMcpBackend(operation) {
|
|
1777
2375
|
ensureSupportedNodeVersion();
|
|
1778
2376
|
const { platform, tool } = operation;
|
package/package.json
CHANGED
|
@@ -40,30 +40,37 @@ Prefer the direct CLI when the agent can run shell commands. It does not require
|
|
|
40
40
|
|
|
41
41
|
```bash
|
|
42
42
|
npx -y socialdatax-skills@latest xhs comments --note-id "<note_id>" --pretty
|
|
43
|
+
npx -y socialdatax-skills@latest xhs comments --note-id "<note_id>" --all --include-replies --pretty
|
|
43
44
|
npx -y socialdatax-skills@latest xhs comments --url "<note_url_or_share_text>" --pretty
|
|
44
45
|
npx -y socialdatax-skills@latest xhs sub-comments --note-id "<note_id>" --comment-id "<comment_id>" --pretty
|
|
45
46
|
npx -y socialdatax-skills@latest douyin comments --aweme-id "<aweme_id>" --pretty
|
|
47
|
+
npx -y socialdatax-skills@latest douyin comments --aweme-id "<aweme_id>" --all --include-replies --pretty
|
|
46
48
|
npx -y socialdatax-skills@latest douyin comments --url "<douyin_content_url_or_share_text>" --pretty
|
|
47
49
|
npx -y socialdatax-skills@latest douyin replies --aweme-id "<aweme_id>" --comment-id "<comment_id>" --pretty
|
|
48
50
|
npx -y socialdatax-skills@latest kuaishou comments --photo-id "<photo_id>" --pretty
|
|
51
|
+
npx -y socialdatax-skills@latest kuaishou comments --photo-id "<photo_id>" --all --include-replies --pretty
|
|
49
52
|
npx -y socialdatax-skills@latest kuaishou comments --url "<kuaishou_content_url_or_share_text>" --pretty
|
|
50
53
|
npx -y socialdatax-skills@latest kuaishou replies --photo-id "<photo_id>" --comment-id "<comment_id>" --pretty
|
|
51
54
|
```
|
|
52
55
|
|
|
53
56
|
Optional arguments:
|
|
54
57
|
|
|
55
|
-
- XHS `--note-id <note_id>`:
|
|
58
|
+
- XHS `--note-id <note_id>`: use the complete 24-character lowercase hexadecimal `note_id` returned from search, detail, comments, or creator note lists; do not pass only a prefix.
|
|
56
59
|
- Douyin `--aweme-id <aweme_id>`: preferred when the video ID is already known and should anchor the comment thread.
|
|
57
60
|
- `--url <url_or_share_text>`: use for a content page URL, short link, or share text for first-level comments.
|
|
58
61
|
- Douyin URL safety: do not pass `video.play_url`; use a Douyin content page URL, short link, or share text instead.
|
|
59
62
|
- `--comment-id <comment_id>`: required for reply commands; use the first-level comment ID under the same content item.
|
|
60
|
-
- `--page-token <next_page_token>`: opaque pagination token; pass back
|
|
63
|
+
- `--page-token <next_page_token>`: opaque pagination token; pass the complete returned `next_page_token` back unchanged for the same content item or comment chain. Do not modify, truncate, redact, mask, omit, normalize, rebuild, generate, or replace the middle with ellipses.
|
|
64
|
+
- `--pages <n>`: fetch and merge N pages of first-level comments or replies.
|
|
65
|
+
- `--all`: continue first-level comments or replies until `next_page_token` is empty; there is no default item or page cap.
|
|
66
|
+
- `--max-items <n>`: stop after collecting N primary comments or replies.
|
|
67
|
+
- `--include-replies`: for first-level `comments` commands only, also fetch all second-level replies under each returned first-level comment.
|
|
61
68
|
- `--pretty`: output formatting only.
|
|
62
69
|
- Kuaishou `--photo-id <photo_id>`: preferred when the Kuaishou work photo_id is already known and should anchor the comment thread.
|
|
63
70
|
|
|
64
71
|
Use either the content ID option or the URL option for first-level comments, not both. For reply commands, use the content ID together with `--comment-id`.
|
|
65
72
|
|
|
66
|
-
The command prints JSON with `platform`, `tool`, `arguments`, and `data`.
|
|
73
|
+
The command prints JSON with `platform`, `tool`, `arguments`, and `data`. Multi-page output keeps merged primary comments in `data.items` and adds `page_count`, `item_count`, and the next-page marker. With `--include-replies`, each first-level comment includes `replies`, `replies_page_count`, and `replies_next_page_token`.
|
|
67
74
|
|
|
68
75
|
## Safety Boundary
|
|
69
76
|
|
|
@@ -84,14 +91,14 @@ MCP tools matching the direct CLI commands above:
|
|
|
84
91
|
- `kuaishou_get_video_comment_replies_by_comment_id`
|
|
85
92
|
|
|
86
93
|
If MCP tools are already available in the current agent, use one of these tools:
|
|
87
|
-
- `xhs_get_note_comments_by_note_id`: use when the
|
|
94
|
+
- `xhs_get_note_comments_by_note_id`: use when the complete 24-character lowercase hexadecimal `note_id` is known; do not pass only a prefix.
|
|
88
95
|
- `xhs_get_note_comments_by_note_url`: use for note URLs, short links, or share text.
|
|
89
|
-
- `xhs_get_note_sub_comments_by_comment_id`: use when the
|
|
96
|
+
- `xhs_get_note_sub_comments_by_comment_id`: use when the complete 24-character lowercase hexadecimal `note_id` and first-level comment ID are known; do not pass only a note ID prefix.
|
|
90
97
|
- `douyin_get_video_comments_by_aweme_id`: use when the aweme_id is known.
|
|
91
98
|
- `douyin_get_video_comments_by_url`: use for Douyin content page URLs, short links, or share text; do not pass playback URLs such as `video.play_url`.
|
|
92
|
-
- `douyin_get_video_comment_replies_by_comment_id`: use when
|
|
99
|
+
- `douyin_get_video_comment_replies_by_comment_id`: use when both aweme_id and first-level comment ID are known; use page_token to continue pagination.
|
|
93
100
|
|
|
94
|
-
Comment pagination uses `page_token
|
|
101
|
+
Comment pagination uses opaque `page_token` values. Pass the complete returned `next_page_token` back unchanged for the same content item or comment chain. Do not modify, truncate, redact, mask, omit, normalize, rebuild, generate, or replace the middle with ellipses. Prefer CLI `--pages`, `--all`, and `--include-replies` when the user asks for multiple pages or a full first-level plus second-level comment tree.
|
|
95
102
|
For Douyin comments and replies, continue only when `next_page_token` is non-empty; an empty string means there are no more comments or replies to request.
|
|
96
103
|
XHS reply pagination also uses `page_token` and is bound to the current comment.
|
|
97
104
|
- `kuaishou_get_video_comments_by_photo_id`: use when the photo_id is known.
|
|
@@ -49,7 +49,7 @@ npx -y socialdatax-skills@latest kuaishou detail --url "<kuaishou_content_url_or
|
|
|
49
49
|
|
|
50
50
|
Optional arguments:
|
|
51
51
|
|
|
52
|
-
- XHS `--note-id <note_id>`:
|
|
52
|
+
- XHS `--note-id <note_id>`: use the complete 24-character lowercase hexadecimal `note_id` returned from search, comments, creator note lists, or a previous detail result; do not pass only a prefix.
|
|
53
53
|
- XHS `--url <note_url_or_share_text>`: use for a note link, short link, or share text.
|
|
54
54
|
- Douyin `--aweme-id <aweme_id>`: preferred when the Douyin work ID is already known.
|
|
55
55
|
- Douyin `--url <douyin_content_url_or_share_text>`: use for a Douyin content page URL, short link, or share text; do not pass `video.play_url`.
|
|
@@ -77,7 +77,7 @@ MCP tools matching the direct CLI commands above:
|
|
|
77
77
|
- `kuaishou_get_video_detail_by_url`
|
|
78
78
|
|
|
79
79
|
If MCP tools are already available in the current agent, use one of these tools:
|
|
80
|
-
- `xhs_get_note_detail_by_note_id`: use when
|
|
80
|
+
- `xhs_get_note_detail_by_note_id`: use when the complete 24-character lowercase hexadecimal `note_id` is already known; do not pass only a prefix.
|
|
81
81
|
- `xhs_get_note_detail_by_note_url`: use for note URLs, short links, or share text.
|
|
82
82
|
- `douyin_get_video_detail_by_aweme_id`: use when an aweme_id is already known.
|
|
83
83
|
- `douyin_get_video_detail_by_url`: use for Douyin content page URLs, short links, or share text; do not pass playback URLs such as `video.play_url`.
|
|
@@ -87,6 +87,8 @@ If MCP tools are already available in the current agent, use one of these tools:
|
|
|
87
87
|
## Output Guidance
|
|
88
88
|
|
|
89
89
|
Return factual fields such as title or description, content, author, publish time, interaction counts, images, and media summary when available.
|
|
90
|
+
For XHS detail results, preserve any returned `note_url` exactly, including `xsec_token` query parameters. Do not modify, truncate, redact, mask, normalize, rebuild, or synthesize the URL from `note_id`; if `note_url` is null, show the `note_id` or say that no directly openable full link is available.
|
|
91
|
+
For XHS `note_id`, copy the complete 24-character lowercase hexadecimal ID exactly; do not pass or display only a prefix.
|
|
90
92
|
For Douyin detail, include `content_type` when available.
|
|
91
93
|
For Douyin detail, use `images` for image/text posts; `video` is the platform player resource and may be audio for image/text posts; `music` is the bound music or original-sound asset.
|
|
92
94
|
Detail access is read-only and does not provide account actions.
|
|
@@ -40,8 +40,11 @@ Prefer the direct CLI when the agent can run shell commands. It does not require
|
|
|
40
40
|
|
|
41
41
|
```bash
|
|
42
42
|
npx -y socialdatax-skills@latest xhs search --keyword "<keyword>" --pretty
|
|
43
|
+
npx -y socialdatax-skills@latest xhs search --keyword "<keyword>" --pages 3 --pretty
|
|
43
44
|
npx -y socialdatax-skills@latest douyin search --keyword "<keyword>" --pretty
|
|
45
|
+
npx -y socialdatax-skills@latest douyin search --keyword "<keyword>" --pages 3 --pretty
|
|
44
46
|
npx -y socialdatax-skills@latest kuaishou search --keyword "<keyword>" --pretty
|
|
47
|
+
npx -y socialdatax-skills@latest kuaishou search --keyword "<keyword>" --pages 3 --pretty
|
|
45
48
|
```
|
|
46
49
|
|
|
47
50
|
Required arguments:
|
|
@@ -56,13 +59,17 @@ Optional arguments:
|
|
|
56
59
|
- XHS `--sort-type <general|time_descending|like_count_descending|comment_count_descending|collect_count_descending>`: optional sort value; omit it for default sorting.
|
|
57
60
|
- XHS `--note-type <all|image|video>`: optional note type filter; default is `all`.
|
|
58
61
|
- XHS `--publish-time-range <all|day|week|half_year>`: optional publish-time filter; default is `all`.
|
|
59
|
-
- Douyin `--page-token <next_page_token>`: opaque pagination token returned
|
|
62
|
+
- Douyin `--page-token <next_page_token>`: opaque pagination token; omit it on the first search request. Continue only with the complete returned `next_page_token` from the same search pagination chain. Do not modify, truncate, redact, mask, omit, normalize, rebuild, generate, or replace the middle with ellipses.
|
|
60
63
|
- Douyin `--sort-type <general|time_descending|like_count_descending>`: optional sort value; omit it for the default sort.
|
|
61
64
|
- Douyin `--publish-time-range <all|day|week|half_year>`: optional publish-time filter; omit it for no publish-time filter.
|
|
62
65
|
- Douyin `--duration-range <all|under_1_minute|one_to_five_minutes|over_5_minutes>`: optional duration filter; omit it for no duration filter.
|
|
63
66
|
- Douyin `--content-type <all|video|image>`: optional content type filter; omit it for all content types.
|
|
67
|
+
- XHS `--pages <n>`: fetch and merge N search pages from the current starting point; continue with returned `next_page`.
|
|
68
|
+
- Douyin `--pages <n>`: fetch and merge N search pages from the current starting point; continue with returned `next_page_token`.
|
|
69
|
+
- Kuaishou `--pages <n>`: fetch and merge N search pages from the current starting point; continue with returned `next_page_token`.
|
|
70
|
+
- `--max-items <n>`: stop after collecting N search results.
|
|
64
71
|
- `--pretty`: output formatting only.
|
|
65
|
-
- Kuaishou `--page-token <next_page_token>`: opaque pagination token returned
|
|
72
|
+
- Kuaishou `--page-token <next_page_token>`: opaque pagination token; omit it on the first search request. Continue only with the complete returned `next_page_token` from the same search pagination chain. Do not modify, truncate, redact, mask, omit, normalize, rebuild, generate, or replace the middle with ellipses.
|
|
66
73
|
|
|
67
74
|
XHS sort values:
|
|
68
75
|
- `general`: default sorting.
|
|
@@ -104,9 +111,10 @@ Douyin content type filter values:
|
|
|
104
111
|
- `video`: video works.
|
|
105
112
|
- `image`: image/text posts.
|
|
106
113
|
|
|
107
|
-
The command prints JSON with `platform`, `tool`, `arguments`, and `data`.
|
|
114
|
+
The command prints JSON with `platform`, `tool`, `arguments`, and `data`. Search supports `--pages` and `--max-items`, but not `--all`, because search has no stable complete-result boundary. Multi-page output keeps merged results in `data.items` and adds `page_count`, `item_count`, and the next-page marker.
|
|
108
115
|
Kuaishou search pagination:
|
|
109
116
|
- Continue only when `next_page_token` is not empty.
|
|
117
|
+
- Pass the complete returned `next_page_token` back unchanged as `page_token` for the same search pagination chain. Do not modify, truncate, redact, mask, omit, normalize, rebuild, generate, or replace the middle with ellipses.
|
|
110
118
|
|
|
111
119
|
## Safety Boundary
|
|
112
120
|
|
|
@@ -131,21 +139,27 @@ Use the same meanings as the CLI XHS sort and filter values above.
|
|
|
131
139
|
|
|
132
140
|
For Douyin, call `douyin_search_videos` with:
|
|
133
141
|
- `keyword`: required search phrase or topic.
|
|
134
|
-
- `page_token`: optional opaque pagination token from the
|
|
142
|
+
- `page_token`: optional opaque pagination token. Continue only with the complete returned `next_page_token` from the same search pagination chain. Do not modify, truncate, redact, mask, omit, normalize, rebuild, generate, or replace the middle with ellipses.
|
|
143
|
+
Do not pass `page` to `douyin_search_videos`; omit `page_token` on the first request.
|
|
135
144
|
- `sort_type`: optional, one of `general`, `time_descending`, `like_count_descending`; omit it for the default sort.
|
|
136
145
|
- `publish_time_range`: optional publish-time filter, one of `all`, `day`, `week`, `half_year`; omit it for no publish-time filter.
|
|
137
146
|
- `duration_range`: optional duration filter, one of `all`, `under_1_minute`, `one_to_five_minutes`, `over_5_minutes`; omit it for no duration filter.
|
|
138
147
|
- `content_type`: optional content type filter, one of `all`, `video`, `image`; omit it for all content types.
|
|
139
148
|
|
|
140
149
|
Continue XHS pagination only when `next_page` is not `null`, and keep the same `keyword`, `sort_type`, `note_type`, and `publish_time_range` while changing only `page`.
|
|
141
|
-
Continue Douyin pagination only when `next_page_token` is not empty.
|
|
150
|
+
Continue Douyin pagination only when `next_page_token` is not empty. Pass the complete returned `next_page_token` back unchanged as `page_token` for the same keyword and filter chain.
|
|
142
151
|
Do not stop only because one page has empty `items`.
|
|
143
152
|
For Kuaishou, call `kuaishou_search_videos` with:
|
|
144
|
-
|
|
153
|
+
- `keyword`: required search phrase or topic.
|
|
154
|
+
- `page_token`: optional opaque pagination token. Continue only with the complete returned `next_page_token` from the same search pagination chain. Do not modify, truncate, redact, mask, omit, normalize, rebuild, generate, or replace the middle with ellipses.
|
|
155
|
+
Do not pass `page` to `kuaishou_search_videos`; omit `page_token` on the first request.
|
|
156
|
+
Continue Kuaishou pagination only when `next_page_token` is not empty. Pass the complete returned `next_page_token` back unchanged as `page_token` for the same keyword chain.
|
|
145
157
|
|
|
146
158
|
## Output Guidance
|
|
147
159
|
|
|
148
160
|
Summarize visible evidence separately from interpretation. Include useful content IDs, URLs, titles or descriptions, authors, counts, and publish time when the user needs traceability.
|
|
161
|
+
For XHS search results, preserve any returned `note_url` exactly, including `xsec_token` query parameters. Do not modify, truncate, redact, mask, normalize, rebuild, or synthesize the URL from `note_id`.
|
|
162
|
+
For XHS `note_id`, copy the complete 24-character lowercase hexadecimal ID exactly; do not pass or display only a prefix.
|
|
149
163
|
For Douyin search results, include `content_type` when the user needs traceability.
|
|
150
164
|
For Douyin image/text search results, use returned `images` and treat `video.media_type="audio"` as an audio player resource rather than a video post.
|
|
151
165
|
For Kuaishou search results, include `photo_id`, `share_url`, author facts, and visible interaction counts when the user needs traceability.
|
|
@@ -41,12 +41,16 @@ Prefer the direct CLI when the agent can run shell commands. It does not require
|
|
|
41
41
|
|
|
42
42
|
```bash
|
|
43
43
|
npx -y socialdatax-skills@latest xhs user-posts --user-id "<user_id>" --pretty
|
|
44
|
+
npx -y socialdatax-skills@latest xhs user-posts --user-id "<user_id>" --all --pretty
|
|
44
45
|
npx -y socialdatax-skills@latest xhs user-posts --profile-url "<profile_url_or_share_text>" --pretty
|
|
45
46
|
npx -y socialdatax-skills@latest douyin user-posts --sec-user-id "<sec_user_id>" --pretty
|
|
47
|
+
npx -y socialdatax-skills@latest douyin user-posts --sec-user-id "<sec_user_id>" --all --pretty
|
|
46
48
|
npx -y socialdatax-skills@latest douyin user-posts --profile-url "<profile_url_or_share_text>" --pretty
|
|
47
49
|
npx -y socialdatax-skills@latest douyin user-series --sec-user-id "<sec_user_id>" --pretty
|
|
50
|
+
npx -y socialdatax-skills@latest douyin user-series --sec-user-id "<sec_user_id>" --all --pretty
|
|
48
51
|
npx -y socialdatax-skills@latest douyin user-series --profile-url "<profile_url_or_share_text>" --pretty
|
|
49
52
|
npx -y socialdatax-skills@latest kuaishou user-posts --user-id "<user_id>" --pretty
|
|
53
|
+
npx -y socialdatax-skills@latest kuaishou user-posts --user-id "<user_id>" --all --pretty
|
|
50
54
|
npx -y socialdatax-skills@latest kuaishou user-posts --profile-url "<profile_url_or_share_text>" --pretty
|
|
51
55
|
```
|
|
52
56
|
|
|
@@ -57,14 +61,17 @@ Optional arguments:
|
|
|
57
61
|
- Douyin `--sec-user-id <sec_user_id>`: preferred when the creator sec_user_id is already known.
|
|
58
62
|
- Douyin `--profile-url <profile_url_or_share_text>`: use for a profile URL, short link, or profile share text.
|
|
59
63
|
- Douyin `user-series`: use for a creator's short-drama series list instead of regular published works.
|
|
60
|
-
- `--page-token <next_page_token>`: opaque pagination token; pass back
|
|
64
|
+
- `--page-token <next_page_token>`: opaque pagination token; pass the complete returned `next_page_token` back unchanged for the same creator content-list or series chain. Do not modify, truncate, redact, mask, omit, normalize, rebuild, generate, or replace the middle with ellipses.
|
|
65
|
+
- `--pages <n>`: fetch and merge N pages of creator content or creator series.
|
|
66
|
+
- `--all`: continue until `next_page_token` is empty; there is no default item or page cap.
|
|
67
|
+
- `--max-items <n>`: stop after collecting N creator content or series items.
|
|
61
68
|
- `--pretty`: output formatting only.
|
|
62
69
|
- Kuaishou `--user-id <user_id>`: preferred when the creator user_id is already known.
|
|
63
70
|
- Kuaishou `--profile-url <profile_url_or_share_text>`: use for a profile URL, short link, or profile share text.
|
|
64
71
|
|
|
65
72
|
Use either the ID option or the profile URL option for a single command, not both.
|
|
66
73
|
|
|
67
|
-
The command prints JSON with `platform`, `tool`, `arguments`, and `data`.
|
|
74
|
+
The command prints JSON with `platform`, `tool`, `arguments`, and `data`. Multi-page output keeps merged creator content or series items in `data.items` and adds `page_count`, `item_count`, and `next_page_token`.
|
|
68
75
|
|
|
69
76
|
## Safety Boundary
|
|
70
77
|
|
|
@@ -91,14 +98,15 @@ If MCP tools are already available in the current agent, use one of these tools:
|
|
|
91
98
|
- `douyin_get_user_series_by_sec_user_id`: preferred for creator short-drama series when `sec_user_id` is already known.
|
|
92
99
|
- `douyin_get_user_series_by_profile_url`: use for creator short-drama series from profile URLs, short links, or profile share text.
|
|
93
100
|
|
|
94
|
-
Creator content-list and series pagination use `page_token
|
|
101
|
+
Creator content-list and series pagination use opaque `page_token` values. Pass the complete returned `next_page_token` back unchanged for the same user and command family. Do not modify, truncate, redact, mask, omit, normalize, rebuild, generate, or replace the middle with ellipses. Prefer CLI `--pages`, `--all`, and `--max-items` when the user asks for multiple pages or all available creator content.
|
|
95
102
|
- `kuaishou_get_user_posted_videos_by_user_id`: preferred when `user_id` is already known.
|
|
96
103
|
- `kuaishou_get_user_posted_videos_by_profile_url`: use for profile URLs, short links, or profile share text.
|
|
97
|
-
Kuaishou creator work pagination uses `page_token
|
|
104
|
+
Kuaishou creator work pagination uses opaque `page_token` values; pass the complete returned `next_page_token` back unchanged for the same user. Do not modify, truncate, redact, mask, omit, normalize, rebuild, generate, or replace the middle with ellipses.
|
|
98
105
|
|
|
99
106
|
## Output Guidance
|
|
100
107
|
|
|
101
108
|
Summarize content-list evidence by title or description, summary, publish time, interaction counts, media links, and content type when present.
|
|
109
|
+
For XHS creator note-list results, copy each returned `note_id` as the complete 24-character lowercase hexadecimal ID exactly; do not pass or display only a prefix.
|
|
102
110
|
For Douyin image/text posts, use `image_urls` rather than assuming a video playback URL exists.
|
|
103
111
|
For Douyin short-drama series, report series IDs, titles, descriptions, covers, prices, and author facts when present.
|
|
104
112
|
Use returned content IDs to chain into detail or comment analysis when needed.
|
|
@@ -57,22 +57,31 @@ Prefer the direct CLI when the agent can run shell commands. It does not require
|
|
|
57
57
|
```bash
|
|
58
58
|
npx -y socialdatax-skills@latest xhs hot-search --pretty
|
|
59
59
|
npx -y socialdatax-skills@latest xhs search --keyword "<keyword>" --pretty
|
|
60
|
+
npx -y socialdatax-skills@latest xhs search --keyword "<keyword>" --pages 3 --pretty
|
|
60
61
|
npx -y socialdatax-skills@latest xhs detail --note-id "<note_id>" --pretty
|
|
61
62
|
npx -y socialdatax-skills@latest xhs comments --note-id "<note_id>" --pretty
|
|
63
|
+
npx -y socialdatax-skills@latest xhs comments --note-id "<note_id>" --all --include-replies --pretty
|
|
62
64
|
npx -y socialdatax-skills@latest xhs user-info --user-id "<user_id>" --pretty
|
|
63
65
|
npx -y socialdatax-skills@latest xhs user-posts --user-id "<user_id>" --pretty
|
|
66
|
+
npx -y socialdatax-skills@latest xhs user-posts --user-id "<user_id>" --all --pretty
|
|
64
67
|
npx -y socialdatax-skills@latest douyin hot-search --pretty
|
|
65
68
|
npx -y socialdatax-skills@latest douyin search --keyword "<keyword>" --pretty
|
|
69
|
+
npx -y socialdatax-skills@latest douyin search --keyword "<keyword>" --pages 3 --pretty
|
|
66
70
|
npx -y socialdatax-skills@latest douyin detail --aweme-id "<aweme_id>" --pretty
|
|
67
71
|
npx -y socialdatax-skills@latest douyin comments --aweme-id "<aweme_id>" --pretty
|
|
72
|
+
npx -y socialdatax-skills@latest douyin comments --aweme-id "<aweme_id>" --all --include-replies --pretty
|
|
68
73
|
npx -y socialdatax-skills@latest douyin user-info --sec-user-id "<sec_user_id>" --pretty
|
|
69
74
|
npx -y socialdatax-skills@latest douyin user-posts --sec-user-id "<sec_user_id>" --pretty
|
|
75
|
+
npx -y socialdatax-skills@latest douyin user-posts --sec-user-id "<sec_user_id>" --all --pretty
|
|
70
76
|
npx -y socialdatax-skills@latest kuaishou hot-search --pretty
|
|
71
77
|
npx -y socialdatax-skills@latest kuaishou search --keyword "<keyword>" --pretty
|
|
78
|
+
npx -y socialdatax-skills@latest kuaishou search --keyword "<keyword>" --pages 3 --pretty
|
|
72
79
|
npx -y socialdatax-skills@latest kuaishou detail --photo-id "<photo_id>" --pretty
|
|
73
80
|
npx -y socialdatax-skills@latest kuaishou comments --photo-id "<photo_id>" --pretty
|
|
81
|
+
npx -y socialdatax-skills@latest kuaishou comments --photo-id "<photo_id>" --all --include-replies --pretty
|
|
74
82
|
npx -y socialdatax-skills@latest kuaishou user-info --user-id "<user_id>" --pretty
|
|
75
83
|
npx -y socialdatax-skills@latest kuaishou user-posts --user-id "<user_id>" --pretty
|
|
84
|
+
npx -y socialdatax-skills@latest kuaishou user-posts --user-id "<user_id>" --all --pretty
|
|
76
85
|
```
|
|
77
86
|
|
|
78
87
|
Additional direct CLI entrypoints:
|
|
@@ -87,6 +96,7 @@ npx -y socialdatax-skills@latest douyin comments --url "<douyin_content_url_or_s
|
|
|
87
96
|
npx -y socialdatax-skills@latest douyin user-info --profile-url "<profile_url_or_share_text>" --pretty
|
|
88
97
|
npx -y socialdatax-skills@latest douyin user-posts --profile-url "<profile_url_or_share_text>" --pretty
|
|
89
98
|
npx -y socialdatax-skills@latest douyin user-series --sec-user-id "<sec_user_id>" --pretty
|
|
99
|
+
npx -y socialdatax-skills@latest douyin user-series --sec-user-id "<sec_user_id>" --all --pretty
|
|
90
100
|
npx -y socialdatax-skills@latest douyin user-series --profile-url "<profile_url_or_share_text>" --pretty
|
|
91
101
|
npx -y socialdatax-skills@latest xhs sub-comments --note-id "<note_id>" --comment-id "<comment_id>" --pretty
|
|
92
102
|
npx -y socialdatax-skills@latest douyin replies --aweme-id "<aweme_id>" --comment-id "<comment_id>" --pretty
|
|
@@ -110,7 +120,12 @@ Required arguments:
|
|
|
110
120
|
|
|
111
121
|
Optional arguments:
|
|
112
122
|
|
|
113
|
-
-
|
|
123
|
+
- XHS numeric `page` is only for XHS search; Douyin and Kuaishou search use `page_token` only and do not accept `page`. Omit `page_token` on the first Douyin/Kuaishou search request, and continue only with the complete returned `next_page_token` from the same chain.
|
|
124
|
+
- `--page-token <next_page_token>`: use for Douyin/Kuaishou search continuation and for token-paginated comments, replies, creator posts, creator notes, and creator series. Keep the original topic, content item, or creator target stable. Do not modify, truncate, redact, mask, omit, normalize, rebuild, generate, or replace the middle with ellipses.
|
|
125
|
+
- `--pages <n>`: fetch and merge N pages for search, comments, replies, creator posts, or creator series.
|
|
126
|
+
- `--all`: fetch comments, replies, creator posts, or creator series until `next_page_token` is empty; do not use it for search.
|
|
127
|
+
- `--max-items <n>`: stop after collecting N primary results.
|
|
128
|
+
- `--include-replies`: for first-level comments, also fetch nested second-level replies under each returned comment.
|
|
114
129
|
- `--sort-type`, `--note-type`, `--publish-time-range`, `--duration-range`, and `--content-type`: apply only to XHS or Douyin keyword research commands when the user asks for filtering; Kuaishou search currently uses `--keyword` and optional `--page-token`.
|
|
115
130
|
- `--comment-id`: required for reply/sub-comment commands together with the content ID.
|
|
116
131
|
- `--pretty`: output formatting only.
|
|
@@ -171,7 +186,7 @@ MCP tools matching the direct CLI commands above:
|
|
|
171
186
|
- `kuaishou_get_video_comment_replies_by_comment_id`
|
|
172
187
|
|
|
173
188
|
Use the automatically listed MCP tools above as the source of truth for tool names. Pick the narrowest tool for the user's platform and task.
|
|
174
|
-
|
|
189
|
+
Use numeric `page` only for `xhs_search_notes`. Do not pass `page` to `douyin_search_videos` or `kuaishou_search_videos`; omit `page_token` on the first request and pass only the complete returned `next_page_token` when continuing the same chain.
|
|
175
190
|
|
|
176
191
|
## Output Guidance
|
|
177
192
|
|