porting all github actions from bluesky-social/indigo to tangled CI
at main 7.5 kB view raw
1package main 2 3import ( 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 19var 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 34var 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{Val: &follow}, 57 }) 58 if err != nil { 59 return err 60 } 61 62 fmt.Println(resp.Uri) 63 64 return nil 65 }, 66} 67 68var 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 97var 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{Val: &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 130func 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 142var 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, "", "", false, 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 216var 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 249var 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 295var 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 _, err = comatproto.RepoDeleteRecord(context.TODO(), xrpcc, &comatproto.RepoDeleteRecord_Input{ 318 Repo: xrpcc.Auth.Did, 319 Collection: schema, 320 Rkey: rkey, 321 }) 322 return err 323 }, 324} 325 326var bskyNotificationsCmd = &cli.Command{ 327 Name: "notifs", 328 Usage: "fetch bsky notifications (requires auth)", 329 Flags: []cli.Flag{}, 330 Action: func(cctx *cli.Context) error { 331 ctx := context.TODO() 332 333 xrpcc, err := cliutil.GetXrpcClient(cctx, true) 334 if err != nil { 335 return err 336 } 337 338 notifs, err := appbsky.NotificationListNotifications(ctx, xrpcc, "", 50, false, nil, "") 339 if err != nil { 340 return err 341 } 342 343 for _, n := range notifs.Notifications { 344 b, err := json.Marshal(n) 345 if err != nil { 346 return err 347 } 348 349 fmt.Println(string(b)) 350 } 351 352 return nil 353 }, 354}