koishi-plugin-chat-analyse 1.6.13 → 1.7.1
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.d.ts +1 -1
- package/lib/index.js +61 -20
- package/package.json +1 -1
- package/readme.md +35 -34
package/lib/index.d.ts
CHANGED
package/lib/index.js
CHANGED
|
@@ -1605,7 +1605,7 @@ var Renderer = class {
|
|
|
1605
1605
|
*/
|
|
1606
1606
|
async *renderList(data, headers) {
|
|
1607
1607
|
const { title, time, list } = data;
|
|
1608
|
-
const CHUNK_SIZE =
|
|
1608
|
+
const CHUNK_SIZE = 200;
|
|
1609
1609
|
const totalItems = list.length;
|
|
1610
1610
|
const countHeaderIndex = headers?.findIndex((h7) => ["总计发言", "条数", "次数", "数量"].includes(h7)) ?? -1;
|
|
1611
1611
|
const totalCount = data.total || (countHeaderIndex > -1 ? list.reduce((sum, row) => sum + (Number(row[countHeaderIndex]) || 0), 0) : totalItems);
|
|
@@ -1907,7 +1907,7 @@ var Stat = class {
|
|
|
1907
1907
|
}
|
|
1908
1908
|
}, "handleAction");
|
|
1909
1909
|
if (this.config.enableCmdStat) {
|
|
1910
|
-
cmd.subcommand("cmdstat", "命令统计").usage("查询命令统计,可指定查询范围,默认当前群组。").option("user", "-u <user:string> 指定用户").option("guild", "-g <guildId:string> 指定群组").option("separate", "-p 分离子命令").option("sortByTime", "-s 以时间排序").option("all", "-a 全局统计").action(({ session, options }) => handleAction(session, (async () => {
|
|
1910
|
+
cmd.subcommand("cmdstat", "命令统计").usage("查询命令统计,可指定查询范围,默认当前群组。").option("user", "-u <user:string> 指定用户").option("guild", "-g <guildId:string> 指定群组").option("limit", "-l <count:number> 限制数量").option("separate", "-p 分离子命令").option("sortByTime", "-s 以时间排序").option("all", "-a 全局统计").action(({ session, options }) => handleAction(session, (async () => {
|
|
1911
1911
|
const scope = await this.parseScope(session, options);
|
|
1912
1912
|
if (scope.error) return scope.error;
|
|
1913
1913
|
const query = scope.uids ? { uid: { $in: scope.uids } } : {};
|
|
@@ -1932,14 +1932,15 @@ var Stat = class {
|
|
|
1932
1932
|
} else {
|
|
1933
1933
|
processedStats.sort((a, b) => b.count - a.count);
|
|
1934
1934
|
}
|
|
1935
|
+
const limitedStats = options.limit > 0 ? processedStats.slice(0, options.limit) : processedStats;
|
|
1935
1936
|
const total = processedStats.reduce((sum, r) => sum + r.count, 0);
|
|
1936
|
-
const list =
|
|
1937
|
+
const list = limitedStats.map((item) => [item.command, item.count, item.lastUsed]);
|
|
1937
1938
|
const title = await generateTitle(this.ctx, scope.scopeDesc, { main: "命令" });
|
|
1938
1939
|
return this.renderer.renderList({ title, time: /* @__PURE__ */ new Date(), total, list }, ["命令", "次数", "最后使用"]);
|
|
1939
1940
|
})()));
|
|
1940
1941
|
}
|
|
1941
1942
|
if (this.config.enableMsgStat) {
|
|
1942
|
-
cmd.subcommand("msgstat", "发言统计").usage("查询发言统计,可指定查询范围,默认当前群组。").option("user", "-u <user:string> 指定用户").option("guild", "-g <guildId:string> 指定群组").option("type", "-t <type:string> 指定类型").option("sortByTime", "-s 以时间排序").option("all", "-a 全局统计").action(({ session, options }) => handleAction(session, (async () => {
|
|
1943
|
+
cmd.subcommand("msgstat", "发言统计").usage("查询发言统计,可指定查询范围,默认当前群组。").option("user", "-u <user:string> 指定用户").option("guild", "-g <guildId:string> 指定群组").option("type", "-t <type:string> 指定类型").option("limit", "-l <count:number> 限制数量").option("sortByTime", "-s 以时间排序").option("all", "-a 全局统计").action(({ session, options }) => handleAction(session, (async () => {
|
|
1943
1944
|
const scope = await this.parseScope(session, options);
|
|
1944
1945
|
if (scope.error) return scope.error;
|
|
1945
1946
|
const query = scope.uids ? { uid: { $in: scope.uids } } : {};
|
|
@@ -1956,8 +1957,9 @@ var Stat = class {
|
|
|
1956
1957
|
const stats2 = await this.ctx.database.select("analyse_msg").where(query).groupBy("type", { count: /* @__PURE__ */ __name((row) => import_koishi3.$.sum(row.count), "count"), lastUsed: /* @__PURE__ */ __name((row) => import_koishi3.$.max(row.timestamp), "lastUsed") }).execute();
|
|
1957
1958
|
if (stats2.length === 0) return "暂无统计数据";
|
|
1958
1959
|
applySort(stats2);
|
|
1960
|
+
const limitedStats2 = options.limit > 0 ? stats2.slice(0, options.limit) : stats2;
|
|
1959
1961
|
const total2 = stats2.reduce((sum, r) => sum + r.count, 0);
|
|
1960
|
-
const list2 =
|
|
1962
|
+
const list2 = limitedStats2.map((item) => [item.type, item.count, item.lastUsed]);
|
|
1961
1963
|
return this.renderer.renderList({ title, time: /* @__PURE__ */ new Date(), total: total2, list: list2 }, ["类型", "条数", "最后发言"]);
|
|
1962
1964
|
}
|
|
1963
1965
|
if (options.user) {
|
|
@@ -1966,14 +1968,16 @@ var Stat = class {
|
|
|
1966
1968
|
const stats2 = await this.ctx.database.select("analyse_msg").where(query).groupBy("uid", { count: /* @__PURE__ */ __name((row) => import_koishi3.$.sum(row.count), "count"), lastUsed: /* @__PURE__ */ __name((row) => import_koishi3.$.max(row.timestamp), "lastUsed") }).execute();
|
|
1967
1969
|
if (stats2.length === 0) return "暂无统计数据";
|
|
1968
1970
|
applySort(stats2);
|
|
1971
|
+
const limitedStats2 = options.limit > 0 ? stats2.slice(0, options.limit) : stats2;
|
|
1969
1972
|
const total2 = stats2.reduce((sum, r) => sum + r.count, 0);
|
|
1970
|
-
const list2 =
|
|
1973
|
+
const list2 = limitedStats2.map((item) => [uidToChannelMap.get(item.uid) || `未知群组`, item.count, item.lastUsed]);
|
|
1971
1974
|
return this.renderer.renderList({ title, time: /* @__PURE__ */ new Date(), total: total2, list: list2 }, ["群组", "条数", "最后发言"]);
|
|
1972
1975
|
}
|
|
1973
1976
|
const stats = await this.ctx.database.select("analyse_msg").where(query).groupBy("uid", { count: /* @__PURE__ */ __name((row) => import_koishi3.$.sum(row.count), "count"), lastUsed: /* @__PURE__ */ __name((row) => import_koishi3.$.max(row.timestamp), "lastUsed") }).execute();
|
|
1974
1977
|
if (stats.length === 0) return "暂无统计数据";
|
|
1975
1978
|
applySort(stats);
|
|
1976
|
-
const
|
|
1979
|
+
const limitedStats = options.limit > 0 ? stats.slice(0, options.limit) : stats;
|
|
1980
|
+
const allUids = limitedStats.map((s) => s.uid);
|
|
1977
1981
|
const userNameMap = /* @__PURE__ */ new Map();
|
|
1978
1982
|
const BATCH_SIZE2 = 4096;
|
|
1979
1983
|
for (let i = 0; i < allUids.length; i += BATCH_SIZE2) {
|
|
@@ -1984,12 +1988,12 @@ var Stat = class {
|
|
|
1984
1988
|
}
|
|
1985
1989
|
}
|
|
1986
1990
|
const total = stats.reduce((sum, r) => sum + r.count, 0);
|
|
1987
|
-
const list =
|
|
1991
|
+
const list = limitedStats.map((item) => [userNameMap.get(item.uid) || `UID ${item.uid}`, item.count, item.lastUsed]);
|
|
1988
1992
|
return this.renderer.renderList({ title, time: /* @__PURE__ */ new Date(), total, list }, ["用户", "条数", "最后发言"]);
|
|
1989
1993
|
})()));
|
|
1990
1994
|
}
|
|
1991
1995
|
if (this.config.enableRankStat) {
|
|
1992
|
-
cmd.subcommand("rankstat", "发言排行").usage("查询发言排行,可指定查询范围,默认当前群组。").option("user", "-u <user:string> 指定用户").option("guild", "-g <guildId:string> 指定群组").option("type", "-t <type:string> 指定类型").option("duration", "-n <hours:number> 指定时长", { fallback: 24 }).option("offset", "-o <hours:number> 指定偏移", { fallback: 0 }).option("all", "-a 全局统计").action(({ session, options }) => handleAction(session, (async () => {
|
|
1996
|
+
cmd.subcommand("rankstat", "发言排行").usage("查询发言排行,可指定查询范围,默认当前群组。").option("user", "-u <user:string> 指定用户").option("guild", "-g <guildId:string> 指定群组").option("type", "-t <type:string> 指定类型").option("duration", "-n <hours:number> 指定时长", { fallback: 24 }).option("offset", "-o <hours:number> 指定偏移", { fallback: 0 }).option("limit", "-l <count:number> 限制数量").option("all", "-a 全局统计").action(({ session, options }) => handleAction(session, (async () => {
|
|
1993
1997
|
const scope = await this.parseScope(session, options);
|
|
1994
1998
|
if (scope.error) return scope.error;
|
|
1995
1999
|
const until = new Date(Date.now() - options.offset * import_koishi3.Time.hour);
|
|
@@ -2001,8 +2005,9 @@ var Stat = class {
|
|
|
2001
2005
|
if (options.user && options.guild) {
|
|
2002
2006
|
const stats2 = await this.ctx.database.select("analyse_rank").where(query).groupBy("type", { count: /* @__PURE__ */ __name((row) => import_koishi3.$.sum(row.count), "count") }).orderBy("count", "desc").execute();
|
|
2003
2007
|
if (stats2.length === 0) return "暂无统计数据";
|
|
2008
|
+
const limitedStats2 = options.limit > 0 ? stats2.slice(0, options.limit) : stats2;
|
|
2004
2009
|
const total2 = stats2.reduce((sum, r) => sum + r.count, 0);
|
|
2005
|
-
const list2 =
|
|
2010
|
+
const list2 = limitedStats2.map((r) => [r.type, r.count, total2 > 0 ? `${(r.count / total2 * 100).toFixed(2)}%` : "0.00%"]);
|
|
2006
2011
|
return this.renderer.renderList({ title, time: /* @__PURE__ */ new Date(), total: total2, list: list2 }, ["类型", "条数", "占比"]);
|
|
2007
2012
|
}
|
|
2008
2013
|
if (options.user) {
|
|
@@ -2010,13 +2015,15 @@ var Stat = class {
|
|
|
2010
2015
|
const uidToChannelMap = new Map(userRecords.map((u) => [u.uid, u.channelName || u.channelId]));
|
|
2011
2016
|
const stats2 = await this.ctx.database.select("analyse_rank").where(query).groupBy("uid", { count: /* @__PURE__ */ __name((row) => import_koishi3.$.sum(row.count), "count") }).orderBy("count", "desc").execute();
|
|
2012
2017
|
if (stats2.length === 0) return "暂无统计数据";
|
|
2018
|
+
const limitedStats2 = options.limit > 0 ? stats2.slice(0, options.limit) : stats2;
|
|
2013
2019
|
const total2 = stats2.reduce((sum, r) => sum + r.count, 0);
|
|
2014
|
-
const list2 =
|
|
2020
|
+
const list2 = limitedStats2.map((r) => [uidToChannelMap.get(r.uid) || "未知群组", r.count, total2 > 0 ? `${(r.count / total2 * 100).toFixed(2)}%` : "0.00%"]);
|
|
2015
2021
|
return this.renderer.renderList({ title, time: /* @__PURE__ */ new Date(), total: total2, list: list2 }, ["群组", "条数", "占比"]);
|
|
2016
2022
|
}
|
|
2017
2023
|
const stats = await this.ctx.database.select("analyse_rank").where(query).groupBy("uid", { count: /* @__PURE__ */ __name((row) => import_koishi3.$.sum(row.count), "count") }).orderBy("count", "desc").execute();
|
|
2018
2024
|
if (stats.length === 0) return "暂无统计数据";
|
|
2019
|
-
const
|
|
2025
|
+
const limitedStats = options.limit > 0 ? stats.slice(0, options.limit) : stats;
|
|
2026
|
+
const allUids = limitedStats.map((s) => s.uid);
|
|
2020
2027
|
const userNameMap = /* @__PURE__ */ new Map();
|
|
2021
2028
|
const BATCH_SIZE2 = 4096;
|
|
2022
2029
|
for (let i = 0; i < allUids.length; i += BATCH_SIZE2) {
|
|
@@ -2027,7 +2034,7 @@ var Stat = class {
|
|
|
2027
2034
|
}
|
|
2028
2035
|
}
|
|
2029
2036
|
const total = stats.reduce((sum, r) => sum + r.count, 0);
|
|
2030
|
-
const list =
|
|
2037
|
+
const list = limitedStats.map((r) => [userNameMap.get(r.uid) || `UID ${r.uid}`, r.count, total > 0 ? `${(r.count / total * 100).toFixed(2)}%` : "0.00%"]);
|
|
2031
2038
|
return this.renderer.renderList({ title, time: /* @__PURE__ */ new Date(), total, list }, ["用户", "条数", "占比"]);
|
|
2032
2039
|
})()));
|
|
2033
2040
|
}
|
|
@@ -2152,7 +2159,7 @@ var Data = class {
|
|
|
2152
2159
|
if (allUsers.length === 0) return;
|
|
2153
2160
|
const uidToUserInfoMap = new Map(allUsers.map((u) => [u.uid, u]));
|
|
2154
2161
|
const targetDate = /* @__PURE__ */ new Date();
|
|
2155
|
-
targetDate.
|
|
2162
|
+
targetDate.setDate(targetDate.getDate() - 1);
|
|
2156
2163
|
const year = targetDate.getFullYear();
|
|
2157
2164
|
const monthIndex = targetDate.getMonth();
|
|
2158
2165
|
const day = targetDate.getDate();
|
|
@@ -2317,6 +2324,40 @@ var Data = class {
|
|
|
2317
2324
|
}
|
|
2318
2325
|
});
|
|
2319
2326
|
if (this.config.enableOriRecord) {
|
|
2327
|
+
cmd.subcommand(".archive [date:date]", "手动归档", { authority: 4 }).usage("手动归档指定日期的原始消息记录。默认归档前一天的记录。").action(async ({}, date) => {
|
|
2328
|
+
const targetDate = date || /* @__PURE__ */ new Date();
|
|
2329
|
+
if (!date) targetDate.setDate(targetDate.getDate() - 1);
|
|
2330
|
+
try {
|
|
2331
|
+
await fs.mkdir(this.dataDir, { recursive: true });
|
|
2332
|
+
const allUsers = await this.ctx.database.get("analyse_user", {});
|
|
2333
|
+
if (allUsers.length === 0) return "暂无用户数据";
|
|
2334
|
+
const uidToUserInfoMap = new Map(allUsers.map((u) => [u.uid, u]));
|
|
2335
|
+
const year = targetDate.getFullYear();
|
|
2336
|
+
const monthIndex = targetDate.getMonth();
|
|
2337
|
+
const day = targetDate.getDate();
|
|
2338
|
+
const monthString = (monthIndex + 1).toString().padStart(2, "0");
|
|
2339
|
+
const dayString = day.toString().padStart(2, "0");
|
|
2340
|
+
const filename = `analyse_cache_${year}-${monthString}-${dayString}.json`;
|
|
2341
|
+
const filepath = path.join(this.dataDir, filename);
|
|
2342
|
+
const startDate = new Date(year, monthIndex, day);
|
|
2343
|
+
const endDate = new Date(year, monthIndex, day + 1);
|
|
2344
|
+
const records = await this.ctx.database.get("analyse_cache", { timestamp: { $gte: startDate, $lt: endDate } });
|
|
2345
|
+
if (records.length === 0) return `暂无统计数据`;
|
|
2346
|
+
const dataToExport = records.map((record) => {
|
|
2347
|
+
const userInfo = uidToUserInfoMap.get(record.uid);
|
|
2348
|
+
if (!userInfo) return null;
|
|
2349
|
+
const { id, uid, ...restOfRecord } = record;
|
|
2350
|
+
return { userId: userInfo.userId, channelId: userInfo.channelId, ...restOfRecord };
|
|
2351
|
+
}).filter(Boolean);
|
|
2352
|
+
if (dataToExport.length > 0) {
|
|
2353
|
+
await fs.writeFile(filepath, JSON.stringify(dataToExport, null, 2));
|
|
2354
|
+
return `已归档${year}年${monthString}月${dayString}日的${dataToExport.length}条记录`;
|
|
2355
|
+
}
|
|
2356
|
+
} catch (error) {
|
|
2357
|
+
this.ctx.logger.error("手动归档失败:", error);
|
|
2358
|
+
return "手动归档失败";
|
|
2359
|
+
}
|
|
2360
|
+
});
|
|
2320
2361
|
cmd.subcommand(".view [time:string]", "查询消息记录", { authority: 4 }).usage("查询指定时间点之前的消息记录,默认查询当前群组。").option("user", "-u <user:string> 指定用户").option("guild", "-g <guildId:string> 指定群组").option("count", "-n <count:number> 指定数量", { fallback: 100 }).action(async ({ session, options }, time) => {
|
|
2321
2362
|
const until = time ? new Date(time) : /* @__PURE__ */ new Date();
|
|
2322
2363
|
if (time && isNaN(until.getTime())) return "时间格式无效";
|
|
@@ -2453,7 +2494,7 @@ var Analyse = class {
|
|
|
2453
2494
|
}
|
|
2454
2495
|
});
|
|
2455
2496
|
}
|
|
2456
|
-
if (this.config.
|
|
2497
|
+
if (this.config.enableSimiActivity) {
|
|
2457
2498
|
cmd.subcommand("simiactive", "相似活跃分析").usage("分析你和群友的活跃规律,找出谁和你的作息最相似。").option("hours", "-n <hours:number> 指定时长", { fallback: 24 }).option("separate", "-p 分时分析").action(async ({ session, options }) => {
|
|
2458
2499
|
const effectiveChannelId = session.guildId || session.channelId;
|
|
2459
2500
|
if (!effectiveChannelId) return "请在群组中使用此命令";
|
|
@@ -2553,17 +2594,17 @@ var Config3 = import_koishi7.Schema.intersect([
|
|
|
2553
2594
|
enableCmdStat: import_koishi7.Schema.boolean().default(true).description("启用命令统计"),
|
|
2554
2595
|
enableMsgStat: import_koishi7.Schema.boolean().default(true).description("启用消息统计"),
|
|
2555
2596
|
enableActivity: import_koishi7.Schema.boolean().default(true).description("启用活跃统计"),
|
|
2597
|
+
enableSimiActivity: import_koishi7.Schema.boolean().default(true).description("启用活跃比较"),
|
|
2556
2598
|
enableRankStat: import_koishi7.Schema.boolean().default(true).description("启用发言排行"),
|
|
2557
|
-
rankRetentionDays: import_koishi7.Schema.number().min(0).default(365).description("排行保留天数"),
|
|
2558
2599
|
enableWhoAt: import_koishi7.Schema.boolean().default(true).description("启用提及记录"),
|
|
2600
|
+
rankRetentionDays: import_koishi7.Schema.number().min(0).default(365).description("排行保留天数"),
|
|
2559
2601
|
atRetentionDays: import_koishi7.Schema.number().min(0).default(3).description("提及保留天数")
|
|
2560
2602
|
}).description("基础分析配置"),
|
|
2561
2603
|
import_koishi7.Schema.object({
|
|
2562
2604
|
enableOriRecord: import_koishi7.Schema.boolean().default(true).description("启用原始记录"),
|
|
2563
|
-
cacheRetentionDays: import_koishi7.Schema.number().min(0).default(31).description("记录保留天数"),
|
|
2564
|
-
enableAutoBackup: import_koishi7.Schema.boolean().default(false).description("启用自动备份"),
|
|
2565
2605
|
enableWordCloud: import_koishi7.Schema.boolean().default(true).description("启用词云生成"),
|
|
2566
|
-
|
|
2606
|
+
enableAutoBackup: import_koishi7.Schema.boolean().default(true).description("启用自动归档"),
|
|
2607
|
+
cacheRetentionDays: import_koishi7.Schema.number().min(0).default(7).description("记录保留天数")
|
|
2567
2608
|
}).description("高级分析配置"),
|
|
2568
2609
|
import_koishi7.Schema.object({
|
|
2569
2610
|
maxWords: import_koishi7.Schema.number().min(0).default(1024).description("最大词量"),
|
|
@@ -2622,7 +2663,7 @@ function apply(ctx, config) {
|
|
|
2622
2663
|
new Stat(ctx, config).registerCommands(analyse);
|
|
2623
2664
|
if (config.enableWhoAt) new WhoAt(ctx, config).registerCommand(analyse);
|
|
2624
2665
|
if (config.enableDataIO) new Data(ctx, config).registerCommands(analyse);
|
|
2625
|
-
if (config.enableWordCloud || config.
|
|
2666
|
+
if (config.enableWordCloud || config.enableSimiActivity) new Analyse(ctx, config).registerCommands(analyse);
|
|
2626
2667
|
}
|
|
2627
2668
|
__name(apply, "apply");
|
|
2628
2669
|
// Annotate the CommonJS export names for ESM import in node:
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -8,18 +8,18 @@
|
|
|
8
8
|
|
|
9
9
|
**高效数据收集**:采用异步、高并发和缓存机制,在不影响机器人性能的前提下,精确高效地收集聊天数据。
|
|
10
10
|
**多维度统计分析**:
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
**命令统计** (`cmdstat`):追踪指令使用频率,了解用户最常用的功能,支持按次数或时间排序。
|
|
12
|
+
**发言统计** (`msgstat`):分析用户发言类型与数量,掌握核心用户群体,支持按条数或时间排序。
|
|
13
|
+
**发言排行** (`rankstat`):生成指定时间范围内的用户发言排行榜,发掘群聊中的“龙王”。
|
|
14
|
+
**活跃度分析** (`activity`):以小时或天为单位,生成直观的周期性活跃度图表,洞察社群活跃时段。
|
|
15
15
|
**高级文本分析**:
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
**词云生成** (`wordcloud`):基于聊天记录,利用 Jieba 分词生成热门话题词云图,快速了解近期热点。
|
|
17
|
+
**相似活跃分析** (`simiactive`):分析指定时间内,找出与您作息模式最相似的群友,并通过对比图表直观展示。
|
|
18
|
+
**提及追踪** (`whoatme`):轻松查询谁在什么时候因为什么内容提及了您,不再错过重要信息。
|
|
19
19
|
**强大的数据管理**:
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
**备份与恢复** (`.backup`/`.restore`):一键备份所有统计数据至本地,并可随时恢复,保障数据安全。
|
|
21
|
+
**精确清理** (`.clear`):提供多维度的筛选条件(如按时间、用户、群组、发言数),精确清理不再需要的数据。
|
|
22
|
+
**记录查看** (`.view`):可回溯查看指定时间点之前的原始消息记录。
|
|
23
23
|
**精美图表渲染**:借助 Puppeteer 服务,将复杂的统计数据渲染成美观、易读的图片,方便在聊天中分享。
|
|
24
24
|
**高度可配置**:所有功能模块均可独立开关,并可自定义数据保留时长等核心参数,以适应不同场景的需求。
|
|
25
25
|
|
|
@@ -38,18 +38,19 @@
|
|
|
38
38
|
### 指令列表
|
|
39
39
|
|
|
40
40
|
| 指令 | 别名 | 描述 | 选项 |
|
|
41
|
-
|
|
|
42
|
-
| `cmdstat` | 命令统计 | 查询命令使用情况,展示方式根据选项变化 | `-u`, `-g`, `-
|
|
43
|
-
| `msgstat` | 发言统计 | 查询用户发言统计,展示方式根据选项变化 | `-u`, `-g`, `-
|
|
44
|
-
| `rankstat` | 发言排行 | 查询指定时间内的发言排行 | `-u`, `-g`, `-
|
|
45
|
-
| `activity` | 活跃统计 | 查询周期性活跃度图表 | `-u`, `-g`, `-
|
|
41
|
+
| :--- | :--- | :--- | :--- |
|
|
42
|
+
| `cmdstat` | 命令统计 | 查询命令使用情况,展示方式根据选项变化 | `-u`, `-g`, `-l`, `-p`, `-s`, `-a` |
|
|
43
|
+
| `msgstat` | 发言统计 | 查询用户发言统计,展示方式根据选项变化 | `-u`, `-g`, `-t`, `-l`, `-s`, `-a` |
|
|
44
|
+
| `rankstat` | 发言排行 | 查询指定时间内的发言排行 | `-u`, `-g`, `-t`, `-n`, `-o`, `-l`, `-a` |
|
|
45
|
+
| `activity` | 活跃统计 | 查询周期性活跃度图表 | `-u`, `-g`, `-d`, `-n`, `-o`, `-a` |
|
|
46
46
|
| `wordcloud` | 生成词云 | 基于聊天记录生成词云图 | `-u`, `-g`, `-t` |
|
|
47
47
|
| `simiactive`| 相似活跃分析 | 分析并找出与您作息相似的群友 | `-n`, `-p` |
|
|
48
48
|
| `whoatme` | 谁提及我 | 查看最近谁提及了您 | (无) |
|
|
49
|
-
| `analyse.view <time>`| 查询记录 | (管理)
|
|
49
|
+
| `analyse.view <time>`| 查询记录 | (管理) 查询指定时间点之前的消息记录 | `-u`, `-g`, `-n` |
|
|
50
50
|
| `analyse.list` | 列出数据 | (管理) 列出已记录的频道和命令 | (无) |
|
|
51
|
-
| `analyse.backup` | 备份数据 | (管理) 将所有数据备份为本地 JSON 文件 |
|
|
52
|
-
| `analyse.restore` | 恢复数据 | (管理) 从本地 JSON 文件恢复数据 |
|
|
51
|
+
| `analyse.backup` | 备份数据 | (管理) 将所有数据备份为本地 JSON 文件 | `-a` |
|
|
52
|
+
| `analyse.restore` | 恢复数据 | (管理) 从本地 JSON 文件恢复数据 | `-a` |
|
|
53
|
+
| `analyse.archive [date]`| 手动归档 | (管理) 手动归档指定日期的原始消息记录 | (无) |
|
|
53
54
|
| `analyse.clear` | 清除数据 | (管理) 根据条件精确清理数据 | `-t`, `-g`, `-u`, `-d`, `-c`, `-l`, `-a` |
|
|
54
55
|
|
|
55
56
|
**通用选项说明:**
|
|
@@ -57,6 +58,7 @@
|
|
|
57
58
|
`-u, --user <user>`: 指定用户 (可使用 @ 或 userID)。
|
|
58
59
|
`-g, --guild <guild>`: 指定群组 (需使用群组ID)。
|
|
59
60
|
`-a, --all`: 查询全局数据。
|
|
61
|
+
`-l, --limit <count>`: 限制返回的条目数量。
|
|
60
62
|
|
|
61
63
|
### 🔎 指令详解
|
|
62
64
|
|
|
@@ -77,7 +79,7 @@
|
|
|
77
79
|
**`msgstat -g <群组ID>`**: 查询**指定群组**的发言统计 (按**用户**展示)。
|
|
78
80
|
**`msgstat -u @用户 -g <群组ID>`**: 查询**指定用户**在**指定群组**的发言统计 (按**消息类型**展示)。
|
|
79
81
|
**`msgstat -a`**: 查询**全局**发言统计 (按**用户**展示)。
|
|
80
|
-
**选项 `-t, --type <类型>`**: 筛选指定消息类型 (`text`, `face`, `
|
|
82
|
+
**选项 `-t, --type <类型>`**: 筛选指定消息类型 (`text`, `face`, `img` 等),不改变上述展示逻辑。
|
|
81
83
|
**选项 `-s, --sortByTime`**: 按最后发言时间降序排序(默认按发言条数)。
|
|
82
84
|
|
|
83
85
|
#### `rankstat` (发言排行)
|
|
@@ -88,7 +90,7 @@
|
|
|
88
90
|
**`rankstat -a`**: 查询**全局**发言排行 (按**用户**排名)。
|
|
89
91
|
**选项 `-n, --duration <小时数>`**: 指定查询范围的时长,默认为 `24`。
|
|
90
92
|
**选项 `-o, --offset <小时数>`**: 指定查询结束时间的偏移量(从现在往前推的小时数),默认为 `0`。
|
|
91
|
-
**选项 `-t, --type
|
|
93
|
+
**选项 `-t, --type <类型>`**: 筛选指定消息类型。
|
|
92
94
|
|
|
93
95
|
#### `activity` (活跃统计)
|
|
94
96
|
|
|
@@ -118,7 +120,7 @@
|
|
|
118
120
|
该指令用于高级数据管理,请谨慎使用。
|
|
119
121
|
|
|
120
122
|
| 选项 | 别名 | 描述 |
|
|
121
|
-
|
|
|
123
|
+
| :--- | :--- | :--- |
|
|
122
124
|
| `--table <表名>` | `-t` | 指定要清除的表名 (如 `analyse_cmd`)。若不指定,则默认清理除 `analyse_user` 外的所有表。 |
|
|
123
125
|
| `--guild <群组ID>` | `-g` | 仅清除指定群组的数据。 |
|
|
124
126
|
| `--user <用户>` | `-u` | 仅清除指定用户的数据。 |
|
|
@@ -141,35 +143,34 @@
|
|
|
141
143
|
`enableCmdStat`: **启用命令统计**。 (默认: `true`)
|
|
142
144
|
`enableMsgStat`: **启用消息统计**。 (默认: `true`)
|
|
143
145
|
`enableActivity`: **启用活跃统计**。 (默认: `true`)
|
|
146
|
+
`enableSimiActivity`: **启用活跃比较**。(默认: `true`)
|
|
144
147
|
`enableRankStat`: **启用发言排行**。 (默认: `true`)
|
|
145
|
-
`rankRetentionDays`: **排行保留天数**。发言排行数据的保留时长(天),`0` 为永久保留。 (默认: `365`)
|
|
146
148
|
`enableWhoAt`: **启用提及记录**。 (默认: `true`)
|
|
149
|
+
`rankRetentionDays`: **排行保留天数**。发言排行数据的保留时长(天),`0` 为永久保留。 (默认: `365`)
|
|
147
150
|
`atRetentionDays`: **提及保留天数**。`whoatme` 数据的保留时长(天),`0` 为永久保留。 (默认: `3`)
|
|
148
151
|
|
|
149
152
|
### 高级分析配置
|
|
150
153
|
|
|
151
154
|
`enableOriRecord`: **启用原始记录**。是否记录原始消息内容。这是 `.view` 和 `wordcloud` 功能的基础。 (默认: `true`)
|
|
152
|
-
`cacheRetentionDays`: **原始记录保留天数**。原始消息记录的保留时长(天),`0` 为永久保留。 (默认: `31`)
|
|
153
|
-
`enableAutoBackup`: **启用自动备份记录**。是否开启每月自动备份原始消息记录。 (默认: `false`)
|
|
154
155
|
`enableWordCloud`: **启用词云生成**。
|
|
155
|
-
|
|
156
|
-
`
|
|
157
|
-
|
|
156
|
+
> **!** 此功能依赖 **`启用原始记录`**。 (默认: `true`)
|
|
157
|
+
`enableAutoBackup`: **启用自动归档**。每日自动将前一天的原始消息记录备份为 JSON 文件。 (默认: `true`)
|
|
158
|
+
`cacheRetentionDays`: **记录保留天数**。原始消息记录在数据库中的保留时长(天),`0` 为永久保留。 (默认: `7`)
|
|
158
159
|
|
|
159
160
|
### 词云生成配置
|
|
160
161
|
|
|
161
162
|
| 配置项 | 描述 | 默认值 |
|
|
162
163
|
| :--- | :--- | :--- |
|
|
163
|
-
| `
|
|
164
|
-
| `shape` | **词云形状**:预设值包括 `circle`, `cardioid`, `diamond`, `square`, `triangle`, `pentagon`, `star`。 | `square` |
|
|
165
|
-
| `maskImage` | **蒙版图片**:提供一个图片的URL作为词云的形状蒙版。**注意:这会覆盖“词云形状”选项。** | (空) |
|
|
166
|
-
| `fontFamily` | **词云字体**:用于渲染词云的字体列表。 | `"Noto Sans CJK SC", "Arial", sans-serif` |
|
|
164
|
+
| `maxWords`| **最大词量**:词云显示的最多词汇数量。 | `1024` |
|
|
167
165
|
| `ellipticity` | **长宽比**:形状的扁平程度(0-1),仅对非方形形状有效。 | `1` |
|
|
166
|
+
| `rotationSteps`| **旋转步数**:旋转角度的选择方式。0表示随机,2表示只在最小/最大角度中二选一。 | `3` |
|
|
168
167
|
| `minRotation` | **最小旋转角**:单词随机旋转的最小角度(弧度)。 | `-1.570796` (-π/2) |
|
|
169
168
|
| `maxRotation` | **最大旋转角**:单词随机旋转的最大角度(弧度)。 | `1.570796` (π/2) |
|
|
170
|
-
| `
|
|
171
|
-
| `
|
|
172
|
-
| `
|
|
169
|
+
| `shape` | **词云形状**:预设值包括 `circle`, `cardioid`, `diamond`, `square`, `triangle`, `pentagon`, `star`。 | `square` |
|
|
170
|
+
| `color` | **词云颜色**:可设为 `random-light`、`random-dark`、CSS颜色值(如`#ff0000`)或一个返回颜色的JS函数。 | `random-light` |
|
|
171
|
+
| `fontFamily` | **词云字体**:用于渲染词云的字体列表。 | `"Noto Sans CJK SC", Arial, sans-serif` |
|
|
172
|
+
| `maskImage` | **蒙版图片**:提供一个图片的URL作为词云的形状蒙版。**注意:这会覆盖“词云形状”选项。** | (空) |
|
|
173
|
+
| `excludeWords`| **屏蔽词**:以下词汇不会显示在词云中,使用英文逗号分隔。 | (空) |
|
|
173
174
|
|
|
174
175
|
## 📌 注意事项
|
|
175
176
|
|