porting all github actions from bluesky-social/indigo to tangled CI
at main 5.5 kB view raw
1package main 2 3import ( 4 "bytes" 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/xrpc" 12 13 "github.com/urfave/cli/v2" 14) 15 16var cmdBlob = &cli.Command{ 17 Name: "blob", 18 Usage: "sub-commands for blobs", 19 Flags: []cli.Flag{}, 20 Subcommands: []*cli.Command{ 21 &cli.Command{ 22 Name: "export", 23 Usage: "download all blobs for given account", 24 ArgsUsage: `<at-identifier>`, 25 Flags: []cli.Flag{ 26 &cli.StringFlag{ 27 Name: "output", 28 Aliases: []string{"o"}, 29 Usage: "directory to store blobs in", 30 }, 31 &cli.StringFlag{ 32 Name: "pds-host", 33 Usage: "URL of the PDS to export blobs from (overrides DID doc)", 34 }, 35 }, 36 Action: runBlobExport, 37 }, 38 &cli.Command{ 39 Name: "ls", 40 Aliases: []string{"list"}, 41 Usage: "list all blobs for account", 42 ArgsUsage: `<at-identifier>`, 43 Flags: []cli.Flag{}, 44 Action: runBlobList, 45 }, 46 &cli.Command{ 47 Name: "download", 48 Usage: "download a single blob from an account", 49 ArgsUsage: `<at-identifier> <cid>`, 50 Flags: []cli.Flag{ 51 &cli.StringFlag{ 52 Name: "output", 53 Aliases: []string{"o"}, 54 Usage: "file path to store blob at", 55 }, 56 }, 57 Action: runBlobDownload, 58 }, 59 &cli.Command{ 60 Name: "upload", 61 Usage: "upload a file", 62 ArgsUsage: `<file>`, 63 Flags: []cli.Flag{}, 64 Action: runBlobUpload, 65 }, 66 }, 67} 68 69func runBlobExport(cctx *cli.Context) error { 70 ctx := context.Background() 71 username := cctx.Args().First() 72 if username == "" { 73 return fmt.Errorf("need to provide username as an argument") 74 } 75 ident, err := resolveIdent(ctx, username) 76 if err != nil { 77 return err 78 } 79 80 pdsHost := cctx.String("pds-host") 81 if pdsHost == "" { 82 pdsHost = ident.PDSEndpoint() 83 } 84 85 // create a new API client to connect to the account's PDS 86 xrpcc := xrpc.Client{ 87 Host: pdsHost, 88 UserAgent: userAgent(), 89 } 90 if xrpcc.Host == "" { 91 return fmt.Errorf("no PDS endpoint for identity") 92 } 93 94 topDir := cctx.String("output") 95 if topDir == "" { 96 topDir = fmt.Sprintf("%s_blobs", username) 97 } 98 99 fmt.Printf("downloading blobs to: %s\n", topDir) 100 os.MkdirAll(topDir, os.ModePerm) 101 102 cursor := "" 103 for { 104 resp, err := comatproto.SyncListBlobs(ctx, &xrpcc, cursor, ident.DID.String(), 500, "") 105 if err != nil { 106 return err 107 } 108 for _, cidStr := range resp.Cids { 109 blobPath := topDir + "/" + cidStr 110 if _, err := os.Stat(blobPath); err == nil { 111 fmt.Printf("%s\texists\n", blobPath) 112 continue 113 } 114 blobBytes, err := comatproto.SyncGetBlob(ctx, &xrpcc, cidStr, ident.DID.String()) 115 if err != nil { 116 fmt.Printf("%s\tfailed %s\n", blobPath, err) 117 continue 118 } 119 if err := os.WriteFile(blobPath, blobBytes, 0666); err != nil { 120 return err 121 } 122 fmt.Printf("%s\tdownloaded\n", blobPath) 123 } 124 if resp.Cursor != nil && *resp.Cursor != "" { 125 cursor = *resp.Cursor 126 } else { 127 break 128 } 129 } 130 return nil 131} 132 133func runBlobList(cctx *cli.Context) error { 134 ctx := context.Background() 135 username := cctx.Args().First() 136 if username == "" { 137 return fmt.Errorf("need to provide username as an argument") 138 } 139 ident, err := resolveIdent(ctx, username) 140 if err != nil { 141 return err 142 } 143 144 // create a new API client to connect to the account's PDS 145 xrpcc := xrpc.Client{ 146 Host: ident.PDSEndpoint(), 147 UserAgent: userAgent(), 148 } 149 if xrpcc.Host == "" { 150 return fmt.Errorf("no PDS endpoint for identity") 151 } 152 153 cursor := "" 154 for { 155 resp, err := comatproto.SyncListBlobs(ctx, &xrpcc, cursor, ident.DID.String(), 500, "") 156 if err != nil { 157 return err 158 } 159 for _, cidStr := range resp.Cids { 160 fmt.Println(cidStr) 161 } 162 if resp.Cursor != nil && *resp.Cursor != "" { 163 cursor = *resp.Cursor 164 } else { 165 break 166 } 167 } 168 return nil 169} 170 171func runBlobDownload(cctx *cli.Context) error { 172 ctx := context.Background() 173 username := cctx.Args().First() 174 if username == "" { 175 return fmt.Errorf("need to provide username as an argument") 176 } 177 if cctx.Args().Len() < 2 { 178 return fmt.Errorf("need to provide blob CID as second argument") 179 } 180 blobCID := cctx.Args().Get(1) 181 ident, err := resolveIdent(ctx, username) 182 if err != nil { 183 return err 184 } 185 186 // create a new API client to connect to the account's PDS 187 xrpcc := xrpc.Client{ 188 Host: ident.PDSEndpoint(), 189 UserAgent: userAgent(), 190 } 191 if xrpcc.Host == "" { 192 return fmt.Errorf("no PDS endpoint for identity") 193 } 194 195 blobPath := cctx.String("output") 196 if blobPath == "" { 197 blobPath = blobCID 198 } 199 200 fmt.Printf("downloading blob to: %s\n", blobCID) 201 202 if _, err := os.Stat(blobPath); err == nil { 203 return fmt.Errorf("file exists: %s", blobPath) 204 } 205 blobBytes, err := comatproto.SyncGetBlob(ctx, &xrpcc, blobCID, ident.DID.String()) 206 if err != nil { 207 return err 208 } 209 return os.WriteFile(blobPath, blobBytes, 0666) 210} 211 212func runBlobUpload(cctx *cli.Context) error { 213 ctx := context.Background() 214 blobPath := cctx.Args().First() 215 if blobPath == "" { 216 return fmt.Errorf("need to provide file path as an argument") 217 } 218 219 xrpcc, err := loadAuthClient(ctx) 220 if err == ErrNoAuthSession { 221 return fmt.Errorf("auth required, but not logged in") 222 } else if err != nil { 223 return err 224 } 225 226 fileBytes, err := os.ReadFile(blobPath) 227 if err != nil { 228 return err 229 } 230 231 resp, err := comatproto.RepoUploadBlob(ctx, xrpcc, bytes.NewReader(fileBytes)) 232 if err != nil { 233 return err 234 } 235 236 b, err := json.MarshalIndent(resp.Blob, "", " ") 237 if err != nil { 238 return err 239 } 240 241 fmt.Println(string(b)) 242 return nil 243}