Files
boltun-ai/src/bot/handlers/context-handlers.ts
yunogasai 510221fd3c Импорт старой переписки из экспорта 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>
2026-09-24 20:18:43 +02:00

76 lines
3.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { Composer, InlineKeyboard, type Context } from "grammy";
import { formatUtc } from "../../libs/time.js";
import type { ChatHistoryService } from "../../services/chat-history-service.js";
import type { ChatMemberService } from "../../services/chat-member-service.js";
const CLEAR_CONFIRM = "clear:yes";
const CLEAR_CANCEL = "clear:no";
interface ContextHandlersDeps {
chatHistoryService: ChatHistoryService;
chatMemberService: ChatMemberService;
}
/** Видит ли бот все сообщения группы: privacy mode выключен или бот — админ группы. */
const visibilityText = async (ctx: Context): Promise<string> => {
if (ctx.chat?.type === "private") return "Это личка — я вижу все сообщения.";
if (ctx.me.can_read_all_group_messages) return "Privacy mode выключен — я вижу все сообщения группы.";
const member = await ctx.getChatMember(ctx.me.id).catch(() => undefined);
if (member?.status === "administrator") return "Я админ группы — вижу все сообщения.";
return (
"⚠️ Privacy mode включён и я не админ — вижу только команды, упоминания и ответы мне.\n" +
"Отключи в @BotFather (/setprivacy → Disable) и добавь меня в группу заново, либо сделай админом."
);
};
/** Что бот помнит о чате и очистка этой памяти. Подключать только за фильтром администратора. */
export const createContextHandlers = ({ chatHistoryService, chatMemberService }: ContextHandlersDeps) => {
const composer = new Composer();
composer.command("context", async (ctx) => {
const { count, oldest, limit } = chatHistoryService.stats(ctx.chat.id);
const members = chatMemberService.list(ctx.chat.id).length;
const since = oldest === null ? "" : `, с ${formatUtc(oldest)}`;
const lines = [
`Помню сообщений: ${count} из ${limit} возможных${since}.`,
`Знаю участников: ${members}.`,
await visibilityText(ctx),
`ID чата: ${ctx.chat.id}`,
"Старые сообщения, написанные до моего добавления, Telegram боту не отдаёт — загрузи экспорт через /import.",
"Очистить переписку: /clear",
];
await ctx.reply(lines.join("\n"));
});
composer.command("clear", async (ctx) => {
const { count } = chatHistoryService.stats(ctx.chat.id);
const keyboard = new InlineKeyboard().text("🗑 Да, очистить", CLEAR_CONFIRM).text("Отмена", CLEAR_CANCEL);
await ctx.reply(`Забыть сохранённую переписку этого чата (${count} сообщений)? Имена участников останутся.`, {
reply_markup: keyboard,
});
});
composer.callbackQuery(CLEAR_CONFIRM, async (ctx) => {
const removed = ctx.chat ? chatHistoryService.clear(ctx.chat.id) : 0;
await ctx.answerCallbackQuery({ text: "Контекст очищен" });
await ctx.editMessageText(`Готово: забыл ${removed} сообщений. Начинаю с чистого листа.`).catch(() => {});
});
composer.callbackQuery(CLEAR_CANCEL, async (ctx) => {
await ctx.answerCallbackQuery({ text: "Отменено" });
await ctx.editMessageText("Очистка отменена.").catch(() => {});
});
return composer;
};