Skip to content

Instantly share code, notes, and snippets.

@ianrenton
Last active January 3, 2025 20:47
Show Gist options
  • Save ianrenton/8f5c3d26e3ad3b3ab2746d53a3e6ea61 to your computer and use it in GitHub Desktop.
Save ianrenton/8f5c3d26e3ad3b3ab2746d53a3e6ea61 to your computer and use it in GitHub Desktop.
Scrape callsigns of ham radio folks from your fedi mutuals
# Find callsigns of all the ham radio folks you are mutuals with on fedi (may only work with Mastodon)
# and produce a list so you can add them to HamAlert or something.
# Ian Renton, January 2025
# Public Domain software
import re
import requests
USERNAME='ian'
INSTANCE='https://mastodon.radio'
CALLSIGN_REGEX=re.compile(r'[a-zA-Z0-9]{1,3}[0-9Ø][a-zA-Z0-9Ø]{0,3}[a-zA-Z]')
NEXT_LINK_REGEX=re.compile(r'<(.+?)>; rel="next"')
# Get user ID
acct_lookup_response = requests.get(INSTANCE + '/api/v1/accounts/lookup?acct=' + USERNAME)
my_id = acct_lookup_response.json()['id']
# Get followers and following. There is a maximum limit per request so we need to go through
# pagination hassle.
followers = []
page = 1
next_followers_url = INSTANCE + '/api/v1/accounts/' + my_id + '/followers'
while True:
print("Getting followers page " + str(page) + "...")
followers_response = requests.get(next_followers_url)
followers.extend(followers_response.json())
link_text = followers_response.headers['link']
link_match = NEXT_LINK_REGEX.search(link_text)
if link_match:
next_followers_url = link_match.group(1)
page += 1
else:
break
print("Found " + str(len(followers)) + " followers.")
following = []
page = 1
next_following_url = INSTANCE + '/api/v1/accounts/' + my_id + '/following'
while True:
print("Getting following page " + str(page) + "...")
following_response = requests.get(next_following_url)
following.extend(following_response.json())
link_text = following_response.headers['link']
link_match = NEXT_LINK_REGEX.search(link_text)
if link_match:
next_following_url = link_match.group(1)
page += 1
else:
break
print("Found " + str(len(following)) + " following.")
# Find mutuals
following_accts = list(map(lambda a: a['acct'], following))
mutuals = list(filter(lambda a: a['acct'] in following_accts, followers))
print("Found " + str(len(mutuals)) + " mutuals.")
# Build a list of things that look like callsigns from your mutuals
callsigns = []
for acct in mutuals:
print('Checking ' + acct['acct'] + '...')
# Check for callsigns in the account name itsef, the display name and any custom fields.
# For account name and custom field values we expect the whole thing to be a valid callsign;
# for display names we allow it to match anywhere.
account = acct['acct']
display_name = acct['display_name']
field_values = list(map(lambda f: f['value'], acct['fields']))
if CALLSIGN_REGEX.fullmatch(account):
callsigns.append(account.upper().replace('Ø', '0'))
continue
cs_in_field = False
for v in field_values:
if CALLSIGN_REGEX.fullmatch(v):
callsigns.append(v.upper().replace('Ø', '0'))
cs_in_field = True
continue
if cs_in_field:
continue
cs_in_display_name = re.findall(CALLSIGN_REGEX, display_name)
if len(cs_in_display_name) > 0:
callsigns.append(cs_in_display_name[0].upper().replace('Ø', '0'))
# Print output
print("\nCallsigns found in your fedi mutuals:")
print(','.join(sorted(callsigns)))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment