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 => { 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; };