forked from jshiffer/matterbridge
eaf92fca4d
When a webhook "edits" a message, it does this by deleting the message and creating a new one with the new content. On creation of this new message, we'll get another ID then already is know by the gateway in its id cache. So we add it in our own cache and replace it whenever we want to edit/delete it again.
279 lines
6.9 KiB
Go
279 lines
6.9 KiB
Go
package bdiscord
|
|
|
|
import (
|
|
"errors"
|
|
"regexp"
|
|
"strings"
|
|
"unicode"
|
|
|
|
"github.com/matterbridge/discordgo"
|
|
)
|
|
|
|
func (b *Bdiscord) getNick(user *discordgo.User, guildID string) 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(guildID, user.ID)
|
|
if err != nil {
|
|
b.Log.Warnf("Failed to fetch information for member %#v on guild %#v: %s", user, guildID, 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 {
|
|
if strings.Contains(name, "/") {
|
|
return b.getCategoryChannelID(name)
|
|
}
|
|
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 && channel.Type == discordgo.ChannelTypeGuildText {
|
|
return channel.ID
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (b *Bdiscord) getCategoryChannelID(name string) string {
|
|
b.channelsMutex.RLock()
|
|
defer b.channelsMutex.RUnlock()
|
|
res := strings.Split(name, "/")
|
|
// shouldn't happen because function should be only called from getChannelID
|
|
if len(res) != 2 {
|
|
return ""
|
|
}
|
|
catName, chanName := res[0], res[1]
|
|
for _, channel := range b.channels {
|
|
// if we have a parentID, lookup the name of that parent (category)
|
|
// and if it matches return it
|
|
if channel.Name == chanName && channel.ParentID != "" {
|
|
for _, cat := range b.channels {
|
|
if cat.ID == channel.ParentID && cat.Name == catName {
|
|
return channel.ID
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (b *Bdiscord) getChannelName(id string) string {
|
|
b.channelsMutex.RLock()
|
|
defer b.channelsMutex.RUnlock()
|
|
|
|
for _, c := range b.channelInfoMap {
|
|
if c.Name == "ID:"+id {
|
|
// if we have ID: specified in our gateway configuration return this
|
|
return c.Name
|
|
}
|
|
}
|
|
|
|
for _, channel := range b.channels {
|
|
if channel.ID == id {
|
|
return b.getCategoryChannelName(channel.Name, channel.ParentID)
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (b *Bdiscord) getCategoryChannelName(name, parentID string) string {
|
|
var usesCat bool
|
|
// do we have a category configuration in the channel config
|
|
for _, c := range b.channelInfoMap {
|
|
if strings.Contains(c.Name, "/") {
|
|
usesCat = true
|
|
break
|
|
}
|
|
}
|
|
// configuration without category, return the normal channel name
|
|
if !usesCat {
|
|
return name
|
|
}
|
|
// create a category/channel response
|
|
for _, c := range b.channels {
|
|
if c.ID == parentID {
|
|
name = c.Name + "/" + name
|
|
}
|
|
}
|
|
return name
|
|
}
|
|
|
|
var (
|
|
// See https://discordapp.com/developers/docs/reference#message-formatting.
|
|
channelMentionRE = regexp.MustCompile("<#[0-9]+>")
|
|
userMentionRE = regexp.MustCompile("@[^@\n]{1,32}")
|
|
emoteRE = regexp.MustCompile(`<a?(:\w+:)\d+>`)
|
|
)
|
|
|
|
func (b *Bdiscord) replaceChannelMentions(text string) string {
|
|
replaceChannelMentionFunc := func(match string) string {
|
|
channelID := match[2 : len(match)-1]
|
|
channelName := b.getChannelName(channelID)
|
|
|
|
// If we don't have the channel refresh our list.
|
|
if channelName == "" {
|
|
var err error
|
|
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 replaceEmotes(text string) string {
|
|
return emoteRE.ReplaceAllString(text, "$1")
|
|
}
|
|
|
|
func (b *Bdiscord) replaceAction(text string) (string, bool) {
|
|
if strings.HasPrefix(text, "_") && strings.HasSuffix(text, "_") {
|
|
return text[1 : len(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]
|
|
}
|
|
|
|
// getcacheID tries to find a corresponding msgID in the webhook cache.
|
|
// if not found returns the original request.
|
|
func (b *Bdiscord) getCacheID(msgID string) string {
|
|
b.webhookMutex.RLock()
|
|
defer b.webhookMutex.RUnlock()
|
|
for k, v := range b.webhookCache {
|
|
if msgID == k {
|
|
return v
|
|
}
|
|
}
|
|
return msgID
|
|
}
|
|
|
|
// updateCacheID updates the cache so that the newID takes the place of
|
|
// the original ID. This is used for edit/deletes in combination with webhooks
|
|
// as editing a message via webhook means deleting the message and creating a
|
|
// new message (with a new ID). This ID needs to be set instead of the original ID
|
|
func (b *Bdiscord) updateCacheID(origID, newID string) {
|
|
b.webhookMutex.Lock()
|
|
match := false
|
|
for k, v := range b.webhookCache {
|
|
if v == origID {
|
|
delete(b.webhookCache, k)
|
|
b.webhookCache[origID] = newID
|
|
match = true
|
|
continue
|
|
}
|
|
}
|
|
if !match && origID != "" {
|
|
b.webhookCache[origID] = newID
|
|
}
|
|
b.webhookMutex.Unlock()
|
|
}
|
|
|
|
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
|
|
}
|