koishi-plugin-kkk 3.9.0 → 3.9.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/CHANGELOG.md CHANGED
@@ -1,5 +1,38 @@
1
1
  # 更新日志
2
2
 
3
+ ## 3.9.1
4
+
5
+ > 修 MySQL 上启动直接报 `ER_TOO_LONG_KEY`、插件起不来的问题。
6
+ > 3.9.0 建的索引太宽了:索引字段现在按实际数据长度定长,最宽的索引从 4080 字节降到 1152 字节。
7
+
8
+ ### 修复 MySQL 索引过长(ER_TOO_LONG_KEY)
9
+
10
+ - **现象**:MySQL 环境启动就报 `ER_TOO_LONG_KEY`(Specified key was too long),索引超过 3072 字节,表建不出来。
11
+ - **原因**:字符串字段没给长度,MySQL 按 `VARCHAR(255)` 建,`utf8mb4` 下每字符 4 字节,
12
+ 单个字段就是 1020 字节。抖音作品缓存表那个由 **四个** 字段组成的唯一键
13
+ (作品 id + sec_uid + 群号 + 推送类型)算下来 4080 字节,直接超了上限。
14
+ 另有几张统计表卡在 3060 字节 —— 离上限只差 12 字节,随时会爆。
15
+ - **处理**:凡是进索引(主键 / 唯一键)的字段,都按实际数据长度定长:
16
+
17
+ | 字段 | 长度 | 说明 |
18
+ | --- | --- | --- |
19
+ | 群号 / QQ 号 / botId / 作品 id / 动态 id | 64 | 最多几十个字符 |
20
+ | 抖音 `sec_uid` | 128 | 实际最长见过 76 |
21
+ | 平台 / 内容形态 / 日期 / 指标名 | 16–32 | 固定枚举、`YYYY-MM-DD` |
22
+ | 过滤词 / 过滤标签 | 128 | 自定义文本 |
23
+ | 备注、昵称等 | 不限制 | 不进索引,不影响 |
24
+
25
+ 现在最宽的索引是 1152 字节(原来是 4080),其它都在 1024 以内。
26
+ - **在 MySQL 上已经建出过半截 `kkk` 表的**,需要先删掉再启动 —— 已有索引不一定能被自动改过来:
27
+
28
+ ```sql
29
+ SELECT TABLE_NAME FROM information_schema.TABLES
30
+ WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME LIKE 'kkk%';
31
+ ```
32
+
33
+ 确认是插件的表之后 `DROP` 掉,重启 Koishi 会按新定义重建。
34
+ - sqlite / postgres 不受影响,也不用做上面这一步(字段长度本来就够)。
35
+
3
36
  ## 3.9.0
4
37
 
5
38
  > 插件不再自己建数据库了,全部改用 Koishi 自带的数据库服务。
@@ -1,10 +1,10 @@
1
1
  {
2
- "version": "3.9.0",
3
- "buildTime": "2026-09-29T04:52:03.418Z",
4
- "buildTimestamp": 1790657523454,
2
+ "version": "3.9.1",
3
+ "buildTime": "2026-09-29T10:19:42.069Z",
4
+ "buildTimestamp": 1790677182093,
5
5
  "name": "koishi-plugin-kkk",
6
6
  "description": "抖音 / B站 / 快手 / 小红书 视频解析与动态推送,带 QQ 交互面板与弹幕烧录(karin-plugin-kkk 的 Koishi 移植版)",
7
7
  "homepage": "https://github.com/maimai993/koishi-plugin-kkk#readme",
8
- "commitHash": "e58bb629886c203412e751fa87ef69b52dc44f52",
9
- "shortCommitHash": "e58bb62"
8
+ "commitHash": "0dde6fc426986698fb0e937768eb4aa86d24f2d4",
9
+ "shortCommitHash": "0dde6fc"
10
10
  }
