func GetMemberNick(m *discordgo.Member) string {
	if m.Nick == "" {
		return m.User.Username
	}
	return m.Nick
}
GetMemberNick returns the real display name for a Discord GuildMember
package bridge
import (
	"fmt"
	"regexp"
	"runtime/debug"
	"strings"
	"github.com/42wim/matterbridge/bridge/discord/transmitter"
	"github.com/qaisjp/go-discord-irc/dstate"
	ircnick "github.com/qaisjp/go-discord-irc/irc/nick"
	"github.com/matterbridge/discordgo"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	log "github.com/sirupsen/logrus"
)
type discordBot struct {
	Session *discordgo.Session
	bridge  *Bridge
	guildID string
	transmitter *transmitter.Transmitter
}
func newDiscord(bridge *Bridge, botToken, guildID string) (*discordBot, error) {
	session, err := discordgo.New("Bot " + botToken)
	if err != nil {
		return nil, errors.Wrap(err, "discord, could not create new session")
	}
	session.StateEnabled = true
	discord := &discordBot{
		Session: session,
		bridge:  bridge,
		guildID: guildID,
	}
	discord.Session.AddHandler(discord.OnReady)
	discord.Session.AddHandler(discord.onMessageCreate)
	discord.Session.AddHandler(discord.onMessageUpdate)
	discord.Session.AddHandler(discord.onGuildEmojiUpdate)
	if !bridge.Config.SimpleMode {
		discord.Session.AddHandler(discord.onMemberListChunk)
		discord.Session.AddHandler(discord.onMemberUpdate)
		discord.Session.AddHandler(discord.onMemberLeave)
		discord.Session.AddHandler(discord.OnPresencesReplace)
		discord.Session.AddHandler(discord.OnPresenceUpdate)
		discord.Session.AddHandler(discord.OnTypingStart)
		discord.Session.AddHandler(discord.OnMessageReactionAdd)
	}
	return discord, nil
}
func (d *discordBot) Open() error {
	d.transmitter = transmitter.New(d.Session, d.guildID, "go-discord-irc", true)
	d.transmitter.Log = logrus.NewEntry(logrus.StandardLogger())
	if err := d.transmitter.RefreshGuildWebhooks(nil); err != nil {
		return fmt.Errorf("failed to refresh guild webhooks: %w", err)
	}
	d.Session.Identify.Intents = discordgo.MakeIntent(discordgo.IntentsAll)
	err := d.Session.Open()
	if err != nil {
		return errors.Wrap(err, "discord, could not open session")
	}
	return nil
}
func (d *discordBot) Close() error {
	return errors.Wrap(d.Session.Close(), "closing discord session")
}
func userToMention(u *discordgo.User) (mention string) {
	mention = u.Username
	if !u.Bot {
		mention = u.Mention()
	}
	return
}
var spoilerPattern = regexp.MustCompile(`\|\|(.*?)\|\|`)
var colorCode = string(rune(3))
func (d *discordBot) publishMessage(s *discordgo.Session, m *discordgo.Message, wasEdit bool) {
	if m.Author == nil || s.State.User == nil {
		return
	}
	if m.Author.ID == s.State.User.ID {
		return
	}
	if d.transmitter.HasWebhook(m.Author.ID) {
		return
	}
	if m.Content == "ping" {
		_, err := s.ChannelMessageSend(m.ChannelID, "Pong!")
		if err != nil {
			log.Warningln("Could not respond to Discord ping message", err.Error())
		}
	}
	if m.MessageReference != nil && m.MessageReference.ChannelID == m.ChannelID {
		prefix := "[reply]"
		msg, err := dstate.ChannelMessage(d.Session, m.MessageReference.ChannelID, m.MessageReference.MessageID)
		if err == nil {
			prefix = userToMention(msg.Author) + ":"
			if !msg.Author.Bot {
				m.Mentions = append(m.Mentions, msg.Author)
			}
		}
		m.Content = prefix + " " + m.Content
	}
	content := d.ParseText(m)
	isAction := len(content) > 2 &&
		m.Content[0] == '_' &&
		m.Content[len(content)-1] == '_'
	if isAction {
		content = content[1 : len(m.Content)-1]
	}
	if wasEdit {
		if isAction {
			content = "/me " + content
		}
		content = "[edit] " + content
	}
	if strings.Count(content, "||") >= 2 {
		content = spoilerPattern.ReplaceAllString(content, colorCode+"1,1$1"+colorCode)
	}
	pmTarget := ""
	if m.GuildID == "" {
		pmTarget, content = pmTargetFromContent(content, d.bridge.Config.Discriminator)
		switch pmTarget {
		case "":
			_, _ = d.Session.ChannelMessageSend(
				m.ChannelID,
				fmt.Sprintf(
					"Don't know who that is. Can't PM. Try 'name@%s, message here'",
					d.bridge.Config.Discriminator))
			return
		case "*UNKNOWN*":
			return
		default:
			break
		}
	}
	d.bridge.discordMessageEventsChan <- &DiscordMessage{
		Message:  m,
		Content:  content,
		IsAction: isAction,
		PmTarget: pmTarget,
	}
	for _, attachment := range m.Attachments {
		d.bridge.discordMessageEventsChan <- &DiscordMessage{
			Message:  m,
			Content:  attachment.URL,
			IsAction: isAction,
			PmTarget: pmTarget,
		}
	}
}
func (d *discordBot) publishReaction(s *discordgo.Session, r *discordgo.MessageReaction) {
	if s.State.User == nil {
		return
	}
	user, err := s.User(r.UserID)
	if err != nil {
		log.Errorln(err)
		return
	}
	m := &discordgo.Message{
		ChannelID: r.ChannelID,
		Author:    user,
		GuildID:   r.GuildID,
	}
	originalMessage, err := dstate.ChannelMessage(d.Session, r.ChannelID, r.MessageID)
	reactionTarget := ""
	if err == nil {
		username := userToMention(originalMessage.Author)
		if !originalMessage.Author.Bot {
			originalMessage.Mentions = append(originalMessage.Mentions, originalMessage.Author)
		}
		originalMessage.Content = fmt.Sprintf(" to <%s> %s", username, TruncateString(40, originalMessage.Content))
		reactionTarget = d.ParseText(originalMessage)
	}
	emoji := r.Emoji.Name
	if r.Emoji.ID != "" {
		emoji = fmt.Sprint(":", emoji, ":")
	}
	content := fmt.Sprint("reacted with ", emoji, reactionTarget)
	d.bridge.discordMessageEventsChan <- &DiscordMessage{
		Message:  m,
		Content:  content,
		IsAction: true,
		PmTarget: "",
	}
}
var channelMention = regexp.MustCompile(`<#(\d+)>`)
var roleMention = regexp.MustCompile(`<@&(\d+)>`)
var patternChannels = regexp.MustCompile("<#[^>]*>")
var emoteRegex = regexp.MustCompile(`<a?(:\w+:)\d+>`)
func (d *discordBot) ParseText(m *discordgo.Message) string {
	content := m.Content
	for _, user := range m.Mentions {
		username := ""
		for _, u := range d.bridge.ircManager.ircConnections {
			if u.discord.ID == user.ID {
				username = u.nick
			}
		}
		if username == "" {
			nick := user.Username
			member, err := d.Session.State.Member(d.guildID, user.ID)
			if err == nil && member.Nick != "" {
				nick = member.Nick
			}
			username = d.bridge.ircManager.generateNickname(DiscordUser{
				ID:            user.ID,
				Username:      user.Username,
				Discriminator: user.Discriminator,
				Nick:          nick,
				Bot:           user.Bot,
				Online:        false,
			})
			log.WithFields(log.Fields{
				"discord-username": user.Username,
				"irc-username":     username,
				"discord-id":       user.ID,
			}).Infoln("Could not convert mention using existing IRC connection")
		} else {
			log.WithFields(log.Fields{
				"discord-username": user.Username,
				"irc-username":     username,
				"discord-id":       user.ID,
			}).Infoln("Converted mention using existing IRC connection")
		}
		content = strings.NewReplacer(
			"<@"+user.ID+">", username,
			"<@!"+user.ID+">", username,
		).Replace(content)
	}
	for _, roleID := range m.MentionRoles {
		role, err := d.Session.State.Role(d.guildID, roleID)
		if err != nil || !role.Mentionable {
			continue
		}
		content = strings.Replace(content, "<&"+role.ID+">", "@"+role.Name, -1)
	}
	content = patternChannels.ReplaceAllStringFunc(content, func(mention string) string {
		channel, err := d.Session.State.Channel(mention[2 : len(mention)-1])
		if err != nil || channel.Type == discordgo.ChannelTypeGuildVoice {
			return mention
		}
		return "#" + channel.Name
	})
	content = strings.Replace(content, "\r\n", "\n", -1)  
	content = strings.Replace(content, "\r", "\n", -1)    
	content = channelMention.ReplaceAllStringFunc(content, func(str string) string {
		channelID := str[2 : len(str)-1]
		channel, err := d.Session.State.Channel(channelID)
		if err == nil {
			return "#" + channel.Name
		} else if err == discordgo.ErrStateNotFound {
			return "#deleted-channel"
		}
		panic(errors.Wrap(err, "Channel mention failed for "+str))
	})
	content = roleMention.ReplaceAllStringFunc(content, func(str string) string {
		roleID := str[3 : len(str)-1]
		role, err := d.Session.State.Role(d.bridge.Config.GuildID, roleID)
		if err == nil {
			return "@" + role.Name
		} else if err == discordgo.ErrStateNotFound {
			return "@deleted-role"
		}
		panic(errors.Wrap(err, "Channel mention failed for "+str))
	})
	content = emoteRegex.ReplaceAllString(content, "$1")
	return content
}
func (d *discordBot) handlePresenceUpdate(uid string, status discordgo.Status, forceOnline bool) {
	if !forceOnline && !isStatusOnline(status) {
		if d.bridge.Config.DebugPresence {
			log.WithField("id", uid).Debugln("PRESENCE", status, "(handlePresenceUpdate - Online: false)")
		}
		d.sendUpdateUserChan(DiscordUser{
			ID:     uid,
			Online: false,
		})
		return
	}
	if d.bridge.Config.DebugPresence {
		log.WithField("id", uid).Debugln("PRESENCE", status, "(handlePresenceUpdate)")
	}
	user, err := d.Session.State.Member(d.guildID, uid)
	if err != nil {
		log.Println(errors.Wrap(err, "get member from state in handlePresenceUpdate failed"))
		return
	}
	d.handleMemberUpdate(user, forceOnline)
}
func isStatusOnline(status discordgo.Status) bool {
	return status != discordgo.StatusOffline
}
func (d *discordBot) sendUpdateUserChan(user DiscordUser) bool {
	if (user.Username == "" || user.Discriminator == "") && user.Online {
		log.WithFields(log.Fields{
			"err":                errors.WithStack(errors.New("Username or Discriminator is empty")).Error(),
			"user.Username":      user.Username,
			"user.Discriminator": user.Discriminator,
			"user.ID":            user.ID,
		}).Println("sendUpdateUserChan called with empty Username and Discriminator (see stack below)")
		debug.PrintStack()
	}
	d.bridge.updateUserChan <- user
	return true
}
func (d *discordBot) GetAvatar(guildID, username string) (_ string) {
	guild, err := d.Session.State.Guild(guildID)
	if err != nil {
		panic(err)
	}
	var foundMember *discordgo.Member
	for _, member := range guild.Members {
		if (username != member.Nick) && (username != member.User.Username) {
			continue
		}
		if foundMember == nil {
			foundMember = member
		} else {
			return
		}
	}
	if foundMember == nil {
		for _, member := range guild.Members {
			if !strings.EqualFold(username, member.Nick) && !strings.EqualFold(username, member.User.Username) {
				continue
			}
			if foundMember == nil {
				foundMember = member
			} else {
				return
			}
		}
	}
	if foundMember == nil {
		return
	}
	return discordgo.EndpointUserAvatar(foundMember.User.ID, foundMember.User.Avatar)
}