I made a small Fennel script to extract the text from a GoToSocial thread and
splat it into a text file. I have used it once and
it seemed to work, so I'm going to explain it a bit.
It needs lua-http,
rxi's json.lua, and
net-url aka net.url aka neturl,
all of which you can
currently get from MS Github, if they haven't already replaced the whole site
with an AI-mediated barrel of slop by the time you read this.
lua-http might be a bit overkill here but I've used before and I went
with what I know. I wrapped it here with a little send-request function
so I didn't have underscores all over the place.
The last time I really dug into HTTP was about 2003, so this HTTP/2
concept that the request method is actually a header field with a
colon in front of its name is ... unusual and new to me.
The url passed into this method comes from net-url and is actually a
table with a metatable that defines __tostring. This is like a
string when you print it, but not quite enough like a string when you
pass it to lua-http, hence the cast.
The next bit is
pretty much a straight translation of
the Gotosocial API login flow documentation from
bash + curl into Fennel.
One surprise here was that the net-url API is very side-effecty. If you write
(let [u (url.parse "HTTPS://example.com")]
(print (/ u "some" "path" "segments")))
It will print https://example.com/some/path/segments, as you
might expect from skimming the docs, but it will also change the value
of u, as you might not expect unless you'd actually read the docs
(guess who didn't and only skimmed them). u:resolve is a more
functional alternative.
(fn oauth-new-client [root-url client-name]
(json-request
(.. (root-url:resolve "/api/v1/apps"))
:POST
{ :content-type "application/json" }
{
"client_name" "fetch-thread"
"redirect_uris" "urn:ietf:wg:oauth:2.0:oob"
"scopes" "read"
}))
(fn oauth-access-token [root-url client_id client_secret code]
(let [body {
: client_id
: client_secret
:redirect_uri "urn:ietf:wg:oauth:2.0:oob"
: code
:grant_type "authorization_code"
}]
(json-request
(.. (root-url:resolve "/oauth/token"))
:POST
{ :content-type "application/json" }
body)))
(fn request-api-token [root-url]
(let [{: client_id : client_secret } (oauth-new-client root-url "unroll.fnl")
redirect-url (..
root-url
"/oauth/authorize?client_id=" client_id
"&redirect_uri=urn:ietf:wg:oauth:2.0:oob&response_type=code&scope=read")]
(io.stdout:write "login to the instance and then paste in the token here\n> ")
;; hardcoded firefox because `xdg-open` doesn't work for
;; me right now. That's something to fix another day.
(os.execute (string.format "firefox %q" redirect-url))
(let [code (io.stdin:read)
{: access_token}
(oauth-access-token root-url client_id client_secret code)]
access_token)))
The net result of all that code will return an access token.
The access token can
be used with the other API calls
but not, as I originally thought it would, for fetching actual statuses
like /users/dan/statuses/01K2Q3MHSGMHB2CFTRCE6FBP5T.
That needs HTTP signatures (a.k.a "authorized fetch") which is something quite else and looks more involved than I wanted to get into at 12:30am.
However, we can fetch the status content in a different format using an API endpoint:
/api/v1/statuses/01K2Q3MHSGMHB2CFTRCE6FBP5T gets the post itself and ..../context gets its ancestor and descendant statuses.
I said in 2020 that I no longer have a
favourite programming language, but maybe five years later I do
again and it's Fennel? It would be nice if I could have LuaJIT and
Lua 5.4 at the same time but ... there's always something we can't have
It's quite hacky: there is no error checking. If it doesn't work,
add printf debugging.
It's quite hacky: it has no tests. I usually prefer to code test-first, but this
isn't. That's because it's 90% made of API client glue and 10% trivial,
which is the kind of scenario where I've never had value from
test harness infra that I couldn't get by running it.
It's quite hacky: we don't check if the access token has expired. If the
access token expires, delete ~/.rethread and run the script again.
Writing this blog entry has provided an opportunity to learn that
the bare url markup in Cripslock is buggy, so yay for finding that
out, I guess.
I would use lua-http and rxi/json again, but net-url is not (yet?)
the perfect beautiful gem of programming I could wish for. I'd like
something a bit closer to (dare I say it) Ruby, for example, where
the URL objects are value objects instead of being mutated in place.
All of that is a very long-winded way of saying "expect to see more
blog posts that are really just recycled fediverse threads", but I'm
now on my fourth fediverse server and I won't put up again with saying
goodbye to all my posts every time I change to a new instance. So,
I am blogging for persistence (and some day, less shitty search).
I've ridden this route three times. The first time I started out
following a Kurviger round trip route and then I was having so much
fun on the B184 that I refused to take the left turn that Kurviger
wanted me to take[*], and instead I followed signposts for a
while. When I got home I spent some time with a map and an open tab on
Google Streetview and figured I had approximately ridden Abridge -
Stanford Rivers - Chipping Ongar - Fyfield - some Rodings - Hatfield
Heath - Matching Green - ("Watery Lane, narrow road with gravel in the
middle) - Moreton - Bovinger - Tyler's Green - North Weald - Epping
and then home, and and almost all of it - all the bits that weren't
Epping rush rour - I would have gladly ridden again. A lot looked
vaguely familiar (although backwards) from riding the Dun Run, albeit
it didn't look that similar because daylight.
After the second time around - a bit faster, because I had no wrong
turns or need of pulling over to look at the map - I looked at the
Hundred Parishes website and found I'd passed through at least one of
the place on their list, so I decided to ride the whole thing a third
time and inaugurate the 100-parishes topic.
This is the green in Matching Green. It has
lots of grass
trees
a pond: very little water currently, no ducks that I saw (I may have missed them)
a cricket pitch and pavilion
The green is overlooked by thatched cottages and a pub. There are traditional stripey signposts in the area
Photos are of the cricket pavilion, if that's the right word, and the weathervane on the roof, depicting a man with a scythe and what appears to be (but probably isn't) a walking frame.
My motorcycle is not thematically appropriate to the "green and rural" theme in this thread but I am going to include this photo anyway (1) to show I was there and didn't lift the pictures from the internet; (2) because it is IMO a very pretty motorbike.
In 2001, "auxillary power" was not a concern of the manufacturers of
sportsbikes - even otherwise practical sportsbikes like the CBR600F.
It doesn't come with any handy USB or 12V sockets or even spare
fuseholders under the seat.
In 2025 I've resisted bedecking it with a million current draws, but I
do need power for the USB connector that my phone
is connected to, and for the wired-in dashcams, and I thought I'd try
and be clever.
The idea was a good one. As eny fule kno, a car/bike battery puts out
around 12V and an alternator provides 14V, so if you have accessories
that you want to work only when the engine's running and you're not at
risk of draining the motorbike battery, you could install a
voltage-sensitive relay so that they're only powered when the supply
voltage exceeds, say, 13V. Therefore:
an Aliexpress clone of the Oxford accessory fuse box, this has 4 5A
fused connections through relays, and turns them on when it sees a
voltage on the thin red wire that you're supposed to wire to a
switched live
but instead of finding a relatively exposed accessory 12v supply
somewhere in the loom and tapping it, I connected the thin red wire
to a 20A voltage-sensitive relay intended for charging caravan
batteries only when the car alternator is running.
So, my accessories get power when the supply voltage exceeds 13V, and cut out again when it drops to 12.2. Which seems on the low side, but there's a little pot in the VSR to adjust it.
Basically I'd made a Healtech Thunderbox clone but half the price and
four times the ugliness.
After a few rides I concluded that it doesn't actually work very well
though, because of the finickiness of the threshold. You see, both
those voltages quoted above are nominal. A fully charged battery
could be pumping out as much as 12.8V, and - on my bike at least -
when the engine is at idle speed and the headlights are on and the
radiator fan is running, the alternator voltage is not much more (or
possibly even less). So, my satnav device, which is quite an elderly
Android phone with not much life left in its own battery, wasn't
getting power at low speeds. And whhen i got home after a ride and
took the seat cover off to take stuff out of storage, I could see the
LEDs merrily glowing away.
So, tl;dr I took it came out again. The fusebox is still there, but I
took the voltage-sensitive magic out and now it just connects to a
wire spliced into the taillight Result: now my USB cable turns on when
the ignition switch and the lights are on[*], not when the voltage
rises above a notional 13V.
You might be able to see in the picture there are three inline fuse
holders. Yes, I actually have more things that need an always-on power
connection (dashcam, optimate, and the accessory fuse box itself) than
I have accessories.
Someone should make a secondary fuse box that has both switched and
unswitched connections.
[*] my bike is old enough to actually have a switch to turn the
headlight off. I pretty much never use it, though, unless it's been
standing for a long time and I'm worried it won't start with the added
power drain from the headlights.
I rewrote my blog engine (again): welcome, Cripslock#
'In my experience Miss Cripslock tends to write down exactly what
one says,' Vetinari observed. 'It's a terrible thing when
journalists do that. It spoils the fun. One feels instinctively that
it's cheating, somehow'
In preparation for rearranging and pouring in a bunch of fediverse
threads from an archive of the recently defunct Pleroma server at
brvt.telent.net, I have rewritten (again) the software that shows you
this blog.
it was that or figure out how to rebuild and reverse-engineer the
ten-year old Clojure program it replaces. New features are the
topic/keyword/tag thing you see in the sidebar on the right, and a new
syntax for making links between one page and another: my intention is
that it's going to get more "timeless" posts (mostly, for the
moment, about motorcycles), and so I need better ways to expose that
stuff. Something a bit like a Bliki.
(A planned new feature is site search, because Google isn't what it
once was)
Visually I carried forward all the CSS from the old site so you
probably won't notice much difference there. The service itself is
written in Fennel because well mostly
because why not?. I had to write a Textile parser again, using an
ugly combination of Lua patterns and Lpeg, but Markdown is provided by
lcmark.
Points of note:
my mental model of Textile is divergent in several ways from the
Textile spec's model of Textile, and as the rôle of Textile in
this software is to display posts that I wrote, my interpretation
is deemed locally correct. This is another reason the Textile
processing is home-grown and I didn't just, say, use Pandoc to turn
it into Markdown.
cqueues is fun. Cripslock has an HTTP server and also an inotify
thing so it can refresh when posts are added/changed, and it's quite
slick to be able to put them in the same event loop. (The side project that I temporarily put
down to write Cripslock (which is itself a side project from
Liminix which is my hobby when I am not at $WORK)
mashes up cqueues and
glib
in an inelegant way, so it was neat to see how it should work)
the initial topic tagging was done with grep and sed and is
probably quite low-quality
in the ~ 25 years since I started writing this blog (ever since the
first post insisting that it was in fact
not a blog - hindsight is
wonderful, no?) I have never specified licence terms except to add a
copyright notice. So, technically, I suppose, that would mean no
copying and arguably no copying would also mean no reading - because
it's in the nature of transmitting it across the internet that
copies end up being made at both ends and quite likely also in the
middle. I intend to rectify this, and also, if I can find some
well-written canned licences that meet my needs, to make it explicit
that I am not permitting its use for training LLMs.
Z is for Zoo and we have reached the end of the alphabet. For the first time in a while, able to get a photo which depicts both the bike and the place I rode to
Ride out was fairly relaxed. Couldn't figure why it took me into some random housing estate and then said "do a u turn" until I realised that was where I'd placed the shaping point on the route in Kurviger.
Didnt do a u-turn there but did do one later. Still proud that I can.
Torrential rain on the way home was a fun experience - and with new tyres. I haven't thought for several months about getting a Pinlock insert but I could have done with one today
Mildly concerned, when I got home and was revving/slipping the clutch to get the bike up the kerb, to get what looked like a cloud of smoke and a burning smell from the front end. I am hoping it was steam and the smell was oily water evaporating off the exhaust headers - apparently this is is not uncommon. I can't see anything that looks melted, anyway.
When I got home I let the bike stand for a while and then wiped most of the water off it with a cloth, then inside for kids' bedtime. Just as I was about to go out and put the cover back on, the heavens opened again. No point covering a wet bike :-(
So the next day I washed it and replaced all the windscreen screws with new stainless screws. The rain had already rinsed away most of the dead insects that had been stuck to the front ...
There's no sign of anything melted. Out of consideration to my neighbours on a Sunday morning (on which I'd already mowed the lawn :-) I didn't start it to see whether smoke billows out or not. Fingers crossed