Skip to content

Commit 67c24ef

Browse files
feat(telegram): Introduce public and full bot command registration
Refactor Telegram command registration to separate public commands visible to all users from full commands for approved users. Implement a new function to register full commands per chat, ensuring that only approved users see the complete command set. Enhance error handling and logging for command registration processes.
1 parent 10d34b0 commit 67c24ef

File tree

1 file changed

+85
-10
lines changed

1 file changed

+85
-10
lines changed

src/services/TelegramService.ts

Lines changed: 85 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -78,19 +78,38 @@ function getTextFromRequest(request: LlmRequest): string {
7878
}
7979

8080
/**
81-
* Bot commands to register with Telegram
81+
* Public bot commands visible to all users (including unapproved)
8282
*/
83-
const BOT_COMMANDS = [
83+
const PUBLIC_BOT_COMMANDS = [
84+
{ command: "start", description: "Start the bot and pair" },
85+
{ command: "help", description: "Show available commands" },
86+
];
87+
88+
/**
89+
* Full bot commands visible only to approved/paired users
90+
*/
91+
const FULL_BOT_COMMANDS = [
8492
{ command: "start", description: "Start the bot and pair" },
8593
{ command: "new", description: "Save session and start fresh" },
8694
{ command: "reset", description: "Clear session without saving" },
8795
{ command: "help", description: "Show available commands" },
8896
];
8997

9098
/**
91-
* Register bot commands with Telegram API
99+
* Track chatIds that have had full commands registered
100+
* so we only register once per session per chat
92101
*/
93-
async function registerBotCommands(botToken: string): Promise<void> {
102+
const registeredChats = new Set<string>();
103+
104+
/**
105+
* Register bot commands with Telegram API at global scopes.
106+
* Called at startup with public commands so all users (including unapproved)
107+
* can see /start and /help in the command menu.
108+
*/
109+
async function registerBotCommands(
110+
botToken: string,
111+
commandList: typeof PUBLIC_BOT_COMMANDS = PUBLIC_BOT_COMMANDS,
112+
): Promise<void> {
94113
const api = `https://api.telegram.org/bot${botToken}`;
95114
const headers = { "Content-Type": "application/json" };
96115
const scopes = [
@@ -113,7 +132,7 @@ async function registerBotCommands(botToken: string): Promise<void> {
113132
const response = await fetch(`${api}/setMyCommands`, {
114133
method: "POST",
115134
headers,
116-
body: JSON.stringify({ commands: BOT_COMMANDS, scope }),
135+
body: JSON.stringify({ commands: commandList, scope }),
117136
});
118137
const result = (await response.json()) as {
119138
ok: boolean;
@@ -126,13 +145,57 @@ async function registerBotCommands(botToken: string): Promise<void> {
126145
}
127146
}
128147
log.info(
129-
`Registered ${BOT_COMMANDS.length} bot commands: ${BOT_COMMANDS.map((c) => `/${c.command}`).join(", ")}`,
148+
`Registered ${commandList.length} bot commands: ${commandList.map((c) => `/${c.command}`).join(", ")}`,
130149
);
131150
} catch (error) {
132151
log.warn(`Could not register bot commands: ${error}`);
133152
}
134153
}
135154

155+
/**
156+
* Register full bot commands for a specific chat (approved user).
157+
* Uses Telegram's per-chat scope so the user sees all commands
158+
* including /new and /reset in their command menu.
159+
*/
160+
async function registerUserCommands(
161+
botToken: string,
162+
chatId: string,
163+
): Promise<void> {
164+
const api = `https://api.telegram.org/bot${botToken}`;
165+
const headers = { "Content-Type": "application/json" };
166+
const scope = { type: "chat", chat_id: Number(chatId) };
167+
168+
try {
169+
// Clear existing per-chat commands first
170+
await fetch(`${api}/deleteMyCommands`, {
171+
method: "POST",
172+
headers,
173+
body: JSON.stringify({ scope }),
174+
});
175+
176+
// Set full command list for this specific chat
177+
const response = await fetch(`${api}/setMyCommands`, {
178+
method: "POST",
179+
headers,
180+
body: JSON.stringify({ commands: FULL_BOT_COMMANDS, scope }),
181+
});
182+
const result = (await response.json()) as {
183+
ok: boolean;
184+
description?: string;
185+
};
186+
if (!result.ok) {
187+
log.warn(
188+
`Failed to register user commands for chat ${chatId}: ${result.description}`,
189+
);
190+
} else {
191+
registeredChats.add(chatId);
192+
log.info(`Registered full commands for chat ${chatId}`);
193+
}
194+
} catch (error) {
195+
log.warn(`Could not register user commands for chat ${chatId}: ${error}`);
196+
}
197+
}
198+
136199
/**
137200
* Command handlers
138201
*/
@@ -259,17 +322,29 @@ export async function startTelegramBot(): Promise<void> {
259322
return getPairingResponse(userId, undefined, chatId);
260323
}
261324

325+
// Register full commands for newly approved users on first message
326+
if (!registeredChats.has(chatId)) {
327+
registerUserCommands(config.telegramBotToken, chatId);
328+
}
329+
262330
// Track in session
263331
sessionManager.getOrCreate(chatId, userId);
264332
sessionManager.addMessage(chatId, "user", messageText);
265333

266334
// Get response from agent
267-
const response = await runner.ask(messageText);
335+
try {
336+
const response = await runner.ask(messageText);
268337

269-
// Track response
270-
sessionManager.addMessage(chatId, "assistant", response);
338+
// Track response
339+
sessionManager.addMessage(chatId, "assistant", response);
271340

272-
return response;
341+
return response;
342+
} catch (error) {
343+
log.error(
344+
`Failed to get response for chatId=${chatId}: ${error instanceof Error ? error.message : error}`,
345+
);
346+
return "⚠️ Sorry, I'm having trouble right now. Please try again in a moment.";
347+
}
273348
},
274349
);
275350

0 commit comments

Comments
 (0)