diff --git a/services/backend/src/modules/media/media.service.ts b/services/backend/src/modules/media/media.service.ts index 34d3722..7f2ce41 100644 --- a/services/backend/src/modules/media/media.service.ts +++ b/services/backend/src/modules/media/media.service.ts @@ -99,7 +99,9 @@ export async function queue( () => publishCommand( COMMAND_MEDIA_QUEUE, - { source, mode }, + // NOTE: gateway MediaHandler reads `payload.url` (not `source`) — + // keep the field name aligned or playback silently no-ops. + { url: source, mode }, DEFAULT_COMMAND_TIMEOUT_MS, ), () => readStatusFallback(), diff --git a/services/backend/src/modules/voice/voice.service.ts b/services/backend/src/modules/voice/voice.service.ts index d9795fe..8c0e258 100644 --- a/services/backend/src/modules/voice/voice.service.ts +++ b/services/backend/src/modules/voice/voice.service.ts @@ -28,6 +28,8 @@ export interface Channel { id: string; name: string; type: "voice" | "text"; + /** Whether the selfbot account can actually join this voice channel. */ + joinable?: boolean; } export interface GuildVoiceEntry { diff --git a/services/discord-gateway/src/modules/command-handler/media.handler.ts b/services/discord-gateway/src/modules/command-handler/media.handler.ts index 2dd3181..5428592 100644 --- a/services/discord-gateway/src/modules/command-handler/media.handler.ts +++ b/services/discord-gateway/src/modules/command-handler/media.handler.ts @@ -124,7 +124,8 @@ export class MediaHandler { } async handleMediaQueue(cmd: CommandMessage): Promise> { - const url = String(cmd.payload.url ?? "").trim(); + // Accept both `url` (canonical) and `source` (legacy FE) for resilience. + const url = String(cmd.payload.url ?? cmd.payload.source ?? "").trim(); const mode: MediaMode = cmd.payload.mode === "screen" ? "screen" : "music"; const requestedBy = String(cmd.payload.requestedBy ?? "unknown"); diff --git a/services/discord-gateway/src/modules/command-handler/voice.handler.ts b/services/discord-gateway/src/modules/command-handler/voice.handler.ts index 7ccdc16..61f869c 100644 --- a/services/discord-gateway/src/modules/command-handler/voice.handler.ts +++ b/services/discord-gateway/src/modules/command-handler/voice.handler.ts @@ -128,6 +128,9 @@ export class VoiceHandler { id: c.id, name: c.name, type: "voice" as const, + // selfbot exposes joinable (permission check) — let FE filter + // channels the account actually may join. + joinable: (c as { joinable?: boolean }).joinable ?? true, })); return { id: cmd.id, success: true, data: voiceChannels }; diff --git a/services/frontend/src/app/(dashboard)/voice/page.tsx b/services/frontend/src/app/(dashboard)/voice/page.tsx index b7f950c..f23e16e 100644 --- a/services/frontend/src/app/(dashboard)/voice/page.tsx +++ b/services/frontend/src/app/(dashboard)/voice/page.tsx @@ -18,6 +18,7 @@ import { useVoiceStatus, } from "@/hooks"; import { useWebSocket } from "@/lib/ws/context"; +import { toast } from "sonner"; type VoiceTab = "connection" | "activity"; @@ -104,12 +105,22 @@ export default function VoicePage() { selectedChannel={selectedChannel} onGuildChange={handleGuildChange} onChannelChange={(v) => setSelectedChannel(v ?? "")} - onConnect={() => - connectMut.mutate({ - guildId: selectedGuild, - channelId: selectedChannel, - }) - } + onConnect={() => { + void connectMut + .mutateAsync({ + guildId: selectedGuild, + channelId: selectedChannel, + }) + .catch((err: unknown) => { + const msg = + err instanceof Error + ? err.message + : "Gagal connect ke voice channel"; + toast.error("Voice connect gagal", { + description: msg, + }); + }); + }} onDisconnect={() => { if (micActive) { setMicActive(false); diff --git a/services/frontend/src/components/voice/connection-card.tsx b/services/frontend/src/components/voice/connection-card.tsx index 0d663e5..88eb7bc 100644 --- a/services/frontend/src/components/voice/connection-card.tsx +++ b/services/frontend/src/components/voice/connection-card.tsx @@ -119,10 +119,19 @@ export function VoiceConnectionCard({ {voiceChannels.map((c) => ( - - {c.name} + + {c.joinable === false ? `${c.name} (no akses)` : c.name} ))} + {voiceChannels.length === 0 && ( +
+ Tidak ada voice channel +
+ )}
diff --git a/services/frontend/src/lib/types/guild.ts b/services/frontend/src/lib/types/guild.ts index 5dbaa79..5f87056 100644 --- a/services/frontend/src/lib/types/guild.ts +++ b/services/frontend/src/lib/types/guild.ts @@ -8,6 +8,8 @@ export interface Channel { id: string; name: string; type: "voice" | "text"; + /** Whether the selfbot account can actually join this voice channel. */ + joinable?: boolean; } /** Shape of the /api/config response (camelCase keys from backend). */