koishi-plugin-bilibili-notify 3.2.8-alpha.2 → 3.2.9-alpha.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/lib/index.js CHANGED
@@ -39,6 +39,7 @@ const path = __toESM$1(require("path"));
39
39
  const qrcode = __toESM$1(require("qrcode"));
40
40
  const cron = __toESM$1(require("cron"));
41
41
  const luxon = __toESM$1(require("luxon"));
42
+ const segmentit = __toESM$1(require("segmentit"));
42
43
  const __satorijs_element_jsx_runtime = __toESM$1(require("@satorijs/element/jsx-runtime"));
43
44
  const node_path = __toESM$1(require("node:path"));
44
45
  const node_url = __toESM$1(require("node:url"));
@@ -646,6 +647,7 @@ var ComRegister = class {
646
647
  privateBot;
647
648
  dynamicJob;
648
649
  liveJob;
650
+ _segmentit = (0, segmentit.useDefault)(new segmentit.Segment());
649
651
  constructor(ctx, config) {
650
652
  this.ctx = ctx;
651
653
  this.init(config);
@@ -1221,10 +1223,16 @@ var ComRegister = class {
1221
1223
  const msg = /* @__PURE__ */ (0, __satorijs_element_jsx_runtime.jsxs)("message", { children: [koishi.h.image(buffer, "image/jpeg"), liveNotifyMsg || ""] });
1222
1224
  return await this.broadcastToTargets(uid, msg, liveType === LiveType.StartBroadcasting ? PushType.StartBroadcasting : PushType.Live);
1223
1225
  }
1226
+ async segmentDanmaku(danmaku, danmakuWeightRecord) {
1227
+ this._segmentit.doSegment(danmaku).map(({ w: w$3, p: p$1 }) => {
1228
+ if (p$1 && p$1 === 2048) return;
1229
+ danmakuWeightRecord[w$3] = (danmakuWeightRecord[w$3] || 0) + 1;
1230
+ });
1231
+ }
1224
1232
  async liveDetectWithListener(roomId, uid, cardStyle) {
1225
1233
  let liveTime;
1226
1234
  let pushAtTimeTimer;
1227
- const currentLiveDanmakuArr = [];
1235
+ const danmakuWeightRecord = {};
1228
1236
  let liveStatus = false;
1229
1237
  let liveRoomInfo;
1230
1238
  let masterInfo;
@@ -1275,10 +1283,10 @@ var ComRegister = class {
1275
1283
  this.logger.error(`[${roomId}]直播间连接发生错误!`);
1276
1284
  },
1277
1285
  onIncomeDanmu: ({ body }) => {
1278
- currentLiveDanmakuArr.push(body.content);
1286
+ this.segmentDanmaku(body.content, danmakuWeightRecord);
1279
1287
  },
1280
1288
  onIncomeSuperChat: ({ body }) => {
1281
- currentLiveDanmakuArr.push(body.content);
1289
+ this.segmentDanmaku(body.content, danmakuWeightRecord);
1282
1290
  },
1283
1291
  onWatchedChange: ({ body }) => {
1284
1292
  watchedNum = body.text_small;
@@ -1333,6 +1341,9 @@ var ComRegister = class {
1333
1341
  }, uid, liveEndMsg);
1334
1342
  pushAtTimeTimer();
1335
1343
  pushAtTimeTimer = null;
1344
+ const words = Object.entries(danmakuWeightRecord);
1345
+ const buffer = await this.ctx.gi.generateWordCloudImg(words, masterInfo.username);
1346
+ await this.broadcastToTargets(uid, koishi.h.image(buffer, "image/jpeg"), PushType.Live);
1336
1347
  }
1337
1348
  };
1338
1349
  await this.ctx.bl.startLiveRoomListener(roomId, handler);
@@ -94312,6 +94323,118 @@ var GenerateImg = class extends koishi.Service {
94312
94323
  throw new Error(`生成图片失败!错误: ${e$1.toString()}`);
94313
94324
  });
94314
94325
  }
