1
1
import type { Dialog , TelegramMessageType } from '@tg-search/core'
2
+ import type { MessageCreateInput , NewChat } from '@tg-search/db'
2
3
3
4
import * as fs from 'node:fs/promises'
4
5
import * as path from 'node:path'
5
6
import * as input from '@inquirer/prompts'
6
- import { useLogger } from '@tg-search/common'
7
+ import { getConfig , useLogger } from '@tg-search/common'
8
+ import { createMessageBatch , updateChat } from '@tg-search/db'
7
9
8
10
import { TelegramCommand } from '../command'
9
11
10
12
const logger = useLogger ( )
11
13
12
14
interface ExportOptions {
13
15
chatId ?: number
14
- format ?: 'json ' | 'html'
16
+ format ?: 'database ' | 'html' | 'json '
15
17
path ?: string
16
18
messageTypes ?: TelegramMessageType [ ]
17
19
startTime ?: Date
@@ -20,6 +22,69 @@ interface ExportOptions {
20
22
batchSize ?: number
21
23
}
22
24
25
+ /**
26
+ * Process a batch of messages for database export
27
+ */
28
+ async function processDatabaseBatch (
29
+ messages : MessageCreateInput [ ] ,
30
+ startIndex : number ,
31
+ ) : Promise < { shouldStop : boolean , failedCount : number } > {
32
+ try {
33
+ // Create messages in batch
34
+ const result = await createMessageBatch ( messages )
35
+ const firstMessage = messages [ 0 ]
36
+ const lastMessage = messages [ messages . length - 1 ]
37
+
38
+ logger . debug (
39
+ `已保存 ${ startIndex + 1 } - ${ startIndex + messages . length } 条消息 `
40
+ + `(${ new Date ( firstMessage . createdAt ) . toLocaleString ( ) } - ${ new Date ( lastMessage . createdAt ) . toLocaleString ( ) } )` ,
41
+ )
42
+
43
+ // If any message already exists, stop the export
44
+ if ( ! result . success || result . duplicateCount > 0 ) {
45
+ logger . debug ( '检测到已存在的消息,导出完成' )
46
+ return { shouldStop : true , failedCount : 0 }
47
+ }
48
+
49
+ return { shouldStop : false , failedCount : 0 }
50
+ }
51
+ catch ( error ) {
52
+ logger . withError ( error ) . error ( `保存批次消息失败 (${ startIndex + 1 } - ${ startIndex + messages . length } )` )
53
+ return { shouldStop : false , failedCount : messages . length }
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Save messages to JSON file
59
+ */
60
+ async function saveToJsonFile ( messages : any [ ] , chatId : number , exportPath : string ) : Promise < boolean > {
61
+ try {
62
+ const fileName = `${ chatId } _${ new Date ( ) . toISOString ( ) . split ( 'T' ) [ 0 ] } `
63
+ const filePath = path . join ( exportPath , `${ fileName } .json` )
64
+ await fs . writeFile ( filePath , JSON . stringify ( messages , null , 2 ) )
65
+ logger . debug ( `已保存 JSON 文件: ${ filePath } ` )
66
+ logger . log ( `已导出到文件: ${ filePath } ` )
67
+ return true
68
+ }
69
+ catch ( error ) {
70
+ logger . withError ( error ) . error ( '保存 JSON 文件失败' )
71
+ return false
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Update chat metadata
77
+ */
78
+ async function updateChatMetadata ( chat : Dialog ) : Promise < void > {
79
+ const chatInput : NewChat = {
80
+ id : chat . id ,
81
+ type : chat . type ,
82
+ title : chat . name ,
83
+ lastSyncTime : new Date ( ) ,
84
+ }
85
+ await updateChat ( chatInput )
86
+ }
87
+
23
88
/**
24
89
* Export command to export messages from Telegram
25
90
*/
@@ -55,16 +120,22 @@ export class ExportCommand extends TelegramCommand {
55
120
const format = options . format || await input . select ( {
56
121
message : '请选择导出格式:' ,
57
122
choices : [
58
- { name : 'HTML' , value : 'html' } ,
59
- { name : 'JSON' , value : 'json' } ,
123
+ { name : 'Database' , value : 'database' } ,
60
124
] ,
61
125
} )
62
126
63
- // Get export path
64
- const exportPath = options . path || await input . input ( {
65
- message : '请输入导出路径:' ,
66
- default : './export' ,
67
- } )
127
+ // Get export path if not exporting to database
128
+ let exportPath = options . path
129
+ if ( format !== 'database' ) {
130
+ exportPath = exportPath || await input . input ( {
131
+ message : '请输入导出路径:' ,
132
+ default : './export' ,
133
+ } )
134
+
135
+ // Create export directory
136
+ await fs . mkdir ( exportPath , { recursive : true } )
137
+ logger . debug ( `已创建导出目录: ${ exportPath } ` )
138
+ }
68
139
69
140
// Get message types
70
141
const messageTypes = options . messageTypes || await input . checkbox ( {
@@ -109,21 +180,14 @@ export class ExportCommand extends TelegramCommand {
109
180
default : '0' ,
110
181
} )
111
182
112
- // Get batch size
113
- const batchSize = options . batchSize || await input . input ( {
114
- message : '每多少条消息提醒一次继续?' ,
115
- default : '3000' ,
116
- } )
117
-
118
- // Create export directory
119
- await fs . mkdir ( exportPath , { recursive : true } )
120
- logger . debug ( `已创建导出目录: ${ exportPath } ` )
183
+ // Get batch size from config
184
+ const batchSize = options . batchSize || getConfig ( ) . messageBatchSize
121
185
122
186
// Export messages
123
187
logger . log ( '正在导出消息...' )
124
188
let count = 0
125
189
let failedCount = 0
126
- const skippedCount = 0
190
+ let shouldStop = false
127
191
const messages = [ ]
128
192
129
193
// Export messages
@@ -138,17 +202,72 @@ export class ExportCommand extends TelegramCommand {
138
202
count ++
139
203
if ( count % Number ( batchSize ) === 0 ) {
140
204
logger . debug ( `已处理 ${ count } 条消息` )
205
+
206
+ // Save current batch to database if format is database
207
+ if ( format === 'database' ) {
208
+ const batch = messages . slice ( count - Number ( batchSize ) , count )
209
+ const messageInputs : MessageCreateInput [ ] = batch . map ( message => ( {
210
+ id : message . id ,
211
+ chatId : message . chatId ,
212
+ type : message . type ,
213
+ content : message . content ,
214
+ fromId : message . fromId ,
215
+ replyToId : message . replyToId ,
216
+ forwardFromChatId : message . forwardFromChatId ,
217
+ forwardFromMessageId : message . forwardFromMessageId ,
218
+ views : message . views ,
219
+ forwards : message . forwards ,
220
+ createdAt : message . createdAt ,
221
+ } ) )
222
+
223
+ const result = await processDatabaseBatch ( messageInputs , count - Number ( batchSize ) )
224
+ shouldStop = result . shouldStop
225
+ failedCount += result . failedCount
226
+
227
+ if ( shouldStop ) {
228
+ break
229
+ }
230
+ }
141
231
}
142
232
}
143
233
144
- // Save to file
145
- const fileName = `${ chatId } _${ new Date ( ) . toISOString ( ) . split ( 'T' ) [ 0 ] } `
146
- const filePath = path . join ( exportPath , `${ fileName } .${ format } ` )
147
-
234
+ // Save to file or database
148
235
try {
149
236
if ( format === 'json' ) {
150
- await fs . writeFile ( filePath , JSON . stringify ( messages , null , 2 ) )
151
- logger . debug ( `已保存 JSON 文件: ${ filePath } ` )
237
+ const success = await saveToJsonFile ( messages , chatId , exportPath ! )
238
+ if ( ! success ) {
239
+ failedCount = count
240
+ }
241
+ }
242
+ else if ( format === 'database' && ! shouldStop ) {
243
+ // Update chat metadata
244
+ await updateChatMetadata ( selectedChat )
245
+
246
+ // Save remaining messages
247
+ const remainingCount = count % Number ( batchSize )
248
+ if ( remainingCount > 0 ) {
249
+ const batch = messages . slice ( - remainingCount )
250
+ const messageInputs : MessageCreateInput [ ] = batch . map ( message => ( {
251
+ id : message . id ,
252
+ chatId : message . chatId ,
253
+ type : message . type ,
254
+ content : message . content ,
255
+ fromId : message . fromId ,
256
+ replyToId : message . replyToId ,
257
+ forwardFromChatId : message . forwardFromChatId ,
258
+ forwardFromMessageId : message . forwardFromMessageId ,
259
+ views : message . views ,
260
+ forwards : message . forwards ,
261
+ createdAt : message . createdAt ,
262
+ } ) )
263
+
264
+ const result = await processDatabaseBatch ( messageInputs , count - remainingCount )
265
+ failedCount += result . failedCount
266
+ }
267
+
268
+ if ( failedCount === 0 ) {
269
+ logger . log ( '已导出到数据库' )
270
+ }
152
271
}
153
272
else {
154
273
// TODO: 实现 HTML 导出
@@ -158,27 +277,13 @@ export class ExportCommand extends TelegramCommand {
158
277
}
159
278
catch ( error ) {
160
279
failedCount = count
161
- logger . withError ( error ) . error ( `保存文件失败: ${ filePath } ` )
162
- }
163
-
164
- if ( failedCount === 0 ) {
165
- logger . log ( `已导出到文件: ${ filePath } ` )
280
+ logger . withError ( error ) . error ( `保存失败` )
166
281
}
167
282
168
283
const summary = failedCount > 0
169
- ? `导出完成,共导出 ${ count } 条消息,${ failedCount } 条消息失败, ${ skippedCount } 条消息已存在 `
170
- : `导出完成,共导出 ${ count } 条消息, ${ skippedCount } 条消息已存在 `
284
+ ? `导出完成,共导出 ${ count } 条消息,${ failedCount } 条消息失败`
285
+ : `导出完成,共导出 ${ count } 条消息`
171
286
logger . log ( summary )
172
-
173
- // Ask if continue
174
- const shouldContinue = await input . confirm ( {
175
- message : '是否继续导出?' ,
176
- default : false ,
177
- } )
178
-
179
- if ( shouldContinue ) {
180
- await this . execute ( _args , options )
181
- }
182
287
}
183
288
}
184
289
0 commit comments