Импорт старой переписки из экспорта Telegram Desktop

- /import: прислать result.json с подписью /import в чат или боту в личку (или ответить на файл)
- Парсер экспорта (zod): текст частями, упоминания по имени-ссылке, медиа, служебные сообщения
- Импорт идемпотентен и не перезаписывает живые сообщения и ники
- Экспорт другого чата в группе отклоняется; в личке чат определяется по экспорту
- CLI import-history для файлов больше 20 МБ
- /context показывает ID чата
- Общие downloadFile и MEDIA_LABELS для живых сообщений, фото и импорта

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
yunogasai
2026-09-24 20:18:43 +02:00
parent e812f1b1e2
commit 510221fd3c
17 changed files with 417 additions and 38 deletions

View File

@@ -28,6 +28,10 @@ export const createMessageRepository = (db: Db) => {
VALUES (@chatId, @messageId, @userId, @date, @author, @text, @replyToMessageId)
ON CONFLICT(chat_id, message_id) DO UPDATE SET text = excluded.text
`),
insertIfMissing: db.prepare(`
INSERT OR IGNORE INTO messages (chat_id, message_id, user_id, date, author, text, reply_to_message_id)
VALUES (@chatId, @messageId, @userId, @date, @author, @text, @replyToMessageId)
`),
pruneChat: db.prepare(`
DELETE FROM messages WHERE chat_id = ? AND message_id <= (
SELECT message_id FROM messages WHERE chat_id = ?
@@ -51,6 +55,18 @@ export const createMessageRepository = (db: Db) => {
statements.pruneChat.run(message.chatId, message.chatId, keep);
});
/**
* Массовая загрузка (импорт): уже сохранённые сообщения не трогаем — живые данные точнее экспорта.
* Возвращает, сколько сообщений добавлено; затем историю чата обрезаем до `keep` последних.
*/
const insertManyAndPrune = db.transaction((chatId: number, messages: ChatMessage[], keep: number): number => {
const inserted = messages.reduce((sum, message) => sum + statements.insertIfMissing.run(message).changes, 0);
statements.pruneChat.run(chatId, chatId, keep);
return inserted;
});
/** Последние `limit` сообщений чата в хронологическом порядке. */
const latest = (chatId: number, limit: number): ChatMessage[] =>
toChronological(statements.latest.all(chatId, limit));
@@ -72,7 +88,7 @@ export const createMessageRepository = (db: Db) => {
/** Удаляет всю сохранённую историю чата; возвращает, сколько сообщений удалено. */
const deleteByChat = (chatId: number): number => statements.deleteByChat.run(chatId).changes;
return { upsertAndPrune, latest, latestByUser, findById, statsByChat, deleteByChat };
return { upsertAndPrune, insertManyAndPrune, latest, latestByUser, findById, statsByChat, deleteByChat };
};
export type MessageRepository = ReturnType<typeof createMessageRepository>;