package main import ( "log" "os" "strconv" "strings" "sync" ) type telegramMessageCacheItem struct { Text string Sender string Media []string } func main() { log.Println("Go Chat Bridge запущен.") telegramToken := os.Getenv("TELEGRAM_TOKEN") discordToken := os.Getenv("DISCORD_TOKEN") if telegramToken == "" || discordToken == "" { log.Fatal("Необходимо задать переменные окружения TELEGRAM_TOKEN, DISCORD_TOKEN") } bridge, err := NewBridge(telegramToken, discordToken) if err != nil { log.Fatalf("Ошибка инициализации Bridge: %v", err) } log.Println("Bridge успешно инициализирован:", bridge) // Получаем ID чатов/каналов из переменных окружения telegramChatID := strings.TrimSpace(os.Getenv("TELEGRAM_CHAT_ID")) // int64 discordChannelID := strings.TrimSpace(os.Getenv("DISCORD_CHANNEL_ID")) // string discordReactionChannelID := strings.TrimSpace(os.Getenv("DISCORD_REACTION_CHANNEL_ID")) // string telegramDuplicateReactionEmoji := strings.TrimSpace(os.Getenv("TELEGRAM_DUPLICATE_REACTION")) if telegramDuplicateReactionEmoji == "" { telegramDuplicateReactionEmoji = "✍️" } normalizedDuplicateReaction := normalizeEmoji(telegramDuplicateReactionEmoji) messageCache := make(map[string]telegramMessageCacheItem) var cacheMu sync.RWMutex cacheKey := func(chatID int64, messageID int) string { return strconv.FormatInt(chatID, 10) + ":" + strconv.Itoa(messageID) } sendTelegramMessageToDiscord := func(channelID, sender, text, msg string, media []string) error { if channelID == "" { return nil } if len(media) > 0 { files, derr := bridge.DownloadFiles(media) if derr == nil && len(files) > 0 { err := bridge.SendFilesToDiscord(channelID, files, "[TG] "+sender+": "+text) bridge.CleanupFiles(files) return err } } return bridge.SendToDiscord(channelID, "[TG] "+sender+": "+msg) } forward := func(text string, from string, sender string, media []string, messageID int, sourceChatID int64) { // Собираем итоговый текст с медиа 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) switch from { case "telegram": // Для Discord отправляем файлы, если есть, иначе текст if err := sendTelegramMessageToDiscord(discordChannelID, sender, text, msg, media); err != nil { log.Printf("[Bridge] failed to send telegram message to main Discord channel: %v", err) } cacheMu.Lock() messageCache[cacheKey(sourceChatID, messageID)] = telegramMessageCacheItem{ Text: text, Sender: sender, Media: append([]string(nil), media...), } cacheMu.Unlock() case "discord": // Для Telegram отправляем файлы, если есть, иначе текст if telegramChatID != "" { if id, err := strconv.ParseInt(telegramChatID, 10, 64); err == nil { if len(media) > 0 { files, derr := bridge.DownloadFiles(media) if derr == nil && len(files) > 0 { _ = bridge.SendFilesToTelegram(id, files, "[DS] "+sender+": "+text) bridge.CleanupFiles(files) } else { _ = bridge.SendToTelegram(id, "[DS] "+sender+": "+msg) } } else { _ = bridge.SendToTelegram(id, "[DS] "+sender+": "+msg) } } } } } bridge.StartTelegramListener( func(text string, sender string, media []string, messageID int, chatID int64) { forward(text, "telegram", sender, media, messageID, chatID) }, func(chatID int64, messageID int, reaction string) { if discordChannelID != "" { _ = bridge.SendToDiscord(discordChannelID, "реакция поставлена") } if discordReactionChannelID == "" { log.Printf("[Bridge] reaction duplicate skipped: DISCORD_REACTION_CHANNEL_ID is empty") return } normalizedReaction := normalizeEmoji(reaction) if normalizedReaction != normalizedDuplicateReaction { log.Printf("[Bridge] reaction %q does not match configured %q", reaction, telegramDuplicateReactionEmoji) return } cacheMu.RLock() item, exists := messageCache[cacheKey(chatID, messageID)] cacheMu.RUnlock() if !exists { log.Printf("[Telegram] reaction %q for message %d in chat %d ignored: original message not in cache", reaction, messageID, chatID) return } msg := item.Text if len(item.Media) > 0 { if msg != "" { msg += "\n" } msg += strings.Join(item.Media, "\n") } if err := sendTelegramMessageToDiscord(discordReactionChannelID, item.Sender, item.Text, msg, item.Media); err != nil { log.Printf("[Bridge] failed to send duplicated message to reaction Discord channel: %v", err) return } log.Printf("[Bridge] duplicated message %d from chat %d to reaction channel by reaction %q", messageID, chatID, reaction) }, ) bridge.StartDiscordListener(discordChannelID, func(text string, sender string, media []string) { forward(text, "discord", sender, media, 0, 0) }) log.Println("Chat Bridge запущен и работает") select {} // Бесконечный цикл для поддержания работы программы } func normalizeEmoji(value string) string { trimmed := strings.TrimSpace(value) filtered := strings.Map(func(r rune) rune { // Variation selectors and skin tone modifiers are optional in user input/reaction payloads. if r == '\uFE0E' || r == '\uFE0F' { return -1 } if r >= '\U0001F3FB' && r <= '\U0001F3FF' { return -1 } return r }, trimmed) return filtered }