this repo has no description
13
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 163 lines 4.2 kB view raw
1package main 2 3import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "log/slog" 8 "sort" 9 "strings" 10 11 "github.com/bluesky-social/indigo/api/agnostic" 12 "github.com/bluesky-social/indigo/atproto/atclient" 13 "github.com/bluesky-social/indigo/atproto/identity" 14 "github.com/bluesky-social/indigo/atproto/lexicon" 15 "github.com/bluesky-social/indigo/atproto/syntax" 16 17 "github.com/urfave/cli/v3" 18) 19 20var ( 21 schemaNSID = syntax.NSID("com.atproto.lexicon.schema") 22) 23 24func nsidGroup(nsid syntax.NSID) string { 25 parts := strings.Split(string(nsid), ".") 26 g := strings.Join(parts[0:len(parts)-1], ".") + "." 27 return g 28} 29 30// Checks if a string is a valid NSID group pattern, which is a partial NSID ending in '.' or '.*' 31func ParseNSIDGroup(raw string) (string, error) { 32 if strings.HasSuffix(raw, ".*") { 33 raw = raw[:len(raw)-1] 34 } 35 if !strings.HasSuffix(raw, ".") { 36 return "", fmt.Errorf("not an NSID group pattern") 37 } 38 _, err := syntax.ParseNSID(raw + "name") 39 if err != nil { 40 return "", fmt.Errorf("not an NSID group pattern") 41 } 42 return raw, nil 43} 44 45// helper which runs a comparison function across local and remote schemas, based on 'cmd' configuration 46func runComparisons(ctx context.Context, cmd *cli.Command, comp func(ctx context.Context, cmd *cli.Command, nsid syntax.NSID, localJSON, remoteJSON json.RawMessage) error) error { 47 48 // collect all NSID/path mappings 49 localSchemas, err := collectSchemaJSON(cmd) 50 if err != nil { 51 return err 52 } 53 remoteSchemas := map[syntax.NSID]json.RawMessage{} 54 55 localGroups := map[string]bool{} 56 allNSIDMap := map[syntax.NSID]bool{} 57 for k := range localSchemas { 58 g := nsidGroup(k) 59 localGroups[g] = true 60 allNSIDMap[k] = true 61 } 62 63 for g := range localGroups { 64 if err := resolveLexiconGroup(ctx, cmd, g, &remoteSchemas); err != nil { 65 return err 66 } 67 } 68 69 for k := range remoteSchemas { 70 allNSIDMap[k] = true 71 } 72 allNSID := []string{} 73 for k := range allNSIDMap { 74 allNSID = append(allNSID, string(k)) 75 } 76 sort.Strings(allNSID) 77 78 anyFailures := false 79 for _, k := range allNSID { 80 nsid := syntax.NSID(k) 81 if err := comp(ctx, cmd, nsid, localSchemas[nsid], remoteSchemas[nsid]); err != nil { 82 if err != ErrLintFailures { 83 return err 84 } 85 anyFailures = true 86 } 87 } 88 89 if anyFailures { 90 return ErrLintFailures 91 } 92 return nil 93} 94 95// helper which resolves and fetches all lexicon schemas (as JSON), storing them in provided map 96func resolveLexiconGroup(ctx context.Context, cmd *cli.Command, group string, remote *map[syntax.NSID]json.RawMessage) error { 97 98 slog.Debug("resolving schemas for NSID group", "group", group) 99 100 // TODO: netclient support for listing records 101 dir := identity.BaseDirectory{} 102 did, err := dir.ResolveNSID(ctx, syntax.NSID(group+"name")) 103 if err != nil { 104 // if NSID isn't registered, just skip comparison 105 slog.Warn("skipping NSID pattern which did not resolve", "group", group) 106 return nil 107 } 108 ident, err := dir.LookupDID(ctx, did) 109 if err != nil { 110 return err 111 } 112 c := atclient.NewAPIClient(ident.PDSEndpoint()) 113 114 cursor := "" 115 for { 116 // collection string, cursor string, limit int64, repo string, reverse bool 117 resp, err := agnostic.RepoListRecords(ctx, c, schemaNSID.String(), cursor, 100, ident.DID.String(), false) 118 if err != nil { 119 return err 120 } 121 for _, rec := range resp.Records { 122 aturi, err := syntax.ParseATURI(rec.Uri) 123 if err != nil { 124 return err 125 } 126 nsid, err := syntax.ParseNSID(aturi.RecordKey().String()) 127 if err != nil { 128 slog.Warn("ignoring invalid schema NSID", "did", ident.DID, "rkey", aturi.RecordKey()) 129 continue 130 } 131 if nsidGroup(nsid) != group { 132 // ignoring other NSIDs 133 continue 134 } 135 if rec.Value == nil { 136 return fmt.Errorf("missing record value: %s", nsid) 137 } 138 139 // parse file to check for errors 140 // TODO: use json/v2 when available for case-sensitivity 141 var sf lexicon.SchemaFile 142 err = json.Unmarshal(*rec.Value, &sf) 143 if err == nil { 144 err = sf.FinishParse() 145 } 146 if err == nil { 147 err = sf.CheckSchema() 148 } 149 if err != nil { 150 return fmt.Errorf("invalid lexicon schema record (%s): %w", nsid, err) 151 } 152 153 (*remote)[nsid] = *rec.Value 154 155 } 156 if resp.Cursor != nil && *resp.Cursor != "" { 157 cursor = *resp.Cursor 158 } else { 159 break 160 } 161 } 162 return nil 163}