Press "Enter" to skip to content

Category: Technology

Command Line!

awk '/in_reply_to_screen_name/ { print $3 }' tweets.json | sed 's/[","]//g' | sort | uniq -c | sort | grep -v BryantD | tail -20
  56 graphxgrrl
  57 patrickoduffy
  58 jessnevins
  59 othergretchen
  60 GlobeChadFinn
  61 ryantomorrow
  65 smakofsky
  66 seclectech
  69 multiplexer
  73 gentlyepigrams
  75 rmd1023
  76 mgrasso
  79 JimHenleyMusic
  79 Wolf_six
  89 carlrigney
 102 _r_o_n_e_
 107 rone_____1
 129 emilytheslayer
 306 rdonoghue
 341 ce_murphy

Physical Game Jam Tracker

I made a thing! I have been on a minor roll with python recently and this seemed like a fun project so I started working on it. Towards the end I reached out to the awesome person who inspired me, since she didn’t seem to have been keeping her tracker up to date, and she said I should go ahead and launch mine. So here we are.

I used this as an excuse to try out new technology and libraries. Click and Cloup made the list; the first because I wanted to try out new argument parsers and the second because I needed option groups. This forced me to learn to use setuptools better, which was a win. I am gonna keep using this tech going forward.

I also wound up sticking Rich in there for better CLI output and it’s kinda great, so that’ll stay in my toolbox too.

Dataset turned out to be too limited, because it’s really just for columnar data in a single table and it turns out itch.io jams have one little thing which break that paradigm; namely, multiple owners per jam. So now I’m using sqlite with JSON support and honestly it’s a bit grungy. Maybe next time I’ll learn SQLAlchemy for real.

My python has gotten significantly better over the last year with this kind of small but enjoyable work, and I am gonna keep doing it.

Seattle Movie Calendar

Before the pandemic, I’d been thinking about writing a little aggregator to pull movie times at my favorite local indie theaters into a calendar. I’m bad at remembering to see that cool showing a month from now but if I had a calendar that would theoretically help.

Obviously I didn’t need it for the last couple of years but the silver lining is that I got better at Python. I spent some time coding over the last week of my sabbatical and voila: the Seattle Arthouse Movie Calendar.

The code is here. I was sort of fiddling around with making it a real library but decided not to chew off too much at once. It was enough fun learning how to use classes to make it super-easy to add a new theater.

Lots of potential improvements. I am probably going to generate separate calendars for each theater next, for convenience. I’d also like to render maybe three days worth of calendar on the Web page. I feel like doing more than that risks pulling traffic away from the official theater sites, which I’d prefer to avoid, but three days seems reasonable.

I’m going to write up some notes on using the code for other cities too. If you’re decent with Python you could probably figure it out from reading what’s there but documentation is a good practice anyhow.

Jupyter Notebooks, GitHub, & Secrets

This week I needed to do some analysis of JIRA tickets that goes beyond the reporting JIRA provides — not entirely an uncommon task. My usual quickie toolkit for that purpose involves Jupyter notebooks, which I prefer over downloading CSVs and playing with spreadsheets because I can automate the notebooks given a JIRA API key.

In this case, though, I really want one of my PMs to be able to run these reports, and I don’t want to get into the whole “OK then type this at the command line” thing. The post title kind of gives this away, but after some thought I realized, hey, just check the notebook into the company’s GitHub and there we go.

But how about that API key? Obviously I don’t want to embed mine in the notebook. Is there some way to use GitHub secrets for this? Answer: yes, there is, and it’s really simple, but I don’t see it documented step by step anywhere else so I’m gonna do that here.

If you want the quick answer: GitHub makes secrets available as environment variables, and if you’re working in the GitHub Jupyter environment, you don’t need to do anything special with workflows to make that happen. Therefore, you can just use Python’s os.environ mapping object to get at secrets.

FoundryVTT on Fly.io

FoundryVTT is a high quality virtual online tabletop platform. Unlike Roll20, however, there’s not a central server — once you buy a license, you have to run it someplace. There are a few services that will do this for you at a reasonable price, but I’m a geek, so if I start using FoundryVTT I want to host it myself.

Fly.io is a very cool new application hosting cloud. I experimented with it a month ago for hosting an NJPWWorld RSS feed generator and it was awesomely simple. They support persistent disk, so I couldn’t see any reason why it wouldn’t work for FoundryVTT. And it did! Details after the cut.

Simplified Unsplash Widget

My old boss Steve Makosfky was singing the praises of Scriptable the other day — it’s an iOS app that allows you to run Javascript in a number of ways, including as a widget. It’s also free (but tip the developer if you like and use it). So I modified an existing script that displays random photos from Flickr to replace my clunky Unsplash photo widget.

It’s way better than my previous solution. It doesn’t clog up my photo library, plus it’s mildly configurable. (Could be more so, but I’ll leave that as an exercise to the reader.) I’ve set mine up such that it only pulls down nature photos, which means I’m no longer seeing people I don’t know on my iOS home screen.

I stuck my code in a gist in case anyone finds this useful.

Weird Instagram Bot Traces

So far this month I’ve received a couple hundred email messages from Instagram notifying me that their Terms of Use have been updated. They’re legitimate emails; it looks like someone signed up hundreds of Instagram accounts using randomized innocence.com email addresses. Since I moved my mail to Fastmail, I’m now seeing them all. I poked around a couple of the accounts (hi, kurt.clemons78446!) and the ones I spot checked have all been deleted.

I imagine someone used innocence.com as a domain for non-existent email addresses, and these either date back to before Instagram added a confirmation step (and were later removed for spamming) or they just never confirmed the accounts. You’d think that deleted accounts would be removed from whatever list of email addresses Facebook is using to generate these emails. Unconfirmed accounts… I guess those should still see the Terms of Use changes.

Good times. Finally added a filtering rule for the emails, anyhow.

Auto-Pause for Zoom

I don’t like wearing headphones all day and since I’m lucky enough to have a spare room for an office, I can play music through my Bluetooth speaker. However, I’m lazy, and I don’t want to fiddle around with my music player just cause I’m starting a Zoom meeting. Thus, automation.

Zoom provides callbacks when meetings start, but that’s aimed at people writing plugin modules. OK, we can go a bit lower level. I can’t just watch for a process, cause Zoom is always running on my laptop. But I can watch for open UDP sockets!

$ lsof -i 4UDP | grep zoom
zoom.us 88028 bryantd 83u IPv4 0xa90f1ce03eca5d5 0t0 UDP xx.xx.xx.xx:57275
zoom.us 88028 bryantd 84u IPv4 0xa90f1ce03eca2ed 0t0 UDP xx.xx.xx.xx:52612
zoom.us 88028 bryantd 90u IPv4 0xa90f1ce09340175 0t0 UDP *:65048
zoom.us 88028 bryantd 91u IPv4 0xa90f1ce0939ce8d 0t0 UDP *:60088
zoom.us 88028 bryantd 95u IPv4 0xa90f1ce0939c8bd 0t0 UDP *:50594

That’ll do, pig. That’ll do.

#!/bin/bash

trap ctrl_c INT

function ctrl_c() {
        stop_music
        exit
}

function stop_music() {
        /usr/bin/osascript -e 'tell application "Music" to pause'
}

while true; do
        zoom_status=$( /usr/sbin/lsof -i UDP | /usr/bin/grep zoom | wc -l )

        if (( $zoom_status > 0 )); then
                stop_music
        fi

        sleep 30
done

I’m catching interrupts because I want a quick keyboard method to stop the music just in case. Right now I don’t think I want it to restart music when I leave a Zoom call, but easy enough to add that if I do.