hmn/src/discord/markdown.go

148 lines
4.1 KiB
Go

package discord
import (
"context"
"errors"
"fmt"
"regexp"
"strings"
"sync"
"git.handmade.network/hmn/hmn/src/config"
"git.handmade.network/hmn/hmn/src/logging"
)
var (
REMarkdownUser = regexp.MustCompile(`<@([0-9]+)>`)
REMarkdownUserNickname = regexp.MustCompile(`<@!([0-9]+)>`)
REMarkdownChannel = regexp.MustCompile(`<#([0-9]+)>`)
REMarkdownRole = regexp.MustCompile(`<@&([0-9]+)>`)
REMarkdownCustomEmoji = regexp.MustCompile(`<a?:(\w+):[0-9]+>`) // includes animated
REMarkdownTimestamp = regexp.MustCompile(`<t:([0-9]+)(:([tTdDfFR]))?>`)
)
func CleanUpMarkdown(ctx context.Context, original string) string {
userMatches := REMarkdownUser.FindAllStringSubmatch(original, -1)
userNicknameMatches := REMarkdownUserNickname.FindAllStringSubmatch(original, -1)
channelMatches := REMarkdownChannel.FindAllStringSubmatch(original, -1)
roleMatches := REMarkdownRole.FindAllStringSubmatch(original, -1)
customEmojiMatches := REMarkdownCustomEmoji.FindAllStringSubmatch(original, -1)
timestampMatches := REMarkdownTimestamp.FindAllStringSubmatch(original, -1)
userIdsToFetch := map[string]struct{}{}
for _, m := range userMatches {
userIdsToFetch[m[1]] = struct{}{}
}
for _, m := range userNicknameMatches {
userIdsToFetch[m[1]] = struct{}{}
}
// do the requests, gathering the resulting data
userNames := map[string]string{}
userNicknames := map[string]string{}
channelNames := map[string]string{}
roleNames := map[string]string{}
var wg sync.WaitGroup
var mutex sync.Mutex
for userId := range userIdsToFetch {
wg.Add(1)
go func(ctx context.Context, userId string) {
defer wg.Done()
member, err := GetGuildMember(ctx, config.Config.Discord.GuildID, userId)
if err != nil {
if errors.Is(err, NotFound) {
// not a problem
} else if err != nil {
logging.ExtractLogger(ctx).Warn().Err(err).Msg("failed to fetch guild member for markdown")
}
return
}
func() {
mutex.Lock()
defer mutex.Unlock()
if member.User != nil {
userNames[userId] = member.User.Username
}
if member.Nick != nil {
userNicknames[userId] = *member.Nick
}
}()
}(ctx, userId)
}
if len(channelMatches) > 0 {
wg.Add(1)
go func(ctx context.Context) {
defer wg.Done()
channels, err := GetGuildChannels(ctx, config.Config.Discord.GuildID)
if err != nil {
logging.ExtractLogger(ctx).Warn().Err(err).Msg("failed to fetch channels for markdown")
return
}
for _, channel := range channels {
channelNames[channel.ID] = channel.Name
}
}(ctx)
}
if len(roleMatches) > 0 {
wg.Add(1)
go func(ctx context.Context) {
defer wg.Done()
roles, err := GetGuildRoles(ctx, config.Config.Discord.GuildID)
if err != nil {
logging.ExtractLogger(ctx).Warn().Err(err).Msg("failed to fetch roles for markdown")
return
}
for _, role := range roles {
roleNames[role.ID] = role.Name
}
}(ctx)
}
wg.Wait()
// Replace all the everything
res := original
for _, m := range userMatches {
resultName := "Unknown User"
if name, ok := userNames[m[1]]; ok {
resultName = name
}
res = strings.Replace(res, m[0], fmt.Sprintf("@%s", resultName), 1)
}
for _, m := range userNicknameMatches {
resultName := "Unknown User"
if name, ok := userNicknames[m[1]]; ok {
resultName = name
} else if name, ok := userNames[m[1]]; ok {
resultName = name
}
res = strings.Replace(res, m[0], fmt.Sprintf("@%s", resultName), 1)
}
for _, m := range channelMatches {
resultName := "Unknown Channel"
if name, ok := channelNames[m[1]]; ok {
resultName = name
}
res = strings.Replace(res, m[0], fmt.Sprintf("#%s", resultName), 1)
}
for _, m := range roleMatches {
resultName := "Unknown Role"
if name, ok := roleNames[m[1]]; ok {
resultName = name
}
res = strings.Replace(res, m[0], fmt.Sprintf("@%s", resultName), 1)
}
for _, m := range customEmojiMatches {
res = strings.Replace(res, m[0], fmt.Sprintf(":%s:", m[1]), 1)
}
for _, m := range timestampMatches {
res = strings.Replace(res, m[0], "<timestamp>", 1) // TODO: Actual timestamp stuff? Is it worth it?
}
return res
}