forked from jshiffer/matterbridge
Split Discord bridge in multiple files (#632)
This commit is contained in:
parent
af7a00d030
commit
0365c0786a
@ -2,9 +2,7 @@ package bdiscord
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
@ -17,18 +15,23 @@ import (
|
|||||||
const MessageLength = 1950
|
const MessageLength = 1950
|
||||||
|
|
||||||
type Bdiscord struct {
|
type Bdiscord struct {
|
||||||
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
|
|
||||||
*bridge.Config
|
*bridge.Config
|
||||||
|
|
||||||
|
c *discordgo.Session
|
||||||
|
|
||||||
|
nick string
|
||||||
|
useChannelID bool
|
||||||
|
guildID string
|
||||||
|
webhookID string
|
||||||
|
webhookToken string
|
||||||
|
|
||||||
|
channelsMutex sync.RWMutex
|
||||||
|
channels []*discordgo.Channel
|
||||||
|
channelInfoMap map[string]*config.ChannelInfo
|
||||||
|
|
||||||
|
membersMutex sync.RWMutex
|
||||||
|
userMemberMap map[string]*discordgo.Member
|
||||||
|
nickMemberMap map[string]*discordgo.Member
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(cfg *bridge.Config) bridge.Bridger {
|
func New(cfg *bridge.Config) bridge.Bridger {
|
||||||
@ -77,28 +80,40 @@ func (b *Bdiscord) Connect() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
serverName := strings.Replace(b.GetString("Server"), "ID:", "", -1)
|
serverName := strings.Replace(b.GetString("Server"), "ID:", "", -1)
|
||||||
b.Nick = userinfo.Username
|
b.nick = userinfo.Username
|
||||||
|
b.channelsMutex.Lock()
|
||||||
for _, guild := range guilds {
|
for _, guild := range guilds {
|
||||||
if guild.Name == serverName || guild.ID == serverName {
|
if guild.Name == serverName || guild.ID == serverName {
|
||||||
b.Channels, err = b.c.GuildChannels(guild.ID)
|
b.channels, err = b.c.GuildChannels(guild.ID)
|
||||||
b.guildID = guild.ID
|
b.guildID = guild.ID
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, channel := range b.Channels {
|
b.channelsMutex.Unlock()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
b.channelsMutex.RLock()
|
||||||
|
for _, channel := range b.channels {
|
||||||
b.Log.Debugf("found channel %#v", channel)
|
b.Log.Debugf("found channel %#v", channel)
|
||||||
}
|
}
|
||||||
// obtaining guild members and initializing nickname mapping
|
b.channelsMutex.RUnlock()
|
||||||
b.Lock()
|
|
||||||
defer b.Unlock()
|
// Obtaining guild members and initializing nickname mapping.
|
||||||
|
b.membersMutex.Lock()
|
||||||
|
defer b.membersMutex.Unlock()
|
||||||
members, err := b.c.GuildMembers(b.guildID, "", 1000)
|
members, err := b.c.GuildMembers(b.guildID, "", 1000)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Log.Error("Error obtaining guild members", err)
|
b.Log.Error("Error obtaining guild members", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, member := range members {
|
for _, member := range members {
|
||||||
|
if member == nil {
|
||||||
|
b.Log.Warnf("Skipping missing information for a user.")
|
||||||
|
continue
|
||||||
|
}
|
||||||
b.userMemberMap[member.User.ID] = member
|
b.userMemberMap[member.User.ID] = member
|
||||||
b.nickMemberMap[member.User.Username] = member
|
b.nickMemberMap[member.User.Username] = member
|
||||||
if member.Nick != "" {
|
if member.Nick != "" {
|
||||||
@ -113,10 +128,13 @@ func (b *Bdiscord) Disconnect() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bdiscord) JoinChannel(channel config.ChannelInfo) error {
|
func (b *Bdiscord) JoinChannel(channel config.ChannelInfo) error {
|
||||||
|
b.channelsMutex.Lock()
|
||||||
|
defer b.channelsMutex.Unlock()
|
||||||
|
|
||||||
b.channelInfoMap[channel.ID] = &channel
|
b.channelInfoMap[channel.ID] = &channel
|
||||||
idcheck := strings.Split(channel.Name, "ID:")
|
idcheck := strings.Split(channel.Name, "ID:")
|
||||||
if len(idcheck) > 1 {
|
if len(idcheck) > 1 {
|
||||||
b.UseChannelID = true
|
b.useChannelID = true
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -139,11 +157,13 @@ func (b *Bdiscord) Send(msg config.Message) (string, error) {
|
|||||||
wToken := b.webhookToken
|
wToken := b.webhookToken
|
||||||
|
|
||||||
// check if have a channel specific webhook
|
// check if have a channel specific webhook
|
||||||
|
b.channelsMutex.RLock()
|
||||||
if ci, ok := b.channelInfoMap[msg.Channel+b.Account]; ok {
|
if ci, ok := b.channelInfoMap[msg.Channel+b.Account]; ok {
|
||||||
if ci.Options.WebhookURL != "" {
|
if ci.Options.WebhookURL != "" {
|
||||||
wID, wToken = b.splitURL(ci.Options.WebhookURL)
|
wID, wToken = b.splitURL(ci.Options.WebhookURL)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
b.channelsMutex.RUnlock()
|
||||||
|
|
||||||
// Use webhook to send the message
|
// Use webhook to send the message
|
||||||
if wID != "" {
|
if wID != "" {
|
||||||
@ -196,7 +216,9 @@ func (b *Bdiscord) Send(msg config.Message) (string, error) {
|
|||||||
if msg.Extra != nil {
|
if msg.Extra != nil {
|
||||||
for _, rmsg := range helper.HandleExtra(&msg, b.General) {
|
for _, rmsg := range helper.HandleExtra(&msg, b.General) {
|
||||||
rmsg.Text = helper.ClipMessage(rmsg.Text, MessageLength)
|
rmsg.Text = helper.ClipMessage(rmsg.Text, MessageLength)
|
||||||
b.c.ChannelMessageSend(channelID, rmsg.Username+rmsg.Text)
|
if _, err := b.c.ChannelMessageSend(channelID, rmsg.Username+rmsg.Text); err != nil {
|
||||||
|
b.Log.Errorf("Could not send message %#v: %v", rmsg, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// check if we have files to upload (from slack, telegram or mattermost)
|
// check if we have files to upload (from slack, telegram or mattermost)
|
||||||
if len(msg.Extra["file"]) > 0 {
|
if len(msg.Extra["file"]) > 0 {
|
||||||
@ -221,246 +243,15 @@ func (b *Bdiscord) Send(msg config.Message) (string, error) {
|
|||||||
return res.ID, err
|
return res.ID, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bdiscord) messageDelete(s *discordgo.Session, m *discordgo.MessageDelete) {
|
|
||||||
rmsg := config.Message{Account: b.Account, ID: m.ID, Event: config.EventMsgDelete, Text: config.EventMsgDelete}
|
|
||||||
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 += 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.EventUserAction
|
|
||||||
}
|
|
||||||
|
|
||||||
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.nickMemberMap[m.Member.User.Username] = m.Member
|
|
||||||
if m.Member.Nick != "" {
|
|
||||||
b.nickMemberMap[m.Member.Nick] = 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
|
|
||||||
// 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("@[^@]{1,32}")
|
|
||||||
text = re.ReplaceAllStringFunc(text, func(m string) string {
|
|
||||||
mention := strings.TrimSpace(m[1:])
|
|
||||||
var member *discordgo.Member
|
|
||||||
var err error
|
|
||||||
for {
|
|
||||||
b.Log.Debugf("Testing mention: '%s'", mention)
|
|
||||||
member, err = b.getGuildMemberByNick(mention)
|
|
||||||
if err != nil {
|
|
||||||
lastSpace := strings.LastIndex(mention, " ")
|
|
||||||
if lastSpace == -1 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
mention = strings.TrimSpace(mention[0:lastSpace])
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
return strings.Replace(m, "@"+mention, member.User.Mention(), -1)
|
|
||||||
})
|
|
||||||
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]
|
|
||||||
}
|
|
||||||
|
|
||||||
// useWebhook returns true if we have a webhook defined somewhere
|
// useWebhook returns true if we have a webhook defined somewhere
|
||||||
func (b *Bdiscord) useWebhook() bool {
|
func (b *Bdiscord) useWebhook() bool {
|
||||||
if b.GetString("WebhookURL") != "" {
|
if b.GetString("WebhookURL") != "" {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
b.channelsMutex.RLock()
|
||||||
|
defer b.channelsMutex.RUnlock()
|
||||||
|
|
||||||
for _, channel := range b.channelInfoMap {
|
for _, channel := range b.channelInfoMap {
|
||||||
if channel.Options.WebhookURL != "" {
|
if channel.Options.WebhookURL != "" {
|
||||||
return true
|
return true
|
||||||
@ -477,6 +268,10 @@ func (b *Bdiscord) isWebhookID(id string) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
b.channelsMutex.RLock()
|
||||||
|
defer b.channelsMutex.RUnlock()
|
||||||
|
|
||||||
for _, channel := range b.channelInfoMap {
|
for _, channel := range b.channelInfoMap {
|
||||||
if channel.Options.WebhookURL != "" {
|
if channel.Options.WebhookURL != "" {
|
||||||
wID, _ := b.splitURL(channel.Options.WebhookURL)
|
wID, _ := b.splitURL(channel.Options.WebhookURL)
|
||||||
|
125
bridge/discord/handlers.go
Normal file
125
bridge/discord/handlers.go
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
package bdiscord
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/42wim/matterbridge/bridge/config"
|
||||||
|
"github.com/bwmarrin/discordgo"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (b *Bdiscord) messageDelete(s *discordgo.Session, m *discordgo.MessageDelete) {
|
||||||
|
rmsg := config.Message{Account: b.Account, ID: m.ID, Event: config.EventMsgDelete, Text: config.EventMsgDelete}
|
||||||
|
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 += 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.EventUserAction
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
if m.Member == nil {
|
||||||
|
b.Log.Warnf("Received member update with no member information: %#v", m)
|
||||||
|
}
|
||||||
|
|
||||||
|
b.membersMutex.Lock()
|
||||||
|
defer b.membersMutex.Unlock()
|
||||||
|
|
||||||
|
if currMember, 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,
|
||||||
|
)
|
||||||
|
delete(b.nickMemberMap, currMember.User.Username)
|
||||||
|
delete(b.nickMemberMap, currMember.Nick)
|
||||||
|
delete(b.userMemberMap, m.Member.User.ID)
|
||||||
|
}
|
||||||
|
b.userMemberMap[m.Member.User.ID] = m.Member
|
||||||
|
b.nickMemberMap[m.Member.User.Username] = m.Member
|
||||||
|
if m.Member.Nick != "" {
|
||||||
|
b.nickMemberMap[m.Member.Nick] = m.Member
|
||||||
|
}
|
||||||
|
}
|
189
bridge/discord/helpers.go
Normal file
189
bridge/discord/helpers.go
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
package bdiscord
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
|
"github.com/bwmarrin/discordgo"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (b *Bdiscord) getNick(user *discordgo.User) string {
|
||||||
|
b.membersMutex.RLock()
|
||||||
|
defer b.membersMutex.RUnlock()
|
||||||
|
|
||||||
|
if member, ok := b.userMemberMap[user.ID]; ok {
|
||||||
|
if member.Nick != "" {
|
||||||
|
// Only return if nick is set.
|
||||||
|
return member.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 {
|
||||||
|
b.Log.Warnf("Failed to fetch information for member %#v: %#v", user, err)
|
||||||
|
return user.Username
|
||||||
|
} else if member == nil {
|
||||||
|
b.Log.Warnf("Got no information for member %#v", user)
|
||||||
|
return user.Username
|
||||||
|
}
|
||||||
|
b.userMemberMap[user.ID] = member
|
||||||
|
b.nickMemberMap[member.User.Username] = member
|
||||||
|
if member.Nick != "" {
|
||||||
|
b.nickMemberMap[member.Nick] = member
|
||||||
|
return member.Nick
|
||||||
|
}
|
||||||
|
return user.Username
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bdiscord) getGuildMemberByNick(nick string) (*discordgo.Member, error) {
|
||||||
|
b.membersMutex.RLock()
|
||||||
|
defer b.membersMutex.RUnlock()
|
||||||
|
|
||||||
|
if member, ok := b.nickMemberMap[nick]; ok {
|
||||||
|
return member, 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 {
|
||||||
|
b.channelsMutex.RLock()
|
||||||
|
defer b.channelsMutex.RUnlock()
|
||||||
|
|
||||||
|
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 {
|
||||||
|
b.channelsMutex.RLock()
|
||||||
|
defer b.channelsMutex.RUnlock()
|
||||||
|
|
||||||
|
for _, channel := range b.channels {
|
||||||
|
if channel.ID == id {
|
||||||
|
return channel.Name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// See https://discordapp.com/developers/docs/reference#message-formatting.
|
||||||
|
channelMentionRE = regexp.MustCompile("<#[0-9]+>")
|
||||||
|
emojiRE = regexp.MustCompile("<(:.*?:)[0-9]+>")
|
||||||
|
userMentionRE = regexp.MustCompile("@[^@\n]{1,32}")
|
||||||
|
)
|
||||||
|
|
||||||
|
func (b *Bdiscord) replaceChannelMentions(text string) string {
|
||||||
|
replaceChannelMentionFunc := func(match string) string {
|
||||||
|
var err error
|
||||||
|
channelID := match[2 : len(match)-1]
|
||||||
|
|
||||||
|
channelName := b.getChannelName(channelID)
|
||||||
|
// If we don't have the channel refresh our list.
|
||||||
|
if channelName == "" {
|
||||||
|
b.channels, err = b.c.GuildChannels(b.guildID)
|
||||||
|
if err != nil {
|
||||||
|
return "#unknownchannel"
|
||||||
|
}
|
||||||
|
channelName = b.getChannelName(channelID)
|
||||||
|
}
|
||||||
|
return "#" + channelName
|
||||||
|
}
|
||||||
|
return channelMentionRE.ReplaceAllStringFunc(text, replaceChannelMentionFunc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bdiscord) replaceUserMentions(text string) string {
|
||||||
|
replaceUserMentionFunc := func(match string) string {
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
member *discordgo.Member
|
||||||
|
username string
|
||||||
|
)
|
||||||
|
|
||||||
|
usernames := enumerateUsernames(match[1:])
|
||||||
|
for _, username = range usernames {
|
||||||
|
b.Log.Debugf("Testing mention: '%s'", username)
|
||||||
|
member, err = b.getGuildMemberByNick(username)
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if member == nil {
|
||||||
|
return match
|
||||||
|
}
|
||||||
|
return strings.Replace(match, "@"+username, member.User.Mention(), 1)
|
||||||
|
}
|
||||||
|
return userMentionRE.ReplaceAllStringFunc(text, replaceUserMentionFunc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bdiscord) stripCustomoji(text string) string {
|
||||||
|
return emojiRE.ReplaceAllString(text, `$1`)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bdiscord) replaceAction(text string) (string, bool) {
|
||||||
|
if strings.HasPrefix(text, "_") && strings.HasSuffix(text, "_") {
|
||||||
|
return text[1:], true
|
||||||
|
}
|
||||||
|
return text, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// splitURL splits a webhookURL and returns the ID and token.
|
||||||
|
func (b *Bdiscord) splitURL(url string) (string, string) {
|
||||||
|
const (
|
||||||
|
expectedWebhookSplitCount = 7
|
||||||
|
webhookIdxID = 5
|
||||||
|
webhookIdxToken = 6
|
||||||
|
)
|
||||||
|
webhookURLSplit := strings.Split(url, "/")
|
||||||
|
if len(webhookURLSplit) != expectedWebhookSplitCount {
|
||||||
|
b.Log.Fatalf("%s is no correct discord WebhookURL", url)
|
||||||
|
}
|
||||||
|
return webhookURLSplit[webhookIdxID], webhookURLSplit[webhookIdxToken]
|
||||||
|
}
|
||||||
|
|
||||||
|
func enumerateUsernames(s string) []string {
|
||||||
|
onlySpace := true
|
||||||
|
for _, r := range s {
|
||||||
|
if !unicode.IsSpace(r) {
|
||||||
|
onlySpace = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if onlySpace {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var username, endSpace string
|
||||||
|
var usernames []string
|
||||||
|
skippingSpace := true
|
||||||
|
for _, r := range s {
|
||||||
|
if unicode.IsSpace(r) {
|
||||||
|
if !skippingSpace {
|
||||||
|
usernames = append(usernames, username)
|
||||||
|
skippingSpace = true
|
||||||
|
}
|
||||||
|
endSpace += string(r)
|
||||||
|
username += string(r)
|
||||||
|
} else {
|
||||||
|
endSpace = ""
|
||||||
|
username += string(r)
|
||||||
|
skippingSpace = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if endSpace == "" {
|
||||||
|
usernames = append(usernames, username)
|
||||||
|
}
|
||||||
|
return usernames
|
||||||
|
}
|
46
bridge/discord/helpers_test.go
Normal file
46
bridge/discord/helpers_test.go
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
package bdiscord
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEnumerateUsernames(t *testing.T) {
|
||||||
|
testcases := map[string]struct {
|
||||||
|
match string
|
||||||
|
expectedUsernames []string
|
||||||
|
}{
|
||||||
|
"only space": {
|
||||||
|
match: " \t\n \t",
|
||||||
|
expectedUsernames: nil,
|
||||||
|
},
|
||||||
|
"single word": {
|
||||||
|
match: "veni",
|
||||||
|
expectedUsernames: []string{"veni"},
|
||||||
|
},
|
||||||
|
"single word with preceeding space": {
|
||||||
|
match: " vidi",
|
||||||
|
expectedUsernames: []string{" vidi"},
|
||||||
|
},
|
||||||
|
"single word with suffixed space": {
|
||||||
|
match: "vici ",
|
||||||
|
expectedUsernames: []string{"vici"},
|
||||||
|
},
|
||||||
|
"multi-word with varying whitespace": {
|
||||||
|
match: "just me and\tmy friends \t",
|
||||||
|
expectedUsernames: []string{
|
||||||
|
"just",
|
||||||
|
"just me",
|
||||||
|
"just me and",
|
||||||
|
"just me and\tmy",
|
||||||
|
"just me and\tmy friends",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for testname, testcase := range testcases {
|
||||||
|
foundUsernames := enumerateUsernames(testcase.match)
|
||||||
|
assert.Equalf(t, testcase.expectedUsernames, foundUsernames, "Should have found the expected usernames for testcase %s", testname)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user