@ikenxuan/amagi 6.0.0-beta.3 → 6.1.2

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.
@@ -1262,6 +1262,29 @@ const KuaishouCommentParamsSchema = zod.default.object({
1262
1262
  photoId: zod.default.string({ error: "photoId must be a string" }).min(1, { error: "photoId cannot be empty" })
1263
1263
  });
1264
1264
  /**
1265
+ * 快手用户主页参数验证模式
1266
+ */
1267
+ const KuaishouUserProfileParamsSchema = zod.default.object({
1268
+ methodType: zod.default.literal("userProfile", { error: "methodType must be \"userProfile\"" }),
1269
+ principalId: zod.default.string({ error: "principalId must be a string" }).min(1, { error: "principalId cannot be empty" })
1270
+ });
1271
+ /**
1272
+ * 快手用户作品列表参数验证模式
1273
+ */
1274
+ const KuaishouUserWorkListParamsSchema = zod.default.object({
1275
+ methodType: zod.default.literal("userWorkList", { error: "methodType must be \"userWorkList\"" }),
1276
+ principalId: zod.default.string({ error: "principalId must be a string" }).min(1, { error: "principalId cannot be empty" }),
1277
+ pcursor: zod.default.string({ error: "pcursor must be a string" }).optional(),
1278
+ count: zod.default.number({ error: "count must be a number" }).int({ error: "count must be an integer" }).positive({ error: "count must be positive" }).max(100, { error: "count must be less than or equal to 100" }).optional()
1279
+ });
1280
+ /**
1281
+ * 快手直播间信息参数验证模式
1282
+ */
1283
+ const KuaishouLiveRoomInfoParamsSchema = zod.default.object({
1284
+ methodType: zod.default.literal("liveRoomInfo", { error: "methodType must be \"liveRoomInfo\"" }),
1285
+ principalId: zod.default.string({ error: "principalId must be a string" }).min(1, { error: "principalId cannot be empty" })
1286
+ });
1287
+ /**
1265
1288
  * 快手表情参数验证模式
1266
1289
  */
1267
1290
  const KuaishouEmojiParamsSchema = zod.default.object({ methodType: zod.default.literal("emojiList", { error: "methodType must be \"emojiList\"" }) });
