Initial commit — DRB server stack

Includes c2-core (FastAPI/MQTT/Firestore), discord-bot (slash commands),
frontend (Next.js admin UI), and mosquitto config.
This commit is contained in:
Logan
2026-04-05 19:01:39 -04:00
commit 2f0597c81b
77 changed files with 4126 additions and 0 deletions
@@ -0,0 +1,130 @@
import discord
from discord import app_commands
from discord.ext import commands
from typing import Optional
from app.internal.c2_client import c2
from app.internal.logger import logger
class RadioCommands(commands.Cog):
def __init__(self, bot: commands.Bot):
self.bot = bot
# ------------------------------------------------------------------
# Autocomplete — system names from C2
# ------------------------------------------------------------------
async def system_autocomplete(
self, interaction: discord.Interaction, current: str
) -> list[app_commands.Choice[str]]:
systems = await c2.get_systems()
return [
app_commands.Choice(name=s["name"], value=s["system_id"])
for s in systems
if current.lower() in s["name"].lower()
][:25]
# ------------------------------------------------------------------
# /join
# ------------------------------------------------------------------
@app_commands.command(name="join", description="Stream a radio system to your voice channel.")
@app_commands.describe(system="The radio system to listen to.")
@app_commands.autocomplete(system=system_autocomplete)
async def join(self, interaction: discord.Interaction, system: str):
await interaction.response.defer(ephemeral=True)
if not interaction.user.voice or not interaction.user.voice.channel:
await interaction.followup.send("You need to be in a voice channel first.")
return
channel = interaction.user.voice.channel
guild_id = str(interaction.guild_id)
channel_id = str(channel.id)
node = await c2.find_node_for_system(system)
if not node:
await interaction.followup.send(
"No online node is assigned to that system. Check `/status` for availability."
)
return
ok = await c2.send_command(node["node_id"], {
"action": "discord_join",
"guild_id": guild_id,
"channel_id": channel_id,
})
if ok:
systems = await c2.get_systems()
system_name = next((s["name"] for s in systems if s["system_id"] == system), system)
await interaction.followup.send(
f"Streaming **{system_name}** from node `{node['node_id']}` to {channel.mention}."
)
else:
await interaction.followup.send("Failed to contact the node. It may be offline.")
# ------------------------------------------------------------------
# /leave
# ------------------------------------------------------------------
@app_commands.command(name="leave", description="Stop streaming radio in this server.")
async def leave(self, interaction: discord.Interaction):
await interaction.response.defer(ephemeral=True)
# Find any node currently streaming to this guild
nodes = await c2.get_nodes()
streaming_nodes = [
n for n in nodes if n.get("status") in ("online", "recording")
]
if not streaming_nodes:
await interaction.followup.send("No nodes appear to be active right now.")
return
# Send leave to all online nodes in case more than one joined
for node in streaming_nodes:
await c2.send_command(node["node_id"], {"action": "discord_leave"})
await interaction.followup.send("Disconnected.")
# ------------------------------------------------------------------
# /status
# ------------------------------------------------------------------
@app_commands.command(name="status", description="Show all node and system status.")
async def status(self, interaction: discord.Interaction):
await interaction.response.defer()
nodes = await c2.get_nodes()
systems = await c2.get_systems()
system_map = {s["system_id"]: s["name"] for s in systems}
status_emoji = {
"online": "🟢",
"recording": "🔴",
"offline": "",
"unconfigured": "🟡",
}
embed = discord.Embed(title="DRB Node Status", color=0x2b2d31)
if not nodes:
embed.description = "No nodes registered."
else:
for node in sorted(nodes, key=lambda n: n.get("name", "")):
s = node.get("status", "offline")
emoji = status_emoji.get(s, "")
system_name = system_map.get(node.get("assigned_system_id", ""), "Unassigned")
embed.add_field(
name=f"{emoji} {node.get('name', node['node_id'])}",
value=f"`{s}` — {system_name}",
inline=True,
)
await interaction.followup.send(embed=embed)
async def setup(bot: commands.Bot):
await bot.add_cog(RadioCommands(bot))