forked from mirror/mautrix-discord
Refactor tag rendering to avoid recreating goldmark instance for each message
This commit is contained in:
parent
694733a4e9
commit
e7615ef4be
3 changed files with 45 additions and 32 deletions
39
formatter.go
39
formatter.go
|
@ -33,9 +33,16 @@ import (
|
||||||
"maunium.net/go/mautrix/util/variationselector"
|
"maunium.net/go/mautrix/util/variationselector"
|
||||||
)
|
)
|
||||||
|
|
||||||
var discordExtensions = goldmark.WithExtensions(extension.Strikethrough, mdext.SimpleSpoiler, mdext.DiscordUnderline, &DiscordEveryone{})
|
// escapeFixer is a hacky partial fix for the difference in escaping markdown, used with escapeReplacement
|
||||||
|
//
|
||||||
|
// Discord allows escaping with just one backslash, e.g. \__a__,
|
||||||
|
// but standard markdown requires both to be escaped (\_\_a__)
|
||||||
var escapeFixer = regexp.MustCompile(`\\(__[^_]|\*\*[^*])`)
|
var escapeFixer = regexp.MustCompile(`\\(__[^_]|\*\*[^*])`)
|
||||||
|
|
||||||
|
func escapeReplacement(s string) string {
|
||||||
|
return s[:2] + `\` + s[2:]
|
||||||
|
}
|
||||||
|
|
||||||
// indentableParagraphParser is the default paragraph parser with CanAcceptIndentedLine.
|
// indentableParagraphParser is the default paragraph parser with CanAcceptIndentedLine.
|
||||||
// Used when disabling CodeBlockParser (as disabling it without a replacement will make indented blocks disappear).
|
// Used when disabling CodeBlockParser (as disabling it without a replacement will make indented blocks disappear).
|
||||||
type indentableParagraphParser struct {
|
type indentableParagraphParser struct {
|
||||||
|
@ -48,24 +55,24 @@ func (b *indentableParagraphParser) CanAcceptIndentedLine() bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (portal *Portal) renderDiscordMarkdownOnlyHTML(text string) string {
|
var discordRenderer = goldmark.New(
|
||||||
text = escapeFixer.ReplaceAllStringFunc(text, func(s string) string {
|
goldmark.WithParser(mdext.ParserWithoutFeatures(
|
||||||
return s[:2] + `\` + s[2:]
|
parser.NewListParser(), parser.NewListItemParser(), parser.NewHTMLBlockParser(), parser.NewRawHTMLParser(),
|
||||||
})
|
parser.NewSetextHeadingParser(), parser.NewATXHeadingParser(), parser.NewThematicBreakParser(),
|
||||||
|
parser.NewLinkParser(), parser.NewCodeBlockParser(),
|
||||||
|
)),
|
||||||
|
goldmark.WithParserOptions(parser.WithBlockParsers(util.Prioritized(defaultIndentableParagraphParser, 500))),
|
||||||
|
format.HTMLOptions,
|
||||||
|
goldmark.WithExtensions(extension.Strikethrough, mdext.SimpleSpoiler, mdext.DiscordUnderline, ExtDiscordEveryone, ExtDiscordTag),
|
||||||
|
)
|
||||||
|
|
||||||
mdRenderer := goldmark.New(
|
func (portal *Portal) renderDiscordMarkdownOnlyHTML(text string) string {
|
||||||
goldmark.WithParser(mdext.ParserWithoutFeatures(
|
text = escapeFixer.ReplaceAllStringFunc(text, escapeReplacement)
|
||||||
parser.NewListParser(), parser.NewListItemParser(), parser.NewHTMLBlockParser(), parser.NewRawHTMLParser(),
|
|
||||||
parser.NewSetextHeadingParser(), parser.NewATXHeadingParser(), parser.NewThematicBreakParser(),
|
|
||||||
parser.NewLinkParser(), parser.NewCodeBlockParser(),
|
|
||||||
)),
|
|
||||||
goldmark.WithParserOptions(parser.WithBlockParsers(util.Prioritized(defaultIndentableParagraphParser, 500))),
|
|
||||||
format.HTMLOptions, discordExtensions,
|
|
||||||
goldmark.WithExtensions(&DiscordTag{portal}),
|
|
||||||
)
|
|
||||||
|
|
||||||
var buf strings.Builder
|
var buf strings.Builder
|
||||||
err := mdRenderer.Convert([]byte(text), &buf)
|
ctx := parser.NewContext()
|
||||||
|
ctx.Set(parserContextPortal, portal)
|
||||||
|
err := discordRenderer.Convert([]byte(text), &buf, parser.WithContext(ctx))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(fmt.Errorf("markdown parser errored: %w", err))
|
panic(fmt.Errorf("markdown parser errored: %w", err))
|
||||||
}
|
}
|
||||||
|
|
|
@ -96,9 +96,11 @@ func (r *discordEveryoneHTMLRenderer) renderDiscordEveryone(w util.BufWriter, so
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
type DiscordEveryone struct{}
|
type discordEveryone struct{}
|
||||||
|
|
||||||
func (e *DiscordEveryone) Extend(m goldmark.Markdown) {
|
var ExtDiscordEveryone = &discordEveryone{}
|
||||||
|
|
||||||
|
func (e *discordEveryone) Extend(m goldmark.Markdown) {
|
||||||
m.Parser().AddOptions(parser.WithInlineParsers(
|
m.Parser().AddOptions(parser.WithInlineParsers(
|
||||||
util.Prioritized(defaultDiscordEveryoneParser, 600),
|
util.Prioritized(defaultDiscordEveryoneParser, 600),
|
||||||
))
|
))
|
||||||
|
|
|
@ -37,7 +37,8 @@ import (
|
||||||
|
|
||||||
type astDiscordTag struct {
|
type astDiscordTag struct {
|
||||||
ast.BaseInline
|
ast.BaseInline
|
||||||
id int64
|
portal *Portal
|
||||||
|
id int64
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ ast.Node = (*astDiscordTag)(nil)
|
var _ ast.Node = (*astDiscordTag)(nil)
|
||||||
|
@ -143,7 +144,10 @@ func (s *discordTagParser) Trigger() []byte {
|
||||||
return []byte{'<'}
|
return []byte{'<'}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var parserContextPortal = parser.NewContextKey()
|
||||||
|
|
||||||
func (s *discordTagParser) Parse(parent ast.Node, block text.Reader, pc parser.Context) ast.Node {
|
func (s *discordTagParser) Parse(parent ast.Node, block text.Reader, pc parser.Context) ast.Node {
|
||||||
|
portal := pc.Get(parserContextPortal).(*Portal)
|
||||||
//before := block.PrecendingCharacter()
|
//before := block.PrecendingCharacter()
|
||||||
line, _ := block.PeekLine()
|
line, _ := block.PeekLine()
|
||||||
match := discordTagRegex.FindSubmatch(line)
|
match := discordTagRegex.FindSubmatch(line)
|
||||||
|
@ -157,7 +161,7 @@ func (s *discordTagParser) Parse(parent ast.Node, block text.Reader, pc parser.C
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
tag := astDiscordTag{id: id}
|
tag := astDiscordTag{id: id, portal: portal}
|
||||||
tagName := string(match[1])
|
tagName := string(match[1])
|
||||||
switch {
|
switch {
|
||||||
case tagName == "@":
|
case tagName == "@":
|
||||||
|
@ -199,9 +203,9 @@ func (s *discordTagParser) CloseBlock(parent ast.Node, pc parser.Context) {
|
||||||
// nothing to do
|
// nothing to do
|
||||||
}
|
}
|
||||||
|
|
||||||
type discordTagHTMLRenderer struct {
|
type discordTagHTMLRenderer struct{}
|
||||||
portal *Portal
|
|
||||||
}
|
var defaultDiscordTagHTMLRenderer = &discordTagHTMLRenderer{}
|
||||||
|
|
||||||
func (r *discordTagHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
|
func (r *discordTagHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
|
||||||
reg.Register(astKindDiscordTag, r.renderDiscordMention)
|
reg.Register(astKindDiscordTag, r.renderDiscordMention)
|
||||||
|
@ -259,17 +263,17 @@ func (r *discordTagHTMLRenderer) renderDiscordMention(w util.BufWriter, source [
|
||||||
}
|
}
|
||||||
switch node := n.(type) {
|
switch node := n.(type) {
|
||||||
case *astDiscordUserMention:
|
case *astDiscordUserMention:
|
||||||
puppet := r.portal.bridge.GetPuppetByID(strconv.FormatInt(node.id, 10))
|
puppet := node.portal.bridge.GetPuppetByID(strconv.FormatInt(node.id, 10))
|
||||||
_, _ = fmt.Fprintf(w, `<a href="https://matrix.to/#/%s">%s</a>`, puppet.MXID, puppet.Name)
|
_, _ = fmt.Fprintf(w, `<a href="https://matrix.to/#/%s">%s</a>`, puppet.MXID, puppet.Name)
|
||||||
return
|
return
|
||||||
case *astDiscordRoleMention:
|
case *astDiscordRoleMention:
|
||||||
role := r.portal.bridge.DB.Role.GetByID(r.portal.GuildID, strconv.FormatInt(node.id, 10))
|
role := node.portal.bridge.DB.Role.GetByID(node.portal.GuildID, strconv.FormatInt(node.id, 10))
|
||||||
if role != nil {
|
if role != nil {
|
||||||
_, _ = fmt.Fprintf(w, `<font color="#%06x"><strong>@%s</strong></font>`, role.Color, role.Name)
|
_, _ = fmt.Fprintf(w, `<font color="#%06x"><strong>@%s</strong></font>`, role.Color, role.Name)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case *astDiscordChannelMention:
|
case *astDiscordChannelMention:
|
||||||
portal := r.portal.bridge.GetExistingPortalByID(database.PortalKey{
|
portal := node.portal.bridge.GetExistingPortalByID(database.PortalKey{
|
||||||
ChannelID: strconv.FormatInt(node.id, 10),
|
ChannelID: strconv.FormatInt(node.id, 10),
|
||||||
Receiver: "",
|
Receiver: "",
|
||||||
})
|
})
|
||||||
|
@ -282,7 +286,7 @@ func (r *discordTagHTMLRenderer) renderDiscordMention(w util.BufWriter, source [
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case *astDiscordCustomEmoji:
|
case *astDiscordCustomEmoji:
|
||||||
reactionMXC := r.portal.getEmojiMXCByDiscordID(strconv.FormatInt(node.id, 10), node.name, node.animated)
|
reactionMXC := node.portal.getEmojiMXCByDiscordID(strconv.FormatInt(node.id, 10), node.name, node.animated)
|
||||||
if !reactionMXC.IsEmpty() {
|
if !reactionMXC.IsEmpty() {
|
||||||
_, _ = fmt.Fprintf(w, `<img data-mx-emoticon src="%[1]s" alt="%[2]s" title="%[2]s" height="32"/>`, reactionMXC.String(), node.name)
|
_, _ = fmt.Fprintf(w, `<img data-mx-emoticon src="%[1]s" alt="%[2]s" title="%[2]s" height="32"/>`, reactionMXC.String(), node.name)
|
||||||
return
|
return
|
||||||
|
@ -310,15 +314,15 @@ func (r *discordTagHTMLRenderer) renderDiscordMention(w util.BufWriter, source [
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
type DiscordTag struct {
|
type discordTag struct{}
|
||||||
Portal *Portal
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *DiscordTag) Extend(m goldmark.Markdown) {
|
var ExtDiscordTag = &discordTag{}
|
||||||
|
|
||||||
|
func (e *discordTag) Extend(m goldmark.Markdown) {
|
||||||
m.Parser().AddOptions(parser.WithInlineParsers(
|
m.Parser().AddOptions(parser.WithInlineParsers(
|
||||||
util.Prioritized(defaultDiscordTagParser, 600),
|
util.Prioritized(defaultDiscordTagParser, 600),
|
||||||
))
|
))
|
||||||
m.Renderer().AddOptions(renderer.WithNodeRenderers(
|
m.Renderer().AddOptions(renderer.WithNodeRenderers(
|
||||||
util.Prioritized(&discordTagHTMLRenderer{e.Portal}, 600),
|
util.Prioritized(defaultDiscordTagHTMLRenderer, 600),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue