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