94326
+ async generateWordCloudImg(words, masterName) {
94327
+ const html = `
94328
+ <!DOCTYPE html>
94329
+ <html lang="zh-CN">
94330
+
94331
+ <head>
94332
+ <meta charset="UTF-8">
94333
+ <title>高清词云展示</title>
94334
+ <link href="https://fonts.googleapis.com/css2?family=Quicksand:wght@500&display=swap" rel="stylesheet">
94335
+ <style>
94336
+ * {
94337
+ margin: 0;
94338
+ padding: 0;
94339
+ box-sizing: border-box;
94340
+ }
94341
+
94342
+ html {
94343
+ width: 720px;
94344
+ height: 520px;
94345
+ }
94346
+
94347
+ .wordcloud-bg {
94348
+ width: 720px;
94349
+ height: 520px;
94350
+ background: linear-gradient(to right, #e0eafc, #cfdef3);
94351
+ font-family: 'Quicksand', sans-serif;
94352
+ display: flex;
94353
+ justify-content: center;
94354
+ align-items: center;
94355
+ }
94356
+
94357
+ .wordcloud-card {
94358
+ width: 700px;
94359
+ height: 500px;
94360
+ backdrop-filter: blur(10px);
94361
+ background: rgba(255, 255, 255, 0.25);
94362
+ border-radius: 20px;
94363
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
94364
+ padding: 20px;
94365
+ display: flex;
94366
+ flex-direction: column;
94367
+ align-items: center;
94368
+ justify-content: center;
94369
+ }
94370
+
94371
+ h2 {
94372
+ margin: 0 0 10px;
94373
+ color: #333;
94374
+ font-size: 24px;
94375
+ }
94376
+
94377
+ canvas {
94378
+ width: 100%;
94379
+ height: 100%;
94380
+ display: block;
94381
+ }
94382
+ </style>
94383
+ </head>
94384
+
94385
+ <body>
94386
+ <div class="wordcloud-bg">
94387
+ <div class="wordcloud-card">
94388
+ <h2>${masterName}直播弹幕词云</h2>
94389
+ <canvas id="wordCloudCanvas"></canvas>
94390
+ </div>
94391
+ </div>
94392
+
94393
+ <script src="https://cdn.jsdelivr.net/npm/wordcloud@1.1.2/src/wordcloud2.min.js"></script>
94394
+ <script>
94395
+ const canvas = document.getElementById('wordCloudCanvas');
94396
+ const ctx = canvas.getContext('2d');
94397
+
94398
+ // 获取 CSS 大小
94399
+ const style = getComputedStyle(canvas);
94400
+ const cssWidth = parseInt(style.width);
94401
+ const cssHeight = parseInt(style.height);
94402
+ const ratio = window.devicePixelRatio || 1;
94403
+
94404
+ // 设置 canvas 分辨率 & 缩放
94405
+ canvas.width = cssWidth * ratio;
94406
+ canvas.height = cssHeight * ratio;
94407
+ ctx.scale(ratio, ratio);
94408
+
94409
+ const words = ${JSON.stringify(words)}
94410
+
94411
+ WordCloud(canvas, {
94412
+ list: words,
94413
+ gridSize: Math.round(8 * (cssWidth / 1024)), // 自动适配大小
94414
+ weightFactor: size => size * (cssWidth / 1024) * 1.2,
94415
+ fontFamily: 'Quicksand, sans-serif',
94416
+ color: () => {
94417
+ const colors = ['#007CF0', '#00DFD8', '#7928CA', '#FF0080', '#FF4D4D', '#F9CB28'];
94418
+ return colors[Math.floor(Math.random() * colors.length)];
94419
+ },
94420
+ rotateRatio: 0.5,
94421
+ rotationSteps: 2,
94422
+ backgroundColor: 'transparent',
94423
+ drawOutOfBound: false,
94424
+ origin: [cssWidth / 2, cssHeight / 2], // 居中关键点
94425
+ // 明确告诉 wordcloud2 使用这个宽高(以 CSS 尺寸为准)
94426
+ width: cssWidth,
94427
+ height: cssHeight,
94428
+ });
94429
+ </script>
94430
+ </body>
94431
+
94432
+ </html>
94433
+ `;
94434
+ return await withRetry(() => this.imgHandler(html)).catch((e$1) => {
94435
+ throw new Error(`生成图片失败!错误: ${e$1.toString()}`);
94436
+ });
94437
+ }
94315
94438
  async getLiveStatus(time, liveStatus) {
94316
94439
  let titleStatus;
94317
94440
  let liveTime;
package/lib/index.mjs CHANGED
@@ -4,6 +4,7 @@ import { resolve } from "path";
4
4
  import QRCode from "qrcode";
5
5
  import { CronJob } from "cron";
6
6
  import { DateTime } from "luxon";
7
+ import { Segment, useDefault } from "segmentit";
7
8
  import { Fragment, jsx, jsxs } from "@satorijs/element/jsx-runtime";
8
9
  import { resolve as resolve$1 } from "node:path";
9
10
  import { pathToFileURL } from "node:url";
@@ -648,6 +649,7 @@ var ComRegister = class {
648
649
  privateBot;
649
650
  dynamicJob;
650
651
  liveJob;
652
+ _segmentit = useDefault(new Segment());
651
653
  constructor(ctx, config) {
652
654
  this.ctx = ctx;
653
655
  this.init(config);
@@ -1223,10 +1225,16 @@ var ComRegister = class {
1223
1225
  const msg = /* @__PURE__ */ jsxs("message", { children: [h.image(buffer, "image/jpeg"), liveNotifyMsg || ""] });
1224
1226
  return await this.broadcastToTargets(uid, msg, liveType === LiveType.StartBroadcasting ? PushType.StartBroadcasting : PushType.Live);
1225
1227
  }
1228
+ async segmentDanmaku(danmaku, danmakuWeightRecord) {
1229
+ this._segmentit.doSegment(danmaku).map(({ w: w$3, p: p$1 }) => {
1230
+ if (p$1 && p$1 === 2048) return;
1231
+ danmakuWeightRecord[w$3] = (danmakuWeightRecord[w$3] || 0) + 1;
1232
+ });
1233
+ }
1226
1234
  async liveDetectWithListener(roomId, uid, cardStyle) {
1227
1235
  let liveTime;
1228
1236
  let pushAtTimeTimer;
1229
- const currentLiveDanmakuArr = [];
1237
+ const danmakuWeightRecord = {};
1230
1238
  let liveStatus = false;
1231
1239
  let liveRoomInfo;
1232
1240
  let masterInfo;
@@ -1277,10 +1285,10 @@ var ComRegister = class {
1277
1285
  this.logger.error(`[${roomId}]直播间连接发生错误!`);
1278
1286
  },
1279
1287
  onIncomeDanmu: ({ body }) => {
1280
- currentLiveDanmakuArr.push(body.content);
1288
+ this.segmentDanmaku(body.content, danmakuWeightRecord);
1281
1289
  },
1282
1290
  onIncomeSuperChat: ({ body }) => {
1283
- currentLiveDanmakuArr.push(body.content);
1291
+ this.segmentDanmaku(body.content, danmakuWeightRecord);
1284
1292
  },
1285
1293
  onWatchedChange: ({ body }) => {
1286
1294
  watchedNum = body.text_small;
@@ -1335,6 +1343,9 @@ var ComRegister = class {
1335
1343
  }, uid, liveEndMsg);
1336
1344
  pushAtTimeTimer();
1337
1345
  pushAtTimeTimer = null;
1346
+ const words = Object.entries(danmakuWeightRecord);
1347
+ const buffer = await this.ctx.gi.generateWordCloudImg(words, masterInfo.username);
1348
+ await this.broadcastToTargets(uid, h.image(buffer, "image/jpeg"), PushType.Live);
1338
1349
  }
1339
1350
  };
1340
1351
  await this.ctx.bl.startLiveRoomListener(roomId, handler);
@@ -94314,6 +94325,118 @@ var GenerateImg = class extends Service {
94314
94325
  throw new Error(`生成图片失败!错误: ${e$1.toString()}`);
94315
94326
  });
94316
94327
  }
