Oddbean new post about | logout
 How far did my post go on the Fediverse?
https://shkspr.mobi/blog/2023/09/how-far-did-my-post-go-on-the-fediverse/

I wrote a moderately popular post on Mastodon. Lots of people shared it. Is it possible to find out how many different ActivityPub servers it went to?

Yes!

As we all know, the Fediverse is one big chain mail.  I don't mean that in a derogatory way.

When I write a post, it appears on my server (called an "instance" in Mastodon-speak).

Everyone on my instance can see my post.

My instance looks at all my followers - some of whom are on completely different instances - and sends my post to their instances.

As an example:

I am on mastodon.social
John is on eggman_social.com
Paul is on penny_lane.co.uk
Both John and Paul follow me. So my post gets syndicated to their servers.

With me so far?

What happens when someone shares (reposts) my status?

John is on eggman_social.com
Ringo is on liverpool.drums
Ringo follows John
John reposts my status.
eggman_social.com syndicates my post to liverpool.drums

And so my post goes around the Fediverse!  But can I see where it has gone?  Well... sort of! Let's look at how.

A note on privacy

People on Mastodon and the Fediverse tend to be privacy conscious. So there are limits - both in the API and the culture - as to what is acceptable.

Some people don't share their "social graph". That is, it is impossible to see who follows them or who they follow.

Users can choose to opt-in or -out of publicly sharing their social graph. They remain in control of their privacy.

In the example above, if Ringo were to reshare John's reshare of my status - John doesn't know about it. Only the original poster (me) gets notified. If John doesn't share his social graph, it might be possible to work out where Ringo saw the status - but that's rather unlikely.

Mastodon has an API rate limit which only allows 80 results per request and 1 request per second. That makes it long and tedious to crawl thousands of results.

Similarly, some instances do not share their social data or expose anything of significance. Some servers may no longer exist, or might have changed names. It's impossible to get a comprehensive view of the entire Fediverse network.

And that's OK! People should be able to set limits on what others can do with their data.  The code you're about to see doesn't attempt to breach anyone's privacy. All it does is show me which servers picked up my post. This is information which is already shown to me - but this makes it slightly easier to see.

The Result

I looked at this post of mine which was reposted over 100 times.

It eventually found its way to… 2,547 instances!

Ranging from 0ab.uk to թութ.հայ via godforsaken.website and many more!

And that's one of the things which makes me hopeful this rebellion will succeed. There are a thousand points of light out there - each a shining beacon to doing things differently. And, the more the social media giants tighten their grip, the more these systems will slip through their fingers.

The Code

This is not very efficient code - nor well written. It was designed to scratch an itch.  It uses Mastodon.py to interact with the API.

It gets the instance names of all my followers. Then the instance names of everyone who reposted one of my posts.

But it cannot get the instance names of everyone who follows the users who reposted me - because:
[Image:  Followers from other servers are not displayed. Browse more on the original profile.]

The only way to get a list of followers from a user on a different instance is to apply for an API key for that instance. Which seems a bit impractical.

But I can get the instance name of the followers of accounts on my instance who reposted me. Clear?

I can also get a list of everyone who favourited my post. If they aren't on my instance, or one of my reposter's follower's instances, they're probably from a reposter who isn't on my instance.

My head hurts.

Got it? Here we go!import configfrom mastodon import Mastodonfrom rich.pretty import pprint#  Set up accessmastodon = Mastodon( api_base_url=config.instance, access_token=config.access_token, ratelimit_method='pace' )#  Status to check forstatus_id = 111040801202691232print("Looking up status: " + str(status_id))#  Get my datame = mastodon.me()my_id = me["id"]print("You have User ID: " + str(my_id))#  Empty setsinstances_all        = set()instances_followers  = set()instances_reposters  = set()instances_reposters_followers  = set()instances_favourites = set()#  My Followersfollowers = mastodon.account_followers( my_id )print( "Getting all followers" )followers_all = mastodon.fetch_remaining( followers )print("Total followers = " + str( len(followers_all) ) )#  Get the server names of all my followersfor follower in followers_all:    if ( "@" in follower["acct"]) :        f = follower["acct"].split("@")[1]        instances_all.add( f )        if ( f not in instances_followers):            print( "Follower: " + f )            instances_followers.add( f )    else :        instances_all.add( "mastodon.social" )        instances_followers.add( "mastodon.social" )total_followers  = len(instances_followers)print( "Total Unique Followers Instances = " + str(total_followers)  )#  Reposters#  Find the accounts which reposted my statusreposters     = mastodon.status_reblogged_by( status_id )reposters_all = mastodon.fetch_remaining(reposters)#  Get all the instance names of my repostersfor reposter in reposters_all:    if ( "@" in reposter["acct"]) :        r = reposter["acct"].split("@")[1]        instances_all.add( r )        if ( r not in instances_followers ) :            print( "Reposter: " + r )            instances_reposters.add( r )total_reposters  = len(instances_reposters)print( "Total Unique Reposters Instances = " + str(total_reposters)  )# Followers of reposters     # This can take a *long* time!   for reposter in reposters_all:       if ( "@" not in reposter["acct"]) :          reposter_id = reposter["id"]        print( "Getting followers of reposter " + reposter["acct"] + " with ID " + str(reposter_id) )        reposter_followers = mastodon.account_followers( reposter_id )           reposter_followers_all = mastodon.fetch_remaining( reposter_followers )          for reposter_follower in reposter_followers_all:                if ( "@" in reposter_follower["acct"]) :                 f = reposter_follower["acct"].split("@")[1]                instances_all.add( f )                if (f not in instances_reposters_followers) :                    print( "   Adding " + f + " from " + reposter["acct"] )                    instances_reposters_followers.add( f )   total_instances_reposters_followers  = len(instances_reposters_followers)print( "Total Unique Reposters' Followers Instances = " + str(total_instances_reposters_followers)  )#  Favourites#  Find the accounts which favourited my statusfavourites     = mastodon.status_favourited_by( status_id )favourites_all = mastodon.fetch_remaining(favourites)#  Get all the instance names of my favouritesfor favourite in favourites_all:    if ( "@" in favourite["acct"]) :        f = favourite["acct"].split("@")[1]        instances_all.add( f )        if ( f not in instances_favourites ) :            print( "Favourite: " + f )            instances_favourites.add( r )total_favourites = len(instances_favourites)print( "Total Unique Favourites Instances  = " + str(total_favourites) )print( "Total Unique Reposters Instances = " + str(total_reposters)  )print( "Total Unique Followers Instances = " + str(total_followers)  )print( "Total Unique Reposters' Followers Instances = " + str( len(instances_reposters_followers) ) )print( "Total Unique Instances = " + str( len(instances_all) ) )

https://shkspr.mobi/blog/2023/09/how-far-did-my-post-go-on-the-fediverse/

#mastodon #MastodonAPI #python