karin-plugin-kkk 2.45.0 → 2.45.1-beta.1

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.
@@ -1,7 +1,8 @@
1
1
  globalThis.__KTR_BUNDLED__ = true;
2
2
  import { o as __toESM } from "./rolldown-runtime.js";
3
- import { $r as number$1, Gr as CryptoConfig, Jr as xRapParam, Kr as FingerprintGenerator, Qr as literal, Wr as require_protobufjs, Xr as _enum, Yr as number, Zr as array, ei as object, ni as union, qr as Xhshow, ri as toJSONSchema, ti as string } from "./vendor.js";
3
+ import { $r as record, Gr as require_protobufjs, Jr as any, Kr as number, Qr as object, Xr as literal, Yr as array, Zr as number$1, ei as string, ni as ZodError, qr as _enum, ri as toJSONSchema, ti as union } from "./vendor.js";
4
4
  import crypto, { createCipheriv, createHash, randomBytes, randomUUID } from "node:crypto";
5
+ import { gzipSync } from "node:zlib";
5
6
  import express, { Router } from "node-karin/express";
6
7
  import axios, { AxiosError, AxiosHeaders, mergeConfig } from "node-karin/axios";
7
8
  import { EventEmitter } from "node:events";
@@ -986,7 +987,7 @@ var SALT = "dhzx";
986
987
  /** header 携带的两个明文字节,是这套格式最接近魔数的东西,也是 {@link structureError} 的第一道检查 */
987
988
  var HEADER_MAGIC = [3, 82];
988
989
  /** `"1.0.1.19-fix.01"` 被 SDK 解析后的版本块,实测解码结果 */
