Ian Boraks
Back to Projects

Goggle Buzz

personal2023Source Code
Goggle Buzz logo

Goggle Buzz is a Discord bot built for the Rotor Jackets community: it keeps the server organized, adds a few fun utilities, and—most distinctively—mirrors Velocidrone track activity so pilots can see what people are flying right now.

What and why?

The bot started from a simple observation: competitive sim flying generates a constant stream of interesting “state” (tracks, times, leaderboards) that’s trapped outside Discord unless someone manually posts it.

Velocidrone’s API makes that state accessible. Goggle Buzz polls it, filters it to people you care about (server members), and pushes updates into the right channels so the community doesn’t have to babysit feeds.

How it works

Python + discord.py on the Discord side. The bot runs on a Raspberry Pi 4 as a systemd service, and it currently serves Rotor Jackets and the VT Drone Racing Team server.

Code structure

The project is organized using discord.py cogs: each feature domain is isolated, and main.py loads them at startup.

Cogs overview

CogDescription
/fun/Contains all the fun commands that the bot can do.
/leaderboard/Contains all of the commands that implement a server wide message leaderboard to track the most active users.
/util/Contains all of the utility commands that the bot can do.
/velocidrone/Contains all of the commands that interact with the Velocidrone API.

Each cog folder typically contains:

  • <name>.py — slash commands / listeners registered by the cog
  • <name>_helper.py — shared logic used by those commands
  • json/ — configuration and cached data used at runtime

Cog details

/velocidrone/

This cog talks to Velocidrone’s APIs, maintains a prioritized track list, diffs leaderboards against cached state, and posts embeds when whitelisted pilots improve times.

The heavy lifting is track_update() plus a background loop that keeps leaderboards fresh without hammering APIs or spamming channels.

track_update() ```python async def track_update() -> dict: """Updates the leaderboard for all tracks and returns a dictionary of the differences between the old and new leaderboards
Returns:
    dict: A dictionary of the differences between the old and new leaderboards
"""

track_diff = {}
track_ids = generate_prioritized_track_list()
new_low_priority_track_ids = []

for track_id in track_ids:
    if track_id in new_low_priority_track_ids:
        print(f"Skipping track {track_id} because it was deprioritized")
        continue

    await asyncio.sleep(10)

    saved_leaderboard = get_track(track_id)
    current_leaderboard = get_leaderboard(None, get_JSON_url(track_id))

    if saved_leaderboard[1] != current_leaderboard[1]:
        save_track(current_leaderboard, track_id)
        track_diff[track_id] = {}

        add_track_to_high_priority(track_id)
    else:
        if str(track_id) in config["track_priority"]["high"].keys() and (
            time.time()
            - config["track_priority"]["high"][str(track_id)]["last_changed"]
            > config["track_deprioritize_time"]
        ):
            new_low_priority_track_ids.append(track_id)
            remove_track_from_high_priority(track_id)
        continue

    for i in current_leaderboard[1]:
        first_time = True
        for j in saved_leaderboard[1]:
            if i["playername"] == j["playername"]:
                first_time = False
                if float(i["lap_time"]) < float(j["lap_time"]):
                    track_diff[track_id][i["playername"]] = {
                        "lap_date": i["lap_date"],
                        "lap_time": i["lap_time"],
                        "lap_diff": float(i["lap_time"]) - float(j["lap_time"]),
                        "first_time": False,
                    }
        if first_time:
            track_diff[track_id][i["playername"]] = {
                "lap_date": i["lap_date"],
                "lap_time": i["lap_time"],
                "first_time": True,
            }

return track_diff

</details>

<details>
<summary><code>background_leaderboard_update()</code></summary>
```python
@tasks.loop(
    seconds=config["track_update_interval"],
    count=None,
)
async def background_leaderboard_update(self):
    Velocidrone.background_leaderboard_update.change_interval(
        seconds=(velocidrone_helper.get_number_of_tracks() * 10) + 30
    )

    track_diff = await velocidrone_helper.track_update()

    if track_diff is not {}:
        for guild in self.bot.guilds:
            guild_id = guild.id
            if velocidrone_helper.get_guild_leaderboard_channel(guild_id) is None:
                print(f"Guild {guild_id} does not have a leaderboard channel set")
                continue

            for track_id in track_diff:
                if track_id not in velocidrone_helper.get_guild_track_list(
                    guild_id
                ):
                    continue

                message = """"""
                for pilot in track_diff[track_id].keys():
                    if pilot not in velocidrone_helper.get_guild_whitelist(
                        guild_id
                    ):
                        continue

                    pilot_info = track_diff[track_id][pilot]
                    message += (
                        f"""\n**{pilot}** has set a _{"first" if pilot_info["first_time"] else "new"}_ """
                        + f"""time of **{pilot_info["lap_time"]}**!"""
                    )

                track = velocidrone_helper.get_track(track_id)
                if message != """""":
                    await self.bot.get_channel(
                        velocidrone_helper.get_guild_leaderboard_channel(guild_id)
                    ).send(
                        embed=discord.Embed(
                            title=f"""**{track[0]["track_name"]}** has a new leaderboard!""",
                            description=message,
                            url=velocidrone_helper.get_leaderboard_url(track_id),
                            timestamp=datetime.datetime.now(),
                            color=discord.Color.gold(),
                        )
                    )

Together, track_update() and background_leaderboard_update() implement the full loop: fetch → diff → persist → notify, with dynamic timing based on how many tracks a server tracks.

Example embed (Velocidrone):

leaderboard message


/leaderboard/

This cog tracks message activity across a server: XP bumps on messages (with anti-spam delays), level-ups, and lightweight “leaderboard culture” without needing external services.

on_message() ```python @commands.Cog.listener() async def on_message(self, message): if message.guild == None or not leaderboard_helper.is_whitelisted( message.guild.id ): return
    if message.author.bot:
        return

    if (
        level := leaderboard_helper.adjust_xp(message.guild, message.author)
    ) is not None:
        await message.channel.send(
            f"{message.author.mention} has leveled up to level {level}"
        )

</details>

<details>
<summary><code>adjust_xp()</code></summary>
```python
def adjust_xp(
    guild: discord.guild.Guild,
    member: discord.member.Member,
    xp: int = random.randint(
        config["random_xp_range"][0], config["random_xp_range"][1]
    ),
):
    global leaderboard

    member_ID = str(member.id)
    guild_ID = str(guild.id)
    level_up = False

    author_check(guild, member)
    if (
        time.time() - leaderboard[guild_ID][member_ID]["last_message"]
        > config["delay_XP_seconds"]
    ):
        leaderboard[guild_ID][member_ID]["xp"] += xp
        if (
            leaderboard[guild_ID][member_ID]["xp"]
            >= config["level_up_XP"] * leaderboard[guild_ID][member_ID]["level"]
        ):
            leaderboard[guild_ID][member_ID]["level"] += 1
            leaderboard[guild_ID][member_ID]["xp"] = 0
            level_up = True
        leaderboard[guild_ID][member_ID]["last_message"] = time.time()

    if level_up:
        return leaderboard[guild_ID][member_ID]["level"]
    else:
        return None

on_message() gates XP to real humans in whitelisted guilds; adjust_xp() applies randomized XP with cooldowns and handles level-ups.

Example (message XP):

example-message-leaderboard

Other contributors

Dylan W (SolarBlackhole)