socialdatax-skills 0.2.25 → 0.2.26

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
@@ -79,7 +79,7 @@ Common search phrases for this skill package:
79
79
  - Sensitive Words Check MCP server name: `sensitive-check`
80
80
  - Sensitive Words Check future MCP registry name: `com.socialdatax/sensitive-check`
81
81
  - Unified MCP registry name: none; this package installs skills and calls explicit platform services.
82
- - Current public capability version: `0.2.25`
82
+ - Current public capability version: `0.2.26`
83
83
 
84
84
  ## Direct CLI
85
85
 
package/cli.mjs CHANGED
@@ -12,13 +12,16 @@ import { homedir } from "node:os";
12
12
  import { dirname, join, resolve } from "node:path";
13
13
  import { fileURLToPath } from "node:url";
14
14
 
15
- import { downloadBilibiliVideoFromManifest } from "./lib/media/bilibili-download.mjs";
15
+ import {
16
+ assertBilibiliFfmpegAvailable,
17
+ downloadBilibiliVideoFromManifest,
18
+ } from "./lib/media/bilibili-download.mjs";
16
19
  import { decryptWechatMediaCommand } from "./lib/media/wechat-decrypt.mjs";
17
20
 
18
21
  export { decryptWechatMediaCommand };
19
22
 
20
23
  const PACKAGE_NAME = "socialdatax-skills";
21
- const PACKAGE_VERSION = "0.2.25";
24
+ const PACKAGE_VERSION = "0.2.26";
22
25
  const PACKAGE_SPEC = `${PACKAGE_NAME}@latest`;
23
26
  const LOG_PREFIX = `[${PACKAGE_NAME}]`;
24
27
  const MIN_NODE_VERSION = "20.18.1";
@@ -527,12 +530,12 @@ const PLATFORMS = {
527
530
  {
528
531
  name: "xhs_get_note_comments_by_note_id",
529
532
  description:
530
- "Fetch paginated first-level comments by note ID with optional sort.",
533
+ "Fetch paginated first-level comments by note ID.",
531
534
  },
532
535
  {
533
536
  name: "xhs_get_note_comments_by_note_url",
534
537
  description:
535
- "Fetch paginated first-level comments from a note URL, short link, or share text with optional sort.",
538
+ "Fetch paginated first-level comments from a note URL, short link, or share text.",
536
539
  },
537
540
  {
538
541
  name: "xhs_get_note_sub_comments_by_comment_id",
@@ -2359,6 +2362,10 @@ async function assertBilibiliDownloadLocalPreflight(options) {
2359
2362
  await assertBilibiliParentDirectory(outputDir, "--output-dir parent path");
2360
2363
  }
2361
2364
  }
2365
+
2366
+ ensureSupportedNodeVersion();
2367
+ readDirectApiKey(PLATFORMS.bilibili);
2368
+ await assertBilibiliFfmpegAvailable(options.ffmpegPath || "ffmpeg");
2362
2369
  }
2363
2370
 
