Files
oh-my-pi-telegram/tests/media.test.ts
T
Evandro Fonseca Junior d1d04001d0
Release / release (push) Failing after 5s
Validate / validate (push) Failing after 5m21s
chore: fork @llblab/[email protected] as @evandrodevbr/oh-my-pi-telegram
Fork of upstream llblab/[email protected].0 (SHA afe09c5) repackaged as a
drop-in extension for can1357/oh-my-pi (omp >= 16.5.2).

Changes vs upstream:
- Rename package: @llblab/pi-telegram -> @evandrodevbr/oh-my-pi-telegram
- Bump version: 0.22.0 -> 0.22.0-evandro.1
- Update repository/homepage/bugs URLs to evandrodevbr
- Add omp field in package.json (displayName + compatibleWith)
- README: prepend 'Oh My Pi (omp) Compatibility' section with install
  commands, verified-components table, upstream lineage, and rationale
- CHANGELOG: add '0.22.0-evandro.1' entry under Unreleased
- docs/: rename @llblab/pi-telegram imports to @evandrodevbr/oh-my-pi-telegram
- tests/public-api.test.ts: rename package import paths

Gates re-run on the fork:
- npm run typecheck  -> tsc --noEmit (success)
- npm test           -> 1229 pass / 0 fail / 1 platform-only skip
- npm run audit      -> found 0 vulnerabilities
- npm run pack:check -> evandrodevbr-oh-my-pi-telegram-0.22.0-evandro.1.tgz

Verified:
- omp install --link ./pi-telegram -> Linked OK
- omp plugin list -> @evandrodevbr/[email protected].0-evandro.1

Upstream lineage: badlogic/pi-telegram -> llblab/pi-telegram (0.22.0) -> evandrodevbr/oh-my-pi-telegram (this fork)
2026-07-15 14:18:16 -03:00

453 lines
12 KiB
TypeScript

