socialdatax-skills 0.2.22 → 0.2.23

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.
Files changed (3) hide show
  1. package/README.md +1 -1
  2. package/cli.mjs +185 -1
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -73,7 +73,7 @@ Common search phrases for this skill package:
73
73
  - Sensitive Words Check MCP server name: `sensitive-check`
74
74
  - Sensitive Words Check future MCP registry name: `com.socialdatax/sensitive-check`
75
75
  - Unified MCP registry name: none; this package installs skills and calls explicit platform services.
76
- - Current public capability version: `0.2.22`
76
+ - Current public capability version: `0.2.23`
77
77
 
78
78
  ## Direct CLI
79
79
 
package/cli.mjs CHANGED
@@ -17,7 +17,7 @@ import { dirname, join, resolve } from "node:path";
17
17
  import { fileURLToPath } from "node:url";
18
18
 
19
19
  const PACKAGE_NAME = "socialdatax-skills";
20
- const PACKAGE_VERSION = "0.2.22";
20
+ const PACKAGE_VERSION = "0.2.23";
21
21
  const PACKAGE_SPEC = `${PACKAGE_NAME}@latest`;
22
22
  const LOG_PREFIX = `[${PACKAGE_NAME}]`;
23
23
  const MIN_NODE_VERSION = "20.18.1";
@@ -29,6 +29,37 @@ const MEDIA_DOWNLOAD_TIMEOUT_MS = 10 * 60 * 1000;
29
29
  const MEDIA_DOWNLOAD_MAX_BYTES = 2 * 1024 * 1024 * 1024;
30
30
  const MEDIA_DECRYPT_CHUNK_SIZE = 64 * 1024;
31
31
  const MEDIA_DOWNLOAD_MAX_REDIRECTS = 5;
32
+ const MP4_BOX_HEADER_SIZE = 8;
33
+ const MP4_EXTENDED_BOX_HEADER_SIZE = 16;
34
+ const MP4_MAX_BOX_SCAN_DEPTH = 16;
35
+ const MP4_FULL_BOX_HEADER_SIZE = 4;
36
+ const MP4_STSD_ENTRY_COUNT_SIZE = 4;
37
+ const MP4_VISUAL_SAMPLE_ENTRY_HEADER_SIZE = 78;
38
+ const MP4_VVC_NAL_LENGTH_SIZE_BYTE_OFFSET = 4;
39
+ const MP4_CONTAINER_BOX_TYPES = new Set([
40
+ "dinf",
41
+ "edts",
42
+ "mdia",
43
+ "meta",
44
+ "minf",
45
+ "moof",
46
+ "moov",
47
+ "mfra",
48
+ "stbl",
49
+ "traf",
50
+ "trak",
51
+ "udta",
52
+ ]);
53
+ const MP4_VISUAL_SAMPLE_ENTRY_TYPES = new Set([
54
+ "av01",
55
+ "avc1",
56
+ "avc3",
57
+ "encv",
58
+ "hev1",
59
+ "hvc1",
60
+ "vvc1",
61
+ "vvi1",
62
+ ]);
32
63
  // Keep this local helper scoped to WeChat media host trees; require k for the decrypt key.
33
64
  const WECHAT_MEDIA_DOWNLOAD_HOST_SUFFIXES = [
34
65
  ".wxapp.tc.qq.com",
@@ -2936,6 +2967,7 @@ export async function decryptWechatMediaCommand(
2936
2967
  encryptedLength,
2937
2968
  decodeKey,
2938
2969
  });
2970
+ await patchWechatVvcMp4NalLengthMetadata(tempPath);
2939
2971
  decrypted = true;
2940
2972
  }
2941
2973
  await rename(tempPath, outputPath);
@@ -3185,6 +3217,158 @@ async function decryptWechatMediaFilePrefix(
3185
3217
  }
3186
3218
  }
3187
3219
 
