socialdatax-skills 0.2.6 → 0.2.8
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
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.8`
|
|
52
52
|
|
|
53
53
|
## Direct CLI
|
|
54
54
|
|
|
@@ -93,6 +93,8 @@ npx -y socialdatax-skills@latest kuaishou user-posts --profile-url "<profile_url
|
|
|
93
93
|
|
|
94
94
|
The direct CLI prints a JSON envelope with `platform`, `tool`, `arguments`, and `data`.
|
|
95
95
|
|
|
96
|
+
For XHS outputs with a returned `note_url` field, when `note_url` is non-null, preserve it exactly as the full URL, including `xsec_token` query parameters, such as in final answers or display. Do not modify, truncate, redact, normalize, rebuild, or replace it with a link assembled from `note_id`. If `note_url` is null, do not synthesize a public link from `note_id`.
|
|
97
|
+
|
|
96
98
|
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
99
|
|
|
98
100
|
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.
|
|
@@ -247,7 +249,7 @@ Current Douyin workflows include:
|
|
|
247
249
|
- Search related Douyin works by keyword.
|
|
248
250
|
- Resolve a Douyin content page link, short link, share text, or aweme_id into structured work details.
|
|
249
251
|
- Fetch paginated first-level comments for comment analysis.
|
|
250
|
-
- Fetch paginated replies under a first-level comment.
|
|
252
|
+
- Fetch paginated replies under a first-level comment; pass both `aweme_id` and `comment_id`, and use `page_token` to continue pagination.
|
|
251
253
|
- 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.
|
|
252
254
|
- Read creator profile data from a profile link, short link, share text, or sec_user_id.
|
|
253
255
|
- Fetch paginated creator work lists from a sec_user_id, profile link, short link, or share text.
|
|
@@ -279,7 +281,7 @@ Current Douyin workflows include:
|
|
|
279
281
|
| `douyin_get_video_detail_by_url` | Resolve a Douyin content page link, short link, or share text into structured Douyin work details. |
|
|
280
282
|
| `douyin_get_video_comments_by_aweme_id` | Fetch paginated first-level comments when the caller already has an aweme_id. |
|
|
281
283
|
| `douyin_get_video_comments_by_url` | Fetch paginated first-level comments directly from a Douyin content page URL, short link, or share text. |
|
|
282
|
-
| `douyin_get_video_comment_replies_by_comment_id` | Fetch paginated replies under a first-level Douyin comment
|
|
284
|
+
| `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. |
|
|
283
285
|
| `douyin_get_user_info_by_sec_user_id` | Fetch creator profile data when the caller already has a sec_user_id. |
|
|
284
286
|
| `douyin_get_user_info_by_profile_url` | Resolve a profile link, short link, or share text into creator profile data. |
|
|
285
287
|
| `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. |
|
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.8";
|
|
11
11
|
const PACKAGE_SPEC = `${PACKAGE_NAME}@latest`;
|
|
12
12
|
const LOG_PREFIX = `[${PACKAGE_NAME}]`;
|
|
13
13
|
const MIN_NODE_VERSION = "20.18.1";
|
|
@@ -339,7 +339,7 @@ const PLATFORMS = {
|
|
|
339
339
|
},
|
|
340
340
|
{
|
|
341
341
|
name: "douyin_get_video_comment_replies_by_comment_id",
|
|
342
|
-
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.",
|
|
343
343
|
},
|
|
344
344
|
{
|
|
345
345
|
name: "douyin_get_user_info_by_sec_user_id",
|
|
@@ -1134,7 +1134,7 @@ function printHelp() {
|
|
|
1134
1134
|
console.log(
|
|
1135
1135
|
` npx -y ${PACKAGE_SPEC} douyin replies --aweme-id "<aweme_id>" --comment-id "<comment_id>" --pretty`
|
|
1136
1136
|
);
|
|
1137
|
-
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.");
|
|
1138
1138
|
console.log("");
|
|
1139
1139
|
console.log(` npx -y ${PACKAGE_SPEC} douyin user-info --sec-user-id "<sec_user_id>" --pretty`);
|
|
1140
1140
|
console.log(" Call the Douyin creator profile tool directly and print JSON.");
|
|
@@ -1945,6 +1945,7 @@ async function collectPaginatedDirectData(
|
|
|
1945
1945
|
) {
|
|
1946
1946
|
const pageLimit = pagination.all ? Number.POSITIVE_INFINITY : pagination.pages || 1;
|
|
1947
1947
|
const collectedItems = [];
|
|
1948
|
+
const itemDedupeState = createPaginatedItemDedupeState();
|
|
1948
1949
|
const seenNextMarkers = initialPaginationMarkers(operation);
|
|
1949
1950
|
let pageCount = 0;
|
|
1950
1951
|
let lastPageData;
|
|
@@ -1958,7 +1959,11 @@ async function collectPaginatedDirectData(
|
|
|
1958
1959
|
lastPageData = pageData;
|
|
1959
1960
|
parentContextData = mergeParentContextData(parentContextData, pageData);
|
|
1960
1961
|
|
|
1961
|
-
const pageItems =
|
|
1962
|
+
const pageItems = uniquePaginatedPageItems(
|
|
1963
|
+
operation,
|
|
1964
|
+
directPageItems(pageData),
|
|
1965
|
+
itemDedupeState
|
|
1966
|
+
);
|
|
1962
1967
|
const candidateItems = itemsForRemainingLimit(
|
|
1963
1968
|
pageItems,
|
|
1964
1969
|
collectedItems.length,
|
|
@@ -2018,6 +2023,135 @@ function directPageItems(pageData) {
|
|
|
2018
2023
|
return Array.isArray(pageData?.items) ? pageData.items : [];
|
|
2019
2024
|
}
|
|
2020
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
|
+
|
|
2021
2155
|
function mergeParentContextData(current, pageData) {
|
|
2022
2156
|
return {
|
|
2023
2157
|
...current,
|
package/package.json
CHANGED
|
@@ -96,7 +96,7 @@ If MCP tools are already available in the current agent, use one of these tools:
|
|
|
96
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.
|
|
97
97
|
- `douyin_get_video_comments_by_aweme_id`: use when the aweme_id is known.
|
|
98
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`.
|
|
99
|
-
- `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.
|
|
100
100
|
|
|
101
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.
|
|
102
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.
|
|
@@ -87,7 +87,7 @@ 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,
|
|
90
|
+
For XHS detail results, in every use of a returned `note_url`, such as final answers, display, references, storage, output, or forwarding, preserve it exactly as the full URL, 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
91
|
For XHS `note_id`, copy the complete 24-character lowercase hexadecimal ID exactly; do not pass or display only a prefix.
|
|
92
92
|
For Douyin detail, include `content_type` when available.
|
|
93
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.
|
|
@@ -158,7 +158,7 @@ Continue Kuaishou pagination only when `next_page_token` is not empty. Pass the
|
|
|
158
158
|
## Output Guidance
|
|
159
159
|
|
|
160
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,
|
|
161
|
+
For XHS search results, in every use of a returned `note_url`, such as final answers, display, references, storage, output, or forwarding, preserve it exactly as the full URL, including `xsec_token` query parameters. Do not modify, truncate, redact, mask, normalize, rebuild, or synthesize the URL from `note_id`.
|
|
162
162
|
For XHS `note_id`, copy the complete 24-character lowercase hexadecimal ID exactly; do not pass or display only a prefix.
|
|
163
163
|
For Douyin search results, include `content_type` when the user needs traceability.
|
|
164
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.
|
|
@@ -191,6 +191,8 @@ Use numeric `page` only for `xhs_search_notes`. Do not pass `page` to `douyin_se
|
|
|
191
191
|
## Output Guidance
|
|
192
192
|
|
|
193
193
|
For broad research, summarize visible evidence separately from interpretation and organize findings by platform, content angles, audience needs, trend signals, comment themes, creator positioning, and practical next steps.
|
|
194
|
+
For XHS search or detail results, in every use of a returned `note_url`, such as final answers, display, references, storage, output, or forwarding, preserve it exactly as the full URL, including `xsec_token` query parameters. Do not modify, truncate, redact, mask, normalize, rebuild, or synthesize the URL from `note_id`; if detail `note_url` is null, show the `note_id` or say that no directly openable full link is available.
|
|
195
|
+
For XHS `note_id`, copy the complete 24-character lowercase hexadecimal ID exactly; do not pass or display only a prefix.
|
|
194
196
|
For comments, group observed themes before inferring sentiment or demand.
|
|
195
197
|
For creators, separate profile facts from content-list evidence; include Douyin short-drama series facts when the series command is used and Kuaishou work-list evidence when Kuaishou commands are used.
|
|
196
198
|
For hot-search, report ranking signals separately from keyword search results.
|