feat: add OpenAI OAuth, strict model overrides, and Gmail pull mode
This commit is contained in:
@@ -228,6 +228,35 @@ describe('TelegramAdapter', () => {
|
||||
expect(msg.metadata).toEqual({ isCommand: true, command: 'reset' });
|
||||
});
|
||||
|
||||
it('/model command strips @bot suffix in groups', async () => {
|
||||
const handler = vi.fn();
|
||||
adapter.onMessage(handler);
|
||||
|
||||
await adapter.connect();
|
||||
|
||||
// Find the /model command handler
|
||||
const modelCall = mockCommand.mock.calls.find((call) => call[0] === 'model');
|
||||
expect(modelCall).toBeDefined();
|
||||
const modelHandler = modelCall![1];
|
||||
|
||||
const ctx = {
|
||||
message: { message_id: 123, text: '/model@flynn_bot default github/gpt-5-mini' },
|
||||
chat: { id: 100 },
|
||||
from: { first_name: 'Will' },
|
||||
};
|
||||
|
||||
await modelHandler(ctx);
|
||||
|
||||
expect(handler).toHaveBeenCalledTimes(1);
|
||||
const msg: InboundMessage = handler.mock.calls[0][0];
|
||||
expect(msg.text).toBe('/model default github/gpt-5-mini');
|
||||
expect(msg.metadata).toEqual({
|
||||
isCommand: true,
|
||||
command: 'model',
|
||||
commandArgs: 'default github/gpt-5-mini',
|
||||
});
|
||||
});
|
||||
|
||||
// ── Auth middleware ───────────────────────────────────────────
|
||||
|
||||
it('auth middleware blocks unauthorized chat IDs', async () => {
|
||||
|
||||
@@ -166,7 +166,9 @@ export class TelegramAdapter implements ChannelAdapter {
|
||||
this.bot.command('model', async (ctx) => {
|
||||
if (!this.messageHandler) {return;}
|
||||
|
||||
const args = ctx.message?.text?.replace(/^\/model\s*/, '').trim() ?? '';
|
||||
// Telegram can deliver group commands in the form: /model@bot_username ...
|
||||
// Strip the optional @mention so args parsing is consistent across DMs/groups.
|
||||
const args = ctx.message?.text?.replace(/^\/model(?:@\S+)?\s*/i, '').trim() ?? '';
|
||||
|
||||
this.messageHandler({
|
||||
id: String(ctx.message?.message_id ?? Date.now()),
|
||||
@@ -439,15 +441,48 @@ export class TelegramAdapter implements ChannelAdapter {
|
||||
if (!this.bot) {throw new Error('Telegram adapter not connected');}
|
||||
|
||||
const chatId = Number(peerId);
|
||||
const text = message.text;
|
||||
const text = message.text ?? '';
|
||||
|
||||
// Telegram rejects empty text messages.
|
||||
// If there is no text, skip straight to attachments.
|
||||
if (!text.trim()) {
|
||||
if (message.attachments && message.attachments.length > 0) {
|
||||
for (const attachment of message.attachments) {
|
||||
await this.sendAttachment(chatId, attachment);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const sendChunk = async (chunk: string): Promise<void> => {
|
||||
// We default to Markdown for nicer formatting, but Telegram's Markdown parsing
|
||||
// is strict and can fail on unescaped characters. If Telegram rejects the
|
||||
// message, retry once without parse_mode so users still get the content.
|
||||
try {
|
||||
await this.bot!.api.sendMessage(chatId, chunk, { parse_mode: 'Markdown' });
|
||||
} catch (error) {
|
||||
const description = error && typeof error === 'object' && 'description' in error
|
||||
? String((error as { description?: unknown }).description)
|
||||
: '';
|
||||
|
||||
const isParseError = description.includes("can't parse entities")
|
||||
|| description.includes('message text is empty');
|
||||
|
||||
if (!isParseError) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
await this.bot!.api.sendMessage(chatId, chunk);
|
||||
}
|
||||
};
|
||||
|
||||
// Telegram enforces a 4096-character limit per message
|
||||
if (text.length <= 4096) {
|
||||
await this.bot.api.sendMessage(chatId, text, { parse_mode: 'Markdown' });
|
||||
await sendChunk(text);
|
||||
} else {
|
||||
const chunks = splitMessage(text, 4096);
|
||||
for (const chunk of chunks) {
|
||||
await this.bot.api.sendMessage(chatId, chunk, { parse_mode: 'Markdown' });
|
||||
await sendChunk(chunk);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user