karin-plugin-kkk 2.42.3 → 2.42.5
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/CHANGELOG.md +21 -0
- package/config/default_config/app.yaml +0 -3
- package/config/default_config/config.json +0 -1
- package/lib/build-metadata.json +5 -5
- package/lib/core_chunk/amagi.d.mts +166 -1
- package/lib/core_chunk/amagi.js +2 -2
- package/lib/core_chunk/amagiClient.js +1 -1
- package/lib/core_chunk/db.js +1 -1
- package/lib/core_chunk/main.js +785 -178
- package/lib/core_chunk/richtext.d.mts +19 -2
- package/lib/core_chunk/richtext.js +2 -2
- package/lib/core_chunk/template.js +1 -1
- package/lib/root.js +1 -1
- package/lib/web/assets/{CronEditor-OPCDvfgS.js → CronEditor-CYgFpX-g.js} +1 -1
- package/lib/web/assets/{DesktopLayout-BoYn3eKY.js → DesktopLayout-iI43ij11.js} +1 -1
- package/lib/web/assets/{MobileLayout-U_BkCpN-.js → MobileLayout-CziU8MCR.js} +1 -1
- package/lib/web/assets/{ThemeSwitch-BMYnI5zS.js → ThemeSwitch-DFohhwd9.js} +1 -1
- package/lib/web/assets/{index-DlKtDaUN.js → index-BRROK0vh.js} +2 -2
- package/lib/web/assets/{separator-ugQv-WRE.js → separator-CxcFlCzz.js} +1 -1
- package/lib/web/index.html +1 -1
- package/lib/web.config.js +1 -1
- package/package.json +3 -4
package/lib/core_chunk/main.js
CHANGED
|
@@ -19,7 +19,6 @@ import { generateSync, scanSync } from "@ikenxuan/qrcode";
|
|
|
19
19
|
import { Transform } from "node:stream";
|
|
20
20
|
import { pipeline } from "node:stream/promises";
|
|
21
21
|
import { inflateSync } from "node:zlib";
|
|
22
|
-
import { embedWatermarkToPngBytes } from "@ikenxuan/watermark";
|
|
23
22
|
import _ from "node-karin/lodash";
|
|
24
23
|
//#region src/index.ts
|
|
25
24
|
globalThis.__kkkLoadStart ??= process.hrtime.bigint();
|
|
@@ -750,6 +749,25 @@ var DouyinDanmakuParamsSchema = zod_default.object({
|
|
|
750
749
|
error: "获取弹幕区间的开始时间必须小于结束时间",
|
|
751
750
|
path: ["start_time"]
|
|
752
751
|
});
|
|
752
|
+
/** 游客用户参数验证 */
|
|
753
|
+
var DouyinGuestUserParamsSchema = zod_default.object({
|
|
754
|
+
methodType: zod_default.literal("guestUserInfo", { error: "方法类型必须是\"guestUserInfo\"" }),
|
|
755
|
+
unique_id: zod_default.string({ error: "抖音号必须是字符串" }).min(1, { error: "抖音号不能为空" })
|
|
756
|
+
});
|
|
757
|
+
/** 游客原声参数验证 */
|
|
758
|
+
var DouyinGuestMusicParamsSchema = zod_default.object({
|
|
759
|
+
methodType: zod_default.literal("guestMusicInfo", { error: "方法类型必须是\"guestMusicInfo\"" }),
|
|
760
|
+
music_id: zod_default.string({ error: "音乐ID必须是字符串" }).min(1, { error: "音乐ID不能为空" })
|
|
761
|
+
});
|
|
762
|
+
/** 游客原声作品列表参数验证 */
|
|
763
|
+
var DouyinGuestMusicListParamsSchema = zod_default.object({
|
|
764
|
+
methodType: zod_default.literal("guestMusicAwemeList", { error: "方法类型必须是\"guestMusicAwemeList\"" }),
|
|
765
|
+
music_id: zod_default.string({ error: "音乐ID必须是字符串" }).min(1, { error: "音乐ID不能为空" }),
|
|
766
|
+
number: smartPositiveInteger("作品数量必须是正整数").optional().default(10),
|
|
767
|
+
cursor: zod_default.coerce.number({ error: "游标必须是数字" }).int({ error: "游标必须是整数" }).min(0, { error: "游标不能小于0" }).default(0).optional()
|
|
768
|
+
});
|
|
769
|
+
/** 表情资源包参数验证(该接口不接受任何参数) */
|
|
770
|
+
var DouyinEmojiResourceParamsSchema = zod_default.object({ methodType: zod_default.literal("emojiResourceMeta", { error: "方法类型必须是\"emojiResourceMeta\"" }) });
|
|
753
771
|
/** 抖音参数验证模式映射 */
|
|
754
772
|
var DouyinValidationSchemas = {
|
|
755
773
|
textWork: DouyinWorkParamsSchema,
|
|
@@ -770,7 +788,11 @@ var DouyinValidationSchemas = {
|
|
|
770
788
|
emojiList: DouyinEmojiListParamsSchema,
|
|
771
789
|
dynamicEmojiList: DouyinEmojiProParamsSchema,
|
|
772
790
|
commentReplies: DouyinCommentReplyParamsSchema,
|
|
773
|
-
danmakuList: DouyinDanmakuParamsSchema
|
|
791
|
+
danmakuList: DouyinDanmakuParamsSchema,
|
|
792
|
+
guestUserInfo: DouyinGuestUserParamsSchema,
|
|
793
|
+
guestMusicInfo: DouyinGuestMusicParamsSchema,
|
|
794
|
+
guestMusicAwemeList: DouyinGuestMusicListParamsSchema,
|
|
795
|
+
emojiResourceMeta: DouyinEmojiResourceParamsSchema
|
|
774
796
|
};
|
|
775
797
|
/** 抖音方法路由映射 */
|
|
776
798
|
var DouyinMethodRoutes = {
|
|
@@ -792,7 +814,11 @@ var DouyinMethodRoutes = {
|
|
|
792
814
|
dynamicEmojiList: "/fetch_emoji_pro_list",
|
|
793
815
|
liveRoomInfo: "/fetch_user_live_videos",
|
|
794
816
|
danmakuList: "/fetch_work_danmaku",
|
|
795
|
-
loginQrcode: "/fetch_login_qrcode"
|
|
817
|
+
loginQrcode: "/fetch_login_qrcode",
|
|
818
|
+
guestUserInfo: "/fetch_guest_user_info",
|
|
819
|
+
guestMusicInfo: "/fetch_guest_music_info",
|
|
820
|
+
guestMusicAwemeList: "/fetch_guest_music_aweme_list",
|
|
821
|
+
emojiResourceMeta: "/fetch_emoji_resource_meta"
|
|
796
822
|
};
|
|
797
823
|
//#endregion
|
|
798
824
|
//#region ../amagi/packages/core/src/validation/kuaishou.ts
|
|
@@ -4532,6 +4558,229 @@ var a_bogus_default = (url, user_agent) => {
|
|
|
4532
4558
|
return result_encrypt(generate_random_str() + generate_rc4_bb_str(new URLSearchParams(new URL(url).search).toString(), cleanedUserAgent, "1536|747|1536|834|0|30|0|0|1536|834|1536|864|1525|747|24|24|Win32"), "s4") + "=";
|
|
4533
4559
|
};
|
|
4534
4560
|
//#endregion
|
|
4561
|
+
//#region ../amagi/packages/core/src/platform/douyin/sign/secsdkWebSign.ts
|
|
4562
|
+
/**
|
|
4563
|
+
* 抖音主站 `x-secsdk-web-signature` 纯算实现(TypeScript 移植)
|
|
4564
|
+
*
|
|
4565
|
+
* 移植自 cv-cat/DouYin_Spider `utils/secsdk_web_sign.py`,算法与常量按该实现逐条对齐。
|
|
4566
|
+
*
|
|
4567
|
+
* ## 算法
|
|
4568
|
+
*
|
|
4569
|
+
* ```text
|
|
4570
|
+
* plain = {uifid}_{timestamp}_{WEBSIGN_CONST}_{canonical_query}
|
|
4571
|
+
* signature = md5(plain) 的 32 位小写十六进制
|
|
4572
|
+
* ```
|
|
4573
|
+
*
|
|
4574
|
+
* `WEBSIGN_CONST` 是 secsdk VMP 常量池里的写死盐,与账号 / 会话无关。
|
|
4575
|
+
*
|
|
4576
|
+
* ## canonical_query 规范化规则
|
|
4577
|
+
*
|
|
4578
|
+
* - 保持原始参数顺序,**不排序**;重复参数原样保留。
|
|
4579
|
+
* - 每个 value 先解码(`+`→空格、`%XX`→字节)再用 `encodeURIComponent` 重编码。
|
|
4580
|
+
* - key **只解码、不重编码**。
|
|
4581
|
+
* - 无等号的裸参数补成 `k=`。
|
|
4582
|
+
* - `×tamp={ts}` 追加到 query 末尾后再参与哈希。
|
|
4583
|
+
* - query 里没有 `uifid` 时,从 `UIFID` cookie 取值追加到末尾再签。
|
|
4584
|
+
*
|
|
4585
|
+
* ## 重要
|
|
4586
|
+
*
|
|
4587
|
+
* 签名算的是**规范化后的 query**,服务端也按收到的 query 校验,所以必须发送
|
|
4588
|
+
* {@link signSecsdkWebUrl} 返回的 URL,不能拿原始 URL 直接发。
|
|
4589
|
+
*
|
|
4590
|
+
* 签名绑 query、**不绑 path**(本项目实测:同一份 query 换 path 仍然 200)。
|
|
4591
|
+
*
|
|
4592
|
+
* @module platform/douyin/secsdkWebSign
|
|
4593
|
+
*/
|
|
4594
|
+
/** secsdk VM 常量池里的固定盐(与账号 / 会话无关) */
|
|
4595
|
+
var WEBSIGN_CONST = "A96D855A08C0A9707F8BEF0D9A527E4E";
|
|
4596
|
+
/** 签名参数名 */
|
|
4597
|
+
var SECSDK_SIG_KEY = "x-secsdk-web-signature";
|
|
4598
|
+
/** 时间戳参数名 */
|
|
4599
|
+
var SECSDK_TS_KEY = "timestamp";
|
|
4600
|
+
/** 走 secsdk 签名的 GET 接口路径(来自 SDK 的 webSign 策略配置) */
|
|
4601
|
+
var PROTECTED_PATHS_GET = [
|
|
4602
|
+
"/aweme/v1/web/aweme/detail/",
|
|
4603
|
+
"/aweme/v1/web/aweme/post/",
|
|
4604
|
+
"/aweme/v1/web/aweme/favorite/",
|
|
4605
|
+
"/aweme/v1/web/aweme/listcollection/",
|
|
4606
|
+
"/aweme/v1/web/mix/aweme/",
|
|
4607
|
+
"/aweme/v1/web/tab/feed/",
|
|
4608
|
+
"/aweme/v1/web/mix/list/",
|
|
4609
|
+
"/aweme/v1/web/music/aweme/",
|
|
4610
|
+
"/aweme/v1/web/music/list/",
|
|
4611
|
+
"/aweme/v1/web/mix/detail/",
|
|
4612
|
+
"/aweme/v1/web/mix/listcollection/",
|
|
4613
|
+
"/aweme/v1/web/music/detail/",
|
|
4614
|
+
"/aweme/v1/web/collects/list/",
|
|
4615
|
+
"/aweme/v1/web/collects/video/list/"
|
|
4616
|
+
];
|
|
4617
|
+
/** 走 secsdk 签名的 POST 接口路径 */
|
|
4618
|
+
var PROTECTED_PATHS_POST = [
|
|
4619
|
+
"/aweme/v1/web/aweme/detail/",
|
|
4620
|
+
"/aweme/v1/web/aweme/post/",
|
|
4621
|
+
"/aweme/v1/web/aweme/favorite/",
|
|
4622
|
+
"/aweme/v1/web/aweme/listcollection/",
|
|
4623
|
+
"/aweme/v1/web/mix/aweme/",
|
|
4624
|
+
"/aweme/v1/web/tab/feed/"
|
|
4625
|
+
];
|
|
4626
|
+
var HEX_PAIR = /^[0-9A-Fa-f]{2}$/;
|
|
4627
|
+
/**
|
|
4628
|
+
* 宽容的百分号解码,等价于 Python `urllib.parse.unquote(..., errors='replace')`:
|
|
4629
|
+
* 合法的 `%XX` 解成字节,非法的 `%` 原样保留,最后按 UTF-8 解码(坏字节 → U+FFFD)。
|
|
4630
|
+
*
|
|
4631
|
+
* 不能直接用 `decodeURIComponent` —— 它遇到 `100%` 这种孤立百分号会抛异常,
|
|
4632
|
+
* 而 SDK 的行为是把它当字面量留下(`100%` → 签成 `100%25`)。
|
|
4633
|
+
*
|
|
4634
|
+
* @param input - 待解码字符串
|
|
4635
|
+
* @returns 解码后的字符串
|
|
4636
|
+
*/
|
|
4637
|
+
var percentDecodeLoose = (input) => {
|
|
4638
|
+
if (!input.includes("%")) return input;
|
|
4639
|
+
const bytes = [];
|
|
4640
|
+
let i = 0;
|
|
4641
|
+
while (i < input.length) {
|
|
4642
|
+
if (input[i] === "%" && HEX_PAIR.test(input.slice(i + 1, i + 3))) {
|
|
4643
|
+
bytes.push(parseInt(input.slice(i + 1, i + 3), 16));
|
|
4644
|
+
i += 3;
|
|
4645
|
+
continue;
|
|
4646
|
+
}
|
|
4647
|
+
for (const byte of Buffer.from(input[i], "utf8")) bytes.push(byte);
|
|
4648
|
+
i += 1;
|
|
4649
|
+
}
|
|
4650
|
+
return Buffer.from(bytes).toString("utf8");
|
|
4651
|
+
};
|
|
4652
|
+
/**
|
|
4653
|
+
* 等价于 Python `unquote_plus`:先把 `+` 当空格,再做百分号解码。
|
|
4654
|
+
*
|
|
4655
|
+
* @param input - 待解码字符串
|
|
4656
|
+
* @returns 解码后的字符串
|
|
4657
|
+
*/
|
|
4658
|
+
var unquotePlusLoose = (input) => percentDecodeLoose(input.includes("+") ? input.replace(/\+/g, " ") : input);
|
|
4659
|
+
/**
|
|
4660
|
+
* 按 SDK 规则规范化 query 字符串:顺序不变,value 解码后用 `encodeURIComponent` 重编码,
|
|
4661
|
+
* key 只解码不重编码,无等号的裸参数补成 `k=`。
|
|
4662
|
+
*
|
|
4663
|
+
* @param query - 原始 query 字符串(不含 `?`)
|
|
4664
|
+
* @returns 规范化后的 query 字符串
|
|
4665
|
+
*/
|
|
4666
|
+
var canonicalQuery = (query) => {
|
|
4667
|
+
const parts = [];
|
|
4668
|
+
for (const pair of query.split("&")) {
|
|
4669
|
+
if (!pair) continue;
|
|
4670
|
+
const eq = pair.indexOf("=");
|
|
4671
|
+
const rawKey = eq === -1 ? pair : pair.slice(0, eq);
|
|
4672
|
+
const rawValue = eq === -1 ? "" : pair.slice(eq + 1);
|
|
4673
|
+
parts.push(`${unquotePlusLoose(rawKey)}=${encodeURIComponent(unquotePlusLoose(rawValue))}`);
|
|
4674
|
+
}
|
|
4675
|
+
return parts.join("&");
|
|
4676
|
+
};
|
|
4677
|
+
/**
|
|
4678
|
+
* 拆成 `[base, query]`,并剥掉已有的 `timestamp` / 签名字段,保证可重复调用。
|
|
4679
|
+
*
|
|
4680
|
+
* @param url - 完整 URL
|
|
4681
|
+
* @returns `[base, query]` 二元组
|
|
4682
|
+
*/
|
|
4683
|
+
var splitUrl = (url) => {
|
|
4684
|
+
const mark = url.indexOf("?");
|
|
4685
|
+
if (mark === -1) return [url, ""];
|
|
4686
|
+
return [url.slice(0, mark), url.slice(mark + 1).split("&").filter((pair) => {
|
|
4687
|
+
if (!pair) return false;
|
|
4688
|
+
const name = pair.split("=", 1)[0];
|
|
4689
|
+
return name !== "timestamp" && name !== "x-secsdk-web-signature";
|
|
4690
|
+
}).join("&")];
|
|
4691
|
+
};
|
|
4692
|
+
/**
|
|
4693
|
+
* 从 cookie 字符串里取 `UIFID`。
|
|
4694
|
+
*
|
|
4695
|
+
* 注意 cookie 里通常还有个 `UIFID_TEMP`,取值时必须锚定 `UIFID=` 前面是行首或分隔符,
|
|
4696
|
+
* 否则会取错。
|
|
4697
|
+
*
|
|
4698
|
+
* @param cookie - cookie 字符串
|
|
4699
|
+
* @returns `UIFID` 的值,取不到时返回空串
|
|
4700
|
+
*/
|
|
4701
|
+
var extractUifidFromCookie = (cookie) => {
|
|
4702
|
+
if (!cookie) return "";
|
|
4703
|
+
const hit = /(?:^|;\s*)UIFID=([^;]*)/.exec(cookie);
|
|
4704
|
+
return hit ? hit[1].trim() : "";
|
|
4705
|
+
};
|
|
4706
|
+
/**
|
|
4707
|
+
* 判断该 path 是否需要 secsdk 加签(依据 SDK 的 webSign 策略配置)。
|
|
4708
|
+
*
|
|
4709
|
+
* @param path - URL 的 pathname
|
|
4710
|
+
* @param method - HTTP 方法,默认 `GET`
|
|
4711
|
+
* @returns 是否需要加签
|
|
4712
|
+
*/
|
|
4713
|
+
var isSecsdkProtected = (path, method = "GET") => {
|
|
4714
|
+
return (String(method).toUpperCase() === "POST" ? PROTECTED_PATHS_POST : PROTECTED_PATHS_GET).includes(path);
|
|
4715
|
+
};
|
|
4716
|
+
/**
|
|
4717
|
+
* 计算 `x-secsdk-web-signature`。
|
|
4718
|
+
*
|
|
4719
|
+
* @param url - 完整 API URL(scheme+host+path+query)。已带 `timestamp` /
|
|
4720
|
+
* `x-secsdk-web-signature` 时会自动剥除,可重复调用。
|
|
4721
|
+
* @param options - 时间戳与 uifid 兜底值
|
|
4722
|
+
* @returns 时间戳、签名与**实际应当发送**的规范化 query
|
|
4723
|
+
*/
|
|
4724
|
+
var signSecsdkWebQuery = (url, options = {}) => {
|
|
4725
|
+
const ts = Math.trunc(options.ts ?? Date.now() / 1e3);
|
|
4726
|
+
const [, rawQuery] = splitUrl(url);
|
|
4727
|
+
let canon = canonicalQuery(rawQuery);
|
|
4728
|
+
if (!canon.split("&").filter(Boolean).map((pair) => pair.split("=", 1)[0]).includes("uifid") && options.uifid) {
|
|
4729
|
+
const appended = `uifid=${encodeURIComponent(options.uifid)}`;
|
|
4730
|
+
canon = canon ? `${canon}&${appended}` : appended;
|
|
4731
|
+
}
|
|
4732
|
+
const signedQuery = canon ? `${canon}&${SECSDK_TS_KEY}=${ts}` : `${SECSDK_TS_KEY}=${ts}`;
|
|
4733
|
+
let uifidValue = "";
|
|
4734
|
+
for (const pair of signedQuery.split("&")) if (pair.startsWith("uifid=")) {
|
|
4735
|
+
uifidValue = unquotePlusLoose(pair.slice(6));
|
|
4736
|
+
break;
|
|
4737
|
+
}
|
|
4738
|
+
if (!uifidValue && options.uifid) uifidValue = options.uifid;
|
|
4739
|
+
const plain = `${uifidValue}_${ts}_${WEBSIGN_CONST}_${signedQuery}`;
|
|
4740
|
+
return {
|
|
4741
|
+
ts,
|
|
4742
|
+
signature: crypto.createHash("md5").update(plain, "utf8").digest("hex"),
|
|
4743
|
+
signedQuery
|
|
4744
|
+
};
|
|
4745
|
+
};
|
|
4746
|
+
/**
|
|
4747
|
+
* 返回可直接发送的完整 URL(规范化 query + timestamp + 签名)。
|
|
4748
|
+
*
|
|
4749
|
+
* 服务端按收到的 query 校验签名,所以必须发这个返回值,不能拿原始 URL 直接发。
|
|
4750
|
+
*
|
|
4751
|
+
* @param url - 完整 API URL
|
|
4752
|
+
* @param options - 时间戳与 uifid 兜底值
|
|
4753
|
+
* @returns 带签名的完整 URL
|
|
4754
|
+
*/
|
|
4755
|
+
var signSecsdkWebUrl = (url, options = {}) => {
|
|
4756
|
+
const [base] = splitUrl(url);
|
|
4757
|
+
const { signature, signedQuery } = signSecsdkWebQuery(url, options);
|
|
4758
|
+
return `${base}?${signedQuery}&${SECSDK_SIG_KEY}=${signature}`;
|
|
4759
|
+
};
|
|
4760
|
+
/**
|
|
4761
|
+
* 对受保护接口补上 secsdk 签名;path 不在策略表里时原样返回。
|
|
4762
|
+
*
|
|
4763
|
+
* 这是给取数层用的唯一入口 —— 无条件套在 URL 上即可,不需要调用方判断端点。
|
|
4764
|
+
*
|
|
4765
|
+
* @param url - 完整 API URL(应当是**最后一步**,即 a_bogus 等参数都已拼好之后)
|
|
4766
|
+
* @param options - cookie / uifid / method / ts
|
|
4767
|
+
* @returns 需要加签的返回带签名的 URL,否则原样返回
|
|
4768
|
+
*/
|
|
4769
|
+
var applySecsdkWebSign = (url, options = {}) => {
|
|
4770
|
+
let pathname;
|
|
4771
|
+
try {
|
|
4772
|
+
pathname = new URL(url).pathname;
|
|
4773
|
+
} catch {
|
|
4774
|
+
return url;
|
|
4775
|
+
}
|
|
4776
|
+
if (!isSecsdkProtected(pathname, options.method)) return url;
|
|
4777
|
+
const uifid = options.uifid || extractUifidFromCookie(options.cookie);
|
|
4778
|
+
return signSecsdkWebUrl(url, {
|
|
4779
|
+
ts: options.ts,
|
|
4780
|
+
uifid
|
|
4781
|
+
});
|
|
4782
|
+
};
|
|
4783
|
+
//#endregion
|
|
4535
4784
|
//#region ../amagi/packages/core/src/platform/douyin/sign/x_bogus.ts
|
|
4536
4785
|
/**
|
|
4537
4786
|
* X-Bogus 生成工具(TikTok/Douyin 签名算法)- TypeScript 版本
|
|
@@ -4723,6 +4972,20 @@ var douyinSign = class {
|
|
|
4723
4972
|
static XB(url, userAgent) {
|
|
4724
4973
|
return new XBogus().getXBogus(url, userAgent ?? defaultUserAgent).xbogus;
|
|
4725
4974
|
}
|
|
4975
|
+
/**
|
|
4976
|
+
* `x-secsdk-web-signature` 签名算法
|
|
4977
|
+
*
|
|
4978
|
+
* 与 `AB` / `XB` 不同,它改写整条 URL 而不是返回一个参数值:签名算的是规范化后的 query,
|
|
4979
|
+
* 服务端也按收到的 query 校验,所以必须发送返回的这条 URL。只对 SDK 策略表内的 path 生效,
|
|
4980
|
+
* 其余原样返回,因此可以无条件套用。必须是最后一步(webid → a_bogus → 本签名)。
|
|
4981
|
+
*
|
|
4982
|
+
* @param url 已拼好全部参数(含 a_bogus)的完整地址
|
|
4983
|
+
* @param options `uifid`(query 缺失时从 cookie 取)、`cookie`、`method`、`ts`
|
|
4984
|
+
* @returns 需要加签时返回带签名的完整 URL,否则原样返回
|
|
4985
|
+
*/
|
|
4986
|
+
static SecSdk(url, options = {}) {
|
|
4987
|
+
return applySecsdkWebSign(url, options);
|
|
4988
|
+
}
|
|
4726
4989
|
/** 生成一个唯一的验证字符串 */
|
|
4727
4990
|
static VerifyFpManager() {
|
|
4728
4991
|
const e = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".split("");
|
|
@@ -4767,6 +5030,10 @@ var buildQueryString = (params) => {
|
|
|
4767
5030
|
return Object.entries(params).filter(([_, value]) => value !== void 0 && value !== null).map(([key, value]) => `${key}=${encodeURIComponent(value)}`).join("&");
|
|
4768
5031
|
};
|
|
4769
5032
|
var fp = douyinSign.VerifyFpManager();
|
|
5033
|
+
/** iesdouyin v2 游客接口前缀:不需要 cookie、不需要签名 */
|
|
5034
|
+
var GUEST_BASE = "https://www.iesdouyin.com/web/api/v2";
|
|
5035
|
+
/** 表情资源包元信息,走 App 接口,同样免鉴权(需要 Android UA,见 getdata.ts) */
|
|
5036
|
+
var EMOJI_RESOURCE_URL = "https://api.amemv.com/aweme/v1/im/resources/emoji/?device_platform=android&version_name=17.4.0";
|
|
4770
5037
|
/**
|
|
4771
5038
|
* 抖音 API URL 构建类
|
|
4772
5039
|
*
|
|
@@ -4823,8 +5090,7 @@ var DouyinAPI = class {
|
|
|
4823
5090
|
version_name: "19.5.0",
|
|
4824
5091
|
screen_width: "2328",
|
|
4825
5092
|
screen_height: "1310",
|
|
4826
|
-
round_trip_time: "150"
|
|
4827
|
-
webid: "7351848354471872041"
|
|
5093
|
+
round_trip_time: "150"
|
|
4828
5094
|
})}`;
|
|
4829
5095
|
}
|
|
4830
5096
|
/** 获取评论数据 */
|
|
@@ -4883,7 +5149,6 @@ var DouyinAPI = class {
|
|
|
4883
5149
|
downlink: "10",
|
|
4884
5150
|
effective_type: "4g",
|
|
4885
5151
|
round_trip_time: "50",
|
|
4886
|
-
webid: "7487210762873685515",
|
|
4887
5152
|
verifyFp: fp,
|
|
4888
5153
|
fp
|
|
4889
5154
|
})}`;
|
|
@@ -4903,7 +5168,31 @@ var DouyinAPI = class {
|
|
|
4903
5168
|
}
|
|
4904
5169
|
/** 获取表情数据 */
|
|
4905
5170
|
getEmojiList() {
|
|
4906
|
-
return
|
|
5171
|
+
return `https://www.douyin.com/aweme/v1/web/emoji/list?${buildQueryString({
|
|
5172
|
+
...this.getBaseParams(),
|
|
5173
|
+
/** 缺少该参数只会返回常规表情(214 个),带上才含限时、联名、节日表情(371 个) */
|
|
5174
|
+
need_all: "true"
|
|
5175
|
+
})}`;
|
|
5176
|
+
}
|
|
5177
|
+
/** 抖音号(unique_id)转用户信息,唯一免签名的 sec_uid 获取途径 */
|
|
5178
|
+
getGuestUserInfo(data) {
|
|
5179
|
+
return `${GUEST_BASE}/user/info/?${buildQueryString({ unique_id: data.unique_id })}`;
|
|
5180
|
+
}
|
|
5181
|
+
/** 获取原声本体,响应中没有 play_url,mp3 需从源作品上取 */
|
|
5182
|
+
getGuestMusicInfo(data) {
|
|
5183
|
+
return `${GUEST_BASE}/music/info/?${buildQueryString({ music_id: data.music_id })}`;
|
|
5184
|
+
}
|
|
5185
|
+
/** 获取使用某原声的作品列表,每条的 music 字段被抖音裁空,只能取 aweme_id */
|
|
5186
|
+
getGuestMusicAwemeList(data) {
|
|
5187
|
+
return `${GUEST_BASE}/music/list/aweme/?${buildQueryString({
|
|
5188
|
+
music_id: data.music_id,
|
|
5189
|
+
count: data.number ?? 10,
|
|
5190
|
+
cursor: data.cursor ?? 0
|
|
5191
|
+
})}`;
|
|
5192
|
+
}
|
|
5193
|
+
/** 获取表情资源包元信息 `{ id, md5, resource_url, update_time }`,md5 同时是版本号 */
|
|
5194
|
+
getEmojiResourceMeta() {
|
|
5195
|
+
return EMOJI_RESOURCE_URL;
|
|
4907
5196
|
}
|
|
4908
5197
|
/** 获取用户主页视频数据 */
|
|
4909
5198
|
getUserVideoList(data) {
|
|
@@ -4923,8 +5212,7 @@ var DouyinAPI = class {
|
|
|
4923
5212
|
version_name: "17.4.0",
|
|
4924
5213
|
screen_width: "1552",
|
|
4925
5214
|
screen_height: "970",
|
|
4926
|
-
round_trip_time: "50"
|
|
4927
|
-
webid: "7338423850134226495"
|
|
5215
|
+
round_trip_time: "50"
|
|
4928
5216
|
})}`;
|
|
4929
5217
|
}
|
|
4930
5218
|
/** 获取用户喜欢列表数据 */
|
|
@@ -4946,8 +5234,7 @@ var DouyinAPI = class {
|
|
|
4946
5234
|
version_name: "17.4.0",
|
|
4947
5235
|
screen_width: "2328",
|
|
4948
5236
|
screen_height: "1310",
|
|
4949
|
-
round_trip_time: "0"
|
|
4950
|
-
webid: "7487210762873685515"
|
|
5237
|
+
round_trip_time: "0"
|
|
4951
5238
|
})}`;
|
|
4952
5239
|
}
|
|
4953
5240
|
/** 获取用户推荐列表数据 */
|
|
@@ -4987,7 +5274,6 @@ var DouyinAPI = class {
|
|
|
4987
5274
|
downlink: "10",
|
|
4988
5275
|
effective_type: "4g",
|
|
4989
5276
|
round_trip_time: "50",
|
|
4990
|
-
webid: "7487210762873685515",
|
|
4991
5277
|
msToken: douyinSign.Mstoken(184),
|
|
4992
5278
|
verifyFp: fp,
|
|
4993
5279
|
fp
|
|
@@ -5005,8 +5291,7 @@ var DouyinAPI = class {
|
|
|
5005
5291
|
version_name: "17.4.0",
|
|
5006
5292
|
screen_width: "1552",
|
|
5007
5293
|
screen_height: "970",
|
|
5008
|
-
round_trip_time: "0"
|
|
5009
|
-
webid: "7327957959955580467"
|
|
5294
|
+
round_trip_time: "0"
|
|
5010
5295
|
})}`;
|
|
5011
5296
|
}
|
|
5012
5297
|
/** 获取热点词数据 */
|
|
@@ -5020,8 +5305,7 @@ var DouyinAPI = class {
|
|
|
5020
5305
|
version_name: "17.4.0",
|
|
5021
5306
|
screen_width: "1552",
|
|
5022
5307
|
screen_height: "970",
|
|
5023
|
-
round_trip_time: "50"
|
|
5024
|
-
webid: "7327957959955580467"
|
|
5308
|
+
round_trip_time: "50"
|
|
5025
5309
|
})}`;
|
|
5026
5310
|
}
|
|
5027
5311
|
/** 获取搜索数据 */
|
|
@@ -5050,7 +5334,6 @@ var DouyinAPI = class {
|
|
|
5050
5334
|
support_h265: "1",
|
|
5051
5335
|
version_code: "170400",
|
|
5052
5336
|
version_name: "17.4.0",
|
|
5053
|
-
webid: "7521399115230610959",
|
|
5054
5337
|
...data.search_id && { search_id: data.search_id }
|
|
5055
5338
|
})}`;
|
|
5056
5339
|
else if (searchType === "video") return `https://www.douyin.com/aweme/v1/web/search/item/?${buildQueryString({
|
|
@@ -5076,7 +5359,6 @@ var DouyinAPI = class {
|
|
|
5076
5359
|
support_h265: "1",
|
|
5077
5360
|
version_code: "170400",
|
|
5078
5361
|
version_name: "17.4.0",
|
|
5079
|
-
webid: "7521399115230610959",
|
|
5080
5362
|
...data.search_id && { search_id: data.search_id }
|
|
5081
5363
|
})}`;
|
|
5082
5364
|
else return `https://www.douyin.com/aweme/v1/web/general/search/stream/?${buildQueryString({
|
|
@@ -5100,8 +5382,7 @@ var DouyinAPI = class {
|
|
|
5100
5382
|
support_dash: "1",
|
|
5101
5383
|
support_h265: "1",
|
|
5102
5384
|
version_code: "190600",
|
|
5103
|
-
version_name: "19.6.0"
|
|
5104
|
-
webid: "7521399115230610959"
|
|
5385
|
+
version_name: "19.6.0"
|
|
5105
5386
|
})}`;
|
|
5106
5387
|
}
|
|
5107
5388
|
/** 获取互动表情数据 */
|
|
@@ -5112,7 +5393,8 @@ var DouyinAPI = class {
|
|
|
5112
5393
|
channel: "channel_pc_web",
|
|
5113
5394
|
publish_video_strategy_type: "2",
|
|
5114
5395
|
app_id: "1128",
|
|
5115
|
-
|
|
5396
|
+
/** 原始 JSON 字符串,buildQueryString 会编码一次;预先编码过会变成 %2522,接口回「参数不合法」 */
|
|
5397
|
+
scenes: "[\"interactive_resources\"]",
|
|
5116
5398
|
pc_client_type: "1",
|
|
5117
5399
|
version_code: "170400",
|
|
5118
5400
|
version_name: "17.4.0",
|
|
@@ -5134,7 +5416,6 @@ var DouyinAPI = class {
|
|
|
5134
5416
|
downlink: "1.5",
|
|
5135
5417
|
effective_type: "4g",
|
|
5136
5418
|
round_trip_time: "350",
|
|
5137
|
-
webid: "7347329698282833447",
|
|
5138
5419
|
msToken: douyinSign.Mstoken(116),
|
|
5139
5420
|
verifyFp: fp,
|
|
5140
5421
|
fp
|
|
@@ -5169,7 +5450,6 @@ var DouyinAPI = class {
|
|
|
5169
5450
|
downlink: "1.5",
|
|
5170
5451
|
effective_type: "4g",
|
|
5171
5452
|
round_trip_time: "350",
|
|
5172
|
-
webid: "7347329698282833447",
|
|
5173
5453
|
msToken: douyinSign.Mstoken(116),
|
|
5174
5454
|
verifyFp: fp,
|
|
5175
5455
|
fp
|
|
@@ -5234,7 +5514,6 @@ var DouyinAPI = class {
|
|
|
5234
5514
|
engine_version: "140.0.0.0",
|
|
5235
5515
|
downlink: "1.55",
|
|
5236
5516
|
round_trip_time: "200",
|
|
5237
|
-
webid: "7487210762873685515",
|
|
5238
5517
|
msToken: douyinSign.Mstoken(116),
|
|
5239
5518
|
verifyFp: fp,
|
|
5240
5519
|
fp
|
|
@@ -5253,6 +5532,70 @@ var createDouyinApiUrls = (userAgent) => {
|
|
|
5253
5532
|
/** 默认的 DouyinAPI 实例(使用默认浏览器版本 125.0.0.0) */
|
|
5254
5533
|
var douyinApiUrls = new DouyinAPI();
|
|
5255
5534
|
//#endregion
|
|
5535
|
+
//#region ../amagi/packages/core/src/platform/douyin/webid.ts
|
|
5536
|
+
/**
|
|
5537
|
+
* 抖音 `webid` 的取得与缓存。
|
|
5538
|
+
*
|
|
5539
|
+
* `webid` 是服务端下发的访客 id,客户端算不出来 —— 服务端按 `ttwid` 算好之后,
|
|
5540
|
+
* 通过每个响应的 `cookie_ttwidinfo_webid` 头告知。一个 ttwid 对应一个稳定的 webid。
|
|
5541
|
+
*
|
|
5542
|
+
* 抖音会拿 query 里的 `webid` 与 cookie 的会话交叉校验,对不上就返回 HTTP 200 + 空 body
|
|
5543
|
+
* (不是 403、也不是业务码),最典型的受害者是 `aweme/favorite`。因此缓存必须按 ttwid 分键:
|
|
5544
|
+
* 换了 cookie,webid 就得跟着换。取不到时不传 —— 不传是安全的,传错才致命。
|
|
5545
|
+
*
|
|
5546
|
+
* @module platform/douyin/webid
|
|
5547
|
+
*/
|
|
5548
|
+
/** ttwid → webid。webid 对同一 ttwid 是稳定的,进程内缓存即可 */
|
|
5549
|
+
var cache = /* @__PURE__ */ new Map();
|
|
5550
|
+
/** 抖音回传 webid 的响应头 */
|
|
5551
|
+
var WEBID_HEADER = "cookie_ttwidinfo_webid";
|
|
5552
|
+
/**
|
|
5553
|
+
* 从 cookie 串里取 `ttwid`,用作缓存键
|
|
5554
|
+
*
|
|
5555
|
+
* @param cookie - cookie 串
|
|
5556
|
+
* @returns `ttwid` 的值,取不到时返回空串
|
|
5557
|
+
*/
|
|
5558
|
+
var ttwidOf = (cookie) => {
|
|
5559
|
+
if (!cookie) return "";
|
|
5560
|
+
const hit = /(?:^|;\s*)ttwid=([^;]*)/.exec(cookie);
|
|
5561
|
+
return hit ? hit[1].trim() : "";
|
|
5562
|
+
};
|
|
5563
|
+
/**
|
|
5564
|
+
* 看到响应头就把 webid 记下来,对没有这个头的响应是无操作
|
|
5565
|
+
*
|
|
5566
|
+
* @param requestHeaders - 请求头,用于取 cookie 里的 ttwid 作为键
|
|
5567
|
+
* @param responseHeaders - 响应头
|
|
5568
|
+
*/
|
|
5569
|
+
var rememberDouyinWebid = (requestHeaders, responseHeaders) => {
|
|
5570
|
+
const webid = responseHeaders?.[WEBID_HEADER];
|
|
5571
|
+
if (typeof webid !== "string" || !/^\d{6,}$/.test(webid)) return;
|
|
5572
|
+
const key = ttwidOf(requestHeaders?.Cookie ?? requestHeaders?.cookie);
|
|
5573
|
+
if (key) cache.set(key, webid);
|
|
5574
|
+
};
|
|
5575
|
+
/**
|
|
5576
|
+
* 取这份 cookie 对应的 webid
|
|
5577
|
+
*
|
|
5578
|
+
* @param cookie - cookie 串
|
|
5579
|
+
* @returns 缓存里的 webid,没有时返回空串
|
|
5580
|
+
*/
|
|
5581
|
+
var douyinWebidFor = (cookie) => cache.get(ttwidOf(cookie)) ?? "";
|
|
5582
|
+
/**
|
|
5583
|
+
* 给 URL 补上 `webid`
|
|
5584
|
+
*
|
|
5585
|
+
* 必须在算 a_bogus 之前调用:真实浏览器的顺序是「拼好 query(含 webid)→ a_bogus → secsdk 签名」,
|
|
5586
|
+
* 后两者都覆盖 query,顺序错了签名就不成立。URL 里已有 `webid` 或缓存为空时原样返回。
|
|
5587
|
+
*
|
|
5588
|
+
* @param url - 还没加签的 URL
|
|
5589
|
+
* @param cookie - 本次请求使用的 cookie
|
|
5590
|
+
* @returns 补好 `webid` 的 URL
|
|
5591
|
+
*/
|
|
5592
|
+
var withDouyinWebid = (url, cookie) => {
|
|
5593
|
+
if (url.includes("webid=")) return url;
|
|
5594
|
+
const webid = douyinWebidFor(cookie);
|
|
5595
|
+
if (!webid) return url;
|
|
5596
|
+
return `${url}${url.includes("?") ? "&" : "?"}webid=${webid}`;
|
|
5597
|
+
};
|
|
5598
|
+
//#endregion
|
|
5256
5599
|
//#region ../amagi/packages/core/src/platform/douyin/getdata.ts
|
|
5257
5600
|
/**
|
|
5258
5601
|
* 抖音数据获取模块
|
|
@@ -5297,7 +5640,7 @@ var getSignParamName = (signType = "a_bogus") => {
|
|
|
5297
5640
|
* @param userAgent - 用户代理
|
|
5298
5641
|
* @returns 带签名的完整 URL
|
|
5299
5642
|
*/
|
|
5300
|
-
var
|
|
5643
|
+
var buildSignedUrlBase = (url, signType = "a_bogus", userAgent) => {
|
|
5301
5644
|
const signature = getSignature(url, signType, userAgent);
|
|
5302
5645
|
return `${url}&${getSignParamName(signType)}=${signature}`;
|
|
5303
5646
|
};
|
|
@@ -5323,13 +5666,38 @@ var DouyinData = async (data, cookie, requestConfig) => {
|
|
|
5323
5666
|
const userAgent = baseRequestConfig.headers?.["User-Agent"];
|
|
5324
5667
|
const douyinApiUrls = createDouyinApiUrls(userAgent);
|
|
5325
5668
|
const signType = data.signType ?? "a_bogus";
|
|
5669
|
+
/**
|
|
5670
|
+
* 遮蔽外层的 `buildSignedUrlBase`,让下面所有调用点自动补上 webid 与 secsdk 签名。
|
|
5671
|
+
*
|
|
5672
|
+
* 顺序固定为 webid → a_bogus → secsdk:三者都覆盖 query,颠倒任意一步签名都不成立。
|
|
5673
|
+
* path 不在 SDK 策略表内时 `SecSdk` 原样返回,因此对其余接口是无操作。
|
|
5674
|
+
*/
|
|
5675
|
+
const secsdkUifid = extractUifidFromCookie(baseRequestConfig.headers?.Cookie);
|
|
5676
|
+
const buildSignedUrl = (url, st = signType, ua = userAgent) => douyinSign.SecSdk(buildSignedUrlBase(withDouyinWebid(url, baseRequestConfig.headers?.Cookie), st, ua), {
|
|
5677
|
+
uifid: secsdkUifid,
|
|
5678
|
+
method: baseRequestConfig.method
|
|
5679
|
+
});
|
|
5680
|
+
/** iesdouyin v2 游客端点专用:不签名、不带 cookie(带上只会多一层与会话的交叉校验) */
|
|
5681
|
+
const guestConfig = (url) => {
|
|
5682
|
+
const headers = { ...baseRequestConfig.headers };
|
|
5683
|
+
delete headers.Cookie;
|
|
5684
|
+
return {
|
|
5685
|
+
...baseRequestConfig,
|
|
5686
|
+
headers,
|
|
5687
|
+
url
|
|
5688
|
+
};
|
|
5689
|
+
};
|
|
5326
5690
|
switch (data.methodType) {
|
|
5327
5691
|
case "textWork":
|
|
5328
5692
|
case "parseWork":
|
|
5329
5693
|
case "videoWork":
|
|
5330
5694
|
case "imageAlbumWork":
|
|
5331
5695
|
case "slidesWork": {
|
|
5332
|
-
|
|
5696
|
+
/**
|
|
5697
|
+
* 走 www-hj 边缘 + 视频页自带的两个参数:www.douyin.com 上该接口实测 9/18 被 Argus 拦,
|
|
5698
|
+
* 换成 www-hj 后 18/18 通过。只是降低拦截率而非消除,重试仍需保留。
|
|
5699
|
+
*/
|
|
5700
|
+
const url = douyinApiUrls.getWorkDetail({ aweme_id: data.aweme_id }).replace("//www.douyin.com", "//www-hj.douyin.com") + "&request_source=600&origin_type=video_page";
|
|
5333
5701
|
return await GlobalGetData$3(data.methodType, {
|
|
5334
5702
|
...baseRequestConfig,
|
|
5335
5703
|
url: buildSignedUrl(url, signType, userAgent)
|
|
@@ -5641,6 +6009,24 @@ var DouyinData = async (data, cookie, requestConfig) => {
|
|
|
5641
6009
|
url: buildSignedUrl(url, signType, userAgent)
|
|
5642
6010
|
});
|
|
5643
6011
|
}
|
|
6012
|
+
case "guestUserInfo": return await GlobalGetData$3(data.methodType, guestConfig(douyinApiUrls.getGuestUserInfo({ unique_id: data.unique_id })));
|
|
6013
|
+
case "guestMusicInfo": return await GlobalGetData$3(data.methodType, guestConfig(douyinApiUrls.getGuestMusicInfo({ music_id: data.music_id })));
|
|
6014
|
+
case "guestMusicAwemeList": return await GlobalGetData$3(data.methodType, guestConfig(douyinApiUrls.getGuestMusicAwemeList({
|
|
6015
|
+
music_id: data.music_id,
|
|
6016
|
+
number: data.number,
|
|
6017
|
+
cursor: data.cursor
|
|
6018
|
+
})));
|
|
6019
|
+
case "emojiResourceMeta":
|
|
6020
|
+
/** App 的资源包接口:免鉴权,但要 Android UA,且不能带 douyin.com 那套默认头 */
|
|
6021
|
+
return await GlobalGetData$3(data.methodType, {
|
|
6022
|
+
...baseRequestConfig,
|
|
6023
|
+
headers: {
|
|
6024
|
+
Accept: "application/json, text/plain, */*",
|
|
6025
|
+
"User-Agent": "Mozilla/5.0 (Linux; Android 13; SM-S908E) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Mobile Safari/537.36"
|
|
6026
|
+
},
|
|
6027
|
+
timeout: 15e3,
|
|
6028
|
+
url: douyinApiUrls.getEmojiResourceMeta()
|
|
6029
|
+
});
|
|
5644
6030
|
case "liveRoomInfo": {
|
|
5645
6031
|
let url = douyinApiUrls.getLiveRoomInfo({
|
|
5646
6032
|
room_id: data.room_id,
|
|
@@ -5765,6 +6151,13 @@ var fetchPaginatedData = async (config) => {
|
|
|
5765
6151
|
let lastResponse = {};
|
|
5766
6152
|
let isFirstRequest = true;
|
|
5767
6153
|
const userAgent = requestConfig.headers?.["User-Agent"];
|
|
6154
|
+
/** 分页接口同样在 SDK 的 webSign 策略表里,此处在 `DouyinData` 之外,需要自己再套一层 */
|
|
6155
|
+
const cookie = requestConfig.headers?.Cookie;
|
|
6156
|
+
const secsdkUifid = extractUifidFromCookie(cookie);
|
|
6157
|
+
const buildSignedUrl = (url, st, ua) => douyinSign.SecSdk(buildSignedUrlBase(withDouyinWebid(url, cookie), st, ua), {
|
|
6158
|
+
uifid: secsdkUifid,
|
|
6159
|
+
method: requestConfig.method
|
|
6160
|
+
});
|
|
5768
6161
|
const targetNumber = Number(params.number ?? maxPageSize);
|
|
5769
6162
|
while (fetchedData.length < targetNumber) {
|
|
5770
6163
|
const remaining = targetNumber - fetchedData.length;
|
|
@@ -5798,27 +6191,63 @@ var fetchPaginatedData = async (config) => {
|
|
|
5798
6191
|
var GlobalGetData$3 = async (type, config) => {
|
|
5799
6192
|
let warningMessage = "";
|
|
5800
6193
|
try {
|
|
5801
|
-
const
|
|
5802
|
-
if (isNetworkErrorResult(
|
|
5803
|
-
const networkError = new Error(
|
|
6194
|
+
const response = await fetchResponse(config);
|
|
6195
|
+
if (isNetworkErrorResult(response)) {
|
|
6196
|
+
const networkError = new Error(response.error.amagiError.errorDescription);
|
|
5804
6197
|
Object.assign(networkError, {
|
|
5805
|
-
code:
|
|
6198
|
+
code: response.error.code,
|
|
5806
6199
|
data: null,
|
|
5807
6200
|
amagiError: {
|
|
5808
|
-
...
|
|
6201
|
+
...response.error.amagiError,
|
|
5809
6202
|
requestType: type
|
|
5810
6203
|
}
|
|
5811
6204
|
});
|
|
5812
6205
|
throw networkError;
|
|
5813
6206
|
}
|
|
6207
|
+
/** 抖音在每个响应头里回本次会话的 webid,顺手缓存,下次请求就能带上 */
|
|
6208
|
+
rememberDouyinWebid(config.headers, response.headers);
|
|
6209
|
+
const result = response.data;
|
|
6210
|
+
/**
|
|
6211
|
+
* 纯文本 body 说明被风控拦了,不是数据。
|
|
6212
|
+
*
|
|
6213
|
+
* 判据不能只看「是不是字符串」:综合搜索的 `general/search/stream/` 本来就回
|
|
6214
|
+
* 「十六进制长度行 + JSON」的分块流,由下游 `parseDouyinMultiJson` 解析。
|
|
6215
|
+
* 所以只有命中 Argus 关键字、或者整段里连一个 JSON 对象都没有时才算失败。
|
|
6216
|
+
*/
|
|
6217
|
+
if (typeof result === "string" && result.trim() !== "") {
|
|
6218
|
+
const snippet = result.trim().slice(0, 200);
|
|
6219
|
+
const isArgus = /ArgusSecurityPlugin|Blocked by/i.test(snippet);
|
|
6220
|
+
if (isArgus || !result.includes("{\"")) {
|
|
6221
|
+
const ErrA = {
|
|
6222
|
+
errorDescription: isArgus ? `抖音风控拦截(Argus):${snippet}` : `接口返回了非 JSON 内容:${snippet}`,
|
|
6223
|
+
requestType: type ?? "未知请求类型",
|
|
6224
|
+
requestUrl: config.url
|
|
6225
|
+
};
|
|
6226
|
+
warningMessage = `
|
|
6227
|
+
获取响应数据失败!原因:${ErrA.errorDescription}
|
|
6228
|
+
请求类型:「${type}」
|
|
6229
|
+
请求URL:${config.url}
|
|
6230
|
+
`;
|
|
6231
|
+
emitLogWarn(warningMessage);
|
|
6232
|
+
const argusError = new Error(ErrA.errorDescription);
|
|
6233
|
+
Object.assign(argusError, {
|
|
6234
|
+
code: isArgus ? "ARGUS_BLOCKED" : "NON_JSON_RESPONSE",
|
|
6235
|
+
data: null,
|
|
6236
|
+
amagiError: ErrA,
|
|
6237
|
+
argusBody: snippet
|
|
6238
|
+
});
|
|
6239
|
+
throw argusError;
|
|
6240
|
+
}
|
|
6241
|
+
}
|
|
5814
6242
|
if (!result || result === "") {
|
|
6243
|
+
const emptyReason = "接口返回内容为空,可能的原因:①请求的设备类参数与 cookie 会话不匹配(多为 webid,抖音会静默返回 0 字节);②目标数据本身不公开(例如对方隐藏了「喜欢」列表);③抖音ck已失效";
|
|
5815
6244
|
const Err = {
|
|
5816
|
-
errorDescription:
|
|
6245
|
+
errorDescription: `获取响应数据失败!${emptyReason}`,
|
|
5817
6246
|
requestType: type ?? "未知请求类型",
|
|
5818
6247
|
requestUrl: config.url
|
|
5819
6248
|
};
|
|
5820
6249
|
warningMessage = `
|
|
5821
|
-
|
|
6250
|
+
获取响应数据失败!原因:${emptyReason}
|
|
5822
6251
|
请求类型:「${type}」
|
|
5823
6252
|
请求URL:${config.url}
|
|
5824
6253
|
`;
|
|
@@ -5904,6 +6333,10 @@ var filterSearchResponses = (objs) => {
|
|
|
5904
6333
|
* 抖音 Fetcher 内部通用逻辑
|
|
5905
6334
|
* @module fetchers/douyin/internal
|
|
5906
6335
|
*/
|
|
6336
|
+
/** 被风控拦截时的最大重试次数 */
|
|
6337
|
+
var MAX_ARGUS_RETRIES = 5;
|
|
6338
|
+
/** 触发换参重试的错误码 */
|
|
6339
|
+
var RETRYABLE_CODES = ["ARGUS_BLOCKED", "NON_JSON_RESPONSE"];
|
|
5907
6340
|
/**
|
|
5908
6341
|
* 抖音 API 内部调用函数
|
|
5909
6342
|
* @internal
|
|
@@ -5915,18 +6348,39 @@ var filterSearchResponses = (objs) => {
|
|
|
5915
6348
|
async function fetchDouyinInternal(methodType, options, config) {
|
|
5916
6349
|
const startTime = Date.now();
|
|
5917
6350
|
try {
|
|
5918
|
-
const
|
|
6351
|
+
const apiParams = { ...validateDouyinParams(methodType, options) };
|
|
6352
|
+
/**
|
|
6353
|
+
* 被风控拦下就换一整套请求参数重来。
|
|
6354
|
+
*
|
|
6355
|
+
* `DouyinData` 每次调用都会重新生成 msToken / verifyFp / a_bogus,而 Argus 是按单次请求的
|
|
6356
|
+
* token 组判定、不锁账号,所以换一组即可通过 —— 失败之间互不关联。
|
|
6357
|
+
*/
|
|
6358
|
+
let rawData;
|
|
6359
|
+
let argusRetries = 0;
|
|
6360
|
+
for (;;) {
|
|
6361
|
+
rawData = await DouyinData(apiParams, config.cookie, config.requestConfig);
|
|
6362
|
+
if (!RETRYABLE_CODES.includes(rawData?.code) || argusRetries >= MAX_ARGUS_RETRIES) break;
|
|
6363
|
+
argusRetries++;
|
|
6364
|
+
emitLogWarn(`抖音风控拦截,换一组请求参数重试(${argusRetries}/${MAX_ARGUS_RETRIES}):` + String(rawData.argusBody ?? "").slice(0, 90));
|
|
6365
|
+
await new Promise((resolve) => setTimeout(resolve, 400 * argusRetries));
|
|
6366
|
+
}
|
|
5919
6367
|
const duration = Date.now() - startTime;
|
|
5920
6368
|
if (rawData.data === "" || rawData.status_code !== 0) {
|
|
6369
|
+
/** 带上真实业务码与文案:原先不传第三个参数,抖音的任何失败都恒为 500 */
|
|
6370
|
+
const retriedNote = argusRetries > 0 ? `(已重试 ${argusRetries} 次仍被拦)` : "";
|
|
6371
|
+
const argusDetail = rawData.amagiError?.errorDescription;
|
|
6372
|
+
const failMessage = rawData.status_msg ?? (argusDetail ? argusDetail + retriedNote : "抖音数据获取失败");
|
|
6373
|
+
/** 信封的 code 必须是数字,风控那类字符串码只放进事件的 errorCode */
|
|
6374
|
+
const failCode = typeof rawData.status_code === "number" ? rawData.status_code : 500;
|
|
5921
6375
|
emitApiError({
|
|
5922
6376
|
platform: "douyin",
|
|
5923
6377
|
methodType,
|
|
5924
|
-
errorCode: rawData.
|
|
5925
|
-
errorMessage:
|
|
5926
|
-
url:
|
|
6378
|
+
errorCode: rawData.code ?? failCode,
|
|
6379
|
+
errorMessage: failMessage,
|
|
6380
|
+
url: rawData.amagiError?.requestUrl,
|
|
5927
6381
|
duration
|
|
5928
6382
|
});
|
|
5929
|
-
return createErrorResponse(rawData.amagiError,
|
|
6383
|
+
return createErrorResponse(rawData.amagiError, failMessage, failCode, rawData);
|
|
5930
6384
|
}
|
|
5931
6385
|
const result = createSuccessResponse$1(rawData, "获取成功", 200);
|
|
5932
6386
|
emitApiSuccess({
|
|
@@ -5997,6 +6451,87 @@ async function fetchCommentReplies(options, cookie, requestConfig) {
|
|
|
5997
6451
|
});
|
|
5998
6452
|
}
|
|
5999
6453
|
//#endregion
|
|
6454
|
+
//#region ../amagi/packages/core/src/model/fetchers/douyin/guest.ts
|
|
6455
|
+
/**
|
|
6456
|
+
* 通过抖音号获取用户信息
|
|
6457
|
+
*
|
|
6458
|
+
* 唯一免签名的抖音号转 sec_uid 途径。`searchContent` 需要 a_bogus、会被风控按概率拦下,
|
|
6459
|
+
* 而且是模糊匹配。抖音号不存在时接口返回 `status_code: 5`,落在失败信封里。
|
|
6460
|
+
*
|
|
6461
|
+
* @param options - 请求选项
|
|
6462
|
+
* @param options.unique_id - 抖音号
|
|
6463
|
+
* @param cookie - 抖音 Cookie (该接口不需要,保留以与其他 fetcher 同形)
|
|
6464
|
+
* @param requestConfig - 请求配置 (可选)
|
|
6465
|
+
* @returns 用户信息
|
|
6466
|
+
* @example
|
|
6467
|
+
* ```typescript
|
|
6468
|
+
* const result = await fetchGuestUserInfo({ unique_id: 'ubb_up' })
|
|
6469
|
+
* console.log(result.data.user_info.sec_uid)
|
|
6470
|
+
* ```
|
|
6471
|
+
*/
|
|
6472
|
+
async function fetchGuestUserInfo(options, cookie, requestConfig) {
|
|
6473
|
+
return fetchDouyinInternal("guestUserInfo", options, {
|
|
6474
|
+
cookie,
|
|
6475
|
+
requestConfig
|
|
6476
|
+
});
|
|
6477
|
+
}
|
|
6478
|
+
/**
|
|
6479
|
+
* 获取原声本体 (免鉴权)
|
|
6480
|
+
*
|
|
6481
|
+
* 返回的 `music_info` 没有 `play_url`,mp3 只能从源作品上取 ——
|
|
6482
|
+
* `extra.extract_item_id` 就是创建这条原声的那个作品。
|
|
6483
|
+
*
|
|
6484
|
+
* @param options - 请求选项
|
|
6485
|
+
* @param options.music_id - 原声 ID (mid)
|
|
6486
|
+
* @param cookie - 抖音 Cookie (该接口不需要)
|
|
6487
|
+
* @param requestConfig - 请求配置 (可选)
|
|
6488
|
+
* @returns 原声信息
|
|
6489
|
+
*/
|
|
6490
|
+
async function fetchGuestMusicInfo(options, cookie, requestConfig) {
|
|
6491
|
+
return fetchDouyinInternal("guestMusicInfo", options, {
|
|
6492
|
+
cookie,
|
|
6493
|
+
requestConfig
|
|
6494
|
+
});
|
|
6495
|
+
}
|
|
6496
|
+
/**
|
|
6497
|
+
* 获取使用某条原声的作品列表 (免鉴权)
|
|
6498
|
+
*
|
|
6499
|
+
* 每条的 `music` 字段被抖音裁成空对象,所以这条接口只能用来拿 `aweme_id`。
|
|
6500
|
+
*
|
|
6501
|
+
* @param options - 请求选项
|
|
6502
|
+
* @param options.music_id - 原声 ID
|
|
6503
|
+
* @param options.number - 获取数量,默认 10
|
|
6504
|
+
* @param options.cursor - 游标,用于翻页
|
|
6505
|
+
* @param cookie - 抖音 Cookie (该接口不需要)
|
|
6506
|
+
* @param requestConfig - 请求配置 (可选)
|
|
6507
|
+
* @returns 作品列表
|
|
6508
|
+
*/
|
|
6509
|
+
async function fetchGuestMusicAwemeList(options, cookie, requestConfig) {
|
|
6510
|
+
return fetchDouyinInternal("guestMusicAwemeList", options, {
|
|
6511
|
+
cookie,
|
|
6512
|
+
requestConfig
|
|
6513
|
+
});
|
|
6514
|
+
}
|
|
6515
|
+
/**
|
|
6516
|
+
* 获取表情资源包元信息 (免鉴权)
|
|
6517
|
+
*
|
|
6518
|
+
* 返回的 `android_emoji_resource` 形如 `{ id, md5, resource_url, update_time }`,
|
|
6519
|
+
* `md5` 同时是版本号,抖音 App 自己就按它做增量判断。下载、校验、解包属于业务逻辑,
|
|
6520
|
+
* 这里只回答「是哪个包、去哪下」。
|
|
6521
|
+
*
|
|
6522
|
+
* @param options - 请求选项 (可选)
|
|
6523
|
+
* @param options.typeMode - 类型模式: 'strict' 返回严格类型, 'loose' 返回 any
|
|
6524
|
+
* @param cookie - 抖音 Cookie (该接口不需要)
|
|
6525
|
+
* @param requestConfig - 请求配置 (可选)
|
|
6526
|
+
* @returns 资源包元信息
|
|
6527
|
+
*/
|
|
6528
|
+
async function fetchEmojiResourceMeta(options, cookie, requestConfig) {
|
|
6529
|
+
return fetchDouyinInternal("emojiResourceMeta", {}, {
|
|
6530
|
+
cookie,
|
|
6531
|
+
requestConfig
|
|
6532
|
+
});
|
|
6533
|
+
}
|
|
6534
|
+
//#endregion
|
|
6000
6535
|
//#region ../amagi/packages/core/src/model/fetchers/douyin/misc.ts
|
|
6001
6536
|
/**
|
|
6002
6537
|
* 获取抖音音乐数据
|
|
@@ -6376,7 +6911,11 @@ function createBoundDouyinFetcher(cookie, requestConfig) {
|
|
|
6376
6911
|
fetchLiveRoomInfo: (options, override) => fetchLiveRoomInfo$1(options, ...resolveRequest(override)),
|
|
6377
6912
|
requestLoginQrcode: (options, override) => requestLoginQrcode(options, ...resolveRequest(override)),
|
|
6378
6913
|
fetchEmojiList: (options, override) => fetchEmojiList$2(options, ...resolveRequest(override)),
|
|
6379
|
-
fetchDynamicEmojiList: (options, override) => fetchDynamicEmojiList(options, ...resolveRequest(override))
|
|
6914
|
+
fetchDynamicEmojiList: (options, override) => fetchDynamicEmojiList(options, ...resolveRequest(override)),
|
|
6915
|
+
fetchGuestUserInfo: (options, override) => fetchGuestUserInfo(options, ...resolveRequest(override)),
|
|
6916
|
+
fetchGuestMusicInfo: (options, override) => fetchGuestMusicInfo(options, ...resolveRequest(override)),
|
|
6917
|
+
fetchGuestMusicAwemeList: (options, override) => fetchGuestMusicAwemeList(options, ...resolveRequest(override)),
|
|
6918
|
+
fetchEmojiResourceMeta: (options, override) => fetchEmojiResourceMeta(options, ...resolveRequest(override))
|
|
6380
6919
|
};
|
|
6381
6920
|
}
|
|
6382
6921
|
//#endregion
|
|
@@ -6418,7 +6957,11 @@ var douyinFetcher$1 = {
|
|
|
6418
6957
|
fetchLiveRoomInfo: fetchLiveRoomInfo$1,
|
|
6419
6958
|
requestLoginQrcode,
|
|
6420
6959
|
fetchEmojiList: fetchEmojiList$2,
|
|
6421
|
-
fetchDynamicEmojiList
|
|
6960
|
+
fetchDynamicEmojiList,
|
|
6961
|
+
fetchGuestUserInfo,
|
|
6962
|
+
fetchGuestMusicInfo,
|
|
6963
|
+
fetchGuestMusicAwemeList,
|
|
6964
|
+
fetchEmojiResourceMeta
|
|
6422
6965
|
};
|
|
6423
6966
|
//#endregion
|
|
6424
6967
|
//#region ../amagi/packages/core/src/platform/kuaishou/API.ts
|
|
@@ -14162,34 +14705,6 @@ var resolveUseDarkTheme = async (path, data) => {
|
|
|
14162
14705
|
return Common.useDarkTheme();
|
|
14163
14706
|
};
|
|
14164
14707
|
//#endregion
|
|
14165
|
-
//#region src/module/utils/Render/wm.ts
|
|
14166
|
-
/**
|
|
14167
|
-
* 使用 exiftool 写入图片详细信息
|
|
14168
|
-
*
|
|
14169
|
-
* @param pngBuffer - PNG 图片二进制数据
|
|
14170
|
-
* @returns 写入后的 PNG Buffer
|
|
14171
|
-
*/
|
|
14172
|
-
/**
|
|
14173
|
-
* 将隐水印嵌入到 PNG 图片中
|
|
14174
|
-
* @param pngBytes - PNG 图片的 Buffer 或 Uint8Array
|
|
14175
|
-
* @param watermarkText - 要嵌入的水印文本
|
|
14176
|
-
* @returns 嵌入水印后的 PNG Buffer,失败返回 null
|
|
14177
|
-
*/
|
|
14178
|
-
var embedWatermark = (pngBytes, watermarkText) => {
|
|
14179
|
-
try {
|
|
14180
|
-
const result = embedWatermarkToPngBytes(pngBytes instanceof Buffer ? pngBytes : Buffer.from(pngBytes), watermarkText);
|
|
14181
|
-
return result instanceof Buffer ? result : Buffer.from(result.buffer);
|
|
14182
|
-
} catch {
|
|
14183
|
-
return null;
|
|
14184
|
-
}
|
|
14185
|
-
};
|
|
14186
|
-
/**
|
|
14187
|
-
* 从 PNG 图片中提取隐水印文本
|
|
14188
|
-
*
|
|
14189
|
-
* @param pngBytes - PNG 图片的 Buffer 或 Uint8Array
|
|
14190
|
-
* @returns 提取出的水印文本,失败返回 null
|
|
14191
|
-
*/
|
|
14192
|
-
//#endregion
|
|
14193
14708
|
//#region src/module/utils/Render/index.ts
|
|
14194
14709
|
var isDevRuntime = !fileURLToPath(import.meta.url).replace(/\\/g, "/").includes("/lib/");
|
|
14195
14710
|
var renderTemplate = createTemplateRenderer(import.meta.url, {
|
|
@@ -14204,31 +14719,24 @@ var renderTemplate = createTemplateRenderer(import.meta.url, {
|
|
|
14204
14719
|
* 渲染函数
|
|
14205
14720
|
* 将指定路径的模板渲染为图片元素数组
|
|
14206
14721
|
*
|
|
14207
|
-
* @param event
|
|
14722
|
+
* @param event 消息事件对象,用于按协议端能力决定是否分页截图
|
|
14208
14723
|
* @template P 渲染路径,必须是有效的动态路径
|
|
14209
14724
|
* @param path 渲染路径,格式为 "平台/组件ID" 或 "平台/分类/组件ID"
|
|
14210
14725
|
* @param data 渲染数据,类型根据路径自动推断
|
|
14211
|
-
* @options 渲染选项
|
|
14212
14726
|
* @returns 渲染结果图片元素数组的 Promise
|
|
14213
14727
|
*/
|
|
14214
|
-
var Render = async (event, path, data
|
|
14728
|
+
var Render = async (event, path, data) => {
|
|
14215
14729
|
const templateType = path.split("/")[0];
|
|
14216
14730
|
let hasUpdate = false;
|
|
14217
|
-
|
|
14731
|
+
try {
|
|
14218
14732
|
const lockedVersion = await db.get("kkk:update:lock");
|
|
14219
14733
|
if (typeof lockedVersion === "string" && lockedVersion.length > 0) hasUpdate = isSemverGreater(lockedVersion, Root.pluginVersion);
|
|
14220
14734
|
} catch {}
|
|
14221
|
-
const watermarkText = JSON.stringify({
|
|
14222
|
-
a: Date.now(),
|
|
14223
|
-
b: `Generated by karin-plugin-kkk v${Root.pluginVersion}, with source code open-sourced under the GPL-3.0 license`,
|
|
14224
|
-
c: `Sent by ${event?.bot?.account.selfId ?? "unknown"}|${event?.bot?.account.name ?? "unknown"}`
|
|
14225
|
-
});
|
|
14226
|
-
const watermarkTextBitSize = Buffer.byteLength(watermarkText, "utf8") * 8;
|
|
14227
14735
|
const useDarkTheme = await resolveUseDarkTheme(path, data);
|
|
14228
14736
|
const result = await renderTemplate(path, data, {
|
|
14229
14737
|
scale: Math.min(2, Math.max(.5, Number(Config.app.renderScale) / 100)),
|
|
14230
14738
|
theme: { mode: useDarkTheme ? "dark" : "light" },
|
|
14231
|
-
version:
|
|
14739
|
+
version: {
|
|
14232
14740
|
plugin: "karin-plugin",
|
|
14233
14741
|
pluginName: "kkk",
|
|
14234
14742
|
pluginVersion: Root.pluginVersion,
|
|
@@ -14237,7 +14745,6 @@ var Render = async (event, path, data, options) => {
|
|
|
14237
14745
|
frameworkVersion: Root.karinVersion,
|
|
14238
14746
|
hasUpdate
|
|
14239
14747
|
},
|
|
14240
|
-
watermarkTextBitSize,
|
|
14241
14748
|
ambientCover: {
|
|
14242
14749
|
coverOpacity: Config.app.ambientCover?.coverOpacity,
|
|
14243
14750
|
overlayEdgeOpacity: Config.app.ambientCover?.overlayEdgeOpacity,
|
|
@@ -14268,18 +14775,15 @@ var Render = async (event, path, data, options) => {
|
|
|
14268
14775
|
const imageStats = [];
|
|
14269
14776
|
for (const image of images) {
|
|
14270
14777
|
const imageBuffer = Buffer.from(image, "base64");
|
|
14271
|
-
|
|
14272
|
-
if (options?.skipWatermark) finalImageBuffer = imageBuffer;
|
|
14273
|
-
else finalImageBuffer = embedWatermark(imageBuffer, watermarkText) ?? imageBuffer;
|
|
14274
|
-
const metadata = getImageMetadata(finalImageBuffer);
|
|
14778
|
+
const metadata = getImageMetadata(imageBuffer);
|
|
14275
14779
|
const dimensions = metadata.width && metadata.height ? `${metadata.width}x${metadata.height}` : "unknown";
|
|
14276
14780
|
imageStats.push({
|
|
14277
14781
|
index: ret.length,
|
|
14278
14782
|
type: "png",
|
|
14279
|
-
sizeMb: (
|
|
14783
|
+
sizeMb: (imageBuffer.length / (1024 * 1024)).toFixed(3),
|
|
14280
14784
|
dimensions
|
|
14281
14785
|
});
|
|
14282
|
-
ret.push(segment.image("base64://" +
|
|
14786
|
+
ret.push(segment.image("base64://" + image));
|
|
14283
14787
|
}
|
|
14284
14788
|
const statsText = imageStats.map((stat, i) => ` [${i + 1}] 类型: ${stat.type}, 大小: ${stat.sizeMb}MB, 尺寸: ${stat.dimensions}`).join("\n");
|
|
14285
14789
|
logger.debug(`[Render] 图片处理完成,准备入队发送:\n共 ${imageStats.length} 张图片:\n${statsText}`);
|
|
@@ -14306,24 +14810,6 @@ var getImageMetadata = (buffer) => {
|
|
|
14306
14810
|
};
|
|
14307
14811
|
return {};
|
|
14308
14812
|
};
|
|
14309
|
-
/**
|
|
14310
|
-
* 对已渲染的图片元素数组应用水印
|
|
14311
|
-
* 用于推送场景:渲染一次,按目标群逐个嵌入不同 bot 的水印
|
|
14312
|
-
*/
|
|
14313
|
-
var applyWatermarkToImages = (images, event) => {
|
|
14314
|
-
if (Config.app.RemoveWatermark) return images;
|
|
14315
|
-
const watermarkText = JSON.stringify({
|
|
14316
|
-
a: Date.now(),
|
|
14317
|
-
b: `Generated by karin-plugin-kkk v${Root.pluginVersion}, with source code open-sourced under the GPL-3.0 license`,
|
|
14318
|
-
c: `Sent by ${event?.bot?.account.selfId ?? "unknown"}|${event?.bot?.account.name ?? "unknown"}`
|
|
14319
|
-
});
|
|
14320
|
-
return images.map((img) => {
|
|
14321
|
-
const base64Data = img.file.replace(/^base64:\/\//, "");
|
|
14322
|
-
const buffer = Buffer.from(base64Data, "base64");
|
|
14323
|
-
const watermarked = embedWatermark(buffer, watermarkText) ?? buffer;
|
|
14324
|
-
return segment.image("base64://" + watermarked.toString("base64"));
|
|
14325
|
-
});
|
|
14326
|
-
};
|
|
14327
14813
|
//#endregion
|
|
14328
14814
|
//#region src/module/db/bilibili.ts
|
|
14329
14815
|
/** 数据库操作类 */
|
|
@@ -16864,6 +17350,12 @@ var createWebLinkNode = (text, jumpUrl) => ({
|
|
|
16864
17350
|
text,
|
|
16865
17351
|
jumpUrl
|
|
16866
17352
|
});
|
|
17353
|
+
/** 创建站内图文链接节点。 */
|
|
17354
|
+
var createOpusLinkNode = (text, url) => ({
|
|
17355
|
+
type: "opusLink",
|
|
17356
|
+
text,
|
|
17357
|
+
url
|
|
17358
|
+
});
|
|
16867
17359
|
/** 创建投票节点。 */
|
|
16868
17360
|
var createVoteNode = (text) => ({
|
|
16869
17361
|
type: "vote",
|
|
@@ -16969,6 +17461,7 @@ var extractRichTextPlainText = (document) => {
|
|
|
16969
17461
|
case "at":
|
|
16970
17462
|
case "lottery":
|
|
16971
17463
|
case "webLink":
|
|
17464
|
+
case "opusLink":
|
|
16972
17465
|
case "vote":
|
|
16973
17466
|
case "viewPicture":
|
|
16974
17467
|
case "hashtag":
|
|
@@ -17125,6 +17618,34 @@ var WebLinkIcon = ({ className }) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs
|
|
|
17125
17618
|
fill: "currentColor"
|
|
17126
17619
|
})]
|
|
17127
17620
|
});
|
|
17621
|
+
/** 站内图文链接图标 */
|
|
17622
|
+
var OpusLinkIcon = ({ className }) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", {
|
|
17623
|
+
className: cn$1(className, "inline-block w-16 h-auto mb-2 mr-2 align-middle"),
|
|
17624
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
17625
|
+
viewBox: "0 0 20 20",
|
|
17626
|
+
children: [
|
|
17627
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", {
|
|
17628
|
+
d: "M10 3.124591666666667C8.164908333333335 3.124591666666667 6.791808333333333 3.15845 5.811916666666667 3.1982166666666667C5.002533333333334 3.231058333333334 4.375 3.8969416666666667 4.375 4.743841666666667L4.375 15.12575C4.375 16.01275 5.067016666666667 16.737333333333336 5.943891666666667 16.773000000000003C7.195191666666667 16.823833333333337 8.756833333333335 16.874583333333334 10 16.874583333333334C11.243166666666667 16.874583333333334 12.804833333333335 16.823833333333337 14.056125 16.773000000000003C14.932958333333335 16.737333333333336 15.625 16.01275 15.625 15.12575L15.625 4.743841666666667C15.625 3.8969416666666667 14.997458333333332 3.231058333333334 14.188083333333335 3.1982166666666667C13.208208333333333 3.15845 11.835125 3.124591666666667 10 3.124591666666667zM5.7612250000000005 1.9492416666666668C6.759566666666667 1.908725 8.149641666666668 1.8745916666666667 10 1.8745916666666667C11.850375 1.8745916666666667 13.240458333333335 1.908725 14.238791666666666 1.9492416666666668C15.754791666666668 2.010766666666667 16.875 3.263466666666667 16.875 4.743841666666667L16.875 15.12575C16.875 16.673958333333335 15.663166666666667 17.958708333333334 14.106874999999999 18.021958333333334C12.851916666666668 18.072958333333336 11.269541666666667 18.124583333333334 10 18.124583333333334C8.730458333333333 18.124583333333334 7.148116666666668 18.072958333333336 5.893133333333334 18.021958333333334C4.336833333333334 17.958708333333334 3.125 16.673958333333335 3.125 15.12575L3.125 4.743841666666667C3.125 3.263475 4.245233333333334 2.010766666666667 5.7612250000000005 1.9492416666666668z",
|
|
17629
|
+
fill: "currentColor"
|
|
17630
|
+
}),
|
|
17631
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", {
|
|
17632
|
+
d: "M6.385416666666667 6.2491666666666665C6.385416666666667 5.921250000000001 6.65125 5.655416666666667 6.979166666666667 5.655416666666667L9.479166666666668 5.655416666666667C9.807083333333333 5.655416666666667 10.072916666666668 5.921250000000001 10.072916666666668 6.2491666666666665C10.072916666666668 6.577091666666667 9.807083333333333 6.842908333333333 9.479166666666668 6.842908333333333L6.979166666666667 6.842908333333333C6.65125 6.842908333333333 6.385416666666667 6.577091666666667 6.385416666666667 6.2491666666666665z",
|
|
17633
|
+
fill: "currentColor"
|
|
17634
|
+
}),
|
|
17635
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", {
|
|
17636
|
+
d: "M6.28125 8.749166666666667C6.28125 8.421258333333334 6.547083333333333 8.155408333333334 6.875 8.155408333333334L13.125 8.155408333333334C13.452916666666667 8.155408333333334 13.71875 8.421258333333334 13.71875 8.749166666666667C13.71875 9.077083333333334 13.452916666666667 9.342916666666666 13.125 9.342916666666666L6.875 9.342916666666666C6.547083333333333 9.342916666666666 6.28125 9.077083333333334 6.28125 8.749166666666667z",
|
|
17637
|
+
fill: "currentColor"
|
|
17638
|
+
}),
|
|
17639
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", {
|
|
17640
|
+
d: "M6.28125 11.249166666666667C6.28125 10.92125 6.547083333333333 10.655416666666667 6.875 10.655416666666667L13.125 10.655416666666667C13.452916666666667 10.655416666666667 13.71875 10.92125 13.71875 11.249166666666667C13.71875 11.577083333333334 13.452916666666667 11.842916666666666 13.125 11.842916666666666L6.875 11.842916666666666C6.547083333333333 11.842916666666666 6.28125 11.577083333333334 6.28125 11.249166666666667z",
|
|
17641
|
+
fill: "currentColor"
|
|
17642
|
+
}),
|
|
17643
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", {
|
|
17644
|
+
d: "M6.28125 13.749166666666666C6.28125 13.42125 6.547083333333333 13.155416666666667 6.875 13.155416666666667L11.458333333333334 13.155416666666667C11.78625 13.155416666666667 12.052083333333334 13.42125 12.052083333333334 13.749166666666666C12.052083333333334 14.077083333333333 11.78625 14.342916666666667 11.458333333333334 14.342916666666667L6.875 14.342916666666667C6.547083333333333 14.342916666666667 6.28125 14.077083333333333 6.28125 13.749166666666666z",
|
|
17645
|
+
fill: "currentColor"
|
|
17646
|
+
})
|
|
17647
|
+
]
|
|
17648
|
+
});
|
|
17128
17649
|
/** 投票图标 */
|
|
17129
17650
|
var VoteIcon = ({ className }) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", {
|
|
17130
17651
|
className: cn$1(className, "inline-block w-20 mb-2 align-middle"),
|
|
@@ -17300,6 +17821,19 @@ var renderNodeToReact = (node, index, options) => {
|
|
|
17300
17821
|
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(WebLinkIcon, {})
|
|
17301
17822
|
}), node.text]
|
|
17302
17823
|
}, `weblink-${index}`);
|
|
17824
|
+
case "opusLink": return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
|
|
17825
|
+
className: options.opusLink?.className,
|
|
17826
|
+
"data-richtext-node": "opusLink",
|
|
17827
|
+
"data-url": node.url,
|
|
17828
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
17829
|
+
style: options.iconScale && options.iconScale !== 1 ? {
|
|
17830
|
+
transform: `scale(${options.iconScale})`,
|
|
17831
|
+
transformOrigin: "left center",
|
|
17832
|
+
display: "inline-block"
|
|
17833
|
+
} : void 0,
|
|
17834
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(OpusLinkIcon, {})
|
|
17835
|
+
}), node.text]
|
|
17836
|
+
}, `opuslink-${index}`);
|
|
17303
17837
|
case "vote": return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
|
|
17304
17838
|
className: options.vote?.className,
|
|
17305
17839
|
"data-richtext-node": "vote",
|
|
@@ -18463,7 +18997,7 @@ var ViteLogo = ({ className = "w-auto h-12" }) => /* @__PURE__ */ (0, import_jsx
|
|
|
18463
18997
|
*/
|
|
18464
18998
|
var DefaultLayout = ({ children, ctx, className = "", style = {} }) => {
|
|
18465
18999
|
const useDarkTheme = isDark(ctx);
|
|
18466
|
-
const { version
|
|
19000
|
+
const { version } = ctx;
|
|
18467
19001
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
18468
19002
|
className: cn("relative w-360 shrink-0 overflow-hidden rounded-[5rem] bg-background bg-clip-padding text-foreground font-[HarmonyOSHans-Regular]", className),
|
|
18469
19003
|
style: {
|
|
@@ -18472,9 +19006,9 @@ var DefaultLayout = ({ children, ctx, className = "", style = {} }) => {
|
|
|
18472
19006
|
maxWidth: "1440px",
|
|
18473
19007
|
...style
|
|
18474
19008
|
},
|
|
18475
|
-
children: [children, version ? /* @__PURE__ */ (0, import_jsx_runtime.
|
|
19009
|
+
children: [children, version ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
18476
19010
|
className: "relative z-50 pt-32 pb-20 text-foreground/80",
|
|
18477
|
-
children:
|
|
19011
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
18478
19012
|
className: "flex relative justify-center items-center space-x-8",
|
|
18479
19013
|
children: [
|
|
18480
19014
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
@@ -18594,20 +19128,8 @@ var DefaultLayout = ({ children, ctx, className = "", style = {} }) => {
|
|
|
18594
19128
|
})]
|
|
18595
19129
|
})] })
|
|
18596
19130
|
]
|
|
18597
|
-
}), typeof watermarkTextBitSize === "number" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
18598
|
-
className: "flex justify-center",
|
|
18599
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
|
|
18600
|
-
className: "text-xs font-mono text-foreground/30",
|
|
18601
|
-
children: ["Restore ID: ", watermarkTextBitSize]
|
|
18602
|
-
})
|
|
18603
|
-
})]
|
|
18604
|
-
}) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
18605
|
-
className: "flex items-center justify-center h-24",
|
|
18606
|
-
children: typeof watermarkTextBitSize === "number" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
|
|
18607
|
-
className: "text-xs font-mono text-foreground/30",
|
|
18608
|
-
children: ["Restore ID: ", watermarkTextBitSize]
|
|
18609
19131
|
})
|
|
18610
|
-
})]
|
|
19132
|
+
}) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "h-24" })]
|
|
18611
19133
|
});
|
|
18612
19134
|
};
|
|
18613
19135
|
//#endregion
|
|
@@ -20003,6 +20525,50 @@ var comment_default$3 = defineTemplate({
|
|
|
20003
20525
|
validate: (data) => typeof data === "object" && data !== null && typeof data.share_url === "string"
|
|
20004
20526
|
});
|
|
20005
20527
|
//#endregion
|
|
20528
|
+
//#region ktr/utils/avatarProxy.ts
|
|
20529
|
+
/**
|
|
20530
|
+
* ktr 开发面板预览用的头像代理约定。
|
|
20531
|
+
*
|
|
20532
|
+
* 头像 CDN(尤其 `q*.qlogo.cn`)不返回 `Access-Control-Allow-Origin`,
|
|
20533
|
+
* 浏览器里直接 fetch 会被同源策略拦掉,面板预览的二维码就嵌不上头像。
|
|
20534
|
+
* 生产渲染走 Node SSR(服务端 fetch,无同源策略),不受影响也不需要这个代理。
|
|
20535
|
+
*
|
|
20536
|
+
* 代理端点由 `karin.template.ts` 的 avatarProxyPlugin 挂在 ktr dev 服务器上,仅 serve 态存在。
|
|
20537
|
+
*/
|
|
20538
|
+
/** 代理端点路径,dev 服务器与模板侧共用这一处定义。 */
|
|
20539
|
+
var AVATAR_PROXY_PATH = "/__kkk/avatar-proxy";
|
|
20540
|
+
/**
|
|
20541
|
+
* 允许转发的头像 CDN 域名。
|
|
20542
|
+
*
|
|
20543
|
+
* 白名单是为了不让 dev 服务器变成任意地址的开放转发;
|
|
20544
|
+
* 其它适配器的头像域名如果也缺 CORS 头,在这里补一条即可(不在名单内的走直连)。
|
|
20545
|
+
*/
|
|
20546
|
+
var ALLOWED_AVATAR_HOSTS = [
|
|
20547
|
+
/(?:^|\.)qlogo\.cn$/i,
|
|
20548
|
+
/(?:^|\.)qpic\.cn$/i,
|
|
20549
|
+
/(?:^|\.)hdslb\.com$/i
|
|
20550
|
+
];
|
|
20551
|
+
/**
|
|
20552
|
+
* 判断某个头像 URL 是否允许经代理转发
|
|
20553
|
+
* @param url - 头像地址
|
|
20554
|
+
* @returns 命中白名单且是 http(s) 时为 true
|
|
20555
|
+
*/
|
|
20556
|
+
var isProxyableAvatarUrl = (url) => {
|
|
20557
|
+
try {
|
|
20558
|
+
const parsed = new URL(url);
|
|
20559
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return false;
|
|
20560
|
+
return ALLOWED_AVATAR_HOSTS.some((pattern) => pattern.test(parsed.hostname));
|
|
20561
|
+
} catch {
|
|
20562
|
+
return false;
|
|
20563
|
+
}
|
|
20564
|
+
};
|
|
20565
|
+
/**
|
|
20566
|
+
* 把头像地址改写成同源代理地址
|
|
20567
|
+
* @param url - 原始头像地址
|
|
20568
|
+
* @returns 代理端点地址
|
|
20569
|
+
*/
|
|
20570
|
+
var toProxiedAvatarUrl = (url) => `${AVATAR_PROXY_PATH}?url=${encodeURIComponent(url)}`;
|
|
20571
|
+
//#endregion
|
|
20006
20572
|
//#region ktr/utils/QRCodeAvatar.ts
|
|
20007
20573
|
var MAX_QRCODE_AVATAR_BYTES = 2 * 1024 * 1024;
|
|
20008
20574
|
var QRCODE_AVATAR_TIMEOUT_MS = 5e3;
|
|
@@ -20019,7 +20585,7 @@ var isSupportedImageBytes = (image) => {
|
|
|
20019
20585
|
const isWebp = image.byteLength >= 12 && image[0] === 82 && image[1] === 73 && image[2] === 70 && image[3] === 70 && image[8] === 87 && image[9] === 69 && image[10] === 66 && image[11] === 80;
|
|
20020
20586
|
return isPng || isJpeg || isWebp;
|
|
20021
20587
|
};
|
|
20022
|
-
var
|
|
20588
|
+
var requestAvatarBytes = async (url) => {
|
|
20023
20589
|
try {
|
|
20024
20590
|
const response = await fetch(url, {
|
|
20025
20591
|
headers: { accept: "image/png,image/jpeg,image/webp,image/*;q=0.8,*/*;q=0.5" },
|
|
@@ -20037,6 +20603,23 @@ var fetchQRCodeAvatar = async (url) => {
|
|
|
20037
20603
|
}
|
|
20038
20604
|
};
|
|
20039
20605
|
/**
|
|
20606
|
+
* 依次尝试的取图地址。
|
|
20607
|
+
*
|
|
20608
|
+
* 浏览器(只有 ktr 开发面板会走到)里头像 CDN 多半缺 CORS 头,直连必被同源策略拦,
|
|
20609
|
+
* 所以优先走 dev 服务器的同源代理,代理不在(如面板未启动)时再退回直连。
|
|
20610
|
+
* Node SSR 侧没有同源策略,始终直连。
|
|
20611
|
+
*/
|
|
20612
|
+
var avatarCandidates = (url) => {
|
|
20613
|
+
if (typeof window === "undefined" || !isProxyableAvatarUrl(url)) return [url];
|
|
20614
|
+
return [toProxiedAvatarUrl(url), url];
|
|
20615
|
+
};
|
|
20616
|
+
var fetchQRCodeAvatar = async (url) => {
|
|
20617
|
+
for (const candidate of avatarCandidates(url)) {
|
|
20618
|
+
const image = await requestAvatarBytes(candidate);
|
|
20619
|
+
if (image) return image;
|
|
20620
|
+
}
|
|
20621
|
+
};
|
|
20622
|
+
/**
|
|
20040
20623
|
* 加载并缓存二维码头像。同一头像的并发渲染会复用请求,失败结果不会进入缓存。
|
|
20041
20624
|
*/
|
|
20042
20625
|
var loadQRCodeAvatar = (url) => {
|
|
@@ -20162,6 +20745,7 @@ var BilibiliArticleContent = import_react.memo((props) => {
|
|
|
20162
20745
|
at: { className: "text-[#006A9E] dark:text-[#58B0D5]" },
|
|
20163
20746
|
topic: { className: "text-[#006A9E] dark:text-[#58B0D5]" },
|
|
20164
20747
|
webLink: { className: "text-[#006A9E] dark:text-[#58B0D5] underline" },
|
|
20748
|
+
opusLink: { className: "text-[#006A9E] dark:text-[#58B0D5]" },
|
|
20165
20749
|
mention: { className: "text-[#006A9E] dark:text-[#58B0D5]" },
|
|
20166
20750
|
lottery: { className: "text-[#006A9E] dark:text-[#58B0D5]" },
|
|
20167
20751
|
vote: { className: "text-[#006A9E] dark:text-[#58B0D5]" },
|
|
@@ -20386,10 +20970,7 @@ var BilibiliArticleFooter = import_react.memo((props) => {
|
|
|
20386
20970
|
*/
|
|
20387
20971
|
var BilibiliArticleDynamic = import_react.memo((props) => {
|
|
20388
20972
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DefaultLayout, {
|
|
20389
|
-
ctx:
|
|
20390
|
-
...props.ctx,
|
|
20391
|
-
watermarkTextBitSize: void 0
|
|
20392
|
-
},
|
|
20973
|
+
ctx: props.ctx,
|
|
20393
20974
|
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
20394
20975
|
className: "p-4",
|
|
20395
20976
|
children: [
|
|
@@ -22039,7 +22620,6 @@ var DYNAMIC_TYPE_WORD_default = defineTemplate({
|
|
|
22039
22620
|
*/
|
|
22040
22621
|
var BilibiliQrcodeImg = import_react.memo((props) => {
|
|
22041
22622
|
const dark = isDark(props.ctx);
|
|
22042
|
-
const qrCodeDataUrl = generateQRCode(props.data.share_url, dark);
|
|
22043
22623
|
const theme = {
|
|
22044
22624
|
bg: dark ? "#000000" : "#FFFFFF",
|
|
22045
22625
|
text: dark ? "#FFFFFF" : "#000000",
|
|
@@ -22184,8 +22764,10 @@ var BilibiliQrcodeImg = import_react.memo((props) => {
|
|
|
22184
22764
|
width: "800px",
|
|
22185
22765
|
height: "800px"
|
|
22186
22766
|
},
|
|
22187
|
-
children:
|
|
22188
|
-
|
|
22767
|
+
children: props.data.share_url ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(QRCodeWithAvatar, {
|
|
22768
|
+
value: props.data.share_url,
|
|
22769
|
+
avatarUrl: props.data.avatarUrl,
|
|
22770
|
+
useDarkTheme: dark,
|
|
22189
22771
|
alt: "QR Code",
|
|
22190
22772
|
className: "w-full h-full object-contain"
|
|
22191
22773
|
}) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
@@ -25659,7 +26241,6 @@ var musicinfo_default = defineTemplate({
|
|
|
25659
26241
|
*/
|
|
25660
26242
|
var DouyinQrcodeImg = import_react.memo((props) => {
|
|
25661
26243
|
const dark = isDark(props.ctx);
|
|
25662
|
-
const qrCodeImage = generateQRCode(props.data.share_url || "", dark);
|
|
25663
26244
|
const theme = {
|
|
25664
26245
|
bg: dark ? "#000000" : "#FFFFFF",
|
|
25665
26246
|
text: dark ? "#FFFFFF" : "#000000",
|
|
@@ -25797,8 +26378,10 @@ var DouyinQrcodeImg = import_react.memo((props) => {
|
|
|
25797
26378
|
width: "800px",
|
|
25798
26379
|
height: "800px"
|
|
25799
26380
|
},
|
|
25800
|
-
children:
|
|
25801
|
-
|
|
26381
|
+
children: props.data.share_url ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(QRCodeWithAvatar, {
|
|
26382
|
+
value: props.data.share_url,
|
|
26383
|
+
avatarUrl: props.data.avatarUrl,
|
|
26384
|
+
useDarkTheme: dark,
|
|
25802
26385
|
alt: "QR Code",
|
|
25803
26386
|
className: "w-full h-full object-contain"
|
|
25804
26387
|
}) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
@@ -29950,7 +30533,6 @@ var live_photo_tip_default = defineTemplate({
|
|
|
29950
30533
|
* @returns JSX元素
|
|
29951
30534
|
*/
|
|
29952
30535
|
var QrLogin = import_react.memo((props) => {
|
|
29953
|
-
const qrCodeDataUrl = props.data.qr_url ? generateQRCode(props.data.qr_url, isDark(props.ctx)) : "";
|
|
29954
30536
|
const dark = isDark(props.ctx);
|
|
29955
30537
|
const bgColor = dark ? "#0f0f1a" : "#f8f6ff";
|
|
29956
30538
|
const secondaryColor = dark ? "#a78bfa" : "#8b5cf6";
|
|
@@ -30171,10 +30753,12 @@ var QrLogin = import_react.memo((props) => {
|
|
|
30171
30753
|
}),
|
|
30172
30754
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
30173
30755
|
className: "flex-1 flex flex-col justify-center items-center py-12",
|
|
30174
|
-
children: [
|
|
30756
|
+
children: [props.data.share_url ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
30175
30757
|
className: "flex justify-center items-center w-200 h-200",
|
|
30176
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
30177
|
-
|
|
30758
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(QRCodeWithAvatar, {
|
|
30759
|
+
value: props.data.share_url,
|
|
30760
|
+
avatarUrl: props.data.avatarUrl,
|
|
30761
|
+
useDarkTheme: dark,
|
|
30178
30762
|
alt: "登录二维码",
|
|
30179
30763
|
className: "object-contain w-full h-full"
|
|
30180
30764
|
})
|
|
@@ -33375,6 +33959,25 @@ var resolveUsableBot = async (selfId) => {
|
|
|
33375
33959
|
if (!fallbackBotId) return void 0;
|
|
33376
33960
|
return karin.getBot(fallbackBotId);
|
|
33377
33961
|
};
|
|
33962
|
+
/**
|
|
33963
|
+
* 取触发者头像 URL,用于嵌到登录二维码中心当 logo。
|
|
33964
|
+
*
|
|
33965
|
+
* 适配器(如 console)可能不实现 getAvatarUrl 或返回空串,模板侧 avatarUrl 缺省即退化为普通二维码,
|
|
33966
|
+
* 所以这里任何失败都只记 debug 日志并返回 undefined,不影响登录流程。
|
|
33967
|
+
*
|
|
33968
|
+
* @param e - 触发登录的消息事件
|
|
33969
|
+
* @returns 头像 URL,取不到时为 undefined
|
|
33970
|
+
*/
|
|
33971
|
+
var resolveTriggerAvatarUrl = async (e) => {
|
|
33972
|
+
const userId = e.sender?.userId || e.userId;
|
|
33973
|
+
if (!userId) return void 0;
|
|
33974
|
+
try {
|
|
33975
|
+
return await e.bot.getAvatarUrl(userId) || void 0;
|
|
33976
|
+
} catch (error) {
|
|
33977
|
+
logger.debug("[karin-plugin-kkk] 获取触发者头像失败,二维码将不嵌入头像:", error);
|
|
33978
|
+
return;
|
|
33979
|
+
}
|
|
33980
|
+
};
|
|
33378
33981
|
//#endregion
|
|
33379
33982
|
//#region src/module/utils/ErrorHandler/utils.ts
|
|
33380
33983
|
/**
|
|
@@ -34289,6 +34892,16 @@ var extractWordStyle = (style = {}) => {
|
|
|
34289
34892
|
var parseOpusTextNodes = (nodes, useDarkTheme) => {
|
|
34290
34893
|
const result = [];
|
|
34291
34894
|
for (const node of nodes) {
|
|
34895
|
+
if (node.node_type === 4) {
|
|
34896
|
+
const link = node.link;
|
|
34897
|
+
if (!link?.show_text) {
|
|
34898
|
+
logger.error(`[bilibili] opus 富文本遇到 node_type=4 但缺少 link 数据: ${JSON.stringify(node).slice(0, 500)}`);
|
|
34899
|
+
continue;
|
|
34900
|
+
}
|
|
34901
|
+
if (link.link) result.push(createOpusLinkNode(link.show_text, link.link));
|
|
34902
|
+
else result.push(createTextNode(link.show_text));
|
|
34903
|
+
continue;
|
|
34904
|
+
}
|
|
34292
34905
|
if (node.node_type !== 1 || !node.word) {
|
|
34293
34906
|
if (node.node_type !== void 0 && node.node_type !== 1) logger.error(`[bilibili] opus 富文本遇到未适配的 node_type: ${node.node_type},节点数据: ${JSON.stringify(node).slice(0, 500)}`);
|
|
34294
34907
|
continue;
|
|
@@ -34599,6 +35212,7 @@ var inlineRichTextNodeToText = (node) => {
|
|
|
34599
35212
|
case "hashtag": return node.text;
|
|
34600
35213
|
case "emoji": return node.name;
|
|
34601
35214
|
case "webLink": return formatRichTextLink(node.text, node.jumpUrl);
|
|
35215
|
+
case "opusLink": return formatRichTextLink(node.text, node.url);
|
|
34602
35216
|
case "lineBreak": return "\n";
|
|
34603
35217
|
default: return "";
|
|
34604
35218
|
}
|
|
@@ -36410,7 +37024,10 @@ var getBilibiliID = async (url) => {
|
|
|
36410
37024
|
var bilibiliLogin = async (e) => {
|
|
36411
37025
|
/** 申请二维码 */
|
|
36412
37026
|
const qrcodeurl = await bilibiliFetcher.requestLoginQrcode({ typeMode: "strict" });
|
|
36413
|
-
const qrimg = await Render(e, "bilibili/qrcodeImg", {
|
|
37027
|
+
const qrimg = await Render(e, "bilibili/qrcodeImg", {
|
|
37028
|
+
share_url: qrcodeurl.data.data.url,
|
|
37029
|
+
avatarUrl: await resolveTriggerAvatarUrl(e)
|
|
37030
|
+
});
|
|
36414
37031
|
const base64Data = qrimg[0]?.file;
|
|
36415
37032
|
if (!base64Data) throw new Error("生成二维码图片失败");
|
|
36416
37033
|
const cleanBase64 = base64Data.replace(/^base64:\/\//, "");
|
|
@@ -36779,7 +37396,7 @@ var Bilibilipush = class extends Base {
|
|
|
36779
37396
|
additional: parseAdditionalCard(data[dynamicId].Dynamic_Data.modules.module_dynamic.additional),
|
|
36780
37397
|
dynamicTYPE: "图文动态推送",
|
|
36781
37398
|
dynamic_id: dynamicId
|
|
36782
|
-
}
|
|
37399
|
+
});
|
|
36783
37400
|
break;
|
|
36784
37401
|
/** 处理纯文动态 */
|
|
36785
37402
|
case DynamicType.WORD: {
|
|
@@ -36816,7 +37433,7 @@ var Bilibilipush = class extends Base {
|
|
|
36816
37433
|
additional: parseAdditionalCard(data[dynamicId].Dynamic_Data.modules.module_dynamic.additional),
|
|
36817
37434
|
dynamicTYPE: "纯文动态推送",
|
|
36818
37435
|
dynamic_id: dynamicId
|
|
36819
|
-
}
|
|
37436
|
+
});
|
|
36820
37437
|
break;
|
|
36821
37438
|
}
|
|
36822
37439
|
/** 处理视频动态 */
|
|
@@ -36889,7 +37506,7 @@ var Bilibilipush = class extends Base {
|
|
|
36889
37506
|
dynamicTYPE: "视频动态推送",
|
|
36890
37507
|
dynamic_id: dynamicId,
|
|
36891
37508
|
staff
|
|
36892
|
-
}
|
|
37509
|
+
});
|
|
36893
37510
|
}
|
|
36894
37511
|
break;
|
|
36895
37512
|
/** 处理直播动态 */
|
|
@@ -36907,7 +37524,7 @@ var Bilibilipush = class extends Base {
|
|
|
36907
37524
|
now_time: TimeFormatter.now(),
|
|
36908
37525
|
share_url: "https://live.bilibili.com/" + liveInfo.live_play_info.room_id,
|
|
36909
37526
|
dynamicTYPE: "直播动态推送"
|
|
36910
|
-
}
|
|
37527
|
+
});
|
|
36911
37528
|
break;
|
|
36912
37529
|
}
|
|
36913
37530
|
/** 处理转发动态 */
|
|
@@ -37051,7 +37668,7 @@ var Bilibilipush = class extends Base {
|
|
|
37051
37668
|
original_content,
|
|
37052
37669
|
imgList: imgList.length > 0 ? imgList : null,
|
|
37053
37670
|
dynamic_id: dynamicId
|
|
37054
|
-
}
|
|
37671
|
+
});
|
|
37055
37672
|
break;
|
|
37056
37673
|
}
|
|
37057
37674
|
/** 文章/专栏动态 */
|
|
@@ -37093,7 +37710,7 @@ var Bilibilipush = class extends Base {
|
|
|
37093
37710
|
render_time: TimeFormatter.now(),
|
|
37094
37711
|
share_url: shareUrl,
|
|
37095
37712
|
dynamicTYPE: "专栏动态推送"
|
|
37096
|
-
}
|
|
37713
|
+
});
|
|
37097
37714
|
break;
|
|
37098
37715
|
}
|
|
37099
37716
|
/** 未处理的动态类型 */
|
|
@@ -37114,7 +37731,7 @@ var Bilibilipush = class extends Base {
|
|
|
37114
37731
|
const eventWithBot = this.e;
|
|
37115
37732
|
eventWithBot.bot = bot;
|
|
37116
37733
|
eventWithBot.selfId = botId;
|
|
37117
|
-
const
|
|
37734
|
+
const pushImg = img ?? [];
|
|
37118
37735
|
const parseUrl = buildParseUrl(data[dynamicId]);
|
|
37119
37736
|
const parseButton = bot?.adapter?.name === "QQ Official Bot" && parseUrl ? [segment.button([{
|
|
37120
37737
|
text: "解析",
|
|
@@ -37125,7 +37742,7 @@ var Bilibilipush = class extends Base {
|
|
|
37125
37742
|
callback: true,
|
|
37126
37743
|
data: `#kkk帮助`
|
|
37127
37744
|
}])] : [];
|
|
37128
|
-
status = await karin.sendMsg(botId, Contact, [...
|
|
37745
|
+
status = await karin.sendMsg(botId, Contact, [...pushImg, ...parseButton]);
|
|
37129
37746
|
if (Config.bilibili.push.parsedynamic && Config.bilibili.push.parseDynamicTypes.includes(data[dynamicId].dynamic_type) && status.messageId) switch (data[dynamicId].dynamic_type) {
|
|
37130
37747
|
case "DYNAMIC_TYPE_AV":
|
|
37131
37748
|
if (send_video) {
|
|
@@ -39000,7 +39617,7 @@ var buildDouyinPlayUrl = (playAddr) => {
|
|
|
39000
39617
|
return null;
|
|
39001
39618
|
}
|
|
39002
39619
|
})();
|
|
39003
|
-
return fileId ? `https://
|
|
39620
|
+
return fileId ? `https://aweme.snssdk.com/aweme/v1/play/?video_id=${playAddr.uri}&file_id=${fileId}` : `https://aweme.snssdk.com/aweme/v1/play/?video_id=${playAddr.uri}`;
|
|
39004
39621
|
};
|
|
39005
39622
|
/**
|
|
39006
39623
|
* 把选中的视频源格式化成展示用的清晰度字符串。
|
|
@@ -39509,7 +40126,7 @@ function getDefaultPushLabel(workTypeInfo) {
|
|
|
39509
40126
|
* @returns 渲染后的图片元素数组
|
|
39510
40127
|
*/
|
|
39511
40128
|
async function renderWorkImage(options) {
|
|
39512
|
-
const { e, Detail_Data, create_time, shareLink,
|
|
40129
|
+
const { e, Detail_Data, create_time, shareLink, videoSource } = options;
|
|
39513
40130
|
const workTypeInfo = getWorkTypeInfo(Detail_Data);
|
|
39514
40131
|
const dynamicTypeLabel = options.dynamicTypeLabel ?? getDefaultPushLabel(workTypeInfo);
|
|
39515
40132
|
const coverUrl = getWorkCoverUrl(workTypeInfo, Detail_Data);
|
|
@@ -39521,7 +40138,6 @@ async function renderWorkImage(options) {
|
|
|
39521
40138
|
const authorNickname = Detail_Data.author?.nickname ?? user.nickname;
|
|
39522
40139
|
const cooperationInfo = buildCooperationInfo(Detail_Data);
|
|
39523
40140
|
const mentionCache = /* @__PURE__ */ new Map();
|
|
39524
|
-
const renderOpts = skipWatermark ? { skipWatermark: true } : void 0;
|
|
39525
40141
|
switch (workTypeInfo.mainType) {
|
|
39526
40142
|
case DouyinWorkMainType.ARTICLE: {
|
|
39527
40143
|
const articleInfo = Detail_Data.article_info;
|
|
@@ -39545,7 +40161,7 @@ async function renderWorkImage(options) {
|
|
|
39545
40161
|
关注: Count(user.following_count),
|
|
39546
40162
|
粉丝: Count(user.follower_count),
|
|
39547
40163
|
share_url: shareLink
|
|
39548
|
-
}
|
|
40164
|
+
});
|
|
39549
40165
|
}
|
|
39550
40166
|
case DouyinWorkMainType.VIDEO: return await Render(e, "douyin/video-work", {
|
|
39551
40167
|
image_url: coverUrl,
|
|
@@ -39575,7 +40191,7 @@ async function renderWorkImage(options) {
|
|
|
39575
40191
|
关注: Count(user.following_count),
|
|
39576
40192
|
dynamicTYPE: dynamicTypeLabel,
|
|
39577
40193
|
cooperation_info: cooperationInfo
|
|
39578
|
-
}
|
|
40194
|
+
});
|
|
39579
40195
|
case DouyinWorkMainType.IMAGE: {
|
|
39580
40196
|
const cover = Detail_Data.images?.[0]?.url_list[2] ?? Detail_Data.images?.[0]?.url_list[1] ?? coverUrl;
|
|
39581
40197
|
const rawDesc = Detail_Data.desc ?? "";
|
|
@@ -39604,7 +40220,7 @@ async function renderWorkImage(options) {
|
|
|
39604
40220
|
关注: Count(user.following_count),
|
|
39605
40221
|
dynamicTYPE: dynamicTypeLabel,
|
|
39606
40222
|
cooperation_info: cooperationInfo
|
|
39607
|
-
}
|
|
40223
|
+
});
|
|
39608
40224
|
}
|
|
39609
40225
|
default: return [];
|
|
39610
40226
|
}
|
|
@@ -39616,7 +40232,7 @@ async function renderWorkImage(options) {
|
|
|
39616
40232
|
* @returns 渲染后的图片元素数组
|
|
39617
40233
|
*/
|
|
39618
40234
|
async function renderFavoriteImage(options) {
|
|
39619
|
-
const { e, Detail_Data, create_time, shareLink, remark
|
|
40235
|
+
const { e, Detail_Data, create_time, shareLink, remark } = options;
|
|
39620
40236
|
const coverUrl = getWorkCoverUrl(getWorkTypeInfo(Detail_Data), Detail_Data);
|
|
39621
40237
|
const authorUserInfo = Detail_Data.author_user_info;
|
|
39622
40238
|
const subscriberUser = Detail_Data.user_info.data.user;
|
|
@@ -39636,7 +40252,7 @@ async function renderFavoriteImage(options) {
|
|
|
39636
40252
|
author_avatar: authorUserInfo ? cdnAvatar(authorUserInfo.data.user.avatar_larger.uri) : Detail_Data.author.avatar_thumb.url_list[0],
|
|
39637
40253
|
author_douyin_id: authorUserInfo ? douyinId(authorUserInfo.data.user) : douyinId(Detail_Data.author),
|
|
39638
40254
|
share_url: shareLink
|
|
39639
|
-
}
|
|
40255
|
+
});
|
|
39640
40256
|
}
|
|
39641
40257
|
/**
|
|
39642
40258
|
* 渲染推荐列表推送图片
|
|
@@ -39645,7 +40261,7 @@ async function renderFavoriteImage(options) {
|
|
|
39645
40261
|
* @returns 渲染后的图片元素数组
|
|
39646
40262
|
*/
|
|
39647
40263
|
async function renderRecommendImage(options) {
|
|
39648
|
-
const { e, Detail_Data, create_time, shareLink, remark
|
|
40264
|
+
const { e, Detail_Data, create_time, shareLink, remark } = options;
|
|
39649
40265
|
const coverUrl = getWorkCoverUrl(getWorkTypeInfo(Detail_Data), Detail_Data);
|
|
39650
40266
|
const authorUserInfo = Detail_Data.author_user_info;
|
|
39651
40267
|
const recommenderUser = Detail_Data.user_info.data.user;
|
|
@@ -39665,7 +40281,7 @@ async function renderRecommendImage(options) {
|
|
|
39665
40281
|
author_avatar: authorUserInfo ? cdnAvatar(authorUserInfo.data.user.avatar_larger.uri) : Detail_Data.author.avatar_thumb.url_list[0],
|
|
39666
40282
|
author_douyin_id: authorUserInfo ? douyinId(authorUserInfo.data.user) : douyinId(Detail_Data.author),
|
|
39667
40283
|
share_url: shareLink
|
|
39668
|
-
}
|
|
40284
|
+
});
|
|
39669
40285
|
}
|
|
39670
40286
|
/**
|
|
39671
40287
|
* 渲染直播状态推送图片
|
|
@@ -39675,7 +40291,7 @@ async function renderRecommendImage(options) {
|
|
|
39675
40291
|
* @returns 渲染后的图片元素数组
|
|
39676
40292
|
*/
|
|
39677
40293
|
async function renderLiveImage(options) {
|
|
39678
|
-
const { e, Detail_Data
|
|
40294
|
+
const { e, Detail_Data } = options;
|
|
39679
40295
|
const dynamicTypeLabel = options.dynamicTypeLabel ?? "直播动态推送";
|
|
39680
40296
|
const user = Detail_Data.user_info.data.user;
|
|
39681
40297
|
if (!Detail_Data.room_data || !Detail_Data.live_data) return [];
|
|
@@ -39704,7 +40320,7 @@ async function renderLiveImage(options) {
|
|
|
39704
40320
|
following_count: Count(user.following_count || 0),
|
|
39705
40321
|
total_favorited: Count(user.total_favorited || 0),
|
|
39706
40322
|
has_commerce_goods: liveItem.has_commerce_goods || false
|
|
39707
|
-
}
|
|
40323
|
+
});
|
|
39708
40324
|
}
|
|
39709
40325
|
//#endregion
|
|
39710
40326
|
//#region src/platform/douyin/types.ts
|
|
@@ -40968,20 +41584,11 @@ var DouYinpush = class extends Base {
|
|
|
40968
41584
|
this.injectBotToEventForRender(pushItem.targets);
|
|
40969
41585
|
if (!skip) iddata = await getDouyinID(this.e, shareUrl, false);
|
|
40970
41586
|
if (!skip) {
|
|
40971
|
-
const realUrl = pushItem.pushType !== "live" && workTypeInfo.isVideo && Config.douyin.push.shareType === "web" && await new Network({
|
|
40972
|
-
url: pushItem.Detail_Data.share_url,
|
|
40973
|
-
headers: {
|
|
40974
|
-
"User-Agent": "Apifox/1.0.0 (https://apifox.com)",
|
|
40975
|
-
Accept: "*/*",
|
|
40976
|
-
"Accept-Encoding": "gzip, deflate, br",
|
|
40977
|
-
Connection: "keep-alive"
|
|
40978
|
-
}
|
|
40979
|
-
}).getLocation();
|
|
40980
41587
|
if (pushItem.pushType !== "live" && workTypeInfo.isVideo && pushItem.Detail_Data.video?.bit_rate?.length) selectedVideo = douyinProcessVideos(pushItem.Detail_Data.video.bit_rate, Config.douyin.videoQuality)[0];
|
|
40981
41588
|
let workShareLink;
|
|
40982
41589
|
if (pushItem.pushType !== "live") if (workTypeInfo.isArticle) workShareLink = `https://www.douyin.com/article/${actualAwemeId}`;
|
|
40983
41590
|
else if (workTypeInfo.isImage) workShareLink = `https://www.douyin.com/note/${actualAwemeId}`;
|
|
40984
|
-
else if (Config.douyin.push.shareType === "web") workShareLink =
|
|
41591
|
+
else if (Config.douyin.push.shareType === "web") workShareLink = `https://www.douyin.com/video/${actualAwemeId}`;
|
|
40985
41592
|
else {
|
|
40986
41593
|
const playAddr = selectedVideo?.play_addr ?? pushItem.Detail_Data.video?.play_addr;
|
|
40987
41594
|
workShareLink = playAddr ? buildDouyinPlayUrl(playAddr) : `https://www.douyin.com/video/${actualAwemeId}`;
|
|
@@ -40992,7 +41599,6 @@ var DouYinpush = class extends Base {
|
|
|
40992
41599
|
img = await renderLiveImage({
|
|
40993
41600
|
e: this.e,
|
|
40994
41601
|
Detail_Data: pushItem.Detail_Data,
|
|
40995
|
-
skipWatermark: true,
|
|
40996
41602
|
dynamicTypeLabel: "直播动态推送"
|
|
40997
41603
|
});
|
|
40998
41604
|
break;
|
|
@@ -41002,8 +41608,7 @@ var DouYinpush = class extends Base {
|
|
|
41002
41608
|
Detail_Data: pushItem.Detail_Data,
|
|
41003
41609
|
create_time: pushItem.create_time,
|
|
41004
41610
|
shareLink: workShareLink,
|
|
41005
|
-
remark: pushItem.remark
|
|
41006
|
-
skipWatermark: true
|
|
41611
|
+
remark: pushItem.remark
|
|
41007
41612
|
});
|
|
41008
41613
|
break;
|
|
41009
41614
|
case "recommend":
|
|
@@ -41012,8 +41617,7 @@ var DouYinpush = class extends Base {
|
|
|
41012
41617
|
Detail_Data: pushItem.Detail_Data,
|
|
41013
41618
|
create_time: pushItem.create_time,
|
|
41014
41619
|
shareLink: workShareLink,
|
|
41015
|
-
remark: pushItem.remark
|
|
41016
|
-
skipWatermark: true
|
|
41620
|
+
remark: pushItem.remark
|
|
41017
41621
|
});
|
|
41018
41622
|
break;
|
|
41019
41623
|
default:
|
|
@@ -41022,8 +41626,7 @@ var DouYinpush = class extends Base {
|
|
|
41022
41626
|
Detail_Data: pushItem.Detail_Data,
|
|
41023
41627
|
create_time: pushItem.create_time,
|
|
41024
41628
|
shareLink: workShareLink,
|
|
41025
|
-
videoSource: selectedVideo
|
|
41026
|
-
skipWatermark: true
|
|
41629
|
+
videoSource: selectedVideo
|
|
41027
41630
|
});
|
|
41028
41631
|
break;
|
|
41029
41632
|
}
|
|
@@ -41037,7 +41640,7 @@ var DouYinpush = class extends Base {
|
|
|
41037
41640
|
const eventWithBot = this.e;
|
|
41038
41641
|
eventWithBot.bot = bot;
|
|
41039
41642
|
eventWithBot.selfId = botId;
|
|
41040
|
-
const
|
|
41643
|
+
const pushImg = img ?? [];
|
|
41041
41644
|
const parseButton = bot?.adapter?.name === "QQ Official Bot" && pushItem.pushType !== "live" && pushItem.Detail_Data.share_url ? [segment.button([{
|
|
41042
41645
|
text: "解析",
|
|
41043
41646
|
callback: true,
|
|
@@ -41047,7 +41650,7 @@ var DouYinpush = class extends Base {
|
|
|
41047
41650
|
callback: true,
|
|
41048
41651
|
data: `#kkk帮助`
|
|
41049
41652
|
}])] : [];
|
|
41050
|
-
status = await karin.sendMsg(botId, Contact, [...
|
|
41653
|
+
status = await karin.sendMsg(botId, Contact, [...pushImg, ...parseButton]);
|
|
41051
41654
|
if (pushItem.pushType === "live" && "room_data" in pushItem.Detail_Data && status.message_id) await douyinDBInstance.updateLiveStatus(pushItem.sec_uid, true);
|
|
41052
41655
|
if (pushItem.pushType !== "live" && Config.douyin.push.parsedynamic && status.message_id) {
|
|
41053
41656
|
const Detail_Data = pushItem.Detail_Data;
|
|
@@ -42624,7 +43227,10 @@ var douyinLogin = async (e) => {
|
|
|
42624
43227
|
};
|
|
42625
43228
|
const validFor = qrcode.data.expires_in;
|
|
42626
43229
|
logger.mark(`[抖音登录] 二维码已获取,有效期 ${validFor} 秒`);
|
|
42627
|
-
const rendered = await Render(e, "douyin/qrcodeImg", {
|
|
43230
|
+
const rendered = await Render(e, "douyin/qrcodeImg", {
|
|
43231
|
+
share_url: qrcode.data.content,
|
|
43232
|
+
avatarUrl: await resolveTriggerAvatarUrl(e)
|
|
43233
|
+
});
|
|
42628
43234
|
const base64Data = rendered[0]?.file;
|
|
42629
43235
|
if (!base64Data) throw new Error("生成二维码图片失败");
|
|
42630
43236
|
fs.writeFileSync(`${Common.tempDri.default}DouyinLoginQrcode.png`, Buffer.from(base64Data.replace(/^base64:\/\//, ""), "base64"));
|
|
@@ -43353,7 +43959,8 @@ var handleQrLogin = wrapWithErrorHandler(async (e) => {
|
|
|
43353
43959
|
port,
|
|
43354
43960
|
authKey
|
|
43355
43961
|
}),
|
|
43356
|
-
serverUrl: `${protocol}://${host}:${port}
|
|
43962
|
+
serverUrl: `${protocol}://${host}:${port}`,
|
|
43963
|
+
avatarUrl: await resolveTriggerAvatarUrl(e)
|
|
43357
43964
|
});
|
|
43358
43965
|
await karin.sendMaster(e.selfId, userId, images);
|
|
43359
43966
|
if (e.isGroup) await e.reply("登录二维码已私聊发送,请查收~");
|
|
@@ -44774,4 +45381,4 @@ mkdirSync(`${karinPathBase}/${Root.pluginName}/data`);
|
|
|
44774
45381
|
mkdirSync(Common.tempDri.images);
|
|
44775
45382
|
mkdirSync(Common.tempDri.video);
|
|
44776
45383
|
//#endregion
|
|
44777
|
-
export {
|
|
45384
|
+
export { createViewPictureNode as $, KuaishouEmojiParamsSchema as $n, emitHttpResponse as $r, AdditionalType as $t, testWrapWithErrorHandler as A, douyinFetcher$1 as An, BilibiliBangumiInfoParamsSchema as Ar, DouyinApiRoutes as At, createImageNode as B, bilibiliFetcher$1 as Bn, BilibiliLoginParamsSchema as Br, BilibiliMethodToFetcher as Bt, changelog as C, isNetworkErrorResult as Cn, DouyinValidationSchemas as Cr, reloadAmagiConfig as Ct, dylogin as D, kuaishouFetcher$1 as Dn, BilibiliArticleInfoParamsSchema as Dr, amagi as Dt, biLogin as E, createBoundKuaishouFetcher as En, BilibiliArticleCardParamsSchema as Er, CreateApp as Et, createCodeBlockNode as F, isSmsCodeVerifyWay as Fn, BilibiliCommentReplyParamsSchema as Fr, XiaohongshuMethodMapping as Ft, createLotteryNode as G, validateDouyinParams as Gn, BilibiliValidateCaptchaParamsSchema as Gr, KuaishouInternalMethods as Gt, createLinkCardNode as H, createErrorResponse as Hn, BilibiliQrcodeParamsSchema as Hr, DouyinInternalMethods as Ht, createEmojiNode as I, requestPassportQrcode as In, BilibiliDanmakuParamsSchema as Ir, getApiRoute as It, createParagraphNode as J, XiaohongshuMethodRoutes as Jn, BilibiliVideoParamsSchema as Jr, XiaohongshuFetcherMethods as Jt, createMentionNode as K, validateKuaishouParams as Kn, BilibiliValidationSchemas as Kr, KuaishouMethodToFetcher as Kt, createHashtagNode as L, sendPassportVerifyCode as Ln, BilibiliDynamicParamsSchema as Lr, getEnglishMethodName as Lt, renderRichTextToReact as M, douyinApiUrls as Mn, BilibiliBv2AvParamsSchema as Mr, KuaishouApiRoutes as Mt, createAtNode as N, douyinSign as Nn, BilibiliColumnInfoParamsSchema as Nr, KuaishouMethodMapping as Nt, removeOldFiles as O, kuaishouSign as On, BilibiliArticleParamsSchema as Or, BilibiliApiRoutes as Ot, createBlockquoteNode as P, checkPassportQrcode as Pn, BilibiliCommentParamsSchema as Pr, XiaohongshuApiRoutes as Pt, createTopicNode as Q, KuaishouCommentParamsSchema as Qn, emitHttpRequest as Qr, DynamicType as Qt, createHeadingNode as R, validatePassportVerifyCode as Rn, BilibiliEmojiParamsSchema as Rr, BilibiliFetcherMethods as Rt, setdyPush as S, getHeadersAndData as Sn, DouyinUserParamsSchema as Sr, registerAmagiReloadListener as St, version as T, xiaohongshuFetcher$1 as Tn, BilibiliApplyCaptchaParamsSchema as Tr, xiaohongshuFetcher as Tt, createListItemNode as U, createSuccessResponse$1 as Un, BilibiliQrcodeStatusParamsSchema as Ur, DouyinMethodToFetcher as Ut, createLineBreakNode as V, createBoundBilibiliFetcher as Vn, BilibiliMethodRoutes as Vr, DouyinFetcherMethods as Vt, createListNode as W, validateBilibiliParams as Wn, BilibiliUserParamsSchema as Wr, KuaishouFetcherMethods as Wt, createSearchKeywordNode as X, xiaohongshuApiUrls as Xn, emitApiError as Xr, XiaohongshuMethodToFetcher as Xt, createRichTextDocument as Y, XiaohongshuValidationSchemas as Yn, amagiEvents as Yr, XiaohongshuInternalMethods as Yt, createTextNode as Z, xiaohongshuSign as Zn, emitApiSuccess as Zr, toFetcherMethod as Zt, douyinPush as _, av2bv as _n, DouyinMethodRoutes as _r, AmagiError as _t, bilibiliAPP as a, emitLogWarn as ai, kuaishouUtils as an, KuaishouVideoParamsSchema as ar, webConfig as at, globalIgnore as b, fetchData as bn, DouyinSearchParamsSchema as br, douyinFetcher as bt, prefix as c, bilibiliApiUrls as ci, createDouyinRoutes as cn, DouyinDanmakuParamsSchema as cr, douyinDBInstance as ct, globalStatistics as d, ApiError as dn, DouyinEmojiResourceParamsSchema as dr, getStatisticsDB as dt, emitLog as ei, MajorType as en, KuaishouLiveRoomInfoParamsSchema as er, createVoteNode as et, groupStatistics as f, ValidationError as fn, DouyinGuestMusicListParamsSchema as fr, initAllDatabases as ft, changeBotID as g, parseDmSegMobileReply as gn, DouyinLiveRoomParamsSchema as gr, AmagiBase as gt, bilibiliPushList as h, wbi_sign as hn, DouyinHotWordsParamsSchema as hr, BilibiliDBBase as ht, update as i, emitLogMark as ii, createXiaohongshuRoutes as in, KuaishouValidationSchemas as ir, renderVideoPreviewPage as it, templates as j, createBoundDouyinFetcher as jn, BilibiliBangumiStreamParamsSchema as jr, DouyinMethodMapping as jt, task as k, kuaishouApiUrls as kn, BilibiliAv2BvParamsSchema as kr, BilibiliMethodMapping as kt, xiaohongshuAPP as l, Root as li, bilibiliUtils as ln, DouyinEmojiListParamsSchema as lr, getBilibiliDB as lt, bilibiliPush as m, bilibiliErrorCodeMap as mn, DouyinGuestUserParamsSchema as mr, DouyinDBBase as mt, kkkUpdateCommand as n, emitLogError as ni, createAmagiClient as nn, KuaishouUserProfileParamsSchema as nr, extractRichTextPlainText as nt, douyinAPP as o, emitNetworkError as oi, createKuaishouRoutes as on, DouyinCommentParamsSchema as or, bilibiliDBInstance as ot, qrLogin as p, handleError as pn, DouyinGuestMusicParamsSchema as pr, StatisticsDBBase as pt, createOpusLinkNode as q, validateXiaohongshuParams as qn, BilibiliVideoDownloadParamsSchema as qr, MethodMaps as qt, kkkUpdateTest as r, emitLogInfo as ri, xiaohongshuUtils as rn, KuaishouUserWorkListParamsSchema as rr, normalizeRichTextNodes as rt, kuaishouAPP as s, emitNetworkRetry as si, douyinUtils as sn, DouyinCommentReplyParamsSchema as sr, cleanOldDynamicCache as st, kkkUpdate as t, emitLogDebug as ti, CommentType as tn, KuaishouMethodRoutes as tr, createWebLinkNode as tt, testPush as u, createBilibiliRoutes as un, DouyinEmojiProParamsSchema as ur, getDouyinDB as ut, douyinPushList as v, bv2av as vn, DouyinMusicParamsSchema as vr, SOFT_ERROR_CODES as vt, help as w, createBoundXiaohongshuFetcher as wn, DouyinWorkParamsSchema as wr, softFetch as wt, setbiliPush as x, fetchResponse as xn, DouyinUserListParamsSchema as xr, kuaishouFetcher as xt, forcePush as y, qtparam as yn, DouyinQrcodeParamsSchema as yr, bilibiliFetcher as yt, createHorizontalRuleNode as z, passport_exports as zn, BilibiliLiveParamsSchema as zr, BilibiliInternalMethods as zt };
|