koishi-plugin-chat-analyse 0.5.0 → 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,88 @@
1
+ import { Context } from 'koishi';
2
+ import { Config } from './index';
3
+ declare module 'koishi' {
4
+ interface Tables {
5
+ analyse_user: {
6
+ uid: number;
7
+ channelId: string;
8
+ userId: string;
9
+ channelName: string;
10
+ userName: string;
11
+ };
12
+ analyse_cmd: {
13
+ uid: number;
14
+ command: string;
15
+ count: number;
16
+ timestamp: Date;
17
+ };
18
+ analyse_msg: {
19
+ uid: number;
20
+ type: string;
21
+ count: number;
22
+ timestamp: Date;
23
+ };
24
+ analyse_rank: {
25
+ uid: number;
26
+ type: string;
27
+ count: number;
28
+ timestamp: Date;
29
+ };
30
+ analyse_cache: {
31
+ id: number;
32
+ uid: number;
33
+ content: string;
34
+ timestamp: Date;
35
+ };
36
+ analyse_at: {
37
+ id: number;
38
+ uid: number;
39
+ target: string;
40
+ content: string;
41
+ timestamp: Date;
42
+ };
43
+ }
44
+ }
45
+ /**
46
+ * @class Collector
47
+ * @description 核心数据收集器。根据配置,高效地监听、收集、缓冲并持久化聊天数据。
48
+ */
49
+ export declare class Collector {
50
+ private ctx;
51
+ private config;
52
+ /** @property FLUSH_INTERVAL - 内存缓存区定时刷入数据库的间隔(毫秒)。 */
53
+ private static readonly FLUSH_INTERVAL;
54
+ /** @property BUFFER_THRESHOLD - 内存缓存区触发刷新的消息数量阈值。 */
55
+ private static readonly BUFFER_THRESHOLD;
56
+ private msgStatBuffer;
57
+ private rankStatBuffer;
58
+ private cmdStatBuffer;
59
+ private oriCacheBuffer;
60
+ private whoAtBuffer;
61
+ private userCache;
62
+ private channelCache;
63
+ private pendingRequests;
64
+ private flushInterval;
65
+ /**
66
+ * @param ctx - Koishi 的插件上下文。
67
+ * @param config - 插件的配置对象。
68
+ */
69
+ constructor(ctx: Context, config: Config);
70
+ /**
71
+ * @private @method onMessage
72
+ * @description 统一的消息事件处理器,解析消息并更新各类统计数据的缓冲区。
73
+ * @param session - Koishi 的会话对象。
74
+ */
75
+ private onMessage;
76
+ /**
77
+ * @private @method sanitizeContent
78
+ * @description 将 Koishi 消息元素数组净化为纯文本字符串。
79
+ * @param elements - 消息元素数组。
80
+ * @returns 净化后的纯文本。
81
+ */
82
+ private sanitizeContent;
83
+ /**
84
+ * @private @method flushBuffers
85
+ * @description 将所有内存中的数据缓冲区批量写入数据库,并清空缓冲区。
86
+ */
87
+ private flushBuffers;
88
+ }
@@ -0,0 +1,49 @@
1
+ import { Context } from 'koishi';
2
+ /** 定义了渲染列表中单行数据的格式,是一个由字符串、数字或 `Date` 对象构成的数组。 */
3
+ export type RenderListItem = (string | number | Date)[];
4
+ /**
5
+ * @interface ListRenderData
6
+ * @description 定义了调用 `renderList` 方法所需的数据结构,包含了渲染一张完整列表图片所必需的所有信息。
7
+ */
8
+ export interface ListRenderData {
9
+ title: string;
10
+ time: Date;
11
+ total?: string | number;
12
+ list: RenderListItem[];
13
+ }
14
+ /**
15
+ * @class Renderer
16
+ * @description 负责将结构化的列表数据渲染为设计精美的 PNG 图片。其核心特性是能够动态计算内容尺寸,生成布局紧凑、自适应的图片。
17
+ */
18
+ export declare class Renderer {
19
+ private ctx;
20
+ /**
21
+ * @param ctx - Koishi 的插件上下文,用于访问 `puppeteer` 等核心服务。
22
+ */
23
+ constructor(ctx: Context);
24
+ /**
25
+ * @private
26
+ * @method htmlToImage
27
+ * @description 将一个完整的 HTML 文档字符串转换为 PNG 图片 Buffer。
28
+ * @param fullHtmlContent - 要渲染的、包含 `<html>...</html>` 的完整HTML字符串。
29
+ * @returns 返回一个包含 PNG 图片数据的 Buffer,失败则返回 null。
30
+ */
31
+ private htmlToImage;
32
+ /**
33
+ * @private
34
+ * @method formatDate
35
+ * @description 将 `Date` 对象格式化为易于理解的相对时间或绝对日期字符串。
36
+ * @param date - 需要格式化的日期对象。
37
+ * @returns 格式化后的时间字符串。
38
+ */
39
+ private formatDate;
40
+ /**
41
+ * @public
42
+ * @method renderList
43
+ * @description 构建并渲染一个包含标题、统计信息和数据表格的 HTML 卡片为图片。如果数据过多,则会分片渲染成多张图片。
44
+ * @param data - 包含渲染所需全部信息的对象。
45
+ * @param headers - (可选) 表格的表头字符串数组。
46
+ * @returns 成功时返回包含 PNG 图片的 Buffer 数组,若列表为空则返回提示字符串。
47
+ */
48
+ renderList(data: ListRenderData, headers?: string[]): Promise<string | Buffer[]>;
49
+ }
package/lib/index.js CHANGED
@@ -68,7 +68,7 @@ var Collector = class _Collector {
68
68
  /** @property FLUSH_INTERVAL - 内存缓存区定时刷入数据库的间隔(毫秒)。 */
69
69
  static FLUSH_INTERVAL = import_koishi.Time.minute;
70
70
  /** @property BUFFER_THRESHOLD - 内存缓存区触发刷新的消息数量阈值。 */
71
- static BUFFER_THRESHOLD = 100;
71
+ static BUFFER_THRESHOLD = 60;
72
72
  // 统一的数据缓冲区
73
73
  msgStatBuffer = /* @__PURE__ */ new Map();
74
74
  rankStatBuffer = /* @__PURE__ */ new Map();
@@ -76,7 +76,8 @@ var Collector = class _Collector {
76
76
  oriCacheBuffer = [];
77
77
  whoAtBuffer = [];
78
78
  userCache = /* @__PURE__ */ new Map();
79
- pendingUserRequests = /* @__PURE__ */ new Map();
79
+ channelCache = /* @__PURE__ */ new Map();
80
+ pendingRequests = /* @__PURE__ */ new Map();
80
81
  flushInterval;
81
82
  /**
82
83
  * @private @method onMessage
@@ -90,35 +91,41 @@ var Collector = class _Collector {
90
91
  let user;
91
92
  if (this.userCache.has(cacheKey)) {
92
93
  user = this.userCache.get(cacheKey);
93
- } else if (this.pendingUserRequests.has(cacheKey)) {
94
- user = await this.pendingUserRequests.get(cacheKey);
94
+ } else if (this.pendingRequests.has(cacheKey)) {
95
+ user = await this.pendingRequests.get(cacheKey);
95
96
  } else {
96
97
  const promise = (async () => {
97
98
  try {
98
99
  const [dbUser] = await this.ctx.database.get("analyse_user", { channelId, userId });
99
100
  const currentUserName = session.username ?? "";
100
- const guild = await bot.getGuild(channelId).catch(() => null);
101
- const currentChannelName = guild?.name ?? "";
101
+ let currentChannelName = this.channelCache.get(channelId);
102
+ if (currentChannelName === void 0) {
103
+ const guild = await bot.getGuild(channelId).catch(() => null);
104
+ currentChannelName = guild?.name ?? "";
105
+ if (currentChannelName) this.channelCache.set(channelId, currentChannelName);
106
+ }
102
107
  if (dbUser) {
103
108
  if (currentUserName && dbUser.userName !== currentUserName || currentChannelName && dbUser.channelName !== currentChannelName) {
104
109
  await this.ctx.database.set("analyse_user", { uid: dbUser.uid }, { userName: currentUserName, channelName: currentChannelName });
105
110
  dbUser.userName = currentUserName;
106
111
  dbUser.channelName = currentChannelName;
107
112
  }
108
- this.userCache.set(cacheKey, dbUser);
109
- return dbUser;
113
+ const cacheData2 = { uid: dbUser.uid, userName: dbUser.userName };
114
+ this.userCache.set(cacheKey, cacheData2);
115
+ return cacheData2;
110
116
  }
111
117
  const createdUser = await this.ctx.database.create("analyse_user", { channelId, userId, userName: currentUserName, channelName: currentChannelName });
112
- this.userCache.set(cacheKey, createdUser);
113
- return createdUser;
118
+ const cacheData = { uid: createdUser.uid, userName: createdUser.userName };
119
+ this.userCache.set(cacheKey, cacheData);
120
+ return cacheData;
114
121
  } catch (error) {
115
122
  this.ctx.logger.error(`创建或获取用户(${cacheKey})失败:`, error);
116
123
  return null;
117
124
  } finally {
118
- this.pendingUserRequests.delete(cacheKey);
125
+ this.pendingRequests.delete(cacheKey);
119
126
  }
120
127
  })();
121
- this.pendingUserRequests.set(cacheKey, promise);
128
+ this.pendingRequests.set(cacheKey, promise);
122
129
  user = await promise;
123
130
  }
124
131
  if (!user) return;
@@ -215,15 +222,15 @@ var Renderer = class {
215
222
  /**
216
223
  * @private
217
224
  * @method htmlToImage
218
- * @description HTML 字符串转换为 PNG 图片 Buffer。
219
- * @param html - 要渲染的 HTML 主体内容。
220
- * @returns 返回一个包含 PNG 图片数据的 Buffer。
225
+ * @description 将一个完整的 HTML 文档字符串转换为 PNG 图片 Buffer。
226
+ * @param fullHtmlContent - 要渲染的、包含 `<html>...</html>` 的完整HTML字符串。
227
+ * @returns 返回一个包含 PNG 图片数据的 Buffer,失败则返回 null
221
228
  */
222
- async htmlToImage(html) {
229
+ async htmlToImage(fullHtmlContent) {
223
230
  const page = await this.ctx.puppeteer.page();
224
231
  try {
225
232
  await page.setViewport({ width: 720, height: 1080, deviceScaleFactor: 2 });
226
- await page.setContent(`<!DOCTYPE html><html><head><meta charset="UTF-8"><style>:root{--card-bg:#fff;--text-color:#111827;--header-color:#111827;--sub-text-color:#6b7280;--border-color:#e5e7eb;--accent-color:#4a6ee0;--chip-bg:#f3f4f6;--stripe-bg:#f9fafb;--gold:#f59e0b;--silver:#9ca3af;--bronze:#a16207}body{display:inline-block;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',Arial,sans-serif;background:0 0;margin:0;padding:8px;-webkit-font-smoothing:antialiased}.container{display:inline-block;background:var(--card-bg);border-radius:12px;padding:0;overflow:hidden;box-shadow:0 2px 4px rgba(0,0,0,.05)}.header{padding:10px 14px}.header-table{border-collapse:collapse;width:100%}.header-table-left,.header-table-right{width:1%;white-space:nowrap}.header-table-left{text-align:left}.header-table-center{text-align:center}.header-table-right{text-align:right}.title-text{font-size:18px;font-weight:600;color:var(--header-color);margin:0}.stat-chip,.time-label{display:inline-flex;align-items:baseline;padding:4px 8px;border-radius:8px;background:var(--chip-bg);font-size:13px;color:var(--sub-text-color)}.stat-chip span{font-weight:600;color:var(--text-color);margin-left:4px}.table-container{border-top:1px solid var(--border-color)}.main-table{border-collapse:collapse;width:100%}.main-table th,.main-table td{padding:8px 14px;vertical-align:middle}.main-table th{font-size:12px;font-weight:500;color:var(--sub-text-color);text-transform:uppercase;letter-spacing:.05em;background:var(--stripe-bg)}.main-table td{font-size:14px;color:var(--text-color)}.main-table tbody tr:nth-child(even){background-color:var(--stripe-bg)}.main-table .name-cell,.main-table .name-header{text-align:left}.main-table .rank-cell,.main-table .count-cell,.main-table .date-cell,.main-table .percent-cell,.main-table .header-right-align{text-align:right;white-space:nowrap;width:1%;font-variant-numeric:tabular-nums}.name-cell{font-weight:500}.rank-cell{font-weight:500;color:var(--sub-text-color)}.count-cell{font-weight:600;color:var(--accent-color)}.date-cell{color:var(--sub-text-color)}.rank-gold,.rank-silver,.rank-bronze{font-weight:600}.rank-gold{color:var(--gold)!important}.rank-silver{color:var(--silver)!important}.rank-bronze{color:var(--bronze)!important}.percent-cell{position:relative}.percent-bar{position:absolute;top:0;right:0;height:100%;background-color:var(--accent-color);opacity:.15}.percent-text{position:relative;z-index:1}</style></head><body>${html}</body></html>`, { waitUntil: "networkidle0" });
233
+ await page.setContent(fullHtmlContent, { waitUntil: "networkidle0" });
227
234
  const dimensions = await page.evaluate(() => ({ width: document.body.scrollWidth, height: document.body.scrollHeight }));
228
235
  await page.setViewport({ ...dimensions, deviceScaleFactor: 2 });
229
236
  return await page.screenshot({ type: "png", fullPage: true, omitBackground: true });
@@ -257,17 +264,19 @@ var Renderer = class {
257
264
  /**
258
265
  * @public
259
266
  * @method renderList
260
- * @description 构建并渲染一个包含标题、统计信息和数据表格的 HTML 卡片为图片。
267
+ * @description 构建并渲染一个包含标题、统计信息和数据表格的 HTML 卡片为图片。如果数据过多,则会分片渲染成多张图片。
261
268
  * @param data - 包含渲染所需全部信息的对象。
262
269
  * @param headers - (可选) 表格的表头字符串数组。
263
- * @returns 成功时返回包含 PNG 图片的 Buffer,若列表为空则返回提示字符串。
270
+ * @returns 成功时返回包含 PNG 图片的 Buffer 数组,若列表为空则返回提示字符串。
264
271
  */
265
272
  async renderList(data, headers) {
266
273
  const { title, time, list } = data;
267
274
  if (!list?.length) return "暂无数据可供渲染";
275
+ const CHUNK_SIZE = 100;
276
+ const imageBuffers = [];
277
+ const totalItems = list.length;
268
278
  const countHeaderIndex = headers?.findIndex((h4) => ["总计发言", "条数", "次数", "数量"].includes(h4)) ?? -1;
269
- const totalValue = countHeaderIndex > -1 ? list.reduce((sum, row) => sum + (Number(row[countHeaderIndex]) || 0), 0) : 0;
270
- const totalCount = data.total || totalValue;
279
+ const totalCount = data.total || (countHeaderIndex > -1 ? list.reduce((sum, row) => sum + (Number(row[countHeaderIndex]) || 0), 0) : totalItems);
271
280
  const renderCell = /* @__PURE__ */ __name((cell, i) => {
272
281
  const headerText = headers?.[i] || "";
273
282
  if (headerText.includes("占比")) {
@@ -278,14 +287,34 @@ var Renderer = class {
278
287
  if (typeof cell === "number") return `<td class="count-cell">${cell.toLocaleString()}</td>`;
279
288
  return `<td class="name-cell">${String(cell)}</td>`;
280
289
  }, "renderCell");
281
- const tableHeadHtml = headers?.length ? `<thead><tr><th class="rank-cell">#</th>${headers.map((h4) => `<th class="${typeof list[0]?.[headers.indexOf(h4)] === "string" ? "name-header" : "header-right-align"}">${h4}</th>`).join("")}</tr></thead>` : "";
282
- const tableRowsHtml = list.map((row, index) => {
283
- const rank = index + 1;
284
- const rankClass = rank === 1 ? "rank-gold" : rank === 2 ? "rank-silver" : rank === 3 ? "rank-bronze" : "";
285
- return `<tr><td class="rank-cell ${rankClass}">${rank}</td>${row.map(renderCell).join("")}</tr>`;
286
- }).join("");
287
- const cardHtml = `<div class="container"><div class="header"><table class="header-table"><tr><td class="header-table-left"><div class="stat-chip">总计: <span>${typeof totalCount === "number" ? totalCount.toLocaleString() : totalCount}</span></div></td><td class="header-table-center"><h1 class="title-text">${title}</h1></td><td class="header-table-right"><div class="time-label">${time.toLocaleString("zh-CN", { hour12: false }).replace(/\//g, "-")}</div></td></tr></table></div><div class="table-container"><table class="main-table">${tableHeadHtml}<tbody>${tableRowsHtml}</tbody></table></div></div>`;
288
- return this.htmlToImage(cardHtml);
290
+ const totalPages = Math.ceil(totalItems / CHUNK_SIZE);
291
+ for (let i = 0; i < totalItems; i += CHUNK_SIZE) {
292
+ const chunk = list.slice(i, i + CHUNK_SIZE);
293
+ const pageNum = Math.floor(i / CHUNK_SIZE) + 1;
294
+ const pageTitle = totalPages > 1 ? `${title} (第 ${pageNum}/${totalPages})` : title;
295
+ const tableRowsHtml = chunk.map((row, index) => {
296
+ const rank = i + index + 1;
297
+ const rankClass = rank === 1 ? "rank-gold" : rank === 2 ? "rank-silver" : rank === 3 ? "rank-bronze" : "";
298
+ return `<tr><td class="rank-cell ${rankClass}">${rank}</td>${row.map(renderCell).join("")}</tr>`;
299
+ }).join("");
300
+ const tableHeadHtml = headers?.length ? `<thead><tr><th class="rank-cell">#</th>${headers.map((h4) => `<th class="${typeof list[0]?.[headers.indexOf(h4)] === "string" ? "name-header" : "header-right-align"}">${h4}</th>`).join("")}</tr></thead>` : "";
301
+ const cardHtml = `<div class="container"><div class="header"><table class="header-table"><tr><td class="header-table-left"><div class="stat-chip">总计: <span>${typeof totalCount === "number" ? totalCount.toLocaleString() : totalCount}</span></div></td><td class="header-table-center"><h1 class="title-text">${pageTitle}</h1></td><td class="header-table-right"><div class="time-label">${time.toLocaleString("zh-CN", { hour12: false }).replace(/\//g, "-")}</div></td></tr></table></div><div class="table-container"><table class="main-table">${tableHeadHtml}<tbody>${tableRowsHtml}</tbody></table></div></div>`;
302
+ const fullHtml = `<!DOCTYPE html>
303
+ <html>
304
+ <head>
305
+ <meta charset="UTF-8">
306
+ <style>:root{--card-bg:#fff;--text-color:#111827;--header-color:#111827;--sub-text-color:#6b7280;--border-color:#e5e7eb;--accent-color:#4a6ee0;--chip-bg:#f3f4f6;--stripe-bg:#f9fafb;--gold:#f59e0b;--silver:#9ca3af;--bronze:#a16207}body{display:inline-block;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',Arial,sans-serif;background:0 0;margin:0;padding:8px;-webkit-font-smoothing:antialiased}.container{display:inline-block;background:var(--card-bg);border-radius:12px;padding:0;overflow:hidden;box-shadow:0 2px 4px rgba(0,0,0,.05)}.header{padding:10px 14px}.header-table{border-collapse:collapse;width:100%}.header-table-left,.header-table-right{width:1%;white-space:nowrap}.header-table-left{text-align:left}.header-table-center{text-align:center}.header-table-right{text-align:right}.title-text{font-size:18px;font-weight:600;color:var(--header-color);margin:0}.stat-chip,.time-label{display:inline-flex;align-items:baseline;padding:4px 8px;border-radius:8px;background:var(--chip-bg);font-size:13px;color:var(--sub-text-color)}.stat-chip span{font-weight:600;color:var(--text-color);margin-left:4px}.table-container{border-top:1px solid var(--border-color)}.main-table{border-collapse:collapse;width:100%}.main-table th,.main-table td{padding:8px 14px;vertical-align:middle}.main-table th{font-size:12px;font-weight:500;color:var(--sub-text-color);text-transform:uppercase;letter-spacing:.05em;background:var(--stripe-bg)}.main-table td{font-size:14px;color:var(--text-color)}.main-table tbody tr:nth-child(even){background-color:var(--stripe-bg)}.main-table .name-cell,.main-table .name-header{text-align:left}.main-table .rank-cell,.main-table .count-cell,.main-table .date-cell,.main-table .percent-cell,.main-table .header-right-align{text-align:right;white-space:nowrap;width:1%;font-variant-numeric:tabular-nums}.name-cell{font-weight:500}.rank-cell{font-weight:500;color:var(--sub-text-color)}.count-cell{font-weight:600;color:var(--accent-color)}.date-cell{color:var(--sub-text-color)}.rank-gold,.rank-silver,.rank-bronze{font-weight:600}.rank-gold{color:var(--gold)!important}.rank-silver{color:var(--silver)!important}.rank-bronze{color:var(--bronze)!important}.percent-cell{position:relative}.percent-bar{position:absolute;top:0;right:0;height:100%;background-color:var(--accent-color);opacity:.15}.percent-text{position:relative;z-index:1}</style>
307
+ </head>
308
+ <body>
309
+ ${cardHtml}
310
+ </body>
311
+ </html>`;
312
+ const imageBuffer = await this.htmlToImage(fullHtml);
313
+ if (imageBuffer) {
314
+ imageBuffers.push(imageBuffer);
315
+ }
316
+ }
317
+ return imageBuffers.length > 0 ? imageBuffers : "图片渲染失败";
289
318
  }
290
319
  };
291
320
 
@@ -322,7 +351,13 @@ var Stat = class {
322
351
  if (scope.error) return scope.error;
323
352
  try {
324
353
  const result = await handler(scope, options);
325
- return Buffer.isBuffer(result) ? import_koishi3.h.image(result, "image/png") : result;
354
+ if (typeof result === "string") return result;
355
+ if (Array.isArray(result)) {
356
+ if (result.length === 0) return "图片渲染失败";
357
+ for (const buffer of result) await session.sendQueued(import_koishi3.h.image(buffer, "image/png"));
358
+ return;
359
+ }
360
+ if (Buffer.isBuffer(result)) return import_koishi3.h.image(result, "image/png");
326
361
  } catch (error) {
327
362
  this.ctx.logger.error("渲染统计图片失败:", error);
328
363
  return "渲染统计图片失败";
@@ -370,7 +405,7 @@ var Stat = class {
370
405
  const uidsInScope = guildId ? (await this.ctx.database.get("analyse_user", { channelId: guildId }, ["uid"])).map((u) => u.uid) : void 0;
371
406
  if (guildId && uidsInScope.length === 0) return "暂无指定时段内发言记录";
372
407
  if (uidsInScope) baseQuery.uid = { $in: uidsInScope };
373
- const rankStats = await this.ctx.database.select("analyse_rank").where(baseQuery).groupBy("uid", { count: /* @__PURE__ */ __name((row) => import_koishi3.$.sum(row.count), "count") }).orderBy("count", "desc").limit(100).execute();
408
+ const rankStats = await this.ctx.database.select("analyse_rank").where(baseQuery).groupBy("uid", { count: /* @__PURE__ */ __name((row) => import_koishi3.$.sum(row.count), "count") }).orderBy("count", "desc").execute();
374
409
  if (rankStats.length === 0) return "暂无指定时段内发言记录";
375
410
  const uids = rankStats.map((s) => s.uid);
376
411
  const users = await this.ctx.database.get("analyse_user", { uid: { $in: uids } }, ["uid", "userName"]);
@@ -380,7 +415,12 @@ var Stat = class {
380
415
  const listWithPercentage = list.map((row) => [...row, total > 0 ? `${(row[1] / total * 100).toFixed(2)}%` : "0.00%"]);
381
416
  const title = await this.generateTitle({ guildId }, { main: "发言排行", timeRange: hours, subtype: type });
382
417
  const result = await this.renderer.renderList({ title, time: /* @__PURE__ */ new Date(), total, list: listWithPercentage }, ["用户", "总计发言", "占比"]);
383
- return Buffer.isBuffer(result) ? import_koishi3.h.image(result, "image/png") : result;
418
+ if (typeof result === "string") return result;
419
+ if (Array.isArray(result)) {
420
+ if (result.length === 0) return "图片渲染失败";
421
+ for (const buffer of result) await session.sendQueued(import_koishi3.h.image(buffer, "image/png"));
422
+ return;
423
+ }
384
424
  } catch (error) {
385
425
  this.ctx.logger.error("渲染发言排行图片失败:", error);
386
426
  return "渲染发言排行图片失败";
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-chat-analyse",
3
3
  "description": "聊天记录分析",
4
- "version": "0.5.0",
4
+ "version": "0.5.2",
5
5
  "contributors": [
6
6
  "Yis_Rime <yis_rime@outlook.com>"
7
7
  ],