Fabian Scheuermann

Fetch information from Bluesky

As of right now, it seems like Bluesky is winning the race to replace Twitter. I am not actively using the platform (i.e. I am not posting things), but from time to time, I like to scroll a bit through it. It serves different thinks like feeds or lists to display things one is interested in, but in my experience, the best solution is following people related to topics I am interested in.

When I first started, hopping from one profile to the next helped me discover new profiles. That was fun, but at some point, I ran out of options. Now I'm basically waiting for new people to register, but repeating this process and going through all of them again seems boring.

This is where this platform shows its strength due to its open structure, which enables everyone to contribute. Someone probably already has a suitable solution to my problem, but I it is also fun to play around a bit with it. The atproto package is specifically written to interact with Bluesky, but I did not want to install a dedicated package. For my purpose, it is enough to rely on the HTTP API that is described in this documentation for the HTTP API.

The goal is to get some information about a profile and retrieve a list of people that account is following. This way, I want to construct a list with suggestions of potentially interesting accounts to follow.

handles like fschmnn.com can be updated while the DID is persistent. We start by

import requests 

def resolveHandle(handle: str) -> str:
    """Resolves an atproto handle (hostname) to a DID.

    Parameters
    ----------
    handle : str 
        The handle to resolve.
    """

    base_url = r"https://bsky.social/xrpc"
    function = r"com.atproto.identity.resolveHandle"    
    request_url = f'{base_url}/{function}?handle={handle}'
    response = requests.get(request_url)

    return response.json()['did']

actor = resolveHandle('fschmnn.com')

If we want some additional information about the profile we can get it via

def getProfile(actor: str) -> dict:
    """Get detailed profile view of an actor.

    Parameters
    ----------
    actor : str 
        Handle or DID of account to fetch profile of.
    """

    base_url = r"https://public.api.bsky.app/xrpc"
    function = r"app.bsky.actor.getProfile"
    request_url = f'{base_url}/{function}?actor={actor}'

    response = requests.get(request_url)

    return response.json()

profile = getProfile(actor)

The API only allows to fetch 100 following at once (or less, see limits). If an account follows more than 100 other accounts, a cursor is returned. We can then use this cursor to get the next 100 accounts. The following function does this until no cursor is returned (all accounts are retrieved).

def getFollows(actor: str) -> dict:
    """Enumerates accounts which a specified account (actor) follows.

    Parameters
    ----------
    actor : str 
        Handle or DID of account to fetch follows from.
    """

    base_url = r"https://public.api.bsky.app/xrpc"
    function = r"app.bsky.graph.getFollows"
    cursor   = "" 

    follows = []
    while isinstance(cursor,str): 
        request_url = f'{base_url}/{function}?actor={actor}&limit=100&cursor={cursor}'
        response = requests.get(request_url).json()
        follows += response.get('follows')
        cursor   = response.get('cursor')

    return follows

back to overview