/**
* Regression tests for Telegram media and text extraction helpers
* Covers inbound file-info collection, text extraction, media groups, id collection, and history formatting
*/
import assert from "node:assert/strict";
import test from "node:test";
import {
collectTelegramFileInfos,
collectTelegramMessageIds,
createTelegramMediaGroupController,
createTelegramMediaGroupDispatchRuntime,
downloadTelegramMessageFiles,
extractFirstTelegramMessageText,
extractTelegramMessagePromptText,
extractTelegramMessagesPromptText,
extractTelegramMessagesText,
formatTelegramHistoryText,
getTelegramMediaGroupKey,
guessMediaType,
queueTelegramMediaGroupMessage,
removePendingTelegramMediaGroupMessages,
type TelegramMediaGroupState,
} from "../lib/media.ts";
type TestTimer = ReturnType<typeof setTimeout>;
function createTestTimer(id: number): TestTimer {
return id as unknown as TestTimer;
}
function getTestTimerId(timer: TestTimer): number {
return timer as unknown as number;
}
test("Media helpers collect file infos across Telegram message variants", () => {
const files = collectTelegramFileInfos([
{
message_id: 1,
text: "hello",
photo: [
{ file_id: "small", file_size: 1 },
{ file_id: "large", file_size: 10 },
],
document: {
file_id: "doc",
file_name: "report.png",
mime_type: "image/png",
},
voice: {
file_id: "voice",
mime_type: "audio/ogg",
},
sticker: {
file_id: "sticker",
},
},
]);
assert.deepEqual(
files.map((file) => ({
id: file.file_id,
name: file.fileName,
image: file.isImage,
})),
[
{ id: "large", name: "photo-1.jpg", image: true },
{ id: "doc", name: "report.png", image: true },
{ id: "voice", name: "voice-1.ogg", image: false },
{ id: "sticker", name: "sticker-1.webp", image: true },
],
);
});
test("Media helpers download collected file infos", async () => {
const downloaded = await downloadTelegramMessageFiles(
[
{
message_id: 3,
document: {
file_id: "doc-id",
file_name: "report.pdf",
mime_type: "application/pdf",
},
},
],
{
downloadFile: async (fileId, fileName) => `/tmp/${fileId}-${fileName}`,
},
);
assert.deepEqual(downloaded, [
{
path: "/tmp/doc-id-report.pdf",
fileName: "report.pdf",
isImage: false,
mimeType: "application/pdf",
kind: "document",
},
]);
});
test("Media helpers extract text, ids, and history summaries", () => {
const messages = [
{ message_id: 1, text: "first" },
{ message_id: 2, caption: "second" },
{ message_id: 2, text: "duplicate id" },
];
assert.equal(
extractTelegramMessagesText(messages),
"first\n\nsecond\n\nduplicate id",
);
assert.equal(extractFirstTelegramMessageText(messages), "first");
assert.deepEqual(collectTelegramMessageIds(messages), [1, 2]);
assert.equal(
formatTelegramHistoryText(
"hello",
[{ path: "/tmp/demo.txt" }],
["transcript"],
),
"hello\n\n[attachments] /tmp\n- /demo.txt\n\n[outputs]\n- transcript",
);
});
test("Media helpers keep raw text command-safe and add reply context only for prompts", () => {
const repliedCommand = {
message_id: 1,
text: "/status",
reply_to_message: { text: "quoted context" },
};
const captionReply = {
message_id: 2,
caption: "caption reply",
reply_to_message: { caption: "quoted caption" },
};
assert.equal(extractFirstTelegramMessageText([repliedCommand]), "/status");
assert.equal(extractTelegramMessagesText([repliedCommand]), "/status");
assert.equal(
extractTelegramMessagePromptText(repliedCommand),
"/status\n\n[reply] quoted context",
);
assert.equal(
extractTelegramMessagesPromptText([captionReply]),
"caption reply\n\n[reply] quoted caption",
);
});
test("Media helpers prefer rich-message text for reply context", () => {
const message = {
message_id: 1,
text: "current",
reply_to_message: {
text: "## Raw **Markdown** fallback",
rich_message: {
blocks: [
{ type: "heading", text: "Rendered heading", size: 2 },
{
type: "paragraph",
text: ["Plain ", { type: "bold", text: "reply" }, " text"],
},
{
type: "list",
items: [
{
label: "1.",
blocks: [{ type: "paragraph", text: "First item" }],
},
],
},
],
},
},
};
assert.equal(
extractTelegramMessagePromptText(message),
"current\n\n[reply] Rendered heading\n\nPlain reply text\n\n1. First item",
);
});
test("Media helpers truncate long reply context for prompt text", () => {
const longQuote = `${"a".repeat(1000)} extra`;
assert.equal(
extractTelegramMessagesPromptText([
{
message_id: 1,
text: "current",
reply_to_message: { text: longQuote },
},
]),
`current\n\n[reply] ${"a".repeat(1000)}…`,
);
});
test("Media helpers infer image media types from file paths", () => {
assert.equal(guessMediaType("/tmp/demo.png"), "image/png");
assert.equal(guessMediaType("/tmp/demo.txt"), undefined);
});
test("Media helpers key messages by chat and media group", () => {
assert.equal(
getTelegramMediaGroupKey({
message_id: 1,
chat: { id: 7 },
media_group_id: "album",
}),
"7:private:album",
);
assert.equal(
getTelegramMediaGroupKey({
message_id: 1,
chat: { id: 7 },
message_thread_id: 42,
media_group_id: "album",
}),
"7:thread:42:album",
);
assert.equal(
getTelegramMediaGroupKey({ message_id: 1, chat: { id: 7 } }),
undefined,
);
});
test("Media helpers replace debounce timers and dispatch grouped messages", async () => {
const groups = new Map<
string,
TelegramMediaGroupState<{
message_id: number;
chat: { id: number };
media_group_id?: string;
}>
>();
const cleared: number[] = [];
const callbacks: Array<() => void> = [];
const dispatched: number[][] = [];
let nextTimer = 1;
const setTimer = (callback: () => void): TestTimer => {
callbacks.push(callback);
return createTestTimer(nextTimer++);
};
const clearTimer = (timer: TestTimer): void => {
cleared.push(getTestTimerId(timer));
};
assert.equal(
queueTelegramMediaGroupMessage({
message: { message_id: 1, chat: { id: 7 }, media_group_id: "album" },
groups,
debounceMs: 100,
setTimer,
clearTimer,
dispatchMessages: (messages) =>
dispatched.push(messages.map((message) => message.message_id)),
}),
true,
);
queueTelegramMediaGroupMessage({
message: { message_id: 2, chat: { id: 7 }, media_group_id: "album" },
groups,
debounceMs: 100,
setTimer,
clearTimer,
dispatchMessages: (messages) =>
dispatched.push(messages.map((message) => message.message_id)),
});
assert.deepEqual(cleared, [1]);
callbacks.at(-1)?.();
await Promise.resolve();
assert.deepEqual(dispatched, [[1, 2]]);
assert.equal(groups.size, 0);
});
test("Media group keeps messages until asynchronous dispatch succeeds", async () => {
const groups = new Map<
string,
TelegramMediaGroupState<{
message_id: number;
chat: { id: number };
media_group_id?: string;
}>
>();
const callbacks: Array<() => void> = [];
let attempts = 0;
queueTelegramMediaGroupMessage({
message: { message_id: 1, chat: { id: 7 }, media_group_id: "album" },
groups,
debounceMs: 10,
setTimer: (callback) => {
callbacks.push(callback);
return createTestTimer(callbacks.length);
},
clearTimer: () => {},
dispatchMessages: async () => {
attempts += 1;
if (attempts === 1) throw new Error("queue admission failed");
},
});
callbacks[0]?.();
await Promise.resolve();
await Promise.resolve();
assert.equal(groups.size, 1);
assert.equal(callbacks.length, 2);
callbacks[1]?.();
await Promise.resolve();
await Promise.resolve();
assert.equal(attempts, 2);
assert.equal(groups.size, 0);
});
test("Media group controller owns timers, removal, and cleanup", () => {
const cleared: number[] = [];
const callbacks: Array<() => void> = [];
const dispatched: number[][] = [];
let nextTimer = 1;
const controller = createTelegramMediaGroupController<{
message_id: number;
chat: { id: number };
media_group_id?: string;
}>({
debounceMs: 100,
setTimer: (callback) => {
callbacks.push(callback);
return createTestTimer(nextTimer++);
},
clearTimer: (timer) => {
cleared.push(getTestTimerId(timer));
},
});
assert.equal(
controller.queueMessage({
message: { message_id: 1, chat: { id: 7 }, media_group_id: "album" },
dispatchMessages: (messages) =>
dispatched.push(messages.map((message) => message.message_id)),
}),
true,
);
controller.queueMessage({
message: { message_id: 2, chat: { id: 7 }, media_group_id: "album" },
dispatchMessages: (messages) =>
dispatched.push(messages.map((message) => message.message_id)),
});
assert.deepEqual(cleared, [1]);
assert.equal(controller.removeMessages([2]), 1);
assert.deepEqual(cleared, [1, 2]);
callbacks.at(-1)?.();
assert.equal(dispatched.length, 0);
controller.queueMessage({
message: { message_id: 3, chat: { id: 7 }, media_group_id: "album" },
dispatchMessages: (messages) =>
dispatched.push(messages.map((message) => message.message_id)),
});
controller.clear();
assert.deepEqual(cleared, [1, 2, 3]);
});
test("Media group suspension preserves admitted messages for replacement context", async () => {
const callbacks: Array<() => void> = [];
const cleared: number[] = [];
const dispatched: Array<{ ids: number[]; ctx?: string }> = [];
let nextTimer = 1;
const controller = createTelegramMediaGroupController<
{ message_id: number; chat: { id: number }; media_group_id?: string },
string
>({
debounceMs: 100,
setTimer: (callback) => {
callbacks.push(callback);
return createTestTimer(nextTimer++);
},
clearTimer: (timer) => cleared.push(getTestTimerId(timer)),
});
controller.queueMessage({
message: { message_id: 1, chat: { id: 7 }, media_group_id: "album" },
context: "old-session",
dispatchMessages: (messages, ctx) =>
dispatched.push({
ids: messages.map((message) => message.message_id),
ctx,
}),
});
controller.suspend();
controller.resume("new-session");
callbacks[1]?.();
await Promise.resolve();
assert.deepEqual(cleared, [1]);
assert.deepEqual(dispatched, [{ ids: [1], ctx: "new-session" }]);
});
test("Media group dispatch runtime handles immediate and grouped messages", async () => {
const callbacks: Array<() => void> = [];
const dispatched: Array<{ ids: number[]; ctx: string }> = [];
const controller = createTelegramMediaGroupController<
{
message_id: number;
chat: { id: number };
media_group_id?: string;
},
string
>({
setTimer: (callback) => {
callbacks.push(callback);
return createTestTimer(callbacks.length);
},
clearTimer: () => {},
});
const runtime = createTelegramMediaGroupDispatchRuntime({
mediaGroups: controller,
dispatchMessages: async (messages, ctx: string) => {
dispatched.push({
ids: messages.map((message) => message.message_id),
ctx,
});
},
});
await runtime.handleMessage({ message_id: 1, chat: { id: 7 } }, "ctx-a");
await runtime.handleMessage(
{ message_id: 2, chat: { id: 7 }, media_group_id: "album" },
"ctx-b",
);
await runtime.handleMessage(
{ message_id: 3, chat: { id: 7 }, media_group_id: "album" },
"ctx-b",
);
callbacks.at(-1)?.();
assert.deepEqual(dispatched, [
{ ids: [1], ctx: "ctx-a" },
{ ids: [2, 3], ctx: "ctx-b" },
]);
});
test("Media helpers remove pending groups by message id", () => {
const groups = new Map<
string,
TelegramMediaGroupState<{ message_id: number; chat: { id: number } }>
>();
groups.set("7:album", {
messages: [
{ message_id: 1, chat: { id: 7 } },
{ message_id: 2, chat: { id: 7 } },
],
flushTimer: createTestTimer(10),
});
const cleared: number[] = [];
assert.equal(
removePendingTelegramMediaGroupMessages(groups, [2], (timer) => {
cleared.push(getTestTimerId(timer));
}),
1,
);
assert.deepEqual(cleared, [10]);
assert.equal(groups.size, 0);
});