Interactively go through your bluesky follow graph and decide to keep or remove follow records
0
fork

Configure Feed

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

Continuously get more records instead of just stopping after 5

yemou 380649e3 f78645e0

+113 -108
+113 -108
main.go
··· 129 129 apiClient := session.APIClient() 130 130 dir := identity.DefaultDirectory() 131 131 132 - // TODO: This should be in a while loop until we get no records back 133 - records, err := atproto.RepoListRecords(ctx, apiClient, 134 - "app.bsky.graph.follow", 135 - "", 136 - 5, // TODO: When done testing, increase this number 137 - sessionData.AccountDID.String(), 138 - false, 139 - ) 140 - 141 - if err != nil { 142 - log.Fatal(err) 143 - } 144 - 145 - fmt.Printf("\x1b[7") 146 - defer fmt.Printf("\x1b[8") 147 - fmt.Printf("\x1b[?1049h") 148 - defer fmt.Printf("\x1b[?1049l") 149 - fmt.Printf("\x1b[H") 150 - 151 - for _, record := range records.Records { 152 - followRecord := record.Value.Val.(*bsky.GraphFollow) 153 - profileRecord, err := atproto.RepoGetRecord(ctx, apiClient, 154 - "", 155 - "app.bsky.actor.profile", 156 - followRecord.Subject, 157 - "self", 132 + // TODO: add a cli flag that allows us to resume from a specific cursor 133 + cursor := "" 134 + for { 135 + records, err := atproto.RepoListRecords(ctx, apiClient, 136 + "app.bsky.graph.follow", 137 + cursor, 138 + 50, 139 + sessionData.AccountDID.String(), 140 + false, 158 141 ) 159 142 160 143 if err != nil { 161 144 log.Fatal(err) 162 145 } 163 146 164 - profile := profileRecord.Value.Val.(*bsky.ActorProfile) 165 - did, err := syntax.ParseDID(followRecord.Subject) 166 - if err != nil { 167 - log.Fatal(err) 168 - } 147 + cursor = *records.Cursor 148 + 149 + // TODO: Either put comments for what each of these do or create tiny little functions for them 150 + // Alternatively, I may want to find a terminal ui library and add other things like colors and bolded text 151 + fmt.Printf("\x1b[7") 152 + defer fmt.Printf("\x1b[8") 153 + fmt.Printf("\x1b[?1049h") 154 + defer fmt.Printf("\x1b[?1049l") 155 + fmt.Printf("\x1b[H") 156 + 157 + for _, record := range records.Records { 158 + followRecord := record.Value.Val.(*bsky.GraphFollow) 159 + profileRecord, err := atproto.RepoGetRecord(ctx, apiClient, 160 + "", 161 + "app.bsky.actor.profile", 162 + followRecord.Subject, 163 + "self", 164 + ) 169 165 170 - doc, err := dir.LookupDID(ctx, did) 171 - if err != nil { 172 - log.Fatal(err) 173 - } 166 + if err != nil { 167 + log.Fatal(err) 168 + } 174 169 175 - blobClient := atclient.NewAPIClient(doc.DIDDocument().Service[0].ServiceEndpoint) 176 - avatar, err := atproto.SyncGetBlob(ctx, blobClient, 177 - profile.Avatar.Ref.String(), 178 - followRecord.Subject, 179 - ) 170 + profile := profileRecord.Value.Val.(*bsky.ActorProfile) 171 + did, err := syntax.ParseDID(followRecord.Subject) 172 + if err != nil { 173 + log.Fatal(err) 174 + } 180 175 181 - if err != nil { 182 - log.Fatal(err) 183 - } 176 + doc, err := dir.LookupDID(ctx, did) 177 + if err != nil { 178 + log.Fatal(err) 179 + } 184 180 185 - avatarImg, _, _ := image.Decode(bytes.NewReader(avatar)) 186 - imageWidth := avatarImg.Bounds().Max.X 187 - imageHeight := avatarImg.Bounds().Max.Y 188 - imageRatio := imageWidth / imageHeight 181 + blobClient := atclient.NewAPIClient(doc.DIDDocument().Service[0].ServiceEndpoint) 182 + avatar, err := atproto.SyncGetBlob(ctx, blobClient, 183 + profile.Avatar.Ref.String(), 184 + followRecord.Subject, 185 + ) 189 186 190 - terminalWidth, terminalHeight, err := term.GetSize(0) 191 - if err != nil { 192 - log.Fatal(err) 193 - } 187 + if err != nil { 188 + log.Fatal(err) 189 + } 194 190 195 - if terminalWidth > terminalHeight { 196 - terminalWidth = terminalHeight * imageRatio 197 - } else { 198 - terminalHeight = terminalWidth / imageRatio 199 - } 191 + avatarImg, _, _ := image.Decode(bytes.NewReader(avatar)) 192 + imageWidth := avatarImg.Bounds().Max.X 193 + imageHeight := avatarImg.Bounds().Max.Y 194 + imageRatio := imageWidth / imageHeight 200 195 201 - // TODO: Make the FONT_SIZE configureable via cli flag or figure 202 - // out a way to get the cell height from the terminal 203 - maxImageWidth := (terminalWidth / 2) * font_size 204 - maxImageHeight := (terminalHeight / 2) * font_size 196 + terminalWidth, terminalHeight, err := term.GetSize(0) 197 + if err != nil { 198 + log.Fatal(err) 199 + } 205 200 206 - maxImageRatio := maxImageWidth / maxImageHeight 201 + if terminalWidth > terminalHeight { 202 + terminalWidth = terminalHeight * imageRatio 203 + } else { 204 + terminalHeight = terminalWidth / imageRatio 205 + } 207 206 208 - var width, height int 209 - if maxImageRatio > imageRatio { 210 - width = maxImageWidth 211 - height = width / imageRatio 212 - } else { 213 - height = maxImageHeight 214 - width = height * imageRatio 215 - } 207 + // TODO: Make the FONT_SIZE configureable via cli flag or figure 208 + // out a way to get the cell height from the terminal 209 + maxImageWidth := (terminalWidth / 2) * font_size 210 + maxImageHeight := (terminalHeight / 2) * font_size 216 211 217 - resizedAvatar := image.NewRGBA(image.Rect(0, 0, width, height)) 218 - draw.NearestNeighbor.Scale( 219 - resizedAvatar, 220 - resizedAvatar.Rect, 221 - avatarImg, 222 - avatarImg.Bounds(), 223 - draw.Over, 224 - nil, 225 - ) 212 + maxImageRatio := maxImageWidth / maxImageHeight 226 213 227 - if sixel.NewEncoder(os.Stdout).Encode(resizedAvatar) != nil { 228 - log.Fatal(err) 229 - } 214 + var width, height int 215 + if maxImageRatio > imageRatio { 216 + width = maxImageWidth 217 + height = width / imageRatio 218 + } else { 219 + height = maxImageHeight 220 + width = height * imageRatio 221 + } 230 222 231 - fmt.Printf("\nUser: %s (%s)\n", *profile.DisplayName, doc.Handle) 232 - fmt.Printf("DID: %s\n", followRecord.Subject) 233 - fmt.Println("Profile URL: " + "https://bsky.app/profile/" + followRecord.Subject) 234 - fmt.Printf("Description:\n%s\n\n", *profile.Description) 235 - fmt.Print("Keep this record ([Y]es / [n]o / [q]uit)? ") 223 + resizedAvatar := image.NewRGBA(image.Rect(0, 0, width, height)) 224 + draw.NearestNeighbor.Scale( 225 + resizedAvatar, 226 + resizedAvatar.Rect, 227 + avatarImg, 228 + avatarImg.Bounds(), 229 + draw.Over, 230 + nil, 231 + ) 236 232 237 - user_input: 238 - var answer string 239 - _, err = fmt.Scanln(&answer) 240 - if err != nil { 241 - log.Fatal(err) 242 - } 233 + if sixel.NewEncoder(os.Stdout).Encode(resizedAvatar) != nil { 234 + log.Fatal(err) 235 + } 243 236 244 - switch answer { 245 - case "": 246 - fmt.Println("Default, Yes") 247 - case "Y", "y": 248 - fmt.Println("Yes") 249 - case "N", "n": 250 - fmt.Println("No") 251 - case "Q", "q": 252 - fmt.Println("Quit") 253 - return 254 - default: 255 - fmt.Println("Expected either 'y', 'n', or 'q'") 237 + fmt.Printf("\nUser: %s (%s)\n", *profile.DisplayName, doc.Handle) 238 + fmt.Printf("DID: %s\n", followRecord.Subject) 239 + fmt.Println("Profile URL: " + "https://bsky.app/profile/" + followRecord.Subject) 240 + fmt.Printf("Description:\n%s\n\n", *profile.Description) 256 241 fmt.Print("Keep this record ([Y]es / [n]o / [q]uit)? ") 257 - goto user_input 242 + 243 + user_input: 244 + // TODO: Switch to bufio, fmt.Scanln doesn't accept empty input making the deafult value not really work 245 + var answer string 246 + _, err = fmt.Scanln(&answer) 247 + if err != nil { 248 + log.Fatal(err) 249 + } 250 + 251 + switch answer { 252 + case "", "Y", "y": 253 + fmt.Println("Yes") 254 + case "N", "n": 255 + fmt.Println("No") 256 + case "Q", "q": 257 + return 258 + default: 259 + fmt.Println("Expected either 'y', 'n', or 'q'") 260 + fmt.Print("Keep this record ([Y]es / [n]o / [q]uit)? ") 261 + goto user_input 262 + } 263 + 264 + fmt.Printf("\x1b[2J") 265 + fmt.Printf("\x1b[H") 258 266 } 259 - 260 - fmt.Printf("\x1b[2J") 261 - fmt.Printf("\x1b[H") 262 267 } 263 268 }