S8: main DRB bot optional end to end + owner-flow integration tests (server-26#225)
- Copy: picker, admin Servers/Streams/How-it-works, org Discord card, bot /help and the server-limit refusal no longer imply the DRB server bot is required; a radio bot alone is enough. - Admin overview: per-server main_bot_present, radio_bots, linked_via, directory_source; Servers tab gets a Bots column. - discord_web docstring: states the reversed "never calls Discord" rule (plan finding 5) and the optional server bot. - Server bot directory cog: re-push (which drops C2's cached permission answers) on overwrite changes, role permission changes and role deletes. - tests/test_discord_owner_flows.py: one-bot org (BYO bot, owner-set preferred, OAuth link, picker, start, reconciler) and full setup (server bot + 4 radio bots: placement, shared channel, every refusal explained), against a modelled Discord over httpx.MockTransport. - storage.download_audio removed: no caller on main since #233. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
f49dc86111
commit
9c124d4c8b
@@ -441,7 +441,9 @@ ERROR_TEXT = {
|
||||
"pool_exhausted": ("Every radio bot that can join this server is already busy — a bot can be in only one voice "
|
||||
"channel per server, and a bot carrying radio on another node can't be shared. Add another "
|
||||
"radio bot to DRB and invite it to this server, or stop a radio channel here."),
|
||||
"limit": "This server already has its maximum number of radio channels. Stop one with /leave first.",
|
||||
# Reaches the web picker and /join alike: name both ways to stop one.
|
||||
"limit": "This server already has its maximum number of radio channels. Stop one first (Stop on a node's "
|
||||
"Discord radio panel, or /leave in Discord).",
|
||||
"preferred_unavailable": "That radio bot can't be used here (it was removed, failed validation, or belongs to "
|
||||
"another organization). Pick another bot, or let DRB choose any free one.",
|
||||
"pool_full": ("No radio bot is in this server, and every DRB radio bot that could be invited is already in its "
|
||||
|
||||
@@ -100,31 +100,11 @@ async def upload_audio(data: bytes, filename: str, call_id: str = "") -> Optiona
|
||||
return gcs_uri
|
||||
|
||||
|
||||
async def download_audio(gcs_uri: str) -> Optional[bytes]:
|
||||
"""Fetch an object back out of GCS. Server-side read — no signing involved."""
|
||||
bucket_name, blob_path = split_gcs_uri(gcs_uri)
|
||||
if not bucket_name:
|
||||
return None
|
||||
|
||||
def _download() -> bytes:
|
||||
from google.cloud import storage
|
||||
if settings.gcp_credentials_path:
|
||||
client = storage.Client.from_service_account_json(settings.gcp_credentials_path)
|
||||
else:
|
||||
client = storage.Client()
|
||||
return client.bucket(bucket_name).blob(blob_path).download_as_bytes()
|
||||
|
||||
try:
|
||||
return await asyncio.to_thread(_download)
|
||||
except Exception as e:
|
||||
logger.warning(f"GCS download failed for {gcs_uri}: {e}")
|
||||
return None
|
||||
|
||||
|
||||
# ── Ranged reads for /media playback (server-26#233) ────────────────────────
|
||||
# A 600 s FLAC call is ~13 MB. download_audio() above pulls the whole object
|
||||
# into memory, so /media used to make the browser wait for all of it before
|
||||
# the first sample played. These two read only what a request asks for: the
|
||||
# A 600 s FLAC call is ~13 MB. /media used to pull the whole object into
|
||||
# memory (the old download_audio(), removed once nothing called it — #225 S8),
|
||||
# so the browser waited for all of it before the first sample played. These
|
||||
# two read only what a request asks for: the
|
||||
# object size (one metadata GET) and then an inclusive byte span, which the
|
||||
# router streams out in chunks. One cached client, because the router issues
|
||||
# several span reads per playback and a fresh Client() per read re-resolves
|
||||
|
||||
Reference in New Issue
Block a user