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
| Cog | Description |
|---|---|
/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 commandsjson/— 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/
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):
