Skip to content

Instantly share code, notes, and snippets.

@temujin9
Last active June 20, 2024 17:41
Show Gist options
  • Save temujin9/9a9d8c57b9d95e8fbad4498af9ca909e to your computer and use it in GitHub Desktop.
Save temujin9/9a9d8c57b9d95e8fbad4498af9ca909e to your computer and use it in GitHub Desktop.
#!/usr/bin/env python3
# find my mutuals' active mutuals
import atproto
import session.client
from datetime import *
import dateutil.parser
import random
import os
import tenacity
client = session.client.login()
actor_ns = atproto.xrpc_client.namespaces.sync_ns.ActorNamespace(client)
feed_ns = atproto.xrpc_client.namespaces.sync_ns.FeedNamespace(client)
graph_ns = atproto.xrpc_client.namespaces.sync_ns.GraphNamespace(client)
output = os.path.dirname(os.path.realpath(__file__)) + "/suggestions.html"
retryer = tenacity.Retrying(stop=tenacity.stop_after_attempt(3), wait=tenacity.wait_fixed(1), reraise=True)
def find_follows(did):
follows = {}
params = { "actor": did }
while True:
result = retryer( graph_ns.get_follows, params )
follows.update( dict(map( lambda x: [x.did, x], result.follows )) )
if result.cursor == None: break
params["cursor"] = result.cursor
return(follows)
def find_followers(did):
followers = {}
params = { "actor": did }
while True:
result = retryer( graph_ns.get_followers, params )
followers.update( dict(map( lambda x: [x.did, x], result.followers )) )
if result.cursor == None: break
params["cursor"] = result.cursor
return(followers)
mutuals = {}
follows = find_follows(client.me.did)
for did in follows:
if follows[did].viewer.followed_by: mutuals[did] = follows[did]
knockout = {client.me.did: True}
maybe = {}
found = {}
now = datetime.now(tz=timezone.utc)
mdids = list(mutuals.keys())
random.shuffle(mdids)
print(f"found {len(mutuals)} mutuals, checking their mutuals")
for mdid in mdids:
print(f"checking {mutuals[mdid].handle}: ",end="",flush=True)
mfollows = find_follows(mdid)
mfollowers = find_followers(mdid)
for mfdid in mfollows:
if mfdid in knockout: continue
if mfdid in found:
maybe[mfdid] += 1
continue
i_am = mfollows[mfdid].viewer
if i_am.muted or i_am.blocking or i_am.blocked_by or i_am.following or i_am.followed_by:
knockout[mfdid] = True # Known
continue
if mfdid in mfollowers:
maybe[mfdid] = maybe.get(mfdid,0) + 1
if maybe[mfdid] >= 2:
full = retryer( actor_ns.get_profile, { "actor": mfdid } )
if 2 * full.follows_count < full.followers_count:
knockout[mfdid] = True # Parasocial
continue
if full.follows_count > max(2 * full.followers_count, 5000):
knockout[mfdid] = True # Spam follows
continue
af = retryer( feed_ns.get_author_feed, { "actor": mfdid, "limit": 3 } )
if len(af.feed) == 0:
knockout[mfdid] = True # Inactive
continue
last = af.feed.pop()
if dateutil.parser.isoparse(last.post.indexed_at) < now - timedelta(days=7):
knockout[mfdid] = True # Inactive
continue
found[mfdid] = mfollows[mfdid]
print(f"follows {len(mfollows)}, found {len(found)} of {len(maybe)} possibilities")
if len(found) >= 1000: break
with open(output, "w") as f:
f.write("<body bgcolor='#AAA'>")
for fdid in sorted(maybe, key=maybe.get, reverse=False):
if fdid in found: f.write(f"<a href='https://bsky.app/profile/{fdid}'>{found[fdid].handle}</a><br>\n")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment