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.
@@ -1,22 +1,19 @@
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.DouyinDBBase = void 0;
7
- const node_fs_1 = __importDefault(require("node:fs"));
8
- const node_path_1 = __importDefault(require("node:path"));
9
4
  const node_karin_1 = require("../../../compat/node-karin.js");
10
- const root_1 = require("../../../compat/root.js");
11
- const sqlite3_1 = __importDefault(require("../../../compat/sqlite3.js"));
12
- const utils_1 = require("../../module/utils");
13
5
  const Config_1 = require("../../module/utils/Config");
14
- /** 数据库操作类 */
6
+ const model_1 = require("./model");
7
+ /** 当前时间戳(ISO 字符串,与旧版 sqlite 里的口径一致) */
8
+ const now = () => new Date().toISOString();
9
+ /** 数据库操作类(基于 Koishi 原生数据库服务) */
15
10
  class DouyinDBBase {
16
- db;
17
- dbPath;
18
- constructor() {
19
- this.dbPath = node_path_1.default.join(`${root_1.karinPathBase}/${utils_1.Root.pluginName}/data`, 'douyin.db');
11
+ ctx;
12
+ constructor(ctx) {
13
+ this.ctx = ctx;
14
+ }
15
+ get db() {
16
+ return this.ctx.database;
20
17
  }
21
18
  /**
22
19
  * 初始化数据库
@@ -24,14 +21,7 @@ class DouyinDBBase {
24
21
  async init() {
25
22
  try {
26
23
  node_karin_1.logger.debug(node_karin_1.logger.green('--------------------------[DouyinDB] 开始初始化数据库--------------------------'));
27
- node_karin_1.logger.debug('[DouyinDB] 正在连接数据库...');
28
- // 创建数据库连接
29
- await node_fs_1.default.promises.mkdir(node_path_1.default.dirname(this.dbPath), { recursive: true });
30
- this.db = new sqlite3_1.default.Database(this.dbPath);
31
- // 创建表结构
32
- await this.createTables();
33
- // 检查并升级数据库结构
34
- await this.migrate();
24
+ node_karin_1.logger.debug('[DouyinDB] 使用 Koishi 原生数据库服务,表已由 ctx.model.extend 注册');
35
25
  node_karin_1.logger.debug('[DouyinDB] 数据库模型同步成功');
36
26
  node_karin_1.logger.debug('[DouyinDB] 正在同步配置订阅...');
37
27
  node_karin_1.logger.debug('[DouyinDB] 配置项数量:', Config_1.Config.pushlist.douyin?.length || 0);
@@ -45,227 +35,16 @@ class DouyinDBBase {
45
35
  }
46
36
  return this;
47
37
  }
48
- /**
49
- * 数据库迁移
50
- */
51
- async migrate() {
52
- try {
53
- // 检查 AwemeCaches 表是否有 pushType 字段
54
- try {
55
- await this.runQuery('SELECT pushType FROM AwemeCaches LIMIT 1');
56
- }
57
- catch {
58
- node_karin_1.logger.info('[DouyinDB] 检测到 AwemeCaches 表缺少 pushType 字段,开始执行迁移...');
59
- // 1. 添加 pushType 字段(默认值为 'post')
60
- await this.runQuery("ALTER TABLE AwemeCaches ADD COLUMN pushType TEXT DEFAULT 'post'");
61
- // 2. 由于 SQLite 不支持修改 UNIQUE 约束,我们需要重建表
62
- // 步骤:重命名旧表 -> 创建新表 -> 复制数据 -> 删除旧表
63
- await this.runQuery('ALTER TABLE AwemeCaches RENAME TO AwemeCaches_old');
64
- await this.runQuery(`
65
- CREATE TABLE IF NOT EXISTS AwemeCaches (
66
- id INTEGER PRIMARY KEY AUTOINCREMENT,
67
- aweme_id TEXT NOT NULL,
68
- sec_uid TEXT NOT NULL,
69
- groupId TEXT NOT NULL,
70
- pushType TEXT DEFAULT 'post',
71
- createdAt TEXT DEFAULT CURRENT_TIMESTAMP,
72
- updatedAt TEXT DEFAULT CURRENT_TIMESTAMP,
73
- FOREIGN KEY (sec_uid) REFERENCES DouyinUsers(sec_uid),
74
- UNIQUE(aweme_id, sec_uid, groupId, pushType)
75
- )
76
- `);
77
- await this.runQuery(`
78
- INSERT INTO AwemeCaches (id, aweme_id, sec_uid, groupId, pushType, createdAt, updatedAt)
79
- SELECT id, aweme_id, sec_uid, groupId, pushType, createdAt, updatedAt
80
- FROM AwemeCaches_old
81
- `);
82
- await this.runQuery('DROP TABLE AwemeCaches_old');
83
- node_karin_1.logger.info('[DouyinDB] AwemeCaches 表迁移完成');
84
- }
85
- // 检查并修复外键约束问题
86
- try {
87
- // 尝试获取表结构
88
- const tableInfo = await this.allQuery("SELECT sql FROM sqlite_master WHERE type='table' AND name='AwemeCaches'");
89
- if (tableInfo.length > 0 && tableInfo[0].sql.includes('FOREIGN KEY (groupId) REFERENCES Groups(id)')) {
90
- node_karin_1.logger.info('[DouyinDB] 检测到 AwemeCaches 表存在错误的外键约束,开始修复...');
91
- // 重建表以移除错误的外键约束
92
- await this.runQuery('ALTER TABLE AwemeCaches RENAME TO AwemeCaches_old');
93
- await this.runQuery(`
94
- CREATE TABLE IF NOT EXISTS AwemeCaches (
95
- id INTEGER PRIMARY KEY AUTOINCREMENT,
96
- aweme_id TEXT NOT NULL,
97
- sec_uid TEXT NOT NULL,
98
- groupId TEXT NOT NULL,
99
- pushType TEXT DEFAULT 'post',
100
- createdAt TEXT DEFAULT CURRENT_TIMESTAMP,
101
- updatedAt TEXT DEFAULT CURRENT_TIMESTAMP,
102
- FOREIGN KEY (sec_uid) REFERENCES DouyinUsers(sec_uid),
103
- UNIQUE(aweme_id, sec_uid, groupId, pushType)
104
- )
105
- `);
106
- await this.runQuery(`
107
- INSERT INTO AwemeCaches (id, aweme_id, sec_uid, groupId, pushType, createdAt, updatedAt)
108
- SELECT id, aweme_id, sec_uid, groupId, pushType, createdAt, updatedAt
109
- FROM AwemeCaches_old
110
- `);
111
- await this.runQuery('DROP TABLE AwemeCaches_old');
112
- node_karin_1.logger.info('[DouyinDB] AwemeCaches 表外键约束修复完成');
113
- }
114
- }
115
- catch (error) {
116
- node_karin_1.logger.debug('[DouyinDB] 外键约束检查/修复跳过:', error);
117
- }
118
- }
119
- catch (error) {
120
- node_karin_1.logger.error('[DouyinDB] 数据库迁移失败:', error);
121
- // 不抛出错误,以免影响程序启动,但记录日志
122
- }
123
- }
124
- /**
125
- * 创建数据库表结构
126
- */
127
- async createTables() {
128
- const queries = [
129
- // 创建机器人表
130
- `CREATE TABLE IF NOT EXISTS Bots (
131
- id TEXT PRIMARY KEY,
132
- createdAt TEXT DEFAULT CURRENT_TIMESTAMP,
133
- updatedAt TEXT DEFAULT CURRENT_TIMESTAMP
134
- )`,
135
- // 创建群组表
136
- `CREATE TABLE IF NOT EXISTS Groups (
137
- id TEXT NOT NULL,
138
- botId TEXT NOT NULL,
139
- createdAt TEXT DEFAULT CURRENT_TIMESTAMP,
140
- updatedAt TEXT DEFAULT CURRENT_TIMESTAMP,
141
- PRIMARY KEY (id, botId),
142
- FOREIGN KEY (botId) REFERENCES Bots(id)
143
- )`,
144
- // 创建抖音用户表
145
- `CREATE TABLE IF NOT EXISTS DouyinUsers (
146
- sec_uid TEXT PRIMARY KEY,
147
- short_id TEXT,
148
- remark TEXT,
149
- living INTEGER DEFAULT 0,
150
- filterMode TEXT DEFAULT 'blacklist',
151
- createdAt TEXT DEFAULT CURRENT_TIMESTAMP,
152
- updatedAt TEXT DEFAULT CURRENT_TIMESTAMP
153
- )`,
154
- // 创建群组用户订阅关系表
155
- `CREATE TABLE IF NOT EXISTS GroupUserSubscriptions (
156
- groupId TEXT,
157
- sec_uid TEXT,
158
- createdAt TEXT DEFAULT CURRENT_TIMESTAMP,
159
- updatedAt TEXT DEFAULT CURRENT_TIMESTAMP,
160
- PRIMARY KEY (groupId, sec_uid),
161
- FOREIGN KEY (groupId) REFERENCES Groups(id),
162
- FOREIGN KEY (sec_uid) REFERENCES DouyinUsers(sec_uid)
163
- )`,
164
- // 创建作品缓存表
165
- `CREATE TABLE IF NOT EXISTS AwemeCaches (
166
- id INTEGER PRIMARY KEY AUTOINCREMENT,
167
- aweme_id TEXT NOT NULL,
168
- sec_uid TEXT NOT NULL,
169
- groupId TEXT NOT NULL,
170
- pushType TEXT DEFAULT 'post',
171
- createdAt TEXT DEFAULT CURRENT_TIMESTAMP,
172
- updatedAt TEXT DEFAULT CURRENT_TIMESTAMP,
173
- FOREIGN KEY (sec_uid) REFERENCES DouyinUsers(sec_uid),
174
- UNIQUE(aweme_id, sec_uid, groupId, pushType)
175
- )`,
176
- // 创建过滤词表
177
- `CREATE TABLE IF NOT EXISTS FilterWords (
178
- id INTEGER PRIMARY KEY AUTOINCREMENT,
179
- sec_uid TEXT NOT NULL,
180
- word TEXT NOT NULL,
181
- createdAt TEXT DEFAULT CURRENT_TIMESTAMP,
182
- updatedAt TEXT DEFAULT CURRENT_TIMESTAMP,
183
- FOREIGN KEY (sec_uid) REFERENCES DouyinUsers(sec_uid),
184
- UNIQUE(sec_uid, word)
185
- )`,
186
- // 创建过滤标签表
187
- `CREATE TABLE IF NOT EXISTS FilterTags (
188
- id INTEGER PRIMARY KEY AUTOINCREMENT,
189
- sec_uid TEXT NOT NULL,
190
- tag TEXT NOT NULL,
191
- createdAt TEXT DEFAULT CURRENT_TIMESTAMP,
192
- updatedAt TEXT DEFAULT CURRENT_TIMESTAMP,
193
- FOREIGN KEY (sec_uid) REFERENCES DouyinUsers(sec_uid),
194
- UNIQUE(sec_uid, tag)
195
- )`,
196
- // 创建列表快照表(用于喜欢列表和推荐列表)
197
- `CREATE TABLE IF NOT EXISTS ListSnapshots (
198
- id INTEGER PRIMARY KEY AUTOINCREMENT,
199
- sec_uid TEXT NOT NULL,
200
- pushType TEXT NOT NULL,
201
- aweme_id TEXT NOT NULL,
202
- createdAt TEXT DEFAULT CURRENT_TIMESTAMP,
203
- updatedAt TEXT DEFAULT CURRENT_TIMESTAMP,
204
- FOREIGN KEY (sec_uid) REFERENCES DouyinUsers(sec_uid),
205
- UNIQUE(sec_uid, pushType, aweme_id)
206
- )`
207
- ];
208
- for (const query of queries) {
209
- await this.runQuery(query);
210
- }
211
- }
212
- /**
213
- * 执行SQL查询
214
- */
215
- runQuery(sql, params = []) {
216
- return new Promise((resolve, reject) => {
217
- this.db.run(sql, params, function (err) {
218
- if (err) {
219
- reject(err);
220
- }
221
- else {
222
- resolve({ lastID: this.lastID, changes: this.changes });
223
- }
224
- });
225
- });
226
- }
227
- /**
228
- * 执行SQL查询并获取单个结果
229
- */
230
- getQuery(sql, params = []) {
231
- return new Promise((resolve, reject) => {
232
- this.db.get(sql, params, (err, row) => {
233
- if (err) {
234
- reject(err);
235
- }
236
- else {
237
- resolve(row);
238
- }
239
- });
240
- });
241
- }
242
- /**
243
- * 执行SQL查询并获取所有结果
244
- */
245
- allQuery(sql, params = []) {
246
- return new Promise((resolve, reject) => {
247
- this.db.all(sql, params, (err, rows) => {
248
- if (err) {
249
- reject(err);
250
- }
251
- else {
252
- resolve(rows);
253
- }
254
- });
255
- });
256
- }
257
38
  /**
258
39
  * 获取或创建机器人记录
259
40
  * @param botId 机器人ID
260
41
  */
261
42
  async getOrCreateBot(botId) {
262
- let bot = await this.getQuery('SELECT * FROM Bots WHERE id = ?', [botId]);
263
- if (!bot) {
264
- const now = new Date().toISOString();
265
- await this.runQuery('INSERT INTO Bots (id, createdAt, updatedAt) VALUES (?, ?, ?)', [botId, now, now]);
266
- bot = { id: botId, createdAt: now, updatedAt: now };
267
- }
268
- return bot;
43
+ const [bot] = await this.db.get(model_1.TABLE.douyinBot, { id: botId });
44
+ if (bot)
45
+ return bot;
46
+ const time = now();
47
+ return await this.db.create(model_1.TABLE.douyinBot, { id: botId, createdAt: time, updatedAt: time });
269
48
  }
270
49
  /**
271
50
  * 获取或创建群组记录
@@ -274,13 +53,16 @@ class DouyinDBBase {
274
53
  */
275
54
  async getOrCreateGroup(groupId, botId) {
276
55
  await this.getOrCreateBot(botId);
277
- let group = await this.getQuery('SELECT * FROM Groups WHERE id = ? AND botId = ?', [groupId, botId]);
278
- if (!group) {
279
- const now = new Date().toISOString();
280
- await this.runQuery('INSERT INTO Groups (id, botId, createdAt, updatedAt) VALUES (?, ?, ?, ?)', [groupId, botId, now, now]);
281
- group = { id: groupId, botId, createdAt: now, updatedAt: now };
282
- }
283
- return group;
56
+ const [group] = await this.db.get(model_1.TABLE.douyinGroup, { id: groupId, botId });
57
+ if (group)
58
+ return group;
59
+ const time = now();
60
+ return await this.db.create(model_1.TABLE.douyinGroup, {
61
+ id: groupId,
62
+ botId,
63
+ createdAt: time,
64
+ updatedAt: time
65
+ });
284
66
  }
285
67
  /**
286
68
  * 获取或创建抖音用户记录
@@ -289,47 +71,32 @@ class DouyinDBBase {
289
71
  * @param remark 用户昵称
290
72
  */
291
73
  async getOrCreateDouyinUser(sec_uid, short_id = '', remark = '') {
292
- let user = await this.getQuery('SELECT * FROM DouyinUsers WHERE sec_uid = ?', [sec_uid]);
74
+ const [user] = await this.db.get(model_1.TABLE.douyinUser, { sec_uid });
293
75
  if (!user) {
294
- const now = new Date().toISOString();
295
- await this.runQuery('INSERT INTO DouyinUsers (sec_uid, short_id, remark, living, filterMode, createdAt, updatedAt) VALUES (?, ?, ?, ?, ?, ?, ?)', [sec_uid, short_id, remark, 0, 'blacklist', now, now]);
296
- user = {
76
+ const time = now();
77
+ const created = await this.db.create(model_1.TABLE.douyinUser, {
297
78
  sec_uid,
298
79
  short_id,
299
80
  remark,
300
81
  living: false,
301
82
  filterMode: 'blacklist',
302
- createdAt: now,
303
- updatedAt: now
304
- };
305
- }
306
- else {
307
- // 如果提供了新的信息,更新用户记录
308
- let needUpdate = false;
309
- const updates = [];
310
- const params = [];
311
- if (remark && user.remark !== remark) {
312
- updates.push('remark = ?');
313
- params.push(remark);
314
- user.remark = remark;
315
- needUpdate = true;
316
- }
317
- if (short_id && user.short_id !== short_id) {
318
- updates.push('short_id = ?');
319
- params.push(short_id);
320
- user.short_id = short_id;
321
- needUpdate = true;
322
- }
323
- if (needUpdate) {
324
- const now = new Date().toISOString();
325
- updates.push('updatedAt = ?');
326
- params.push(now);
327
- params.push(sec_uid);
328
- await this.runQuery(`UPDATE DouyinUsers SET ${updates.join(', ')} WHERE sec_uid = ?`, params);
329
- user.updatedAt = now;
330
- }
83
+ createdAt: time,
84
+ updatedAt: time
85
+ });
86
+ return created;
331
87
  }
332
- return user;
88
+ // 如果提供了新的信息,更新用户记录
89
+ const patch = {};
90
+ if (remark && user.remark !== remark)
91
+ patch.remark = remark;
92
+ if (short_id && user.short_id !== short_id)
93
+ patch.short_id = short_id;
94
+ if (Object.keys(patch).length === 0)
95
+ return user;
96
+ const time = now();
97
+ patch.updatedAt = time;
98
+ await this.db.set(model_1.TABLE.douyinUser, { sec_uid }, patch);
99
+ return { ...user, ...patch };
333
100
  }
334
101
  /**
335
102
  * 订阅抖音用户
@@ -342,18 +109,16 @@ class DouyinDBBase {
342
109
  async subscribeDouyinUser(groupId, botId, sec_uid, short_id = '', remark = '') {
343
110
  await this.getOrCreateGroup(groupId, botId);
344
111
  await this.getOrCreateDouyinUser(sec_uid, short_id, remark);
345
- let subscription = await this.getQuery('SELECT * FROM GroupUserSubscriptions WHERE groupId = ? AND sec_uid = ?', [groupId, sec_uid]);
346
- if (!subscription) {
347
- const now = new Date().toISOString();
348
- await this.runQuery('INSERT INTO GroupUserSubscriptions (groupId, sec_uid, createdAt, updatedAt) VALUES (?, ?, ?, ?)', [
349
- groupId,
350
- sec_uid,
351
- now,
352
- now
353
- ]);
354
- subscription = { groupId, sec_uid, createdAt: now, updatedAt: now };
355
- }
356
- return subscription;
112
+ const [subscription] = await this.db.get(model_1.TABLE.douyinSubscription, { groupId, sec_uid });
113
+ if (subscription)
114
+ return subscription;
115
+ const time = now();
116
+ return await this.db.create(model_1.TABLE.douyinSubscription, {
117
+ groupId,
118
+ sec_uid,
119
+ createdAt: time,
120
+ updatedAt: time
121
+ });
357
122
  }
358
123
  /**
359
124
  * 取消订阅抖音用户
@@ -361,24 +126,21 @@ class DouyinDBBase {
361
126
  * @param sec_uid 抖音用户sec_uid
362
127
  */
363
128
  async unsubscribeDouyinUser(groupId, sec_uid) {
364
- const result = await this.runQuery('DELETE FROM GroupUserSubscriptions WHERE groupId = ? AND sec_uid = ?', [groupId, sec_uid]);
129
+ const result = await this.db.remove(model_1.TABLE.douyinSubscription, { groupId, sec_uid });
365
130
  // 清除相关的作品缓存
366
- await this.runQuery('DELETE FROM AwemeCaches WHERE groupId = ? AND sec_uid = ?', [groupId, sec_uid]);
131
+ await this.db.remove(model_1.TABLE.douyinAwemeCache, { groupId, sec_uid });
367
132
  // 检查该用户是否还有其他群组订阅
368
- const remainingSubscriptions = await this.getQuery('SELECT COUNT(*) as count FROM GroupUserSubscriptions WHERE sec_uid = ?', [sec_uid]);
133
+ const remaining = await this.db.get(model_1.TABLE.douyinSubscription, { sec_uid }, { limit: 1 });
369
134
  // 如果没有任何群组订阅该用户,删除用户记录及相关数据
370
- if (remainingSubscriptions && remainingSubscriptions.count === 0) {
135
+ if (remaining.length === 0) {
371
136
  node_karin_1.logger.info(`[DouyinDB] 用户 ${sec_uid} 已无任何群组订阅,清理相关数据`);
372
- // 删除用户记录
373
- await this.runQuery('DELETE FROM DouyinUsers WHERE sec_uid = ?', [sec_uid]);
374
- // 删除过滤词
375
- await this.runQuery('DELETE FROM FilterWords WHERE sec_uid = ?', [sec_uid]);
376
- // 删除过滤标签
377
- await this.runQuery('DELETE FROM FilterTags WHERE sec_uid = ?', [sec_uid]);
137
+ await this.db.remove(model_1.TABLE.douyinUser, { sec_uid });
138
+ await this.db.remove(model_1.TABLE.douyinFilterWord, { sec_uid });
139
+ await this.db.remove(model_1.TABLE.douyinFilterTag, { sec_uid });
378
140
  // 删除所有相关的作品缓存(所有群组的)
379
- await this.runQuery('DELETE FROM AwemeCaches WHERE sec_uid = ?', [sec_uid]);
141
+ await this.db.remove(model_1.TABLE.douyinAwemeCache, { sec_uid });
380
142
  }
381
- return result.changes > 0;
143
+ return (result.removed ?? 0) > 0;
382
144
  }
383
145
  /**
384
146
  * 添加作品缓存
@@ -388,21 +150,18 @@ class DouyinDBBase {
388
150
  * @param pushType 推送类型:post(作品列表)、favorite(喜欢列表)、recommend(推荐列表)、live(直播)
389
151
  */
390
152
  async addAwemeCache(aweme_id, sec_uid, groupId, pushType = 'post') {
391
- let cache = await this.getQuery('SELECT * FROM AwemeCaches WHERE aweme_id = ? AND sec_uid = ? AND groupId = ? AND pushType = ?', [aweme_id, sec_uid, groupId, pushType]);
392
- if (!cache) {
393
- const now = new Date().toISOString();
394
- const result = await this.runQuery('INSERT INTO AwemeCaches (aweme_id, sec_uid, groupId, pushType, createdAt, updatedAt) VALUES (?, ?, ?, ?, ?, ?)', [aweme_id, sec_uid, groupId, pushType, now, now]);
395
- cache = {
396
- id: result.lastID,
397
- aweme_id,
398
- sec_uid,
399
- groupId,
400
- pushType,
401
- createdAt: now,
402
- updatedAt: now
403
- };
404
- }
405
- return cache;
153
+ const [cache] = await this.db.get(model_1.TABLE.douyinAwemeCache, { aweme_id, sec_uid, groupId, pushType });
154
+ if (cache)
155
+ return cache;
156
+ const time = now();
157
+ return await this.db.create(model_1.TABLE.douyinAwemeCache, {
158
+ aweme_id,
159
+ sec_uid,
160
+ groupId,
161
+ pushType,
162
+ createdAt: time,
163
+ updatedAt: time
164
+ });
406
165
  }
407
166
  /**
408
167
  * 检查作品是否已推送
@@ -412,8 +171,8 @@ class DouyinDBBase {
412
171
  * @param pushType 推送类型:post(作品列表)、favorite(喜欢列表)、recommend(推荐列表)、live(直播)
413
172
  */
414
173
  async isAwemePushed(aweme_id, sec_uid, groupId, pushType = 'post') {
415
- const result = await this.getQuery('SELECT COUNT(*) as count FROM AwemeCaches WHERE aweme_id = ? AND sec_uid = ? AND groupId = ? AND pushType = ?', [aweme_id, sec_uid, groupId, pushType]);
416
- return (result?.count || 0) > 0;
174
+ const rows = await this.db.get(model_1.TABLE.douyinAwemeCache, { aweme_id, sec_uid, groupId, pushType }, { limit: 1 });
175
+ return rows.length > 0;
417
176
  }
418
177
  /**
419
178
  * 检查群组是否有推送历史(用于判断是否为新订阅)
@@ -422,15 +181,15 @@ class DouyinDBBase {
422
181
  * @param pushType 推送类型
423
182
  */
424
183
  async hasHistory(sec_uid, groupId, pushType) {
425
- const result = await this.getQuery('SELECT COUNT(*) as count FROM AwemeCaches WHERE sec_uid = ? AND groupId = ? AND pushType = ? LIMIT 1', [sec_uid, groupId, pushType]);
426
- return (result?.count || 0) > 0;
184
+ const rows = await this.db.get(model_1.TABLE.douyinAwemeCache, { sec_uid, groupId, pushType }, { limit: 1 });
185
+ return rows.length > 0;
427
186
  }
428
187
  /**
429
188
  * 获取机器人管理的所有群组
430
189
  * @param botId 机器人ID
431
190
  */
432
191
  async getBotGroups(botId) {
433
- return await this.allQuery('SELECT * FROM Groups WHERE botId = ?', [botId]);
192
+ return await this.db.get(model_1.TABLE.douyinGroup, { botId });
434
193
  }
435
194
  /**
436
195
  * 更新群组的机器人ID
@@ -440,45 +199,51 @@ class DouyinDBBase {
440
199
  */
441
200
  async updateGroupBotId(groupId, oldBotId, newBotId) {
442
201
  await this.getOrCreateBot(newBotId);
443
- const now = new Date().toISOString();
444
- await this.runQuery('UPDATE Groups SET botId = ?, updatedAt = ? WHERE id = ? AND botId = ?', [newBotId, now, groupId, oldBotId]);
202
+ await this.db.set(model_1.TABLE.douyinGroup, { id: groupId, botId: oldBotId }, {
203
+ botId: newBotId,
204
+ updatedAt: now()
205
+ });
445
206
  }
446
207
  /**
447
208
  * 获取群组订阅的所有抖音用户
448
209
  * @param groupId 群组ID
449
210
  */
450
211
  async getGroupSubscriptions(groupId) {
451
- const subscriptions = await this.allQuery(`SELECT
452
- gus.groupId, gus.sec_uid, gus.createdAt, gus.updatedAt,
453
- du.short_id, du.remark, du.living, du.filterMode,
454
- du.createdAt as du_createdAt, du.updatedAt as du_updatedAt
455
- FROM GroupUserSubscriptions gus
456
- LEFT JOIN DouyinUsers du ON gus.sec_uid = du.sec_uid
457
- WHERE gus.groupId = ?`, [groupId]);
458
- return subscriptions.map((sub) => ({
459
- groupId: sub.groupId,
460
- sec_uid: sub.sec_uid,
461
- createdAt: sub.createdAt,
462
- updatedAt: sub.updatedAt,
463
- douyinUser: {
212
+ const subscriptions = await this.db.get(model_1.TABLE.douyinSubscription, { groupId });
213
+ if (!subscriptions.length)
214
+ return [];
215
+ const secUids = [...new Set(subscriptions.map((item) => item.sec_uid))];
216
+ const users = await this.db.get(model_1.TABLE.douyinUser, { sec_uid: { $in: secUids } });
217
+ const userMap = new Map(users.map((user) => [user.sec_uid, user]));
218
+ return subscriptions.map((sub) => {
219
+ const user = userMap.get(sub.sec_uid);
220
+ return {
221
+ groupId: sub.groupId,
464
222
  sec_uid: sub.sec_uid,
465
- short_id: sub.short_id,
466
- remark: sub.remark,
467
- living: !!sub.living,
468
- filterMode: sub.filterMode,
469
- createdAt: sub.du_createdAt,
470
- updatedAt: sub.du_updatedAt
471
- }
472
- }));
223
+ createdAt: sub.createdAt,
224
+ updatedAt: sub.updatedAt,
225
+ douyinUser: {
226
+ sec_uid: sub.sec_uid,
227
+ short_id: user?.short_id ?? '',
228
+ remark: user?.remark ?? '',
229
+ living: !!user?.living,
230
+ filterMode: (user?.filterMode ?? 'blacklist'),
231
+ createdAt: user?.createdAt ?? sub.createdAt,
232
+ updatedAt: user?.updatedAt ?? sub.updatedAt
233
+ }
234
+ };
235
+ });
473
236
  }
474
237
  /**
475
238
  * 获取抖音用户的所有订阅群组
476
239
  * @param sec_uid 抖音用户sec_uid
477
240
  */
478
241
  async getUserSubscribedGroups(sec_uid) {
479
- return await this.allQuery(`SELECT g.* FROM Groups g
480
- INNER JOIN GroupUserSubscriptions gus ON g.id = gus.groupId
481
- WHERE gus.sec_uid = ?`, [sec_uid]);
242
+ const subscriptions = await this.db.get(model_1.TABLE.douyinSubscription, { sec_uid });
243
+ if (!subscriptions.length)
244
+ return [];
245
+ const groupIds = [...new Set(subscriptions.map((item) => item.groupId))];
246
+ return (await this.db.get(model_1.TABLE.douyinGroup, { id: { $in: groupIds } }));
482
247
  }
483
248
  /**
484
249
  * 检查群组是否已订阅抖音用户
@@ -486,8 +251,8 @@ class DouyinDBBase {
486
251
  * @param groupId 群组ID
487
252
  */
488
253
  async isSubscribed(sec_uid, groupId) {
489
- const result = await this.getQuery('SELECT COUNT(*) as count FROM GroupUserSubscriptions WHERE sec_uid = ? AND groupId = ?', [sec_uid, groupId]);
490
- return (result?.count || 0) > 0;
254
+ const rows = await this.db.get(model_1.TABLE.douyinSubscription, { sec_uid, groupId }, { limit: 1 });
255
+ return rows.length > 0;
491
256
  }
492
257
  /**
493
258
  * 获取抖音用户信息
@@ -495,11 +260,10 @@ class DouyinDBBase {
495
260
  * @returns 返回用户信息,如果不存在则返回null
496
261
  */
497
262
  async getDouyinUser(sec_uid) {
498
- const user = await this.getQuery('SELECT * FROM DouyinUsers WHERE sec_uid = ?', [sec_uid]);
499
- if (user) {
500
- user.living = !!user.living; // 转换为boolean
501
- }
502
- return user || null;
263
+ const [user] = await this.db.get(model_1.TABLE.douyinUser, { sec_uid });
264
+ if (!user)
265
+ return null;
266
+ return { ...user, living: !!user.living };
503
267
  }
504
268
  /**
505
269
  * 更新用户直播状态
@@ -510,9 +274,8 @@ class DouyinDBBase {
510
274
  const user = await this.getDouyinUser(sec_uid);
511
275
  if (!user)
512
276
  return false;
513
- const now = new Date().toISOString();
514
- const result = await this.runQuery('UPDATE DouyinUsers SET living = ?, updatedAt = ? WHERE sec_uid = ?', [living ? 1 : 0, now, sec_uid]);
515
- return result.changes > 0;
277
+ const result = await this.db.set(model_1.TABLE.douyinUser, { sec_uid }, { living, updatedAt: now() });
278
+ return (result.matched ?? 0) > 0;
516
279
  }
517
280
  /**
518
281
  * 获取用户直播状态
@@ -527,68 +290,52 @@ class DouyinDBBase {
527
290
  * @param configItems 配置文件中的订阅项
528
291
  */
529
292
  async syncConfigSubscriptions(configItems) {
293
+ const items = configItems ?? [];
530
294
  // 1. 收集配置文件中的所有订阅关系
531
295
  const configSubscriptions = new Map();
532
- // 初始化每个群组的订阅用户集合
533
- for (const item of configItems) {
296
+ for (const item of items) {
534
297
  const sec_uid = item.sec_uid;
535
298
  const short_id = item.short_id ?? '';
536
299
  const remark = item.remark ?? '';
537
300
  // 创建或更新抖音用户记录
538
301
  await this.getOrCreateDouyinUser(sec_uid, short_id, remark);
539
- // 处理该用户的所有群组订阅
540
302
  for (const groupWithBot of item.group_id) {
541
303
  const [groupId, botId] = groupWithBot.split(':');
542
304
  if (!groupId || !botId)
543
305
  continue;
544
306
  // 确保群组存在
545
307
  await this.getOrCreateGroup(groupId, botId);
546
- // 记录配置文件中的订阅关系
547
- if (!configSubscriptions.has(groupId)) {
308
+ if (!configSubscriptions.has(groupId))
548
309
  configSubscriptions.set(groupId, new Set());
549
- }
550
310
  configSubscriptions.get(groupId)?.add(sec_uid);
551
- // 检查是否已订阅
552
- const isSubscribed = await this.isSubscribed(sec_uid, groupId);
553
- // 如果未订阅,创建订阅关系
554
- if (!isSubscribed) {
311
+ if (!await this.isSubscribed(sec_uid, groupId)) {
555
312
  await this.subscribeDouyinUser(groupId, botId, sec_uid, short_id, remark);
556
313
  }
557
314
  }
558
315
  }
559
- // 2. 获取数据库中的所有订阅关系,并与配置文件比较,删除不在配置文件中的订阅
560
- // 获取所有群组
561
- const allGroups = await this.allQuery('SELECT * FROM Groups');
316
+ // 2. 删除数据库里存在、但配置文件里没有的订阅
317
+ const allGroups = await this.db.get(model_1.TABLE.douyinGroup, {});
562
318
  for (const group of allGroups) {
563
319
  const groupId = group.id;
564
320
  const configUsers = configSubscriptions.get(groupId) ?? new Set();
565
- // 获取该群组在数据库中的所有订阅
566
- const dbSubscriptions = await this.getGroupSubscriptions(groupId);
567
- // 找出需要删除的订阅(在数据库中存在但配置文件中不存在)
321
+ const dbSubscriptions = await this.db.get(model_1.TABLE.douyinSubscription, { groupId });
568
322
  for (const subscription of dbSubscriptions) {
569
- const sec_uid = subscription.sec_uid;
570
- if (!configUsers.has(sec_uid)) {
571
- // 删除订阅关系
572
- await this.unsubscribeDouyinUser(groupId, sec_uid);
573
- node_karin_1.logger.mark(`已删除群组 ${groupId} 对抖音用户 ${sec_uid} 的订阅`);
574
- }
323
+ if (configUsers.has(subscription.sec_uid))
324
+ continue;
325
+ await this.unsubscribeDouyinUser(groupId, subscription.sec_uid);
326
+ node_karin_1.logger.mark(`已删除群组 ${groupId} 对抖音用户 ${subscription.sec_uid} 的订阅`);
575
327
  }
576
328
  }
577
329
  // 3. 清理不再被任何群组订阅的抖音用户记录及其过滤词和过滤标签
578
- // 获取所有抖音用户
579
- const allUsers = await this.allQuery('SELECT * FROM DouyinUsers');
330
+ const allUsers = await this.db.get(model_1.TABLE.douyinUser, {});
580
331
  for (const user of allUsers) {
581
- const sec_uid = user.sec_uid;
582
- // 检查该用户是否还有群组订阅
583
- const subscribedGroups = await this.getUserSubscribedGroups(sec_uid);
584
- if (subscribedGroups.length === 0) {
585
- // 删除该用户的过滤词和过滤标签
586
- await this.runQuery('DELETE FROM FilterWords WHERE sec_uid = ?', [sec_uid]);
587
- await this.runQuery('DELETE FROM FilterTags WHERE sec_uid = ?', [sec_uid]);
588
- // 删除该用户记录
589
- await this.runQuery('DELETE FROM DouyinUsers WHERE sec_uid = ?', [sec_uid]);
590
- node_karin_1.logger.mark(`已删除抖音用户 ${sec_uid} 的记录及相关过滤设置(不再被任何群组订阅)`);
591
- }
332
+ const subscribedGroups = await this.getUserSubscribedGroups(user.sec_uid);
333
+ if (subscribedGroups.length > 0)
334
+ continue;
335
+ await this.db.remove(model_1.TABLE.douyinFilterWord, { sec_uid: user.sec_uid });
336
+ await this.db.remove(model_1.TABLE.douyinFilterTag, { sec_uid: user.sec_uid });
337
+ await this.db.remove(model_1.TABLE.douyinUser, { sec_uid: user.sec_uid });
338
+ node_karin_1.logger.mark(`已删除抖音用户 ${user.sec_uid} 的记录及相关过滤设置(不再被任何群组订阅)`);
592
339
  }
593
340
  }
594
341
  /**
@@ -596,7 +343,8 @@ class DouyinDBBase {
596
343
  * @param groupId 群组ID
597
344
  */
598
345
  async getGroupById(groupId) {
599
- return (await this.getQuery('SELECT * FROM Groups WHERE id = ?', [groupId])) || null;
346
+ const [group] = await this.db.get(model_1.TABLE.douyinGroup, { id: groupId });
347
+ return group ?? null;
600
348
  }
601
349
  /**
602
350
  * 更新用户的过滤模式
@@ -605,9 +353,9 @@ class DouyinDBBase {
605
353
  */
606
354
  async updateFilterMode(sec_uid, filterMode) {
607
355
  const user = await this.getOrCreateDouyinUser(sec_uid);
608
- const now = new Date().toISOString();
609
- await this.runQuery('UPDATE DouyinUsers SET filterMode = ?, updatedAt = ? WHERE sec_uid = ?', [filterMode, now, sec_uid]);
610
- return { ...user, filterMode, updatedAt: now };
356
+ const time = now();
357
+ await this.db.set(model_1.TABLE.douyinUser, { sec_uid }, { filterMode, updatedAt: time });
358
+ return { ...user, filterMode, updatedAt: time };
611
359
  }
612
360
  /**
613
361
  * 添加过滤词
@@ -616,24 +364,11 @@ class DouyinDBBase {
616
364
  */
617
365
  async addFilterWord(sec_uid, word) {
618
366
  await this.getOrCreateDouyinUser(sec_uid);
619
- let filterWord = await this.getQuery('SELECT * FROM FilterWords WHERE sec_uid = ? AND word = ?', [sec_uid, word]);
620
- if (!filterWord) {
621
- const now = new Date().toISOString();
622
- const result = await this.runQuery('INSERT INTO FilterWords (sec_uid, word, createdAt, updatedAt) VALUES (?, ?, ?, ?)', [
623
- sec_uid,
624
- word,
625
- now,
626
- now
627
- ]);
628
- filterWord = {
629
- id: result.lastID,
630
- sec_uid,
631
- word,
632
- createdAt: now,
633
- updatedAt: now
634
- };
635
- }
636
- return filterWord;
367
+ const [existing] = await this.db.get(model_1.TABLE.douyinFilterWord, { sec_uid, word });
368
+ if (existing)
369
+ return existing;
370
+ const time = now();
371
+ return await this.db.create(model_1.TABLE.douyinFilterWord, { sec_uid, word, createdAt: time, updatedAt: time });
637
372
  }
638
373
  /**
639
374
  * 删除过滤词
@@ -641,8 +376,8 @@ class DouyinDBBase {
641
376
  * @param word 过滤词
642
377
  */
643
378
  async removeFilterWord(sec_uid, word) {
644
- const result = await this.runQuery('DELETE FROM FilterWords WHERE sec_uid = ? AND word = ?', [sec_uid, word]);
645
- return result.changes > 0;
379
+ const result = await this.db.remove(model_1.TABLE.douyinFilterWord, { sec_uid, word });
380
+ return (result.removed ?? 0) > 0;
646
381
  }
647
382
  /**
648
383
  * 添加过滤标签
@@ -651,24 +386,11 @@ class DouyinDBBase {
651
386
  */
652
387
  async addFilterTag(sec_uid, tag) {
653
388
  await this.getOrCreateDouyinUser(sec_uid);
654
- let filterTag = await this.getQuery('SELECT * FROM FilterTags WHERE sec_uid = ? AND tag = ?', [sec_uid, tag]);
655
- if (!filterTag) {
656
- const now = new Date().toISOString();
657
- const result = await this.runQuery('INSERT INTO FilterTags (sec_uid, tag, createdAt, updatedAt) VALUES (?, ?, ?, ?)', [
658
- sec_uid,
659
- tag,
660
- now,
661
- now
662
- ]);
663
- filterTag = {
664
- id: result.lastID,
665
- sec_uid,
666
- tag,
667
- createdAt: now,
668
- updatedAt: now
669
- };
670
- }
671
- return filterTag;
389
+ const [existing] = await this.db.get(model_1.TABLE.douyinFilterTag, { sec_uid, tag });
390
+ if (existing)
391
+ return existing;
392
+ const time = now();
393
+ return await this.db.create(model_1.TABLE.douyinFilterTag, { sec_uid, tag, createdAt: time, updatedAt: time });
672
394
  }
673
395
  /**
674
396
  * 删除过滤标签
@@ -676,24 +398,24 @@ class DouyinDBBase {
676
398
  * @param tag 过滤标签
677
399
  */
678
400
  async removeFilterTag(sec_uid, tag) {
679
- const result = await this.runQuery('DELETE FROM FilterTags WHERE sec_uid = ? AND tag = ?', [sec_uid, tag]);
680
- return result.changes > 0;
401
+ const result = await this.db.remove(model_1.TABLE.douyinFilterTag, { sec_uid, tag });
402
+ return (result.removed ?? 0) > 0;
681
403
  }
682
404
  /**
683
405
  * 获取用户的所有过滤词
684
406
  * @param sec_uid 抖音用户sec_uid
685
407
  */
686
408
  async getFilterWords(sec_uid) {
687
- const filterWords = await this.allQuery('SELECT * FROM FilterWords WHERE sec_uid = ?', [sec_uid]);
688
- return filterWords.map((word) => word.word);
409
+ const rows = await this.db.get(model_1.TABLE.douyinFilterWord, { sec_uid });
410
+ return rows.map((row) => row.word);
689
411
  }
690
412
  /**
691
413
  * 获取用户的所有过滤标签
692
414
  * @param sec_uid 抖音用户sec_uid
693
415
  */
694
416
  async getFilterTags(sec_uid) {
695
- const filterTags = await this.allQuery('SELECT * FROM FilterTags WHERE sec_uid = ?', [sec_uid]);
696
- return filterTags.map((tag) => tag.tag);
417
+ const rows = await this.db.get(model_1.TABLE.douyinFilterTag, { sec_uid });
418
+ return rows.map((row) => row.tag);
697
419
  }
698
420
  /**
699
421
  * 获取用户的过滤配置
@@ -782,9 +504,10 @@ class DouyinDBBase {
782
504
  async cleanOldAwemeCache(days = 7) {
783
505
  const cutoffDate = new Date();
784
506
  cutoffDate.setDate(cutoffDate.getDate() - days);
785
- const cutoffDateStr = cutoffDate.toISOString();
786
- const result = await this.runQuery('DELETE FROM AwemeCaches WHERE createdAt < ?', [cutoffDateStr]);
787
- return result.changes ?? 0;
507
+ const result = await this.db.remove(model_1.TABLE.douyinAwemeCache, {
508
+ createdAt: { $lt: cutoffDate.toISOString() }
509
+ });
510
+ return result.removed ?? 0;
788
511
  }
789
512
  /** 为了向后兼容,保留groupRepository和awemeCacheRepository属性 */
790
513
  get groupRepository() {
@@ -793,7 +516,7 @@ class DouyinDBBase {
793
516
  if (options?.where?.botId) {
794
517
  return await this.getBotGroups(options.where.botId);
795
518
  }
796
- return await this.allQuery('SELECT * FROM Groups');
519
+ return (await this.db.get(model_1.TABLE.douyinGroup, {}));
797
520
  }
798
521
  };
799
522
  }
@@ -801,46 +524,28 @@ class DouyinDBBase {
801
524
  return {
802
525
  find: async (options = {}) => {
803
526
  const { where = {}, order, take, relations } = options;
804
- let sql = 'SELECT * FROM AwemeCaches';
805
- const params = [];
806
527
  // 构建WHERE条件
807
- const conditions = [];
808
- if (where.groupId) {
809
- conditions.push('groupId = ?');
810
- params.push(where.groupId);
811
- }
812
- if (where.sec_uid) {
813
- conditions.push('sec_uid = ?');
814
- params.push(where.sec_uid);
528
+ const query = {};
529
+ if (where.groupId)
530
+ query.groupId = where.groupId;
531
+ if (where.sec_uid)
532
+ query.sec_uid = where.sec_uid;
533
+ if (where.aweme_id)
534
+ query.aweme_id = where.aweme_id;
535
+ // 构建排序(minato 只认 asc / desc)
536
+ const sort = {};
537
+ const allowedFields = ['id', 'aweme_id', 'sec_uid', 'groupId', 'createdAt', 'updatedAt'];
538
+ for (const [field, direction] of Object.entries(order ?? {})) {
539
+ if (!allowedFields.includes(field))
540
+ continue;
541
+ sort[field] = String(direction).toLowerCase() === 'asc' ? 'asc' : 'desc';
815
542
  }
816
- if (where.aweme_id) {
817
- conditions.push('aweme_id = ?');
818
- params.push(where.aweme_id);
819
- }
820
- if (conditions.length > 0) {
821
- sql += ' WHERE ' + conditions.join(' AND ');
822
- }
823
- // 构建ORDER BY
824
- if (order) {
825
- const orderClauses = [];
826
- const allowedFields = ['id', 'aweme_id', 'sec_uid', 'groupId', 'createdAt', 'updatedAt'];
827
- const allowedDirections = ['ASC', 'DESC'];
828
- for (const [field, direction] of Object.entries(order)) {
829
- // 验证字段名和排序方向,防止SQL注入
830
- if (allowedFields.includes(field) && allowedDirections.includes(direction)) {
831
- orderClauses.push(`${field} ${direction}`);
832
- }
833
- }
834
- if (orderClauses.length > 0) {
835
- sql += ' ORDER BY ' + orderClauses.join(', ');
836
- }
837
- }
838
- // 构建LIMIT
839
- if (take) {
840
- sql += ' LIMIT ?';
841
- params.push(take.toString());
842
- }
843
- const caches = await this.allQuery(sql, params);
543
+ const cursor = {};
544
+ if (Object.keys(sort).length)
545
+ cursor.sort = sort;
546
+ if (take)
547
+ cursor.limit = take;
548
+ const caches = await this.db.get(model_1.TABLE.douyinAwemeCache, query, cursor);
844
549
  // 如果需要关联douyinUser数据
845
550
  if (relations && relations.includes('douyinUser')) {
846
551
  const result = [];
@@ -866,24 +571,24 @@ class DouyinDBBase {
866
571
  const { groupId, sec_uid, aweme_id } = conditions;
867
572
  // 优先处理 aweme_id + groupId 的精确删除(单条记录)
868
573
  if (aweme_id && groupId) {
869
- const result = await this.runQuery('DELETE FROM AwemeCaches WHERE aweme_id = ? AND groupId = ?', [aweme_id, groupId]);
870
- return { affected: result.changes };
574
+ const result = await this.db.remove(model_1.TABLE.douyinAwemeCache, { aweme_id, groupId });
575
+ return { affected: result.removed ?? 0 };
871
576
  }
872
577
  if (groupId && sec_uid) {
873
- const result = await this.runQuery('DELETE FROM AwemeCaches WHERE groupId = ? AND sec_uid = ?', [groupId, sec_uid]);
874
- return { affected: result.changes };
578
+ const result = await this.db.remove(model_1.TABLE.douyinAwemeCache, { groupId, sec_uid });
579
+ return { affected: result.removed ?? 0 };
875
580
  }
876
581
  if (groupId) {
877
- const result = await this.runQuery('DELETE FROM AwemeCaches WHERE groupId = ?', [groupId]);
878
- return { affected: result.changes };
582
+ const result = await this.db.remove(model_1.TABLE.douyinAwemeCache, { groupId });
583
+ return { affected: result.removed ?? 0 };
879
584
  }
880
585
  if (sec_uid) {
881
- const result = await this.runQuery('DELETE FROM AwemeCaches WHERE sec_uid = ?', [sec_uid]);
882
- return { affected: result.changes };
586
+ const result = await this.db.remove(model_1.TABLE.douyinAwemeCache, { sec_uid });
587
+ return { affected: result.removed ?? 0 };
883
588
  }
884
589
  if (aweme_id) {
885
- const result = await this.runQuery('DELETE FROM AwemeCaches WHERE aweme_id = ?', [aweme_id]);
886
- return { affected: result.changes };
590
+ const result = await this.db.remove(model_1.TABLE.douyinAwemeCache, { aweme_id });
591
+ return { affected: result.removed ?? 0 };
887
592
  }
888
593
  return { affected: 0 };
889
594
  }
@@ -896,8 +601,8 @@ class DouyinDBBase {
896
601
  * @param pushType 推送类型
897
602
  */
898
603
  async isAwemeInList(aweme_id, sec_uid, pushType) {
899
- const result = await this.getQuery('SELECT COUNT(*) as count FROM ListSnapshots WHERE aweme_id = ? AND sec_uid = ? AND pushType = ?', [aweme_id, sec_uid, pushType]);
900
- return (result?.count || 0) > 0;
604
+ const rows = await this.db.get(model_1.TABLE.douyinListSnapshot, { aweme_id, sec_uid, pushType }, { limit: 1 });
605
+ return rows.length > 0;
901
606
  }
902
607
  /**
903
608
  * 更新列表快照(用于喜欢列表和推荐列表)
@@ -906,12 +611,26 @@ class DouyinDBBase {
906
611
  * @param aweme_ids 作品ID列表
907
612
  */
908
613
  async updateListSnapshot(sec_uid, pushType, aweme_ids) {
909
- const now = new Date().toISOString();
614
+ const time = now();
910
615
  // 先删除该用户该类型的所有旧快照
911
- await this.runQuery('DELETE FROM ListSnapshots WHERE sec_uid = ? AND pushType = ?', [sec_uid, pushType]);
912
- // 插入新快照
913
- for (const aweme_id of aweme_ids) {
914
- await this.runQuery('INSERT OR IGNORE INTO ListSnapshots (sec_uid, pushType, aweme_id, createdAt, updatedAt) VALUES (?, ?, ?, ?, ?)', [sec_uid, pushType, aweme_id, now, now]);
616
+ await this.db.remove(model_1.TABLE.douyinListSnapshot, { sec_uid, pushType });
617
+ // 插入新快照(去重)
618
+ const existing = await this.db.get(model_1.TABLE.douyinListSnapshot, {
619
+ sec_uid,
620
+ pushType,
621
+ aweme_id: { $in: [...new Set(aweme_ids)] }
622
+ });
623
+ const known = new Set(existing.map((row) => row.aweme_id));
624
+ for (const aweme_id of new Set(aweme_ids)) {
625
+ if (known.has(aweme_id))
626
+ continue;
627
+ await this.db.create(model_1.TABLE.douyinListSnapshot, {
628
+ sec_uid,
629
+ pushType,
630
+ aweme_id,
631
+ createdAt: time,
632
+ updatedAt: time
633
+ });
915
634
  }
916
635
  }
917
636
  }