diff --git a/data.go b/data.go index 1c88ab8..3a49928 100644 --- a/data.go +++ b/data.go @@ -62,7 +62,6 @@ func (ee EnhancedEvent) ModifiedAtStr() string { type Data struct { templateId TemplateID - typ string event *nostr.Event relays []string npub string @@ -112,7 +111,6 @@ func grabData(ctx context.Context, code string, isProfileSitemap bool) (*Data, e parentNevent := "" authorRelays := []string{} var content string - var typ string var templateId TemplateID eventRelays := []string{} @@ -133,7 +131,6 @@ func grabData(ctx context.Context, code string, isProfileSitemap bool) (*Data, e key := "" eventsToFetch := 100 if isProfileSitemap { - typ = "profile_sitemap" key = "lns:" + event.PubKey eventsToFetch = 50000 } else { @@ -246,7 +243,6 @@ func grabData(ctx context.Context, code string, isProfileSitemap bool) (*Data, e return &Data{ templateId: templateId, - typ: typ, event: event, relays: eventRelays, npub: npub, diff --git a/main.go b/main.go index 2b90028..a72e24e 100644 --- a/main.go +++ b/main.go @@ -3,7 +3,6 @@ package main import ( "context" "embed" - "html" "net/http" "os" "text/template" @@ -67,28 +66,6 @@ func main() { ctx := context.Background() go updateArchives(ctx) - // initialize templates - // use a mapping to expressly link the templates and share them between more kinds/types - templateMapping = map[string]string{ - "profile_sitemap": "sitemap.xml", - } - - funcMap := template.FuncMap{ - "s": func() Settings { return s }, - "basicFormatting": func(input string) string { return basicFormatting(input, false, false) }, - "previewNotesFormatting": previewNotesFormatting, - "escapeString": html.EscapeString, - "sanitizeXSS": sanitizeXSS, - "trimProtocol": trimProtocol, - "normalizeWebsiteURL": normalizeWebsiteURL, - } - - tmpls = template.Must( - template.New("tmpl"). - Funcs(funcMap). - ParseFS(templates, "templates/*"), - ) - // routes mux := http.NewServeMux() mux.Handle("/njump/static/", http.StripPrefix("/njump/", http.FileServer(http.FS(static)))) @@ -101,7 +78,10 @@ func main() { mux.HandleFunc("/njump/proxy/", proxy) mux.HandleFunc("/favicon.ico", renderFavicon) mux.HandleFunc("/robots.txt", renderRobots) - mux.HandleFunc("/try", renderTry) + mux.HandleFunc("/r/", renderRelayPage) + mux.HandleFunc("/try", redirectFromFormSubmit) + mux.HandleFunc("/e/", redirectFromESlash) + mux.HandleFunc("/p/", redirectFromPSlash) mux.HandleFunc("/", render) log.Print("listening at http://0.0.0.0:" + s.Port) diff --git a/pages.go b/pages.go index 7a4e54e..24d54e1 100644 --- a/pages.go +++ b/pages.go @@ -14,8 +14,7 @@ import ( type TemplateID int const ( - Profile TemplateID = iota - Note + Note TemplateID = iota LongForm TelegramInstantView Other diff --git a/redirect.go b/redirect.go new file mode 100644 index 0000000..b209713 --- /dev/null +++ b/redirect.go @@ -0,0 +1,29 @@ +package main + +import ( + "net/http" + + "github.com/nbd-wtf/go-nostr/nip19" +) + +func redirectFromFormSubmit(w http.ResponseWriter, r *http.Request) { + err := r.ParseForm() + if err != nil { + http.Error(w, "failed to parse form data", http.StatusBadRequest) + return + } + nip19entity := r.FormValue("nip19entity") + http.Redirect(w, r, "/"+nip19entity, http.StatusFound) +} + +func redirectFromPSlash(w http.ResponseWriter, r *http.Request) { + code, _ := nip19.EncodePublicKey(r.URL.Path[3:]) + http.Redirect(w, r, "/"+code, http.StatusFound) + return +} + +func redirectFromESlash(w http.ResponseWriter, r *http.Request) { + code, _ := nip19.EncodeEvent(r.URL.Path[3:], []string{}, "") + http.Redirect(w, r, "/"+code, http.StatusFound) + return +} diff --git a/render.go b/render.go index f6324b1..2285033 100644 --- a/render.go +++ b/render.go @@ -18,35 +18,25 @@ func render(w http.ResponseWriter, r *http.Request) { fmt.Println(r.URL.Path, "#/", r.Header.Get("user-agent")) w.Header().Set("Content-Type", "text/html") - code := r.URL.Path[1:] - isProfileSitemap := false + code := r.URL.Path[1:] // hopefully a nip19 code - if strings.HasPrefix(code, "e/") { - code, _ = nip19.EncodeEvent(code[2:], []string{}, "") - } else if strings.HasPrefix(code, "p/") { - if urlSuffixMatcher.MatchString(code) { - // it's a nip05 - code = code[2:] - } else { - // it's a hex pubkey - code, _ = nip19.EncodePublicKey(code[2:]) - } - } else if strings.HasPrefix(code, "r/") { - hostname := code[2:] - if strings.HasPrefix(hostname, "wss:/") || strings.HasPrefix(hostname, "ws:/") { - hostname = trimProtocol(hostname) - http.Redirect(w, r, "/r/"+hostname, http.StatusFound) - } else { - renderRelayPage(w, r) - } + // it's the homepage + if code == "" { + renderHomepage(w, r) return - } else if strings.HasPrefix(code, "nostr:") { - http.Redirect(w, r, "/"+code[6:], http.StatusFound) - } else if strings.HasPrefix(code, "npub") && strings.HasSuffix(code, ".xml") { - isProfileSitemap = true - code = code[:len(code)-4] } + if strings.HasPrefix(code, "nostr:") { + // remove the "nostr:" prefix + http.Redirect(w, r, "/"+code[6:], http.StatusFound) + return + } else if strings.HasPrefix(code, "npub") || strings.HasPrefix(code, "nprofile") { + // it's a profile + renderProfile(w, r, code) + return + } + + // force note1 to become nevent1 if strings.HasPrefix(code, "note1") { _, redirectHex, err := nip19.Decode(code) if err != nil { @@ -58,15 +48,10 @@ func render(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, "/"+redirectNevent, http.StatusFound) } - if code == "" { - renderHomepage(w, r) - return - } - host := r.Header.Get("X-Forwarded-Host") style := getPreviewStyle(r) - data, err := grabData(r.Context(), code, isProfileSitemap) + data, err := grabData(r.Context(), code, false) if err != nil { w.Header().Set("Cache-Control", "max-age=60") http.Error(w, "error fetching event: "+err.Error(), 404) @@ -194,20 +179,18 @@ func render(w http.ResponseWriter, r *http.Request) { data.content = strings.ReplaceAll(data.content, placeholderTag, "nostr:"+nreplace) } if data.event.Kind == 30023 || data.event.Kind == 30024 { - data.content = mdToHTML(data.content, data.typ == "telegram_instant_view") + data.content = mdToHTML(data.content, data.templateId == TelegramInstantView) } else { // first we run basicFormatting, which turns URLs into their appropriate HTML tags data.content = basicFormatting(html.EscapeString(data.content), true, false) // then we render quotes as HTML, which will also apply basicFormatting to all the internal quotes - data.content = renderQuotesAsHTML(r.Context(), data.content, data.typ == "telegram_instant_view") + data.content = renderQuotesAsHTML(r.Context(), data.content, data.templateId == TelegramInstantView) // we must do this because inside we must treat s different when telegram_instant_view } - if data.typ == "telegram_instant_view" { + if data.templateId == TelegramInstantView { w.Header().Set("Cache-Control", "no-cache") - } else if strings.Contains(data.typ, "profile") && len(data.renderableLastNotes) != 0 { - w.Header().Set("Cache-Control", "max-age=3600") - } else if !strings.Contains(data.typ, "profile") && len(data.content) != 0 { + } else if len(data.content) != 0 { w.Header().Set("Cache-Control", "max-age=604800") } else { w.Header().Set("Cache-Control", "max-age=60") @@ -231,49 +214,6 @@ func render(w http.ResponseWriter, r *http.Request) { w.Header().Add("Link", "<"+oembed+"&format=xml>; rel=\"alternate\"; type=\"text/xml+oembed\"") } - if currentTemplate, ok := templateMapping[data.typ]; ok { - // template stuff - params := map[string]any{ - "style": style, - "createdAt": data.createdAt, - "modifiedAt": data.modifiedAt, - "clients": generateClientList(code, data.event), - "type": data.typ, - "title": title, - "titleizedContent": titleizedContent, - "twitterTitle": twitterTitle, - "npub": data.npub, - "npubShort": data.npubShort, - "nevent": data.nevent, - "naddr": data.naddr, - "metadata": data.metadata, - "authorLong": data.authorLong, - "subject": subject, - "description": description, - "summary": summary, - "event": data.event, - "eventJSON": string(eventJSON), - "content": data.content, - "textImageURL": textImageURL, - "videoType": data.videoType, - "image": data.image, - "video": data.video, - "proxy": "https://" + host + "/njump/proxy?src=", - "kindDescription": data.kindDescription, - "kindNIP": data.kindNIP, - "lastNotes": data.renderableLastNotes, - "seenOn": data.relays, - "parentNevent": data.parentNevent, - "authorRelays": data.authorRelays, - "oembed": oembed, - } - - if err := tmpls.ExecuteTemplate(w, currentTemplate, params); err != nil { - log.Error().Err(err).Msg("error rendering") - return - } - } - // migrating to templ switch data.templateId { case TelegramInstantView: @@ -328,28 +268,6 @@ func render(w http.ResponseWriter, r *http.Request) { Video: data.video, VideoType: data.videoType, }) - case Profile: - err = ProfileTemplate.Render(w, &ProfilePage{ - HeadCommonPartial: HeadCommonPartial{IsProfile: true}, - DetailsPartial: DetailsPartial{ - HideDetails: true, - CreatedAt: data.createdAt, - KindDescription: data.kindDescription, - KindNIP: data.kindNIP, - EventJSON: string(eventJSON), - Kind: data.event.Kind, - }, - ClientsPartial: ClientsPartial{ - Clients: generateClientList(code, data.event), - }, - - Metadata: data.metadata, - NormalizedAuthorWebsiteURL: normalizeWebsiteURL(data.metadata.Website), - RenderedAuthorAboutText: template.HTML(basicFormatting(html.EscapeString(data.metadata.About), false, false)), - Npub: data.npub, - AuthorRelays: data.authorRelays, - LastNotes: data.renderableLastNotes, - }) case Other: err = OtherTemplate.Render(w, &OtherPage{ HeadCommonPartial: HeadCommonPartial{IsProfile: false}, diff --git a/render_profile.go b/render_profile.go new file mode 100644 index 0000000..1dd3ba1 --- /dev/null +++ b/render_profile.go @@ -0,0 +1,61 @@ +package main + +import ( + "encoding/json" + "fmt" + "html" + "html/template" + "net/http" + "strings" +) + +func renderProfile(w http.ResponseWriter, r *http.Request, code string) { + fmt.Println(r.URL.Path, "@.", r.Header.Get("user-agent")) + w.Header().Set("Content-Type", "text/html") + + isProfileSitemap := false + if strings.HasSuffix(code, ".xml") { + isProfileSitemap = true + code = code[:len(code)-4] + } + + data, err := grabData(r.Context(), code, isProfileSitemap) + if err != nil { + w.Header().Set("Cache-Control", "max-age=60") + http.Error(w, "error fetching event: "+err.Error(), 404) + return + } + + if len(data.renderableLastNotes) != 0 { + w.Header().Set("Cache-Control", "max-age=3600") + } + + // pretty JSON + eventJSON, _ := json.MarshalIndent(data.event, "", " ") + + err = ProfileTemplate.Render(w, &ProfilePage{ + HeadCommonPartial: HeadCommonPartial{IsProfile: true}, + DetailsPartial: DetailsPartial{ + HideDetails: true, + CreatedAt: data.createdAt, + KindDescription: data.kindDescription, + KindNIP: data.kindNIP, + EventJSON: string(eventJSON), + Kind: data.event.Kind, + }, + ClientsPartial: ClientsPartial{ + Clients: generateClientList(code, data.event), + }, + + Metadata: data.metadata, + NormalizedAuthorWebsiteURL: normalizeWebsiteURL(data.metadata.Website), + RenderedAuthorAboutText: template.HTML(basicFormatting(html.EscapeString(data.metadata.About), false, false)), + Npub: data.npub, + AuthorRelays: data.authorRelays, + LastNotes: data.renderableLastNotes, + }) + if err != nil { + log.Error().Err(err).Msg("error rendering tmpl") + } + return +} diff --git a/render_relay.go b/render_relay.go index d0dd03e..3ea4c13 100644 --- a/render_relay.go +++ b/render_relay.go @@ -11,8 +11,14 @@ import ( ) func renderRelayPage(w http.ResponseWriter, r *http.Request) { - code := r.URL.Path[1:] - hostname := code[2:] + hostname := r.URL.Path[3:] + + if strings.HasPrefix(hostname, "wss:/") || strings.HasPrefix(hostname, "ws:/") { + hostname = trimProtocol(hostname) + http.Redirect(w, r, "/r/"+hostname, http.StatusFound) + return + } + isSitemap := false numResults := 1000 diff --git a/render_try.go b/render_try.go deleted file mode 100644 index ca319c4..0000000 --- a/render_try.go +++ /dev/null @@ -1,15 +0,0 @@ -package main - -import ( - "net/http" -) - -func renderTry(w http.ResponseWriter, r *http.Request) { - err := r.ParseForm() - if err != nil { - http.Error(w, "Failed to parse form data", http.StatusBadRequest) - return - } - nip19entity := r.FormValue("nip19entity") - http.Redirect(w, r, "/"+nip19entity, http.StatusFound) -}