A Python wrapper for the Discord API forked from discord.py
—
Voice connection management, audio playbook, and voice state handling for Discord voice features with comprehensive audio processing capabilities.
Core voice connection functionality for joining voice channels and managing audio playback.
import nextcord
from nextcord import VoiceClient, AudioSource, PCMVolumeTransformer
from typing import Optional, Callable, Any
import asyncio
class VoiceClient:
"""Represents a Discord voice connection.
This is used to manage voice connections and audio playback in Discord voice channels.
Attributes
----------
token: str
The voice connection token.
guild: Guild
The guild this voice client is connected to.
channel: Optional[VoiceChannel]
The voice channel this client is connected to.
endpoint: str
The voice server endpoint.
endpoint_ip: str
The voice server IP address.
port: int
The voice server port.
ssrc: int
The synchronization source identifier.
secret_key: bytes
The secret key for voice encryption.
sequence: int
The current sequence number.
timestamp: int
The current timestamp.
mode: str
The voice encryption mode.
user: ClientUser
The bot user associated with this voice client.
"""
@property
def latency(self) -> float:
"""float: The latency of the voice connection in seconds."""
...
@property
def average_latency(self) -> float:
"""float: The average latency over the last 20 heartbeats."""
...
def is_connected(self) -> bool:
"""bool: Whether the voice client is connected to a voice channel."""
...
def is_playing(self) -> bool:
"""bool: Whether audio is currently being played."""
...
def is_paused(self) -> bool:
"""bool: Whether audio playback is currently paused."""
...
async def connect(
self,
*,
timeout: float = 60.0,
reconnect: bool = True,
self_deaf: bool = False,
self_mute: bool = False
) -> None:
"""Connect to the voice channel.
Parameters
----------
timeout: float
The timeout for connecting to voice.
reconnect: bool
Whether to reconnect on disconnection.
self_deaf: bool
Whether to deafen the bot upon connection.
self_mute: bool
Whether to mute the bot upon connection.
"""
...
async def disconnect(self, *, force: bool = False) -> None:
"""Disconnect from the voice channel.
Parameters
----------
force: bool
Whether to forcefully disconnect even if audio is playing.
"""
...
async def move_to(self, channel: nextcord.VoiceChannel) -> None:
"""Move the voice client to a different voice channel.
Parameters
----------
channel: nextcord.VoiceChannel
The voice channel to move to.
"""
...
def play(
self,
source: AudioSource,
*,
after: Optional[Callable[[Optional[Exception]], Any]] = None
) -> None:
"""Play an audio source.
Parameters
----------
source: AudioSource
The audio source to play.
after: Optional[Callable]
A callback function called after the audio finishes playing.
"""
...
def stop(self) -> None:
"""Stop the currently playing audio."""
...
def pause(self) -> None:
"""Pause the currently playing audio."""
...
def resume(self) -> None:
"""Resume the currently paused audio."""
...
@property
def source(self) -> Optional[AudioSource]:
"""Optional[AudioSource]: The currently playing audio source."""
...
# Basic voice connection example
@bot.command()
async def join(ctx):
"""Join the user's voice channel."""
if not ctx.author.voice:
await ctx.send("❌ You are not connected to a voice channel.")
return
channel = ctx.author.voice.channel
if ctx.voice_client is not None:
# Already connected, move to new channel
await ctx.voice_client.move_to(channel)
await ctx.send(f"🔊 Moved to {channel.name}")
else:
# Connect to the channel
voice_client = await channel.connect()
await ctx.send(f"🔊 Connected to {channel.name}")
@bot.command()
async def leave(ctx):
"""Leave the voice channel."""
if ctx.voice_client is None:
await ctx.send("❌ Not connected to a voice channel.")
return
await ctx.voice_client.disconnect()
await ctx.send("👋 Disconnected from voice channel.")
# Voice channel connection with error handling
async def connect_to_voice_channel(
channel: nextcord.VoiceChannel,
timeout: float = 10.0
) -> Optional[VoiceClient]:
"""Connect to a voice channel with proper error handling."""
try:
voice_client = await channel.connect(timeout=timeout)
print(f"Connected to {channel.name} in {channel.guild.name}")
return voice_client
except asyncio.TimeoutError:
print(f"Timeout connecting to {channel.name}")
return None
except nextcord.ClientException as e:
print(f"Already connected to voice: {e}")
return None
except nextcord.Forbidden:
print(f"No permission to connect to {channel.name}")
return None
except Exception as e:
print(f"Unexpected error connecting to voice: {e}")
return NoneAudio source classes for playing various types of audio content.
class AudioSource:
"""Base class for audio sources.
All audio sources must inherit from this class and implement the read method.
"""
def read(self) -> bytes:
"""Read audio data.
Returns
-------
bytes
20ms of audio data in PCM format, or empty bytes to signal end.
"""
...
def cleanup(self) -> None:
"""Clean up any resources used by the audio source."""
...
def is_opus(self) -> bool:
"""bool: Whether this source provides Opus-encoded audio."""
return False
class FFmpegPCMAudio(AudioSource):
"""An audio source that uses FFmpeg to convert audio to PCM.
This is the most common audio source for playing files or streams.
"""
def __init__(
self,
source: str,
*,
executable: str = 'ffmpeg',
pipe: bool = False,
stderr: Optional[Any] = None,
before_options: Optional[str] = None,
options: Optional[str] = None
):
"""Initialize FFmpeg PCM audio source.
Parameters
----------
source: str
The audio source (file path or URL).
executable: str
The FFmpeg executable path.
pipe: bool
Whether to pipe the audio through stdin.
stderr: Optional[Any]
Where to redirect stderr output.
before_options: Optional[str]
FFmpeg options to use before the input source.
options: Optional[str]
FFmpeg options to use after the input source.
"""
...
class FFmpegOpusAudio(AudioSource):
"""An audio source that uses FFmpeg to provide Opus-encoded audio.
This is more efficient than PCM audio as it doesn't require re-encoding.
"""
def __init__(
self,
source: str,
*,
bitrate: int = 128,
**kwargs
):
"""Initialize FFmpeg Opus audio source.
Parameters
----------
source: str
The audio source (file path or URL).
bitrate: int
The audio bitrate in kbps.
**kwargs
Additional arguments passed to FFmpegPCMAudio.
"""
...
def is_opus(self) -> bool:
"""bool: Always returns True for Opus sources."""
return True
class PCMVolumeTransformer(AudioSource):
"""A volume transformer for PCM audio sources.
This allows you to control the volume of audio playback.
"""
def __init__(self, original: AudioSource, volume: float = 0.5):
"""Initialize volume transformer.
Parameters
----------
original: AudioSource
The original audio source to transform.
volume: float
The volume level (0.0 to 1.0).
"""
...
@property
def volume(self) -> float:
"""float: The current volume level."""
...
@volume.setter
def volume(self, value: float) -> None:
"""Set the volume level.
Parameters
----------
value: float
The volume level (0.0 to 1.0).
"""
...
# Audio source examples
@bot.command()
async def play_file(ctx, *, filename: str):
"""Play an audio file."""
if not ctx.voice_client:
await ctx.send("❌ Not connected to a voice channel. Use `!join` first.")
return
try:
# Create audio source from file
source = nextcord.FFmpegPCMAudio(filename)
# Play the audio
ctx.voice_client.play(source, after=lambda e: print(f'Player error: {e}') if e else None)
await ctx.send(f"🎵 Now playing: {filename}")
except Exception as e:
await ctx.send(f"❌ Error playing file: {e}")
@bot.command()
async def play_url(ctx, *, url: str):
"""Play audio from a URL."""
if not ctx.voice_client:
await ctx.send("❌ Not connected to a voice channel.")
return
try:
# Use FFmpeg to stream from URL
# Common options for streaming
ffmpeg_options = {
'before_options': '-reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5',
'options': '-vn' # Disable video
}
source = nextcord.FFmpegPCMAudio(url, **ffmpeg_options)
ctx.voice_client.play(source)
await ctx.send(f"🎵 Now playing from URL")
except Exception as e:
await ctx.send(f"❌ Error playing from URL: {e}")
@bot.command()
async def play_with_volume(ctx, volume: float, *, filename: str):
"""Play an audio file with specified volume."""
if not ctx.voice_client:
await ctx.send("❌ Not connected to a voice channel.")
return
if not 0.0 <= volume <= 1.0:
await ctx.send("❌ Volume must be between 0.0 and 1.0")
return
try:
# Create audio source with volume control
original_source = nextcord.FFmpegPCMAudio(filename)
source = nextcord.PCMVolumeTransformer(original_source, volume=volume)
ctx.voice_client.play(source)
await ctx.send(f"🎵 Now playing {filename} at {volume:.0%} volume")
except Exception as e:
await ctx.send(f"❌ Error: {e}")
@bot.command()
async def volume(ctx, new_volume: float):
"""Change the volume of currently playing audio."""
if not ctx.voice_client or not ctx.voice_client.source:
await ctx.send("❌ Nothing is currently playing.")
return
if not isinstance(ctx.voice_client.source, nextcord.PCMVolumeTransformer):
await ctx.send("❌ Current audio source doesn't support volume control.")
return
if not 0.0 <= new_volume <= 1.0:
await ctx.send("❌ Volume must be between 0.0 and 1.0")
return
ctx.voice_client.source.volume = new_volume
await ctx.send(f"🔊 Volume set to {new_volume:.0%}")Complete music bot implementation with queue management and playback controls.
import asyncio
import youtube_dl
from collections import deque
from typing import Dict, List, Optional, Any
class Song:
"""Represents a song in the music queue."""
def __init__(
self,
title: str,
url: str,
duration: Optional[int] = None,
thumbnail: Optional[str] = None,
requester: Optional[nextcord.Member] = None
):
self.title = title
self.url = url
self.duration = duration
self.thumbnail = thumbnail
self.requester = requester
def __str__(self) -> str:
return self.title
@property
def duration_formatted(self) -> str:
"""Get formatted duration string."""
if not self.duration:
return "Unknown"
minutes, seconds = divmod(self.duration, 60)
hours, minutes = divmod(minutes, 60)
if hours:
return f"{hours:02d}:{minutes:02d}:{seconds:02d}"
else:
return f"{minutes:02d}:{seconds:02d}"
class MusicQueue:
"""Manages a music queue for a guild."""
def __init__(self):
self.queue = deque()
self.current_song = None
self.loop_mode = "off" # "off", "song", "queue"
self.shuffle = False
def add(self, song: Song) -> None:
"""Add a song to the queue."""
self.queue.append(song)
def next(self) -> Optional[Song]:
"""Get the next song from the queue."""
if self.loop_mode == "song" and self.current_song:
return self.current_song
if not self.queue:
if self.loop_mode == "queue" and self.current_song:
# Add current song back to queue for looping
self.queue.append(self.current_song)
else:
return None
if self.shuffle:
import random
song = random.choice(self.queue)
self.queue.remove(song)
else:
song = self.queue.popleft()
self.current_song = song
return song
def clear(self) -> int:
"""Clear the queue and return number of songs removed."""
count = len(self.queue)
self.queue.clear()
return count
def remove(self, index: int) -> Optional[Song]:
"""Remove a song by index."""
try:
return self.queue.popleft() if index == 0 else self.queue.remove(list(self.queue)[index])
except (IndexError, ValueError):
return None
def get_queue_list(self, limit: int = 10) -> List[Song]:
"""Get a list of songs in the queue."""
return list(self.queue)[:limit]
class YouTubeSource:
"""YouTube audio source with metadata extraction."""
ytdl_format_options = {
'format': 'bestaudio/best',
'outtmpl': '%(extractor)s-%(id)s-%(title)s.%(ext)s',
'restrictfilenames': True,
'noplaylist': True,
'nocheckcertificate': True,
'ignoreerrors': False,
'logtostderr': False,
'quiet': True,
'no_warnings': True,
'default_search': 'auto',
'source_address': '0.0.0.0'
}
ffmpeg_options = {
'before_options': '-reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5',
'options': '-vn'
}
ytdl = youtube_dl.YoutubeDL(ytdl_format_options)
@classmethod
async def from_url(
cls,
url: str,
*,
loop: Optional[asyncio.AbstractEventLoop] = None,
stream: bool = False
) -> Dict[str, Any]:
"""Extract song information from YouTube URL."""
loop = loop or asyncio.get_event_loop()
# Run youtube-dl extraction in thread pool
data = await loop.run_in_executor(
None,
lambda: cls.ytdl.extract_info(url, download=not stream)
)
if 'entries' in data:
# Playlist - take first item
data = data['entries'][0]
filename = data['url'] if stream else cls.ytdl.prepare_filename(data)
return {
'title': data.get('title'),
'url': data.get('webpage_url'),
'stream_url': data.get('url'),
'duration': data.get('duration'),
'thumbnail': data.get('thumbnail'),
'filename': filename
}
@classmethod
def get_audio_source(cls, data: Dict[str, Any]) -> nextcord.AudioSource:
"""Get audio source from extracted data."""
return nextcord.FFmpegPCMAudio(data['filename'], **cls.ffmpeg_options)
class MusicBot:
"""Complete music bot implementation."""
def __init__(self, bot: commands.Bot):
self.bot = bot
self.guilds: Dict[int, MusicQueue] = {}
def get_queue(self, guild_id: int) -> MusicQueue:
"""Get or create a music queue for a guild."""
if guild_id not in self.guilds:
self.guilds[guild_id] = MusicQueue()
return self.guilds[guild_id]
async def play_next(self, ctx: commands.Context) -> None:
"""Play the next song in the queue."""
queue = self.get_queue(ctx.guild.id)
if not ctx.voice_client:
return
next_song = queue.next()
if not next_song:
# Queue is empty
embed = nextcord.Embed(
title="🎵 Queue Finished",
description="No more songs in the queue.",
color=nextcord.Color.blue()
)
await ctx.send(embed=embed)
return
try:
# Extract audio source
data = await YouTubeSource.from_url(next_song.url, stream=True)
source = YouTubeSource.get_audio_source(data)
# Play the song
ctx.voice_client.play(
source,
after=lambda e: self.bot.loop.create_task(self.play_next(ctx)) if not e else print(f'Player error: {e}')
)
# Send now playing message
embed = nextcord.Embed(
title="🎵 Now Playing",
description=f"**{next_song.title}**",
color=nextcord.Color.green()
)
if next_song.duration:
embed.add_field(name="Duration", value=next_song.duration_formatted, inline=True)
if next_song.requester:
embed.add_field(name="Requested by", value=next_song.requester.mention, inline=True)
if next_song.thumbnail:
embed.set_thumbnail(url=next_song.thumbnail)
await ctx.send(embed=embed)
except Exception as e:
await ctx.send(f"❌ Error playing {next_song.title}: {e}")
# Try to play next song
await self.play_next(ctx)
# Music bot commands
music_bot = MusicBot(bot)
@bot.command(aliases=['p'])
async def play(ctx, *, search: str):
"""Play a song from YouTube."""
if not ctx.author.voice:
await ctx.send("❌ You must be in a voice channel to use this command.")
return
# Connect to voice if not already connected
if not ctx.voice_client:
await ctx.author.voice.channel.connect()
elif ctx.voice_client.channel != ctx.author.voice.channel:
await ctx.voice_client.move_to(ctx.author.voice.channel)
# Show processing message
processing_msg = await ctx.send("🔍 Searching...")
try:
# Extract song information
data = await YouTubeSource.from_url(search, loop=bot.loop, stream=True)
# Create song object
song = Song(
title=data['title'],
url=data['url'],
duration=data['duration'],
thumbnail=data['thumbnail'],
requester=ctx.author
)
# Add to queue
queue = music_bot.get_queue(ctx.guild.id)
queue.add(song)
# Edit processing message
if ctx.voice_client.is_playing():
embed = nextcord.Embed(
title="📝 Added to Queue",
description=f"**{song.title}**",
color=nextcord.Color.blue()
)
embed.add_field(name="Position in queue", value=len(queue.queue), inline=True)
embed.add_field(name="Duration", value=song.duration_formatted, inline=True)
embed.add_field(name="Requested by", value=ctx.author.mention, inline=True)
if song.thumbnail:
embed.set_thumbnail(url=song.thumbnail)
await processing_msg.edit(content=None, embed=embed)
else:
# Start playing immediately
await processing_msg.delete()
await music_bot.play_next(ctx)
except Exception as e:
await processing_msg.edit(content=f"❌ Error: {e}")
@bot.command()
async def skip(ctx):
"""Skip the current song."""
if not ctx.voice_client or not ctx.voice_client.is_playing():
await ctx.send("❌ Nothing is currently playing.")
return
queue = music_bot.get_queue(ctx.guild.id)
skipped_song = queue.current_song
ctx.voice_client.stop() # This will trigger play_next
embed = nextcord.Embed(
title="⏭️ Song Skipped",
description=f"Skipped **{skipped_song.title if skipped_song else 'Unknown'}**",
color=nextcord.Color.orange()
)
await ctx.send(embed=embed)
@bot.command()
async def queue(ctx, page: int = 1):
"""Show the music queue."""
music_queue = music_bot.get_queue(ctx.guild.id)
if not music_queue.current_song and not music_queue.queue:
await ctx.send("❌ The queue is empty.")
return
embed = nextcord.Embed(
title="🎵 Music Queue",
color=nextcord.Color.blue()
)
# Current song
if music_queue.current_song:
embed.add_field(
name="🎵 Now Playing",
value=f"**{music_queue.current_song.title}**\nRequested by {music_queue.current_song.requester.mention if music_queue.current_song.requester else 'Unknown'}",
inline=False
)
# Queue
queue_list = music_queue.get_queue_list(10)
if queue_list:
queue_text = []
for i, song in enumerate(queue_list, 1):
queue_text.append(f"{i}. **{song.title}** ({song.duration_formatted})")
embed.add_field(
name=f"📝 Up Next ({len(music_queue.queue)} songs)",
value="\n".join(queue_text),
inline=False
)
# Queue stats
if music_queue.queue:
total_duration = sum(song.duration for song in music_queue.queue if song.duration)
hours, remainder = divmod(total_duration, 3600)
minutes, _ = divmod(remainder, 60)
embed.set_footer(text=f"Total queue time: {hours}h {minutes}m | Loop: {music_queue.loop_mode}")
await ctx.send(embed=embed)
@bot.command()
async def pause(ctx):
"""Pause the current song."""
if not ctx.voice_client or not ctx.voice_client.is_playing():
await ctx.send("❌ Nothing is currently playing.")
return
ctx.voice_client.pause()
await ctx.send("⏸️ Paused the music.")
@bot.command()
async def resume(ctx):
"""Resume the paused song."""
if not ctx.voice_client or not ctx.voice_client.is_paused():
await ctx.send("❌ Nothing is currently paused.")
return
ctx.voice_client.resume()
await ctx.send("▶️ Resumed the music.")
@bot.command()
async def stop(ctx):
"""Stop the music and clear the queue."""
if not ctx.voice_client:
await ctx.send("❌ Not connected to a voice channel.")
return
queue = music_bot.get_queue(ctx.guild.id)
cleared_count = queue.clear()
queue.current_song = None
ctx.voice_client.stop()
embed = nextcord.Embed(
title="⏹️ Music Stopped",
description=f"Cleared {cleared_count} songs from the queue.",
color=nextcord.Color.red()
)
await ctx.send(embed=embed)
@bot.command()
async def loop(ctx, mode: str = None):
"""Set loop mode (off, song, queue)."""
queue = music_bot.get_queue(ctx.guild.id)
if mode is None:
await ctx.send(f"Current loop mode: **{queue.loop_mode}**")
return
if mode.lower() not in ['off', 'song', 'queue']:
await ctx.send("❌ Invalid loop mode. Use: `off`, `song`, or `queue`")
return
queue.loop_mode = mode.lower()
loop_emojis = {
'off': '❌',
'song': '🔂',
'queue': '🔁'
}
await ctx.send(f"{loop_emojis[mode.lower()]} Loop mode set to: **{mode}**")
@bot.command()
async def shuffle(ctx):
"""Toggle shuffle mode."""
queue = music_bot.get_queue(ctx.guild.id)
queue.shuffle = not queue.shuffle
status = "enabled" if queue.shuffle else "disabled"
emoji = "🔀" if queue.shuffle else "➡️"
await ctx.send(f"{emoji} Shuffle {status}")
@bot.command()
async def nowplaying(ctx):
"""Show information about the currently playing song."""
queue = music_bot.get_queue(ctx.guild.id)
if not ctx.voice_client or not queue.current_song:
await ctx.send("❌ Nothing is currently playing.")
return
song = queue.current_song
embed = nextcord.Embed(
title="🎵 Now Playing",
description=f"**{song.title}**",
color=nextcord.Color.green(),
url=song.url
)
if song.duration:
embed.add_field(name="Duration", value=song.duration_formatted, inline=True)
if song.requester:
embed.add_field(name="Requested by", value=song.requester.mention, inline=True)
embed.add_field(name="Status", value="Playing ▶️" if ctx.voice_client.is_playing() else "Paused ⏸️", inline=True)
if song.thumbnail:
embed.set_thumbnail(url=song.thumbnail)
# Queue info
if queue.queue:
embed.add_field(name="Next in queue", value=f"{len(queue.queue)} songs", inline=True)
embed.add_field(name="Loop", value=queue.loop_mode.title(), inline=True)
embed.add_field(name="Shuffle", value="On" if queue.shuffle else "Off", inline=True)
await ctx.send(embed=embed)Voice state tracking and event handling for advanced voice features.
# Voice state event handlers
@bot.event
async def on_voice_state_update(
member: nextcord.Member,
before: nextcord.VoiceState,
after: nextcord.VoiceState
):
"""Handle voice state updates."""
# Member joined a voice channel
if before.channel is None and after.channel is not None:
print(f"{member} joined {after.channel.name}")
# Log join to a channel
log_channel = nextcord.utils.get(member.guild.channels, name="voice-logs")
if log_channel:
embed = nextcord.Embed(
title="🔊 Voice Channel Joined",
description=f"{member.mention} joined {after.channel.mention}",
color=nextcord.Color.green(),
timestamp=datetime.now()
)
await log_channel.send(embed=embed)
# Member left a voice channel
elif before.channel is not None and after.channel is None:
print(f"{member} left {before.channel.name}")
# Check if bot should leave empty channel
if before.channel and len(before.channel.members) == 1:
# Only bot left in channel
bot_member = before.channel.guild.me
if bot_member in before.channel.members:
voice_client = nextcord.utils.get(bot.voice_clients, guild=member.guild)
if voice_client and voice_client.channel == before.channel:
await voice_client.disconnect()
print(f"Left {before.channel.name} - no other members")
# Log leave to a channel
log_channel = nextcord.utils.get(member.guild.channels, name="voice-logs")
if log_channel:
embed = nextcord.Embed(
title="🔇 Voice Channel Left",
description=f"{member.mention} left {before.channel.mention}",
color=nextcord.Color.red(),
timestamp=datetime.now()
)
await log_channel.send(embed=embed)
# Member moved between channels
elif before.channel != after.channel:
print(f"{member} moved from {before.channel.name} to {after.channel.name}")
# Follow the user if they're the only one listening
voice_client = nextcord.utils.get(bot.voice_clients, guild=member.guild)
if voice_client and voice_client.channel == before.channel:
# Check if there are other non-bot members in the old channel
human_members = [m for m in before.channel.members if not m.bot]
if len(human_members) == 0 and member in after.channel.members:
await voice_client.move_to(after.channel)
print(f"Followed {member} to {after.channel.name}")
# Member mute/unmute status changed
if before.self_mute != after.self_mute:
status = "muted" if after.self_mute else "unmuted"
print(f"{member} {status} themselves")
# Member deaf/undeaf status changed
if before.self_deaf != after.self_deaf:
status = "deafened" if after.self_deaf else "undeafened"
print(f"{member} {status} themselves")
# Server mute/unmute
if before.mute != after.mute:
status = "server muted" if after.mute else "server unmuted"
print(f"{member} was {status}")
# Server deaf/undeaf
if before.deaf != after.deaf:
status = "server deafened" if after.deaf else "server undeafened"
print(f"{member} was {status}")
# Voice channel management commands
@bot.command()
async def voice_info(ctx, member: nextcord.Member = None):
"""Get voice information about a member."""
target = member or ctx.author
if not target.voice:
await ctx.send(f"❌ {target.display_name} is not in a voice channel.")
return
voice = target.voice
channel = voice.channel
embed = nextcord.Embed(
title=f"🔊 Voice Info: {target.display_name}",
color=nextcord.Color.blue()
)
embed.add_field(name="Channel", value=channel.mention, inline=True)
embed.add_field(name="Channel Type", value=channel.type.name.title(), inline=True)
embed.add_field(name="Members", value=len(channel.members), inline=True)
if hasattr(channel, 'bitrate'):
embed.add_field(name="Bitrate", value=f"{channel.bitrate // 1000} kbps", inline=True)
if hasattr(channel, 'user_limit') and channel.user_limit:
embed.add_field(name="User Limit", value=channel.user_limit, inline=True)
# Voice state info
states = []
if voice.self_mute:
states.append("🔇 Self Muted")
if voice.self_deaf:
states.append("🔇 Self Deafened")
if voice.mute:
states.append("🔇 Server Muted")
if voice.deaf:
states.append("🔇 Server Deafened")
if voice.self_stream:
states.append("📺 Streaming")
if voice.self_video:
states.append("📹 Video On")
if states:
embed.add_field(name="Status", value="\n".join(states), inline=False)
await ctx.send(embed=embed)
@bot.command()
async def voice_stats(ctx):
"""Show voice channel statistics for the server."""
voice_channels = [c for c in ctx.guild.channels if isinstance(c, nextcord.VoiceChannel)]
if not voice_channels:
await ctx.send("❌ This server has no voice channels.")
return
embed = nextcord.Embed(
title=f"🔊 Voice Statistics - {ctx.guild.name}",
color=nextcord.Color.blue()
)
total_members = 0
active_channels = 0
channel_info = []
for channel in voice_channels:
member_count = len(channel.members)
total_members += member_count
if member_count > 0:
active_channels += 1
# Show members in channel
member_names = [m.display_name for m in channel.members[:5]] # Show first 5
member_text = ", ".join(member_names)
if len(channel.members) > 5:
member_text += f" and {len(channel.members) - 5} more"
channel_info.append(f"🔊 **{channel.name}** ({member_count})\n{member_text}")
embed.add_field(
name="Overview",
value=f"**Total Channels:** {len(voice_channels)}\n"
f"**Active Channels:** {active_channels}\n"
f"**Total Members:** {total_members}",
inline=False
)
if channel_info:
# Show first 5 active channels
embed.add_field(
name="Active Channels",
value="\n\n".join(channel_info[:5]) +
(f"\n\n... and {len(channel_info) - 5} more" if len(channel_info) > 5 else ""),
inline=False
)
await ctx.send(embed=embed)
# Voice channel moderation
@bot.command()
async def voice_kick(ctx, member: nextcord.Member, *, reason: str = None):
"""Kick a member from their voice channel."""
if not ctx.author.guild_permissions.move_members:
await ctx.send("❌ You don't have permission to move members.")
return
if not member.voice or not member.voice.channel:
await ctx.send(f"❌ {member.mention} is not in a voice channel.")
return
try:
channel_name = member.voice.channel.name
await member.move_to(None, reason=reason or f"Voice kicked by {ctx.author}")
embed = nextcord.Embed(
title="🔇 Voice Kick",
description=f"{member.mention} has been disconnected from {channel_name}",
color=nextcord.Color.orange()
)
if reason:
embed.add_field(name="Reason", value=reason, inline=False)
embed.set_footer(text=f"Action by {ctx.author}", icon_url=ctx.author.display_avatar.url)
await ctx.send(embed=embed)
except nextcord.Forbidden:
await ctx.send("❌ I don't have permission to disconnect this member.")
except nextcord.HTTPException as e:
await ctx.send(f"❌ Failed to disconnect member: {e}")This comprehensive documentation covers all aspects of nextcord's voice and audio capabilities, providing developers with the tools needed to create sophisticated music bots and voice-enabled Discord applications.
Install with Tessl CLI
npx tessl i tessl/pypi-nextcord