4 min read

Getting Collections From The Bluesky API

Getting Collections From The Bluesky API

Yesterday almost immediately gave me pause about spending a month learning more about ATProto. Bluesky CEO Jay Graber is having a bit of an ongoing crashout about moderation and the general culture of Bluesky, in this case about the mods' ongoing work to appeal to right-wingers and alienate trans and other marginalized users. To some degree I'm sympathetic to the broader culture problems that Graber takes issue with. Bluesky is full of the type of people who are willing to leave Twitter for moral reasons, which results in a lot of people getting into each other's mentions being upset or resolutely refusing to get the joke. But my dude, you are the CEO of the company. It is probably not a good look to publicly resent your users and, like, the selling point of your specific app is that it's not for nazis. You may not like the culture you've established but there's really no way around it. Trying to grow the app by trying to make it more appealing to right-wingers is like trying to grow Steak-umm's market share by advertising to vegans.

Despite all this, I'm still fairly bullish on ATProto's prospects, or at least the prospects of something like it. While I agree with his assessment of the overall bluesky situation I'm not sold on Bruno Dias's critiques of its centralization in the linked blog post. You can host a full network relay for $34 a month (allegedly some people are even doing it on a Raspberry Pi now, something I might attempt doing during this project). There isn't a meaningful alternative to Bluesky at the moment, but Blacksky appears to be getting there, and regardless I think a lot of people are still treating Bluesky as the end-all be-all of ATProto. I think of Bluesky as a proof of concept: it's successful at what it's trying to be right now, which is basically a skeuomorphism of Twitter. With that comes all the problems of being a Twitter-like: culture, moderation, people being insane online, and so on.

What's promising to me about ATProto isn't that we can have infinite Twitters - Mastodon is already doing that and it kind of sucks - but that owning your own social graph and data is meaningfully possible. A lot of this is just about what you're designing for. For better or worse, people are going to naturally migrate to platforms that are easy to use. Selling people on open social is unfortunately going to require VC-funded platforms with nice design and a lot of money behind them. The appeal is not that we are going to eliminate tech companies and tech culture entirely, but that we can tear down walled gardens. ATProto's killer app isn't going to be bluesky and probably isn't going to be any of the current projects in the ecosystem. It's likely to be something no one has thought of yet. Right now the existing social media apps have the first mover advantage. But once that app is there and you can share your social graph across whatever social services you use: git, blogging platforms, short-form video, or whatever else, I think the appeal will start to become clearer.

As always, I reserve the right to change my mind about this, but I still think ATProto strikes the right balance between decentralization and usability in a way that ActivityPub just doesn't. Anyway, back to trying to actually use the software.


Since I ended up largely underwhelmed by the atproto quickstart, I decided to check the source code for the bluesky repo explorer, which is able to successfully fetch data from multiple PDSes. The author openly admits all these tools were vibe-coded so I have to assume it's relatively trivial. (I don't intend to vibe code anything during this project since it kind of defeats the purpose, but good to know it's possible).

I borrowed their API code and wrote a few calls to get my follow graph and their collections directly from the bluesky API, then filtered to see how many people have non-default bluesky collections:

const user = await getActorProfile('bront.rodeo')
const desc = await describeRepo(user.did)
const following = await fetchBskyApi('app.bsky.graph.getFollows', 'GET', { actor: user.did }, null, null, true)
function has_non_bsky_collection(collections) {
    const non_bsky_elements = collections.filter(c => {
        return !c.includes('bsky')
    })
    return non_bsky_elements.length > 0
}
const following_repos = following.follows.map( async f => {
    return await describeRepo(f.did).catch(e => { console.error(`Error describing repo for ${f.did}:`, e); return null; });
})

const non_bsky_following = await Promise.all(following_repos).then(results => {
    return results.filter(desc => {
        console.log('desc:', desc)
        return desc && "collections" in desc && has_non_bsky_collection(desc.collections)
    })
});

console.log('user: ', user)
console.log('desc: ', desc)
console.log('following: ', following)
console.log('follows with non-bluesky collections: ', non_bsky_following)
console.log('sicko count: ', non_bsky_following.length)

This resulted in a list of 10 profiles with some truly insane collection counts, like this:

I filtered these again to find anyone who had done yesterday's tutorial, and came back with just one person, the owner of the huge collection count above: Ted Han, an ATProto dev and ATMosphere conference organizer. I don't know whether to consider this a disappointment or just as a sign that the more interesting development work around ATProto is happening elsewhere in the stack.

The way these things are tied together makes my head hurt but I think this is technical proof that I could, hypothetically, get all this info from any open ATProto API and not be reliant on bluesky specifically - the key was the com.atProto.repo.describeRepo endpoint that I hadn't been able to find yesterday. The follow graph is still bluesky-specific but it seems like it would be easy enough to map to a non-bluesky service. I think my brain has done enough reps for one day but I'd like to dig deeper on this tomorrow.