karin-plugin-kkk 2.32.3 → 2.33.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.
@@ -18863,20 +18863,50 @@ var AmbientBackground$1 = import_react.memo(({ pic }) => /* @__PURE__ */ (0, imp
18863
18863
  ]
18864
18864
  }));
18865
18865
  AmbientBackground$1.displayName = "AmbientBackground";
18866
+ var MAX_DANMAKU_ROWS = 4;
18867
+ /**
18868
+ * 封面弹幕层:模拟视频平台截图中的滚动弹幕轨道,行数由 MAX_DANMAKU_ROWS 控制。
18869
+ */
18870
+ var DanmakuOverlay = import_react.memo(({ items }) => {
18871
+ if (!items.length) return null;
18872
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
18873
+ className: "pointer-events-none absolute inset-x-0 top-0 z-20 overflow-hidden px-16",
18874
+ style: {
18875
+ height: `calc(${MAX_DANMAKU_ROWS} * 20rem)`,
18876
+ maskImage: "linear-gradient(to right, transparent 0, black 3rem, black calc(100% - 10rem), transparent 100%)",
18877
+ WebkitMaskImage: "linear-gradient(to right, transparent 0, black 3rem, black calc(100% - 10rem), transparent 100%)"
18878
+ },
18879
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
18880
+ className: "relative pt-8 overflow-visible whitespace-normal text-[0] leading-16",
18881
+ children: items.map((item, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
18882
+ className: "mr-20 inline-block shrink-0 whitespace-nowrap align-top text-4xl font-black leading-16 text-white",
18883
+ style: {
18884
+ marginLeft: index === 0 ? void 0 : `${(index * 5 + item.content.length * 3) % 5 * 2}rem`,
18885
+ opacity: Math.max(.2, 1 - index * .025),
18886
+ textShadow: "0 3px 8px rgba(0,0,0,0.85), 0 0 2px rgba(0,0,0,0.95)",
18887
+ WebkitTextStroke: "1px rgba(0,0,0,0.58)"
18888
+ },
18889
+ children: item.content
18890
+ }, `${item.content}-${index}`))
18891
+ })
18892
+ });
18893
+ });
18894
+ DanmakuOverlay.displayName = "DanmakuOverlay";
18866
18895
  var BilibiliVideoInfo = import_react.memo((props) => {
18867
18896
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DefaultLayout, {
18868
18897
  ...props,
18869
18898
  className: "relative overflow-hidden",
18870
18899
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(AmbientBackground$1, { pic: props.data.pic }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
18871
18900
  className: "relative z-10",
18872
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
18901
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
18902
+ className: "relative overflow-hidden",
18873
18903
  style: coverMaskStyle$1,
18874
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EnhancedImage, {
18904
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(EnhancedImage, {
18875
18905
  src: props.data.pic,
18876
18906
  alt: props.data.title,
18877
18907
  className: "object-cover w-full",
18878
18908
  placeholder: "视频封面"
18879
- })
18909
+ }), props.data.hotDanmaku && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DanmakuOverlay, { items: props.data.hotDanmaku })]
18880
18910
  }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
18881
18911
  className: "flex flex-col gap-10 px-16 pt-20",
18882
18912
  children: [
@@ -35288,6 +35318,30 @@ async function getBiliFrameRate(path) {
35288
35318
  } catch {}
35289
35319
  return 30;
35290
35320
  }
35321
+ /**
35322
+ * 统计用于视频信息封面展示的弹幕(相同内容聚合)
35323
+ * @param danmakuList 弹幕列表
35324
+ * @param topN 返回的条数,默认 5
35325
+ * @returns 重复弹幕优先,不足时用真实弹幕按出现顺序补齐
35326
+ */
35327
+ function getHotDanmaku(danmakuList, topN = 5) {
35328
+ const counter = /* @__PURE__ */ new Map();
35329
+ const firstSeen = /* @__PURE__ */ new Map();
35330
+ const sortedDanmaku = [...danmakuList].sort((a, b) => a.progress - b.progress);
35331
+ for (const dm of sortedDanmaku) {
35332
+ const content = dm.content?.trim();
35333
+ if (!content) continue;
35334
+ if (!firstSeen.has(content)) firstSeen.set(content, dm.progress);
35335
+ counter.set(content, (counter.get(content) ?? 0) + 1);
35336
+ }
35337
+ return [...counter.entries()].map(([content, count]) => ({
35338
+ content,
35339
+ count
35340
+ })).sort((a, b) => {
35341
+ if (b.count !== a.count) return b.count - a.count;
35342
+ return (firstSeen.get(a.content) ?? 0) - (firstSeen.get(b.content) ?? 0);
35343
+ }).slice(0, topN);
35344
+ }
35291
35345
  /** 字号配置映射 */
