forked from jshiffer/matterbridge
e60949ff3f
* Support webhook message deletions (discord) Messages sent via webhook can now be deleted. It seems it can do this without any special permissions. This copies discordgo.WebhookExecute and makes it support the returning of discordgo.Message. A pull request has been sent upstream, so we should use that if @bwmariin accepts the pull request: https://github.com/bwmarrin/discordgo/pull/663 Changes in behaviour (webhook mode only): - Previously messages *edited* on other platforms would just be retransmitted as a brand new message to Discord. - Message *edits* will now be ignored. - Debug: message edits will now print out a "permission error". In the future it may be good to send an "message edited" react to those webhook messages, so at least people know that the message was edited on other platforms. (Even though it can't actually show the new message.) Alternatively, message edits could just send a brand new message with a link back to the old one. This is a little ugly but it would ensure that Discord users are able to see the edited message. These "message edit notifications" would be sent from the bot user (not from a webhook), so we could edit the "edit notification" if subsequent edits to the original message are made.
214 lines
5.2 KiB
Go
214 lines
5.2 KiB
Go
package bdiscord
|
|
|
|
import (
|
|
"encoding/json"
|
|
"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: %s", 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
|
|
}
|
|
|
|
// webhookExecute executes a webhook.
|
|
// webhookID: The ID of a webhook.
|
|
// token : The auth token for the webhook
|
|
// wait : Waits for server confirmation of message send and ensures that the return struct is populated (it is nil otherwise)
|
|
func (b *Bdiscord) webhookExecute(webhookID, token string, wait bool, data *discordgo.WebhookParams) (st *discordgo.Message, err error) {
|
|
uri := discordgo.EndpointWebhookToken(webhookID, token)
|
|
|
|
if wait {
|
|
uri += "?wait=true"
|
|
}
|
|
response, err := b.c.RequestWithBucketID("POST", uri, data, discordgo.EndpointWebhookToken("", ""))
|
|
if !wait || err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = json.Unmarshal(response, &st)
|
|
if err != nil {
|
|
return nil, discordgo.ErrJSONUnmarshal
|
|
}
|
|
|
|
return st, nil
|
|
}
|