max-telegram-bridge-bot/tgsender.go
Andrey Lugovskoy f8e4ff0ebd Migrate TG library to go-telegram/bot with forum topic support
Replace go-telegram-bot-api/v5 (2021, no forum topics) with
go-telegram/bot v1.20.0 (Bot API 9.5) via TGSender adapter pattern.

- New TGSender interface + tgBotSender implementation isolating library
- Native message_thread_id support: saved at /bridge, used in MAX→TG
  sends, echoed in all command responses, looked up in queue retries
- All 16 files migrated, zero old library references remain
- Proper error wrapping: MigrateError, Forbidden, BadRequest, NotFound,
  TooManyRequests → TGError with codes
- ForwardFromChat → ForwardOriginChat (Bot API MessageOrigin pattern)
- Repository: GetTgThreadID/SetTgThreadID (migration 000012 already applied)
- Tests for convertMsg, convertCallback, wrapErr, toInputFile,
  toLibInputMedia, tgEntitiesToMarkdown, maxMarkupsToHTML

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 17:48:33 +03:00

198 lines
4.6 KiB
Go

package main
import (
"context"
"fmt"
)
// --- Custom types for TG adapter ---
type ChatInfo struct {
ID int64
Type string
Title string
}
type UserInfo struct {
ID int64
IsBot bool
UserName string
FirstName string
LastName string
}
type PhotoSize struct {
FileID string
FileSize int
}
type FileInfo struct {
FileID string
FileName string
FileSize int
}
type DocInfo struct {
FileID string
FileName string
FileSize int
MimeType string
}
type AudioInfo struct {
FileID string
FileName string
FileSize int
}
type StickerInfo struct {
FileID string
FileSize int
IsAnimated bool
}
type Entity struct {
Type string
Offset int
Length int
URL string
}
type TGMessage struct {
MessageID int
MessageThreadID int
Chat ChatInfo
From *UserInfo
SenderChat *ChatInfo
Text string
Caption string
Photo []PhotoSize
Video *FileInfo
Document *DocInfo
Animation *FileInfo
Sticker *StickerInfo
Voice *FileInfo
Audio *AudioInfo
VideoNote *FileInfo
MediaGroupID string
ReplyToMessage *TGMessage
ForwardOriginChat *ChatInfo // replaces ForwardFromChat, from forward_origin
MigrateToChatID int64
Entities []Entity
CaptionEntities []Entity
}
type TGCallback struct {
ID string
From *UserInfo
Message *TGMessage
Data string
}
type TGUpdate struct {
Message *TGMessage
EditedMessage *TGMessage
ChannelPost *TGMessage
EditedChannelPost *TGMessage
CallbackQuery *TGCallback
}
// SendOpts — optional parameters for send methods.
type SendOpts struct {
ThreadID int
ReplyToID int
ParseMode string
Caption string
ReplyMarkup *InlineKeyboardMarkup
}
type InlineKeyboardMarkup struct {
Rows [][]InlineKeyboardButton
}
type InlineKeyboardButton struct {
Text string
CallbackData string
}
// FileArg — source for file upload: either Bytes (upload) or URL (send from URL).
type FileArg struct {
Name string
Bytes []byte
URL string
}
// TGInputMedia — item for media groups and edit-media.
type TGInputMedia struct {
Type string // "photo", "video", "audio", "document"
File FileArg
Caption string
ParseMode string
}
type BotCommand struct {
Command string
Description string
}
type CommandScope struct {
Type string // "", "all_chat_administrators"
}
// TGError represents a Telegram API error.
type TGError struct {
Code int
Description string
MigrateToChatID int64
}
func (e *TGError) Error() string {
return fmt.Sprintf("telegram: %s (%d)", e.Description, e.Code)
}
// --- Keyboard helpers ---
func NewInlineKeyboard(rows ...[]InlineKeyboardButton) *InlineKeyboardMarkup {
return &InlineKeyboardMarkup{Rows: rows}
}
func NewInlineRow(buttons ...InlineKeyboardButton) []InlineKeyboardButton {
return buttons
}
func NewInlineButton(text, data string) InlineKeyboardButton {
return InlineKeyboardButton{Text: text, CallbackData: data}
}
// --- Interface ---
// TGSender abstracts Telegram Bot API. All TG calls go through this interface.
type TGSender interface {
// Send methods return message ID.
SendMessage(ctx context.Context, chatID int64, text string, opts *SendOpts) (int, error)
SendPhoto(ctx context.Context, chatID int64, file FileArg, opts *SendOpts) (int, error)
SendVideo(ctx context.Context, chatID int64, file FileArg, opts *SendOpts) (int, error)
SendAudio(ctx context.Context, chatID int64, file FileArg, opts *SendOpts) (int, error)
SendDocument(ctx context.Context, chatID int64, file FileArg, opts *SendOpts) (int, error)
SendMediaGroup(ctx context.Context, chatID int64, media []TGInputMedia, opts *SendOpts) ([]int, error)
EditMessageText(ctx context.Context, chatID int64, msgID int, text string, opts *SendOpts) error
EditMessageMedia(ctx context.Context, chatID int64, msgID int, media TGInputMedia) error
DeleteMessage(ctx context.Context, chatID int64, msgID int) error
AnswerCallback(ctx context.Context, callbackID string, text string) error
GetFile(ctx context.Context, fileID string) (filePath string, err error)
GetFileDirectURL(filePath string) string
GetChatMember(ctx context.Context, chatID, userID int64) (status string, err error)
SetMyCommands(ctx context.Context, commands []BotCommand, scope *CommandScope) error
GetChat(ctx context.Context, chatID int64) (title string, err error)
SetWebhook(ctx context.Context, url string) error
DeleteWebhook(ctx context.Context) error
StartWebhook(path string) <-chan TGUpdate
StartPolling(ctx context.Context) <-chan TGUpdate
BotUsername() string
BotToken() string
}