@@ -27,6 +27,14 @@ exports.TABLE = {
27
27
  statsMetric: 'kkk.statistics.metric',
28
28
  };
29
29
  /* ------------------------------ 建表 ------------------------------ */
30
+ /**
31
+ * 定长字符串字段。
32
+ *
33
+ * 走索引的字段必须给长度(见文件头「为什么索引字段都写了明确长度」),
34
+ * 这里的数字都是按实际数据留的余量:群号 / QQ 号 / 作品 id 这类最多几十个字符,
35
+ * 抖音 sec_uid 最长见过 76,给 128。
36
+ */
37
+ const str = (length) => ({ type: 'string', length });
30
38
  /** 进程内只建一次(插件热重载时 apply 会再跑一遍,重复 extend 没意义) */
31
39
  let modelsReady = false;
32
40
  /**
@@ -39,162 +47,162 @@ const extendModels = (ctx) => {
39
47
  modelsReady = true;
40
48
  // ---------- 抖音 ----------
41
49
  ctx.model.extend(exports.TABLE.douyinBot, {
42
- id: 'string',
43
- createdAt: 'string',
44
- updatedAt: 'string',
50
+ id: str(64),
51
+ createdAt: str(32),
52
+ updatedAt: str(32),
45
53
  }, { primary: 'id' });
46
54
  ctx.model.extend(exports.TABLE.douyinGroup, {
47
- id: 'string',
48
- botId: 'string',
49
- createdAt: 'string',
50
- updatedAt: 'string',
55
+ id: str(64),
56
+ botId: str(64),
57
+ createdAt: str(32),
58
+ updatedAt: str(32),
51
59
  }, { primary: ['id', 'botId'] });
52
60
  ctx.model.extend(exports.TABLE.douyinUser, {
53
- sec_uid: 'string',
54
- short_id: 'string',
61
+ sec_uid: str(128),
62
+ short_id: str(64),
55
63
  remark: 'string',
56
64
  living: 'boolean',
57
- filterMode: 'string',
58
- createdAt: 'string',
59
- updatedAt: 'string',
65
+ filterMode: str(16),
66
+ createdAt: str(32),
67
+ updatedAt: str(32),
60
68
  }, { primary: 'sec_uid' });
61
69
  ctx.model.extend(exports.TABLE.douyinSubscription, {
62
- groupId: 'string',
63
- sec_uid: 'string',
64
- createdAt: 'string',
65
- updatedAt: 'string',
70
+ groupId: str(64),
71
+ sec_uid: str(128),
72
+ createdAt: str(32),
73
+ updatedAt: str(32),
66
74
  }, { primary: ['groupId', 'sec_uid'] });
67
75
  ctx.model.extend(exports.TABLE.douyinAwemeCache, {
68
76
  id: 'unsigned',
69
- aweme_id: 'string',
70
- sec_uid: 'string',
71
- groupId: 'string',
72
- pushType: 'string',
73
- createdAt: 'string',
74
- updatedAt: 'string',
77
+ aweme_id: str(64),
78
+ sec_uid: str(128),
79
+ groupId: str(64),
80
+ pushType: str(32),
81
+ createdAt: str(32),
82
+ updatedAt: str(32),
75
83
  }, { autoInc: true, unique: [['aweme_id', 'sec_uid', 'groupId', 'pushType']] });
76
84
  ctx.model.extend(exports.TABLE.douyinFilterWord, {
77
85
  id: 'unsigned',
78
- sec_uid: 'string',
79
- word: 'string',
80
- createdAt: 'string',
81
- updatedAt: 'string',
86
+ sec_uid: str(128),
87
+ word: str(128),
88
+ createdAt: str(32),
89
+ updatedAt: str(32),
82
90
  }, { autoInc: true, unique: [['sec_uid', 'word']] });
83
91
  ctx.model.extend(exports.TABLE.douyinFilterTag, {
84
92
  id: 'unsigned',
85
- sec_uid: 'string',
86
- tag: 'string',
87
- createdAt: 'string',
88
- updatedAt: 'string',
93
+ sec_uid: str(128),
94
+ tag: str(128),
95
+ createdAt: str(32),
96
+ updatedAt: str(32),
89
97
  }, { autoInc: true, unique: [['sec_uid', 'tag']] });
90
98
  ctx.model.extend(exports.TABLE.douyinListSnapshot, {
91
99
  id: 'unsigned',
92
- sec_uid: 'string',
93
- pushType: 'string',
94
- aweme_id: 'string',
95
- createdAt: 'string',
96
- updatedAt: 'string',
100
+ sec_uid: str(128),
101
+ pushType: str(32),
102
+ aweme_id: str(64),
103
+ createdAt: str(32),
104
+ updatedAt: str(32),
97
105
  }, { autoInc: true, unique: [['sec_uid', 'pushType', 'aweme_id']] });
98
106
  // ---------- B站 ----------
99
107
  ctx.model.extend(exports.TABLE.bilibiliBot, {
100
- id: 'string',
101
- createdAt: 'string',
102
- updatedAt: 'string',
108
+ id: str(64),
109
+ createdAt: str(32),
110
+ updatedAt: str(32),
103
111
  }, { primary: 'id' });
104
112
  ctx.model.extend(exports.TABLE.bilibiliGroup, {
105
- id: 'string',
106
- botId: 'string',
107
- createdAt: 'string',
108
- updatedAt: 'string',
113
+ id: str(64),
114
+ botId: str(64),
115
+ createdAt: str(32),
116
+ updatedAt: str(32),
109
117
  }, { primary: ['id', 'botId'] });
110
118
  ctx.model.extend(exports.TABLE.bilibiliUser, {
111
119
  host_mid: 'unsigned',
112
120
  remark: 'string',
113
- filterMode: 'string',
114
- createdAt: 'string',
115
- updatedAt: 'string',
121
+ filterMode: str(16),
122
+ createdAt: str(32),
123
+ updatedAt: str(32),
116
124
  }, { primary: 'host_mid' });
117
125
  ctx.model.extend(exports.TABLE.bilibiliSubscription, {
118
- groupId: 'string',
126
+ groupId: str(64),
119
127
  host_mid: 'unsigned',
120
- createdAt: 'string',
121
- updatedAt: 'string',
128
+ createdAt: str(32),
129
+ updatedAt: str(32),
122
130
  }, { primary: ['groupId', 'host_mid'] });
123
131
  ctx.model.extend(exports.TABLE.bilibiliDynamicCache, {
124
132
  id: 'unsigned',
125
- dynamic_id: 'string',
133
+ dynamic_id: str(64),
126
134
  host_mid: 'unsigned',
127
- groupId: 'string',
128
- dynamic_type: 'string',
129
- createdAt: 'string',
130
- updatedAt: 'string',
135
+ groupId: str(64),
136
+ dynamic_type: str(32),
137
+ createdAt: str(32),
138
+ updatedAt: str(32),
131
139
  }, { autoInc: true, unique: [['dynamic_id', 'host_mid', 'groupId']] });
132
140
  ctx.model.extend(exports.TABLE.bilibiliFilterWord, {
133
141
  id: 'unsigned',
134
142
  host_mid: 'unsigned',
135
- word: 'string',
136
- createdAt: 'string',
137
- updatedAt: 'string',
143
+ word: str(128),
144
+ createdAt: str(32),
145
+ updatedAt: str(32),
138
146
  }, { autoInc: true, unique: [['host_mid', 'word']] });
139
147
  ctx.model.extend(exports.TABLE.bilibiliFilterTag, {
140
148
  id: 'unsigned',
141
149
  host_mid: 'unsigned',
142
- tag: 'string',
143
- createdAt: 'string',
144
- updatedAt: 'string',
150
+ tag: str(128),
151
+ createdAt: str(32),
152
+ updatedAt: str(32),
145
153
  }, { autoInc: true, unique: [['host_mid', 'tag']] });
146
154
  // ---------- 统计 ----------
147
155
  ctx.model.extend(exports.TABLE.statsParse, {
148
156
  id: 'unsigned',
149
- groupId: 'string',
150
- userId: 'string',
151
- platform: 'string',
157
+ groupId: str(64),
158
+ userId: str(64),
159
+ platform: str(32),
152
160
  parseCount: 'unsigned',
153
- createdAt: 'string',
154
- updatedAt: 'string',
161
+ createdAt: str(32),
162
+ updatedAt: str(32),
155
163
  }, { autoInc: true, unique: [['groupId', 'userId', 'platform']] });
156
164
  ctx.model.extend(exports.TABLE.statsHistory, {
157
165
  id: 'unsigned',
158
- date: 'string',
166
+ date: str(16),
159
167
  totalParses: 'unsigned',
160
168
  douyin: 'unsigned',
161
169
  bilibili: 'unsigned',
162
170
  kuaishou: 'unsigned',
163
171
  xiaohongshu: 'unsigned',
164
- createdAt: 'string',
172
+ createdAt: str(32),
165
173
  }, { autoInc: true, unique: [['date']] });
166
174
  ctx.model.extend(exports.TABLE.statsGlobal, {
167
- key: 'string',
175
+ key: str(64),
168
176
  value: 'unsigned',
169
- updatedAt: 'string',
177
+ updatedAt: str(32),
170
178
  }, { primary: 'key' });
171
179
  ctx.model.extend(exports.TABLE.statsGroupHistory, {
172
- groupId: 'string',
173
- date: 'string',
174
- platform: 'string',
180
+ groupId: str(64),
181
+ date: str(16),
182
+ platform: str(32),
175
183
  parseCount: 'unsigned',
176
- updatedAt: 'string',
184
+ updatedAt: str(32),
177
185
  }, { primary: ['groupId', 'date', 'platform'] });
178
186
  ctx.model.extend(exports.TABLE.statsHour, {
179
- groupId: 'string',
187
+ groupId: str(64),
180
188
  hour: 'unsigned',
181
- platform: 'string',
189
+ platform: str(32),
182
190
  parseCount: 'unsigned',
183
- updatedAt: 'string',
191
+ updatedAt: str(32),
184
192
  }, { primary: ['groupId', 'hour', 'platform'] });
185
193
  ctx.model.extend(exports.TABLE.statsWorkType, {
186
- groupId: 'string',
187
- platform: 'string',
188
- workType: 'string',
194
+ groupId: str(64),
195
+ platform: str(32),
196
+ workType: str(32),
189
197
  parseCount: 'unsigned',
190
- updatedAt: 'string',
198
+ updatedAt: str(32),
191
199
  }, { primary: ['groupId', 'platform', 'workType'] });
192
200
  ctx.model.extend(exports.TABLE.statsMetric, {
193
- groupId: 'string',
194
- metric: 'string',
195
- bucket: 'string',
201
+ groupId: str(64),
202
+ metric: str(32),
203
+ bucket: str(32),
196
204
  parseCount: 'unsigned',
197
- updatedAt: 'string',
205
+ updatedAt: str(32),
198
206
  }, { primary: ['groupId', 'metric', 'bucket'] });
199
207
  };
200
208
  exports.extendModels = extendModels;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "koishi-plugin-kkk",
3
- "version": "3.9.0",
3
+ "version": "3.9.1",
4
4
  "description": "抖音 / B站 / 快手 / 小红书 视频解析与动态推送,带 QQ 交互面板与弹幕烧录(karin-plugin-kkk 的 Koishi 移植版)",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
@@ -157,6 +157,49 @@ const main = async () => {
157
157
  const removed = await statisticsDB.getRecentHistory(30)
158
158
  ok('二次读取稳定', removed.length === 1)
159
159
 
160
+ // ---------------- MySQL 索引长度自检 ----------------
161
+ // InnoDB 单个索引上限 3072 字节,utf8mb4 下每字符 4 字节(最多 768 字符)。
162
+ // 本地冒烟只能跑 sqlite,MySQL 的 ER_TOO_LONG_KEY 在这儿触发不了,
163
+ // 所以直接从模型定义把每个索引的字节数算出来,超了就失败。
164
+ const LIMIT = 3072
165
+ const CHARSET_BYTES = 4
166
+ // 字符串不给长度时,驱动按 VARCHAR(255) 建
167
+ const DEFAULT_STRING_LENGTH = 255
168
+ const NUMERIC_BYTES = { unsigned: 4, integer: 4, bigint: 8, float: 4, double: 8, decimal: 8, boolean: 1, date: 8, time: 8, timestamp: 8 }
169
+
170
+ // 索引定义分散在三处:primary(主键)、unique(唯一键)、indexes(显式索引)
171
+ const collectIndexes = (model) => {
172
+ const list = []
173
+ if (model.primary) list.push({ kind: 'primary', keys: [].concat(model.primary) })
174
+ for (const item of model.unique ?? []) list.push({ kind: 'unique', keys: [].concat(item) })
175
+ for (const item of model.indexes ?? []) list.push({ kind: 'index', keys: Object.keys(item.keys ?? {}) })
176
+ return list
177
+ }
178
+
179
+ let worst = { label: '', bytes: 0 }
180
+ let checked = 0
181
+ for (const [name, model] of Object.entries(ctx.model.tables ?? {})) {
182
+ if (!name.startsWith('kkk.')) continue
183
+ for (const index of collectIndexes(model)) {
184
+ let bytes = 0
185
+ for (const key of index.keys) {
186
+ const field = model.fields?.[key]
187
+ const type = typeof field === 'string' ? field : (field?.deftype ?? field?.type?.type)
188
+ if (type === 'string' || type === 'text') {
189
+ bytes += (typeof field === 'object' && field.length ? field.length : DEFAULT_STRING_LENGTH) * CHARSET_BYTES
190
+ } else {
191
+ bytes += NUMERIC_BYTES[type] ?? 8
192
+ }
193
+ }
194
+ checked++
195
+ const label = name + ' [' + index.keys.join(', ') + ']'
196
+ if (bytes > worst.bytes) worst = { label, bytes }
197
+ ok('索引长度 ' + index.kind + ' ' + label + ' = ' + bytes + 'B', bytes <= LIMIT)
198
+ }
199
+ }
200
+ ok('索引自检覆盖到表', checked >= 20, checked)
201
+ console.log(' 最宽索引:' + worst.label + ' = ' + worst.bytes + ' 字节(MySQL InnoDB 上限 ' + LIMIT + ')')
202
+
160
203
  await ctx.stop()
161
204
  fs.rmSync(path.dirname(dbFile), { recursive: true, force: true })
162
205
 
@@ -10,6 +10,15 @@
10
10
  * `createdAt` / `updatedAt` **仍然存 ISO 字符串**(而不是 timestamp / Date):
11
11
  * 上层几百处读的都是字符串(比如 `createdAt < 截止日期` 这种字典序比较),
12
12
  * 换成 Date 要全改一遍,没必要。
13
+ *
14
+ * ## 为什么索引字段都写了明确长度
15
+ *
16
+ * MySQL InnoDB 的单个索引总长上限是 3072 字节,utf8mb4 下每个字符 4 字节,
17
+ * 也就是**一个索引最多 768 个字符**。字符串字段不给长度时,驱动按 VARCHAR(255) 建,
18
+ * 复合索引很容易爆:比如作品缓存表原本 `aweme_id + sec_uid + groupId + pushType`
19
+ * 四个 255 字段就是 1020 字符 = 4080 字节,直接 `ER_TOO_LONG_KEY`。
20
+ * 所以凡是进了 primary / unique 的字段,都按实际数据长度写死(群号、QQ 号这类
21
+ * 给 64 已经很宽裕),不进索引的字段(备注、昵称之类)保持默认。
13
22
  */
