๐Ÿ’พ Archived View for m0yng.uk โ€บ 2022 โ€บ 05 โ€บ mastoWelcomer captured on 2022-06-11 at 20:37:15. Gemini links have been rewritten to link to archived content

View Raw

More Information

โฌ…๏ธ Previous capture (2022-06-03)

โžก๏ธ Next capture (2022-07-16)

-=-=-=-=-=-=-

mastoWelcomer - M0YNG.uk

Created 2022-05-22

Tagged

Page Content

[TOC]

Background

I run mastodon.radio, and I have it set to require approval for new accounts. I also like to welcome every new user.

This can result in delays, as I need to do a complex dance, something like:

I wrote a snippet of JavaScript to run in the dev console to copy out the selected usernames, which helped but for security reasons you can't put stuff on the clipboard without user interaction so I still had to manually select and copy the string... the entire process is a faff.

Well, mastodon has an API, and there exists Mastodon.py

Guess what comes next?

That's right! SCOPE CREEP!

Proof of concept

I wrote a small python script to fetch a list of pending accounts, show me the info, and let me pick which ones to approve. It then does that, and follows each account, and toots a welcome message.

It worked well!

Script output listing 3 pending accounts, details redacted [IMG]

But ...

Current version

So, I extended it a bit.

Now it also:

Script output listing 3 pending accounts, with the above improvements [IMG]

The Code

It's probably not much use to anyone else, but if you remove the callsign and QRZ lookups it might be a good base for your own?

from datetime import datetime, timezone
from mastodon import Mastodon
from random import choice
from qrz import QRZ
import humanize
import pprint
import re

callsignRegex = '[a-zA-Z0-9]{1,3}[0-9][a-zA-Z0-9]{0,3}[a-zA-Z]'
ruleCanary = 'hippo cornflakes'

qrz = QRZ(cfg='./settings.cfg')

pp = pprint.PrettyPrinter(indent=4)

''' create app - do this once
Mastodon.create_app(
     'mastoWelcome',
     scopes=['admin:read:accounts', 'admin:write:accounts', 'write:statuses', 'write:follows'],
     api_base_url = 'https://mastodon.radio',
     to_file = 'mastoWelcome_clientcred.secret'
)
'''

''' authorize - do this once
mastodon = Mastodon(
    client_id = 'mastoWelcome_clientcred.secret',
    api_base_url = 'https://mastodon.radio'
)
'''

''' log in - do this as often as needed
mastodon.log_in(
    'username',
    'password',
    scopes=['admin:read:accounts', 'admin:write:accounts', 'write:statuses', 'write:follows'],
    to_file = 'mastoWelcome_usercred.secret'
)
'''

# connect with saved credentials
mastodon = Mastodon(
    access_token = 'mastoWelcome_usercred.secret',
    api_base_url = 'https://mastodon.radio'
)

pendingAccounts = mastodon.admin_accounts(status='pending')

print(str(len(pendingAccounts)) + ' Pending accounts')
i = 0
for pAccount in pendingAccounts:
    # easy to type ID
    print(f'\n{i}')
    # attempt to find callsigns
    allPossibleCallsigns = re.findall(callsignRegex, pAccount['username'])
    allPossibleCallsigns.extend(re.findall(callsignRegex, pAccount['email']))
    allPossibleCallsigns.extend(re.findall(callsignRegex, pAccount['invite_request']))
    # tell me about the account
    print(f"Username: {pAccount['username']}")
    print(f'{humanize.naturaltime(datetime.now(timezone.utc) - pAccount["created_at"])} {pAccount["email"]}')
    print(f'{pAccount["invite_request"]}')
    if ruleCanary in pAccount["invite_request"]:
        print(f'\033[93mRule Canary: {ruleCanary in pAccount["invite_request"]}\033[0m')
    else:
        print(f'\033[95mRule Canary: {ruleCanary in pAccount["invite_request"]}\033[0m')
    if pAccount["confirmed"]:
        print(f'\033[93mEmail confirmed: {pAccount["confirmed"]}\033[0m')
    else:
        print(f'\033[95mEmail confirmed: {pAccount["confirmed"]}\033[0m')
    # dedupe list
    possibleCallsigns = []
    for call in allPossibleCallsigns:
        lowerCall = call.casefold()
        if lowerCall not in possibleCallsigns:
            possibleCallsigns.append(lowerCall)
    print(f'Possible Callsigns: {possibleCallsigns}')
    # get info from QRZ
    print('QRZ info')
    for call in possibleCallsigns:
        result = qrz.callsign(call)
        pp.pprint(result)
    i+=1

