Add lots of info and debug log. Update error handling

This commit is contained in:
Ev-1 committed 2022-10-09 10:00:00 +02:00
1 parent 896c70e453
commit d31cab9ffd
9 files changed
+188 -126

No files matched your search

+1 -1
View File
@@ -106,7 +106,7 @@ class Bot(commands.Bot):
except Exception as e:
tb = e.__traceback__
traceback.print_tb(tb)
self.logger.error(e)
self.logger.exception(e)
def run_bot(datadir, debug: bool = False):
+11 -5
View File
@@ -2,7 +2,7 @@
import discord
from discord.ext import commands
import traceback
import sys
from ..utils.userinteraction import ClearOn, Scroller
from .helpformatter import commandhelper
@@ -62,6 +62,13 @@ class Errors(commands.Cog):
elif (err.original == 'The channel is currently full'):
return await send_error_embed('{errors.full_channel}')
# Regular command invoke errors
if isinstance(err, commands.UserInputError):
if self.bot.debug:
self.logger.debug("User input error")
self.logger.debug(err)
return
if isinstance(err, WrongVoiceChannelError):
if (err.original.startswith("You need to be in my voice channel")):
return await send_error_embed(err.channels[0].name, title='{errors.my_channel}')
@@ -103,16 +110,15 @@ class Errors(commands.Cog):
else:
# Log all exceptions if the bot is in debug mode
if self.bot.debug:
tb = err.__traceback__
traceback.print_tb(tb)
self.logger.debug("Error running command: %s\nTraceback: %s" % (ctx.command, err))
self.logger.error("Got error in command %s" % ctx.command)
self.logger.exception(err.with_traceback(sys.exc_info()[2]))
else:
to_log = (RuntimeError, commands.CheckFailure, commands.CommandInvokeError,
commands.NoPrivateMessage)
if isinstance(err, to_log):
self.logger.debug("Error running command: %s\nTraceback: %s" % (ctx.command, err))
self.logger.info("Error running command: %s\nTraceback: %s" % (ctx.command, err))
async def setup(bot):
+84 -68
View File
@@ -9,8 +9,8 @@ from lavalink.models import AudioTrack
import asyncio
import json
import re
import urllib
from typing import List
import urllib.parse as urlparse
from typing import List, Optional
from bs4 import BeautifulSoup
@@ -49,10 +49,11 @@ class Music(commands.Cog):
async def cog_before_invoke(self, ctx):
""" Ensures a valid player exists whenever a command is run """
# Creates a new only if one doesn't exist, ensures a valid player for all checks.
self.bot.lavalink.player_manager.create(ctx.guild.id)
if ctx.guild:
self.bot.lavalink.player_manager.create(ctx.guild.id)
async def enqueue(self, ctx, track, embed, silent=False, check_max_length=True) -> tuple[discord.Embed, bool]:
player = self.bot.lavalink.player_manager.get(ctx.guild.id)
player: MixPlayer = self.bot.lavalink.player_manager.get(ctx.guild.id)
# Only add tracks that don't exceed the max track length
if maxlength := self.max_track_length(ctx.guild, player):
@@ -70,7 +71,7 @@ class Music(commands.Cog):
track.requester = ctx.author.id
# Add to player
track, pos_global, pos_local = player.add(requester=ctx.author.id, track=track)
track, pos_global, pos_local = player.add(requester=ctx.author, track=track)
if player.current is not None and not silent:
if player.current.stream:
@@ -137,7 +138,7 @@ class Music(commands.Cog):
async def _play(self, ctx, *, query: str):
""" Searches and plays a song from a given query. """
self.logger.debug("Query: %s" % query)
player = self.bot.lavalink.player_manager.get(ctx.guild.id)
player: MixPlayer = self.bot.lavalink.player_manager.get(ctx.guild.id)
query = query.strip('<>')
if not url_rx.match(query):
@@ -175,13 +176,13 @@ class Music(commands.Cog):
@require_playing(require_user_listening=True)
async def _seek(self, ctx, *, time: str):
""" Seeks to a given position in a track. """
player = self.bot.lavalink.player_manager.get(ctx.guild.id)
player: MixPlayer = self.bot.lavalink.player_manager.get(ctx.guild.id)
if seconds := time_rx.search(time):
# Convert to milliseconds, include sign
milliseconds = int(seconds.group())*1000 * (-1 if time.startswith('-1') else 1)
track_time = player.position + milliseconds
await player.seek(track_time)
await player.seek(int(track_time))
msg = ctx.localizer.format_str("{seek.track_moved}", _position=timeformatter.format_ms(track_time))
await ctx.send(msg)
else:
@@ -193,7 +194,7 @@ class Music(commands.Cog):
@voteable(requester_override=True, react_to_vote=True)
async def _skip(self, ctx):
""" Skips the current track. """
player = self.bot.lavalink.player_manager.get(ctx.guild.id)
player: MixPlayer = self.bot.lavalink.player_manager.get(ctx.guild.id)
await player.skip()
embed = self.get_current_song_embed(ctx)
@@ -204,7 +205,7 @@ class Music(commands.Cog):
@require_playing(require_user_listening=True)
async def _skip_to(self, ctx, pos: int = 1):
""" Plays the queue from a specific point. Disregards tracks before the pos. """
player = self.bot.lavalink.player_manager.get(ctx.guild.id)
player: MixPlayer = self.bot.lavalink.player_manager.get(ctx.guild.id)
# TODO: Do all queue out of range messages the same way
if pos < 1:
@@ -214,8 +215,11 @@ class Music(commands.Cog):
await player.skip(pos - 1)
msg = ctx.localizer.format_str("{skip_to.skipped_to}", _title=player.current.title, _pos=pos)
await ctx.send(msg)
if player.current:
msg = ctx.localizer.format_str("{skip_to.skipped_to}", _title=player.current.title, _pos=pos)
await ctx.send(msg)
else:
self.logger.error("Expected current track to be valid due to bounds checks")
@commands.command(name='stop')
@require_voice_connection()
@@ -223,7 +227,7 @@ class Music(commands.Cog):
@voteable(DJ_override=True, react_to_vote=True)
async def _stop(self, ctx):
""" Stops the player and clears its queue. """
player = self.bot.lavalink.player_manager.get(ctx.guild.id)
player: MixPlayer = self.bot.lavalink.player_manager.get(ctx.guild.id)
player.queue.clear()
await player.stop()
@@ -239,9 +243,9 @@ class Music(commands.Cog):
@commands.command(name='queue')
@require_queue(require_member_queue=True)
async def _queue(self, ctx, *, member: discord.Member = None):
async def _queue(self, ctx, *, member: Optional[discord.Member] = None):
""" Shows the player's queue. """
player = self.bot.lavalink.player_manager.get(ctx.guild.id)
player: MixPlayer = self.bot.lavalink.player_manager.get(ctx.guild.id)
pagified_queue = QueuePaginator(ctx.localizer, player, color=ctx.me.color, member=member)
scroller = Scroller(ctx, pagified_queue)
await scroller.start_scrolling(ClearOn.ManualExit | ClearOn.Timeout)
@@ -258,7 +262,7 @@ class Music(commands.Cog):
@require_playing()
async def _pause(self, ctx):
""" Pauses/Resumes the current track. """
player = self.bot.lavalink.player_manager.get(ctx.guild.id)
player: MixPlayer = self.bot.lavalink.player_manager.get(ctx.guild.id)
await player.set_pause(not player.paused)
if player.paused:
await ctx.send(ctx.localizer.format_str("{resume.paused}"))
@@ -270,8 +274,8 @@ class Music(commands.Cog):
@require_queue(require_author_queue=True)
async def _shuffle(self, ctx):
""" Shuffles your queue. """
player = self.bot.lavalink.player_manager.get(ctx.guild.id)
player.shuffle_user_queue(ctx.author.id)
player: MixPlayer = self.bot.lavalink.player_manager.get(ctx.guild.id)
player.shuffle_user_queue(ctx.author)
await ctx.send(ctx.localizer.format_str("{shuffle}"))
@commands.command(name='move')
@@ -304,7 +308,7 @@ class Music(commands.Cog):
page = 0
while True:
# Prompt the user for which track to move
selector = move_selector(ctx, player.user_queue(ctx.author.id), "{moved.choose_pos}", True)
selector = move_selector(ctx, player.user_queue(ctx.author), "{moved.choose_pos}", True)
message, track_to_move = await selector.start_scrolling(ClearOn.Timeout, message, page)
page = selector.page_number
@@ -312,7 +316,7 @@ class Music(commands.Cog):
break
# Prompt the user for where to move it
selector = move_selector(ctx, player.user_queue(ctx.author.id), "{moved.choose_song}", False)
selector = move_selector(ctx, player.user_queue(ctx.author), "{moved.choose_song}", False)
message, track_to_replace = await selector.start_scrolling(ClearOn.Timeout, message, page)
page = selector.page_number
@@ -323,11 +327,11 @@ class Music(commands.Cog):
# one in the queue. Find the index of both songs in the current queue
# Keep updating the user queue in case it changes while the user is selecting songs
user_queue = player.user_queue(ctx.author.id)
user_queue = player.user_queue(ctx.author)
try:
pos_initial = user_queue.index(track_to_move[0])
pos_final = user_queue.index(track_to_replace[0])
player.move_user_track(ctx.author.id, pos_initial, pos_final)
player.move_user_track(ctx.author, pos_initial, pos_final)
except ValueError:
# Track not found in queue. Queue could have changed during selection
# if a song was skipped or the current song finished
@@ -342,7 +346,7 @@ class Music(commands.Cog):
async def _remove(self, ctx):
player: MixPlayer = self.bot.lavalink.player_manager.get(ctx.guild.id)
user_queue: List[AudioTrack] = player.user_queue(ctx.author.id)
user_queue: List[AudioTrack] = player.user_queue(ctx.author)
tracks_to_remove = []
@@ -379,13 +383,13 @@ class Music(commands.Cog):
paginator = TextPaginator(color=ctx.me.color, title="Removed")
# If the user spent a long time selecting songs the queue might have changed
user_queue = player.user_queue(ctx.author.id)
user_queue = player.user_queue(ctx.author)
track_indexes_to_remove = [i for i, track in enumerate(user_queue) if track in tracks_to_remove]
# Sort in reverse order since songs shift index when deleting
tracks_removed = []
for track in sorted(track_indexes_to_remove, reverse=True):
if removed_track := player.remove_user_track(ctx.author.id, track):
if removed_track := player.remove_user_track(ctx.author, track):
tracks_removed.append(f"{removed_track.title}")
# We want to display the removed tracks in the original queue order, so reverse the list once again
@@ -400,22 +404,26 @@ class Music(commands.Cog):
@checks.dj_or()
@require_voice_connection()
@require_queue()
async def _djremove(self, ctx, pos: int, member: discord.Member = None):
async def _djremove(self, ctx, pos: int, member: Optional[discord.Member] = None):
""" Remove a song from either the global queue or a users queue"""
player = self.bot.lavalink.player_manager.get(ctx.guild.id)
player: MixPlayer = self.bot.lavalink.player_manager.get(ctx.guild.id)
# TODO: add way to allow "member" as regular arg in require_queue() instead of only kwargs
queue = player.user_queue(member.id) if member else player.queue
queue = player.user_queue(member) if member else player.queue
# TODO: Do all queue out of range messages the same way
if pos <= len(queue) and pos >= 1:
if member is None:
removed = player.remove_global_track(pos - 1)
else:
removed = player.remove_user_track(member.id, pos - 1)
removed = player.remove_user_track(member, pos - 1)
requester = self.bot.get_user(removed.requester)
await ctx.send(ctx.localizer.format_str("{dj_removed}", _title=removed.title, _user=requester.display_name))
if removed:
requester = self.bot.get_user(removed.requester)
await ctx.send(ctx.localizer.format_str("{dj_removed}", _title=removed.title,
_user=requester.display_name))
else:
self.logger.error("Did not remove a track when trying to remove index {pos}, despite bounds check")
else:
await ctx.send(ctx.localizer.format_str("{out_of_range}", _len=len(queue)))
@@ -425,8 +433,8 @@ class Music(commands.Cog):
@require_queue(require_member_queue=True)
async def _user_queue_remove(self, ctx, *, member: discord.Member):
""" Remove a song from either the global queue or a users queue"""
player = self.bot.lavalink.player_manager.get(ctx.guild.id)
player.remove_user_queue(member.id)
player: MixPlayer = self.bot.lavalink.player_manager.get(ctx.guild.id)
player.remove_user_queue(member)
embed = discord.Embed(description="{dj_remove_user}", color=ctx.me.color)
embed = ctx.localizer.format_embed(embed, _id=member.id)
@@ -436,8 +444,9 @@ class Music(commands.Cog):
@commands.command(name='search')
@require_voice_connection(should_connect=True)
async def _search(self, ctx, *, query):
player = self.bot.lavalink.player_manager.get(ctx.guild.id)
player: MixPlayer = self.bot.lavalink.player_manager.get(ctx.guild.id)
self.logger.debug("Query: %s" % query)
if not query.startswith('ytsearch:') and not query.startswith('scsearch:'):
query = 'ytsearch:' + query
@@ -478,7 +487,7 @@ class Music(commands.Cog):
@voteable(DJ_override=True, react_to_vote=True)
async def _disconnect(self, ctx):
""" Disconnects the player from the voice channel and clears its queue. """
player = self.bot.lavalink.player_manager.get(ctx.guild.id)
player: MixPlayer = self.bot.lavalink.player_manager.get(ctx.guild.id)
player.queue.clear()
await player.stop()
@@ -492,7 +501,7 @@ class Music(commands.Cog):
@voteable(DJ_override=True, react_to_vote=True)
async def _reconnect(self, ctx):
""" Tries to disconnect then reconnect the player in case the bot gets stuck on a song """
player = self.bot.lavalink.player_manager.get(ctx.guild.id)
player: MixPlayer = self.bot.lavalink.player_manager.get(ctx.guild.id)
current_channel = player.channel_id
async def recon():
@@ -508,16 +517,16 @@ class Music(commands.Cog):
start_time = player.position
await recon()
if track:
await player.play(track, start_time=start_time)
await player.play(track, start_time=int(start_time))
else:
await recon()
@commands.command(name='volume')
@checks.dj_or(alone=True, track_requester=True)
@require_playing(require_user_listening=True)
async def _volume(self, ctx, volume: int = None):
async def _volume(self, ctx, volume: Optional[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.player_manager.get(ctx.guild.id)
player: MixPlayer = self.bot.lavalink.player_manager.get(ctx.guild.id)
if not volume:
return await ctx.send(f'🔈 | {player.volume}%')
@@ -538,7 +547,7 @@ class Music(commands.Cog):
@voteable(DJ_override=True, react_to_vote=True)
async def _forceplay(self, ctx, *, query: str):
""" Searches and plays a song from a given query. """
player = self.bot.lavalink.player_manager.get(ctx.guild.id)
player: MixPlayer = self.bot.lavalink.player_manager.get(ctx.guild.id)
query = query.strip('<>')
if not url_rx.match(query):
@@ -575,11 +584,12 @@ class Music(commands.Cog):
async def _forcedisconnect(self, ctx):
""" Attempts to force disconnect the bot without checking if it is connected initially. """
try:
player = self.bot.lavalink.player_manager.get(ctx.guild.id)
player: MixPlayer = self.bot.lavalink.player_manager.get(ctx.guild.id)
player.queue.clear()
await player.stop()
except Exception:
self.logger.exception("Error forcedisconnecting")
except Exception as e:
self.logger.error("Error forcedisconnecting")
self.logger.exception(e)
await ctx.voice_client.disconnect(force=True)
embed = discord.Embed(description='{disconnect.disconnected}', color=ctx.me.color)
embed = ctx.localizer.format_embed(embed)
@@ -603,7 +613,7 @@ class Music(commands.Cog):
@checks.dj_or(alone=True)
async def _boost(self, ctx, boost: bool = False):
""" Set the equalizer to bass boost the music """
player = self.bot.lavalink.player_manager.get(ctx.guild.id)
player: MixPlayer = self.bot.lavalink.player_manager.get(ctx.guild.id)
if boost is not None:
await player.bassboost(boost)
@@ -630,7 +640,7 @@ class Music(commands.Cog):
@commands.command(name='history')
async def _history(self, ctx):
""" Show the last 10 songs played """
player = self.bot.lavalink.player_manager.get(ctx.guild.id)
player: MixPlayer = self.bot.lavalink.player_manager.get(ctx.guild.id)
history = player.get_history()
if not history:
embed = discord.Embed(description='{history.empty}', color=ctx.me.color)
@@ -647,8 +657,9 @@ class Music(commands.Cog):
embed = discord.Embed(title=ctx.localizer.format_str('{history.title}'), color=ctx.me.color,
description=description)
if thumbnail_url := player.current.extra["thumbnail_url"]:
embed.set_thumbnail(url=thumbnail_url)
if current := player.current:
if thumbnail_url := current.extra["thumbnail_url"]:
embed.set_thumbnail(url=thumbnail_url)
await ctx.send(embed=embed)
@commands.cooldown(1, 5, commands.BucketType.guild)
@@ -656,7 +667,7 @@ class Music(commands.Cog):
async def _lyrics(self, ctx, *query: str):
# TODO:
player = self.bot.lavalink.player_manager.get(ctx.guild.id)
player: MixPlayer = self.bot.lavalink.player_manager.get(ctx.guild.id)
genius_access_token = self.bot.APIkeys.get('genius', None)
@@ -666,13 +677,13 @@ class Music(commands.Cog):
excluded_words = ['music', 'video', 'version', 'original', 'lyrics', 'lyric',
'official', 'live', 'instrumental', 'audio', 'hd']
query = ' '.join(query)
query_as_string = ' '.join(query)
if not query and player.is_playing:
query = player.current.title
query = re.sub(r'[-()_\[\]]', '', query)
filtered_words = [word for word in query.split() if word.lower() not in excluded_words]
query = ' '.join(filtered_words)
if not query_as_string and player.is_playing and player.current:
query_as_string = player.current.title
query_as_string = re.sub(r'[-()_\[\]]', '', query_as_string)
filtered_words = [word for word in query_as_string.split() if word.lower() not in excluded_words]
query_as_string = ' '.join(filtered_words)
async def get_site_content(url):
async with self.bot.session.get(url) as resp:
@@ -683,8 +694,8 @@ class Music(commands.Cog):
status_msg = await ctx.send(embed=embed)
try:
url = 'https://api.genius.com/search?' + urllib.parse.urlencode(
{'access_token': genius_access_token, 'q': query})
url = 'https://api.genius.com/search?' + urlparse.urlencode(
{'access_token': genius_access_token, 'q': query_as_string})
result = await get_site_content(url)
response = json.loads(result)
song_id = response['response']['hits'][0]['result']['id']
@@ -715,7 +726,7 @@ class Music(commands.Cog):
await ctx.send(embed=page)
else:
paginator.add_page_indicator(ctx.localizer)
await Scroller(ctx, paginator).start_scrolling(ClearOn.AnyExi)
await Scroller(ctx, paginator).start_scrolling(ClearOn.AnyExit)
@commands.command(name='scrub')
@checks.dj_or(alone=True)
@@ -755,7 +766,7 @@ class Music(commands.Cog):
async def _loop(self, ctx):
# This is done using subcommands to have separate vote counts for starting and stopping.
if ctx.invoked_subcommand is None:
player = self.bot.lavalink.player_manager.get(ctx.guild.id)
player: MixPlayer = self.bot.lavalink.player_manager.get(ctx.guild.id)
embed = discord.Embed(color=ctx.me.color, title='Loop status')
if player.looping:
@@ -769,7 +780,7 @@ class Music(commands.Cog):
@voteable(DJ_override=True, react_to_vote=True)
async def _loop_start(self, ctx):
""" Set the equalizer to bass boost the music """
player = self.bot.lavalink.player_manager.get(ctx.guild.id)
player: MixPlayer = self.bot.lavalink.player_manager.get(ctx.guild.id)
player.enable_looping(True)
embed = discord.Embed(color=ctx.me.color)
embed.description = 'Looping turned on'
@@ -781,7 +792,7 @@ class Music(commands.Cog):
@voteable(DJ_override=True, react_to_vote=True)
async def _loop_stop(self, ctx):
""" Set the equalizer to bass boost the music """
player = self.bot.lavalink.player_manager.get(ctx.guild.id)
player: MixPlayer = self.bot.lavalink.player_manager.get(ctx.guild.id)
player.enable_looping(False)
embed = discord.Embed(color=ctx.me.color)
embed.description = 'Looping turned off'
@@ -794,7 +805,7 @@ class Music(commands.Cog):
async def track_hook(self, event):
if isinstance(event, TrackEndEvent):
channel = self.bot.get_channel(event.player.fetch('channel'))
player = self.bot.lavalink.player_manager.get(channel.guild.id)
player: MixPlayer = self.bot.lavalink.player_manager.get(channel.guild.id)
player.skip_voters.clear()
if isinstance(event, TrackStartEvent):
@@ -814,30 +825,31 @@ class Music(commands.Cog):
pass
@commands.Cog.listener()
async def on_voice_state_update(self, member, before, after):
async def on_voice_state_update(self, member: discord.Member, _: discord.VoiceState, after: discord.VoiceState):
""" Updates listeners when the bot or a user changes voice state """
if member.id == self.bot.user.id and after.channel is not None:
voice_channel = after.channel
player = self.bot.lavalink.player_manager.get(member.guild.id)
player: MixPlayer = self.bot.lavalink.player_manager.get(member.guild.id)
player.clear_listeners()
for member in voice_channel.members:
if not member.bot:
player.update_listeners(member, member.voice)
if not member.bot:
player = self.bot.lavalink.player_manager.get(member.guild.id)
player: MixPlayer = self.bot.lavalink.player_manager.get(member.guild.id)
if player is not None:
player.update_listeners(member, after)
await self.check_leave_voice(member.guild)
async def check_leave_voice(self, guild):
async def check_leave_voice(self, guild: discord.Guild):
""" Checks if the bot should leave the voice channel """
# TODO, disconnect timer?
player = self.bot.lavalink.player_manager.get(guild.id)
player: MixPlayer = self.bot.lavalink.player_manager.get(guild.id)
if len(player.listeners) == 0 and player.is_connected:
if player.queue.empty and player.current is None:
await player.stop()
await guild.voice_client.disconnect()
if voice_client := guild.voice_client:
await voice_client.disconnect(force=False)
async def leave_check(self):
for player_id in self.bot.lavalink.player_manager.players:
@@ -848,10 +860,14 @@ class Music(commands.Cog):
try:
await self.leave_check()
except Exception as err:
self.logger.debug("Error in leave_timer loop.\nTraceback: %s" % (err))
self.logger.error("Error in leave_timer loop")
self.logger.exception(err)
def wrap_in_button_callback(func, *args):
"""
Take a callback and convert it to the form required by a SelectorItem
"""
async def inner(_interaction, _button):
return await func(*args)
return inner
+2 -2
View File
@@ -136,7 +136,7 @@ def require_queue(require_member_queue=False, require_author_queue=False):
if require_member_queue:
try:
if member := kwargs['member']: # Ignore if member is None
user_queue = player.user_queue(member.id)
user_queue = player.user_queue(member)
if not user_queue:
embed = discord.Embed(description='{queue.user_empty}', color=ctx.me.color)
embed = ctx.localizer.format_embed(embed, _user=member.display_name)
@@ -145,7 +145,7 @@ def require_queue(require_member_queue=False, require_author_queue=False):
raise Exception("require_member_error can only be used on commands with a member keyword argument")
if require_author_queue:
user_queue = player.user_queue(ctx.author.id)
user_queue = player.user_queue(ctx.author)
if not user_queue:
embed = discord.Embed(description='{my_queue}', color=ctx.me.color)
embed = ctx.localizer.format_embed(embed)
+4 -2
View File
@@ -47,16 +47,18 @@ class NodeManager(commands.Cog):
for node in self.bot.lavalink.node_manager.nodes:
name_cache.append(node.name)
self.logger.debug("BEFORE")
for node in conf['lavalink nodes'] + self.settings.get('lavalink', 'nodes', []):
if node['name'] in name_cache:
continue
name_cache.append(node['name'])
self.bot.lavalink.add_node(**node)
self.logger.debug("Adding Lavalink node: %s on %s with the port %s in %s" % (
node['name'], node['host'],
node['port'], node['region'],))
new_nodes.append({**node})
name_cache.append(node['name'])
return new_nodes
async def _regioner(self, region):
@@ -163,7 +165,7 @@ class NodeManager(commands.Cog):
embed.title = 'Removed node from bot'
await ctx.send(embed=embed)
self.bot.lavalink.node_manager.remove_node(_node)
self.logger.info("Removed Lavalink node: %s" % _node.host)
self.logger.info("Removed Lavalink node: %s, %s" % (_node.name, _node.host))
self.settings.set('lavalink', 'nodes', [self.get_node_properties(n) for
n in self.bot.lavalink.node_manager.nodes])
+23 -16
View File
@@ -1,3 +1,6 @@
# Discord Packages
from lavalink.models import AudioTrack
import logging
from collections import OrderedDict, deque
from itertools import chain, cycle, islice
@@ -5,7 +8,7 @@ from random import shuffle
from typing import Generic, Iterable, Iterator, List, Optional, Tuple, TypeVar
# Would like to ensure the T has a "requester" attribute, but don't know if that is possible
T = TypeVar('T')
T = TypeVar('T', bound=AudioTrack)
QueueType = List[T]
@@ -31,7 +34,6 @@ class MixQueue(Generic[T]):
self.looping = False
self.loop_offset = 0
self.logger = logging.getLogger("musicbot").getChild("Queue")
self.logger = self.logger.getChild("Queue")
def __str__(self) -> str:
tmp = ''
@@ -96,9 +98,9 @@ class MixQueue(Generic[T]):
self._history.append(next_track)
return next_track
except IndexError as e:
self.logger.error(e)
self.logger.error(self.loop_offset)
self.logger.error(len(self))
self.logger.error(f"Failed to get the next track in looping mode: loop offset {self.loop_offset}." +
" Queue length: {len(self)}")
self.logger.exception(e)
self.loop_offset = min(self.loop_offset, len(self)-1)
else:
if first_queue := self.first_queue:
@@ -123,12 +125,13 @@ class MixQueue(Generic[T]):
localpos = pos
# Return info about track position
return track, self._loc_to_glob(requester, localpos), localpos
global_position = self._loc_to_glob(requester, localpos)
return track, global_position, localpos
def add_next_track(self, track: T) -> None:
def add_priorty_queue_track(self, track: T) -> None:
self.priority_queue.append(track)
def remove_user_queue(self, requester: int) -> None:
def remove_user_queue(self, requester: int) -> QueueType:
user_queue = self.queues.get(requester, [])
if user_queue:
if self.looping:
@@ -137,6 +140,7 @@ class MixQueue(Generic[T]):
self.remove_user_track(requester, pos)
else:
self.queues.pop(requester)
return user_queue
def remove_user_track(self, requester: int, pos: int) -> Optional[T]:
user_queue = self.queues.get(requester)
@@ -172,6 +176,7 @@ class MixQueue(Generic[T]):
queue.insert(final, track)
return track
except IndexError:
self.logger.debug(f"Got invalid index when moving track from {initial} to {final}")
pass
def shuffle_user_queue(self, requester: int) -> None:
@@ -180,17 +185,19 @@ class MixQueue(Generic[T]):
shuffle(queue)
def enable_looping(self, looping: bool) -> None:
if (not self.looping) and looping: # Not enabled and turning on
if (not self.looping) and looping: # Enable only if not already enabled
self.looping = looping
self.loop_offset = 0
elif self.looping and not looping: # Enabled to turning off
tmp = [track for track in self.get_queue()]
for key, _ in self.queues.items():
self.queues = OrderedDict()
self.logger.debug("Looping enabled")
elif self.looping and not looping: # Disable only if enabled
# Copy queue in current (looping) state
queue_copy = [track for track in self.get_queue()]
self.queues = OrderedDict()
self.looping = looping
for track in tmp:
for track in queue_copy:
self.add_track(track.requester, track)
self.loop_offset = 0
self.logger.debug("Looping disabled, queue reordered")
# Switches the order of user queues
def _shift_queues(self) -> None:
@@ -217,9 +224,9 @@ class MixQueue(Generic[T]):
globpos += 1
return globpos
def _glob_to_loc(self, pos: int) -> Tuple[Optional[T], Optional[int]]:
def _glob_to_loc(self, pos: int) -> Tuple[Optional[int], Optional[int]]:
try:
song = next(islice(self, pos, pos + 1))
song: T = next(islice(self, pos, pos + 1))
except (ValueError, StopIteration):
return None, None
user_queue = self.queues.get(song.requester, [])
+60 -29
View File
@@ -5,10 +5,13 @@ from lavalink import AudioTrack, DefaultPlayer, Node
from lavalink.events import QueueEndEvent, TrackEndEvent, TrackExceptionEvent, TrackStartEvent, TrackStuckEvent
from lavalink.filters import Equalizer, Timescale
import logging
from typing import Dict, List, Optional, Set, Tuple
from .mixqueue import MixQueue
RequesterType = discord.Member
class MixPlayer(DefaultPlayer):
def __init__(self, guild_id: int, node: Node):
@@ -16,11 +19,12 @@ class MixPlayer(DefaultPlayer):
self.queue: MixQueue[AudioTrack] = MixQueue()
self.listeners: Set[int] = set()
self.voteables: Dict[int, Set[int]] = {}
self.listeners: Set[RequesterType] = set()
self.voteables: Dict[int, Set[RequesterType]] = {}
self.skip_voters: Set[int] = set()
self.boosted: bool = False
self.nightcore_enabled: bool = False
self.logger = logging.getLogger("musicbot").getChild("MixPlayer")
bands = [
(0, 0.15),
(1, 0.15),
@@ -36,38 +40,50 @@ class MixPlayer(DefaultPlayer):
self.nightcore_filter: lavalink.Filter = Timescale()
self.nightcore_filter.update(speed=1.25, pitch=1.25)
def add(self, requester: int, track: AudioTrack, pos: Optional[int] = None) -> Tuple[AudioTrack, int, int]:
def add(self, requester: RequesterType, track: AudioTrack,
pos: Optional[int] = None) -> Tuple[AudioTrack, int, int]:
""" Adds a track to the queue. """
return self.queue.add_track(requester, track, pos)
track, global_position, localpos = self.queue.add_track(requester.id, track, pos)
self.logger.info(f"Track {track.title} added for {requester.display_name} @ ({global_position}, {localpos}).")
return track, global_position, localpos
def add_priority(self, track: AudioTrack):
""" Adds a track to beginning of the queue """
self.queue.add_next_track(track)
self.queue.add_priorty_queue_track(track)
self.logger.info(f"Track {track} added to priority queue.")
def move_user_track(self, requester: int, initial: int, final: int):
def move_user_track(self, requester: RequesterType, initial: int, final: int):
""" Moves a track in a users queue"""
return self.queue.move_user_track(requester, initial, final)
if moved := self.queue.move_user_track(requester.id, initial, final):
self.logger.info(f"Track {moved.title} moved from {initial} to {final}" +
f"for in queue for {requester.display_name}")
def remove_user_queue(self, requester: int):
self.queue.remove_user_queue(requester)
def remove_user_queue(self, requester: RequesterType):
removed = self.queue.remove_user_queue(requester.id)
if removed: # Removed queues can be empty
self.logger.info(f"Queue for requester {requester.display_name} removed. {len(removed)} tracks removed")
else:
self.logger.debug(f"User {requester.display_name} has no more tracks. Remove from queue")
def remove_user_track(self, requester: int, pos: int) -> Optional[AudioTrack]:
def remove_user_track(self, requester: RequesterType, pos: int) -> Optional[AudioTrack]:
""" Removes the song at <pos> from the queue of requester """
return self.queue.remove_user_track(requester, pos)
if track := self.queue.remove_user_track(requester.id, pos):
self.logger.info(f"Track {track.title} for requester {requester.display_name} removed.")
return track
def remove_global_track(self, pos: int) -> Optional[AudioTrack]:
""" Removes the song at <pos> in the global queue """
return self.queue.remove_global_track(pos)
def shuffle_user_queue(self, requester: int):
def shuffle_user_queue(self, requester: RequesterType):
""" Randomly reorders the queue of requester """
self.queue.shuffle_user_queue(requester)
self.queue.shuffle_user_queue(requester.id)
def user_queue(self, user: int) -> List[AudioTrack]:
return self.queue.get_user_queue(user)
def user_queue(self, requester: RequesterType) -> List[AudioTrack]:
return self.queue.get_user_queue(requester.id)
def user_queue_with_global_index(self, user: int) -> List[Tuple[AudioTrack, int]]:
return self.queue.get_user_queue_with_index(user)
def user_queue_with_global_index(self, requester: RequesterType) -> List[Tuple[AudioTrack, int]]:
return self.queue.get_user_queue_with_index(requester.id)
def global_queue(self) -> List[AudioTrack]:
return self.queue.get_queue()
@@ -76,9 +92,9 @@ class MixPlayer(DefaultPlayer):
return self.queue.history
def queue_duration(self, include_current: bool = False,
member: Optional[discord.Member] = None, end_pos: Optional[int] = None):
member: Optional[RequesterType] = None, end_pos: Optional[int] = None):
duration = 0
queue = self.user_queue(member.id) if member else self.queue
queue = self.user_queue(member) if member else self.queue
for i, track in enumerate(queue):
if i == end_pos:
@@ -101,8 +117,9 @@ class MixPlayer(DefaultPlayer):
if not track:
if self.queue.empty:
self.boosted = False
self.nightcore_enabled = False
self.logger.debug("No track provided to play function and queue is empty. Resetting Audio filters")
await self.bassboost(False)
await self.nightcoreify(False)
await self.stop()
await self.node._dispatch_event(QueueEndEvent(self))
return
@@ -117,11 +134,14 @@ class MixPlayer(DefaultPlayer):
await self.node._send(op='play', guildId=str(self.guild_id),
track=track.track, startTime=start_time)
await self.node._dispatch_event(TrackStartEvent(self, track))
self.logger.info(f"Playing track: {track.title}")
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()
for _ in range(pos):
track = self.queue.pop_first()
self.logger.debug(f"Track {track} skipped")
self.logger.info(f"Skipped {pos + 1} tracks")
self.clear_votes()
await self.play()
@@ -129,36 +149,43 @@ class MixPlayer(DefaultPlayer):
""" Stops the player. """
await self.node._send(op='stop', guildId=str(self.guild_id))
self.current = None
self.queue.looping = False
self.queue.enable_looping(False)
self.logger.info("Music player stopped, clearing current track and stopping looping")
self.clear_votes()
def update_listeners(self, member, voice_state):
def update_listeners(self, member: RequesterType, voice_state):
if self.channel_id is not None:
vc = int(self.channel_id)
if voice_state.channel is None or voice_state.channel.id != vc:
self.logger.debug(f"User {member.display_name} left the voice channel")
self.listeners.discard(member)
self.remove_member_votes(member)
else:
if voice_state.deaf or voice_state.self_deaf:
self.logger.debug(f"User {member.display_name} is now deafened")
self.listeners.discard(member)
self.remove_member_votes(member)
else:
self.listeners.add(member)
self.logger.debug(f"Member voice state update. Votes are now {self.voteables}")
def clear_listeners(self):
self.logger.debug("Listeners cleared")
self.listeners.clear()
def add_vote(self, category, member):
def add_vote(self, category, member: RequesterType):
if member in self.listeners:
if category not in self.voteables:
self.voteables[category] = set()
self.voteables[category].add(member)
self.logger.debug(f"{member.display_name} added vote for {category}")
def remove_member_votes(self, member):
def remove_member_votes(self, member: RequesterType):
for _, votes in self.voteables.items():
votes.discard(member)
self.logger.debug(f"Removing votes for {member.display_name}.")
def get_voters(self, category):
def get_voters(self, category) -> Set[RequesterType]:
if category not in self.voteables:
self.voteables[category] = set()
return self.voteables[category]
@@ -167,7 +194,7 @@ class MixPlayer(DefaultPlayer):
for _, votes in self.voteables.items():
votes.clear()
def enable_looping(self, looping):
def enable_looping(self, looping: bool):
if (not self.queue.looping) and looping:
self.queue.enable_looping(looping)
if track := self.current:
@@ -180,6 +207,7 @@ class MixPlayer(DefaultPlayer):
""" Handles the given event as necessary. """
if isinstance(event, (TrackStuckEvent, TrackExceptionEvent)) or \
isinstance(event, TrackEndEvent) and event.reason == 'FINISHED':
self.logger.debug("Track ended, clearing votes")
self.skip_voters.clear()
for _, votes in self.voteables.items():
votes.clear()
@@ -190,13 +218,16 @@ class MixPlayer(DefaultPlayer):
if boost:
await self.set_filter(self.bass_boost_filter)
else:
self.logger.info("Disabling bass boost")
await self.remove_filter(self.bass_boost_filter)
async def nightcoreify(self, nightcore: bool = False):
self.nightcore_enabled = nightcore
if nightcore:
self.logger.info("Enabling nightcore mode")
await self.set_filter(self.nightcore_filter)
else:
self.logger.info("Disabling nightcore mode")
await self.remove_filter(self.nightcore_filter)
@property
+2 -2
View File
@@ -62,8 +62,8 @@ class TestMixQueue():
def test_priority_queue(self):
queue = self.setup_baisc_queue()
queue.add_next_track("priority_item1")
queue.add_next_track("priority_item2")
queue.add_priorty_queue_track("priority_item1")
queue.add_priorty_queue_track("priority_item2")
assert queue.pop_first() == "priority_item1"
assert queue.pop_first() == "priority_item2"
@@ -16,7 +16,7 @@ class QueuePaginator(TextPaginator):
duration = player.queue_duration(member=member, include_current=include_current)
if member:
member_queue = player.user_queue_with_global_index(member.id)
member_queue = player.user_queue_with_global_index(member)
title = localizer.format_str("{queue.userqueue}",
_user=member.display_name, _length=len(member_queue), _duration=duration)