move some things to enhanced_event.go, that makes more sense.

This commit is contained in:
fiatjaf
2024-06-19 22:01:55 -03:00
parent eb96592189
commit 47423ddcc4
20 changed files with 333 additions and 350 deletions

View File

@@ -13,9 +13,9 @@ func formatPartecipants(partecipants []nip52.Participant) string {
for _, p := range partecipants {
nreplace, _ := nip19.EncodePublicKey(p.PubKey)
bytes := []byte(p.Role)
bytes[0] = byte(unicode.ToUpper(rune(bytes[0])))
role := string(bytes)
nreplace = replaceNostrURLsWithHTMLTags(nostrNpubNprofileMatcher, "nostr:" + nreplace)
bytes[0] = byte(unicode.ToUpper(rune(bytes[0])))
role := string(bytes)
nreplace = replaceNostrURLsWithHTMLTags(nostrNpubNprofileMatcher, "nostr:"+nreplace)
if p.Role != "" {
nreplace = nreplace + " as " + role
}
@@ -27,10 +27,9 @@ func formatPartecipants(partecipants []nip52.Participant) string {
templ calendarEventTemplate(params CalendarPageParams) {
<!DOCTYPE html>
@eventPageTemplate(
"Calendar Event: " + params.CalendarEvent.Title,
"Calendar Event: "+params.CalendarEvent.Title,
params.OpenGraphParams,
params.HeadParams,
params.Metadata,
params.Clients,
params.Details,
params.Event,
@@ -58,13 +57,13 @@ templ calendarEventTemplate(params CalendarPageParams) {
</div>
</div>
if params.EndAtTime != "" {
<div class="sm:w-auto sm:grow xl:grow-0 xl:w-1/3">
<div class="font-semibold text-sm ml-2">End date</div>
<div class="py-2 px-4 bg-strongpink text-white rounded-md">
<div class="whitespace-nowrap">{ params.EndAtDate }</div>
<div class="text-sm">{ params.EndAtTime } ({ params.TimeZone })</div>
<div class="sm:w-auto sm:grow xl:grow-0 xl:w-1/3">
<div class="font-semibold text-sm ml-2">End date</div>
<div class="py-2 px-4 bg-strongpink text-white rounded-md">
<div class="whitespace-nowrap">{ params.EndAtDate }</div>
<div class="text-sm">{ params.EndAtTime } ({ params.TimeZone })</div>
</div>
</div>
</div>
}
}
if params.CalendarEvent.Locations[0] != "" {
@@ -76,7 +75,6 @@ templ calendarEventTemplate(params CalendarPageParams) {
</div>
}
</div>
<div class="mb-4 pt-6">
if len(params.CalendarEvent.Participants) != 0 {
<div class="pb-4">
@@ -85,11 +83,10 @@ templ calendarEventTemplate(params CalendarPageParams) {
</div>
}
if params.CalendarEvent.Image != "" {
<img class="w-full mt-2" src={ params.CalendarEvent.Image } />
<img class="w-full mt-2" src={ params.CalendarEvent.Image }/>
}
@templ.Raw(params.Content)
</div>
<div class="mb-4">
for _, v := range params.CalendarEvent.Hashtags {
<span class="mr-2 whitespace-nowrap rounded bg-neutral-200 px-2 dark:bg-neutral-700 dark:text-white">
@@ -98,4 +95,4 @@ templ calendarEventTemplate(params CalendarPageParams) {
}
</div>
}
}
}

View File

@@ -1,5 +1,7 @@
package main
import "github.com/nbd-wtf/nostr-sdk"
var compileTimeTs string
templ headCommonTemplate(params HeadParams) {
@@ -37,7 +39,7 @@ on load get [navigator.userAgent.includes('Safari'), navigator.userAgent.include
</script>
}
templ authorHeaderTemplate(metadata Metadata) {
templ authorHeaderTemplate(metadata sdk.ProfileMetadata) {
<header
itemprop="author"
itemscope

33
data.go
View File

@@ -12,7 +12,6 @@ import (
"github.com/nbd-wtf/go-nostr/nip52"
"github.com/nbd-wtf/go-nostr/nip53"
"github.com/nbd-wtf/go-nostr/nip94"
sdk "github.com/nbd-wtf/nostr-sdk"
)
type Data struct {
@@ -26,9 +25,7 @@ type Data struct {
createdAt string
modifiedAt string
parentLink template.HTML
metadata Metadata
authorRelaysPretty []string
authorLong string
renderableLastNotes []EnhancedEvent
kindDescription string
kindNIP string
@@ -65,10 +62,7 @@ func grabData(ctx context.Context, code string, isProfileSitemap bool) (*Data, e
}
data := &Data{
event: EnhancedEvent{
Event: event,
relays: relays,
},
event: NewEnhancedEvent(ctx, event, relays),
}
data.authorRelaysPretty = make([]string, 0, len(relays))
@@ -85,9 +79,6 @@ func grabData(ctx context.Context, code string, isProfileSitemap bool) (*Data, e
}
data.authorRelaysPretty = unique(data.authorRelaysPretty)
npub, _ := nip19.EncodePublicKey(event.PubKey)
npubShort := npub[:8] + "…" + npub[len(npub)-4:]
data.authorLong = npub // hopefully will be replaced later
data.nevent, _ = nip19.EncodeEvent(event.ID, relaysForNip19, event.PubKey)
data.neventNaked, _ = nip19.EncodeEvent(event.ID, nil, event.PubKey)
data.naddr = ""
@@ -110,7 +101,7 @@ func grabData(ctx context.Context, code string, isProfileSitemap bool) (*Data, e
lastNotes := authorLastNotes(ctx, event.PubKey, isProfileSitemap)
data.renderableLastNotes = make([]EnhancedEvent, len(lastNotes))
for i, levt := range lastNotes {
data.renderableLastNotes[i] = EnhancedEvent{levt, []string{}}
data.renderableLastNotes[i] = NewEnhancedEvent(ctx, levt, []string{})
}
if err != nil {
return nil, err
@@ -146,25 +137,7 @@ func grabData(ctx context.Context, code string, isProfileSitemap bool) (*Data, e
data.templateId = Other
}
if event.Kind == 0 {
data.nprofile, _ = nip19.EncodeProfile(event.PubKey, limitAt(relays, 2))
spm, _ := sdk.ParseMetadata(event)
data.metadata = Metadata{spm}
} else {
ctx, cancel := context.WithTimeout(ctx, time.Second*3)
defer cancel()
author, relays, _ := getEvent(ctx, npub, relaysForNip19)
if author == nil {
data.metadata = Metadata{sdk.ProfileMetadata{PubKey: event.PubKey}}
} else {
spm, _ := sdk.ParseMetadata(author)
data.metadata = Metadata{spm}
if data.metadata.Name != "" {
data.authorLong = fmt.Sprintf("%s (%s)", data.metadata.Name, npubShort)
}
}
data.nprofile, _ = nip19.EncodeProfile(event.PubKey, limitAt(relays, 2))
}
data.nprofile, _ = nip19.EncodeProfile(event.PubKey, limitAt(relays, 2))
data.kindDescription = kindNames[event.Kind]
if data.kindDescription == "" {

246
enhanced_event.go Normal file
View File

@@ -0,0 +1,246 @@
package main
import (
"context"
"encoding/json"
"fmt"
"html"
"html/template"
"regexp"
"strconv"
"strings"
"time"
"github.com/nbd-wtf/go-nostr"
"github.com/nbd-wtf/go-nostr/nip10"
"github.com/nbd-wtf/go-nostr/nip19"
sdk "github.com/nbd-wtf/nostr-sdk"
"github.com/texttheater/golang-levenshtein/levenshtein"
)
type EnhancedEvent struct {
*nostr.Event
relays []string
subject string
summary string
author sdk.ProfileMetadata
}
func NewEnhancedEvent(
ctx context.Context,
event *nostr.Event,
relays []string,
) EnhancedEvent {
ee := EnhancedEvent{
Event: event,
relays: relays,
}
for _, tag := range event.Tags {
if tag[0] == "subject" || tag[0] == "title" {
ee.subject = tag[1]
}
if tag[0] == "summary" {
ee.summary = tag[1]
}
}
if ctx != nil {
if event.Kind == 0 {
spm, _ := sdk.ParseMetadata(event)
ee.author = spm
} else {
ctx, cancel := context.WithTimeout(ctx, time.Second*3)
defer cancel()
ee.author = sys.FetchProfileMetadata(ctx, event.PubKey)
}
}
return ee
}
func (ee EnhancedEvent) authorLong() string {
if ee.author.Name != "" {
return fmt.Sprintf("%s (%s)", ee.author.Name, ee.author.NpubShort())
}
return ee.author.Npub()
}
func (ee EnhancedEvent) getParentNevent() string {
parentNevent := ""
switch ee.Kind {
case 1, 1063:
replyTag := nip10.GetImmediateReply(ee.Tags)
if replyTag != nil {
var relays []string
if (len(*replyTag) > 2) && ((*replyTag)[2] != "") {
relays = []string{(*replyTag)[2]}
}
eventId := (*replyTag)[1]
if (*replyTag)[0] == "a" { // Reply to a ndaddr event
eventId = strings.Split(eventId, ":")[1]
kind, _ := strconv.Atoi(strings.Split((*replyTag)[1], ":")[0])
identifier := strings.Split((*replyTag)[1], ":")[2]
var relays []string
if (len(*replyTag) > 2) && ((*replyTag)[2] != "") {
relays = []string{(*replyTag)[2]}
}
parentNevent, _ = nip19.EncodeEntity(
eventId,
kind,
identifier,
relays)
} else {
parentNevent, _ = nip19.EncodeEvent(eventId, relays, "")
}
}
case 1311:
if atag := ee.Tags.GetFirst([]string{"a", ""}); atag != nil {
parts := strings.Split((*atag)[1], ":")
kind, _ := strconv.Atoi(parts[0])
var relays []string
if (len(*atag) > 2) && ((*atag)[2] != "") {
relays = []string{(*atag)[2]}
}
parentNevent, _ = nip19.EncodeEntity(parts[1], kind, parts[2], relays)
}
}
return parentNevent
}
func (ee EnhancedEvent) isReply() bool {
return nip10.GetImmediateReply(ee.Event.Tags) != nil
}
func (ee EnhancedEvent) Preview() template.HTML {
lines := strings.Split(html.EscapeString(ee.Event.Content), "\n")
var processedLines []string
for _, line := range lines {
if strings.TrimSpace(line) == "" {
continue
}
processedLine := shortenNostrURLs(line)
processedLines = append(processedLines, processedLine)
}
return template.HTML(strings.Join(processedLines, "<br/>"))
}
func (ee EnhancedEvent) RssTitle() string {
regex := regexp.MustCompile(`(?i)<br\s?/?>`)
replacedString := regex.ReplaceAllString(string(ee.Preview()), " ")
words := strings.Fields(replacedString)
title := ""
for i, word := range words {
if len(title)+len(word)+1 <= 65 { // +1 for space
if title != "" {
title += " "
}
title += word
} else {
if i > 1 { // the first word len is > 65
title = title + " ..."
} else {
title = ""
}
break
}
}
content := ee.RssContent()
distance := levenshtein.DistanceForStrings([]rune(title), []rune(content), levenshtein.DefaultOptions)
similarityThreshold := 5
if distance <= similarityThreshold {
return ""
} else {
return title
}
}
func (ee EnhancedEvent) RssContent() string {
content := ee.Event.Content
content = basicFormatting(html.EscapeString(content), true, false, false)
content = renderQuotesAsHTML(context.Background(), content, false)
if nevent := ee.getParentNevent(); nevent != "" {
neventShort := nevent[:8] + "…" + nevent[len(nevent)-4:]
content = "In reply to <a href='/" + nevent + "'>" + neventShort + "</a><br/>_________________________<br/><br/>" + content
}
return content
}
func (ee EnhancedEvent) Thumb() string {
imgRegex := regexp.MustCompile(`(https?://[^\s]+\.(?:png|jpe?g|gif|bmp|svg)(?:/[^\s]*)?)`)
matches := imgRegex.FindAllStringSubmatch(ee.Event.Content, -1)
if len(matches) > 0 {
// The first match group captures the image URL
return matches[0][1]
}
return ""
}
func (ee EnhancedEvent) Npub() string {
npub, _ := nip19.EncodePublicKey(ee.Event.PubKey)
return npub
}
func (ee EnhancedEvent) NpubShort() string {
npub := ee.Npub()
return npub[:8] + "…" + npub[len(npub)-4:]
}
func (ee EnhancedEvent) Nevent() string {
nevent, _ := nip19.EncodeEvent(ee.Event.ID, ee.relays, ee.Event.PubKey)
return nevent
}
func (ee EnhancedEvent) CreatedAtStr() string {
return time.Unix(int64(ee.Event.CreatedAt), 0).Format("2006-01-02 15:04:05")
}
func (ee EnhancedEvent) ModifiedAtStr() string {
return time.Unix(int64(ee.Event.CreatedAt), 0).Format("2006-01-02T15:04:05Z07:00")
}
func (ee EnhancedEvent) ToJSONHTML() template.HTML {
tagsHTML := "["
for t, tag := range ee.Tags {
tagsHTML += "\n ["
for i, item := range tag {
cls := `"text-zinc-500 dark:text-zinc-50"`
if i == 0 {
cls = `"text-amber-500 dark:text-amber-200"`
}
itemJSON, _ := json.Marshal(item)
tagsHTML += "\n <span class=" + cls + ">" + html.EscapeString(string(itemJSON))
if i < len(tag)-1 {
tagsHTML += ","
} else {
tagsHTML += "\n "
}
}
tagsHTML += "]"
if t < len(ee.Tags)-1 {
tagsHTML += ","
} else {
tagsHTML += "\n "
}
}
tagsHTML += "]"
contentJSON, _ := json.Marshal(ee.Content)
keyCls := "text-purple-700 dark:text-purple-300"
return template.HTML(fmt.Sprintf(
`{
<span class="`+keyCls+`">"id":</span> <span class="text-zinc-500 dark:text-zinc-50">"%s"</span>,
<span class="`+keyCls+`">"pubkey":</span> <span class="text-zinc-500 dark:text-zinc-50">"%s"</span>,
<span class="`+keyCls+`">"created_at":</span> <span class="text-green-600">%d</span>,
<span class="`+keyCls+`">"kind":</span> <span class="text-amber-500 dark:text-amber-200">%d</span>,
<span class="`+keyCls+`">"tags":</span> %s,
<span class="`+keyCls+`">"content":</span> <span class="text-zinc-500 dark:text-zinc-50">%s</span>,
<span class="`+keyCls+`">"sig":</span> <span class="text-zinc-500 dark:text-zinc-50 content">"%s"</span>
}`, ee.ID, ee.PubKey, ee.CreatedAt, ee.Kind, tagsHTML, html.EscapeString(string(contentJSON)), ee.Sig),
)
}

View File

@@ -4,7 +4,6 @@ templ eventPageTemplate(
title string,
og OpenGraphParams,
head HeadParams,
author Metadata,
clients []ClientReference,
details DetailsParams,
event EnhancedEvent,
@@ -25,7 +24,7 @@ templ eventPageTemplate(
class="w-full max-w-screen-2xl justify-between gap-10 overflow-visible print:w-full sm:flex sm:w-11/12 sm:px-4 md:w-10/12 lg:w-9/12 lg:gap-48vw"
>
<div class="w-full break-words print:w-full sm:w-3/4">
@authorHeaderTemplate(author)
@authorHeaderTemplate(event.author)
<div itemprop="dateCreated" class="w-full text-right text-sm text-stone-400">
{ event.CreatedAtStr() }
</div>

View File

@@ -6,7 +6,6 @@ templ fileMetadataTemplate(params FileMetadataPageParams) {
"File Metadata",
params.OpenGraphParams,
params.HeadParams,
params.Metadata,
params.Clients,
params.Details,
params.Event,

4
go.mod
View File

@@ -19,8 +19,8 @@ require (
github.com/kelseyhightower/envconfig v1.4.0
github.com/microcosm-cc/bluemonday v1.0.24
github.com/nbd-wtf/emoji v0.0.3
github.com/nbd-wtf/go-nostr v0.32.0
github.com/nbd-wtf/nostr-sdk v0.2.1
github.com/nbd-wtf/go-nostr v0.33.0
github.com/nbd-wtf/nostr-sdk v0.2.3
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
github.com/pelletier/go-toml v1.9.5
github.com/pemistahl/lingua-go v1.4.0

8
go.sum
View File

@@ -182,10 +182,10 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/nbd-wtf/emoji v0.0.3 h1:YtkT7MVPXvqU1SQjvC/CShlWexnREzqNCxmhUnL00CA=
github.com/nbd-wtf/emoji v0.0.3/go.mod h1:tS6D9iI34qwBmWc5g8X7tVDkWXulqbTJRsvsM6QsS88=
github.com/nbd-wtf/go-nostr v0.32.0 h1:ShRerjhXvqZbiVUc11iPqxLuOImxqbJQ0zTz4t6Tjps=
github.com/nbd-wtf/go-nostr v0.32.0/go.mod h1:NZQkxl96ggbO8rvDpVjcsojJqKTPwqhP4i82O7K5DJs=
github.com/nbd-wtf/nostr-sdk v0.2.1 h1:Ae1186pm//Byy5gOIsB3Nhy6EQfTzOHeU9enRWVd204=
github.com/nbd-wtf/nostr-sdk v0.2.1/go.mod h1:iYZYgu0lilem16G8fk08FipgMOzsQs2bO1j6uEnRhvA=
github.com/nbd-wtf/go-nostr v0.33.0 h1:6hZx25JAgwEOY49gCjrVZYFno7Z8L7HIwF6IMDGPjaU=
github.com/nbd-wtf/go-nostr v0.33.0/go.mod h1:NZQkxl96ggbO8rvDpVjcsojJqKTPwqhP4i82O7K5DJs=
github.com/nbd-wtf/nostr-sdk v0.2.3 h1:wQrr92VwYhOl+r1Cg3hUvggQT4S2wcTRRFHMD3znbe4=
github.com/nbd-wtf/nostr-sdk v0.2.3/go.mod h1:AZZW6QzMk3Tc14fXRPktHFEEAAywLZ/TZf+Wdkr2ksI=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=

View File

@@ -3,10 +3,9 @@ package main
templ liveEventTemplate(params LiveEventPageParams) {
<!DOCTYPE html>
@eventPageTemplate(
params.LiveEvent.title(),
params.LiveEvent.title(),
params.OpenGraphParams,
params.HeadParams,
params.Metadata,
params.Clients,
params.Details,
params.Event,

View File

@@ -3,10 +3,9 @@ package main
templ liveEventMessageTemplate(params LiveEventMessagePageParams) {
<!DOCTYPE html>
@eventPageTemplate(
params.TitleizedContent,
params.TitleizedContent,
params.OpenGraphParams,
params.HeadParams,
params.Metadata,
params.Clients,
params.Details,
params.Event,

View File

@@ -6,7 +6,6 @@ templ noteTemplate(params NotePageParams) {
params.TitleizedContent,
params.OpenGraphParams,
params.HeadParams,
params.Metadata,
params.Clients,
params.Details,
params.Event,
@@ -15,7 +14,7 @@ templ noteTemplate(params NotePageParams) {
<h1 class="text-2xl" itemprop="headline">{ params.Subject }</h1>
} else {
<h1 class="hidden">
{ params.Metadata.ShortName() } on Nostr: { params.TitleizedContent }
{ params.Event.author.ShortName() } on Nostr: { params.TitleizedContent }
</h1>
}
if params.Cover != "" {

View File

@@ -62,9 +62,9 @@ func renderOEmbed(w http.ResponseWriter, r *http.Request) {
Version: "1.0",
ProviderName: "njump",
ProviderURL: "https://" + host,
Title: data.metadata.Name + " wrote",
AuthorName: data.authorLong,
AuthorURL: fmt.Sprintf("https://%s/%s", host, data.metadata.Npub()),
Title: data.event.author.Name + " wrote",
AuthorName: data.event.authorLong(),
AuthorURL: fmt.Sprintf("https://%s/%s", host, data.event.Npub()),
}
switch {

View File

@@ -11,6 +11,7 @@ import (
"github.com/a-h/templ"
"github.com/nbd-wtf/go-nostr/nip11"
sdk "github.com/nbd-wtf/nostr-sdk"
)
type TemplateID int
@@ -48,7 +49,7 @@ type DetailsParams struct {
HideDetails bool
CreatedAt string
EventJSON template.HTML
Metadata Metadata
Metadata sdk.ProfileMetadata
Nevent string
Nprofile string
SeenOn []string
@@ -75,7 +76,7 @@ type TelegramInstantViewParams struct {
Content template.HTML
Description string
Subject string
Metadata Metadata
Metadata sdk.ProfileMetadata
AuthorLong string
CreatedAt string
ParentNevent string
@@ -95,7 +96,7 @@ type AboutParams struct {
type EmbeddedNoteParams struct {
Content template.HTML
CreatedAt string
Metadata Metadata
Metadata sdk.ProfileMetadata
SeenOn []string
Subject string
Url string
@@ -110,7 +111,7 @@ type ProfilePageParams struct {
CreatedAt string
Domain string
LastNotes []EnhancedEvent
Metadata Metadata
Metadata sdk.ProfileMetadata
NormalizedAuthorWebsiteURL string
RenderedAuthorAboutText template.HTML
Nevent string
@@ -125,7 +126,7 @@ type EmbeddedProfileParams struct {
Content string
CreatedAt string
Domain string
Metadata Metadata
Metadata sdk.ProfileMetadata
NormalizedAuthorWebsiteURL string
RenderedAuthorAboutText template.HTML
Nevent string
@@ -137,7 +138,7 @@ type EmbeddedProfileParams struct {
type RelayPageParams struct {
HeadParams
Info *nip11.RelayInformationDocument
Info nip11.RelayInformationDocument
Hostname string
Proxy string
LastNotes []EnhancedEvent
@@ -171,10 +172,9 @@ func (e *ErrorPageParams) MessageHTML() template.HTML {
}
type BaseEventPageParams struct {
Event EnhancedEvent
Metadata Metadata
Style Style
Alt string
Event EnhancedEvent
Style Style
Alt string
}
type NotePageParams struct {

View File

@@ -46,16 +46,16 @@ func renderEmbedded(w http.ResponseWriter, r *http.Request, code string) {
component = embeddedNoteTemplate(EmbeddedNoteParams{
Content: template.HTML(data.content),
CreatedAt: data.createdAt,
Metadata: data.metadata,
Metadata: data.event.author,
Subject: subject,
Url: code,
})
case Profile:
component = embeddedProfileTemplate(EmbeddedProfileParams{
Metadata: data.metadata,
NormalizedAuthorWebsiteURL: normalizeWebsiteURL(data.metadata.Website),
RenderedAuthorAboutText: template.HTML(basicFormatting(html.EscapeString(data.metadata.About), false, false, true)),
Metadata: data.event.author,
NormalizedAuthorWebsiteURL: normalizeWebsiteURL(data.event.author.Website),
RenderedAuthorAboutText: template.HTML(basicFormatting(html.EscapeString(data.event.author.About), false, false, true)),
AuthorRelays: data.authorRelaysPretty,
})
default:

View File

@@ -96,7 +96,7 @@ func renderEvent(w http.ResponseWriter, r *http.Request) {
// if the result is a kind:0 render this as a profile
if data.event.Kind == 0 {
renderProfile(w, r, data.metadata.Npub())
renderProfile(w, r, data.event.author.Npub())
return
}
@@ -136,17 +136,6 @@ func renderEvent(w http.ResponseWriter, r *http.Request) {
host = r.Host
}
var subject string
var summary string
for _, tag := range data.event.Tags {
if tag[0] == "subject" || tag[0] == "title" {
subject = tag[1]
}
if tag[0] == "summary" {
summary = tag[1]
}
}
useTextImage := false
if data.event.Kind == 1 || data.event.Kind == 30023 {
@@ -188,10 +177,10 @@ func renderEvent(w http.ResponseWriter, r *http.Request) {
} else {
subscript = fmt.Sprintf("kind:%d event", data.event.Kind)
}
if subject != "" {
subscript += " (" + subject + ")"
if data.event.subject != "" {
subscript += " (" + data.event.subject + ")"
}
subscript += " by " + data.metadata.ShortName()
subscript += " by " + data.event.author.ShortName()
if data.event.isReply() {
subscript += " (reply)"
}
@@ -205,17 +194,17 @@ func renderEvent(w http.ResponseWriter, r *http.Request) {
description := ""
if useTextImage {
textImageURL = fmt.Sprintf("https://%s/njump/image/%s?%s", host, code, r.URL.RawQuery)
if subject != "" {
if data.event.subject != "" {
if seenOnRelays != "" {
description = fmt.Sprintf("%s -- %s", subject, seenOnRelays)
description = fmt.Sprintf("%s -- %s", data.event.subject, seenOnRelays)
} else {
description = subject
description = data.event.subject
}
} else {
description = seenOnRelays
}
} else if summary != "" {
description = summary
} else if data.event.summary != "" {
description = data.event.summary
} else {
// if content is valid JSON, parse that and print as TOML for easier readability
var parsedJson any
@@ -280,7 +269,7 @@ func renderEvent(w http.ResponseWriter, r *http.Request) {
}
if data.event.Kind == 30023 || data.event.Kind == 30024 {
// Remove duplicate title inside the body
data.content = strings.ReplaceAll(data.content, "# "+subject, "")
data.content = strings.ReplaceAll(data.content, "# "+data.event.subject, "")
data.content = mdToHTML(data.content, data.templateId == TelegramInstantView, false)
} else {
// first we run basicFormatting, which turns URLs into their appropriate HTML tags
@@ -322,7 +311,7 @@ func renderEvent(w http.ResponseWriter, r *http.Request) {
EventJSON: data.event.ToJSONHTML(),
Kind: data.event.Kind,
SeenOn: data.event.relays,
Metadata: data.metadata,
Metadata: data.event.author,
}
opengraph := OpenGraphParams{
@@ -332,17 +321,16 @@ func renderEvent(w http.ResponseWriter, r *http.Request) {
VideoType: data.videoType,
ProxiedImage: "https://" + host + "/njump/proxy?src=" + data.image,
Superscript: data.authorLong,
Superscript: data.event.authorLong(),
Subscript: subscript,
Text: strings.TrimSpace(description),
}
var component templ.Component
baseEventPageParams := BaseEventPageParams{
Event: data.event,
Metadata: data.metadata,
Style: style,
Alt: data.alt,
Event: data.event,
Style: style,
Alt: data.alt,
}
switch data.templateId {
@@ -351,12 +339,12 @@ func renderEvent(w http.ResponseWriter, r *http.Request) {
Video: data.video,
VideoType: data.videoType,
Image: data.image,
Summary: template.HTML(summary),
Summary: template.HTML(data.event.summary),
Content: template.HTML(data.content),
Description: description,
Subject: subject,
Metadata: data.metadata,
AuthorLong: data.authorLong,
Subject: data.event.subject,
Metadata: data.event.author,
AuthorLong: data.event.authorLong(),
CreatedAt: data.createdAt,
ParentNevent: data.event.getParentNevent(),
})
@@ -399,12 +387,10 @@ func renderEvent(w http.ResponseWriter, r *http.Request) {
NaddrNaked: data.naddrNaked,
NeventNaked: data.neventNaked,
},
Clients: generateClientList(data.event.Kind, enhancedCode),
Details: detailsData,
Content: template.HTML(content),
Cover: data.cover,
Subject: subject,
TitleizedContent: titleizedContent,
})
case FileMetadata:

View File

@@ -16,6 +16,7 @@ import (
"github.com/fogleman/gg"
"github.com/go-text/typesetting/shaping"
"github.com/golang/freetype/truetype"
sdk "github.com/nbd-wtf/nostr-sdk"
"github.com/nfnt/resize"
xfont "golang.org/x/image/font"
)
@@ -70,7 +71,7 @@ func renderImage(w http.ResponseWriter, r *http.Request) {
"",
)
img, err := drawImage(paragraphs, getPreviewStyle(r), data.metadata, data.createdAt)
img, err := drawImage(paragraphs, getPreviewStyle(r), data.event.author, data.createdAt)
if err != nil {
log.Warn().Err(err).Msg("failed to draw paragraphs as image")
http.Error(w, "error writing image!", 500)
@@ -86,7 +87,12 @@ func renderImage(w http.ResponseWriter, r *http.Request) {
}
}
func drawImage(paragraphs []string, style Style, metadata Metadata, date string) (image image.Image, err error) {
func drawImage(
paragraphs []string,
style Style,
metadata sdk.ProfileMetadata,
date string,
) (image image.Image, err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("panic while drawing image")

View File

@@ -44,7 +44,7 @@ func renderProfile(w http.ResponseWriter, r *http.Request, code string) {
err = RSSTemplate.Render(w, &RSSPage{
Host: s.Domain,
ModifiedAt: data.modifiedAt,
Metadata: data.metadata,
Metadata: data.event.author,
LastNotes: data.renderableLastNotes,
})
} else {
@@ -59,11 +59,11 @@ func renderProfile(w http.ResponseWriter, r *http.Request, code string) {
KindNIP: data.kindNIP,
EventJSON: data.event.ToJSONHTML(),
Kind: data.event.Kind,
Metadata: data.metadata,
Metadata: data.event.author,
},
Metadata: data.metadata,
NormalizedAuthorWebsiteURL: normalizeWebsiteURL(data.metadata.Website),
RenderedAuthorAboutText: template.HTML(basicFormatting(html.EscapeString(data.metadata.About), false, false, false)),
Metadata: data.event.author,
NormalizedAuthorWebsiteURL: normalizeWebsiteURL(data.event.author.Website),
RenderedAuthorAboutText: template.HTML(basicFormatting(html.EscapeString(data.event.author.About), false, false, false)),
Nprofile: data.nprofile,
AuthorRelays: data.authorRelaysPretty,
LastNotes: data.renderableLastNotes,
@@ -75,7 +75,7 @@ func renderProfile(w http.ResponseWriter, r *http.Request, code string) {
if c == primalWeb {
s = strings.Replace(
strings.Replace(s, "/e/", "/p/", 1),
data.nprofile, data.metadata.Npub(), 1)
data.nprofile, data.event.author.Npub(), 1)
}
return s
},

View File

@@ -30,20 +30,9 @@ func renderRelayPage(w http.ResponseWriter, r *http.Request) {
}
// relay metadata
info, err := nip11.Fetch(r.Context(), hostname)
if err != nil {
w.Header().Set("Cache-Control", "max-age=60")
w.WriteHeader(http.StatusNotFound)
errorTemplate(ErrorPageParams{
Message: "The relay you are looking for does not exist or is offline; check the name in the url or try later",
Errors: err.Error(),
}).Render(r.Context(), w)
return
}
if info == nil {
info = &nip11.RelayInformationDocument{
Name: hostname,
}
info, _ := nip11.Fetch(r.Context(), hostname)
if info.Name == "" {
info.Name = hostname
}
// last notes
@@ -54,7 +43,7 @@ func renderRelayPage(w http.ResponseWriter, r *http.Request) {
lastEventAt = time.Unix(int64(lastNotes[0].CreatedAt), 0)
}
for i, levt := range lastNotes {
renderableLastNotes[i] = EnhancedEvent{levt, []string{"wss://" + hostname}}
renderableLastNotes[i] = NewEnhancedEvent(nil, levt, []string{"wss://" + hostname})
}
if len(renderableLastNotes) != 0 {

View File

@@ -1,224 +1,12 @@
package main
import (
"context"
"encoding/json"
"fmt"
"html"
"html/template"
"regexp"
"strconv"
"strings"
"time"
"github.com/nbd-wtf/go-nostr"
"github.com/nbd-wtf/go-nostr/nip10"
"github.com/nbd-wtf/go-nostr/nip19"
"github.com/nbd-wtf/go-nostr/nip52"
"github.com/nbd-wtf/go-nostr/nip53"
"github.com/nbd-wtf/go-nostr/nip94"
sdk "github.com/nbd-wtf/nostr-sdk"
"github.com/texttheater/golang-levenshtein/levenshtein"
)
type Metadata struct {
sdk.ProfileMetadata
}
func (m Metadata) Npub() string {
npub, _ := nip19.EncodePublicKey(m.PubKey)
return npub
}
func (m Metadata) NpubShort() string {
npub := m.Npub()
return npub[:8] + "…" + npub[len(npub)-4:]
}
type EnhancedEvent struct {
*nostr.Event
relays []string
}
func (ee EnhancedEvent) getParentNevent() string {
parentNevent := ""
switch ee.Kind {
case 1, 1063:
replyTag := nip10.GetImmediateReply(ee.Tags)
if replyTag != nil {
var relays []string
if (len(*replyTag) > 2) && ((*replyTag)[2] != "") {
relays = []string{(*replyTag)[2]}
}
eventId := (*replyTag)[1]
if (*replyTag)[0] == "a" { // Reply to a ndaddr event
eventId = strings.Split(eventId, ":")[1]
kind, _ := strconv.Atoi(strings.Split((*replyTag)[1], ":")[0])
identifier := strings.Split((*replyTag)[1], ":")[2]
var relays []string
if (len(*replyTag) > 2) && ((*replyTag)[2] != "") {
relays = []string{(*replyTag)[2]}
}
parentNevent, _ = nip19.EncodeEntity(
eventId,
kind,
identifier,
relays)
} else {
parentNevent, _ = nip19.EncodeEvent(eventId, relays, "")
}
}
case 1311:
if atag := ee.Tags.GetFirst([]string{"a", ""}); atag != nil {
parts := strings.Split((*atag)[1], ":")
kind, _ := strconv.Atoi(parts[0])
var relays []string
if (len(*atag) > 2) && ((*atag)[2] != "") {
relays = []string{(*atag)[2]}
}
parentNevent, _ = nip19.EncodeEntity(parts[1], kind, parts[2], relays)
}
}
return parentNevent
}
func (ee EnhancedEvent) isReply() bool {
return nip10.GetImmediateReply(ee.Event.Tags) != nil
}
func (ee EnhancedEvent) Preview() template.HTML {
lines := strings.Split(html.EscapeString(ee.Event.Content), "\n")
var processedLines []string
for _, line := range lines {
if strings.TrimSpace(line) == "" {
continue
}
processedLine := shortenNostrURLs(line)
processedLines = append(processedLines, processedLine)
}
return template.HTML(strings.Join(processedLines, "<br/>"))
}
func (ee EnhancedEvent) RssTitle() string {
regex := regexp.MustCompile(`(?i)<br\s?/?>`)
replacedString := regex.ReplaceAllString(string(ee.Preview()), " ")
words := strings.Fields(replacedString)
title := ""
for i, word := range words {
if len(title)+len(word)+1 <= 65 { // +1 for space
if title != "" {
title += " "
}
title += word
} else {
if i > 1 { // the first word len is > 65
title = title + " ..."
} else {
title = ""
}
break
}
}
content := ee.RssContent()
distance := levenshtein.DistanceForStrings([]rune(title), []rune(content), levenshtein.DefaultOptions)
similarityThreshold := 5
if distance <= similarityThreshold {
return ""
} else {
return title
}
}
func (ee EnhancedEvent) RssContent() string {
content := ee.Event.Content
content = basicFormatting(html.EscapeString(content), true, false, false)
content = renderQuotesAsHTML(context.Background(), content, false)
if nevent := ee.getParentNevent(); nevent != "" {
neventShort := nevent[:8] + "…" + nevent[len(nevent)-4:]
content = "In reply to <a href='/" + nevent + "'>" + neventShort + "</a><br/>_________________________<br/><br/>" + content
}
return content
}
func (ee EnhancedEvent) Thumb() string {
imgRegex := regexp.MustCompile(`(https?://[^\s]+\.(?:png|jpe?g|gif|bmp|svg)(?:/[^\s]*)?)`)
matches := imgRegex.FindAllStringSubmatch(ee.Event.Content, -1)
if len(matches) > 0 {
// The first match group captures the image URL
return matches[0][1]
}
return ""
}
func (ee EnhancedEvent) Npub() string {
npub, _ := nip19.EncodePublicKey(ee.Event.PubKey)
return npub
}
func (ee EnhancedEvent) NpubShort() string {
npub := ee.Npub()
return npub[:8] + "…" + npub[len(npub)-4:]
}
func (ee EnhancedEvent) Nevent() string {
nevent, _ := nip19.EncodeEvent(ee.Event.ID, ee.relays, ee.Event.PubKey)
return nevent
}
func (ee EnhancedEvent) CreatedAtStr() string {
return time.Unix(int64(ee.Event.CreatedAt), 0).Format("2006-01-02 15:04:05")
}
func (ee EnhancedEvent) ModifiedAtStr() string {
return time.Unix(int64(ee.Event.CreatedAt), 0).Format("2006-01-02T15:04:05Z07:00")
}
func (ee EnhancedEvent) ToJSONHTML() template.HTML {
tagsHTML := "["
for t, tag := range ee.Tags {
tagsHTML += "\n ["
for i, item := range tag {
cls := `"text-zinc-500 dark:text-zinc-50"`
if i == 0 {
cls = `"text-amber-500 dark:text-amber-200"`
}
itemJSON, _ := json.Marshal(item)
tagsHTML += "\n <span class=" + cls + ">" + html.EscapeString(string(itemJSON))
if i < len(tag)-1 {
tagsHTML += ","
} else {
tagsHTML += "\n "
}
}
tagsHTML += "]"
if t < len(ee.Tags)-1 {
tagsHTML += ","
} else {
tagsHTML += "\n "
}
}
tagsHTML += "]"
contentJSON, _ := json.Marshal(ee.Content)
keyCls := "text-purple-700 dark:text-purple-300"
return template.HTML(fmt.Sprintf(
`{
<span class="`+keyCls+`">"id":</span> <span class="text-zinc-500 dark:text-zinc-50">"%s"</span>,
<span class="`+keyCls+`">"pubkey":</span> <span class="text-zinc-500 dark:text-zinc-50">"%s"</span>,
<span class="`+keyCls+`">"created_at":</span> <span class="text-green-600">%d</span>,
<span class="`+keyCls+`">"kind":</span> <span class="text-amber-500 dark:text-amber-200">%d</span>,
<span class="`+keyCls+`">"tags":</span> %s,
<span class="`+keyCls+`">"content":</span> <span class="text-zinc-500 dark:text-zinc-50">%s</span>,
<span class="`+keyCls+`">"sig":</span> <span class="text-zinc-500 dark:text-zinc-50 content">"%s"</span>
}`, ee.ID, ee.PubKey, ee.CreatedAt, ee.Kind, tagsHTML, html.EscapeString(string(contentJSON)), ee.Sig),
)
}
type Kind1063Metadata struct {
nip94.FileMetadata
}

View File

@@ -4,6 +4,7 @@ import (
_ "embed"
"github.com/nbd-wtf/go-nostr/nip11"
sdk "github.com/nbd-wtf/nostr-sdk"
"github.com/tylermmorton/tmpl"
)
@@ -18,11 +19,11 @@ type SitemapPage struct {
ModifiedAt string
// for the profile sitemap
Metadata Metadata
Metadata sdk.ProfileMetadata
// for the relay sitemap
RelayHostname string
Info *nip11.RelayInformationDocument
Info nip11.RelayInformationDocument
// for the profile and relay sitemaps
LastNotes []EnhancedEvent
@@ -59,11 +60,11 @@ type RSSPage struct {
Title string
// for the profile RSS
Metadata Metadata
Metadata sdk.ProfileMetadata
// for the relay RSS
RelayHostname string
Info *nip11.RelayInformationDocument
Info nip11.RelayInformationDocument
// for the profile and relay RSSs
LastNotes []EnhancedEvent