# ask me what accounts look OK
approveWhat = input("Which accounts to approve?: ")
# split the space separated list
approveWhat = approveWhat.split()

print('\nYou want to approve these account:')
for aAccount in approveWhat:
    print(pendingAccounts[int(aAccount)]['username'])

# make a case sensitive YES to make sure I'm paying attention
confirmYES = ''.join(map(choice, zip('yes', 'YES')))
confirm = input(f"type '{confirmYES}' to confirm: ")

# store the usernames in "@ + name" format
userNames = ''

if confirm == confirmYES:
    print('Approving the accounts...')
    for aAccount in approveWhat:
        updatedAccount = mastodon.admin_account_approve(pendingAccounts[int(aAccount)]['id'])
        print(f'Approved {updatedAccount["username"]}!')
        mastodon.account_follow(pendingAccounts[int(aAccount)]['id'])
        print('Followed them!')
        userNames += f'@{updatedAccount["username"]}\n'
        # YOU'LL WANT TO CHANGE THIS MESSAGE (I assume)
        mastodon.toot(f'Hello @{updatedAccount["username"]}\nPlease toot an introduction (using # introductions) so we can get to know you :alex_grin: add some hashtags too so we know what interests you\n\nYou can find more people on our server using the local timeline & directory\nhttps://mastodon.radio/web/directory\n\nThis list of radio amateurs on mastodon, may be of interest: https://pad.dc7ia.eu/p/radio_amateurs_on_the_fediverse\n\nI recommend the third-party apps\nhttps://joinmastodon.org/apps\nRemember to describe your images (ask for help!) + fill out your bio before following people', visibility='unlisted')
        print('Tooting to welcome them!')
    templateToot = f'Everyone, please welcome\n{userNames}\n\n#introductions'
    print('Tooting bulk welcome message...')
    mastodon.toot(templateToot)
else:
    print('\nOk, DOING NOTHING.')

Rules Canary?

mastodon.radio isn't a big corporate server, we have rules that we enforce. Some are pretty common (don't be a dick) some are important for the community (describe your images) and I see people not describing their images. Which makes me think they haven't read the rules. So I wondered how I could check up on this.

Maybe you've heard of a warrant canary[1]? The idea being a message says that "The FBI has not been here" and is removed if they have been, even if an organisation is forbidden from saying, for example, that the FBI HAS been there.

1: https://en.wikipedia.org/wiki/Warrant_canary

Based on this idea I added "rule 7" to mastodon.radio, it reads

When apply for an account, if you've actually read these rules, please end your "Why do you want to join?" with the phrase "hippo cornflakes"

Very few requests mention cornflakes, or hippos. But that's not really the point, I'm not denying entry to people who don't. Yet. But it is interesting to see, from the administrator's view, who actually read the rules AND bothered to follow them.

-+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+-

๐Ÿ–ค Black Lives Matter

๐Ÿ’™๐Ÿค๐Ÿ’œ Trans Rights are Human Rights

โค๏ธ๐Ÿงก๐Ÿ’›๐Ÿ’š๐Ÿ’™๐Ÿ’œ Love is Love

Copyright ยฉ 2022 Christopher M0YNG

Code snippets are licenced under the Hippocratic License 3.0 (or later.)

Page generated 2022-05-22 by Complex 19