94328
+ async generateWordCloudImg(words, masterName) {
94329
+ const html = `
94330
+ <!DOCTYPE html>
94331
+ <html lang="zh-CN">
94332
+
94333
+ <head>
94334
+ <meta charset="UTF-8">
94335
+ <title>高清词云展示</title>
94336
+ <link href="https://fonts.googleapis.com/css2?family=Quicksand:wght@500&display=swap" rel="stylesheet">
94337
+ <style>
94338
+ * {
94339
+ margin: 0;
94340
+ padding: 0;
94341
+ box-sizing: border-box;
94342
+ }
94343
+
94344
+ html {
94345
+ width: 720px;
94346
+ height: 520px;
94347
+ }
94348
+
94349
+ .wordcloud-bg {
94350
+ width: 720px;
94351
+ height: 520px;
94352
+ background: linear-gradient(to right, #e0eafc, #cfdef3);
94353
+ font-family: 'Quicksand', sans-serif;
94354
+ display: flex;
94355
+ justify-content: center;
94356
+ align-items: center;
94357
+ }
94358
+
94359
+ .wordcloud-card {
94360
+ width: 700px;
94361
+ height: 500px;
94362
+ backdrop-filter: blur(10px);
94363
+ background: rgba(255, 255, 255, 0.25);
94364
+ border-radius: 20px;
94365
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
94366
+ padding: 20px;
94367
+ display: flex;
94368
+ flex-direction: column;
94369
+ align-items: center;
94370
+ justify-content: center;
94371
+ }
94372
+
94373
+ h2 {
94374
+ margin: 0 0 10px;
94375
+ color: #333;
94376
+ font-size: 24px;
94377
+ }
94378
+
94379
+ canvas {
94380
+ width: 100%;
94381
+ height: 100%;
94382
+ display: block;
94383
+ }
94384
+ </style>
94385
+ </head>
94386
+
94387
+ <body>
94388
+ <div class="wordcloud-bg">
94389
+ <div class="wordcloud-card">
94390
+ <h2>${masterName}直播弹幕词云</h2>
94391
+ <canvas id="wordCloudCanvas"></canvas>
94392
+ </div>
94393
+ </div>
94394
+
94395
+ <script src="https://cdn.jsdelivr.net/npm/wordcloud@1.1.2/src/wordcloud2.min.js"></script>
94396
+ <script>
94397
+ const canvas = document.getElementById('wordCloudCanvas');
94398
+ const ctx = canvas.getContext('2d');
94399
+
94400
+ // 获取 CSS 大小
94401
+ const style = getComputedStyle(canvas);
94402
+ const cssWidth = parseInt(style.width);
94403
+ const cssHeight = parseInt(style.height);
94404
+ const ratio = window.devicePixelRatio || 1;
94405
+
94406
+ // 设置 canvas 分辨率 & 缩放
94407
+ canvas.width = cssWidth * ratio;
94408
+ canvas.height = cssHeight * ratio;
94409
+ ctx.scale(ratio, ratio);
94410
+
94411
+ const words = ${JSON.stringify(words)}
94412
+
94413
+ WordCloud(canvas, {
94414
+ list: words,
94415
+ gridSize: Math.round(8 * (cssWidth / 1024)), // 自动适配大小
94416
+ weightFactor: size => size * (cssWidth / 1024) * 1.2,
94417
+ fontFamily: 'Quicksand, sans-serif',
94418
+ color: () => {
94419
+ const colors = ['#007CF0', '#00DFD8', '#7928CA', '#FF0080', '#FF4D4D', '#F9CB28'];
94420
+ return colors[Math.floor(Math.random() * colors.length)];
94421
+ },
94422
+ rotateRatio: 0.5,
94423
+ rotationSteps: 2,
94424
+ backgroundColor: 'transparent',
94425
+ drawOutOfBound: false,
94426
+ origin: [cssWidth / 2, cssHeight / 2], // 居中关键点
94427
+ // 明确告诉 wordcloud2 使用这个宽高(以 CSS 尺寸为准)
94428
+ width: cssWidth,
94429
+ height: cssHeight,
94430
+ });
94431
+ </script>
94432
+ </body>
94433
+
94434
+ </html>
94435
+ `;
94436
+ return await withRetry(() => this.imgHandler(html)).catch((e$1) => {
94437
+ throw new Error(`生成图片失败!错误: ${e$1.toString()}`);
94438
+ });
94439
+ }
94317
94440
  async getLiveStatus(time, liveStatus) {
94318
94441
  let titleStatus;
94319
94442
  let liveTime;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-bilibili-notify",
3
3
  "description": "Koishi bilibili notify plugin",
4
- "version": "3.2.8-alpha.2",
4
+ "version": "3.2.9-alpha.0",
5
5
  "contributors": [
6
6
  "Akokko <admin@akokko.com>"
7
7
  ],
@@ -45,6 +45,7 @@
45
45
  "luxon": "^3.6.1",
46
46
  "md5": "^2.3.0",
47
47
  "qrcode": "^1.5.4",
48
+ "segmentit": "^2.0.3",
48
49
  "tough-cookie": "^5.1.2"
49
50
  },
50
51
  "devDependencies": {
package/readme.md CHANGED
@@ -299,6 +299,10 @@ uid为必填参数,为要推送的UP主的UID,index为可选参数,为要
299
299
  > - ver 3.2.8-alpha.1 修复:直播推送没有推送语;
300
300
  > - ver 3.2.8-alpha.2 优化:直播推送语中,会换行所有换行符而不是第一个,其余参数仍只会替换第一个
301
301
 
302
+ > [!CAUTION]
303
+ > - ver 3.2.9-alpha.0 新增:弹幕词云; 不建议更新,目前仅做测试用!
304
+
305
+
302
306
  ## 交流群
303
307
 
304
308
  > [!TIP]