forked from lug/matterbridge
		
	
		
			
				
	
	
		
			280 lines
		
	
	
		
			6.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			280 lines
		
	
	
		
			6.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package bdiscord
 | |
| 
 | |
| import (
 | |
| 	"errors"
 | |
| 	"regexp"
 | |
| 	"strings"
 | |
| 	"unicode"
 | |
| 
 | |
| 	"github.com/matterbridge/discordgo"
 | |
| )
 | |
| 
 | |
| 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) {
 | |
| 	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]
 | |
| }
 | |
| 
 | |
| // getcacheID tries to find a corresponding msgID in the webhook cache.
 | |
| // if not found returns the original request.
 | |
| func (b *Bdiscord) getCacheID(msgID string) string {
 | |
| 	b.webhookMutex.RLock()
 | |
| 	defer b.webhookMutex.RUnlock()
 | |
| 	for k, v := range b.webhookCache {
 | |
| 		if msgID == k {
 | |
| 			return v
 | |
| 		}
 | |
| 	}
 | |
| 	return msgID
 | |
| }
 | |
| 
 | |
| // updateCacheID updates the cache so that the newID takes the place of
 | |
| // the original ID. This is used for edit/deletes in combination with webhooks
 | |
| // as editing a message via webhook means deleting the message and creating a
 | |
| // new message (with a new ID). This ID needs to be set instead of the original ID
 | |
| func (b *Bdiscord) updateCacheID(origID, newID string) {
 | |
| 	b.webhookMutex.Lock()
 | |
| 	match := false
 | |
| 	for k, v := range b.webhookCache {
 | |
| 		if v == origID {
 | |
| 			delete(b.webhookCache, k)
 | |
| 			b.webhookCache[origID] = newID
 | |
| 			match = true
 | |
| 			continue
 | |
| 		}
 | |
| 	}
 | |
| 	if !match && origID != "" {
 | |
| 		b.webhookCache[origID] = newID
 | |
| 	}
 | |
| 	b.webhookMutex.Unlock()
 | |
| }
 | |
| 
 | |
| 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
 | |
| }
 | 
