Custom queue implemented

This commit is contained in:
Ev-1 committed 2019-01-09 22:37:53 +01:00
1 parent d35304715d
commit af5ea7dc6e
4 files changed
+399 -34

No files matched your search

+35 -22
View File
@@ -3,6 +3,8 @@ import os
import codecs
import discord
import time
import sys
import traceback
from discord.ext import commands
from cogs.utils.settings import Settings
@@ -28,11 +30,12 @@ def _get_prefix(bot, message):
class Bot(commands.Bot):
def __init__(self):
def __init__(self, debug: bool=False):
super().__init__(command_prefix=_get_prefix,
description=config["description"])
self.settings = Settings(**config)
self.debug = debug
for extension in initial_extension:
try:
@@ -41,33 +44,40 @@ class Bot(commands.Bot):
print(e)
async def on_command_error(self, ctx, err):
if (isinstance(err, commands.MissingRequiredArgument) or
isinstance(err, commands.BadArgument)):
formatter = ctx.bot.formatter
if ctx.invoked_subcommand is None:
_help = await formatter.format_help_for(ctx, ctx.command)
else:
_help = await formatter.format_help_for(ctx,
ctx.invoked_subcommand)
if not self.debug:
if (isinstance(err, commands.MissingRequiredArgument) or
isinstance(err, commands.BadArgument)):
formatter = ctx.bot.formatter
if ctx.invoked_subcommand is None:
_help = await formatter.format_help_for(ctx, ctx.command)
else:
_help = await formatter.format_help_for(ctx,
ctx.invoked_subcommand)
for message in _help:
await ctx.send(message)
for message in _help:
await ctx.send(message)
if isinstance(err, commands.CommandInvokeError):
pass
if isinstance(err, commands.CommandInvokeError):
pass
elif isinstance(err, commands.NoPrivateMessage):
await ctx.send("Denne kommandoen er ikke tilgjengelig i DMs")
elif isinstance(err, commands.NoPrivateMessage):
await ctx.send("Denne kommandoen er ikke tilgjengelig i DMs")
elif isinstance(err, commands.CheckFailure):
pass
elif isinstance(err, commands.CheckFailure):
pass
elif isinstance(err, commands.CommandNotFound):
pass
elif isinstance(err, commands.CommandNotFound):
pass
else:
tb = err.__traceback__
traceback.print_tb(tb)
print(err)
async def on_ready(self):
if not hasattr(self, 'uptime'):
self.uptime = time.time()
if self.debug:
print('\n\nDebug mode')
print(f'\nLogged in as: {self.user.name}' +
f' in {len(self.guilds)} servers.')
@@ -84,9 +94,12 @@ class Bot(commands.Bot):
print('ifkn', e)
def run_bot():
bot = Bot()
def run_bot(debug: bool=False):
bot = Bot(debug=debug)
bot.run()
if __name__ == '__main__':
run_bot()
if 'debug' in sys.argv:
run_bot(debug=True)
else:
run_bot(debug=False)
+43 -12
View File
@@ -13,6 +13,8 @@ import re
import discord
import lavalink
from discord.ext import commands
from .utils.mixplayer.mixplayer import MixPlayer
from lavasettings import *
time_rx = re.compile('[0-9]+')
@@ -27,7 +29,8 @@ class Music:
lavalink.Client(bot=bot, password='youshallnotpass',
loop=bot.loop, log_level=logging.INFO,
host=host, ws_port=ws_port,
rest_port=rest_port)
rest_port=rest_port,
player=MixPlayer)
self.bot.lavalink.register_hook(self._track_hook)
def __unload(self):
@@ -48,7 +51,7 @@ class Music:
if isinstance(event, lavalink.Events.TrackStartEvent):
await channel.send(embed=discord.Embed(title='Now playing:',
description=event.track.title,
color=discord.Color.blurple()))
color=0xEFD26C))
elif isinstance(event, lavalink.Events.QueueEndEvent):
await channel.send('Queue ended! Why not queue more songs?')
@@ -69,7 +72,7 @@ class Music:
if not results or not results['tracks']:
return await ctx.send('Nothing found!')
embed = discord.Embed(color=discord.Color.blurple())
embed = discord.Embed(color=0xEFD26C)
if results['loadType'] == 'PLAYLIST_LOADED':
tracks = results['tracks']
@@ -205,7 +208,7 @@ class Music:
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(),
embed = discord.Embed(color=0xEFD26C,
title='Now Playing', description=song)
await ctx.send(embed=embed)
@@ -215,21 +218,49 @@ class Music:
""" Shows the player's queue. """
player = self.bot.lavalink.players.get(ctx.guild.id)
if not player.queue:
if player.queue.is_empty():
return await ctx.send('There\'s nothing in the queue! Why not queue something?')
queue = player.queue.get_queue()
items_per_page = 10
pages = math.ceil(len(player.queue) / items_per_page)
pages = math.ceil(len(queue) / items_per_page)
start = (page - 1) * items_per_page
end = start + items_per_page
queue_list = ''
for index, track in enumerate(player.queue[start:end], start=start):
queue_list += f'`{index + 1}.` [**{track.title}**]({track.uri})\n'
for index, track in enumerate(queue[start:end], start=start):
queue_list += f'`{index + 1}.` [**{track.title}**]({track.uri}) _by <@{track.requester}>_\n'
embed = discord.Embed(colour=discord.Color.blurple(),
description=f'**{len(player.queue)} tracks**\n\n{queue_list}')
embed = discord.Embed(color=0xEFD26C,
description=f'**{len(queue)} tracks**\n\n{queue_list}')
embed.set_footer(text=f'Viewing page {page}/{pages}')
await ctx.send(embed=embed)
@commands.command(name='myqueue', aliases=['mq'])
@commands.guild_only()
async def _myqueue(self, ctx, page: int = 1):
""" Shows the player's queue. """
player = self.bot.lavalink.players.get(ctx.guild.id)
if player.queue.is_empty():
return await ctx.send('There\'s nothing in the queue! Why not queue something?')
queue = player.queue.get_user_queue(ctx.author.id, pos=True)
if not queue:
return await ctx.send('You haven\'t queued anything')
items_per_page = 10
pages = math.ceil(len(queue) / items_per_page)
start = (page - 1) * items_per_page
end = start + items_per_page
queue_list = ''
for index, (track, globalpos) in enumerate(queue[start:end], start=start):
queue_list += f'`{index + 1}({globalpos + 1}).` [**{track.title}**]({track.uri})\n'
embed = discord.Embed(color=0xEFD26C,
description=f'**{len(queue)} tracks**\n\n{queue_list}')
embed.set_footer(text=f'Viewing page {page}/{pages}')
await ctx.send(embed=embed)
@@ -322,7 +353,7 @@ class Music:
o += f'`{index}.` [{track_title}]({track_uri})\n'
embed = discord.Embed(color=discord.Color.blurple(), description=o)
embed = discord.Embed(color=0xEFD26C, description=o)
await ctx.send(embed=embed)
@commands.command(name='disconnect', aliases=['dc'])
+167
View File
@@ -0,0 +1,167 @@
from .mixqueue import MixQueue
from abc import ABC, abstractmethod
from random import randrange
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.shuffle = False
self.repeat = False
self.queue = MixQueue()
self.current = None
self.previous = None
@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. """
#self.queue.append(AudioTrack().build(track, requester))
self.queue.add_song(requester, AudioTrack().build(track, requester))
def add_next(self, requester: int, track: dict):
""" Adds a track to beginning of the queue """
#self.queue.insert(0, AudioTrack().build(track, requester))
self.queue.add_next_song(AudioTrack().build(track, requester))
def add_at(self, index: int, requester: int, track: dict):
""" Adds a track at a specific index in the queue. """
#self.queue.insert(min(index, len(self.queue) - 1), AudioTrack().build(track, requester))
self.queue.add_song(requester, AudioTrack().build(track, requester), index)
async def play(self, track_index: int = 0, ignore_shuffle: bool = False):
""" Plays the first track in the queue, if any or plays a track from the specified index in the queue. """
if self.repeat and self.current:
self.queue.append(self.current)
self.previous = self.current
self.current = None
self.position = 0
self.paused = False
if self.queue.is_empty():
await self.stop()
await self._lavalink.dispatch_event(QueueEndEvent(self))
else:
# if self.shuffle and not ignore_shuffle:
# track = self.queue.pop(randrange(len(self.queue)))
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(ignore_shuffle=True)
async def play_at(self, index: int):
""" Play the queue from a specific point. Disregards tracks before the index. """
self.queue = self.queue[min(index, len(self.queue) - 1):len(self.queue)]
await self.play(ignore_shuffle=True)
async def play_previous(self):
""" Plays previous track if it exist, if it doesn't raises a NoPreviousTrack error. """
if not self.previous:
raise NoPreviousTrack
self.queue.insert(0, self.previous)
await self.play(ignore_shuffle=True)
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. """
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()
+154
View File
@@ -0,0 +1,154 @@
from collections import OrderedDict
from itertools import cycle, islice, chain
from random import shuffle
from lavalink import AudioTrack
def roundrobin(*iterables):
"roundrobin('ABC', 'D', 'EF') --> A D E B F C"
# Recipe credited to George Sakkis
num_active = len(iterables)
nexts = cycle(iter(it).__next__ for it in iterables)
while num_active:
try:
for next in nexts:
yield next()
except StopIteration:
num_active -= 1
nexts = cycle(islice(nexts, num_active))
class MixQueue:
def __init__(self):
self.queues = OrderedDict()
self.priority_queue = []
def __str__(self):
tmp = ''
for requester in self.queues.keys():
user_queue = list(map(str, self.queues[requester]))
tmp += f'{requester}: {user_queue}\n'
return tmp
def __iter__(self):
global_queue = roundrobin(*[x for x in self.queues.values()])
priority_queue = self.priority_queue
out = chain(priority_queue, global_queue)
return out
def __len__(self):
length = 0
for i in self:
length += 1
return length
def get_queue(self):
return list(self)
def is_empty(self):
return len(self) == 0
def clear(self):
self.queues = OrderedDict()
self.priority_queue = []
# if pos is true also returns global positions of songs
def get_user_queue(self, requester: int, pos: bool=False):
queue = self.queues.get(requester, [])
if pos and queue:
pos = [self._loc_to_glob(requester, i) for i in range(len(queue))]
combined = zip(queue, pos)
return list(combined)
return queue
def pop_first(self):
if self.priority_queue:
next_song = self.priority_queue.pop(0)
return next_song
try:
next_song = self.queues[self.first_queue].pop(0)
self._shuffle()
self._clear_empty()
return next_song
except KeyError:
pass
def add_song(self, requester: int, track: AudioTrack, pos: int=None):
user_queue = self.queues.get(requester)
if user_queue is None:
self.queues[requester] = [track]
elif pos is None:
user_queue.append(track)
else:
user_queue.insert(pos, track)
def add_next_song(self, track: AudioTrack):
self.priority_queue.append(track)
def remove_song_from(self, requester: int, pos: int):
user_queue = self.queues.get(requester)
if user_queue is not None:
if pos < len(user_queue):
user_queue.pop(pos)
self._clear_empty()
def remove_song_at(self, pos: int):
q, pos = self._glob_to_loc(pos)
if q is None or pos is None:
return
queue = self.queues.get(q)
queue.pop(pos)
self._clear_empty()
def switch_user_songs(self, requester: int, first: int, second: int):
queue = self.queues.get(requester, [])
if queue:
try:
queue[first], queue[second] = queue[second], queue[first]
except IndexError:
pass
def shuffle_user_queue(self, requester: int):
queue = self.queues.get(requester, [])
if queue:
shuffle(queue)
# Switches the order of user queues
def _shuffle(self):
self.queues.move_to_end(self.first_queue)
# Removes empty user queues
def _clear_empty(self):
to_remove = [q for q in reversed(self.queues) if not self.queues[q]]
for i in to_remove:
self.queues.pop(i)
# Convert between global and local queue positions
def _loc_to_glob(self, requester: int, pos: int):
queue = self.queues.get(requester, [])
if queue:
try:
song = queue[pos]
for i, s in enumerate(self):
if s == song:
return i
except IndexError:
pass
def _glob_to_loc(self, pos: int):
song = None
for i, s in enumerate(self):
if i == pos:
song = s
if song is None:
return None, None
for requester, q in self.queues.items():
for i, s in enumerate(q):
if s == song:
return requester, i
@property
def first_queue(self):
try:
return list(self.queues.keys())[0]
except IndexError:
pass