Revisiting Lys, a Eurovision-flavored social media bot
I launched Lys back in late 2019 as a Twitter bot to post reminders about Eurovision national selection TV shows from all over Europe. Five years later, it weathered a Twitter (now X) API mess, went through a well needed refactoring, got a brand-new management app, and grew in scope to offer its services on 2 additional platforms and a very simple web page - all while still costing absolutely nothing in hosting. Let’s revisit the thing, and discuss the various tips and tricks in use to keep it running aux frais de la princesse Bezos.
Where the grass is greener… and the sky bluer (molto più blu)
With Twitter losing its grace in favor of spawn-off Bluesky and, a few months later, totally-not-trying-to-replace-failing-X-formerly-Twitter Threads, a platform hastily put together by Meta to fill what was more and more shaping to become a void in the World Wide Web, it became relevant, and a fun challenge, to deploy Lys to more platforms. Threads, with its (now fulfilled) ambition to open up to the Fediverse, was particularly interesting to the goal of making the bot more available to a fleeing Eurovision Twitter community - sadly, it took a whole year of waiting for the team at Meta to finally open up a basic API.
I started with Bluesky, not only as I had gotten an invite code from a uni friend in the summer of 2023, but also because it was the only platform to 1) be actually accessible to us UE peasants (*) and 2) come with an API.
(*) I posted this banger of a… thread before Meta aggressively cut off access to the backend from the EU and COME ON how did it get so few likes??
View on Threads
But I digress - Bluesky, was I saying. While Lys was very much a live project, the code base itself was still a hot mess of a proof of concept that I refused to refactor. So, naturally, adding Bluesky was not only a pain, but also extraordinarily ugly, and it was promising to be very annoying to maintain or expand to another platforms.
While on topic, a quick word of appreciation for the customization options offered by the Bluesky API - I particularly appreciate the ability to turn any part of your posts into a link (using facets) and choose to embed or not a URL card. More of that, API makers, please!
{
"$type": "app.bsky.feed.post",
"text": "TONIGHT | \ud83c\uddf8\ud83c\uddea SWEDEN\n---------\n\ud83d\udcfc Melodifestivalen\n\ud83c\udfc6 Final\n\ud83d\udd53 20:00 CET\n---------\n\ud83d\udcfa svtplay.se OR svtplay.se (English commentary).",
"createdAt": "2025-03-08T15:00:00.000000Z",
"langs": [
"en-US"
],
"facets": [
{
"index": {
"byteStart": 99,
"byteEnd": 109
},
"features": [
{
"$type": "app.bsky.richtext.facet#link",
"uri": "https://www.svtplay.se/video/eEqyADd/melodifestivalen/final"
}
]
},
{
"index": {
"byteStart": 113,
"byteEnd": 123
},
"features": [
{
"$type": "app.bsky.richtext.facet#link",
"uri": "https://www.svtplay.se/video/j16Gnw7/melodifestivalen-the-final/melodifestivalen-2025-the-final"
}
]
}
],
"embed": {
"$type": "app.bsky.embed.external",
"external": {
"uri": "https://www.svtplay.se/video/eEqyADd/melodifestivalen/final",
"title": "Melodifestivalen \u2013 Final",
"description": "Direkts\u00e4ndning fr\u00e5n Stockholm. I kv\u00e4ll avg\u00f6rs Melodifestivalen 2025. Tolv bidrag t\u00e4vlar om vinsten och att f\u00e5 representera Sverige i Eurovision Song Contest i Basel, Schweiz, i maj. Programledare: Kristina “Keyyo” Petrushina och Edvin T\u00f6rnblom."
}
}
}
TONIGHT | 🇸🇪 SWEDEN --------- 📼 Melodifestivalen 🏆 Final 🕓 20:00 CET --------- 📺 svtplay.se OR svtplay.se (English commentary).
— Lys ( @eurovisionlys.bsky.social) March 8, 2025 at 4:00 PM
[image or embed]
🧵 Knot a Twitter clone
Threads eventually got an API, almost a whole year after launch. Aside from its little unique quirks (longer publication time due to the decentralized nature of the platform, and long-lived tokens that need to be refreshed every couple of months), it’s your average social media API - no real challenge here.
What ended up taking most of my summer vacation was the inevitable refactoring of the Lys code base. The plan was to move from a pile of if/else and duplicated code to a modular and extensible project that would support platform-specific features (e.g. Bluesky’s facets) and future additions (with Threads being the first, but, seeing how fast the web moves nowadays, maybe not the last).
To keep it short, the Lys entrypoint is now a publisher, that is selected based on the input target (Bluesky, Threads, or Twitter) and run mode (daily, weekly, or 5-minute reminder). Each publisher is built from a generator, that generates a thread of social media posts, a client, that encapsulates the social media API logic, and, optionally, a formatter, that enriches the posts before publishing.
Now, if I want to support a new platform, I only need to create a corresponding API client and build the relevant publishers. Plus, I finally have code I’m not ashamed to push to GitHub.
With the same Lys processes now running 3 times a day, the read operations on my DynamoDB table spiked and got dangerously close to the free tier limits, and a long overdue, second refactoring was needed, to extract the database querying out of the Lys publishing process so that it’s only performed once.
An API-less web calendar
OK this one is on paper dull as dirt but it’s actually a cool one, because it’s one of these instances where you have to think about your use case first, and then start coding.
Eventually, I realized that it’s a shame to sit on all of this data (I basically have an exhaustive calendar of Eurovision pre-selections!) and only expose it through scheduled social media posts. Why not put together a nice little web calendar?
The issue is - the data is locked away in an AWS DynamoDB table. To expose it to the internet, I would have to make it flow through AWS’ API Gateway, which sadly doesn’t include any long term free tier offering, and comes with a few extra considerations proper to public interfaces (authentication & authorization, volume, etc.).
I went back to my use case: I want my data to be displayed as a calendar on a web page. This data doesn’t change very often (at worst, a few times a day, to add a last minute link for example), so I could do with a periodic extract (say, daily), even if that means I have to manually trigger a refresh every now and then.
An overview of the write volume to my “events” DynamoDB table during the peak of the Eurovision selection season ( January to mid March). Updates are largely concentrated in the morning (when I browse overnight date suggestions to review) or in the evening (when I find a link for an upcoming show).
OMG THE FIK FINAL STREAM LINK DROPPED EARLY!!
I settled for an automated daily process that “dumps” the DynamoDB table to a JSON array, and pushes it to a GitHub repository, essentially using GitHub raw as a public API. This process is handled by a separate Lambda that runs everyday early in the morning - whatever change I make to the calendar will be reflected the next day, and if I really need to push an update, I can simply re-run the lambda.
All that was left was writing some HTML, plugging a Javascript GET onto the raw URL, wrapping the whole shebang in a GitHub Pages website and voilà!
The nerdy .github.io URL is well worth the monthly savings
Rebuilding the management app
I didn’t care much about keeping the code base of the Android app that I built up to date and I, of course, lived to regret it. But in my misfortune, I saw an opportunity: I was soon to be jobless, and I thought I would fare better on the market in my upcoming job search with a few extra skills under my belt - so I took to write a brand-new management app for Lys, with the aim to use a modern web framework.
I settled for Next.js, and with the help of Tailwind CSS for display, SWR for aggressive caching, the AWS Javascript SDK, and Vercel for free hosting, I built a 100% responsive webapp that I can access anywhere, does what the old app did and more, and doesn’t send a million and a half requests to DynamoDB.
What’s next?
I do have a few ideas for Lys but nothing concrete yet. As I mentioned in my last blog post, Lys is a never-ending project, so who knows what I’ll have to say in the next one. I do hope the future holds less fascist billionaires resetting my API accesses to their newly acquired toy though.
Until then, you can read the previous blog post I wrote about Lys, or check out the Lys section on my portfolio to get an overview of the project.