diff --git a/bot.py b/bot.py index c6762ce..47f93b9 100644 --- a/bot.py +++ b/bot.py @@ -12,15 +12,14 @@ import aiohttp import yaml # Bot Utilities -from cogs.helpformatter import commandhelper from cogs.utils.alias import Aliaser from cogs.utils.context import Context from cogs.utils.localizer import Localizer, LocalizerWrapper from cogs.utils.logger import BotLogger -from cogs.utils.paginator import Scroller from cogs.utils.settingsmanager import Settings initial_extensions = [ + 'cogs.errors', 'cogs.cogs', 'cogs.settings', 'cogs.misc', @@ -63,42 +62,6 @@ class Bot(commands.Bot): except Exception: self.logger.exception("Loading of extension %s failed" % extension) - async def on_command_error(self, ctx, err): - if not self.debug: - if (isinstance(err, commands.MissingRequiredArgument) or - isinstance(err, commands.BadArgument)): - paginator = commandhelper(ctx, ctx.command, ctx.invoker, include_subcmd=False) - scroller = Scroller(ctx, paginator) - await scroller.start_scrolling() - - if isinstance(err, commands.CommandInvokeError): - self.logger.debug("Error running command: %s\n Traceback: %s" % (ctx.command, err)) - pass - - elif isinstance(err, commands.NoPrivateMessage): - self.logger.debug("Error running command: %s\n Traceback: %s" % (ctx.command, err)) - await ctx.send('That command is not available in DMs') - - elif isinstance(err, commands.CommandOnCooldown): - await ctx.send(f"{ctx.message.author.mention} Command is on cooldown. " - f"Try again in `{err.retry_after:.1f}` seconds.") - - elif isinstance(err, RuntimeError): - self.logger.debug("Error running command: %s\n Traceback: %s" % (ctx.command, err)) - pass - - elif isinstance(err, commands.CheckFailure): - self.logger.debug("Error running command: %s\n Traceback: %s" % (ctx.command, err)) - pass - - elif isinstance(err, commands.CommandNotFound): - pass - else: - tb = err.__traceback__ - traceback.print_tb(tb) - print(err) - self.logger.debug("Error running command: %s\n Traceback: %s" % (ctx.command, err)) - async def on_message(self, message): if message.author.bot: return @@ -121,6 +84,9 @@ class Bot(commands.Bot): async def on_ready(self): if not hasattr(self, 'uptime'): self.uptime = time.time() + + self.session = aiohttp.ClientSession(loop=self.loop) + for extension in on_ready_extensions: try: self.logger.debug("Loading extension %s" % extension) diff --git a/cogs/cogs.py b/cogs/cogs.py index fd78a2c..1c00b3d 100644 --- a/cogs/cogs.py +++ b/cogs/cogs.py @@ -23,8 +23,8 @@ class Cogs(commands.Cog): try: self.bot.load_extension(f'cogs.{module}') await ctx.send(f'{module} loaded') - except Exception as e: - await ctx.send(f'```py\n{traceback.format_exc(e)}\n```') + except Exception: + await ctx.send(f'```py\n{traceback.format_exc()}\n```') @_cogs.command() @commands.is_owner() @@ -67,7 +67,7 @@ class Cogs(commands.Cog): @commands.is_owner() async def _shutdown(self, ctx): """Logs out and stops.""" - self.bot.lavalink.players.clear() + self.bot.lavalink.player_manager.players.clear() await self.bot.logout() diff --git a/cogs/errors.py b/cogs/errors.py new file mode 100644 index 0000000..f0ea6da --- /dev/null +++ b/cogs/errors.py @@ -0,0 +1,84 @@ +# Discord Packages +import discord +from discord.ext import commands + +import traceback + +# Bot Utilities +from cogs.helpformatter import commandhelper +from cogs.utils.paginator import Scroller + + +class Errors(commands.Cog): + def __init__(self, bot): + self.bot = bot + self.logger = self.bot.main_logger.bot_logger.getChild("Errors") + + async def base_msg(self, ctx, state: int = 0xFCBA03): + embed = discord.Embed(color=state) + embed.title = '{errors.error_occurred}' + embed.set_footer(icon_url=ctx.author.avatar_url, + text=f'{ctx.author.name}#{ctx.author.discriminator}') + return embed + + @commands.Cog.listener() + async def on_command_error(self, ctx, err): + + # Help commands + if (isinstance(err, commands.MissingRequiredArgument) or + isinstance(err, commands.BadArgument)): + paginator = commandhelper(ctx, ctx.command, ctx.invoker, include_subcmd=False) + scroller = Scroller(ctx, paginator) + await scroller.start_scrolling() + + if isinstance(err, (commands.CommandNotFound)): + return + + async def send_error_embed(description): + embed = await self.base_msg(ctx, state=0xFC0303) + embed.description = description + embed = ctx.localizer.format_embed(embed) + return await ctx.send(embed=embed) + + # Music related errors + if isinstance(err, commands.CommandInvokeError): + if (err.original == 'Join a voicechannel first.'): + return await send_error_embed('{errors.join_voice_first}') + + elif (err.original == 'Not connected.'): + return await send_error_embed('{errors.not_connected}') + + elif (err.original == 'I need the `CONNECT` and `SPEAK` permissions.'): + return await send_error_embed('{errors.need_permission}') + + elif (err.original == 'You need to be in the right voice channel'): + return await send_error_embed('{errors.right_channel}') + + elif (err.original == 'You need to be in my voicechannel.'): + return await send_error_embed('{errors.my_channel}') + + + if isinstance(err, commands.CommandOnCooldown): + await ctx.send(f"{ctx.message.author.mention} Command is on cooldown. " + f"Try again in `{err.retry_after:.1f}` seconds.") + + elif isinstance(err, commands.NoPrivateMessage): + await ctx.send('That command is not available in DMs') + + 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)) + + 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)) + + +def setup(bot): + bot.add_cog(Errors(bot)) diff --git a/cogs/misc.py b/cogs/misc.py index b51707b..05873f8 100644 --- a/cogs/misc.py +++ b/cogs/misc.py @@ -54,7 +54,7 @@ class Misc(commands.Cog): lavalink = self.bot.lavalink listeners = 0 - for guild, player in lavalink.player_manager.players: + for guild, player in lavalink.player_manager.players.items(): listeners += len(player.listeners) embed.add_field(name='{music.players}', value=f'{len(lavalink.player_manager.players)}') diff --git a/cogs/music.py b/cogs/music.py index 7f04ca4..3742dce 100644 --- a/cogs/music.py +++ b/cogs/music.py @@ -15,7 +15,8 @@ from typing import Optional from bs4 import BeautifulSoup -from .utils import RoxUtils, checks, timeformatter +from .utils import checks, thumbnailer, timeformatter +from .utils.mixplayer import MixPlayer from .utils.paginator import QueuePaginator, Scroller, TextPaginator from .utils.selector import Selector @@ -69,13 +70,17 @@ class Music(commands.Cog): numtracks = 0 for track in tracks: if track['info']['length'] <= maxlength: - track = lavalink.models.AudioTrack(track, ctx.author.id) + thumbnail_url = await thumbnailer.ThumbNailer.identify( + self, track['info']['identifier'], track['info']['uri']) + track = lavalink.models.AudioTrack(track, ctx.author.id, thumbnail_url=thumbnail_url) player.add(requester=ctx.author.id, track=track) numtracks += 1 else: numtracks = len(tracks) for track in tracks: - track = lavalink.models.AudioTrack(track, ctx.author.id) + thumbnail_url = await thumbnailer.ThumbNailer.identify( + self, track['info']['identifier'], track['info']['uri']) + track = lavalink.models.AudioTrack(track, ctx.author.id, thumbnail_url=thumbnail_url) player.add(requester=ctx.author.id, track=track) embed.title = '{playlist_enqued}' @@ -84,7 +89,9 @@ class Music(commands.Cog): await ctx.send(embed=embed) else: track = results['tracks'][0] - track = lavalink.models.AudioTrack(track, ctx.author.id) + thumbnail_url = await thumbnailer.ThumbNailer.identify( + self, track['info']['identifier'], track['info']['uri']) + track = lavalink.models.AudioTrack(track, ctx.author.id, thumbnail_url=thumbnail_url) await self.enqueue(ctx, track, embed) embed = ctx.localizer.format_embed(embed) await ctx.send(embed=embed) @@ -135,7 +142,7 @@ class Music(commands.Cog): if player.current: song = f'**[{player.current.title}]({player.current.uri})**' embed = discord.Embed(color=ctx.me.color, description=song, title='{now}') - thumbnail_url = await RoxUtils.ThumbNailer.identify(self, player.current.identifier, player.current.uri) + thumbnail_url = player.current.extra["thumbnail_url"] member = ctx.guild.get_member(player.current.requester) if thumbnail_url: embed.set_thumbnail(url=thumbnail_url) @@ -186,7 +193,8 @@ class Music(commands.Cog): player.queue.clear() await player.stop() - await ctx.send(ctx.localizer.format_str("{stop}")) + embed = discord.Embed(color=ctx.me.color, title='{stop}') + await ctx.send(embed=ctx.localizer.format_embed(embed)) @commands.command(name='now') async def _now(self, ctx): @@ -205,7 +213,7 @@ class Music(commands.Cog): member = ctx.guild.get_member(player.current.requester) embed = discord.Embed(color=ctx.me.color, description=song, title='{now}') - thumbnail_url = await RoxUtils.ThumbNailer.identify(self, player.current.identifier, player.current.uri) + thumbnail_url = player.current.extra["thumbnail_url"] if thumbnail_url: embed.set_thumbnail(url=thumbnail_url) @@ -325,7 +333,7 @@ class Music(commands.Cog): # Create a nice embed explaining what happened song = f'**[{moved.title}]({moved.uri})**' embed = discord.Embed(color=ctx.me.color, description=song, title='{moved.moved}') - thumbnail_url = await RoxUtils.ThumbNailer.identify(self, moved.identifier, moved.uri) + thumbnail_url = moved.extra["thumbnail_url"] member = ctx.guild.get_member(moved.requester) if thumbnail_url: embed.set_thumbnail(url=thumbnail_url) @@ -551,7 +559,7 @@ class Music(commands.Cog): description = ctx.localizer.format_str("{history.current}", _title=track.title, _uri=track.uri, _id=track.requester) + '\n\n' description += ctx.localizer.format_str("{history.previous}", _len=len(history)-1) + '\n' - thumb_url = await RoxUtils.ThumbNailer.identify(self, track.identifier, track.uri) + thumbnail_url = track.extra["thumbnail_url"] for index, track in enumerate(history[1:], start=1): description += ctx.localizer.format_str("{history.track}", _index=-index, _title=track.title, _uri=track.uri, _id=track.requester) + '\n' @@ -559,8 +567,8 @@ class Music(commands.Cog): embed = discord.Embed(title=ctx.localizer.format_str('{history.title}'), color=ctx.me.color, description=description) - if thumb_url: - embed.set_thumbnail(url=thumb_url) + if thumbnail_url: + embed.set_thumbnail(url=thumbnail_url) await ctx.send(embed=embed) @commands.cooldown(1, 5, commands.BucketType.guild) @@ -784,10 +792,10 @@ class Music(commands.Cog): embed.add_field(name="{enqueue.playing_in}", value=f"`{until_play} ({{enqueue.estimated}})`", inline=True) embed.title = '{enqueue.enqueued}' - thumb_url = await RoxUtils.ThumbNailer.identify(self, track.identifier, track.uri) + thumbnail_url = track.extra["thumbnail_url"] - if thumb_url: - embed.set_thumbnail(url=thumb_url) + if thumbnail_url: + embed.set_thumbnail(url=thumbnail_url) duration = timeformatter.format_ms(int(track.duration)) embed.description = f'[{track.title}]({track.uri})\n**{duration}**' diff --git a/cogs/musicevents.py b/cogs/musicevents.py index 2c40e74..04d5091 100644 --- a/cogs/musicevents.py +++ b/cogs/musicevents.py @@ -5,13 +5,14 @@ A cog to separate events from regular music commands # Discord Packages import lavalink import lavalink.events -from discord.ext import commands +from discord.ext import commands, tasks class MusicEvents(commands.Cog): def __init__(self, bot): self.bot = bot - + self.leave_timer.start() + self.logger = self.bot.main_logger.bot_logger.getChild("Errors") bot.lavalink.add_event_hook(self.track_hook) def cog_unload(self): @@ -65,6 +66,17 @@ class MusicEvents(commands.Cog): await player.stop() await self.connect_to(guild.id, None) + async def leave_check(self): + for player_id in self.bot.lavalink.player_manager.players: + await self.check_leave_voice(self.bot.get_guild(player_id)) + + @tasks.loop(seconds=10.0) + async def leave_timer(self): + try: + await self.leave_check() + except Exception as err: + self.logger.debug("Error in leave_timer loop.\nTraceback: %s" % (err)) + def setup(bot): bot.add_cog(MusicEvents(bot)) diff --git a/cogs/utils/RoxUtils.py b/cogs/utils/thumbnailer.py similarity index 100% rename from cogs/utils/RoxUtils.py rename to cogs/utils/thumbnailer.py diff --git a/localization/en_en/music.yaml b/localization/en_en/music.yaml index 2afafbb..f7c84f4 100644 --- a/localization/en_en/music.yaml +++ b/localization/en_en/music.yaml @@ -15,7 +15,7 @@ skip_to: invalid_pos: Invalid specified position. exceeds_queue: The position exceeds the queue\'s length. skipped_to: skipped to `{_title}` at position `{_pos}` -stop: "⏹ | Stopped." +stop: "Stopped." live: "\U0001F534 LIVE" now: Now Playing queue: @@ -74,3 +74,10 @@ history: settings_check: textchannel: "You can't use music commands in this channel.\nTry in: " voicechannel: 'I can only play music in: ' +errors: + error_occurred: An error occurred + join_voice_first: You need to join a voicechannel first + not_connected: Not connected + need_permission: I need the `CONNECT` and `SPEAK` permissions + right_channel: You need to be in the right voice channel + my_channel: You need to be in my voicechannel diff --git a/localization/nb_no/music.yaml b/localization/nb_no/music.yaml index 42c6c61..d91492f 100644 --- a/localization/nb_no/music.yaml +++ b/localization/nb_no/music.yaml @@ -74,3 +74,10 @@ history: settings_check: textchannel: "Du kan ikke bruke musikkommandoer i denne kanalen.\nPrøv i: " voicechannel: 'Jeg kan bare spille musikk i: ' +errors: + error_occurred: En feil oppstod + join_voice_first: Du må være i en stemmekanal + not_connected: Ikke tilkoblet + need_permission: Jeg trenger tillgang til `CONNECT` og `SPEAK` + right_channel: Du må være i riktig kanal + my_channel: Du må være i samme stemmekanal som meg diff --git a/requirements.txt b/requirements.txt index d65fede..9f1d579 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,5 @@ -discord.py >= 1.3.3 -git+https://github.com/Devoxin/Lavalink.py@7a93b5b50d324f9dfd073bd8a3c835261a98a50b +discord.py +git+https://github.com/Devoxin/Lavalink.py@dev asyncio pyyaml BeautifulSoup4 -aiohttp>=3.6.0,<3.7.0 \ No newline at end of file