improve and fix telegram instant preview for markdown articles.

This commit is contained in:
fiatjaf
2023-09-14 10:31:38 -03:00
parent 90ec9d92dd
commit b0bda60577
5 changed files with 57 additions and 19 deletions

View File

@@ -3,7 +3,6 @@ package main
import ( import (
"context" "context"
"embed" "embed"
"fmt"
"html" "html"
"net/http" "net/http"
"os" "os"
@@ -39,7 +38,6 @@ func updateArchives(ctx context.Context) {
for { for {
select { select {
case <-ctx.Done(): case <-ctx.Done():
fmt.Println("exit updateArchives gracefully...")
return return
default: default:
loadNpubsArchive(ctx) loadNpubsArchive(ctx)

View File

@@ -213,7 +213,7 @@ func contactsForPubkey(ctx context.Context, pubkey string, extraRelays ...string
pubkeyContacts := make([]string, 0, 100) pubkeyContacts := make([]string, 0, 100)
relays := make([]string, 0, 12) relays := make([]string, 0, 12)
if ok := cache.GetJSON("cc:"+pubkey, &pubkeyContacts); !ok { if ok := cache.GetJSON("cc:"+pubkey, &pubkeyContacts); !ok {
fmt.Printf("Searching contacts for %s\n", pubkey) log.Debug().Msgf("searching contacts for %s", pubkey)
ctx, cancel := context.WithTimeout(ctx, time.Second*3) ctx, cancel := context.WithTimeout(ctx, time.Second*3)
pubkeyRelays := relaysForPubkey(ctx, pubkey, relays...) pubkeyRelays := relaysForPubkey(ctx, pubkey, relays...)

View File

@@ -304,7 +304,7 @@ func render(w http.ResponseWriter, r *http.Request) {
content = strings.ReplaceAll(content, placeholderTag, "nostr:"+nreplace) content = strings.ReplaceAll(content, placeholderTag, "nostr:"+nreplace)
} }
if event.Kind == 30023 || event.Kind == 30024 { if event.Kind == 30023 || event.Kind == 30024 {
content = mdToHTML(content) content = mdToHTML(content, typ == "telegram_instant_view")
} else { } else {
content = renderInlineMentions(basicFormatting(html.EscapeString(content))) content = renderInlineMentions(basicFormatting(html.EscapeString(content)))
} }
@@ -327,6 +327,7 @@ func render(w http.ResponseWriter, r *http.Request) {
"authorLong": authorLong, "authorLong": authorLong,
"subject": subject, "subject": subject,
"description": description, "description": description,
"summary": summary,
"event": event, "event": event,
"eventJSON": string(eventJSON), "eventJSON": string(eventJSON),
"content": content, "content": content,

View File

@@ -6,7 +6,10 @@
<meta property="article:published_time" content="{{.createdAt}}" /> <meta property="article:published_time" content="{{.createdAt}}" />
<!-- stuff that goes in the actual telegram message preview --> <!-- stuff that goes in the actual telegram message preview -->
<meta property="og:site_name" content="{{.authorLong | escapeString}}" /> <meta
property="og:site_name"
content="{{ if .subject }} {{.subject | escapeString}} {{ else }} {{.authorLong | escapeString}} {{ end }}"
/>
{{ if .description }} {{ if .description }}
<meta property="og:description" content="{{.description | escapeString}}" /> <meta property="og:description" content="{{.description | escapeString}}" />
{{ end }} {{ end }}
@@ -28,8 +31,14 @@
<!-- basic content of the preview window --> <!-- basic content of the preview window -->
<article> <article>
<h1> <h1>
{{ if (not (eq .subject ""))}} {{.subject | escapeString}} {{ else }} {{ if .subject }} {{.subject | escapeString}} {{ else }} {{.metadata.Name |
{{.metadata.Name | escapeString}} wrote: {{ end }} escapeString}} wrote: {{ end }}
</h1> </h1>
{{ if .summary }}
<h2>{{ .summary }}</h2>
{{ end }}
<!---->
{{.content}} {{.content}}
</article> </article>

View File

@@ -4,12 +4,15 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io"
"net/http" "net/http"
"regexp" "regexp"
"strings" "strings"
"time" "time"
"github.com/gomarkdown/markdown" "github.com/gomarkdown/markdown"
"github.com/gomarkdown/markdown/ast"
"github.com/gomarkdown/markdown/html"
mdhtml "github.com/gomarkdown/markdown/html" mdhtml "github.com/gomarkdown/markdown/html"
"github.com/gomarkdown/markdown/parser" "github.com/gomarkdown/markdown/parser"
"github.com/microcosm-cc/bluemonday" "github.com/microcosm-cc/bluemonday"
@@ -201,7 +204,7 @@ func getParentNevent(event *nostr.Event) string {
// Rendering functions // Rendering functions
// ### ### ### ### ### ### ### ### ### ### ### // ### ### ### ### ### ### ### ### ### ### ###
func replateImageURLsWithTags(input string, replacement string) string { func replaceImageURLsWithTags(input string, replacement string) string {
// Match and replace image URLs with a custom replacement // Match and replace image URLs with a custom replacement
// Usually is html <img> => ` <img src="%s" alt=""> ` // Usually is html <img> => ` <img src="%s" alt=""> `
// or markdown !()[...] tags for further processing => `![](%s)` // or markdown !()[...] tags for further processing => `![](%s)`
@@ -221,7 +224,7 @@ func replateImageURLsWithTags(input string, replacement string) string {
return input return input
} }
func replateVideoURLsWithTags(input string, replacement string) string { func replaceVideoURLsWithTags(input string, replacement string) string {
// Match and replace video URLs with a custom replacement // Match and replace video URLs with a custom replacement
// Usually is html <video> => ` <video controls width="100%%"><source src="%s"></video> ` // Usually is html <video> => ` <video controls width="100%%"><source src="%s"></video> `
// or markdown !()[...] tags for further processing => `![](%s)` // or markdown !()[...] tags for further processing => `![](%s)`
@@ -287,12 +290,12 @@ func renderInlineMentions(input string) string {
func replaceURLsWithTags(line string) string { func replaceURLsWithTags(line string) string {
var rline string var rline string
rline = replateImageURLsWithTags(line, ` <img src="%s" alt=""> `) rline = replaceImageURLsWithTags(line, ` <img src="%s" alt=""> `)
if rline != line { if rline != line {
return rline return rline
} }
rline = replateVideoURLsWithTags(line, `<video controls width="100%%"><source src="%s"></video>`) rline = replaceVideoURLsWithTags(line, `<video controls width="100%%"><source src="%s"></video>`)
if rline != line { if rline != line {
return rline return rline
} }
@@ -333,24 +336,51 @@ func basicFormatting(input string) string {
return strings.Join(processedLines, "<br/>") return strings.Join(processedLines, "<br/>")
} }
func mdToHTML(md string) string { func mdToHTML(md string, usingTelegramInstantView bool) string {
md = strings.ReplaceAll(md, "\u00A0", " ") md = strings.ReplaceAll(md, "\u00A0", " ")
md = replateImageURLsWithTags(md, `![](%s)`) md = replaceImageURLsWithTags(md, `![](%s)`)
md = replateVideoURLsWithTags(md, `<video controls width="100%%"><source src="%s"></video>`) md = replaceVideoURLsWithTags(md, `<video controls width="100%%"><source src="%s"></video>`)
md = replaceNostrURLsWithTags(md) md = replaceNostrURLsWithTags(md)
// create markdown parser with extensions // create markdown parser with extensions
extensions := parser.CommonExtensions | parser.AutoHeadingIDs | parser.NoEmptyLineBeforeBlock | parser.Footnotes p := parser.NewWithExtensions(
p := parser.NewWithExtensions(extensions) parser.CommonExtensions | parser.AutoHeadingIDs | parser.NoEmptyLineBeforeBlock | parser.Footnotes)
doc := p.Parse([]byte(md)) doc := p.Parse([]byte(md))
var customNodeHook html.RenderNodeFunc = nil
if usingTelegramInstantView {
// telegram instant view really doesn't like when there is an image inside a paragraph (like <p><img></p>)
// so we use this custom thing to stop all paragraphs before the images, print the images then start a new
// paragraph afterwards.
customNodeHook = func(w io.Writer, node ast.Node, entering bool) (ast.WalkStatus, bool) {
if img, ok := node.(*ast.Image); ok {
if entering {
src := img.Destination
w.Write([]byte(`</p><img src="`))
html.EscLink(w, src)
w.Write([]byte(`" alt="`))
} else {
if img.Title != nil {
w.Write([]byte(`" title="`))
html.EscapeHTML(w, img.Title)
}
w.Write([]byte(`" /><p>`))
}
return ast.GoToNext, true
}
return ast.GoToNext, false
}
}
// create HTML renderer with extensions // create HTML renderer with extensions
htmlFlags := mdhtml.CommonFlags | mdhtml.HrefTargetBlank opts := mdhtml.RendererOptions{
opts := mdhtml.RendererOptions{Flags: htmlFlags} Flags: mdhtml.CommonFlags | mdhtml.HrefTargetBlank,
RenderNodeHook: customNodeHook,
}
renderer := mdhtml.NewRenderer(opts) renderer := mdhtml.NewRenderer(opts)
output := string(markdown.Render(doc, renderer)) output := string(markdown.Render(doc, renderer))
// Sanitize content // sanitize content
output = sanitizeXSS(output) output = sanitizeXSS(output)
return output return output