Фото в вопросах, Gemini 3.8 Flash по умолчанию, команды /context и /clear
- Бот смотрит фото: подпись с упоминанием, /ask в подписи или ответом на фото, фото в личке - Картинки скачиваются через Bot API и передаются модели байтами (ссылка с токеном наружу не уходит) - OpenRouter по умолчанию — openrouter/google/gemini-3.8-flash; AI_IMAGE_INPUT для явного выбора - Модель без зрения получает пометку, что фото есть, но она его не видит - /context: сколько бот помнит и видит ли всю переписку (privacy mode / админ) - /clear: очистка сохранённой переписки чата с подтверждением Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
76
src/bot/handlers/context-handlers.ts
Normal file
76
src/bot/handlers/context-handlers.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import { Composer, InlineKeyboard, type Context } from "grammy";
|
||||
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;
|
||||
}
|
||||
|
||||
const formatDate = (unixSeconds: number): string =>
|
||||
new Date(unixSeconds * 1000).toISOString().slice(0, 16).replace("T", " ");
|
||||
|
||||
/** Видит ли бот все сообщения группы: 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 ? "" : `, с ${formatDate(oldest)} UTC`;
|
||||
|
||||
const lines = [
|
||||
`Помню сообщений: ${count} из ${limit} возможных${since}.`,
|
||||
`Знаю участников: ${members}.`,
|
||||
await visibilityText(ctx),
|
||||
"Старые сообщения, написанные до моего добавления, Telegram боту не отдаёт.",
|
||||
"Очистить переписку: /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;
|
||||
};
|
||||
Reference in New Issue
Block a user