clone

a self-hosted multitenant nostr relay with sites, file storage, git hosting, and inbox and outbox delivery

main
tinyrelay / tinyclient / social_profile.go
social_profile.go 2.6K
1package tinyclient23import (4 "encoding/hex"5 "encoding/json"6 "errors"7 "net/http"8 "strings"9)1011// socialProfileAuthor accepts the relay's canonical hex key and the usual12// Nostr npub spelling, returning the canonical key used by backend queries.13func socialProfileAuthor(raw string) (string, error) {14 raw = strings.TrimSpace(raw)15 if strings.HasPrefix(strings.ToLower(raw), "npub1") {16 hrp, data, ok := bech32Decode(strings.ToLower(raw))17 if !ok || hrp != "npub" || len(data) != 32 {18 return "", errors.New("invalid npub")19 }20 return hex.EncodeToString(data), nil21 }22 if len(raw) != 64 {23 return "", errors.New("author must be a 64-character public key or npub")24 }25 if _, err := hex.DecodeString(raw); err != nil {26 return "", errors.New("author must be a 64-character public key or npub")27 }28 return strings.ToLower(raw), nil29}3031func (a *App) socialProfilePage(w http.ResponseWriter, r *http.Request) {32 actor, actorErr := a.resolveActor(r)33 query := r.URL.Query()34 data := PageData{Tab: "social-profile", Title: "Profile | " + a.backend.Slug(), Query: query}35 if actorErr != nil {36 data.Error = actorErr.Error()37 w.WriteHeader(socialErrorStatus(actorErr))38 a.render(w, r, data)39 return40 }41 author := strings.TrimSpace(query.Get("author"))42 if author == "" {43 author = actor44 }45 if author == "" {46 a.render(w, r, data)47 return48 }49 canonical, err := socialProfileAuthor(author)50 if err != nil {51 data.Error = err.Error()52 w.WriteHeader(http.StatusBadRequest)53 a.render(w, r, data)54 return55 }56 data.View = canonical57 data.Query.Set("author", canonical)5859 profileRaw, err := json.Marshal(map[string]any{"pubkey": canonical})60 if err != nil {61 http.Error(w, "invalid profile query", http.StatusBadRequest)62 return63 }64 profile, profileErr := a.backend.Query(r.Context(), "browseprofile", []json.RawMessage{profileRaw}, actor)65 if profileErr != nil {66 data.Error = profileErr.Error()67 w.WriteHeader(socialErrorStatus(profileErr))68 a.render(w, r, data)69 return70 }71 feedParams := socialParameters(data.Query)72 feedParams["author"] = canonical73 feedRaw, marshalErr := json.Marshal(feedParams)74 if marshalErr != nil {75 http.Error(w, "invalid social query", http.StatusBadRequest)76 return77 }78 feed, feedErr := a.backend.Query(r.Context(), "browsesocial", []json.RawMessage{feedRaw}, actor)79 if feedErr != nil {80 data.Error = feedErr.Error()81 w.WriteHeader(socialErrorStatus(feedErr))82 }83 data.Event = map[string]any{84 "profile": podcastProfile(valueMap(profile)["profile"], valueMap(feed)["show"]),85 "show": valueMap(feed)["show"],86 "pubkey": canonical,87 "next_cursor": valueMap(feed)["next_cursor"],88 }89 data.Feed = browseRows(feed)90 a.render(w, r, data)91}