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 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.6`
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 by aweme_id and comment_id. |
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.6";
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 by aweme_id and comment_id.",
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 directly and print JSON.");
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 = directPageItems(pageData);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "socialdatax-skills",
3
- "version": "0.2.6",
3
+ "version": "0.2.7",
4
4
  "description": "SocialDataX / 社媒数据助手 skill installer and direct CLI helper for hosted platform social media MCP services.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -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 the aweme_id and first-level comment ID are known.
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.