Adding [advanced example from wavelink](https://github.com/PythonistaGuild/Wavelink/blob/master/examples/advanced.py)
This commit is contained in:
parent
7d040a594c
commit
7d25374fd9
1 changed files with 709 additions and 34 deletions
|
@ -16,62 +16,737 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
DEALINGS IN THE SOFTWARE.
|
DEALINGS IN THE SOFTWARE.
|
||||||
"""
|
"""
|
||||||
|
import asyncio
|
||||||
|
import async_timeout
|
||||||
|
import copy
|
||||||
|
import datetime
|
||||||
import discord
|
import discord
|
||||||
from wavelink import Client as wClient
|
import math
|
||||||
from discord.ext import commands
|
import random
|
||||||
|
import re
|
||||||
|
import typing
|
||||||
|
import wavelink
|
||||||
|
from discord.ext import commands, menus
|
||||||
from utils.core import load
|
from utils.core import load
|
||||||
|
|
||||||
def setup(client):
|
def setup(client):
|
||||||
client.add_cog(Music(client))
|
client.add_cog(Music(client))
|
||||||
|
|
||||||
class Music(commands.Cog):
|
|
||||||
"""Commandes relatives à la musique."""
|
# URL matching REGEX...
|
||||||
def __init__(self, client):
|
URL_REG = re.compile(r'https?://(?:www\.)?.+')
|
||||||
self.client = client
|
|
||||||
|
|
||||||
|
class NoChannelProvided(commands.CommandError):
|
||||||
|
"""Error raised when no suitable voice channel was supplied."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class IncorrectChannelError(commands.CommandError):
|
||||||
|
"""Error raised when commands are issued outside of the players session channel."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Track(wavelink.Track):
|
||||||
|
"""Wavelink Track object with a requester attribute."""
|
||||||
|
|
||||||
|
__slots__ = ('requester', )
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args)
|
||||||
|
|
||||||
|
self.requester = kwargs.get('requester')
|
||||||
|
|
||||||
|
|
||||||
|
class Player(wavelink.Player):
|
||||||
|
"""Custom wavelink Player class."""
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
self.context: commands.Context = kwargs.get('context', None)
|
||||||
|
if self.context:
|
||||||
|
self.dj: discord.Member = self.context.author
|
||||||
|
|
||||||
|
self.queue = asyncio.Queue()
|
||||||
|
self.controller = None
|
||||||
|
|
||||||
|
self.waiting = False
|
||||||
|
self.updating = False
|
||||||
|
|
||||||
|
self.pause_votes = set()
|
||||||
|
self.resume_votes = set()
|
||||||
|
self.skip_votes = set()
|
||||||
|
self.shuffle_votes = set()
|
||||||
|
self.stop_votes = set()
|
||||||
|
|
||||||
|
async def do_next(self) -> None:
|
||||||
|
if self.is_playing or self.waiting:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Clear the votes for a new song...
|
||||||
|
self.pause_votes.clear()
|
||||||
|
self.resume_votes.clear()
|
||||||
|
self.skip_votes.clear()
|
||||||
|
self.shuffle_votes.clear()
|
||||||
|
self.stop_votes.clear()
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.waiting = True
|
||||||
|
with async_timeout.timeout(300):
|
||||||
|
track = await self.queue.get()
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
# No music has been played for 5 minutes, cleanup and disconnect...
|
||||||
|
return await self.teardown()
|
||||||
|
|
||||||
|
await self.play(track)
|
||||||
|
self.waiting = False
|
||||||
|
|
||||||
|
# Invoke our players controller...
|
||||||
|
await self.invoke_controller()
|
||||||
|
|
||||||
|
async def invoke_controller(self) -> None:
|
||||||
|
"""Method which updates or sends a new player controller."""
|
||||||
|
if self.updating:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.updating = True
|
||||||
|
|
||||||
|
if not self.controller:
|
||||||
|
self.controller = InteractiveController(embed=self.build_embed(), player=self)
|
||||||
|
await self.controller.start(self.context)
|
||||||
|
|
||||||
|
elif not await self.is_position_fresh():
|
||||||
|
try:
|
||||||
|
await self.controller.message.delete()
|
||||||
|
except discord.HTTPException:
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.controller.stop()
|
||||||
|
|
||||||
|
self.controller = InteractiveController(embed=self.build_embed(), player=self)
|
||||||
|
await self.controller.start(self.context)
|
||||||
|
|
||||||
|
else:
|
||||||
|
embed = self.build_embed()
|
||||||
|
await self.controller.message.edit(content=None, embed=embed)
|
||||||
|
|
||||||
|
self.updating = False
|
||||||
|
|
||||||
|
def build_embed(self) -> typing.Optional[discord.Embed]:
|
||||||
|
"""Method which builds our players controller embed."""
|
||||||
|
track = self.current
|
||||||
|
if not track:
|
||||||
|
return
|
||||||
|
|
||||||
|
channel = self.bot.get_channel(int(self.channel_id))
|
||||||
|
qsize = self.queue.qsize()
|
||||||
|
|
||||||
|
embed = discord.Embed(title=f'Music Controller | {channel.name}', colour=0xebb145)
|
||||||
|
embed.description = f'Now Playing:\n**`{track.title}`**\n\n'
|
||||||
|
embed.set_thumbnail(url=track.thumb)
|
||||||
|
|
||||||
|
embed.add_field(name='Duration', value=str(datetime.timedelta(milliseconds=int(track.length))))
|
||||||
|
embed.add_field(name='Queue Length', value=str(qsize))
|
||||||
|
embed.add_field(name='Volume', value=f'**`{self.volume}%`**')
|
||||||
|
embed.add_field(name='Requested By', value=track.requester.mention)
|
||||||
|
embed.add_field(name='DJ', value=self.dj.mention)
|
||||||
|
embed.add_field(name='Video URL', value=f'[Click Here!]({track.uri})')
|
||||||
|
|
||||||
|
return embed
|
||||||
|
|
||||||
|
async def is_position_fresh(self) -> bool:
|
||||||
|
"""Method which checks whether the player controller should be remade or updated."""
|
||||||
|
try:
|
||||||
|
async for message in self.context.channel.history(limit=5):
|
||||||
|
if message.id == self.controller.message.id:
|
||||||
|
return True
|
||||||
|
except (discord.HTTPException, AttributeError):
|
||||||
|
return False
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def teardown(self):
|
||||||
|
"""Clear internal states, remove player controller and disconnect."""
|
||||||
|
try:
|
||||||
|
await self.controller.message.delete()
|
||||||
|
except discord.HTTPException:
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.controller.stop()
|
||||||
|
|
||||||
|
try:
|
||||||
|
await self.destroy()
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class InteractiveController(menus.Menu):
|
||||||
|
"""The Players interactive controller menu class."""
|
||||||
|
|
||||||
|
def __init__(self, *, embed: discord.Embed, player: Player):
|
||||||
|
super().__init__(timeout=None)
|
||||||
|
|
||||||
|
self.embed = embed
|
||||||
|
self.player = player
|
||||||
|
|
||||||
|
def update_context(self, payload: discord.RawReactionActionEvent):
|
||||||
|
"""Update our context with the user who reacted."""
|
||||||
|
ctx = copy.copy(self.ctx)
|
||||||
|
ctx.author = payload.member
|
||||||
|
|
||||||
|
return ctx
|
||||||
|
|
||||||
|
def reaction_check(self, payload: discord.RawReactionActionEvent):
|
||||||
|
if payload.event_type == 'REACTION_REMOVE':
|
||||||
|
return False
|
||||||
|
|
||||||
|
if not payload.member:
|
||||||
|
return False
|
||||||
|
if payload.member.bot:
|
||||||
|
return False
|
||||||
|
if payload.message_id != self.message.id:
|
||||||
|
return False
|
||||||
|
if payload.member not in self.bot.get_channel(int(self.player.channel_id)).members:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return payload.emoji in self.buttons
|
||||||
|
|
||||||
|
async def send_initial_message(self, ctx: commands.Context, channel: discord.TextChannel) -> discord.Message:
|
||||||
|
return await channel.send(embed=self.embed)
|
||||||
|
|
||||||
|
@menus.button(emoji='\u25B6')
|
||||||
|
async def resume_command(self, payload: discord.RawReactionActionEvent):
|
||||||
|
"""Resume button."""
|
||||||
|
ctx = self.update_context(payload)
|
||||||
|
|
||||||
|
command = self.bot.get_command('resume')
|
||||||
|
ctx.command = command
|
||||||
|
|
||||||
|
await self.bot.invoke(ctx)
|
||||||
|
|
||||||
|
@menus.button(emoji='\u23F8')
|
||||||
|
async def pause_command(self, payload: discord.RawReactionActionEvent):
|
||||||
|
"""Pause button"""
|
||||||
|
ctx = self.update_context(payload)
|
||||||
|
|
||||||
|
command = self.bot.get_command('pause')
|
||||||
|
ctx.command = command
|
||||||
|
|
||||||
|
await self.bot.invoke(ctx)
|
||||||
|
|
||||||
|
@menus.button(emoji='\u23F9')
|
||||||
|
async def stop_command(self, payload: discord.RawReactionActionEvent):
|
||||||
|
"""Stop button."""
|
||||||
|
ctx = self.update_context(payload)
|
||||||
|
|
||||||
|
command = self.bot.get_command('stop')
|
||||||
|
ctx.command = command
|
||||||
|
|
||||||
|
await self.bot.invoke(ctx)
|
||||||
|
|
||||||
|
@menus.button(emoji='\u23ED')
|
||||||
|
async def skip_command(self, payload: discord.RawReactionActionEvent):
|
||||||
|
"""Skip button."""
|
||||||
|
ctx = self.update_context(payload)
|
||||||
|
|
||||||
|
command = self.bot.get_command('skip')
|
||||||
|
ctx.command = command
|
||||||
|
|
||||||
|
await self.bot.invoke(ctx)
|
||||||
|
|
||||||
|
@menus.button(emoji='\U0001F500')
|
||||||
|
async def shuffle_command(self, payload: discord.RawReactionActionEvent):
|
||||||
|
"""Shuffle button."""
|
||||||
|
ctx = self.update_context(payload)
|
||||||
|
|
||||||
|
command = self.bot.get_command('shuffle')
|
||||||
|
ctx.command = command
|
||||||
|
|
||||||
|
await self.bot.invoke(ctx)
|
||||||
|
|
||||||
|
@menus.button(emoji='\u2795')
|
||||||
|
async def volup_command(self, payload: discord.RawReactionActionEvent):
|
||||||
|
"""Volume up button"""
|
||||||
|
ctx = self.update_context(payload)
|
||||||
|
|
||||||
|
command = self.bot.get_command('vol_up')
|
||||||
|
ctx.command = command
|
||||||
|
|
||||||
|
await self.bot.invoke(ctx)
|
||||||
|
|
||||||
|
@menus.button(emoji='\u2796')
|
||||||
|
async def voldown_command(self, payload: discord.RawReactionActionEvent):
|
||||||
|
"""Volume down button."""
|
||||||
|
ctx = self.update_context(payload)
|
||||||
|
|
||||||
|
command = self.bot.get_command('vol_down')
|
||||||
|
ctx.command = command
|
||||||
|
|
||||||
|
await self.bot.invoke(ctx)
|
||||||
|
|
||||||
|
@menus.button(emoji='\U0001F1F6')
|
||||||
|
async def queue_command(self, payload: discord.RawReactionActionEvent):
|
||||||
|
"""Player queue button."""
|
||||||
|
ctx = self.update_context(payload)
|
||||||
|
|
||||||
|
command = self.bot.get_command('queue')
|
||||||
|
ctx.command = command
|
||||||
|
|
||||||
|
await self.bot.invoke(ctx)
|
||||||
|
|
||||||
|
|
||||||
|
class PaginatorSource(menus.ListPageSource):
|
||||||
|
"""Player queue paginator class."""
|
||||||
|
|
||||||
|
def __init__(self, entries, *, per_page=8):
|
||||||
|
super().__init__(entries, per_page=per_page)
|
||||||
|
|
||||||
|
async def format_page(self, menu: menus.Menu, page):
|
||||||
|
embed = discord.Embed(title='Coming Up...', colour=0x4f0321)
|
||||||
|
embed.description = '\n'.join(f'`{index}. {title}`' for index, title in enumerate(page, 1))
|
||||||
|
|
||||||
|
return embed
|
||||||
|
|
||||||
|
def is_paginating(self):
|
||||||
|
# We always want to embed even on 1 page of results...
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class Music(commands.Cog, wavelink.WavelinkMixin):
|
||||||
|
"""Music Cog."""
|
||||||
|
|
||||||
|
def __init__(self, bot: commands.Bot):
|
||||||
|
self.bot = bot
|
||||||
self.keys = load(["REGION_DISCORD", "DOCKER_KASSOUBOT"])
|
self.keys = load(["REGION_DISCORD", "DOCKER_KASSOUBOT"])
|
||||||
|
|
||||||
if not hasattr(client, 'wavelink'):
|
if not hasattr(bot, 'wavelink'):
|
||||||
self.client.wavelink = wClient(bot = self.client)
|
self.bot.wavelink = wavelink.Client(bot = bot)
|
||||||
|
|
||||||
if self.keys["DOCKER_KASSOUBOT"] == True:
|
if self.keys["DOCKER_KASSOUBOT"] == True:
|
||||||
self.url = "lavalink" # name of the service, working fine with the DNS in docker
|
self.url = "lavalink" # name of the service, working fine with the DNS in docker
|
||||||
else:
|
else:
|
||||||
self.url = "localhost" # localhost
|
self.url = "localhost" # localhost
|
||||||
|
|
||||||
self.client.loop.create_task(self.start_nodes())
|
self.bot.loop.create_task(self.start_nodes())
|
||||||
|
|
||||||
async def start_nodes(self):
|
async def start_nodes(self) -> None:
|
||||||
await self.client.wait_until_ready()
|
"""Connect and intiate nodes."""
|
||||||
|
await self.bot.wait_until_ready()
|
||||||
|
|
||||||
await self.client.wavelink.initiate_node(host = f"{self.url}",
|
if self.bot.wavelink.nodes:
|
||||||
port = 2333,
|
previous = self.bot.wavelink.nodes.copy()
|
||||||
rest_uri = f"http://{self.url}:2333",
|
|
||||||
password = "youshallnotpass",
|
|
||||||
identifier = "PROD",
|
|
||||||
region = self.keys["REGION_DISCORD"])
|
|
||||||
|
|
||||||
@commands.command(name='connect')
|
for node in previous.values():
|
||||||
async def connect_(self, ctx, *, channel: discord.VoiceChannel = None):
|
await node.destroy()
|
||||||
|
|
||||||
|
nodes = {'MAIN': {'host': f'{self.url}',
|
||||||
|
'port': 2333,
|
||||||
|
'rest_uri': f'http://{self.url}:2333',
|
||||||
|
'password': 'youshallnotpass',
|
||||||
|
'identifier': 'MAIN',
|
||||||
|
'region': self.keys["REGION_DISCORD"]
|
||||||
|
}}
|
||||||
|
|
||||||
|
for n in nodes.values():
|
||||||
|
await self.bot.wavelink.initiate_node(**n)
|
||||||
|
|
||||||
|
@wavelink.WavelinkMixin.listener()
|
||||||
|
async def on_node_ready(self, node: wavelink.Node):
|
||||||
|
print(f'Node {node.identifier} is ready!')
|
||||||
|
|
||||||
|
@wavelink.WavelinkMixin.listener('on_track_stuck')
|
||||||
|
@wavelink.WavelinkMixin.listener('on_track_end')
|
||||||
|
@wavelink.WavelinkMixin.listener('on_track_exception')
|
||||||
|
async def on_player_stop(self, node: wavelink.Node, payload):
|
||||||
|
await payload.player.do_next()
|
||||||
|
|
||||||
|
@commands.Cog.listener()
|
||||||
|
async def on_voice_state_update(self, member: discord.Member, before: discord.VoiceState, after: discord.VoiceState):
|
||||||
|
if member.bot:
|
||||||
|
return
|
||||||
|
|
||||||
|
player: Player = self.bot.wavelink.get_player(member.guild.id, cls=Player)
|
||||||
|
|
||||||
|
if not player.channel_id or not player.context:
|
||||||
|
player.node.players.pop(member.guild.id)
|
||||||
|
return
|
||||||
|
|
||||||
|
channel = self.bot.get_channel(int(player.channel_id))
|
||||||
|
|
||||||
|
if member == player.dj and after.channel is None:
|
||||||
|
for m in channel.members:
|
||||||
|
if m.bot:
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
player.dj = m
|
||||||
|
return
|
||||||
|
|
||||||
|
elif after.channel == channel and player.dj not in channel.members:
|
||||||
|
player.dj = member
|
||||||
|
|
||||||
|
async def cog_command_error(self, ctx: commands.Context, error: Exception):
|
||||||
|
"""Cog wide error handler."""
|
||||||
|
if isinstance(error, IncorrectChannelError):
|
||||||
|
return
|
||||||
|
|
||||||
|
if isinstance(error, NoChannelProvided):
|
||||||
|
return await ctx.send('You must be in a voice channel or provide one to connect to.')
|
||||||
|
|
||||||
|
async def cog_check(self, ctx: commands.Context):
|
||||||
|
"""Cog wide check, which disallows commands in DMs."""
|
||||||
|
if not ctx.guild:
|
||||||
|
await ctx.send('Music commands are not available in Private Messages.')
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
async def cog_before_invoke(self, ctx: commands.Context):
|
||||||
|
"""Coroutine called before command invocation.
|
||||||
|
We mainly just want to check whether the user is in the players controller channel.
|
||||||
|
"""
|
||||||
|
player: Player = self.bot.wavelink.get_player(ctx.guild.id, cls=Player, context=ctx)
|
||||||
|
|
||||||
|
if player.context:
|
||||||
|
if player.context.channel != ctx.channel:
|
||||||
|
await ctx.send(f'{ctx.author.mention}, you must be in {player.context.channel.mention} for this session.')
|
||||||
|
raise IncorrectChannelError
|
||||||
|
|
||||||
|
if ctx.command.name == 'connect' and not player.context:
|
||||||
|
return
|
||||||
|
elif self.is_privileged(ctx):
|
||||||
|
return
|
||||||
|
|
||||||
|
if not player.channel_id:
|
||||||
|
return
|
||||||
|
|
||||||
|
channel = self.bot.get_channel(int(player.channel_id))
|
||||||
if not channel:
|
if not channel:
|
||||||
try:
|
return
|
||||||
channel = ctx.author.voice.channel
|
|
||||||
except AttributeError:
|
if player.is_connected:
|
||||||
raise discord.DiscordException("Aucun channel à rejoindre. Veuillez soit spécifier un canal valide, soit en rejoindre un.")
|
if ctx.author not in channel.members:
|
||||||
|
await ctx.send(f'{ctx.author.mention}, you must be in `{channel.name}` to use voice commands.')
|
||||||
|
raise IncorrectChannelError
|
||||||
|
|
||||||
|
def required(self, ctx: commands.Context):
|
||||||
|
"""Method which returns required votes based on amount of members in a channel."""
|
||||||
|
player: Player = self.bot.wavelink.get_player(guild_id=ctx.guild.id, cls=Player, context=ctx)
|
||||||
|
channel = self.bot.get_channel(int(player.channel_id))
|
||||||
|
required = math.ceil((len(channel.members) - 1) / 2.5)
|
||||||
|
|
||||||
|
if ctx.command.name == 'stop':
|
||||||
|
if len(channel.members) == 3:
|
||||||
|
required = 2
|
||||||
|
|
||||||
|
return required
|
||||||
|
|
||||||
|
def is_privileged(self, ctx: commands.Context):
|
||||||
|
"""Check whether the user is an Admin or DJ."""
|
||||||
|
player: Player = self.bot.wavelink.get_player(guild_id=ctx.guild.id, cls=Player, context=ctx)
|
||||||
|
|
||||||
|
return player.dj == ctx.author or ctx.author.guild_permissions.kick_members
|
||||||
|
|
||||||
|
@commands.command()
|
||||||
|
async def connect(self, ctx: commands.Context, *, channel: typing.Union[discord.VoiceChannel, discord.StageChannel] = None):
|
||||||
|
"""Connect to a voice channel."""
|
||||||
|
player: Player = self.bot.wavelink.get_player(guild_id=ctx.guild.id, cls=Player, context=ctx)
|
||||||
|
|
||||||
|
if player.is_connected:
|
||||||
|
return
|
||||||
|
|
||||||
|
channel = getattr(ctx.author.voice, 'channel', channel)
|
||||||
|
if channel is None:
|
||||||
|
raise NoChannelProvided
|
||||||
|
|
||||||
player = self.client.wavelink.get_player(ctx.guild.id)
|
|
||||||
await ctx.send(f"Connexion à **`{channel.name}`**")
|
|
||||||
await player.connect(channel.id)
|
await player.connect(channel.id)
|
||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
async def play(self, ctx, *, query: str):
|
async def play(self, ctx: commands.Context, *, query: str):
|
||||||
tracks = await self.client.wavelink.get_tracks(f"ytsearch:{query}")
|
"""Play or queue a song with the given query."""
|
||||||
|
player: Player = self.bot.wavelink.get_player(guild_id=ctx.guild.id, cls=Player, context=ctx)
|
||||||
|
|
||||||
if not tracks:
|
|
||||||
return await ctx.send("Je n'ai pas trouvé la musique demandé.")
|
|
||||||
|
|
||||||
player = self.client.wavelink.get_player(ctx.guild.id)
|
|
||||||
if not player.is_connected:
|
if not player.is_connected:
|
||||||
await ctx.invoke(self.connect_)
|
await ctx.invoke(self.connect)
|
||||||
|
|
||||||
await ctx.send(f"Ajout de {str(tracks[0])} à la file d'attente.")
|
query = query.strip('<>')
|
||||||
await player.play(tracks[0])
|
if not URL_REG.match(query):
|
||||||
|
query = f'ytsearch:{query}'
|
||||||
|
|
||||||
|
tracks = await self.bot.wavelink.get_tracks(query)
|
||||||
|
if not tracks:
|
||||||
|
return await ctx.send('No songs were found with that query. Please try again.', delete_after=15)
|
||||||
|
|
||||||
|
if isinstance(tracks, wavelink.TrackPlaylist):
|
||||||
|
for track in tracks.tracks:
|
||||||
|
track = Track(track.id, track.info, requester=ctx.author)
|
||||||
|
await player.queue.put(track)
|
||||||
|
|
||||||
|
await ctx.send(f'```ini\nAdded the playlist {tracks.data["playlistInfo"]["name"]}'
|
||||||
|
f' with {len(tracks.tracks)} songs to the queue.\n```', delete_after=15)
|
||||||
|
else:
|
||||||
|
track = Track(tracks[0].id, tracks[0].info, requester=ctx.author)
|
||||||
|
await ctx.send(f'```ini\nAdded {track.title} to the Queue\n```', delete_after=15)
|
||||||
|
await player.queue.put(track)
|
||||||
|
|
||||||
|
if not player.is_playing:
|
||||||
|
await player.do_next()
|
||||||
|
|
||||||
|
@commands.command()
|
||||||
|
async def pause(self, ctx: commands.Context):
|
||||||
|
"""Pause the currently playing song."""
|
||||||
|
player: Player = self.bot.wavelink.get_player(guild_id=ctx.guild.id, cls=Player, context=ctx)
|
||||||
|
|
||||||
|
if player.is_paused or not player.is_connected:
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.is_privileged(ctx):
|
||||||
|
await ctx.send('An admin or DJ has paused the player.', delete_after=10)
|
||||||
|
player.pause_votes.clear()
|
||||||
|
|
||||||
|
return await player.set_pause(True)
|
||||||
|
|
||||||
|
required = self.required(ctx)
|
||||||
|
player.pause_votes.add(ctx.author)
|
||||||
|
|
||||||
|
if len(player.pause_votes) >= required:
|
||||||
|
await ctx.send('Vote to pause passed. Pausing player.', delete_after=10)
|
||||||
|
player.pause_votes.clear()
|
||||||
|
await player.set_pause(True)
|
||||||
|
else:
|
||||||
|
await ctx.send(f'{ctx.author.mention} has voted to pause the player.', delete_after=15)
|
||||||
|
|
||||||
|
@commands.command()
|
||||||
|
async def resume(self, ctx: commands.Context):
|
||||||
|
"""Resume a currently paused player."""
|
||||||
|
player: Player = self.bot.wavelink.get_player(guild_id=ctx.guild.id, cls=Player, context=ctx)
|
||||||
|
|
||||||
|
if not player.is_paused or not player.is_connected:
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.is_privileged(ctx):
|
||||||
|
await ctx.send('An admin or DJ has resumed the player.', delete_after=10)
|
||||||
|
player.resume_votes.clear()
|
||||||
|
|
||||||
|
return await player.set_pause(False)
|
||||||
|
|
||||||
|
required = self.required(ctx)
|
||||||
|
player.resume_votes.add(ctx.author)
|
||||||
|
|
||||||
|
if len(player.resume_votes) >= required:
|
||||||
|
await ctx.send('Vote to resume passed. Resuming player.', delete_after=10)
|
||||||
|
player.resume_votes.clear()
|
||||||
|
await player.set_pause(False)
|
||||||
|
else:
|
||||||
|
await ctx.send(f'{ctx.author.mention} has voted to resume the player.', delete_after=15)
|
||||||
|
|
||||||
|
@commands.command()
|
||||||
|
async def skip(self, ctx: commands.Context):
|
||||||
|
"""Skip the currently playing song."""
|
||||||
|
player: Player = self.bot.wavelink.get_player(guild_id=ctx.guild.id, cls=Player, context=ctx)
|
||||||
|
|
||||||
|
if not player.is_connected:
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.is_privileged(ctx):
|
||||||
|
await ctx.send('An admin or DJ has skipped the song.', delete_after=10)
|
||||||
|
player.skip_votes.clear()
|
||||||
|
|
||||||
|
return await player.stop()
|
||||||
|
|
||||||
|
if ctx.author == player.current.requester:
|
||||||
|
await ctx.send('The song requester has skipped the song.', delete_after=10)
|
||||||
|
player.skip_votes.clear()
|
||||||
|
|
||||||
|
return await player.stop()
|
||||||
|
|
||||||
|
required = self.required(ctx)
|
||||||
|
player.skip_votes.add(ctx.author)
|
||||||
|
|
||||||
|
if len(player.skip_votes) >= required:
|
||||||
|
await ctx.send('Vote to skip passed. Skipping song.', delete_after=10)
|
||||||
|
player.skip_votes.clear()
|
||||||
|
await player.stop()
|
||||||
|
else:
|
||||||
|
await ctx.send(f'{ctx.author.mention} has voted to skip the song.', delete_after=15)
|
||||||
|
|
||||||
|
@commands.command()
|
||||||
|
async def stop(self, ctx: commands.Context):
|
||||||
|
"""Stop the player and clear all internal states."""
|
||||||
|
player: Player = self.bot.wavelink.get_player(guild_id=ctx.guild.id, cls=Player, context=ctx)
|
||||||
|
|
||||||
|
if not player.is_connected:
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.is_privileged(ctx):
|
||||||
|
await ctx.send('An admin or DJ has stopped the player.', delete_after=10)
|
||||||
|
return await player.teardown()
|
||||||
|
|
||||||
|
required = self.required(ctx)
|
||||||
|
player.stop_votes.add(ctx.author)
|
||||||
|
|
||||||
|
if len(player.stop_votes) >= required:
|
||||||
|
await ctx.send('Vote to stop passed. Stopping the player.', delete_after=10)
|
||||||
|
await player.teardown()
|
||||||
|
else:
|
||||||
|
await ctx.send(f'{ctx.author.mention} has voted to stop the player.', delete_after=15)
|
||||||
|
|
||||||
|
@commands.command(aliases=['v', 'vol'])
|
||||||
|
async def volume(self, ctx: commands.Context, *, vol: int):
|
||||||
|
"""Change the players volume, between 1 and 100."""
|
||||||
|
player: Player = self.bot.wavelink.get_player(guild_id=ctx.guild.id, cls=Player, context=ctx)
|
||||||
|
|
||||||
|
if not player.is_connected:
|
||||||
|
return
|
||||||
|
|
||||||
|
if not self.is_privileged(ctx):
|
||||||
|
return await ctx.send('Only the DJ or admins may change the volume.')
|
||||||
|
|
||||||
|
if not 0 < vol < 101:
|
||||||
|
return await ctx.send('Please enter a value between 1 and 100.')
|
||||||
|
|
||||||
|
await player.set_volume(vol)
|
||||||
|
await ctx.send(f'Set the volume to **{vol}**%', delete_after=7)
|
||||||
|
|
||||||
|
@commands.command(aliases=['mix'])
|
||||||
|
async def shuffle(self, ctx: commands.Context):
|
||||||
|
"""Shuffle the players queue."""
|
||||||
|
player: Player = self.bot.wavelink.get_player(guild_id=ctx.guild.id, cls=Player, context=ctx)
|
||||||
|
|
||||||
|
if not player.is_connected:
|
||||||
|
return
|
||||||
|
|
||||||
|
if player.queue.qsize() < 3:
|
||||||
|
return await ctx.send('Add more songs to the queue before shuffling.', delete_after=15)
|
||||||
|
|
||||||
|
if self.is_privileged(ctx):
|
||||||
|
await ctx.send('An admin or DJ has shuffled the playlist.', delete_after=10)
|
||||||
|
player.shuffle_votes.clear()
|
||||||
|
return random.shuffle(player.queue._queue)
|
||||||
|
|
||||||
|
required = self.required(ctx)
|
||||||
|
player.shuffle_votes.add(ctx.author)
|
||||||
|
|
||||||
|
if len(player.shuffle_votes) >= required:
|
||||||
|
await ctx.send('Vote to shuffle passed. Shuffling the playlist.', delete_after=10)
|
||||||
|
player.shuffle_votes.clear()
|
||||||
|
random.shuffle(player.queue._queue)
|
||||||
|
else:
|
||||||
|
await ctx.send(f'{ctx.author.mention} has voted to shuffle the playlist.', delete_after=15)
|
||||||
|
|
||||||
|
@commands.command(hidden=True)
|
||||||
|
async def vol_up(self, ctx: commands.Context):
|
||||||
|
"""Command used for volume up button."""
|
||||||
|
player: Player = self.bot.wavelink.get_player(guild_id=ctx.guild.id, cls=Player, context=ctx)
|
||||||
|
|
||||||
|
if not player.is_connected or not self.is_privileged(ctx):
|
||||||
|
return
|
||||||
|
|
||||||
|
vol = int(math.ceil((player.volume + 10) / 10)) * 10
|
||||||
|
|
||||||
|
if vol > 100:
|
||||||
|
vol = 100
|
||||||
|
await ctx.send('Maximum volume reached', delete_after=7)
|
||||||
|
|
||||||
|
await player.set_volume(vol)
|
||||||
|
|
||||||
|
@commands.command(hidden=True)
|
||||||
|
async def vol_down(self, ctx: commands.Context):
|
||||||
|
"""Command used for volume down button."""
|
||||||
|
player: Player = self.bot.wavelink.get_player(guild_id=ctx.guild.id, cls=Player, context=ctx)
|
||||||
|
|
||||||
|
if not player.is_connected or not self.is_privileged(ctx):
|
||||||
|
return
|
||||||
|
|
||||||
|
vol = int(math.ceil((player.volume - 10) / 10)) * 10
|
||||||
|
|
||||||
|
if vol < 0:
|
||||||
|
vol = 0
|
||||||
|
await ctx.send('Player is currently muted', delete_after=10)
|
||||||
|
|
||||||
|
await player.set_volume(vol)
|
||||||
|
|
||||||
|
@commands.command(aliases=['eq'])
|
||||||
|
async def equalizer(self, ctx: commands.Context, *, equalizer: str):
|
||||||
|
"""Change the players equalizer."""
|
||||||
|
player: Player = self.bot.wavelink.get_player(guild_id=ctx.guild.id, cls=Player, context=ctx)
|
||||||
|
|
||||||
|
if not player.is_connected:
|
||||||
|
return
|
||||||
|
|
||||||
|
if not self.is_privileged(ctx):
|
||||||
|
return await ctx.send('Only the DJ or admins may change the equalizer.')
|
||||||
|
|
||||||
|
eqs = {'flat': wavelink.Equalizer.flat(),
|
||||||
|
'boost': wavelink.Equalizer.boost(),
|
||||||
|
'metal': wavelink.Equalizer.metal(),
|
||||||
|
'piano': wavelink.Equalizer.piano()}
|
||||||
|
|
||||||
|
eq = eqs.get(equalizer.lower(), None)
|
||||||
|
|
||||||
|
if not eq:
|
||||||
|
joined = "\n".join(eqs.keys())
|
||||||
|
return await ctx.send(f'Invalid EQ provided. Valid EQs:\n\n{joined}')
|
||||||
|
|
||||||
|
await ctx.send(f'Successfully changed equalizer to {equalizer}', delete_after=15)
|
||||||
|
await player.set_eq(eq)
|
||||||
|
|
||||||
|
@commands.command(aliases=['q', 'que'])
|
||||||
|
async def queue(self, ctx: commands.Context):
|
||||||
|
"""Display the players queued songs."""
|
||||||
|
player: Player = self.bot.wavelink.get_player(guild_id=ctx.guild.id, cls=Player, context=ctx)
|
||||||
|
|
||||||
|
if not player.is_connected:
|
||||||
|
return
|
||||||
|
|
||||||
|
if player.queue.qsize() == 0:
|
||||||
|
return await ctx.send('There are no more songs in the queue.', delete_after=15)
|
||||||
|
|
||||||
|
entries = [track.title for track in player.queue._queue]
|
||||||
|
pages = PaginatorSource(entries=entries)
|
||||||
|
paginator = menus.MenuPages(source=pages, timeout=None, delete_message_after=True)
|
||||||
|
|
||||||
|
await paginator.start(ctx)
|
||||||
|
|
||||||
|
@commands.command(aliases=['np', 'now_playing', 'current'])
|
||||||
|
async def nowplaying(self, ctx: commands.Context):
|
||||||
|
"""Update the player controller."""
|
||||||
|
player: Player = self.bot.wavelink.get_player(guild_id=ctx.guild.id, cls=Player, context=ctx)
|
||||||
|
|
||||||
|
if not player.is_connected:
|
||||||
|
return
|
||||||
|
|
||||||
|
await player.invoke_controller()
|
||||||
|
|
||||||
|
@commands.command(aliases=['swap'])
|
||||||
|
async def swap_dj(self, ctx: commands.Context, *, member: discord.Member = None):
|
||||||
|
"""Swap the current DJ to another member in the voice channel."""
|
||||||
|
player: Player = self.bot.wavelink.get_player(guild_id=ctx.guild.id, cls=Player, context=ctx)
|
||||||
|
|
||||||
|
if not player.is_connected:
|
||||||
|
return
|
||||||
|
|
||||||
|
if not self.is_privileged(ctx):
|
||||||
|
return await ctx.send('Only admins and the DJ may use this command.', delete_after=15)
|
||||||
|
|
||||||
|
members = self.bot.get_channel(int(player.channel_id)).members
|
||||||
|
|
||||||
|
if member and member not in members:
|
||||||
|
return await ctx.send(f'{member} is not currently in voice, so can not be a DJ.', delete_after=15)
|
||||||
|
|
||||||
|
if member and member == player.dj:
|
||||||
|
return await ctx.send('Cannot swap DJ to the current DJ... :)', delete_after=15)
|
||||||
|
|
||||||
|
if len(members) <= 2:
|
||||||
|
return await ctx.send('No more members to swap to.', delete_after=15)
|
||||||
|
|
||||||
|
if member:
|
||||||
|
player.dj = member
|
||||||
|
return await ctx.send(f'{member.mention} is now the DJ.')
|
||||||
|
|
||||||
|
for m in members:
|
||||||
|
if m == player.dj or m.bot:
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
player.dj = m
|
||||||
|
return await ctx.send(f'{member.mention} is now the DJ.')
|
||||||
|
|
Reference in a new issue