Thumbnail

rani/matterbridge.git

Clone URL: https://git.buni.party/rani/matterbridge.git

Viewing file on branch master

1package bdiscord
2
3import (
4 "errors"
5 "regexp"
6 "strings"
7 "unicode"
8
9 "github.com/bwmarrin/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[strings.TrimSpace(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