mirror of
https://github.com/UiO-Gaming/ShiteMusicBot.git
synced 2026-11-06 22:34:30 +01:00
Custom queue implemented
This commit is contained in:
1 parent
d35304715d
commit
af5ea7dc6e
4 files changed
+399
-34
No files matched your search
@@ -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
@@ -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'])
|
||||
|
||||
@@ -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()
|
||||
@@ -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
|
||||
Reference in new issue
Block a user