yao/integrations/telegram/message.go
Max 09af247a7c feat(robot): add Weixin integration and enhance existing adapters
- Introduced Weixin integration support with new configuration options for WeChat iLink Bot.
- Updated existing adapters (DingTalk, Discord, Feishu, Telegram) to include sender_id and app_id in message metadata for improved context handling.
- Enhanced dispatcher logic to accommodate the new Weixin adapter and ensure proper initialization and shutdown processes.
- Improved message handling across integrations to support typing indicators, providing a more interactive user experience.
2026-03-23 23:47:25 +08:00

213 lines
6.1 KiB
Go

package telegram
import (
"context"
"fmt"
"io"
"strings"
"github.com/go-telegram/bot"
"github.com/go-telegram/bot/models"
"github.com/yaoapp/yao/attachment"
)
// SendTyping sends a "typing" chat action to indicate the bot is preparing a response.
func (b *Bot) SendTyping(ctx context.Context, chatID int64) error {
sdk, err := b.sdk()
if err != nil {
return err
}
_, err = sdk.SendChatAction(ctx, &bot.SendChatActionParams{
ChatID: chatID,
Action: models.ChatActionTyping,
})
return err
}
// SendMessage sends a message to a chat. If the text contains Markdown formatting,
// it is automatically converted to Telegram-compatible HTML.
func (b *Bot) SendMessage(ctx context.Context, chatID int64, text string, replyTo int64) error {
sdk, err := b.sdk()
if err != nil {
return err
}
formatted := FormatTelegramHTML(text)
params := &bot.SendMessageParams{
ChatID: chatID,
Text: formatted,
ParseMode: models.ParseModeHTML,
}
if replyTo > 0 {
params.ReplyParameters = &models.ReplyParameters{MessageID: int(replyTo)}
}
_, err = sdk.SendMessage(ctx, params)
return err
}
// MediaType indicates which Telegram send method to use.
type MediaType string
const (
MediaPhoto MediaType = "photo"
MediaDocument MediaType = "document"
MediaAudio MediaType = "audio"
MediaVideo MediaType = "video"
MediaVoice MediaType = "voice"
MediaAnimation MediaType = "animation"
MediaSticker MediaType = "sticker"
)
// SendMedia sends a media message from a Yao attachment wrapper
// (e.g. "__yao.attachment://ccd472d11feb96e03a3fc468f494045c").
// It reads the file from the attachment manager, detects the media type from
// the stored content type, and uploads it to the Telegram chat.
func (b *Bot) SendMedia(ctx context.Context, chatID int64, wrapper string, caption string, replyTo int64) error {
managerName, fileID, err := parseWrapper(wrapper)
if err != nil {
return err
}
manager, exists := attachment.Managers[managerName]
if !exists {
return fmt.Errorf("attachment manager %s not found", managerName)
}
resp, err := manager.Download(ctx, fileID)
if err != nil {
return fmt.Errorf("attachment download %s: %w", fileID, err)
}
defer resp.Reader.Close()
mediaType := DetectMediaType(resp.ContentType)
filename := fileID + resp.Extension
file := &models.InputFileUpload{Filename: filename, Data: resp.Reader}
return b.sendMedia(ctx, chatID, mediaType, file, caption, replyTo)
}
// SendMediaByURL sends a media message from a public URL.
// Telegram downloads the file directly from the URL.
func (b *Bot) SendMediaByURL(ctx context.Context, chatID int64, mediaType MediaType, url string, caption string, replyTo int64) error {
file := &models.InputFileString{Data: url}
return b.sendMedia(ctx, chatID, mediaType, file, caption, replyTo)
}
// SendMediaByReader sends a media message by uploading raw bytes.
func (b *Bot) SendMediaByReader(ctx context.Context, chatID int64, mediaType MediaType, filename string, data io.Reader, caption string, replyTo int64) error {
file := &models.InputFileUpload{Filename: filename, Data: data}
return b.sendMedia(ctx, chatID, mediaType, file, caption, replyTo)
}
// DetectMediaType guesses the MediaType from a MIME string.
// Falls back to MediaDocument for unknown types.
func DetectMediaType(mimeType string) MediaType {
lower := strings.ToLower(mimeType)
switch {
case strings.HasPrefix(lower, "image/webp"):
return MediaSticker
case strings.HasPrefix(lower, "image/gif"):
return MediaAnimation
case strings.HasPrefix(lower, "image/"):
return MediaPhoto
case strings.HasPrefix(lower, "video/"):
return MediaVideo
case strings.HasPrefix(lower, "audio/ogg"):
return MediaVoice
case strings.HasPrefix(lower, "audio/"):
return MediaAudio
default:
return MediaDocument
}
}
// parseWrapper splits "__yao.attachment://fileID" into manager name and file ID.
func parseWrapper(wrapper string) (managerName string, fileID string, err error) {
idx := strings.Index(wrapper, "://")
if idx < 0 {
return "", "", fmt.Errorf("invalid attachment wrapper: %s", wrapper)
}
return wrapper[:idx], wrapper[idx+3:], nil
}
func (b *Bot) sendMedia(ctx context.Context, chatID int64, mediaType MediaType, file models.InputFile, caption string, replyTo int64) error {
sdk, err := b.sdk()
if err != nil {
return err
}
var replyParams *models.ReplyParameters
if replyTo > 0 {
replyParams = &models.ReplyParameters{MessageID: int(replyTo)}
}
htmlCaption := FormatTelegramHTML(caption)
switch mediaType {
case MediaPhoto:
_, err = sdk.SendPhoto(ctx, &bot.SendPhotoParams{
ChatID: chatID,
Photo: file,
Caption: htmlCaption,
ParseMode: models.ParseModeHTML,
ReplyParameters: replyParams,
})
case MediaDocument:
_, err = sdk.SendDocument(ctx, &bot.SendDocumentParams{
ChatID: chatID,
Document: file,
Caption: htmlCaption,
ParseMode: models.ParseModeHTML,
ReplyParameters: replyParams,
})
case MediaAudio:
_, err = sdk.SendAudio(ctx, &bot.SendAudioParams{
ChatID: chatID,
Audio: file,
Caption: htmlCaption,
ParseMode: models.ParseModeHTML,
ReplyParameters: replyParams,
})
case MediaVideo:
_, err = sdk.SendVideo(ctx, &bot.SendVideoParams{
ChatID: chatID,
Video: file,
Caption: htmlCaption,
ParseMode: models.ParseModeHTML,
ReplyParameters: replyParams,
})
case MediaVoice:
_, err = sdk.SendVoice(ctx, &bot.SendVoiceParams{
ChatID: chatID,
Voice: file,
Caption: htmlCaption,
ParseMode: models.ParseModeHTML,
ReplyParameters: replyParams,
})
case MediaAnimation:
_, err = sdk.SendAnimation(ctx, &bot.SendAnimationParams{
ChatID: chatID,
Animation: file,
Caption: htmlCaption,
ParseMode: models.ParseModeHTML,
ReplyParameters: replyParams,
})
case MediaSticker:
_, err = sdk.SendSticker(ctx, &bot.SendStickerParams{
ChatID: chatID,
Sticker: file,
ReplyParameters: replyParams,
})
default:
return fmt.Errorf("unsupported media type: %s", mediaType)
}
return err
}