this repo has no description
0
fork

Configure Feed

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

gosky: bunch of moves and refactors (#348)

Started with combining `getRepo` and `downloadRepo`, and ended up moving
around a bunch of sub-commands and adding usage notes. individual
commits are descriptive.

did not change any of the behavior/implementation of individual commands
in this PR.

the most disruptive thing is probably moving `createInvites` to `admin
createInvites`.

removed the "rebaseRepo" command entirely.

happy to revert or re-structure, this is just a proposal.

top-level output:

```
COMMANDS:
admin sub-commands for PDS administration
bgs sub-commands for administering a BGS
car sub-commands to work with CAR files on local disk
createFeedGen
debug a set of debugging utilities for atproto
did sub-commands for working with DIDs
getRecord
handle sub-commands for working handles
list print all of the records for a repo or local CAR file
readStream
sync sub-commands for repo sync endpoints
help, h Shows a list of commands or help for one command
```

authored by

bnewbold and committed by
GitHub
88a7f1a2 d4dfa2e3

+1103 -1125
+215
cmd/gosky/account.go
··· 1 + package main 2 + 3 + import ( 4 + "bufio" 5 + "context" 6 + "encoding/json" 7 + "fmt" 8 + "os" 9 + 10 + comatproto "github.com/bluesky-social/indigo/api/atproto" 11 + "github.com/bluesky-social/indigo/util/cliutil" 12 + 13 + cli "github.com/urfave/cli/v2" 14 + ) 15 + 16 + var accountCmd = &cli.Command{ 17 + Name: "account", 18 + Usage: "sub-commands for auth session and account management", 19 + Subcommands: []*cli.Command{ 20 + createSessionCmd, 21 + newAccountCmd, 22 + refreshAuthTokenCmd, 23 + resetPasswordCmd, 24 + requestAccountDeletionCmd, 25 + deleteAccountCmd, 26 + }, 27 + } 28 + 29 + var createSessionCmd = &cli.Command{ 30 + Name: "create-session", 31 + ArgsUsage: `<handle> <password>`, 32 + Action: func(cctx *cli.Context) error { 33 + xrpcc, err := cliutil.GetXrpcClient(cctx, false) 34 + if err != nil { 35 + return err 36 + } 37 + args, err := needArgs(cctx, "handle", "password") 38 + if err != nil { 39 + return err 40 + } 41 + handle, password := args[0], args[1] 42 + 43 + ses, err := comatproto.ServerCreateSession(context.TODO(), xrpcc, &comatproto.ServerCreateSession_Input{ 44 + Identifier: handle, 45 + Password: password, 46 + }) 47 + if err != nil { 48 + return err 49 + } 50 + 51 + b, err := json.MarshalIndent(ses, "", " ") 52 + if err != nil { 53 + return err 54 + } 55 + 56 + fmt.Println(string(b)) 57 + return nil 58 + }, 59 + } 60 + 61 + var newAccountCmd = &cli.Command{ 62 + Name: "new", 63 + ArgsUsage: `<email> <handle> <password> [inviteCode]`, 64 + Action: func(cctx *cli.Context) error { 65 + xrpcc, err := cliutil.GetXrpcClient(cctx, false) 66 + if err != nil { 67 + return err 68 + } 69 + 70 + args, err := needArgs(cctx, "email", "handle", "password") 71 + if err != nil { 72 + return err 73 + } 74 + email, handle, password := args[0], args[1], args[2] 75 + 76 + var invite *string 77 + if inv := cctx.Args().Get(3); inv != "" { 78 + invite = &inv 79 + } 80 + 81 + acc, err := comatproto.ServerCreateAccount(context.TODO(), xrpcc, &comatproto.ServerCreateAccount_Input{ 82 + Email: email, 83 + Handle: handle, 84 + InviteCode: invite, 85 + Password: password, 86 + }) 87 + if err != nil { 88 + return err 89 + } 90 + 91 + b, err := json.MarshalIndent(acc, "", " ") 92 + if err != nil { 93 + return err 94 + } 95 + 96 + fmt.Println(string(b)) 97 + return nil 98 + }, 99 + } 100 + 101 + var resetPasswordCmd = &cli.Command{ 102 + Name: "reset-password", 103 + ArgsUsage: `<email>`, 104 + Action: func(cctx *cli.Context) error { 105 + ctx := context.TODO() 106 + 107 + xrpcc, err := cliutil.GetXrpcClient(cctx, false) 108 + if err != nil { 109 + return err 110 + } 111 + 112 + args, err := needArgs(cctx, "email") 113 + if err != nil { 114 + return err 115 + } 116 + email := args[0] 117 + 118 + err = comatproto.ServerRequestPasswordReset(ctx, xrpcc, &comatproto.ServerRequestPasswordReset_Input{ 119 + Email: email, 120 + }) 121 + if err != nil { 122 + return err 123 + } 124 + 125 + inp := bufio.NewScanner(os.Stdin) 126 + fmt.Println("Enter recovery code from email:") 127 + inp.Scan() 128 + code := inp.Text() 129 + 130 + fmt.Println("Enter new password:") 131 + inp.Scan() 132 + npass := inp.Text() 133 + 134 + if err := comatproto.ServerResetPassword(ctx, xrpcc, &comatproto.ServerResetPassword_Input{ 135 + Password: npass, 136 + Token: code, 137 + }); err != nil { 138 + return err 139 + } 140 + 141 + return nil 142 + }, 143 + } 144 + 145 + var refreshAuthTokenCmd = &cli.Command{ 146 + Name: "refresh-session", 147 + Usage: "refresh your auth token and overwrite it with new auth info", 148 + Action: func(cctx *cli.Context) error { 149 + xrpcc, err := cliutil.GetXrpcClient(cctx, true) 150 + if err != nil { 151 + return err 152 + } 153 + 154 + a := xrpcc.Auth 155 + a.AccessJwt = a.RefreshJwt 156 + 157 + ctx := context.TODO() 158 + nauth, err := comatproto.ServerRefreshSession(ctx, xrpcc) 159 + if err != nil { 160 + return err 161 + } 162 + 163 + b, err := json.Marshal(nauth) 164 + if err != nil { 165 + return err 166 + } 167 + 168 + if err := os.WriteFile(cctx.String("auth"), b, 0600); err != nil { 169 + return err 170 + } 171 + 172 + return nil 173 + }, 174 + } 175 + 176 + var requestAccountDeletionCmd = &cli.Command{ 177 + Name: "request-deletion", 178 + Action: func(cctx *cli.Context) error { 179 + xrpcc, err := cliutil.GetXrpcClient(cctx, false) 180 + if err != nil { 181 + return err 182 + } 183 + 184 + err = comatproto.ServerRequestAccountDelete(cctx.Context, xrpcc) 185 + if err != nil { 186 + return err 187 + } 188 + 189 + return nil 190 + }, 191 + } 192 + 193 + var deleteAccountCmd = &cli.Command{ 194 + Name: "delete", 195 + Action: func(cctx *cli.Context) error { 196 + xrpcc, err := cliutil.GetXrpcClient(cctx, false) 197 + if err != nil { 198 + return err 199 + } 200 + 201 + token := cctx.Args().First() 202 + password := cctx.Args().Get(1) 203 + 204 + err = comatproto.ServerDeleteAccount(cctx.Context, xrpcc, &comatproto.ServerDeleteAccount_Input{ 205 + Did: xrpcc.Auth.Did, 206 + Token: token, 207 + Password: password, 208 + }) 209 + if err != nil { 210 + return err 211 + } 212 + 213 + return nil 214 + }, 215 + }
+118 -73
cmd/gosky/admin.go
··· 12 12 13 13 "github.com/bluesky-social/indigo/api" 14 14 "github.com/bluesky-social/indigo/api/atproto" 15 + comatproto "github.com/bluesky-social/indigo/api/atproto" 15 16 "github.com/bluesky-social/indigo/util/cliutil" 16 17 cli "github.com/urfave/cli/v2" 17 18 ) 18 19 19 20 var adminCmd = &cli.Command{ 20 - Name: "admin", 21 + Name: "admin", 22 + Usage: "sub-commands for PDS administration", 23 + Flags: []cli.Flag{ 24 + &cli.StringFlag{ 25 + Name: "admin-password", 26 + EnvVars: []string{"ATP_AUTH_ADMIN_PASSWORD"}, 27 + Required: true, 28 + }, 29 + }, 21 30 Subcommands: []*cli.Command{ 22 31 buildInviteTreeCmd, 23 32 checkUserCmd, 24 - reportsCmd, 33 + createInviteCmd, 25 34 disableInvitesCmd, 26 35 enableInvitesCmd, 36 + getModerationActionsCmd, 27 37 listInviteTreeCmd, 38 + reportsCmd, 28 39 takeDownAccountCmd, 29 - getModerationActionsCmd, 30 40 }, 31 41 } 32 42 33 43 var checkUserCmd = &cli.Command{ 34 - Name: "checkUser", 44 + Name: "check-user", 35 45 Flags: []cli.Flag{ 36 - &cli.StringFlag{ 37 - Name: "admin-password", 38 - EnvVars: []string{"ATP_AUTH_ADMIN_PASSWORD"}, 39 - Required: true, 40 - }, 41 - &cli.StringFlag{ 42 - Name: "plc", 43 - Usage: "method, hostname, and port of PLC registry", 44 - Value: "https://plc.directory", 45 - EnvVars: []string{"ATP_PLC_HOST"}, 46 - }, 47 46 &cli.BoolFlag{ 48 47 Name: "raw", 49 48 }, ··· 169 168 } 170 169 171 170 var buildInviteTreeCmd = &cli.Command{ 172 - Name: "buildInviteTree", 171 + Name: "build-invite-tree", 173 172 Flags: []cli.Flag{ 174 - &cli.StringFlag{ 175 - Name: "admin-password", 176 - EnvVars: []string{"ATP_AUTH_ADMIN_PASSWORD"}, 177 - Required: true, 178 - }, 179 173 &cli.StringFlag{ 180 174 Name: "invite-list", 181 175 }, ··· 363 357 var listReportsCmd = &cli.Command{ 364 358 Name: "list", 365 359 Flags: []cli.Flag{ 366 - &cli.StringFlag{ 367 - Name: "admin-password", 368 - EnvVars: []string{"ATP_AUTH_ADMIN_PASSWORD"}, 369 - Required: true, 370 - }, 371 - &cli.StringFlag{ 372 - Name: "plc", 373 - Usage: "method, hostname, and port of PLC registry", 374 - Value: "https://plc.directory", 375 - EnvVars: []string{"ATP_PLC_HOST"}, 376 - }, 377 360 &cli.BoolFlag{ 378 361 Name: "raw", 379 362 }, ··· 431 414 } 432 415 433 416 var disableInvitesCmd = &cli.Command{ 434 - Name: "disableInvites", 435 - Flags: []cli.Flag{ 436 - &cli.StringFlag{ 437 - Name: "admin-password", 438 - EnvVars: []string{"ATP_AUTH_ADMIN_PASSWORD"}, 439 - Required: true, 440 - }, 441 - }, 417 + Name: "disable-invites", 442 418 Action: func(cctx *cli.Context) error { 443 419 444 420 xrpcc, err := cliutil.GetXrpcClient(cctx, false) ··· 479 455 } 480 456 481 457 var enableInvitesCmd = &cli.Command{ 482 - Name: "enableInvites", 483 - Flags: []cli.Flag{ 484 - &cli.StringFlag{ 485 - Name: "admin-password", 486 - EnvVars: []string{"ATP_AUTH_ADMIN_PASSWORD"}, 487 - Required: true, 488 - }, 489 - }, 458 + Name: "enable-invites", 490 459 Action: func(cctx *cli.Context) error { 491 460 492 461 xrpcc, err := cliutil.GetXrpcClient(cctx, false) ··· 517 486 } 518 487 519 488 var listInviteTreeCmd = &cli.Command{ 520 - Name: "listInviteTree", 489 + Name: "list-invite-tree", 521 490 Flags: []cli.Flag{ 522 - &cli.StringFlag{ 523 - Name: "admin-password", 524 - EnvVars: []string{"ATP_AUTH_ADMIN_PASSWORD"}, 525 - Required: true, 526 - }, 527 - &cli.StringFlag{ 528 - Name: "plc", 529 - Usage: "method, hostname, and port of PLC registry", 530 - Value: "https://plc.directory", 531 - EnvVars: []string{"ATP_PLC_HOST"}, 532 - }, 533 491 &cli.BoolFlag{ 534 492 Name: "disable-invites", 535 493 Usage: "additionally disable invites for all printed DIDs", ··· 628 586 } 629 587 630 588 var takeDownAccountCmd = &cli.Command{ 631 - Name: "takeDownAccount", 589 + Name: "account-takedown", 632 590 Flags: []cli.Flag{ 633 591 &cli.StringFlag{ 634 - Name: "admin-password", 635 - EnvVars: []string{"ATP_AUTH_ADMIN_PASSWORD"}, 636 - Required: true, 637 - }, 638 - &cli.StringFlag{ 639 592 Name: "reason", 640 593 Usage: "why the account is being taken down", 641 594 Required: true, ··· 735 688 736 689 var getModerationActionsCmd = &cli.Command{ 737 690 Name: "get-moderation-actions", 738 - Flags: []cli.Flag{ 739 - &cli.StringFlag{ 740 - Name: "admin-password", 741 - EnvVars: []string{"ATP_AUTH_ADMIN_PASSWORD"}, 742 - Required: true, 743 - }, 744 - }, 745 691 Action: func(cctx *cli.Context) error { 746 692 747 693 xrpcc, err := cliutil.GetXrpcClient(cctx, false) ··· 779 725 return nil 780 726 }, 781 727 } 728 + 729 + var createInviteCmd = &cli.Command{ 730 + Name: "create-invites", 731 + Flags: []cli.Flag{ 732 + &cli.IntFlag{ 733 + Name: "useCount", 734 + Value: 1, 735 + }, 736 + &cli.IntFlag{ 737 + Name: "num", 738 + Value: 1, 739 + }, 740 + &cli.StringFlag{ 741 + Name: "bulk", 742 + }, 743 + }, 744 + ArgsUsage: "[handle]", 745 + Action: func(cctx *cli.Context) error { 746 + xrpcc, err := cliutil.GetXrpcClient(cctx, false) 747 + if err != nil { 748 + return err 749 + } 750 + 751 + adminKey := cctx.String("admin-password") 752 + 753 + count := cctx.Int("useCount") 754 + num := cctx.Int("num") 755 + 756 + phr := &api.ProdHandleResolver{} 757 + if bulkfi := cctx.String("bulk"); bulkfi != "" { 758 + xrpcc.AdminToken = &adminKey 759 + dids, err := readDids(bulkfi) 760 + if err != nil { 761 + return err 762 + } 763 + 764 + for i, d := range dids { 765 + if !strings.HasPrefix(d, "did:plc:") { 766 + out, err := phr.ResolveHandleToDid(context.TODO(), d) 767 + if err != nil { 768 + return fmt.Errorf("failed to resolve %q: %w", d, err) 769 + } 770 + 771 + dids[i] = out 772 + } 773 + } 774 + 775 + for n := 0; n < len(dids); n += 500 { 776 + slice := dids 777 + if len(slice) > 500 { 778 + slice = slice[:500] 779 + } 780 + 781 + _, err = comatproto.ServerCreateInviteCodes(context.TODO(), xrpcc, &comatproto.ServerCreateInviteCodes_Input{ 782 + UseCount: int64(count), 783 + ForAccounts: slice, 784 + CodeCount: int64(num), 785 + }) 786 + if err != nil { 787 + return err 788 + } 789 + } 790 + 791 + return nil 792 + } 793 + 794 + var usrdid []string 795 + if forUser := cctx.Args().Get(0); forUser != "" { 796 + if !strings.HasPrefix(forUser, "did:") { 797 + resp, err := phr.ResolveHandleToDid(context.TODO(), forUser) 798 + if err != nil { 799 + return fmt.Errorf("resolving handle: %w", err) 800 + } 801 + 802 + usrdid = []string{resp} 803 + } else { 804 + usrdid = []string{forUser} 805 + } 806 + } 807 + 808 + xrpcc.AdminToken = &adminKey 809 + resp, err := comatproto.ServerCreateInviteCodes(context.TODO(), xrpcc, &comatproto.ServerCreateInviteCodes_Input{ 810 + UseCount: int64(count), 811 + ForAccounts: usrdid, 812 + CodeCount: int64(num), 813 + }) 814 + if err != nil { 815 + return fmt.Errorf("creating codes: %w", err) 816 + } 817 + 818 + for _, c := range resp.Codes { 819 + for _, cc := range c.Codes { 820 + fmt.Println(cc) 821 + } 822 + } 823 + 824 + return nil 825 + }, 826 + }
+2 -1
cmd/gosky/bgs.go
··· 13 13 ) 14 14 15 15 var bgsAdminCmd = &cli.Command{ 16 - Name: "bgs", 16 + Name: "bgs", 17 + Usage: "sub-commands for administering a BGS", 17 18 Flags: []cli.Flag{ 18 19 &cli.StringFlag{ 19 20 Name: "key",
+353
cmd/gosky/bsky.go
··· 1 + package main 2 + 3 + import ( 4 + "context" 5 + "encoding/json" 6 + "fmt" 7 + "strings" 8 + "time" 9 + 10 + comatproto "github.com/bluesky-social/indigo/api/atproto" 11 + appbsky "github.com/bluesky-social/indigo/api/bsky" 12 + lexutil "github.com/bluesky-social/indigo/lex/util" 13 + "github.com/bluesky-social/indigo/util" 14 + "github.com/bluesky-social/indigo/util/cliutil" 15 + 16 + cli "github.com/urfave/cli/v2" 17 + ) 18 + 19 + var bskyCmd = &cli.Command{ 20 + Name: "bsky", 21 + Usage: "sub-commands for bsky-specific endpoints", 22 + Subcommands: []*cli.Command{ 23 + bskyFollowCmd, 24 + bskyListFollowsCmd, 25 + bskyPostCmd, 26 + bskyGetFeedCmd, 27 + bskyLikeCmd, 28 + bskyDeletePostCmd, 29 + bskyActorGetSuggestionsCmd, 30 + bskyNotificationsCmd, 31 + }, 32 + } 33 + 34 + var bskyFollowCmd = &cli.Command{ 35 + Name: "follow", 36 + Usage: "create a follow relationship (auth required)", 37 + Flags: []cli.Flag{}, 38 + ArgsUsage: `<user>`, 39 + Action: func(cctx *cli.Context) error { 40 + xrpcc, err := cliutil.GetXrpcClient(cctx, true) 41 + if err != nil { 42 + return err 43 + } 44 + 45 + user := cctx.Args().First() 46 + 47 + follow := appbsky.GraphFollow{ 48 + LexiconTypeID: "app.bsky.graph.follow", 49 + CreatedAt: time.Now().Format(time.RFC3339), 50 + Subject: user, 51 + } 52 + 53 + resp, err := comatproto.RepoCreateRecord(context.TODO(), xrpcc, &comatproto.RepoCreateRecord_Input{ 54 + Collection: "app.bsky.graph.follow", 55 + Repo: xrpcc.Auth.Did, 56 + Record: &lexutil.LexiconTypeDecoder{&follow}, 57 + }) 58 + if err != nil { 59 + return err 60 + } 61 + 62 + fmt.Println(resp.Uri) 63 + 64 + return nil 65 + }, 66 + } 67 + 68 + var bskyListFollowsCmd = &cli.Command{ 69 + Name: "list-follows", 70 + Usage: "print list of follows for account", 71 + ArgsUsage: `[actor]`, 72 + Action: func(cctx *cli.Context) error { 73 + xrpcc, err := cliutil.GetXrpcClient(cctx, false) 74 + if err != nil { 75 + return err 76 + } 77 + 78 + user := cctx.Args().First() 79 + if user == "" { 80 + user = xrpcc.Auth.Did 81 + } 82 + 83 + ctx := context.TODO() 84 + resp, err := appbsky.GraphGetFollows(ctx, xrpcc, user, "", 100) 85 + if err != nil { 86 + return err 87 + } 88 + 89 + for _, f := range resp.Follows { 90 + fmt.Println(f.Did, f.Handle) 91 + } 92 + 93 + return nil 94 + }, 95 + } 96 + 97 + var bskyPostCmd = &cli.Command{ 98 + Name: "post", 99 + Usage: "create a post record", 100 + ArgsUsage: `<text>`, 101 + Action: func(cctx *cli.Context) error { 102 + xrpcc, err := cliutil.GetXrpcClient(cctx, true) 103 + if err != nil { 104 + return err 105 + } 106 + 107 + auth := xrpcc.Auth 108 + 109 + text := strings.Join(cctx.Args().Slice(), " ") 110 + 111 + resp, err := comatproto.RepoCreateRecord(context.TODO(), xrpcc, &comatproto.RepoCreateRecord_Input{ 112 + Collection: "app.bsky.feed.post", 113 + Repo: auth.Did, 114 + Record: &lexutil.LexiconTypeDecoder{&appbsky.FeedPost{ 115 + Text: text, 116 + CreatedAt: time.Now().Format(util.ISO8601), 117 + }}, 118 + }) 119 + if err != nil { 120 + return fmt.Errorf("failed to create post: %w", err) 121 + } 122 + 123 + fmt.Println(resp.Cid) 124 + fmt.Println(resp.Uri) 125 + 126 + return nil 127 + }, 128 + } 129 + 130 + func prettyPrintPost(p *appbsky.FeedDefs_FeedViewPost, uris bool) { 131 + fmt.Println(strings.Repeat("-", 60)) 132 + rec := p.Post.Record.Val.(*appbsky.FeedPost) 133 + fmt.Printf("%s (%s)", p.Post.Author.Handle, rec.CreatedAt) 134 + if uris { 135 + fmt.Println(" -- ", p.Post.Uri) 136 + } else { 137 + fmt.Println(":") 138 + } 139 + fmt.Println(rec.Text) 140 + } 141 + 142 + var bskyGetFeedCmd = &cli.Command{ 143 + Name: "get-feed", 144 + Usage: "fetch bsky feed", 145 + Flags: []cli.Flag{ 146 + &cli.IntFlag{ 147 + Name: "count", 148 + Value: 100, 149 + }, 150 + &cli.StringFlag{ 151 + Name: "author", 152 + Usage: "specify handle of user to list their authored feed", 153 + }, 154 + &cli.BoolFlag{ 155 + Name: "raw", 156 + Usage: "print out feed in raw json", 157 + }, 158 + &cli.BoolFlag{ 159 + Name: "uris", 160 + Usage: "include URIs in pretty print output", 161 + }, 162 + }, 163 + Action: func(cctx *cli.Context) error { 164 + xrpcc, err := cliutil.GetXrpcClient(cctx, true) 165 + if err != nil { 166 + return err 167 + } 168 + 169 + ctx := context.TODO() 170 + 171 + raw := cctx.Bool("raw") 172 + 173 + uris := cctx.Bool("uris") 174 + 175 + author := cctx.String("author") 176 + if author != "" { 177 + if author == "self" { 178 + author = xrpcc.Auth.Did 179 + } 180 + 181 + tl, err := appbsky.FeedGetAuthorFeed(ctx, xrpcc, author, "", "", 99) 182 + if err != nil { 183 + return err 184 + } 185 + 186 + for i := len(tl.Feed) - 1; i >= 0; i-- { 187 + it := tl.Feed[i] 188 + if raw { 189 + jsonPrint(it) 190 + } else { 191 + prettyPrintPost(it, uris) 192 + } 193 + } 194 + } else { 195 + algo := "reverse-chronological" 196 + tl, err := appbsky.FeedGetTimeline(ctx, xrpcc, algo, "", int64(cctx.Int("count"))) 197 + if err != nil { 198 + return err 199 + } 200 + 201 + for i := len(tl.Feed) - 1; i >= 0; i-- { 202 + it := tl.Feed[i] 203 + if raw { 204 + jsonPrint(it) 205 + } else { 206 + prettyPrintPost(it, uris) 207 + } 208 + } 209 + } 210 + 211 + return nil 212 + 213 + }, 214 + } 215 + 216 + var bskyActorGetSuggestionsCmd = &cli.Command{ 217 + Name: "actor-get-suggestions", 218 + ArgsUsage: "[author]", 219 + Action: func(cctx *cli.Context) error { 220 + xrpcc, err := cliutil.GetXrpcClient(cctx, true) 221 + if err != nil { 222 + return err 223 + } 224 + 225 + ctx := context.TODO() 226 + 227 + author := cctx.Args().First() 228 + if author == "" { 229 + author = xrpcc.Auth.Did 230 + } 231 + 232 + resp, err := appbsky.ActorGetSuggestions(ctx, xrpcc, "", 100) 233 + if err != nil { 234 + return err 235 + } 236 + 237 + b, err := json.MarshalIndent(resp.Actors, "", " ") 238 + if err != nil { 239 + return err 240 + } 241 + 242 + fmt.Println(string(b)) 243 + 244 + return nil 245 + 246 + }, 247 + } 248 + 249 + var bskyLikeCmd = &cli.Command{ 250 + Name: "like", 251 + Usage: "create bsky 'like' record", 252 + ArgsUsage: "<post>", 253 + Action: func(cctx *cli.Context) error { 254 + xrpcc, err := cliutil.GetXrpcClient(cctx, true) 255 + if err != nil { 256 + return err 257 + } 258 + 259 + arg := cctx.Args().First() 260 + 261 + parts := strings.Split(arg, "/") 262 + if len(parts) < 3 { 263 + return fmt.Errorf("invalid post uri: %q", arg) 264 + } 265 + rkey := parts[len(parts)-1] 266 + collection := parts[len(parts)-2] 267 + did := parts[2] 268 + 269 + fmt.Println(did, collection, rkey) 270 + ctx := context.TODO() 271 + resp, err := comatproto.RepoGetRecord(ctx, xrpcc, "", collection, did, rkey) 272 + if err != nil { 273 + return fmt.Errorf("getting record: %w", err) 274 + } 275 + 276 + out, err := comatproto.RepoCreateRecord(ctx, xrpcc, &comatproto.RepoCreateRecord_Input{ 277 + Collection: "app.bsky.feed.like", 278 + Repo: xrpcc.Auth.Did, 279 + Record: &lexutil.LexiconTypeDecoder{ 280 + Val: &appbsky.FeedLike{ 281 + CreatedAt: time.Now().Format(util.ISO8601), 282 + Subject: &comatproto.RepoStrongRef{Uri: resp.Uri, Cid: *resp.Cid}, 283 + }, 284 + }, 285 + }) 286 + if err != nil { 287 + return fmt.Errorf("creating like failed: %w", err) 288 + } 289 + _ = out 290 + return nil 291 + 292 + }, 293 + } 294 + 295 + var bskyDeletePostCmd = &cli.Command{ 296 + Name: "delete-post", 297 + ArgsUsage: `<rkey>`, 298 + Action: func(cctx *cli.Context) error { 299 + xrpcc, err := cliutil.GetXrpcClient(cctx, true) 300 + if err != nil { 301 + return err 302 + } 303 + 304 + rkey := cctx.Args().First() 305 + 306 + if rkey == "" { 307 + return fmt.Errorf("must specify rkey of post to delete") 308 + } 309 + 310 + schema := "app.bsky.feed.post" 311 + if strings.Contains(rkey, "/") { 312 + parts := strings.Split(rkey, "/") 313 + schema = parts[0] 314 + rkey = parts[1] 315 + } 316 + 317 + return comatproto.RepoDeleteRecord(context.TODO(), xrpcc, &comatproto.RepoDeleteRecord_Input{ 318 + Repo: xrpcc.Auth.Did, 319 + Collection: schema, 320 + Rkey: rkey, 321 + }) 322 + }, 323 + } 324 + 325 + var bskyNotificationsCmd = &cli.Command{ 326 + Name: "notifs", 327 + Usage: "fetch bsky notifications (requires auth)", 328 + Flags: []cli.Flag{}, 329 + Action: func(cctx *cli.Context) error { 330 + ctx := context.TODO() 331 + 332 + xrpcc, err := cliutil.GetXrpcClient(cctx, true) 333 + if err != nil { 334 + return err 335 + } 336 + 337 + notifs, err := appbsky.NotificationListNotifications(ctx, xrpcc, "", 50, "") 338 + if err != nil { 339 + return err 340 + } 341 + 342 + for _, n := range notifs.Notifications { 343 + b, err := json.Marshal(n) 344 + if err != nil { 345 + return err 346 + } 347 + 348 + fmt.Println(string(b)) 349 + } 350 + 351 + return nil 352 + }, 353 + }
+1 -1
cmd/gosky/car.go
··· 17 17 18 18 var carCmd = &cli.Command{ 19 19 Name: "car", 20 - Usage: "commands to work with CAR files on local disk", 20 + Usage: "sub-commands to work with CAR files on local disk", 21 21 Subcommands: []*cli.Command{ 22 22 carUnpackCmd, 23 23 },
+3 -3
cmd/gosky/debug.go
··· 36 36 ) 37 37 38 38 var debugCmd = &cli.Command{ 39 - Name: "debug", 40 - Description: "a set of debugging utilities for atproto", 39 + Name: "debug", 40 + Usage: "a set of debugging utilities for atproto", 41 41 Subcommands: []*cli.Command{ 42 42 inspectEventCmd, 43 43 debugStreamCmd, ··· 567 567 }, 568 568 } 569 569 var debugFeedViewCmd = &cli.Command{ 570 - Name: "viewFeed", 570 + Name: "view-feed", 571 571 Action: func(cctx *cli.Context) error { 572 572 xrpcc, err := cliutil.GetXrpcClient(cctx, true) 573 573 if err != nil {
+124
cmd/gosky/did.go
··· 1 + package main 2 + 3 + import ( 4 + "context" 5 + "encoding/json" 6 + "fmt" 7 + 8 + "github.com/bluesky-social/indigo/api" 9 + "github.com/bluesky-social/indigo/util/cliutil" 10 + 11 + cli "github.com/urfave/cli/v2" 12 + ) 13 + 14 + var didCmd = &cli.Command{ 15 + Name: "did", 16 + Usage: "sub-commands for working with DIDs", 17 + Flags: []cli.Flag{}, 18 + Subcommands: []*cli.Command{ 19 + didGetCmd, 20 + didCreateCmd, 21 + didKeyCmd, 22 + }, 23 + } 24 + 25 + var didGetCmd = &cli.Command{ 26 + Name: "get", 27 + ArgsUsage: `<did>`, 28 + Flags: []cli.Flag{ 29 + &cli.BoolFlag{ 30 + Name: "handle", 31 + Usage: "resolve did to handle and print", 32 + }, 33 + }, 34 + Action: func(cctx *cli.Context) error { 35 + s := cliutil.GetDidResolver(cctx) 36 + 37 + did := cctx.Args().First() 38 + 39 + if cctx.Bool("handle") { 40 + xrpcc, err := cliutil.GetXrpcClient(cctx, false) 41 + if err != nil { 42 + return err 43 + } 44 + 45 + phr := &api.ProdHandleResolver{} 46 + h, _, err := api.ResolveDidToHandle(context.TODO(), xrpcc, s, phr, did) 47 + if err != nil { 48 + return err 49 + } 50 + 51 + fmt.Println(h) 52 + return nil 53 + } 54 + 55 + doc, err := s.GetDocument(context.TODO(), did) 56 + if err != nil { 57 + return err 58 + } 59 + 60 + b, err := json.MarshalIndent(doc, "", " ") 61 + if err != nil { 62 + return err 63 + } 64 + 65 + fmt.Println(string(b)) 66 + return nil 67 + }, 68 + } 69 + 70 + var didCreateCmd = &cli.Command{ 71 + Name: "create", 72 + ArgsUsage: `<handle> <service>`, 73 + Flags: []cli.Flag{ 74 + &cli.StringFlag{ 75 + Name: "recoverydid", 76 + }, 77 + &cli.StringFlag{ 78 + Name: "signingkey", 79 + }, 80 + }, 81 + Action: func(cctx *cli.Context) error { 82 + s := cliutil.GetPLCClient(cctx) 83 + 84 + args, err := needArgs(cctx, "handle", "service") 85 + if err != nil { 86 + return err 87 + } 88 + handle, service := args[0], args[1] 89 + 90 + recoverydid := cctx.String("recoverydid") 91 + 92 + sigkey, err := cliutil.LoadKeyFromFile(cctx.String("signingkey")) 93 + if err != nil { 94 + return err 95 + } 96 + 97 + fmt.Println("KEYDID: ", sigkey.Public().DID()) 98 + 99 + ndid, err := s.CreateDID(context.TODO(), sigkey, recoverydid, handle, service) 100 + if err != nil { 101 + return err 102 + } 103 + 104 + fmt.Println(ndid) 105 + return nil 106 + }, 107 + } 108 + 109 + var didKeyCmd = &cli.Command{ 110 + Name: "did-key", 111 + Flags: []cli.Flag{ 112 + &cli.StringFlag{ 113 + Name: "keypath", 114 + }, 115 + }, 116 + Action: func(cctx *cli.Context) error { 117 + sigkey, err := cliutil.LoadKeyFromFile(cctx.String("keypath")) 118 + if err != nil { 119 + return err 120 + } 121 + fmt.Println(sigkey.Public().DID()) 122 + return nil 123 + }, 124 + }
+73
cmd/gosky/handle.go
··· 1 + package main 2 + 3 + import ( 4 + "context" 5 + "fmt" 6 + 7 + api "github.com/bluesky-social/indigo/api" 8 + comatproto "github.com/bluesky-social/indigo/api/atproto" 9 + "github.com/bluesky-social/indigo/util/cliutil" 10 + 11 + cli "github.com/urfave/cli/v2" 12 + ) 13 + 14 + var handleCmd = &cli.Command{ 15 + Name: "handle", 16 + Usage: "sub-commands for working handles", 17 + Subcommands: []*cli.Command{ 18 + resolveHandleCmd, 19 + updateHandleCmd, 20 + }, 21 + } 22 + 23 + var resolveHandleCmd = &cli.Command{ 24 + Name: "resolve", 25 + ArgsUsage: `<handle>`, 26 + Action: func(cctx *cli.Context) error { 27 + ctx := context.TODO() 28 + 29 + args, err := needArgs(cctx, "handle") 30 + if err != nil { 31 + return err 32 + } 33 + handle := args[0] 34 + 35 + phr := &api.ProdHandleResolver{} 36 + out, err := phr.ResolveHandleToDid(ctx, handle) 37 + if err != nil { 38 + return err 39 + } 40 + 41 + fmt.Println(out) 42 + 43 + return nil 44 + }, 45 + } 46 + 47 + var updateHandleCmd = &cli.Command{ 48 + Name: "update", 49 + ArgsUsage: `<handle>`, 50 + Action: func(cctx *cli.Context) error { 51 + ctx := context.TODO() 52 + 53 + xrpcc, err := cliutil.GetXrpcClient(cctx, false) 54 + if err != nil { 55 + return err 56 + } 57 + 58 + args, err := needArgs(cctx, "handle") 59 + if err != nil { 60 + return err 61 + } 62 + handle := args[0] 63 + 64 + err = comatproto.IdentityUpdateHandle(ctx, xrpcc, &comatproto.IdentityUpdateHandle_Input{ 65 + Handle: handle, 66 + }) 67 + if err != nil { 68 + return err 69 + } 70 + 71 + return nil 72 + }, 73 + }
+84 -1047
cmd/gosky/main.go
··· 18 18 "github.com/bluesky-social/indigo/api/atproto" 19 19 comatproto "github.com/bluesky-social/indigo/api/atproto" 20 20 "github.com/bluesky-social/indigo/api/bsky" 21 - appbsky "github.com/bluesky-social/indigo/api/bsky" 22 - "github.com/bluesky-social/indigo/atproto/identity" 23 - "github.com/bluesky-social/indigo/atproto/syntax" 24 21 "github.com/bluesky-social/indigo/events" 25 22 "github.com/bluesky-social/indigo/events/schedulers/sequential" 26 23 lexutil "github.com/bluesky-social/indigo/lex/util" ··· 38 35 "github.com/ipld/go-car" 39 36 40 37 _ "github.com/joho/godotenv/autoload" 38 + _ "go.uber.org/automaxprocs" 41 39 42 40 logging "github.com/ipfs/go-log" 43 41 "github.com/polydawn/refmt/cbor" ··· 75 73 }, 76 74 &cli.StringFlag{ 77 75 Name: "plc", 76 + Usage: "method, hostname, and port of PLC registry", 78 77 Value: "https://plc.directory", 79 78 EnvVars: []string{"ATP_PLC_HOST"}, 80 79 }, 81 80 } 82 81 app.Commands = []*cli.Command{ 83 - actorGetSuggestionsCmd, 82 + adminCmd, 84 83 bgsAdminCmd, 85 - createSessionCmd, 86 84 carCmd, 87 85 debugCmd, 88 - deletePostCmd, 89 86 didCmd, 90 - feedGetCmd, 91 - feedSetVoteCmd, 92 - newAccountCmd, 93 - postCmd, 94 - refreshAuthTokenCmd, 95 - syncCmd, 96 - listAllPostsCmd, 97 - getNotificationsCmd, 98 - followsCmd, 99 - resetPasswordCmd, 100 - readRepoStreamCmd, 101 87 handleCmd, 102 - getRecordCmd, 103 - createInviteCmd, 104 - adminCmd, 88 + syncCmd, 105 89 createFeedGeneratorCmd, 106 - rebaseRepoCmd, 107 - requestAccountDeletionCmd, 108 - deleteAccountCmd, 90 + getRecordCmd, 91 + listAllRecordsCmd, 92 + readRepoStreamCmd, 109 93 } 110 94 111 95 app.RunAndExitOnError() 112 96 } 113 97 114 - var newAccountCmd = &cli.Command{ 115 - Name: "newAccount", 116 - ArgsUsage: `<email> <handle> <password> [inviteCode]`, 117 - Action: func(cctx *cli.Context) error { 118 - xrpcc, err := cliutil.GetXrpcClient(cctx, false) 119 - if err != nil { 120 - return err 121 - } 122 - 123 - args, err := needArgs(cctx, "email", "handle", "password") 124 - if err != nil { 125 - return err 126 - } 127 - email, handle, password := args[0], args[1], args[2] 128 - 129 - var invite *string 130 - if inv := cctx.Args().Get(3); inv != "" { 131 - invite = &inv 132 - } 133 - 134 - acc, err := comatproto.ServerCreateAccount(context.TODO(), xrpcc, &comatproto.ServerCreateAccount_Input{ 135 - Email: email, 136 - Handle: handle, 137 - InviteCode: invite, 138 - Password: password, 139 - }) 140 - if err != nil { 141 - return err 142 - } 143 - 144 - b, err := json.MarshalIndent(acc, "", " ") 145 - if err != nil { 146 - return err 147 - } 148 - 149 - fmt.Println(string(b)) 150 - return nil 151 - }, 152 - } 153 - var createSessionCmd = &cli.Command{ 154 - Name: "createSession", 155 - ArgsUsage: `<handle> <password>`, 156 - Action: func(cctx *cli.Context) error { 157 - xrpcc, err := cliutil.GetXrpcClient(cctx, false) 158 - if err != nil { 159 - return err 160 - } 161 - args, err := needArgs(cctx, "handle", "password") 162 - if err != nil { 163 - return err 164 - } 165 - handle, password := args[0], args[1] 166 - 167 - ses, err := comatproto.ServerCreateSession(context.TODO(), xrpcc, &comatproto.ServerCreateSession_Input{ 168 - Identifier: handle, 169 - Password: password, 170 - }) 171 - if err != nil { 172 - return err 173 - } 174 - 175 - b, err := json.MarshalIndent(ses, "", " ") 176 - if err != nil { 177 - return err 178 - } 179 - 180 - fmt.Println(string(b)) 181 - return nil 182 - }, 183 - } 184 - 185 - var postCmd = &cli.Command{ 186 - Name: "post", 187 - ArgsUsage: `<text>`, 188 - Action: func(cctx *cli.Context) error { 189 - xrpcc, err := cliutil.GetXrpcClient(cctx, true) 190 - if err != nil { 191 - return err 192 - } 193 - 194 - auth := xrpcc.Auth 195 - 196 - text := strings.Join(cctx.Args().Slice(), " ") 197 - 198 - resp, err := comatproto.RepoCreateRecord(context.TODO(), xrpcc, &comatproto.RepoCreateRecord_Input{ 199 - Collection: "app.bsky.feed.post", 200 - Repo: auth.Did, 201 - Record: &lexutil.LexiconTypeDecoder{&appbsky.FeedPost{ 202 - Text: text, 203 - CreatedAt: time.Now().UTC().Format(util.ISO8601), 204 - }}, 205 - }) 206 - if err != nil { 207 - return fmt.Errorf("failed to create post: %w", err) 208 - } 209 - 210 - fmt.Println(resp.Cid) 211 - fmt.Println(resp.Uri) 212 - 213 - return nil 214 - }, 215 - } 216 - 217 - var didCmd = &cli.Command{ 218 - Name: "did", 219 - Flags: []cli.Flag{}, 220 - Subcommands: []*cli.Command{ 221 - didGetCmd, 222 - didCreateCmd, 223 - didKeyCmd, 224 - }, 225 - } 226 - 227 - var didGetCmd = &cli.Command{ 228 - Name: "get", 229 - ArgsUsage: `<did>`, 230 - Flags: []cli.Flag{ 231 - &cli.BoolFlag{ 232 - Name: "handle", 233 - Usage: "resolve did to handle and print", 234 - }, 235 - }, 236 - Action: func(cctx *cli.Context) error { 237 - s := cliutil.GetDidResolver(cctx) 238 - 239 - did := cctx.Args().First() 240 - 241 - if cctx.Bool("handle") { 242 - xrpcc, err := cliutil.GetXrpcClient(cctx, false) 243 - if err != nil { 244 - return err 245 - } 246 - 247 - phr := &api.ProdHandleResolver{} 248 - h, _, err := api.ResolveDidToHandle(context.TODO(), xrpcc, s, phr, did) 249 - if err != nil { 250 - return err 251 - } 252 - 253 - fmt.Println(h) 254 - return nil 255 - } 256 - 257 - doc, err := s.GetDocument(context.TODO(), did) 258 - if err != nil { 259 - return err 260 - } 261 - 262 - b, err := json.MarshalIndent(doc, "", " ") 263 - if err != nil { 264 - return err 265 - } 266 - 267 - fmt.Println(string(b)) 268 - return nil 269 - }, 270 - } 271 - 272 - var didCreateCmd = &cli.Command{ 273 - Name: "create", 274 - ArgsUsage: `<handle> <service>`, 275 - Flags: []cli.Flag{ 276 - &cli.StringFlag{ 277 - Name: "recoverydid", 278 - }, 279 - &cli.StringFlag{ 280 - Name: "signingkey", 281 - }, 282 - }, 283 - Action: func(cctx *cli.Context) error { 284 - s := cliutil.GetPLCClient(cctx) 285 - 286 - args, err := needArgs(cctx, "handle", "service") 287 - if err != nil { 288 - return err 289 - } 290 - handle, service := args[0], args[1] 291 - 292 - recoverydid := cctx.String("recoverydid") 293 - 294 - sigkey, err := cliutil.LoadKeyFromFile(cctx.String("signingkey")) 295 - if err != nil { 296 - return err 297 - } 298 - 299 - fmt.Println("KEYDID: ", sigkey.Public().DID()) 300 - 301 - ndid, err := s.CreateDID(context.TODO(), sigkey, recoverydid, handle, service) 302 - if err != nil { 303 - return err 304 - } 305 - 306 - fmt.Println(ndid) 307 - return nil 308 - }, 309 - } 310 - 311 - var didKeyCmd = &cli.Command{ 312 - Name: "didKey", 313 - Flags: []cli.Flag{ 314 - &cli.StringFlag{ 315 - Name: "keypath", 316 - }, 317 - }, 318 - Action: func(cctx *cli.Context) error { 319 - sigkey, err := cliutil.LoadKeyFromFile(cctx.String("keypath")) 320 - if err != nil { 321 - return err 322 - } 323 - fmt.Println(sigkey.Public().DID()) 324 - return nil 325 - }, 326 - } 327 - 328 - var syncCmd = &cli.Command{ 329 - Name: "sync", 330 - Subcommands: []*cli.Command{ 331 - syncGetRepoCmd, 332 - syncDownloadRepoCmd, 333 - syncGetRootCmd, 334 - syncListReposCmd, 335 - }, 336 - } 337 - 338 - var syncGetRepoCmd = &cli.Command{ 339 - Name: "getRepo", 340 - Flags: []cli.Flag{ 341 - &cli.BoolFlag{ 342 - Name: "raw", 343 - }, 344 - }, 345 - ArgsUsage: `<did>`, 346 - Action: func(cctx *cli.Context) error { 347 - xrpcc, err := cliutil.GetXrpcClient(cctx, false) 348 - if err != nil { 349 - return err 350 - } 351 - 352 - ctx := context.TODO() 353 - 354 - repobytes, err := comatproto.SyncGetRepo(ctx, xrpcc, cctx.Args().First(), "") 355 - if err != nil { 356 - return err 357 - } 358 - 359 - if cctx.Bool("raw") { 360 - os.Stdout.Write(repobytes) 361 - } else { 362 - fmt.Printf("%x", repobytes) 363 - } 364 - 365 - return nil 366 - }, 367 - } 368 - 369 - var syncDownloadRepoCmd = &cli.Command{ 370 - Name: "downloadRepo", 371 - Flags: []cli.Flag{ 372 - &cli.StringFlag{ 373 - Name: "car-file", 374 - }, 375 - }, 376 - ArgsUsage: `<at-identifier>`, 377 - Action: func(cctx *cli.Context) error { 378 - ctx := context.Background() 379 - arg := cctx.Args().First() 380 - if arg == "" { 381 - return fmt.Errorf("identifier arg is required") 382 - } 383 - atid, err := syntax.ParseAtIdentifier(arg) 384 - if err != nil { 385 - return err 386 - } 387 - dir := identity.DefaultDirectory() 388 - ident, err := dir.Lookup(ctx, *atid) 389 - if err != nil { 390 - return err 391 - } 392 - 393 - carPath := cctx.String("car-file") 394 - if carPath == "" { 395 - carPath = ident.DID.String() + ".car" 396 - } 397 - 398 - xrpcc, err := cliutil.GetXrpcClient(cctx, false) 399 - if err != nil { 400 - return err 401 - } 402 - xrpcc.Host = ident.PDSEndpoint() 403 - if xrpcc.Host == "" { 404 - return fmt.Errorf("no PDS endpoint for identity") 405 - } 406 - 407 - log.Infof("downloading from %s to: %s", xrpcc.Host, carPath) 408 - repoBytes, err := comatproto.SyncGetRepo(ctx, xrpcc, ident.DID.String(), "") 409 - if err != nil { 410 - return err 411 - } 412 - 413 - return os.WriteFile(carPath, repoBytes, 0666) 414 - }, 415 - } 416 - 417 - var syncGetRootCmd = &cli.Command{ 418 - Name: "getRoot", 419 - ArgsUsage: `<did>`, 420 - Action: func(cctx *cli.Context) error { 421 - xrpcc, err := cliutil.GetXrpcClient(cctx, false) 422 - if err != nil { 423 - return err 424 - } 425 - 426 - ctx := context.TODO() 427 - 428 - root, err := comatproto.SyncGetHead(ctx, xrpcc, cctx.Args().First()) 429 - if err != nil { 430 - return err 431 - } 432 - 433 - fmt.Println(root.Root) 434 - 435 - return nil 436 - }, 437 - } 438 - 439 98 func jsonPrint(i any) { 440 99 b, err := json.MarshalIndent(i, "", " ") 441 100 if err != nil { ··· 445 104 fmt.Println(string(b)) 446 105 } 447 106 448 - func prettyPrintPost(p *appbsky.FeedDefs_FeedViewPost, uris bool) { 449 - fmt.Println(strings.Repeat("-", 60)) 450 - rec := p.Post.Record.Val.(*appbsky.FeedPost) 451 - fmt.Printf("%s (%s)", p.Post.Author.Handle, rec.CreatedAt) 452 - if uris { 453 - fmt.Println(" -- ", p.Post.Uri) 454 - } else { 455 - fmt.Println(":") 456 - } 457 - fmt.Println(rec.Text) 458 - } 459 - 460 - var feedGetCmd = &cli.Command{ 461 - Name: "feed", 462 - Flags: []cli.Flag{ 463 - &cli.IntFlag{ 464 - Name: "count", 465 - Value: 100, 466 - }, 467 - &cli.StringFlag{ 468 - Name: "author", 469 - Usage: "specify handle of user to list their authored feed", 470 - }, 471 - &cli.BoolFlag{ 472 - Name: "raw", 473 - Usage: "print out feed in raw json", 474 - }, 475 - &cli.BoolFlag{ 476 - Name: "uris", 477 - Usage: "include URIs in pretty print output", 478 - }, 479 - }, 480 - Action: func(cctx *cli.Context) error { 481 - xrpcc, err := cliutil.GetXrpcClient(cctx, true) 482 - if err != nil { 483 - return err 484 - } 485 - 486 - ctx := context.TODO() 487 - 488 - raw := cctx.Bool("raw") 489 - 490 - uris := cctx.Bool("uris") 491 - 492 - author := cctx.String("author") 493 - if author != "" { 494 - if author == "self" { 495 - author = xrpcc.Auth.Did 496 - } 497 - 498 - tl, err := appbsky.FeedGetAuthorFeed(ctx, xrpcc, author, "", "", 99) 499 - if err != nil { 500 - return err 501 - } 502 - 503 - for i := len(tl.Feed) - 1; i >= 0; i-- { 504 - it := tl.Feed[i] 505 - if raw { 506 - jsonPrint(it) 507 - } else { 508 - prettyPrintPost(it, uris) 509 - } 510 - } 511 - } else { 512 - algo := "reverse-chronological" 513 - tl, err := appbsky.FeedGetTimeline(ctx, xrpcc, algo, "", int64(cctx.Int("count"))) 514 - if err != nil { 515 - return err 516 - } 517 - 518 - for i := len(tl.Feed) - 1; i >= 0; i-- { 519 - it := tl.Feed[i] 520 - if raw { 521 - jsonPrint(it) 522 - } else { 523 - prettyPrintPost(it, uris) 524 - } 525 - } 526 - } 527 - 528 - return nil 529 - 530 - }, 531 - } 532 - 533 - var actorGetSuggestionsCmd = &cli.Command{ 534 - Name: "actorGetSuggestions", 535 - ArgsUsage: "[author]", 536 - Action: func(cctx *cli.Context) error { 537 - xrpcc, err := cliutil.GetXrpcClient(cctx, true) 538 - if err != nil { 539 - return err 540 - } 541 - 542 - ctx := context.TODO() 543 - 544 - author := cctx.Args().First() 545 - if author == "" { 546 - author = xrpcc.Auth.Did 547 - } 548 - 549 - resp, err := appbsky.ActorGetSuggestions(ctx, xrpcc, "", 100) 550 - if err != nil { 551 - return err 552 - } 553 - 554 - b, err := json.MarshalIndent(resp.Actors, "", " ") 555 - if err != nil { 556 - return err 557 - } 558 - 559 - fmt.Println(string(b)) 560 - 561 - return nil 562 - 563 - }, 564 - } 565 - 566 - var feedSetVoteCmd = &cli.Command{ 567 - Name: "vote", 568 - ArgsUsage: "<post>", 569 - Action: func(cctx *cli.Context) error { 570 - xrpcc, err := cliutil.GetXrpcClient(cctx, true) 571 - if err != nil { 572 - return err 573 - } 574 - 575 - arg := cctx.Args().First() 576 - 577 - parts := strings.Split(arg, "/") 578 - if len(parts) < 3 { 579 - return fmt.Errorf("invalid post uri: %q", arg) 580 - } 581 - rkey := parts[len(parts)-1] 582 - collection := parts[len(parts)-2] 583 - did := parts[2] 584 - 585 - fmt.Println(did, collection, rkey) 586 - ctx := context.TODO() 587 - resp, err := comatproto.RepoGetRecord(ctx, xrpcc, "", collection, did, rkey) 588 - if err != nil { 589 - return fmt.Errorf("getting record: %w", err) 590 - } 591 - 592 - out, err := comatproto.RepoCreateRecord(ctx, xrpcc, &comatproto.RepoCreateRecord_Input{ 593 - Collection: "app.bsky.feed.like", 594 - Repo: xrpcc.Auth.Did, 595 - Record: &lexutil.LexiconTypeDecoder{ 596 - Val: &appbsky.FeedLike{ 597 - CreatedAt: time.Now().Format(util.ISO8601), 598 - Subject: &comatproto.RepoStrongRef{Uri: resp.Uri, Cid: *resp.Cid}, 599 - }, 600 - }, 601 - }) 602 - if err != nil { 603 - return fmt.Errorf("creating vote failed: %w", err) 604 - } 605 - _ = out 606 - return nil 607 - 608 - }, 609 - } 610 - 611 - var refreshAuthTokenCmd = &cli.Command{ 612 - Name: "refresh", 613 - Usage: "refresh your auth token and overwrite it with new auth info", 614 - Action: func(cctx *cli.Context) error { 615 - xrpcc, err := cliutil.GetXrpcClient(cctx, true) 616 - if err != nil { 617 - return err 618 - } 619 - 620 - a := xrpcc.Auth 621 - a.AccessJwt = a.RefreshJwt 622 - 623 - ctx := context.TODO() 624 - nauth, err := comatproto.ServerRefreshSession(ctx, xrpcc) 625 - if err != nil { 626 - return err 627 - } 628 - 629 - b, err := json.Marshal(nauth) 630 - if err != nil { 631 - return err 632 - } 633 - 634 - if err := os.WriteFile(cctx.String("auth"), b, 0600); err != nil { 635 - return err 636 - } 637 - 638 - return nil 639 - }, 640 - } 641 - 642 - var deletePostCmd = &cli.Command{ 643 - Name: "delete", 644 - ArgsUsage: `<rkey>`, 645 - Action: func(cctx *cli.Context) error { 646 - xrpcc, err := cliutil.GetXrpcClient(cctx, true) 647 - if err != nil { 648 - return err 649 - } 650 - 651 - rkey := cctx.Args().First() 652 - 653 - if rkey == "" { 654 - return fmt.Errorf("must specify rkey of post to delete") 655 - } 656 - 657 - schema := "app.bsky.feed.post" 658 - if strings.Contains(rkey, "/") { 659 - parts := strings.Split(rkey, "/") 660 - schema = parts[0] 661 - rkey = parts[1] 662 - } 663 - 664 - return comatproto.RepoDeleteRecord(context.TODO(), xrpcc, &comatproto.RepoDeleteRecord_Input{ 665 - Repo: xrpcc.Auth.Did, 666 - Collection: schema, 667 - Rkey: rkey, 668 - }) 669 - }, 670 - } 671 - 672 - var listAllPostsCmd = &cli.Command{ 673 - Name: "list", 674 - Flags: []cli.Flag{ 675 - &cli.BoolFlag{ 676 - Name: "all", 677 - }, 678 - &cli.BoolFlag{ 679 - Name: "values", 680 - }, 681 - &cli.BoolFlag{ 682 - Name: "cids", 683 - }, 684 - }, 685 - ArgsUsage: `<did>|<repo-path>`, 686 - Action: func(cctx *cli.Context) error { 687 - 688 - arg := cctx.Args().First() 689 - ctx := context.TODO() 690 - 691 - var repob []byte 692 - if strings.HasPrefix(arg, "did:") { 693 - xrpcc, err := cliutil.GetXrpcClient(cctx, true) 694 - if err != nil { 695 - return err 696 - } 697 - 698 - if arg == "" { 699 - arg = xrpcc.Auth.Did 700 - } 701 - 702 - rrb, err := comatproto.SyncGetRepo(ctx, xrpcc, arg, "") 703 - if err != nil { 704 - return err 705 - } 706 - repob = rrb 707 - } else { 708 - if len(arg) == 0 { 709 - return cli.Exit("must specify DID string or repo path", 127) 710 - } 711 - fb, err := os.ReadFile(arg) 712 - if err != nil { 713 - return err 714 - } 715 - 716 - repob = fb 717 - } 718 - 719 - rr, err := repo.ReadRepoFromCar(ctx, bytes.NewReader(repob)) 720 - if err != nil { 721 - return err 722 - } 723 - 724 - collection := "app.bsky.feed.post" 725 - if cctx.Bool("all") { 726 - collection = "" 727 - } 728 - vals := cctx.Bool("values") 729 - cids := cctx.Bool("cids") 730 - 731 - if err := rr.ForEach(ctx, collection, func(k string, v cid.Cid) error { 732 - if !strings.HasPrefix(k, collection) { 733 - return repo.ErrDoneIterating 734 - } 735 - 736 - fmt.Print(k) 737 - if cids { 738 - fmt.Println(" - ", v) 739 - } else { 740 - fmt.Println() 741 - } 742 - if vals { 743 - b, err := rr.Blockstore().Get(ctx, v) 744 - if err != nil { 745 - return err 746 - } 747 - 748 - convb, err := cborToJson(b.RawData()) 749 - if err != nil { 750 - return err 751 - } 752 - fmt.Println(string(convb)) 753 - } 754 - return nil 755 - }); err != nil { 756 - return err 757 - } 758 - 759 - return nil 760 - }, 761 - } 762 - 763 - var getNotificationsCmd = &cli.Command{ 764 - Name: "notifs", 765 - Flags: []cli.Flag{}, 766 - Action: func(cctx *cli.Context) error { 767 - ctx := context.TODO() 768 - 769 - xrpcc, err := cliutil.GetXrpcClient(cctx, true) 770 - if err != nil { 771 - return err 772 - } 773 - 774 - notifs, err := appbsky.NotificationListNotifications(ctx, xrpcc, "", 50, "") 775 - if err != nil { 776 - return err 777 - } 778 - 779 - for _, n := range notifs.Notifications { 780 - b, err := json.Marshal(n) 781 - if err != nil { 782 - return err 783 - } 784 - 785 - fmt.Println(string(b)) 786 - } 787 - 788 - return nil 789 - }, 790 - } 791 - 792 - var followsCmd = &cli.Command{ 793 - Name: "follows", 794 - Subcommands: []*cli.Command{ 795 - followsAddCmd, 796 - followsListCmd, 797 - }, 798 - } 799 - 800 - var followsAddCmd = &cli.Command{ 801 - Name: "add", 802 - Flags: []cli.Flag{}, 803 - ArgsUsage: `<user>`, 804 - Action: func(cctx *cli.Context) error { 805 - xrpcc, err := cliutil.GetXrpcClient(cctx, true) 806 - if err != nil { 807 - return err 808 - } 809 - 810 - user := cctx.Args().First() 811 - 812 - follow := appbsky.GraphFollow{ 813 - LexiconTypeID: "app.bsky.graph.follow", 814 - CreatedAt: time.Now().Format(time.RFC3339), 815 - Subject: user, 816 - } 817 - 818 - resp, err := comatproto.RepoCreateRecord(context.TODO(), xrpcc, &comatproto.RepoCreateRecord_Input{ 819 - Collection: "app.bsky.graph.follow", 820 - Repo: xrpcc.Auth.Did, 821 - Record: &lexutil.LexiconTypeDecoder{&follow}, 822 - }) 823 - if err != nil { 824 - return err 825 - } 826 - 827 - fmt.Println(resp.Uri) 828 - 829 - return nil 830 - }, 831 - } 832 - 833 - var followsListCmd = &cli.Command{ 834 - Name: "list", 835 - ArgsUsage: `[actor]`, 836 - Action: func(cctx *cli.Context) error { 837 - xrpcc, err := cliutil.GetXrpcClient(cctx, false) 838 - if err != nil { 839 - return err 840 - } 841 - 842 - user := cctx.Args().First() 843 - if user == "" { 844 - user = xrpcc.Auth.Did 845 - } 846 - 847 - ctx := context.TODO() 848 - resp, err := appbsky.GraphGetFollows(ctx, xrpcc, user, "", 100) 849 - if err != nil { 850 - return err 851 - } 852 - 853 - for _, f := range resp.Follows { 854 - fmt.Println(f.Did, f.Handle) 855 - } 856 - 857 - return nil 858 - }, 859 - } 860 - 861 107 func cborToJson(data []byte) ([]byte, error) { 862 108 defer func() { 863 109 if r := recover(); r != nil { ··· 877 123 return buf.Bytes(), nil 878 124 } 879 125 880 - var resetPasswordCmd = &cli.Command{ 881 - Name: "resetPassword", 882 - ArgsUsage: `<email>`, 883 - Action: func(cctx *cli.Context) error { 884 - ctx := context.TODO() 885 - 886 - xrpcc, err := cliutil.GetXrpcClient(cctx, false) 887 - if err != nil { 888 - return err 889 - } 890 - 891 - args, err := needArgs(cctx, "email") 892 - if err != nil { 893 - return err 894 - } 895 - email := args[0] 896 - 897 - err = comatproto.ServerRequestPasswordReset(ctx, xrpcc, &comatproto.ServerRequestPasswordReset_Input{ 898 - Email: email, 899 - }) 900 - if err != nil { 901 - return err 902 - } 903 - 904 - inp := bufio.NewScanner(os.Stdin) 905 - fmt.Println("Enter recovery code from email:") 906 - inp.Scan() 907 - code := inp.Text() 908 - 909 - fmt.Println("Enter new password:") 910 - inp.Scan() 911 - npass := inp.Text() 912 - 913 - if err := comatproto.ServerResetPassword(ctx, xrpcc, &comatproto.ServerResetPassword_Input{ 914 - Password: npass, 915 - Token: code, 916 - }); err != nil { 917 - return err 918 - } 919 - 920 - return nil 921 - }, 922 - } 923 - 924 - var handleCmd = &cli.Command{ 925 - Name: "handle", 926 - Subcommands: []*cli.Command{ 927 - resolveHandleCmd, 928 - updateHandleCmd, 929 - }, 930 - } 931 - 932 - var resolveHandleCmd = &cli.Command{ 933 - Name: "resolve", 934 - ArgsUsage: `<handle>`, 935 - Action: func(cctx *cli.Context) error { 936 - ctx := context.TODO() 937 - 938 - args, err := needArgs(cctx, "handle") 939 - if err != nil { 940 - return err 941 - } 942 - handle := args[0] 943 - 944 - phr := &api.ProdHandleResolver{} 945 - out, err := phr.ResolveHandleToDid(ctx, handle) 946 - if err != nil { 947 - return err 948 - } 949 - 950 - fmt.Println(out) 951 - 952 - return nil 953 - }, 954 - } 955 - 956 - var updateHandleCmd = &cli.Command{ 957 - Name: "update", 958 - ArgsUsage: `<handle>`, 959 - Action: func(cctx *cli.Context) error { 960 - ctx := context.TODO() 961 - 962 - xrpcc, err := cliutil.GetXrpcClient(cctx, false) 963 - if err != nil { 964 - return err 965 - } 966 - 967 - args, err := needArgs(cctx, "handle") 968 - if err != nil { 969 - return err 970 - } 971 - handle := args[0] 972 - 973 - err = comatproto.IdentityUpdateHandle(ctx, xrpcc, &comatproto.IdentityUpdateHandle_Input{ 974 - Handle: handle, 975 - }) 976 - if err != nil { 977 - return err 978 - } 979 - 980 - return nil 981 - }, 982 - } 983 - 984 126 type cachedHandle struct { 985 127 Handle string 986 128 Valid time.Time 987 129 } 988 130 989 131 var readRepoStreamCmd = &cli.Command{ 990 - Name: "readStream", 132 + Name: "read-stream", 133 + Usage: "subscribe to a repo event stream", 991 134 Flags: []cli.Flag{ 992 135 &cli.BoolFlag{ 993 136 Name: "json", ··· 1213 356 } 1214 357 1215 358 var getRecordCmd = &cli.Command{ 1216 - Name: "getRecord", 359 + Name: "get-record", 360 + Usage: "fetch a single record for a given repo", 1217 361 Flags: []cli.Flag{ 1218 362 &cli.StringFlag{ 1219 363 Name: "repo", ··· 1302 446 }, 1303 447 } 1304 448 1305 - var createInviteCmd = &cli.Command{ 1306 - Name: "createInvites", 1307 - Flags: []cli.Flag{ 1308 - &cli.StringFlag{ 1309 - Name: "admin-password", 1310 - EnvVars: []string{"ATP_AUTH_ADMIN_PASSWORD"}, 1311 - Required: true, 1312 - }, 1313 - &cli.IntFlag{ 1314 - Name: "useCount", 1315 - Value: 1, 1316 - }, 1317 - &cli.IntFlag{ 1318 - Name: "num", 1319 - Value: 1, 1320 - }, 1321 - &cli.StringFlag{ 1322 - Name: "bulk", 1323 - }, 1324 - }, 1325 - ArgsUsage: "[handle]", 1326 - Action: func(cctx *cli.Context) error { 1327 - xrpcc, err := cliutil.GetXrpcClient(cctx, false) 1328 - if err != nil { 1329 - return err 1330 - } 1331 - 1332 - adminKey := cctx.String("admin-password") 1333 - 1334 - count := cctx.Int("useCount") 1335 - num := cctx.Int("num") 1336 - 1337 - phr := &api.ProdHandleResolver{} 1338 - if bulkfi := cctx.String("bulk"); bulkfi != "" { 1339 - xrpcc.AdminToken = &adminKey 1340 - dids, err := readDids(bulkfi) 1341 - if err != nil { 1342 - return err 1343 - } 1344 - 1345 - for i, d := range dids { 1346 - if !strings.HasPrefix(d, "did:plc:") { 1347 - out, err := phr.ResolveHandleToDid(context.TODO(), d) 1348 - if err != nil { 1349 - return fmt.Errorf("failed to resolve %q: %w", d, err) 1350 - } 1351 - 1352 - dids[i] = out 1353 - } 1354 - } 1355 - 1356 - for n := 0; n < len(dids); n += 500 { 1357 - slice := dids 1358 - if len(slice) > 500 { 1359 - slice = slice[:500] 1360 - } 1361 - 1362 - _, err = comatproto.ServerCreateInviteCodes(context.TODO(), xrpcc, &comatproto.ServerCreateInviteCodes_Input{ 1363 - UseCount: int64(count), 1364 - ForAccounts: slice, 1365 - CodeCount: int64(num), 1366 - }) 1367 - if err != nil { 1368 - return err 1369 - } 1370 - } 1371 - 1372 - return nil 1373 - } 1374 - 1375 - var usrdid []string 1376 - if forUser := cctx.Args().Get(0); forUser != "" { 1377 - if !strings.HasPrefix(forUser, "did:") { 1378 - resp, err := phr.ResolveHandleToDid(context.TODO(), forUser) 1379 - if err != nil { 1380 - return fmt.Errorf("resolving handle: %w", err) 1381 - } 1382 - 1383 - usrdid = []string{resp} 1384 - } else { 1385 - usrdid = []string{forUser} 1386 - } 1387 - } 1388 - 1389 - xrpcc.AdminToken = &adminKey 1390 - resp, err := comatproto.ServerCreateInviteCodes(context.TODO(), xrpcc, &comatproto.ServerCreateInviteCodes_Input{ 1391 - UseCount: int64(count), 1392 - ForAccounts: usrdid, 1393 - CodeCount: int64(num), 1394 - }) 1395 - if err != nil { 1396 - return fmt.Errorf("creating codes: %w", err) 1397 - } 1398 - 1399 - for _, c := range resp.Codes { 1400 - for _, cc := range c.Codes { 1401 - fmt.Println(cc) 1402 - } 1403 - } 1404 - 1405 - return nil 1406 - }, 1407 - } 1408 - 1409 449 func readDids(f string) ([]string, error) { 1410 450 fi, err := os.Open(f) 1411 451 if err != nil { ··· 1423 463 return out, nil 1424 464 } 1425 465 1426 - var syncListReposCmd = &cli.Command{ 1427 - Name: "listRepos", 1428 - Action: func(cctx *cli.Context) error { 1429 - xrpcc, err := cliutil.GetXrpcClient(cctx, false) 1430 - if err != nil { 1431 - return err 1432 - } 1433 - 1434 - var curs string 1435 - for { 1436 - out, err := comatproto.SyncListRepos(context.TODO(), xrpcc, curs, 1000) 1437 - if err != nil { 1438 - return err 1439 - } 1440 - 1441 - if len(out.Repos) == 0 { 1442 - break 1443 - } 1444 - 1445 - for _, r := range out.Repos { 1446 - fmt.Println(r.Did) 1447 - } 1448 - 1449 - if out.Cursor == nil { 1450 - break 1451 - } 1452 - 1453 - curs = *out.Cursor 1454 - } 1455 - 1456 - return nil 1457 - }, 1458 - } 1459 - 1460 466 func needArgs(cctx *cli.Context, name ...string) ([]string, error) { 1461 467 var out []string 1462 468 for i, n := range name { ··· 1470 476 } 1471 477 1472 478 var createFeedGeneratorCmd = &cli.Command{ 1473 - Name: "createFeedGen", 479 + Name: "create-feed-gen", 480 + Usage: "create a feed generator record", 1474 481 Flags: []cli.Flag{ 1475 482 &cli.StringFlag{ 1476 483 Name: "name", ··· 1547 554 }, 1548 555 } 1549 556 1550 - var rebaseRepoCmd = &cli.Command{ 1551 - Name: "rebase", 1552 - Flags: []cli.Flag{}, 557 + var listAllRecordsCmd = &cli.Command{ 558 + Name: "list", 559 + Usage: "print all of the records for a repo or local CAR file", 560 + Flags: []cli.Flag{ 561 + &cli.BoolFlag{ 562 + Name: "all", 563 + }, 564 + &cli.BoolFlag{ 565 + Name: "values", 566 + }, 567 + &cli.BoolFlag{ 568 + Name: "cids", 569 + }, 570 + }, 571 + ArgsUsage: `<did>|<repo-path>`, 1553 572 Action: func(cctx *cli.Context) error { 1554 - xrpcc, err := cliutil.GetXrpcClient(cctx, true) 1555 - if err != nil { 1556 - return err 1557 - } 1558 573 1559 - did := xrpcc.Auth.Did 574 + arg := cctx.Args().First() 575 + ctx := context.TODO() 576 + 577 + var repob []byte 578 + if strings.HasPrefix(arg, "did:") { 579 + xrpcc, err := cliutil.GetXrpcClient(cctx, true) 580 + if err != nil { 581 + return err 582 + } 1560 583 1561 - if err := atproto.RepoRebaseRepo(context.Background(), xrpcc, &atproto.RepoRebaseRepo_Input{ 1562 - Repo: did, 1563 - }); err != nil { 1564 - return err 1565 - } 584 + if arg == "" { 585 + arg = xrpcc.Auth.Did 586 + } 1566 587 1567 - return nil 1568 - }, 1569 - } 588 + rrb, err := comatproto.SyncGetRepo(ctx, xrpcc, arg, "") 589 + if err != nil { 590 + return err 591 + } 592 + repob = rrb 593 + } else { 594 + if len(arg) == 0 { 595 + return cli.Exit("must specify DID string or repo path", 127) 596 + } 597 + fb, err := os.ReadFile(arg) 598 + if err != nil { 599 + return err 600 + } 1570 601 1571 - var requestAccountDeletionCmd = &cli.Command{ 1572 - Name: "request-account-deletion", 1573 - Action: func(cctx *cli.Context) error { 1574 - xrpcc, err := cliutil.GetXrpcClient(cctx, false) 1575 - if err != nil { 1576 - return err 602 + repob = fb 1577 603 } 1578 604 1579 - err = comatproto.ServerRequestAccountDelete(cctx.Context, xrpcc) 605 + rr, err := repo.ReadRepoFromCar(ctx, bytes.NewReader(repob)) 1580 606 if err != nil { 1581 607 return err 1582 608 } 1583 609 1584 - return nil 1585 - }, 1586 - } 1587 - 1588 - var deleteAccountCmd = &cli.Command{ 1589 - Name: "delete-account", 1590 - Action: func(cctx *cli.Context) error { 1591 - xrpcc, err := cliutil.GetXrpcClient(cctx, false) 1592 - if err != nil { 1593 - return err 610 + collection := "app.bsky.feed.post" 611 + if cctx.Bool("all") { 612 + collection = "" 1594 613 } 614 + vals := cctx.Bool("values") 615 + cids := cctx.Bool("cids") 1595 616 1596 - token := cctx.Args().First() 1597 - password := cctx.Args().Get(1) 617 + if err := rr.ForEach(ctx, collection, func(k string, v cid.Cid) error { 618 + if !strings.HasPrefix(k, collection) { 619 + return repo.ErrDoneIterating 620 + } 621 + 622 + fmt.Print(k) 623 + if cids { 624 + fmt.Println(" - ", v) 625 + } else { 626 + fmt.Println() 627 + } 628 + if vals { 629 + b, err := rr.Blockstore().Get(ctx, v) 630 + if err != nil { 631 + return err 632 + } 1598 633 1599 - err = comatproto.ServerDeleteAccount(cctx.Context, xrpcc, &comatproto.ServerDeleteAccount_Input{ 1600 - Did: xrpcc.Auth.Did, 1601 - Token: token, 1602 - Password: password, 1603 - }) 1604 - if err != nil { 634 + convb, err := cborToJson(b.RawData()) 635 + if err != nil { 636 + return err 637 + } 638 + fmt.Println(string(convb)) 639 + } 640 + return nil 641 + }); err != nil { 1605 642 return err 1606 643 } 1607 644
+1
cmd/gosky/streamdiff.go
··· 14 14 15 15 // TODO: WIP - turns out to be more complicated than i initially thought 16 16 var streamCompareCmd = &cli.Command{ 17 + Usage: "utility to subscribe and compare output from two repo streams", 17 18 Name: "diff-stream", 18 19 Flags: []cli.Flag{}, 19 20 ArgsUsage: `<hostA> <hostB>`,
+129
cmd/gosky/sync.go
··· 1 + package main 2 + 3 + import ( 4 + "context" 5 + "fmt" 6 + "os" 7 + 8 + comatproto "github.com/bluesky-social/indigo/api/atproto" 9 + "github.com/bluesky-social/indigo/atproto/identity" 10 + "github.com/bluesky-social/indigo/atproto/syntax" 11 + "github.com/bluesky-social/indigo/util/cliutil" 12 + 13 + cli "github.com/urfave/cli/v2" 14 + ) 15 + 16 + var syncCmd = &cli.Command{ 17 + Name: "sync", 18 + Usage: "sub-commands for repo sync endpoints", 19 + Subcommands: []*cli.Command{ 20 + syncGetRepoCmd, 21 + syncGetRootCmd, 22 + syncListReposCmd, 23 + }, 24 + } 25 + 26 + var syncGetRepoCmd = &cli.Command{ 27 + Name: "get-repo", 28 + Usage: "download repo from account's PDS to local file (or '-' for stdout). for hex combine with 'xxd -ps -u -c 0'", 29 + ArgsUsage: `<at-identifier> [<car-file-path>]`, 30 + Action: func(cctx *cli.Context) error { 31 + ctx := context.Background() 32 + arg := cctx.Args().First() 33 + if arg == "" { 34 + return fmt.Errorf("at-identifier arg is required") 35 + } 36 + atid, err := syntax.ParseAtIdentifier(arg) 37 + if err != nil { 38 + return err 39 + } 40 + dir := identity.DefaultDirectory() 41 + ident, err := dir.Lookup(ctx, *atid) 42 + if err != nil { 43 + return err 44 + } 45 + 46 + carPath := cctx.Args().Get(1) 47 + if carPath == "" { 48 + carPath = ident.DID.String() + ".car" 49 + } 50 + 51 + xrpcc, err := cliutil.GetXrpcClient(cctx, false) 52 + if err != nil { 53 + return err 54 + } 55 + xrpcc.Host = ident.PDSEndpoint() 56 + if xrpcc.Host == "" { 57 + return fmt.Errorf("no PDS endpoint for identity") 58 + } 59 + 60 + log.Infof("downloading from %s to: %s", xrpcc.Host, carPath) 61 + repoBytes, err := comatproto.SyncGetRepo(ctx, xrpcc, ident.DID.String(), "") 62 + if err != nil { 63 + return err 64 + } 65 + 66 + if carPath == "-" { 67 + _, err = os.Stdout.Write(repoBytes) 68 + return err 69 + } else { 70 + return os.WriteFile(carPath, repoBytes, 0666) 71 + } 72 + }, 73 + } 74 + 75 + var syncGetRootCmd = &cli.Command{ 76 + Name: "get-root", 77 + ArgsUsage: `<did>`, 78 + Action: func(cctx *cli.Context) error { 79 + xrpcc, err := cliutil.GetXrpcClient(cctx, false) 80 + if err != nil { 81 + return err 82 + } 83 + 84 + ctx := context.TODO() 85 + 86 + root, err := comatproto.SyncGetHead(ctx, xrpcc, cctx.Args().First()) 87 + if err != nil { 88 + return err 89 + } 90 + 91 + fmt.Println(root.Root) 92 + 93 + return nil 94 + }, 95 + } 96 + 97 + var syncListReposCmd = &cli.Command{ 98 + Name: "list-repos", 99 + Action: func(cctx *cli.Context) error { 100 + xrpcc, err := cliutil.GetXrpcClient(cctx, false) 101 + if err != nil { 102 + return err 103 + } 104 + 105 + var curs string 106 + for { 107 + out, err := comatproto.SyncListRepos(context.TODO(), xrpcc, curs, 1000) 108 + if err != nil { 109 + return err 110 + } 111 + 112 + if len(out.Repos) == 0 { 113 + break 114 + } 115 + 116 + for _, r := range out.Repos { 117 + fmt.Println(r.Did) 118 + } 119 + 120 + if out.Cursor == nil { 121 + break 122 + } 123 + 124 + curs = *out.Cursor 125 + } 126 + 127 + return nil 128 + }, 129 + }