Add music bot for self-hosted Stoat with web control panel
Plays audio into Stoat voice channels over LiveKit and exposes the same player through both chat commands and a browser panel, so the two never drift apart: everything routes through a single MusicManager. - core: per-server GuildPlayer (queue, loop, shuffle, seek, volume, idle auto-leave) driving revoice.js/@livekit/rtc-node and ffmpeg - sources: yt-dlp for YouTube/SoundCloud, direct media URLs and internet radio, optional local library with path-traversal guards - bot: 18 chat commands with aliases, plus !panel one-time login links - api: Fastify REST + WebSocket, sessions authenticated against the instance's own /auth/session/login (TOTP supported), permissions re-checked against Stoat membership and roles on every request - web: React panel with search, queue editing, seek and volume - deploy: Dockerfile, compose.override.yml and Caddyfile snippets for dropping the service into an existing /opt/stoat stack Verified with npm run typecheck, both builds, and scripts/smoke-api.mjs (9 API checks). Voice playback itself needs a live instance to test. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
d9d0e9f6bf
commit
a9b7ccdd16
@@ -0,0 +1,54 @@
|
||||
export type SourceKind = "youtube" | "soundcloud" | "direct" | "local";
|
||||
export type LoopMode = "off" | "track" | "queue";
|
||||
export type PlayerStatus = "idle" | "connecting" | "buffering" | "playing" | "paused";
|
||||
|
||||
export interface Track {
|
||||
id: string;
|
||||
title: string;
|
||||
author: string | null;
|
||||
duration: number;
|
||||
isLive: boolean;
|
||||
url: string;
|
||||
thumbnail: string | null;
|
||||
source: SourceKind;
|
||||
requestedBy: { id: string; username: string };
|
||||
}
|
||||
|
||||
export interface PlayerState {
|
||||
serverId: string;
|
||||
serverName: string | null;
|
||||
voiceChannelId: string | null;
|
||||
voiceChannelName: string | null;
|
||||
status: PlayerStatus;
|
||||
current: Track | null;
|
||||
position: number;
|
||||
queue: Track[];
|
||||
history: Track[];
|
||||
volume: number;
|
||||
loop: LoopMode;
|
||||
updatedAt: number;
|
||||
}
|
||||
|
||||
export interface VoiceChannel {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface ServerRef {
|
||||
id: string;
|
||||
name: string;
|
||||
iconUrl: string | null;
|
||||
}
|
||||
|
||||
export interface Me {
|
||||
user: { id: string; username: string };
|
||||
servers: ServerRef[];
|
||||
features: { localLibrary: boolean };
|
||||
}
|
||||
|
||||
export interface ServerStateResponse {
|
||||
state: PlayerState;
|
||||
voiceChannels: VoiceChannel[];
|
||||
yourVoiceChannel: VoiceChannel | null;
|
||||
canControl: boolean;
|
||||
}
|
||||
Reference in New Issue
Block a user