Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 24 additions & 11 deletions src/handlers/onPhoto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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)
Expand All @@ -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` : "";
Expand All @@ -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);
};
Expand Down
12 changes: 12 additions & 0 deletions src/telegram/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
});
}
17 changes: 17 additions & 0 deletions tests/handlers/onPhotoMain.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
});
31 changes: 21 additions & 10 deletions tests/telegram/context.test.ts
Original file line number Diff line number Diff line change
@@ -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();
Expand Down Expand Up @@ -33,6 +34,14 @@ function createCtx(update?: Partial<Update>, 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({
Expand Down Expand Up @@ -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") });
Expand Down Expand Up @@ -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 });
});
});
Loading