No. 0543 min read
huh. What is this string, exactly?
A timestamp, a cron line, an exit code, an invisible Unicode character.
No. 0032 min read
grime95.com posts daily fiction to a website, Facebook and Instagram. Posting was easy. Keeping a retry from posting the same damned thing again took some work.
You want a script that posts something once a day, forty lines should do it. Then you get to spend the next week figuring out what happens when those forty lines half-work.
I run grime95.com[1]Ref. 1grime95.com grime95.com, a serialized fiction site publishing one fictional small-town booking record every day at noon Eastern. Each goes to a static site (a GitHub Pages[2]Ref. 2GitHub Pages pages.github.com repo), a Facebook Page, and Instagram. Cron[3]Ref. 3Cron en.wikipedia.org does it. Twenty percent of that script took eighty percent of the thought, most of it about what could go wrong.
My billing mediation habit kicks in here, because I've spent a lot of time reconciling things and I want a ledger I can check. I used SQLite[4]Ref. 4SQLite sqlite.org, one row per (entry, channel). Status: pending | success | uncertain.
Three statuses, because sometimes I genuinely don't know what happened.
When a POST to Meta's Graph API[5]Ref. 5Meta's Graph API developers.facebook.com throws, you don't know whether the post exists. Maybe it completed and the response disappeared. Maybe the token expired and nothing happened. Instagram takes two calls, create a container and publish it, so it can half-happen. A leftover container expires on its own, but isn't easily distinguished from one that published.
POSTs get exactly one attempt. An exception marks the row uncertain, and the script stops touching that (entry, channel) pair. On the next run, GETs retry freely. Reading is idempotent, so I can keep looking without posting another copy.
Every channel adapter gets a checkpoint callback. The instant it has a remote ID, it calls that callback.
Verification can fail. The process can get killed. The ledger already knows a post with that ID exists, so the next run sees remote_id and doesn't repost. Instagram checkpoints even the container ID as container:<id> before publishing. Half-done counts as something I want written down.
About 480 lines of Python. Standard library plus Pillow[6]Ref. 6Pillow pillow.readthedocs.io for image checks. I wrote it in an afternoon and I'm proud of the thing. Getting the failure states right took the week after.
I don't want to babysit it every day at noon.
No. 0543 min read
A timestamp, a cron line, an exit code, an invisible Unicode character.
No. 0531 min read
A shell that can undo the last thing you did.
No. 0382 min read
Cron starts commands when the clock matches, folks.
No. 0231 min read
A fake county, real HTML, and two files called content.json.