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 +1 -1
- package/cli.mjs +22 -11
- package/lib/media/bilibili-download.mjs +73 -8
- package/package.json +1 -1
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.
|
|
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 {
|
|
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.
|
|
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
|
|
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
|
|
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 =
|
|
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 {
|
|
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
|
|
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 =
|
|
226
|
-
|
|
227
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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