- discord_oauth: POST /discord/oauth/start and GET /options take an optional system_id. The system must be the org's own or shared (another org's or unknown = 404). Its preferred bot is strict: one that can't connect is a 404, never a quiet swap to Automatic. No preference = the usual order. The picker's "bot for a system" now always sends system_id. - #257: _channel_state gives a bot whose token Discord rejected its own "invalid" state; the picker shows "Radio bot needs replacing" with no invite links, instead of "No radio bot". - #256: Start no longer stays disabled on "Needs fix". It re-checks with Discord first (refresh=1) and starts only if the fresh answer allows it. "Not a voice channel" still disables Start. - #258: own tooltip for "Not a voice channel"; "platform admin" -> "DRB support"; PreferredBotsCard says plainly that adding your own bot isn't self-serve (no support address exists yet, see #47), and a load failure gets a plain message + Try again instead of the raw C2 error. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
13a3525e4c
commit
32d223e556
@@ -35,9 +35,9 @@ const post = <T,>(path: string, body: object) => send<T>("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, 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. */
|
||||
/** The bot marked preferred for this system (the org's own system or a shared one),
|
||||
* including a DRB pool bot a platform admin pinned, whose token_id the org never
|
||||
* sees. 404 if that bot can't connect servers; c2-core never swaps in another. */
|
||||
system_id?: string;
|
||||
/** Kept for the plan's node-page entry point: the preferred bot of this node's system. */
|
||||
node_id?: string;
|
||||
|
||||
@@ -164,7 +164,7 @@ export function parseC2Error(err: unknown): ParsedC2Error {
|
||||
export type BotAccess = "ok" | "denied" | "unknown" | "not_member" | "invalid" | "bad_channel" | "unchecked";
|
||||
|
||||
/** Per channel, as c2-core sums it up over the bots that are free. */
|
||||
export type ChannelState = "ready" | "needs_fix" | "unknown" | "busy" | "no_bot" | "bad_channel" | "unchecked";
|
||||
export type ChannelState = "ready" | "needs_fix" | "unknown" | "busy" | "no_bot" | "invalid" | "bad_channel" | "unchecked";
|
||||
|
||||
export interface VoiceCheckBot {
|
||||
/** Admin route only. */
|
||||
@@ -206,7 +206,7 @@ export interface VoiceCheck {
|
||||
}
|
||||
|
||||
const BOT_ACCESS: readonly string[] = ["ok", "denied", "unknown", "not_member", "invalid", "bad_channel", "unchecked"];
|
||||
const CHANNEL_STATES: readonly string[] = ["ready", "needs_fix", "unknown", "busy", "no_bot", "bad_channel", "unchecked"];
|
||||
const CHANNEL_STATES: readonly string[] = ["ready", "needs_fix", "unknown", "busy", "no_bot", "invalid", "bad_channel", "unchecked"];
|
||||
|
||||
function normBot(b: Record<string, unknown>): VoiceCheckBot {
|
||||
const status = (typeof b.status === "string" && BOT_ACCESS.includes(b.status)
|
||||
@@ -267,6 +267,8 @@ export type ChannelVerdict =
|
||||
| { kind: "unknown"; limit: number }
|
||||
/** No radio bot is in this server. */
|
||||
| { kind: "no_bot"; limit: number }
|
||||
/** A radio bot IS in this server, but Discord rejected its token (server-26#257). No invite: it is already there. */
|
||||
| { kind: "invalid"; limit: number }
|
||||
/** The channel exists but is not a voice channel — Start is disabled. */
|
||||
| { kind: "not_voice"; limit: number };
|
||||
|
||||
@@ -283,6 +285,7 @@ export function channelVerdict(c: VoiceCheckChannel | null | undefined): Channel
|
||||
case "busy": return { kind: "busy", limit };
|
||||
case "unknown": return { kind: "unknown", limit };
|
||||
case "no_bot": return { kind: "no_bot", limit };
|
||||
case "invalid": return { kind: "invalid", limit };
|
||||
case "bad_channel": return { kind: "not_voice", limit };
|
||||
case "unchecked": return null;
|
||||
case "needs_fix": return firstDenied ? { kind: "fix", limit, bot: firstDenied } : { kind: "unknown", limit };
|
||||
@@ -293,6 +296,7 @@ export function channelVerdict(c: VoiceCheckChannel | null | undefined): Channel
|
||||
if (free.some((b) => b.ok === null)) return { kind: "unknown", limit };
|
||||
if (denied.length) return { kind: "fix", limit, bot: denied[0] };
|
||||
if (c.bots.some((b) => b.ok === true)) return { kind: "busy", limit };
|
||||
if (c.bots.some((b) => b.status === "invalid")) return { kind: "invalid", limit };
|
||||
return { kind: "unknown", limit };
|
||||
}
|
||||
|
||||
@@ -321,9 +325,9 @@ export function deniedSentence(bot: { name: string; missing: string[]; reason: s
|
||||
/** Where in Discord to fix it — c2-core's own sentence when it sent one. */
|
||||
export function fixSentence(bot: { name: string; missing: string[]; reason: string | null; fix?: string | null }): string {
|
||||
if (bot.fix) return bot.fix;
|
||||
if (bot.reason && /timed out/i.test(bot.reason)) return `In Discord: remove ${bot.name}'s timeout, then Re-check.`;
|
||||
if (bot.reason && /timed out/i.test(bot.reason)) return `In Discord: remove ${bot.name}'s timeout, then try again.`;
|
||||
const perms = bot.missing.length ? bot.missing.join(" and ") : "View Channel, Connect and Speak";
|
||||
return `In Discord: Edit Channel → Permissions → add ${bot.name} → allow ${perms}, then Re-check.`;
|
||||
return `In Discord: Edit Channel → Permissions → add ${bot.name} → allow ${perms}, then try again.`;
|
||||
}
|
||||
|
||||
/** "40s ago" / "3 min ago" for the Re-check footer. */
|
||||
@@ -355,7 +359,7 @@ export function botLine(b: VoiceCheckBot, channelName?: string | null): { tone:
|
||||
case "not_member":
|
||||
return { tone: "warn", text: `${b.name}: Discord says it is not in this server — invite it again` };
|
||||
case "invalid":
|
||||
return { tone: "bad", text: `${b.name}: its token was rejected by Discord — a platform admin has to replace it` };
|
||||
return { tone: "bad", text: `${b.name}: Discord rejected its token — DRB support has to replace it` };
|
||||
case "bad_channel":
|
||||
return { tone: "bad", text: `${b.name}: ${channelRef(channelName)} is not a voice channel` };
|
||||
case "unchecked":
|
||||
|
||||
Reference in New Issue
Block a user