@koishi-ce/plugin-analytics 1.0.1 → 1.1.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/src/index.ts CHANGED
@@ -28,6 +28,13 @@ import {
28
28
  Time,
29
29
  type Universal,
30
30
  } from "@koishi-ce/koishi";
31
+ import deDE from "../locales/de-DE.yml";
32
+ import enUS from "../locales/en-US.yml";
33
+ import frFR from "../locales/fr-FR.yml";
34
+ import jaJP from "../locales/ja-JP.yml";
35
+ import ruRU from "../locales/ru-RU.yml";
36
+ import zhCN from "../locales/zh-CN.yml";
37
+ import zhTW from "../locales/zh-TW.yml";
31
38
 
32
39
  declare module "@koishi-ce/koishi" {
33
40
  interface Tables {
@@ -65,6 +72,14 @@ class Analytics extends DataService<Analytics.Payload> {
65
72
  recentDayCount: Schema.natural()
66
73
  .description("统计最近几天的数据。")
67
74
  .default(7),
75
+ }).i18n({
76
+ "de-DE": deDE,
77
+ "en-US": enUS,
78
+ "fr-FR": frFR,
79
+ "ja-JP": jaJP,
80
+ "ru-RU": ruRU,
81
+ "zh-CN": zhCN,
82
+ "zh-TW": zhTW,
68
83
  });
69
84
 
70
85
  // 基类 Service 声明了 config: unknown;此处收敛为必填并在构造时归一化
@@ -89,7 +104,8 @@ class Analytics extends DataService<Analytics.Payload> {
89
104
  super(ctx, "analytics");
90
105
 
91
106
  this.config = {
92
- statsInternal: config.statsInternal ?? Time.minute * 10,
107
+ statsInternal:
108
+ config.statsInternal ?? Time.minute * 10,
93
109
  recentDayCount: config.recentDayCount ?? 7,
94
110
  };
95
111
 
@@ -105,7 +121,13 @@ class Analytics extends DataService<Analytics.Payload> {
105
121
  count: "integer",
106
122
  },
107
123
  {
108
- primary: ["date", "hour", "type", "selfId", "platform"],
124
+ primary: [
125
+ "date",
126
+ "hour",
127
+ "type",
128
+ "selfId",
129
+ "platform",
130
+ ],
109
131
  },
110
132
  );
111
133
 
@@ -157,20 +179,23 @@ class Analytics extends DataService<Analytics.Payload> {
157
179
  this.upload();
158
180
  });
159
181
 
