feat(telegram): add silent message option (#2382)
* feat(telegram): add silent message option (disable_notification) Add support for sending Telegram messages silently without notification sound via the `silent` parameter on the message tool. Changes: - Add `silent` boolean to message tool schema - Extract and pass `silent` through telegram plugin - Add `disable_notification: true` to Telegram API calls - Add `--silent` flag to CLI `message send` command - Add unit test for silent flag Closes #2249 AI-assisted (Claude) - fully tested with unit tests + manual Telegram testing * feat(telegram): add silent send option (#2382) (thanks @Suksham-sharma) --------- Co-authored-by: Pocket Clawd <pocket@Pockets-Mac-mini.local>
This commit is contained in:
@@ -41,6 +41,7 @@ Status: unreleased.
|
||||
- Routing: precompile session key regexes. (#1697) Thanks @Ray0907.
|
||||
- TUI: avoid width overflow when rendering selection lists. (#1686) Thanks @mossein.
|
||||
- Telegram: keep topic IDs in restart sentinel notifications. (#1807) Thanks @hsrvc.
|
||||
- Telegram: add optional silent send flag (disable notifications). (#2382) Thanks @Suksham-sharma.
|
||||
- Config: apply config.env before ${VAR} substitution. (#1813) Thanks @spanishflu-est1918.
|
||||
- Slack: clear ack reaction after streamed replies. (#2044) Thanks @fancyboi999.
|
||||
- macOS: keep custom SSH usernames in remote target. (#2046) Thanks @algal.
|
||||
|
||||
@@ -59,6 +59,7 @@ function buildSendSchema(options: { includeButtons: boolean; includeCards: boole
|
||||
replyTo: Type.Optional(Type.String()),
|
||||
threadId: Type.Optional(Type.String()),
|
||||
asVoice: Type.Optional(Type.Boolean()),
|
||||
silent: Type.Optional(Type.Boolean()),
|
||||
bestEffort: Type.Optional(Type.Boolean()),
|
||||
gifPlayback: Type.Optional(Type.Boolean()),
|
||||
buttons: Type.Optional(
|
||||
|
||||
@@ -176,6 +176,7 @@ export async function handleTelegramAction(
|
||||
replyToMessageId: replyToMessageId ?? undefined,
|
||||
messageThreadId: messageThreadId ?? undefined,
|
||||
asVoice: typeof params.asVoice === "boolean" ? params.asVoice : undefined,
|
||||
silent: typeof params.silent === "boolean" ? params.silent : undefined,
|
||||
});
|
||||
return jsonResult({
|
||||
ok: true,
|
||||
|
||||
@@ -36,4 +36,30 @@ describe("telegramMessageActions", () => {
|
||||
cfg,
|
||||
);
|
||||
});
|
||||
|
||||
it("passes silent flag for silent sends", async () => {
|
||||
handleTelegramAction.mockClear();
|
||||
const cfg = { channels: { telegram: { botToken: "tok" } } } as ClawdbotConfig;
|
||||
|
||||
await telegramMessageActions.handleAction({
|
||||
action: "send",
|
||||
params: {
|
||||
to: "456",
|
||||
message: "Silent notification test",
|
||||
silent: true,
|
||||
},
|
||||
cfg,
|
||||
accountId: undefined,
|
||||
});
|
||||
|
||||
expect(handleTelegramAction).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
action: "sendMessage",
|
||||
to: "456",
|
||||
content: "Silent notification test",
|
||||
silent: true,
|
||||
}),
|
||||
cfg,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -20,6 +20,7 @@ function readTelegramSendParams(params: Record<string, unknown>) {
|
||||
const threadId = readStringParam(params, "threadId");
|
||||
const buttons = params.buttons;
|
||||
const asVoice = typeof params.asVoice === "boolean" ? params.asVoice : undefined;
|
||||
const silent = typeof params.silent === "boolean" ? params.silent : undefined;
|
||||
return {
|
||||
to,
|
||||
content,
|
||||
@@ -28,6 +29,7 @@ function readTelegramSendParams(params: Record<string, unknown>) {
|
||||
messageThreadId: threadId ?? undefined,
|
||||
buttons,
|
||||
asVoice,
|
||||
silent,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,8 @@ export function registerMessageSendCommand(message: Command, helpers: MessageCli
|
||||
.option("--card <json>", "Adaptive Card JSON object (when supported by the channel)")
|
||||
.option("--reply-to <id>", "Reply-to message id")
|
||||
.option("--thread-id <id>", "Thread id (Telegram forum thread)")
|
||||
.option("--gif-playback", "Treat video media as GIF playback (WhatsApp only).", false),
|
||||
.option("--gif-playback", "Treat video media as GIF playback (WhatsApp only).", false)
|
||||
.option("--silent", "Send message silently without notification (Telegram only)", false),
|
||||
)
|
||||
.action(async (opts) => {
|
||||
await helpers.runMessageAction("send", opts);
|
||||
|
||||
@@ -159,7 +159,8 @@ export const ToolPolicySchema = ToolPolicyBaseSchema.superRefine((value, ctx) =>
|
||||
if (value.allow && value.allow.length > 0 && value.alsoAllow && value.alsoAllow.length > 0) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "tools policy cannot set both allow and alsoAllow in the same scope (merge alsoAllow into allow, or remove allow and use profile + alsoAllow)",
|
||||
message:
|
||||
"tools policy cannot set both allow and alsoAllow in the same scope (merge alsoAllow into allow, or remove allow and use profile + alsoAllow)",
|
||||
});
|
||||
}
|
||||
}).optional();
|
||||
|
||||
@@ -476,6 +476,28 @@ describe("sendMessageTelegram", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("sets disable_notification when silent is true", async () => {
|
||||
const chatId = "123";
|
||||
const sendMessage = vi.fn().mockResolvedValue({
|
||||
message_id: 1,
|
||||
chat: { id: chatId },
|
||||
});
|
||||
const api = { sendMessage } as unknown as {
|
||||
sendMessage: typeof sendMessage;
|
||||
};
|
||||
|
||||
await sendMessageTelegram(chatId, "hi", {
|
||||
token: "tok",
|
||||
api,
|
||||
silent: true,
|
||||
});
|
||||
|
||||
expect(sendMessage).toHaveBeenCalledWith(chatId, "hi", {
|
||||
parse_mode: "HTML",
|
||||
disable_notification: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("parses message_thread_id from recipient string (telegram:group:...:topic:...)", async () => {
|
||||
const chatId = "-1001234567890";
|
||||
const sendMessage = vi.fn().mockResolvedValue({
|
||||
|
||||
@@ -40,6 +40,8 @@ type TelegramSendOpts = {
|
||||
plainText?: string;
|
||||
/** Send audio as voice message (voice bubble) instead of audio file. Defaults to false. */
|
||||
asVoice?: boolean;
|
||||
/** Send message silently (no notification). Defaults to false. */
|
||||
silent?: boolean;
|
||||
/** Message ID to reply to (for threading) */
|
||||
replyToMessageId?: number;
|
||||
/** Forum topic thread ID (for forum supergroups) */
|
||||
@@ -245,6 +247,7 @@ export async function sendMessageTelegram(
|
||||
const sendParams = {
|
||||
parse_mode: "HTML" as const,
|
||||
...baseParams,
|
||||
...(opts.silent === true ? { disable_notification: true } : {}),
|
||||
};
|
||||
const res = await requestWithDiag(
|
||||
() => api.sendMessage(chatId, htmlText, sendParams),
|
||||
@@ -298,6 +301,7 @@ export async function sendMessageTelegram(
|
||||
caption: htmlCaption,
|
||||
...(htmlCaption ? { parse_mode: "HTML" as const } : {}),
|
||||
...baseMediaParams,
|
||||
...(opts.silent === true ? { disable_notification: true } : {}),
|
||||
};
|
||||
let result:
|
||||
| Awaited<ReturnType<typeof api.sendPhoto>>
|
||||
|
||||
Reference in New Issue
Block a user