It’s been a minute

I fell off the blog-writing momentum once JXN Film Club’s Summer Movie Nights kicked into high gear. Balancing blog-writing and a film club and a full-time job is a lot of time!

I’ve still been writing my own bits of software, as always, so I figured I’d give some insight into what I’ve been working on.

michaellamb.dev on Discord

If you’re reading this, you’ve probably seen the Discord server widget in the About Me section. Clicking on it or visiting this link will get you into the official michaellamb.dev server. Come chat with me!

Discord4J

I had a pretty simple problem which motivated me to stand up a Discord bot: I run a Valheim dedicated server for myself and other friends on Discord but beyond attempting to connect to the server in-game there wasn’t an easy way to see the server’s status or if anyone was online.

Connecting the Steam Web API to Uptime Kuma, my status montioring app, let me track the uptime of the game server. The app supports the creation of multiple status pages, so I added valheim.michaellamb.dev and pinned a message with connection details.

Where the Discord bot comes in is updating its presence to a status message. Bots and members alike can have a presence status, indicating a current activity. The michaellamb.dev bot has a presence shown below.

michaellamb.dev bot, presence status: "Playing Valheim, 0/10 online"

Agent Logic

I won’t go into the how to set up a Discord bot using Discord4J, their docs can do that better than I can. Let’s just look at the agent logic used to update the presence.

    @Scheduled(fixedRate = 1, timeUnit = TimeUnit.MINUTES)
    @Override
    public void checkValheimPlayerCount() {
        try {
            SteamGameServerStatusResponse response = steamGameServerService.getValheimInstanceStatus();

            if (response.isNotEmpty()) {
                SteamGameServerStatusResponse.Server valheimServer = response.getResponse().getServers().get(0);
                playerCount.set(valheimServer.getPlayers());
                String activity = String.format("Valheim | %d/10 Online", playerCount.get());

                LOG.info("valheimServer [{}], playerCount [{}], activity [{}]", 
                valheimServer.toString(), 
                playerCount.toString(), 
                activity.toString());

                LOG.info("Updating ClientPresence with ClientActivity for Valheim");
                client.updatePresence(ClientPresence.online(ClientActivity.playing(activity)))
                .block();
            } else {
                LOG.info("Updating ClientPresence with ClientActivity for idling");
                client.updatePresence(ClientPresence.online(ClientActivity.streaming("status.michaellamb.dev", STATUS_URL)))
                .block();
            }
        } catch (Exception e) {
            LOG.error("An exception occurred", e);
        }
    }

The @Scheduled annotation tells Spring Boot to invoke this method on the given schedule, in this instance once per minute. Everything else is straightforward: we invoke steamGameServerService.getValheimInstanceStatus() and store it in response.

It may be helpful to know what Steam Web API sends back in a response here before moving on, so I’ve included the response that we should receive when the Valheim server is up.

{
    "response": {
        "servers": [{
            "addr": "173.235.136.46:2457",
            "gameport": 2456,
            "steamid": "90158276832560132",
            "name": "michaellamb",
            "appid": 892970,
            "gamedir": "valheim",
            "version": "1.0.0.0",
            "product": "valheim",
            "region": -1,
            "players": 0,
            "max_players": 64,
            "bots": 0,
            "map": "michaellamb",
            "secure": false,
            "dedicated": true,
            "os": "w",
            "gametype": "0.208.1"
        }]
    }
}

If the response comes back with an empty array of servers then we consider the game server to be down. If this happens, then bot presence is updated to an “idling” fallback status. Otherwise, the bot looks at response.servers.players and updates the total number of players in the tracking variable playerCount.

You may be wondering whether this agent/business logic is designed with other servers in mind. The service layer which handles the request/response to and from the Steam Web API specifically filters by appid, meaning that only Valheim game servers will be returned in this particular response.

This simple task felt like a good introduction to using Discord4J. It’s well-supported and is compatible with the Spring ecosystem.

Future

I use Discord webhooks with Uptime Kuma to get notifications when services go up or down, but I’d like to be able to fetch other stats from Uptime Kuma that are only accessible via the dashboard. The dashboard is LAN only so I’d have to use my self-hosted VPN to be able to look at it. Future work may include integrating slash commands to the bot which fetches this data and displays it.

Lately


About michaellamb.dev

Michael Lamb is a software engineer working at C Spire. If you have a blog-specific inquiry please create a new issue on GitHub. Feel free to fork this blog and build your own!

Get to know who I am in my first post Hello, World!

© Copyright 2021-2024
Using Discord4J in Spring Boot | Michael Lamb