Skip to content

Instantly share code, notes, and snippets.

@jph00
Created December 2, 2024 04:08
Show Gist options
  • Save jph00/e98a0ebe61091a7d914acf0d6c50c64e to your computer and use it in GitHub Desktop.
Save jph00/e98a0ebe61091a7d914acf0d6c50c64e to your computer and use it in GitHub Desktop.
Unfollow bsky non-mutuals
Display the source blob
Display the rendered blob
Raw
{
"cells": [
{
"metadata": {},
"id": "00324ac4",
"cell_type": "markdown",
"source": "# Remove bsky non-mutual follows"
},
{
"metadata": {},
"id": "4cc30be4",
"cell_type": "markdown",
"source": "You'll need to pip install fastprogress and atproto."
},
{
"metadata": {
"trusted": true
},
"id": "e45d6bc4",
"cell_type": "code",
"source": "from fastcore.utils import *\nfrom fastcore.xtras import *\nfrom atproto import Client\nfrom fastprogress.fastprogress import master_bar, progress_bar",
"execution_count": 65,
"outputs": []
},
{
"metadata": {},
"id": "3dc1eb44",
"cell_type": "markdown",
"source": "Create a file \".env\" containing one line: `bsky_pass=...`, in the same directory."
},
{
"metadata": {
"trusted": true
},
"id": "f6f46131",
"cell_type": "code",
"source": "env = parse_env(fn='.env')\ncli = Client()\npv = cli.login('howard.fm', env['bsky_pass'])\ndid = pv.did",
"execution_count": 66,
"outputs": []
},
{
"metadata": {
"trusted": true
},
"id": "c587f69b",
"cell_type": "code",
"source": "pv.posts_count,pv.followers_count,pv.follows_count",
"execution_count": 67,
"outputs": [
{
"output_type": "execute_result",
"execution_count": 67,
"data": {
"text/plain": "(246, 15061, 979)"
},
"metadata": {}
}
]
},
{
"metadata": {
"trusted": true
},
"id": "b500e1af",
"cell_type": "code",
"source": "def at_paged(did, meth):\n \"Return all pages of results from some method\"\n resp = None\n while True:\n resp = meth(did, cursor=resp.cursor if resp else None)\n yield resp\n if not resp.cursor: return",
"execution_count": 68,
"outputs": []
},
{
"metadata": {
"trusted": true
},
"id": "7659876c",
"cell_type": "code",
"source": "posts = L(at_paged(did, cli.get_author_feed)).attrgot('feed').concat()",
"execution_count": 74,
"outputs": []
},
{
"metadata": {
"trusted": true
},
"id": "350bdd43",
"cell_type": "code",
"source": "posts[-2].post.record",
"execution_count": 86,
"outputs": [
{
"output_type": "execute_result",
"execution_count": 86,
"data": {
"text/plain": "Record(created_at='2023-05-08T03:17:47.510Z', text='👋🏽', embed=None, entities=None, facets=None, labels=None, langs=None, reply=ReplyRef(parent=Main(cid='bafyreib7cq47fgqz7fpa4kdlwuz3m7ukdcfyftkev75kldvaorrqqennle', uri='at://did:plc:cn7lps422rbgdpsh3dg5e5c2/app.bsky.feed.post/3jv6axng6re2r', py_type='com.atproto.repo.strongRef'), root=Main(cid='bafyreib7cq47fgqz7fpa4kdlwuz3m7ukdcfyftkev75kldvaorrqqennle', uri='at://did:plc:cn7lps422rbgdpsh3dg5e5c2/app.bsky.feed.post/3jv6axng6re2r', py_type='com.atproto.repo.strongRef'), py_type='app.bsky.feed.post#replyRef'), tags=None, py_type='app.bsky.feed.post')"
},
"metadata": {}
}
]
},
{
"metadata": {
"trusted": true
},
"id": "be262711",
"cell_type": "code",
"source": "fws = L(at_paged(did, cli.get_follows))",
"execution_count": 45,
"outputs": []
},
{
"metadata": {
"trusted": true
},
"id": "001687b2",
"cell_type": "code",
"source": "frs = L(at_paged(did, cli.get_followers))",
"execution_count": 46,
"outputs": []
},
{
"metadata": {
"trusted": true
},
"id": "84f21a27",
"cell_type": "code",
"source": "following = fws.attrgot('follows').concat()\nfollowers = frs.attrgot('followers').concat()",
"execution_count": 47,
"outputs": []
},
{
"metadata": {
"trusted": true
},
"id": "09849316",
"cell_type": "code",
"source": "len(following),len(followers)",
"execution_count": 48,
"outputs": [
{
"output_type": "execute_result",
"execution_count": 48,
"data": {
"text/plain": "(2144, 15016)"
},
"metadata": {}
}
]
},
{
"metadata": {
"trusted": true
},
"id": "ef988087",
"cell_type": "code",
"source": "to_unfollow = set(following.attrgot('did')) - set(followers.attrgot('did'))\nlen(to_unfollow)",
"execution_count": 52,
"outputs": [
{
"output_type": "execute_result",
"execution_count": 52,
"data": {
"text/plain": "1172"
},
"metadata": {}
}
]
},
{
"metadata": {
"trusted": true
},
"id": "e2d72b5d",
"cell_type": "code",
"source": "followd = {o.did:o.viewer.following for o in following}",
"execution_count": 59,
"outputs": []
},
{
"metadata": {
"trusted": true,
"scrolled": true
},
"id": "1497b747",
"cell_type": "code",
"source": "for fdid in progress_bar(to_unfollow):\n try: cli.unfollow(followd[fdid])\n except Exception as e: print(f\"Could not unfollow {fdid}: {e}\")",
"execution_count": 61,
"outputs": [
{
"output_type": "display_data",
"data": {
"text/plain": "<IPython.core.display.HTML object>",
"text/html": "\n<style>\n /* Turns off some styling */\n progress {\n /* gets rid of default border in Firefox and Opera. */\n border: none;\n /* Needs to be in here for Safari polyfill so background images work as expected. */\n background-size: auto;\n }\n progress:not([value]), progress:not([value])::-webkit-progress-bar {\n background: repeating-linear-gradient(45deg, #7e7e7e, #7e7e7e 10px, #5c5c5c 10px, #5c5c5c 20px);\n }\n .progress-bar-interrupted, .progress-bar-interrupted::-webkit-progress-bar {\n background: #F44336;\n }\n</style>\n"
},
"metadata": {}
},
{
"output_type": "display_data",
"data": {
"text/plain": "<IPython.core.display.HTML object>",
"text/html": "\n <div>\n <progress value='1172' class='' max='1172' style='width:300px; height:20px; vertical-align: middle;'></progress>\n 100.00% [1172/1172 04:56&lt;00:00]\n </div>\n "
},
"metadata": {}
}
]
}
],
"metadata": {
"_draft": {
"nbviewer_url": "https://gist.github.com/jph00/741542588aa121794473e4fec3a41635"
},
"gist": {
"id": "741542588aa121794473e4fec3a41635",
"data": {
"description": "Unfollow bsky non-mutuals",
"public": true
}
},
"kernelspec": {
"name": "python3",
"display_name": "Python 3 (ipykernel)",
"language": "python"
},
"language_info": {
"name": "python",
"version": "3.11.8",
"mimetype": "text/x-python",
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"pygments_lexer": "ipython3",
"nbconvert_exporter": "python",
"file_extension": ".py"
},
"toc": {
"base_numbering": 1,
"nav_menu": {
"height": "387.791px",
"width": "214.794px"
},
"number_sections": true,
"sideBar": true,
"skip_h1_title": false,
"title_cell": "Table of Contents",
"title_sidebar": "Contents",
"toc_cell": false,
"toc_position": {},
"toc_section_display": true,
"toc_window_display": false
}
},
"nbformat": 4,
"nbformat_minor": 5
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment