diff --git a/src/handlers/onPhoto.ts b/src/handlers/onPhoto.ts index 601780c..495a648 100644 --- a/src/handlers/onPhoto.ts +++ b/src/handlers/onPhoto.ts @@ -6,6 +6,7 @@ import { recognizeImageText } from "../helpers/vision.ts"; import { log } from "../helpers"; import { useConfig } from "../config.ts"; import { sendTelegramMessage } from "../telegram/send.ts"; +import { createNewContext } from "../telegram/context.ts"; // Type guard to check if update has a message function isMessageUpdate(update: Update): update is Update.MessageUpdate { @@ -32,6 +33,26 @@ export default async function onPhoto(ctx: Context) { role: "user", }); + if (msg.caption && msg.caption.length > 100) { + log({ + msg: `[photo] caption too long, skip ocr: ${msg.caption.length}`, + logLevel: "info", + chatId: msg.chat.id, + chatTitle, + }); + + const newMsg = { + ...msg, + text: msg.caption, + entities: [], + } as const; + + const contextWithCaption = createNewContext(ctx, newMsg); + + await onTextMessage(contextWithCaption); + return; + } + const config = useConfig(); const model = config?.vision?.model || ""; if (!model) @@ -42,13 +63,13 @@ export default async function onPhoto(ctx: Context) { // Create a new message object with the recognized text const processPhoto = async () => { - let text = ''; + let text = ""; try { text = await recognizeImageText(msg, chat); } catch (error) { await sendTelegramMessage( msg.chat.id, - `Ошибка при распознавании текста: ${error instanceof Error ? error.message : 'Неизвестная ошибка'}` + `Ошибка при распознавании текста: ${error instanceof Error ? error.message : "Неизвестная ошибка"}`, ); } const caption = msg.caption ? `${msg.caption}\n` : ""; @@ -69,15 +90,7 @@ export default async function onPhoto(ctx: Context) { } as const; // Create a new context by extending the original context - const contextWithNewMessage = Object.create(Object.getPrototypeOf(ctx), { - ...Object.getOwnPropertyDescriptors(ctx), - message: { value: newMsg, writable: true, configurable: true }, - update: { - value: { ...ctx.update, message: newMsg }, - writable: true, - configurable: true, - }, - }); + const contextWithNewMessage = createNewContext(ctx, newMsg); await onTextMessage(contextWithNewMessage); }; diff --git a/src/telegram/context.ts b/src/telegram/context.ts index 985dcb3..d3079f6 100644 --- a/src/telegram/context.ts +++ b/src/telegram/context.ts @@ -142,3 +142,15 @@ export function getCtxChatMsg(ctx: Context): { return { chat, msg }; } + +export function createNewContext(ctx: Context, newMsg: Message) { + return Object.create(Object.getPrototypeOf(ctx), { + ...Object.getOwnPropertyDescriptors(ctx), + message: { value: newMsg, writable: true, configurable: true }, + update: { + value: { ...ctx.update, message: newMsg }, + writable: true, + configurable: true, + }, + }); +} \ No newline at end of file diff --git a/tests/handlers/onPhotoMain.test.ts b/tests/handlers/onPhotoMain.test.ts index 01ec007..f502f81 100644 --- a/tests/handlers/onPhotoMain.test.ts +++ b/tests/handlers/onPhotoMain.test.ts @@ -73,4 +73,21 @@ describe("onPhoto main flow", () => { expect(calledCtx.message.text).toBe("cap\nocr"); expect(mockSendTelegramMessage).not.toHaveBeenCalled(); }); + + it("skips ocr when caption is long", async () => { + const caption = "a".repeat(101); + const msg = { + chat: { id: 1, type: "private", title: "t" }, + photo: [{ file_id: "f" }], + caption, + } as Message.PhotoMessage; + mockCheckAccessLevel.mockResolvedValue({ msg, chat: {} as ConfigChatType }); + mockUseConfig.mockReturnValue({ vision: { model: "m" } }); + const ctx = createCtx(msg); + await onPhoto(ctx); + expect(mockRecognizeImageText).not.toHaveBeenCalled(); + expect(mockOnTextMessage).toHaveBeenCalled(); + const calledCtx = mockOnTextMessage.mock.calls[0][0]; + expect(calledCtx.message.text).toBe(caption); + }); }); diff --git a/tests/telegram/context.test.ts b/tests/telegram/context.test.ts index 2812eae..87b9ae7 100644 --- a/tests/telegram/context.test.ts +++ b/tests/telegram/context.test.ts @@ -1,7 +1,8 @@ import { jest, describe, it, expect, beforeEach } from "@jest/globals"; -import type { Context, Update } from "telegraf"; -import type { Message } from "telegraf/types"; +import type { Context } from "telegraf"; +import type { Message, Update } from "telegraf/types"; import type { ConfigChatType } from "../../src/types"; +import { createNewContext } from "../../src/telegram/context"; const mockUseConfig = jest.fn(); const mockLog = jest.fn(); @@ -33,6 +34,14 @@ function createCtx(update?: Partial, botName = "bot") { } as unknown as Context; } +function createMsg(username: string): Message.TextMessage { + return { + chat: { id: 123, type: "private", username }, + from: { username }, + text: "hi", + } as unknown as Message.TextMessage; +} + describe("getActionUserMsg", () => { it("returns user and message from callback query", () => { const ctx = createCtx({ @@ -61,14 +70,6 @@ describe("getCtxChatMsg", () => { toolParams: {}, } as ConfigChatType; - function createMsg(username: string): Message.TextMessage { - return { - chat: { id: 123, type: "private", username }, - from: { username }, - text: "hi", - } as unknown as Message.TextMessage; - } - it("returns chat when user allowed", () => { mockUseConfig.mockReturnValue({ chats: [baseChat], privateUsers: [] }); const ctx = createCtx({ message: createMsg("u") }); @@ -114,3 +115,13 @@ describe("getCtxChatMsg", () => { expect(chat).toMatchObject(baseChat); }); }); + +describe("createNewContext", () => { + it("creates new context with new message", () => { + const ctx = createCtx({ message: createMsg("u") }); + const newMsg = createMsg("new"); + const newCtx = createNewContext(ctx, newMsg); + expect(newCtx.message).toEqual(newMsg); + expect(newCtx.update).toEqual({ message: newMsg }); + }); +});