3220
+ async function patchWechatVvcMp4NalLengthMetadata(filePath) {
3221
+ const file = await openFile(filePath, "r+");
3222
+ try {
3223
+ const stats = await file.stat();
3224
+ await patchMp4ChildBoxes(file, 0, stats.size, 0);
3225
+ } finally {
3226
+ await file.close();
3227
+ }
3228
+ }
3229
+
3230
+ async function patchMp4ChildBoxes(file, startOffset, endOffset, depth) {
3231
+ if (
3232
+ depth > MP4_MAX_BOX_SCAN_DEPTH ||
3233
+ startOffset < 0 ||
3234
+ endOffset - startOffset < MP4_BOX_HEADER_SIZE
3235
+ ) {
3236
+ return;
3237
+ }
3238
+
3239
+ let offset = startOffset;
3240
+ while (offset + MP4_BOX_HEADER_SIZE <= endOffset) {
3241
+ const box = await readMp4BoxHeader(file, offset, endOffset);
3242
+ if (!box) {
3243
+ return;
3244
+ }
3245
+ if (box.type === "vvcC") {
3246
+ await patchVvcConfigurationNalLengthSize(file, box);
3247
+ } else {
3248
+ const childRange = mp4ChildBoxRange(box);
3249
+ if (childRange) {
3250
+ await patchMp4ChildBoxes(
3251
+ file,
3252
+ childRange.startOffset,
3253
+ childRange.endOffset,
3254
+ depth + 1
3255
+ );
3256
+ }
3257
+ }
3258
+ offset = box.endOffset;
3259
+ }
3260
+ }
3261
+
3262
+ async function readMp4BoxHeader(file, offset, parentEndOffset) {
3263
+ const baseHeader = Buffer.allocUnsafe(MP4_BOX_HEADER_SIZE);
3264
+ const baseRead = await file.read(
3265
+ baseHeader,
3266
+ 0,
3267
+ MP4_BOX_HEADER_SIZE,
3268
+ offset
3269
+ );
3270
+ if (baseRead.bytesRead !== MP4_BOX_HEADER_SIZE) {
3271
+ return undefined;
3272
+ }
3273
+
3274
+ const size32 = baseHeader.readUInt32BE(0);
3275
+ const type = baseHeader.toString("ascii", 4, 8);
3276
+ let headerSize = MP4_BOX_HEADER_SIZE;
3277
+ let size;
3278
+ if (size32 === 0) {
3279
+ size = parentEndOffset - offset;
3280
+ } else if (size32 === 1) {
3281
+ if (offset + MP4_EXTENDED_BOX_HEADER_SIZE > parentEndOffset) {
3282
+ return undefined;
3283
+ }
3284
+ const extendedSizeBuffer = Buffer.allocUnsafe(8);
3285
+ const extendedRead = await file.read(
3286
+ extendedSizeBuffer,
3287
+ 0,
3288
+ 8,
3289
+ offset + MP4_BOX_HEADER_SIZE
3290
+ );
3291
+ if (extendedRead.bytesRead !== 8) {
3292
+ return undefined;
3293
+ }
3294
+ const extendedSize = extendedSizeBuffer.readBigUInt64BE(0);
3295
+ if (extendedSize > BigInt(Number.MAX_SAFE_INTEGER)) {
3296
+ return undefined;
3297
+ }
3298
+ size = Number(extendedSize);
3299
+ headerSize = MP4_EXTENDED_BOX_HEADER_SIZE;
3300
+ } else {
3301
+ size = size32;
3302
+ }
3303
+
3304
+ if (
3305
+ size < headerSize ||
3306
+ offset + size > parentEndOffset ||
3307
+ !isMp4BoxType(type)
3308
+ ) {
3309
+ return undefined;
3310
+ }
3311
+
3312
+ return {
3313
+ type,
3314
+ startOffset: offset,
3315
+ headerSize,
3316
+ payloadOffset: offset + headerSize,
3317
+ endOffset: offset + size,
3318
+ };
3319
+ }
3320
+
3321
+ function isMp4BoxType(type) {
3322
+ return /^[\x20-\x7e]{4}$/.test(type);
3323
+ }
3324
+
3325
+ function mp4ChildBoxRange(box) {
3326
+ if (box.type === "stsd") {
3327
+ const childStart =
3328
+ box.payloadOffset + MP4_FULL_BOX_HEADER_SIZE + MP4_STSD_ENTRY_COUNT_SIZE;
3329
+ return childStart < box.endOffset
3330
+ ? { startOffset: childStart, endOffset: box.endOffset }
3331
+ : undefined;
3332
+ }
3333
+ if (box.type === "meta") {
3334
+ const childStart = box.payloadOffset + MP4_FULL_BOX_HEADER_SIZE;
3335
+ return childStart < box.endOffset
3336
+ ? { startOffset: childStart, endOffset: box.endOffset }
3337
+ : undefined;
3338
+ }
3339
+ if (MP4_VISUAL_SAMPLE_ENTRY_TYPES.has(box.type)) {
3340
+ const childStart = box.payloadOffset + MP4_VISUAL_SAMPLE_ENTRY_HEADER_SIZE;
3341
+ return childStart < box.endOffset
3342
+ ? { startOffset: childStart, endOffset: box.endOffset }
3343
+ : undefined;
3344
+ }
3345
+ if (MP4_CONTAINER_BOX_TYPES.has(box.type)) {
3346
+ return box.payloadOffset < box.endOffset
3347
+ ? { startOffset: box.payloadOffset, endOffset: box.endOffset }
3348
+ : undefined;
3349
+ }
3350
+ return undefined;
3351
+ }
3352
+
3353
+ async function patchVvcConfigurationNalLengthSize(file, box) {
3354
+ const targetOffset =
3355
+ box.payloadOffset + MP4_VVC_NAL_LENGTH_SIZE_BYTE_OFFSET;
3356
+ if (targetOffset >= box.endOffset) {
3357
+ return;
3358
+ }
3359
+ const buffer = Buffer.allocUnsafe(1);
3360
+ const { bytesRead } = await file.read(buffer, 0, 1, targetOffset);
3361
+ if (bytesRead !== 1) {
3362
+ return;
3363
+ }
3364
+ const patched = (buffer[0] & 0xf8) | 0x07;
3365
+ if (patched === buffer[0]) {
3366
+ return;
3367
+ }
3368
+ buffer[0] = patched;
3369
+ await writeAllToFile(file, buffer, targetOffset);
3370
+ }
3371
+
3188
3372
  function isAbortLikeError(error) {
3189
3373
  return error?.name === "AbortError" || error?.name === "TimeoutError";
3190
3374
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "socialdatax-skills",
3
- "version": "0.2.22",
3
+ "version": "0.2.23",
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",