socialdatax-skills 0.2.6 → 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 +3 -3
- package/cli.mjs +138 -4
- package/package.json +1 -1
- package/skills/media-comments/SKILL.md +1 -1
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
|
|
|
@@ -247,7 +247,7 @@ Current Douyin workflows include:
|
|
|
247
247
|
- Search related Douyin works by keyword.
|
|
248
248
|
- Resolve a Douyin content page link, short link, share text, or aweme_id into structured work details.
|
|
249
249
|
- Fetch paginated first-level comments for comment analysis.
|
|
250
|
-
- 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.
|
|
251
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.
|
|
252
252
|
- Read creator profile data from a profile link, short link, share text, or sec_user_id.
|
|
253
253
|
- Fetch paginated creator work lists from a sec_user_id, profile link, short link, or share text.
|
|
@@ -279,7 +279,7 @@ Current Douyin workflows include:
|
|
|
279
279
|
| `douyin_get_video_detail_by_url` | Resolve a Douyin content page link, short link, or share text into structured Douyin work details. |
|
|
280
280
|
| `douyin_get_video_comments_by_aweme_id` | Fetch paginated first-level comments when the caller already has an aweme_id. |
|
|
281
281
|
| `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
|
|
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. |
|
|
283
283
|
| `douyin_get_user_info_by_sec_user_id` | Fetch creator profile data when the caller already has a sec_user_id. |
|
|
284
284
|
| `douyin_get_user_info_by_profile_url` | Resolve a profile link, short link, or share text into creator profile data. |
|
|
285
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. |
|
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";
|
|
@@ -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.
|