14
23
  import { Context } from 'koishi'
15
24
 
@@ -245,6 +254,15 @@ declare module 'koishi' {
245
254
 
246
255
  /* ------------------------------ 建表 ------------------------------ */
247
256
 
257
+ /**
258
+ * 定长字符串字段。
259
+ *
260
+ * 走索引的字段必须给长度(见文件头「为什么索引字段都写了明确长度」),
261
+ * 这里的数字都是按实际数据留的余量:群号 / QQ 号 / 作品 id 这类最多几十个字符,
262
+ * 抖音 sec_uid 最长见过 76,给 128。
263
+ */
264
+ const str = (length: number) => ({ type: 'string', length }) as const
265
+
248
266
  /** 进程内只建一次(插件热重载时 apply 会再跑一遍,重复 extend 没意义) */
249
267
  let modelsReady = false
250
268
 
@@ -258,182 +276,182 @@ export const extendModels = (ctx: Context): void => {
258
276
 
259
277
  // ---------- 抖音 ----------
260
278
  ctx.model.extend(TABLE.douyinBot, {
261
- id: 'string',
262
- createdAt: 'string',
263
- updatedAt: 'string',
279
+ id: str(64),
280
+ createdAt: str(32),
281
+ updatedAt: str(32),
264
282
  }, { primary: 'id' })
265
283
 
266
284
  ctx.model.extend(TABLE.douyinGroup, {
267
- id: 'string',
268
- botId: 'string',
269
- createdAt: 'string',
270
- updatedAt: 'string',
285
+ id: str(64),
286
+ botId: str(64),
287
+ createdAt: str(32),
288
+ updatedAt: str(32),
271
289
  }, { primary: ['id', 'botId'] })
272
290
 
273
291
  ctx.model.extend(TABLE.douyinUser, {
274
- sec_uid: 'string',
275
- short_id: 'string',
292
+ sec_uid: str(128),
293
+ short_id: str(64),
276
294
  remark: 'string',
277
295
  living: 'boolean',
278
- filterMode: 'string',
279
- createdAt: 'string',
280
- updatedAt: 'string',
296
+ filterMode: str(16),
297
+ createdAt: str(32),
298
+ updatedAt: str(32),
281
299
  }, { primary: 'sec_uid' })
282
300
 
283
301
  ctx.model.extend(TABLE.douyinSubscription, {
284
- groupId: 'string',
285
- sec_uid: 'string',
286
- createdAt: 'string',
287
- updatedAt: 'string',
302
+ groupId: str(64),
303
+ sec_uid: str(128),
304
+ createdAt: str(32),
305
+ updatedAt: str(32),
288
306
  }, { primary: ['groupId', 'sec_uid'] })
289
307
 
290
308
  ctx.model.extend(TABLE.douyinAwemeCache, {
291
309
  id: 'unsigned',
292
- aweme_id: 'string',
293
- sec_uid: 'string',
294
- groupId: 'string',
295
- pushType: 'string',
296
- createdAt: 'string',
297
- updatedAt: 'string',
310
+ aweme_id: str(64),
311
+ sec_uid: str(128),
312
+ groupId: str(64),
313
+ pushType: str(32),
314
+ createdAt: str(32),
315
+ updatedAt: str(32),
298
316
  }, { autoInc: true, unique: [['aweme_id', 'sec_uid', 'groupId', 'pushType']] })
299
317
 
300
318
  ctx.model.extend(TABLE.douyinFilterWord, {
301
319
  id: 'unsigned',
302
- sec_uid: 'string',
303
- word: 'string',
304
- createdAt: 'string',
305
- updatedAt: 'string',
320
+ sec_uid: str(128),
321
+ word: str(128),
322
+ createdAt: str(32),
323
+ updatedAt: str(32),
306
324
  }, { autoInc: true, unique: [['sec_uid', 'word']] })
307
325
 
308
326
  ctx.model.extend(TABLE.douyinFilterTag, {
309
327
  id: 'unsigned',
310
- sec_uid: 'string',
311
- tag: 'string',
312
- createdAt: 'string',
313
- updatedAt: 'string',
328
+ sec_uid: str(128),
329
+ tag: str(128),
330
+ createdAt: str(32),
331
+ updatedAt: str(32),
314
332
  }, { autoInc: true, unique: [['sec_uid', 'tag']] })
315
333
 
316
334
  ctx.model.extend(TABLE.douyinListSnapshot, {
317
335
  id: 'unsigned',
318
- sec_uid: 'string',
319
- pushType: 'string',
320
- aweme_id: 'string',
321
- createdAt: 'string',
322
- updatedAt: 'string',
336
+ sec_uid: str(128),
337
+ pushType: str(32),
338
+ aweme_id: str(64),
339
+ createdAt: str(32),
340
+ updatedAt: str(32),
323
341
  }, { autoInc: true, unique: [['sec_uid', 'pushType', 'aweme_id']] })
324
342
 
325
343
  // ---------- B站 ----------
326
344
  ctx.model.extend(TABLE.bilibiliBot, {
327
- id: 'string',
328
- createdAt: 'string',
329
- updatedAt: 'string',
345
+ id: str(64),
346
+ createdAt: str(32),
347
+ updatedAt: str(32),
330
348
  }, { primary: 'id' })
331
349
 
332
350
  ctx.model.extend(TABLE.bilibiliGroup, {
333
- id: 'string',
334
- botId: 'string',
335
- createdAt: 'string',
336
- updatedAt: 'string',
351
+ id: str(64),
352
+ botId: str(64),
353
+ createdAt: str(32),
354
+ updatedAt: str(32),
337
355
  }, { primary: ['id', 'botId'] })
338
356
 
339
357
  ctx.model.extend(TABLE.bilibiliUser, {
340
358
  host_mid: 'unsigned',
341
359
  remark: 'string',
342
- filterMode: 'string',
343
- createdAt: 'string',
344
- updatedAt: 'string',
360
+ filterMode: str(16),
361
+ createdAt: str(32),
362
+ updatedAt: str(32),
345
363
  }, { primary: 'host_mid' })
346
364
 
347
365
  ctx.model.extend(TABLE.bilibiliSubscription, {
348
- groupId: 'string',
366
+ groupId: str(64),
349
367
  host_mid: 'unsigned',
350
- createdAt: 'string',
351
- updatedAt: 'string',
368
+ createdAt: str(32),
369
+ updatedAt: str(32),
352
370
  }, { primary: ['groupId', 'host_mid'] })
353
371
 
354
372
  ctx.model.extend(TABLE.bilibiliDynamicCache, {
355
373
  id: 'unsigned',
356
- dynamic_id: 'string',
374
+ dynamic_id: str(64),
357
375
  host_mid: 'unsigned',
358
- groupId: 'string',
359
- dynamic_type: 'string',
360
- createdAt: 'string',
361
- updatedAt: 'string',
376
+ groupId: str(64),
377
+ dynamic_type: str(32),
378
+ createdAt: str(32),
379
+ updatedAt: str(32),
362
380
  }, { autoInc: true, unique: [['dynamic_id', 'host_mid', 'groupId']] })
363
381
 
364
382
  ctx.model.extend(TABLE.bilibiliFilterWord, {
365
383
  id: 'unsigned',
366
384
  host_mid: 'unsigned',
367
- word: 'string',
368
- createdAt: 'string',
369
- updatedAt: 'string',
385
+ word: str(128),
386
+ createdAt: str(32),
387
+ updatedAt: str(32),
370
388
  }, { autoInc: true, unique: [['host_mid', 'word']] })
371
389
 
372
390
  ctx.model.extend(TABLE.bilibiliFilterTag, {
373
391
  id: 'unsigned',
374
392
  host_mid: 'unsigned',
375
- tag: 'string',
376
- createdAt: 'string',
377
- updatedAt: 'string',
393
+ tag: str(128),
394
+ createdAt: str(32),
395
+ updatedAt: str(32),
378
396
  }, { autoInc: true, unique: [['host_mid', 'tag']] })
379
397
 
380
398
  // ---------- 统计 ----------
381
399
  ctx.model.extend(TABLE.statsParse, {
382
400
  id: 'unsigned',
383
- groupId: 'string',
384
- userId: 'string',
385
- platform: 'string',
401
+ groupId: str(64),
402
+ userId: str(64),
403
+ platform: str(32),
386
404
  parseCount: 'unsigned',
387
- createdAt: 'string',
388
- updatedAt: 'string',
405
+ createdAt: str(32),
406
+ updatedAt: str(32),
389
407
  }, { autoInc: true, unique: [['groupId', 'userId', 'platform']] })
390
408
 
391
409
  ctx.model.extend(TABLE.statsHistory, {
392
410
  id: 'unsigned',
393
- date: 'string',
411
+ date: str(16),
394
412
  totalParses: 'unsigned',
395
413
  douyin: 'unsigned',
396
414
  bilibili: 'unsigned',
397
415
  kuaishou: 'unsigned',
398
416
  xiaohongshu: 'unsigned',
399
- createdAt: 'string',
417
+ createdAt: str(32),
400
418
  }, { autoInc: true, unique: [['date']] })
401
419
 
402
420
  ctx.model.extend(TABLE.statsGlobal, {
403
- key: 'string',
421
+ key: str(64),
404
422
  value: 'unsigned',
405
- updatedAt: 'string',
423
+ updatedAt: str(32),
406
424
  }, { primary: 'key' })
407
425
 
408
426
  ctx.model.extend(TABLE.statsGroupHistory, {
409
- groupId: 'string',
410
- date: 'string',
411
- platform: 'string',
427
+ groupId: str(64),
428
+ date: str(16),
429
+ platform: str(32),
412
430
  parseCount: 'unsigned',
413
- updatedAt: 'string',
431
+ updatedAt: str(32),
414
432
  }, { primary: ['groupId', 'date', 'platform'] })
415
433
 
416
434
  ctx.model.extend(TABLE.statsHour, {
417
- groupId: 'string',
435
+ groupId: str(64),
418
436
  hour: 'unsigned',
419
- platform: 'string',
437
+ platform: str(32),
420
438
  parseCount: 'unsigned',
421
- updatedAt: 'string',
439
+ updatedAt: str(32),
422
440
  }, { primary: ['groupId', 'hour', 'platform'] })
423
441
 
424
442
  ctx.model.extend(TABLE.statsWorkType, {
425
- groupId: 'string',
426
- platform: 'string',
427
- workType: 'string',
443
+ groupId: str(64),
444
+ platform: str(32),
445
+ workType: str(32),
428
446
  parseCount: 'unsigned',
429
- updatedAt: 'string',
447
+ updatedAt: str(32),
430
448
  }, { primary: ['groupId', 'platform', 'workType'] })
431
449
 
432
450
  ctx.model.extend(TABLE.statsMetric, {
433
- groupId: 'string',
434
- metric: 'string',
435
- bucket: 'string',
451
+ groupId: str(64),
452
+ metric: str(32),
453
+ bucket: str(32),
436
454
  parseCount: 'unsigned',
437
- updatedAt: 'string',
455
+ updatedAt: str(32),
438
456
  }, { primary: ['groupId', 'metric', 'bucket'] })
439
457
  }