diff --git a/bot.py b/bot.py index f475c2b..42dab77 100644 --- a/bot.py +++ b/bot.py @@ -12,8 +12,7 @@ from cogs.utils.settings import Settings initial_extension = [ 'cogs.cogs', 'cogs.botsettings', - 'cogs.misc', - 'cogs.music' + 'cogs.misc' ] @@ -84,6 +83,8 @@ class Bot(commands.Bot): f' in {len(self.guilds)} servers.') print(f'Version: {discord.__version__}\n') + self.load_extension('cogs.music') + await self.change_presence(activity=discord.Game(type=0, name=config["playing"]), status=discord.Status.online) diff --git a/cogs/music.py b/cogs/music.py index 7a15d7a..ac292a9 100644 --- a/cogs/music.py +++ b/cogs/music.py @@ -1,75 +1,66 @@ -#!/usr/bin/env python3 - """ -This is an example code that shows how you would setup a simple music bot for Lavalink v3. -This example is only compatible with the discord.py rewrite branch. -Because of the F-Strings, you also must have Python 3.6 or higher installed. +This is an example cog that shows how you would make use of Lavalink.py. +This example cog requires that you have python 3.6 or higher due to the f-strings. """ - -import logging import math import re -import typing +import asyncio + +import discord +import lavalink +from discord.ext import commands import time -import asyncio -import discord -from discord.ext import commands -import lavalink - from .utils import checks, RoxUtils -from .utils.mixplayer.mixplayer import MixPlayer +from .utils.mixplayer import MixPlayer +from typing import Optional + from .utils.embedscroller import QueueScroller from lavasettings import * time_rx = re.compile('[0-9]+') -url_rx = re.compile('https?:\/\/(?:www\.)?.+') +url_rx = re.compile('https?:\\/\\/(?:www\\.)?.+') class Music: def __init__(self, bot): - self.bot = bot - if not hasattr(bot, 'lavalink'): - lavalink.Client(bot=bot, password='youshallnotpass', - loop=bot.loop, log_level=logging.INFO, - host=host, ws_port=ws_port, - rest_port=rest_port, - player=MixPlayer) - self.bot.lavalink.register_hook(self._track_hook) + if not hasattr(bot, 'lavalink'): # This ensures the client isn't overwritten during cog reloads. + bot.lavalink = lavalink.Client(bot.user.id, player=MixPlayer) + bot.lavalink.add_node(host, port, password, region, 'default-node') # Host, Port, Password, Region, Name + bot.add_listener(bot.lavalink.voice_update_handler, 'on_socket_response') + + bot.lavalink.add_event_hook(self.track_hook) def __unload(self): - for guild_id, player in self.bot.lavalink.players: - self.bot.loop.create_task(player.disconnect()) - player.cleanup() - # Clear the players from Lavalink's internal cache - self.bot.lavalink.players.clear() - self.bot.lavalink.unregister_hook(self._track_hook) + self.bot.lavalink._event_hooks.clear() - async def __local_check(self, ctx): - is_guild = ctx.guild is not None - return is_guild - - async def _track_hook(self, event): - if isinstance(event, lavalink.Events.StatsUpdateEvent): - return - channel = self.bot.get_channel(event.player.fetch('channel')) - if not channel: - return - - if isinstance(event, lavalink.Events.TrackStartEvent): - embed = discord.Embed(title='Now playing:', description=event.track.title, color=0xEFD26C) - thumbnail_url = await RoxUtils.ThumbNailer.identify(self, event.player.current.identifier, event.player.current.uri) - if thumbnail_url: - embed.set_thumbnail(url=thumbnail_url) - await channel.send(embed=embed) - elif isinstance(event, lavalink.Events.QueueEndEvent): - await channel.send('Queue ended.') + async def track_hook(self, event): + if isinstance(event, lavalink.events.TrackEndEvent): + pass # Send track ended message to channel. + if isinstance(event, lavalink.events.QueueEndEvent): + channel = self.bot.get_channel(event.player.fetch('channel')) await self.check_leave_voice(channel.guild) - @commands.command(name='play', aliases=['p']) - async def _play(self, ctx, *, query: str): + async def __before_invoke(self, ctx): + guild_check = ctx.guild is not None + # This is essentially the same as `@commands.guild_only()` + # except it saves us repeating ourselves (and also a few lines). + + if guild_check: + await self.ensure_voice(ctx) + # Ensure that the bot and command author share a mutual voicechannel. + + return guild_check + + async def connect_to(self, guild_id: int, channel_id: str): + """ Connects to the given voicechannel ID. A channel_id of `None` means disconnect. """ + ws = self.bot._connection._get_websocket(guild_id) + await ws.voice_state(str(guild_id), channel_id) + + @commands.command(aliases=['p']) + async def play(self, ctx, *, query: str): """ Searches and plays a song from a given query. """ player = self.bot.lavalink.players.get(ctx.guild.id) @@ -78,7 +69,7 @@ class Music: if not url_rx.match(query): query = f'ytsearch:{query}' - results = await self.bot.lavalink.get_tracks(query) + results = await player.node.get_tracks(query) if not results or not results['tracks']: return await ctx.send('Nothing found!') @@ -97,6 +88,7 @@ class Music: else: track = results['tracks'][0] embed.title = 'Track Enqueued' + # Rox thumbnail_url = await RoxUtils.ThumbNailer.identify(self, track['info']['identifier'], track['info']['uri']) if thumbnail_url: embed.set_thumbnail(url=thumbnail_url) @@ -108,51 +100,9 @@ class Music: if not player.is_playing: await player.play() - @commands.command(name='playnow', aliases=['pn']) - async def _playnow(self, ctx, *, query: str): - """ Plays immediately a song. """ - player = self.bot.lavalink.players.get(ctx.guild.id) - - if not player.queue and not player.is_playing: - return await ctx.invoke(self._play, query=query) - - query = query.strip('<>') - - if not url_rx.match(query): - query = f'ytsearch:{query}' - - results = await self.bot.lavalink.get_tracks(query) - - if not results or not results['tracks']: - return await ctx.send('Nothing found!') - - tracks = results['tracks'] - track = tracks.pop(0) - - if results['loadType'] == 'PLAYLIST_LOADED': - for _track in tracks: - player.add(requester=ctx.author.id, track=_track) - - await player.play_now(requester=ctx.author.id, track=track) - - @commands.command(name='skipto', aliases=['st']) + @commands.command() @checks.is_DJ() - async def _skip_to(self, ctx, index: int): - """ Plays the queue from a specific point. Disregards tracks before the index. """ - player = self.bot.lavalink.players.get(ctx.guild.id) - - if ctx.author not in player.listeners: - return await ctx.send('You have to be listening to the bot') - - if index < 1: - return await ctx.send('Invalid specified index.') - if len(player.queue) < index: - return await ctx.send('This index exceeds the queue\'s length.') - await player.skip_to(index - 1) - - @commands.command(name='seek') - @checks.is_DJ() - async def _seek(self, ctx, *, time: str): + async def seek(self, ctx, *, time: str): """ Seeks to a given position in a track. """ player = self.bot.lavalink.players.get(ctx.guild.id) @@ -173,10 +123,10 @@ class Music: track_time = player.position + seconds await player.seek(track_time) - await ctx.send(f'Moved track to **{lavalink.Utils.format_time(track_time)}**') + await ctx.send(f'Moved track to **{lavalink.utils.format_time(track_time)}**') - @commands.command(name='skip', aliases=['forceskip', 'fs']) - async def _skip(self, ctx): + @commands.command(aliases=['forceskip']) + async def skip(self, ctx): """ Skips the current track. """ player = self.bot.lavalink.players.get(ctx.guild.id) @@ -193,9 +143,24 @@ class Music: if skips != 0: await ctx.send(f'{skips} out of {math.ceil(total/2)} required haters have voted to skip.') - @commands.command(name='stop') + @commands.command(name='skipto', aliases=['st','skip_to']) @checks.is_DJ() - async def _stop(self, ctx): + async def skip_to(self, ctx, pos: int): + """ Plays the queue from a specific point. Disregards tracks before the pos. """ + player = self.bot.lavalink.players.get(ctx.guild.id) + + if ctx.author not in player.listeners: + return await ctx.send('You have to be listening to the bot') + + if pos < 1: + return await ctx.send('Invalid specified position.') + if len(player.queue) < pos: + return await ctx.send('The position exceeds the queue\'s length.') + await player.skip(pos - 1) + + @commands.command() + @checks.is_DJ() + async def stop(self, ctx): """ Stops the player and clears its queue. """ player = self.bot.lavalink.players.get(ctx.guild.id) @@ -209,36 +174,34 @@ class Music: await player.stop() await ctx.send('⏹ | Stopped.') - @commands.command(name='now', aliases=['np', 'n', 'playing']) - async def _now(self, ctx): + @commands.command(aliases=['np', 'n', 'playing']) + async def now(self, ctx): """ Shows some stats about the currently playing song. """ - - #def scrubber(song, length): - - player = self.bot.lavalink.players.get(ctx.guild.id) - song = 'Nothing' - if player.current: - position = lavalink.Utils.format_time(player.position) - if player.current.stream: - duration = '🔴 LIVE' - else: - duration = lavalink.Utils.format_time(player.current.duration) - song = f'**[{player.current.title}]({player.current.uri})**\n({position}/{duration})' + if not player.current: + return await ctx.send('Nothing playing.') - embed = discord.Embed(color=0xEFD26C, title='Now Playing', description=song) + position = lavalink.utils.format_time(player.position) + if player.current.stream: + duration = '🔴 LIVE' + else: + duration = lavalink.utils.format_time(player.current.duration) + song = f'**[{player.current.title}]({player.current.uri})**\n({position}/{duration})' + + embed = discord.Embed(color=discord.Color.blurple(), + title='Now Playing', description=song) thumbnail_url = await RoxUtils.ThumbNailer.identify(self, player.current.identifier, player.current.uri) if thumbnail_url: embed.set_thumbnail(url=thumbnail_url) - await ctx.send(embed=embed) - @commands.command(name='queue', aliases=['q']) - async def _queue(self, ctx, user: discord.Member=None): - """ Shows the global queue or another users queue. """ + @commands.command(aliases=['q']) + async def queue(self, ctx, user: discord.Member=None): + """ Shows the player's queue. """ player = self.bot.lavalink.players.get(ctx.guild.id) + if player.queue.empty: return await ctx.send('The queue is empty') @@ -267,8 +230,9 @@ class Music: scroller = QueueScroller(ctx, user_queue, lines_per_page=10, user_name=ctx.author.name) await scroller.start_scrolling() - @commands.command(name='pause', aliases=['resume']) - async def _pause(self, ctx): + @commands.command(aliases=['resume']) + @checks.is_DJ() + async def pause(self, ctx): """ Pauses/Resumes the current track. """ player = self.bot.lavalink.players.get(ctx.guild.id) @@ -282,9 +246,9 @@ class Music: await player.set_pause(True) await ctx.send('⏯ | Paused') - @commands.command(name='volume', aliases=['vol']) + @commands.command(aliases=['vol']) @checks.is_DJ() - async def _volume(self, ctx, volume: int = None): + async def volume(self, ctx, volume: int = None): """ Changes the player's volume. Must be between 0 and 1000. Error Handling for that is done by Lavalink. """ player = self.bot.lavalink.players.get(ctx.guild.id) @@ -294,22 +258,6 @@ class Music: await player.set_volume(volume) await ctx.send(f'🔈 | Set to {player.volume}%') - @commands.command(name='boost', aliases=['boo']) - async def _boost(self, ctx, boost: bool=None): - """ Changes the player's volume. Must be between 0 and 1000. Error Handling for that is done by Lavalink. """ - player = self.bot.lavalink.players.get(ctx.guild.id) - if boost is None: - if player.boosted: - await ctx.send('Bass boost is on') - else: - await ctx.send('Bass boost is off') - else: - await player.bassboost(boost) - if boost: - await ctx.send('Bass boost turned on') - else: - await ctx.send('Bass boost turned off') - @commands.command(name='shuffle') async def _shuffle(self, ctx): """ Shuffles your queue. """ @@ -340,9 +288,9 @@ class Music: await ctx.send(f'{moved.title} moved from position {from_pos} to {to_pos} in your queue') - @commands.command(name='remove') - async def _remove(self, ctx, pos: int): - """ Removes the song at pos from your queue. """ + @commands.command() + async def remove(self, ctx, pos: int): + """ Removes an item from the player's queue with the given pos. """ player = self.bot.lavalink.players.get(ctx.guild.id) if player.queue.empty: @@ -390,89 +338,79 @@ class Music: removed = player.remove_user_track(user.id, pos - 1) await ctx.send(f'**{removed.title}** queued by {user.name} removed.') - - @commands.command(name='find') - async def _find(self, ctx, *, query): + @commands.command() + async def find(self, ctx, *, query): """ Lists the first 10 search results from a given query. """ - await ctx.trigger_typing() - not_reacted = True + player = self.bot.lavalink.players.get(ctx.guild.id) + # Rox + limit_results = 5 # Var to set max results react_emoji = {1: "\u0030\u20E3", 2: "\u0031\u20E3", 3: "\u0032\u20E3", 4: "\u0033\u20E3", 5: "\u0034\u20E3", 6: "\u0035\u20E3", 7: "\u0036\u20E3", 8: "\u0037\u20E3", 9: "\u0038\u20E3", 10: "\u0039\u20E3"} + # Rox if not query.startswith('ytsearch:') and not query.startswith('scsearch:'): query = 'ytsearch:' + query - results = await self.bot.lavalink.get_tracks(query) + results = await player.node.get_tracks(query) if not results or not results['tracks']: return await ctx.send('Nothing found') - tracks = results['tracks'][:10] # First 10 results + tracks = results['tracks'][:limit_results] # First 10 results o = '' for index, track in enumerate(tracks, start=1): - track_title = track["info"]["title"] - track_uri = track["info"]["uri"] + track_title = track['info']['title'] + track_uri = track['info']['uri'] + o += f'`{index}.` [{track_title}]({track_uri})\n' - o += f'{react_emoji[index]} [{track_title}]({track_uri})\n' + embed = discord.Embed(color=0xEFD26C, description=o) + start_msg = await ctx.send(embed=embed) # Sending a message, so we can delete it later - embed_start = discord.Embed(color=0xEFD26C, description=o) - start_msg = await ctx.send(embed=embed_start) - await ctx.trigger_typing() - for num_index in range(min(len(tracks), 10)): + # Adding reactions to messages, doing this outside previous loop to give the user some + # "thinkingtime while reacting + for num_index in range(min(len(tracks), limit_results)): index = num_index + 1 await start_msg.add_reaction(react_emoji[index]) await start_msg.add_reaction("\u274C") - # React does shit - time_start = time.time() - while not_reacted: - await ctx.trigger_typing() - embed = discord.Embed(color=0xEFD26C) + # Loop to detect reactions on the message + time_start = time.time() + while True: timer = time.time() - time_start msg_id = await ctx.get_message(start_msg.id) - if int(timer) >= 10: + if int(timer) >= 10: # Checks timer. ends loop after 10 seconds await start_msg.clear_reactions() - embed.title = "Sorry" - embed.description = "Timer expired" + embed = discord.Embed(color=0xEFD26C, title="Sorry", description="Timer expired") await start_msg.edit(embed=embed) - ping = await ctx.send(content=f"{ctx.author.mention}") - await asyncio.sleep(10) - await ctx.message.delete() - await ping.delete() - await msg_id.delete() - break + return + for react in msg_id.reactions: async for user in react.users(): if user is ctx.author: if react.emoji[:-1].isdigit(): - not_reacted = False - track_num = int(react.emoji[:-1]) - track_ = tracks[track_num] - await self.send_to_play(ctx, track_) - embed.title = "Song sent to queue" - thumb_url = await RoxUtils.ThumbNailer.identify(self, track_['info']['identifier'],track_['info']['uri']) + track_ = tracks[int(react.emoji[:-1])] + info = track_['info'] + embed = discord.Embed(color=0xEFD26C, title="Song sent to queue") + thumb_url = await RoxUtils.ThumbNailer.identify(self, info['identifier'], info['uri']) if thumb_url: embed.set_thumbnail(url=thumb_url) - embed.description = f'[{track_["info"]["title"]}]({track_["info"]["uri"]})' + embed.description = f'[{info["title"]}]({info["uri"]})' await start_msg.edit(embed=embed) await start_msg.clear_reactions() - break + # await self.ensure_voice(ctx, True) + player.add(requester=ctx.author.id, track=track_) + if not player.is_playing: + await player.play() + return if react.emoji == "\u274C": - not_reacted = False await start_msg.clear_reactions() - embed.title = "Sorry" - embed.description = "Search cancelled by user" + embed = discord.Embed(color=0xEFD26C, title="Sorry", description="Search cancelled by user") await start_msg.edit(embed=embed) - ping = await ctx.send(content=f"{user.mention}") - await asyncio.sleep(10) - await ctx.message.delete() - await ping.delete() - await msg_id.delete() - break + return - @commands.command(name='disconnect', aliases=['dc']) - async def _disconnect(self, ctx): + @commands.command(aliases=['dc']) + async def disconnect(self, ctx): """ Disconnects the player from the voice channel and clears its queue. """ player = self.bot.lavalink.players.get(ctx.guild.id) @@ -483,40 +421,44 @@ class Music: return await ctx.send('You\'re not in my voicechannel!') player.queue.clear() - await player.disconnect() + await player.stop() + await self.connect_to(ctx.guild.id, None) await ctx.send('*⃣ | Disconnected.') - async def send_to_play(self, ctx, track): - await self.ensure_voice_real(ctx) + @commands.command(name='boost', aliases=['boo']) + async def _boost(self, ctx, boost: bool=None): + """ Changes the player's volume. Must be between 0 and 1000. Error Handling for that is done by Lavalink. """ player = self.bot.lavalink.players.get(ctx.guild.id) - player.add(requester=ctx.author.id, track=track) - if not player.is_playing: - await player.play() + + if boost is not None: + await player.bassboost(boost) - @_playnow.before_invoke - @_play.before_invoke - async def ensure_voice(self, ctx): - await self.ensure_voice_real(ctx) + if player.boosted: + await ctx.send('Bass boost is on') + else: + await ctx.send('Bass boost is off') - async def ensure_voice_real(self, ctx): - """ A few checks to make sure the bot can join a voice channel. """ - player = self.bot.lavalink.players.get(ctx.guild.id) + async def ensure_voice(self, ctx, do_connect: Optional[bool] = None): + """ This check ensures that the bot and command author are in the same voicechannel. """ + player = self.bot.lavalink.players.create(ctx.guild.id, endpoint=ctx.guild.region.value) + # Create returns a player if one exists, otherwise creates. + + should_connect = ctx.command.name in ('play', "find") # Add commands that require joining voice to work. + + if not ctx.author.voice or not ctx.author.voice.channel: + raise commands.CommandInvokeError('Join a voicechannel first.') if not player.is_connected: - if not ctx.author.voice or not ctx.author.voice.channel: - await ctx.send('You aren\'t connected to any voice channel.') - raise commands.CommandInvokeError( - 'Author not connected to voice channel.') + if not should_connect: + raise commands.CommandInvokeError('Not connected.') permissions = ctx.author.voice.channel.permissions_for(ctx.me) - if not permissions.connect or not permissions.speak: - await ctx.send('Missing permissions `CONNECT` and/or `SPEAK`.') - raise commands.CommandInvokeError( - 'Bot has no permissions CONNECT and/or SPEAK') + if not permissions.connect or not permissions.speak: # Check user limit too? + raise commands.CommandInvokeError('I need the `CONNECT` and `SPEAK` permissions.') player.store('channel', ctx.channel.id) - await player.connect(ctx.author.voice.channel.id) + await self.connect_to(ctx.guild.id, str(ctx.author.voice.channel.id)) # Get listeners voice_channel = ctx.author.voice.channel @@ -527,23 +469,23 @@ class Music: if not member.bot: player.update_listeners(member, member.voice) - else: - if player.connected_channel.id != ctx.author.voice.channel.id: - return await ctx.send('Join my voice channel!') - + if int(player.channel_id) != ctx.author.voice.channel.id: + raise commands.CommandInvokeError('You need to be in my voicechannel.') async def on_voice_state_update(self, member, before, after): if not member.bot: player = self.bot.lavalink.players.get(member.guild.id) - player.update_listeners(member, after) - await self.check_leave_voice(member.guild) + if player is not None: + player.update_listeners(member, after) + await self.check_leave_voice(member.guild) async def check_leave_voice(self, guild): player = self.bot.lavalink.players.get(guild.id) if len(player.listeners) == 0 and player.is_connected: if player.queue.empty and player.current is None: - await player.disconnect() + await player.stop() + await self.connect_to(guild.id, None) def setup(bot): diff --git a/cogs/utils/mixplayer/mixqueue.py b/cogs/utils/mixplayer.py similarity index 53% rename from cogs/utils/mixplayer/mixqueue.py rename to cogs/utils/mixplayer.py index fe24380..8bcb9a7 100644 --- a/cogs/utils/mixplayer/mixqueue.py +++ b/cogs/utils/mixplayer.py @@ -1,7 +1,130 @@ +import time +import lavalink + from collections import OrderedDict from itertools import cycle, islice, chain from random import shuffle -from lavalink import AudioTrack + +from lavalink.events import (TrackStartEvent, TrackStuckEvent, + TrackExceptionEvent, TrackEndEvent, + QueueEndEvent, PlayerUpdateEvent, + NodeChangedEvent) +from lavalink import Node, DefaultPlayer, AudioTrack + + +class MixPlayer(DefaultPlayer): + def __init__(self, guild_id: int, node: Node): + super().__init__(guild_id, node) + + self.queue = MixQueue() + + self.listeners = set() + self.skip_voters = set() + self.boosted = False + + def add(self, requester: int, track: dict, pos: int=None): + """ Adds a track to the queue. """ + return self.queue.add_track(requester, + AudioTrack.build(track, requester), + pos) + + def add_next(self, requester: int, track: dict): + """ Adds a track to beginning of the queue """ + self.queue.add_next_track(AudioTrack.build(track, requester)) + + def move_user_track(self, requester: int, initial: int, final: int): + """ Moves a track in a users queue""" + return self.queue.move_user_track(requester, initial, final) + + def remove_user_track(self, requester: int, pos: int): + """ Removes the song at from the queue of requester """ + return self.queue.remove_user_track(requester, pos) + + def remove_global_track(self, pos: int): + """ Removes the song at in the global queue """ + return self.queue.remove_global_track(pos) + + def shuffle_user_queue(self, requester: int): + """ Randomly reorders the queue of requester """ + self.queue.shuffle_user_queue(requester) + + def user_queue(self, user: int, dual: bool=False): + return self.queue.get_user_queue(user, dual) + + def global_queue(self): + return self.queue.get_queue() + + def queue_duration(self, include_current: bool=True): + duration = 0 + for track in self.queue: + duration += track.duration + remaining = self.current.duration - self.position + if include_current: + return lavalink.Utils.format_time(duration + remaining) + return lavalink.Utils.format_time(duration) + + async def play(self, track: AudioTrack=None, start_time: int=0): + + self.current = None + self.last_update = 0 + self.last_position = 0 + self.position_timestamp = 0 + self.paused = False + + if not track: + if self.queue.empty: + self.boosted = False + await self.stop() + await self.node._dispatch_event(QueueEndEvent(self)) + return + else: + track = self.queue.pop_first() + + self.current = track + await self.node._send(op='play', guildId=self.guild_id, + track=track.track, startTime=start_time) + await self.node._dispatch_event(TrackStartEvent(self, track)) + + async def skip(self, pos: int=0): + """ Plays the next track in the queue, if any. """ + for i in range(pos): + _ = self.queue.pop_first() + self.skip_voters.clear() + await self.play() + + def update_listeners(self, member, voice_state): + if self.is_connected: + vc = int(self.channel_id) + if voice_state.channel is None or voice_state.channel.id != vc: + self.listeners.discard(member) + self.skip_voters.discard(member) + else: + if voice_state.deaf or voice_state.self_deaf: + self.listeners.discard(member) + self.skip_voters.discard(member) + else: + self.listeners.add(member) + + def add_skipper(self, member): + if member in self.listeners: + self.skip_voters.add(member) + + async def bassboost(self, boost: bool=False): + if boost: + gains = [ + (0, 0.15), + (1, 0.15), + (2, 0.25), + (3, 0.15), + (4, -0.15), + (5, -0.1), + (6, -0.05) + ] + self.boosted = True + await self.set_gains(*gains) + else: + self.boosted = False + await self.reset_equalizer() def roundrobin(*iterables): @@ -35,7 +158,7 @@ class MixQueue: def __iter__(self): global_queue = roundrobin(*[x for x in self.queues.values()]) - priority_queue = self.priority_queue + priority_queue = self.priority_queue out = chain(priority_queue, global_queue) return out @@ -167,4 +290,3 @@ class MixQueue: @property def empty(self): return len(self) == 0 - diff --git a/cogs/utils/mixplayer/mixplayer.py b/cogs/utils/mixplayer/mixplayer.py deleted file mode 100644 index 65a44c3..0000000 --- a/cogs/utils/mixplayer/mixplayer.py +++ /dev/null @@ -1,233 +0,0 @@ -from .mixqueue import MixQueue - -from abc import ABC, abstractmethod -from random import randrange - -import lavalink -from lavalink import BasePlayer -from lavalink.AudioTrack import AudioTrack -from lavalink.Events import QueueEndEvent, TrackExceptionEvent, TrackEndEvent, TrackStartEvent, TrackStuckEvent - - -''' - -Version of lavalink.py's DefaultPlayer with a custom queue(mixqueue). - -''' - -class MixPlayer(BasePlayer): - def __init__(self, lavalink, guild_id: int): - super().__init__(lavalink, guild_id) - - self._user_data = {} - self.channel_id = None - - self.paused = False - self.position = 0 - self.position_timestamp = 0 - self.volume = 100 - - self.queue = MixQueue() - self.current = None - - self.listeners = set() - self.skip_voters = set() - self.boosted = False - - @property - def is_playing(self): - """ Returns the player's track state. """ - return self.connected_channel is not None and self.current is not None - - @property - def is_connected(self): - """ Returns the player's connection state. """ - return self.connected_channel is not None - - @property - def connected_channel(self): - """ Returns the voice channel the player is connected to. """ - if not self.channel_id: - return None - - return self._lavalink.bot.get_channel(int(self.channel_id)) - - async def connect(self, channel_id: int): - """ Connects to a voice channel. """ - ws = self._lavalink.bot._connection._get_websocket(int(self.guild_id)) - await ws.voice_state(self.guild_id, str(channel_id)) - - async def disconnect(self): - """ Disconnects from the voice channel, if any. """ - if not self.is_connected: - return - - await self.stop() - - ws = self._lavalink.bot._connection._get_websocket(int(self.guild_id)) - await ws.voice_state(self.guild_id, None) - - def store(self, key: object, value: object): - """ Stores custom user data. """ - self._user_data.update({key: value}) - - def fetch(self, key: object, default=None): - """ Retrieves the related value from the stored user data. """ - return self._user_data.get(key, default) - - def delete(self, key: object): - """ Removes an item from the the stored user data. """ - try: - del self._user_data[key] - except KeyError: - pass - - def add(self, requester: int, track: dict): - """ Adds a track to the queue. """ - return self.queue.add_track(requester, AudioTrack().build(track, requester)) - - def add_next(self, requester: int, track: dict): - """ Adds a track to beginning of the queue """ - self.queue.add_next_track(AudioTrack().build(track, requester)) - - def add_at(self, index: int, requester: int, track: dict): - """ Adds a track at a specific index in the queue. """ - return self.queue.add_track(requester, AudioTrack().build(track, requester), index) - - def move_user_track(self, requester: int, initial: int, final: int): - """ Moves a track in a users queue""" - return self.queue.move_user_track(requester, initial, final) - - def remove_user_track(self, requester: int, pos: int): - """ Removes the song at from the queue of requester """ - return self.queue.remove_user_track(requester, pos) - - def remove_global_track(self, pos: int): - """ Removes the song at in the global queue """ - return self.queue.remove_global_track(pos) - - def shuffle_user_queue(self, requester: int): - """ Randomly reorders the queue of requester """ - self.queue.shuffle_user_queue(requester) - - def user_queue(self, user: int, dual: bool=False): - return self.queue.get_user_queue(user, dual) - - def global_queue(self): - return self.queue.get_queue() - - def queue_duration(self, include_current: bool=True): - duration = 0 - for track in self.queue: - duration += track.duration - remaining = self.current.duration - self.position - if include_current: - return lavalink.Utils.format_time(duration + remaining) - return lavalink.Utils.format_time(duration) - - def current_pos(self): - return lavalink.Utils.format_time(self.position) - - async def play(self, track_index: int = 0): - """ Plays the first track in the queue, if any or plays a track from the specified index in the queue. """ - - self.current = None - self.position = 0 - self.paused = False - - if self.queue.empty: - await self.stop() - await self._lavalink.dispatch_event(QueueEndEvent(self)) - else: - track = self.queue.pop_first() - - self.current = track - await self._lavalink.ws.send(op='play', guildId=self.guild_id, track=track.track) - await self._lavalink.dispatch_event(TrackStartEvent(self, track)) - - async def play_now(self, requester: int, track: dict): - """ Add track and play it. """ - self.add_next(requester, track) - await self.play() - - async def skip_to(self, index: int): - """ Play the queue from a specific point. Disregards tracks before the index. """ - for i in range(index): - _ = self.queue.pop_first() - await self.play() - - async def stop(self): - """ Stops the player, if playing. """ - await self._lavalink.ws.send(op='stop', guildId=self.guild_id) - self.current = None - - async def skip(self): - """ Plays the next track in the queue, if any. """ - self.skip_voters.clear() - await self.play() - - async def set_pause(self, pause: bool): - """ Sets the player's paused state. """ - await self._lavalink.ws.send(op='pause', guildId=self.guild_id, pause=pause) - self.paused = pause - - async def set_volume(self, vol: int): - """ Sets the player's volume (150% or 1000% limit imposed by lavalink depending on the version). """ - if self._lavalink._server_version <= 2: - self.volume = max(min(vol, 150), 0) - else: - self.volume = max(min(vol, 1000), 0) - await self._lavalink.ws.send(op='volume', guildId=self.guild_id, volume=self.volume) - - async def seek(self, pos: int): - """ Seeks to a given position in the track. """ - await self._lavalink.ws.send(op='seek', guildId=self.guild_id, position=pos) - - async def handle_event(self, event): - """ Makes the player play the next song from the queue if a song has finished or an issue occurred. """ - if isinstance(event, (TrackStuckEvent, TrackExceptionEvent)) or \ - isinstance(event, TrackEndEvent) and event.reason == 'FINISHED': - await self.play() - - def update_listeners(self, member, voice_state): - if self.is_connected: - vc = self.connected_channel - if voice_state.channel is None or voice_state.channel != vc: - self.listeners.discard(member) - self.skip_voters.discard(member) - else: - if voice_state.deaf or voice_state.self_deaf: - self.listeners.discard(member) - self.skip_voters.discard(member) - else: - self.listeners.add(member) - - - def add_skipper(self, member): - if member in self.listeners: - self.skip_voters.add(member) - - - async def bassboost(self, boost: bool=False): - if boost: - boostval = 1 - else: - boostval = 0 - - bands = [ - {"band": 0, "gain": boostval * 0.15}, # 25Hz - {"band": 1, "gain": boostval * 0.15}, # 40Hz - {"band": 2, "gain": boostval * 0.25}, # 63Hz - {"band": 3, "gain": boostval * 0.15}, #100Hz - {"band": 4, "gain": boostval * -0.15}, #160Hz - {"band": 5, "gain": boostval * -0.1}, #250Hz - {"band": 6, "gain": boostval * -0.05}, #400Hz - ] - - if boost: - await self._lavalink.ws.send(op="equalizer", guildId=self.guild_id, bands=bands) - self.boosted = True - else: - await self._lavalink.ws.send(op="equalizer", guildId=self.guild_id, bands=bands) - self.boosted = False - diff --git a/lavasettings.py.example b/lavasettings.py.example index c5d95f1..d63f663 100644 --- a/lavasettings.py.example +++ b/lavasettings.py.example @@ -1,3 +1,4 @@ host = 'localhost' -rest_port = 2333 -ws_port = 2333 \ No newline at end of file +port = 2333 +password = 'youshallnotpass' +region = 'eu' \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 29d07af..360fed9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ git+https://github.com/Rapptz/discord.py@rewrite +git+https://github.com/Devoxin/Lavalink.py@dev asyncio -lavalink bs4 -aiohttp \ No newline at end of file +aiohttp<3.5.0,>=3.3.0 \ No newline at end of file