diff options
| author | Mistivia <i@mistivia.com> | 2025-11-02 15:27:18 +0800 |
|---|---|---|
| committer | Mistivia <i@mistivia.com> | 2025-11-02 15:27:18 +0800 |
| commit | e9c24f4af7ed56760f6db7941827d09f6db9020b (patch) | |
| tree | 62128c43b883ce5e3148113350978755779bb5de /teleirc/matterbridge/bridge/discord/helpers.go | |
| parent | 58d5e7cfda4781d8a57ec52aefd02983835c301a (diff) | |
add matterbridge
Diffstat (limited to 'teleirc/matterbridge/bridge/discord/helpers.go')
| -rw-r--r-- | teleirc/matterbridge/bridge/discord/helpers.go | 269 |
1 files changed, 269 insertions, 0 deletions
diff --git a/teleirc/matterbridge/bridge/discord/helpers.go b/teleirc/matterbridge/bridge/discord/helpers.go new file mode 100644 index 0000000..2e18f46 --- /dev/null +++ b/teleirc/matterbridge/bridge/discord/helpers.go @@ -0,0 +1,269 @@ +package bdiscord + +import ( + "errors" + "regexp" + "strings" + "unicode" + + "github.com/bwmarrin/discordgo" +) + +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, + } +} + +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) { + length := len(text) + if length > 1 && text[0] == '_' && text[length-1] == '_' { + return text[1 : length-1], true + } + return text, false +} + +// splitURL splits a webhookURL and returns the ID and token. +func (b *Bdiscord) splitURL(url string) (string, string, bool) { + const ( + expectedWebhookSplitCount = 7 + webhookIdxID = 5 + webhookIdxToken = 6 + ) + webhookURLSplit := strings.Split(url, "/") + if len(webhookURLSplit) != expectedWebhookSplitCount { + return "", "", false + } + return webhookURLSplit[webhookIdxID], webhookURLSplit[webhookIdxToken], true +} + +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 +} |
