feat(telegram): add typing cue
This commit is contained in:
@@ -1,10 +1,19 @@
|
|||||||
import { describe, expect, it, vi } from "vitest";
|
import { describe, expect, it, vi } from "vitest";
|
||||||
|
import * as replyModule from "../auto-reply/reply.js";
|
||||||
|
import { createTelegramBot } from "./bot.js";
|
||||||
|
|
||||||
const useSpy = vi.fn();
|
const useSpy = vi.fn();
|
||||||
const onSpy = vi.fn();
|
const onSpy = vi.fn();
|
||||||
const stopSpy = vi.fn();
|
const stopSpy = vi.fn();
|
||||||
type ApiStub = { config: { use: (arg: unknown) => void } };
|
const sendChatActionSpy = vi.fn();
|
||||||
const apiStub: ApiStub = { config: { use: useSpy } };
|
type ApiStub = {
|
||||||
|
config: { use: (arg: unknown) => void };
|
||||||
|
sendChatAction: typeof sendChatActionSpy;
|
||||||
|
};
|
||||||
|
const apiStub: ApiStub = {
|
||||||
|
config: { use: useSpy },
|
||||||
|
sendChatAction: sendChatActionSpy,
|
||||||
|
};
|
||||||
|
|
||||||
vi.mock("grammy", () => ({
|
vi.mock("grammy", () => ({
|
||||||
Bot: class {
|
Bot: class {
|
||||||
@@ -24,13 +33,13 @@ vi.mock("@grammyjs/transformer-throttler", () => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock("../auto-reply/reply.js", () => {
|
vi.mock("../auto-reply/reply.js", () => {
|
||||||
const replySpy = vi.fn();
|
const replySpy = vi.fn(async (_ctx, opts) => {
|
||||||
|
await opts?.onReplyStart?.();
|
||||||
|
return undefined;
|
||||||
|
});
|
||||||
return { getReplyFromConfig: replySpy, __replySpy: replySpy };
|
return { getReplyFromConfig: replySpy, __replySpy: replySpy };
|
||||||
});
|
});
|
||||||
|
|
||||||
import { createTelegramBot } from "./bot.js";
|
|
||||||
import * as replyModule from "../auto-reply/reply.js";
|
|
||||||
|
|
||||||
describe("createTelegramBot", () => {
|
describe("createTelegramBot", () => {
|
||||||
it("installs grammY throttler", () => {
|
it("installs grammY throttler", () => {
|
||||||
createTelegramBot({ token: "tok" });
|
createTelegramBot({ token: "tok" });
|
||||||
@@ -47,7 +56,9 @@ describe("createTelegramBot", () => {
|
|||||||
|
|
||||||
createTelegramBot({ token: "tok" });
|
createTelegramBot({ token: "tok" });
|
||||||
expect(onSpy).toHaveBeenCalledWith("message", expect.any(Function));
|
expect(onSpy).toHaveBeenCalledWith("message", expect.any(Function));
|
||||||
const handler = onSpy.mock.calls[0][1] as (ctx: any) => Promise<void>;
|
const handler = onSpy.mock.calls[0][1] as (
|
||||||
|
ctx: Record<string, unknown>,
|
||||||
|
) => Promise<void>;
|
||||||
|
|
||||||
const message = {
|
const message = {
|
||||||
chat: { id: 1234, type: "private" },
|
chat: { id: 1234, type: "private" },
|
||||||
@@ -72,4 +83,21 @@ describe("createTelegramBot", () => {
|
|||||||
);
|
);
|
||||||
expect(payload.Body).toContain("hello world");
|
expect(payload.Body).toContain("hello world");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("triggers typing cue via onReplyStart", async () => {
|
||||||
|
onSpy.mockReset();
|
||||||
|
sendChatActionSpy.mockReset();
|
||||||
|
|
||||||
|
createTelegramBot({ token: "tok" });
|
||||||
|
const handler = onSpy.mock.calls[0][1] as (
|
||||||
|
ctx: Record<string, unknown>,
|
||||||
|
) => Promise<void>;
|
||||||
|
await handler({
|
||||||
|
message: { chat: { id: 42, type: "private" }, text: "hi" },
|
||||||
|
me: { username: "clawdis_bot" },
|
||||||
|
getFile: async () => ({ download: async () => new Uint8Array() }),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(sendChatActionSpy).toHaveBeenCalledWith(42, "typing");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import { Buffer } from "node:buffer";
|
import { Buffer } from "node:buffer";
|
||||||
|
|
||||||
import { apiThrottler } from "@grammyjs/transformer-throttler";
|
import { apiThrottler } from "@grammyjs/transformer-throttler";
|
||||||
import type { ApiClientOptions, Message } from "grammy";
|
import type { ApiClientOptions, Message } from "grammy";
|
||||||
import { Bot, InputFile, webhookCallback } from "grammy";
|
import { Bot, InputFile, webhookCallback } from "grammy";
|
||||||
|
|
||||||
import { chunkText } from "../auto-reply/chunk.js";
|
import { chunkText } from "../auto-reply/chunk.js";
|
||||||
|
import { formatAgentEnvelope } from "../auto-reply/envelope.js";
|
||||||
import { getReplyFromConfig } from "../auto-reply/reply.js";
|
import { getReplyFromConfig } from "../auto-reply/reply.js";
|
||||||
import type { ReplyPayload } from "../auto-reply/types.js";
|
import type { ReplyPayload } from "../auto-reply/types.js";
|
||||||
import { formatAgentEnvelope } from "../auto-reply/envelope.js";
|
|
||||||
import { loadConfig } from "../config/config.js";
|
import { loadConfig } from "../config/config.js";
|
||||||
import { danger, logVerbose } from "../globals.js";
|
import { danger, logVerbose } from "../globals.js";
|
||||||
import { getChildLogger } from "../logging.js";
|
import { getChildLogger } from "../logging.js";
|
||||||
@@ -70,6 +71,16 @@ export function createTelegramBot(opts: TelegramBotOptions) {
|
|||||||
const isGroup =
|
const isGroup =
|
||||||
msg.chat.type === "group" || msg.chat.type === "supergroup";
|
msg.chat.type === "group" || msg.chat.type === "supergroup";
|
||||||
|
|
||||||
|
const sendTyping = async () => {
|
||||||
|
try {
|
||||||
|
await bot.api.sendChatAction(chatId, "typing");
|
||||||
|
} catch (err) {
|
||||||
|
logVerbose(
|
||||||
|
`telegram typing cue failed for chat ${chatId}: ${String(err)}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// allowFrom for direct chats
|
// allowFrom for direct chats
|
||||||
if (!isGroup && Array.isArray(allowFrom) && allowFrom.length > 0) {
|
if (!isGroup && Array.isArray(allowFrom) && allowFrom.length > 0) {
|
||||||
const candidate = String(chatId);
|
const candidate = String(chatId);
|
||||||
@@ -99,7 +110,12 @@ export function createTelegramBot(opts: TelegramBotOptions) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const media = await resolveMedia(ctx, mediaMaxBytes);
|
const media = await resolveMedia(ctx, mediaMaxBytes);
|
||||||
const rawBody = (msg.text ?? msg.caption ?? media?.placeholder ?? "").trim();
|
const rawBody = (
|
||||||
|
msg.text ??
|
||||||
|
msg.caption ??
|
||||||
|
media?.placeholder ??
|
||||||
|
""
|
||||||
|
).trim();
|
||||||
if (!rawBody) return;
|
if (!rawBody) return;
|
||||||
|
|
||||||
const body = formatAgentEnvelope({
|
const body = formatAgentEnvelope({
|
||||||
@@ -126,7 +142,11 @@ export function createTelegramBot(opts: TelegramBotOptions) {
|
|||||||
MediaUrl: media?.path,
|
MediaUrl: media?.path,
|
||||||
};
|
};
|
||||||
|
|
||||||
const replyResult = await getReplyFromConfig(ctxPayload, {}, cfg);
|
const replyResult = await getReplyFromConfig(
|
||||||
|
ctxPayload,
|
||||||
|
{ onReplyStart: sendTyping },
|
||||||
|
cfg,
|
||||||
|
);
|
||||||
const replies = replyResult
|
const replies = replyResult
|
||||||
? Array.isArray(replyResult)
|
? Array.isArray(replyResult)
|
||||||
? replyResult
|
? replyResult
|
||||||
|
|||||||
Reference in New Issue
Block a user