koishi-plugin-chat-analyse 1.4.0 → 1.4.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.
- package/lib/Data.d.ts +6 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +51 -14
- package/package.json +1 -1
- package/readme.md +1 -0
package/lib/Data.d.ts
CHANGED
|
@@ -8,6 +8,12 @@ export declare class Data {
|
|
|
8
8
|
private config;
|
|
9
9
|
private dataDir;
|
|
10
10
|
constructor(ctx: Context, config: any);
|
|
11
|
+
/**
|
|
12
|
+
* @private
|
|
13
|
+
* @method backupCache
|
|
14
|
+
* @description 备份 analyse_cache 表到 JSON 文件。
|
|
15
|
+
*/
|
|
16
|
+
private backupCache;
|
|
11
17
|
/**
|
|
12
18
|
* @public
|
|
13
19
|
* @method registerCommands
|
package/lib/index.d.ts
CHANGED
package/lib/index.js
CHANGED
|
@@ -2104,17 +2104,51 @@ var import_koishi5 = require("koishi");
|
|
|
2104
2104
|
var fs = __toESM(require("fs/promises"));
|
|
2105
2105
|
var path = __toESM(require("path"));
|
|
2106
2106
|
var ALL_TABLES = ["analyse_user", "analyse_cmd", "analyse_msg", "analyse_rank", "analyse_at", "analyse_cache"];
|
|
2107
|
-
var
|
|
2107
|
+
var DEFAULT_BACKUP_TABLES = ["analyse_user", "analyse_cmd", "analyse_msg", "analyse_rank"];
|
|
2108
|
+
var BATCH_SIZE = 1e3;
|
|
2108
2109
|
var Data = class {
|
|
2109
2110
|
constructor(ctx, config) {
|
|
2110
2111
|
this.ctx = ctx;
|
|
2111
2112
|
this.config = config;
|
|
2112
2113
|
this.dataDir = path.join(this.ctx.baseDir, "data", "chat-analyse");
|
|
2114
|
+
if (this.config.enableAutoBackup) this.ctx.cron("0 0 1 * *", () => {
|
|
2115
|
+
this.backupCache();
|
|
2116
|
+
});
|
|
2113
2117
|
}
|
|
2114
2118
|
static {
|
|
2115
2119
|
__name(this, "Data");
|
|
2116
2120
|
}
|
|
2117
2121
|
dataDir;
|
|
2122
|
+
/**
|
|
2123
|
+
* @private
|
|
2124
|
+
* @method backupCache
|
|
2125
|
+
* @description 备份 analyse_cache 表到 JSON 文件。
|
|
2126
|
+
*/
|
|
2127
|
+
async backupCache() {
|
|
2128
|
+
try {
|
|
2129
|
+
await fs.mkdir(this.dataDir, { recursive: true });
|
|
2130
|
+
const now = /* @__PURE__ */ new Date();
|
|
2131
|
+
const lastMonth = new Date(now.getFullYear(), now.getMonth() - 1, 1);
|
|
2132
|
+
const year = lastMonth.getFullYear();
|
|
2133
|
+
const month = (lastMonth.getMonth() + 1).toString().padStart(2, "0");
|
|
2134
|
+
const filename = `analyse_cache_${year}-${month}.json`;
|
|
2135
|
+
const filepath = path.join(this.dataDir, filename);
|
|
2136
|
+
const allUsers = await this.ctx.database.get("analyse_user", {});
|
|
2137
|
+
if (allUsers.length === 0) return;
|
|
2138
|
+
const uidToUserInfoMap = new Map(allUsers.map((u) => [u.uid, u]));
|
|
2139
|
+
const records = await this.ctx.database.get("analyse_cache", {});
|
|
2140
|
+
if (records.length === 0) return;
|
|
2141
|
+
const dataToExport = records.map((record) => {
|
|
2142
|
+
const userInfo = uidToUserInfoMap.get(record.uid);
|
|
2143
|
+
if (!userInfo) return null;
|
|
2144
|
+
const { id, uid, ...restOfRecord } = record;
|
|
2145
|
+
return { userId: userInfo.userId, channelId: userInfo.channelId, ...restOfRecord };
|
|
2146
|
+
}).filter(Boolean);
|
|
2147
|
+
await fs.writeFile(filepath, JSON.stringify(dataToExport, null, 2));
|
|
2148
|
+
} catch (error) {
|
|
2149
|
+
this.ctx.logger.error("原始记录备份失败:", error);
|
|
2150
|
+
}
|
|
2151
|
+
}
|
|
2118
2152
|
/**
|
|
2119
2153
|
* @public
|
|
2120
2154
|
* @method registerCommands
|
|
@@ -2122,12 +2156,13 @@ var Data = class {
|
|
|
2122
2156
|
* @param cmd - 主命令实例。
|
|
2123
2157
|
*/
|
|
2124
2158
|
registerCommands(cmd) {
|
|
2125
|
-
cmd.subcommand(".backup", "备份数据", { authority: 4 }).usage("
|
|
2159
|
+
cmd.subcommand(".backup", "备份数据", { authority: 4 }).usage("将统计数据导出为 JSON 文件,并保存到本地。").option("all", "-a 全量备份").action(async ({ options }) => {
|
|
2160
|
+
const tablesToProcess = options.all ? ALL_TABLES : DEFAULT_BACKUP_TABLES;
|
|
2126
2161
|
try {
|
|
2127
2162
|
await fs.mkdir(this.dataDir, { recursive: true });
|
|
2128
2163
|
const allUsers = await this.ctx.database.get("analyse_user", {});
|
|
2129
2164
|
const uidToUserInfoMap = new Map(allUsers.map((u) => [u.uid, u]));
|
|
2130
|
-
for (const tableName of
|
|
2165
|
+
for (const tableName of tablesToProcess) {
|
|
2131
2166
|
const filepath = path.join(this.dataDir, `${tableName}.json`);
|
|
2132
2167
|
let dataToExport;
|
|
2133
2168
|
if (tableName === "analyse_user") {
|
|
@@ -2149,14 +2184,15 @@ var Data = class {
|
|
|
2149
2184
|
return "数据备份失败";
|
|
2150
2185
|
}
|
|
2151
2186
|
});
|
|
2152
|
-
cmd.subcommand(".restore", "恢复数据", { authority: 4 }).usage(
|
|
2187
|
+
cmd.subcommand(".restore", "恢复数据", { authority: 4 }).usage("从本地的 JSON 文件中恢复统计数据。").option("all", "-a 全量恢复").action(async ({ options }) => {
|
|
2153
2188
|
try {
|
|
2154
2189
|
const userTablePath = path.join(this.dataDir, "analyse_user.json");
|
|
2155
2190
|
const usersToImport = JSON.parse(await fs.readFile(userTablePath, "utf-8").catch(() => "[]"));
|
|
2156
2191
|
if (usersToImport.length) for (let i = 0; i < usersToImport.length; i += BATCH_SIZE) await this.ctx.database.upsert("analyse_user", usersToImport.slice(i, i + BATCH_SIZE));
|
|
2157
2192
|
const allUsers = await this.ctx.database.get("analyse_user", {});
|
|
2158
2193
|
const userToUidMap = new Map(allUsers.map((u) => [`${u.channelId}:${u.userId}`, u.uid]));
|
|
2159
|
-
|
|
2194
|
+
const tablesToProcess = options.all ? ALL_TABLES.filter((t) => t !== "analyse_user") : DEFAULT_BACKUP_TABLES.filter((t) => t !== "analyse_user");
|
|
2195
|
+
for (const tableName of tablesToProcess) {
|
|
2160
2196
|
const filepath = path.join(this.dataDir, `${tableName}.json`);
|
|
2161
2197
|
const recordsToImport = JSON.parse(await fs.readFile(filepath, "utf-8").catch(() => "[]"));
|
|
2162
2198
|
if (!recordsToImport.length) continue;
|
|
@@ -2245,10 +2281,9 @@ var Data = class {
|
|
|
2245
2281
|
}
|
|
2246
2282
|
});
|
|
2247
2283
|
if (this.config.enableOriRecord) {
|
|
2248
|
-
cmd.subcommand(".view
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
if (isNaN(since.getTime())) return "时间格式无效";
|
|
2284
|
+
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) => {
|
|
2285
|
+
const until = time ? new Date(time) : /* @__PURE__ */ new Date();
|
|
2286
|
+
if (time && isNaN(until.getTime())) return "时间格式无效";
|
|
2252
2287
|
try {
|
|
2253
2288
|
const userQuery = {};
|
|
2254
2289
|
if (!options.guild && !options.user) {
|
|
@@ -2263,12 +2298,13 @@ var Data = class {
|
|
|
2263
2298
|
const uids = usersInScope.map((u) => u.uid);
|
|
2264
2299
|
const records = await this.ctx.database.get("analyse_cache", {
|
|
2265
2300
|
uid: { $in: uids },
|
|
2266
|
-
timestamp: { $
|
|
2301
|
+
timestamp: { $lte: until }
|
|
2267
2302
|
}, {
|
|
2268
|
-
sort: { timestamp: "
|
|
2269
|
-
limit:
|
|
2303
|
+
sort: { timestamp: "desc" },
|
|
2304
|
+
limit: options.count
|
|
2270
2305
|
});
|
|
2271
2306
|
if (records.length === 0) return "暂无统计数据";
|
|
2307
|
+
records.reverse();
|
|
2272
2308
|
const recordUids = [...new Set(records.map((r) => r.uid))];
|
|
2273
2309
|
const users = await this.ctx.database.get("analyse_user", { uid: { $in: recordUids } }, ["uid", "userName", "userId"]);
|
|
2274
2310
|
const userInfoMap = new Map(users.map((u) => [u.uid, { name: u.userName, id: u.userId }]));
|
|
@@ -2472,13 +2508,14 @@ var Config3 = import_koishi7.Schema.intersect([
|
|
|
2472
2508
|
enableMsgStat: import_koishi7.Schema.boolean().default(true).description("启用消息统计"),
|
|
2473
2509
|
enableActivity: import_koishi7.Schema.boolean().default(true).description("启用活跃统计"),
|
|
2474
2510
|
enableRankStat: import_koishi7.Schema.boolean().default(true).description("启用发言排行"),
|
|
2475
|
-
rankRetentionDays: import_koishi7.Schema.number().min(0).default(
|
|
2511
|
+
rankRetentionDays: import_koishi7.Schema.number().min(0).default(365).description("排行保留天数"),
|
|
2476
2512
|
enableWhoAt: import_koishi7.Schema.boolean().default(true).description("启用提及记录"),
|
|
2477
2513
|
atRetentionDays: import_koishi7.Schema.number().min(0).default(3).description("提及保留天数")
|
|
2478
2514
|
}).description("基础分析配置"),
|
|
2479
2515
|
import_koishi7.Schema.object({
|
|
2480
2516
|
enableOriRecord: import_koishi7.Schema.boolean().default(true).description("启用原始记录"),
|
|
2481
|
-
cacheRetentionDays: import_koishi7.Schema.number().min(0).default(
|
|
2517
|
+
cacheRetentionDays: import_koishi7.Schema.number().min(0).default(31).description("记录保留天数"),
|
|
2518
|
+
enableAutoBackup: import_koishi7.Schema.boolean().default(false).description("启用自动备份"),
|
|
2482
2519
|
enableWordCloud: import_koishi7.Schema.boolean().default(true).description("启用词云生成"),
|
|
2483
2520
|
enableSimilarActivity: import_koishi7.Schema.boolean().default(true).description("启用相似活跃分析")
|
|
2484
2521
|
}).description("高级分析配置")
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -150,6 +150,7 @@
|
|
|
150
150
|
|
|
151
151
|
`enableOriRecord`: **启用原始记录**。是否记录原始消息内容。这是 `.view` 和 `wordcloud` 功能的基础。 (默认: `true`)
|
|
152
152
|
`cacheRetentionDays`: **原始记录保留天数**。原始消息记录的保留时长(天),`0` 为永久保留。 (默认: `30`)
|
|
153
|
+
`enableAutoBackup`: **启用自动备份记录**。是否开启每月自动备份原始消息记录。 (默认: `false`)
|
|
153
154
|
`enableWordCloud`: **启用词云生成**。
|
|
154
155
|
> **!** 此功能依赖 **`启用原始记录`**。 (默认: `true`)
|
|
155
156
|
`enableSimilarActivity`: **启用相似活跃分析**。
|