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}