"use client";
import { useState } from "react";
import { c2api } from "@/lib/c2api";
import {
type DiscordAdminOverview,
type RadioBot,
type RadioBotAddResult,
fmtWhen,
guildLabel,
inviteForGuild,
parseC2Error,
shortId,
} from "@/lib/discord";
import { Button } from "@/components/ui/Button";
import { DIM, Field, INPUT, Panel, Status, Table, TD } from "./ui";
// Radio bots = the bot_tokens pool (routers/tokens.py). Used by the Discord
// admin page's "Radio bots" tab and by /tokens. Every action here is a real
// platform-admin route: POST /tokens, POST /tokens/{id}/refresh,
// DELETE /tokens/{id}, POST /tokens/flush.
function Checks({ t }: { t: RadioBot }) {
if (t.valid === false) {
return (
Token rejected by Discord
{t.check_error &&
{t.check_error}
}
);
}
if (t.valid) {
return (
Token valid
checked {fmtWhen(t.validated_at)}
{t.check_error &&
last check failed: {t.check_error}
}
);
}
return (
Not checked yet
{t.check_error &&
{t.check_error}
}
);
}
function Membership({ t, overview }: { t: RadioBot; overview: DiscordAdminOverview | null }) {
if (t.guild_ids == null) return unknown — refresh;
const known = new Map((overview?.guilds ?? []).map((g) => [g.guild_id, g]));
const members = t.guild_ids;
// Servers this bot could serve (its own org's for a BYO bot, every one for the pool) but is not in.
const missing = (overview?.guilds ?? []).filter(
(g) => !members.includes(g.guild_id) && (t.org_id == null || g.acting_org_id === t.org_id),
);
if (!members.length && !missing.length) return no servers;
return (
Added {added.name}
{added.valid ? ` · token valid · in ${added.guild_ids?.length ?? 0} server(s)` : " · Discord could not be reached, membership unknown"}
{added.check_error ? ` (${added.check_error})` : ""}.
{added.invite_url && (
Invite it to a server
)}
)}
{error &&
{error}
}
{bots.length === 0 ? (
No radio bots yet. Add one to let nodes carry radio into Discord voice channels.