So remember a long time ago (Tuesday), when I blogged about using the Bluesky API with BoxLang? As expected, I'm following that up today with a look at using the Mastodon APIs. Personally, I'm down to just two social networks, Bluesky and Mastodon. Originally I was using Mastodon a lot more, but I've been vibing with Bluesky more lately so I tend to check it more often. That being said, whenever I release a new blog post, I've got an automated process to post to both, so I thought I should cover both for BoxLang as well.
Even better... I already did this in ColdFusion! Way back in October 2023, I blogged about the topic and even shared a simple ColdFusion component for it. That made 'translating' to BoxLang even easier.
Auth
As a refresher, authentication with Mastodon is simpler. In your profile, you go into your developer settings and create a token. That's it. All you need along with that is the server your account runs on. So I specified too environment variables for this, MASTO_TOKEN and MASTO_SERVER. I verified it like so:
MASTO_TOKEN = server.system.environment?.MASTO_TOKEN ?: '';
MASTO_SERVER = server.system.environment?.MASTO_SERVER ?: '';
if(MASTO_TOKEN == "" || MASTO_SERVER == "") {
println('Ensure the Mastodon token env vars are set: MASTO_TOKEN and MASTO_SERVER');
abort;
}
Posting Messages
As I said, you don't need anything more than that token, so posting a toot (message) is as simple as:
toot = 'Hello World from BoxLang, #now()#';
bx:http url='https://#MASTO_SERVER#/api/v1/statuses' method='post' result='result' {
bx:httpparam type='header' name='Authorization' value='Bearer #MASTO_TOKEN#';
bx:httpparam type='formfield' name='status' value=toot;
}
Literally, that's it. You get a large post object back you can inspect if need be.
Using Images
How about images? Like Bluesky, they've got a different endpoint for that. Here's an example sending up a cat picture.
bx:http url='https://#MASTO_SERVER#/api/v2/media' method='post' result='result' {
bx:httpparam type='header' name='Authorization' value='Bearer #MASTO_TOKEN#';
bx:httpparam type='file' name='file' file=expandPath('./cat1.jpg');
}
mediaOb = result.filecontent.fromJSON();
This returns a media object where all you need is the id, and to add it to your post, it's one line of code:
toot = 'Hello World from BoxLang, #now()#, with an image.';
bx:http url='https://#MASTO_SERVER#/api/v1/statuses' method='post' result='result' {
bx:httpparam type='header' name='Authorization' value='Bearer #MASTO_TOKEN#';
bx:httpparam type='formfield' name='status' value=toot;
bx:httpparam type='formfield' name='media_ids[]' value=mediaOb.id;
}
Here's a complete script showing this in action:
MASTO_TOKEN = server.system.environment?.MASTO_TOKEN ?: '';
MASTO_SERVER = server.system.environment?.MASTO_SERVER ?: '';
if(MASTO_TOKEN == "" || MASTO_SERVER == "") {
println('Ensure the Mastodon token env vars are set: MASTO_TOKEN and MASTO_SERVER');
abort;
}
toot = 'Hello World from BoxLang, #now()#';
bx:http url='https://#MASTO_SERVER#/api/v1/statuses' method='post' result='result' {
bx:httpparam type='header' name='Authorization' value='Bearer #MASTO_TOKEN#';
bx:httpparam type='formfield' name='status' value=toot;
}
dump(result.fileContent);
// test with media
bx:http url='https://#MASTO_SERVER#/api/v2/media' method='post' result='result' {
bx:httpparam type='header' name='Authorization' value='Bearer #MASTO_TOKEN#';
bx:httpparam type='file' name='file' file=expandPath('./cat1.jpg');
}
mediaOb = result.filecontent.fromJSON();
toot = 'Hello World from BoxLang, #now()#, with an image.';
bx:http url='https://#MASTO_SERVER#/api/v1/statuses' method='post' result='result' {
bx:httpparam type='header' name='Authorization' value='Bearer #MASTO_TOKEN#';
bx:httpparam type='formfield' name='status' value=toot;
bx:httpparam type='formfield' name='media_ids[]' value=mediaOb.id;
}
dump(result.fileContent.fromJSON());
I turned this into a BoxLang class. It's a bit different from the Bluesky one in terms of API shape and I may address that at some point, but for now, here's that class:
class {
property name="token" type="string";
property name="server" type="string";
public function uploadMedia(required string path) {
checkAuth();
bx:http url='https://#variables.server#/api/v2/media' method='post' result='result' {
bx:httpparam type='header' name='Authorization' value='Bearer #variables.token#';
bx:httpparam type='file' name='file' file=path;
}
return result.fileContent.fromJSON();
}
public function post(required string toot, image="") {
checkAuth();
bx:http url='https://#variables.server#/api/v1/statuses' method='post' result='result' {
bx:httpparam type='header' name='Authorization' value='Bearer #variables.token#';
bx:httpparam type='formfield' name='status' value=toot;
if(image !== '') {
imageOb = uploadMedia(image);
bx:httpparam type='formfield' name='media_ids[]' value=imageOb.id;
}
}
return result.fileContent.fromJSON();
}
/*
* Utility function to ensure auth is set.
*/
private function checkAuth() {
if(variables.token == "" || variables.server == "") {
throw("Component initialized with blank, or missing, handle and password values.");
}
}
}
And an example using it:
masto = new mastodon(token=MASTO_TOKEN, server=MASTO_SERVER);
post = masto.post("Hello from the API, promise this is the last(ish) test.");
dump(post);
// now with an image test
post = masto.post("Honest, this should be the last test. Really.",expandPath('./cat1.jpg'));
dump(post);
println('done');
Here's my most recent test on one of my cooler bot accounts:
Fairly simple, right? If you take this along with the code from the previous post, you could easily automate posting to both in a few lines of code.
As before, you can find these samples in the BoxLang demos repo here: https://github.com/ortus-boxlang/bx-demos/tree/master/scripting Look for test_mastodon.bxs
, test_mastodon2.bxs
, and mastodon.bx
. Enjoy!