160
- ctx.any().before("command/execute", ({ command, session }) => {
161
- if (!command || !session) return;
162
- // 观察字段在类型上不可索引,按宽泛视图读取( createIndex 同理)
163
- // biome-ignore lint/suspicious/noExplicitAny: Session<never,...> 的 user
164
- const user = (session as Session<any, any, any>).user;
165
- this.addAudit(this.commands, {
166
- ...this.createIndex(session),
167
- name: command.name,
168
- // 库表列声明为 integer,而 user.id 为字符串,这里按数值归一
169
- userId: +(user?.["id"] || 0),
170
- channelId: session.channelId ?? "",
182
+ ctx
183
+ .any()
184
+ .before("command/execute", ({ command, session }) => {
185
+ if (!command || !session) return;
186
+ // 观察字段在类型上不可索引,按宽泛视图读取( createIndex 同理)
187
+ // biome-ignore lint/suspicious/noExplicitAny: Session<never,...> 的 user
188
+ const user = (session as Session<any, any, any>)
189
+ .user;
190
+ this.addAudit(this.commands, {
191
+ ...this.createIndex(session),
192
+ name: command.name,
193
+ // 库表列声明为 integer,而 user.id 为字符串,这里按数值归一
194
+ userId: +(user?.["id"] || 0),
195
+ channelId: session.channelId ?? "",
196
+ });
197
+ this.upload();
171
198
  });
172
- this.upload();
173
- });
174
199
 
175
200
  ctx.console.addEntry({
176
201
  dev: resolve(__dirname, "../client/index.ts"),
@@ -179,8 +204,10 @@ class Analytics extends DataService<Analytics.Payload> {
179
204
  }
180
205
 
181
206
  // 不协变,各事件回调处的具体泛型互不相同,内部工具方法统一放宽
182
- // biome-ignore lint/suspicious/noExplicitAny: Session 泛型在 user 观察字段上
183
- private createIndex(session: Session<any, any, any>): Analytics.Index {
207
+ private createIndex(
208
+ // biome-ignore lint/suspicious/noExplicitAny: Session 泛型在 user 观察字段上
209
+ session: Session<any, any, any>,
210
+ ): Analytics.Index {
184
211
  return {
185
212
  selfId: session.selfId,
186
213
  platform: session.platform,
@@ -198,7 +225,10 @@ class Analytics extends DataService<Analytics.Payload> {
198
225
  index: Omit<T, "count">,
199
226
  ) {
200
227
  const audit = buffer.find((data) =>
201
- deepEqual(pick(data, Object.keys(index) as (keyof T)[]), index),
228
+ deepEqual(
229
+ pick(data, Object.keys(index) as (keyof T)[]),
230
+ index,
231
+ ),
202
232
  );
203
233
  if (audit) {
204
234
  audit.count += 1;
@@ -236,14 +266,21 @@ class Analytics extends DataService<Analytics.Payload> {
236
266
  const dateHour = date.getHours();
237
267
  if (
238
268
  forced ||
239
- +date - +this.lastUpdate > this.config.statsInternal ||
269
+ +date - +this.lastUpdate >
270
+ this.config.statsInternal ||
240
271
  dateHour !== this.updateHour
241
272
  ) {
242
273
  this.lastUpdate = date;
243
274
  this.updateHour = dateHour;
244
275
  await Promise.all([
245
- this.uploadAudit("analytics.message", this.messages),
246
- this.uploadAudit("analytics.command", this.commands),
276
+ this.uploadAudit(
277
+ "analytics.message",
278
+ this.messages,
279
+ ),
280
+ this.uploadAudit(
281
+ "analytics.command",
282
+ this.commands,
283
+ ),
247
284
  ]);
248
285
  logger.debug("analytics updated");
249
286
  }
@@ -252,7 +289,8 @@ class Analytics extends DataService<Analytics.Payload> {
252
289
  /** 最近 N 天(不含今天)的日期号查询区间,供各聚合查询复用。 */
253
290
  private queryRecent(): Query.FieldExpr<number> {
254
291
  return {
255
- $gte: Time.getDateNumber() - this.config.recentDayCount,
292
+ $gte:
293
+ Time.getDateNumber() - this.config.recentDayCount,
256
294
  $lt: Time.getDateNumber(),
257
295
  };
258
296
  }
@@ -263,7 +301,9 @@ class Analytics extends DataService<Analytics.Payload> {
263
301
  *
264
302
  * @param lengthTask 参与平均的天数(见 download 中的计算)
265
303
  */
266
- private async getCommandRate(lengthTask: Promise<number>) {
304
+ private async getCommandRate(
305
+ lengthTask: Promise<number>,
306
+ ) {
267
307
  const data = await this.ctx.database
268
308
  .select("analytics.command", {
269
309
  date: this.queryRecent(),
@@ -288,14 +328,20 @@ class Analytics extends DataService<Analytics.Payload> {
288
328
  private async getDauHistory() {
289
329
  const data = await this.ctx.database
290
330
  .select("analytics.command", {
291
- date: { $gte: Time.getDateNumber() - this.config.recentDayCount },
331
+ date: {
332
+ $gte:
333
+ Time.getDateNumber() -
334
+ this.config.recentDayCount,
335
+ },
292
336
  userId: { $gt: 0 },
293
337
  })
294
338
  .groupBy(["date"], {
295
339
  count: (row) => $.count(row.userId),
296
340
  })
297
341
  .execute();
298
- const result: number[] = new Array(this.config.recentDayCount + 1).fill(0);
342
+ const result: number[] = new Array(
343
+ this.config.recentDayCount + 1,
344
+ ).fill(0);
299
345
  const today = Time.getDateNumber();
300
346
  data.forEach((stat) => {
301
347
  result[today - stat.date] = stat.count;
@@ -310,7 +356,9 @@ class Analytics extends DataService<Analytics.Payload> {
310
356
  *
311
357
  * @param lengthTask 参与平均的天数(见 download 中的计算)
312
358
  */
313
- private async getMessageByBot(lengthTask: Promise<number>) {
359
+ private async getMessageByBot(
360
+ lengthTask: Promise<number>,
361
+ ) {
314
362
  const data = await this.ctx.database
315
363
  .select("analytics.message", {
316
364
  date: this.queryRecent(),
@@ -321,16 +369,22 @@ class Analytics extends DataService<Analytics.Payload> {
321
369
  .execute();
322
370
  const length = await lengthTask;
323
371
  // 机器人资料(bot.user)运行时可能缺席,按 Partial 记录
324
- const result = {} as Dict<Dict<MessageStats & Partial<Universal.User>>>;
372
+ const result = {} as Dict<
373
+ Dict<MessageStats & Partial<Universal.User>>
374
+ >;
325
375
  data.forEach((stat) => {
326
- const bot = this.ctx.bots[`${stat.platform}:${stat.selfId}`];
327
- const entry = ((result[stat.platform] ||= {})[stat.selfId] ||= {
376
+ const bot =
377
+ this.ctx.bots[`${stat.platform}:${stat.selfId}`];
378
+ const entry = ((result[stat.platform] ||= {})[
379
+ stat.selfId
380
+ ] ||= {
328
381
  ...(bot?.user ?? {}),
329
382
  send: 0,
330
383
  receive: 0,
331
384
  });
332
385
  // type 列的取值集合由写入端约定为 send / receive 两种
333
- entry[stat.type as "send" | "receive"] = stat.count / length;
386
+ entry[stat.type as "send" | "receive"] =
387
+ stat.count / length;
334
388
  });
335
389
  return result;
336
390
  }
@@ -353,7 +407,10 @@ class Analytics extends DataService<Analytics.Payload> {
353
407
  const today = Time.getDateNumber();
354
408
  const result: MessageStats[] = [];
355
409
  data.forEach((stat) => {
356
- const entry = (result[today - stat.date] ||= { send: 0, receive: 0 });
410
+ const entry = (result[today - stat.date] ||= {
411
+ send: 0,
412
+ receive: 0,
413
+ });
357
414
  entry[stat.type as "send" | "receive"] = stat.count;
358
415
  });
359
416
  for (let i = 0; i < result.length; i++) {
@@ -368,7 +425,9 @@ class Analytics extends DataService<Analytics.Payload> {
368
425
  *
369
426
  * @param lengthTask 参与平均的天数(见 download 中的计算)
370
427
  */
371
- private async getMessageByHour(lengthTask: Promise<number>) {
428
+ private async getMessageByHour(
429
+ lengthTask: Promise<number>,
430
+ ) {
372
431
  const data = await this.ctx.database
373
432
  .select("analytics.message", {
374
433
  date: this.queryRecent(),
@@ -384,7 +443,8 @@ class Analytics extends DataService<Analytics.Payload> {
384
443
  data.forEach((stat) => {
385
444
  const entry = result[stat.hour];
386
445
  if (!entry) return;
387
- entry[stat.type as "send" | "receive"] = stat.count / length;
446
+ entry[stat.type as "send" | "receive"] =
447
+ stat.count / length;
388
448
  });
389
449
  return result;
390
450
  }
@@ -400,7 +460,10 @@ class Analytics extends DataService<Analytics.Payload> {
400
460
  async download(): Promise<Analytics.Payload> {
401
461
  const messageByDateTask = this.getMessageByDate();
402
462
  const lengthTask = messageByDateTask.then((data) => {
403
- return Math.min(Math.max(data.length - 1, 1), this.config.recentDayCount);
463
+ return Math.min(
464
+ Math.max(data.length - 1, 1),
465
+ this.config.recentDayCount,
466
+ );
404
467
  });
405
468
  const [
406
469
  userCount,
@@ -414,14 +477,22 @@ class Analytics extends DataService<Analytics.Payload> {
414
477
  messageByHour,
415
478
  ] = await Promise.all([
416
479
  // 用户总数
417
- this.ctx.database.eval("user", (row) => $.count(row.id)),
480
+ this.ctx.database.eval("user", (row) =>
481
+ $.count(row.id),
482
+ ),
418
483
  // 昨日新增用户数(createdAt 落在昨天一整天)
419
- this.ctx.database.eval("user", (row) => $.count(row.id), {
420
- createdAt: {
421
- $gte: Time.fromDateNumber(Time.getDateNumber() - 1),
422
- $lt: Time.fromDateNumber(Time.getDateNumber()),
484
+ this.ctx.database.eval(
485
+ "user",
486
+ (row) => $.count(row.id),
487
+ {
488
+ createdAt: {
489
+ $gte: Time.fromDateNumber(
490
+ Time.getDateNumber() - 1,
491
+ ),
492
+ $lt: Time.fromDateNumber(Time.getDateNumber()),
493
+ },
423
494
  },
424
- }),
495
+ ),
425
496
  // 群组总数:channel 表中 id === guildId 的行即群本身(而非普通子频道)
426
497
  this.ctx.database.eval(
427
498
  "channel",
@@ -435,8 +506,14 @@ class Analytics extends DataService<Analytics.Payload> {
435
506
  (row) =>
436
507
  $.and(
437
508
  $.eq(row.id, row.guildId),
438
- $.gte(row.createdAt, Time.fromDateNumber(Time.getDateNumber() - 1)),
439
- $.lt(row.createdAt, Time.fromDateNumber(Time.getDateNumber())),
509
+ $.gte(
510
+ row.createdAt,
511
+ Time.fromDateNumber(Time.getDateNumber() - 1),
512
+ ),
513
+ $.lt(
514
+ row.createdAt,
515
+ Time.fromDateNumber(Time.getDateNumber()),
516
+ ),
440
517
  ),
441
518
  ),
442
519
  this.getCommandRate(lengthTask),
@@ -465,7 +542,10 @@ class Analytics extends DataService<Analytics.Payload> {
465
542
  */
466
543
  override async get() {
467
544
  const date = new Date();
468
- const dateNumber = Time.getDateNumber(date, date.getTimezoneOffset());
545
+ const dateNumber = Time.getDateNumber(
546
+ date,
547
+ date.getTimezoneOffset(),
548
+ );
469
549
  if (dateNumber !== this.cachedDate) {
470
550
  this.cachedData = this.download();
471
551
  this.cachedDate = dateNumber;
@@ -511,7 +591,9 @@ namespace Analytics {
511
591
  guildIncrement: number;
512
592
  dauHistory: number[];
513
593
  commandRate: Dict<number>;
514
- messageByBot: Dict<Dict<MessageStats & Partial<Universal.User>>>;
594
+ messageByBot: Dict<
595
+ Dict<MessageStats & Partial<Universal.User>>
596
+ >;
515
597
  messageByDate: MessageStats[];
516
598
  messageByHour: MessageStats[];
517
599
  }