2018-12-07 23:36:01 +01:00
|
|
|
package bdiscord
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"regexp"
|
|
|
|
"strings"
|
|
|
|
"unicode"
|
|
|
|
|
2020-03-08 17:08:18 +01:00
|
|
|
"github.com/matterbridge/discordgo"
|
2018-12-07 23:36:01 +01:00
|
|
|
)
|
|
|
|
|
2021-05-13 22:39:25 +02:00
|
|
|
func (b *Bdiscord) getAllowedMentions() *discordgo.MessageAllowedMentions {
|
|
|
|
// If AllowMention is not specified, then allow all mentions (default Discord behavior)
|
|
|
|
if !b.IsKeySet("AllowMention") {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Otherwise, allow only the mentions that are specified
|
|
|
|
allowedMentionTypes := make([]discordgo.AllowedMentionType, 0, 3)
|
|
|
|
for _, m := range b.GetStringSlice("AllowMention") {
|
|
|
|
switch m {
|
|
|
|
case "everyone":
|
|
|
|
allowedMentionTypes = append(allowedMentionTypes, discordgo.AllowedMentionTypeEveryone)
|
|
|
|
case "roles":
|
|
|
|
allowedMentionTypes = append(allowedMentionTypes, discordgo.AllowedMentionTypeRoles)
|
|
|
|
case "users":
|
|
|
|
allowedMentionTypes = append(allowedMentionTypes, discordgo.AllowedMentionTypeUsers)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return &discordgo.MessageAllowedMentions{
|
|
|
|
Parse: allowedMentionTypes,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-15 20:25:42 +02:00
|
|
|
func (b *Bdiscord) getNick(user *discordgo.User, guildID string) string {
|
2018-12-07 23:36:01 +01:00
|
|
|
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.
|
2019-09-15 20:25:42 +02:00
|
|
|
member, err := b.c.GuildMember(guildID, user.ID)
|
2018-12-07 23:36:01 +01:00
|
|
|
if err != nil {
|
2019-09-15 20:25:42 +02:00
|
|
|
b.Log.Warnf("Failed to fetch information for member %#v on guild %#v: %s", user, guildID, err)
|
2018-12-07 23:36:01 +01:00
|
|
|
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 {
|
2019-07-15 21:56:35 +02:00
|
|
|
if strings.Contains(name, "/") {
|
|
|
|
return b.getCategoryChannelID(name)
|
|
|
|
}
|
2018-12-07 23:36:01 +01:00
|
|
|
b.channelsMutex.RLock()
|
|
|
|
defer b.channelsMutex.RUnlock()
|
|
|
|
|
|
|
|
idcheck := strings.Split(name, "ID:")
|
|
|
|
if len(idcheck) > 1 {
|
|
|
|
return idcheck[1]
|
|
|
|
}
|
|
|
|
for _, channel := range b.channels {
|
2019-07-15 21:56:35 +02:00
|
|
|
if channel.Name == name && channel.Type == discordgo.ChannelTypeGuildText {
|
2018-12-07 23:36:01 +01:00
|
|
|
return channel.ID
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
2019-07-15 21:56:35 +02:00
|
|
|
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 ""
|
|
|
|
}
|
|
|
|
|
2018-12-07 23:36:01 +01:00
|
|
|
func (b *Bdiscord) getChannelName(id string) string {
|
|
|
|
b.channelsMutex.RLock()
|
|
|
|
defer b.channelsMutex.RUnlock()
|
|
|
|
|
2020-01-09 23:54:04 +01:00
|
|
|
for _, c := range b.channelInfoMap {
|
|
|
|
if c.Name == "ID:"+id {
|
|
|
|
// if we have ID: specified in our gateway configuration return this
|
|
|
|
return c.Name
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-12-07 23:36:01 +01:00
|
|
|
for _, channel := range b.channels {
|
|
|
|
if channel.ID == id {
|
2019-07-15 21:56:35 +02:00
|
|
|
return b.getCategoryChannelName(channel.Name, channel.ParentID)
|
2018-12-07 23:36:01 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
2019-07-15 21:56:35 +02:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2018-12-07 23:36:01 +01:00
|
|
|
var (
|
|
|
|
// See https://discordapp.com/developers/docs/reference#message-formatting.
|
|
|
|
channelMentionRE = regexp.MustCompile("<#[0-9]+>")
|
|
|
|
userMentionRE = regexp.MustCompile("@[^@\n]{1,32}")
|
2020-03-22 14:16:31 +01:00
|
|
|
emoteRE = regexp.MustCompile(`<a?(:\w+:)\d+>`)
|
2018-12-07 23:36:01 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
func (b *Bdiscord) replaceChannelMentions(text string) string {
|
|
|
|
replaceChannelMentionFunc := func(match string) string {
|
|
|
|
channelID := match[2 : len(match)-1]
|
|
|
|
channelName := b.getChannelName(channelID)
|
2019-07-14 19:53:09 +02:00
|
|
|
|
2018-12-07 23:36:01 +01:00
|
|
|
// If we don't have the channel refresh our list.
|
|
|
|
if channelName == "" {
|
2019-07-15 21:56:35 +02:00
|
|
|
var err error
|
|
|
|
b.channels, err = b.c.GuildChannels(b.guildID)
|
2018-12-07 23:36:01 +01:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2020-03-22 14:16:31 +01:00
|
|
|
func replaceEmotes(text string) string {
|
|
|
|
return emoteRE.ReplaceAllString(text, "$1")
|
|
|
|
}
|
|
|
|
|
2018-12-07 23:36:01 +01:00
|
|
|
func (b *Bdiscord) replaceAction(text string) (string, bool) {
|
2020-04-25 14:22:22 +02:00
|
|
|
length := len(text)
|
|
|
|
if length > 1 && text[0] == '_' && text[length-1] == '_' {
|
|
|
|
return text[1 : length-1], true
|
2018-12-07 23:36:01 +01:00
|
|
|
}
|
|
|
|
return text, false
|
|
|
|
}
|
|
|
|
|
|
|
|
// splitURL splits a webhookURL and returns the ID and token.
|
2020-11-30 06:47:02 +01:00
|
|
|
func (b *Bdiscord) splitURL(url string) (string, string, bool) {
|
2018-12-07 23:36:01 +01:00
|
|
|
const (
|
|
|
|
expectedWebhookSplitCount = 7
|
|
|
|
webhookIdxID = 5
|
|
|
|
webhookIdxToken = 6
|
|
|
|
)
|
|
|
|
webhookURLSplit := strings.Split(url, "/")
|
|
|
|
if len(webhookURLSplit) != expectedWebhookSplitCount {
|
2020-11-30 06:47:02 +01:00
|
|
|
return "", "", false
|
2018-12-07 23:36:01 +01:00
|
|
|
}
|
2020-11-30 06:47:02 +01:00
|
|
|
return webhookURLSplit[webhookIdxID], webhookURLSplit[webhookIdxToken], true
|
2018-12-07 23:36:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|