demo CLI tool for grain.social
at main 4.0 kB view raw
1package main 2 3import ( 4 "bytes" 5 "context" 6 "fmt" 7 "log" 8 "os" 9 10 _ "github.com/joho/godotenv/autoload" 11 12 agnostic "github.com/bluesky-social/indigo/api/agnostic" 13 comatproto "github.com/bluesky-social/indigo/api/atproto" 14 "github.com/bluesky-social/indigo/atproto/client" 15 "github.com/bluesky-social/indigo/atproto/identity" 16 "github.com/bluesky-social/indigo/atproto/syntax" 17 "github.com/carlmjohnson/versioninfo" 18 "github.com/urfave/cli/v3" 19) 20 21func main() { 22 23 cmd := &cli.Command{ 24 Name: "halide", 25 Usage: "CLI tool for for grain.social (atproto)", 26 Version: versioninfo.Short(), 27 Commands: []*cli.Command{ 28 { 29 Name: "login", 30 Usage: "create auth session", 31 Action: runLogin, 32 Flags: []cli.Flag{ 33 &cli.StringFlag{ 34 Name: "username", 35 Aliases: []string{"u"}, 36 Required: true, 37 Sources: cli.EnvVars("ATP_USERNAME"), 38 }, 39 &cli.StringFlag{ 40 Name: "password", 41 Aliases: []string{"p"}, 42 Required: true, 43 Sources: cli.EnvVars("ATP_PASSWORD"), 44 }, 45 }, 46 }, 47 { 48 Name: "photo", 49 Commands: []*cli.Command{ 50 { 51 Name: "list", 52 Aliases: []string{"ls"}, 53 Usage: "enumerate photos", 54 Action: runPhotoList, 55 }, 56 { 57 Name: "upload", 58 Usage: "upload a photo", 59 ArgsUsage: `<file>`, 60 Flags: []cli.Flag{ 61 &cli.StringFlag{ 62 Name: "alt", 63 Usage: "image alt text (for accessibility)", 64 }, 65 }, 66 Action: runPhotoUpload, 67 }, 68 }, 69 }, 70 { 71 Name: "gallery", 72 Commands: []*cli.Command{ 73 { 74 Name: "list", 75 Aliases: []string{"ls"}, 76 Usage: "enumerate galleries", 77 Action: runGalleryList, 78 }, 79 }, 80 }, 81 }, 82 } 83 84 if err := cmd.Run(context.Background(), os.Args); err != nil { 85 log.Fatal(err) 86 } 87} 88 89func runLogin(ctx context.Context, cmd *cli.Command) error { 90 atid, err := syntax.ParseAtIdentifier(cmd.String("username")) 91 if err != nil { 92 return err 93 } 94 95 dir := identity.DefaultDirectory() 96 c, err := client.LoginWithPassword(ctx, dir, *atid, cmd.String("password"), "", nil) 97 if err != nil { 98 return err 99 } 100 101 pa := c.Auth.(*client.PasswordAuth) 102 return persistAuthSession(pa.Session) 103} 104 105func runPhotoUpload(ctx context.Context, cmd *cli.Command) error { 106 photoPath := cmd.Args().First() 107 if photoPath == "" { 108 return fmt.Errorf("need to provide file path as an argument") 109 } 110 111 c, err := loadClient() 112 if err != nil { 113 return err 114 } 115 116 // TODO: refactor to do upload with c.Do(), streaming from file 117 fileBytes, err := os.ReadFile(photoPath) 118 if err != nil { 119 return err 120 } 121 122 resp, err := comatproto.RepoUploadBlob(ctx, c, bytes.NewReader(fileBytes)) 123 if err != nil { 124 return err 125 } 126 127 photo := make(map[string]any) 128 photo["$type"] = "social.grain.photo" 129 photo["createdAt"] = syntax.DatetimeNow() 130 photo["photo"] = resp.Blob 131 // TODO: if alt was not required, this would be behind an 'if' 132 photo["alt"] = cmd.String("alt") 133 134 res, err := agnostic.RepoCreateRecord(ctx, c, &agnostic.RepoCreateRecord_Input{ 135 Collection: "social.grain.photo", 136 Record: photo, 137 Repo: c.AccountDID.String(), 138 }) 139 if err != nil { 140 return err 141 } 142 143 fmt.Printf("%s\t%s\n", res.Uri, res.Cid) 144 return nil 145} 146 147func runPhotoList(ctx context.Context, cmd *cli.Command) error { 148 c, err := loadClient() 149 if err != nil { 150 return err 151 } 152 153 out, err := agnostic.RepoListRecords(ctx, c, "social.grain.photo", "", 100, c.AccountDID.String(), false) 154 if err != nil { 155 return err 156 } 157 158 for _, rec := range out.Records { 159 // TODO: parse rec.Value to lexgen struct type 160 fmt.Printf("%s\n", rec.Uri) 161 } 162 163 return nil 164} 165 166func runGalleryList(ctx context.Context, cmd *cli.Command) error { 167 c, err := loadClient() 168 if err != nil { 169 return err 170 } 171 172 out, err := agnostic.RepoListRecords(ctx, c, "social.grain.gallery", "", 100, c.AccountDID.String(), false) 173 if err != nil { 174 return err 175 } 176 177 for _, rec := range out.Records { 178 // TODO: parse rec.Value to lexgen struct type 179 fmt.Printf("%s\n", rec.Uri) 180 } 181 182 return nil 183}