989
- var SDK_VERSION = [
990
+ var SDK_VERSION$1 = [
990
991
  1,
991
992
  0,
992
993
  1,
@@ -1458,7 +1459,7 @@ var ABogus = class {
1458
1459
  const now = options.nowMs ?? Date.now();
1459
1460
  if (now < 17218368e5) throw new Error(`时钟早于 a_bogus 纪元:${now}`);
1460
1461
  const fields = this.fields(query, body, now);
1461
- const version = [...maskPair(SDK_VERSION.slice(0, 2), this.rng), ...maskPair(SDK_VERSION.slice(2, 4), this.rng, {
1462
+ const version = [...maskPair(SDK_VERSION$1.slice(0, 2), this.rng), ...maskPair(SDK_VERSION$1.slice(2, 4), this.rng, {
1462
1463
  low: probeNoise(this.rng),
1463
1464
  high: tripwireNoise(this.rng)
1464
1465
  })];
@@ -2563,7 +2564,7 @@ var deriveKuaishouPureSignature = (context) => {
2563
2564
  };
2564
2565
  };
2565
2566
  //#endregion
2566
- //#region ../amagi/packages/core/dist/steps-bZrsb4W6.mjs
2567
+ //#region ../amagi/packages/core/dist/steps-BuOxvrAo.mjs
2567
2568
  /**
2568
2569
  * B站视频流签名器(qtparam 前置)。
2569
2570
  *
@@ -3438,32 +3439,2544 @@ var hxfalcon = (signer = sharedKuaishouSigner) => ({
3438
3439
  phase: "sign",
3439
3440
  apply: createHxfalconSigner(signer)
3440
3441
  });
3441
- /** 初始化签名配置。 */
3442
- var createXiaohongshuCryptoConfig = () => new CryptoConfig().withOverrides({
3443
- DATA_WEB_BUILD: "6.12.3",
3444
- SIGNATURE_DATA_TEMPLATE: {
3442
+ /**
3443
+ * 平台判定:把 UA 映射成小红书前端签名里用到的那几个平台量。
3444
+ *
3445
+ * 前端 `vendor-dynamic.js` 里有一个枚举和一个 switch,两者一起决定
3446
+ * `x-s-common` 的 `s0` 与 `x2`:
3447
+ *
3448
+ * ```js
3449
+ * PlatformCode: Windows=0, iOS=1, Android=2, MacOs=3, Linux=4, other=5
3450
+ *
3451
+ * function getPlatformCode (e) {
3452
+ * switch (e) {
3453
+ * case "Android": return PlatformCode.Android
3454
+ * case "iOS": return PlatformCode.iOS
3455
+ * case "Mac OS": return PlatformCode.MacOs
3456
+ * case "Linux": return PlatformCode.Linux
3457
+ * default: return PlatformCode.other
3458
+ * }
3459
+ * }
3460
+ * ```
3461
+ *
3462
+ * 那个 switch **没有 `case "Windows"`** —— Windows 落到 `default`,于是它的 `s0`
3463
+ * 是 5(other)而不是枚举里的 0。这不是前端的 bug,是必须照抄的行为:
3464
+ * 2026-09-25 真机 Edge 153 登录态抓下来的 `x-s-common` 解出来就是
3465
+ * `{"s0":5,"x2":"Windows",...}`。
3466
+ *
3467
+ * a1 Cookie 尾部那一位平台码走的是另一条路径(前端 `generateLocalId`),但用的是
3468
+ * **同一套映射** —— 同一次实测里 a1 尾部是 `"50000"`,平台位也是 `5`。于是
3469
+ * `PlatformCode.Windows = 0` 在 Web 端两条路径上都产不出来。
3470
+ */
3471
+ /** 前端 `PlatformCode` 枚举原值。照录用,Web 端实际只会取到 1~5。 */
3472
+ var PLATFORM_CODE = {
3473
+ Windows: 0,
3474
+ iOS: 1,
3475
+ Android: 2,
3476
+ MacOs: 3,
3477
+ Linux: 4,
3478
+ other: 5
3479
+ };
3480
+ /**
3481
+ * 从 UA 推断平台名,对应前端的 `e.platform`。
3482
+ *
3483
+ * 判定顺序要紧:iOS 的 UA 里带 `Mac OS X`,Android 的 UA 里带 `Linux`,
3484
+ * 所以移动端必须先判,否则 iPhone 会被认成 Mac、Android 会被认成 Linux。
3485
+ * @param userAgent - 实际发请求用的 UA
3486
+ * @returns 平台名;认不出时返回 undefined,由调用方决定兜底
3487
+ */
3488
+ function platformFromUserAgent(userAgent) {
3489
+ if (!userAgent) return void 0;
3490
+ const ua = userAgent.toLowerCase();
3491
+ if (/iphone|ipad|ipod/.test(ua)) return "iOS";
3492
+ if (/android/.test(ua)) return "Android";
3493
+ if (/windows|win64|win32/.test(ua)) return "Windows";
3494
+ if (/mac os x|macintosh/.test(ua)) return "Mac OS";
3495
+ if (/linux|x11|cros/.test(ua)) return "Linux";
3496
+ }
3497
+ /**
3498
+ * 复刻前端 `getPlatformCode`,产出 `x-s-common` 的 `s0`。
3499
+ *
3500
+ * 刻意保留「没有 Windows 分支」这个行为 —— Windows 必须落到 `other`(5)。
3501
+ * 把它"修正"成 0 会让签名与真实浏览器不一致。
3502
+ * @param platform - 平台名,取自 {@link platformFromUserAgent}
3503
+ * @returns `s0` 的取值
3504
+ */
3505
+ function getPlatformCode(platform) {
3506
+ switch (platform) {
3507
+ case "Android": return PLATFORM_CODE.Android;
3508
+ case "iOS": return PLATFORM_CODE.iOS;
3509
+ case "Mac OS": return PLATFORM_CODE.MacOs;
3510
+ case "Linux": return PLATFORM_CODE.Linux;
3511
+ default: return PLATFORM_CODE.other;
3512
+ }
3513
+ }
3514
+ /**
3515
+ * `XYW_` 载荷里 `x2` 那 19 位环境标志的真实浏览器取值。
3516
+ *
3517
+ * **这一串不按 OS 变,别给它建平台查表。** 2026-09-25 同机对照实测:
3518
+ *
3519
+ * | 环境 | `navigator.webdriver` | 第 11 位 |
3520
+ * | --- | --- | --- |
3521
+ * | Windows 真机 Edge 153(登录态) | `false` | `0` |
3522
+ * | Windows 自动化 Chrome 154 | `true` | `1` |
3523
+ *
3524
+ * 同一台机器、同一个 OS,只有自动化状态不同,第 11 位就翻转 —— 它测的是
3525
+ * webdriver,不是平台。此前有份调研归档把「第 11 位为 1」记成 Linux 的特征,
3526
+ * 那是在沙箱里抓的,测到的其实是沙箱自身的自动化标志。照那个建按 OS 查表的话,
3527
+ * 跑在 Linux 服务器上的签名会主动自报自动化特征,等于给风控送判据。
3528
+ *
3529
+ * 真正随平台变的是 `x-s-common` 的 `s0` 与 `x2`,见本文件上面两个函数。
3530
+ */
3531
+ var XYW_ENV_FLAGS_BROWSER = "0|0|0|1|0|0|1|0|0|0|1|0|0|0|0|1|0|0|0";
3532
+ var XHS_SESSION_START_MS = Date.now();
3533
+ var CryptoConfig = class CryptoConfig {
3534
+ DES_KEY = "zbp30y86";
3535
+ GID_URL = "https://as.xiaohongshu.com/api/sec/v1/shield/webprofile";
3536
+ DATA_PLATFORM = "Windows";
3537
+ DATA_SVN = "2";
3538
+ DATA_SDK_VERSION = "4.3.5";
3539
+ DATA_WEB_BUILD = "6.56.3";
3540
+ MAX_32BIT = 4294967295;
3541
+ MAX_SIGNED_32BIT = 2147483647;
3542
+ MAX_BYTE = 255;
3543
+ STANDARD_BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
3544
+ CUSTOM_BASE64_ALPHABET = "ZmserbBoHQtNP+wOcza/LpngG8yJq42KWYj0DSfdikx3VT16IlUAFM97hECvuRX5";
3545
+ X3_BASE64_ALPHABET = "MfgqrsbcyzPQRStuvC7mn501HIJBo2DEFTKdeNOwxWXYZap89+/A4UVLhijkl63G";
3546
+ HEX_KEY = "71a302257793271ddd273bcee3e4b98d9d7935e1da33f5765e2ea8afb6dc77a51a499d23b67c20660025860cbf13d4540d92497f58686c574e508f46e1956344f39139bf4faf22a3eef120b79258145b2feb5193b6478669961298e79bedca646e1a693a926154a5a7a1bd1cf0dedb742f917a747a1e388b234f2277516db7116035439730fa61e9822a0eca7bff72d8";
3547
+ EXPECTED_HEX_LENGTH = 32;
3548
+ OUTPUT_BYTE_COUNT = 8;
3549
+ HEX_CHUNK_SIZE = 2;
3550
+ VERSION_BYTES = [
3551
+ 121,
3552
+ 104,
3553
+ 96,
3554
+ 41
3555
+ ];
3556
+ PAYLOAD_LENGTH = 144;
3557
+ A1_LENGTH = 52;
3558
+ APP_ID_LENGTH = 10;
3559
+ MD5_XOR_LENGTH = 8;
3560
+ A3_PREFIX = [
3561
+ 2,
3562
+ 97,
3563
+ 51,
3564
+ 16
3565
+ ];
3566
+ TIMESTAMP_LE_LENGTH = 8;
3567
+ SEQUENCE_VALUE_MIN = 15;
3568
+ SEQUENCE_VALUE_MAX = 50;
3569
+ WINDOW_PROPS_LENGTH_MIN = 1e3;
3570
+ WINDOW_PROPS_LENGTH_MAX = 1200;
3571
+ SESSION_WINDOW_PROPS_INIT_MIN = 1e3;
3572
+ SESSION_WINDOW_PROPS_INIT_MAX = 2e3;
3573
+ SESSION_SEQUENCE_INIT_MIN = 15;
3574
+ SESSION_SEQUENCE_INIT_MAX = 17;
3575
+ SESSION_SEQUENCE_STEP_MIN = 0;
3576
+ SESSION_SEQUENCE_STEP_MAX = 1;
3577
+ SESSION_WINDOW_PROPS_STEP_MIN = 1;
3578
+ SESSION_WINDOW_PROPS_STEP_MAX = 10;
3579
+ CHECKSUM_VERSION = 1;
3580
+ CHECKSUM_XOR_KEY = 115;
3581
+ CHECKSUM_FIXED_TAIL = [
3582
+ 249,
3583
+ 65,
3584
+ 103,
3585
+ 103,
3586
+ 201,
3587
+ 181,
3588
+ 129,
3589
+ 99,
3590
+ 94,
3591
+ 7,
3592
+ 68,
3593
+ 250,
3594
+ 132,
3595
+ 21
3596
+ ];
3597
+ ENV_TABLE = [
3598
+ 115,
3599
+ 248,
3600
+ 83,
3601
+ 102,
3602
+ 103,
3603
+ 201,
3604
+ 181,
3605
+ 131,
3606
+ 99,
3607
+ 94,
3608
+ 4,
3609
+ 68,
3610
+ 250,
3611
+ 132,
3612
+ 21
3613
+ ];
3614
+ ENV_CHECKS_DEFAULT = [
3615
+ 0,
3616
+ 1,
3617
+ 18,
3618
+ 1,
3619
+ 0,
3620
+ 0,
3621
+ 0,
3622
+ 2,
3623
+ 0,
3624
+ 0,
3625
+ 3,
3626
+ 0,
3627
+ 0,
3628
+ 0,
3629
+ 0
3630
+ ];
3631
+ HASH_IV = [
3632
+ 1831565813,
3633
+ 461845907,
3634
+ 2246822507,
3635
+ 3266489909
3636
+ ];
3637
+ ENV_FINGERPRINT_XOR_KEY = 41;
3638
+ ENV_FINGERPRINT_TIME_OFFSET_MIN = 10;
3639
+ ENV_FINGERPRINT_TIME_OFFSET_MAX = 50;
3640
+ SIGNATURE_DATA_TEMPLATE = {
3445
3641
  x0: "4.4.3",
3446
3642
  x1: "xhs-pc-web",
3447
3643
  x2: "Windows",
3448
3644
  x3: "",
3449
3645
  x4: ""
3450
- },
3451
- SIGNATURE_XSCOMMON_TEMPLATE: {
3646
+ };
3647
+ SSK_SERVER_PUBLIC_KEY = "Kr0iygsCu3inYJNXCL4k4JuzaYQ2afI1xbwc7BH6sm8=";
3648
+ X3_PREFIX = "mns0301_";
3649
+ XYS_PREFIX = "XYS_";
3650
+ XYW_PREFIX = "XYW_";
3651
+ XYW_SIGN_SVN = "56";
3652
+ XYW_SIGN_TYPE = "x2";
3653
+ XYW_SIGN_VERSION = "1";
3654
+ XYW_AES_KEY = "7cc4adla5ay0701v";
3655
+ XYW_AES_IV = "4uzjr7mbsibcaldp";
3656
+ XYW_ENV_FLAGS_DEFAULT = XYW_ENV_FLAGS_BROWSER;
3657
+ HEX_CHARS = "abcdef0123456789";
3658
+ XRAY_TRACE_ID_SEQ_MAX = 8388607;
3659
+ XRAY_TRACE_ID_TIMESTAMP_SHIFT = 23;
3660
+ XRAY_TRACE_ID_PART1_LENGTH = 16;
3661
+ XRAY_TRACE_ID_PART2_LENGTH = 16;
3662
+ B3_TRACE_ID_LENGTH = 16;
3663
+ B1_SECRET_KEY = "xhswebmplfbt";
3664
+ XRAP_SDK_VERSION = 10301;
3665
+ XRAP_MASTER_KEY = "kqI1DTcwKX90ZtAy";
3666
+ SIGNATURE_XSCOMMON_TEMPLATE = {
3452
3667
  s0: 5,
3453
3668
  s1: "",
3454
3669
  x0: "1",
3455
- x1: "4.3.5",
3670
+ x1: "4.4.3",
3456
3671
  x2: "Windows",
3457
3672
  x3: "xhs-pc-web",
3458
- x4: "6.12.3",
3673
+ x4: "6.56.3",
3459
3674
  x5: "",
3460
3675
  x6: "",
3461
3676
  x7: "",
3462
3677
  x8: "",
3463
3678
  x9: -596800761,
3464
3679
  x10: 0,
3465
- x11: "normal"
3680
+ x11: "normal",
3681
+ get x12() {
3682
+ return `${Date.now()};${XHS_SESSION_START_MS}`;
3683
+ }
3684
+ };
3685
+ PUBLIC_USERAGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36 Edg/142.0.0.0";
3686
+ /**
3687
+ * Create a new config instance with overridden values
3688
+ */
3689
+ withOverrides(overrides) {
3690
+ const newConfig = new CryptoConfig();
3691
+ Object.assign(newConfig, overrides);
3692
+ return newConfig;
3693
+ }
3694
+ /**
3695
+ * 按 UA 派生出平台相关的那几个字段,返回新实例(不改动当前实例)。
3696
+ *
3697
+ * 会跟着 UA 变的只有三处,都经真机验证过是平台绑定的:
3698
+ *
3699
+ * - `SIGNATURE_XSCOMMON_TEMPLATE.s0` —— 复刻前端 `getPlatformCode`,
3700
+ * Windows 落 `other`(5)、Mac OS 是 3、Linux 是 4、iOS 1、Android 2
3701
+ * - `SIGNATURE_XSCOMMON_TEMPLATE.x2` 与 `SIGNATURE_DATA_TEMPLATE.x2` ——
3702
+ * 平台名字符串,认不出时用前端的兜底值 `'PC'`
3703
+ * - `DATA_PLATFORM` —— webprofile(gid)请求体里的平台名
3704
+ * - `PUBLIC_USERAGENT` —— b1 指纹直接把 UA 写进 `fingerprint.x1`,必须跟着一起换
3705
+ *
3706
+ * `XYW_ENV_FLAGS_DEFAULT` 刻意**不**跟着 UA 变:那 19 位里唯一观测到会变的是
3707
+ * webdriver 位,与 OS 无关,详见 `./platform.ts`。
3708
+ *
3709
+ * 与 {@link withOverrides} 可组合,后者优先:
3710
+ * ```ts
3711
+ * new CryptoConfig().forUserAgent(ua).withOverrides({ DATA_WEB_BUILD: '6.12.3' })
3712
+ * ```
3713
+ * @param userAgent - 实际发请求用的 UA;认不出平台时退回 `'PC'`
3714
+ * @returns 平台字段已就位的新配置实例
3715
+ */
3716
+ forUserAgent(userAgent) {
3717
+ const platform = platformFromUserAgent(userAgent);
3718
+ const platformName = platform ?? "PC";
3719
+ const field = (value) => ({
3720
+ value,
3721
+ enumerable: true,
3722
+ writable: true,
3723
+ configurable: true
3724
+ });
3725
+ const xsCommonTemplate = Object.defineProperties({}, {
3726
+ ...Object.getOwnPropertyDescriptors(this.SIGNATURE_XSCOMMON_TEMPLATE),
3727
+ s0: field(getPlatformCode(platform)),
3728
+ x2: field(platformName)
3729
+ });
3730
+ return this.withOverrides({
3731
+ ...this,
3732
+ ...userAgent ? { PUBLIC_USERAGENT: userAgent } : {},
3733
+ DATA_PLATFORM: platformName,
3734
+ SIGNATURE_DATA_TEMPLATE: {
3735
+ ...this.SIGNATURE_DATA_TEMPLATE,
3736
+ x2: platformName
3737
+ },
3738
+ SIGNATURE_XSCOMMON_TEMPLATE: xsCommonTemplate
3739
+ });
3740
+ }
3741
+ };
3742
+ var BitOperations = class {
3743
+ config;
3744
+ constructor(config) {
3745
+ this.config = config || new CryptoConfig();
3746
+ }
3747
+ normalizeTo32bit(value) {
3748
+ return value & this.config.MAX_32BIT;
3749
+ }
3750
+ toSigned32bit(unsignedValue) {
3751
+ if (unsignedValue > this.config.MAX_SIGNED_32BIT) return unsignedValue - 4294967296;
3752
+ return unsignedValue;
3753
+ }
3754
+ computeSeedValue(seed32bit) {
3755
+ const normalizedSeed = this.normalizeTo32bit(seed32bit);
3756
+ const shift15Bits = normalizedSeed >> 15;
3757
+ const shift13Bits = normalizedSeed >> 13;
3758
+ const shift12Bits = normalizedSeed >> 12;
3759
+ const shift10Bits = normalizedSeed >> 10;
3760
+ const shiftedResult = ((shift15Bits & ~shift13Bits | shift13Bits & ~shift15Bits) ^ shift12Bits ^ shift10Bits) << 31 & this.config.MAX_32BIT;
3761
+ return this.toSigned32bit(shiftedResult);
3762
+ }
3763
+ xorTransformArray(sourceIntegers) {
3764
+ const result = new Uint8Array(sourceIntegers.length);
3765
+ const keyBytes = Buffer.from(this.config.HEX_KEY, "hex");
3766
+ const keyLength = keyBytes.length;
3767
+ for (let i = 0; i < sourceIntegers.length; i++) if (i < keyLength) result[i] = (sourceIntegers[i] ^ keyBytes[i]) & 255;
3768
+ else result[i] = sourceIntegers[i] & 255;
3769
+ return result;
3770
+ }
3771
+ };
3772
+ var Base64Encoder = class {
3773
+ config;
3774
+ customEncodeTable;
3775
+ customDecodeTable;
3776
+ x3EncodeTable;
3777
+ x3DecodeTable;
3778
+ constructor(config) {
3779
+ this.config = config || new CryptoConfig();
3780
+ this.customEncodeTable = this.buildTranslationTable(this.config.STANDARD_BASE64_ALPHABET, this.config.CUSTOM_BASE64_ALPHABET);
3781
+ this.customDecodeTable = this.buildTranslationTable(this.config.CUSTOM_BASE64_ALPHABET, this.config.STANDARD_BASE64_ALPHABET);
3782
+ this.x3EncodeTable = this.buildTranslationTable(this.config.STANDARD_BASE64_ALPHABET, this.config.X3_BASE64_ALPHABET);
3783
+ this.x3DecodeTable = this.buildTranslationTable(this.config.X3_BASE64_ALPHABET, this.config.STANDARD_BASE64_ALPHABET);
3784
+ }
3785
+ buildTranslationTable(from, to) {
3786
+ const table = /* @__PURE__ */ new Map();
3787
+ for (let i = 0; i < from.length; i++) table.set(from[i], to[i]);
3788
+ return table;
3789
+ }
3790
+ translate(str, table) {
3791
+ let result = "";
3792
+ for (const char of str) result += table.get(char) ?? char;
3793
+ return result;
3794
+ }
3795
+ /**
3796
+ * Encode a string using custom Base64 alphabet
3797
+ */
3798
+ encode(dataToEncode) {
3799
+ const standardEncoded = Buffer.from(dataToEncode, "utf8").toString("base64");
3800
+ return this.translate(standardEncoded, this.customEncodeTable);
3801
+ }
3802
+ /**
3803
+ * Decode string using custom Base64 alphabet
3804
+ */
3805
+ decode(encodedString) {
3806
+ const standardEncoded = this.translate(encodedString, this.customDecodeTable);
3807
+ try {
3808
+ return Buffer.from(standardEncoded, "base64").toString("utf8");
3809
+ } catch {
3810
+ throw new Error("Invalid Base64 input: unable to decode string");
3811
+ }
3812
+ }
3813
+ /**
3814
+ * Decode x3 signature using X3_BASE64_ALPHABET
3815
+ */
3816
+ decodeX3(encodedString) {
3817
+ const standardEncoded = this.translate(encodedString, this.x3DecodeTable);
3818
+ try {
3819
+ return Buffer.from(standardEncoded, "base64");
3820
+ } catch {
3821
+ throw new Error("Invalid Base64 input: unable to decode string");
3822
+ }
3823
+ }
3824
+ /**
3825
+ * Encode x3 signature using X3_BASE64_ALPHABET
3826
+ */
3827
+ encodeX3(inputBytes) {
3828
+ const standardEncoded = Buffer.from(inputBytes).toString("base64");
3829
+ return this.translate(standardEncoded, this.x3EncodeTable);
3830
+ }
3831
+ };
3832
+ var HexProcessor = class {
3833
+ config;
3834
+ constructor() {
3835
+ this.config = new CryptoConfig();
3836
+ }
3837
+ hexStringToBytes(hexString) {
3838
+ const byteValues = [];
3839
+ for (let i = 0; i < hexString.length; i += this.config.HEX_CHUNK_SIZE) {
3840
+ const hexChunk = hexString.slice(i, i + this.config.HEX_CHUNK_SIZE);
3841
+ byteValues.push(parseInt(hexChunk, 16));
3842
+ }
3843
+ return byteValues;
3844
+ }
3845
+ processHexParameter(hexString, xorKey) {
3846
+ if (hexString.length !== this.config.EXPECTED_HEX_LENGTH) throw new Error(`hex parameter must be ${this.config.EXPECTED_HEX_LENGTH} characters`);
3847
+ return this.hexStringToBytes(hexString).map((byteVal) => byteVal ^ xorKey).slice(0, this.config.OUTPUT_BYTE_COUNT);
3848
+ }
3849
+ };
3850
+ var BASE36_CHARS = "0123456789abcdefghijklmnopqrstuvwxyz";
3851
+ var A1_CHARSET = "abcdefghijklmnopqrstuvwxyz1234567890";
3852
+ var STD_CRC32_TABLE = null;
3853
+ function stdCrc32(bytes) {
3854
+ if (STD_CRC32_TABLE === null) {
3855
+ const tbl = new Array(256);
3856
+ for (let n = 0; n < 256; n++) {
3857
+ let c = n;
3858
+ for (let k = 0; k < 8; k++) c = c & 1 ? c >>> 1 ^ 3988292384 : c >>> 1;
3859
+ tbl[n] = c >>> 0;
3860
+ }
3861
+ STD_CRC32_TABLE = tbl;
3862
+ }
3863
+ let crc = 4294967295;
3864
+ for (let i = 0; i < bytes.length; i++) crc = (STD_CRC32_TABLE[(crc ^ bytes[i]) & 255] ^ crc >>> 8) >>> 0;
3865
+ return (crc ^ 4294967295) >>> 0;
3866
+ }
3867
+ /**
3868
+ * Convert a non-negative BigInt to a base36 string.
3869
+ */
3870
+ function intToBase36(value) {
3871
+ if (value === 0n) return "0";
3872
+ let result = "";
3873
+ const base = 36n;
3874
+ while (value > 0n) {
3875
+ const remainder = value % base;
3876
+ value = value / base;
3877
+ result = BASE36_CHARS[Number(remainder)] + result;
3878
+ }
3879
+ return result;
3880
+ }
3881
+ var RandomGenerator = class {
3882
+ config;
3883
+ constructor() {
3884
+ this.config = new CryptoConfig();
3885
+ }
3886
+ /**
3887
+ * Generate random byte array
3888
+ */
3889
+ generateRandomBytes(byteCount) {
3890
+ return Array.from({ length: byteCount }, () => Math.floor(Math.random() * (this.config.MAX_BYTE + 1)));
3891
+ }
3892
+ /**
3893
+ * Generate random integer in range
3894
+ */
3895
+ generateRandomByteInRange(minVal, maxVal) {
3896
+ return Math.floor(Math.random() * (maxVal - minVal + 1)) + minVal;
3897
+ }
3898
+ /**
3899
+ * Generate 32-bit random integer
3900
+ */
3901
+ generateRandomInt() {
3902
+ return Math.floor(Math.random() * (this.config.MAX_32BIT + 1));
3903
+ }
3904
+ /**
3905
+ * Generate x-b3-traceid (16 random hex characters)
3906
+ */
3907
+ generateB3TraceId() {
3908
+ let result = "";
3909
+ for (let i = 0; i < this.config.B3_TRACE_ID_LENGTH; i++) result += this.config.HEX_CHARS[Math.floor(Math.random() * this.config.HEX_CHARS.length)];
3910
+ return result;
3911
+ }
3912
+ /**
3913
+ * Generate x-xray-traceid (32 characters: 16 timestamp+seq + 16 random)
3914
+ */
3915
+ generateXrayTraceId(timestamp, seq) {
3916
+ if (timestamp === void 0) timestamp = Date.now();
3917
+ if (seq === void 0) seq = Math.floor(Math.random() * (this.config.XRAY_TRACE_ID_SEQ_MAX + 1));
3918
+ const part1 = (BigInt(timestamp) << BigInt(this.config.XRAY_TRACE_ID_TIMESTAMP_SHIFT) | BigInt(seq)).toString(16).padStart(this.config.XRAY_TRACE_ID_PART1_LENGTH, "0");
3919
+ let part2 = "";
3920
+ for (let i = 0; i < this.config.XRAY_TRACE_ID_PART2_LENGTH; i++) part2 += this.config.HEX_CHARS[Math.floor(Math.random() * this.config.HEX_CHARS.length)];
3921
+ return part1 + part2;
3922
+ }
3923
+ /**
3924
+ * Generate a random ASCII string of the given length
3925
+ */
3926
+ static generateRandomAscii(length, charset = "abcdefghijklmnopqrstuvwxyz0123456789") {
3927
+ let result = "";
3928
+ for (let i = 0; i < length; i++) result += charset[Math.floor(Math.random() * charset.length)];
3929
+ return result;
3930
+ }
3931
+ /**
3932
+ * Generate an a1 cookie value (52 characters)
3933
+ */
3934
+ static generateA1() {
3935
+ const tsHex = Math.floor(Date.now()).toString(16);
3936
+ let randomStr = "";
3937
+ for (let i = 0; i < 30; i++) randomStr += A1_CHARSET[Math.floor(Math.random() * 36)];
3938
+ const aPart = tsHex + randomStr + "50000";
3939
+ const crc = stdCrc32(Buffer.from(aPart)) >>> 0;
3940
+ return (aPart + String(crc)).slice(0, 52);
3941
+ }
3942
+ /**
3943
+ * Generate web_id (32-character hex MD5) from an a1 cookie value
3944
+ */
3945
+ static generateWebId(a1) {
3946
+ return createHash("md5").update(a1).digest("hex");
3947
+ }
3948
+ /**
3949
+ * Generate search_id for search endpoints (base36 of (timestamp_ms << 64) + random)
3950
+ */
3951
+ generateSearchId() {
3952
+ const timestampMs = BigInt(Date.now());
3953
+ const randomPart = BigInt(Math.ceil(2147483646 * Math.random()));
3954
+ return intToBase36((timestampMs << 64n) + randomPart);
3955
+ }
3956
+ /**
3957
+ * Generate search request_id for search endpoints. Format: "{random}-{timestamp_ms}"
3958
+ */
3959
+ generateSearchRequestId() {
3960
+ const timestampMs = Date.now();
3961
+ return `${Math.ceil(2147483646 * Math.random())}-${timestampMs}`;
3962
+ }
3963
+ };
3964
+ /**
3965
+ * Extract URI path from full URL (removes protocol, host, query, fragment)
3966
+ */
3967
+ function extractUri(url) {
3968
+ if (!url || typeof url !== "string") throw new Error("URL must be a non-empty string");
3969
+ url = url.trim();
3970
+ try {
3971
+ if (url.startsWith("http://") || url.startsWith("https://")) {
3972
+ const path = new URL(url).pathname;
3973
+ if (!path || path === "/") throw new Error(`Cannot extract valid URI path from URL: ${url}`);
3974
+ return path;
3975
+ }
3976
+ const queryIndex = url.indexOf("?");
3977
+ const path = queryIndex >= 0 ? url.substring(0, queryIndex) : url;
3978
+ if (!path || path === "/") throw new Error(`Cannot extract valid URI path from URL: ${url}`);
3979
+ return path;
3980
+ } catch (e) {
3981
+ if (e instanceof Error && e.message.includes("Cannot extract")) throw e;
3982
+ throw new Error(`Cannot extract valid URI path from URL: ${url}`);
3983
+ }
3984
+ }
3985
+ /**
3986
+ * Build complete URL with query parameters (handles parameter escaping)
3987
+ *
3988
+ * IMPORTANT: This function uses XHS platform-specific encoding rules.
3989
+ * Only '=' characters are encoded as '%3D'. Other special characters
3990
+ * (including ',') are NOT encoded, as required by XHS signature algorithm.
3991
+ */
3992
+ function buildUrl(baseUrl, params) {
3993
+ if (!baseUrl || typeof baseUrl !== "string") throw new Error("baseUrl must be a non-empty string");
3994
+ if (!params) return baseUrl;
3995
+ const queryParts = [];
3996
+ for (const [key, value] of Object.entries(params)) {
3997
+ let formattedValue;
3998
+ if (Array.isArray(value)) formattedValue = value.map((v) => String(v)).join(",");
3999
+ else if (value !== null && value !== void 0) formattedValue = String(value);
4000
+ else formattedValue = "";
4001
+ const encodedValue = formattedValue.replace(/=/g, "%3D");
4002
+ queryParts.push(`${key}=${encodedValue}`);
4003
+ }
4004
+ const queryString = queryParts.join("&");
4005
+ let separator;
4006
+ if (!baseUrl.includes("?")) separator = "?";
4007
+ else if (baseUrl.endsWith("?") || baseUrl.endsWith("&")) separator = "";
4008
+ else separator = "&";
4009
+ return `${baseUrl}${separator}${queryString}`;
4010
+ }
4011
+ /**
4012
+ * Pure TypeScript xxHash32 implementation.
4013
+ *
4014
+ * Ported from the upstream Python `utils/hash.py`. Used by the x-rap-param
4015
+ * generator to compute integrity hashes over the request body and envelope.
4016
+ */
4017
+ var MASK_32 = 4294967295;
4018
+ /**
4019
+ * 32-bit left rotation (unsigned)
4020
+ */
4021
+ function rotl32$1(val, shift) {
4022
+ val = val >>> 0;
4023
+ return (val << shift | val >>> 32 - shift) >>> 0;
4024
+ }
4025
+ /**
4026
+ * Compute the xxHash32 digest of the given bytes.
4027
+ *
4028
+ * @param data - Input bytes
4029
+ * @param seed - Optional seed (default 0)
4030
+ * @returns Unsigned 32-bit hash value
4031
+ */
4032
+ function xxh32(data, seed = 0) {
4033
+ const buf = data;
4034
+ const length = buf.length;
4035
+ let pos = 0;
4036
+ const prime1 = 2654435761;
4037
+ const prime2 = 2246822519;
4038
+ const prime3 = 3266489917;
4039
+ const prime4 = 668265263;
4040
+ const prime5 = 374761393;
4041
+ let digest;
4042
+ if (length >= 16) {
4043
+ let acc1 = seed + prime1 + prime2 >>> 0;
4044
+ let acc2 = seed + prime2 >>> 0;
4045
+ let acc3 = seed >>> 0;
4046
+ let acc4 = seed - prime1 >>> 0;
4047
+ const end = length - 16;
4048
+ while (pos <= end) for (let accRef = 0; accRef < 4; accRef++) {
4049
+ const word = readU32LE(buf, pos);
4050
+ pos += 4;
4051
+ if (accRef === 0) acc1 = Math.imul(rotl32$1(acc1 + Math.imul(word, prime2) >>> 0, 13), prime1) >>> 0;
4052
+ else if (accRef === 1) acc2 = Math.imul(rotl32$1(acc2 + Math.imul(word, prime2) >>> 0, 13), prime1) >>> 0;
4053
+ else if (accRef === 2) acc3 = Math.imul(rotl32$1(acc3 + Math.imul(word, prime2) >>> 0, 13), prime1) >>> 0;
4054
+ else acc4 = Math.imul(rotl32$1(acc4 + Math.imul(word, prime2) >>> 0, 13), prime1) >>> 0;
4055
+ }
4056
+ digest = rotl32$1(acc1, 1) + rotl32$1(acc2, 7) + rotl32$1(acc3, 12) + rotl32$1(acc4, 18) >>> 0;
4057
+ } else digest = seed + prime5 >>> 0;
4058
+ digest = digest + length >>> 0;
4059
+ while (pos + 4 <= length) {
4060
+ const word = readU32LE(buf, pos);
4061
+ pos += 4;
4062
+ digest = Math.imul(rotl32$1(digest + Math.imul(word, prime3) >>> 0, 17), prime4) >>> 0;
4063
+ }
4064
+ while (pos < length) {
4065
+ digest = Math.imul(rotl32$1(digest + Math.imul(buf[pos], prime5) >>> 0, 11), prime1) >>> 0;
4066
+ pos += 1;
4067
+ }
4068
+ digest ^= digest >>> 15;
4069
+ digest = Math.imul(digest, prime2) >>> 0;
4070
+ digest ^= digest >>> 13;
4071
+ digest = Math.imul(digest, prime3) >>> 0;
4072
+ digest ^= digest >>> 16;
4073
+ return (digest & MASK_32) >>> 0;
4074
+ }
4075
+ /**
4076
+ * Read a 32-bit little-endian unsigned integer from a byte buffer.
4077
+ */
4078
+ function readU32LE(buf, off) {
4079
+ return (buf[off] | buf[off + 1] << 8 | buf[off + 2] << 16 | buf[off + 3] << 24) >>> 0;
4080
+ }
4081
+ /**
4082
+ * Sharding key generation for the `xy-direction` request header.
4083
+ *
4084
+ * Ported from the upstream Python `utils/sharding.py`. When a `user_id` is
4085
+ * provided, the key is derived deterministically via a MurmurHash3-x86-32
4086
+ * variant; otherwise a random value in [10, 100] is returned.
4087
+ */
4088
+ var C1 = 3432918353;
4089
+ var C2 = 461845907;
4090
+ /**
4091
+ * 32-bit left rotation (unsigned)
4092
+ */
4093
+ function rotl32(x, r) {
4094
+ x = x >>> 0;
4095
+ return (x << r | x >>> 32 - r) >>> 0;
4096
+ }
4097
+ /**
4098
+ * Compute the sharding key for the `xy-direction` header.
4099
+ *
4100
+ * @param userId - Optional user id. If omitted, a random value in [10, 100].
4101
+ * @returns Sharding key
4102
+ */
4103
+ function getShardingKey(userId) {
4104
+ if (userId === null || userId === void 0) return Math.floor(Math.random() * 91) + 10;
4105
+ const data = Buffer.from(userId, "utf-8");
4106
+ const length = data.length;
4107
+ let r = 151488;
4108
+ const nblocks = Math.floor(length / 4);
4109
+ for (let o = 0; o < nblocks; o++) {
4110
+ const i = 4 * o;
4111
+ let u = (data[i] | data[i + 1] << 8 | data[i + 2] << 16 | data[i + 3] << 24) >>> 0;
4112
+ u = Math.imul(u, C1) >>> 0;
4113
+ u = rotl32(u, 15);
4114
+ u = Math.imul(u, C2) >>> 0;
4115
+ r = (r ^ u) >>> 0;
4116
+ r = rotl32(r, 13);
4117
+ r = Math.imul(r, 5) + 3864292196 >>> 0;
4118
+ }
4119
+ const s = 4 * nblocks;
4120
+ let c = 0;
4121
+ const rem = length % 4;
4122
+ if (rem >= 3) c ^= data[s + 2] << 16;
4123
+ if (rem >= 2) c ^= data[s + 1] << 8;
4124
+ if (rem >= 1) {
4125
+ c ^= data[s];
4126
+ c = Math.imul(c, C1) >>> 0;
4127
+ c = rotl32(c, 15);
4128
+ c = Math.imul(c, C2) >>> 0;
4129
+ r = (r ^ c) >>> 0;
4130
+ }
4131
+ r = (r ^ length) >>> 0;
4132
+ r ^= r >>> 16;
4133
+ r = Math.imul(r, 2246822507) >>> 0;
4134
+ r ^= r >>> 13;
4135
+ r = Math.imul(r, 3266489909) >>> 0;
4136
+ r ^= r >>> 16;
4137
+ r = r >>> 0;
4138
+ return r % 100 + 1;
4139
+ }
4140
+ var CryptoProcessor = class {
4141
+ config;
4142
+ bitOps;
4143
+ b64encoder;
4144
+ hexProcessor;
4145
+ randomGen;
4146
+ constructor(config) {
4147
+ this.config = config || new CryptoConfig();
4148
+ this.bitOps = new BitOperations(this.config);
4149
+ this.b64encoder = new Base64Encoder(this.config);
4150
+ this.hexProcessor = new HexProcessor();
4151
+ this.randomGen = new RandomGenerator();
4152
+ }
4153
+ intToLeBytes(val, length = 4) {
4154
+ const arr = [];
4155
+ for (let i = 0; i < length; i++) {
4156
+ arr.push(val & 255);
4157
+ val = Math.floor(val / 256);
4158
+ }
4159
+ return arr;
4160
+ }
4161
+ /**
4162
+ * 32-bit left rotation
4163
+ */
4164
+ rotateLeft(val, n) {
4165
+ return (val << n | val >>> 32 - n) >>> 0;
4166
+ }
4167
+ /**
4168
+ * Custom hash function for a3 field generation
4169
+ * Input: byte array (must be multiple of 8)
4170
+ * Output: 16-byte array
4171
+ */
4172
+ customHashV2(inputBytes) {
4173
+ let [s0, s1, s2, s3] = this.config.HASH_IV;
4174
+ const length = inputBytes.length;
4175
+ s0 ^= length;
4176
+ s1 ^= length << 8;
4177
+ s2 ^= length << 16;
4178
+ s3 ^= length << 24;
4179
+ for (let i = 0; i < Math.floor(length / 8); i++) {
4180
+ const offset = i * 8;
4181
+ const v0 = (inputBytes[offset] | inputBytes[offset + 1] << 8 | inputBytes[offset + 2] << 16 | inputBytes[offset + 3] << 24) >>> 0;
4182
+ const v1 = (inputBytes[offset + 4] | inputBytes[offset + 5] << 8 | inputBytes[offset + 6] << 16 | inputBytes[offset + 7] << 24) >>> 0;
4183
+ s0 = this.rotateLeft(s0 + v0 & this.config.MAX_32BIT ^ s2, 7);
4184
+ s1 = this.rotateLeft((v0 ^ s1) + s3 & this.config.MAX_32BIT, 11);
4185
+ s2 = this.rotateLeft(s2 + v1 & this.config.MAX_32BIT ^ s0, 13);
4186
+ s3 = this.rotateLeft((s3 ^ v1) + s1 & this.config.MAX_32BIT, 17);
4187
+ }
4188
+ const t0 = s0 ^ length;
4189
+ const t1 = s1 ^ t0;
4190
+ const t2 = s2 + t1 & this.config.MAX_32BIT;
4191
+ const t3 = s3 ^ t2;
4192
+ const rotT0 = this.rotateLeft(t0, 9);
4193
+ const rotT1 = this.rotateLeft(t1, 13);
4194
+ const rotT2 = this.rotateLeft(t2, 17);
4195
+ const rotT3 = this.rotateLeft(t3, 19);
4196
+ s0 = rotT0 + rotT2 & this.config.MAX_32BIT;
4197
+ s1 = rotT1 ^ rotT3;
4198
+ s2 = rotT2 + s0 & this.config.MAX_32BIT;
4199
+ s3 = rotT3 ^ s1;
4200
+ const result = [];
4201
+ for (const s of [
4202
+ s0,
4203
+ s1,
4204
+ s2,
4205
+ s3
4206
+ ]) result.push(...this.intToLeBytes(s, 4));
4207
+ return result;
4208
+ }
4209
+ /**
4210
+ * Build 144-byte payload array (mns0301 version)
4211
+ *
4212
+ * @param hexParameter - 32-char hex (MD5 of uri+data), used for the MD5 XOR segment
4213
+ * @param hexMd5Path - 32-char hex path hash (GET: MD5 of uri+params, POST: MD5 of uri)
4214
+ * @param a1Value - a1 value from cookies
4215
+ * @param appIdentifier - Application identifier, default "xhs-pc-web"
4216
+ * @param stringParam - String parameter (URI+data, used for length calculation)
4217
+ * @param timestamp - Unix timestamp in seconds (defaults to current time)
4218
+ * @param signState - Optional session state
4219
+ */
4220
+ buildPayloadArray(hexParameter, hexMd5Path, a1Value, appIdentifier = "xhs-pc-web", stringParam = "", timestamp, signState) {
4221
+ if (timestamp === void 0) timestamp = Date.now() / 1e3;
4222
+ const seed = this.randomGen.generateRandomInt();
4223
+ const seedByte = seed & 255;
4224
+ const payload = [];
4225
+ payload.push(...this.config.VERSION_BYTES);
4226
+ payload.push(...this.intToLeBytes(seed, 4));
4227
+ const tsBytes = this.intToLeBytes(Math.floor(timestamp * 1e3), this.config.TIMESTAMP_LE_LENGTH);
4228
+ payload.push(...tsBytes);
4229
+ if (signState) {
4230
+ payload.push(...this.intToLeBytes(signState.pageLoadTimestamp, this.config.TIMESTAMP_LE_LENGTH));
4231
+ payload.push(...this.intToLeBytes(signState.sequenceValue, 4));
4232
+ payload.push(...this.intToLeBytes(signState.windowPropsLength, 4));
4233
+ payload.push(...this.intToLeBytes(signState.uriLength, 4));
4234
+ } else {
4235
+ const timeOffset = this.randomGen.generateRandomByteInRange(this.config.ENV_FINGERPRINT_TIME_OFFSET_MIN, this.config.ENV_FINGERPRINT_TIME_OFFSET_MAX);
4236
+ const effectiveTsMs = Math.floor((timestamp - timeOffset) * 1e3);
4237
+ payload.push(...this.intToLeBytes(effectiveTsMs, this.config.TIMESTAMP_LE_LENGTH));
4238
+ const sequenceValue = this.randomGen.generateRandomByteInRange(this.config.SEQUENCE_VALUE_MIN, this.config.SEQUENCE_VALUE_MAX);
4239
+ payload.push(...this.intToLeBytes(sequenceValue, 4));
4240
+ const windowPropsLength = this.randomGen.generateRandomByteInRange(this.config.WINDOW_PROPS_LENGTH_MIN, this.config.WINDOW_PROPS_LENGTH_MAX);
4241
+ payload.push(...this.intToLeBytes(windowPropsLength, 4));
4242
+ const uriLength = Buffer.from(stringParam, "utf-8").length;
4243
+ payload.push(...this.intToLeBytes(uriLength, 4));
4244
+ }
4245
+ const md5Bytes = Buffer.from(hexParameter, "hex");
4246
+ for (let i = 0; i < this.config.MD5_XOR_LENGTH; i++) payload.push(md5Bytes[i] ^ seedByte);
4247
+ const a1Bytes = Buffer.from(a1Value, "utf-8").subarray(0, this.config.A1_LENGTH);
4248
+ const a1Padded = Buffer.alloc(this.config.A1_LENGTH);
4249
+ a1Bytes.copy(a1Padded);
4250
+ payload.push(a1Padded.length);
4251
+ payload.push(...Array.from(a1Padded));
4252
+ const appBytes = Buffer.from(appIdentifier, "utf-8").subarray(0, this.config.APP_ID_LENGTH);
4253
+ const appPadded = Buffer.alloc(this.config.APP_ID_LENGTH);
4254
+ appBytes.copy(appPadded);
4255
+ payload.push(appPadded.length);
4256
+ payload.push(...Array.from(appPadded));
4257
+ const part11 = [1, seedByte ^ this.config.ENV_TABLE[0]];
4258
+ for (let i = 1; i < 15; i++) part11.push(this.config.ENV_TABLE[i] ^ this.config.ENV_CHECKS_DEFAULT[i]);
4259
+ payload.push(...part11);
4260
+ const md5PathBytes = [];
4261
+ for (let i = 0; i < 32; i += 2) md5PathBytes.push(parseInt(hexMd5Path.substring(i, i + 2), 16));
4262
+ const a3Hash = this.customHashV2([...tsBytes, ...md5PathBytes]);
4263
+ const a3Field = [...this.config.A3_PREFIX, ...a3Hash.map((b) => b ^ seedByte)];
4264
+ payload.push(...a3Field);
4265
+ return payload;
4266
+ }
4267
+ };
4268
+ /**
4269
+ * Browser fingerprint data constants
4270
+ */
4271
+ var GPU_VENDORS = [
4272
+ "Google Inc. (Intel)|ANGLE (Intel, Intel(R) HD Graphics 400 (0x00000166) Direct3D11 vs_5_0 ps_5_0, D3D11)",
4273
+ "Google Inc. (Intel)|ANGLE (Intel, Intel(R) HD Graphics 4400 (0x00001112) Direct3D11 vs_5_0 ps_5_0, D3D11)",
4274
+ "Google Inc. (Intel)|ANGLE (Intel, Intel(R) HD Graphics 4600 (0x00000412) Direct3D11 vs_5_0 ps_5_0, D3D11)",
4275
+ "Google Inc. (Intel)|ANGLE (Intel, Intel(R) HD Graphics 520 (0x1912) Direct3D11 vs_5_0 ps_5_0, D3D11)",
4276
+ "Google Inc. (Intel)|ANGLE (Intel, Intel(R) HD Graphics 530 (0x00001912) Direct3D11 vs_5_0 ps_5_0, D3D11)",
4277
+ "Google Inc. (Intel)|ANGLE (Intel, Intel(R) HD Graphics 550 (0x00001512) Direct3D11 vs_5_0 ps_5_0, D3D11)",
4278
+ "Google Inc. (Intel)|ANGLE (Intel, Intel(R) HD Graphics 6000 (0x1606) Direct3D11 vs_5_0 ps_5_0, D3D11)",
4279
+ "Google Inc. (Intel)|ANGLE (Intel, Intel(R) Iris(TM) Graphics 540 (0x1912) Direct3D11 vs_5_0 ps_5_0, D3D11)",
4280
+ "Google Inc. (Intel)|ANGLE (Intel, Intel(R) Iris(TM) Graphics 550 (0x1913) Direct3D11 vs_5_0 ps_5_0, D3D11)",
4281
+ "Google Inc. (Intel)|ANGLE (Intel, Intel(R) Iris(TM) Plus Graphics 640 (0x161C) Direct3D11 vs_5_0 ps_5_0, D3D11)",
4282
+ "Google Inc. (Intel)|ANGLE (Intel, Intel(R) UHD Graphics 600 (0x3E80) Direct3D11 vs_5_0 ps_5_0, D3D11)",
4283
+ "Google Inc. (Intel)|ANGLE (Intel, Intel(R) UHD Graphics 620 (0x00003EA0) Direct3D11 vs_5_0 ps_5_0, D3D11)",
4284
+ "Google Inc. (Intel)|ANGLE (Intel, Intel(R) UHD Graphics 630 (0x00003E9B) Direct3D11 vs_5_0 ps_5_0, D3D11)",
4285
+ "Google Inc. (Intel)|ANGLE (Intel, Intel(R) UHD Graphics 655 (0x00009BC8) Direct3D11 vs_5_0 ps_5_0, D3D11)",
4286
+ "Google Inc. (Intel)|ANGLE (Intel, Intel(R) Iris(R) Xe Graphics (0x000046A8) Direct3D11 vs_5_0 ps_5_0, D3D11)",
4287
+ "Google Inc. (Intel)|ANGLE (Intel, Intel(R) Iris(R) Xe Graphics (0x00009A49) Direct3D11 vs_5_0 ps_5_0, D3D11)",
4288
+ "Google Inc. (Intel)|ANGLE (Intel, Intel(R) Iris(R) Xe MAX Graphics (0x00009BC0) Direct3D11 vs_5_0 ps_5_0, D3D11)",
4289
+ "Google Inc. (Intel)|ANGLE (Intel, Intel Arc A370M (0x0000AF51) Direct3D11 vs_5_0 ps_5_0, D3D11)",
4290
+ "Google Inc. (Intel)|ANGLE (Intel, Intel Arc A380 (0x0000AF41) Direct3D11 vs_5_0 ps_5_0, D3D11)",
4291
+ "Google Inc. (Intel)|ANGLE (Intel, Intel Arc A380M (0x0000AF5E) Direct3D11 vs_5_0 ps_5_0, D3D11)",
4292
+ "Google Inc. (Intel)|ANGLE (Intel, Intel Arc A550 (0x0000AF42) Direct3D11 vs_5_0 ps_5_0, D3D11)",
4293
+ "Google Inc. (Intel)|ANGLE (Intel, Intel Arc A770 (0x0000AF43) Direct3D11 vs_5_0 ps_5_0, D3D11)",
4294
+ "Google Inc. (Intel)|ANGLE (Intel, Intel Arc A770M (0x0000AF50) Direct3D11 vs_5_0 ps_5_0, D3D11)",
4295
+ "Google Inc. (Intel)|ANGLE (Intel, Mesa Intel(R) Graphics (RPL‑P GT1) (0x0000A702) OpenGL 4.6)",
4296
+ "Google Inc. (Intel)|ANGLE (Intel, Mesa Intel(R) UHD Graphics 770 (0x00004680) OpenGL 4.6)",
4297
+ "Google Inc. (Intel)|ANGLE (Intel, Mesa Intel(R) HD Graphics 4400 (0x00001122) OpenGL 4.6)",
4298
+ "Google Inc. (Intel)|ANGLE (Intel, Mesa Intel(R) Graphics (ADL‑S GT1) (0x0000A0A1) OpenGL 4.6)",
4299
+ "Google Inc. (Intel)|ANGLE (Intel, Mesa Intel(R) Graphics (RKL GT1) (0x0000A9A1) OpenGL 4.6)",
4300
+ "Google Inc. (Intel)|ANGLE (Intel, Mesa Intel(R) UHD Graphics (CML GT2) (0x00009A14) OpenGL 4.6)",
4301
+ "Google Inc. (Intel)|ANGLE (Intel, Intel(R) HD Graphics 3000 (0x00001022) Direct3D9Ex vs_3_0 ps_3_0, igdumd64.dll)",
4302
+ "Google Inc. (Intel)|ANGLE (Intel, Intel(R) HD Graphics Family (0x00000A16) Direct3D11 vs_5_0 ps_5_0, D3D11)",
4303
+ "Google Inc. (Intel)|ANGLE (Intel, Intel(R) Iris Pro OpenGL Engine, OpenGL 4.1)",
4304
+ "Google Inc. (Intel)|ANGLE (Intel, Intel(R) Iris(TM) Plus Graphics 645 (0x1616) Direct3D11 vs_5_0 ps_5_0, D3D11)",
4305
+ "Google Inc. (Intel)|ANGLE (Intel, Intel(R) Iris(TM) Plus Graphics 655 (0x161E) Direct3D11 vs_5_0 ps_5_0, D3D11)",
4306
+ "Google Inc. (Intel)|ANGLE (Intel, Intel(R) UHD Graphics 730 (0x0000A100) Direct3D11 vs_5_0 ps_5_0, D3D11)",
4307
+ "Google Inc. (Intel)|ANGLE (Intel, Intel(R) UHD Graphics 805 (0x0000B0A0) Direct3D11 vs_5_0 ps_5_0, D3D11)",
4308
+ "Google Inc. (AMD)|ANGLE (AMD, AMD Radeon Vega 3 Graphics (0x000015E0) Direct3D11 vs_5_0 ps_5_0, D3D11)",
4309
+ "Google Inc. (AMD)|ANGLE (AMD, AMD Radeon Vega 8 Graphics (0x000015D8) Direct3D11 vs_5_0 ps_5_0, D3D11)",
4310
+ "Google Inc. (AMD)|ANGLE (AMD, AMD Radeon Vega 11 Graphics (0x000015DD) Direct3D11 vs_5_0 ps_5_0, D3D11)",
4311
+ "Google Inc. (AMD)|ANGLE (AMD, AMD Radeon Graphics (0x00001636) Direct3D11 vs_5_0 ps_5_0, D3D11)",
4312
+ "Google Inc. (AMD)|ANGLE (AMD, AMD Radeon RX 5500 XT Direct3D11 vs_5_0 ps_5_0, D3D11)",
4313
+ "Google Inc. (AMD)|ANGLE (AMD, AMD Radeon RX 560 (0x000067EF) Direct3D11 vs_5_0 ps_5_0, D3D11)",
4314
+ "Google Inc. (AMD)|ANGLE (AMD, AMD Radeon RX 570 (0x000067DF) Direct3D11 vs_5_0 ps_5_0, D3D11)",
4315
+ "Google Inc. (AMD)|ANGLE (AMD, AMD Radeon RX 580 2048SP (0x00006FDF) Direct3D11 vs_5_0 ps_5_0, D3D11)",
4316
+ "Google Inc. (AMD)|ANGLE (AMD, AMD Radeon RX 590 (0x000067FF) Direct3D11 vs_5_0 ps_5_0, D3D11)",
4317
+ "Google Inc. (AMD)|ANGLE (AMD, AMD Radeon RX 6600 (0x000073FF) Direct3D11 vs_5_0 ps_5_0, D3D11)",
4318
+ "Google Inc. (AMD)|ANGLE (AMD, AMD Radeon RX 6600 XT (0x000073FF) Direct3D11 vs_5_0 ps_5_0, D3D11)",
4319
+ "Google Inc. (AMD)|ANGLE (AMD, AMD Radeon RX 6650 XT Direct3D11 vs_5_0 ps_5_0, D3D11)",
4320
+ "Google Inc. (AMD)|ANGLE (AMD, AMD Radeon RX 6700 XT (0x000073DF) Direct3D11 vs_5_0 ps_5_0, D3D11)",
4321
+ "Google Inc. (AMD)|ANGLE (AMD, AMD Radeon RX 6800 (0x000073BF) Direct3D11 vs_5_0 ps_5_0, D3D11)",
4322
+ "Google Inc. (AMD)|ANGLE (AMD, AMD Radeon RX 6900 XT (0x000073C2) Direct3D11 vs_5_0 ps_5_0, D3D11)",
4323
+ "Google Inc. (AMD)|ANGLE (AMD, AMD Radeon RX 7700 XT Direct3D11 vs_5_0 ps_5_0, D3D11)",
4324
+ "Google Inc. (AMD)|ANGLE (AMD, AMD Radeon Pro 5300M OpenGL Engine, OpenGL 4.1)",
4325
+ "Google Inc. (AMD)|ANGLE (AMD, AMD Radeon Pro 5500 XT OpenGL Engine, OpenGL 4.1)",
4326
+ "Google Inc. (AMD)|ANGLE (AMD, AMD Radeon R7 370 Series (0x00006811) Direct3D11 vs_5_0 ps_5_0, D3D11)",
4327
+ "Google Inc. (AMD)|ANGLE (AMD, ATI Technologies Inc. AMD Radeon RX Vega 64 OpenGL Engine, OpenGL 4.1)",
4328
+ "Google Inc. (NVIDIA)|ANGLE (NVIDIA, NVIDIA GeForce GTX 1050 (0x00001C81) Direct3D11 vs_5_0 ps_5_0, D3D11)",
4329
+ "Google Inc. (NVIDIA)|ANGLE (NVIDIA, NVIDIA GeForce GTX 1050 Ti (0x00001C8C) Direct3D11 vs_5_0 ps_5_0, D3D11)",
4330
+ "Google Inc. (NVIDIA)|ANGLE (NVIDIA, NVIDIA GeForce GTX 1060 6GB (0x000010DE) Direct3D11 vs_5_0 ps_5_0, D3D11)",
4331
+ "Google Inc. (NVIDIA)|ANGLE (NVIDIA, NVIDIA GeForce GTX 1070 (0x00001B81) Direct3D11 vs_5_0 ps_5_0, D3D11)",
4332
+ "Google Inc. (NVIDIA)|ANGLE (NVIDIA, NVIDIA GeForce GTX 1080 (0x00001B80) Direct3D11 vs_5_0 ps_5_0, D3D11)",
4333
+ "Google Inc. (NVIDIA)|ANGLE (NVIDIA, NVIDIA GeForce RTX 2060 (0x00001F06) Direct3D11 vs_5_0 ps_5_0, D3D11)",
4334
+ "Google Inc. (NVIDIA)|ANGLE (NVIDIA, NVIDIA GeForce RTX 2060 SUPER (0x00001F06) Direct3D11 vs_5_0 ps_5_0, D3D11)",
4335
+ "Google Inc. (NVIDIA)|ANGLE (NVIDIA, NVIDIA GeForce RTX 2070 (0x00001F10) Direct3D11 vs_5_0 ps_5_0, D3D11)",
4336
+ "Google Inc. (NVIDIA)|ANGLE (NVIDIA, NVIDIA GeForce RTX 2070 SUPER (0x00001F10) Direct3D11 vs_5_0 ps_5_0, D3D11)",
4337
+ "Google Inc. (NVIDIA)|ANGLE (NVIDIA, NVIDIA GeForce RTX 3060 (0x0000250F) Direct3D11 vs_5_0 ps_5_0, D3D11)",
4338
+ "Google Inc. (NVIDIA)|ANGLE (NVIDIA, NVIDIA GeForce RTX 3060 Ti (0x00002489) Direct3D11 vs_5_0 ps_5_0, D3D11)",
4339
+ "Google Inc. (NVIDIA)|ANGLE (NVIDIA, NVIDIA GeForce RTX 3070 (0x00002488) Direct3D11 vs_5_0 ps_5_0, D3D11)",
4340
+ "Google Inc. (NVIDIA)|ANGLE (NVIDIA, NVIDIA GeForce RTX 3070 Ti (0x000028A5) Direct3D11 vs_5_0 ps_5_0, D3D11)",
4341
+ "Google Inc. (NVIDIA)|ANGLE (NVIDIA, NVIDIA GeForce RTX 3080 (0x00002206) Direct3D11 vs_5_0 ps_5_0, D3D11)",
4342
+ "Google Inc. (NVIDIA)|ANGLE (NVIDIA, NVIDIA GeForce RTX 3080 Ti (0x00002208) Direct3D11 vs_5_0 ps_5_0, D3D11)",
4343
+ "Google Inc. (NVIDIA)|ANGLE (NVIDIA, NVIDIA GeForce RTX 3090 (0x00002204) Direct3D11 vs_5_0 ps_5_0, D3D11)",
4344
+ "Google Inc. (NVIDIA)|ANGLE (NVIDIA, NVIDIA GeForce RTX 4060 (0x00002882) Direct3D11 vs_5_0 ps_5_0, D3D11)",
4345
+ "Google Inc. (NVIDIA)|ANGLE (NVIDIA, NVIDIA GeForce RTX 4060 Ti (0x00002803) Direct3D11 vs_5_0 ps_5_0, D3D11)",
4346
+ "Google Inc. (NVIDIA)|ANGLE (NVIDIA, NVIDIA GeForce RTX 4070 (0x00002786) Direct3D11 vs_5_0 ps_5_0, D3D11)",
4347
+ "Google Inc. (NVIDIA)|ANGLE (NVIDIA, NVIDIA GeForce RTX 4070 Ti (0x00002857) Direct3D11 vs_5_0 ps_5_0, D3D11)",
4348
+ "Google Inc. (NVIDIA)|ANGLE (NVIDIA, NVIDIA GeForce RTX 4080 (0x00002819) Direct3D11 vs_5_0 ps_5_0, D3D11)",
4349
+ "Google Inc. (NVIDIA)|ANGLE (NVIDIA, NVIDIA GeForce RTX 4090 (0x00002684) Direct3D11 vs_5_0 ps_5_0, D3D11)",
4350
+ "Google Inc. (NVIDIA)|ANGLE (NVIDIA, NVIDIA Quadro RTX 5000 Ada Generation (0x000026B2) Direct3D11 vs_5_0 ps_5_0, D3D11)",
4351
+ "Google Inc. (NVIDIA)|ANGLE (NVIDIA, NVIDIA Quadro P400 (0x00001CB3) Direct3D11 vs_5_0 ps_5_0, D3D11)",
4352
+ "Google Inc. (Google)|ANGLE (Google, Vulkan 1.3.0 (SwiftShader Device (Subzero) (0x0000C0DE)), SwiftShader driver)",
4353
+ "Google Inc. (Google)|ANGLE (Google, Vulkan 1.3.0 (SwiftShader Device (Subzero)), SwiftShader driver)",
4354
+ "Google Inc. (Google)|ANGLE (Google, Vulkan 1.3.0 (SwiftShader Device), SwiftShader driver)"
4355
+ ];
4356
+ var SCREEN_RESOLUTIONS = {
4357
+ resolutions: [
4358
+ "1366;768",
4359
+ "1600;900",
4360
+ "1920;1080",
4361
+ "2560;1440",
4362
+ "3840;2160",
4363
+ "7680;4320"
4364
+ ],
4365
+ weights: [
4366
+ .25,
4367
+ .15,
4368
+ .35,
4369
+ .15,
4370
+ .08,
4371
+ .02
4372
+ ]
4373
+ };
4374
+ var COLOR_DEPTH_OPTIONS = {
4375
+ values: [
4376
+ 16,
4377
+ 24,
4378
+ 30,
4379
+ 32
4380
+ ],
4381
+ weights: [
4382
+ .05,
4383
+ .6,
4384
+ .05,
4385
+ .3
4386
+ ]
4387
+ };
4388
+ var DEVICE_MEMORY_OPTIONS = {
4389
+ values: [
4390
+ 1,
4391
+ 2,
4392
+ 4,
4393
+ 8,
4394
+ 12,
4395
+ 16
4396
+ ],
4397
+ weights: [
4398
+ .1,
4399
+ .25,
4400
+ .4,
4401
+ .2,
4402
+ .03,
4403
+ .01
4404
+ ]
4405
+ };
4406
+ var CORE_OPTIONS = {
4407
+ values: [
4408
+ 2,
4409
+ 4,
4410
+ 6,
4411
+ 8,
4412
+ 12,
4413
+ 16,
4414
+ 24,
4415
+ 32
4416
+ ],
4417
+ weights: [
4418
+ .1,
4419
+ .4,
4420
+ .2,
4421
+ .15,
4422
+ .08,
4423
+ .04,
4424
+ .02,
4425
+ .01
4426
+ ]
4427
+ };
4428
+ var BROWSER_PLUGINS = "PDF Viewer,Chrome PDF Viewer,Chromium PDF Viewer,Microsoft Edge PDF Viewer,WebKit built-in PDF";
4429
+ var VOICE_HASH_OPTIONS = "10311144241322244122";
4430
+ /**
4431
+ * b1 的 x82:首位 `1` + 页面 `window` 自有的非标准键(按 `Object.keys` 顺序取前 14 个),`|` 连接后截到 300 字符。
4432
+ * 2026-10-07 在 Edge 154 无扩展、无登录的隔离上下文里打开 explore 实测(fp 脚本 `as/v2/fp/c4a1036f…`);
4433
+ * 这里存的是截断前的 14 个键,最后一段被 300 字符截断是浏览器的原样行为。
4434
+ */
4435
+ var B1_PAGE_GLOBALS = [
4436
+ "_BHjFmfUMEtxhI",
4437
+ "_AUuXfEG27Xa3x",
4438
+ "__XHS_AGENT_V2",
4439
+ "__xhsPendingNotePoint25300ReportMap",
4440
+ "__xhsReportedNotePoint25300RecordMap",
4441
+ "anti_hp_sign_config",
4442
+ "__rap_app_id__",
4443
+ "__rap_report__",
4444
+ "__rap_last_sign_cost__",
4445
+ "__rap_last_transform_cost__",
4446
+ "__rap_hijack_installed__",
4447
+ "__ed1a7dddf7c818e4bd",
4448
+ "__hp_xhs_search_input_state__",
4449
+ "__hp_xhs_search_submit_state__"
4450
+ ];
4451
+ var FONTS = "system-ui, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\", -apple-system, \"Segoe UI\", Roboto, Ubuntu, Cantarell, \"Noto Sans\", sans-serif, BlinkMacSystemFont, \"Helvetica Neue\", Arial, \"PingFang SC\", \"PingFang TC\", \"PingFang HK\", \"Microsoft Yahei\", \"Microsoft JhengHei\"";
4452
+ /**
4453
+ * Fingerprint generation helper functions
4454
+ */
4455
+ /**
4456
+ * Random choice a value from list according to the given weights
4457
+ */
4458
+ function weightedRandomChoice(options, weights) {
4459
+ const totalWeight = weights.reduce((sum, w) => sum + w, 0);
4460
+ let random = Math.random() * totalWeight;
4461
+ for (let i = 0; i < options.length; i++) {
4462
+ random -= weights[i];
4463
+ if (random <= 0) return String(options[i]);
4464
+ }
4465
+ return String(options[options.length - 1]);
4466
+ }
4467
+ /**
4468
+ * Get random GPU renderer information
4469
+ */
4470
+ function getRendererInfo() {
4471
+ const [vendor, renderer] = GPU_VENDORS[Math.floor(Math.random() * GPU_VENDORS.length)].split("|");
4472
+ return [vendor, renderer];
4473
+ }
4474
+ /**
4475
+ * Get random screen configuration with width, height, and available dimensions
4476
+ */
4477
+ function getScreenConfig() {
4478
+ const [widthStr, heightStr] = weightedRandomChoice(SCREEN_RESOLUTIONS.resolutions, SCREEN_RESOLUTIONS.weights).split(";");
4479
+ const width = parseInt(widthStr, 10);
4480
+ const height = parseInt(heightStr, 10);
4481
+ let availWidth;
4482
+ let availHeight;
4483
+ if (Math.random() < .5) {
4484
+ availWidth = width - parseInt(weightedRandomChoice([
4485
+ 0,
4486
+ 30,
4487
+ 60,
4488
+ 80
4489
+ ], [
4490
+ .1,
4491
+ .4,
4492
+ .3,
4493
+ .2
4494
+ ]), 10);
4495
+ availHeight = height;
4496
+ } else {
4497
+ availWidth = width;
4498
+ availHeight = height - parseInt(weightedRandomChoice([
4499
+ 30,
4500
+ 60,
4501
+ 80,
4502
+ 100
4503
+ ], [
4504
+ .2,
4505
+ .5,
4506
+ .2,
4507
+ .1
4508
+ ]), 10);
4509
+ }
4510
+ return {
4511
+ width,
4512
+ height,
4513
+ availWidth,
4514
+ availHeight
4515
+ };
4516
+ }
4517
+ /**
4518
+ * Generate WebGL fingerprint hash
4519
+ */
4520
+ function generateWebglHash() {
4521
+ return createHash("md5").update(randomBytes(32)).digest("hex");
4522
+ }
4523
+ /**
4524
+ * Browser fingerprint generator
4525
+ */
4526
+ /** b1 只取指纹里的这 19 个键,顺序即浏览器 JSON 的键序 */
4527
+ var B1_KEYS = [
4528
+ "x33",
4529
+ "x34",
4530
+ "x35",
4531
+ "x36",
4532
+ "x37",
4533
+ "x38",
4534
+ "x39",
4535
+ "x42",
4536
+ "x43",
4537
+ "x44",
4538
+ "x45",
4539
+ "x46",
4540
+ "x48",
4541
+ "x49",
4542
+ "x50",
4543
+ "x51",
4544
+ "x52",
4545
+ "x82",
4546
+ "x84"
4547
+ ];
4548
+ var FingerprintGenerator = class {
4549
+ config;
4550
+ b1Key;
4551
+ encoder;
4552
+ constructor(config) {
4553
+ this.config = config || new CryptoConfig();
4554
+ this.b1Key = Buffer.from(this.config.B1_SECRET_KEY, "utf8");
4555
+ this.encoder = new Base64Encoder(this.config);
4556
+ }
4557
+ /**
4558
+ * Generate b1 parameter from fingerprint
4559
+ *
4560
+ * 格式:customBase64(UTF-8(RC4(JSON(19 键), B1_SECRET_KEY)))。RC4 密文按 latin1 读成字符串后再走
4561
+ * UTF-8,所以 ≥0x80 的字节各占两个字节 —— 与 Python 版 `quote` + 按 `%` 拆字节等价。
4562
+ * 2026-10-07 用 Edge 154 的 `localStorage.b1` 反解确认(fp 脚本 `as/v2/fp/c4a1036f…`)。
4563
+ * 旧实现把字节数组再 `JSON.stringify` 一次才编码,产出的是浏览器从不会发的「数字数组文本」。
4564
+ */
4565
+ generateB1(fp) {
4566
+ const b1Fp = {};
4567
+ for (const key of B1_KEYS) b1Fp[key] = fp[key];
4568
+ return this.encoder.encode(this.rc4Encrypt(JSON.stringify(b1Fp), this.b1Key));
4569
+ }
4570
+ /**
4571
+ * RC4 encryption implementation
4572
+ */
4573
+ rc4Encrypt(data, key) {
4574
+ const S = new Array(256);
4575
+ for (let i = 0; i < 256; i++) S[i] = i;
4576
+ let j = 0;
4577
+ for (let i = 0; i < 256; i++) {
4578
+ j = (j + S[i] + key[i % key.length]) % 256;
4579
+ [S[i], S[j]] = [S[j], S[i]];
4580
+ }
4581
+ const dataBytes = Buffer.from(data, "utf8");
4582
+ const result = Buffer.alloc(dataBytes.length);
4583
+ let i = 0;
4584
+ j = 0;
4585
+ for (let k = 0; k < dataBytes.length; k++) {
4586
+ i = (i + 1) % 256;
4587
+ j = (j + S[i]) % 256;
4588
+ [S[i], S[j]] = [S[j], S[i]];
4589
+ const t = (S[i] + S[j]) % 256;
4590
+ result[k] = dataBytes[k] ^ S[t];
4591
+ }
4592
+ return result.toString("latin1");
4593
+ }
4594
+ /**
4595
+ * Generate browser fingerprint
4596
+ */
4597
+ generate(cookies, userAgent) {
4598
+ const cookieString = Object.entries(cookies).map(([k, v]) => `${k}=${v}`).join("; ");
4599
+ const screenConfig = getScreenConfig();
4600
+ const isIncognitoMode = weightedRandomChoice(["true", "false"], [.95, .05]);
4601
+ const [vendor, renderer] = getRendererInfo();
4602
+ const x78Y = Math.floor(Math.random() * 100) + 2350;
4603
+ const collectedAt = Date.now() - 100 - Math.floor(Math.random() * 1400);
4604
+ const timeOrigin = Math.round((collectedAt - 3e3 - Math.random() * 11e3) * 10) / 10;
4605
+ return {
4606
+ x1: userAgent,
4607
+ x2: "false",
4608
+ x3: "zh-CN",
4609
+ x4: weightedRandomChoice(COLOR_DEPTH_OPTIONS.values, COLOR_DEPTH_OPTIONS.weights),
4610
+ x5: weightedRandomChoice(DEVICE_MEMORY_OPTIONS.values, DEVICE_MEMORY_OPTIONS.weights),
4611
+ x6: "24",
4612
+ x7: `${vendor},${renderer}`,
4613
+ x8: weightedRandomChoice(CORE_OPTIONS.values, CORE_OPTIONS.weights),
4614
+ x9: `${screenConfig.width};${screenConfig.height}`,
4615
+ x10: `${screenConfig.availWidth};${screenConfig.availHeight}`,
4616
+ x11: "-480",
4617
+ x12: "Asia/Shanghai",
4618
+ x13: isIncognitoMode,
4619
+ x14: isIncognitoMode,
4620
+ x15: isIncognitoMode,
4621
+ x16: "false",
4622
+ x17: "false",
4623
+ x18: "un",
4624
+ x19: "Win32",
4625
+ x20: "",
4626
+ x21: BROWSER_PLUGINS,
4627
+ x22: generateWebglHash(),
4628
+ x23: "false",
4629
+ x24: "false",
4630
+ x25: "false",
4631
+ x26: "false",
4632
+ x27: "false",
4633
+ x28: "0,false,false",
4634
+ x29: "4,7,8",
4635
+ x30: "swf object not loaded",
4636
+ x33: "0",
4637
+ x34: "0",
4638
+ x35: "0",
4639
+ x36: `${Math.floor(Math.random() * 20) + 1}`,
4640
+ x37: "0|0|0|0|0|0|0|0|0|1|0|0|1|0|0|0|0|1|1|0|0|0|0|0",
4641
+ x38: "0|0|1|0|1|0|0|0|0|0|1|0|1|0|1|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0",
4642
+ x39: "0",
4643
+ x40: "0",
4644
+ x41: "0",
4645
+ x42: "3.5.6",
4646
+ x43: "Canvas not supported",
4647
+ x44: `${collectedAt}`,
4648
+ x45: "__SEC_CAV__1-1-1-1-1|",
4649
+ x46: "false",
4650
+ x47: "1|0|0|0|0|0",
4651
+ x48: "",
4652
+ x49: "{list:[],type:}",
4653
+ x50: "",
4654
+ x51: "",
4655
+ x52: "",
4656
+ x55: "380,380,360,400,380,400,420,380,400,400,360,360,440,420",
4657
+ x56: `${vendor}|${renderer}|${generateWebglHash()}|35`,
4658
+ x57: cookieString,
4659
+ x58: "180",
4660
+ x59: "2",
4661
+ x60: "63",
4662
+ x61: "1291",
4663
+ x62: "2047",
4664
+ x63: "0",
4665
+ x64: "0",
4666
+ x65: "0",
4667
+ x66: {
4668
+ referer: "",
4669
+ location: "https://www.xiaohongshu.com/explore",
4670
+ frame: 0
4671
+ },
4672
+ x67: "1|0",
4673
+ x68: "0",
4674
+ x69: "326|1292|30",
4675
+ x70: ["location"],
4676
+ x71: "true",
4677
+ x72: "complete",
4678
+ x73: "1191",
4679
+ x74: "0|0|0",
4680
+ x75: "Google Inc.",
4681
+ x76: "true",
4682
+ x77: "1|1|1|1|1|1|1|1|1|1",
4683
+ x78: {
4684
+ x: 0,
4685
+ y: x78Y,
4686
+ left: 0,
4687
+ right: 290.828125,
4688
+ bottom: x78Y + 18,
4689
+ height: 18,
4690
+ top: x78Y,
4691
+ width: 290.828125,
4692
+ font: FONTS
4693
+ },
4694
+ x82: `1|${B1_PAGE_GLOBALS.join("|")}`.slice(0, 300),
4695
+ x84: `{mt:{to:${timeOrigin}},m:{},k:{},p:{ulr:0,f:1,ps:1},st:{h:0,f:1,kr:0},ft:{ae:null,ak:null,cdr:null,bf:null,fi:null}}`,
4696
+ x31: "124.04347527516074",
4697
+ x79: "144|599565058866",
4698
+ x53: createHash("md5").update(randomBytes(32)).digest("hex"),
4699
+ x54: VOICE_HASH_OPTIONS,
4700
+ x80: "1|[object FileSystemDirectoryHandle]"
4701
+ };
4702
+ }
4703
+ /**
4704
+ * Update fingerprint with new cookies and URL
4705
+ */
4706
+ update(fp, cookies, url) {
4707
+ const cookieString = Object.entries(cookies).map(([k, v]) => `${k}=${v}`).join("; ");
4708
+ fp.x39 = "0";
4709
+ fp.x44 = `${Date.now()}`;
4710
+ fp.x57 = cookieString;
4711
+ fp.x66 = {
4712
+ referer: "https://www.xiaohongshu.com/explore",
4713
+ location: url,
4714
+ frame: 0
4715
+ };
4716
+ }
4717
+ };
4718
+ /** b1 按不透明字符串保留;undefined 表示未提供。 */
4719
+ function validateB1(b1) {
4720
+ if (b1 !== void 0 && (typeof b1 !== "string" || b1.trim().length === 0)) throw new TypeError("b1 必须是非空字符串");
4721
+ }
4722
+ /**
4723
+ * Custom CRC32 helper class.
4724
+ *
4725
+ * This implements a JavaScript-style CRC32 variant compatible with:
4726
+ * (-1 ^ c ^ 0xEDB88320) >>> 0
4727
+ * where `c` is the intermediate CRC state produced by the core CRC32 loop.
4728
+ */
4729
+ var MASK32 = 4294967295;
4730
+ var POLY = 3988292384;
4731
+ var TABLE = null;
4732
+ function ensureTable() {
4733
+ if (TABLE !== null) return TABLE;
4734
+ const tbl = new Array(256);
4735
+ for (let d = 0; d < 256; d++) {
4736
+ let r = d;
4737
+ for (let i = 0; i < 8; i++) {
4738
+ r = r & 1 ? r >>> 1 ^ POLY : r >>> 1;
4739
+ r = r >>> 0;
4740
+ }
4741
+ tbl[d] = r;
4742
+ }
4743
+ TABLE = tbl;
4744
+ return TABLE;
4745
+ }
4746
+ function crc32Core(data, stringMode = "js") {
4747
+ const table = ensureTable();
4748
+ let c = MASK32;
4749
+ let bytes;
4750
+ if (typeof data === "string") {
4751
+ if (stringMode === "utf8") bytes = Array.from(Buffer.from(data, "utf8"));
4752
+ else {
4753
+ bytes = [];
4754
+ for (let i = 0; i < data.length; i++) bytes.push(data.charCodeAt(i) & 255);
4755
+ }
4756
+ } else bytes = data.map((b) => b & 255);
4757
+ for (const b of bytes) c = (table[(c & 255 ^ b) & 255] ^ c >>> 8) >>> 0;
4758
+ return c;
4759
+ }
4760
+ function toSigned32(u) {
4761
+ return u & 2147483648 ? u - 4294967296 : u;
4762
+ }
4763
+ var CRC32 = class {
4764
+ /**
4765
+ * JavaScript-style CRC32 public entry.
4766
+ *
4767
+ * This matches the JS expression:
4768
+ * (-1 ^ c ^ 0xEDB88320) >>> 0
4769
+ */
4770
+ static crc32JsInt(data, options = {}) {
4771
+ const { stringMode = "js", signed = true } = options;
4772
+ const u = (crc32Core(data, stringMode) ^ 306674911) >>> 0;
4773
+ return signed ? toSigned32(u) : u;
4774
+ }
4775
+ };
4776
+ /**
4777
+ * x-s-common signature generation
4778
+ */
4779
+ var XsCommonSigner = class {
4780
+ config;
4781
+ encoder;
4782
+ constructor(config) {
4783
+ this.config = config || new CryptoConfig();
4784
+ this.encoder = new Base64Encoder(this.config);
4785
+ }
4786
+ /**
4787
+ * Generate x-s-common signature
4788
+ */
4789
+ sign(cookieDict, options = {}) {
4790
+ const a1Value = cookieDict.a1;
4791
+ if (!a1Value) throw new Error("Missing 'a1' in cookies");
4792
+ let b1 = options.b1;
4793
+ validateB1(b1);
4794
+ if (b1 === void 0) {
4795
+ const fpGenerator = new FingerprintGenerator(this.config);
4796
+ b1 = fpGenerator.generateB1(fpGenerator.generate(cookieDict, this.config.PUBLIC_USERAGENT));
4797
+ }
4798
+ const x9 = CRC32.crc32JsInt(b1);
4799
+ const signStruct = { ...this.config.SIGNATURE_XSCOMMON_TEMPLATE };
4800
+ signStruct.x5 = a1Value;
4801
+ signStruct.x8 = b1;
4802
+ signStruct.x9 = x9;
4803
+ if (cookieDict.webBuild) signStruct.x4 = cookieDict.webBuild;
4804
+ const signJson = JSON.stringify(signStruct);
4805
+ return this.encoder.encode(signJson);
4806
+ }
4807
+ };
4808
+ /**
4809
+ * XYW_ signature crypto (AES-128-CBC).
4810
+ *
4811
+ * Ported from the upstream Python `core/xyw_crypto.py`. Data-fetching APIs
4812
+ * (user_posted, otherinfo, etc.) reject the traditional XYS_ format with HTTP
4813
+ * 406; the XYW_ format uses an independent AES-128-CBC crypto path.
4814
+ *
4815
+ * The upstream ships a hand-rolled AES-128 implementation. Since it is
4816
+ * standard AES-128 (identical S-box, RCON, key schedule and MixColumns), this
4817
+ * port uses Node's native `aes-128-cbc` cipher, which produces byte-identical
4818
+ * ciphertext while being far less error-prone.
4819
+ */
4820
+ var AES_BLOCK_SIZE = 16;
4821
+ /**
4822
+ * Apply PKCS#7 padding to a byte buffer.
4823
+ */
4824
+ function pkcs7Pad(data) {
4825
+ const padLen = AES_BLOCK_SIZE - data.length % AES_BLOCK_SIZE;
4826
+ return Buffer.concat([data, Buffer.alloc(padLen, padLen)]);
4827
+ }
4828
+ /**
4829
+ * AES-128-CBC cipher matching the upstream XYW implementation.
4830
+ */
4831
+ var XywCipher = class {
4832
+ key;
4833
+ iv;
4834
+ constructor(key, iv) {
4835
+ if (key.length !== AES_BLOCK_SIZE) throw new Error("AES-128 requires a 16-byte key");
4836
+ if (iv.length !== AES_BLOCK_SIZE) throw new Error("AES-CBC IV must be 16 bytes");
4837
+ this.key = key;
4838
+ this.iv = iv;
4839
+ }
4840
+ /**
4841
+ * Encrypt PKCS#7-padded plaintext with AES-128-CBC.
4842
+ *
4843
+ * @param plaintextBytes - Plaintext, already padded to a 16-byte boundary
4844
+ * @returns Ciphertext bytes
4845
+ */
4846
+ encrypt(plaintextBytes) {
4847
+ if (plaintextBytes.length % AES_BLOCK_SIZE !== 0) throw new Error("plaintextBytes must be PKCS#7 padded to a 16-byte boundary");
4848
+ const cipher = createCipheriv("aes-128-cbc", this.key, this.iv);
4849
+ cipher.setAutoPadding(false);
4850
+ return Buffer.concat([cipher.update(plaintextBytes), cipher.final()]);
4851
+ }
4852
+ };
4853
+ /**
4854
+ * Build the hex-encoded payload for an XYW_ signature.
4855
+ *
4856
+ * @param options.fullUri - The content string (uri + params/body)
4857
+ * @param options.a1Value - a1 value from cookies
4858
+ * @param options.timestampMs - Millisecond timestamp string
4859
+ * @param options.config - Crypto configuration
4860
+ * @param options.envFlags - Optional environment flags string
4861
+ * @returns Hex-encoded AES-128-CBC ciphertext
4862
+ */
4863
+ function buildXywPayloadHex(options) {
4864
+ const { fullUri, a1Value, timestampMs, config, envFlags } = options;
4865
+ const x1 = createHash("md5").update(`url=${fullUri}`).digest("hex");
4866
+ const x2 = envFlags || config.XYW_ENV_FLAGS_DEFAULT;
4867
+ const message = Buffer.from(`x1=${x1};x2=${x2};x3=${a1Value};x4=${timestampMs};`);
4868
+ const plaintext = pkcs7Pad(Buffer.from(message.toString("base64")));
4869
+ return new XywCipher(Buffer.from(config.XYW_AES_KEY, "utf-8"), Buffer.from(config.XYW_AES_IV, "utf-8")).encrypt(plaintext).toString("hex");
4870
+ }
4871
+ /**
4872
+ * x-rap-param generation.
4873
+ *
4874
+ * Ported from the upstream Python `core/xrap.py`. Required for feed, search,
4875
+ * and note-publishing endpoints. Builds a TLV body structure, gzip-compresses
4876
+ * it, then XOR + block-encrypts and wraps it in a base64 envelope.
4877
+ *
4878
+ * 分组密码这一层此前被当成「换了 S 盒和轮密钥的 SM4 变体」,实际是**换了 S 盒的
4879
+ * AES-128** —— 轮函数、T 表构造、ShiftRows、末轮置换都与 AES 一致,只换了那张
4880
+ * 256 项代换表。据此 11 组轮密钥不必再抄成魔数:用 ASCII 主密钥
4881
+ * `kqI1DTcwKX90ZtAy` 配合同一张 S 盒做标准 AES 密钥展开,可逐字复现
4882
+ * SDK 里硬编码的全部 11 组(含末轮),见 {@link expandXrapKey}。
4883
+ * 这样平台换密钥时只需替换 16 字节,而不是重新逆一遍常量池。
4884
+ */
4885
+ var cfg = new CryptoConfig();
4886
+ var XRAP_SDK_VERSION = cfg.XRAP_SDK_VERSION;
4887
+ var SBOX = [
4888
+ 122,
4889
+ 1,
4890
+ 88,
4891
+ 224,
4892
+ 80,
4893
+ 78,
4894
+ 2,
4895
+ 121,
4896
+ 29,
4897
+ 75,
4898
+ 83,
4899
+ 218,
4900
+ 107,
4901
+ 72,
4902
+ 212,
4903
+ 82,
4904
+ 237,
4905
+ 119,
4906
+ 18,
4907
+ 33,
4908
+ 20,
4909
+ 21,
4910
+ 236,
4911
+ 16,
4912
+ 24,
4913
+ 229,
4914
+ 185,
4915
+ 241,
4916
+ 12,
4917
+ 8,
4918
+ 252,
4919
+ 125,
4920
+ 249,
4921
+ 205,
4922
+ 181,
4923
+ 200,
4924
+ 230,
4925
+ 55,
4926
+ 38,
4927
+ 135,
4928
+ 86,
4929
+ 186,
4930
+ 184,
4931
+ 43,
4932
+ 173,
4933
+ 240,
4934
+ 104,
4935
+ 247,
4936
+ 139,
4937
+ 141,
4938
+ 211,
4939
+ 94,
4940
+ 54,
4941
+ 77,
4942
+ 46,
4943
+ 146,
4944
+ 49,
4945
+ 130,
4946
+ 242,
4947
+ 41,
4948
+ 112,
4949
+ 61,
4950
+ 45,
4951
+ 215,
4952
+ 182,
4953
+ 64,
4954
+ 178,
4955
+ 67,
4956
+ 68,
4957
+ 128,
4958
+ 120,
4959
+ 210,
4960
+ 13,
4961
+ 73,
4962
+ 74,
4963
+ 9,
4964
+ 99,
4965
+ 108,
4966
+ 7,
4967
+ 58,
4968
+ 158,
4969
+ 213,
4970
+ 6,
4971
+ 198,
4972
+ 225,
4973
+ 98,
4974
+ 244,
4975
+ 52,
4976
+ 36,
4977
+ 89,
4978
+ 169,
4979
+ 87,
4980
+ 42,
4981
+ 0,
4982
+ 62,
4983
+ 23,
4984
+ 44,
4985
+ 10,
4986
+ 26,
4987
+ 66,
4988
+ 250,
4989
+ 147,
4990
+ 190,
4991
+ 220,
4992
+ 245,
4993
+ 179,
4994
+ 106,
4995
+ 19,
4996
+ 232,
4997
+ 3,
4998
+ 199,
4999
+ 151,
5000
+ 187,
5001
+ 115,
5002
+ 118,
5003
+ 134,
5004
+ 227,
5005
+ 70,
5006
+ 114,
5007
+ 71,
5008
+ 208,
5009
+ 5,
5010
+ 76,
5011
+ 56,
5012
+ 124,
5013
+ 31,
5014
+ 129,
5015
+ 171,
5016
+ 117,
5017
+ 81,
5018
+ 235,
5019
+ 243,
5020
+ 50,
5021
+ 116,
5022
+ 17,
5023
+ 143,
5024
+ 132,
5025
+ 137,
5026
+ 156,
5027
+ 113,
5028
+ 34,
5029
+ 126,
5030
+ 157,
5031
+ 207,
5032
+ 63,
5033
+ 145,
5034
+ 105,
5035
+ 101,
5036
+ 60,
5037
+ 109,
5038
+ 150,
5039
+ 162,
5040
+ 152,
5041
+ 153,
5042
+ 51,
5043
+ 57,
5044
+ 154,
5045
+ 202,
5046
+ 195,
5047
+ 159,
5048
+ 160,
5049
+ 188,
5050
+ 228,
5051
+ 163,
5052
+ 164,
5053
+ 84,
5054
+ 127,
5055
+ 167,
5056
+ 168,
5057
+ 4,
5058
+ 111,
5059
+ 93,
5060
+ 172,
5061
+ 183,
5062
+ 39,
5063
+ 175,
5064
+ 176,
5065
+ 40,
5066
+ 65,
5067
+ 174,
5068
+ 180,
5069
+ 110,
5070
+ 11,
5071
+ 27,
5072
+ 223,
5073
+ 142,
5074
+ 48,
5075
+ 177,
5076
+ 254,
5077
+ 144,
5078
+ 97,
5079
+ 96,
5080
+ 192,
5081
+ 203,
5082
+ 92,
5083
+ 14,
5084
+ 239,
5085
+ 22,
5086
+ 131,
5087
+ 234,
5088
+ 32,
5089
+ 233,
5090
+ 201,
5091
+ 85,
5092
+ 196,
5093
+ 69,
5094
+ 133,
5095
+ 204,
5096
+ 30,
5097
+ 170,
5098
+ 103,
5099
+ 138,
5100
+ 123,
5101
+ 53,
5102
+ 214,
5103
+ 25,
5104
+ 216,
5105
+ 217,
5106
+ 194,
5107
+ 219,
5108
+ 148,
5109
+ 221,
5110
+ 28,
5111
+ 222,
5112
+ 166,
5113
+ 255,
5114
+ 248,
5115
+ 191,
5116
+ 91,
5117
+ 90,
5118
+ 15,
5119
+ 231,
5120
+ 193,
5121
+ 189,
5122
+ 209,
5123
+ 102,
5124
+ 197,
5125
+ 37,
5126
+ 238,
5127
+ 140,
5128
+ 226,
5129
+ 95,
5130
+ 136,
5131
+ 161,
5132
+ 59,
5133
+ 165,
5134
+ 246,
5135
+ 206,
5136
+ 149,
5137
+ 47,
5138
+ 100,
5139
+ 35,
5140
+ 251,
5141
+ 253,
5142
+ 79,
5143
+ 155
5144
+ ];
5145
+ /** AES 密钥展开的轮常量,前 10 轮。 */
5146
+ var RCON = [
5147
+ 1,
5148
+ 2,
5149
+ 4,
5150
+ 8,
5151
+ 16,
5152
+ 32,
5153
+ 64,
5154
+ 128,
5155
+ 27,
5156
+ 54
5157
+ ];
5158
+ /**
5159
+ * 用自定义 S 盒做标准 AES-128 密钥展开。
5160
+ *
5161
+ * 与教科书 AES 的唯一差别是 `SubWord` 查的是上面那张 {@link SBOX}。
5162
+ * 对主密钥 `kqI1DTcwKX90ZtAy` 展开的结果与 SDK 里硬编码的 11 组轮密钥逐字相同 ——
5163
+ * 第 0 轮本身就是主密钥的 ASCII 字节(`6b714931 44546377 4b583930 5a744179`)。
5164
+ * @param masterKey - 16 字节主密钥
5165
+ * @returns 11 组轮密钥,每组 4 个 32 位字;末组即末轮密钥
5166
+ */
5167
+ function expandXrapKey(masterKey) {
5168
+ if (masterKey.length !== 16) throw new Error(`x-rap master key must be 16 bytes, got ${masterKey.length}`);
5169
+ const subWord = (w) => (SBOX[w >>> 24 & 255] << 24 | SBOX[w >>> 16 & 255] << 16 | SBOX[w >>> 8 & 255] << 8 | SBOX[w & 255]) >>> 0;
5170
+ const rotWord = (w) => (w << 8 | w >>> 24) >>> 0;
5171
+ const words = [];
5172
+ for (let i = 0; i < 4; i++) words.push(masterKey.readUInt32BE(i * 4));
5173
+ for (let i = 4; i < 44; i++) {
5174
+ let temp = words[i - 1];
5175
+ if (i % 4 === 0) temp = (subWord(rotWord(temp)) ^ RCON[i / 4 - 1] << 24) >>> 0;
5176
+ words.push((words[i - 4] ^ temp) >>> 0);
5177
+ }
5178
+ return Array.from({ length: 11 }, (_, r) => words.slice(r * 4, r * 4 + 4).map((w) => w >>> 0));
5179
+ }
5180
+ var EXPANDED_KEYS = expandXrapKey(Buffer.from(cfg.XRAP_MASTER_KEY, "ascii"));
5181
+ var ROUND_KEYS = EXPANDED_KEYS.slice(0, 10);
5182
+ var LAST_ROUND_KEY = EXPANDED_KEYS[10];
5183
+ var LAST_ROUND_KEY_BYTES = (() => {
5184
+ const b = Buffer.alloc(16);
5185
+ for (let i = 0; i < 4; i++) b.writeUInt32BE(LAST_ROUND_KEY[i] >>> 0, i * 4);
5186
+ return b;
5187
+ })();
5188
+ function gfDouble(x) {
5189
+ x <<= 1;
5190
+ return x & 256 ? (x ^ 283) & 255 : x & 255;
5191
+ }
5192
+ function gfMul(x, n) {
5193
+ if (n === 1) return x;
5194
+ if (n === 2) return gfDouble(x);
5195
+ if (n === 3) return gfDouble(x) ^ x;
5196
+ throw new Error(`unsupported GF multiplier: ${n}`);
5197
+ }
5198
+ /**
5199
+ * Pre-compute 4 shifted T-tables from the S-box for the round function.
5200
+ */
5201
+ function buildLookupTables() {
5202
+ const col0 = [];
5203
+ const col1 = [];
5204
+ const col2 = [];
5205
+ const col3 = [];
5206
+ for (const s of SBOX) {
5207
+ const a = gfMul(s, 2);
5208
+ const d = gfMul(s, 3);
5209
+ col0.push((a << 24 | s << 16 | s << 8 | d) >>> 0);
5210
+ col1.push((d << 24 | a << 16 | s << 8 | s) >>> 0);
5211
+ col2.push((s << 24 | d << 16 | a << 8 | s) >>> 0);
5212
+ col3.push((s << 24 | s << 16 | d << 8 | a) >>> 0);
5213
+ }
5214
+ return [
5215
+ col0,
5216
+ col1,
5217
+ col2,
5218
+ col3
5219
+ ];
5220
+ }
5221
+ var [LUT0, LUT1, LUT2, LUT3] = buildLookupTables();
5222
+ function readU32BE(buf, off = 0) {
5223
+ return buf.readUInt32BE(off);
5224
+ }
5225
+ function toCompactJson(data) {
5226
+ if (typeof data === "string") return data;
5227
+ if (Buffer.isBuffer(data) || data instanceof Uint8Array) return Buffer.from(data).toString("utf-8");
5228
+ return JSON.stringify(data);
5229
+ }
5230
+ function fieldByte(tag, val = 0) {
5231
+ const b = Buffer.alloc(3);
5232
+ b.writeUInt16BE(tag, 0);
5233
+ b.writeUInt8(val & 255, 2);
5234
+ return b;
5235
+ }
5236
+ function fieldU32(tag, val) {
5237
+ const b = Buffer.alloc(6);
5238
+ b.writeUInt16BE(tag, 0);
5239
+ b.writeUInt32BE(val >>> 0, 2);
5240
+ return b;
5241
+ }
5242
+ function fieldU64(tag, val) {
5243
+ const b = Buffer.alloc(10);
5244
+ b.writeUInt16BE(tag, 0);
5245
+ b.writeBigUInt64BE(BigInt(val), 2);
5246
+ return b;
5247
+ }
5248
+ function fieldBlob(tag, data) {
5249
+ const head = Buffer.alloc(6);
5250
+ head.writeUInt16BE(tag, 0);
5251
+ head.writeUInt32BE(data.length >>> 0, 2);
5252
+ return Buffer.concat([head, data]);
5253
+ }
5254
+ /**
5255
+ * Encrypt a single 16-byte block; shorter input is zero-padded on the right.
5256
+ */
5257
+ function encryptBlock16(block) {
5258
+ const src = Buffer.from(block);
5259
+ if (src.length > 16) throw new Error("block length must be <= 16");
5260
+ const padded = Buffer.alloc(16);
5261
+ src.copy(padded);
5262
+ let s0 = (readU32BE(padded, 0) ^ ROUND_KEYS[0][0]) >>> 0;
5263
+ let s1 = (readU32BE(padded, 4) ^ ROUND_KEYS[0][1]) >>> 0;
5264
+ let s2 = (readU32BE(padded, 8) ^ ROUND_KEYS[0][2]) >>> 0;
5265
+ let s3 = (readU32BE(padded, 12) ^ ROUND_KEYS[0][3]) >>> 0;
5266
+ for (let rnd = 1; rnd < 10; rnd++) {
5267
+ const rk = ROUND_KEYS[rnd];
5268
+ const n0 = (LUT0[s0 >>> 24 & 255] ^ LUT1[s1 >>> 16 & 255] ^ LUT2[s2 >>> 8 & 255] ^ LUT3[s3 & 255] ^ rk[0]) >>> 0;
5269
+ const n1 = (LUT0[s1 >>> 24 & 255] ^ LUT1[s2 >>> 16 & 255] ^ LUT2[s3 >>> 8 & 255] ^ LUT3[s0 & 255] ^ rk[1]) >>> 0;
5270
+ const n2 = (LUT0[s2 >>> 24 & 255] ^ LUT1[s3 >>> 16 & 255] ^ LUT2[s0 >>> 8 & 255] ^ LUT3[s1 & 255] ^ rk[2]) >>> 0;
5271
+ const n3 = (LUT0[s3 >>> 24 & 255] ^ LUT1[s0 >>> 16 & 255] ^ LUT2[s1 >>> 8 & 255] ^ LUT3[s2 & 255] ^ rk[3]) >>> 0;
5272
+ s0 = n0;
5273
+ s1 = n1;
5274
+ s2 = n2;
5275
+ s3 = n3;
5276
+ }
5277
+ const state = [
5278
+ s0,
5279
+ s1,
5280
+ s2,
5281
+ s3
5282
+ ];
5283
+ const out = Buffer.alloc(16);
5284
+ for (let row = 0; row < 4; row++) for (let col = 0; col < 4; col++) {
5285
+ const idx = state[row + col & 3] >>> 24 - 8 * col & 255;
5286
+ out[4 * row + col] = SBOX[idx] ^ LAST_ROUND_KEY_BYTES[4 * row + col];
5287
+ }
5288
+ return out;
5289
+ }
5290
+ /**
5291
+ * Encrypt data by splitting into 16-byte blocks (ECB mode).
5292
+ */
5293
+ function encryptBlocks(src) {
5294
+ const parts = [];
5295
+ for (let i = 0; i < src.length; i += 16) parts.push(encryptBlock16(src.subarray(i, i + 16)));
5296
+ return Buffer.concat(parts);
5297
+ }
5298
+ /**
5299
+ * Encrypt the 16-byte session key and append its length.
5300
+ */
5301
+ function encryptSessionKey(key) {
5302
+ if (key.length !== 16) throw new Error("session key must be 16 bytes");
5303
+ const len = Buffer.alloc(4);
5304
+ len.writeUInt32BE(16, 0);
5305
+ return Buffer.concat([encryptBlock16(key), len]);
5306
+ }
5307
+ var DEFAULT_INTERACTION_TRACE = Buffer.from("0002000200004700000000000001ffd9ffa8005c23fff1ffff001501ff90fff9000022fff2ffff001501ffb800770061a5ffe3ffff002a01fffcffe70000f0ffe4ffff002a01ffd30000003e01ffd40000003e01ffc8ffff005301ffbdfffa006901ffbefffb006901ffb1fff4007d01ffa6ffee009301ffa8ffef009301ff9effe900a801ff96ffe600be01ff97ffe600be01ff93ffe500d301ff92ffe500ea01ff92ffe4010501ff92ffe3011b01ff92ffe402d101ff93ffe502f201ff94ffe6040501", "hex");
5308
+ var DEFAULT_ENVIRONMENT_SNAPSHOT = Buffer.from("000000010000000000000000fffeffff00000000000000390001ffff00bc00000000006e00020001013400000000012f00000002011a00000000016100000000019f0000000002470000000002790000000002b1", "hex");
5309
+ /**
5310
+ * Build the pre-compression TLV payload for the RAP parameter.
5311
+ */
5312
+ function buildBodyStructure(api, data, options = {}) {
5313
+ const bodyStr = toCompactJson(data);
5314
+ const hashInput = Buffer.from(api + bodyStr, "utf-8");
5315
+ const ts = options.timestampMs === null || options.timestampMs === void 0 ? Math.floor(Date.now()) : Math.floor(options.timestampMs);
5316
+ let key;
5317
+ if (typeof options.sessionKey === "string") key = Buffer.from(options.sessionKey, "ascii");
5318
+ else if (options.sessionKey) key = Buffer.from(options.sessionKey);
5319
+ else key = Buffer.from(RandomGenerator.generateRandomAscii(16), "ascii");
5320
+ if (key.length !== 16) throw new Error("sessionKey must be 16 bytes");
5321
+ const xorByte = options.obfuscationByte === null || options.obfuscationByte === void 0 ? Math.floor(Math.random() * 255) + 1 : options.obfuscationByte & 255;
5322
+ const randNonce = options.nonce === null || options.nonce === void 0 ? Math.floor(Math.random() * 4294967296) >>> 0 : options.nonce >>> 0;
5323
+ const trace = options.interactionTrace ?? DEFAULT_INTERACTION_TRACE;
5324
+ const env = options.environmentSnapshot ?? DEFAULT_ENVIRONMENT_SNAPSHOT;
5325
+ const parts = [];
5326
+ parts.push(fieldU64(1e3, ts));
5327
+ parts.push(fieldU32(1001, randNonce));
5328
+ parts.push(fieldBlob(1002, key));
5329
+ parts.push(fieldU32(1003, xxh32(hashInput)));
5330
+ const flagTags = [];
5331
+ for (let t = 1051; t < 1066; t++) flagTags.push(t);
5332
+ flagTags.push(1070);
5333
+ for (let t = 1066; t < 1070; t++) flagTags.push(t);
5334
+ for (const tag of flagTags) parts.push(fieldByte(tag));
5335
+ parts.push(fieldU32(1100, 0));
5336
+ for (let t = 1071; t < 1074; t++) parts.push(fieldByte(t));
5337
+ parts.push(fieldU32(1075, 1380));
5338
+ parts.push(fieldU32(1076, 44));
5339
+ parts.push(fieldU64(1077, Math.max(0, ts - 1076)));
5340
+ parts.push(fieldBlob(1078, trace));
5341
+ parts.push(fieldU32(1082, 0));
5342
+ parts.push(fieldU32(1084, 0));
5343
+ parts.push(fieldU32(1085, 0));
5344
+ parts.push(fieldU32(1086, 100));
5345
+ parts.push(fieldU64(1087, Math.max(0, ts - 727)));
5346
+ parts.push(fieldBlob(1088, env));
5347
+ parts.push(fieldU32(1090, 0));
5348
+ parts.push(fieldU32(1097, 0));
5349
+ parts.push(fieldU32(1092, 1382));
5350
+ parts.push(fieldU32(1094, 1305));
5351
+ parts.push(fieldU64(1095, Math.max(0, ts - 8588)));
5352
+ parts.push(fieldU32(1093, 0));
5353
+ parts.push(fieldByte(1096));
5354
+ parts.push(fieldBlob(1091, Buffer.from([
5355
+ 0,
5356
+ 0,
5357
+ 255,
5358
+ 255
5359
+ ])));
5360
+ for (let t = 1151; t < 1157; t++) parts.push(fieldByte(t));
5361
+ const buf = Buffer.concat(parts);
5362
+ const out = Buffer.from(buf);
5363
+ for (let i = 16; i < out.length; i++) out[i] = out[i] ^ xorByte;
5364
+ return out;
5365
+ }
5366
+ /**
5367
+ * Gzip-compress with OS byte patched to match the browser runtime (0x03).
5368
+ */
5369
+ function compressBody(raw, mtime, level = 6) {
5370
+ const mt = mtime === null || mtime === void 0 ? Math.floor(Date.now() / 1e3) : mtime;
5371
+ const out = Buffer.from(gzipSync(raw, { level }));
5372
+ if (out.length >= 10) {
5373
+ out.writeUInt32LE(mt >>> 0, 4);
5374
+ out[9] = 3;
5375
+ }
5376
+ return out;
5377
+ }
5378
+ function cyclicXor(data, key) {
5379
+ const out = Buffer.alloc(data.length);
5380
+ for (let i = 0; i < data.length; i++) out[i] = data[i] ^ key[i % key.length];
5381
+ return out;
5382
+ }
5383
+ /**
5384
+ * Encrypt, wrap, and base64-encode the compressed body into the final x-rap-param.
5385
+ */
5386
+ function packEnvelope(compressed, options = {}) {
5387
+ const gz = Buffer.from(compressed);
5388
+ let key;
5389
+ if (typeof options.encryptionKey === "string") key = Buffer.from(options.encryptionKey, "ascii");
5390
+ else if (options.encryptionKey) key = Buffer.from(options.encryptionKey);
5391
+ else key = Buffer.from(RandomGenerator.generateRandomAscii(16), "ascii");
5392
+ if (key.length !== 16) throw new Error("encryptionKey must be 16 bytes");
5393
+ const salt = options.saltString === null || options.saltString === void 0 ? RandomGenerator.generateRandomAscii([
5394
+ 4,
5395
+ 5,
5396
+ 6
5397
+ ][Math.floor(Math.random() * 3)]) : options.saltString;
5398
+ const saltBytes = Buffer.from(salt, "ascii");
5399
+ if (saltBytes.length < 0 || saltBytes.length > 255) throw new Error("saltString length must fit one byte");
5400
+ const cipherBlocks = encryptBlocks(cyclicXor(gz, key));
5401
+ const gzLen = Buffer.alloc(4);
5402
+ gzLen.writeUInt32BE(gz.length >>> 0, 0);
5403
+ const cipherBody = Buffer.concat([cipherBlocks, gzLen]);
5404
+ const content = Buffer.concat([
5405
+ saltBytes,
5406
+ encryptSessionKey(key),
5407
+ cipherBody
5408
+ ]);
5409
+ const contentHash = xxh32(content);
5410
+ const encTime = options.processingTime === null || options.processingTime === void 0 ? Math.floor(Math.random() * 181) + 60 : options.processingTime;
5411
+ const protocolVersion = options.protocolVersion ?? XRAP_SDK_VERSION;
5412
+ const header = Buffer.alloc(36);
5413
+ header[0] = 7;
5414
+ header[1] = 36;
5415
+ header[2] = 1;
5416
+ header[3] = saltBytes.length & 255;
5417
+ header.writeUInt32BE(1, 4);
5418
+ header.writeUInt32BE(20, 8);
5419
+ header.writeUInt32BE(cipherBody.length >>> 0, 12);
5420
+ header.writeUInt32BE(contentHash >>> 0, 16);
5421
+ header.writeUInt32BE(protocolVersion >>> 0, 20);
5422
+ header.writeUInt32BE(encTime >>> 0, 24);
5423
+ return Buffer.concat([header, content]).toString("base64");
5424
+ }
5425
+ /**
5426
+ * Generate x-rap-param from API path and request payload.
5427
+ *
5428
+ * @param api - Request API path (e.g. "//edith.xiaohongshu.com/api/sns/web/v1/feed")
5429
+ * @param data - Request body as object, JSON string, or raw bytes
5430
+ * @param options - Optional deterministic overrides (used for testing)
5431
+ * @returns Base64-encoded x-rap-param string
5432
+ */
5433
+ function xRapParam(api, data, options = {}) {
5434
+ return packEnvelope(compressBody(buildBodyStructure(api, data, {
5435
+ sessionKey: options.innerKey,
5436
+ timestampMs: options.timestampMs,
5437
+ nonce: options.bodyRand32,
5438
+ obfuscationByte: options.mask,
5439
+ interactionTrace: options.tracePayload,
5440
+ environmentSnapshot: options.envPayload
5441
+ }), options.gzipMtime, options.compresslevel ?? 6), {
5442
+ encryptionKey: options.aesKey,
5443
+ saltString: options.randomString,
5444
+ processingTime: options.bodyEncryTime,
5445
+ protocolVersion: options.sdkVersion ?? XRAP_SDK_VERSION
5446
+ });
5447
+ }
5448
+ /**
5449
+ * XYS 签名的会话 SSK:x6/x7 证明,以及拿到 SSK 的 X25519 交换。
5450
+ *
5451
+ * 逆自前端 `vendor-dynamic.js` 的 `buildEncSskSign` / `createWebSskExchange`。
5452
+ * 2026-09-25 用页面自己签出的 21 条 XYS_(GET 16 / POST 5)逐字节核对,x6/x7 全部一致。
5453
+ */
5454
+ /** SSK 至少 32 字节;超出部分原样拼进 x7 尾部。 */
5455
+ var SSK_MIN_LENGTH = 32;
5456
+ var NONCE_LENGTH = 4;
5457
+ var sha1 = (data) => createHash("sha1").update(data).digest();
5458
+ function decodeStandardBase64(value) {
5459
+ if (typeof value !== "string" || value.length % 4 !== 0 || !/^[A-Za-z0-9+/]*={0,2}$/.test(value)) throw new Error("Invalid Base64 value");
5460
+ return Buffer.from(value, "base64");
5461
+ }
5462
+ /**
5463
+ * 生成 x7 开头的 4 字节 nonce:`uint32LE((Date.now() >>> 0) + 随机 u32)`,与前端 `createEncSskNonce` 一致。
5464
+ */
5465
+ function createNonce() {
5466
+ const nonce = Buffer.alloc(NONCE_LENGTH);
5467
+ nonce.writeUInt32LE((Date.now() >>> 0) + randomBytes(NONCE_LENGTH).readUInt32LE(0) >>> 0);
5468
+ return nonce;
5469
+ }
5470
+ /**
5471
+ * 计算 XYS 的 x6 / x7(会话 SSK 证明)。
5472
+ *
5473
+ * 对 webSsk 里的每个 appId 各算一份;SSK 解不开或不足 32 字节的项跳过。
5474
+ * 一项都没有时返回 `null`,此时 x-s 里不应出现 x6/x7(与前端一致)。
5475
+ * @param contentMd5 - x5,即签名内容串的 MD5(32 位 hex)
5476
+ * @param webSsk - appId → SSK 映射
5477
+ * @param nonce - 固定 4 字节 nonce,仅用于测试与复现;省略时每个 appId 各自随机
5478
+ * @returns x6 / x7,或 `null`
5479
+ */
5480
+ function buildSskProof(contentMd5, webSsk, nonce) {
5481
+ if (!/^[0-9a-f]{32}$/i.test(contentMd5)) throw new Error("contentMd5 must be a 16-byte MD5 hex string");
5482
+ const digest = Buffer.from(contentMd5, "hex");
5483
+ if (nonce !== void 0 && nonce.length !== NONCE_LENGTH) throw new Error(`nonce must be ${NONCE_LENGTH} bytes`);
5484
+ const x6 = Object.create(null);
5485
+ const x7 = Object.create(null);
5486
+ for (const [appId, value] of Object.entries(webSsk)) {
5487
+ let ssk;
5488
+ try {
5489
+ ssk = decodeStandardBase64(value);
5490
+ } catch {
5491
+ continue;
5492
+ }
5493
+ if (!appId || ssk.length < SSK_MIN_LENGTH) continue;
5494
+ const v = nonce ? Buffer.from(nonce) : createNonce();
5495
+ const encSsk = Buffer.concat([
5496
+ v,
5497
+ sha1(Buffer.concat([ssk, v])),
5498
+ ssk.subarray(SSK_MIN_LENGTH)
5499
+ ]);
5500
+ x6[appId] = sha1(Buffer.concat([digest, encSsk])).toString("base64");
5501
+ x7[appId] = encSsk.toString("base64");
5502
+ }
5503
+ return Object.keys(x6).length > 0 ? {
5504
+ x6,
5505
+ x7
5506
+ } : null;
5507
+ }
5508
+ var methodSchema = _enum(["GET", "POST"], { message: "method must be \"GET\" or \"POST\"" });
5509
+ var uriSchema = string({ message: "uri must be a string" }).min(1, "uri cannot be empty");
5510
+ var a1ValueSchema = string({ message: "a1Value must be a string" }).min(1, "a1Value cannot be empty");
5511
+ var cookieSchema = union([record(string(), any()), string()]);
5512
+ var signatureParamsSchema = object({
5513
+ method: methodSchema,
5514
+ uri: uriSchema,
5515
+ a1Value: a1ValueSchema
5516
+ });
5517
+ var getSignatureParamsSchema = object({
5518
+ uri: uriSchema,
5519
+ a1Value: a1ValueSchema
5520
+ });
5521
+ var postSignatureParamsSchema = object({
5522
+ uri: uriSchema,
5523
+ a1Value: a1ValueSchema
5524
+ });
5525
+ var xsCommonParamsSchema = object({ cookieDict: cookieSchema });
5526
+ function validateSignatureParams(method, uri, a1Value) {
5527
+ try {
5528
+ signatureParamsSchema.parse({
5529
+ method,
5530
+ uri,
5531
+ a1Value
5532
+ });
5533
+ } catch (error) {
5534
+ if (error instanceof ZodError) {
5535
+ const firstError = error.issues[0];
5536
+ throw new TypeError(firstError.message);
5537
+ }
5538
+ throw error;
5539
+ }
5540
+ }
5541
+ function validateGetSignatureParams(uri, a1Value) {
5542
+ try {
5543
+ getSignatureParamsSchema.parse({
5544
+ uri,
5545
+ a1Value
5546
+ });
5547
+ } catch (error) {
5548
+ if (error instanceof ZodError) {
5549
+ const firstError = error.issues[0];
5550
+ throw new TypeError(firstError.message);
5551
+ }
5552
+ throw error;
5553
+ }
5554
+ }
5555
+ function validatePostSignatureParams(uri, a1Value) {
5556
+ try {
5557
+ postSignatureParamsSchema.parse({
5558
+ uri,
5559
+ a1Value
5560
+ });
5561
+ } catch (error) {
5562
+ if (error instanceof ZodError) {
5563
+ const firstError = error.issues[0];
5564
+ throw new TypeError(firstError.message);
5565
+ }
5566
+ throw error;
5567
+ }
5568
+ }
5569
+ function validateXsCommonParams(cookieDict) {
5570
+ try {
5571
+ xsCommonParamsSchema.parse({ cookieDict });
5572
+ } catch (error) {
5573
+ if (error instanceof ZodError) {
5574
+ const firstError = error.issues[0];
5575
+ throw new TypeError(firstError.message);
5576
+ }
5577
+ throw error;
5578
+ }
5579
+ }
5580
+ var Xhshow = class {
5581
+ config;
5582
+ cryptoProcessor;
5583
+ randomGenerator;
5584
+ /**
5585
+ * 创建 Xhshow 实例
5586
+ * @param config - 可选的加密配置对象,不传则使用默认配置
5587
+ */
5588
+ constructor(config) {
5589
+ this.config = config || new CryptoConfig();
5590
+ this.cryptoProcessor = new CryptoProcessor(this.config);
5591
+ this.randomGenerator = new RandomGenerator();
5592
+ }
5593
+ /**
5594
+ * 构建内容字符串(用于 MD5 计算和签名生成)
5595
+ * @param method - HTTP 请求方法
5596
+ * @param uri - 请求 URI 路径
5597
+ * @param payload - 请求参数或请求体
5598
+ * @returns 用于签名的内容字符串
5599
+ */
5600
+ buildContentString(method, uri, payload = null) {
5601
+ const normalizedPayload = payload || {};
5602
+ if (method.toUpperCase() === "POST") return uri + JSON.stringify(normalizedPayload);
5603
+ else {
5604
+ if (Object.keys(normalizedPayload).length === 0) return uri;
5605
+ return `${uri}?${Object.entries(normalizedPayload).map(([key, value]) => {
5606
+ let valStr = "";
5607
+ if (Array.isArray(value)) valStr = value.map((v) => String(v)).join(",");
5608
+ else valStr = value !== null && value !== void 0 ? String(value) : "";
5609
+ return `${key}=${valStr.replace(/=/g, "%3D")}`;
5610
+ }).join("&")}`;
5611
+ }
5612
+ }
5613
+ /**
5614
+ * 根据内容字符串生成 d 值(MD5 哈希)
5615
+ * @param content - 内容字符串
5616
+ * @returns MD5 哈希值(十六进制字符串)
5617
+ */
5618
+ generateDValue(content) {
5619
+ return createHash("md5").update(content, "utf8").digest("hex");
5620
+ }
5621
+ /**
5622
+ * 构建签名
5623
+ * @param dValue - MD5 哈希值
5624
+ * @param a1Value - Cookie 中的 a1 值
5625
+ * @param xsecAppid - 应用 ID,默认为 'xhs-pc-web'
5626
+ * @param stringParam - 字符串参数
5627
+ * @param timestamp - 可选的时间戳
5628
+ * @param signState - 可选的签名状态(用于会话管理)
5629
+ * @returns 编码后的签名字符串
5630
+ */
5631
+ buildSignature(dValue, mValue, a1Value, xsecAppid = "xhs-pc-web", stringParam = "", timestamp, signState) {
5632
+ const payloadArray = this.cryptoProcessor.buildPayloadArray(dValue, mValue, a1Value, xsecAppid, stringParam, timestamp, signState);
5633
+ const xorResult = this.cryptoProcessor.bitOps.xorTransformArray(payloadArray);
5634
+ return this.cryptoProcessor.b64encoder.encodeX3(xorResult.subarray(0, this.config.PAYLOAD_LENGTH));
5635
+ }
5636
+ /**
5637
+ * 生成请求签名(支持 GET 和 POST)
5638
+ *
5639
+ * 字段口径与前端 `seccore_signv2` 一致:x0~x2 取自模板,x3 为 mnsv2 签名,
5640
+ * x4 为请求体类型(POST 为 `'object'`、GET 为 `''`),x5 为签名内容串的 MD5;
5641
+ * 会话带 webSsk 时再加 x6/x7(会话 SSK 证明,见 {@link buildSskProof})。
5642
+ *
5643
+ * @param method - HTTP 请求方法,'GET' 或 'POST'
5644
+ * @param uri - 请求 URI 路径(可以是完整 URL,会自动提取路径部分)
5645
+ * @param a1Value - Cookie 中的 a1 值
5646
+ * @param xsecAppid - 应用 ID,默认为 'xhs-pc-web'
5647
+ * @param payload - GET 请求的查询参数或 POST 请求的请求体
5648
+ * @param timestamp - 可选的时间戳(秒),不传则使用当前时间
5649
+ * @param session - 可选的会话管理器,用于生成更真实的签名;带 webSsk 时生成 x6/x7
5650
+ * @returns x-s 签名字符串
5651
+ */
5652
+ signXs(method, uri, a1Value, xsecAppid = "xhs-pc-web", payload = null, timestamp, session) {
5653
+ validateSignatureParams(method, uri, a1Value);
5654
+ uri = extractUri(uri);
5655
+ const signatureData = { ...this.config.SIGNATURE_DATA_TEMPLATE };
5656
+ const contentString = this.buildContentString(method, uri, payload);
5657
+ const dValue = this.generateDValue(contentString);
5658
+ const mValue = method === "GET" ? dValue : createHash("md5").update(uri, "utf8").digest("hex");
5659
+ const signState = session ? session.getCurrentState(contentString) : void 0;
5660
+ signatureData.x3 = this.config.X3_PREFIX + this.buildSignature(dValue, mValue, a1Value, xsecAppid, contentString, timestamp, signState);
5661
+ signatureData.x4 = method === "POST" ? "object" : "";
5662
+ signatureData.x5 = dValue;
5663
+ const proof = session ? buildSskProof(dValue, session.webSsk) : null;
5664
+ if (proof) {
5665
+ signatureData.x6 = proof.x6;
5666
+ signatureData.x7 = proof.x7;
5667
+ }
5668
+ const jsonStr = JSON.stringify(signatureData);
5669
+ return this.config.XYS_PREFIX + this.cryptoProcessor.b64encoder.encode(jsonStr);
5670
+ }
5671
+ /**
5672
+ * 生成 XYW_ 格式签名(AES-128-CBC 加密)
5673
+ *
5674
+ * 使用独立于 signXs 的加密路径。签名格式由调用方显式选择,
5675
+ * 不根据登录态或 HTTP 406/461 自动切换。
5676
+ *
5677
+ * @param method - HTTP 请求方法,'GET' 或 'POST'
5678
+ * @param uri - 请求 URI 路径(可以是完整 URL)
5679
+ * @param a1Value - Cookie 中的 a1 值
5680
+ * @param xsecAppid - 应用 ID,默认为 'xhs-pc-web'
5681
+ * @param payload - GET 请求的查询参数或 POST 请求的请求体
5682
+ * @param timestamp - 可选的时间戳(秒),不传则使用当前时间
5683
+ * @param _session - 未使用,仅为保持与 signXs 的接口一致
5684
+ * @returns XYW_ 格式签名字符串
5685
+ */
5686
+ signXyw(method, uri, a1Value, xsecAppid = "xhs-pc-web", payload = null, timestamp, _session) {
5687
+ validateSignatureParams(method, uri, a1Value);
5688
+ uri = extractUri(uri);
5689
+ const payloadHex = buildXywPayloadHex({
5690
+ fullUri: this.buildContentString(method, uri, payload),
5691
+ a1Value,
5692
+ timestampMs: String(this.getXT(timestamp)),
5693
+ config: this.config
5694
+ });
5695
+ const xywData = {
5696
+ signSvn: this.config.XYW_SIGN_SVN,
5697
+ signType: this.config.XYW_SIGN_TYPE,
5698
+ appId: xsecAppid,
5699
+ signVersion: this.config.XYW_SIGN_VERSION,
5700
+ payload: payloadHex
5701
+ };
5702
+ const xywJson = JSON.stringify(xywData);
5703
+ return this.config.XYW_PREFIX + Buffer.from(xywJson, "utf-8").toString("base64");
5704
+ }
5705
+ /**
5706
+ * 生成 x-s-common 签名
5707
+ * @param cookieDict - Cookie 字典对象或 Cookie 字符串
5708
+ * @param options - 同一会话的 b1;省略时保留原有生成行为
5709
+ * @returns x-s-common 签名字符串
5710
+ */
5711
+ signXsCommon(cookieDict, options = {}) {
5712
+ const parsedCookies = this.parseCookies(cookieDict);
5713
+ return new XsCommonSigner(this.config).sign(parsedCookies, options);
5714
+ }
5715
+ /**
5716
+ * 生成 GET 请求签名(便捷方法)
5717
+ * @param uri - 请求 URI 路径
5718
+ * @param a1Value - Cookie 中的 a1 值
5719
+ * @param xsecAppid - 应用 ID,默认为 'xhs-pc-web'
5720
+ * @param params - 查询参数对象
5721
+ * @param timestamp - 可选的时间戳(秒)
5722
+ * @param session - 可选的会话管理器
5723
+ * @returns x-s 签名字符串
5724
+ */
5725
+ signXsGet(uri, a1Value, xsecAppid = "xhs-pc-web", params = null, timestamp, session) {
5726
+ validateGetSignatureParams(uri, a1Value);
5727
+ return this.signXs("GET", uri, a1Value, xsecAppid, params, timestamp, session);
5728
+ }
5729
+ /**
5730
+ * 生成 POST 请求签名(便捷方法)
5731
+ * @param uri - 请求 URI 路径
5732
+ * @param a1Value - Cookie 中的 a1 值
5733
+ * @param xsecAppid - 应用 ID,默认为 'xhs-pc-web'
5734
+ * @param payload - 请求体对象
5735
+ * @param timestamp - 可选的时间戳(秒)
5736
+ * @param session - 可选的会话管理器
5737
+ * @returns x-s 签名字符串
5738
+ */
5739
+ signXsPost(uri, a1Value, xsecAppid = "xhs-pc-web", payload = null, timestamp, session) {
5740
+ validatePostSignatureParams(uri, a1Value);
5741
+ return this.signXs("POST", uri, a1Value, xsecAppid, payload, timestamp, session);
5742
+ }
5743
+ /**
5744
+ * 生成 x-s-common 签名的便捷包装方法
5745
+ * @param cookieDict - Cookie 字典对象或 Cookie 字符串
5746
+ * @param options - 同一会话的 b1;省略时保留原有生成行为
5747
+ * @returns x-s-common 签名字符串
5748
+ */
5749
+ signXsc(cookieDict, options = {}) {
5750
+ validateXsCommonParams(cookieDict);
5751
+ return this.signXsCommon(cookieDict, options);
5752
+ }
5753
+ /**
5754
+ * 解密 x3 签名(Base64 格式)
5755
+ * @param x3Signature - x3 签名字符串
5756
+ * @returns 解密后的字节数组
5757
+ */
5758
+ decodeX3(x3Signature) {
5759
+ if (x3Signature.startsWith(this.config.X3_PREFIX)) x3Signature = x3Signature.slice(this.config.X3_PREFIX.length);
5760
+ const decodedBytes = this.cryptoProcessor.b64encoder.decodeX3(x3Signature);
5761
+ return this.cryptoProcessor.bitOps.xorTransformArray(Array.from(decodedBytes));
5762
+ }
5763
+ /**
5764
+ * 解密完整的 XYS 签名
5765
+ * @param xsSignature - x-s 签名字符串
5766
+ * @returns 解密后的签名数据对象
5767
+ * @throws 如果 JSON 解析失败则抛出错误
5768
+ */
5769
+ decodeXs(xsSignature) {
5770
+ if (xsSignature.startsWith(this.config.XYS_PREFIX)) xsSignature = xsSignature.slice(this.config.XYS_PREFIX.length);
5771
+ const jsonString = this.cryptoProcessor.b64encoder.decode(xsSignature);
5772
+ try {
5773
+ return JSON.parse(jsonString);
5774
+ } catch (e) {
5775
+ throw new Error(`Invalid signature: JSON decode failed - ${e}`);
5776
+ }
5777
+ }
5778
+ /**
5779
+ * 构建带查询参数的完整 URL(便捷方法)
5780
+ * @param baseUrl - 基础 URL
5781
+ * @param params - 可选的查询参数对象
5782
+ * @returns 完整的 URL 字符串
5783
+ */
5784
+ buildUrl(baseUrl, params) {
5785
+ return buildUrl(baseUrl, params);
5786
+ }
5787
+ /**
5788
+ * 构建 POST 请求的 JSON 请求体字符串(便捷方法)
5789
+ * @param payload - 请求体对象
5790
+ * @returns JSON 字符串
5791
+ */
5792
+ buildJsonBody(payload) {
5793
+ return JSON.stringify(payload);
5794
+ }
5795
+ /**
5796
+ * 生成 HTTP 请求头中的 x-b3-traceid
5797
+ * @returns x-b3-traceid 字符串
5798
+ */
5799
+ getB3TraceId() {
5800
+ return this.randomGenerator.generateB3TraceId();
5801
+ }
5802
+ /**
5803
+ * 生成 HTTP 请求头中的 x-xray-traceid
5804
+ * @param timestamp - 可选的时间戳(毫秒)
5805
+ * @param seq - 可选的序列号
5806
+ * @returns x-xray-traceid 字符串
5807
+ */
5808
+ getXrayTraceId(timestamp, seq) {
5809
+ return this.randomGenerator.generateXrayTraceId(timestamp, seq);
5810
+ }
5811
+ /**
5812
+ * 生成搜索接口所需的 search_id
5813
+ * @returns Base36 编码的搜索 ID
5814
+ */
5815
+ getSearchId() {
5816
+ return this.randomGenerator.generateSearchId();
5817
+ }
5818
+ /**
5819
+ * 生成搜索接口所需的 request_id
5820
+ * @returns 格式为 "{random}-{timestamp_ms}" 的字符串
5821
+ */
5822
+ getSearchRequestId() {
5823
+ return this.randomGenerator.generateSearchRequestId();
5824
+ }
5825
+ /**
5826
+ * 生成 a1 Cookie 值
5827
+ * @returns 52 字符的 a1 值
5828
+ */
5829
+ static generateA1() {
5830
+ return RandomGenerator.generateA1();
5831
+ }
5832
+ /**
5833
+ * 根据 a1 值生成 web_id
5834
+ * @param a1 - a1 Cookie 值
5835
+ * @returns 32 字符的十六进制 web_id
5836
+ */
5837
+ static generateWebId(a1) {
5838
+ return RandomGenerator.generateWebId(a1);
5839
+ }
5840
+ /**
5841
+ * 生成 x-t 请求头值(Unix 时间戳,毫秒)
5842
+ * @param timestamp - 可选的时间戳(秒),不传则使用当前时间
5843
+ * @returns Unix 时间戳(毫秒)
5844
+ */
5845
+ getXT(timestamp) {
5846
+ if (timestamp === void 0) timestamp = Date.now() / 1e3;
5847
+ return Math.floor(timestamp * 1e3);
5848
+ }
5849
+ /**
5850
+ * 将 Cookie 解析为字典格式
5851
+ * @param cookies - Cookie 字典对象或 Cookie 字符串
5852
+ * @returns Cookie 字典对象
5853
+ */
5854
+ parseCookies(cookies) {
5855
+ if (typeof cookies === "string") {
5856
+ const result = {};
5857
+ const pairs = cookies.split(";");
5858
+ for (const pair of pairs) {
5859
+ const trimmed = pair.trim();
5860
+ const eqIndex = trimmed.indexOf("=");
5861
+ if (eqIndex > 0) {
5862
+ const key = trimmed.substring(0, eqIndex).trim();
5863
+ result[key] = trimmed.substring(eqIndex + 1).trim();
5864
+ }
5865
+ }
5866
+ return result;
5867
+ }
5868
+ return cookies;
5869
+ }
5870
+ /**
5871
+ * 生成包含签名和追踪 ID 的完整请求头
5872
+ * @param method - HTTP 请求方法,'GET' 或 'POST'
5873
+ * @param uri - 请求 URI 路径
5874
+ * @param cookies - Cookie 字典对象或 Cookie 字符串(必须包含 a1)
5875
+ * @param xsecAppid - 应用 ID,默认为 'xhs-pc-web'
5876
+ * @param params - GET 请求的查询参数(POST 请求时不可用)
5877
+ * @param payload - POST 请求的请求体(GET 请求时不可用)
5878
+ * @param timestamp - 可选的时间戳(秒)
5879
+ * @param session - 可选的会话管理器
5880
+ * @returns 包含 x-s、x-s-common、x-t、x-b3-traceid、x-xray-traceid 的请求头对象
5881
+ * @throws 如果 GET 请求使用 payload 或 POST 请求使用 params 则抛出错误
5882
+ * @throws 如果 cookies 中缺少 a1 则抛出错误
5883
+ */
5884
+ signHeaders(method, uri, cookies, xsecAppid = "xhs-pc-web", params, payload, timestamp, session, signFormat = "xys", userId, xRap = false) {
5885
+ if (timestamp === void 0) timestamp = Date.now() / 1e3;
5886
+ const methodUpper = method.toUpperCase();
5887
+ let requestData;
5888
+ if (methodUpper === "GET") {
5889
+ if (payload !== void 0 && payload !== null) throw new Error("GET requests must use 'params', not 'payload'");
5890
+ requestData = params ?? null;
5891
+ } else if (methodUpper === "POST") {
5892
+ if (params !== void 0 && params !== null) throw new Error("POST requests must use 'payload', not 'params'");
5893
+ requestData = payload ?? null;
5894
+ } else throw new Error(`Unsupported method: ${method}`);
5895
+ if (signFormat !== "xys" && signFormat !== "xyw") throw new Error(`Unsupported signFormat=${JSON.stringify(signFormat)}; expected 'xys' or 'xyw'`);
5896
+ const cookieDict = this.parseCookies(cookies);
5897
+ const a1Value = cookieDict.a1;
5898
+ if (!a1Value) throw new Error("Missing 'a1' in cookies");
5899
+ const xS = signFormat === "xyw" ? this.signXyw(methodUpper, uri, a1Value, xsecAppid, requestData, timestamp) : this.signXs(methodUpper, uri, a1Value, xsecAppid, requestData, timestamp, session);
5900
+ const xSCommon = this.signXsCommon(cookieDict, { b1: session?.b1 });
5901
+ const xT = this.getXT(timestamp);
5902
+ const xB3Traceid = this.getB3TraceId();
5903
+ const xXrayTraceid = this.getXrayTraceId(Math.floor(timestamp * 1e3));
5904
+ const headers = {
5905
+ "x-s": xS,
5906
+ "x-s-common": xSCommon,
5907
+ "x-t": String(xT),
5908
+ "x-b3-traceid": xB3Traceid,
5909
+ "x-xray-traceid": xXrayTraceid,
5910
+ "x-mns": "unload",
5911
+ "xy-direction": String(getShardingKey(userId))
5912
+ };
5913
+ if (xRap) headers["x-rap-param"] = xRapParam("//edith.xiaohongshu.com" + extractUri(uri), requestData || {});
5914
+ return headers;
5915
+ }
5916
+ /**
5917
+ * 生成 GET 请求的完整请求头(便捷方法)
5918
+ * @param uri - 请求 URI 路径
5919
+ * @param cookies - Cookie 字典对象或 Cookie 字符串(必须包含 a1)
5920
+ * @param xsecAppid - 应用 ID,默认为 'xhs-pc-web'
5921
+ * @param params - 查询参数对象
5922
+ * @param timestamp - 可选的时间戳(秒)
5923
+ * @param session - 可选的会话管理器
5924
+ * @returns 包含签名和追踪 ID 的请求头对象
5925
+ */
5926
+ signHeadersGet(uri, cookies, xsecAppid = "xhs-pc-web", params, timestamp, session, signFormat = "xys", userId, xRap = false) {
5927
+ return this.signHeaders("GET", uri, cookies, xsecAppid, params, void 0, timestamp, session, signFormat, userId, xRap);
3466
5928
  }
5929
+ /**
5930
+ * 生成 POST 请求的完整请求头(便捷方法)
5931
+ * @param uri - 请求 URI 路径
5932
+ * @param cookies - Cookie 字典对象或 Cookie 字符串(必须包含 a1)
5933
+ * @param xsecAppid - 应用 ID,默认为 'xhs-pc-web'
5934
+ * @param payload - 请求体对象
5935
+ * @param timestamp - 可选的时间戳(秒)
5936
+ * @param session - 可选的会话管理器
5937
+ * @returns 包含签名和追踪 ID 的请求头对象
5938
+ */
5939
+ signHeadersPost(uri, cookies, xsecAppid = "xhs-pc-web", payload, timestamp, session, signFormat = "xys", userId, xRap = false) {
5940
+ return this.signHeaders("POST", uri, cookies, xsecAppid, void 0, payload, timestamp, session, signFormat, userId, xRap);
5941
+ }
5942
+ };
5943
+ /**
5944
+ * 小红书 Web 端的**运行时版本号** —— 真机抓包实测,比库默认值新。
5945
+ *
5946
+ * 2026-10-12 对真实游客会话抓包:`webBuild` cookie 是 `6.57.2`,webprofile 请求体里的
5947
+ * `sdkVersion` 是 `"4.4.3"`。库默认是 `6.56.3` / `4.3.5`(上游 2026-09-25 测)——
5948
+ * 站点构建号随发版递增,所以这里钉更新的那组。
5949
+ *
5950
+ * 两个值的去向不同,**都不是只影响一个地方**:
5951
+ * - `DATA_WEB_BUILD` → 游客 CK 的 `webBuild` cookie;而 x-s-common 的 `x4` 自上游 v1.2.0
5952
+ * 起会**优先读这个 cookie**(见 `xhshow/core/commonSign.ts`),所以它同时决定了请求头里的 x4。
5953
+ * - `DATA_SDK_VERSION` → webprofile(gid)请求体的 `sdkVersion`。
5954
+ */
5955
+ var WEB_BUILD = "6.57.2";
5956
+ var SDK_VERSION = "4.4.3";
5957
+ /**
5958
+ * 初始化小红书签名配置。
5959
+ *
5960
+ * ## 为什么不再覆盖那两个模板
5961
+ *
5962
+ * 此前这里用对象字面量整个替换 `SIGNATURE_DATA_TEMPLATE` 与 `SIGNATURE_XSCOMMON_TEMPLATE`,
5963
+ * 用来把签名世代手工抬到真机的 `4.4.3` / `6.57.2`。**上游 v1.2.0 已经自己对齐了**
5964
+ * (x-s 的 `x0` 与 x-s-common 的 `x1` 默认值都是 `4.4.3`),所以那几处 override 已成冗余。
5965
+ *
5966
+ * 更要紧的是**留着它们会坏**:`SIGNATURE_XSCOMMON_TEMPLATE.x12` 是个 **getter**
5967
+ * (每次求值刷新时间戳 `${Date.now()};<会话起点>`),用对象字面量整个替换会把它求值成
5968
+ * 一个固定字符串 —— 之后每个请求的 x-s-common 都带同一个时间戳,签名不再随时间滚动。
5969
+ *
5970
+ * ## 为什么不调 `forUserAgent()`
5971
+ *
5972
+ * 上游 v1.2.0 新增的 `forUserAgent(ua)` 按 UA 派生 `s0` / `x2` / `DATA_PLATFORM` /
5973
+ * `PUBLIC_USERAGENT`,看着正合适 —— 但**无 UA 时它会退回 `'PC'`**,而本函数是模块级
5974
+ * 单例用的、拿不到调用方的 UA;真实浏览器发的是 `"Windows"`。保持库默认的 Windows
5975
+ * 那组反而正确。要真按平台派生,得先把 UA 从 client 串到这里,那是另一件事。
5976
+ */
5977
+ var createXiaohongshuCryptoConfig = () => new CryptoConfig().withOverrides({
5978
+ DATA_WEB_BUILD: WEB_BUILD,
5979
+ DATA_SDK_VERSION: SDK_VERSION
3467
5980
  });
3468
5981
  /** @see https://github.com/Cialle/RedCrack */
3469
5982
  /** 小红书 Web 端游客会话初始化使用的浏览器请求头。 */
@@ -3482,8 +5995,22 @@ var GUEST_HEADERS$1 = {
3482
5995
  "sec-fetch-site": "same-site",
3483
5996
  "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0"
3484
5997
  };
3485
- /** 生成 a1 随机段时使用的 Web 端字符集。 */
3486
- var COOKIE_RANDOM_CHARS$1 = "abcdefghijklmnopqrstuvwxyz1234567890";
5998
+ /**
5999
+ * 生成 a1,以及由 a1 派生的 webId —— 直接用 `./xhshow` 的纯算实现。
6000
+ *
6001
+ * 逆向实证(xhshow-ts dev.ts 里一份真机 a1
6002
+ * `196d54a86df7…ielezi1r50000628328`):a1 结构是 `hex(ts) + rand + "50000" + crc32`,
6003
+ * 常量段是 **`50000`**。amagi 旧的手搓版写成 `"5000"`(少一个 0),a1 内部 crc 因此是
6004
+ * 对错误结构算的 —— 虽也能换到 web_session,但疑为被风控降权(search 回 -104、homefeed
6005
+ * 回 461)。改用 `Xhshow.generateA1 / generateWebId` 对齐真实格式。
6006
+ */
6007
+ var generateA1AndWebId$1 = () => {
6008
+ const a1 = Xhshow.generateA1();
6009
+ return {
6010
+ a1,
6011
+ webId: Xhshow.generateWebId(a1)
6012
+ };
6013
+ };
3487
6014
  /** 将 Cookie Jar 序列化为可直接写入 HTTP Cookie 请求头的字符串。 */
3488
6015
  var toCookieString$1 = (cookies) => Object.entries(cookies).map(([key, value]) => `${key}=${value}`).join("; ");
3489
6016
  /** 将响应头中的 Set-Cookie 字段合并到当前游客会话的 Cookie Jar。 */
@@ -3495,24 +6022,6 @@ var updateCookiesFromResponse$1 = (cookies, setCookie) => {
3495
6022
  if (separatorIndex > 0) cookies[nameValue.slice(0, separatorIndex).trim()] = nameValue.slice(separatorIndex + 1).trim();
3496
6023
  }
3497
6024
  };
3498
- /** 按小红书 Web 端规则生成 a1,以及由 a1 派生的 webId。 */
3499
- var generateA1AndWebId$1 = () => {
3500
- const source = `${Date.now().toString(16)}${Array.from(randomBytes(30), (byte) => COOKIE_RANDOM_CHARS$1[byte % 36]).join("")}5000`;
3501
- const a1 = `${source}${crc32$1(source)}`.slice(0, 52);
3502
- return {
3503
- a1,
3504
- webId: createHash("md5").update(a1).digest("hex")
3505
- };
3506
- };
3507
- /** 计算与浏览器端实现兼容的无符号 CRC32 校验值。 */
3508
- var crc32$1 = (input) => {
3509
- let value = 4294967295;
3510
- for (const byte of Buffer.from(input)) {
3511
- value ^= byte;
3512
- for (let bit = 0; bit < 8; bit += 1) value = value & 1 ? value >>> 1 ^ 3988292384 : value >>> 1;
3513
- }
3514
- return (value ^ 4294967295) >>> 0;
3515
- };
3516
6025
  /** 从 scripting 接口返回的 VMP 数据中解码 websectiga Cookie。 */
3517
6026
  var generateWebsectiga$1 = (payload) => {
3518
6027
  const bMatch = payload.match(/"b":"(.*?)",/);
@@ -3615,18 +6124,70 @@ var createXiaohongshuGuestCookie$1 = async (send, requestConfig) => {
3615
6124
  return toCookieString$1(cookies);
3616
6125
  };
3617
6126
  /**
6127
+ * 游客 Cookie 进程级缓存:键为 UA、值为铸造好的 Cookie 串。
6128
+ *
6129
+ * 铸造一次要走 scripting → webprofile → activate 三个真实会话请求,开销不小;匿名
6130
+ * 游客会话在进程内可复用,故按 UA 缓存(指纹由 UA 参与生成,不同 UA 视作不同会话)。
6131
+ */
6132
+ var guestCookieCache = /* @__PURE__ */ new Map();
6133
+ /** in-flight 去重:并发首调共享同一次铸造,避免并发打出多份会话初始化 */
6134
+ var inflightGuestMint = /* @__PURE__ */ new Map();
6135
+ /** 缓存条目上限,防异常 UA 把缓存撑爆 */
6136
+ var GUEST_CACHE_MAX_ENTRIES = 32;
6137
+ /**
6138
+ * 端点 `prepare` 钩子:**下游未传有效用户 Cookie(无 a1)时,用游客 Cookie 解析接口**。
6139
+ *
6140
+ * 判据沿用既有约定(见 fetcher 测试「cookie 带 a1 时不再跑 guest-cookie 前置请求」):
6141
+ * cookie 里有 a1 视作有效用户凭证、原样放行;没有就铸造一份游客 Cookie 顶上。铸造走
6142
+ * `ctx.send`(而非自带 transport)—— 游客会话初始化的三个请求因此与主请求同一出口
6143
+ * (代理 / agent / 超时 / 出口 IP 一致);小红书游客会话与出口 IP、指纹绑定,同出口才稳,
6144
+ * 这也是不走平台级 `ensureCookie`(拿不到 `ctx.send`)而留在 `prepare` 的原因。
6145
+ *
6146
+ * 铸造结果按 UA 进程级缓存并对并发去重:重复调用只铸一次。会话失效时主请求会被 judge
6147
+ * 判失败,调用方可 {@link resetXiaohongshuGuestCookieCache} 后重试。
6148
+ * @param ctx - 端点执行上下文(取 `cookie` / `userAgent` / `requestConfig` / `send`)
6149
+ * @param mint - 铸造函数,默认 {@link createXiaohongshuGuestCookie};测试可注入假铸造
6150
+ * @returns 需要升级时返回 `{ cookie }`,否则 `{}`(保持原 cookie)
6151
+ */
6152
+ var prepareXiaohongshuGuestCookie = async (ctx, mint = createXiaohongshuGuestCookie$1) => {
6153
+ if (getCookieValue(ctx.cookie, "a1")) return {};
6154
+ const key = ctx.userAgent || "";
6155
+ const cached = guestCookieCache.get(key);
6156
+ if (cached !== void 0) return { cookie: cached };
6157
+ let pending = inflightGuestMint.get(key);
6158
+ if (pending === void 0) {
6159
+ pending = (async () => {
6160
+ try {
6161
+ const cookie = await mint(ctx.send, ctx.requestConfig);
6162
+ guestCookieCache.set(key, cookie);
6163
+ if (guestCookieCache.size > GUEST_CACHE_MAX_ENTRIES) {
6164
+ const oldest = guestCookieCache.keys().next().value;
6165
+ if (oldest !== void 0) guestCookieCache.delete(oldest);
6166
+ }
6167
+ return cookie;
6168
+ } finally {
6169
+ inflightGuestMint.delete(key);
6170
+ }
6171
+ })();
6172
+ inflightGuestMint.set(key, pending);
6173
+ }
6174
+ return { cookie: await pending };
6175
+ };
6176
+ /**
3618
6177
  * 小红书签名算法。
3619
6178
  *
3620
- * 包装 `@ikenxuan/xhshow-ts` 的 `Xhshow` 实例,提供 GET / POST / X-S-Common /
6179
+ * 包装 `./xhshow` 的 `Xhshow` 实例,提供 GET / POST / X-S-Common /
3621
6180
  * X-T / X-B3-Traceid / X-Xray-Traceid / X-Rap-Param / 搜索 ID / 搜索 request_id
3622
6181
  * 的生成函数。a1 从 cookie 取值的逻辑用 `contracts/cookie.ts` 的 `getCookieValue`。
3623
6182
  *
3624
6183
  * `Xhshow` 实例是模块级单例,行为与性能无额外开销。
3625
6184
  *
3626
6185
  * 上游 2026-02 之后的新协议由这里落地:
3627
- * - **XYW_ 格式**(`generateXSGetXyw` / `generateXSPostXyw`):数据获取类接口
3628
- * (`user_posted`、`user/otherinfo` 等)自 2026-03 起以 HTTP 406 拒绝传统 XYS_
3629
- * 格式,改用基于 AES-128-CBC 的 XYW_ 签名。x-s 值以 `XYW_` 开头。
6186
+ * - **XYW_ 格式**(`generateXSGetXyw` / `generateXSPostXyw`):AES-128-CBC 的另一套 x-s
6187
+ * 信封,值以 `XYW_` 开头。曾按「数据获取类接口自 2026-03 起拒收 XYS_」全线切到它 ——
6188
+ * 该说法**未经线上验证、且已被真机实测推翻**(2026-10-11:同一游客 cookie 下
6189
+ * `user_posted` 用 XYW_ 回 HTTP 461 空 data、用 XYS_ 回 200 + 真数据)。当前
6190
+ * **所有端点都用 XYS_**,本格式保留实现与形状检查,但无端点引用。
3630
6191
  * - **x-rap-param**(`generateXRapParam`):feed / 搜索 / 发布类接口的额外校验头。
3631
6192
  * - **x-xray-traceid**(`generateXrayTraceid`):追踪头,随现代浏览器每次请求下发。
3632
6193
  * - **search request_id**(`getSearchRequestId`):搜索接口所需的 `{random}-{ts_ms}`。
@@ -3654,8 +6215,8 @@ var generateXSPost = (path, a1Cookie, clientType = "xhs-pc-web", body = {}) => c
3654
6215
  /**
3655
6216
  * 生成 GET 请求的 XYW_ 格式 X-S 签名(AES-128-CBC)。
3656
6217
  *
3657
- * 数据获取类接口(`user_posted` 等)自 2026-03 起以 HTTP 406 拒绝 XYS_ 格式,
3658
- * 需改用本函数。返回值以 `XYW_` 开头,走独立于 `signXsGet` 的加密路径。
6218
+ * **当前无端点使用**:那轮「数据获取类接口拒收 XYS_、需换本函数」的切换已被真机实测
6219
+ * 推翻(见文件头),端点一律走 XYS_。返回值以 `XYW_` 开头,走独立于 `signXsGet` 的加密路径。
3659
6220
  * @param path - API 路径,如 `/api/sns/web/v1/user_posted`
3660
6221
  * @param a1Cookie - a1 cookie 值
3661
6222
  * @param clientType - 客户端类型,默认 `'xhs-pc-web'`
@@ -3722,9 +6283,10 @@ var getSearchId = () => client.getSearchId();
3722
6283
  * ## XYS_ 与 XYW_ 两套 x-s 格式
3723
6284
  *
3724
6285
  * - **XYS_(传统)**:`xhsGetSigner` / `xhsPostSigner`,x-s 用 `signXsGet` / `signXsPost`。
3725
- * - **XYW_(2026-03 之后)**:`xhsGetXywSigner` / `xhsPostXywSigner`,数据获取类接口
3726
- * (`user_posted` 等)自 2026-03 起以 HTTP 406 拒绝 XYS_,x-s 改用以 `XYW_` 开头的
3727
- * AES-128-CBC 签名。
6286
+ * - **XYW_(2026-03 之后)**:`xhsGetXywSigner` / `xhsPostXywSigner`,以 `XYW_` 开头的
6287
+ * AES-128-CBC 签名。那轮「数据获取类接口自 2026-03 起拒收 XYS_」的切换**未经线上验证、
6288
+ * 且已被真机实测推翻**(2026-10-11:`user_posted` 用 XYW_ 回 461 空 data、XYS_ 回
6289
+ * 200 + 真数据),故这条路径**当前无端点使用** —— 实现与形状检查保留,便于日后平台再变。
3728
6290
  *
3729
6291
  * 签名路径取自 `spec.signPath`(build 里填 `apiPath`),不是 `spec.url`:小红书签名只用
3730
6292
  * 接口路径,与完整 URL 无关。x-rap-param 例外,它要 host + path(见 {@link rapApiOf})。
@@ -3779,7 +6341,7 @@ var xhsPostXywSigner = (spec, ctx) => {
3779
6341
  * `x-s` + `x-s-common` + `x-t` + `x-xray-traceid` 四个签名头。
3780
6342
  *
3781
6343
  * @param method - GET 签 query、POST 签 body
3782
- * @param protocol - `'xys'` 传统 / `'xyw'` 绕 406(2026-03 后数据接口必用)
6344
+ * @param protocol - `'xys'` 传统 / `'xyw'` XYW_ 信封(当前无端点使用,见文件头)
3783
6345
  */
3784
6346
  var xs = (method, protocol) => ({
3785
6347
  phase: "sign",
@@ -3804,7 +6366,7 @@ var rap = () => ({
3804
6366
  apply: (spec) => withHeader(spec, "x-rap-param", generateXRapParam(rapApiOf(spec), spec.body ?? {}))
3805
6367
  });
3806
6368
  //#endregion
3807
- //#region ../amagi/packages/core/dist/src-Bu8lly0v.mjs
6369
+ //#region ../amagi/packages/core/dist/src-4GqoEcVR.mjs
3808
6370
  var import_protobufjs = /* @__PURE__ */ __toESM(require_protobufjs(), 1);
3809
6371
  /**
3810
6372
  * B站评论区类型代码(`comments` / `commentReplies` 的 `type` 参数)。
@@ -6811,12 +9373,23 @@ var createXiaohongshuConfig = (cookie, requestConfig) => {
6811
9373
  /**
6812
9374
  * 小红书平台默认响应判定。
6813
9375
  *
6814
- * 两条行为:
9376
+ * 三条行为:
6815
9377
  * - **HTML 反爬页判为 `risk` / `ANTIBOT_PAGE`**:字符串响应(含 `<html>` 的反爬页
6816
9378
  * 与纯文本拦截页)判失败,原始 HTML 留在 `error.raw`
6817
9379
  * (`createClient({ debug: true })` 时才填,见 `ClientOptions.debug`)。
6818
9380
  * - **不把一切失败归一化为 500**:judge 只分类,失败信封由 runtime 统一从原始响应
6819
9381
  * 提取 `platform.code` / `message`。
9382
+ * - **HTTP 461 判为 `risk` / `RISK_CONTROL`**:461 是小红书专属的风控码,必须在
9383
+ * {@link verdictFromHttpStatus} **之前**拦下 —— 那个通用 helper 只特判
9384
+ * 401/403/404/408/429,461 会落进它的兜底分支,被报成 `kind: 'unknown'` /
9385
+ * `PLATFORM_ERROR` / `retryable: false`:把一个明确的风控状态说成「未知平台错误」,
9386
+ * 可重试性也与同为风控的 403 相左。它属于平台专属码,因此留在本 judge 而不下沉到
9387
+ * 共享 helper(下沉会让其它三个平台也认领一个它们不会遇到的码)。
9388
+ *
9389
+ * 461 的典型面目是**伪装成成功的空壳**:`HTTP 461` 配
9390
+ * `{"code": 0, "success": true, "msg": "成功", "data": {}}` —— 平台不告诉你原因,
9391
+ * 只看响应体像是成功了。实测(2026-10-12 真机抓包,游客会话):`comment/page`
9392
+ * 首页正常、**翻页**必回此形态。
6820
9393
  *
6821
9394
  * 成功判定:`code === 0`。没有 `code` 字段的 JSON 响应(如 `emojiList` 之外的部分
6822
9395
  * 端点)视为成功,交给 normalize 处理 —— 缺失 code 不判失败。
@@ -6830,6 +9403,12 @@ var xiaohongshuJudge = (raw, http) => {
6830
9403
  if (typeof raw === "object" && raw !== null && "code" in raw) {
6831
9404
  if (raw.code !== 0) return { ok: false };
6832
9405
  }
9406
+ if (http.status === 461) return {
9407
+ ok: false,
9408
+ kind: "risk",
9409
+ code: "RISK_CONTROL",
9410
+ retryable: true
9411
+ };
6833
9412
  return verdictFromHttpStatus(http.status) ?? { ok: true };
6834
9413
  };
6835
9414
  /**
@@ -6852,8 +9431,8 @@ var xiaohongshuJudge = (raw, http) => {
6852
9431
  * - `'xhs-get-trace'`:GET + `x-b3-traceid`(userNoteList 专用)
6853
9432
  * - `'xhs-post-rap'`:POST + `x-rap-param`(feed / 搜索 / 发布类接口的额外校验)
6854
9433
  *
6855
- * XYW_(2026-03 之后)路径 —— 数据获取类接口以 HTTP 406 拒绝 XYS_,x-s 改用以 `XYW_`
6856
- * 开头的 AES-128-CBC 签名:
9434
+ * XYW_(2026-03 之后)路径 —— 以 `XYW_` 开头的 AES-128-CBC 签名。那轮「数据获取类接口
9435
+ * 拒收 XYS_」的切换已被真机实测推翻(见 `./steps.ts` 文件头),三个名字保留但无端点引用:
6857
9436
  * - `'xhs-get-xyw'`:GET,x-s 用 `signXyw`
6858
9437
  * - `'xhs-post-xyw'`:POST,x-s 用 `signXyw`
6859
9438
  * - `'xhs-get-xyw-trace'`:GET XYW + `x-b3-traceid`(userNoteList 专用)
@@ -7672,10 +10251,14 @@ var resolveTarget = (raw, maxPageSize) => {
7672
10251
  *
7673
10252
  * **不 catch 任何异常** —— `runPage` 抛出的东西直接往外传,由 `execute`
7674
10253
  * 唯一的那处 catch 归因。
10254
+ *
10255
+ * **中途失败不丢已有页**:只要已经取到过至少一页,后续某页失败就返回**部分结果**,
10256
+ * 而不是整体失败 —— 丢掉已取到的条目毫无道理,「比目标少」与 `hasMore: false`
10257
+ * 提前收尾本是同一种结局。**第一页就失败仍是整体失败**(没有任何部分可言)。
7675
10258
  * @param def - 翻页声明
7676
10259
  * @param params - 校验后的参数
7677
10260
  * @param runPage - 跑一页的回调
7678
- * @returns 累积结果,或第一个失败页的错误
10261
+ * @returns 累积结果(页数可能少于目标),或第一页的错误
7679
10262
  */
7680
10263
  var runPaginated = async (def, params, runPage) => {
7681
10264
  const limitKey = def.limitParam ?? "number";
@@ -7689,6 +10272,8 @@ var runPaginated = async (def, params, runPage) => {
7689
10272
  let current = params;
7690
10273
  let lastPage;
7691
10274
  let isFirst = true;
10275
+ /** 已经跑完的页数:中断现场据此标页码,也用来区分「第一页就失败」 */
10276
+ let donePages = 0;
7692
10277
  while (collected.length < target) {
7693
10278
  const pageSize = Math.min(target - collected.length, def.maxPageSize);
7694
10279
  const pageParams = {
@@ -7696,10 +10281,21 @@ var runPaginated = async (def, params, runPage) => {
7696
10281
  [countKey]: pageSize
7697
10282
  };
7698
10283
  const outcome = await runPage(pageParams, isFirst ? "initial" : "page");
7699
- if (!outcome.ok) return {
7700
- ok: false,
7701
- error: outcome.error
7702
- };
10284
+ if (!outcome.ok) {
10285
+ if (donePages === 0) return {
10286
+ ok: false,
10287
+ error: outcome.error
10288
+ };
10289
+ return {
10290
+ ok: true,
10291
+ value: {
10292
+ empty: false,
10293
+ lastPage,
10294
+ items: collected.slice(0, target)
10295
+ }
10296
+ };
10297
+ }
10298
+ donePages += 1;
7703
10299
  const page = outcome.value;
7704
10300
  lastPage = page;
7705
10301
  const list = def.items(page);
@@ -12214,7 +14810,7 @@ var comments = defineKuaishouEndpoint({
12214
14810
  },
12215
14811
  params: object({
12216
14812
  photoId: string().min(1, { error: "photoId 不能为空" }).describe("作品 ID"),
12217
- number: number().int().min(1).max(500).optional().describe("目标条数,默认一页")
14813
+ number: number().int().min(1).max(500).optional().describe("目标条数;不传时按单页上限翻页取满,而不是只取一页")
12218
14814
  }),
12219
14815
  sign: [hxfalcon()],
12220
14816
  prepare: kuaishouDidPrepare,
@@ -13236,7 +15832,7 @@ var SEARCH_NOTE_TYPE = {
13236
15832
  /**
13237
15833
  * 构建查询字符串:跳过 null / undefined。
13238
15834
  *
13239
- * **编码规则必须与 `@ikenxuan/xhshow-ts` 的签名内部编码完全一致**:只把值里的
15835
+ * **编码规则必须与 `sign/xhshow/` 的签名内部编码完全一致**:只把值里的
13240
15836
  * `=` 转成 `%3D`,其余字符(含 `,`)原样保留。小红书对 GET 请求按「路径 + 这套
13241
15837
  * 编码的 query」计算 x-s,若这里用 `encodeURIComponent`(会把 `,` 转成 `%2C`),
13242
15838
  * 发出去的 query 与被签名的内容不一致,平台返回 HTTP 406。
@@ -13295,10 +15891,12 @@ var noteComments$1 = (data) => {
13295
15891
  signParams: params
13296
15892
  };
13297
15893
  };
13298
- /** 用户信息(GET,请求的是 HTML 页,由 judge / decode 层处理) */
15894
+ /** 用户信息(GET,请求的是 HTML 页,由 judge / decode 层处理)。
15895
+ * 游客必须带 `xsec_token` + `xsec_source`(否则只拿到未水合空壳页);有效登录 CK 可省
15896
+ * (省略时 URL 回到 v6 形态 `/user/profile/{id}`,不改变已登录用户的既有行为)。 */
13299
15897
  var userProfile$1 = (data) => ({
13300
15898
  apiPath: "/api/sns/web/v1/user/otherinfo",
13301
- Url: `https://www.xiaohongshu.com/user/profile/${data.user_id}`
15899
+ Url: `https://www.xiaohongshu.com/user/profile/${data.user_id}${data.xsec_token ? `?xsec_token=${data.xsec_token}&xsec_source=pc_feed` : ""}`
13302
15900
  });
13303
15901
  /** 用户笔记列表(GET,参数进 query) */
13304
15902
  var userNoteList$1 = (data) => {
@@ -13311,6 +15909,7 @@ var userNoteList$1 = (data) => {
13311
15909
  "webp",
13312
15910
  "avif"
13313
15911
  ].join(","),
15912
+ ...data.xsec_token ? { xsec_token: data.xsec_token } : {},
13314
15913
  xsec_source: "pc_feed"
13315
15914
  };
13316
15915
  return {
@@ -13378,13 +15977,37 @@ var xiaohongshuApiUrls$1 = {
13378
15977
  /**
13379
15978
  * 小红书端点声明。
13380
15979
  *
13381
- * 就是 {@link defineEndpoint} 绑定了小红书签名器名联合 {@link XiaohongshuSignerName}:
13382
- * `sign` 的字符串分支被收窄成小红书那几个 `xhs-*` 名字,写错名字编译期即报错,不必
13383
- * 等运行时查表失败。运行时仍是恒等函数,其余行为与通用 `defineEndpoint` 完全一致。
15980
+ * 在 {@link defineEndpoint} 的基础上做两件小红书专属的事:
15981
+ *
15982
+ * 1. **签名器名收窄**:`sign` 的字符串分支被收窄成小红书那几个 `xhs-*` 名字
15983
+ * ({@link XiaohongshuSignerName}),写错名字编译期即报错,不必等运行时查表失败。
15984
+ * 2. **游客 Cookie 降级**:注入平台级 `prepare`({@link prepareXiaohongshuGuestCookie})——
15985
+ * 下游未传有效用户 Cookie(无 a1)时自动铸造游客 Cookie 来解析接口。这是**每个**
15986
+ * 小红书端点都要的横切能力,放在这里一处注入,保证全体端点(含将来新增的)一致
15987
+ * 覆盖,不会漏配。端点若自带 `prepare`,在游客降级**之后**再跑、其结果优先。
15988
+ *
13384
15989
  * @param def - 端点声明(`sign` 的名字受小红书签名器表约束)
13385
- * @returns 原样返回 `def`,带上推导好的具体类型
13386
- */
13387
- var defineXiaohongshuEndpoint = (def) => defineEndpoint(def);
15990
+ * @returns 带上推导好的具体类型、并注入游客降级 `prepare` 的 `def`
15991
+ */
15992
+ var defineXiaohongshuEndpoint = (def) => {
15993
+ const own = def.prepare;
15994
+ const prepare = async (ctx) => {
15995
+ const upgraded = await prepareXiaohongshuGuestCookie(ctx);
15996
+ if (own === void 0) return upgraded;
15997
+ const ownResult = await own({
15998
+ ...ctx,
15999
+ ...upgraded
16000
+ });
16001
+ return {
16002
+ ...upgraded,
16003
+ ...ownResult
16004
+ };
16005
+ };
16006
+ return defineEndpoint({
16007
+ ...def,
16008
+ prepare
16009
+ });
16010
+ };
13388
16011
  /**
13389
16012
  * 表情列表(GET,无参数)。
13390
16013
  *
@@ -13411,14 +16034,11 @@ var emojiList = defineXiaohongshuEndpoint({
13411
16034
  response: type()
13412
16035
  });
13413
16036
  /**
13414
- * 首页推荐(POST + prepare 换 guest cookie)。
13415
- *
13416
- * 覆盖两种非常规形态:POST 请求体 + 前置请求换凭证。
16037
+ * 首页推荐(POST)。
13417
16038
  *
13418
- * `prepare`:cookie 里没有 a1 时,用 transport 换一份 guest cookie
13419
- * (scripting → webprofile → activate 三个会话请求,`reason: 'prepare'`
13420
- * 进 trace)。已有 a1 直接跳过 —— guest cookie 流程有真实网络开销,
13421
- * 不能每次调用都重跑。
16039
+ * 无有效用户 Cookie(无 a1)时自动用游客 Cookie 解析 —— 该降级已上移到
16040
+ * `defineXiaohongshuEndpoint` 的平台级 `prepare`(见 `sign/guestCookie.ts` 的
16041
+ * `prepareXiaohongshuGuestCookie`),对全体小红书端点一致生效,homeFeed 不再单独声明。
13422
16042
  */
13423
16043
  var homeFeed = defineXiaohongshuEndpoint({
13424
16044
  name: "xiaohongshu.homeFeed",
@@ -13435,10 +16055,6 @@ var homeFeed = defineXiaohongshuEndpoint({
13435
16055
  category: string().optional().describe("内容分类,默认首页推荐流"),
13436
16056
  search_key: string().optional().describe("搜索关键词,默认空字符串")
13437
16057
  }),
13438
- prepare: async (ctx) => {
13439
- if (getCookieValue(ctx.cookie, "a1")) return {};
13440
- return { cookie: await createXiaohongshuGuestCookie$1(ctx.send, ctx.requestConfig) };
13441
- },
13442
16058
  build: (p) => {
13443
16059
  const { Url, Body, apiPath } = homeFeed$1(p);
13444
16060
  return {
@@ -13460,19 +16076,49 @@ var homeFeed = defineXiaohongshuEndpoint({
13460
16076
  * xhs 响应里的 `data.cursor` 是 string(平台协议如此),
13461
16077
  * `data.has_more` 是 boolean,与抖音的 `has_more === 1` 不同 ——
13462
16078
  * 差异收敛在 `paginate` 声明里,管线不感知。
16079
+ *
16080
+ * ## 游客会话只能取到第一页
16081
+ *
16082
+ * 真机实测(2026-10-12):游客身份下首页(`cursor=''`)稳定 HTTP 200 并返回
16083
+ * **10 条**;**第二页**(非空 cursor)必被平台以 **HTTP 461** 拒绝,响应体还是
16084
+ * 一个伪装成成功的空壳 `{"code":0,"success":true,"msg":"成功","data":{}}`。
16085
+ * 这是平台侧对「游客 + 签名内出现翻页 cursor」的硬门,纯算无解 ——
16086
+ * 单页判为 `risk` / `RISK_CONTROL`(见 `judge.ts`)。
16087
+ *
16088
+ * 后果:游客调这个端点**最多拿到 10 条**,而且返回的是一个**正常的成功信封** ——
16089
+ * 翻页在第二页停下、把首页那 10 条原样返回,不报错也不加文案(见
16090
+ * `runtime/paginate.ts` 的部分结果规则)。想让 `number` 生效必须有**有效登录 CK**;
16091
+ * 游客传超过单页容量没有副作用,只是拿到的比要的少。
16092
+ *
16093
+ * ## `comment/page` 的限流比 `/v1/feed` 紧得多
16094
+ *
16095
+ * 真机实测(2026-10-12):**同一会话、同一时刻**,`noteDetail`(`/v1/feed`)能过而
16096
+ * 本端点回 461 —— 两个端点的预算不是一个量级。这是「详情解析成功、评论却失败」这类
16097
+ * 现象的主因。
16098
+ *
16099
+ * 超限后的表现与上面的翻页硬门**完全一样**(461 + 伪装成成功的空壳),平台不给任何
16100
+ * 可区分的信号,所以**两者在错误码上分不开**,都只能报 `RISK_CONTROL`。
16101
+ *
16102
+ * 限流是**滑动窗口**,恢复要分钟级:实测 1.5 秒间隔连打 8 次,第 3 次起连续失败;
16103
+ * 而窗口空闲时同一份会话能连过 7 次。`isRetryableStatus` 只认 429 与 ≥500,461
16104
+ * 因此**不会自动重试**,失败是即时的、要靠调用方自己退避。
16105
+ *
16106
+ * **排查时踩过的坑**:一度把「连发的第 4 个请求必挂」误判成「`xsec_source=pc_share`
16107
+ * 是毒药」—— 其实是请求顺序造成的假象(两次跑挂的都是第 4 个,只是恰好那次轮到
16108
+ * `pc_share`)。顺序敏感的探针**必须做重复组或打乱顺序**,单次结果不能下结论。
13463
16109
  */
13464
16110
  var noteComments = defineXiaohongshuEndpoint({
13465
16111
  name: "xiaohongshu.noteComments",
13466
16112
  route: "/fetch_note_comments",
13467
16113
  doc: {
13468
16114
  summary: "笔记评论列表",
13469
- description: "取笔记评论列表。`number` 指定目标条数,翻页由端点自动完成。"
16115
+ description: "取笔记评论列表。`number` 指定目标条数,翻页由端点自动完成。**游客身份下平台对本接口限流较紧**:超限回 HTTP 461(伪装成成功的空壳),退避需分钟级、秒级无效;而且**不传 `number` 会触发翻页、多烧一次请求** —— 传具体小值可把请求数降到 1。"
13470
16116
  },
13471
16117
  params: object({
13472
16118
  cursor: internalParam(string().optional().describe("分页游标,由端点接管,不用传")),
13473
16119
  note_id: string().min(1, { error: "note_id 不能为空" }).describe("笔记 ID;从笔记分享链接里取"),
13474
- xsec_token: string().min(1, { error: "xsec_token 不能为空" }).describe("反爬令牌,随笔记分享链接下发"),
13475
- number: number().int().min(1).max(500).optional().describe("目标条数,默认一页")
16120
+ xsec_token: string().min(1, { error: "xsec_token 不能为空" }).describe("反爬令牌,随笔记分享链接下发;**与发放它的会话绑定** —— 游客下换会话即 461,须同会话现取或带上原游客 CK"),
16121
+ number: number().int().min(1).max(500).optional().describe("目标条数;不传时按单页上限翻页取满,而不是只取一页 —— 游客下翻页必被拒且多烧一次请求,传具体小值可降到 1 次")
13476
16122
  }),
13477
16123
  build: (p) => {
13478
16124
  const { Url, apiPath, signParams } = noteComments$1(p);
@@ -13516,7 +16162,7 @@ var noteDetail = defineXiaohongshuEndpoint({
13516
16162
  },
13517
16163
  params: object({
13518
16164
  note_id: string().min(1, { error: "note_id 不能为空" }).describe("笔记 ID;从笔记分享链接里取"),
13519
- xsec_token: string().min(1, { error: "xsec_token 不能为空" }).describe("反爬令牌,随笔记分享链接下发,不能自己拼")
16165
+ xsec_token: string().min(1, { error: "xsec_token 不能为空" }).describe("反爬令牌,随笔记分享链接下发;**与发放它的会话绑定** —— 游客下换会话即 461,须同会话现取或带上原游客 CK")
13520
16166
  }),
13521
16167
  build: (p) => {
13522
16168
  const { Url, Body, apiPath } = noteDetail$1(p);
@@ -13561,11 +16207,14 @@ var searchNotes = defineXiaohongshuEndpoint({
13561
16207
  response: type()
13562
16208
  });
13563
16209
  /**
13564
- * 用户笔记列表(GET + x-b3-traceid,XYW_ 签名)。
16210
+ * 用户笔记列表(GET + x-b3-traceid,XYS_ 签名)。
13565
16211
  *
13566
- * 这个端点额外带 `x-b3-traceid` 头,且属于数据获取类接口 —— 自 2026-03 起传统
13567
- * XYS_ 签名会被平台以 HTTP 406 拒绝,因此用 `'xhs-get-xyw-trace'`
13568
- * (XYW_ GET 签名 + traceid),而不是给所有 GET 都换。
16212
+ * 这个端点额外带 `x-b3-traceid` 头。签名用 XYS_(与 v6 一致)—— 真机实证(2026-10-11):
16213
+ * 同一游客 cookie 下 `user_posted` 用 **XYS_ 回 200 + 真数据**、用 XYW_ 回 **HTTP 461 空 data**;
16214
+ * 且 v6/legacy 本就只有 XYS(无 XYW)。早先「数据接口 XYS 会 406 必须换 XYW」是未经线上验证的
16215
+ * 文档默认值,已被平台改回、不再成立(见 [[amagi-xhshow-xyw-rap-integration]] 的修正)。
16216
+ * `xsec_token`:游客必传(user 级令牌,缺它平台回 461),有效登录 CK 可省(省略时 query 与 v6
16217
+ * 逐字节一致)。
13569
16218
  */
13570
16219
  var userNoteList = defineXiaohongshuEndpoint({
13571
16220
  name: "xiaohongshu.userNoteList",
@@ -13576,7 +16225,7 @@ var userNoteList = defineXiaohongshuEndpoint({
13576
16225
  },
13577
16226
  params: object({
13578
16227
  user_id: string().min(1, { error: "user_id 不能为空" }).describe("用户 ID"),
13579
- xsec_token: string().min(1, { error: "xsec_token 不能为空" }).describe("反爬令牌,随笔记分享链接下发"),
16228
+ xsec_token: string().optional().describe("反爬令牌,随用户主页分享链接下发;游客必传、有效登录 CK 可省;**与发放它的会话绑定**(游客下换会话即 461)"),
13580
16229
  cursor: string().optional().describe("翻页游标;传上一页最后一条的 ID,首页不传"),
13581
16230
  num: number().int().min(1).max(100).optional().describe("单次请求的笔记条数,默认 30")
13582
16231
  }),
@@ -13589,7 +16238,7 @@ var userNoteList = defineXiaohongshuEndpoint({
13589
16238
  extra: { signParams }
13590
16239
  };
13591
16240
  },
13592
- sign: [xs("get", "xyw"), traceId()],
16241
+ sign: [xs("get", "xys"), traceId()],
13593
16242
  response: type()
13594
16243
  });
13595
16244
  /**
@@ -13611,7 +16260,10 @@ var xiaohongshuRegistry = {
13611
16260
  summary: "用户主页信息",
13612
16261
  description: "取用户主页的资料与统计。笔记列表见 `userNoteList`。"
13613
16262
  },
13614
- params: object({ user_id: string().min(1, { error: "user_id 不能为空" }).describe("用户 ID,从主页地址里取") }),
16263
+ params: object({
16264
+ user_id: string().min(1, { error: "user_id 不能为空" }).describe("用户 ID,从主页地址里取"),
16265
+ xsec_token: string().optional().describe("反爬令牌,随用户主页分享链接下发;游客必传、有效登录 CK 可省;**与发放它的会话绑定**(游客下换会话即 461)")
16266
+ }),
13615
16267
  build: (p) => {
13616
16268
  const { Url, apiPath } = userProfile$1(p);
13617
16269
  return {
@@ -15469,6 +18121,21 @@ var RESPONSE_SCHEMAS = {
15469
18121
  "success"
15470
18122
  ]
15471
18123
  },
18124
+ "xiaohongshu_userNoteList": {
18125
+ "type": "object",
18126
+ "properties": {
18127
+ "code": { "type": "number" },
18128
+ "data": { "$ref": "#/components/schemas/xiaohongshu_userNoteList.UserNoteList_V0.Data" },
18129
+ "msg": { "type": "string" },
18130
+ "success": { "type": "boolean" }
18131
+ },
18132
+ "required": [
18133
+ "code",
18134
+ "data",
18135
+ "msg",
18136
+ "success"
18137
+ ]
18138
+ },
15472
18139
  "xiaohongshu_userProfile": {
15473
18140
  "type": "object",
15474
18141
  "properties": {
@@ -54711,6 +57378,107 @@ var RESPONSE_SCHEMAS = {
54711
57378
  "xsec_token"
54712
57379
  ]
54713
57380
  },
57381
+ "xiaohongshu_userNoteList.UserNoteList_V0.Cover": {
57382
+ "type": "object",
57383
+ "properties": {
57384
+ "file_id": { "type": "string" },
57385
+ "height": { "type": "number" },
57386
+ "info_list": {
57387
+ "type": "array",
57388
+ "items": { "$ref": "#/components/schemas/xiaohongshu_userNoteList.UserNoteList_V0.InfoList" }
57389
+ },
57390
+ "trace_id": { "type": "string" },
57391
+ "url": { "type": "string" },
57392
+ "url_default": { "type": "string" },
57393
+ "url_pre": { "type": "string" },
57394
+ "width": { "type": "number" }
57395
+ },
57396
+ "required": [
57397
+ "file_id",
57398
+ "height",
57399
+ "info_list",
57400
+ "trace_id",
57401
+ "url",
57402
+ "url_default",
57403
+ "url_pre",
57404
+ "width"
57405
+ ]
57406
+ },
57407
+ "xiaohongshu_userNoteList.UserNoteList_V0.Data": {
57408
+ "type": "object",
57409
+ "properties": {
57410
+ "cursor": { "type": "string" },
57411
+ "has_more": { "type": "boolean" },
57412
+ "notes": {
57413
+ "type": "array",
57414
+ "items": { "$ref": "#/components/schemas/xiaohongshu_userNoteList.UserNoteList_V0.Note" }
57415
+ }
57416
+ },
57417
+ "required": [
57418
+ "cursor",
57419
+ "has_more",
57420
+ "notes"
57421
+ ]
57422
+ },
57423
+ "xiaohongshu_userNoteList.UserNoteList_V0.InfoList": {
57424
+ "type": "object",
57425
+ "properties": {
57426
+ "image_scene": { "type": "string" },
57427
+ "url": { "type": "string" }
57428
+ },
57429
+ "required": ["image_scene", "url"]
57430
+ },
57431
+ "xiaohongshu_userNoteList.UserNoteList_V0.InteractInfo": {
57432
+ "type": "object",
57433
+ "properties": {
57434
+ "liked": { "type": "boolean" },
57435
+ "liked_count": { "type": "string" },
57436
+ "sticky": { "type": "boolean" }
57437
+ },
57438
+ "required": [
57439
+ "liked",
57440
+ "liked_count",
57441
+ "sticky"
57442
+ ]
57443
+ },
57444
+ "xiaohongshu_userNoteList.UserNoteList_V0.Note": {
57445
+ "type": "object",
57446
+ "properties": {
57447
+ "cover": { "$ref": "#/components/schemas/xiaohongshu_userNoteList.UserNoteList_V0.Cover" },
57448
+ "display_title": { "type": "string" },
57449
+ "interact_info": { "$ref": "#/components/schemas/xiaohongshu_userNoteList.UserNoteList_V0.InteractInfo" },
57450
+ "note_id": { "type": "string" },
57451
+ "time": { "type": "number" },
57452
+ "type": { "type": "string" },
57453
+ "user": { "$ref": "#/components/schemas/xiaohongshu_userNoteList.UserNoteList_V0.User" },
57454
+ "xsec_token": { "type": "string" }
57455
+ },
57456
+ "required": [
57457
+ "cover",
57458
+ "display_title",
57459
+ "interact_info",
57460
+ "note_id",
57461
+ "time",
57462
+ "type",
57463
+ "user",
57464
+ "xsec_token"
57465
+ ]
57466
+ },
57467
+ "xiaohongshu_userNoteList.UserNoteList_V0.User": {
57468
+ "type": "object",
57469
+ "properties": {
57470
+ "avatar": { "type": "string" },
57471
+ "nick_name": { "type": "string" },
57472
+ "nickname": { "type": "string" },
57473
+ "user_id": { "type": "string" }
57474
+ },
57475
+ "required": [
57476
+ "avatar",
57477
+ "nick_name",
57478
+ "nickname",
57479
+ "user_id"
57480
+ ]
57481
+ },
54714
57482
  "xiaohongshu_userProfile.UserProfile_V0.BasicInfo": {
54715
57483
  "type": "object",
54716
57484
  "properties": {
@@ -55168,7 +57936,7 @@ var buildOpenApiSpec = (options = {}) => {
55168
57936
  openapi: "3.1.0",
55169
57937
  info: {
55170
57938
  title: "amagi HTTP API",
55171
- version: options.version ?? "7.0.0-beta.8",
57939
+ version: options.version ?? "7.0.0-beta.9",
55172
57940
  description: [
55173
57941
  "由端点注册表派生的规范 —— 手写的那份已经漂移,这份不会(`gen-openapi --check` 进 CI)。",
55174
57942
  "",
@@ -57214,7 +59982,7 @@ var DynamicType = /* @__PURE__ */ function(DynamicType) {
57214
59982
  * 构建后使用 __VERSION__,开发环境从 package.json 读取
57215
59983
  */
57216
59984
  var getVersion = () => {
57217
- return "7.0.0-beta.8";
59985
+ return "7.0.0-beta.9";
57218
59986
  };
57219
59987
  var VERSION = getVersion();
57220
59988
  /**