karin-plugin-kkk 2.45.1-beta.4 → 2.45.2-beta.0
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 +20 -0
- package/lib/build-metadata.json +5 -5
- package/lib/core_chunk/default.js +164 -3
- package/lib/core_chunk/main.js +95 -22
- package/lib/core_chunk/vendor.js +509 -502
- package/lib/web/assets/{CronEditor-Bvl90nHa.js → CronEditor-DSx8kkQQ.js} +1 -1
- package/lib/web/assets/{DesktopLayout-t8vT4Hmg.js → DesktopLayout-BJTY9p_l.js} +1 -1
- package/lib/web/assets/{MobileLayout-CTAoQc-J.js → MobileLayout-BKJi8d_L.js} +1 -1
- package/lib/web/assets/{ThemeSwitch-DNCjCIP-.js → ThemeSwitch-Cipx1aP1.js} +1 -1
- package/lib/web/assets/{index-Zn_7piH-.js → index-RFCFN09q.js} +14 -14
- package/lib/web/assets/{separator-CFLVDixH.js → separator-CCrVXPvv.js} +1 -1
- package/lib/web/index.html +1 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
# Changelog
|
|
4
4
|
|
|
5
|
+
## [2.45.2-beta.0](https://github.com/ikenxuan/karin-plugin-kkk/compare/v2.45.1...v2.45.2-beta.0) (2026-10-24)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### ✨ 新功能
|
|
9
|
+
|
|
10
|
+
* **douyin:** 推送二维码改用 amagi 签发的短链 ([e2975a3](https://github.com/ikenxuan/karin-plugin-kkk/commit/e2975a32b26384a6dcf2f1ad982b1b52abcbdbb6))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### 🐛 错误修复
|
|
14
|
+
|
|
15
|
+
* **bilibili:** 下载时避开被代理屏蔽的 PCDN 直链 (#370) ([1c64ace](https://github.com/ikenxuan/karin-plugin-kkk/commit/1c64ace13e9384a45a7e88853251a644229692cb))
|
|
16
|
+
* **xiaohongshu:** 优化合并转发消息上传逻辑 ([e093d05](https://github.com/ikenxuan/karin-plugin-kkk/commit/e093d056455b9d914e7b7728d8d2ad2513c30dbf))
|
|
17
|
+
|
|
18
|
+
## [2.45.1](https://github.com/ikenxuan/karin-plugin-kkk/compare/v2.45.0...v2.45.1) (2026-10-17)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
### 📦 依赖更新
|
|
22
|
+
|
|
23
|
+
* update ([3435298](https://github.com/ikenxuan/karin-plugin-kkk/commit/343529868a87c0cc73cdbef2ffa31923f275d399))
|
|
24
|
+
|
|
5
25
|
## [2.45.1-beta.4](https://github.com/ikenxuan/karin-plugin-kkk/compare/v2.45.1-beta.3...v2.45.1-beta.4) (2026-10-17)
|
|
6
26
|
|
|
7
27
|
|
package/lib/build-metadata.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "2.45.
|
|
3
|
-
"buildTime": "2026-10-
|
|
4
|
-
"buildTimestamp":
|
|
2
|
+
"version": "2.45.2-beta.0",
|
|
3
|
+
"buildTime": "2026-10-23T19:30:08.731Z",
|
|
4
|
+
"buildTimestamp": 1792783808732,
|
|
5
5
|
"name": "karin-plugin-kkk",
|
|
6
6
|
"description": "Karin 的「抖音」「B 站」视频解析/动态推送插件",
|
|
7
7
|
"homepage": "https://github.com/ikenxuan/karin-plugin-kkk",
|
|
8
|
-
"commitHash": "
|
|
9
|
-
"shortCommitHash": "
|
|
8
|
+
"commitHash": "f7656b43d22b833c3388975bb9fd03e2d2892be6",
|
|
9
|
+
"shortCommitHash": "f7656b43"
|
|
10
10
|
}
|
|
@@ -7335,7 +7335,7 @@ var liveXs = () => ({
|
|
|
7335
7335
|
}
|
|
7336
7336
|
});
|
|
7337
7337
|
//#endregion
|
|
7338
|
-
//#region ../amagi/packages/core/dist/src-
|
|
7338
|
+
//#region ../amagi/packages/core/dist/src-DLPJF1Oy.mjs
|
|
7339
7339
|
var import_protobufjs = /* @__PURE__ */ __toESM(require_protobufjs(), 1);
|
|
7340
7340
|
/**
|
|
7341
7341
|
* B站评论区类型代码(`comments` / `commentReplies` 的 `type` 参数)。
|
|
@@ -13392,6 +13392,7 @@ var METHOD_NAMES = {
|
|
|
13392
13392
|
"douyin.userRecommendList": "fetchUserRecommendList",
|
|
13393
13393
|
/** ⚠️ 不规则:`search` → `searchContent` */
|
|
13394
13394
|
"douyin.search": "searchContent",
|
|
13395
|
+
"douyin.shortUrl": "fetchShortUrl",
|
|
13395
13396
|
"douyin.suggestWords": "fetchSuggestWords",
|
|
13396
13397
|
"douyin.musicInfo": "fetchMusicInfo",
|
|
13397
13398
|
"douyin.musicAwemeList": "fetchMusicAwemeList",
|
|
@@ -13922,6 +13923,21 @@ need_all: "true" }))}`;
|
|
|
13922
13923
|
count: data.number ?? 20
|
|
13923
13924
|
}))}`;
|
|
13924
13925
|
}
|
|
13926
|
+
/**
|
|
13927
|
+
* 分享链接转短链接。
|
|
13928
|
+
*
|
|
13929
|
+
* `target` 只编码一次(`buildQueryString` 的 `encodeURIComponent`),与抓包一致 ——
|
|
13930
|
+
* 预先编码会变成 `%25`,接口会把它当另一个链接。
|
|
13931
|
+
* `belong=aweme` 是能缩短的前提,缺了或换成别的值会回 `reason: "not allowed to shorten"`;
|
|
13932
|
+
* `persist=1` 照浏览器抓包固定发送(实测省略也能成功,保留只为与浏览器同形)。
|
|
13933
|
+
*/
|
|
13934
|
+
getWebShorten(data) {
|
|
13935
|
+
return `https://www.douyin.com/aweme/v1/web/web_shorten/?${buildQueryString$3(webQuery(this.browser, {
|
|
13936
|
+
target: data.target,
|
|
13937
|
+
belong: data.belong ?? "aweme",
|
|
13938
|
+
persist: "1"
|
|
13939
|
+
}))}`;
|
|
13940
|
+
}
|
|
13925
13941
|
/** 获取用户主页视频数据 */
|
|
13926
13942
|
getUserVideoList(data) {
|
|
13927
13943
|
return `https://www.douyin.com/aweme/v1/web/aweme/post/?${buildQueryString$3(webQuery(this.browser, {
|
|
@@ -14992,6 +15008,116 @@ var search = defineDouyinEndpoint({
|
|
|
14992
15008
|
response: type()
|
|
14993
15009
|
});
|
|
14994
15010
|
/**
|
|
15011
|
+
* `web_shorten` 的响应判定。
|
|
15012
|
+
*
|
|
15013
|
+
* 成功与失败**都是 HTTP 200 + `code: 0`**,差别只在 `message` / `reason`:
|
|
15014
|
+
*
|
|
15015
|
+
* ```jsonc
|
|
15016
|
+
* // 成功
|
|
15017
|
+
* { "code": 0, "message": "success", "now": 1792253001, "data": "https://v.douyin.com/V6jASdpUrtY/" }
|
|
15018
|
+
* // 签名没带 / 不对
|
|
15019
|
+
* { "code": 0, "message": "error", "reason": "Bad request: check failed", "now": 1792252373 }
|
|
15020
|
+
* // belong 缺失或不是 aweme
|
|
15021
|
+
* { "code": 0, "message": "error", "reason": "not allowed to shorten", "now": 1792252323,
|
|
15022
|
+
* "data": "https://www.iesdouyin.com/share/video/…" }
|
|
15023
|
+
* ```
|
|
15024
|
+
*
|
|
15025
|
+
* 平台默认的 {@link douyinJudge} 只看 `status_code`,而这个接口用 `code` 且两种失败都回 0,
|
|
15026
|
+
* 默认判定会把失败当成功 —— 调用方会拿到一个装着原链接、甚至没有 `data` 的「成功」信封。
|
|
15027
|
+
* 所以这里补一层:
|
|
15028
|
+
*
|
|
15029
|
+
* - `code` 非 0 → `PLATFORM_ERROR`;
|
|
15030
|
+
* - `reason` 点名签名校验失败 → `SIGNATURE_REFUSED`(是签名器的问题,换 cookie 无用);
|
|
15031
|
+
* - 其余带 `reason` 的拒绝 → `forbidden` / `PLATFORM_ERROR`(请求本身合法,平台不放行)。
|
|
15032
|
+
*
|
|
15033
|
+
* `message` 的原文是 `error`,`reason` 才是原因;runtime 的 error 文案只认
|
|
15034
|
+
* `message` / `status_msg` / `msg`,所以失败信封里看不到 reason 原文 —— 要排查看
|
|
15035
|
+
* `debug: true` 下的 `error.raw`。
|
|
15036
|
+
* @param raw - 解码后的响应体
|
|
15037
|
+
* @param http - HTTP 状态与响应头
|
|
15038
|
+
* @returns 平台判定结论
|
|
15039
|
+
*/
|
|
15040
|
+
var shortUrlJudge = (raw, http) => {
|
|
15041
|
+
const verdict = douyinJudge(raw, http);
|
|
15042
|
+
if (!verdict.ok) return verdict;
|
|
15043
|
+
if (typeof raw !== "object" || raw === null) return verdict;
|
|
15044
|
+
const body = raw;
|
|
15045
|
+
if (body.code !== void 0 && (typeof body.code === "number" || typeof body.code === "string") && Number(body.code) !== 0) return {
|
|
15046
|
+
ok: false,
|
|
15047
|
+
kind: "unknown",
|
|
15048
|
+
code: "PLATFORM_ERROR",
|
|
15049
|
+
retryable: false
|
|
15050
|
+
};
|
|
15051
|
+
if (typeof body.reason !== "string" || body.reason.length === 0) return verdict;
|
|
15052
|
+
if (/check failed/i.test(body.reason)) return {
|
|
15053
|
+
ok: false,
|
|
15054
|
+
kind: "risk",
|
|
15055
|
+
code: "SIGNATURE_REFUSED",
|
|
15056
|
+
retryable: false
|
|
15057
|
+
};
|
|
15058
|
+
return {
|
|
15059
|
+
ok: false,
|
|
15060
|
+
kind: "forbidden",
|
|
15061
|
+
code: "PLATFORM_ERROR",
|
|
15062
|
+
retryable: false
|
|
15063
|
+
};
|
|
15064
|
+
};
|
|
15065
|
+
/**
|
|
15066
|
+
* 分享链接转短链接(`/aweme/v1/web/web_shorten/`)。
|
|
15067
|
+
*
|
|
15068
|
+
* ## 这是唯一一条「免登录但仍要签名」的工具端点
|
|
15069
|
+
*
|
|
15070
|
+
* 2026-10-18 实测(零 cookie、随机 msToken、本项目 a_bogus):
|
|
15071
|
+
*
|
|
15072
|
+
* ```text
|
|
15073
|
+
* 只带 target + belong + a_bogus → 200 success,返回 v.douyin.com 短链
|
|
15074
|
+
* 同一组参数不带 a_bogus → 200,reason: "Bad request: check failed"
|
|
15075
|
+
* target + belong=aweme → 200 success
|
|
15076
|
+
* target 不带 belong / belong=other → 200,reason: "not allowed to shorten"
|
|
15077
|
+
* ```
|
|
15078
|
+
*
|
|
15079
|
+
* 也就是说 **cookie 完全不需要**(空串、伪造的 `ttwid/UIFID`、刚铸的游客 cookie 三种都成功),
|
|
15080
|
+
* 但 **`a_bogus` 必须带**,且 `belong=aweme` 是能缩短的前提 —— 客户端算不出短链、也没有
|
|
15081
|
+
* 别的接口能补,这条没有「免签」的退路。
|
|
15082
|
+
*
|
|
15083
|
+
* ## 为什么保留完整的 web 版式
|
|
15084
|
+
*
|
|
15085
|
+
* 浏览器抓包里这条的 query 很长(`target,belong,persist` + 25 个环境参数 + tail)。
|
|
15086
|
+
* 实测最小化到 `target+belong+a_bogus` 也能成功,但这里仍走 `webQuery` 的完整版式:
|
|
15087
|
+
* 这是 `/aweme/v1/web/*` 的统一形状,抖音按查询串重算签名,形状一致不增加成本。
|
|
15088
|
+
* `persist=1` 照抓包固定发送(实测省略也能成功,保留是为了与浏览器一致)。
|
|
15089
|
+
*
|
|
15090
|
+
* ## 短链是稳定映射
|
|
15091
|
+
*
|
|
15092
|
+
* 同一个 `target` 多次调用返回同一个短码(跨进程、跨 cookie 实测一致),
|
|
15093
|
+
* 不是一次性令牌;`https://www.douyin.com/video/<id>` 与它的分享链接会得到**不同**的短码,
|
|
15094
|
+
* 取决于传入的字符串本身。
|
|
15095
|
+
*/
|
|
15096
|
+
var shortUrl = defineDouyinEndpoint({
|
|
15097
|
+
name: "douyin.shortUrl",
|
|
15098
|
+
route: "/fetch_short_url",
|
|
15099
|
+
doc: {
|
|
15100
|
+
summary: "分享链接转短链接",
|
|
15101
|
+
description: "把抖音作品分享链接转换成 `v.douyin.com` 短链;`belong=aweme` 是能缩短的前提。"
|
|
15102
|
+
},
|
|
15103
|
+
params: object$1({
|
|
15104
|
+
target: string().min(1, { error: "目标链接不能为空" }).describe("需要缩短的分享链接"),
|
|
15105
|
+
belong: string().min(1).default("aweme").optional().describe("短链归属类型,抖音作品固定为 aweme,默认 aweme")
|
|
15106
|
+
}),
|
|
15107
|
+
build: (p, ctx) => ({
|
|
15108
|
+
method: "GET",
|
|
15109
|
+
url: createDouyinApiUrls(ctx.userAgent).getWebShorten({
|
|
15110
|
+
target: p.target,
|
|
15111
|
+
belong: p.belong ?? "aweme"
|
|
15112
|
+
})
|
|
15113
|
+
}),
|
|
15114
|
+
sign: douyinBogus(184),
|
|
15115
|
+
judge: shortUrlJudge,
|
|
15116
|
+
retryOn: DOUYIN_RISK_RETRY,
|
|
15117
|
+
retryFresh: true,
|
|
15118
|
+
response: type()
|
|
15119
|
+
});
|
|
15120
|
+
/**
|
|
14995
15121
|
* 图文/幻灯片作品详情(新路径 `/fetch_slides_work`,避免与其他作品类型共用路由)。
|
|
14996
15122
|
*
|
|
14997
15123
|
* 行为与旧版一致:`getWorkDetail` GET + a_bogus 签名。
|
|
@@ -15237,11 +15363,14 @@ var userVideoList = defineDouyinEndpoint({
|
|
|
15237
15363
|
/**
|
|
15238
15364
|
* 抖音端点注册表。
|
|
15239
15365
|
*
|
|
15240
|
-
* 共
|
|
15366
|
+
* 共 26 条路由,路由唯一:5 个作品端点拆成 5 条独立路由
|
|
15241
15367
|
* (`parseWork` 保留 `/fetch_one_work`)。
|
|
15242
15368
|
*
|
|
15243
15369
|
* 末尾 4 条是**免鉴权**端点(`sign: false` + `dropHeaders` 去掉 cookie)。
|
|
15244
|
-
* 它们与前面
|
|
15370
|
+
* 它们与前面 22 条走同一条管线,所以事件、trace、信封形状一致。
|
|
15371
|
+
*
|
|
15372
|
+
* `shortUrl` 是例外:免登录**但仍要签名**(`sign: douyinBogus(184)`),
|
|
15373
|
+
* 所以按签名端点计数;它不在免鉴权那 4 条里。
|
|
15245
15374
|
*/
|
|
15246
15375
|
var douyinRegistry = {
|
|
15247
15376
|
parseWork,
|
|
@@ -15278,6 +15407,7 @@ var douyinRegistry = {
|
|
|
15278
15407
|
userRecommendList,
|
|
15279
15408
|
search,
|
|
15280
15409
|
suggestWords,
|
|
15410
|
+
shortUrl,
|
|
15281
15411
|
musicInfo,
|
|
15282
15412
|
musicAwemeList,
|
|
15283
15413
|
mixAweme,
|
|
@@ -22906,6 +23036,37 @@ var RESPONSE_SCHEMAS = {
|
|
|
22906
23036
|
]
|
|
22907
23037
|
}
|
|
22908
23038
|
] },
|
|
23039
|
+
"douyin_shortUrl": { "anyOf": [{
|
|
23040
|
+
"type": "object",
|
|
23041
|
+
"properties": {
|
|
23042
|
+
"code": { "type": "number" },
|
|
23043
|
+
"data": { "type": "string" },
|
|
23044
|
+
"message": { "type": "string" },
|
|
23045
|
+
"now": { "type": "number" }
|
|
23046
|
+
},
|
|
23047
|
+
"required": [
|
|
23048
|
+
"code",
|
|
23049
|
+
"data",
|
|
23050
|
+
"message",
|
|
23051
|
+
"now"
|
|
23052
|
+
]
|
|
23053
|
+
}, {
|
|
23054
|
+
"type": "object",
|
|
23055
|
+
"properties": {
|
|
23056
|
+
"code": { "type": "number" },
|
|
23057
|
+
"data": { "type": "string" },
|
|
23058
|
+
"message": { "type": "string" },
|
|
23059
|
+
"now": { "type": "number" },
|
|
23060
|
+
"reason": { "type": "string" }
|
|
23061
|
+
},
|
|
23062
|
+
"required": [
|
|
23063
|
+
"code",
|
|
23064
|
+
"data",
|
|
23065
|
+
"message",
|
|
23066
|
+
"now",
|
|
23067
|
+
"reason"
|
|
23068
|
+
]
|
|
23069
|
+
}] },
|
|
22909
23070
|
"douyin_slidesWork": { "anyOf": [
|
|
22910
23071
|
{
|
|
22911
23072
|
"type": "object",
|
package/lib/core_chunk/main.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
globalThis.__KTR_BUNDLED__ = true;
|
|
2
2
|
import { o as __toESM } from "./rolldown-runtime.js";
|
|
3
3
|
import { a as createBilibiliRoutes, c as createXiaohongshuRoutes, i as bilibiliApiUrls, l as wbi_sign, n as DynamicType, o as createDouyinRoutes, r as MajorType, s as createKuaishouRoutes, t as Client } from "./default.js";
|
|
4
|
-
import { $ as RiPieChart2Fill, $n as
|
|
4
|
+
import { $ as RiPieChart2Fill, $n as GitBranch, $t as Video, A as MdFitScreen, An as Quote, Ar as Calendar, At as n, B as AiFillHeart, Bn as MessageCircle, Br as require_jsx_runtime, Bt as p, C as webassembly_default, Cn as ScanLine, Cr as CircleDashed, Ct as o$3, D as tailwind_css_default, Dn as Repeat, Dr as Check, Dt as s, E as tsdown_default, En as RotateCcw, Er as CircleAlert, Et as o$1, F as FaCommentDots, Fn as Pencil, Fr as BookOpenText, Ft as m$1, G as RiArrowRightFill, Gn as ListChecks, Gr as require_server_node, Gt as parse, H as AiFillStar, Hn as Maximize, Hr as clsx, Ht as s$1, I as FaMusic, In as Palette, Ir as BellRing, It as m$2, J as RiHeart3Fill, Jn as Info, Jr as defineTemplate, Jt as format, K as RiGroupLine, Kn as Link, Kr as require_react, Kt as fromUnixTime, L as FaTiktok, Ln as Network$1, Lr as ArrowRight, Lt as c$1, M as MdLightbulbOutline, Mn as Puzzle, Mr as Box, Mt as e, N as MdLocationOn, Nn as Plus, Nr as Bot, Nt as c$2, O as sqlite_default, On as RefreshCw, Or as ChartColumn, Ot as m, P as MdSchedule, Pn as Play, Pr as Bookmark, Pt as a$1, Q as RiMessage3Fill, Qn as Globe, Qt as WandSparkles, R as FaUserGroup, Rn as Moon, Rr as Chip, Rt as o$2, S as rehypeHighlight, Sn as Search, Sr as CircleEllipsis, St as SiAnthropic, T as typescript_default, Tn as Route, Tr as CircleCheck, Tt as o, U as AiOutlineVideoCamera, Un as MapPin, Ur as createTemplateRenderer, Ut as m$3, V as AiFillPushpin, Vn as Menu, Vr as twMerge, Vt as n$1, W as Markdown, Wn as LoaderCircle, Wr as resolveTemplateStyle, Wt as zhCN, X as RiHeartLine, Xn as Heart, Xt as Zap, Y as RiHeart3Line, Yn as Image$1, Yt as differenceInSeconds, Z as RiLiveLine, Zn as Hash, Zt as X, _ as use, _n as Shield, _r as Clock, _t as SiHonor, a as drawDither, an as Upload, ar as FilePlay, at as RiTrophyFill, b as install$2, bn as Share2, br as CircleQuestionMark, bt as SiBilibili, c as install$11, cn as Timer, cr as Eye, ct as RiVideoLine, d as install$8, dn as Star, dr as Download, dt as SiVivo, en as Users, er as Gift, et as RiShareForwardFill, f as install$7, fn as Square, fr as Crown, ft as SiSamsung, g as install$5, gn as ShoppingBag, gr as Code, gt as SiHuawei, h as install$6, hn as Smartphone, hr as Copy, ht as SiKuaishou, in as UserPlus, ir as FileText, it as RiTiktokFill, j as MdInfoOutline, jn as QrCode, jr as Braces, jt as p$1, k as react_default, kn as Radio, kr as Camera, kt as a, l as install$10, ln as Terminal, lr as ExternalLink, lt as SiXiaomi, m as install$4, mn as Sparkles, mr as CornerDownLeft, mt as SiOneplus, nn as User, nr as Gamepad2, nt as RiStarLine, o as require_jpeg_js, on as TriangleAlert, or as FileExclamationPoint, ot as RiUserFollowLine, p as install$3, pn as SquarePen, pr as Cpu, pt as SiOppo, q as RiHashtag, qn as LayoutTemplate, qr as HtmlWrapper, qt as formatDistanceToNow, rn as UserRound, rr as FingerprintPattern, rt as RiThumbUpFill, s as require_heic_decode, sn as Trash, sr as EyeOff, st as RiVerifiedBadgeFill, tn as UsersRound, tr as Gauge, tt as RiStarFill, u as install$9, un as Sun, ur as EighthNotes, ut as SiXiaohongshu, v as init, vn as ShieldCheck, vr as Clock3, vt as SiGooglephotos, w as vite_default, wn as Save, wr as CircleCheckBig, wt as r, x as install$1, xn as Settings2, xr as CircleFadingArrowUp, xt as SiApple, y as install, yn as ShieldAlert, yr as Clapperboard, yt as SiGithub, z as BiImage, zn as Monitor, zr as Button, zt as c } from "./vendor.js";
|
|
5
5
|
import { createRequire } from "node:module";
|
|
6
6
|
import fs from "node:fs";
|
|
7
7
|
import path, { resolve } from "node:path";
|
|
@@ -18658,7 +18658,7 @@ var MusicInfoSection = ({ desc, musicId, userCount, createTime }) => {
|
|
|
18658
18658
|
className: "flex flex-col gap-2 text-[45px] text-muted font-light mb-2.5",
|
|
18659
18659
|
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
18660
18660
|
className: "flex gap-2 items-center select-text",
|
|
18661
|
-
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
18661
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(EighthNotes, { className: "w-11 h-11" }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: ["音乐ID: ", musicId] })]
|
|
18662
18662
|
}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
18663
18663
|
className: "flex gap-2 items-center select-text",
|
|
18664
18664
|
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Users, { className: "w-11 h-11" }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [userCount, " 人使用过"] })]
|
|
@@ -23023,7 +23023,7 @@ var iconRegistry = {
|
|
|
23023
23023
|
"lucide:message-circle": MessageCircle,
|
|
23024
23024
|
"lucide:monitor": Monitor,
|
|
23025
23025
|
"lucide:moon": Moon,
|
|
23026
|
-
"lucide:music":
|
|
23026
|
+
"lucide:music": EighthNotes,
|
|
23027
23027
|
"lucide:palette": Palette,
|
|
23028
23028
|
"lucide:pencil": Pencil,
|
|
23029
23029
|
"lucide:play": Play,
|
|
@@ -31253,6 +31253,41 @@ var buildBilibiliRichTextForwardMessage = async (document, options = {}) => {
|
|
|
31253
31253
|
//#endregion
|
|
31254
31254
|
//#region src/platform/bilibili/bilibili.ts
|
|
31255
31255
|
var img$1;
|
|
31256
|
+
/**
|
|
31257
|
+
* B站 PCDN 线路的域名特征。
|
|
31258
|
+
* 这类域名常被代理/去广告规则的 hosts 指向 0.0.0.0,直接请求会 ECONNREFUSED。
|
|
31259
|
+
*/
|
|
31260
|
+
var BILIBILI_PCDN_HOST_RE = /(?:^|\.)(?:mcdn|pcdn)\.bilivideo\.(?:cn|com)$|(?:^|\.)szbdyd\.com$/i;
|
|
31261
|
+
/** B站自有 CDN 域名,PCDN 之外的线路可作为稳定备选 */
|
|
31262
|
+
var BILIBILI_CDN_HOST_RE = /(?:^|\.)bilivideo\.(?:cn|com)$/i;
|
|
31263
|
+
/** 取 URL 的 hostname,非法 URL 返回空串 */
|
|
31264
|
+
var getUrlHostname = (url) => {
|
|
31265
|
+
try {
|
|
31266
|
+
return new URL(url).hostname;
|
|
31267
|
+
} catch {
|
|
31268
|
+
return "";
|
|
31269
|
+
}
|
|
31270
|
+
};
|
|
31271
|
+
/** 判断一条 B站直链是否走 PCDN 线路 */
|
|
31272
|
+
var isBilibiliPcdnUrl = (url) => BILIBILI_PCDN_HOST_RE.test(getUrlHostname(url));
|
|
31273
|
+
/** 判断一条 B站直链是否走 B站自有 CDN */
|
|
31274
|
+
var isBilibiliCdnUrl = (url) => BILIBILI_CDN_HOST_RE.test(getUrlHostname(url));
|
|
31275
|
+
/**
|
|
31276
|
+
* 从播放接口返回的流信息里选一条可用直链。
|
|
31277
|
+
*
|
|
31278
|
+
* `base_url` 可能落在 PCDN 线路上,而 `backup_url` 里通常还有常规 CDN;
|
|
31279
|
+
* 主直链不是 PCDN 时保持原行为,是 PCDN 时优先换到 B站自有 CDN 的备用直链,
|
|
31280
|
+
* 全部不可用时退回 `base_url`。
|
|
31281
|
+
*/
|
|
31282
|
+
var getBilibiliStreamUrl = (stream) => {
|
|
31283
|
+
if (!stream) return void 0;
|
|
31284
|
+
const baseUrl = stream.base_url ?? stream.baseUrl;
|
|
31285
|
+
if (baseUrl && !isBilibiliPcdnUrl(baseUrl)) return baseUrl;
|
|
31286
|
+
const backups = (stream.backup_url ?? stream.backupUrl ?? []).filter((url) => Boolean(url));
|
|
31287
|
+
const officialBackup = backups.find((url) => isBilibiliCdnUrl(url) && !isBilibiliPcdnUrl(url));
|
|
31288
|
+
const normalBackup = backups.find((url) => !isBilibiliPcdnUrl(url));
|
|
31289
|
+
return officialBackup ?? normalBackup ?? baseUrl ?? backups[0];
|
|
31290
|
+
};
|
|
31256
31291
|
/** 评论请求统一使用匿名态,避免账号 Cookie 改变评论热度池结果。 */
|
|
31257
31292
|
var bilibiliAnonymousRequestConfig = { headers: { Cookie: "" } };
|
|
31258
31293
|
/**
|
|
@@ -31375,7 +31410,7 @@ var Bilibili = class extends Base {
|
|
|
31375
31410
|
/** 替换原始的视频信息对象 */
|
|
31376
31411
|
playUrlData.data.data.dash.video = simplify;
|
|
31377
31412
|
/** 没有音频流(如纯视频稿件)时拿不到音频地址,按只统计视频流大小处理 */
|
|
31378
|
-
const audioUrl = playUrlData.data.data.dash.audio?.[0]
|
|
31413
|
+
const audioUrl = getBilibiliStreamUrl(playUrlData.data.data.dash.audio?.[0]);
|
|
31379
31414
|
/** 给视频信息对象删除不符合条件的视频流 */
|
|
31380
31415
|
correctList = await bilibiliProcessVideos({
|
|
31381
31416
|
accept_description: playUrlData.data.data.accept_description,
|
|
@@ -31385,7 +31420,7 @@ var Bilibili = class extends Base {
|
|
|
31385
31420
|
playUrlData.data.data.dash.video = correctList.videoList;
|
|
31386
31421
|
playUrlData.data.data.accept_description = correctList.accept_description;
|
|
31387
31422
|
/** 获取第一个视频流的大小 */
|
|
31388
|
-
videoSize = await getvideosize(correctList.videoList[0]
|
|
31423
|
+
videoSize = await getvideosize(getBilibiliStreamUrl(correctList.videoList[0]), audioUrl, infoData.data.data.bvid);
|
|
31389
31424
|
} else videoSize = (nockData.data.durl[0].size / 1048576).toFixed(2);
|
|
31390
31425
|
if (Config.bilibili.sendContent.some((content) => content === "comment")) {
|
|
31391
31426
|
const commentsData = await softFetch(() => this.amagi.bilibili.fetcher.fetchComments({
|
|
@@ -31526,7 +31561,7 @@ var Bilibili = class extends Base {
|
|
|
31526
31561
|
accept_description: playUrlData.result.accept_description,
|
|
31527
31562
|
bvid: videoInfo.data.result.season_id.toString(),
|
|
31528
31563
|
qn: Config.bilibili.videoQuality
|
|
31529
|
-
}, simplify, playUrlData.result.dash.audio?.[0]
|
|
31564
|
+
}, simplify, getBilibiliStreamUrl(playUrlData.result.dash.audio?.[0]));
|
|
31530
31565
|
playUrlData.result.dash.video = correctList.videoList;
|
|
31531
31566
|
playUrlData.result.cept_description = correctList.accept_description;
|
|
31532
31567
|
}
|
|
@@ -32105,12 +32140,12 @@ var Bilibili = class extends Base {
|
|
|
32105
32140
|
logger$1.debug("是否登录:", this.islogin);
|
|
32106
32141
|
switch (this.islogin) {
|
|
32107
32142
|
case true: {
|
|
32108
|
-
logger$1.debug("视频 URL:", this.Type === "one_video" ? playUrlData.data?.dash?.video[0]
|
|
32143
|
+
logger$1.debug("视频 URL:", this.Type === "one_video" ? getBilibiliStreamUrl(playUrlData.data?.dash?.video[0]) : getBilibiliStreamUrl(playUrlData.result.dash.video[0]));
|
|
32109
32144
|
const downloadHeaders = {
|
|
32110
32145
|
...this.headers,
|
|
32111
32146
|
Referer: "https://www.bilibili.com"
|
|
32112
32147
|
};
|
|
32113
|
-
const bmp4Raw = await downloadFile(this.Type === "one_video" ? playUrlData.data?.dash?.video[0]
|
|
32148
|
+
const bmp4Raw = await downloadFile(this.Type === "one_video" ? getBilibiliStreamUrl(playUrlData.data?.dash?.video[0]) : getBilibiliStreamUrl(playUrlData.result.dash.video[0]), {
|
|
32114
32149
|
title: `Bil_V_${this.Type === "one_video" ? infoData && infoData.data.bvid : infoData && infoData.result.season_id}.m4s`,
|
|
32115
32150
|
headers: downloadHeaders
|
|
32116
32151
|
});
|
|
@@ -32120,7 +32155,7 @@ var Bilibili = class extends Base {
|
|
|
32120
32155
|
return false;
|
|
32121
32156
|
}
|
|
32122
32157
|
await Common.removeFile(bmp4Raw.filepath, true);
|
|
32123
|
-
const audioUrl = this.Type === "one_video" ? playUrlData.data?.dash?.audio?.[0]
|
|
32158
|
+
const audioUrl = this.Type === "one_video" ? getBilibiliStreamUrl(playUrlData.data?.dash?.audio?.[0]) : getBilibiliStreamUrl(playUrlData.result.dash.audio?.[0]);
|
|
32124
32159
|
logger$1.debug("音频 URL:", audioUrl);
|
|
32125
32160
|
/** 没有音频流(如纯视频稿件)时为 undefined,此时无从合成,直接发视频流 */
|
|
32126
32161
|
let bmp3;
|
|
@@ -32465,7 +32500,7 @@ var bilibiliProcessVideos = async (qualityOptions, videoList, audioUrl) => {
|
|
|
32465
32500
|
}
|
|
32466
32501
|
const results = {};
|
|
32467
32502
|
for (const video of videoList) {
|
|
32468
|
-
const size = await getvideosize(video
|
|
32503
|
+
const size = await getvideosize(getBilibiliStreamUrl(video), audioUrl, qualityOptions.bvid);
|
|
32469
32504
|
results[video.id] = size;
|
|
32470
32505
|
}
|
|
32471
32506
|
const sizes = Object.values(results).map((size) => parseFloat(size.replace("MB", "")));
|
|
@@ -33778,7 +33813,7 @@ var Bilibilipush = class extends Base {
|
|
|
33778
33813
|
/** 替换原始的视频信息对象 */
|
|
33779
33814
|
playUrlData.data.data.dash.video = simplify;
|
|
33780
33815
|
/** 没有音频流(如纯视频稿件)时拿不到音频地址,按只统计视频流大小处理 */
|
|
33781
|
-
const audioUrl = playUrlData.data.data.dash.audio?.[0]
|
|
33816
|
+
const audioUrl = getBilibiliStreamUrl(playUrlData.data.data.dash.audio?.[0]);
|
|
33782
33817
|
/** 给视频信息对象删除不符合条件的视频流 */
|
|
33783
33818
|
correctList = await bilibiliProcessVideos({
|
|
33784
33819
|
accept_description: playUrlData.data.data.accept_description,
|
|
@@ -33789,14 +33824,14 @@ var Bilibilipush = class extends Base {
|
|
|
33789
33824
|
playUrlData.data.data.dash.video = correctList.videoList;
|
|
33790
33825
|
playUrlData.data.data.accept_description = correctList.accept_description;
|
|
33791
33826
|
/** 获取第一个视频流的大小 */
|
|
33792
|
-
videoSize = await getvideosize(correctList.videoList[0]
|
|
33827
|
+
videoSize = await getvideosize(getBilibiliStreamUrl(correctList.videoList[0]), audioUrl, data[dynamicId].Dynamic_Data.modules.module_dynamic.major.archive.bvid);
|
|
33793
33828
|
if (Config.app.usefilelimit && Number(videoSize) > Number(Config.app.filelimit) && !Config.app.compress) {
|
|
33794
33829
|
await karin.sendMsg(botId, Contact, [segment.text(`设定的最大上传大小为 ${Config.app.filelimit}MB\n当前解析到的视频大小为 ${Number(videoSize)}MB\n视频太大了,还是去B站看吧~`), segment.reply(status.messageId)]);
|
|
33795
33830
|
break;
|
|
33796
33831
|
}
|
|
33797
33832
|
logger$1.mark(`当前处于自动推送状态,解析到的视频大小为 ${logger$1.yellow(Number(videoSize))} MB`);
|
|
33798
33833
|
const infoData = await this.amagi.bilibili.fetcher.fetchVideoInfo({ bvid: data[dynamicId].Dynamic_Data.modules.module_dynamic.major.archive.bvid });
|
|
33799
|
-
const mp4File = await downloadFile(playUrlData.data?.data?.dash?.video[0]
|
|
33834
|
+
const mp4File = await downloadFile(getBilibiliStreamUrl(playUrlData.data?.data?.dash?.video[0]), {
|
|
33800
33835
|
title: `Bil_V_${infoData.data.data.bvid}.mp4`,
|
|
33801
33836
|
headers: bilibiliBaseHeaders
|
|
33802
33837
|
});
|
|
@@ -37433,6 +37468,26 @@ async function processRecommendList(contentList, sec_uid, userinfo, item, target
|
|
|
37433
37468
|
return result;
|
|
37434
37469
|
}
|
|
37435
37470
|
//#endregion
|
|
37471
|
+
//#region src/platform/douyin/shortUrl.ts
|
|
37472
|
+
/**
|
|
37473
|
+
* 把分享链接换成抖音签发的 `v.douyin.com` 短链(二维码专用)。
|
|
37474
|
+
*
|
|
37475
|
+
* 短链由 amagi 的 `douyin.shortUrl` 端点签发:同一 target 恒得同一短码,
|
|
37476
|
+
* 内容比任何规范链接都短,能明显降低二维码密度。该端点免登录但仍要 `a_bogus` 签名,
|
|
37477
|
+
* 可能因签名或平台策略失败 —— 失败不阻塞推送,回退原链接即可。
|
|
37478
|
+
* @param target - 待缩短的分享链接(规范链接或视频直链均可)
|
|
37479
|
+
* @returns 短链;签发失败时原样返回 target
|
|
37480
|
+
*/
|
|
37481
|
+
var toDouyinShortUrl = async (target) => {
|
|
37482
|
+
try {
|
|
37483
|
+
const shortUrl = (await douyinFetcher.fetchShortUrl({ target })).data?.data;
|
|
37484
|
+
return typeof shortUrl === "string" && shortUrl.length > 0 ? shortUrl : target;
|
|
37485
|
+
} catch (error) {
|
|
37486
|
+
logger$1.debug(`[抖音短链] 签发失败,二维码回退原链接:${error instanceof Error ? error.message : String(error)}`);
|
|
37487
|
+
return target;
|
|
37488
|
+
}
|
|
37489
|
+
};
|
|
37490
|
+
//#endregion
|
|
37436
37491
|
//#region src/platform/douyin/push.ts
|
|
37437
37492
|
var douyinBaseHeaders = {
|
|
37438
37493
|
...BASE_HEADERS,
|
|
@@ -37610,6 +37665,7 @@ var DouYinpush = class extends Base {
|
|
|
37610
37665
|
const playAddr = selectedVideo?.play_addr ?? pushItem.Detail_Data.video?.play_addr;
|
|
37611
37666
|
workShareLink = playAddr ? buildDouyinPlayUrl(playAddr) : `https://www.douyin.com/video/${actualAwemeId}`;
|
|
37612
37667
|
}
|
|
37668
|
+
workShareLink = await toDouyinShortUrl(workShareLink);
|
|
37613
37669
|
}
|
|
37614
37670
|
switch (pushItem.pushType) {
|
|
37615
37671
|
case "live":
|
|
@@ -39280,6 +39336,8 @@ var Xiaohongshu = class extends Base {
|
|
|
39280
39336
|
let staticImgPath = "";
|
|
39281
39337
|
if (staticImg.filepath) staticImgPath = staticImg.filepath;
|
|
39282
39338
|
const livePhotoVideo = xiaohongshuGetLivePhotoVideo(item.stream);
|
|
39339
|
+
/** 这条动图是否已产出至少一个待发送元素(视频 / 实况图 / 静态兜底) */
|
|
39340
|
+
let hasPushedItem = false;
|
|
39283
39341
|
if (livePhotoVideo) {
|
|
39284
39342
|
const livePhotoPath = Common.tempDri.video + `livephoto_${Date.now()}_${index}.mp4`;
|
|
39285
39343
|
const livePhoto = await downloadFile(livePhotoVideo.master_url, {
|
|
@@ -39317,6 +39375,7 @@ var Xiaohongshu = class extends Base {
|
|
|
39317
39375
|
});
|
|
39318
39376
|
const videoPath = Config.app.videoSendMode === "base64" ? `base64://${fs.readFileSync(filePath).toString("base64")}` : `file://${filePath}`;
|
|
39319
39377
|
processedImages.push(segment.video(videoPath));
|
|
39378
|
+
hasPushedItem = true;
|
|
39320
39379
|
}
|
|
39321
39380
|
}
|
|
39322
39381
|
if (shouldGenerateLivePhoto) {
|
|
@@ -39334,6 +39393,7 @@ var Xiaohongshu = class extends Base {
|
|
|
39334
39393
|
});
|
|
39335
39394
|
const motionPhotoCover = processLocalImageFile(motionPhotoCoverPath);
|
|
39336
39395
|
processedImages.push(segment.image(motionPhotoCover));
|
|
39396
|
+
hasPushedItem = true;
|
|
39337
39397
|
hasPushedMotionPhotoCover = true;
|
|
39338
39398
|
hasGeneratedLivePhoto = true;
|
|
39339
39399
|
logger.debug(`小红书实况图生成成功: ${motionPhotoCoverPath}`);
|
|
@@ -39342,12 +39402,17 @@ var Xiaohongshu = class extends Base {
|
|
|
39342
39402
|
if (!hasPushedMotionPhotoCover) {
|
|
39343
39403
|
const imageUrl = await processImageUrl(item.url_default, title, index);
|
|
39344
39404
|
processedImages.push(segment.image(imageUrl));
|
|
39405
|
+
hasPushedItem = true;
|
|
39345
39406
|
}
|
|
39346
39407
|
}
|
|
39347
39408
|
logger.mark("正在尝试删除缓存文件");
|
|
39348
39409
|
await Common.removeFile(livePhoto.filepath, true);
|
|
39349
39410
|
}
|
|
39350
39411
|
}
|
|
39412
|
+
if (!hasPushedItem) {
|
|
39413
|
+
const fallbackImage = staticImgPath ? processLocalImageFile(staticImgPath) : await processImageUrl(item.url_default, title, index);
|
|
39414
|
+
processedImages.push(segment.image(fallbackImage));
|
|
39415
|
+
}
|
|
39351
39416
|
if (staticImgPath) temp.push({
|
|
39352
39417
|
filepath: staticImgPath,
|
|
39353
39418
|
totalBytes: 0
|
|
@@ -39375,6 +39440,10 @@ var Xiaohongshu = class extends Base {
|
|
|
39375
39440
|
} finally {
|
|
39376
39441
|
for (const item of temp) await Common.removeFile(item.filepath, true);
|
|
39377
39442
|
}
|
|
39443
|
+
else {
|
|
39444
|
+
logger.warn(`小红书图集解析未生成可发送内容,note_id=${data.note_id}`);
|
|
39445
|
+
for (const item of temp) await Common.removeFile(item.filepath, true);
|
|
39446
|
+
}
|
|
39378
39447
|
}
|
|
39379
39448
|
if (NoteData.data.data.items[0].note_card.video && Config.xiaohongshu.sendContent.includes("video")) {
|
|
39380
39449
|
const video = NoteData.data.data.items[0].note_card.video;
|
|
@@ -41216,13 +41285,17 @@ var globalStatistics = karin.command(/^#?kkk全局解析统计$/, handleGlobalSt
|
|
|
41216
41285
|
});
|
|
41217
41286
|
//#endregion
|
|
41218
41287
|
//#region src/apps/testPush.ts
|
|
41219
|
-
/**
|
|
41220
|
-
function buildWorkShareLink(aweme, selectedVideo) {
|
|
41288
|
+
/** 构建与生产推送一致的作品二维码短链(签发失败回退原链接)。 */
|
|
41289
|
+
async function buildWorkShareLink(aweme, selectedVideo) {
|
|
41221
41290
|
const workTypeInfo = getWorkTypeInfo(aweme);
|
|
41222
|
-
|
|
41223
|
-
if (workTypeInfo.
|
|
41224
|
-
|
|
41225
|
-
|
|
41291
|
+
let target;
|
|
41292
|
+
if (workTypeInfo.isArticle) target = `https://www.douyin.com/article/${aweme.aweme_id}`;
|
|
41293
|
+
else if (workTypeInfo.isImage) target = `https://www.douyin.com/note/${aweme.aweme_id}`;
|
|
41294
|
+
else {
|
|
41295
|
+
const playAddr = selectedVideo?.play_addr ?? aweme.video?.play_addr;
|
|
41296
|
+
target = playAddr ? buildDouyinPlayUrl(playAddr) : `https://www.douyin.com/video/${aweme.aweme_id}`;
|
|
41297
|
+
}
|
|
41298
|
+
return await toDouyinShortUrl(target);
|
|
41226
41299
|
}
|
|
41227
41300
|
/**
|
|
41228
41301
|
* 测试抖音推送命令处理器
|
|
@@ -41266,7 +41339,7 @@ var handleTestPush = wrapWithErrorHandler(async (e) => {
|
|
|
41266
41339
|
const aweme = workData.data.aweme_detail;
|
|
41267
41340
|
const Detail_Data = buildDouyinWorkDetail(aweme, { user_info: (await douyinFetcher.fetchUserProfile({ sec_uid: aweme.author.sec_uid })).data });
|
|
41268
41341
|
const selectedVideo = aweme.video?.bit_rate?.length ? douyinProcessVideos(aweme.video.bit_rate, Config.douyin.videoQuality)[0] : null;
|
|
41269
|
-
const shareLink = buildWorkShareLink(Detail_Data, selectedVideo);
|
|
41342
|
+
const shareLink = await buildWorkShareLink(Detail_Data, selectedVideo);
|
|
41270
41343
|
images = await renderWorkImage({
|
|
41271
41344
|
e,
|
|
41272
41345
|
Detail_Data,
|
|
@@ -41305,7 +41378,7 @@ var handleTestPush = wrapWithErrorHandler(async (e) => {
|
|
|
41305
41378
|
user_info: userinfo.data,
|
|
41306
41379
|
author_user_info: authorUserInfo
|
|
41307
41380
|
});
|
|
41308
|
-
const shareLink = buildWorkShareLink(Detail_Data, aweme.video?.bit_rate?.length ? douyinProcessVideos(aweme.video.bit_rate, Config.douyin.videoQuality)[0] : null);
|
|
41381
|
+
const shareLink = await buildWorkShareLink(Detail_Data, aweme.video?.bit_rate?.length ? douyinProcessVideos(aweme.video.bit_rate, Config.douyin.videoQuality)[0] : null);
|
|
41309
41382
|
images = await renderFavoriteImage({
|
|
41310
41383
|
e,
|
|
41311
41384
|
Detail_Data,
|
|
@@ -41344,7 +41417,7 @@ var handleTestPush = wrapWithErrorHandler(async (e) => {
|
|
|
41344
41417
|
user_info: userinfo.data,
|
|
41345
41418
|
author_user_info: authorUserInfo
|
|
41346
41419
|
});
|
|
41347
|
-
const shareLink = buildWorkShareLink(Detail_Data, aweme.video?.bit_rate?.length ? douyinProcessVideos(aweme.video.bit_rate, Config.douyin.videoQuality)[0] : null);
|
|
41420
|
+
const shareLink = await buildWorkShareLink(Detail_Data, aweme.video?.bit_rate?.length ? douyinProcessVideos(aweme.video.bit_rate, Config.douyin.videoQuality)[0] : null);
|
|
41348
41421
|
images = await renderRecommendImage({
|
|
41349
41422
|
e,
|
|
41350
41423
|
Detail_Data,
|