X-Git-Url: https://git.giacomolli.uk/onechat/blobdiff_plain/48d7bce28b18ecef4c52c98821675605464cecb9..f5c601f1801aaa276f8c335fcb51c373edc0b50c:/telegram.go diff --git a/telegram.go b/telegram.go index f3b3bb2..4ed0861 100644 --- a/telegram.go +++ b/telegram.go @@ -108,6 +108,7 @@ func loadTelegramConfig(dir string) (TelegramConfig, bool, error) { func newTelegramBot(api *TelegramAPI, owner int64, offsetPath string, logger *slog.Logger) *TelegramBot { return &TelegramBot{ api: api, + voice: &VoiceTranscriber{}, owner: owner, offsetPath: offsetPath, logger: logger, @@ -139,13 +140,13 @@ func (c TelegramConfig) validate() error { } // TelegramBot connects the private chat of the owner with the agent. It polls -// the Bot API for new messages and sends owner text and images to the agent. It -// ignores every other update. Its sender sends the replies, the errors, the -// notices of the bot, and the user messages of the other clients to the chat, -// and shows the typing action while a turn runs. Only the replies have a -// format: their Markdown goes as rich messages, which Telegram renders itself, -// and as message entities only when Telegram refuses a rich message. The rest -// is plain text. Tool calls do not go to the chat. +// the Bot API for new messages and sends owner text, images, and voice notes to +// the agent. It ignores every other update. Its sender sends the replies, the +// errors, the notices of the bot, and the user messages of the other clients to +// the chat, and shows the typing action while a turn runs. Only the replies +// have a format: their Markdown goes as rich messages, which Telegram renders +// itself, and as message entities only when Telegram refuses a rich message. +// The rest is plain text. Tool calls do not go to the chat. // // The offset file holds the ID of the next update. It moves past an update // only after the message of the update is durable in the log, so a restart @@ -158,6 +159,7 @@ func (c TelegramConfig) validate() error { // and the retries between them log nothing. type TelegramBot struct { api *TelegramAPI + voice *VoiceTranscriber owner int64 offsetPath string logger *slog.Logger @@ -399,8 +401,9 @@ func (b *TelegramBot) pause(ctx context.Context, outage *TelegramOutage, err err // /stop interrupts the turn, the line /stop NAME stops the subagent NAME, and a // line that starts with /whatsapp runs a WhatsApp command on whatsapp, whose // answers go to the chat only. Photos and image documents enter the shared -// image store with their caption. Other media keep the text-only answer. Every -// other update is ignored, and its content is not logged. +// image store with their caption. Voice notes keep their audio and local +// transcript. Other media keep the text-only answer. Every other update is +// ignored, and its content is not logged. func (b *TelegramBot) handle(ctx context.Context, l *TurnLoop, whatsapp *WhatsApp, update TelegramUpdate) error { message := update.Message if message == nil || message.Chat.Type != "private" || message.Chat.ID != b.owner || message.From == nil || message.From.ID != b.owner { @@ -412,6 +415,17 @@ func (b *TelegramBot) handle(ctx context.Context, l *TurnLoop, whatsapp *WhatsAp name, stop := stopSubagentName(message.Text) args, isWhatsApp := whatsappCommandArgs(message.Text) switch { + case message.Voice != nil: + text := telegramVoiceText(ctx, b.api, l.agent.Voices(), b.voice, message) + _, err := l.Submit(telegramSource, text) + if err != nil { + return fmt.Errorf("submit Telegram voice: %w", err) + } + + err = l.Flush(context.WithoutCancel(ctx)) + if err != nil { + return fmt.Errorf("flush Telegram voice: %w", err) + } case len(message.Photo) > 0 || message.Document != nil: text, err := telegramImageText(ctx, b.api, l.agent.Images(), message) if err != nil {