A locally focused bluesky appview
1package main
2
3import (
4 "bytes"
5 "context"
6 "fmt"
7 "log/slog"
8 "strings"
9
10 "github.com/bluesky-social/indigo/api/atproto"
11 "github.com/bluesky-social/indigo/api/bsky"
12 "github.com/bluesky-social/indigo/atproto/syntax"
13 xrpclib "github.com/bluesky-social/indigo/xrpc"
14 "github.com/ipfs/go-cid"
15 "github.com/labstack/gommon/log"
16)
17
18func (s *Server) addMissingProfile(ctx context.Context, did string) {
19 select {
20 case s.missingProfiles <- did:
21 case <-ctx.Done():
22 }
23}
24
25func (s *Server) missingProfileFetcher() {
26 for did := range s.missingProfiles {
27 if err := s.fetchMissingProfile(context.TODO(), did); err != nil {
28 log.Warn("failed to fetch missing profile", "did", did, "error", err)
29 }
30 }
31}
32
33func (s *Server) fetchMissingProfile(ctx context.Context, did string) error {
34 repo, err := s.backend.getOrCreateRepo(ctx, did)
35 if err != nil {
36 return err
37 }
38
39 resp, err := s.dir.LookupDID(ctx, syntax.DID(did))
40 if err != nil {
41 return err
42 }
43
44 c := &xrpclib.Client{
45 Host: resp.PDSEndpoint(),
46 }
47
48 rec, err := atproto.RepoGetRecord(ctx, c, "", "app.bsky.actor.profile", did, "self")
49 if err != nil {
50 return err
51 }
52
53 prof, ok := rec.Value.Val.(*bsky.ActorProfile)
54 if !ok {
55 return fmt.Errorf("record we got back wasnt a profile somehow")
56 }
57
58 buf := new(bytes.Buffer)
59 if err := prof.MarshalCBOR(buf); err != nil {
60 return err
61 }
62
63 cc, err := cid.Decode(*rec.Cid)
64 if err != nil {
65 return err
66 }
67
68 return s.backend.HandleUpdateProfile(ctx, repo, "self", "", buf.Bytes(), cc)
69}
70
71func (s *Server) addMissingPost(ctx context.Context, uri string) {
72 slog.Info("adding missing post to fetch queue", "uri", uri)
73 select {
74 case s.missingPosts <- uri:
75 case <-ctx.Done():
76 }
77}
78
79func (s *Server) missingPostFetcher() {
80 for uri := range s.missingPosts {
81 if err := s.fetchMissingPost(context.TODO(), uri); err != nil {
82 log.Warn("failed to fetch missing post", "uri", uri, "error", err)
83 }
84 }
85}
86
87func (s *Server) fetchMissingPost(ctx context.Context, uri string) error {
88 // Parse AT URI: at://did:plc:xxx/app.bsky.feed.post/rkey
89 parts := strings.Split(uri, "/")
90 if len(parts) < 5 || !strings.HasPrefix(parts[2], "did:") {
91 return fmt.Errorf("invalid AT URI: %s", uri)
92 }
93
94 did := parts[2]
95 collection := parts[3]
96 rkey := parts[4]
97
98 repo, err := s.backend.getOrCreateRepo(ctx, did)
99 if err != nil {
100 return err
101 }
102
103 resp, err := s.dir.LookupDID(ctx, syntax.DID(did))
104 if err != nil {
105 return err
106 }
107
108 c := &xrpclib.Client{
109 Host: resp.PDSEndpoint(),
110 }
111
112 rec, err := atproto.RepoGetRecord(ctx, c, "", collection, did, rkey)
113 if err != nil {
114 return err
115 }
116
117 post, ok := rec.Value.Val.(*bsky.FeedPost)
118 if !ok {
119 return fmt.Errorf("record we got back wasn't a post somehow")
120 }
121
122 buf := new(bytes.Buffer)
123 if err := post.MarshalCBOR(buf); err != nil {
124 return err
125 }
126
127 cc, err := cid.Decode(*rec.Cid)
128 if err != nil {
129 return err
130 }
131
132 return s.backend.HandleCreatePost(ctx, repo, rkey, buf.Bytes(), cc)
133}