diff --git a/bridge/discord/discord.go b/bridge/discord/discord.go index 16ce8c05..13bdd248 100644 --- a/bridge/discord/discord.go +++ b/bridge/discord/discord.go @@ -2,6 +2,7 @@ package bdiscord import ( "bytes" + "errors" "fmt" "strings" "sync" @@ -15,6 +16,20 @@ import ( const MessageLength = 1950 type Bdiscord struct { +<<<<<<< HEAD +======= + c *discordgo.Session + Channels []*discordgo.Channel + Nick string + UseChannelID bool + userMemberMap map[string]*discordgo.Member + nickMemberMap map[string]*discordgo.Member + guildID string + webhookID string + webhookToken string + channelInfoMap map[string]*config.ChannelInfo + sync.RWMutex +>>>>>>> 52cf8e7535318a6c5ea470d2cd554b449a0eb0e9 *bridge.Config c *discordgo.Session @@ -243,6 +258,228 @@ func (b *Bdiscord) Send(msg config.Message) (string, error) { return res.ID, err } +<<<<<<< HEAD +======= +func (b *Bdiscord) messageDelete(s *discordgo.Session, m *discordgo.MessageDelete) { + rmsg := config.Message{Account: b.Account, ID: m.ID, Event: config.EVENT_MSG_DELETE, Text: config.EVENT_MSG_DELETE} + rmsg.Channel = b.getChannelName(m.ChannelID) + if b.UseChannelID { + rmsg.Channel = "ID:" + m.ChannelID + } + b.Log.Debugf("<= Sending message from %s to gateway", b.Account) + b.Log.Debugf("<= Message is %#v", rmsg) + b.Remote <- rmsg +} + +func (b *Bdiscord) messageUpdate(s *discordgo.Session, m *discordgo.MessageUpdate) { + if b.GetBool("EditDisable") { + return + } + // only when message is actually edited + if m.Message.EditedTimestamp != "" { + b.Log.Debugf("Sending edit message") + m.Content = m.Content + b.GetString("EditSuffix") + b.messageCreate(s, (*discordgo.MessageCreate)(m)) + } +} + +func (b *Bdiscord) messageCreate(s *discordgo.Session, m *discordgo.MessageCreate) { + var err error + + // not relay our own messages + if m.Author.Username == b.Nick { + return + } + // if using webhooks, do not relay if it's ours + if b.useWebhook() && m.Author.Bot && b.isWebhookID(m.Author.ID) { + return + } + + // add the url of the attachments to content + if len(m.Attachments) > 0 { + for _, attach := range m.Attachments { + m.Content = m.Content + "\n" + attach.URL + } + } + + rmsg := config.Message{Account: b.Account, Avatar: "https://cdn.discordapp.com/avatars/" + m.Author.ID + "/" + m.Author.Avatar + ".jpg", UserID: m.Author.ID, ID: m.ID} + + if m.Content != "" { + b.Log.Debugf("== Receiving event %#v", m.Message) + m.Message.Content = b.stripCustomoji(m.Message.Content) + m.Message.Content = b.replaceChannelMentions(m.Message.Content) + rmsg.Text, err = m.ContentWithMoreMentionsReplaced(b.c) + if err != nil { + b.Log.Errorf("ContentWithMoreMentionsReplaced failed: %s", err) + rmsg.Text = m.ContentWithMentionsReplaced() + } + } + + // set channel name + rmsg.Channel = b.getChannelName(m.ChannelID) + if b.UseChannelID { + rmsg.Channel = "ID:" + m.ChannelID + } + + // set username + if !b.GetBool("UseUserName") { + rmsg.Username = b.getNick(m.Author) + } else { + rmsg.Username = m.Author.Username + } + + // if we have embedded content add it to text + if b.GetBool("ShowEmbeds") && m.Message.Embeds != nil { + for _, embed := range m.Message.Embeds { + rmsg.Text = rmsg.Text + "embed: " + embed.Title + " - " + embed.Description + " - " + embed.URL + "\n" + } + } + + // no empty messages + if rmsg.Text == "" { + return + } + + // do we have a /me action + var ok bool + rmsg.Text, ok = b.replaceAction(rmsg.Text) + if ok { + rmsg.Event = config.EVENT_USER_ACTION + } + + b.Log.Debugf("<= Sending message from %s on %s to gateway", m.Author.Username, b.Account) + b.Log.Debugf("<= Message is %#v", rmsg) + b.Remote <- rmsg +} + +func (b *Bdiscord) memberUpdate(s *discordgo.Session, m *discordgo.GuildMemberUpdate) { + b.Lock() + if _, ok := b.userMemberMap[m.Member.User.ID]; ok { + b.Log.Debugf("%s: memberupdate: user %s (nick %s) changes nick to %s", b.Account, m.Member.User.Username, b.userMemberMap[m.Member.User.ID].Nick, m.Member.Nick) + } + b.userMemberMap[m.Member.User.ID] = m.Member + b.Unlock() +} + +func (b *Bdiscord) getNick(user *discordgo.User) string { + var err error + b.Lock() + defer b.Unlock() + if _, ok := b.userMemberMap[user.ID]; ok { + if b.userMemberMap[user.ID] != nil { + if b.userMemberMap[user.ID].Nick != "" { + // only return if nick is set + return b.userMemberMap[user.ID].Nick + } + // otherwise return username + return user.Username + } + } + // if we didn't find nick, search for it + member, err := b.c.GuildMember(b.guildID, user.ID) + if err != nil { + return user.Username + } + b.userMemberMap[user.ID] = member + b.nickMemberMap[member.Nick] = member + // only return if nick is set + if b.userMemberMap[user.ID].Nick != "" { + return b.userMemberMap[user.ID].Nick + } + return user.Username +} + +func (b *Bdiscord) getGuildMemberByNick(nick string) (*discordgo.Member, error) { + b.Lock() + defer b.Unlock() + if _, ok := b.nickMemberMap[nick]; ok { + if b.nickMemberMap[nick] != nil { + return b.nickMemberMap[nick], nil + } + } + + return nil, errors.New("Couldn't find guild member with nick " + nick) // This will most likely get ignored by the caller +} + +func (b *Bdiscord) getChannelID(name string) string { + idcheck := strings.Split(name, "ID:") + if len(idcheck) > 1 { + return idcheck[1] + } + for _, channel := range b.Channels { + if channel.Name == name { + return channel.ID + } + } + return "" +} + +func (b *Bdiscord) getChannelName(id string) string { + for _, channel := range b.Channels { + if channel.ID == id { + return channel.Name + } + } + return "" +} + +func (b *Bdiscord) replaceChannelMentions(text string) string { + var err error + re := regexp.MustCompile("<#[0-9]+>") + text = re.ReplaceAllStringFunc(text, func(m string) string { + channel := b.getChannelName(m[2 : len(m)-1]) + // if at first don't succeed, try again + if channel == "" { + b.Channels, err = b.c.GuildChannels(b.guildID) + if err != nil { + return "#unknownchannel" + } + channel = b.getChannelName(m[2 : len(m)-1]) + return "#" + channel + } + return "#" + channel + }) + return text +} + +func (b *Bdiscord) replaceUserMentions(text string) string { + re := regexp.MustCompile("@[^ ]+") + text = re.ReplaceAllStringFunc(text, func(m string) string { + mention := m[1:] + b.Log.Debugf("Testing mention: '%s'", mention) + member, err := b.getGuildMemberByNick(mention) + if err != nil { + return m + } + return member.User.Mention() + }) + b.Log.Debugf("Message with mention replaced: %s", text) + return text +} + +func (b *Bdiscord) replaceAction(text string) (string, bool) { + if strings.HasPrefix(text, "_") && strings.HasSuffix(text, "_") { + return strings.Replace(text, "_", "", -1), true + } + return text, false +} + +func (b *Bdiscord) stripCustomoji(text string) string { + // <:doge:302803592035958784> + re := regexp.MustCompile("<(:.*?:)[0-9]+>") + return re.ReplaceAllString(text, `$1`) +} + +// splitURL splits a webhookURL and returns the id and token +func (b *Bdiscord) splitURL(url string) (string, string) { + webhookURLSplit := strings.Split(url, "/") + if len(webhookURLSplit) != 7 { + b.Log.Fatalf("%s is no correct discord WebhookURL", url) + } + return webhookURLSplit[len(webhookURLSplit)-2], webhookURLSplit[len(webhookURLSplit)-1] +} + +>>>>>>> 52cf8e7535318a6c5ea470d2cd554b449a0eb0e9 // useWebhook returns true if we have a webhook defined somewhere func (b *Bdiscord) useWebhook() bool { if b.GetString("WebhookURL") != "" {