dkforest

A forum and chat platform (onion)
git clone https://git.dasho.dev/n0tr1v/dkforest.git
Log | Files | Refs | LICENSE

commit 345aadaf1235880291ce03aa14f81c4c2382b0da
parent 775b77b2d918a1d249d82ea5ff764fbb3be0aed4
Author: n0tr1v <n0tr1v@protonmail.com>
Date:   Mon,  1 Jan 2024 11:32:41 -0500

move code, better process message

Diffstat:
Mpkg/database/renderer.go | 477++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Mpkg/database/utils/processMessage.go | 426+------------------------------------------------------------------------------
Mpkg/database/utils/processMessage_test.go | 6+++---
Mpkg/web/handlers/handlers.go | 11+++++------
4 files changed, 474 insertions(+), 446 deletions(-)

diff --git a/pkg/database/renderer.go b/pkg/database/renderer.go @@ -3,19 +3,27 @@ package database import ( "dkforest/pkg/bfchroma" bf "dkforest/pkg/blackfriday/v2" + "dkforest/pkg/clockwork" + "dkforest/pkg/config" + "dkforest/pkg/utils" + "fmt" "github.com/alecthomas/chroma/formatters/html" html2 "html" "io" + "net/url" "regexp" "strings" + "time" ) -func MyRenderer(db *DkfDB, withLineNumbers, lineNumbersInTable bool) *Renderer { +func MyRenderer(db *DkfDB, withLineNumbers, lineNumbersInTable bool, roomID RoomID, authUserID UserID) *Renderer { // Defines the HTML rendering flags that are used var flags = bf.UseXHTML | bf.SkipImages r := &Renderer{ - DB: db, + RoomID: roomID, + AuthUserID: authUserID, + DB: db, Base: bfchroma.NewRenderer( bfchroma.WithoutAutodetect(), bfchroma.ChromaOptions( @@ -54,29 +62,468 @@ func MyRendererForum(withLineNumbers, lineNumbersInTable bool) *Renderer { } type Renderer struct { - DB *DkfDB - Base *bfchroma.Renderer + DB *DkfDB + Base *bfchroma.Renderer + RoomID RoomID + AuthUserID UserID } var roomNameF = `\w{3,50}` var roomTagRgx = regexp.MustCompile(`#(` + roomNameF + `)`) +func linkRoomTags(db *DkfDB, html string) string { + if roomTagRgx.MatchString(html) { + html = roomTagRgx.ReplaceAllStringFunc(html, func(s string) string { + if room, err := db.GetChatRoomByName(strings.TrimPrefix(s, "#")); err == nil { + return `<a href="/chat/` + room.Name + `" target="_top">` + s + `</a>` + } + return s + }) + } + return html +} + +func convertBangShortcuts(html string) string { + r := strings.NewReplacer( + "!bhc", config.BhcOnion, + "!cryptbb", config.CryptbbOnion, + "!dread", config.DreadOnion, + "!dkf", config.DkfOnion, + "!rroom", config.DkfOnion+`/red-room`, + "!dnmx", config.DnmxOnion, + "!whonix", config.WhonixOnion, + "!age", config.AgeUrl, + ) + return r.Replace(html) +} + +// Convert timestamps such as 01:23:45 to an archive link if a message with that timestamp exists. +// eg: "Some text 14:31:46 some more text" +func convertArchiveLinks(db *DkfDB, html string, roomID RoomID, authUserID UserID) string { + start, rest := "", html + + // Do not replace timestamps that are inside a quote text + const quoteSuffix = `”` + endOfQuoteIdx := strings.LastIndex(html, quoteSuffix) + if endOfQuoteIdx != -1 { + start, rest = html[:endOfQuoteIdx], html[endOfQuoteIdx:] + } + + archiveRgx := regexp.MustCompile(`(\d{2}-\d{2} )?\d{2}:\d{2}:\d{2}`) + if archiveRgx.MatchString(rest) { + rest = archiveRgx.ReplaceAllStringFunc(rest, func(s string) string { + var dt time.Time + var err error + if len(s) == 8 { // HH:MM:SS + dt, err = utils.ParsePrevDatetimeAt(s, clockwork.NewRealClock()) + } else if len(s) == 14 { // mm-dd HH:MM:SS + dt, err = utils.ParsePrevDatetimeAt2(s, clockwork.NewRealClock()) + } + if err != nil { + return s + } + if msgs, err := db.GetRoomChatMessagesByDate(roomID, dt.UTC()); err == nil && len(msgs) > 0 { + msg := msgs[0] + if len(msgs) > 1 { + for _, msgTmp := range msgs { + if msgTmp.User.ID == authUserID || (msgTmp.ToUserID != nil && *msgTmp.ToUserID == authUserID) { + msg = msgTmp + break + } + } + } + return fmt.Sprintf(`<a href="/chat/%s/archive#%s" target="_blank" rel="noopener noreferrer">%s</a>`, msg.Room.Name, msg.UUID, s) + } + return s + }) + } + return start + rest +} + +var LibredditURLs = []string{ + "http://spjmllawtheisznfs7uryhxumin26ssv2draj7oope3ok3wuhy43eoyd.onion", + "http://fwhhsbrbltmrct5hshrnqlqygqvcgmnek3cnka55zj4y7nuus5muwyyd.onion", + "http://kphht2jcflojtqte4b4kyx7p2ahagv4debjj32nre67dxz7y57seqwyd.onion", + "http://inytumdgnri7xsqtvpntjevaelxtgbjqkuqhtf6txxhwbll2fwqtakqd.onion", + "http://liredejj74h5xjqr2dylnl5howb2bpikfowqoveub55ru27x43357iid.onion", + "http://kzhfp3nvb4qp575vy23ccbrgfocezjtl5dx66uthgrhu7nscu6rcwjyd.onion", + "http://ecue64ybzvn6vjzl37kcsnwt4ycmbsyf74nbttyg7rkc3t3qwnj7mcyd.onion", + "http://ledditqo2mxfvlgobxnlhrkq4dh34jss6evfkdkb2thlvy6dn4f4gpyd.onion", + "http://ol5begilptoou34emq2sshf3may3hlblvipdjtybbovpb7c7zodxmtqd.onion", + "http://lbrdtjaj7567ptdd4rv74lv27qhxfkraabnyphgcvptl64ijx2tijwid.onion", +} + +var InvidiousURLs = []string{ + "http://c7hqkpkpemu6e7emz5b4vyz7idjgdvgaaa3dyimmeojqbgpea3xqjoid.onion", + "http://kbjggqkzv65ivcqj6bumvp337z6264huv5kpkwuv6gu5yjiskvan7fad.onion", + "http://grwp24hodrefzvjjuccrkw3mjq4tzhaaq32amf33dzpmuxe7ilepcmad.onion"} + +var WikilessURLs = []string{ + "http://c2pesewpalbi6lbfc5hf53q4g3ovnxe4s7tfa6k2aqkf7jd7a7dlz5ad.onion", + "http://dj2tbh2nqfxyfmvq33cjmhuw7nb6am7thzd3zsjvizeqf374fixbrxyd.onion"} + +var NitterURLs = []string{ + "http://nitraeju2mipeziu2wtcrqsxg7h62v5y4eqgwi75uprynkj74gevvuqd.onion"} + +var RimgoURLs = []string{ + "http://be7udfhmnzqyt7cxysg6c4pbawarvaofjjywp35nhd5qamewdfxl6sid.onion"} + +func convertLinks(in string, + roomID RoomID, + getUserByUsername func(Username) (User, error), + getLinkByShorthand func(string) (Link, error), + getChatMessageByUUID func(string) (ChatMessage, error)) string { + quote, rest := splitQuote(in) + + knownOnions := [][]string{ + {"http://git.dkf.onion", config.DkfGitOnion}, + {"http://dkfgit.onion", config.DkfGit1Onion}, + {"http://dread.onion", config.DreadOnion}, + {"http://cryptbb.onion", config.CryptbbOnion}, + {"http://blkhat.onion", config.BhcOnion}, + {"http://dnmx.onion", config.DnmxOnion}, + {"http://whonix.onion", config.WhonixOnion}, + } + + newRest := linkOrProfileRgx.ReplaceAllStringFunc(rest, func(link string) string { + // Convert all occurrences of "/u/username" to a link to user profile page if the user exists + if userProfileLinkRgx.MatchString(link) { + user, err := getUserByUsername(Username(strings.TrimPrefix(link, "/u/"))) + if err != nil { + return link + } + href := "/u/" + string(user.Username) + return makeHtmlLink(href, href) + } + + // Convert all occurrences of "/l/shorthand" to a link to link page if the shorthand exists + if linkShorthandPageLinkRgx.MatchString(link) { + l, err := getLinkByShorthand(strings.TrimPrefix(link, "/l/")) + if err != nil { + return link + } + href := "/l/" + *l.Shorthand + return makeHtmlLink(href, href) + } + + // Handle reddit links + if strings.HasPrefix(link, "https://www.reddit.com/") { + old := strings.Replace(link, "https://www.reddit.com/", "https://old.reddit.com/", 1) + libredditLink := "/external-link/libreddit/" + url.PathEscape(strings.TrimPrefix(link, "https://www.reddit.com/")) + oldHtmlLink := makeHtmlLink("old", old) + libredditHtmlLink := makeHtmlLink("libredditLink", libredditLink) + htmlLink := makeHtmlLink(link, link) + return htmlLink + ` (` + oldHtmlLink + ` | ` + libredditHtmlLink + `)` + } else if strings.HasPrefix(link, "https://old.reddit.com/") { + libredditLink := "/external-link/libreddit/" + url.PathEscape(strings.TrimPrefix(link, "https://old.reddit.com/")) + libredditHtmlLink := makeHtmlLink("libredditLink", libredditLink) + htmlLink := makeHtmlLink(link, link) + return htmlLink + ` (` + libredditHtmlLink + `)` + } + for _, libredditURL := range LibredditURLs { + if strings.HasPrefix(link, libredditURL) { + newPrefix := strings.Replace(link, libredditURL, "http://reddit.onion", 1) + old := strings.Replace(link, libredditURL, "https://old.reddit.com", 1) + oldHtmlLink := makeHtmlLink("old", old) + htmlLink := makeHtmlLink(newPrefix, link) + return htmlLink + ` (` + oldHtmlLink + `)` + } + } + + // Append YouTube link to invidious link + for _, invidiousURL := range InvidiousURLs { + if strings.HasPrefix(link, invidiousURL) { + if strings.Contains(link, ".onion/watch?v=") { + newPrefix := strings.Replace(link, invidiousURL, "http://invidious.onion", 1) + m := invidiousIDRgx.FindStringSubmatch(link) + if len(m) == 2 { + videoID := m[1] + youtubeLink := "https://www.youtube.com/watch?v=" + videoID + youtubeHtmlLink := makeHtmlLink("Youtube", youtubeLink) + htmlLink := makeHtmlLink(newPrefix, link) + return htmlLink + ` (` + youtubeHtmlLink + `)` + } + } + } + } + // Unknown invidious links + if strings.Contains(link, ".onion/watch?v=") { + m := invidiousIDRgx.FindStringSubmatch(link) + if len(m) == 2 { + videoID := m[1] + youtubeLink := "https://www.youtube.com/watch?v=" + videoID + youtubeHtmlLink := makeHtmlLink("Youtube", youtubeLink) + htmlLink := makeHtmlLink(link, link) + return htmlLink + ` (` + youtubeHtmlLink + `)` + } + } + + // Append wikiless link to wikipedia link + if strings.HasPrefix(link, "https://en.wikipedia.org/") { + wikilessLink := "/external-link/nitter/" + url.PathEscape(strings.TrimPrefix(link, "https://en.wikipedia.org/")) + wikilessHtmlLink := makeHtmlLink("Wikiless", wikilessLink) + htmlLink := makeHtmlLink(link, link) + return htmlLink + ` (` + wikilessHtmlLink + `)` + } + for _, wikilessURL := range WikilessURLs { + if strings.HasPrefix(link, wikilessURL) { + newPrefix := strings.Replace(link, wikilessURL, "http://wikiless.onion", 1) + wikipediaPrefix := strings.Replace(link, wikilessURL, "https://en.wikipedia.org", 1) + wikipediaHtmlLink := makeHtmlLink("Wikipedia", wikipediaPrefix) + htmlLink := makeHtmlLink(newPrefix, link) + return htmlLink + ` (` + wikipediaHtmlLink + `)` + } + } + + // Append nitter link to twitter link + if strings.HasPrefix(link, "https://twitter.com/") { + nitterLink := "/external-link/nitter/" + url.PathEscape(strings.TrimPrefix(link, "https://twitter.com/")) + nitterHtmlLink := makeHtmlLink("Nitter", nitterLink) + htmlLink := makeHtmlLink(link, link) + return htmlLink + ` (` + nitterHtmlLink + `)` + } + for _, nitterURL := range NitterURLs { + if strings.HasPrefix(link, nitterURL) { + newPrefix := strings.Replace(link, nitterURL, "http://nitter.onion", 1) + twitterPrefix := strings.Replace(link, nitterURL, "https://twitter.com", 1) + twitterHtmlLink := makeHtmlLink("Twitter", twitterPrefix) + htmlLink := makeHtmlLink(newPrefix, link) + return htmlLink + ` (` + twitterHtmlLink + `)` + } + } + + // Append rimgo link to imgur link + if strings.HasPrefix(link, "https://imgur.com/") { + rimgoLink := "/external-link/rimgo/" + url.PathEscape(strings.TrimPrefix(link, "https://imgur.com/")) + rimgoHtmlLink := makeHtmlLink("Rimgo", rimgoLink) + htmlLink := makeHtmlLink(link, link) + return htmlLink + ` (` + rimgoHtmlLink + `)` + } + for _, rimgoURL := range RimgoURLs { + if strings.HasPrefix(link, rimgoURL) { + newPrefix := strings.Replace(link, rimgoURL, "http://rimgo.onion", 1) + imgurPrefix := strings.Replace(link, rimgoURL, "https://imgur.com", 1) + imgurHtmlLink := makeHtmlLink("Imgur", imgurPrefix) + htmlLink := makeHtmlLink(newPrefix, link) + return htmlLink + ` (` + imgurHtmlLink + `)` + } + } + + // Append invidious link to YouTube/yewtube link + var videoID string + var m []string + var isShortUrl, isYewtube bool + if strings.HasPrefix(link, "https://youtu.be/") { + m = youtuBeIDRgx.FindStringSubmatch(link) + } else if strings.HasPrefix(link, "https://www.youtube.com/watch?v=") { + m = youtubeComIDRgx.FindStringSubmatch(link) + } else if strings.HasPrefix(link, "https://yewtu.be/") || strings.HasPrefix(link, "https://www.yewtu.be/") { + m = yewtubeBeIDRgx.FindStringSubmatch(link) + isYewtube = true + } else if strings.HasPrefix(link, "https://www.youtube.com/shorts/") { + m = youtubeComShortsIDRgx.FindStringSubmatch(link) + isShortUrl = true + } + if len(m) == 2 { + videoID = m[1] + } + if videoID != "" { + invidiousLink := "/external-link/invidious/" + url.PathEscape("watch?v="+videoID+"&local=true") + invidiousHtmlLink := makeHtmlLink("Invidious", invidiousLink) + htmlLink := makeHtmlLink(link, link) + youtubeLink := "https://www.youtube.com/watch?v=" + videoID + youtubeHtmlLink := makeHtmlLink("YT", youtubeLink) + out := htmlLink + ` (` + invidiousHtmlLink + `)` + if isShortUrl || isYewtube { + out = htmlLink + ` (` + youtubeHtmlLink + ` | ` + invidiousHtmlLink + `)` + } + return out + } + + // Special case for dkf links. + { + dkfLocalPrefix := "http://127.0.0.1:8080" + dkfShortPrefix := "http://dkf.onion" + dkfLongPrefix := config.DkfOnion + hasLocalPrefix := strings.HasPrefix(link, dkfLocalPrefix) + hasDkfShortPrefix := strings.HasPrefix(link, dkfShortPrefix) + hasDkfLongPrefix := strings.HasPrefix(link, dkfLongPrefix) + if hasLocalPrefix || hasDkfLongPrefix || hasDkfShortPrefix { + var trimmed string + if hasLocalPrefix { + trimmed = strings.TrimPrefix(link, dkfLocalPrefix) + } else if hasDkfLongPrefix { + trimmed = strings.TrimPrefix(link, dkfLongPrefix) + } else if hasDkfShortPrefix { + trimmed = strings.TrimPrefix(link, dkfShortPrefix) + } + label := dkfShortPrefix + trimmed + href := trimmed + // Shorten archive links + if m := dkfArchiveRgx.FindStringSubmatch(label); len(m) == 3 { + if msg, err := getChatMessageByUUID(m[2]); err == nil { + if roomID == msg.RoomID { + label = msg.CreatedAt.Format("[Jan 02 03:04:05]") + } else { + label = msg.CreatedAt.Format("[#" + m[1] + " Jan 02 03:04:05]") + } + } + } + // Allows to have messages such as: "my profile is /u/username :)" + if userProfileLinkRgx.MatchString(trimmed) { + if user, err := getUserByUsername(Username(strings.TrimPrefix(trimmed, "/u/"))); err == nil { + label = "/u/" + string(user.Username) + href = "/u/" + string(user.Username) + } + } else if linkShorthandPageLinkRgx.MatchString(trimmed) { + // Convert all occurrences of "/l/shorthand" to a link to link page if the shorthand exists + if l, err := getLinkByShorthand(strings.TrimPrefix(trimmed, "/l/")); err == nil { + label = "/l/" + *l.Shorthand + href = "/l/" + *l.Shorthand + } + } + return makeHtmlLink(label, href) + } + } + + for _, el := range knownOnions { + shortPrefix := el[0] + longPrefix := el[1] + if strings.HasPrefix(link, longPrefix) { + return makeHtmlLink(shortPrefix+strings.TrimPrefix(link, longPrefix), link) + } else if strings.HasPrefix(link, shortPrefix) { + return makeHtmlLink(link, longPrefix+strings.TrimPrefix(link, shortPrefix)) + } + } + return makeHtmlLink(link, link) + }) + + return quote + newRest +} + +var linkRgxStr = `(http|ftp|https):\/\/([\w\-_]+(?:(?:\.[\w\-_]+)+))([\w\-\.,@?^=%&amp;:/~\+#\(\)]*[\w\-\@?^=%&amp;/~\+#\(\)])?` +var profileRgxStr = `/u/\w{3,20}` +var linkShorthandRgxStr = `/l/\w{3,20}` +var dkfArchiveRgx = regexp.MustCompile(`/chat/([\w_]{3,50})/archive\?uuid=([\w-]{36})#[\w-]{36}`) +var linkOrProfileRgx = regexp.MustCompile(`(` + linkRgxStr + `|` + profileRgxStr + `|` + linkShorthandRgxStr + `)`) +var userProfileLinkRgx = regexp.MustCompile(`^` + profileRgxStr + `$`) +var linkShorthandPageLinkRgx = regexp.MustCompile(`^` + linkShorthandRgxStr + `$`) +var youtubeComIDRgx = regexp.MustCompile(`watch\?v=([\w-]+)`) +var youtubeComShortsIDRgx = regexp.MustCompile(`/shorts/([\w-]+)`) +var youtuBeIDRgx = regexp.MustCompile(`https://youtu\.be/([\w-]+)`) +var yewtubeBeIDRgx = youtubeComIDRgx +var invidiousIDRgx = youtubeComIDRgx + +func makeHtmlLink(label, link string) string { + // We replace @ to prevent ColorifyTaggedUsers from trying to generate html inside the links. + r := strings.NewReplacer("@", "&#64;", "#", "&#35;") + label = r.Replace(label) + link = r.Replace(link) + return fmt.Sprintf(`<a href="%s" rel="noopener noreferrer" target="_blank">%s</a>`, link, label) +} + +func splitQuote(in string) (string, string) { + const quotePrefix = `<p>“[` + const quoteSuffix = `”` + idx := strings.Index(in, quoteSuffix) + if idx == -1 || !strings.HasPrefix(in, quotePrefix) { + return "", in + } + return in[:idx], in[idx:] +} + +func linkDefaultRooms(html string) string { + r := strings.NewReplacer( + "#general", `<a href="/chat/general" target="_top">#general</a>`, + "#programming", `<a href="/chat/programming" target="_top">#programming</a>`, + "#hacking", `<a href="/chat/hacking" target="_top">#hacking</a>`, + "#suggestions", `<a href="/chat/suggestions" target="_top">#suggestions</a>`, + "#announcements", `<a href="/chat/announcements" target="_top">#announcements</a>`, + ) + return r.Replace(html) +} + +var emojiReplacer = strings.NewReplacer( + ":):", `<span class="emoji" title=":):">☺</span>`, + ":smile:", `<span class="emoji" title=":smile:">☺</span>`, + ":happy:", `<span class="emoji" title=":happy:">😃</span>`, + ":see-no-evil:", `<span class="emoji" title=":see-no-evil:">🙈</span>`, + ":hear-no-evil:", `<span class="emoji" title=":hear-no-evil:">🙉</span>`, + ":speak-no-evil:", `<span class="emoji" title=":speak-no-evil:">🙊</span>`, + ":poop:", `<span class="emoji" title=":poop:">💩</span>`, + ":+1:", `<span class="emoji" title=":+1:">👍</span>`, + ":evil:", `<span class="emoji" title=":evil:">😈</span>`, + ":cat-happy:", `<span class="emoji" title=":cat-happy:">😸</span>`, + ":eyes:", `<span class="emoji" title=":eyes:">👀</span>`, + ":wave:", `<span class="emoji" title=":wave:">👋</span>`, + ":clap:", `<span class="emoji" title=":clap:">👏</span>`, + ":fire:", `<span class="emoji" title=":fire:">🔥</span>`, + ":sparkles:", `<span class="emoji" title=":sparkles:">✨</span>`, + ":sweat:", `<span class="emoji" title=":sweat:">💦</span>`, + ":heart:", `<span class="emoji" title=":heart:">❤</span>`, + ":broken-heart:", `<span class="emoji" title=":broken-heart:">💔</span>`, + ":anatomical-heart:", `<span class="emoji" title=":anatomical-heart:">🫀</span>`, + ":zzz:", `<span class="emoji" title=":zzz:">💤</span>`, + ":praise:", `<span class="emoji" title=":praise:">🙌</span>`, + ":joy:", `<span class="emoji" title=":joy:">😂</span>`, + ":sob:", `<span class="emoji" title=":sob:">😭</span>`, + ":pleading-face:", `<span class="emoji" title=":pleading-face:">🥺</span>`, + ":shush:", `<span class="emoji" title=":shush:">🤫</span>`, + ":scream:", `<span class="emoji" title=":scream:">😱</span>`, + ":heart-eyes:", `<span class="emoji" title=":heart-eyes:">😍</span>`, + ":blush:", `<span class="emoji" title=":blush:">☺</span>`, + ":crazy:", `<span class="emoji" title=":crazy:">😜</span>`, + ":angry:", `<span class="emoji" title=":angry:">😡</span>`, + ":triumph:", `<span class="emoji" title=":triumph:">😤</span>`, + ":vomit:", `<span class="emoji" title=":vomit:">🤮</span>`, + ":skull:", `<span class="emoji" title=":skull:">💀</span>`, + ":alien:", `<span class="emoji" title=":alien:">👽</span>`, + ":sleeping:", `<span class="emoji" title=":sleeping:">😴</span>`, + ":tongue:", `<span class="emoji" title=":tongue:">😛</span>`, + ":cool:", `<span class="emoji" title=":cool:">😎</span>`, + ":wink:", `<span class="emoji" title=":wink:">😉</span>`, + ":thinking:", `<span class="emoji" title=":thinking:">🤔</span>`, + ":happy-sweat:", `<span class="emoji" title=":happy-sweat:">😅</span>`, + ":nerd:", `<span class="emoji" title=":nerd:">🤓</span>`, + ":money-mouth:", `<span class="emoji" title=":money-mouth:">🤑</span>`, + ":fox:", `<span class="emoji" title=":fox:">🦊</span>`, + ":popcorn:", `<span class="emoji" title=":popcorn:">🍿</span>`, + ":money-bag:", `<span class="emoji" title=":money-bag:">💰</span>`, + ":facepalm:", `<span class="emoji" title=":facepalm:">🤦</span>`, + ":lungs:", `<span class="emoji" title=":lungs:">🫁</span>`, + ":shrug:", `¯\_(ツ)_/¯`, + ":flip:", `(╯°□°)╯︵ ┻━┻`, + ":flip-all:", `┻━┻︵ \(°□°)/ ︵ ┻━┻`, + ":fix-table:", `(ヘ・_・)ヘ┳━┳`, + ":disap:", `ಠ_ಠ`, +) + +var noSchemeOnionLinkRgx = regexp.MustCompile(`\s[a-z2-7]{56}\.onion`) + +// Fix up onion links that are missing the http scheme. This often happen when copy/pasting a link. +func convertLinksWithoutScheme(in string) string { + out := noSchemeOnionLinkRgx.ReplaceAllStringFunc(in, func(s string) string { + return " http://" + strings.TrimSpace(s) + }) + return out +} + func (r Renderer) RenderNode(w io.Writer, node *bf.Node, entering bool) bf.WalkStatus { switch node.Type { case bf.Text: if node.Parent.Type != bf.Link { - node.Literal = []byte(html2.UnescapeString(string(node.Literal))) + out := string(node.Literal) + out = linkRoomTags(r.DB, out) + out = convertBangShortcuts(out) + out = convertArchiveLinks(r.DB, out, r.RoomID, r.AuthUserID) + out = convertLinks(out, r.RoomID, r.DB.GetUserByUsername, r.DB.GetLinkByShorthand, r.DB.GetChatMessageByUUID) + out = linkDefaultRooms(out) + out = emojiReplacer.Replace(out) + out = convertLinksWithoutScheme(out) - if roomTagRgx.MatchString(string(node.Literal)) { - node.Literal = []byte(roomTagRgx.ReplaceAllStringFunc(string(node.Literal), func(s string) string { - if room, err := r.DB.GetChatRoomByName(strings.TrimPrefix(s, "#")); err == nil { - return `<a href="/chat/` + room.Name + `" target="_top">` + s + `</a>` - } - return s - })) - _, _ = w.Write(node.Literal) - return bf.GoToNext - } + node.Literal = []byte(out) + _, _ = w.Write(node.Literal) + return bf.GoToNext } case bf.Code: node.Literal = []byte(html2.UnescapeString(string(node.Literal))) diff --git a/pkg/database/utils/processMessage.go b/pkg/database/utils/processMessage.go @@ -14,10 +14,8 @@ import ( "github.com/microcosm-cc/bluemonday" html2 "html" "math" - "net/url" "regexp" "strings" - "time" ) const ( @@ -31,61 +29,6 @@ const ( pgpSignedSuffix = "-----END PGP SIGNATURE-----" ) -var emojiReplacer = strings.NewReplacer( - ":):", `<span class="emoji" title=":):">☺</span>`, - ":smile:", `<span class="emoji" title=":smile:">☺</span>`, - ":happy:", `<span class="emoji" title=":happy:">😃</span>`, - ":see-no-evil:", `<span class="emoji" title=":see-no-evil:">🙈</span>`, - ":hear-no-evil:", `<span class="emoji" title=":hear-no-evil:">🙉</span>`, - ":speak-no-evil:", `<span class="emoji" title=":speak-no-evil:">🙊</span>`, - ":poop:", `<span class="emoji" title=":poop:">💩</span>`, - ":+1:", `<span class="emoji" title=":+1:">👍</span>`, - ":evil:", `<span class="emoji" title=":evil:">😈</span>`, - ":cat-happy:", `<span class="emoji" title=":cat-happy:">😸</span>`, - ":eyes:", `<span class="emoji" title=":eyes:">👀</span>`, - ":wave:", `<span class="emoji" title=":wave:">👋</span>`, - ":clap:", `<span class="emoji" title=":clap:">👏</span>`, - ":fire:", `<span class="emoji" title=":fire:">🔥</span>`, - ":sparkles:", `<span class="emoji" title=":sparkles:">✨</span>`, - ":sweat:", `<span class="emoji" title=":sweat:">💦</span>`, - ":heart:", `<span class="emoji" title=":heart:">❤</span>`, - ":broken-heart:", `<span class="emoji" title=":broken-heart:">💔</span>`, - ":anatomical-heart:", `<span class="emoji" title=":anatomical-heart:">🫀</span>`, - ":zzz:", `<span class="emoji" title=":zzz:">💤</span>`, - ":praise:", `<span class="emoji" title=":praise:">🙌</span>`, - ":joy:", `<span class="emoji" title=":joy:">😂</span>`, - ":sob:", `<span class="emoji" title=":sob:">😭</span>`, - ":pleading-face:", `<span class="emoji" title=":pleading-face:">🥺</span>`, - ":shush:", `<span class="emoji" title=":shush:">🤫</span>`, - ":scream:", `<span class="emoji" title=":scream:">😱</span>`, - ":heart-eyes:", `<span class="emoji" title=":heart-eyes:">😍</span>`, - ":blush:", `<span class="emoji" title=":blush:">☺</span>`, - ":crazy:", `<span class="emoji" title=":crazy:">😜</span>`, - ":angry:", `<span class="emoji" title=":angry:">😡</span>`, - ":triumph:", `<span class="emoji" title=":triumph:">😤</span>`, - ":vomit:", `<span class="emoji" title=":vomit:">🤮</span>`, - ":skull:", `<span class="emoji" title=":skull:">💀</span>`, - ":alien:", `<span class="emoji" title=":alien:">👽</span>`, - ":sleeping:", `<span class="emoji" title=":sleeping:">😴</span>`, - ":tongue:", `<span class="emoji" title=":tongue:">😛</span>`, - ":cool:", `<span class="emoji" title=":cool:">😎</span>`, - ":wink:", `<span class="emoji" title=":wink:">😉</span>`, - ":thinking:", `<span class="emoji" title=":thinking:">🤔</span>`, - ":happy-sweat:", `<span class="emoji" title=":happy-sweat:">😅</span>`, - ":nerd:", `<span class="emoji" title=":nerd:">🤓</span>`, - ":money-mouth:", `<span class="emoji" title=":money-mouth:">🤑</span>`, - ":fox:", `<span class="emoji" title=":fox:">🦊</span>`, - ":popcorn:", `<span class="emoji" title=":popcorn:">🍿</span>`, - ":money-bag:", `<span class="emoji" title=":money-bag:">💰</span>`, - ":facepalm:", `<span class="emoji" title=":facepalm:">🤦</span>`, - ":lungs:", `<span class="emoji" title=":lungs:">🫁</span>`, - ":shrug:", `¯\_(ツ)_/¯`, - ":flip:", `(╯°□°)╯︵ ┻━┻`, - ":flip-all:", `┻━┻︵ \(°□°)/ ︵ ┻━┻`, - ":fix-table:", `(ヘ・_・)ヘ┳━┳`, - ":disap:", `ಠ_ಠ`, -) - var usernameF = `\w{3,20}` // username (regex Fragment) var roomNameF = `\w{3,50}` var userOr0 = usernameF + `|0` @@ -93,7 +36,6 @@ var optAtGUserOr0 = `@?(` + userOr0 + `)` // Optional @, Grouped, Username or 0 var pmRgx = regexp.MustCompile(`^/pm ` + optAtGUserOr0 + `(?:\s(?s:(.*)))?`) var tagRgx = regexp.MustCompile(`(?:\\?)@(` + userOr0 + `)`) var roomTagRgx = regexp.MustCompile(`#(` + roomNameF + `)`) -var noSchemeOnionLinkRgx = regexp.MustCompile(`\s[a-z2-7]{56}\.onion`) var msgPolicy = bluemonday.NewPolicy(). AllowElements("a", "p", "span", "strong", "del", "code", "pre", "em", "ul", "li", "br", "small", "i"). @@ -114,14 +56,9 @@ func ProcessRawMessage(db *database.DkfDB, in, roomKey string, authUserID databa html = convertPGPMessageToFile(db, html, authUserID) html = convertPGPPublicKeyToFile(db, html, authUserID) html = convertAgeMessageToFile(db, html, authUserID) - html = convertLinksWithoutScheme(html) - html = convertMarkdown(db, html, canUseMultiline, manualML) - html = convertBangShortcuts(html) - html = convertArchiveLinks(db, html, roomID, authUserID) - html = convertLinks(html, roomID, db.GetUserByUsername, db.GetLinkByShorthand, db.GetChatMessageByUUID) - html = linkDefaultRooms(html) + html = convertMarkdown(db, html, canUseMultiline, manualML, roomID, authUserID) + html, taggedUsersIDsMap := ColorifyTaggedUsers(html, db.GetUsersByUsername) - html = emojiReplacer.Replace(html) html = styleQuote(html, quoted) html = appendUploadLink(html, upload) if quoted != nil { // Add quoted message owner for inboxes @@ -389,14 +326,6 @@ func convertInlinePGPPublicKey(inlinePKey string) string { return inlinePKey } -// Fix up onion links that are missing the http scheme. This often happen when copy/pasting a link. -func convertLinksWithoutScheme(in string) string { - html := noSchemeOnionLinkRgx.ReplaceAllStringFunc(in, func(s string) string { - return " http://" + strings.TrimSpace(s) - }) - return html -} - var linkRgxStr = `(http|ftp|https):\/\/([\w\-_]+(?:(?:\.[\w\-_]+)+))([\w\-\.,@?^=%&amp;:/~\+#\(\)]*[\w\-\@?^=%&amp;/~\+#\(\)])?` var profileRgxStr = `/u/\w{3,20}` var linkShorthandRgxStr = `/l/\w{3,20}` @@ -410,285 +339,6 @@ var youtuBeIDRgx = regexp.MustCompile(`https://youtu\.be/([\w-]+)`) var yewtubeBeIDRgx = youtubeComIDRgx var invidiousIDRgx = youtubeComIDRgx -func makeHtmlLink(label, link string) string { - // We replace @ to prevent ColorifyTaggedUsers from trying to generate html inside the links. - r := strings.NewReplacer("@", "&#64;", "#", "&#35;") - label = r.Replace(label) - link = r.Replace(link) - return fmt.Sprintf(`<a href="%s" rel="noopener noreferrer" target="_blank">%s</a>`, link, label) -} - -func splitQuote(in string) (string, string) { - const quotePrefix = `<p>“[` - const quoteSuffix = `”` - idx := strings.Index(in, quoteSuffix) - if idx == -1 || !strings.HasPrefix(in, quotePrefix) { - return "", in - } - return in[:idx], in[idx:] -} - -var LibredditURLs = []string{ - "http://spjmllawtheisznfs7uryhxumin26ssv2draj7oope3ok3wuhy43eoyd.onion", - "http://fwhhsbrbltmrct5hshrnqlqygqvcgmnek3cnka55zj4y7nuus5muwyyd.onion", - "http://kphht2jcflojtqte4b4kyx7p2ahagv4debjj32nre67dxz7y57seqwyd.onion", - "http://inytumdgnri7xsqtvpntjevaelxtgbjqkuqhtf6txxhwbll2fwqtakqd.onion", - "http://liredejj74h5xjqr2dylnl5howb2bpikfowqoveub55ru27x43357iid.onion", - "http://kzhfp3nvb4qp575vy23ccbrgfocezjtl5dx66uthgrhu7nscu6rcwjyd.onion", - "http://ecue64ybzvn6vjzl37kcsnwt4ycmbsyf74nbttyg7rkc3t3qwnj7mcyd.onion", - "http://ledditqo2mxfvlgobxnlhrkq4dh34jss6evfkdkb2thlvy6dn4f4gpyd.onion", - "http://ol5begilptoou34emq2sshf3may3hlblvipdjtybbovpb7c7zodxmtqd.onion", - "http://lbrdtjaj7567ptdd4rv74lv27qhxfkraabnyphgcvptl64ijx2tijwid.onion", -} - -var InvidiousURLs = []string{ - "http://c7hqkpkpemu6e7emz5b4vyz7idjgdvgaaa3dyimmeojqbgpea3xqjoid.onion", - "http://kbjggqkzv65ivcqj6bumvp337z6264huv5kpkwuv6gu5yjiskvan7fad.onion", - "http://grwp24hodrefzvjjuccrkw3mjq4tzhaaq32amf33dzpmuxe7ilepcmad.onion"} - -var WikilessURLs = []string{ - "http://c2pesewpalbi6lbfc5hf53q4g3ovnxe4s7tfa6k2aqkf7jd7a7dlz5ad.onion", - "http://dj2tbh2nqfxyfmvq33cjmhuw7nb6am7thzd3zsjvizeqf374fixbrxyd.onion"} - -var NitterURLs = []string{ - "http://nitraeju2mipeziu2wtcrqsxg7h62v5y4eqgwi75uprynkj74gevvuqd.onion"} - -var RimgoURLs = []string{ - "http://be7udfhmnzqyt7cxysg6c4pbawarvaofjjywp35nhd5qamewdfxl6sid.onion"} - -func convertLinks(in string, - roomID database.RoomID, - getUserByUsername func(database.Username) (database.User, error), - getLinkByShorthand func(string) (database.Link, error), - getChatMessageByUUID func(string) (database.ChatMessage, error)) string { - quote, rest := splitQuote(in) - - knownOnions := [][]string{ - {"http://git.dkf.onion", config.DkfGitOnion}, - {"http://dkfgit.onion", config.DkfGit1Onion}, - {"http://dread.onion", config.DreadOnion}, - {"http://cryptbb.onion", config.CryptbbOnion}, - {"http://blkhat.onion", config.BhcOnion}, - {"http://dnmx.onion", config.DnmxOnion}, - {"http://whonix.onion", config.WhonixOnion}, - } - - newRest := linkOrProfileRgx.ReplaceAllStringFunc(rest, func(link string) string { - // Convert all occurrences of "/u/username" to a link to user profile page if the user exists - if userProfileLinkRgx.MatchString(link) { - user, err := getUserByUsername(database.Username(strings.TrimPrefix(link, "/u/"))) - if err != nil { - return link - } - href := "/u/" + string(user.Username) - return makeHtmlLink(href, href) - } - - // Convert all occurrences of "/l/shorthand" to a link to link page if the shorthand exists - if linkShorthandPageLinkRgx.MatchString(link) { - l, err := getLinkByShorthand(strings.TrimPrefix(link, "/l/")) - if err != nil { - return link - } - href := "/l/" + *l.Shorthand - return makeHtmlLink(href, href) - } - - // Handle reddit links - if strings.HasPrefix(link, "https://www.reddit.com/") { - old := strings.Replace(link, "https://www.reddit.com/", "https://old.reddit.com/", 1) - libredditLink := "/external-link/libreddit/" + url.PathEscape(strings.TrimPrefix(link, "https://www.reddit.com/")) - oldHtmlLink := makeHtmlLink("old", old) - libredditHtmlLink := makeHtmlLink("libredditLink", libredditLink) - htmlLink := makeHtmlLink(link, link) - return htmlLink + ` (` + oldHtmlLink + ` | ` + libredditHtmlLink + `)` - } else if strings.HasPrefix(link, "https://old.reddit.com/") { - libredditLink := "/external-link/libreddit/" + url.PathEscape(strings.TrimPrefix(link, "https://old.reddit.com/")) - libredditHtmlLink := makeHtmlLink("libredditLink", libredditLink) - htmlLink := makeHtmlLink(link, link) - return htmlLink + ` (` + libredditHtmlLink + `)` - } - for _, libredditURL := range LibredditURLs { - if strings.HasPrefix(link, libredditURL) { - newPrefix := strings.Replace(link, libredditURL, "http://reddit.onion", 1) - old := strings.Replace(link, libredditURL, "https://old.reddit.com", 1) - oldHtmlLink := makeHtmlLink("old", old) - htmlLink := makeHtmlLink(newPrefix, link) - return htmlLink + ` (` + oldHtmlLink + `)` - } - } - - // Append YouTube link to invidious link - for _, invidiousURL := range InvidiousURLs { - if strings.HasPrefix(link, invidiousURL) { - if strings.Contains(link, ".onion/watch?v=") { - newPrefix := strings.Replace(link, invidiousURL, "http://invidious.onion", 1) - m := invidiousIDRgx.FindStringSubmatch(link) - if len(m) == 2 { - videoID := m[1] - youtubeLink := "https://www.youtube.com/watch?v=" + videoID - youtubeHtmlLink := makeHtmlLink("Youtube", youtubeLink) - htmlLink := makeHtmlLink(newPrefix, link) - return htmlLink + ` (` + youtubeHtmlLink + `)` - } - } - } - } - // Unknown invidious links - if strings.Contains(link, ".onion/watch?v=") { - m := invidiousIDRgx.FindStringSubmatch(link) - if len(m) == 2 { - videoID := m[1] - youtubeLink := "https://www.youtube.com/watch?v=" + videoID - youtubeHtmlLink := makeHtmlLink("Youtube", youtubeLink) - htmlLink := makeHtmlLink(link, link) - return htmlLink + ` (` + youtubeHtmlLink + `)` - } - } - - // Append wikiless link to wikipedia link - if strings.HasPrefix(link, "https://en.wikipedia.org/") { - wikilessLink := "/external-link/nitter/" + url.PathEscape(strings.TrimPrefix(link, "https://en.wikipedia.org/")) - wikilessHtmlLink := makeHtmlLink("Wikiless", wikilessLink) - htmlLink := makeHtmlLink(link, link) - return htmlLink + ` (` + wikilessHtmlLink + `)` - } - for _, wikilessURL := range WikilessURLs { - if strings.HasPrefix(link, wikilessURL) { - newPrefix := strings.Replace(link, wikilessURL, "http://wikiless.onion", 1) - wikipediaPrefix := strings.Replace(link, wikilessURL, "https://en.wikipedia.org", 1) - wikipediaHtmlLink := makeHtmlLink("Wikipedia", wikipediaPrefix) - htmlLink := makeHtmlLink(newPrefix, link) - return htmlLink + ` (` + wikipediaHtmlLink + `)` - } - } - - // Append nitter link to twitter link - if strings.HasPrefix(link, "https://twitter.com/") { - nitterLink := "/external-link/nitter/" + url.PathEscape(strings.TrimPrefix(link, "https://twitter.com/")) - nitterHtmlLink := makeHtmlLink("Nitter", nitterLink) - htmlLink := makeHtmlLink(link, link) - return htmlLink + ` (` + nitterHtmlLink + `)` - } - for _, nitterURL := range NitterURLs { - if strings.HasPrefix(link, nitterURL) { - newPrefix := strings.Replace(link, nitterURL, "http://nitter.onion", 1) - twitterPrefix := strings.Replace(link, nitterURL, "https://twitter.com", 1) - twitterHtmlLink := makeHtmlLink("Twitter", twitterPrefix) - htmlLink := makeHtmlLink(newPrefix, link) - return htmlLink + ` (` + twitterHtmlLink + `)` - } - } - - // Append rimgo link to imgur link - if strings.HasPrefix(link, "https://imgur.com/") { - rimgoLink := "/external-link/rimgo/" + url.PathEscape(strings.TrimPrefix(link, "https://imgur.com/")) - rimgoHtmlLink := makeHtmlLink("Rimgo", rimgoLink) - htmlLink := makeHtmlLink(link, link) - return htmlLink + ` (` + rimgoHtmlLink + `)` - } - for _, rimgoURL := range RimgoURLs { - if strings.HasPrefix(link, rimgoURL) { - newPrefix := strings.Replace(link, rimgoURL, "http://rimgo.onion", 1) - imgurPrefix := strings.Replace(link, rimgoURL, "https://imgur.com", 1) - imgurHtmlLink := makeHtmlLink("Imgur", imgurPrefix) - htmlLink := makeHtmlLink(newPrefix, link) - return htmlLink + ` (` + imgurHtmlLink + `)` - } - } - - // Append invidious link to YouTube/yewtube link - var videoID string - var m []string - var isShortUrl, isYewtube bool - if strings.HasPrefix(link, "https://youtu.be/") { - m = youtuBeIDRgx.FindStringSubmatch(link) - } else if strings.HasPrefix(link, "https://www.youtube.com/watch?v=") { - m = youtubeComIDRgx.FindStringSubmatch(link) - } else if strings.HasPrefix(link, "https://yewtu.be/") || strings.HasPrefix(link, "https://www.yewtu.be/") { - m = yewtubeBeIDRgx.FindStringSubmatch(link) - isYewtube = true - } else if strings.HasPrefix(link, "https://www.youtube.com/shorts/") { - m = youtubeComShortsIDRgx.FindStringSubmatch(link) - isShortUrl = true - } - if len(m) == 2 { - videoID = m[1] - } - if videoID != "" { - invidiousLink := "/external-link/invidious/" + url.PathEscape("watch?v="+videoID+"&local=true") - invidiousHtmlLink := makeHtmlLink("Invidious", invidiousLink) - htmlLink := makeHtmlLink(link, link) - youtubeLink := "https://www.youtube.com/watch?v=" + videoID - youtubeHtmlLink := makeHtmlLink("YT", youtubeLink) - out := htmlLink + ` (` + invidiousHtmlLink + `)` - if isShortUrl || isYewtube { - out = htmlLink + ` (` + youtubeHtmlLink + ` | ` + invidiousHtmlLink + `)` - } - return out - } - - // Special case for dkf links. - { - dkfLocalPrefix := "http://127.0.0.1:8080" - dkfShortPrefix := "http://dkf.onion" - dkfLongPrefix := config.DkfOnion - hasLocalPrefix := strings.HasPrefix(link, dkfLocalPrefix) - hasDkfShortPrefix := strings.HasPrefix(link, dkfShortPrefix) - hasDkfLongPrefix := strings.HasPrefix(link, dkfLongPrefix) - if hasLocalPrefix || hasDkfLongPrefix || hasDkfShortPrefix { - var trimmed string - if hasLocalPrefix { - trimmed = strings.TrimPrefix(link, dkfLocalPrefix) - } else if hasDkfLongPrefix { - trimmed = strings.TrimPrefix(link, dkfLongPrefix) - } else if hasDkfShortPrefix { - trimmed = strings.TrimPrefix(link, dkfShortPrefix) - } - label := dkfShortPrefix + trimmed - href := trimmed - // Shorten archive links - if m := dkfArchiveRgx.FindStringSubmatch(label); len(m) == 3 { - if msg, err := getChatMessageByUUID(m[2]); err == nil { - if roomID == msg.RoomID { - label = msg.CreatedAt.Format("[Jan 02 03:04:05]") - } else { - label = msg.CreatedAt.Format("[#" + m[1] + " Jan 02 03:04:05]") - } - } - } - // Allows to have messages such as: "my profile is /u/username :)" - if userProfileLinkRgx.MatchString(trimmed) { - if user, err := getUserByUsername(database.Username(strings.TrimPrefix(trimmed, "/u/"))); err == nil { - label = "/u/" + string(user.Username) - href = "/u/" + string(user.Username) - } - } else if linkShorthandPageLinkRgx.MatchString(trimmed) { - // Convert all occurrences of "/l/shorthand" to a link to link page if the shorthand exists - if l, err := getLinkByShorthand(strings.TrimPrefix(trimmed, "/l/")); err == nil { - label = "/l/" + *l.Shorthand - href = "/l/" + *l.Shorthand - } - } - return makeHtmlLink(label, href) - } - } - - for _, el := range knownOnions { - shortPrefix := el[0] - longPrefix := el[1] - if strings.HasPrefix(link, longPrefix) { - return makeHtmlLink(shortPrefix+strings.TrimPrefix(link, longPrefix), link) - } else if strings.HasPrefix(link, shortPrefix) { - return makeHtmlLink(link, longPrefix+strings.TrimPrefix(link, shortPrefix)) - } - } - return makeHtmlLink(link, link) - }) - - return quote + newRest -} - func appendUploadLink(html string, upload *database.Upload) string { if upload != nil { if html != "" { @@ -761,82 +411,14 @@ func ColorifyTaggedUsers(html string, getUsersByUsername getUsersByUsernameFn) ( return html, taggedUsersIDsMap } -func linkDefaultRooms(html string) string { - r := strings.NewReplacer( - "#general", `<a href="/chat/general" target="_top">#general</a>`, - "#programming", `<a href="/chat/programming" target="_top">#programming</a>`, - "#hacking", `<a href="/chat/hacking" target="_top">#hacking</a>`, - "#suggestions", `<a href="/chat/suggestions" target="_top">#suggestions</a>`, - "#announcements", `<a href="/chat/announcements" target="_top">#announcements</a>`, - ) - return r.Replace(html) -} - -// Convert timestamps such as 01:23:45 to an archive link if a message with that timestamp exists. -// eg: "Some text 14:31:46 some more text" -func convertArchiveLinks(db *database.DkfDB, html string, roomID database.RoomID, authUserID database.UserID) string { - start, rest := "", html - - // Do not replace timestamps that are inside a quote text - const quoteSuffix = `”` - endOfQuoteIdx := strings.LastIndex(html, quoteSuffix) - if endOfQuoteIdx != -1 { - start, rest = html[:endOfQuoteIdx], html[endOfQuoteIdx:] - } - - archiveRgx := regexp.MustCompile(`(\d{2}-\d{2} )?\d{2}:\d{2}:\d{2}`) - if archiveRgx.MatchString(rest) { - rest = archiveRgx.ReplaceAllStringFunc(rest, func(s string) string { - var dt time.Time - var err error - if len(s) == 8 { // HH:MM:SS - dt, err = utils.ParsePrevDatetimeAt(s, clockwork.NewRealClock()) - } else if len(s) == 14 { // mm-dd HH:MM:SS - dt, err = utils.ParsePrevDatetimeAt2(s, clockwork.NewRealClock()) - } - if err != nil { - return s - } - if msgs, err := db.GetRoomChatMessagesByDate(roomID, dt.UTC()); err == nil && len(msgs) > 0 { - msg := msgs[0] - if len(msgs) > 1 { - for _, msgTmp := range msgs { - if msgTmp.User.ID == authUserID || (msgTmp.ToUserID != nil && *msgTmp.ToUserID == authUserID) { - msg = msgTmp - break - } - } - } - return fmt.Sprintf(`<a href="/chat/%s/archive#%s" target="_blank" rel="noopener noreferrer">%s</a>`, msg.Room.Name, msg.UUID, s) - } - return s - }) - } - return start + rest -} - -func convertBangShortcuts(html string) string { - r := strings.NewReplacer( - "!bhc", config.BhcOnion, - "!cryptbb", config.CryptbbOnion, - "!dread", config.DreadOnion, - "!dkf", config.DkfOnion, - "!rroom", config.DkfOnion+`/red-room`, - "!dnmx", config.DnmxOnion, - "!whonix", config.WhonixOnion, - "!age", config.AgeUrl, - ) - return r.Replace(html) -} - -func convertMarkdown(db *database.DkfDB, in string, canUseMultiline, manualML bool) string { +func convertMarkdown(db *database.DkfDB, in string, canUseMultiline, manualML bool, roomID database.RoomID, authUserID database.UserID) string { out := strings.Replace(in, "\r", "", -1) flags := bf.NoIntraEmphasis | bf.Tables | bf.FencedCode | bf.Strikethrough | bf.SpaceHeadings | bf.DefinitionLists | bf.HardLineBreak | bf.NoLink if canUseMultiline && manualML { flags |= bf.ManualLineBreak } - resBytes := bf.Run([]byte(out), bf.WithRenderer(database.MyRenderer(db, false, false)), bf.WithExtensions(flags)) + resBytes := bf.Run([]byte(out), bf.WithRenderer(database.MyRenderer(db, false, false, roomID, authUserID)), bf.WithExtensions(flags)) out = string(resBytes) return out } diff --git a/pkg/database/utils/processMessage_test.go b/pkg/database/utils/processMessage_test.go @@ -12,13 +12,13 @@ import ( func TestConvertMarkdown(t *testing.T) { // Convert markdown will not remove dangerous html msg := `<noscript>` - out := convertMarkdown(msg, false, false) + out := convertMarkdown(nil, msg, false, false) expected := "<p><noscript></p>\n" assert.Equal(t, expected, out) - + // Testing censored feature msg = `This #is censored# text` - out = convertMarkdown(msg, false, false) + out = convertMarkdown(nil, msg, false, false) expected = "<p>This <span class=\"censored\">is censored</span> text</p>\n" assert.Equal(t, expected, out) } diff --git a/pkg/web/handlers/handlers.go b/pkg/web/handlers/handlers.go @@ -6,7 +6,6 @@ import ( "dkforest/pkg/captcha" "dkforest/pkg/config" "dkforest/pkg/database" - dutils "dkforest/pkg/database/utils" "dkforest/pkg/odometer" "dkforest/pkg/utils" hutils "dkforest/pkg/web/handlers/utils" @@ -265,15 +264,15 @@ func ExternalLinkHandler(c echo.Context) error { original, _ := url.PathUnescape(c.Param("original")) baseURL := "/" if service == "invidious" { - baseURL = utils.RandChoice(dutils.InvidiousURLs) + baseURL = utils.RandChoice(database.InvidiousURLs) } else if service == "libreddit" { - baseURL = utils.RandChoice(dutils.LibredditURLs) + baseURL = utils.RandChoice(database.LibredditURLs) } else if service == "wikiless" { - baseURL = utils.RandChoice(dutils.WikilessURLs) + baseURL = utils.RandChoice(database.WikilessURLs) } else if service == "nitter" { - baseURL = utils.RandChoice(dutils.NitterURLs) + baseURL = utils.RandChoice(database.NitterURLs) } else if service == "rimgo" { - baseURL = utils.RandChoice(dutils.RimgoURLs) + baseURL = utils.RandChoice(database.RimgoURLs) } else { return c.String(http.StatusNotFound, "Not found") }