2364
2371
  async function assertBilibiliParentDirectory(targetPath, displayName) {
@@ -4203,13 +4210,7 @@ function buildRepliesOperationForComment(platform, comment, parentArguments, par
4203
4210
  async function callMcpBackend(operation) {
4204
4211
  ensureSupportedNodeVersion();
4205
4212
  const { platform, tool } = operation;
4206
- const apiKey = readFirstEnv(platform.apiKeyEnv);
4207
- if (!apiKey) {
4208
- throw new Error(
4209
- `Missing API Key. Set ${PRIMARY_API_KEY_ENV} before running direct CLI calls.`
4210
- );
4211
- }
4212
-
4213
+ const apiKey = readDirectApiKey(platform);
4213
4214
  const { Client, StreamableHTTPClientTransport } = await loadMcpSdkModules();
4214
4215
  const upstreamUrl = resolveUpstreamUrl(platform);
4215
4216
  const client = new Client(
@@ -4258,6 +4259,16 @@ async function callMcpBackend(operation) {
4258
4259
  }
4259
4260
  }
4260
4261
 
4262
+ function readDirectApiKey(platform) {
4263
+ const apiKey = readFirstEnv(platform.apiKeyEnv);
4264
+ if (!apiKey) {
4265
+ throw new Error(
4266
+ `Missing API Key. Set ${PRIMARY_API_KEY_ENV} before running direct CLI calls.`
4267
+ );
4268
+ }
4269
+ return apiKey;
4270
+ }
4271
+
4261
4272
  async function loadMcpSdkModules() {
4262
4273
  if (!mcpSdkModules) {
4263
4274
  const [{ Client }, { StreamableHTTPClientTransport }] = await Promise.all([
@@ -8,7 +8,12 @@ import {
8
8
  stat,
9
9
  unlink,
10
10
  } from "node:fs/promises";
11
- import { homedir } from "node:os";
11
+ import {
12
+ arch as osArch,
13
+ homedir,
14
+ platform as osPlatform,
15
+ release as osRelease,
16
+ } from "node:os";
12
17
  import { dirname, join, resolve } from "node:path";
13
18
 
14
19
  import {
@@ -39,11 +44,18 @@ export async function downloadBilibiliVideoFromManifest(
39
44
  validateBilibiliDownloadManifest(manifest);
40
45
 
41
46
  const outputPath = resolveBilibiliOutputPath(manifest, options);
47
+ const ffmpegPath = options.ffmpegPath || "ffmpeg";
42
48
  await mkdir(dirname(outputPath), { recursive: true });
43
49
  await reserveBilibiliOutputPath(outputPath);
44
50
 
45
51
  const downloadedTracks = [];
46
52
  try {
53
+ await assertBilibiliTrackPathsAvailable(
54
+ manifest.download_manifest.tracks,
55
+ outputPath
56
+ );
57
+ await assertBilibiliFfmpegAvailable(ffmpegPath, spawnProcess);
58
+
47
59
  for (const track of manifest.download_manifest.tracks) {
48
60
  downloadedTracks.push(
49
61
  await downloadBilibiliTrack(track, {
@@ -63,7 +75,7 @@ export async function downloadBilibiliVideoFromManifest(
63
75
  }
64
76
 
65
77
  await mergeBilibiliTracksWithFfmpeg({
66
- ffmpegPath: options.ffmpegPath || "ffmpeg",
78
+ ffmpegPath,
67
79
  videoPath: videoTrack.path,
68
80
  audioPath: audioTrack.path,
69
81
  outputPath,
@@ -222,9 +234,10 @@ async function downloadBilibiliTrack(
222
234
  maxDownloadBytes,
223
235
  }
224
236
  ) {
225
- const trackType = String(track.type);
226
- const ext = sanitizeFileExtension(track.ext || "m4s");
227
- const trackPath = `${outputPath}.${trackType}.${ext}`;
237
+ const { trackType, ext, trackPath } = resolveBilibiliTrackTarget(
238
+ track,
239
+ outputPath
240
+ );
228
241
  const tempPath = `${trackPath}.tmp-${process.pid}-${Date.now()}-${randomUUID()}`;
229
242
  await mkdir(dirname(trackPath), { recursive: true });
230
243
 
@@ -275,6 +288,31 @@ async function downloadBilibiliTrack(
275
288
  };
276
289
  }
277
290
 
291
+ async function assertBilibiliTrackPathsAvailable(tracks, outputPath) {
292
+ for (const track of tracks) {
293
+ const { trackType, trackPath } = resolveBilibiliTrackTarget(track, outputPath);
294
+ const existingTrack = await stat(trackPath).catch((error) => {
295
+ if (error?.code === "ENOENT") {
296
+ return undefined;
297
+ }
298
+ throw error;
299
+ });
300
+ if (existingTrack) {
301
+ throw new Error(`Bilibili ${trackType} track file already exists.`);
302
+ }
303
+ }
304
+ }
305
+
306
+ function resolveBilibiliTrackTarget(track, outputPath) {
307
+ const trackType = String(track.type);
308
+ const ext = sanitizeFileExtension(track.ext || "m4s");
309
+ return {
310
+ trackType,
311
+ ext,
312
+ trackPath: `${outputPath}.${trackType}.${ext}`,
313
+ };
314
+ }
315
+
278
316
  async function reserveBilibiliTrackPath(trackPath, trackType) {
279
317
  let file;
280
318
  try {
@@ -341,7 +379,21 @@ async function mergeBilibiliTracksWithFfmpeg({
341
379
  await runFfmpegProcess(ffmpegPath, args, spawnProcess);
342
380
  }
343
381
 
344
- async function runFfmpegProcess(ffmpegPath, args, spawnProcess) {
382
+ export async function assertBilibiliFfmpegAvailable(
383
+ ffmpegPath = "ffmpeg",
384
+ spawnProcess = spawn
385
+ ) {
386
+ await runFfmpegProcess(ffmpegPath, ["-version"], spawnProcess, {
387
+ failurePrefix: "ffmpeg availability check failed",
388
+ });
389
+ }
390
+
391
+ async function runFfmpegProcess(
392
+ ffmpegPath,
393
+ args,
394
+ spawnProcess,
395
+ { failurePrefix = "ffmpeg failed for bilibili download" } = {}
396
+ ) {
345
397
  return new Promise((resolvePromise, reject) => {
346
398
  let stderr = "";
347
399
  const child = spawnProcess(ffmpegPath, args, {
@@ -358,7 +410,7 @@ async function runFfmpegProcess(ffmpegPath, args, spawnProcess) {
358
410
  if (error?.code === "ENOENT") {
359
411
  reject(
360
412
  new Error(
361
- "ffmpeg not found. Install ffmpeg or pass --ffmpeg-path for bilibili download."
413
+ buildFfmpegMissingMessage(ffmpegPath)
362
414
  )
363
415
  );
364
416
  return;
@@ -372,13 +424,26 @@ async function runFfmpegProcess(ffmpegPath, args, spawnProcess) {
372
424
  }
373
425
  reject(
374
426
  new Error(
375
- `ffmpeg failed for bilibili download with exit code ${status}. ${stderr.trim()}`.trim()
427
+ `${failurePrefix} with exit code ${status}. ${stderr.trim()}`.trim()
376
428
  )
377
429
  );
378
430
  });
379
431
  });
380
432
  }
381
433
 
434
+ function buildFfmpegMissingMessage(ffmpegPath) {
435
+ const detectedSystem = `${osPlatform()} ${osRelease()} ${osArch()}`;
436
+ return [
437
+ `ffmpeg not found at "${ffmpegPath}".`,
438
+ `Detected system: ${detectedSystem}.`,
439
+ "Install ffmpeg, then retry:",
440
+ "macOS (Homebrew): brew install ffmpeg",
441
+ "Ubuntu/Debian: sudo apt update && sudo apt install ffmpeg",
442
+ "Windows (winget): winget install Gyan.FFmpeg",
443
+ "If ffmpeg is already installed, pass --ffmpeg-path /path/to/ffmpeg for bilibili download.",
444
+ ].join("\n");
445
+ }
446
+
382
447
  function stringValue(value) {
383
448
  return typeof value === "string" && value.trim() ? value : undefined;
384
449
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "socialdatax-skills",
3
- "version": "0.2.25",
3
+ "version": "0.2.26",
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",