koishi-plugin-chat-analyse 1.3.5 → 1.3.7
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 +71 -44
- package/package.json +1 -1
- package/readme.md +1 -0
package/lib/index.js
CHANGED
|
@@ -1487,20 +1487,22 @@ var Renderer = class {
|
|
|
1487
1487
|
__name(this, "Renderer");
|
|
1488
1488
|
}
|
|
1489
1489
|
COLOR_PALETTES = [
|
|
1490
|
-
// 1. Oceanic:
|
|
1491
|
-
["#
|
|
1492
|
-
// 2.
|
|
1493
|
-
["#
|
|
1494
|
-
// 3.
|
|
1495
|
-
["#
|
|
1496
|
-
// 4.
|
|
1497
|
-
["#
|
|
1498
|
-
// 5. Candy:
|
|
1499
|
-
["#
|
|
1500
|
-
// 6. Retro:
|
|
1501
|
-
["#264653", "#
|
|
1502
|
-
// 7.
|
|
1503
|
-
["#
|
|
1490
|
+
// 1. Oceanic Blues: 宁静的蓝色系,适合专业、冷静的图表
|
|
1491
|
+
["#CAF0F8", "#90E0EF", "#00B4D8", "#0077B6", "#03045E"],
|
|
1492
|
+
// 2. Forest Greens: 自然的绿色系,代表成长与和谐
|
|
1493
|
+
["#D8F3DC", "#95D5B2", "#52B788", "#2D6A4F", "#1B4332"],
|
|
1494
|
+
// 3. Royal Purples: 优雅的紫色系,带有一丝神秘感
|
|
1495
|
+
["#E0AAFF", "#C77DFF", "#9D4EDD", "#7B2CBF", "#5A189A"],
|
|
1496
|
+
// 4. Sunset Oranges: 温暖的橙色系,充满活力与热情
|
|
1497
|
+
["#FFF3B0", "#FFD670", "#FFB703", "#F8961E", "#E85D04"],
|
|
1498
|
+
// 5. Vivid Candy: 鲜艳的糖果色,活泼、醒目
|
|
1499
|
+
["#E63946", "#F1FAEE", "#A8DADC", "#457B9D", "#1D3557"],
|
|
1500
|
+
// 6. Retro Groove: 复古风格,兼具沉稳与活力
|
|
1501
|
+
["#264653", "#2A9D8F", "#E9C46A", "#F4A261", "#E76F51"],
|
|
1502
|
+
// 7. Pastel Rainbow: 温和的彩虹色,柔和、美观
|
|
1503
|
+
["#FFADAD", "#FDFFB6", "#CAFFBF", "#9BF6FF", "#A0C4FF"],
|
|
1504
|
+
// 8. Bold & Contrasting: 大胆的撞色,对比强烈,引人注目
|
|
1505
|
+
["#D00000", "#FFBA08", "#3F88C5", "#032B43", "#136F63"]
|
|
1504
1506
|
];
|
|
1505
1507
|
COMMON_STYLE = `
|
|
1506
1508
|
:root {
|
|
@@ -1691,12 +1693,12 @@ var Renderer = class {
|
|
|
1691
1693
|
*/
|
|
1692
1694
|
async *renderLineChart(data) {
|
|
1693
1695
|
const { title, time, series, labels } = data;
|
|
1694
|
-
const
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1696
|
+
const colorfulPalettes = this.COLOR_PALETTES.slice(4);
|
|
1697
|
+
const selectedPalette = colorfulPalettes[Math.floor(Math.random() * colorfulPalettes.length)];
|
|
1698
|
+
const shuffledColors = [...selectedPalette].sort(() => 0.5 - Math.random());
|
|
1699
|
+
const seriesColors = series.map((_, index) => shuffledColors[index % shuffledColors.length]);
|
|
1698
1700
|
const width = 600, height = 320;
|
|
1699
|
-
const padding = { top:
|
|
1701
|
+
const padding = { top: 15, right: 15, bottom: 60, left: 25 };
|
|
1700
1702
|
const chartWidth = width - padding.left - padding.right;
|
|
1701
1703
|
const chartHeight = height - padding.top - padding.bottom;
|
|
1702
1704
|
const maxVal = Math.max(1, ...series.flatMap((s) => s.data));
|
|
@@ -2316,41 +2318,66 @@ var Analyse = class {
|
|
|
2316
2318
|
});
|
|
2317
2319
|
}
|
|
2318
2320
|
if (this.config.enableSimilarActivity) {
|
|
2319
|
-
cmd.subcommand("simiactive", "相似活跃分析").usage("
|
|
2321
|
+
cmd.subcommand("simiactive", "相似活跃分析").usage("分析你和群友的活跃规律,找出谁和你的作息最相似。").option("hours", "-n <hours:number> 指定时长", { fallback: 24 }).option("separate", "-p 分时分析").action(async ({ session, options }) => {
|
|
2320
2322
|
if (!session.guildId) return "请在群组中使用此命令";
|
|
2321
2323
|
try {
|
|
2322
|
-
const until = /* @__PURE__ */ new Date();
|
|
2323
|
-
const since = new Date(until.getTime() - options.hours * import_koishi6.Time.hour);
|
|
2324
|
-
const points = options.hours;
|
|
2325
2324
|
const guildUsers = await this.ctx.database.get("analyse_user", { channelId: session.guildId });
|
|
2326
2325
|
if (guildUsers.length < 2) return "暂无用户数据";
|
|
2327
2326
|
const selfUser = guildUsers.find((u) => u.userId === session.userId);
|
|
2328
2327
|
const guildUserUids = guildUsers.map((u) => u.uid);
|
|
2329
2328
|
const uidToNameMap = new Map(guildUsers.map((u) => [u.uid, u.userName]));
|
|
2330
|
-
const
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2329
|
+
const until = /* @__PURE__ */ new Date();
|
|
2330
|
+
let analysisConfig;
|
|
2331
|
+
if (options.separate) {
|
|
2332
|
+
const { hours } = options;
|
|
2333
|
+
analysisConfig = {
|
|
2334
|
+
points: hours,
|
|
2335
|
+
since: new Date(until.getTime() - hours * import_koishi6.Time.hour),
|
|
2336
|
+
title: `${hours}小时相似活跃分析`,
|
|
2337
|
+
labels: Array.from({ length: hours }, (_, i) => String(new Date(until.getTime() - (hours - 1 - i) * import_koishi6.Time.hour).getHours())),
|
|
2338
|
+
getIndex: /* @__PURE__ */ __name((timestamp) => {
|
|
2339
|
+
const diff = until.getTime() - timestamp.getTime();
|
|
2340
|
+
const index = hours - 1 - Math.floor(diff / import_koishi6.Time.hour);
|
|
2341
|
+
return index >= 0 && index < hours ? index : -1;
|
|
2342
|
+
}, "getIndex"),
|
|
2343
|
+
reorderVector: /* @__PURE__ */ __name((vec) => vec, "reorderVector")
|
|
2344
|
+
};
|
|
2345
|
+
} else {
|
|
2346
|
+
const daysToAnalyse = Math.floor(options.hours / 24);
|
|
2347
|
+
if (daysToAnalyse < 1) return "分析时长请指定至少 1 天";
|
|
2348
|
+
const hoursToAnalyse = daysToAnalyse * 24;
|
|
2349
|
+
const currentHour = until.getHours();
|
|
2350
|
+
const labels = Array.from({ length: 24 }, (_, i) => String((currentHour - (23 - i) + 24) % 24));
|
|
2351
|
+
analysisConfig = {
|
|
2352
|
+
points: 24,
|
|
2353
|
+
since: new Date(until.getTime() - hoursToAnalyse * import_koishi6.Time.hour),
|
|
2354
|
+
title: `${daysToAnalyse}天相似活跃分析`,
|
|
2355
|
+
labels,
|
|
2356
|
+
getIndex: /* @__PURE__ */ __name((timestamp) => timestamp.getHours(), "getIndex"),
|
|
2357
|
+
reorderVector: /* @__PURE__ */ __name((vector) => labels.map((label) => vector[parseInt(label)]), "reorderVector")
|
|
2358
|
+
};
|
|
2359
|
+
}
|
|
2360
|
+
const records = await this.ctx.database.get("analyse_rank", { uid: { $in: guildUserUids }, timestamp: { $gte: analysisConfig.since } });
|
|
2361
|
+
if (!records.length) return "暂无统计数据";
|
|
2362
|
+
const activityVectors = new Map(guildUserUids.map((uid) => [uid, Array(analysisConfig.points).fill(0)]));
|
|
2363
|
+
for (const record of records) {
|
|
2364
|
+
const index = analysisConfig.getIndex(record.timestamp);
|
|
2365
|
+
if (index !== -1) activityVectors.get(record.uid)[index] += record.count;
|
|
2366
|
+
}
|
|
2341
2367
|
const selfVector = activityVectors.get(selfUser.uid);
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2368
|
+
const similarities = guildUserUids.filter((uid) => uid !== selfUser.uid && activityVectors.get(uid).some((v) => v !== 0)).map((uid) => ({
|
|
2369
|
+
uid,
|
|
2370
|
+
score: cosineSimilarity(selfVector, activityVectors.get(uid))
|
|
2371
|
+
})).sort((a, b) => b.score - a.score);
|
|
2372
|
+
if (!similarities.length) return "暂无相似用户";
|
|
2345
2373
|
const top5 = similarities.slice(0, 5);
|
|
2346
|
-
const series = [{ name: uidToNameMap.get(selfUser.uid) || "您", data: selfVector }];
|
|
2347
|
-
|
|
2374
|
+
const series = [{ name: uidToNameMap.get(selfUser.uid) || "您", data: analysisConfig.reorderVector(selfVector) }];
|
|
2375
|
+
for (const sim of top5) {
|
|
2348
2376
|
const name2 = uidToNameMap.get(sim.uid) || `UID ${sim.uid}`;
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
const
|
|
2353
|
-
const imageGenerator = this.renderer.renderLineChart({ title, time: /* @__PURE__ */ new Date(), series, labels });
|
|
2377
|
+
const data = analysisConfig.reorderVector(activityVectors.get(sim.uid));
|
|
2378
|
+
series.push({ name: `${name2} (${(sim.score * 100).toFixed(1)}%)`, data });
|
|
2379
|
+
}
|
|
2380
|
+
const imageGenerator = this.renderer.renderLineChart({ title: analysisConfig.title, time: /* @__PURE__ */ new Date(), series, labels: analysisConfig.labels });
|
|
2354
2381
|
for await (const buffer of imageGenerator) await session.send(import_koishi6.h.image(buffer, "image/png"));
|
|
2355
2382
|
} catch (error) {
|
|
2356
2383
|
this.ctx.logger.error("生成作息分析图片失败:", error);
|
package/package.json
CHANGED
package/readme.md
CHANGED