koishi-plugin-kkk 3.7.2 → 3.9.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/CHANGELOG.md +48 -0
- package/lib/build-metadata.json +5 -5
- package/lib/index.js +8 -3
- package/lib/karin/module/db/bilibili.js +185 -393
- package/lib/karin/module/db/douyin.js +219 -500
- package/lib/karin/module/db/index.js +30 -5
- package/lib/karin/module/db/model.js +200 -0
- package/lib/karin/module/db/statistics.js +197 -242
- package/lib/karin/module/utils/amagiClient.js +40 -15
- package/lib/karin/platform/douyin/douyin.js +10 -0
- package/lib/karin/setup.js +2 -4
- package/package.json +3 -2
- package/scripts/build.mjs +38 -5
- package/scripts/deploy.mjs +76 -27
- package/scripts/smoke-amagi-capability.cjs +97 -0
- package/scripts/smoke-comments.cjs +164 -0
- package/scripts/smoke-db-koishi.cjs +170 -0
- package/src/index.ts +8 -3
- package/src/karin/module/db/bilibili.ts +189 -448
- package/src/karin/module/db/douyin.ts +219 -587
- package/src/karin/module/db/index.ts +30 -4
- package/src/karin/module/db/model.ts +439 -0
- package/src/karin/module/db/statistics.ts +213 -380
- package/src/karin/module/utils/amagiClient.ts +49 -27
- package/src/karin/platform/douyin/douyin.ts +10 -0
- package/src/karin/setup.ts +2 -4
- package/lib/compat/sqlite3.js +0 -117
- package/lib/karin/module/db/migration.js +0 -199
- package/src/compat/sqlite3.ts +0 -123
- package/src/karin/module/db/migration.ts +0 -249
|
@@ -1,23 +1,20 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
3
|
exports.BilibiliDBBase = void 0;
|
|
7
|
-
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
-
const node_path_1 = __importDefault(require("node:path"));
|
|
9
4
|
const amagi_1 = require("@ikenxuan/amagi");
|
|
10
5
|
const node_karin_1 = require("../../../compat/node-karin.js");
|
|
11
|
-
const root_1 = require("../../../compat/root.js");
|
|
12
|
-
const sqlite3_1 = __importDefault(require("../../../compat/sqlite3.js"));
|
|
13
|
-
const utils_1 = require("../../module/utils");
|
|
14
6
|
const Config_1 = require("../../module/utils/Config");
|
|
15
|
-
|
|
7
|
+
const model_1 = require("./model");
|
|
8
|
+
/** 当前时间戳(ISO 字符串,与旧版 sqlite 里的口径一致) */
|
|
9
|
+
const now = () => new Date().toISOString();
|
|
10
|
+
/** 数据库操作类(基于 Koishi 原生数据库服务) */
|
|
16
11
|
class BilibiliDBBase {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
12
|
+
ctx;
|
|
13
|
+
constructor(ctx) {
|
|
14
|
+
this.ctx = ctx;
|
|
15
|
+
}
|
|
16
|
+
get db() {
|
|
17
|
+
return this.ctx.database;
|
|
21
18
|
}
|
|
22
19
|
/**
|
|
23
20
|
* 初始化数据库
|
|
@@ -25,13 +22,7 @@ class BilibiliDBBase {
|
|
|
25
22
|
async init() {
|
|
26
23
|
try {
|
|
27
24
|
node_karin_1.logger.debug(node_karin_1.logger.green('--------------------------[BilibiliDB] 开始初始化数据库--------------------------'));
|
|
28
|
-
node_karin_1.logger.debug('[BilibiliDB]
|
|
29
|
-
// 创建数据库连接
|
|
30
|
-
await node_fs_1.default.promises.mkdir(node_path_1.default.dirname(this.dbPath), { recursive: true });
|
|
31
|
-
this.db = new sqlite3_1.default.Database(this.dbPath);
|
|
32
|
-
// 创建表结构
|
|
33
|
-
await this.createTables();
|
|
34
|
-
node_karin_1.logger.debug('[BilibiliDB] 数据库模型同步成功');
|
|
25
|
+
node_karin_1.logger.debug('[BilibiliDB] 使用 Koishi 原生数据库服务,表已由 ctx.model.extend 注册');
|
|
35
26
|
node_karin_1.logger.debug('[BilibiliDB] 正在同步配置订阅...');
|
|
36
27
|
node_karin_1.logger.debug('[BilibiliDB] 配置项数量:', Config_1.Config.pushlist.bilibili?.length || 0);
|
|
37
28
|
await this.syncConfigSubscriptions(Config_1.Config.pushlist.bilibili);
|
|
@@ -44,139 +35,16 @@ class BilibiliDBBase {
|
|
|
44
35
|
}
|
|
45
36
|
return this;
|
|
46
37
|
}
|
|
47
|
-
/**
|
|
48
|
-
* 创建数据库表结构
|
|
49
|
-
*/
|
|
50
|
-
async createTables() {
|
|
51
|
-
const queries = [
|
|
52
|
-
// 创建机器人表
|
|
53
|
-
`CREATE TABLE IF NOT EXISTS Bots (
|
|
54
|
-
id TEXT PRIMARY KEY,
|
|
55
|
-
createdAt TEXT DEFAULT CURRENT_TIMESTAMP,
|
|
56
|
-
updatedAt TEXT DEFAULT CURRENT_TIMESTAMP
|
|
57
|
-
)`,
|
|
58
|
-
// 创建群组表
|
|
59
|
-
`CREATE TABLE IF NOT EXISTS Groups (
|
|
60
|
-
id TEXT NOT NULL,
|
|
61
|
-
botId TEXT NOT NULL,
|
|
62
|
-
createdAt TEXT DEFAULT CURRENT_TIMESTAMP,
|
|
63
|
-
updatedAt TEXT DEFAULT CURRENT_TIMESTAMP,
|
|
64
|
-
PRIMARY KEY (id, botId),
|
|
65
|
-
FOREIGN KEY (botId) REFERENCES Bots(id)
|
|
66
|
-
)`,
|
|
67
|
-
// 创建B站用户表
|
|
68
|
-
`CREATE TABLE IF NOT EXISTS BilibiliUsers (
|
|
69
|
-
host_mid INTEGER PRIMARY KEY,
|
|
70
|
-
remark TEXT,
|
|
71
|
-
filterMode TEXT DEFAULT 'blacklist',
|
|
72
|
-
createdAt TEXT DEFAULT CURRENT_TIMESTAMP,
|
|
73
|
-
updatedAt TEXT DEFAULT CURRENT_TIMESTAMP
|
|
74
|
-
)`,
|
|
75
|
-
// 创建群组用户订阅关系表
|
|
76
|
-
`CREATE TABLE IF NOT EXISTS GroupUserSubscriptions (
|
|
77
|
-
groupId TEXT,
|
|
78
|
-
host_mid INTEGER,
|
|
79
|
-
createdAt TEXT DEFAULT CURRENT_TIMESTAMP,
|
|
80
|
-
updatedAt TEXT DEFAULT CURRENT_TIMESTAMP,
|
|
81
|
-
PRIMARY KEY (groupId, host_mid),
|
|
82
|
-
FOREIGN KEY (groupId) REFERENCES Groups(id),
|
|
83
|
-
FOREIGN KEY (host_mid) REFERENCES BilibiliUsers(host_mid)
|
|
84
|
-
)`,
|
|
85
|
-
// 创建动态缓存表
|
|
86
|
-
`CREATE TABLE IF NOT EXISTS DynamicCaches (
|
|
87
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
88
|
-
dynamic_id TEXT NOT NULL,
|
|
89
|
-
host_mid INTEGER NOT NULL,
|
|
90
|
-
groupId TEXT NOT NULL,
|
|
91
|
-
dynamic_type TEXT,
|
|
92
|
-
createdAt TEXT DEFAULT CURRENT_TIMESTAMP,
|
|
93
|
-
updatedAt TEXT DEFAULT CURRENT_TIMESTAMP,
|
|
94
|
-
FOREIGN KEY (host_mid) REFERENCES BilibiliUsers(host_mid),
|
|
95
|
-
FOREIGN KEY (groupId) REFERENCES Groups(id),
|
|
96
|
-
UNIQUE(dynamic_id, host_mid, groupId)
|
|
97
|
-
)`,
|
|
98
|
-
// 创建过滤词表
|
|
99
|
-
`CREATE TABLE IF NOT EXISTS FilterWords (
|
|
100
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
101
|
-
host_mid INTEGER NOT NULL,
|
|
102
|
-
word TEXT NOT NULL,
|
|
103
|
-
createdAt TEXT DEFAULT CURRENT_TIMESTAMP,
|
|
104
|
-
updatedAt TEXT DEFAULT CURRENT_TIMESTAMP,
|
|
105
|
-
FOREIGN KEY (host_mid) REFERENCES BilibiliUsers(host_mid),
|
|
106
|
-
UNIQUE(host_mid, word)
|
|
107
|
-
)`,
|
|
108
|
-
// 创建过滤标签表
|
|
109
|
-
`CREATE TABLE IF NOT EXISTS FilterTags (
|
|
110
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
111
|
-
host_mid INTEGER NOT NULL,
|
|
112
|
-
tag TEXT NOT NULL,
|
|
113
|
-
createdAt TEXT DEFAULT CURRENT_TIMESTAMP,
|
|
114
|
-
updatedAt TEXT DEFAULT CURRENT_TIMESTAMP,
|
|
115
|
-
FOREIGN KEY (host_mid) REFERENCES BilibiliUsers(host_mid),
|
|
116
|
-
UNIQUE(host_mid, tag)
|
|
117
|
-
)`
|
|
118
|
-
];
|
|
119
|
-
for (const query of queries) {
|
|
120
|
-
await this.runQuery(query);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
/**
|
|
124
|
-
* 执行SQL查询
|
|
125
|
-
*/
|
|
126
|
-
runQuery(sql, params = []) {
|
|
127
|
-
return new Promise((resolve, reject) => {
|
|
128
|
-
this.db.run(sql, params, function (err) {
|
|
129
|
-
if (err) {
|
|
130
|
-
reject(err);
|
|
131
|
-
}
|
|
132
|
-
else {
|
|
133
|
-
resolve({ lastID: this.lastID, changes: this.changes });
|
|
134
|
-
}
|
|
135
|
-
});
|
|
136
|
-
});
|
|
137
|
-
}
|
|
138
|
-
/**
|
|
139
|
-
* 执行SQL查询并获取单个结果
|
|
140
|
-
*/
|
|
141
|
-
getQuery(sql, params = []) {
|
|
142
|
-
return new Promise((resolve, reject) => {
|
|
143
|
-
this.db.get(sql, params, (err, row) => {
|
|
144
|
-
if (err) {
|
|
145
|
-
reject(err);
|
|
146
|
-
}
|
|
147
|
-
else {
|
|
148
|
-
resolve(row);
|
|
149
|
-
}
|
|
150
|
-
});
|
|
151
|
-
});
|
|
152
|
-
}
|
|
153
|
-
/**
|
|
154
|
-
* 执行SQL查询并获取所有结果
|
|
155
|
-
*/
|
|
156
|
-
allQuery(sql, params = []) {
|
|
157
|
-
return new Promise((resolve, reject) => {
|
|
158
|
-
this.db.all(sql, params, (err, rows) => {
|
|
159
|
-
if (err) {
|
|
160
|
-
reject(err);
|
|
161
|
-
}
|
|
162
|
-
else {
|
|
163
|
-
resolve(rows);
|
|
164
|
-
}
|
|
165
|
-
});
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
38
|
/**
|
|
169
39
|
* 获取或创建机器人记录
|
|
170
40
|
* @param botId 机器人ID
|
|
171
41
|
*/
|
|
172
42
|
async getOrCreateBot(botId) {
|
|
173
|
-
|
|
174
|
-
if (
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
}
|
|
179
|
-
return bot;
|
|
43
|
+
const [bot] = await this.db.get(model_1.TABLE.bilibiliBot, { id: botId });
|
|
44
|
+
if (bot)
|
|
45
|
+
return bot;
|
|
46
|
+
const time = now();
|
|
47
|
+
return await this.db.create(model_1.TABLE.bilibiliBot, { id: botId, createdAt: time, updatedAt: time });
|
|
180
48
|
}
|
|
181
49
|
/**
|
|
182
50
|
* 获取或创建群组记录
|
|
@@ -185,13 +53,16 @@ class BilibiliDBBase {
|
|
|
185
53
|
*/
|
|
186
54
|
async getOrCreateGroup(groupId, botId) {
|
|
187
55
|
await this.getOrCreateBot(botId);
|
|
188
|
-
|
|
189
|
-
if (
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
56
|
+
const [group] = await this.db.get(model_1.TABLE.bilibiliGroup, { id: groupId, botId });
|
|
57
|
+
if (group)
|
|
58
|
+
return group;
|
|
59
|
+
const time = now();
|
|
60
|
+
return await this.db.create(model_1.TABLE.bilibiliGroup, {
|
|
61
|
+
id: groupId,
|
|
62
|
+
botId,
|
|
63
|
+
createdAt: time,
|
|
64
|
+
updatedAt: time
|
|
65
|
+
});
|
|
195
66
|
}
|
|
196
67
|
/**
|
|
197
68
|
* 获取B站用户记录
|
|
@@ -199,8 +70,8 @@ class BilibiliDBBase {
|
|
|
199
70
|
* @returns 返回用户信息,如果不存在则返回null
|
|
200
71
|
*/
|
|
201
72
|
async getBilibiliUser(host_mid) {
|
|
202
|
-
const user = await this.
|
|
203
|
-
return user
|
|
73
|
+
const [user] = await this.db.get(model_1.TABLE.bilibiliUser, { host_mid });
|
|
74
|
+
return user ?? null;
|
|
204
75
|
}
|
|
205
76
|
/**
|
|
206
77
|
* 获取或创建B站用户记录
|
|
@@ -208,34 +79,25 @@ class BilibiliDBBase {
|
|
|
208
79
|
* @param remark UP主昵称
|
|
209
80
|
*/
|
|
210
81
|
async getOrCreateBilibiliUser(host_mid, remark = '') {
|
|
211
|
-
|
|
212
|
-
if (!
|
|
213
|
-
const
|
|
214
|
-
await this.
|
|
215
|
-
host_mid,
|
|
216
|
-
remark,
|
|
217
|
-
'blacklist',
|
|
218
|
-
now,
|
|
219
|
-
now
|
|
220
|
-
]);
|
|
221
|
-
user = {
|
|
82
|
+
const [row] = await this.db.get(model_1.TABLE.bilibiliUser, { host_mid });
|
|
83
|
+
if (!row) {
|
|
84
|
+
const time = now();
|
|
85
|
+
const created = await this.db.create(model_1.TABLE.bilibiliUser, {
|
|
222
86
|
host_mid,
|
|
223
87
|
remark,
|
|
224
88
|
filterMode: 'blacklist',
|
|
225
|
-
createdAt:
|
|
226
|
-
updatedAt:
|
|
227
|
-
};
|
|
89
|
+
createdAt: time,
|
|
90
|
+
updatedAt: time
|
|
91
|
+
});
|
|
92
|
+
return created;
|
|
228
93
|
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
user.remark = remark;
|
|
235
|
-
user.updatedAt = now;
|
|
236
|
-
}
|
|
94
|
+
// 如果提供了新的remark,更新用户记录
|
|
95
|
+
if (remark && row.remark !== remark) {
|
|
96
|
+
const time = now();
|
|
97
|
+
await this.db.set(model_1.TABLE.bilibiliUser, { host_mid }, { remark, updatedAt: time });
|
|
98
|
+
return { ...row, remark, updatedAt: time };
|
|
237
99
|
}
|
|
238
|
-
return
|
|
100
|
+
return row;
|
|
239
101
|
}
|
|
240
102
|
/**
|
|
241
103
|
* 订阅B站用户
|
|
@@ -247,18 +109,16 @@ class BilibiliDBBase {
|
|
|
247
109
|
async subscribeBilibiliUser(groupId, botId, host_mid, remark = '') {
|
|
248
110
|
await this.getOrCreateGroup(groupId, botId);
|
|
249
111
|
await this.getOrCreateBilibiliUser(host_mid, remark);
|
|
250
|
-
|
|
251
|
-
if (
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
}
|
|
261
|
-
return subscription;
|
|
112
|
+
const [subscription] = await this.db.get(model_1.TABLE.bilibiliSubscription, { groupId, host_mid });
|
|
113
|
+
if (subscription)
|
|
114
|
+
return subscription;
|
|
115
|
+
const time = now();
|
|
116
|
+
return await this.db.create(model_1.TABLE.bilibiliSubscription, {
|
|
117
|
+
groupId,
|
|
118
|
+
host_mid,
|
|
119
|
+
createdAt: time,
|
|
120
|
+
updatedAt: time
|
|
121
|
+
});
|
|
262
122
|
}
|
|
263
123
|
/**
|
|
264
124
|
* 取消订阅B站用户
|
|
@@ -266,24 +126,21 @@ class BilibiliDBBase {
|
|
|
266
126
|
* @param host_mid B站用户UID
|
|
267
127
|
*/
|
|
268
128
|
async unsubscribeBilibiliUser(groupId, host_mid) {
|
|
269
|
-
const result = await this.
|
|
129
|
+
const result = await this.db.remove(model_1.TABLE.bilibiliSubscription, { groupId, host_mid });
|
|
270
130
|
// 清除相关的动态缓存
|
|
271
|
-
await this.
|
|
131
|
+
await this.db.remove(model_1.TABLE.bilibiliDynamicCache, { groupId, host_mid });
|
|
272
132
|
// 检查该用户是否还有其他群组订阅
|
|
273
|
-
const
|
|
133
|
+
const remaining = await this.db.get(model_1.TABLE.bilibiliSubscription, { host_mid }, { limit: 1 });
|
|
274
134
|
// 如果没有任何群组订阅该用户,删除用户记录及相关数据
|
|
275
|
-
if (
|
|
135
|
+
if (remaining.length === 0) {
|
|
276
136
|
node_karin_1.logger.info(`[BilibiliDB] 用户 ${host_mid} 已无任何群组订阅,清理相关数据`);
|
|
277
|
-
|
|
278
|
-
await this.
|
|
279
|
-
|
|
280
|
-
await this.runQuery('DELETE FROM FilterWords WHERE host_mid = ?', [host_mid]);
|
|
281
|
-
// 删除过滤标签
|
|
282
|
-
await this.runQuery('DELETE FROM FilterTags WHERE host_mid = ?', [host_mid]);
|
|
137
|
+
await this.db.remove(model_1.TABLE.bilibiliUser, { host_mid });
|
|
138
|
+
await this.db.remove(model_1.TABLE.bilibiliFilterWord, { host_mid });
|
|
139
|
+
await this.db.remove(model_1.TABLE.bilibiliFilterTag, { host_mid });
|
|
283
140
|
// 删除所有相关的动态缓存(所有群组的)
|
|
284
|
-
await this.
|
|
141
|
+
await this.db.remove(model_1.TABLE.bilibiliDynamicCache, { host_mid });
|
|
285
142
|
}
|
|
286
|
-
return result.
|
|
143
|
+
return (result.removed ?? 0) > 0;
|
|
287
144
|
}
|
|
288
145
|
/**
|
|
289
146
|
* 添加动态缓存
|
|
@@ -293,25 +150,18 @@ class BilibiliDBBase {
|
|
|
293
150
|
* @param dynamic_type 动态类型
|
|
294
151
|
*/
|
|
295
152
|
async addDynamicCache(dynamic_id, host_mid, groupId, dynamic_type) {
|
|
296
|
-
|
|
153
|
+
const [cache] = await this.db.get(model_1.TABLE.bilibiliDynamicCache, { dynamic_id, host_mid, groupId });
|
|
154
|
+
if (cache)
|
|
155
|
+
return cache;
|
|
156
|
+
const time = now();
|
|
157
|
+
return await this.db.create(model_1.TABLE.bilibiliDynamicCache, {
|
|
297
158
|
dynamic_id,
|
|
298
159
|
host_mid,
|
|
299
|
-
groupId
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
cache = {
|
|
305
|
-
id: result.lastID,
|
|
306
|
-
dynamic_id,
|
|
307
|
-
host_mid,
|
|
308
|
-
groupId,
|
|
309
|
-
dynamic_type,
|
|
310
|
-
createdAt: now,
|
|
311
|
-
updatedAt: now
|
|
312
|
-
};
|
|
313
|
-
}
|
|
314
|
-
return cache;
|
|
160
|
+
groupId,
|
|
161
|
+
dynamic_type,
|
|
162
|
+
createdAt: time,
|
|
163
|
+
updatedAt: time
|
|
164
|
+
});
|
|
315
165
|
}
|
|
316
166
|
/**
|
|
317
167
|
* 检查动态是否已推送
|
|
@@ -320,15 +170,15 @@ class BilibiliDBBase {
|
|
|
320
170
|
* @param groupId 群组ID
|
|
321
171
|
*/
|
|
322
172
|
async isDynamicPushed(dynamic_id, host_mid, groupId) {
|
|
323
|
-
const
|
|
324
|
-
return
|
|
173
|
+
const rows = await this.db.get(model_1.TABLE.bilibiliDynamicCache, { dynamic_id, host_mid, groupId }, { limit: 1 });
|
|
174
|
+
return rows.length > 0;
|
|
325
175
|
}
|
|
326
176
|
/**
|
|
327
177
|
* 获取机器人管理的所有群组
|
|
328
178
|
* @param botId 机器人ID
|
|
329
179
|
*/
|
|
330
180
|
async getBotGroups(botId) {
|
|
331
|
-
return await this.
|
|
181
|
+
return await this.db.get(model_1.TABLE.bilibiliGroup, { botId });
|
|
332
182
|
}
|
|
333
183
|
/**
|
|
334
184
|
* 更新群组的机器人ID
|
|
@@ -338,43 +188,49 @@ class BilibiliDBBase {
|
|
|
338
188
|
*/
|
|
339
189
|
async updateGroupBotId(groupId, oldBotId, newBotId) {
|
|
340
190
|
await this.getOrCreateBot(newBotId);
|
|
341
|
-
|
|
342
|
-
|
|
191
|
+
await this.db.set(model_1.TABLE.bilibiliGroup, { id: groupId, botId: oldBotId }, {
|
|
192
|
+
botId: newBotId,
|
|
193
|
+
updatedAt: now()
|
|
194
|
+
});
|
|
343
195
|
}
|
|
344
196
|
/**
|
|
345
197
|
* 获取群组订阅的所有B站用户
|
|
346
198
|
* @param groupId 群组ID
|
|
347
199
|
*/
|
|
348
200
|
async getGroupSubscriptions(groupId) {
|
|
349
|
-
const subscriptions = await this.
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
createdAt: sub.createdAt,
|
|
360
|
-
updatedAt: sub.updatedAt,
|
|
361
|
-
bilibiliUser: {
|
|
201
|
+
const subscriptions = await this.db.get(model_1.TABLE.bilibiliSubscription, { groupId });
|
|
202
|
+
if (!subscriptions.length)
|
|
203
|
+
return [];
|
|
204
|
+
const hosts = [...new Set(subscriptions.map((item) => item.host_mid))];
|
|
205
|
+
const users = await this.db.get(model_1.TABLE.bilibiliUser, { host_mid: { $in: hosts } });
|
|
206
|
+
const userMap = new Map(users.map((user) => [user.host_mid, user]));
|
|
207
|
+
return subscriptions.map((sub) => {
|
|
208
|
+
const user = userMap.get(sub.host_mid);
|
|
209
|
+
return {
|
|
210
|
+
groupId: sub.groupId,
|
|
362
211
|
host_mid: sub.host_mid,
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
212
|
+
createdAt: sub.createdAt,
|
|
213
|
+
updatedAt: sub.updatedAt,
|
|
214
|
+
bilibiliUser: {
|
|
215
|
+
host_mid: sub.host_mid,
|
|
216
|
+
remark: user?.remark ?? '',
|
|
217
|
+
filterMode: (user?.filterMode ?? 'blacklist'),
|
|
218
|
+
createdAt: user?.createdAt ?? sub.createdAt,
|
|
219
|
+
updatedAt: user?.updatedAt ?? sub.updatedAt
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
});
|
|
369
223
|
}
|
|
370
224
|
/**
|
|
371
225
|
* 获取B站用户的所有订阅群组
|
|
372
226
|
* @param host_mid B站用户UID
|
|
373
227
|
*/
|
|
374
228
|
async getUserSubscribedGroups(host_mid) {
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
229
|
+
const subscriptions = await this.db.get(model_1.TABLE.bilibiliSubscription, { host_mid });
|
|
230
|
+
if (!subscriptions.length)
|
|
231
|
+
return [];
|
|
232
|
+
const groupIds = [...new Set(subscriptions.map((item) => item.groupId))];
|
|
233
|
+
return (await this.db.get(model_1.TABLE.bilibiliGroup, { id: { $in: groupIds } }));
|
|
378
234
|
}
|
|
379
235
|
/**
|
|
380
236
|
* 获取群组的动态缓存
|
|
@@ -382,14 +238,9 @@ class BilibiliDBBase {
|
|
|
382
238
|
* @param host_mid 可选的B站用户UID过滤
|
|
383
239
|
*/
|
|
384
240
|
async getGroupDynamicCache(groupId, host_mid) {
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
sql += ' AND host_mid = ?';
|
|
389
|
-
params.push(host_mid);
|
|
390
|
-
}
|
|
391
|
-
sql += ' ORDER BY createdAt DESC';
|
|
392
|
-
return await this.allQuery(sql, params);
|
|
241
|
+
return await this.db.get(model_1.TABLE.bilibiliDynamicCache, host_mid ? { groupId, host_mid } : { groupId }, {
|
|
242
|
+
sort: { createdAt: 'desc' }
|
|
243
|
+
});
|
|
393
244
|
}
|
|
394
245
|
/**
|
|
395
246
|
* 检查群组是否已订阅B站用户
|
|
@@ -397,75 +248,59 @@ class BilibiliDBBase {
|
|
|
397
248
|
* @param groupId 群组ID
|
|
398
249
|
*/
|
|
399
250
|
async isSubscribed(host_mid, groupId) {
|
|
400
|
-
const
|
|
401
|
-
return
|
|
251
|
+
const rows = await this.db.get(model_1.TABLE.bilibiliSubscription, { host_mid, groupId }, { limit: 1 });
|
|
252
|
+
return rows.length > 0;
|
|
402
253
|
}
|
|
403
254
|
/**
|
|
404
255
|
* 批量同步配置文件中的订阅到数据库
|
|
405
256
|
* @param configItems 配置文件中的订阅项
|
|
406
257
|
*/
|
|
407
258
|
async syncConfigSubscriptions(configItems) {
|
|
259
|
+
const items = configItems ?? [];
|
|
408
260
|
// 1. 收集配置文件中的所有订阅关系
|
|
409
261
|
const configSubscriptions = new Map();
|
|
410
|
-
|
|
411
|
-
for (const item of configItems) {
|
|
262
|
+
for (const item of items) {
|
|
412
263
|
const host_mid = item.host_mid;
|
|
413
264
|
const remark = item.remark ?? '';
|
|
414
265
|
// 创建或更新B站用户记录
|
|
415
266
|
await this.getOrCreateBilibiliUser(host_mid, remark);
|
|
416
|
-
// 处理该UP主的所有群组订阅
|
|
417
267
|
for (const groupWithBot of item.group_id) {
|
|
418
268
|
const [groupId, botId] = groupWithBot.split(':');
|
|
419
269
|
if (!groupId || !botId)
|
|
420
270
|
continue;
|
|
421
271
|
// 确保群组存在
|
|
422
272
|
await this.getOrCreateGroup(groupId, botId);
|
|
423
|
-
|
|
424
|
-
if (!configSubscriptions.has(groupId)) {
|
|
273
|
+
if (!configSubscriptions.has(groupId))
|
|
425
274
|
configSubscriptions.set(groupId, new Set());
|
|
426
|
-
}
|
|
427
275
|
configSubscriptions.get(groupId)?.add(host_mid);
|
|
428
|
-
|
|
429
|
-
const isSubscribed = await this.isSubscribed(host_mid, groupId);
|
|
430
|
-
// 如果未订阅,创建订阅关系
|
|
431
|
-
if (!isSubscribed) {
|
|
276
|
+
if (!await this.isSubscribed(host_mid, groupId)) {
|
|
432
277
|
await this.subscribeBilibiliUser(groupId, botId, host_mid, remark);
|
|
433
278
|
}
|
|
434
279
|
}
|
|
435
280
|
}
|
|
436
|
-
// 2.
|
|
437
|
-
|
|
438
|
-
const allGroups = await this.allQuery('SELECT * FROM Groups');
|
|
281
|
+
// 2. 删除数据库里存在、但配置文件里没有的订阅
|
|
282
|
+
const allGroups = await this.db.get(model_1.TABLE.bilibiliGroup, {});
|
|
439
283
|
for (const group of allGroups) {
|
|
440
284
|
const groupId = group.id;
|
|
441
285
|
const configUps = configSubscriptions.get(groupId) ?? new Set();
|
|
442
|
-
|
|
443
|
-
const dbSubscriptions = await this.getGroupSubscriptions(groupId);
|
|
444
|
-
// 找出需要删除的订阅(在数据库中存在但配置文件中不存在)
|
|
286
|
+
const dbSubscriptions = await this.db.get(model_1.TABLE.bilibiliSubscription, { groupId });
|
|
445
287
|
for (const subscription of dbSubscriptions) {
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
node_karin_1.logger.mark(`已删除群组 ${groupId} 对UP主 ${host_mid} 的订阅`);
|
|
451
|
-
}
|
|
288
|
+
if (configUps.has(subscription.host_mid))
|
|
289
|
+
continue;
|
|
290
|
+
await this.unsubscribeBilibiliUser(groupId, subscription.host_mid);
|
|
291
|
+
node_karin_1.logger.mark(`已删除群组 ${groupId} 对UP主 ${subscription.host_mid} 的订阅`);
|
|
452
292
|
}
|
|
453
293
|
}
|
|
454
294
|
// 3. 清理不再被任何群组订阅的UP主记录及其过滤词和过滤标签
|
|
455
|
-
|
|
456
|
-
const allUsers = await this.allQuery('SELECT * FROM BilibiliUsers');
|
|
295
|
+
const allUsers = await this.db.get(model_1.TABLE.bilibiliUser, {});
|
|
457
296
|
for (const user of allUsers) {
|
|
458
|
-
const
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
// 删除该UP主记录
|
|
466
|
-
await this.runQuery('DELETE FROM BilibiliUsers WHERE host_mid = ?', [host_mid]);
|
|
467
|
-
node_karin_1.logger.mark(`已删除UP主 ${host_mid} 的记录及相关过滤设置(不再被任何群组订阅)`);
|
|
468
|
-
}
|
|
297
|
+
const subscribedGroups = await this.getUserSubscribedGroups(user.host_mid);
|
|
298
|
+
if (subscribedGroups.length > 0)
|
|
299
|
+
continue;
|
|
300
|
+
await this.db.remove(model_1.TABLE.bilibiliFilterWord, { host_mid: user.host_mid });
|
|
301
|
+
await this.db.remove(model_1.TABLE.bilibiliFilterTag, { host_mid: user.host_mid });
|
|
302
|
+
await this.db.remove(model_1.TABLE.bilibiliUser, { host_mid: user.host_mid });
|
|
303
|
+
node_karin_1.logger.mark(`已删除UP主 ${user.host_mid} 的记录及相关过滤设置(不再被任何群组订阅)`);
|
|
469
304
|
}
|
|
470
305
|
}
|
|
471
306
|
/**
|
|
@@ -475,9 +310,9 @@ class BilibiliDBBase {
|
|
|
475
310
|
*/
|
|
476
311
|
async updateFilterMode(host_mid, filterMode) {
|
|
477
312
|
const user = await this.getOrCreateBilibiliUser(host_mid);
|
|
478
|
-
const
|
|
479
|
-
await this.
|
|
480
|
-
return { ...user, filterMode, updatedAt:
|
|
313
|
+
const time = now();
|
|
314
|
+
await this.db.set(model_1.TABLE.bilibiliUser, { host_mid }, { filterMode, updatedAt: time });
|
|
315
|
+
return { ...user, filterMode, updatedAt: time };
|
|
481
316
|
}
|
|
482
317
|
/**
|
|
483
318
|
* 添加过滤词
|
|
@@ -486,24 +321,11 @@ class BilibiliDBBase {
|
|
|
486
321
|
*/
|
|
487
322
|
async addFilterWord(host_mid, word) {
|
|
488
323
|
await this.getOrCreateBilibiliUser(host_mid);
|
|
489
|
-
|
|
490
|
-
if (
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
word,
|
|
495
|
-
now,
|
|
496
|
-
now
|
|
497
|
-
]);
|
|
498
|
-
filterWord = {
|
|
499
|
-
id: result.lastID,
|
|
500
|
-
host_mid,
|
|
501
|
-
word,
|
|
502
|
-
createdAt: now,
|
|
503
|
-
updatedAt: now
|
|
504
|
-
};
|
|
505
|
-
}
|
|
506
|
-
return filterWord;
|
|
324
|
+
const [existing] = await this.db.get(model_1.TABLE.bilibiliFilterWord, { host_mid, word });
|
|
325
|
+
if (existing)
|
|
326
|
+
return existing;
|
|
327
|
+
const time = now();
|
|
328
|
+
return await this.db.create(model_1.TABLE.bilibiliFilterWord, { host_mid, word, createdAt: time, updatedAt: time });
|
|
507
329
|
}
|
|
508
330
|
/**
|
|
509
331
|
* 删除过滤词
|
|
@@ -511,8 +333,8 @@ class BilibiliDBBase {
|
|
|
511
333
|
* @param word 过滤词
|
|
512
334
|
*/
|
|
513
335
|
async removeFilterWord(host_mid, word) {
|
|
514
|
-
const result = await this.
|
|
515
|
-
return result.
|
|
336
|
+
const result = await this.db.remove(model_1.TABLE.bilibiliFilterWord, { host_mid, word });
|
|
337
|
+
return (result.removed ?? 0) > 0;
|
|
516
338
|
}
|
|
517
339
|
/**
|
|
518
340
|
* 添加过滤标签
|
|
@@ -521,24 +343,11 @@ class BilibiliDBBase {
|
|
|
521
343
|
*/
|
|
522
344
|
async addFilterTag(host_mid, tag) {
|
|
523
345
|
await this.getOrCreateBilibiliUser(host_mid);
|
|
524
|
-
|
|
525
|
-
if (
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
tag,
|
|
530
|
-
now,
|
|
531
|
-
now
|
|
532
|
-
]);
|
|
533
|
-
filterTag = {
|
|
534
|
-
id: result.lastID,
|
|
535
|
-
host_mid,
|
|
536
|
-
tag,
|
|
537
|
-
createdAt: now,
|
|
538
|
-
updatedAt: now
|
|
539
|
-
};
|
|
540
|
-
}
|
|
541
|
-
return filterTag;
|
|
346
|
+
const [existing] = await this.db.get(model_1.TABLE.bilibiliFilterTag, { host_mid, tag });
|
|
347
|
+
if (existing)
|
|
348
|
+
return existing;
|
|
349
|
+
const time = now();
|
|
350
|
+
return await this.db.create(model_1.TABLE.bilibiliFilterTag, { host_mid, tag, createdAt: time, updatedAt: time });
|
|
542
351
|
}
|
|
543
352
|
/**
|
|
544
353
|
* 删除过滤标签
|
|
@@ -546,24 +355,24 @@ class BilibiliDBBase {
|
|
|
546
355
|
* @param tag 过滤标签
|
|
547
356
|
*/
|
|
548
357
|
async removeFilterTag(host_mid, tag) {
|
|
549
|
-
const result = await this.
|
|
550
|
-
return result.
|
|
358
|
+
const result = await this.db.remove(model_1.TABLE.bilibiliFilterTag, { host_mid, tag });
|
|
359
|
+
return (result.removed ?? 0) > 0;
|
|
551
360
|
}
|
|
552
361
|
/**
|
|
553
362
|
* 获取用户的所有过滤词
|
|
554
363
|
* @param host_mid B站用户UID
|
|
555
364
|
*/
|
|
556
365
|
async getFilterWords(host_mid) {
|
|
557
|
-
const
|
|
558
|
-
return
|
|
366
|
+
const rows = await this.db.get(model_1.TABLE.bilibiliFilterWord, { host_mid });
|
|
367
|
+
return rows.map((row) => row.word);
|
|
559
368
|
}
|
|
560
369
|
/**
|
|
561
370
|
* 获取用户的所有过滤标签
|
|
562
371
|
* @param host_mid B站用户UID
|
|
563
372
|
*/
|
|
564
373
|
async getFilterTags(host_mid) {
|
|
565
|
-
const
|
|
566
|
-
return
|
|
374
|
+
const rows = await this.db.get(model_1.TABLE.bilibiliFilterTag, { host_mid });
|
|
375
|
+
return rows.map((row) => row.tag);
|
|
567
376
|
}
|
|
568
377
|
/**
|
|
569
378
|
* 获取用户的过滤配置
|
|
@@ -720,9 +529,10 @@ class BilibiliDBBase {
|
|
|
720
529
|
async cleanOldDynamicCache(days = 7) {
|
|
721
530
|
const cutoffDate = new Date();
|
|
722
531
|
cutoffDate.setDate(cutoffDate.getDate() - days);
|
|
723
|
-
const
|
|
724
|
-
|
|
725
|
-
|
|
532
|
+
const result = await this.db.remove(model_1.TABLE.bilibiliDynamicCache, {
|
|
533
|
+
createdAt: { $lt: cutoffDate.toISOString() }
|
|
534
|
+
});
|
|
535
|
+
return result.removed ?? 0;
|
|
726
536
|
}
|
|
727
537
|
/** 为了向后兼容,保留groupRepository和dynamicCacheRepository属性 */
|
|
728
538
|
get groupRepository() {
|
|
@@ -731,7 +541,7 @@ class BilibiliDBBase {
|
|
|
731
541
|
if (options?.where?.botId) {
|
|
732
542
|
return await this.getBotGroups(options.where.botId);
|
|
733
543
|
}
|
|
734
|
-
return await this.
|
|
544
|
+
return (await this.db.get(model_1.TABLE.bilibiliGroup, {}));
|
|
735
545
|
}
|
|
736
546
|
};
|
|
737
547
|
}
|
|
@@ -739,51 +549,33 @@ class BilibiliDBBase {
|
|
|
739
549
|
return {
|
|
740
550
|
find: async (options = {}) => {
|
|
741
551
|
const { where = {}, order, take, relations } = options;
|
|
742
|
-
let sql = 'SELECT * FROM DynamicCaches';
|
|
743
|
-
const params = [];
|
|
744
552
|
// 构建WHERE条件
|
|
745
|
-
const
|
|
746
|
-
if (where.groupId)
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
if (where.
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
sql += ' WHERE ' + conditions.join(' AND ');
|
|
760
|
-
}
|
|
761
|
-
// 构建ORDER BY
|
|
762
|
-
if (order) {
|
|
763
|
-
const orderClauses = [];
|
|
764
|
-
const allowedFields = ['id', 'dynamic_id', 'host_mid', 'groupId', 'dynamic_type', 'createdAt', 'updatedAt'];
|
|
765
|
-
const allowedDirections = ['ASC', 'DESC'];
|
|
766
|
-
for (const [field, direction] of Object.entries(order)) {
|
|
767
|
-
// 验证字段名和排序方向,防止SQL注入
|
|
768
|
-
if (allowedFields.includes(field) && allowedDirections.includes(direction)) {
|
|
769
|
-
orderClauses.push(`${field} ${direction}`);
|
|
770
|
-
}
|
|
771
|
-
}
|
|
772
|
-
if (orderClauses.length > 0) {
|
|
773
|
-
sql += ' ORDER BY ' + orderClauses.join(', ');
|
|
774
|
-
}
|
|
775
|
-
}
|
|
776
|
-
// 构建LIMIT
|
|
777
|
-
if (take) {
|
|
778
|
-
sql += ' LIMIT ?';
|
|
779
|
-
params.push(take.toString());
|
|
553
|
+
const query = {};
|
|
554
|
+
if (where.groupId)
|
|
555
|
+
query.groupId = where.groupId;
|
|
556
|
+
if (where.host_mid)
|
|
557
|
+
query.host_mid = where.host_mid;
|
|
558
|
+
if (where.dynamic_id)
|
|
559
|
+
query.dynamic_id = where.dynamic_id;
|
|
560
|
+
// 构建排序(minato 只认 asc / desc)
|
|
561
|
+
const sort = {};
|
|
562
|
+
const allowedFields = ['id', 'dynamic_id', 'host_mid', 'groupId', 'dynamic_type', 'createdAt', 'updatedAt'];
|
|
563
|
+
for (const [field, direction] of Object.entries(order ?? {})) {
|
|
564
|
+
if (!allowedFields.includes(field))
|
|
565
|
+
continue;
|
|
566
|
+
sort[field] = String(direction).toLowerCase() === 'asc' ? 'asc' : 'desc';
|
|
780
567
|
}
|
|
781
|
-
const
|
|
568
|
+
const cursor = {};
|
|
569
|
+
if (Object.keys(sort).length)
|
|
570
|
+
cursor.sort = sort;
|
|
571
|
+
if (take)
|
|
572
|
+
cursor.limit = take;
|
|
573
|
+
const caches = await this.db.get(model_1.TABLE.bilibiliDynamicCache, query, cursor);
|
|
782
574
|
// 如果需要关联bilibiliUser数据
|
|
783
575
|
if (relations && relations.includes('bilibiliUser')) {
|
|
784
576
|
const result = [];
|
|
785
577
|
for (const cache of caches) {
|
|
786
|
-
const bilibiliUser = await this.
|
|
578
|
+
const bilibiliUser = await this.getBilibiliUser(cache.host_mid);
|
|
787
579
|
result.push({
|
|
788
580
|
...cache,
|
|
789
581
|
bilibiliUser,
|
|
@@ -804,24 +596,24 @@ class BilibiliDBBase {
|
|
|
804
596
|
const { groupId, host_mid, dynamic_id } = conditions;
|
|
805
597
|
// 优先处理 dynamic_id + groupId 的精确删除(单条记录)
|
|
806
598
|
if (dynamic_id && groupId) {
|
|
807
|
-
const result = await this.
|
|
808
|
-
return { affected: result.
|
|
599
|
+
const result = await this.db.remove(model_1.TABLE.bilibiliDynamicCache, { dynamic_id, groupId });
|
|
600
|
+
return { affected: result.removed ?? 0 };
|
|
809
601
|
}
|
|
810
602
|
if (groupId && host_mid) {
|
|
811
|
-
const result = await this.
|
|
812
|
-
return { affected: result.
|
|
603
|
+
const result = await this.db.remove(model_1.TABLE.bilibiliDynamicCache, { groupId, host_mid });
|
|
604
|
+
return { affected: result.removed ?? 0 };
|
|
813
605
|
}
|
|
814
606
|
if (groupId) {
|
|
815
|
-
const result = await this.
|
|
816
|
-
return { affected: result.
|
|
607
|
+
const result = await this.db.remove(model_1.TABLE.bilibiliDynamicCache, { groupId });
|
|
608
|
+
return { affected: result.removed ?? 0 };
|
|
817
609
|
}
|
|
818
610
|
if (host_mid) {
|
|
819
|
-
const result = await this.
|
|
820
|
-
return { affected: result.
|
|
611
|
+
const result = await this.db.remove(model_1.TABLE.bilibiliDynamicCache, { host_mid });
|
|
612
|
+
return { affected: result.removed ?? 0 };
|
|
821
613
|
}
|
|
822
614
|
if (dynamic_id) {
|
|
823
|
-
const result = await this.
|
|
824
|
-
return { affected: result.
|
|
615
|
+
const result = await this.db.remove(model_1.TABLE.bilibiliDynamicCache, { dynamic_id });
|
|
616
|
+
return { affected: result.removed ?? 0 };
|
|
825
617
|
}
|
|
826
618
|
return { affected: 0 };
|
|
827
619
|
}
|