changes
This commit is contained in:
@@ -11,7 +11,7 @@ class RadioCommands(commands.Cog):
|
||||
self.bot = bot
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Autocomplete — system names from C2
|
||||
# Autocomplete helpers
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
async def system_autocomplete(
|
||||
@@ -24,14 +24,27 @@ class RadioCommands(commands.Cog):
|
||||
if current.lower() in s["name"].lower()
|
||||
][:25]
|
||||
|
||||
async def token_autocomplete(
|
||||
self, interaction: discord.Interaction, current: str
|
||||
) -> list[app_commands.Choice[str]]:
|
||||
tokens = await c2.get_tokens()
|
||||
return [
|
||||
app_commands.Choice(name=f"{t['name']} {'(in use)' if t.get('in_use') else '(free)'}", value=t["token_id"])
|
||||
for t in tokens
|
||||
if current.lower() in t["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):
|
||||
@app_commands.describe(
|
||||
system="The radio system to listen to.",
|
||||
token="Optionally pick a specific bot token from the pool.",
|
||||
)
|
||||
@app_commands.autocomplete(system=system_autocomplete, token=token_autocomplete)
|
||||
async def join(self, interaction: discord.Interaction, system: str, token: Optional[str] = None):
|
||||
await interaction.response.defer(ephemeral=True)
|
||||
|
||||
if not interaction.user.voice or not interaction.user.voice.channel:
|
||||
@@ -49,11 +62,15 @@ class RadioCommands(commands.Cog):
|
||||
)
|
||||
return
|
||||
|
||||
ok = await c2.send_command(node["node_id"], {
|
||||
cmd: dict = {
|
||||
"action": "discord_join",
|
||||
"guild_id": guild_id,
|
||||
"channel_id": channel_id,
|
||||
})
|
||||
}
|
||||
if token:
|
||||
cmd["preferred_token_id"] = token
|
||||
|
||||
ok = await c2.send_command(node["node_id"], cmd)
|
||||
|
||||
if ok:
|
||||
systems = await c2.get_systems()
|
||||
@@ -72,7 +89,6 @@ class RadioCommands(commands.Cog):
|
||||
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")
|
||||
@@ -82,7 +98,6 @@ class RadioCommands(commands.Cog):
|
||||
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"})
|
||||
|
||||
@@ -125,6 +140,51 @@ class RadioCommands(commands.Cog):
|
||||
|
||||
await interaction.followup.send(embed=embed)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# /help
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
@app_commands.command(name="help", description="How to use this bot.")
|
||||
async def help(self, interaction: discord.Interaction):
|
||||
embed = discord.Embed(
|
||||
title="DRB Radio Bot — Help",
|
||||
color=0x5865f2,
|
||||
)
|
||||
|
||||
embed.add_field(
|
||||
name="/join `system`",
|
||||
value=(
|
||||
"Join your current voice channel and start streaming a radio system.\n"
|
||||
"Use the `token` option to pick a specific bot from the pool (optional).\n"
|
||||
"You must be in a voice channel first."
|
||||
),
|
||||
inline=False,
|
||||
)
|
||||
embed.add_field(
|
||||
name="/leave",
|
||||
value="Stop streaming and disconnect all active radio bots in this server.",
|
||||
inline=False,
|
||||
)
|
||||
embed.add_field(
|
||||
name="/status",
|
||||
value="Show all registered edge nodes and which systems they're monitoring.",
|
||||
inline=False,
|
||||
)
|
||||
|
||||
embed.add_field(
|
||||
name="Radio bot direct commands",
|
||||
value=(
|
||||
"Once a radio bot is in your voice channel, you can control it by **mentioning it** in any text channel:\n"
|
||||
"- **@botname joinme** — Move the bot to your current voice channel\n"
|
||||
"- **@botname leave** — Disconnect the bot from voice\n\n"
|
||||
"The radio bot's green speaking ring lights up only when radio is actively transmitting."
|
||||
),
|
||||
inline=False,
|
||||
)
|
||||
|
||||
embed.set_footer(text="Audio is streamed live from SDR edge nodes via Icecast.")
|
||||
await interaction.response.send_message(embed=embed)
|
||||
|
||||
|
||||
async def setup(bot: commands.Bot):
|
||||
await bot.add_cog(RadioCommands(bot))
|
||||
|
||||
@@ -5,6 +5,7 @@ from typing import Optional
|
||||
class Settings(BaseSettings):
|
||||
discord_token: str
|
||||
c2_url: str = "http://localhost:8000"
|
||||
c2_service_key: Optional[str] = None # must match C2_SERVICE_KEY on c2-core
|
||||
dev_guild_id: Optional[int] = None # set to sync commands instantly during dev
|
||||
|
||||
class Config:
|
||||
|
||||
@@ -8,10 +8,15 @@ class C2Client:
|
||||
def __init__(self):
|
||||
self.base = settings.c2_url.rstrip("/")
|
||||
|
||||
def _headers(self) -> dict:
|
||||
if settings.c2_service_key:
|
||||
return {"Authorization": f"Bearer {settings.c2_service_key}"}
|
||||
return {}
|
||||
|
||||
async def get_nodes(self) -> list:
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=10) as client:
|
||||
r = await client.get(f"{self.base}/nodes")
|
||||
r = await client.get(f"{self.base}/nodes", headers=self._headers())
|
||||
r.raise_for_status()
|
||||
return r.json()
|
||||
except Exception as e:
|
||||
@@ -21,19 +26,30 @@ class C2Client:
|
||||
async def get_systems(self) -> list:
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=10) as client:
|
||||
r = await client.get(f"{self.base}/systems")
|
||||
r = await client.get(f"{self.base}/systems", headers=self._headers())
|
||||
r.raise_for_status()
|
||||
return r.json()
|
||||
except Exception as e:
|
||||
logger.error(f"C2 get_systems failed: {e}")
|
||||
return []
|
||||
|
||||
async def get_tokens(self) -> list:
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=10) as client:
|
||||
r = await client.get(f"{self.base}/tokens", headers=self._headers())
|
||||
r.raise_for_status()
|
||||
return r.json()
|
||||
except Exception as e:
|
||||
logger.error(f"C2 get_tokens failed: {e}")
|
||||
return []
|
||||
|
||||
async def send_command(self, node_id: str, payload: dict) -> bool:
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=10) as client:
|
||||
r = await client.post(
|
||||
f"{self.base}/nodes/{node_id}/command",
|
||||
json=payload,
|
||||
headers=self._headers(),
|
||||
)
|
||||
r.raise_for_status()
|
||||
return True
|
||||
|
||||
Reference in New Issue
Block a user