From 2e1fb49d9a2d8580abcadc49cd4ec47c1b010942 Mon Sep 17 00:00:00 2001 From: Taz Date: Thu, 4 Sep 2025 13:32:58 +0200 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B0=20=D0=BF=D0=B5=D1=80=D0=B5=D1=81=D1=8B=D0=BB=D0=BA?= =?UTF-8?q?=D0=B0=20=D0=BC=D0=B5=D0=B4=D0=B8=D0=B0=20=D1=84=D0=B0=D0=B9?= =?UTF-8?q?=D0=BB=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- discord_listener.go | 12 +- main.go | 253 ++++++++++++++++++++++--------------------- telegram_listener.go | 61 ++++++++++- vk_listener.go | 7 +- 4 files changed, 203 insertions(+), 130 deletions(-) diff --git a/discord_listener.go b/discord_listener.go index a0cc192..d07de95 100644 --- a/discord_listener.go +++ b/discord_listener.go @@ -2,16 +2,24 @@ package main import ( "log" + "github.com/bwmarrin/discordgo" ) -func (b *Bridge) StartDiscordListener(forward func(text string, from string)) { +func (b *Bridge) StartDiscordListener(forward func(text string, sender string, media []string)) { b.Discord.AddHandler(func(s *discordgo.Session, m *discordgo.MessageCreate) { if m.Author.Bot { return } + // Собираем URL вложений + media := make([]string, 0) + for _, a := range m.Attachments { + if a.URL != "" { + media = append(media, a.URL) + } + } log.Printf("[Discord] %s: %s", m.Author.Username, m.Content) - forward(m.Content, m.Author.Username) + forward(m.Content, m.Author.Username, media) }) _ = b.Discord.Open() } diff --git a/main.go b/main.go index b23e2de..741981c 100644 --- a/main.go +++ b/main.go @@ -1,142 +1,151 @@ package main import ( + "encoding/json" "log" + "net/http" "os" "strconv" - "net/http" - "encoding/json" + "strings" ) func main() { - log.Println("Go Chat Bridge запущен.") + log.Println("Go Chat Bridge запущен.") - telegramToken := os.Getenv("TELEGRAM_TOKEN") - discordToken := os.Getenv("DISCORD_TOKEN") - vkToken := os.Getenv("VK_TOKEN") + telegramToken := os.Getenv("TELEGRAM_TOKEN") + discordToken := os.Getenv("DISCORD_TOKEN") + vkToken := os.Getenv("VK_TOKEN") - if telegramToken == "" || discordToken == "" || vkToken == "" { - log.Fatal("Необходимо задать переменные окружения TELEGRAM_TOKEN, DISCORD_TOKEN, VK_TOKEN") - } + if telegramToken == "" || discordToken == "" || vkToken == "" { + log.Fatal("Необходимо задать переменные окружения TELEGRAM_TOKEN, DISCORD_TOKEN, VK_TOKEN") + } - bridge, err := NewBridge(telegramToken, discordToken, vkToken) - if err != nil { - log.Fatalf("Ошибка инициализации Bridge: %v", err) - } + bridge, err := NewBridge(telegramToken, discordToken, vkToken) + if err != nil { + log.Fatalf("Ошибка инициализации Bridge: %v", err) + } - log.Println("Bridge успешно инициализирован:", bridge) + log.Println("Bridge успешно инициализирован:", bridge) - // Получаем ID чатов/каналов из переменных окружения - telegramChatID := os.Getenv("TELEGRAM_CHAT_ID") // int64 - discordChannelID := os.Getenv("DISCORD_CHANNEL_ID") // string - vkPeerID := os.Getenv("VK_PEER_ID") // int + // Получаем ID чатов/каналов из переменных окружения + telegramChatID := os.Getenv("TELEGRAM_CHAT_ID") // int64 + discordChannelID := os.Getenv("DISCORD_CHANNEL_ID") // string + vkPeerID := os.Getenv("VK_PEER_ID") // int - forward := func(text string, from string, sender string) { - log.Printf("[Bridge] Пересылка сообщения от %s (%s): %s", from, sender, text) - - switch from { - case "telegram": - if discordChannelID != "" { - _ = bridge.SendToDiscord(discordChannelID, "[TG] "+sender+": "+text) - } - if vkPeerID != "" { - if id, err := strconv.Atoi(vkPeerID); err == nil { - _ = bridge.SendToVK(id, "[TG] "+sender+": "+text) - } - } - _ = bridge.SendToSite("[TG] " + sender + ": " + text) - case "discord": - if telegramChatID != "" { - if id, err := strconv.ParseInt(telegramChatID, 10, 64); err == nil { - _ = bridge.SendToTelegram(id, "[DS] "+sender+": "+text) - } - } - if vkPeerID != "" { - if id, err := strconv.Atoi(vkPeerID); err == nil { - _ = bridge.SendToVK(id, "[DS] "+sender+": "+text) - } - } - _ = bridge.SendToSite("[DS] " + sender + ": " + text) - case "vk": - if telegramChatID != "" { - if id, err := strconv.ParseInt(telegramChatID, 10, 64); err == nil { - _ = bridge.SendToTelegram(id, "[VK] "+sender+": "+text) - } - } - if discordChannelID != "" { - _ = bridge.SendToDiscord(discordChannelID, "[VK] "+sender+": "+text) - } - _ = bridge.SendToSite("[VK] " + sender + ": " + text) - case "site": - if telegramChatID != "" { - if id, err := strconv.ParseInt(telegramChatID, 10, 64); err == nil { - _ = bridge.SendToTelegram(id, "[SITE] "+sender+": "+text) - } - } - if discordChannelID != "" { - _ = bridge.SendToDiscord(discordChannelID, "[SITE] "+sender+": "+text) - } - if vkPeerID != "" { - if id, err := strconv.Atoi(vkPeerID); err == nil { - _ = bridge.SendToVK(id, "[SITE] "+sender+": "+text) - } - } - } + forward := func(text string, from string, sender string, media []string) { + // Собираем итоговый текст с медиа URL, если есть + msg := text + if len(media) > 0 { + if msg != "" { + msg += "\n" + } + msg += strings.Join(media, "\n") } + log.Printf("[Bridge] Пересылка сообщения от %s (%s): %s", from, sender, msg) - bridge.StartTelegramListener(func(text, sender string) { forward(text, "telegram", sender) }) - bridge.StartDiscordListener(func(text, sender string) { forward(text, "discord", sender) }) - bridge.StartVKListener(func(text, sender string) { forward(text, "vk", sender) }) + switch from { + case "telegram": + if discordChannelID != "" { + _ = bridge.SendToDiscord(discordChannelID, "[TG] "+sender+": "+msg) + } + if vkPeerID != "" { + if id, err := strconv.Atoi(vkPeerID); err == nil { + _ = bridge.SendToVK(id, "[TG] "+sender+": "+msg) + } + } + _ = bridge.SendToSite("[TG] " + sender + ": " + msg) + case "discord": + if telegramChatID != "" { + if id, err := strconv.ParseInt(telegramChatID, 10, 64); err == nil { + _ = bridge.SendToTelegram(id, "[DS] "+sender+": "+msg) + } + } + if vkPeerID != "" { + if id, err := strconv.Atoi(vkPeerID); err == nil { + _ = bridge.SendToVK(id, "[DS] "+sender+": "+msg) + } + } + _ = bridge.SendToSite("[DS] " + sender + ": " + msg) + case "vk": + if telegramChatID != "" { + if id, err := strconv.ParseInt(telegramChatID, 10, 64); err == nil { + _ = bridge.SendToTelegram(id, "[VK] "+sender+": "+msg) + } + } + if discordChannelID != "" { + _ = bridge.SendToDiscord(discordChannelID, "[VK] "+sender+": "+msg) + } + _ = bridge.SendToSite("[VK] " + sender + ": " + msg) + case "site": + if telegramChatID != "" { + if id, err := strconv.ParseInt(telegramChatID, 10, 64); err == nil { + _ = bridge.SendToTelegram(id, "[SITE] "+sender+": "+msg) + } + } + if discordChannelID != "" { + _ = bridge.SendToDiscord(discordChannelID, "[SITE] "+sender+": "+msg) + } + if vkPeerID != "" { + if id, err := strconv.Atoi(vkPeerID); err == nil { + _ = bridge.SendToVK(id, "[SITE] "+sender+": "+msg) + } + } + } + } - // webhook endpoint - http.HandleFunc("/webhook", func(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) - return - } - apiKey := os.Getenv("WEBHOOK_API_KEY") - if apiKey == "" { - http.Error(w, "Webhook API key not set", http.StatusInternalServerError) - return - } - key := r.URL.Query().Get("key") - if key == "" { - // также можно принимать ключ в теле запроса - var req struct { - Key string `json:"key"` - Text string `json:"text"` - Sender string `json:"sender"` - } - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - http.Error(w, "Bad request", http.StatusBadRequest) - return - } - if req.Key != apiKey { - http.Error(w, "Forbidden", http.StatusForbidden) - return - } - forward(req.Text, "site", req.Sender) - w.WriteHeader(http.StatusOK) - w.Write([]byte("ok")) - return - } - if key != apiKey { - http.Error(w, "Forbidden", http.StatusForbidden) - return - } - var req struct { - Text string `json:"text"` - Sender string `json:"sender"` - } - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - http.Error(w, "Bad request", http.StatusBadRequest) - return - } - forward(req.Text, "site", req.Sender) - w.WriteHeader(http.StatusOK) - w.Write([]byte("ok")) - }) + bridge.StartTelegramListener(func(text string, sender string, media []string) { forward(text, "telegram", sender, media) }) + bridge.StartDiscordListener(func(text string, sender string, media []string) { forward(text, "discord", sender, media) }) + bridge.StartVKListener(func(text string, sender string, media []string) { forward(text, "vk", sender, media) }) - log.Println("Webhook listening on :8080 (/webhook)") - log.Fatal(http.ListenAndServe(":8080", nil)) + // webhook endpoint + http.HandleFunc("/webhook", func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + apiKey := os.Getenv("WEBHOOK_API_KEY") + if apiKey == "" { + http.Error(w, "Webhook API key not set", http.StatusInternalServerError) + return + } + key := r.URL.Query().Get("key") + if key == "" { + // также можно принимать ключ в теле запроса + var req struct { + Key string `json:"key"` + Text string `json:"text"` + Sender string `json:"sender"` + } + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + http.Error(w, "Bad request", http.StatusBadRequest) + return + } + if req.Key != apiKey { + http.Error(w, "Forbidden", http.StatusForbidden) + return + } + forward(req.Text, "site", req.Sender, nil) + w.WriteHeader(http.StatusOK) + w.Write([]byte("ok")) + return + } + if key != apiKey { + http.Error(w, "Forbidden", http.StatusForbidden) + return + } + var req struct { + Text string `json:"text"` + Sender string `json:"sender"` + } + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + http.Error(w, "Bad request", http.StatusBadRequest) + return + } + forward(req.Text, "site", req.Sender, nil) + w.WriteHeader(http.StatusOK) + w.Write([]byte("ok")) + }) + + log.Println("Webhook listening on :8080 (/webhook)") + log.Fatal(http.ListenAndServe(":8080", nil)) } diff --git a/telegram_listener.go b/telegram_listener.go index d2343a2..fc9b38b 100644 --- a/telegram_listener.go +++ b/telegram_listener.go @@ -1,12 +1,13 @@ package main import ( + "fmt" "log" tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5" ) -func (b *Bridge) StartTelegramListener(forward func(text string, from string)) { +func (b *Bridge) StartTelegramListener(forward func(text string, sender string, media []string)) { u := tgbotapi.NewUpdate(0) u.Timeout = 60 @@ -22,9 +23,63 @@ func (b *Bridge) StartTelegramListener(forward func(text string, from string)) { if update.Message.From.UserName != "" { userInfo += " " + update.Message.From.UserName } + // Собираем медиа URL'ы + media := make([]string, 0) + + getFileURL := func(fileID string) (string, error) { + file, err := b.Telegram.GetFile(tgbotapi.FileConfig{FileID: fileID}) + if err != nil { + return "", err + } + return fmt.Sprintf("https://api.telegram.org/file/bot%s/%s", b.Telegram.Token, file.FilePath), nil + } + + // Фото (берём самое большое) + if len(update.Message.Photo) > 0 { + photo := update.Message.Photo[len(update.Message.Photo)-1] + if url, err := getFileURL(photo.FileID); err == nil { + media = append(media, url) + } + } + + // Document + if update.Message.Document != nil { + if url, err := getFileURL(update.Message.Document.FileID); err == nil { + media = append(media, url) + } + } + + // Video + if update.Message.Video != nil { + if url, err := getFileURL(update.Message.Video.FileID); err == nil { + media = append(media, url) + } + } + + // Audio + if update.Message.Audio != nil { + if url, err := getFileURL(update.Message.Audio.FileID); err == nil { + media = append(media, url) + } + } + + // Voice + if update.Message.Voice != nil { + if url, err := getFileURL(update.Message.Voice.FileID); err == nil { + media = append(media, url) + } + } + + // Animation (GIF) + if update.Message.Animation != nil { + if url, err := getFileURL(update.Message.Animation.FileID); err == nil { + media = append(media, url) + } + } + log.Printf("[Telegram] %s: %s", userInfo, update.Message.Text) - // Передаём имя отправителя - forward(update.Message.Text, userInfo) + // Передаём имя отправителя и медиа + forward(update.Message.Text, userInfo, media) } }() } diff --git a/vk_listener.go b/vk_listener.go index fac4eb4..5f4a67d 100644 --- a/vk_listener.go +++ b/vk_listener.go @@ -1,15 +1,16 @@ package main import ( + "context" "log" "os" "strconv" - "context" + "github.com/SevereCloud/vksdk/v2/events" "github.com/SevereCloud/vksdk/v2/longpoll-bot" ) -func (b *Bridge) StartVKListener(forward func(text string, from string)) { +func (b *Bridge) StartVKListener(forward func(text string, sender string, media []string)) { groupIDStr := os.Getenv("VK_GROUP_ID") if groupIDStr == "" { log.Println("VK_GROUP_ID не задана в переменных окружения") @@ -33,7 +34,7 @@ func (b *Bridge) StartVKListener(forward func(text string, from string)) { if name, err := b.GetVKUserName(obj.Message.FromID); err == nil { sender = name } - forward(obj.Message.Text, sender) + forward(obj.Message.Text, sender, nil) }) go lp.Run() }