@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.
- package/README.md +56 -21
- package/dist/default/index.cjs +2222 -145
- package/dist/default/index.d.ts +723 -42
- package/dist/default/index.mjs +2219 -146
- package/package.json +3 -2
package/dist/default/index.mjs
CHANGED
|
@@ -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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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
|
-
*
|
|
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
|
-
/**
|
|
5323
|
+
/**
|
|
5324
|
+
* 快手 API 请求描述集合。
|
|
5325
|
+
*
|
|
5326
|
+
* 该对象只负责返回请求描述,不直接发起网络请求。
|
|
5327
|
+
*/
|
|
5035
5328
|
const kuaishouApiUrls = new API();
|
|
5036
5329
|
|
|
5037
5330
|
//#endregion
|
|
5038
|
-
//#region src/platform/kuaishou/
|
|
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
|
-
*
|
|
5043
|
-
*
|
|
5044
|
-
*/
|
|
5045
|
-
|
|
5046
|
-
|
|
5047
|
-
|
|
5048
|
-
|
|
5049
|
-
|
|
5050
|
-
|
|
5051
|
-
|
|
5052
|
-
|
|
5053
|
-
|
|
5054
|
-
|
|
5055
|
-
|
|
5056
|
-
|
|
5057
|
-
|
|
5058
|
-
|
|
5059
|
-
|
|
5060
|
-
|
|
5061
|
-
|
|
5062
|
-
|
|
5063
|
-
|
|
5064
|
-
|
|
5065
|
-
|
|
5066
|
-
|
|
5067
|
-
|
|
5068
|
-
|
|
5069
|
-
|
|
5070
|
-
|
|
5071
|
-
|
|
5072
|
-
|
|
5073
|
-
|
|
5074
|
-
|
|
5075
|
-
|
|
5076
|
-
|
|
5077
|
-
|
|
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
|
-
|
|
5081
|
-
|
|
5082
|
-
|
|
5083
|
-
|
|
5084
|
-
|
|
5085
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
5470
|
+
* 构造 `HUDR_` 头部中的 `infoCache` 字段。
|
|
5471
|
+
*
|
|
5472
|
+
* @param scriptCount - 页面脚本数量
|
|
5473
|
+
* @returns `HUDR_` 载荷中的脚本信息缓存字节
|
|
5096
5474
|
*/
|
|
5097
|
-
const
|
|
5098
|
-
|
|
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
|
-
*
|
|
5158
|
-
*
|
|
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
|
-
*
|
|
5162
|
-
*
|
|
5163
|
-
* @param
|
|
5164
|
-
* @
|
|
5165
|
-
* @param config - 配置项 (cookie, requestConfig)
|
|
5166
|
-
* @returns Promise<Result<T>>
|
|
5496
|
+
* 对 `HUDR_` 原始载荷执行页面同款掩码异或。
|
|
5497
|
+
*
|
|
5498
|
+
* @param payload - 原始 `HUDR_` 字节载荷
|
|
5499
|
+
* @returns 掩码处理后的字节数组
|
|
5167
5500
|
*/
|
|
5168
|
-
|
|
5169
|
-
const
|
|
5170
|
-
|
|
5171
|
-
|
|
5172
|
-
|
|
5173
|
-
|
|
5174
|
-
|
|
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.
|
|
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
|