package main import ( "io" "mime" "net/http" "net/url" "os" "path" "strings" "github.com/bwmarrin/discordgo" tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5" ) // Структура для хранения клиентов type Bridge struct { Telegram *tgbotapi.BotAPI Discord *discordgo.Session } func NewBridge(telegramToken, discordToken string) (*Bridge, error) { // Telegram tgBot, err := tgbotapi.NewBotAPI(telegramToken) if err != nil { return nil, err } // Discord dg, err := discordgo.New("Bot " + discordToken) if err != nil { return nil, err } return &Bridge{ Telegram: tgBot, Discord: dg, }, nil } // Отправка сообщения в Telegram func (b *Bridge) SendToTelegram(chatID int64, text string) error { msg := tgbotapi.NewMessage(chatID, text) _, err := b.Telegram.Send(msg) return err } // Отправка сообщения в Discord func (b *Bridge) SendToDiscord(channelID, text string) error { _, err := b.Discord.ChannelMessageSend(channelID, text) return err } // Загруженный файл на диск для пересылки как вложение type DownloadedFile struct { FileName string TempPath string Mime string } // DownloadFiles скачивает файлы по URL и сохраняет во временные файлы func (b *Bridge) DownloadFiles(urls []string) ([]DownloadedFile, error) { results := make([]DownloadedFile, 0, len(urls)) for _, u := range urls { resp, err := http.Get(u) if err != nil { return results, err } if resp.Body != nil { defer resp.Body.Close() } parsed, err := url.Parse(u) if err != nil { return results, err } name := path.Base(parsed.Path) if name == "" || name == "/" || name == "." { name = "file" } ext := path.Ext(name) pattern := "bridge_*" + ext tmpFile, err := os.CreateTemp("", pattern) if err != nil { return results, err } _, copyErr := io.Copy(tmpFile, resp.Body) closeErr := tmpFile.Close() if copyErr != nil { _ = os.Remove(tmpFile.Name()) return results, copyErr } if closeErr != nil { _ = os.Remove(tmpFile.Name()) return results, closeErr } // Try to determine mime: header -> by extension -> sniff file contentType := resp.Header.Get("Content-Type") if contentType == "" || contentType == "application/octet-stream" { if m := mime.TypeByExtension(ext); m != "" { contentType = m } } if contentType == "" || contentType == "application/octet-stream" { if f2, err := os.Open(tmpFile.Name()); err == nil { buf := make([]byte, 512) n, _ := f2.Read(buf) _ = f2.Close() if n > 0 { contentType = http.DetectContentType(buf[:n]) } } } results = append(results, DownloadedFile{FileName: name, TempPath: tmpFile.Name(), Mime: contentType}) } return results, nil } // CleanupFiles удаляет временные файлы func (b *Bridge) CleanupFiles(files []DownloadedFile) { for _, f := range files { _ = os.Remove(f.TempPath) } } // SendFilesToDiscord отправляет один или несколько файлов в Discord канал func (b *Bridge) SendFilesToDiscord(channelID string, files []DownloadedFile, content string) error { dgFiles := make([]*discordgo.File, 0, len(files)) openFiles := make([]*os.File, 0, len(files)) for _, f := range files { fh, err := os.Open(f.TempPath) if err != nil { for _, of := range openFiles { _ = of.Close() } return err } openFiles = append(openFiles, fh) dgFiles = append(dgFiles, &discordgo.File{Name: f.FileName, Reader: fh}) } _, err := b.Discord.ChannelMessageSendComplex(channelID, &discordgo.MessageSend{Content: content, Files: dgFiles}) for _, of := range openFiles { _ = of.Close() } return err } // SendFilesToTelegram отправляет файлы как документы, caption добавляется только к первому func (b *Bridge) SendFilesToTelegram(chatID int64, files []DownloadedFile, caption string) error { for i, f := range files { fh, err := os.Open(f.TempPath) if err != nil { return err } var sendErr error if strings.HasPrefix(f.Mime, "image/") { media := tgbotapi.NewPhoto(chatID, tgbotapi.FileReader{Name: f.FileName, Reader: fh}) if i == 0 && caption != "" { media.Caption = caption } _, sendErr = b.Telegram.Send(media) } else if strings.HasPrefix(f.Mime, "video/") { media := tgbotapi.NewVideo(chatID, tgbotapi.FileReader{Name: f.FileName, Reader: fh}) if i == 0 && caption != "" { media.Caption = caption } _, sendErr = b.Telegram.Send(media) } else { media := tgbotapi.NewDocument(chatID, tgbotapi.FileReader{Name: f.FileName, Reader: fh}) if i == 0 && caption != "" { media.Caption = caption } _, sendErr = b.Telegram.Send(media) } closeErr := fh.Close() if sendErr != nil { return sendErr } if closeErr != nil { return closeErr } } return nil }