1package bdiscord 2 3import ( 4 "errors" 5 "regexp" 6 "strings" 7 "unicode" 8 9 "github.com/matterbridge/discordgo" 10) 11 12func (b *Bdiscord) getAllowedMentions() *discordgo.MessageAllowedMentions { 13 // If AllowMention is not specified, then allow all mentions (default Discord behavior) 14 if !b.IsKeySet("AllowMention") { 15 return nil 16 } 17 18 // Otherwise, allow only the mentions that are specified 19 allowedMentionTypes := make([]discordgo.AllowedMentionType, 0, 3) 20 for _, m := range b.GetStringSlice("AllowMention") { 21 switch m { 22 case "everyone": 23 allowedMentionTypes = append(allowedMentionTypes, discordgo.AllowedMentionTypeEveryone) 24 case "roles": 25 allowedMentionTypes = append(allowedMentionTypes, discordgo.AllowedMentionTypeRoles) 26 case "users": 27 allowedMentionTypes = append(allowedMentionTypes, discordgo.AllowedMentionTypeUsers) 28 } 29 } 30 31 return &discordgo.MessageAllowedMentions{ 32 Parse: allowedMentionTypes, 33 } 34} 35 36func (b *Bdiscord) getNick(user *discordgo.User, guildID string) string { 37 b.membersMutex.RLock() 38 defer b.membersMutex.RUnlock() 39 40 if member, ok := b.userMemberMap[user.ID]; ok { 41 if member.Nick != "" { 42 // Only return if nick is set. 43 return member.Nick 44 } 45 // Otherwise return username. 46 return user.Username 47 } 48 49 // If we didn't find nick, search for it. 50 member, err := b.c.GuildMember(guildID, user.ID) 51 if err != nil { 52 b.Log.Warnf("Failed to fetch information for member %#v on guild %#v: %s", user, guildID, err) 53 return user.Username 54 } else if member == nil { 55 b.Log.Warnf("Got no information for member %#v", user) 56 return user.Username 57 } 58 b.userMemberMap[user.ID] = member 59 b.nickMemberMap[member.User.Username] = member 60 if member.Nick != "" { 61 b.nickMemberMap[member.Nick] = member 62 return member.Nick 63 } 64 return user.Username 65} 66 67func (b *Bdiscord) getGuildMemberByNick(nick string) (*discordgo.Member, error) { 68 b.membersMutex.RLock() 69 defer b.membersMutex.RUnlock() 70 71 if member, ok := b.nickMemberMap[nick]; ok { 72 return member, nil 73 } 74 return nil, errors.New("Couldn't find guild member with nick " + nick) // This will most likely get ignored by the caller 75} 76 77func (b *Bdiscord) getChannelID(name string) string { 78 if strings.Contains(name, "/") { 79 return b.getCategoryChannelID(name) 80 } 81 b.channelsMutex.RLock() 82 defer b.channelsMutex.RUnlock() 83 84 idcheck := strings.Split(name, "ID:") 85 if len(idcheck) > 1 { 86 return idcheck[1] 87 } 88 for _, channel := range b.channels { 89 if channel.Name == name && channel.Type == discordgo.ChannelTypeGuildText { 90 return channel.ID 91 } 92 } 93 return "" 94} 95 96func (b *Bdiscord) getCategoryChannelID(name string) string { 97 b.channelsMutex.RLock() 98 defer b.channelsMutex.RUnlock() 99 res := strings.Split(name, "/") 100 // shouldn't happen because function should be only called from getChannelID 101 if len(res) != 2 { 102 return "" 103 } 104 catName, chanName := res[0], res[1] 105 for _, channel := range b.channels { 106 // if we have a parentID, lookup the name of that parent (category) 107 // and if it matches return it 108 if channel.Name == chanName && channel.ParentID != "" { 109 for _, cat := range b.channels { 110 if cat.ID == channel.ParentID && cat.Name == catName { 111 return channel.ID 112 } 113 } 114 } 115 } 116 return "" 117} 118 119func (b *Bdiscord) getChannelName(id string) string { 120 b.channelsMutex.RLock() 121 defer b.channelsMutex.RUnlock() 122 123 for _, c := range b.channelInfoMap { 124 if c.Name == "ID:"+id { 125 // if we have ID: specified in our gateway configuration return this 126 return c.Name 127 } 128 } 129 130 for _, channel := range b.channels { 131 if channel.ID == id { 132 return b.getCategoryChannelName(channel.Name, channel.ParentID) 133 } 134 } 135 return "" 136} 137 138func (b *Bdiscord) getCategoryChannelName(name, parentID string) string { 139 var usesCat bool 140 // do we have a category configuration in the channel config 141 for _, c := range b.channelInfoMap { 142 if strings.Contains(c.Name, "/") { 143 usesCat = true 144 break 145 } 146 } 147 // configuration without category, return the normal channel name 148 if !usesCat { 149 return name 150 } 151 // create a category/channel response 152 for _, c := range b.channels { 153 if c.ID == parentID { 154 name = c.Name + "/" + name 155 } 156 } 157 return name 158} 159 160var ( 161 // See https://discordapp.com/developers/docs/reference#message-formatting. 162 channelMentionRE = regexp.MustCompile("<#[0-9]+>") 163 userMentionRE = regexp.MustCompile("@[^@\n]{1,32}") 164 emoteRE = regexp.MustCompile(`<a?(:\w+:)\d+>`) 165) 166 167func (b *Bdiscord) replaceChannelMentions(text string) string { 168 replaceChannelMentionFunc := func(match string) string { 169 channelID := match[2 : len(match)-1] 170 channelName := b.getChannelName(channelID) 171 172 // If we don't have the channel refresh our list. 173 if channelName == "" { 174 var err error 175 b.channels, err = b.c.GuildChannels(b.guildID) 176 if err != nil { 177 return "#unknownchannel" 178 } 179 channelName = b.getChannelName(channelID) 180 } 181 return "#" + channelName 182 } 183 return channelMentionRE.ReplaceAllStringFunc(text, replaceChannelMentionFunc) 184} 185 186func (b *Bdiscord) replaceUserMentions(text string) string { 187 replaceUserMentionFunc := func(match string) string { 188 var ( 189 err error 190 member *discordgo.Member 191 username string 192 ) 193 194 usernames := enumerateUsernames(match[1:]) 195 for _, username = range usernames { 196 b.Log.Debugf("Testing mention: '%s'", username) 197 member, err = b.getGuildMemberByNick(username) 198 if err == nil { 199 break 200 } 201 } 202 if member == nil { 203 return match 204 } 205 return strings.Replace(match, "@"+username, member.User.Mention(), 1) 206 } 207 return userMentionRE.ReplaceAllStringFunc(text, replaceUserMentionFunc) 208} 209 210func replaceEmotes(text string) string { 211 return emoteRE.ReplaceAllString(text, "$1") 212} 213 214func (b *Bdiscord) replaceAction(text string) (string, bool) { 215 length := len(text) 216 if length > 1 && text[0] == '_' && text[length-1] == '_' { 217 return text[1 : length-1], true 218 } 219 return text, false 220} 221 222// splitURL splits a webhookURL and returns the ID and token. 223func (b *Bdiscord) splitURL(url string) (string, string, bool) { 224 const ( 225 expectedWebhookSplitCount = 7 226 webhookIdxID = 5 227 webhookIdxToken = 6 228 ) 229 webhookURLSplit := strings.Split(url, "/") 230 if len(webhookURLSplit) != expectedWebhookSplitCount { 231 return "", "", false 232 } 233 return webhookURLSplit[webhookIdxID], webhookURLSplit[webhookIdxToken], true 234} 235 236func enumerateUsernames(s string) []string { 237 onlySpace := true 238 for _, r := range s { 239 if !unicode.IsSpace(r) { 240 onlySpace = false 241 break 242 } 243 } 244 if onlySpace { 245 return nil 246 } 247 248 var username, endSpace string 249 var usernames []string 250 skippingSpace := true 251 for _, r := range s { 252 if unicode.IsSpace(r) { 253 if !skippingSpace { 254 usernames = append(usernames, username) 255 skippingSpace = true 256 } 257 endSpace += string(r) 258 username += string(r) 259 } else { 260 endSpace = "" 261 username += string(r) 262 skippingSpace = false 263 } 264 } 265 if endSpace == "" { 266 usernames = append(usernames, username) 267 } 268 return usernames 269} 270