bluesky appview implementation using microcosm and other services server.reddwarf.app
appview bluesky reddwarf microcosm

profileviews

Changed files
+226 -10
shims
lex
app
bsky
actor
utils
+61 -10
main.go
··· 2 2 3 3 import ( 4 4 "context" 5 + "encoding/json" 6 + "flag" 5 7 "fmt" 6 8 "log" 7 9 "net/http" 8 - "os" 9 10 "time" 10 11 11 12 "tangled.org/whey.party/red-dwarf-server/microcosm/constellation" 12 13 "tangled.org/whey.party/red-dwarf-server/microcosm/slingshot" 14 + appbskyactordefs "tangled.org/whey.party/red-dwarf-server/shims/lex/app/bsky/actor" 15 + "tangled.org/whey.party/red-dwarf-server/shims/utils" 13 16 "tangled.org/whey.party/red-dwarf-server/sticket" 14 17 15 18 // "github.com/bluesky-social/indigo/atproto/atclient" ··· 22 25 // "github.com/bluesky-social/jetstream/pkg/models" 23 26 ) 24 27 28 + var ( 29 + JETSTREAM_URL string 30 + SPACEDUST_URL string 31 + SLINGSHOT_URL string 32 + CONSTELLATION_URL string 33 + ) 34 + 35 + func initURLs(prod bool) { 36 + if !prod { 37 + JETSTREAM_URL = "wss://jetstream.whey.party/subscribe" 38 + SPACEDUST_URL = "wss://spacedust.whey.party/subscribe" 39 + SLINGSHOT_URL = "https://slingshot.whey.party" 40 + CONSTELLATION_URL = "https://constellation.whey.party" 41 + } else { 42 + JETSTREAM_URL = "ws://localhost:6008/subscribe" 43 + SPACEDUST_URL = "ws://localhost:9998/subscribe" 44 + SLINGSHOT_URL = "http://localhost:7729" 45 + CONSTELLATION_URL = "http://localhost:7728" 46 + } 47 + } 48 + 25 49 const ( 26 - JETSTREAM_URL = "ws://localhost:6008/subscribe" 27 - SPACEDUST_URL = "ws://localhost:9998/subscribe" 28 - SLINGSHOT_URL = "http://localhost:7729" 29 - CONSTELLATION_URL = "http://localhost:7728" 50 + BSKYIMAGECDN_URL = "https://cdn.bsky.app" 51 + BSKYVIDEOCDN_URL = "https://video.bsky.app" 30 52 ) 31 53 32 54 func main() { 33 - fmt.Fprintf(os.Stdout, "red-dwarf-server started") 55 + log.Println("red-dwarf-server started") 56 + prod := flag.Bool("prod", false, "use production URLs instead of localhost") 57 + flag.Parse() 58 + 59 + initURLs(*prod) 34 60 35 61 ctx := context.Background() 36 62 mailbox := sticket.New() ··· 39 65 // spacedust is type definitions only 40 66 // jetstream types is probably available from jetstream/pkg/models 41 67 42 - responsewow, _ := agnostic.RepoGetRecord(ctx, sl, "", "app.bsky.feed.profile", "did:plc:44ybard66vv44zksje25o7dz", "self") 68 + responsewow, err := agnostic.RepoGetRecord(ctx, sl, "", "app.bsky.actor.profile", "did:web:did12.whey.party", "self") 69 + if err != nil { 70 + log.Println(err) 71 + } 72 + 73 + log.Println(responsewow.Uri) 74 + 75 + var didtest *utils.DID 76 + didval, errdid := utils.NewDID("did:web:did12.whey.party") 77 + if errdid != nil { 78 + didtest = nil 79 + } else { 80 + didtest = &didval 81 + } 82 + profiletest, _, _ := appbskyactordefs.ProfileViewBasic(ctx, *didtest, sl, BSKYIMAGECDN_URL) 43 83 44 - fmt.Fprintf(os.Stdout, responsewow.Uri) 84 + log.Println(*profiletest.DisplayName) 85 + log.Println(*profiletest.Avatar) 45 86 46 87 http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) { 47 88 mailbox.HandleWS(&w, r) 48 89 }) 49 90 91 + bskyappdid, _ := utils.NewDID("did:plc:z72i7hdynmk6r22z27h6tvur") 92 + 93 + profiletest2, _, _ := appbskyactordefs.ProfileViewDetailed(ctx, bskyappdid, sl, cs, BSKYIMAGECDN_URL) 94 + 95 + data, err := json.MarshalIndent(profiletest2, "", " ") 96 + if err != nil { 97 + panic(err) 98 + } 99 + fmt.Println(string(data)) 100 + 50 101 http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 51 - fmt.Fprintf(w, "hello worldio !") 102 + log.Println("hello worldio !") 52 103 clientUUID := sticket.GetUUIDFromRequest(r) 53 104 hasSticket := clientUUID != "" 54 105 if hasSticket { ··· 77 128 } 78 129 79 130 func getPostThreadV2(w http.ResponseWriter, r *http.Request) { 80 - fmt.Fprintf(w, "hello worldio !") 131 + log.Println("hello worldio !") 81 132 }
+124
shims/lex/app/bsky/actor/defs.go
··· 1 + package appbskyactordefs 2 + 3 + import ( 4 + "context" 5 + "encoding/json" 6 + 7 + "github.com/bluesky-social/indigo/api/agnostic" 8 + appbsky "github.com/bluesky-social/indigo/api/bsky" 9 + "tangled.org/whey.party/red-dwarf-server/microcosm" 10 + "tangled.org/whey.party/red-dwarf-server/microcosm/constellation" 11 + "tangled.org/whey.party/red-dwarf-server/microcosm/slingshot" 12 + "tangled.org/whey.party/red-dwarf-server/shims/utils" 13 + ) 14 + 15 + func ProfileViewBasic(ctx context.Context, did utils.DID, sl *microcosm.MicrocosmClient, imgcdn string) (*appbsky.ActorDefs_ProfileViewBasic, *appbsky.ActorProfile, error) { 16 + profileview, profile, err := ProfileView(ctx, did, sl, imgcdn) 17 + 18 + return &appbsky.ActorDefs_ProfileViewBasic{ 19 + Associated: profileview.Associated, 20 + Avatar: profileview.Avatar, 21 + CreatedAt: profileview.CreatedAt, 22 + Debug: profileview.Debug, 23 + Did: profileview.Did, 24 + DisplayName: profileview.DisplayName, 25 + Handle: profileview.Handle, 26 + Labels: profileview.Labels, 27 + Pronouns: profileview.Pronouns, 28 + Status: profileview.Status, 29 + Verification: profileview.Verification, 30 + Viewer: profileview.Viewer, 31 + }, profile, err 32 + } 33 + 34 + func ProfileView(ctx context.Context, did utils.DID, sl *microcosm.MicrocosmClient, imgcdn string) (*appbsky.ActorDefs_ProfileView, *appbsky.ActorProfile, error) { 35 + identity, err_i := slingshot.ResolveMiniDoc(ctx, sl, string(did)) 36 + if err_i != nil { 37 + identity = nil 38 + } 39 + profilerecord, err_r := agnostic.RepoGetRecord(ctx, sl, "", "app.bsky.actor.profile", string(did), "self") 40 + if err_r != nil { 41 + return nil, nil, err_r 42 + } 43 + 44 + var profile appbsky.ActorProfile 45 + if err := json.Unmarshal(*profilerecord.Value, &profile); err != nil { 46 + return nil, nil, err 47 + } 48 + 49 + var handle string 50 + if identity != nil { 51 + handle = identity.Handle 52 + } else { 53 + handle = string(did) 54 + } 55 + 56 + var displayName string 57 + if profile.DisplayName != nil { 58 + displayName = *profile.DisplayName 59 + } else { 60 + if handle != "" { 61 + displayName = handle 62 + } else { 63 + displayName = string(did) 64 + } 65 + } 66 + avatar := utils.MakeImageCDN(did, imgcdn, "avatar", profile.Avatar.Ref.String()) 67 + 68 + return &appbsky.ActorDefs_ProfileView{ 69 + Associated: nil, 70 + Avatar: &avatar, 71 + CreatedAt: profile.CreatedAt, 72 + Debug: nil, 73 + Description: profile.Description, 74 + Did: string(did), 75 + DisplayName: &displayName, 76 + Handle: handle, 77 + IndexedAt: profile.CreatedAt, 78 + Labels: nil, 79 + Pronouns: nil, 80 + Status: nil, 81 + Verification: nil, 82 + Viewer: nil, 83 + }, &profile, nil 84 + } 85 + 86 + func ProfileViewDetailed(ctx context.Context, did utils.DID, sl *microcosm.MicrocosmClient, cs *microcosm.MicrocosmClient, imgcdn string) (*appbsky.ActorDefs_ProfileViewDetailed, *appbsky.ActorProfile, error) { 87 + profileview, profile, err := ProfileView(ctx, did, sl, imgcdn) 88 + if err != nil { 89 + return nil, nil, err 90 + } 91 + followerCount_Out, err_i := constellation.LegacyLinksCountDistinctDids(ctx, cs, string(did), "app.bsky.graph.follow", ".subject", nil) 92 + if err_i != nil { 93 + followerCount_Out = nil 94 + } 95 + followerCount := int64(followerCount_Out.Total) 96 + 97 + banner := utils.MakeImageCDN(did, imgcdn, "banner", profile.Avatar.Ref.String()) 98 + 99 + nilCount := int64(-1) 100 + 101 + return &appbsky.ActorDefs_ProfileViewDetailed{ 102 + Associated: profileview.Associated, 103 + Avatar: profileview.Avatar, 104 + Banner: &banner, 105 + CreatedAt: profileview.CreatedAt, 106 + Debug: profileview.Debug, 107 + Description: profileview.Description, 108 + Did: profileview.Did, 109 + DisplayName: profileview.DisplayName, 110 + FollowersCount: &followerCount, 111 + FollowsCount: &nilCount, // hardcoded placeholder 112 + Handle: profileview.Handle, 113 + IndexedAt: profileview.IndexedAt, 114 + JoinedViaStarterPack: nil, // hardcoded placeholder 115 + Labels: profileview.Labels, 116 + PinnedPost: profile.PinnedPost, 117 + PostsCount: &nilCount, // hardcoded placeholder 118 + Pronouns: profileview.Pronouns, 119 + Status: profileview.Status, 120 + Verification: profileview.Verification, 121 + Viewer: profileview.Viewer, 122 + Website: profile.Website, 123 + }, profile, nil 124 + }
+41
shims/utils/utils.go
··· 1 + package utils 2 + 3 + import ( 4 + "fmt" 5 + "regexp" 6 + ) 7 + 8 + type DID string 9 + 10 + var didPattern = regexp.MustCompile(`^did:(plc|web):.+$`) 11 + 12 + func NewDID(s string) (DID, error) { 13 + if !didPattern.MatchString(s) { 14 + return "", fmt.Errorf("invalid DID: %s", s) 15 + } 16 + return DID(s), nil 17 + } 18 + 19 + type AtURI string 20 + 21 + var atUriPattern = regexp.MustCompile(`^at://did:(plc|web):.+/.+/.+$`) 22 + 23 + func NewAtURI(s string) (AtURI, error) { 24 + if !atUriPattern.MatchString(s) { 25 + return "", fmt.Errorf("invalid AtURI: %s", s) 26 + } 27 + return AtURI(s), nil 28 + } 29 + 30 + func SafeStringPtr(s *string) *string { 31 + if s != nil { 32 + return s 33 + } 34 + return nil 35 + } 36 + 37 + func PtrString(s string) *string { return &s } 38 + 39 + func MakeImageCDN(did DID, imgcdn string, kind string, cid string) string { 40 + return imgcdn + "/img/" + kind + "/plain/" + string(did) + "/" + cid + "@jpeg" 41 + }