Импорт только текстовых сообщений, HISTORY_LIMIT по умолчанию 5000

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
yunogasai
2026-09-24 20:20:03 +02:00
parent 510221fd3c
commit c5f87f3195
4 changed files with 11 additions and 11 deletions

View File

@@ -18,4 +18,4 @@ OPENROUTER_API_KEY=
# Путь к SQLite-базе и сколько сообщений на чат хранить # Путь к SQLite-базе и сколько сообщений на чат хранить
DB_PATH=data/bot.db DB_PATH=data/bot.db
HISTORY_LIMIT=2000 HISTORY_LIMIT=5000

View File

@@ -22,6 +22,8 @@ Telegram не отдаёт ботам сообщения, написанные
2. Пришли боту `result.json` с подписью `/import` — прямо в этот чат или боту в личку (в личке бот сам поймёт, какой это чат). Можно и ответить `/import` на уже отправленный файл. 2. Пришли боту `result.json` с подписью `/import` — прямо в этот чат или боту в личку (в личке бот сам поймёт, какой это чат). Можно и ответить `/import` на уже отправленный файл.
3. Бот ответит, сколько сообщений и участников добавил. 3. Бот ответит, сколько сообщений и участников добавил.
Загружаются только сообщения с текстом: стикеры, голосовые, опросы и медиа без подписи пропускаются. HTML-экспорт не подходит — в нём нет ID авторов.
- Импорт идемпотентен: повторная загрузка ничего не дублирует, уже сохранённые ботом сообщения и ники не перезаписываются. - Импорт идемпотентен: повторная загрузка ничего не дублирует, уже сохранённые ботом сообщения и ники не перезаписываются.
- Хранится не больше `HISTORY_LIMIT` последних сообщений на чат — для большой истории подними лимит. - Хранится не больше `HISTORY_LIMIT` последних сообщений на чат — для большой истории подними лимит.
- Ников в экспорте нет, только имена: участники из экспорта находятся по имени, ник подтянется, когда человек напишет при боте. - Ников в экспорте нет, только имена: участники из экспорта находятся по имени, ник подтянется, когда человек напишет при боте.
@@ -61,7 +63,7 @@ pnpm dev
\* нужен ключ того провайдера, чья модель выбрана; бот проверяет это при старте. \* нужен ключ того провайдера, чья модель выбрана; бот проверяет это при старте.
| `DB_PATH` | нет | путь к SQLite, по умолчанию `data/bot.db` (в Docker — `/app/data/bot.db`) | | `DB_PATH` | нет | путь к SQLite, по умолчанию `data/bot.db` (в Docker — `/app/data/bot.db`) |
| `HISTORY_LIMIT` | нет | сколько сообщений на чат хранить, по умолчанию 2000 | | `HISTORY_LIMIT` | нет | сколько сообщений на чат хранить, по умолчанию 5000 |
## Деплой (Dokploy) ## Деплой (Dokploy)

View File

@@ -90,7 +90,7 @@ const parseModel = (): ModelChoice => {
export const DB_PATH = envValue("DB_PATH") ?? "data/bot.db"; export const DB_PATH = envValue("DB_PATH") ?? "data/bot.db";
/** Сколько последних сообщений каждого чата хранить в базе; нужен боту и скрипту импорта. */ /** Сколько последних сообщений каждого чата хранить в базе; нужен боту и скрипту импорта. */
export const HISTORY_LIMIT = positiveInt("HISTORY_LIMIT", 2000); export const HISTORY_LIMIT = positiveInt("HISTORY_LIMIT", 5000);
export const loadBotConfig = () => ({ export const loadBotConfig = () => ({
botToken: required("BOT_TOKEN"), botToken: required("BOT_TOKEN"),

View File

@@ -21,8 +21,6 @@ const exportMessageSchema = z.object({
file: z.string().optional(), file: z.string().optional(),
file_name: z.string().optional(), file_name: z.string().optional(),
media_type: z.string().optional(), media_type: z.string().optional(),
sticker_emoji: z.string().optional(),
poll: z.object({ question: z.string() }).optional(),
}); });
/** Экспорт одного чата из Telegram Desktop: «Экспорт истории чата» → формат JSON. */ /** Экспорт одного чата из Telegram Desktop: «Экспорт истории чата» → формат JSON. */
@@ -66,19 +64,19 @@ const plainText = (text: ExportMessage["text"]): string => {
return text.map((part) => (typeof part === "string" ? part : part.text)).join(""); return text.map((part) => (typeof part === "string" ? part : part.text)).join("");
}; };
/** Та же разметка медиа, что у живых сообщений (`MEDIA_LABELS`), чтобы история читалась одинаково. */ /**
* Импортируем только сообщения с текстом: стикеры, голосовые, опросы и медиа без подписи пропускаем.
* У медиа с подписью оставляем пометку из `MEDIA_LABELS` — как у живых сообщений.
*/
const exportMessageText = (msg: ExportMessage): string | undefined => { const exportMessageText = (msg: ExportMessage): string | undefined => {
const text = plainText(msg.text).trim(); const text = plainText(msg.text).trim();
if (!text) return undefined;
if (msg.photo) return withLabel(MEDIA_LABELS.photo, text); if (msg.photo) return withLabel(MEDIA_LABELS.photo, text);
if (msg.media_type === "sticker") return withLabel(MEDIA_LABELS.sticker, msg.sticker_emoji ?? "");
if (msg.media_type === "voice_message") return MEDIA_LABELS.voice;
if (msg.media_type === "video_message") return MEDIA_LABELS.videoNote;
if (msg.media_type === "video_file" || msg.media_type === "animation") return withLabel(MEDIA_LABELS.video, text); if (msg.media_type === "video_file" || msg.media_type === "animation") return withLabel(MEDIA_LABELS.video, text);
if (msg.file) return withLabel(MEDIA_LABELS.file(msg.file_name), text); if (msg.file) return withLabel(MEDIA_LABELS.file(msg.file_name), text);
if (msg.poll) return withLabel(MEDIA_LABELS.poll, msg.poll.question);
return text || undefined; return text;
}; };
/** Новые экспорты кладут `date_unixtime`; в старых только локальное время без пояса — берём как есть. */ /** Новые экспорты кладут `date_unixtime`; в старых только локальное время без пояса — берём как есть. */