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.

initial commit

Currently we just authenticate the user's handle, next step is to
determine if we will be sifting follows or likes and then to iterate
through all of those records

yemou d8506d9c

+286
+27
flake.lock
··· 1 + { 2 + "nodes": { 3 + "nixpkgs": { 4 + "locked": { 5 + "lastModified": 1770197578, 6 + "narHash": "sha256-AYqlWrX09+HvGs8zM6ebZ1pwUqjkfpnv8mewYwAo+iM=", 7 + "owner": "NixOS", 8 + "repo": "nixpkgs", 9 + "rev": "00c21e4c93d963c50d4c0c89bfa84ed6e0694df2", 10 + "type": "github" 11 + }, 12 + "original": { 13 + "owner": "NixOS", 14 + "ref": "nixos-unstable", 15 + "repo": "nixpkgs", 16 + "type": "github" 17 + } 18 + }, 19 + "root": { 20 + "inputs": { 21 + "nixpkgs": "nixpkgs" 22 + } 23 + } 24 + }, 25 + "root": "root", 26 + "version": 7 27 + }
+25
flake.nix
··· 1 + { 2 + description = "Interactively filter likes and follows out of your account"; 3 + 4 + inputs.nixpkgs.url = "github:NixOS/nixpkgs?ref=nixos-unstable"; 5 + 6 + outputs = 7 + { self, nixpkgs }: 8 + let 9 + systems = [ "x86_64-linux" ]; 10 + forSystems = func: nixpkgs.lib.genAttrs systems (system: func (import nixpkgs { inherit system; })); 11 + in 12 + { 13 + formatter = forSystems (pkgs: pkgs.nixfmt-tree.override { settings.formatter.nixfmt.options = [ "-w 120" ]; }); 14 + 15 + devShells = forSystems (pkgs: { 16 + default = pkgs.mkShell { 17 + buildInputs = with pkgs; [ 18 + go 19 + gopls 20 + gotools 21 + ]; 22 + }; 23 + }); 24 + }; 25 + }
+26
go.mod
··· 1 + module tangled.org/yemou.pink/bsky-sieve 2 + 3 + go 1.25.5 4 + 5 + require github.com/bluesky-social/indigo v0.0.0-20260206210545-4bec71212487 6 + 7 + require ( 8 + github.com/beorn7/perks v1.0.1 // indirect 9 + github.com/cespare/xxhash/v2 v2.2.0 // indirect 10 + github.com/earthboundkid/versioninfo/v2 v2.24.1 // indirect 11 + github.com/golang-jwt/jwt/v5 v5.2.2 // indirect 12 + github.com/google/go-querystring v1.1.0 // indirect 13 + github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect 14 + github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect 15 + github.com/mr-tron/base58 v1.2.0 // indirect 16 + github.com/prometheus/client_golang v1.17.0 // indirect 17 + github.com/prometheus/client_model v0.5.0 // indirect 18 + github.com/prometheus/common v0.45.0 // indirect 19 + github.com/prometheus/procfs v0.12.0 // indirect 20 + gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b // indirect 21 + gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 // indirect 22 + golang.org/x/crypto v0.21.0 // indirect 23 + golang.org/x/sys v0.22.0 // indirect 24 + golang.org/x/time v0.3.0 // indirect 25 + google.golang.org/protobuf v1.33.0 // indirect 26 + )
+74
go.sum
··· 1 + github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 2 + github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 3 + github.com/bluesky-social/indigo v0.0.0-20260206210545-4bec71212487 h1:LOV9OnRY8crRR6ZE0PpOwrGxKTeJUtX384NU+rvCVmg= 4 + github.com/bluesky-social/indigo v0.0.0-20260206210545-4bec71212487/go.mod h1:VG/LeqLGNI3Ew7lsYixajnZGFfWPv144qbUddh+Oyag= 5 + github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= 6 + github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 7 + github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 8 + github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 9 + github.com/earthboundkid/versioninfo/v2 v2.24.1 h1:SJTMHaoUx3GzjjnUO1QzP3ZXK6Ee/nbWyCm58eY3oUg= 10 + github.com/earthboundkid/versioninfo/v2 v2.24.1/go.mod h1:VcWEooDEuyUJnMfbdTh0uFN4cfEIg+kHMuWB2CDCLjw= 11 + github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= 12 + github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= 13 + github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 14 + github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 15 + github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 16 + github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= 17 + github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= 18 + github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= 19 + github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= 20 + github.com/ipfs/go-cid v0.4.1 h1:A/T3qGvxi4kpKWWcPC/PgbvDA2bjVLO7n4UeVwnbs/s= 21 + github.com/ipfs/go-cid v0.4.1/go.mod h1:uQHwDeX4c6CtyrFwdqyhpNcxVewur1M7l7fNU7LKwZk= 22 + github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= 23 + github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= 24 + github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= 25 + github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= 26 + github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= 27 + github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= 28 + github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= 29 + github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= 30 + github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE= 31 + github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI= 32 + github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0= 33 + github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4= 34 + github.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g= 35 + github.com/multiformats/go-multibase v0.2.0/go.mod h1:bFBZX4lKCA/2lyOFSAoKH5SS6oPyjtnzK/XTFDPkNuk= 36 + github.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7BFvVU9RSh+U= 37 + github.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM= 38 + github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8= 39 + github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU= 40 + github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 41 + github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 42 + github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q= 43 + github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY= 44 + github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= 45 + github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= 46 + github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM= 47 + github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= 48 + github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= 49 + github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= 50 + github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= 51 + github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= 52 + github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 53 + github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 54 + github.com/whyrusleeping/cbor-gen v0.2.1-0.20241030202151-b7a6831be65e h1:28X54ciEwwUxyHn9yrZfl5ojgF4CBNLWX7LR0rvBkf4= 55 + github.com/whyrusleeping/cbor-gen v0.2.1-0.20241030202151-b7a6831be65e/go.mod h1:pM99HXyEbSQHcosHc0iW7YFmwnscr+t9Te4ibko05so= 56 + gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b h1:CzigHMRySiX3drau9C6Q5CAbNIApmLdat5jPMqChvDA= 57 + gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b/go.mod h1:/y/V339mxv2sZmYYR64O07VuCpdNZqCTwO8ZcouTMI8= 58 + gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 h1:qwDnMxjkyLmAFgcfgTnfJrmYKWhHnci3GjDqcZp1M3Q= 59 + gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02/go.mod h1:JTnUj0mpYiAsuZLmKjTx/ex3AtMowcCgnE7YNyCEP0I= 60 + golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= 61 + golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= 62 + golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= 63 + golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 64 + golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= 65 + golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 66 + golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 67 + golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= 68 + golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= 69 + google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= 70 + google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 71 + gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 72 + gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 73 + lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI= 74 + lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=
+12
license
··· 1 + Copyright (C) 2026, yemou <dev@mou.pink> 2 + 3 + Permission to use, copy, modify, and/or distribute this software for any purpose 4 + with or without fee is hereby granted. 5 + 6 + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 7 + REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND 8 + FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 9 + INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS 10 + OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER 11 + TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF 12 + THIS SOFTWARE.
+89
main.go
··· 1 + package main 2 + 3 + import ( 4 + "context" 5 + _ "embed" 6 + "errors" 7 + "flag" 8 + "fmt" 9 + "log" 10 + "net" 11 + "net/http" 12 + "net/url" 13 + 14 + "github.com/bluesky-social/indigo/atproto/auth/oauth" 15 + ) 16 + 17 + //go:embed static/callback.html 18 + var callbackHTML []byte 19 + 20 + func callbackListener(ctx context.Context, result chan url.Values) (int, error) { 21 + listener, err := net.Listen("tcp", ":0") 22 + if err != nil { 23 + return 0, err 24 + } 25 + 26 + mux := http.NewServeMux() 27 + server := &http.Server{Handler: mux} 28 + 29 + mux.HandleFunc("/callback", func(writer http.ResponseWriter, request *http.Request) { 30 + result <- request.URL.Query() 31 + writer.Header().Set("Content-Type", "text/html;charset=utf-8") 32 + writer.WriteHeader(200) 33 + writer.Write(callbackHTML) 34 + go server.Shutdown(ctx) 35 + }) 36 + 37 + go func() { 38 + err := server.Serve(listener) 39 + if !errors.Is(err, http.ErrServerClosed) { 40 + panic(err) 41 + } 42 + }() 43 + 44 + return listener.Addr().(*net.TCPAddr).Port, nil 45 + } 46 + 47 + func main() { 48 + handle := flag.String("h", "", "The account handle to use") 49 + flag.Parse() 50 + 51 + if *handle == "" { 52 + log.Fatal("Missing bluesky handle") 53 + } 54 + 55 + config := oauth.ClientConfig{ 56 + ClientID: "https://bsky-sieve.yem.pink/oauth-client-metadata.json", 57 + Scopes: []string{ 58 + "atproto", 59 + "atproto repo?collection=app.bsky.feed.like&collection=app.bsky.graph.follow&action=delete", 60 + }, 61 + UserAgent: "bsky-sieve", 62 + } 63 + 64 + oauthClient := oauth.NewClientApp(&config, oauth.NewMemStore()) 65 + 66 + ctx := context.Background() 67 + 68 + callbackResult := make(chan url.Values, 1) 69 + listenPort, err := callbackListener(ctx, callbackResult) 70 + if err != nil { 71 + log.Fatal(err) 72 + } 73 + 74 + config.CallbackURL = fmt.Sprintf("http://127.0.0.1:%d/callback", listenPort) 75 + 76 + authURL, err := oauthClient.StartAuthFlow(ctx, *handle) 77 + if err != nil { 78 + log.Fatal(err) 79 + } 80 + 81 + fmt.Println("Login URL:", authURL) 82 + 83 + session, err := oauthClient.ProcessCallback(ctx, <-callbackResult) 84 + if err != nil { 85 + log.Fatal(err) 86 + } 87 + 88 + fmt.Printf("Logged in as: %s (%s)\n", *handle, session.AccountDID) 89 + }
+3
readme.md
··· 1 + # bsky-sieve 2 + Interactively goes through either likes and follows and allows you to decide to 3 + keep or remove the record from your account.
+13
static/callback.html
··· 1 + <!DOCTYPE html> 2 + <html lang="en"> 3 + <head> 4 + <meta charset="UTF-8"> 5 + <meta name="viewport" content="width=device-width"> 6 + <title>Bsky Sieve</title> 7 + </head> 8 + <body> 9 + <h1>Bsky Sieve</h1> 10 + <p><strong>Authenticated!</strong></p> 11 + <p>You can safely close this tab.</p> 12 + </body> 13 + </html>
+17
static/client-metadata.json
··· 1 + { 2 + "client_id": "https://bsky-sieve.yem.pink/oauth-client-metadata.json", 3 + "application_type": "native", 4 + "grant_types": [ 5 + "authorization_code" 6 + ], 7 + "scope": "atproto repo?collection=app.bsky.feed.like&collection=app.bsky.graph.follow&action=delete", 8 + "response_types": [ 9 + "code" 10 + ], 11 + "redirect_uris": [ 12 + "http://127.0.0.1/callback" 13 + ], 14 + "token_endpoint_auth_method": "none", 15 + "dpop_bound_access_tokens": true, 16 + "client_name": "bsky-sieve" 17 + }