diff --git a/drb-frontend/components/DiscordServersCard.tsx b/drb-frontend/components/DiscordServersCard.tsx index 72003b1..b144f40 100644 --- a/drb-frontend/components/DiscordServersCard.tsx +++ b/drb-frontend/components/DiscordServersCard.tsx @@ -28,10 +28,17 @@ const SELECT = // Option values for the bot picker: "" = let C2 choose, "tok:" = one of // the org's own bots, "sys:" = whichever bot is preferred for that system. -function startRequest(choice: string): OAuthStartRequest { +function startRequest(choice: string, data: OrgPreferredBots | null): OAuthStartRequest { const req: OAuthStartRequest = { return_to: "/settings/organization" }; if (choice.startsWith("tok:")) req.token_id = choice.slice(4); - else if (choice.startsWith("sys:")) req.system_id = choice.slice(4); + else if (choice.startsWith("sys:")) { + const id = choice.slice(4); + // The org's own bot preferred for this system: name it by token_id, which + // S6 already accepts. Only a platform-pinned pool bot needs system_id. + const pref = data?.systems.find((s) => s.system_id === id)?.preferred_token_id; + if (pref) req.token_id = pref; + else req.system_id = id; + } return req; } @@ -125,18 +132,19 @@ export function DiscordServersCard({ canEdit, isAdmin }: { canEdit: boolean; isA setActionError(null); setJustLinked(null); try { - const { url } = await startDiscordOAuth(startRequest(choice)); + const { url } = await startDiscordOAuth(startRequest(choice, choices)); window.location.assign(url); // Leave the button in its busy state: the page is navigating to Discord. } catch (err) { if (err instanceof OAuthNotConfigured) { - setOauthOff(true); setShowCode(true); - setActionError( - choice - ? "That bot can't connect servers yet. Try Automatic, or link with a code below." - : "Connecting with Discord isn't set up yet. Link with a code below instead.", - ); + if (choice) { + // Only this bot is off: keep Connect and the picker so Automatic can be tried. + setActionError("That bot can't connect servers yet. Try Automatic, or link with a code below."); + } else { + setOauthOff(true); + setActionError("Connecting with Discord isn't set up yet. Link with a code below instead."); + } } else { setActionError(parseC2Error(err).message); } diff --git a/drb-frontend/components/discord/RadioBotsPanel.tsx b/drb-frontend/components/discord/RadioBotsPanel.tsx index c91f9bf..5fb2942 100644 --- a/drb-frontend/components/discord/RadioBotsPanel.tsx +++ b/drb-frontend/components/discord/RadioBotsPanel.tsx @@ -54,11 +54,12 @@ function Checks({ t }: { t: RadioBot }) { function ServerCount({ t, cap }: { t: RadioBot; cap: number }) { if (t.guild_ids == null) return null; const n = t.guild_ids.length; - const full = t.org_id == null && n >= cap; + const limit = t.server_cap ?? cap; + const full = t.org_id == null && n >= limit; return (
= DISCORD_UNVERIFIED_GUILD_LIMIT ? "text-sev-moderate" : "text-ink-muted"}`}> {n} of {DISCORD_UNVERIFIED_GUILD_LIMIT} servers - {full && ` · full at ${cap}: Connect skips it, add another pool bot`} + {full && ` · full at ${limit}: Connect skips it, add another pool bot`}
); } diff --git a/drb-frontend/lib/discord.ts b/drb-frontend/lib/discord.ts index 6bda84b..24b8c7a 100644 --- a/drb-frontend/lib/discord.ts +++ b/drb-frontend/lib/discord.ts @@ -30,6 +30,8 @@ export interface RadioBot { /** "Connect a Discord server" is on for this bot: its OAuth client secret is * set server-side (server-26#225 S6). The secret itself is never sent. */ oauth_enabled?: boolean | null; + /** GET /tokens (S6): the pool cap for a DRB pool bot, null for a BYO bot. */ + server_cap?: number | null; } export interface RadioBotAddResult { diff --git a/drb-frontend/lib/discordOAuth.ts b/drb-frontend/lib/discordOAuth.ts index 3b2448a..5aaf06f 100644 --- a/drb-frontend/lib/discordOAuth.ts +++ b/drb-frontend/lib/discordOAuth.ts @@ -35,7 +35,9 @@ const post = (path: string, body: object) => send("POST", path, body); export interface OAuthStartRequest { /** One of the org's own (BYO) bots, by token_id. */ token_id?: string; - /** The bot marked preferred for this system (works for a DRB bot a platform admin pinned too). */ + /** The bot marked preferred for this system, when it is a DRB pool bot a platform + * admin pinned (its token_id is not shown to the org). NEEDS S6 to accept + * system_id — until it does, c2-core ignores it and picks Automatic. */ system_id?: string; /** Kept for the plan's node-page entry point: the preferred bot of this node's system. */ node_id?: string; @@ -52,7 +54,7 @@ export interface OAuthStartResult { export class OAuthNotConfigured extends Error {} export async function startDiscordOAuth(req: OAuthStartRequest): Promise { - let raw: { url?: string; authorize_url?: string; bot_name?: string | null; token_name?: string | null }; + let raw: { url?: string; authorize_url?: string; bot?: { name?: string | null } | null; bot_name?: string | null }; try { raw = await post("/discord/oauth/start", req); } catch (err) { @@ -63,7 +65,7 @@ export async function startDiscordOAuth(req: OAuthStartRequest): Promise