--- /dev/null
+package main
+
+import (
+ "bufio"
+ "context"
+ "log"
+ "os"
+ "regexp"
+ "sort"
+ "strconv"
+ "strings"
+
+ "github.com/bwmarrin/discordgo"
+ tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
+)
+
+var (
+ TgApiToken = "6444047266:AAH86vKKDeMktgpqDIwWElH_s9nu-ZgqdB8"
+ DiscApiToken = "MTIwMzg0MzAyMDg5ODgzMjM5NA.G6e3Eo.yU90uQzHOMCw-cHguOhzwlXhnIcHo5YId60U64"
+
+ UpdateTextBtn = "Update"
+ InitBotText = "I'm new here, I need to update my text. Click the button below to update it."
+ menuTextMarkup = tgbotapi.NewInlineKeyboardMarkup(
+ tgbotapi.NewInlineKeyboardRow(
+ tgbotapi.NewInlineKeyboardButtonData(UpdateTextBtn, UpdateTextBtn),
+ ),
+ )
+
+ telegram *tgbotapi.BotAPI
+
+ // bot msg
+ botMsg *tgbotapi.Message
+
+ // bot text message
+ botText string = InitBotText
+
+ Members []*discordgo.Member
+
+ // List of users online
+ usersOnline []string
+
+ // List of users in voice chat
+ usersInVoiceChat []string
+)
+
+func main() {
+ var err error
+ telegram, err = tgbotapi.NewBotAPI(TgApiToken)
+ if err != nil {
+ // Abort if something is wrong
+ log.Panic(err)
+ }
+
+ // Set this to true to log all interactions with telegram servers
+ telegram.Debug = false
+
+ u := tgbotapi.NewUpdate(0)
+ u.Timeout = 60
+
+ // Create a new cancellable background context. Calling `cancel()` leads to the cancellation of the context
+ ctx := context.Background()
+ ctx, cancel := context.WithCancel(ctx)
+
+ // `updates` is a golang channel which receives telegram updates
+ updates := telegram.GetUpdatesChan(u)
+
+ // Pass cancellable context to goroutine
+ go receiveUpdates(ctx, updates)
+
+ // Tell the user the bot is online
+ log.Println("Telegram Bot ready and listening for updates.")
+
+ // now start discord stuff
+ discord, err := discordgo.New("Bot " + DiscApiToken)
+ if err != nil {
+ log.Println("error creating Discord session,", err)
+ return
+ }
+ discord.LogLevel = discordgo.LogDebug
+ discord.AddHandlerOnce(ready)
+ discord.AddHandlerOnce(guildCreated)
+ discord.AddHandler(voiceChatUpdate)
+ discord.AddHandler(guildUpdates)
+
+ discord.Identify.Intents = discordgo.IntentGuilds | discordgo.IntentGuildPresences | discordgo.IntentGuildVoiceStates
+
+ // Open a websocket connection to Discord and begin listening.
+ err = discord.Open()
+ if err != nil {
+ log.Println("error opening discord session: ", err)
+ }
+
+ // Tell the user the bot is online
+ log.Println("Discord Bot ready.")
+
+ log.Println("Press enter to stop")
+ // Wait for a newline symbol, then cancel handling updates
+ bufio.NewReader(os.Stdin).ReadBytes('\n')
+ cancel()
+
+}
+
+func receiveUpdates(ctx context.Context, updates tgbotapi.UpdatesChannel) {
+ // `for {` means the loop is infinite until we manually stop it
+ for {
+ select {
+ // stop looping if ctx is cancelled
+ case <-ctx.Done():
+ return
+ // receive update from channel and then handle it
+ case update := <-updates:
+ handleUpdate(update)
+ }
+ }
+}
+
+func handleUpdate(update tgbotapi.Update) {
+ switch {
+ // Handle messages
+ case update.Message != nil:
+ handleMessage(update.Message)
+
+ // handle buttons click
+ case update.CallbackQuery != nil:
+ handleButton(update.CallbackQuery)
+ }
+}
+
+func handleMessage(message *tgbotapi.Message) {
+ user := message.From
+ text := message.Text
+
+ if user == nil {
+ return
+ }
+
+ // Print to console
+ log.Printf("%s wrote %s", user.FirstName, text)
+
+ var err error
+ if strings.HasPrefix(text, "/") {
+ err = handleCommand(message.Chat.ID, text)
+ }
+ // else {
+ // // This is equivalent to forwarding, without the sender's name
+ // // copyMsg := tgbotapi.NewCopyMessage(message.Chat.ID, message.Chat.ID, message.MessageID)
+ // // _, err = telegram.CopyMessage(copyMsg)
+ // }
+
+ if err != nil {
+ log.Printf("An error occured: %s", err.Error())
+ }
+}
+
+// When we get a command, we react accordingly
+func handleCommand(chatId int64, command string) error {
+ var err error
+
+ switch command {
+ case "/start":
+ err = sendMenu(chatId)
+ }
+
+ return err
+}
+
+func sendMenu(chatId int64) error {
+ msg := tgbotapi.NewMessage(chatId, botText)
+ msg.ParseMode = tgbotapi.ModeHTML
+ msg.ReplyMarkup = menuTextMarkup
+ msg.DisableNotification = true
+ resMsg, err := telegram.Send(msg)
+ botMsg = &resMsg
+ return err
+}
+
+func handleButton(query *tgbotapi.CallbackQuery) {
+ var text string
+
+ markup := tgbotapi.NewInlineKeyboardMarkup()
+ message := query.Message
+
+ if botMsg == nil {
+ botMsg = message
+ }
+
+ if query.Data == UpdateTextBtn {
+ text = botText
+ markup = menuTextMarkup
+ }
+
+ callbackCfg := tgbotapi.NewCallback(query.ID, "")
+ telegram.Send(callbackCfg)
+
+ // Replace menu text and keyboard
+ msg := tgbotapi.NewEditMessageTextAndMarkup(message.Chat.ID, message.MessageID, text, markup)
+ msg.ParseMode = tgbotapi.ModeHTML
+ telegram.Send(msg)
+}
+
+//region discord bot stuff
+
+// This function will be called (due to AddHandler above) when the bot receives
+// the "ready" event from Discord.
+func ready(s *discordgo.Session, event *discordgo.Ready) {
+ // Set the playing status.
+ s.UpdateListeningStatus("te a ti")
+}
+
+func voiceChatUpdate(s *discordgo.Session, m *discordgo.VoiceStateUpdate) {
+ if m.ChannelID != "" {
+ // The user joined a channel, moved to a new channel, or edited its status
+ usersInVoiceChat = append(removeMemberFromSlice(usersInVoiceChat, *m.Member), formatUserVoiceStatus(*m.Member, *m.VoiceState))
+ } else {
+ // The user left a channel
+ usersInVoiceChat = removeMemberFromSlice(usersInVoiceChat, *m.Member)
+ }
+
+ formatBotMessage()
+}
+
+// The guild was created (or the bot was added to a new guild, or the bot was restarted)
+func guildCreated(s *discordgo.Session, m *discordgo.GuildCreate) {
+ // update the list of users online
+ for _, presence := range m.Presences {
+ if presence.User != nil && m.Members != nil && presence.Status != discordgo.StatusOffline && presence.Status != discordgo.StatusInvisible {
+ Members = m.Members
+ var member = getMemberFromMembers(m.Members, presence.User.ID)
+ if member.User.Bot {
+ continue
+ }
+ usersOnline = append(usersOnline, formatUserStatus(*member, presence.Status))
+ }
+ }
+
+ // update the list of users in voice chat
+ for _, vs := range m.VoiceStates {
+ if vs.UserID != "" {
+ var member = getMemberFromMembers(m.Members, vs.UserID)
+ if member != nil {
+ usersInVoiceChat = append(usersInVoiceChat, formatUserVoiceStatus(*member, *vs))
+ }
+ }
+ }
+
+ formatBotMessage()
+}
+
+// The guild was updated (an user did something or changed status)
+func guildUpdates(s *discordgo.Session, m *discordgo.PresenceUpdate) {
+ if m.Presence.User != nil {
+ var member = getMemberFromMembers(Members, m.Presence.User.ID)
+ if m.Presence.Status != discordgo.StatusOffline && m.Presence.Status != discordgo.StatusInvisible {
+ usersOnline = append(removeMemberFromSlice(usersOnline, *member), formatUserStatus(*member, m.Presence.Status))
+ } else {
+ usersOnline = removeMemberFromSlice(usersOnline, *member)
+ }
+ }
+ formatBotMessage()
+}
+
+func formatBotMessage() {
+ log.Println("formatting bot message...")
+ botText = ""
+ re := regexp.MustCompile(`\\\[[0-9]+\]`) // remove user id from the string
+
+ if len(usersOnline) > 0 {
+ botText += "π€ <b>EN LΓNEA (" + strconv.Itoa(len(usersOnline)) + ")</b>" + "\n"
+
+ sort.Strings(usersOnline)
+ for _, user := range usersOnline {
+ user = re.ReplaceAllString(user, "")
+ botText += user + "\n"
+ }
+ }
+
+ if len(usersInVoiceChat) > 0 {
+ botText += "\n" + "π <b>CHAT DE VOZ (" + strconv.Itoa(len(usersInVoiceChat)) + ")</b>" + "\n"
+
+ sort.Strings(usersInVoiceChat)
+ for _, user := range usersInVoiceChat {
+ user = re.ReplaceAllString(user, "")
+ botText += user + "\n"
+ }
+ }
+
+ if botText == "" {
+ botText = "π€ Parece que no hay nadie conectado."
+ }
+
+ log.Println(botText)
+
+ if botMsg != nil {
+ msg := tgbotapi.NewEditMessageTextAndMarkup(botMsg.Chat.ID, botMsg.MessageID, botText, menuTextMarkup)
+ msg.ParseMode = tgbotapi.ModeHTML
+ telegram.Send(msg)
+ }
+}
+
+func formatUserStatus(member discordgo.Member, status discordgo.Status) string {
+ var emoji string
+
+ switch status {
+ case discordgo.StatusOnline:
+ emoji = "π’"
+ case discordgo.StatusIdle:
+ emoji = "π‘"
+ case discordgo.StatusDoNotDisturb:
+ emoji = "π΄"
+ }
+
+ return emoji + " " + formatUserVisibleName(member)
+}
+
+func formatUserVoiceStatus(member discordgo.Member, voiceState discordgo.VoiceState) string {
+ var emoji string
+
+ emoji = "π΅"
+ if member.Mute || voiceState.Mute || voiceState.SelfMute {
+ emoji = "π"
+ }
+ if member.Deaf || voiceState.Deaf || voiceState.SelfDeaf {
+ emoji = "π"
+ }
+ if voiceState.SelfVideo {
+ emoji += "πΉ"
+ }
+ if voiceState.SelfStream {
+ emoji += "π₯"
+ }
+
+ emoji += " "
+
+ return emoji + formatUserVisibleName(member)
+}
+
+func formatUserVisibleName(member discordgo.Member) string {
+ var user string
+ var id = member.User.ID
+
+ if member.User.DisplayName != "" {
+ user = member.User.DisplayName
+ } else {
+ user = member.User.Username
+ }
+
+ if member.Nick != "" {
+ user = member.Nick + " (" + user + ")"
+ }
+
+ return user + "\\[" + id + "]"
+}
+
+func removeMemberFromSlice(slice []string, member discordgo.Member) []string {
+ var id = "\\[" + member.User.ID + "]"
+
+ for i, v := range slice {
+ if strings.Contains(v, id) {
+ return append(slice[:i], slice[i+1:]...)
+ }
+ }
+
+ return slice
+}
+
+func getMemberFromMembers(slice []*discordgo.Member, id string) *discordgo.Member {
+ for _, v := range slice {
+ if v.User.ID == id {
+ return v
+ }
+ }
+ return nil
+}