35292
35346
  var FONT_SIZE_MAP$1 = {
35293
35347
  small: {
@@ -36057,6 +36111,9 @@ var Bilibili = class extends Base {
36057
36111
  host_mid: infoData.data.data.owner.mid,
36058
36112
  typeMode: "strict"
36059
36113
  });
36114
+ const danmakuCid = iddata.p ? infoData.data.data.pages[iddata.p - 1]?.cid ?? infoData.data.data.cid : infoData.data.data.cid;
36115
+ const danmakuDuration = iddata.p ? infoData.data.data.pages[iddata.p - 1]?.duration ?? infoData.data.data.duration : infoData.data.data.duration;
36116
+ const hotDanmaku = getHotDanmaku(await this.fetchVideoDanmakuList(danmakuCid, danmakuDuration), 20);
36060
36117
  const img = await Render(this.e, "bilibili/videoInfo", {
36061
36118
  share_url: "https://b23.tv/" + infoData.data.data.bvid,
36062
36119
  title: infoData.data.data.title,
@@ -36065,6 +36122,7 @@ var Bilibili = class extends Base {
36065
36122
  bvid: infoData.data.data.bvid,
36066
36123
  ctime: infoData.data.data.ctime,
36067
36124
  pic: infoData.data.data.pic,
36125
+ hotDanmaku,
36068
36126
  owner: {
36069
36127
  ...infoData.data.data.owner,
36070
36128
  usernameMeta: getUsernameMetadata(userProfileData.data.data.card),
@@ -36140,20 +36198,10 @@ var Bilibili = class extends Base {
36140
36198
  else {
36141
36199
  if (Config.bilibili.videoQuality !== 0 && Config.bilibili.videoQuality < 64) this.islogin = false;
36142
36200
  let danmakuList = [];
36143
- if (this.forceBurnDanmaku || Config.bilibili.burnDanmaku) try {
36201
+ if (this.forceBurnDanmaku || Config.bilibili.burnDanmaku) {
36144
36202
  const cid = iddata.p ? infoData.data.data.pages[iddata.p - 1]?.cid ?? infoData.data.data.cid : infoData.data.data.cid;
36145
36203
  const duration = iddata.p ? infoData.data.data.pages[iddata.p - 1]?.duration ?? infoData.data.data.duration : infoData.data.data.duration;
36146
- const segmentCount = Math.ceil(duration / 360);
36147
- logger.debug(`视频时长: ${duration}秒, 需要获取 ${segmentCount} 个弹幕分段`);
36148
- const danmakuPromises = Array.from({ length: segmentCount }, (_, i) => this.amagi.bilibili.fetcher.fetchVideoDanmaku({
36149
- cid,
36150
- segment_index: i + 1,
36151
- typeMode: "strict"
36152
- }).then((res) => res.data?.data?.elems || []).catch(() => []));
36153
- danmakuList = (await Promise.all(danmakuPromises)).flat();
36154
- logger.debug(`获取到 ${danmakuList.length} 条弹幕(${segmentCount} 个分段)`);
36155
- } catch (err) {
36156
- logger.warn("获取弹幕失败,将不烧录弹幕", err);
36204
+ danmakuList = await this.fetchVideoDanmakuList(cid, duration);
36157
36205
  }
36158
36206
  await this.getvideo(Config.bilibili.videoQuality !== 0 && Config.bilibili.videoQuality < 64 ? {
36159
36207
  playUrlData: nockData.data,
@@ -36821,6 +36869,29 @@ var Bilibili = class extends Base {
36821
36869
  default: break;
36822
36870
  }
36823
36871
  }
36872
+ /**
36873
+ * 获取视频弹幕列表(按每 6 分钟一段并行拉取所有分段)
36874
+ * @param cid 视频分P的 cid
36875
+ * @param duration 视频时长(秒)
36876
+ * @returns 合并后的弹幕列表
36877
+ */
36878
+ async fetchVideoDanmakuList(cid, duration) {
36879
+ try {
36880
+ const segmentCount = Math.ceil(duration / 360);
36881
+ logger.debug(`视频时长: ${duration}秒, 需要获取 ${segmentCount} 个弹幕分段`);
36882
+ const danmakuPromises = Array.from({ length: segmentCount }, (_, i) => this.amagi.bilibili.fetcher.fetchVideoDanmaku({
36883
+ cid,
36884
+ segment_index: i + 1,
36885
+ typeMode: "strict"
36886
+ }).then((res) => res.data?.data?.elems || []).catch(() => []));
36887
+ const danmakuList = (await Promise.all(danmakuPromises)).flat();
36888
+ logger.debug(`获取到 ${danmakuList.length} 条弹幕(${segmentCount} 个分段)`);
36889
+ return danmakuList;
36890
+ } catch (err) {
36891
+ logger.warn("获取弹幕失败", err);
36892
+ return [];
36893
+ }
36894
+ }
36824
36895
  async getvideo({ infoData, playUrlData, danmakuList = [] }) {
36825
36896
  /** 获取视频 => FFmpeg合成 */
36826
36897
  logger.debug("是否登录:", this.islogin);
@@ -1257,6 +1257,15 @@ interface BilibiliVideoOwner {
1257
1257
  /** 头像框图片URL */
1258
1258
  frame?: string;
1259
1259
  }
1260
+ /**
1261
+ * 热门弹幕项接口(相同内容的弹幕聚合)
1262
+ */
1263
+ interface BilibiliHotDanmaku {
1264
+ /** 弹幕内容 */
1265
+ content: string;
1266
+ /** 出现次数 */
1267
+ count: number;
1268
+ }
1260
1269
  /**
1261
1270
  * B站视频信息数据接口
1262
1271
  */
@@ -1277,6 +1286,8 @@ interface BilibiliVideoInfoData {
1277
1286
  pic: string;
1278
1287
  /** UP主信息 */
1279
1288
  owner: BilibiliVideoOwner;
1289
+ /** 出现次数最多的热门弹幕(可选,按次数降序) */
1290
+ hotDanmaku?: BilibiliHotDanmaku[];
1280
1291
  }
1281
1292
  /**
1282
1293
  * B站视频信息组件属性接口