@@ -1271,6 +1294,9 @@ const KuaishouEmojiParamsSchema = zod.default.object({ methodType: zod.default.l
1271
1294
  const KuaishouValidationSchemas = {
1272
1295
  videoWork: KuaishouVideoParamsSchema,
1273
1296
  comments: KuaishouCommentParamsSchema,
1297
+ userProfile: KuaishouUserProfileParamsSchema,
1298
+ userWorkList: KuaishouUserWorkListParamsSchema,
1299
+ liveRoomInfo: KuaishouLiveRoomInfoParamsSchema,
1274
1300
  emojiList: KuaishouEmojiParamsSchema
1275
1301
  };
1276
1302
  /**
@@ -1279,6 +1305,9 @@ const KuaishouValidationSchemas = {
1279
1305
  const KuaishouMethodRoutes = {
1280
1306
  videoWork: "/fetch_one_work",
1281
1307
  comments: "/fetch_work_comments",
1308
+ userProfile: "/fetch_user_profile",
1309
+ userWorkList: "/fetch_user_work_list",
1310
+ liveRoomInfo: "/fetch_live_room_info",
1282
1311
  emojiList: "/fetch_emoji_list"
1283
1312
  };
1284
1313
 
@@ -2065,7 +2094,7 @@ async function fetchDynamicCard(options, cookie, requestConfig) {
2065
2094
  * console.log(result.data.title) // 直播间标题
2066
2095
  * ```
2067
2096
  */
2068
- async function fetchLiveRoomInfo$1(options, cookie, requestConfig) {
2097
+ async function fetchLiveRoomInfo$2(options, cookie, requestConfig) {
2069
2098
  return fetchBilibiliInternal("liveRoomInfo", options, {
2070
2099
  cookie,
2071
2100
  requestConfig
@@ -2321,7 +2350,7 @@ function createBoundBilibiliFetcher(cookie, requestConfig) {
2321
2350
  fetchDynamicCard: (options) => fetchDynamicCard(options, cookie, requestConfig),
2322
2351
  fetchBangumiInfo: (options) => fetchBangumiInfo(options, cookie, requestConfig),
2323
2352
  fetchBangumiStreamUrl: (options) => fetchBangumiStreamUrl(options, cookie, requestConfig),
2324
- fetchLiveRoomInfo: (options) => fetchLiveRoomInfo$1(options, cookie, requestConfig),
2353
+ fetchLiveRoomInfo: (options) => fetchLiveRoomInfo$2(options, cookie, requestConfig),
2325
2354
  fetchLiveRoomInitInfo: (options) => fetchLiveRoomInitInfo(options, cookie, requestConfig),
2326
2355
  fetchArticleContent: (options) => fetchArticleContent(options, cookie, requestConfig),
2327
2356
  fetchArticleCards: (options) => fetchArticleCards(options, cookie, requestConfig),
@@ -2368,7 +2397,7 @@ const bilibiliFetcher = {
2368
2397
  fetchDynamicCard,
2369
2398
  fetchBangumiInfo,
2370
2399
  fetchBangumiStreamUrl,
2371
- fetchLiveRoomInfo: fetchLiveRoomInfo$1,
2400
+ fetchLiveRoomInfo: fetchLiveRoomInfo$2,
2372
2401
  fetchLiveRoomInitInfo,
2373
2402
  fetchArticleContent,
2374
2403
  fetchArticleCards,
@@ -4587,7 +4616,7 @@ async function fetchMusicInfo(options, cookie, requestConfig) {
4587
4616
  * console.log(result.data.title) // 直播间标题
4588
4617
  * ```
4589
4618
  */
4590
- async function fetchLiveRoomInfo(options, cookie, requestConfig) {
4619
+ async function fetchLiveRoomInfo$1(options, cookie, requestConfig) {
4591
4620
  return fetchDouyinInternal("liveRoomInfo", options, {
4592
4621
  cookie,
4593
4622
  requestConfig
@@ -4714,7 +4743,7 @@ async function fetchSuggestWords(options, cookie, requestConfig) {
4714
4743
  * console.log(result.data.user.nickname) // 用户昵称
4715
4744
  * ```
4716
4745
  */
4717
- async function fetchUserProfile$1(options, cookie, requestConfig) {
4746
+ async function fetchUserProfile$2(options, cookie, requestConfig) {
4718
4747
  return fetchDouyinInternal("userProfile", options, {
4719
4748
  cookie,
4720
4749
  requestConfig
@@ -4926,14 +4955,14 @@ function createBoundDouyinFetcher(cookie, requestConfig) {
4926
4955
  fetchDanmakuList: (options, reqConfig) => fetchDanmakuList(options, cookie, reqConfig ?? requestConfig),
4927
4956
  fetchWorkComments: (options, reqConfig) => fetchWorkComments$1(options, cookie, reqConfig ?? requestConfig),
4928
4957
  fetchCommentReplies: (options, reqConfig) => fetchCommentReplies(options, cookie, reqConfig ?? requestConfig),
4929
- fetchUserProfile: (options, reqConfig) => fetchUserProfile$1(options, cookie, reqConfig ?? requestConfig),
4958
+ fetchUserProfile: (options, reqConfig) => fetchUserProfile$2(options, cookie, reqConfig ?? requestConfig),
4930
4959
  fetchUserVideoList: (options, reqConfig) => fetchUserVideoList(options, cookie, reqConfig ?? requestConfig),
4931
4960
  fetchUserFavoriteList: (options, reqConfig) => fetchUserFavoriteList(options, cookie, reqConfig ?? requestConfig),
4932
4961
  fetchUserRecommendList: (options, reqConfig) => fetchUserRecommendList(options, cookie, reqConfig ?? requestConfig),
4933
4962
  searchContent: (options, reqConfig) => searchContent(options, cookie, reqConfig ?? requestConfig),
4934
4963
  fetchSuggestWords: (options, reqConfig) => fetchSuggestWords(options, cookie, reqConfig ?? requestConfig),
4935
4964
  fetchMusicInfo: (options, reqConfig) => fetchMusicInfo(options, cookie, reqConfig ?? requestConfig),
4936
- fetchLiveRoomInfo: (options, reqConfig) => fetchLiveRoomInfo(options, cookie, reqConfig ?? requestConfig),
4965
+ fetchLiveRoomInfo: (options, reqConfig) => fetchLiveRoomInfo$1(options, cookie, reqConfig ?? requestConfig),
4937
4966
  requestLoginQrcode: (options, reqConfig) => requestLoginQrcode(options, cookie, reqConfig ?? requestConfig),
4938
4967
  fetchEmojiList: (options, reqConfig) => fetchEmojiList$2(options, cookie, reqConfig ?? requestConfig),
4939
4968
  fetchDynamicEmojiList: (options, reqConfig) => fetchDynamicEmojiList(options, cookie, reqConfig ?? requestConfig)
@@ -4965,14 +4994,14 @@ const douyinFetcher = {
4965
4994
  fetchDanmakuList,
4966
4995
  fetchWorkComments: fetchWorkComments$1,
4967
4996
  fetchCommentReplies,
4968
- fetchUserProfile: fetchUserProfile$1,
4997
+ fetchUserProfile: fetchUserProfile$2,
4969
4998
  fetchUserVideoList,
4970
4999
  fetchUserFavoriteList,
4971
5000
  fetchUserRecommendList,
4972
5001
  searchContent,
4973
5002
  fetchSuggestWords,
4974
5003
  fetchMusicInfo,
4975
- fetchLiveRoomInfo,
5004
+ fetchLiveRoomInfo: fetchLiveRoomInfo$1,
4976
5005
  requestLoginQrcode,
4977
5006
  fetchEmojiList: fetchEmojiList$2,
4978
5007
  fetchDynamicEmojiList
@@ -4981,8 +5010,33 @@ const douyinFetcher = {
4981
5010
  //#endregion
4982
5011
  //#region src/platform/kuaishou/API.ts
4983
5012
  /**
5013
+ * 构造快手 `live_api` 请求描述对象。
5014
+ *
5015
+ * 之所以将 `signPath` 放在 API 描述层,而不是放进签名器内部硬编码,
5016
+ * 是为了让“接口公开路径”和“算法规范路径”的关系显式可见。
5017
+ *
5018
+ * @param type - 内部请求类型标识
5019
+ * @param pathname - 实际请求路径
5020
+ * @param query - 要拼接到 URL 上的查询参数
5021
+ * @param signPath - 可选的签名规范路径;当页面路径与算法路径不一致时必须传入
5022
+ * @returns 可供请求层和签名层复用的请求描述对象
5023
+ */
5024
+ const createKuaishouLiveApiRequest = (type, pathname, query, options) => {
5025
+ const url = new URL(`https://live.kuaishou.com${pathname}`);
5026
+ for (const [key, value] of Object.entries(query)) url.searchParams.set(key, String(value));
5027
+ return {
5028
+ type,
5029
+ url: url.toString(),
5030
+ method: options?.method ?? "GET",
5031
+ ...typeof options?.requiresSign === "boolean" ? { requiresSign: options.requiresSign } : {},
5032
+ ...options?.signPath ? { signPath: options.signPath } : {},
5033
+ ...options?.body ? { body: options.body } : {}
5034
+ };
5035
+ };
5036
+ /**
4984
5037
  * 快手 API 地址构建类
4985
- * 该类下的所有方法只会返回拼接好参数后的 Url 地址和请求体,需要手动请求该地址以获取数据
5038
+ * 该类下的方法只负责返回请求描述对象,需要手动请求对应地址以获取数据。
5039
+ * 其中 `live_api` 方法会在必要时额外携带 `signPath`,用于声明快手签名算法实际参与计算的规范路径。
4986
5040
  */
4987
5041
  var API = class {
4988
5042
  /**
@@ -5024,6 +5078,241 @@ var API = class {
5024
5078
  };
5025
5079
  }
5026
5080
  /**
5081
+ * 获取用户主页基础资料接口地址。
5082
+ *
5083
+ * 该接口需要 `__NS_hxfalcon`,且签名使用的规范路径并不是公开 `live_api`
5084
+ * 路径,而是内部的 `/rest/k/user/info`。
5085
+ *
5086
+ * @param data - 用户主页参数
5087
+ * @returns 请求配置
5088
+ */
5089
+ userInfoById(data$1) {
5090
+ return createKuaishouLiveApiRequest("userInfoById", "/live_api/baseuser/userinfo/byid", {
5091
+ caver: 2,
5092
+ principalId: data$1.principalId
5093
+ }, { signPath: "/rest/k/user/info" });
5094
+ }
5095
+ /**
5096
+ * 获取用户敏感资料接口地址
5097
+ * @param data - 用户主页参数
5098
+ * @returns 请求配置
5099
+ */
5100
+ userSensitiveInfo(data$1) {
5101
+ return createKuaishouLiveApiRequest("userSensitiveInfo", "/live_api/baseuser/userinfo/sensitive", {
5102
+ caver: 2,
5103
+ principalId: data$1.principalId
5104
+ }, { signPath: "/rest/k/user/info/sensitive" });
5105
+ }
5106
+ /**
5107
+ * 获取用户主页公开数据接口地址
5108
+ * @param data - 用户主页参数
5109
+ * @returns 请求配置
5110
+ */
5111
+ profilePublic(data$1) {
5112
+ return createKuaishouLiveApiRequest("profilePublic", "/live_api/profile/public", {
5113
+ caver: 2,
5114
+ count: "count" in data$1 ? data$1.count ?? 12 : 12,
5115
+ hasMore: true,
5116
+ pcursor: "pcursor" in data$1 ? data$1.pcursor ?? "" : "",
5117
+ principalId: data$1.principalId,
5118
+ privacy: "public"
5119
+ }, { signPath: "/rest/k/feed/profile" });
5120
+ }
5121
+ /**
5122
+ * 获取用户主页公开视频列表接口地址。
5123
+ *
5124
+ * 该方法对 `live_api/profile/public` 做领域化封装,
5125
+ * 用于独立承接用户作品列表与分页能力。
5126
+ *
5127
+ * @param data - 用户作品列表参数
5128
+ * @returns 请求配置
5129
+ */
5130
+ userWorkList(data$1) {
5131
+ return {
5132
+ ...this.profilePublic(data$1),
5133
+ type: "userWorkList"
5134
+ };
5135
+ }
5136
+ /**
5137
+ * 获取用户主页私密列表接口地址。
5138
+ *
5139
+ * 真实页面抓包表明该接口可以直接走纯协议请求,不必额外追加 `__NS_hxfalcon`。
5140
+ *
5141
+ * @param data - 用户主页参数
5142
+ * @returns 请求配置
5143
+ */
5144
+ profilePrivate(data$1) {
5145
+ return createKuaishouLiveApiRequest("profilePrivate", "/live_api/profile/private", {
5146
+ caver: 2,
5147
+ count: 12,
5148
+ hasMore: true,
5149
+ pcursor: "",
5150
+ principalId: data$1.principalId,
5151
+ privacy: "private"
5152
+ }, { requiresSign: false });
5153
+ }
5154
+ /**
5155
+ * 获取用户主页点赞列表接口地址。
5156
+ *
5157
+ * @param data - 用户主页参数
5158
+ * @returns 请求配置
5159
+ */
5160
+ profileLiked(data$1) {
5161
+ return createKuaishouLiveApiRequest("profileLiked", "/live_api/profile/liked", {
5162
+ caver: 2,
5163
+ count: 12,
5164
+ hasMore: true,
5165
+ pcursor: "",
5166
+ principalId: data$1.principalId,
5167
+ privacy: "liked"
5168
+ }, { requiresSign: false });
5169
+ }
5170
+ /**
5171
+ * 获取用户主页兴趣推荐列表接口地址
5172
+ * @param data - 用户主页参数
5173
+ * @returns 请求配置
5174
+ */
5175
+ profileInterestList(data$1) {
5176
+ return createKuaishouLiveApiRequest("profileInterestList", "/live_api/profile/interestlist", {
5177
+ caver: 2,
5178
+ limit: 4,
5179
+ principalId: data$1.principalId
5180
+ });
5181
+ }
5182
+ /**
5183
+ * 获取主播回放列表接口地址。
5184
+ *
5185
+ * 快手页面使用 `cursor` 作为翻页参数,而返回体里仍沿用 `pcursor`。
5186
+ *
5187
+ * @param data - 用户主页参数
5188
+ * @returns 请求配置
5189
+ */
5190
+ playbackList(data$1) {
5191
+ return createKuaishouLiveApiRequest("playbackList", "/live_api/playback/list", {
5192
+ principalId: data$1.principalId,
5193
+ count: 12,
5194
+ cursor: "",
5195
+ hasMore: true
5196
+ }, { requiresSign: false });
5197
+ }
5198
+ /**
5199
+ * 获取用户主页兴趣分组接口地址。
5200
+ *
5201
+ * `interestMask/list` 是当前协议链中更贴近用户兴趣分组语义的接口,
5202
+ * 相比旧的 `profileInterestMask/list` 更适合作为领域数据源。
5203
+ *
5204
+ * @returns 请求配置
5205
+ */
5206
+ interestMaskList() {
5207
+ return createKuaishouLiveApiRequest("interestMaskList", "/live_api/interestMask/list", {}, { requiresSign: false });
5208
+ }
5209
+ /**
5210
+ * 获取分类配置接口地址。
5211
+ *
5212
+ * 该接口可用于构造用户主页 `categoryMask.config`。
5213
+ *
5214
+ * @returns 请求配置
5215
+ */
5216
+ categoryConfig() {
5217
+ return createKuaishouLiveApiRequest("categoryConfig", "/live_api/category/config", {}, { requiresSign: false });
5218
+ }
5219
+ /**
5220
+ * 获取热门分类列表接口地址。
5221
+ *
5222
+ * 该接口提供完整的分类卡片字段,可直接映射到
5223
+ * 用户主页 `categoryMask.hotList`。
5224
+ *
5225
+ * @returns 请求配置
5226
+ */
5227
+ categoryData() {
5228
+ return createKuaishouLiveApiRequest("categoryData", "/live_api/category/data", {}, { requiresSign: false });
5229
+ }
5230
+ /**
5231
+ * 获取分类分组列表接口地址。
5232
+ *
5233
+ * @returns 请求配置
5234
+ */
5235
+ categoryClassify() {
5236
+ return createKuaishouLiveApiRequest("categoryClassify", "/live_api/category/classify", {
5237
+ type: 4,
5238
+ source: 2,
5239
+ page: 1,
5240
+ pageSize: 20
5241
+ }, { requiresSign: false });
5242
+ }
5243
+ /**
5244
+ * 获取直播间详情主接口地址。
5245
+ *
5246
+ * 真实页面 store 会用该接口按 `principalId` 获取当前房间主体,
5247
+ * 返回结构中直接包含 `liveStream`、`author`、`gameInfo`、`noticeList`
5248
+ * 与基础 `config`,比 `userFollowCount` 更符合“直播间详情”语义。
5249
+ *
5250
+ * @param data - 直播间参数
5251
+ * @param authToken - 私密房间等场景下可能需要的 authToken
5252
+ * @returns 请求配置
5253
+ */
5254
+ liveDetail(data$1, authToken) {
5255
+ const query = { principalId: data$1.principalId };
5256
+ if (authToken?.trim()) query.authToken = authToken.trim();
5257
+ return createKuaishouLiveApiRequest("liveDetail", "/live_api/liveroom/livedetail", query, { requiresSign: false });
5258
+ }
5259
+ /**
5260
+ * 获取当前登录用户关注中的开播列表接口地址。
5261
+ *
5262
+ * 该接口更接近“关注流”而不是“当前直播间详情”,
5263
+ * 因此不应再作为直播间主对象的首选来源。
5264
+ *
5265
+ * @returns 请求配置
5266
+ */
5267
+ userFollowCount() {
5268
+ return createKuaishouLiveApiRequest("userFollowCount", "/live_api/baseuser/userFollowCount", {}, { requiresSign: false });
5269
+ }
5270
+ /**
5271
+ * 获取直播间推荐流接口地址。
5272
+ *
5273
+ * 该接口使用 POST JSON,请求体中的 `gameFavour`
5274
+ * 会影响推荐房间列表的主题偏好。
5275
+ *
5276
+ * @param gameId - 当前直播房间所属游戏 ID
5277
+ * @returns 请求配置
5278
+ */
5279
+ liveReco(gameId) {
5280
+ return createKuaishouLiveApiRequest("liveReco", "/live_api/liveroom/reco", {}, {
5281
+ method: "POST",
5282
+ requiresSign: false,
5283
+ body: {
5284
+ followingParam: {
5285
+ queryFollowing: true,
5286
+ followingWeight: 50
5287
+ },
5288
+ gameFavour: [{
5289
+ gameId: Number(gameId) > 0 ? Number(gameId) : 1001,
5290
+ totalStayLength: 100
5291
+ }]
5292
+ }
5293
+ });
5294
+ }
5295
+ /**
5296
+ * 获取直播间 WebSocket 元信息接口地址。
5297
+ *
5298
+ * @param liveStreamId - 直播流 ID
5299
+ * @returns 请求配置
5300
+ */
5301
+ liveWebsocketInfo(liveStreamId) {
5302
+ return createKuaishouLiveApiRequest("liveWebsocketInfo", "/live_api/liveroom/websocketinfo", {
5303
+ caver: 2,
5304
+ liveStreamId
5305
+ }, { signPath: "/rest/k/live/websocket/info" });
5306
+ }
5307
+ /**
5308
+ * 获取直播间礼物列表接口地址
5309
+ * @param liveStreamId - 直播流 ID
5310
+ * @returns 请求配置
5311
+ */
5312
+ liveGiftList(liveStreamId) {
5313
+ return createKuaishouLiveApiRequest("liveGiftList", "/live_api/emoji/gift-list", { liveStreamId }, { requiresSign: false });
5314
+ }
5315
+ /**
5027
5316
  * 获取表情列表
5028
5317
  * @returns 请求配置
5029
5318
  */
@@ -5039,147 +5328,1871 @@ var API = class {
5039
5328
  };
5040
5329
  }
5041
5330
  };
5042
- /** 该类下的所有方法只会返回拼接好参数后的 Url 地址和请求体,需要手动请求该地址以获取数据 */
5331
+ /**
5332
+ * 快手 API 请求描述集合。
5333
+ *
5334
+ * 该对象只负责返回请求描述,不直接发起网络请求。
5335
+ */
5043
5336
  const kuaishouApiUrls = new API();
5044
5337
 
5045
5338
  //#endregion
5046
- //#region src/platform/kuaishou/getdata.ts
5339
+ //#region src/platform/kuaishou/sign/hudr.ts
5340
+ const KUAISHOU_HUDR_PREFIX = "HUDR_";
5341
+ const KUAISHOU_HUDR_MASK_BYTE = 35;
5342
+ const KUAISHOU_HUDR_BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
5343
+ const KUAISHOU_HUDR_CHACHA_KEY = [
5344
+ 4183807412,
5345
+ 394484062,
5346
+ 1106561997,
5347
+ 2378328696,
5348
+ 630790222,
5349
+ 2546784104,
5350
+ 2891127470,
5351
+ 1922531795
5352
+ ];
5353
+ const KUAISHOU_HUDR_CHACHA_NONCE = [
5354
+ 2215853858,
5355
+ 1643070585,
5356
+ 1849059804
5357
+ ];
5358
+ /** 将字符串转换为 Unicode code point 数组。 */
5359
+ const toCodePointArray = (value) => {
5360
+ return Array.from(value).map((char) => char.codePointAt(0) ?? 0);
5361
+ };
5362
+ /** 以小端序输出固定长度字节数组。 */
5363
+ const toLittleEndianBytes = (value, size = 4) => {
5364
+ if (size >= 4 && value >= Math.pow(2, 32)) return [
5365
+ 255,
5366
+ 255,
5367
+ 255,
5368
+ 255
5369
+ ];
5370
+ const result = [];
5371
+ for (let index = 0; index <= size - 1; index++) result[index] = value >>> 8 * index & 255;
5372
+ return result;
5373
+ };
5374
+ /** 对字节切片执行标准 Base64 编码。 */
5375
+ const encodeBase64Slice = (bytes, start, end) => {
5376
+ const result = [];
5377
+ for (let index = start; index < end; index += 3) {
5378
+ const value = (bytes[index] << 16 & 16711680) + (bytes[index + 1] << 8 & 65280) + (bytes[index + 2] & 255);
5379
+ result.push(KUAISHOU_HUDR_BASE64_ALPHABET[value >> 18 & 63], KUAISHOU_HUDR_BASE64_ALPHABET[value >> 12 & 63], KUAISHOU_HUDR_BASE64_ALPHABET[value >> 6 & 63], KUAISHOU_HUDR_BASE64_ALPHABET[value & 63]);
5380
+ }
5381
+ return result.join("");
5382
+ };
5383
+ /** 快手 `HUDR_` 使用的 Base64URL 变体编码。 */
5384
+ const encodeBase64Url = (bytes) => {
5385
+ const result = [];
5386
+ const remainder = bytes.length % 3;
5387
+ const tripletLength = bytes.length - remainder;
5388
+ for (let index = 0; index < tripletLength; index += 16383) {
5389
+ const end = tripletLength < index + 16383 ? tripletLength : index + 16383;
5390
+ result.push(encodeBase64Slice(bytes, index, end));
5391
+ }
5392
+ if (remainder === 1) {
5393
+ const value = bytes[bytes.length - 1];
5394
+ result.push(KUAISHOU_HUDR_BASE64_ALPHABET[value >> 2], KUAISHOU_HUDR_BASE64_ALPHABET[value << 4 & 63], "==");
5395
+ } else if (remainder === 2) {
5396
+ const value = (bytes[bytes.length - 2] << 8) + bytes[bytes.length - 1];
5397
+ result.push(KUAISHOU_HUDR_BASE64_ALPHABET[value >> 10], KUAISHOU_HUDR_BASE64_ALPHABET[value >> 4 & 63], KUAISHOU_HUDR_BASE64_ALPHABET[value << 2 & 63], "=");
5398
+ }
5399
+ return result.join("").replace(/[+/=]/g, (char) => {
5400
+ if (char === "+") return "-";
5401
+ if (char === "/") return "_";
5402
+ return ".";
5403
+ });
5404
+ };
5047
5405
  /**
5048
- * 快手数据获取模块
5406
+ * 快手 `HUDR_` 所用的 ChaCha20 变体实现。
5049
5407
  *
5050
- * 注意:为避免循环依赖,此文件直接从具体模块导入,而不是从平台 index 文件导入
5051
- * 循环依赖链:DataFetchers → getdata → platform/kuaishou → DataFetchers
5052
- */
5053
- /**
5054
- * 快手数据获取函数
5055
- * @param data - 请求数据参数
5056
- * @param cookie - 用户Cookie
5057
- * @param requestConfig - 外部请求配置(优先级最高)
5058
- * @returns 返回快手数据
5059
- */
5060
- const KuaishouData = async (data$1, cookie, requestConfig) => {
5061
- const defHeaders = getKuaishouDefaultConfig(cookie)["headers"];
5062
- const baseRequestConfig = {
5063
- method: "POST",
5064
- timeout: 1e4,
5065
- ...requestConfig,
5066
- headers: {
5067
- ...defHeaders,
5068
- ...requestConfig?.headers ?? {}
5069
- }
5070
- };
5071
- switch (data$1.methodType) {
5072
- case "videoWork": {
5073
- const body = kuaishouApiUrls.videoWork({ photoId: data$1.photoId });
5074
- return await GlobalGetData$2(data$1.methodType, {
5075
- ...baseRequestConfig,
5076
- url: body.url,
5077
- data: body.body
5078
- });
5079
- }
5080
- case "comments": {
5081
- const body = kuaishouApiUrls.comments({ photoId: data$1.photoId });
5082
- return await GlobalGetData$2(data$1.methodType, {
5083
- ...baseRequestConfig,
5084
- url: body.url,
5085
- data: body.body
5086
- });
5408
+ * 该实现并非通用密码库封装,而是为对齐页面 `$encode`
5409
+ * 数据流而最小化保留的局部算法。
5410
+ */
5411
+ var KuaishouChaChaCipher = class {
5412
+ wordIndex = 0;
5413
+ state = new Array(16).fill(0);
5414
+ constructor(key, nonce) {
5415
+ this.key = key;
5416
+ this.nonce = nonce;
5417
+ }
5418
+ rotateLeft(value, shift) {
5419
+ return (value << shift | value >>> 32 - shift) >>> 0;
5420
+ }
5421
+ quarterRound(target, a, b, c, d) {
5422
+ target[a] = target[a] + target[b] >>> 0;
5423
+ target[d] ^= target[a];
5424
+ target[d] = this.rotateLeft(target[d], 16);
5425
+ target[c] = target[c] + target[d] >>> 0;
5426
+ target[b] ^= target[c];
5427
+ target[b] = this.rotateLeft(target[b], 12);
5428
+ target[a] = target[a] + target[b] >>> 0;
5429
+ target[d] ^= target[a];
5430
+ target[d] = this.rotateLeft(target[d], 8);
5431
+ target[c] = target[c] + target[d] >>> 0;
5432
+ target[b] ^= target[c];
5433
+ target[b] = this.rotateLeft(target[b], 7);
5434
+ }
5435
+ refillBlock() {
5436
+ const working = this.state.slice();
5437
+ for (let round = 0; round < 20; round += 2) {
5438
+ this.quarterRound(working, 0, 4, 8, 12);
5439
+ this.quarterRound(working, 1, 5, 9, 13);
5440
+ this.quarterRound(working, 2, 6, 10, 14);
5441
+ this.quarterRound(working, 3, 7, 11, 15);
5442
+ this.quarterRound(working, 0, 5, 10, 15);
5443
+ this.quarterRound(working, 1, 6, 11, 12);
5444
+ this.quarterRound(working, 2, 7, 8, 13);
5445
+ this.quarterRound(working, 3, 4, 9, 14);
5087
5446
  }
5088
- case "emojiList": {
5089
- const body = kuaishouApiUrls.emojiList();
5090
- return await GlobalGetData$2(data$1.methodType, {
5091
- ...baseRequestConfig,
5092
- url: body.url,
5093
- data: body.body
5094
- });
5447
+ const mixedState = new Array(16).fill(0);
5448
+ for (let index = 0; index < 16; index++) mixedState[index] = working[index] + this.state[index] >>> 0;
5449
+ return mixedState;
5450
+ }
5451
+ encrypt(input) {
5452
+ this.wordIndex = 0;
5453
+ this.state[0] = 394484062;
5454
+ this.state[1] = 2378328696;
5455
+ this.state[2] = 630790222;
5456
+ this.state[3] = 1922531795;
5457
+ for (let index = 0; index < 8; index++) this.state[index + 4] = this.key[index] >>> 0;
5458
+ this.state[12] = 1;
5459
+ this.state[13] = this.nonce[0] >>> 0;
5460
+ this.state[14] = this.nonce[1] >>> 0;
5461
+ this.state[15] = this.nonce[2] >>> 0;
5462
+ let mixedState = this.refillBlock();
5463
+ const output = new Uint8Array(input.length);
5464
+ for (let index = 0; index < input.length; index++) {
5465
+ if (this.wordIndex === 64) {
5466
+ this.state[12] = this.state[12] + 1 >>> 0;
5467
+ mixedState = this.refillBlock();
5468
+ this.wordIndex = 0;
5469
+ }
5470
+ const keystreamByte = mixedState[this.wordIndex >> 2] >>> ((this.wordIndex & 3) << 3) & 255;
5471
+ this.wordIndex += 1;
5472
+ output[index] = input[index] ^ keystreamByte;
5095
5473
  }
5096
- default:
5097
- logger.warn(`Unknown Kuaishou API method: "${logger.red(data$1.methodType)}"`);
5098
- return null;
5474
+ return output;
5099
5475
  }
5100
5476
  };
5101
5477
  /**
5102
- * 数据获取函数
5103
- * @param options - 网络请求配置选项
5478
+ * 构造 `HUDR_` 头部中的 `infoCache` 字段。
5479
+ *
5480
+ * @param scriptCount - 页面脚本数量
5481
+ * @returns `HUDR_` 载荷中的脚本信息缓存字节
5104
5482
  */
5105
- const GlobalGetData$2 = async (type, options) => {
5106
- let warningMessage = "";
5107
- try {
5108
- const result = await fetchData(options);
5109
- if (isNetworkErrorResult(result)) {
5110
- const networkError = new Error(result.error.amagiError.errorDescription);
5111
- Object.assign(networkError, {
5112
- code: result.error.code,
5113
- data: null,
5114
- amagiError: {
5115
- ...result.error.amagiError,
5116
- requestType: type
5117
- }
5118
- });
5119
- throw networkError;
5120
- }
5121
- if (result === "" || !result || result.result === 2) {
5122
- const Err = {
5123
- errorDescription: "获取响应数据失败!接口返回内容为空!",
5124
- requestType: type ?? "未知请求类型",
5125
- requestUrl: options.url,
5126
- requestBody: JSON.stringify(options.data)
5127
- };
5128
- warningMessage = `
5129
- 获取响应数据失败!原因:${logger.yellow("接口返回内容为空,你的快手ck可能已经失效!")}
5130
- 请求类型:「${type}」
5131
- 请求URL:${options.url}
5132
- 请求参数:${JSON.stringify(options.data, null, 2)}
5133
- `;
5134
- logger.warn(warningMessage);
5135
- const cookieError = new Error(Err.errorDescription);
5136
- Object.assign(cookieError, {
5137
- code: kuaishouAPIErrorCode.COOKIE,
5138
- data: result,
5139
- amagiError: Err
5140
- });
5141
- throw cookieError;
5142
- }
5143
- return result;
5144
- } catch (error) {
5145
- if (error && typeof error === "object") return {
5146
- ...error,
5147
- amagiMessage: warningMessage
5148
- };
5149
- return {
5150
- code: amagiAPIErrorCode.UNKNOWN,
5151
- data: null,
5152
- amagiError: {
5153
- errorDescription: "未知错误",
5154
- requestType: type,
5155
- requestUrl: options.url
5156
- },
5157
- amagiMessage: warningMessage
5158
- };
5159
- }
5483
+ const buildKuaishouHudrInfoCache = (scriptCount = 0) => {
5484
+ return [68, 0].concat(toLittleEndianBytes(scriptCount, 4));
5160
5485
  };
5161
-
5162
- //#endregion
5163
- //#region src/model/fetchers/kuaishou/internal.ts
5164
5486
  /**
5165
- * 快手 Fetcher 内部通用逻辑
5166
- * @module fetchers/kuaishou/internal
5487
+ * 构造快手 `HUDR_` 原始载荷。
5488
+ *
5489
+ * @param context - 生成 `HUDR_` 所需的上下文参数
5490
+ * @returns 尚未异或和加密的原始字节载荷
5167
5491
  */
5492
+ const buildKuaishouHudrPayload = (context) => {
5493
+ const stackTail = context.secs?.s ?? "";
5494
+ const secsCount = context.secs?.c ?? 0;
5495
+ const count = context.count || 0;
5496
+ return [
5497
+ 45,
5498
+ 61,
5499
+ 0,
5500
+ 2
5501
+ ].concat(buildKuaishouHudrInfoCache(context.scriptCount)).concat([112, 0], toLittleEndianBytes(count, 4)).concat([114, 1], toLittleEndianBytes(stackTail.length, 2), toCodePointArray(stackTail)).concat([115, 0], toLittleEndianBytes(secsCount, 4));
5502
+ };
5168
5503
  /**
5169
- * 快手 API 内部调用函数
5170
- * @internal
5171
- * @param methodType - 方法类型
5172
- * @param options - 请求参数
5173
- * @param config - 配置项 (cookie, requestConfig)
5174
- * @returns Promise<Result<T>>
5504
+ * `HUDR_` 原始载荷执行页面同款掩码异或。
5505
+ *
5506
+ * @param payload - 原始 `HUDR_` 字节载荷
5507
+ * @returns 掩码处理后的字节数组
5175
5508
  */
5176
- async function fetchKuaishouInternal(methodType, options, config) {
5177
- const startTime = Date.now();
5178
- try {
5179
- const rawData = await KuaishouData({ ...validateKuaishouParams(methodType, options) }, config.cookie, config.requestConfig);
5180
- const duration = Date.now() - startTime;
5181
- if (rawData.code && Object.values(kuaishouAPIErrorCode).includes(rawData.code)) {
5182
- emitApiError({
5509
+ const maskKuaishouHudrPayload = (payload) => {
5510
+ const masked = new Uint8Array(payload.length);
5511
+ for (let index = 0; index < payload.length; index++) masked[index] = KUAISHOU_HUDR_MASK_BYTE ^ payload[index];
5512
+ return masked;
5513
+ };
5514
+ /**
5515
+ * 推导快手签名中的 `HUDR_` 段。
5516
+ *
5517
+ * @param context - 生成 `HUDR_` 所需的上下文参数
5518
+ * @returns `HUDR_` 的完整结果及若干中间态,便于对拍与调试
5519
+ */
5520
+ const deriveKuaishouHudrBody = (context) => {
5521
+ const maskedPayload = maskKuaishouHudrPayload(buildKuaishouHudrPayload(context));
5522
+ const body = encodeBase64Url(new KuaishouChaChaCipher(KUAISHOU_HUDR_CHACHA_KEY, KUAISHOU_HUDR_CHACHA_NONCE).encrypt(maskedPayload));
5523
+ return {
5524
+ body,
5525
+ full: `${KUAISHOU_HUDR_PREFIX}${body}`,
5526
+ infoCache: buildKuaishouHudrInfoCache(context.scriptCount),
5527
+ maskedPayload,
5528
+ nextCount: context.count + 1
5529
+ };
5530
+ };
5531
+
5532
+ //#endregion
5533
+ //#region src/platform/kuaishou/sign/primitives.ts
5534
+ const KUAISHOU_BLAKE2S_IV = [
5535
+ 2837534710,
5536
+ 2845986804,
5537
+ 2436420605,
5538
+ 706843635,
5539
+ 719254516,
5540
+ 2557931286,
5541
+ 2596197199,
5542
+ 2432949778
5543
+ ];
5544
+ const KUAISHOU_BLAKE2S_SIGMA = [
5545
+ [
5546
+ 0,
5547
+ 1,
5548
+ 2,
5549
+ 3,
5550
+ 4,
5551
+ 5,
5552
+ 6,
5553
+ 7,
5554
+ 8,
5555
+ 9,
5556
+ 10,
5557
+ 11,
5558
+ 12,
5559
+ 13,
5560
+ 14,
5561
+ 15
5562
+ ],
5563
+ [
5564
+ 14,
5565
+ 10,
5566
+ 4,
5567
+ 8,
5568
+ 9,
5569
+ 15,
5570
+ 13,
5571
+ 6,
5572
+ 1,
5573
+ 12,
5574
+ 0,
5575
+ 2,
5576
+ 11,
5577
+ 7,
5578
+ 5,
5579
+ 3
5580
+ ],
5581
+ [
5582
+ 11,
5583
+ 8,
5584
+ 12,
5585
+ 0,
5586
+ 5,
5587
+ 2,
5588
+ 15,
5589
+ 13,
5590
+ 10,
5591
+ 14,
5592
+ 3,
5593
+ 6,
5594
+ 7,
5595
+ 1,
5596
+ 9,
5597
+ 4
5598
+ ],
5599
+ [
5600
+ 7,
5601
+ 9,
5602
+ 3,
5603
+ 1,
5604
+ 13,
5605
+ 12,
5606
+ 11,
5607
+ 14,
5608
+ 2,
5609
+ 6,
5610
+ 5,
5611
+ 10,
5612
+ 4,
5613
+ 0,
5614
+ 15,
5615
+ 8
5616
+ ],
5617
+ [
5618
+ 9,
5619
+ 0,
5620
+ 5,
5621
+ 7,
5622
+ 2,
5623
+ 4,
5624
+ 10,
5625
+ 15,
5626
+ 14,
5627
+ 1,
5628
+ 11,
5629
+ 12,
5630
+ 6,
5631
+ 8,
5632
+ 3,
5633
+ 13
5634
+ ],
5635
+ [
5636
+ 2,
5637
+ 12,
5638
+ 6,
5639
+ 10,
5640
+ 0,
5641
+ 11,
5642
+ 8,
5643
+ 3,
5644
+ 4,
5645
+ 13,
5646
+ 7,
5647
+ 5,
5648
+ 15,
5649
+ 14,
5650
+ 1,
5651
+ 9
5652
+ ],
5653
+ [
5654
+ 12,
5655
+ 5,
5656
+ 1,
5657
+ 15,
5658
+ 14,
5659
+ 13,
5660
+ 4,
5661
+ 10,
5662
+ 0,
5663
+ 7,
5664
+ 6,
5665
+ 3,
5666
+ 9,
5667
+ 2,
5668
+ 8,
5669
+ 11
5670
+ ],
5671
+ [
5672
+ 13,
5673
+ 11,
5674
+ 7,
5675
+ 14,
5676
+ 12,
5677
+ 1,
5678
+ 3,
5679
+ 9,
5680
+ 5,
5681
+ 0,
5682
+ 15,
5683
+ 4,
5684
+ 8,
5685
+ 6,
5686
+ 2,
5687
+ 10
5688
+ ],
5689
+ [
5690
+ 6,
5691
+ 15,
5692
+ 14,
5693
+ 9,
5694
+ 11,
5695
+ 3,
5696
+ 0,
5697
+ 8,
5698
+ 12,
5699
+ 2,
5700
+ 13,
5701
+ 7,
5702
+ 1,
5703
+ 4,
5704
+ 10,
5705
+ 5
5706
+ ],
5707
+ [
5708
+ 10,
5709
+ 2,
5710
+ 8,
5711
+ 4,
5712
+ 7,
5713
+ 6,
5714
+ 1,
5715
+ 5,
5716
+ 15,
5717
+ 11,
5718
+ 9,
5719
+ 14,
5720
+ 3,
5721
+ 12,
5722
+ 13,
5723
+ 0
5724
+ ]
5725
+ ];
5726
+ const KUAISHOU_CTS_STATE_VECTOR = new Int8Array([
5727
+ 98,
5728
+ 0,
5729
+ 0,
5730
+ 128,
5731
+ 49,
5732
+ 117,
5733
+ 185,
5734
+ 253,
5735
+ 224,
5736
+ 172,
5737
+ 104,
5738
+ 36,
5739
+ 223,
5740
+ 155,
5741
+ 87,
5742
+ 19,
5743
+ 32,
5744
+ 0,
5745
+ 0,
5746
+ 64,
5747
+ 2,
5748
+ 0,
5749
+ 0,
5750
+ 16,
5751
+ 255,
5752
+ 255,
5753
+ 255,
5754
+ 127,
5755
+ 255,
5756
+ 255,
5757
+ 255,
5758
+ 63,
5759
+ 0,
5760
+ 0,
5761
+ 0,
5762
+ 240,
5763
+ 0,
5764
+ 0,
5765
+ 0,
5766
+ 192,
5767
+ 0,
5768
+ 0,
5769
+ 0,
5770
+ 128,
5771
+ 255,
5772
+ 255,
5773
+ 255,
5774
+ 15
5775
+ ]);
5776
+ const rotateRight = (value, shift) => {
5777
+ return (value >>> shift | value << 32 - shift) >>> 0;
5778
+ };
5779
+ const toUtf8Int8Array = (value) => {
5780
+ return new Int8Array(unescape(encodeURIComponent(value)).split("").map((char) => char.charCodeAt(0) || 0));
5781
+ };
5782
+ const toHex32 = (value) => {
5783
+ return (value >>> 0).toString(16).padStart(8, "0");
5784
+ };
5785
+ const blake2sQuarterRound = (state, a, b, c, d, x, y) => {
5786
+ state[a] = state[a] + state[b] + x >>> 0;
5787
+ state[d] = rotateRight(state[d] ^ state[a], 16);
5788
+ state[c] = state[c] + state[d] >>> 0;
5789
+ state[b] = rotateRight(state[b] ^ state[c], 12);
5790
+ state[a] = state[a] + state[b] + y >>> 0;
5791
+ state[d] = rotateRight(state[d] ^ state[a], 8);
5792
+ state[c] = state[c] + state[d] >>> 0;
5793
+ state[b] = rotateRight(state[b] ^ state[c], 7);
5794
+ };
5795
+ const blake2sCompress = (hash, words, offset, counter, length, isLastBlock) => {
5796
+ const work = new Array(16).fill(0);
5797
+ const block = new Array(16).fill(0);
5798
+ for (let index = 0; index < 8; index++) {
5799
+ work[index] = hash[index] >>> 0;
5800
+ work[index + 8] = KUAISHOU_BLAKE2S_IV[index] >>> 0;
5801
+ }
5802
+ work[12] ^= counter >>> 0;
5803
+ if (isLastBlock) work[14] ^= 4294967295;
5804
+ for (let index = 0; index < length; index++) block[index % 16] ^= words[offset + index] >>> 0;
5805
+ for (const sigma of KUAISHOU_BLAKE2S_SIGMA) {
5806
+ blake2sQuarterRound(work, 0, 4, 8, 12, block[sigma[0]], block[sigma[1]]);
5807
+ blake2sQuarterRound(work, 1, 5, 9, 13, block[sigma[2]], block[sigma[3]]);
5808
+ blake2sQuarterRound(work, 2, 6, 10, 14, block[sigma[4]], block[sigma[5]]);
5809
+ blake2sQuarterRound(work, 3, 7, 11, 15, block[sigma[6]], block[sigma[7]]);
5810
+ blake2sQuarterRound(work, 0, 5, 10, 15, block[sigma[8]], block[sigma[9]]);
5811
+ blake2sQuarterRound(work, 1, 6, 11, 12, block[sigma[10]], block[sigma[11]]);
5812
+ blake2sQuarterRound(work, 2, 7, 8, 13, block[sigma[12]], block[sigma[13]]);
5813
+ blake2sQuarterRound(work, 3, 4, 9, 14, block[sigma[14]], block[sigma[15]]);
5814
+ }
5815
+ for (let index = 0; index < 8; index++) hash[index] = (hash[index] ^ work[index] ^ work[index + 8]) >>> 0;
5816
+ return hash;
5817
+ };
5818
+ const deriveB2hasWords = (value) => {
5819
+ const utf8 = toUtf8Int8Array(value);
5820
+ const padding = utf8.length % 4 === 0 ? 0 : 4 - utf8.length % 4;
5821
+ const padded = new Int8Array(utf8.length + padding);
5822
+ for (let index = 0; index < utf8.length; index++) padded[index] = utf8[index];
5823
+ const words = new Array(padded.length / 4);
5824
+ for (let index = 0; index < padded.length; index += 4) words[index / 4] = new Int32Array(padded.slice(index, index + 4).buffer)[0];
5825
+ return words;
5826
+ };
5827
+ const deriveB2hasHash = (words) => {
5828
+ const hash = KUAISHOU_BLAKE2S_IV.slice();
5829
+ hash[0] ^= 16842784;
5830
+ let offset = 0;
5831
+ let length = words.length;
5832
+ let counter = 0;
5833
+ while (length > 64) {
5834
+ length -= 64;
5835
+ counter += 64;
5836
+ blake2sCompress(hash, words, offset, counter, 64, false);
5837
+ offset += 64;
5838
+ }
5839
+ return blake2sCompress(hash, words, offset, counter + length, length, true);
5840
+ };
5841
+ /**
5842
+ * 计算快手页面中的 `b2has` 字符串。
5843
+ *
5844
+ * 这是后续 `b2sa` / `cts` 链路的起点。
5845
+ *
5846
+ * @param value - 原始输入字符串
5847
+ * @returns `b2has` 结果字符串
5848
+ */
5849
+ const deriveKuaishouB2has = (value) => {
5850
+ return deriveB2hasHash(deriveB2hasWords(value)).map(toHex32).join("");
5851
+ };
5852
+ /**
5853
+ * 将输入字符串转换为快手 `b2sa` 所需的字节数组。
5854
+ *
5855
+ * @param value - 原始输入字符串
5856
+ * @returns `b2has` 结果转 UTF-8 后的 Int8Array
5857
+ */
5858
+ const deriveKuaishouB2sa = (value) => {
5859
+ return toUtf8Int8Array(deriveKuaishouB2has(value));
5860
+ };
5861
+ const createKuaishouCtsState = () => {
5862
+ return {
5863
+ s: new Int32Array(KUAISHOU_CTS_STATE_VECTOR.slice(12, 16).buffer)[0],
5864
+ u: new Int32Array(KUAISHOU_CTS_STATE_VECTOR.slice(8, 12).buffer)[0],
5865
+ c: new Int32Array(KUAISHOU_CTS_STATE_VECTOR.slice(4, 8).buffer)[0],
5866
+ l: new Int32Array(KUAISHOU_CTS_STATE_VECTOR.slice(0, 4).buffer)[0],
5867
+ p: new Int32Array(KUAISHOU_CTS_STATE_VECTOR.slice(16, 20).buffer)[0],
5868
+ f: new Int32Array(KUAISHOU_CTS_STATE_VECTOR.slice(20, 24).buffer)[0],
5869
+ d: new Int32Array(KUAISHOU_CTS_STATE_VECTOR.slice(24, 28).buffer)[0],
5870
+ y: new Int32Array(KUAISHOU_CTS_STATE_VECTOR.slice(28, 32).buffer)[0],
5871
+ h: new Int32Array(KUAISHOU_CTS_STATE_VECTOR.slice(44, 48).buffer)[0],
5872
+ E: new Int32Array(KUAISHOU_CTS_STATE_VECTOR.slice(40, 44).buffer)[0],
5873
+ m: new Int32Array(KUAISHOU_CTS_STATE_VECTOR.slice(36, 40).buffer)[0],
5874
+ b: new Int32Array(KUAISHOU_CTS_STATE_VECTOR.slice(32, 36).buffer)[0]
5875
+ };
5876
+ };
5877
+ const seedKuaishouCtsState = (state, seed) => {
5878
+ const chars = new Int8Array(seed.split("").map((char) => char.codePointAt(0) ?? 0)).slice(0, seed.length);
5879
+ for (let index = 0; index < 4; index++) {
5880
+ state.s = (state.s <<= 8) | chars[index + 4];
5881
+ state.u = (state.u <<= 8) | chars[index + 4];
5882
+ state.c = (state.c <<= 8) | chars[index + 4];
5883
+ }
5884
+ if (state.s === 0) state.s = 324508639;
5885
+ if (state.u === 0) state.u = 610839776;
5886
+ if (state.c === 0) state.c = 4256789809;
5887
+ };
5888
+ const deriveKuaishouCtsByte = (state, value) => {
5889
+ let result = 0;
5890
+ let rightBit = 1 & state.u;
5891
+ let leftBit = 1 & state.c;
5892
+ for (let index = 0; index < 8; index++) {
5893
+ if (1 & state.s) {
5894
+ state.s = state.s ^ state.l >> 1 & 4294967295 | state.E;
5895
+ rightBit = 1 & state.u ? (state.u = state.u ^ state.p >> 1 & 4294967295 | state.m, 1) : (state.u = state.u >> 1 & 4294967295 & state.y, 0);
5896
+ } else {
5897
+ state.s = state.s >> 1 & 4294967295 & state.d;
5898
+ leftBit = 1 & state.c ? (state.c = state.c ^ state.f >> 1 & 4294967295 | state.b, 1) : (state.c = state.c >> 1 & 4294967295 & state.h, 0);
5899
+ }
5900
+ const mixed = result << 1 & 4294967295 | rightBit ^ leftBit;
5901
+ result = mixed > 127 ? mixed - 256 : mixed < -128 ? mixed + 256 : mixed;
5902
+ }
5903
+ return value ^ result + 3;
5904
+ };
5905
+ /**
5906
+ * 执行快手页面中的 `cts` 字节流变换。
5907
+ *
5908
+ * 该变换会作用在 `b2sa` 的输出上,随后被 `$HE_` 使用。
5909
+ *
5910
+ * @param input - 输入字节数组
5911
+ * @returns 变换后的字节数组
5912
+ */
5913
+ const deriveKuaishouCts = (input) => {
5914
+ const state = createKuaishouCtsState();
5915
+ seedKuaishouCtsState(state, "Vuz4fCHxn1CO");
5916
+ const result = new Int8Array(input.length);
5917
+ for (let index = 0; index < input.length; index++) result[index] = deriveKuaishouCtsByte(state, input[index]);
5918
+ return result;
5919
+ };
5920
+ /**
5921
+ * 将字节数组编码为小写 hex 字符串。
5922
+ *
5923
+ * @param bytes - 任意可按字节访问的数组
5924
+ * @returns 小写 hex 字符串
5925
+ */
5926
+ const bytesToLowerHex = (bytes) => {
5927
+ let result = "";
5928
+ for (const byte of Array.from(bytes)) {
5929
+ const value = byte & 255;
5930
+ result += value === 0 ? "00" : `${value < 16 ? "0" : ""}${value.toString(16)}`;
5931
+ }
5932
+ return result;
5933
+ };
5934
+ /**
5935
+ * 将 hex 字符串还原为带符号字节数组。
5936
+ *
5937
+ * @param value - 偶数字节长度的 hex 字符串
5938
+ * @returns 带符号 byte 数组
5939
+ */
5940
+ const hexToSignedBytes = (value) => {
5941
+ const result = [];
5942
+ for (let index = 0; index < value.length; index += 2) {
5943
+ const parsed = parseInt(value.slice(index, index + 2), 16);
5944
+ const range = Math.pow(2, value.slice(index, index + 2).length / 2 * 8);
5945
+ result.push(parsed > range / 2 - 1 ? parsed - range : parsed);
5946
+ }
5947
+ return result;
5948
+ };
5949
+ /**
5950
+ * 对两个字节数组执行循环异或。
5951
+ *
5952
+ * @param left - 被处理的字节数组
5953
+ * @param right - 异或掩码字节数组
5954
+ * @returns 异或后的 Int8Array
5955
+ */
5956
+ const xorByteArrays = (left, right) => {
5957
+ let index = 0;
5958
+ const result = new Int8Array(left.length);
5959
+ while (index < left.length) for (let rightIndex = 0; rightIndex < right.length && index < left.length; rightIndex++) {
5960
+ result[index] = left[index] ^ 255 & right[rightIndex];
5961
+ index += 1;
5962
+ }
5963
+ return result;
5964
+ };
5965
+ /**
5966
+ * 将数值编码为固定长度的小端 hex。
5967
+ *
5968
+ * @param value - 原始数值
5969
+ * @param size - 输出字节长度
5970
+ * @returns 小端序 hex 字符串
5971
+ */
5972
+ const toLittleEndianHex = (value, size) => {
5973
+ const normalized = BigInt.asUintN(size * 8, typeof value === "bigint" ? value : BigInt(Math.trunc(value)));
5974
+ let result = "";
5975
+ for (let index = 0; index < size; index++) {
5976
+ const byte = Number(normalized >> BigInt(8 * index) & 255n);
5977
+ result += byte.toString(16).padStart(2, "0");
5978
+ }
5979
+ return result;
5980
+ };
5981
+ /**
5982
+ * 计算快手 `$HE_` 所使用的 8-bit LRC 校验值。
5983
+ *
5984
+ * @param sourceHex - 原始 hex 字符串
5985
+ * @returns 单字节 LRC 的 hex 表示
5986
+ */
5987
+ const computeKuaishouLrcHex = (sourceHex) => {
5988
+ return (-hexToSignedBytes(sourceHex).reduce((total, value) => total + (value & 255), 0) & 255).toString(16).padStart(2, "0");
5989
+ };
5990
+ /**
5991
+ * 对 `$HE_` 的 preHex + checksum 执行最终变换。
5992
+ *
5993
+ * 页面实现会以最后一个字节为 key,对前面的字节逐个异或,
5994
+ * 最后再整体转回小写 hex。
5995
+ *
5996
+ * @param prefixHex - 不含最终 LRC 的前缀 hex
5997
+ * @param checksumHex - 最终 LRC hex
5998
+ * @returns `$HE_` 最终输出的 hex 字符串
5999
+ */
6000
+ const transformKuaishouHeHex = (prefixHex, checksumHex) => {
6001
+ const input = hexToSignedBytes(`${prefixHex}${checksumHex}`);
6002
+ const output = new Int8Array(input.length);
6003
+ const xorKey = input[input.length - 1];
6004
+ for (let index = 0; index < input.length - 1; index++) output[index] = input[index] ^ xorKey;
6005
+ output[input.length - 1] = xorKey;
6006
+ return bytesToLowerHex(output);
6007
+ };
6008
+
6009
+ //#endregion
6010
+ //#region src/platform/kuaishou/sign/he.ts
6011
+ const KUAISHOU_HE_HEADER_HEX = "4B54";
6012
+ const KUAISHOU_HE_VERSION_HEX = "cda9";
6013
+ const KUAISHOU_HE_STARTUP_MARKER_HEX = "ab";
6014
+ const KUAISHOU_HE_FIXED_BODY_HEX = "0100000001";
6015
+ const KUAISHOU_HE_INPUT_XOR_MASK = [
6016
+ 45,
6017
+ 211,
6018
+ 69,
6019
+ 192
6020
+ ];
6021
+ const KUAISHOU_HE_COUNTER_XOR_MASK = 3131873467;
6022
+ const KUAISHOU_HE_TIME_XOR_MASK = 3360347992n;
6023
+ const KUAISHOU_HE_TAIL_HEX = "9b563eda7b563e";
6024
+ const KUAISHOU_HE_RANDOM_MAX = 0xffffffffffff;
6025
+ /**
6026
+ * 计算 `$HE_` 中的 hash field。
6027
+ *
6028
+ * 它由 `signInput + HUDRBody` 经 `b2sa -> cts -> 截断 -> 异或掩码`
6029
+ * 这条链路导出,是 `$HE_` 中与接口输入最直接相关的一段。
6030
+ *
6031
+ * @param signInput - 快手 `__NS_hxfalcon` 的 sign input
6032
+ * @param hudrBody - `HUDR_` 去前缀后的主体
6033
+ * @returns `$HE_` 载荷中的 4 字节 hash field hex
6034
+ */
6035
+ const deriveKuaishouHeHashFieldHex = (signInput, hudrBody) => {
6036
+ return bytesToLowerHex(xorByteArrays(hexToSignedBytes(bytesToLowerHex(deriveKuaishouCts(deriveKuaishouB2sa(`${signInput}HUDR_${hudrBody}`))).slice(0, 8)), KUAISHOU_HE_INPUT_XOR_MASK));
6037
+ };
6038
+ /**
6039
+ * 推导快手签名中的 `$HE_` 段。
6040
+ *
6041
+ * @param context - 生成 `$HE_` 所需的上下文参数
6042
+ * @returns `$HE_` 的最终 hex、中间 hash field 和 preHex
6043
+ */
6044
+ const deriveKuaishouHeHex = (context) => {
6045
+ const random48 = Math.floor(context.randomValue * KUAISHOU_HE_RANDOM_MAX);
6046
+ const hashFieldHex = deriveKuaishouHeHashFieldHex(context.signInput, context.hudrBody);
6047
+ const timeXor = BigInt(context.timestamp) ^ KUAISHOU_HE_TIME_XOR_MASK;
6048
+ const preHex = [
6049
+ KUAISHOU_HE_HEADER_HEX,
6050
+ KUAISHOU_HE_VERSION_HEX,
6051
+ KUAISHOU_HE_STARTUP_MARKER_HEX,
6052
+ toLittleEndianHex(context.startupRandom, 6),
6053
+ toLittleEndianHex(random48, 6),
6054
+ KUAISHOU_HE_FIXED_BODY_HEX,
6055
+ toLittleEndianHex(context.count ^ KUAISHOU_HE_COUNTER_XOR_MASK, 4),
6056
+ hashFieldHex,
6057
+ toLittleEndianHex(timeXor, 6),
6058
+ KUAISHOU_HE_TAIL_HEX,
6059
+ computeKuaishouLrcHex(KUAISHOU_HE_TAIL_HEX)
6060
+ ].join("");
6061
+ return {
6062
+ finalHex: transformKuaishouHeHex(preHex, computeKuaishouLrcHex(preHex)),
6063
+ hashFieldHex,
6064
+ preHex
6065
+ };
6066
+ };
6067
+ /**
6068
+ * 一次性推导快手完整纯算法签名。
6069
+ *
6070
+ * 该方法会先生成 `HUDR_`,再拼出 `$HE_`,最终返回完整
6071
+ * `HUDR_...$HE_...` 形式的 `__NS_hxfalcon`。
6072
+ *
6073
+ * @param context - 快手纯算法签名上下文
6074
+ * @returns 完整签名结果及关键中间态
6075
+ */
6076
+ const deriveKuaishouPureSignature = (context) => {
6077
+ const hudr = deriveKuaishouHudrBody(context);
6078
+ const he = deriveKuaishouHeHex({
6079
+ count: context.count,
6080
+ hudrBody: hudr.body,
6081
+ randomValue: context.randomValue,
6082
+ signInput: context.signInput,
6083
+ startupRandom: context.startupRandom,
6084
+ timestamp: context.timestamp
6085
+ });
6086
+ return {
6087
+ ...hudr,
6088
+ he,
6089
+ signResult: `${hudr.full}$HE_${he.finalHex}`
6090
+ };
6091
+ };
6092
+
6093
+ //#endregion
6094
+ //#region src/platform/kuaishou/sign/helpers.ts
6095
+ const SIGN_INPUT_SKIP_KEYWORD = "__NS";
6096
+ const KUAISHOU_ANONYMOUS_KWW_KEY = "K8wm5PvY9nX7qJc2";
6097
+ const KUAISHOU_ANONYMOUS_KWW_SUFFIX = "###ssrd";
6098
+ const KUAISHOU_ANONYMOUS_KWW_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
6099
+ let kuaishouAnonymousKwwCache = "";
6100
+ const compareLexicographically = (left, right) => {
6101
+ if (left === right) return 0;
6102
+ return left < right ? -1 : 1;
6103
+ };
6104
+ const toComparableEntry = (key, value) => {
6105
+ if (typeof value === "object" && value !== null && !Array.isArray(value)) return `${key}=[object Object]`;
6106
+ return `${key}=${String(value)}`;
6107
+ };
6108
+ const normalizePathname = (urlOrPath) => {
6109
+ try {
6110
+ return new URL(urlOrPath).pathname;
6111
+ } catch {
6112
+ return urlOrPath.split("?")[0] ?? urlOrPath;
6113
+ }
6114
+ };
6115
+ const sortSearchParams = (searchParams) => {
6116
+ return [...searchParams.entries()].sort(([leftKey], [rightKey]) => compareLexicographically(leftKey, rightKey)).reduce((result, [key, value]) => {
6117
+ result[key] = value;
6118
+ return result;
6119
+ }, {});
6120
+ };
6121
+ /**
6122
+ * 解析快手签名所使用的规范路径。
6123
+ *
6124
+ * 快手部分 `live_api` 接口的公开 URL 与算法内部参与签名的路径并不一致,
6125
+ * 因此这里优先使用调用方显式传入的 `signPath`,否则回退到 URL 自身的 pathname。
6126
+ *
6127
+ * @param urlOrPath - 原始 URL 或路径
6128
+ * @param signPath - 调用方声明的规范签名路径
6129
+ * @returns 参与签名的规范 pathname
6130
+ */
6131
+ const resolveKuaishouHxfalconSignPath = (urlOrPath, signPath) => {
6132
+ const pathname = normalizePathname(signPath ?? urlOrPath);
6133
+ if (!pathname.startsWith("/")) throw new Error(`Invalid Kuaishou signing path: ${pathname}`);
6134
+ return pathname;
6135
+ };
6136
+ /**
6137
+ * 从请求 URL 构造快手签名载荷。
6138
+ *
6139
+ * @param url - 实际请求 URL
6140
+ * @param signPath - 可选的规范签名路径
6141
+ * @returns 供纯算法签名链路使用的结构化载荷
6142
+ */
6143
+ const buildKuaishouHxfalconPayload = (url, signPath) => {
6144
+ const parsedUrl = new URL(url);
6145
+ const realPath = resolveKuaishouHxfalconSignPath(parsedUrl.pathname, signPath);
6146
+ const query = sortSearchParams(parsedUrl.searchParams);
6147
+ if (!query.caver) throw new Error(`Missing caver query parameter for Kuaishou signing: ${url}`);
6148
+ return {
6149
+ url: realPath,
6150
+ query,
6151
+ form: {},
6152
+ requestBody: {}
6153
+ };
6154
+ };
6155
+ /**
6156
+ * 构造快手 `__NS_hxfalcon` 的 sign input。
6157
+ *
6158
+ * 该字符串就是后续 `HUDR_` / `$HE_` 两段算法共同依赖的输入材料。
6159
+ *
6160
+ * @param payload - 已标准化的签名载荷
6161
+ * @returns 按快手页面规则拼接后的 sign input
6162
+ */
6163
+ const buildKuaishouHxfalconSignInput = (payload) => {
6164
+ const combinedParams = {
6165
+ ...payload.query,
6166
+ ...payload.form
6167
+ };
6168
+ const serializedParams = Object.keys(combinedParams).filter((key) => !key.includes(SIGN_INPUT_SKIP_KEYWORD)).map((key) => toComparableEntry(key, combinedParams[key])).sort(compareLexicographically).join("");
6169
+ const requestBody = Object.keys(payload.requestBody).length > 0 ? JSON.stringify(payload.requestBody) : "";
6170
+ return `${normalizePathname(payload.url)}${serializedParams}${requestBody}`;
6171
+ };
6172
+ /**
6173
+ * 从 Cookie 字符串中提取指定键值。
6174
+ *
6175
+ * @param cookie - 原始 Cookie 字符串
6176
+ * @param key - 目标键名
6177
+ * @returns 命中的 Cookie 值;未命中时返回空字符串
6178
+ */
6179
+ const extractCookieValue = (cookie, key) => {
6180
+ if (!cookie?.trim()) return "";
6181
+ const pattern = /* @__PURE__ */ new RegExp(`(?:^|;\\s*)${key}=([^;]*)`);
6182
+ return cookie.match(pattern)?.[1] ?? "";
6183
+ };
6184
+ const generateKuaishouAnonymousKwwSeed = () => {
6185
+ let randomPart = "";
6186
+ for (let index = 0; index < 8; index++) {
6187
+ const randomIndex = Math.floor(Math.random() * 62);
6188
+ randomPart += KUAISHOU_ANONYMOUS_KWW_ALPHABET[randomIndex];
6189
+ }
6190
+ return `${Date.now()}|${randomPart}`;
6191
+ };
6192
+ const encryptKuaishouAnonymousKwwSeed = (seed) => {
6193
+ const key = Buffer.from(KUAISHOU_ANONYMOUS_KWW_KEY, "utf8");
6194
+ const cipher = node_crypto.default.createCipheriv("aes-128-cbc", key, key);
6195
+ return Buffer.concat([cipher.update(seed, "utf8"), cipher.final()]).toString("base64");
6196
+ };
6197
+ /**
6198
+ * 生成匿名访问快手 `live_api` 时所需的 `kww`。
6199
+ *
6200
+ * 页面未持有 `kwfv1` 时,会退回到一段本地 AES 生成值。
6201
+ * 这里将其缓存到当前进程,模拟浏览器侧“同一访客会话复用同一份访客标识”的行为。
6202
+ *
6203
+ * @returns 可用于匿名请求的 `kww`
6204
+ */
6205
+ const deriveKuaishouAnonymousKww = () => {
6206
+ if (!kuaishouAnonymousKwwCache) kuaishouAnonymousKwwCache = `${encryptKuaishouAnonymousKwwSeed(generateKuaishouAnonymousKwwSeed())}${KUAISHOU_ANONYMOUS_KWW_SUFFIX}`;
6207
+ return kuaishouAnonymousKwwCache;
6208
+ };
6209
+ /**
6210
+ * 解析快手 `kww` 请求头。
6211
+ *
6212
+ * 当前页面存在两条路径:
6213
+ * 1. 若已有 `kwfv1`,则 `kww` 直接复用该值
6214
+ * 2. 若还没有访客 Cookie,则退回到页面本地生成的匿名 `kww`
6215
+ *
6216
+ * @param cookie - 原始 Cookie 字符串
6217
+ * @returns 可用于请求头的 `kww` 值
6218
+ */
6219
+ const deriveKuaishouKww = (cookie) => {
6220
+ const kwfv1 = extractCookieValue(cookie, "kwfv1");
6221
+ if (kwfv1) return kwfv1;
6222
+ return deriveKuaishouAnonymousKww();
6223
+ };
6224
+
6225
+ //#endregion
6226
+ //#region src/platform/kuaishou/sign/state.ts
6227
+ const KUAISHOU_DEFAULT_CAT_VERSION = "2";
6228
+ const KUAISHOU_DEFAULT_COUNT = 100;
6229
+ const KUAISHOU_SECS_STACK_LIMIT = 100;
6230
+ let pureRuntimeState = null;
6231
+ const captureKuaishouEncodeStack = () => {
6232
+ try {
6233
+ throw new Error();
6234
+ } catch (error) {
6235
+ return error instanceof Error ? error.stack ?? "" : "";
6236
+ }
6237
+ };
6238
+ /**
6239
+ * 计算快手 `SECS.s` 所需的调用栈尾部。
6240
+ *
6241
+ * 真实页面会取 `Error().stack` 的最后 100 个字符,
6242
+ * 这里保持同样的裁剪规则。
6243
+ *
6244
+ * @param stack - 可选的完整调用栈;不传时会即时捕获
6245
+ * @returns 用于 `SECS.s` 的栈尾字符串
6246
+ */
6247
+ const deriveKuaishouSecsStackTail = (stack = captureKuaishouEncodeStack()) => {
6248
+ return stack.length > KUAISHOU_SECS_STACK_LIMIT ? stack.slice(-KUAISHOU_SECS_STACK_LIMIT) : stack;
6249
+ };
6250
+ /**
6251
+ * 构造快手 `window.SECS` 的纯算法等价状态。
6252
+ *
6253
+ * @param count - 当前签名计数器
6254
+ * @param stack - 可选的完整调用栈
6255
+ * @returns 包含 `s` 与 `c` 的 `SECS` 状态对象
6256
+ */
6257
+ const deriveKuaishouSecsState = (count, stack) => {
6258
+ return {
6259
+ s: deriveKuaishouSecsStackTail(stack),
6260
+ c: count
6261
+ };
6262
+ };
6263
+ /**
6264
+ * 获取快手纯算法运行时状态。
6265
+ *
6266
+ * 首次调用时会初始化默认 `caver`、`count` 和 `startupRandom`,
6267
+ * 后续调用复用同一份状态。
6268
+ *
6269
+ * @returns 当前进程级的快手纯算法运行时状态
6270
+ */
6271
+ const getKuaishouPureRuntimeState = () => {
6272
+ if (pureRuntimeState) return pureRuntimeState;
6273
+ pureRuntimeState = {
6274
+ catVersion: KUAISHOU_DEFAULT_CAT_VERSION,
6275
+ count: KUAISHOU_DEFAULT_COUNT,
6276
+ startupRandom: Date.now()
6277
+ };
6278
+ return pureRuntimeState;
6279
+ };
6280
+
6281
+ //#endregion
6282
+ //#region src/platform/kuaishou/sign/index.ts
6283
+ /**
6284
+ * 快手签名工具集。
6285
+ *
6286
+ * 负责将请求描述对象转换为带 `__NS_hxfalcon` / `kww` 的最终请求材料。
6287
+ */
6288
+ var kuaishouSign = class {
6289
+ /**
6290
+ * 获取当前纯算法运行时使用的 `caver`。
6291
+ *
6292
+ * @returns 快手 `caver` 值
6293
+ */
6294
+ static getCatVersion() {
6295
+ return getKuaishouPureRuntimeState().catVersion;
6296
+ }
6297
+ /**
6298
+ * 生成快手请求头中的 `kww` 值。
6299
+ *
6300
+ * @param cookie - 原始 Cookie 字符串
6301
+ * @returns `kww` 请求头值
6302
+ */
6303
+ static generateKww(cookie) {
6304
+ return deriveKuaishouKww(cookie);
6305
+ }
6306
+ /**
6307
+ * 根据结构化签名载荷生成 `__NS_hxfalcon`。
6308
+ *
6309
+ * @param payload - 已标准化的快手签名载荷
6310
+ * @returns 包含最终签名串、sign input 与 `caver` 的结果
6311
+ */
6312
+ static generateHxfalconFromPayload(payload) {
6313
+ const signInput = buildKuaishouHxfalconSignInput(payload);
6314
+ const runtimeState = getKuaishouPureRuntimeState();
6315
+ const count = runtimeState.count;
6316
+ const secs = deriveKuaishouSecsState(count);
6317
+ const scriptCount = globalThis.document?.scripts?.length ?? 0;
6318
+ const catVersion = runtimeState.catVersion;
6319
+ const signResult = deriveKuaishouPureSignature({
6320
+ count,
6321
+ randomValue: Math.random(),
6322
+ scriptCount,
6323
+ secs,
6324
+ signInput,
6325
+ startupRandom: runtimeState.startupRandom,
6326
+ timestamp: Date.now()
6327
+ }).signResult;
6328
+ runtimeState.count += 1;
6329
+ return {
6330
+ signResult,
6331
+ signInput,
6332
+ catVersion
6333
+ };
6334
+ }
6335
+ /**
6336
+ * 为快手 `live_api` URL 签名。
6337
+ *
6338
+ * @param url - 实际请求 URL
6339
+ * @param cookie - 原始 Cookie 字符串
6340
+ * @param signPath - 可选的规范签名路径
6341
+ * @returns 带签名 URL、附加请求头和调试信息
6342
+ */
6343
+ static signLiveApiUrl(url, cookie, signPath) {
6344
+ const payload = buildKuaishouHxfalconPayload(url, signPath);
6345
+ const { signResult, signInput, catVersion } = this.generateHxfalconFromPayload(payload);
6346
+ const signedUrl = new URL(url);
6347
+ const headers = {};
6348
+ const kww = deriveKuaishouKww(cookie);
6349
+ signedUrl.searchParams.set("__NS_hxfalcon", signResult);
6350
+ signedUrl.searchParams.set("caver", catVersion);
6351
+ if (kww) headers.kww = kww;
6352
+ return {
6353
+ url: signedUrl.toString(),
6354
+ headers,
6355
+ signResult,
6356
+ signInput,
6357
+ catVersion
6358
+ };
6359
+ }
6360
+ /**
6361
+ * 为结构化的快手 `live_api` 请求描述对象签名。
6362
+ *
6363
+ * 这是项目层更推荐使用的入口,因为它能显式保留 `signPath` 元数据。
6364
+ *
6365
+ * @param request - 快手 `live_api` 请求描述对象
6366
+ * @param cookie - 原始 Cookie 字符串
6367
+ * @returns 带签名 URL、附加请求头和调试信息
6368
+ */
6369
+ static signLiveApiRequest(request, cookie) {
6370
+ return this.signLiveApiUrl(request.url, cookie, request.signPath);
6371
+ }
6372
+ };
6373
+
6374
+ //#endregion
6375
+ //#region src/platform/kuaishou/getdata.ts
6376
+ /**
6377
+ * 快手数据获取模块
6378
+ *
6379
+ * 注意:为避免循环依赖,此文件直接从具体模块导入,而不是从平台 index 文件导入
6380
+ * 循环依赖链:DataFetchers → getdata → platform/kuaishou → DataFetchers
6381
+ */
6382
+ const KUAISHOU_PROFILE_TAB_TYPE_MAP = {
6383
+ public: "public",
6384
+ private: "private",
6385
+ liked: "liked",
6386
+ playback: "playback"
6387
+ };
6388
+ const KUAISHOU_BAN_STATE_MAP = {
6389
+ banned: "BANNED",
6390
+ socialBanned: "SOCIALBANNED",
6391
+ isolate: "ISOLATE",
6392
+ cleanState: "CLEAN"
6393
+ };
6394
+ /**
6395
+ * 构造快手 `live_api` 请求配置。
6396
+ *
6397
+ * 该函数负责把 API 层产出的 `KuaishouLiveApiRequest`
6398
+ * 与签名层拼装起来,生成最终可发送的 JSON 请求配置。
6399
+ *
6400
+ * @param request - 快手 `live_api` 请求描述对象
6401
+ * @param refererPath - 用于构造 Referer 的页面相对路径
6402
+ * @param cookie - 用户 Cookie
6403
+ * @param requestConfig - 外部透传的请求配置
6404
+ * @returns 可直接用于 `fetchData` 的 Axios 请求配置
6405
+ */
6406
+ const buildLiveApiRequestConfig = (request, refererPath, cookie, requestConfig) => {
6407
+ const signedRequest = request.requiresSign === false ? {
6408
+ url: request.url,
6409
+ headers: {}
6410
+ } : kuaishouSign.signLiveApiRequest(request, cookie);
6411
+ const kww = kuaishouSign.generateKww(cookie);
6412
+ const isPostRequest = request.method === "POST";
6413
+ return {
6414
+ method: request.method,
6415
+ timeout: 1e4,
6416
+ ...requestConfig,
6417
+ url: signedRequest.url,
6418
+ ...isPostRequest ? { data: request.body ?? {} } : {},
6419
+ headers: {
6420
+ Accept: "application/json, text/plain, */*",
6421
+ "Accept-Encoding": "gzip, deflate, br, zstd",
6422
+ "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
6423
+ "Cache-Control": "no-cache",
6424
+ Cookie: cookie?.trim() ?? "",
6425
+ Pragma: "no-cache",
6426
+ Priority: "u=0, i",
6427
+ Referer: `https://live.kuaishou.com/${refererPath}`,
6428
+ "Sec-Ch-Ua": "\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Google Chrome\";v=\"146\"",
6429
+ "Sec-Ch-Ua-Mobile": "?0",
6430
+ "Sec-Ch-Ua-Platform": "\"Windows\"",
6431
+ "Sec-Fetch-Dest": "empty",
6432
+ "Sec-Fetch-Mode": "cors",
6433
+ "Sec-Fetch-Site": "same-origin",
6434
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36",
6435
+ ...isPostRequest ? { "Content-Type": "application/json" } : {},
6436
+ ...signedRequest.headers,
6437
+ ...!signedRequest.headers.kww && kww ? { kww } : {},
6438
+ ...requestConfig?.headers ?? {}
6439
+ }
6440
+ };
6441
+ };
6442
+ /**
6443
+ * 判断返回值是否已是项目统一的错误描述对象。
6444
+ *
6445
+ * @param result - 任意待判断对象
6446
+ * @returns 是否符合 `ErrorDetail` 结构
6447
+ */
6448
+ const isErrorDetailLike = (result) => {
6449
+ return Boolean(result && typeof result === "object" && "amagiError" in result);
6450
+ };
6451
+ /**
6452
+ * 判断值是否为普通对象。
6453
+ *
6454
+ * @param value - 待判断的值
6455
+ * @returns 是否为对象字面量
6456
+ */
6457
+ const isRecord = (value) => {
6458
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
6459
+ };
6460
+ /**
6461
+ * 判断对象是否至少包含一个键。
6462
+ *
6463
+ * @param value - 待判断对象
6464
+ * @returns 是否为非空对象
6465
+ */
6466
+ const hasPopulatedRecord = (value) => {
6467
+ return isRecord(value) && Object.keys(value).length > 0;
6468
+ };
6469
+ /**
6470
+ * 取首个非空字符串。
6471
+ *
6472
+ * @param values - 候选字符串
6473
+ * @returns 首个非空字符串;若都为空则返回空串
6474
+ */
6475
+ const pickFirstNonEmptyString = (...values) => {
6476
+ for (const value of values) if (typeof value === "string" && value.trim()) return value;
6477
+ return "";
6478
+ };
6479
+ /**
6480
+ * 创建快手用户主页通用空列表数据。
6481
+ *
6482
+ * @returns 空列表 tab 数据
6483
+ */
6484
+ const createEmptyUserListTabData = () => ({
6485
+ list: [],
6486
+ pcursor: ""
6487
+ });
6488
+ /**
6489
+ * 创建快手用户主页公开视频 tab 的空数据。
6490
+ *
6491
+ * @returns 含 `live` 字段的空 tab 数据
6492
+ */
6493
+ const createEmptyUserPublicTabData = () => ({
6494
+ live: null,
6495
+ list: [],
6496
+ pcursor: ""
6497
+ });
6498
+ /**
6499
+ * 创建空的用户作品列表结果。
6500
+ *
6501
+ * @param principalId - 用户主页 principalId
6502
+ * @returns 空的 `userWorkList` 结果
6503
+ */
6504
+ const createEmptyUserWorkListResult = (principalId) => {
6505
+ return {
6506
+ principalId,
6507
+ list: [],
6508
+ pcursor: "",
6509
+ hasMore: false,
6510
+ result: 0
6511
+ };
6512
+ };
6513
+ /**
6514
+ * 创建匿名态/降级场景下的空用户结果。
6515
+ *
6516
+ * 当补充接口不可用时,使用该结构保证
6517
+ * `userProfile` 仍能返回稳定的最小骨架。
6518
+ *
6519
+ * @param principalId - 用户主页 principalId
6520
+ * @returns 空的 `userProfile` 结果
6521
+ */
6522
+ const createEmptyUserProfileResult = (principalId) => {
6523
+ return {
6524
+ principalId,
6525
+ author: {
6526
+ principalId,
6527
+ userInfo: {
6528
+ id: "",
6529
+ name: "",
6530
+ description: "",
6531
+ avatar: "",
6532
+ sex: "",
6533
+ living: false,
6534
+ followStatus: "",
6535
+ constellation: "",
6536
+ cityName: "",
6537
+ originUserId: 0,
6538
+ privacy: false,
6539
+ isNew: false,
6540
+ timestamp: 0,
6541
+ verifiedStatus: {
6542
+ verified: false,
6543
+ description: "",
6544
+ type: 0,
6545
+ new: false,
6546
+ iconUrl: ""
6547
+ },
6548
+ bannedStatus: {
6549
+ banned: false,
6550
+ socialBanned: false,
6551
+ isolate: false,
6552
+ defriend: false
6553
+ },
6554
+ counts: {}
6555
+ },
6556
+ sensitiveInfo: null,
6557
+ followInfo: {},
6558
+ banStateMap: KUAISHOU_BAN_STATE_MAP
6559
+ },
6560
+ profile: {
6561
+ currentTab: "public",
6562
+ pageSize: 12,
6563
+ tabTypeMap: KUAISHOU_PROFILE_TAB_TYPE_MAP,
6564
+ showPlayback: false,
6565
+ publicData: createEmptyUserPublicTabData(),
6566
+ privateData: createEmptyUserListTabData(),
6567
+ likedData: createEmptyUserListTabData(),
6568
+ playbackData: createEmptyUserListTabData(),
6569
+ interestList: [],
6570
+ currentProduct: {}
6571
+ },
6572
+ follow: null,
6573
+ followButton: null,
6574
+ interestMask: [],
6575
+ categoryMask: {
6576
+ config: [],
6577
+ list: [],
6578
+ hotList: [],
6579
+ hasMore: false,
6580
+ hasMoreHot: false
6581
+ }
6582
+ };
6583
+ };
6584
+ /**
6585
+ * 创建空的直播间结果。
6586
+ *
6587
+ * @param principalId - 直播间 principalId
6588
+ * @returns 空的 `liveRoomInfo`
6589
+ */
6590
+ const createEmptyLiveRoomInfoResult = (principalId) => {
6591
+ return {
6592
+ principalId,
6593
+ activeIndex: 0,
6594
+ current: null,
6595
+ playList: [],
6596
+ websocketUrls: [],
6597
+ token: "",
6598
+ noticeList: [],
6599
+ loading: false,
6600
+ emoji: {
6601
+ iconUrls: {},
6602
+ giftList: [],
6603
+ giftPanelList: [],
6604
+ token: "",
6605
+ panelToken: "",
6606
+ longSendGiftType: null
6607
+ }
6608
+ };
6609
+ };
6610
+ /**
6611
+ * 从用户资料中派生关注状态对象。
6612
+ *
6613
+ * 真实页面中的 `follow` 与 `followBtn` 来自页面装配状态;
6614
+ * 在纯协议路径下,这里基于用户资料接口回填稳定最小字段。
6615
+ *
6616
+ * @param userInfo - 用户公开资料
6617
+ * @param sensitiveInfo - 用户敏感资料
6618
+ * @returns 归一化后的 follow 状态
6619
+ */
6620
+ const createDerivedFollowState = (userInfo, sensitiveInfo) => {
6621
+ const followStatus = userInfo?.followStatus ?? sensitiveInfo?.followStatus;
6622
+ if (!followStatus) return null;
6623
+ return {
6624
+ currentFollowStatus: followStatus,
6625
+ needToFollow: false,
6626
+ authorId: userInfo?.id ?? "",
6627
+ data: 0
6628
+ };
6629
+ };
6630
+ /**
6631
+ * 从用户资料中派生关注按钮状态。
6632
+ *
6633
+ * @param userInfo - 用户公开资料
6634
+ * @param sensitiveInfo - 用户敏感资料
6635
+ * @returns 归一化后的 follow button 状态
6636
+ */
6637
+ const createDerivedFollowButtonState = (userInfo, sensitiveInfo) => {
6638
+ const followStatus = userInfo?.followStatus ?? sensitiveInfo?.followStatus;
6639
+ if (!followStatus) return null;
6640
+ return { followStatus };
6641
+ };
6642
+ /**
6643
+ * 解析用户主页 tab 数据。
6644
+ *
6645
+ * @param payload - 接口响应
6646
+ * @param fallback - 兜底数据
6647
+ * @returns 归一化后的 tab 数据
6648
+ */
6649
+ const resolveUserProfileTabData = (payload, fallback) => {
6650
+ if (isErrorDetailLike(payload)) return fallback;
6651
+ const nextData = payload?.data;
6652
+ if (!isRecord(nextData) || nextData.result !== 1 || !Array.isArray(nextData.list)) return fallback;
6653
+ const resolvedData = {
6654
+ ...fallback,
6655
+ ...nextData,
6656
+ list: nextData.list,
6657
+ pcursor: nextData.pcursor ?? fallback.pcursor ?? ""
6658
+ };
6659
+ if ("live" in fallback || "live" in nextData) resolvedData.live = nextData.live ?? fallback.live ?? null;
6660
+ return resolvedData;
6661
+ };
6662
+ /**
6663
+ * 解析快手用户作品分页结果。
6664
+ *
6665
+ * @param principalId - 用户主页 principalId
6666
+ * @param payload - `profile/public` 响应
6667
+ * @returns 归一化后的作品列表结果
6668
+ */
6669
+ const resolveKuaishouUserWorkList = (principalId, payload) => {
6670
+ const fallback = createEmptyUserWorkListResult(principalId);
6671
+ if (isErrorDetailLike(payload)) return fallback;
6672
+ const nextData = payload?.data;
6673
+ if (!isRecord(nextData)) return fallback;
6674
+ return {
6675
+ principalId,
6676
+ list: Array.isArray(nextData.list) ? nextData.list : fallback.list,
6677
+ pcursor: typeof nextData.pcursor === "string" ? nextData.pcursor : fallback.pcursor,
6678
+ hasMore: Boolean(nextData.hasMore ?? nextData.pcursor),
6679
+ result: Number(nextData.result ?? fallback.result)
6680
+ };
6681
+ };
6682
+ /**
6683
+ * 解析快手直播间详情主接口数据。
6684
+ *
6685
+ * `live_api/liveroom/livedetail` 是当前房间主体的纯协议主接口;
6686
+ * 当接口返回 `result=1` 时,`data` 中会直接包含 `liveStream`、
6687
+ * `author`、`gameInfo`、`noticeList`、`websocketInfo` 与 `config`。
6688
+ *
6689
+ * @param payload - `liveroom/livedetail` 响应
6690
+ * @returns 成功时返回标准化后的 `data`,否则返回 `null`
6691
+ */
6692
+ const resolveKuaishouLiveDetailData = (payload) => {
6693
+ if (isErrorDetailLike(payload)) return null;
6694
+ const nextData = payload?.data;
6695
+ if (!isRecord(nextData) || Number(nextData.result ?? 0) !== 1) return null;
6696
+ return nextData;
6697
+ };
6698
+ /**
6699
+ * 从 `livedetail` 中提取 WebSocket 元信息。
6700
+ *
6701
+ * `livedetail` 有时已经自带 `websocketInfo`,这时可以直接复用,
6702
+ * 仅在字段缺失时再补请求 `websocketinfo`。
6703
+ *
6704
+ * @param detailData - `liveroom/livedetail.data`
6705
+ * @returns 归一化后的 WebSocket 元信息
6706
+ */
6707
+ const resolveKuaishouLiveDetailWebsocketMeta = (detailData) => {
6708
+ const websocketInfo = isRecord(detailData?.websocketInfo) ? detailData.websocketInfo : {};
6709
+ return {
6710
+ websocketUrls: Array.isArray(websocketInfo?.websocketUrls) ? websocketInfo.websocketUrls : [],
6711
+ token: typeof websocketInfo?.token === "string" ? websocketInfo.token : ""
6712
+ };
6713
+ };
6714
+ /**
6715
+ * 从 `livedetail` 中提取推荐房间列表。
6716
+ *
6717
+ * @param detailData - `liveroom/livedetail.data`
6718
+ * @returns 推荐房间列表
6719
+ */
6720
+ const resolveKuaishouLiveDetailRecommendList = (detailData) => {
6721
+ return Array.isArray(detailData?.recommendList) ? detailData.recommendList : [];
6722
+ };
6723
+ /**
6724
+ * 归一化快手直播作者对象。
6725
+ *
6726
+ * @param author - 原始作者对象
6727
+ * @returns 与项目返回结构一致的作者对象
6728
+ */
6729
+ const normalizeKuaishouLiveAuthor = (author) => {
6730
+ const verifiedDetail = author?.verifiedDetail;
6731
+ return {
6732
+ id: author?.id ?? author?.principalId ?? author?.kwaiId ?? "",
6733
+ name: author?.name ?? author?.user_name ?? "",
6734
+ description: author?.description ?? author?.user_text ?? "",
6735
+ avatar: author?.avatar ?? author?.headurl ?? "",
6736
+ sex: author?.sex ?? author?.user_sex ?? "",
6737
+ living: Boolean(author?.living ?? author?.live),
6738
+ followStatus: author?.followStatus ?? (author?.following ? "FOLLOWING" : "UN_FOLLOWED"),
6739
+ constellation: author?.constellation ?? "",
6740
+ cityName: author?.cityName ?? "",
6741
+ originUserId: Number(author?.originUserId ?? author?.user_id ?? 0),
6742
+ privacy: Boolean(author?.privacy),
6743
+ isNew: Boolean(author?.isNew),
6744
+ timestamp: Number(author?.timestamp ?? 0),
6745
+ verifiedStatus: author?.verifiedStatus ?? {
6746
+ verified: Boolean(author?.verified),
6747
+ description: verifiedDetail?.description ?? "",
6748
+ type: Number(verifiedDetail?.type ?? 0),
6749
+ new: Boolean(verifiedDetail?.newVerified),
6750
+ iconUrl: verifiedDetail?.iconUrl ?? ""
6751
+ },
6752
+ bannedStatus: author?.bannedStatus ?? {
6753
+ banned: false,
6754
+ socialBanned: false,
6755
+ isolate: false,
6756
+ defriend: false
6757
+ },
6758
+ counts: hasPopulatedRecord(author?.counts) ? author.counts : {}
6759
+ };
6760
+ };
6761
+ /**
6762
+ * 用用户资料接口补强直播作者对象。
6763
+ *
6764
+ * @param fallbackAuthor - 直播接口自带的作者信息
6765
+ * @param userInfo - `userinfo/byid` 返回的公开资料
6766
+ * @param sensitiveInfo - `userinfo/sensitive` 返回的补充资料
6767
+ * @returns 补强后的作者对象
6768
+ */
6769
+ const mergeKuaishouLiveAuthor = (fallbackAuthor, userInfo, sensitiveInfo) => {
6770
+ const normalizedFallback = normalizeKuaishouLiveAuthor(fallbackAuthor);
6771
+ return {
6772
+ ...normalizedFallback,
6773
+ ...hasPopulatedRecord(userInfo) ? userInfo : {},
6774
+ followStatus: userInfo?.followStatus ?? sensitiveInfo?.followStatus ?? normalizedFallback.followStatus,
6775
+ constellation: pickFirstNonEmptyString(userInfo?.constellation, sensitiveInfo?.constellation, normalizedFallback.constellation),
6776
+ cityName: pickFirstNonEmptyString(userInfo?.cityName, sensitiveInfo?.cityName, normalizedFallback.cityName),
6777
+ verifiedStatus: userInfo?.verifiedStatus ?? sensitiveInfo?.verifiedStatus ?? normalizedFallback.verifiedStatus,
6778
+ bannedStatus: userInfo?.bannedStatus ?? sensitiveInfo?.bannedStatus ?? normalizedFallback.bannedStatus,
6779
+ counts: hasPopulatedRecord(userInfo?.counts) ? userInfo.counts : sensitiveInfo?.counts ?? normalizedFallback.counts
6780
+ };
6781
+ };
6782
+ /**
6783
+ * 将 `liveroom/livedetail` 映射为用户主页 `publicData.live`。
6784
+ *
6785
+ * @param detailData - `liveroom/livedetail.data`
6786
+ * @param author - 归一化后的作者对象
6787
+ * @returns 用户主页 live 对象
6788
+ */
6789
+ const mapLiveDetailToUserProfileLiveInfo = (detailData, author) => {
6790
+ const liveStream = isRecord(detailData?.liveStream) ? detailData.liveStream : {};
6791
+ const config = isRecord(detailData?.config) ? detailData.config : {};
6792
+ const gameInfo = isRecord(detailData?.gameInfo) ? detailData.gameInfo : {};
6793
+ const liveStreamId = pickFirstNonEmptyString(liveStream?.id, config?.liveStreamId);
6794
+ return {
6795
+ ...detailData,
6796
+ id: liveStreamId,
6797
+ poster: pickFirstNonEmptyString(liveStream?.poster, config?.coverUrl, config?.rtCoverUrl),
6798
+ playUrls: liveStream?.playUrls ?? config?.multiResolutionPlayUrls ?? [],
6799
+ caption: config?.caption ?? detailData?.caption ?? "",
6800
+ statrtTime: Number(detailData?.startTime ?? config?.startTime ?? liveStream?.startTime ?? 0),
6801
+ author,
6802
+ gameInfo,
6803
+ hasRedPack: Boolean(detailData?.hasRedPack ?? config?.hasRedPack),
6804
+ hasBet: Boolean(detailData?.hasBet ?? config?.hasBet),
6805
+ followed: author?.followStatus === "FOLLOWING",
6806
+ expTag: liveStream?.expTag ?? config?.expTag ?? "",
6807
+ hotIcon: config?.hotIcon ?? detailData?.hotIcon ?? "",
6808
+ living: Boolean(detailData?.isLiving ?? liveStreamId),
6809
+ quality: config?.quality ?? "",
6810
+ qualityLabel: config?.qualityLabel ?? "",
6811
+ watchingCount: config?.watchingCount ?? detailData?.watchingCount ?? gameInfo?.watchingCount ?? "",
6812
+ landscape: Boolean(config?.landscape),
6813
+ likeCount: config?.likeCount ?? detailData?.likeCount ?? "",
6814
+ type: liveStream?.type ?? "live"
6815
+ };
6816
+ };
6817
+ /**
6818
+ * 将 `liveroom/livedetail` 映射为 `liveRoomInfo.current`。
6819
+ *
6820
+ * @param detailData - `liveroom/livedetail.data`
6821
+ * @param author - 补强后的主播对象
6822
+ * @returns 当前直播间对象
6823
+ */
6824
+ const mapLiveDetailToLiveRoomPlayItem = (detailData, author) => {
6825
+ const liveStream = isRecord(detailData?.liveStream) ? detailData.liveStream : {};
6826
+ const config = isRecord(detailData?.config) ? detailData.config : {};
6827
+ const gameInfo = isRecord(detailData?.gameInfo) ? detailData.gameInfo : {};
6828
+ const liveStreamId = pickFirstNonEmptyString(liveStream?.id, config?.liveStreamId);
6829
+ const coverUrl = pickFirstNonEmptyString(config?.coverUrl, config?.rtCoverUrl, liveStream?.poster);
6830
+ return {
6831
+ liveStream: {
6832
+ id: liveStreamId,
6833
+ poster: pickFirstNonEmptyString(liveStream?.poster, coverUrl),
6834
+ playUrls: liveStream?.playUrls ?? config?.multiResolutionPlayUrls ?? {},
6835
+ url: liveStream?.url ?? "",
6836
+ hlsPlayUrl: pickFirstNonEmptyString(config?.hlsPlayUrl, liveStream?.hlsPlayUrl),
6837
+ location: liveStream?.location ?? null,
6838
+ type: liveStream?.type ?? "live",
6839
+ liveGuess: Boolean(liveStream?.liveGuess),
6840
+ expTag: liveStream?.expTag ?? config?.expTag ?? "",
6841
+ privateLive: Boolean(liveStream?.privateLive ?? config?.privateLive)
6842
+ },
6843
+ author,
6844
+ gameInfo: {
6845
+ id: String(gameInfo?.id ?? gameInfo?.gameId ?? ""),
6846
+ name: gameInfo?.name ?? "",
6847
+ poster: pickFirstNonEmptyString(gameInfo?.poster, gameInfo?.coverUrl),
6848
+ description: gameInfo?.description ?? "",
6849
+ categoryAbbr: gameInfo?.categoryAbbr ?? gameInfo?.category ?? "",
6850
+ categoryName: gameInfo?.categoryName ?? "",
6851
+ watchingCount: config?.watchingCount ?? detailData?.watchingCount ?? gameInfo?.watchingCount ?? "",
6852
+ roomCount: gameInfo?.roomCount ?? ""
6853
+ },
6854
+ isLiving: Boolean(detailData?.isLiving ?? liveStreamId),
6855
+ authToken: typeof detailData?.authToken === "string" ? detailData.authToken : null,
6856
+ config: {
6857
+ ...config,
6858
+ liveStreamId,
6859
+ hlsPlayUrl: pickFirstNonEmptyString(config?.hlsPlayUrl, liveStream?.hlsPlayUrl),
6860
+ coverUrl,
6861
+ rtCoverUrl: pickFirstNonEmptyString(config?.rtCoverUrl, liveStream?.poster),
6862
+ gameInfo,
6863
+ multiResolutionPlayUrls: config?.multiResolutionPlayUrls ?? liveStream?.playUrls ?? []
6864
+ },
6865
+ websocketInfo: isRecord(detailData?.websocketInfo) ? detailData.websocketInfo : {},
6866
+ status: detailData?.status ?? { forbiddenState: Number(detailData?.result ?? 0) }
6867
+ };
6868
+ };
6869
+ /**
6870
+ * 将 `liveroom/reco` 返回项映射为 `playList` 项。
6871
+ *
6872
+ * @param recoItem - 推荐房间对象
6873
+ * @returns 归一化后的推荐房间对象
6874
+ */
6875
+ const mapRecoItemToLiveRoomPlayItem = (recoItem) => ({
6876
+ liveStream: {
6877
+ id: recoItem?.liveStream?.id ?? "",
6878
+ poster: recoItem?.liveStream?.poster ?? recoItem?.config?.coverUrl ?? "",
6879
+ playUrls: recoItem?.liveStream?.playUrls ?? {},
6880
+ url: recoItem?.liveStream?.url ?? "",
6881
+ hlsPlayUrl: recoItem?.liveStream?.hlsPlayUrl ?? recoItem?.config?.hlsPlayUrl ?? "",
6882
+ location: recoItem?.liveStream?.location ?? null,
6883
+ type: recoItem?.liveStream?.type ?? "live",
6884
+ liveGuess: Boolean(recoItem?.liveStream?.liveGuess),
6885
+ expTag: recoItem?.liveStream?.expTag ?? "",
6886
+ privateLive: Boolean(recoItem?.liveStream?.privateLive ?? recoItem?.config?.privateLive)
6887
+ },
6888
+ author: normalizeKuaishouLiveAuthor(recoItem?.author),
6889
+ gameInfo: {
6890
+ id: recoItem?.gameInfo?.id ?? "",
6891
+ name: recoItem?.gameInfo?.name ?? "",
6892
+ poster: recoItem?.gameInfo?.poster ?? "",
6893
+ description: recoItem?.gameInfo?.description ?? "",
6894
+ categoryAbbr: recoItem?.gameInfo?.categoryAbbr ?? "",
6895
+ categoryName: recoItem?.gameInfo?.categoryName ?? "",
6896
+ watchingCount: recoItem?.gameInfo?.watchingCount ?? "",
6897
+ roomCount: recoItem?.gameInfo?.roomCount ?? ""
6898
+ },
6899
+ isLiving: Boolean(recoItem?.isLiving),
6900
+ authToken: recoItem?.authToken ?? null,
6901
+ config: {
6902
+ ...recoItem?.config ?? {},
6903
+ liveStreamId: recoItem?.config?.liveStreamId ?? recoItem?.liveStream?.id ?? ""
6904
+ },
6905
+ websocketInfo: recoItem?.websocketInfo ?? {},
6906
+ status: recoItem?.status ?? {}
6907
+ });
6908
+ /**
6909
+ * 对直播房间列表按直播流 ID 去重。
6910
+ *
6911
+ * @param items - 待去重房间列表
6912
+ * @returns 去重后的房间列表
6913
+ */
6914
+ const dedupeLiveRoomPlayList = (items) => {
6915
+ const seenLiveStreamIds = /* @__PURE__ */ new Set();
6916
+ const normalizedItems = [];
6917
+ for (const item of items) {
6918
+ if (!item) continue;
6919
+ const liveStreamId = item.liveStream?.id ?? item.config?.liveStreamId ?? "";
6920
+ if (liveStreamId && seenLiveStreamIds.has(liveStreamId)) continue;
6921
+ if (liveStreamId) seenLiveStreamIds.add(liveStreamId);
6922
+ normalizedItems.push(item);
6923
+ }
6924
+ return normalizedItems;
6925
+ };
6926
+ /**
6927
+ * 快手数据获取函数
6928
+ * @param data - 请求数据参数
6929
+ * @param cookie - 用户Cookie
6930
+ * @param requestConfig - 外部请求配置(优先级最高)
6931
+ * @returns 返回快手数据;当前 user/live 能力已优先走纯协议聚合链路
6932
+ */
6933
+ const KuaishouData = async (data$1, cookie, requestConfig) => {
6934
+ const defHeaders = getKuaishouDefaultConfig(cookie)["headers"];
6935
+ const baseRequestConfig = {
6936
+ method: "POST",
6937
+ timeout: 1e4,
6938
+ ...requestConfig,
6939
+ headers: {
6940
+ ...defHeaders,
6941
+ ...requestConfig?.headers ?? {}
6942
+ }
6943
+ };
6944
+ /**
6945
+ * 执行一次快手 GraphQL 请求。
6946
+ *
6947
+ * @param type - 当前请求所属的方法类型
6948
+ * @param request - GraphQL 请求描述对象
6949
+ * @param config - 附加容错配置
6950
+ * @returns 原始响应或标准化错误对象
6951
+ */
6952
+ const fetchKuaishouGraphqlPayload = (type, request, config) => {
6953
+ return GlobalGetData$2(type, {
6954
+ ...baseRequestConfig,
6955
+ url: request.url,
6956
+ data: request.body
6957
+ }, config);
6958
+ };
6959
+ /**
6960
+ * 执行一次快手 `live_api` 请求。
6961
+ *
6962
+ * @param type - 当前请求所属的方法类型
6963
+ * @param request - `live_api` 请求描述对象
6964
+ * @param refererPath - 请求对应的页面 Referer 路径
6965
+ * @param config - 附加容错配置
6966
+ * @returns 原始响应或标准化错误对象
6967
+ */
6968
+ const fetchKuaishouLiveApiPayload = (type, request, refererPath, config) => {
6969
+ return GlobalGetData$2(type, buildLiveApiRequestConfig(request, refererPath, cookie, requestConfig), config);
6970
+ };
6971
+ switch (data$1.methodType) {
6972
+ case "videoWork": return fetchKuaishouGraphqlPayload(data$1.methodType, kuaishouApiUrls.videoWork({ photoId: data$1.photoId }));
6973
+ case "comments": return fetchKuaishouGraphqlPayload(data$1.methodType, kuaishouApiUrls.comments({ photoId: data$1.photoId }));
6974
+ case "userProfile": {
6975
+ const refererPath = `profile/${encodeURIComponent(data$1.principalId)}`;
6976
+ const [userInfoPayload, sensitivePayload, publicPayload, privatePayload, likedPayload, playbackPayload, interestListPayload, interestMaskPayload, categoryConfigPayload, categoryDataPayload, categoryClassifyPayload, liveDetailPayload] = await Promise.all([
6977
+ fetchKuaishouLiveApiPayload(data$1.methodType, kuaishouApiUrls.userInfoById({ principalId: data$1.principalId }), refererPath),
6978
+ fetchKuaishouLiveApiPayload(data$1.methodType, kuaishouApiUrls.userSensitiveInfo({ principalId: data$1.principalId }), refererPath, { allowResult2: true }),
6979
+ fetchKuaishouLiveApiPayload(data$1.methodType, kuaishouApiUrls.profilePublic({ principalId: data$1.principalId }), refererPath, { allowResult2: true }),
6980
+ fetchKuaishouLiveApiPayload(data$1.methodType, kuaishouApiUrls.profilePrivate({ principalId: data$1.principalId }), refererPath, { allowResult2: true }),
6981
+ fetchKuaishouLiveApiPayload(data$1.methodType, kuaishouApiUrls.profileLiked({ principalId: data$1.principalId }), refererPath, { allowResult2: true }),
6982
+ fetchKuaishouLiveApiPayload(data$1.methodType, kuaishouApiUrls.playbackList({ principalId: data$1.principalId }), refererPath, { allowResult2: true }),
6983
+ fetchKuaishouLiveApiPayload(data$1.methodType, kuaishouApiUrls.profileInterestList({ principalId: data$1.principalId }), refererPath, { allowResult2: true }),
6984
+ fetchKuaishouLiveApiPayload(data$1.methodType, kuaishouApiUrls.interestMaskList(), refererPath, { allowResult2: true }),
6985
+ fetchKuaishouLiveApiPayload(data$1.methodType, kuaishouApiUrls.categoryConfig(), refererPath),
6986
+ fetchKuaishouLiveApiPayload(data$1.methodType, kuaishouApiUrls.categoryData(), refererPath),
6987
+ fetchKuaishouLiveApiPayload(data$1.methodType, kuaishouApiUrls.categoryClassify(), refererPath),
6988
+ fetchKuaishouLiveApiPayload(data$1.methodType, kuaishouApiUrls.liveDetail({ principalId: data$1.principalId }), refererPath, { allowResult2: true })
6989
+ ]);
6990
+ if (isErrorDetailLike(userInfoPayload)) return userInfoPayload;
6991
+ const userProfile = createEmptyUserProfileResult(data$1.principalId);
6992
+ const userInfo = userInfoPayload?.data?.userInfo;
6993
+ const sensitiveInfo = isErrorDetailLike(sensitivePayload) ? null : sensitivePayload?.data?.sensitiveUserInfo ?? null;
6994
+ const liveDetailData = resolveKuaishouLiveDetailData(liveDetailPayload);
6995
+ const normalizedAuthor = mergeKuaishouLiveAuthor(liveDetailData?.author, userInfo, sensitiveInfo ?? void 0);
6996
+ const nextPublicData = resolveUserProfileTabData(publicPayload, userProfile.profile.publicData);
6997
+ const nextPrivateData = resolveUserProfileTabData(privatePayload, userProfile.profile.privateData);
6998
+ const nextLikedData = resolveUserProfileTabData(likedPayload, userProfile.profile.likedData);
6999
+ const nextPlaybackData = resolveUserProfileTabData(playbackPayload, userProfile.profile.playbackData);
7000
+ if (!nextPublicData.live && liveDetailData) nextPublicData.live = mapLiveDetailToUserProfileLiveInfo(liveDetailData, normalizedAuthor);
7001
+ const nextInterestList = !isErrorDetailLike(interestListPayload) && Array.isArray(interestListPayload?.data) ? interestListPayload.data : userProfile.profile.interestList;
7002
+ const nextInterestMask = !isErrorDetailLike(interestMaskPayload) && Array.isArray(interestMaskPayload?.data) ? interestMaskPayload.data : userProfile.interestMask;
7003
+ return {
7004
+ ...userProfile,
7005
+ principalId: data$1.principalId,
7006
+ author: {
7007
+ ...userProfile.author,
7008
+ principalId: data$1.principalId,
7009
+ userInfo: normalizedAuthor,
7010
+ sensitiveInfo,
7011
+ followInfo: {},
7012
+ banStateMap: KUAISHOU_BAN_STATE_MAP
7013
+ },
7014
+ profile: {
7015
+ ...userProfile.profile,
7016
+ pageSize: 12,
7017
+ tabTypeMap: KUAISHOU_PROFILE_TAB_TYPE_MAP,
7018
+ showPlayback: Boolean(nextPublicData.showPlayback ?? nextPlaybackData.list.length > 0),
7019
+ publicData: nextPublicData,
7020
+ privateData: nextPrivateData,
7021
+ likedData: nextLikedData,
7022
+ playbackData: nextPlaybackData,
7023
+ interestList: nextInterestList,
7024
+ currentProduct: {}
7025
+ },
7026
+ follow: createDerivedFollowState(userInfo, sensitiveInfo ?? void 0),
7027
+ followButton: createDerivedFollowButtonState(userInfo, sensitiveInfo ?? void 0),
7028
+ interestMask: nextInterestMask,
7029
+ categoryMask: {
7030
+ config: !isErrorDetailLike(categoryConfigPayload) && Array.isArray(categoryConfigPayload?.data) ? categoryConfigPayload.data : userProfile.categoryMask.config,
7031
+ list: !isErrorDetailLike(categoryClassifyPayload) && Array.isArray(categoryClassifyPayload?.data?.list) ? categoryClassifyPayload.data.list : userProfile.categoryMask.list,
7032
+ hotList: !isErrorDetailLike(categoryDataPayload) && Array.isArray(categoryDataPayload?.data?.list) ? categoryDataPayload.data.list : userProfile.categoryMask.hotList,
7033
+ hasMore: Boolean(categoryClassifyPayload?.data?.hasMore),
7034
+ hasMoreHot: Boolean(categoryDataPayload?.data?.hasMore)
7035
+ }
7036
+ };
7037
+ }
7038
+ case "userWorkList": {
7039
+ const refererPath = `profile/${encodeURIComponent(data$1.principalId)}`;
7040
+ const publicPayload = await fetchKuaishouLiveApiPayload(data$1.methodType, kuaishouApiUrls.userWorkList({
7041
+ principalId: data$1.principalId,
7042
+ pcursor: data$1.pcursor,
7043
+ count: data$1.count
7044
+ }), refererPath, { allowResult2: true });
7045
+ if (isErrorDetailLike(publicPayload)) return publicPayload;
7046
+ return resolveKuaishouUserWorkList(data$1.principalId, publicPayload);
7047
+ }
7048
+ case "liveRoomInfo": {
7049
+ const refererPath = `u/${encodeURIComponent(data$1.principalId)}`;
7050
+ const [liveDetailPayload, userInfoPayload, sensitivePayload, emojiPayload] = await Promise.all([
7051
+ fetchKuaishouLiveApiPayload(data$1.methodType, kuaishouApiUrls.liveDetail({ principalId: data$1.principalId }), refererPath, { allowResult2: true }),
7052
+ fetchKuaishouLiveApiPayload(data$1.methodType, kuaishouApiUrls.userInfoById({ principalId: data$1.principalId }), refererPath, { allowResult2: true }),
7053
+ fetchKuaishouLiveApiPayload(data$1.methodType, kuaishouApiUrls.userSensitiveInfo({ principalId: data$1.principalId }), refererPath, { allowResult2: true }),
7054
+ fetchKuaishouGraphqlPayload(data$1.methodType, kuaishouApiUrls.emojiList(), { allowResult2: true })
7055
+ ]);
7056
+ if (isErrorDetailLike(liveDetailPayload)) return liveDetailPayload;
7057
+ const liveRoomInfo = createEmptyLiveRoomInfoResult(data$1.principalId);
7058
+ const liveDetailData = resolveKuaishouLiveDetailData(liveDetailPayload);
7059
+ if (!liveDetailData) return liveRoomInfo;
7060
+ const userInfo = isErrorDetailLike(userInfoPayload) ? void 0 : userInfoPayload?.data?.userInfo;
7061
+ const sensitiveInfo = isErrorDetailLike(sensitivePayload) ? void 0 : sensitivePayload?.data?.sensitiveUserInfo;
7062
+ const currentLiveRoomItem = mapLiveDetailToLiveRoomPlayItem(liveDetailData, mergeKuaishouLiveAuthor(liveDetailData?.author, userInfo, sensitiveInfo));
7063
+ const liveStreamId = currentLiveRoomItem.liveStream?.id ?? currentLiveRoomItem.config?.liveStreamId;
7064
+ const currentGameId = liveDetailData?.gameInfo?.id ?? liveDetailData?.gameInfo?.gameId;
7065
+ const liveDetailWebsocketMeta = resolveKuaishouLiveDetailWebsocketMeta(liveDetailData);
7066
+ const liveDetailRecommendList = resolveKuaishouLiveDetailRecommendList(liveDetailData);
7067
+ if (!liveStreamId) return {
7068
+ ...liveRoomInfo,
7069
+ current: currentLiveRoomItem,
7070
+ playList: [currentLiveRoomItem]
7071
+ };
7072
+ const shouldFetchWebsocketInfo = liveDetailWebsocketMeta.websocketUrls.length === 0 || !liveDetailWebsocketMeta.token;
7073
+ const shouldFetchRecommendList = liveDetailRecommendList.length === 0;
7074
+ const [giftPayload, websocketPayload, recoPayload] = await Promise.all([
7075
+ fetchKuaishouLiveApiPayload(data$1.methodType, kuaishouApiUrls.liveGiftList(liveStreamId), refererPath, { allowResult2: true }),
7076
+ shouldFetchWebsocketInfo ? fetchKuaishouLiveApiPayload(data$1.methodType, kuaishouApiUrls.liveWebsocketInfo(liveStreamId), refererPath, { allowResult2: true }) : Promise.resolve(null),
7077
+ shouldFetchRecommendList ? fetchKuaishouLiveApiPayload(data$1.methodType, kuaishouApiUrls.liveReco(currentGameId), refererPath, { allowResult2: true }) : Promise.resolve(null)
7078
+ ]);
7079
+ const resolvedRecommendList = !isErrorDetailLike(recoPayload) && Array.isArray(recoPayload?.data?.list) ? recoPayload.data.list : liveDetailRecommendList;
7080
+ const nextPlayList = dedupeLiveRoomPlayList([currentLiveRoomItem, ...Array.isArray(resolvedRecommendList) ? resolvedRecommendList.map((item) => mapRecoItemToLiveRoomPlayItem(item)) : []]);
7081
+ return {
7082
+ ...liveRoomInfo,
7083
+ principalId: data$1.principalId,
7084
+ activeIndex: 0,
7085
+ current: currentLiveRoomItem,
7086
+ playList: nextPlayList,
7087
+ websocketUrls: !isErrorDetailLike(websocketPayload) && Array.isArray(websocketPayload?.data?.websocketUrls) ? websocketPayload.data.websocketUrls : liveDetailWebsocketMeta.websocketUrls,
7088
+ token: !isErrorDetailLike(websocketPayload) ? websocketPayload?.data?.token ?? liveDetailWebsocketMeta.token : liveDetailWebsocketMeta.token,
7089
+ noticeList: Array.isArray(liveDetailData?.noticeList) ? liveDetailData.noticeList : liveRoomInfo.noticeList,
7090
+ loading: false,
7091
+ emoji: {
7092
+ iconUrls: !isErrorDetailLike(emojiPayload) && isRecord(emojiPayload?.data?.visionBaseEmoticons?.iconUrls) ? emojiPayload.data.visionBaseEmoticons.iconUrls : liveRoomInfo.emoji.iconUrls,
7093
+ giftList: !isErrorDetailLike(giftPayload) && Array.isArray(giftPayload?.data?.gifts) ? giftPayload.data.gifts : liveRoomInfo.emoji.giftList,
7094
+ giftPanelList: !isErrorDetailLike(giftPayload) && Array.isArray(giftPayload?.data?.giftPanelList) ? giftPayload.data.giftPanelList : liveRoomInfo.emoji.giftPanelList,
7095
+ token: !isErrorDetailLike(giftPayload) ? giftPayload?.data?.token ?? liveRoomInfo.emoji.token : liveRoomInfo.emoji.token,
7096
+ panelToken: !isErrorDetailLike(giftPayload) ? giftPayload?.data?.panelToken ?? liveRoomInfo.emoji.panelToken : liveRoomInfo.emoji.panelToken,
7097
+ longSendGiftType: !isErrorDetailLike(giftPayload) ? giftPayload?.data?.longSendGiftType ?? liveRoomInfo.emoji.longSendGiftType : liveRoomInfo.emoji.longSendGiftType
7098
+ }
7099
+ };
7100
+ }
7101
+ case "emojiList": return fetchKuaishouGraphqlPayload(data$1.methodType, kuaishouApiUrls.emojiList());
7102
+ default:
7103
+ logger.warn(`Unknown Kuaishou API method: "${logger.red(data$1.methodType)}"`);
7104
+ return null;
7105
+ }
7106
+ };
7107
+ /**
7108
+ * 快手底层数据请求函数。
7109
+ *
7110
+ * 负责执行单次网络请求,并将网络错误、空响应和 `result=2`
7111
+ * 这类平台错误统一归一化为 `ErrorDetail`。
7112
+ *
7113
+ * @param type - 当前请求所属的方法类型
7114
+ * @param options - 网络请求配置选项
7115
+ * @param config - 附加容错配置
7116
+ * @returns 请求成功时返回原始响应,否则返回 `ErrorDetail`
7117
+ */
7118
+ const GlobalGetData$2 = async (type, options, config) => {
7119
+ let warningMessage = "";
7120
+ try {
7121
+ const result = await fetchData(options);
7122
+ if (isNetworkErrorResult(result)) {
7123
+ const networkError = new Error(result.error.amagiError.errorDescription);
7124
+ Object.assign(networkError, {
7125
+ code: result.error.code,
7126
+ data: null,
7127
+ amagiError: {
7128
+ ...result.error.amagiError,
7129
+ requestType: type
7130
+ }
7131
+ });
7132
+ throw networkError;
7133
+ }
7134
+ if (result === "" || !result || !config?.allowResult2 && typeof result === "object" && result !== null && result.result === 2) {
7135
+ const Err = {
7136
+ errorDescription: "获取响应数据失败!接口返回内容为空!",
7137
+ requestType: type ?? "未知请求类型",
7138
+ requestUrl: options.url,
7139
+ requestBody: JSON.stringify(options.data)
7140
+ };
7141
+ warningMessage = `
7142
+ 获取响应数据失败!原因:${logger.yellow("接口返回内容为空,你的快手ck可能已经失效!")}
7143
+ 请求类型:「${type}」
7144
+ 请求URL:${options.url}
7145
+ 请求参数:${JSON.stringify(options.data, null, 2)}
7146
+ `;
7147
+ logger.warn(warningMessage);
7148
+ const cookieError = new Error(Err.errorDescription);
7149
+ Object.assign(cookieError, {
7150
+ code: kuaishouAPIErrorCode.COOKIE,
7151
+ data: result,
7152
+ amagiError: Err
7153
+ });
7154
+ throw cookieError;
7155
+ }
7156
+ return result;
7157
+ } catch (error) {
7158
+ if (error && typeof error === "object") return {
7159
+ ...error,
7160
+ amagiMessage: warningMessage
7161
+ };
7162
+ return {
7163
+ code: amagiAPIErrorCode.UNKNOWN,
7164
+ data: null,
7165
+ amagiError: {
7166
+ errorDescription: "未知错误",
7167
+ requestType: type,
7168
+ requestUrl: options.url
7169
+ },
7170
+ amagiMessage: warningMessage
7171
+ };
7172
+ }
7173
+ };
7174
+
7175
+ //#endregion
7176
+ //#region src/model/fetchers/kuaishou/internal.ts
7177
+ /**
7178
+ * 快手 Fetcher 内部通用逻辑
7179
+ * @module fetchers/kuaishou/internal
7180
+ */
7181
+ /**
7182
+ * 快手 API 内部调用函数
7183
+ * @internal
7184
+ * @param methodType - 方法类型
7185
+ * @param options - 请求参数
7186
+ * @param config - 配置项 (cookie, requestConfig)
7187
+ * @returns Promise<Result<T>>
7188
+ */
7189
+ async function fetchKuaishouInternal(methodType, options, config) {
7190
+ const startTime = Date.now();
7191
+ try {
7192
+ const rawData = await KuaishouData({ ...validateKuaishouParams(methodType, options) }, config.cookie, config.requestConfig);
7193
+ const duration = Date.now() - startTime;
7194
+ if (rawData.code && Object.values(kuaishouAPIErrorCode).includes(rawData.code)) {
7195
+ emitApiError({
5183
7196
  platform: "kuaishou",
5184
7197
  methodType,
5185
7198
  errorCode: rawData.code,
@@ -5253,6 +7266,50 @@ async function fetchWorkComments(options, cookie, requestConfig) {
5253
7266
  });
5254
7267
  }
5255
7268
  /**
7269
+ * 获取快手用户主页数据
7270
+ * @param options - 用户主页参数
7271
+ * @param options.principalId - 用户 principalId
7272
+ * @param cookie - 快手 Cookie (可选)
7273
+ * @param requestConfig - 请求配置 (可选)
7274
+ * @returns 用户主页纯协议聚合结果
7275
+ */
7276
+ async function fetchUserProfile$1(options, cookie, requestConfig) {
7277
+ return fetchKuaishouInternal("userProfile", options, {
7278
+ cookie,
7279
+ requestConfig
7280
+ });
7281
+ }
7282
+ /**
7283
+ * 获取快手用户作品列表数据
7284
+ * @param options - 用户作品列表参数
7285
+ * @param options.principalId - 用户 principalId
7286
+ * @param options.pcursor - 分页游标 (可选)
7287
+ * @param options.count - 每页数量,默认 12
7288
+ * @param cookie - 快手 Cookie (可选)
7289
+ * @param requestConfig - 请求配置 (可选)
7290
+ * @returns 用户作品列表纯协议分页结果
7291
+ */
7292
+ async function fetchUserWorkList(options, cookie, requestConfig) {
7293
+ return fetchKuaishouInternal("userWorkList", options, {
7294
+ cookie,
7295
+ requestConfig
7296
+ });
7297
+ }
7298
+ /**
7299
+ * 获取快手直播间信息数据
7300
+ * @param options - 直播间参数
7301
+ * @param options.principalId - 直播间 principalId
7302
+ * @param cookie - 快手 Cookie (可选)
7303
+ * @param requestConfig - 请求配置 (可选)
7304
+ * @returns 当前直播间纯协议聚合结果
7305
+ */
7306
+ async function fetchLiveRoomInfo(options, cookie, requestConfig) {
7307
+ return fetchKuaishouInternal("liveRoomInfo", options, {
7308
+ cookie,
7309
+ requestConfig
7310
+ });
7311
+ }
7312
+ /**
5256
7313
  * 获取快手表情列表
5257
7314
  * @param options - 请求选项 (可选)
5258
7315
  * @param options.typeMode - 类型模式: 'strict' 返回严格类型, 'loose' 返回 any
@@ -5287,6 +7344,9 @@ async function fetchEmojiList$1(options, cookie, requestConfig) {
5287
7344
  const kuaishouFetcher = {
5288
7345
  fetchVideoWork,
5289
7346
  fetchWorkComments,
7347
+ fetchUserProfile: fetchUserProfile$1,
7348
+ fetchUserWorkList,
7349
+ fetchLiveRoomInfo,
5290
7350
  fetchEmojiList: fetchEmojiList$1
5291
7351
  };
5292
7352
  /**
@@ -5306,6 +7366,9 @@ function createBoundKuaishouFetcher(cookie, requestConfig) {
5306
7366
  return {
5307
7367
  fetchVideoWork: (options, reqConfig) => fetchVideoWork(options, cookie, reqConfig ?? requestConfig),
5308
7368
  fetchWorkComments: (options, reqConfig) => fetchWorkComments(options, cookie, reqConfig ?? requestConfig),
7369
+ fetchUserProfile: (options, reqConfig) => fetchUserProfile$1(options, cookie, reqConfig ?? requestConfig),
7370
+ fetchUserWorkList: (options, reqConfig) => fetchUserWorkList(options, cookie, reqConfig ?? requestConfig),
7371
+ fetchLiveRoomInfo: (options, reqConfig) => fetchLiveRoomInfo(options, cookie, reqConfig ?? requestConfig),
5309
7372
  fetchEmojiList: (options, reqConfig) => fetchEmojiList$1(options, cookie, reqConfig ?? requestConfig)
5310
7373
  };
5311
7374
  }
@@ -7135,15 +9198,6 @@ const douyinUtils = {
7135
9198
  //#endregion
7136
9199
  //#region src/platform/kuaishou/KuaishouApi.ts
7137
9200
  /**
7138
- * 快手 API 模块 (已废弃)
7139
- *
7140
- * 此模块中的 API 已在 v6 版本废弃
7141
- * 请使用 kuaishouFetcher 或 client.kuaishou.fetcher 替代
7142
- *
7143
- * @module platform/kuaishou/KuaishouApi
7144
- * @deprecated v6 已废弃,请使用 fetcher API 替代
7145
- */
7146
- /**
7147
9201
  * 创建废弃的 API 存根函数
7148
9202
  */
7149
9203
  const createDeprecatedStub$1 = (methodName) => {
@@ -7160,6 +9214,9 @@ const createDeprecatedStub$1 = (methodName) => {
7160
9214
  const kuaishou = {
7161
9215
  getWorkInfo: createDeprecatedStub$1("getWorkInfo"),
7162
9216
  getComments: createDeprecatedStub$1("getComments"),
9217
+ getUserProfile: createDeprecatedStub$1("getUserProfile"),
9218
+ getUserWorkList: createDeprecatedStub$1("getUserWorkList"),
9219
+ getLiveRoomInfo: createDeprecatedStub$1("getLiveRoomInfo"),
7163
9220
  getEmojiList: createDeprecatedStub$1("getEmojiList")
7164
9221
  };
7165
9222
  /**
@@ -7223,6 +9280,7 @@ const createKuaishouRoutes = (cookie, requestConfig = getKuaishouDefaultConfig(c
7223
9280
  //#region src/platform/kuaishou/index.ts
7224
9281
  /** 快手相关功能模块 (工具集) */
7225
9282
  const kuaishouUtils = {
9283
+ sign: kuaishouSign,
7226
9284
  kuaishouApiUrls,
7227
9285
  api: kuaishou
7228
9286
  };
@@ -7688,12 +9746,18 @@ const DouyinFetcherMethods = {
7688
9746
  const KuaishouInternalMethods = {
7689
9747
  VIDEO_WORK: "单个视频作品数据",
7690
9748
  WORK_COMMENTS: "评论数据",
9749
+ USER_PROFILE: "用户主页数据",
9750
+ USER_WORK_LIST: "用户作品列表数据",
9751
+ LIVE_ROOM_INFO: "直播间信息数据",
7691
9752
  EMOJI_LIST: "Emoji数据"
7692
9753
  };
7693
9754
  /** 快手 Fetcher 方法名 (英文,对外 API) */
7694
9755
  const KuaishouFetcherMethods = {
7695
9756
  VIDEO_WORK: "fetchVideoWork",
7696
9757
  WORK_COMMENTS: "fetchWorkComments",
9758
+ USER_PROFILE: "fetchUserProfile",
9759
+ USER_WORK_LIST: "fetchUserWorkList",
9760
+ LIVE_ROOM_INFO: "fetchLiveRoomInfo",
7697
9761
  EMOJI_LIST: "fetchEmojiList"
7698
9762
  };
7699
9763
  /** 小红书内部方法名 (中文,用于 getdata.ts) */
@@ -7770,6 +9834,9 @@ const DouyinMethodToFetcher = {
7770
9834
  const KuaishouMethodToFetcher = {
7771
9835
  [KuaishouInternalMethods.VIDEO_WORK]: KuaishouFetcherMethods.VIDEO_WORK,
7772
9836
  [KuaishouInternalMethods.WORK_COMMENTS]: KuaishouFetcherMethods.WORK_COMMENTS,
9837
+ [KuaishouInternalMethods.USER_PROFILE]: KuaishouFetcherMethods.USER_PROFILE,
9838
+ [KuaishouInternalMethods.USER_WORK_LIST]: KuaishouFetcherMethods.USER_WORK_LIST,
9839
+ [KuaishouInternalMethods.LIVE_ROOM_INFO]: KuaishouFetcherMethods.LIVE_ROOM_INFO,
7773
9840
  [KuaishouInternalMethods.EMOJI_LIST]: KuaishouFetcherMethods.EMOJI_LIST
7774
9841
  };
7775
9842
  /** 小红书:内部中文方法名 -> Fetcher 英文方法名 */
@@ -7881,6 +9948,9 @@ const BilibiliMethodReverseMapping = Object.fromEntries(Object.entries(BilibiliM
7881
9948
  const KuaishouMethodMapping = {
7882
9949
  单个视频作品数据: "fetchVideoWork",
7883
9950
  评论数据: "fetchWorkComments",
9951
+ 用户主页数据: "fetchUserProfile",
9952
+ 用户作品列表数据: "fetchUserWorkList",
9953
+ 直播间信息数据: "fetchLiveRoomInfo",
7884
9954
  Emoji数据: "fetchEmojiList"
7885
9955
  };
7886
9956
  /** 英文方法名 -> 中文方法名 反向映射 */
@@ -7964,6 +10034,9 @@ const BilibiliApiRoutes = {
7964
10034
  const KuaishouApiRoutes = {
7965
10035
  videoWork: "/work",
7966
10036
  comments: "/comments",
10037
+ userProfile: "/user",
10038
+ userWorkList: "/user/works",
10039
+ liveRoomInfo: "/live",
7967
10040
  emojiList: "/emoji"
7968
10041
  };
7969
10042
  /**
@@ -8008,7 +10081,7 @@ function getApiRoute(platform, methodType) {
8008
10081
  * 构建后使用 __VERSION__,开发环境从 package.json 读取
8009
10082
  */
8010
10083
  const getVersion = () => {
8011
- return "6.0.0-beta.2";
10084
+ return "6.1.2";
8012
10085
  };
8013
10086
  const VERSION = getVersion();
8014
10087
  /**
@@ -8116,9 +10189,12 @@ exports.KuaishouCommentParamsSchema = KuaishouCommentParamsSchema;
8116
10189
  exports.KuaishouEmojiParamsSchema = KuaishouEmojiParamsSchema;
8117
10190
  exports.KuaishouFetcherMethods = KuaishouFetcherMethods;
8118
10191
  exports.KuaishouInternalMethods = KuaishouInternalMethods;
10192
+ exports.KuaishouLiveRoomInfoParamsSchema = KuaishouLiveRoomInfoParamsSchema;
8119
10193
  exports.KuaishouMethodMapping = KuaishouMethodMapping;
8120
10194
  exports.KuaishouMethodRoutes = KuaishouMethodRoutes;
8121
10195
  exports.KuaishouMethodToFetcher = KuaishouMethodToFetcher;
10196
+ exports.KuaishouUserProfileParamsSchema = KuaishouUserProfileParamsSchema;
10197
+ exports.KuaishouUserWorkListParamsSchema = KuaishouUserWorkListParamsSchema;
8122
10198
  exports.KuaishouValidationSchemas = KuaishouValidationSchemas;
8123
10199
  exports.KuaishouVideoParamsSchema = KuaishouVideoParamsSchema;
8124
10200
  exports.MajorType = MajorType;
@@ -8189,6 +10265,7 @@ exports.isNetworkErrorResult = isNetworkErrorResult;
8189
10265
  exports.kuaishou = kuaishou;
8190
10266
  exports.kuaishouApiUrls = kuaishouApiUrls;
8191
10267
  exports.kuaishouFetcher = kuaishouFetcher;
10268
+ exports.kuaishouSign = kuaishouSign;
8192
10269
  exports.kuaishouUtils = kuaishouUtils;
8193
10270
  exports.logMiddleware = logMiddleware;
8194
10271
  exports.logger = logger;