Skip to content

Instantly share code, notes, and snippets.

@mlforcada
Created January 3, 2025 07:59
Show Gist options
  • Select an option

  • Save mlforcada/43429ea622f42fe1227274e2b122fbf6 to your computer and use it in GitHub Desktop.

Select an option

Save mlforcada/43429ea622f42fe1227274e2b122fbf6 to your computer and use it in GitHub Desktop.
Scrape callsigns of ham radio folks from your fedi mutuals (modified)
# 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
# Mods by Mikel Forcada (new callsign regular expression, and workaround as
# re.findall(CALLSIGN_REGEX, display_name) did not work well with parentheses in that
# regular expression)
import re
import requests
# 'string' needed to remove punctuation when splitting display_name into words
import string
USERNAME='ea5iyl'
INSTANCE='https://mastodon.radio'
# Callsign according to ITU https://www.itu.int/pub/R-REG-RR-2024, section III, article 19
CALLSIGN_REGEX=re.compile(r'([BFGKIMNRW]|[0-9Ø][A-Z]|[A-Z][0-9Ø]|[A-Z][A-Z])[0-9Ø][A-Z0-9Ø]{0,3}[A-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
translator = str.maketrans("", "", string.punctuation)
for word in display_name.split():
word = word.translate(translator).upper()
if CALLSIGN_REGEX.fullmatch(word):
callsigns.append(word.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