Merge pull request #54 from r-Norge/feature/update_decorator_and_error_handling

Feature/update decorator and error handling
This commit is contained in:
Ev-1 authored and GitHub committed 2022-10-09 14:33:40 +02:00
commit ca43adf925
14 files changed
+287 -176

No files matched your search

+5 -2
View File
@@ -1,5 +1,6 @@
# Discord Packages
import discord
import lavalink
from discord.ext import commands
from discord.flags import MemberCacheFlags
@@ -8,6 +9,7 @@ import os
import time
import traceback
from argparse import ArgumentParser, RawTextHelpFormatter
from typing import Optional
import aiohttp
import yaml
@@ -35,7 +37,7 @@ def _get_prefix(bot, message):
return commands.when_mentioned_or(*prefixes)(bot, message)
class Bot(commands.Bot):
class MusicBot(commands.Bot):
def __init__(self, datadir, debug: bool = False):
intents = discord.Intents.all()
super().__init__(command_prefix=_get_prefix,
@@ -55,6 +57,7 @@ class Bot(commands.Bot):
self.main_logger = logger
self.logger = self.main_logger.bot_logger.getChild("Bot")
self.logger.debug("Debug: %s" % debug)
self.lavalink: Optional[lavalink.Client] = None
async def on_message(self, message):
if message.author.bot:
@@ -110,7 +113,7 @@ class Bot(commands.Bot):
def run_bot(datadir, debug: bool = False):
bot = Bot(datadir, debug=debug)
bot = MusicBot(datadir, debug=debug)
bot.run()
+4 -2
View File
@@ -3,13 +3,15 @@ from discord.ext import commands
import traceback
from bot import MusicBot
from ..utils.userinteraction import ClearOn, Scroller
from .helpformatter import commandhelper
class CogManager(commands.Cog):
def __init__(self, bot):
self.bot = bot
def __init__(self, bot: MusicBot):
self.bot: MusicBot = bot
self.settings = self.bot.settings
@commands.group(name='cogmanager', hidden=True)
+52 -49
View File
@@ -4,14 +4,16 @@ from discord.ext import commands
import sys
from bot import MusicBot
from ..utils.userinteraction import ClearOn, Scroller
from .helpformatter import commandhelper
from .music.music_errors import WrongTextChannelError, WrongVoiceChannelError
from .music import music_errors
class Errors(commands.Cog):
def __init__(self, bot):
self.bot = bot
def __init__(self, bot: MusicBot):
self.bot: MusicBot = bot
self.logger = self.bot.main_logger.bot_logger.getChild("Errors")
async def base_msg(self, ctx, state: int = 0xFCBA03):
@@ -42,60 +44,61 @@ class Errors(commands.Cog):
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 == 'Not playing'):
return await send_error_embed('{not_playing}')
elif (err.original == 'Not listening'):
return await send_error_embed('{have_to_listen}')
elif (err.original == 'The channel is currently full'):
return await send_error_embed('{errors.full_channel}')
# Regular command invoke errors
# User input 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}')
# Music related errors
if isinstance(err, music_errors.MusicError):
if isinstance(err, music_errors.UserNotConnectedError):
return await send_error_embed('{errors.join_voice_first}')
elif (err.original.startswith('You need to be in the right voice channel')):
changed = False
response = ctx.localizer.format_str('{settings_check.voicechannel}')
for channel_id in err.channels:
channel = ctx.guild.get_channel(channel_id)
if channel is not None:
response += f'{channel.name}, '
changed = True
if changed:
return await send_error_embed(response[:-2], title='{errors.right_channel}')
else:
return await send_error_embed('{errors.right_channel}')
elif isinstance(err, music_errors.BotNotConnectedError):
return await send_error_embed('{errors.not_connected}')
if isinstance(err, WrongTextChannelError):
try:
await ctx.message.delete()
except Exception as e:
self.logger.debug("Error deleting message: %s\nTraceback: %s" % (e, err))
response = ctx.localizer.format_str('{settings_check.textchannel}')
for channel_id in err.channels:
response += f'<#{channel_id}>, '
msg = await send_error_embed(response[:-2], title='{errors.right_channel}')
return await msg.delete(delay=5)
elif isinstance(err, music_errors.MissingPermissionsError):
return await send_error_embed('{errors.need_permission}')
elif isinstance(err, music_errors.PlayBackStatusError):
if isinstance(err, music_errors.RequirePlayingError):
return await send_error_embed('{not_playing}')
if isinstance(err, music_errors.RequireListeningError):
return await send_error_embed('{have_to_listen}')
elif isinstance(err, music_errors.ChannelError):
if isinstance(err, music_errors.VoiceChannelFullError):
return await send_error_embed('{errors.full_channel}')
elif isinstance(err, music_errors.UserInDifferentVoiceChannelError):
return await send_error_embed(err.channels[0].name, title='{errors.my_channel}')
elif isinstance(err, music_errors.ConfiguredChannelsError):
if isinstance(err, music_errors.WrongVoiceChannelError):
changed = False
response = ctx.localizer.format_str('{settings_check.voicechannel}')
for channel_id in err.channels:
channel = ctx.guild.get_channel(channel_id)
if channel is not None:
response += f'{channel.name}, '
changed = True
if changed:
return await send_error_embed(response[:-2], title='{errors.right_channel}')
else:
return await send_error_embed('{errors.right_channel}')
if isinstance(err, music_errors.WrongTextChannelError):
try:
await ctx.message.delete()
except Exception as e:
self.logger.debug("Error deleting message: %s\nTraceback: %s" % (e, err))
response = ctx.localizer.format_str('{settings_check.textchannel}')
for channel_id in err.channels:
response += f'<#{channel_id}>, '
msg = await send_error_embed(response[:-2], title='{errors.right_channel}')
return await msg.delete(delay=5)
if isinstance(err, commands.CheckFailure):
pass
+4 -2
View File
@@ -1,14 +1,16 @@
# Discord Packages
from discord.ext import commands
from bot import MusicBot
from ...utils.userinteraction.scroller import ClearOn, Scroller
from .helpformatter import coghelper, commandhelper, helper, prefix_cleaner
class Help(commands.Cog):
"""Help command"""
def __init__(self, bot):
self.bot = bot
def __init__(self, bot: MusicBot):
self.bot: MusicBot = bot
@commands.command(hidden=True)
async def help(self, ctx): # Takes no args because reasons(using the view directly)
+4 -2
View File
@@ -6,12 +6,14 @@ from lavalink import __version__ as LavalinkVersion
import platform
import time
from bot import MusicBot
from ..utils import bot_version
class Misc(commands.Cog):
def __init__(self, bot):
self.bot = bot
def __init__(self, bot: MusicBot):
self.bot: MusicBot = bot
def get_uptime(self):
now = time.time()
+72 -50
View File
@@ -1,5 +1,7 @@
# Discord Packages
import discord
import lavalink
from discord import VoiceChannel
from discord.ext import commands, tasks
from lavalink.events import (
NodeChangedEvent, NodeConnectedEvent, NodeDisconnectedEvent, PlayerUpdateEvent, QueueEndEvent, TrackEndEvent,
@@ -14,6 +16,8 @@ from typing import List, Optional
from bs4 import BeautifulSoup
from bot import MusicBot
# Bot Utilities
from musicbot.utils.mixplayer.player import MixPlayer
from ...utils import checks, timeformatter
@@ -21,22 +25,26 @@ from ...utils.thumbnailer import Thumbnailer
from ...utils.userinteraction.paginators import QueuePaginator, TextPaginator
from ...utils.userinteraction.scroller import ClearOn, Scroller
from ...utils.userinteraction.selector import SelectMode, Selector, SelectorButton, SelectorItem
from .decorators import BasicVoiceClient, require_playing, require_queue, require_voice_connection, voteable
from .music_errors import WrongTextChannelError
from .decorators import require_playing, require_queue, require_voice_connection, voteable
from .music_errors import MusicError, WrongTextChannelError
from .voice_client import BasicVoiceClient
time_rx = re.compile('[0-9]+')
url_rx = re.compile('https?:\\/\\/(?:www\\.)?.+')
class Music(commands.Cog):
def __init__(self, bot):
self.bot = bot
def __init__(self, bot: MusicBot):
self.bot: MusicBot = bot
self.logger = self.bot.main_logger.bot_logger.getChild("Music")
self.thumbnailer = Thumbnailer(bot=self.bot)
self.leave_timer.start()
self.bot.lavalink.add_event_hook(self.track_hook)
if self.bot.lavalink is None:
raise MusicError("Lavalink is not yet initialized")
self.lavalink: lavalink.Client = self.bot.lavalink
self.lavalink.add_event_hook(self.track_hook)
async def cog_check(self, ctx):
if not ctx.guild:
@@ -50,10 +58,17 @@ class Music(commands.Cog):
""" 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.
if ctx.guild:
self.bot.lavalink.player_manager.create(ctx.guild.id)
self.lavalink.player_manager.create(ctx.guild.id)
def get_player(self, guild: discord.Guild) -> MixPlayer:
player: Optional[MixPlayer] = self.lavalink.player_manager.get(guild.id)
if player is None:
self.logger.error(f"Tried to get player for guild {guild.id} but got None")
raise MusicError(f"Tried to get player for guild {guild.id} but got None")
return player
async def enqueue(self, ctx, track, embed, silent=False, check_max_length=True) -> tuple[discord.Embed, bool]:
player: MixPlayer = self.bot.lavalink.player_manager.get(ctx.guild.id)
player = self.get_player(ctx.guild)
# Only add tracks that don't exceed the max track length
if maxlength := self.max_track_length(ctx.guild, player):
@@ -96,7 +111,7 @@ class Music(commands.Cog):
return embed, True
def get_current_song_embed(self, ctx, include_time=False):
player: MixPlayer = self.bot.lavalink.player_manager.get(ctx.guild.id)
player = self.get_player(ctx.guild)
if not player.current:
return
@@ -138,7 +153,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: MixPlayer = self.bot.lavalink.player_manager.get(ctx.guild.id)
player = self.get_player(ctx.guild)
query = query.strip('<>')
if not url_rx.match(query):
@@ -176,7 +191,7 @@ 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: MixPlayer = self.bot.lavalink.player_manager.get(ctx.guild.id)
player = self.get_player(ctx.guild)
if seconds := time_rx.search(time):
# Convert to milliseconds, include sign
milliseconds = int(seconds.group())*1000 * (-1 if time.startswith('-1') else 1)
@@ -194,7 +209,7 @@ class Music(commands.Cog):
@voteable(requester_override=True, react_to_vote=True)
async def _skip(self, ctx):
""" Skips the current track. """
player: MixPlayer = self.bot.lavalink.player_manager.get(ctx.guild.id)
player = self.get_player(ctx.guild)
await player.skip()
embed = self.get_current_song_embed(ctx)
@@ -205,7 +220,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: MixPlayer = self.bot.lavalink.player_manager.get(ctx.guild.id)
player = self.get_player(ctx.guild)
# TODO: Do all queue out of range messages the same way
if pos < 1:
@@ -227,7 +242,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: MixPlayer = self.bot.lavalink.player_manager.get(ctx.guild.id)
player = self.get_player(ctx.guild)
player.queue.clear()
await player.stop()
@@ -245,7 +260,7 @@ class Music(commands.Cog):
@require_queue(require_member_queue=True)
async def _queue(self, ctx, *, member: Optional[discord.Member] = None):
""" Shows the player's queue. """
player: MixPlayer = self.bot.lavalink.player_manager.get(ctx.guild.id)
player = self.get_player(ctx.guild)
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)
@@ -262,7 +277,7 @@ class Music(commands.Cog):
@require_playing()
async def _pause(self, ctx):
""" Pauses/Resumes the current track. """
player: MixPlayer = self.bot.lavalink.player_manager.get(ctx.guild.id)
player = self.get_player(ctx.guild)
await player.set_pause(not player.paused)
if player.paused:
await ctx.send(ctx.localizer.format_str("{resume.paused}"))
@@ -274,7 +289,7 @@ class Music(commands.Cog):
@require_queue(require_author_queue=True)
async def _shuffle(self, ctx):
""" Shuffles your queue. """
player: MixPlayer = self.bot.lavalink.player_manager.get(ctx.guild.id)
player = self.get_player(ctx.guild)
player.shuffle_user_queue(ctx.author)
await ctx.send(ctx.localizer.format_str("{shuffle}"))
@@ -283,7 +298,7 @@ class Music(commands.Cog):
@require_queue(require_author_queue=True)
async def _move(self, ctx):
""" Moves a song in your queue. """
player: MixPlayer = self.bot.lavalink.player_manager.get(ctx.guild.id)
player = self.get_player(ctx.guild)
# We create a new selector for each selection
def move_selector(ctx, queue: List[AudioTrack], title: str, first: bool):
@@ -344,7 +359,7 @@ class Music(commands.Cog):
@require_voice_connection()
@require_queue(require_author_queue=True)
async def _remove(self, ctx):
player: MixPlayer = self.bot.lavalink.player_manager.get(ctx.guild.id)
player = self.get_player(ctx.guild)
user_queue: List[AudioTrack] = player.user_queue(ctx.author)
@@ -406,7 +421,7 @@ class Music(commands.Cog):
@require_queue()
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: MixPlayer = self.bot.lavalink.player_manager.get(ctx.guild.id)
player = self.get_player(ctx.guild)
# TODO: add way to allow "member" as regular arg in require_queue() instead of only kwargs
queue = player.user_queue(member) if member else player.queue
@@ -419,11 +434,13 @@ class Music(commands.Cog):
removed = player.remove_user_track(member, pos - 1)
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))
if 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(f"Did not get a valid user for track requester {removed.requester}")
else:
self.logger.error("Did not remove a track when trying to remove index {pos}, despite bounds check")
self.logger.error(f"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)))
@@ -433,7 +450,7 @@ 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: MixPlayer = self.bot.lavalink.player_manager.get(ctx.guild.id)
player = self.get_player(ctx.guild)
player.remove_user_queue(member)
embed = discord.Embed(description="{dj_remove_user}", color=ctx.me.color)
@@ -444,7 +461,7 @@ class Music(commands.Cog):
@commands.command(name='search')
@require_voice_connection(should_connect=True)
async def _search(self, ctx, *, query):
player: MixPlayer = self.bot.lavalink.player_manager.get(ctx.guild.id)
player = self.get_player(ctx.guild)
self.logger.debug("Query: %s" % query)
if not query.startswith('ytsearch:') and not query.startswith('scsearch:'):
@@ -487,7 +504,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: MixPlayer = self.bot.lavalink.player_manager.get(ctx.guild.id)
player = self.get_player(ctx.guild)
player.queue.clear()
await player.stop()
@@ -501,7 +518,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: MixPlayer = self.bot.lavalink.player_manager.get(ctx.guild.id)
player = self.get_player(ctx.guild)
current_channel = player.channel_id
async def recon():
@@ -526,7 +543,7 @@ class Music(commands.Cog):
@require_playing(require_user_listening=True)
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: MixPlayer = self.bot.lavalink.player_manager.get(ctx.guild.id)
player = self.get_player(ctx.guild)
if not volume:
return await ctx.send(f'🔈 | {player.volume}%')
@@ -547,7 +564,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: MixPlayer = self.bot.lavalink.player_manager.get(ctx.guild.id)
player = self.get_player(ctx.guild)
query = query.strip('<>')
if not url_rx.match(query):
@@ -584,7 +601,7 @@ class Music(commands.Cog):
async def _forcedisconnect(self, ctx):
""" Attempts to force disconnect the bot without checking if it is connected initially. """
try:
player: MixPlayer = self.bot.lavalink.player_manager.get(ctx.guild.id)
player = self.get_player(ctx.guild)
player.queue.clear()
await player.stop()
except Exception as e:
@@ -600,7 +617,7 @@ class Music(commands.Cog):
@require_voice_connection()
async def _normalize(self, ctx):
""" Reset the equalizer and volume """
player: MixPlayer = self.bot.lavalink.player_manager.get(ctx.guild.id)
player = self.get_player(ctx.guild)
await player.set_volume(100)
await player.bassboost(False)
@@ -613,7 +630,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: MixPlayer = self.bot.lavalink.player_manager.get(ctx.guild.id)
player = self.get_player(ctx.guild)
if boost is not None:
await player.bassboost(boost)
@@ -628,7 +645,7 @@ class Music(commands.Cog):
@checks.dj_or(alone=True)
async def _nightcore(self, ctx, boost: bool = False):
""" Set a filter mimicking nightcore the music """
player: MixPlayer = self.bot.lavalink.player_manager.get(ctx.guild.id)
player = self.get_player(ctx.guild)
await player.nightcoreify(boost)
@@ -640,7 +657,7 @@ class Music(commands.Cog):
@commands.command(name='history')
async def _history(self, ctx):
""" Show the last 10 songs played """
player: MixPlayer = self.bot.lavalink.player_manager.get(ctx.guild.id)
player = self.get_player(ctx.guild)
history = player.get_history()
if not history:
embed = discord.Embed(description='{history.empty}', color=ctx.me.color)
@@ -667,7 +684,7 @@ class Music(commands.Cog):
async def _lyrics(self, ctx, *query: str):
# TODO:
player: MixPlayer = self.bot.lavalink.player_manager.get(ctx.guild.id)
player = self.get_player(ctx.guild)
genius_access_token = self.bot.APIkeys.get('genius', None)
@@ -734,7 +751,7 @@ class Music(commands.Cog):
@require_playing(require_user_listening=True)
async def _scrub(self, ctx):
""" Shows a set of controls which can be used to skip forward or backwards in the song """
player: MixPlayer = self.bot.lavalink.player_manager.get(ctx.guild.id)
player = self.get_player(ctx.guild)
controls = '{scrub.controls}'
def seek_callback(player: MixPlayer, seconds):
@@ -766,7 +783,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: MixPlayer = self.bot.lavalink.player_manager.get(ctx.guild.id)
player = self.get_player(ctx.guild)
embed = discord.Embed(color=ctx.me.color, title='Loop status')
if player.looping:
@@ -780,7 +797,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: MixPlayer = self.bot.lavalink.player_manager.get(ctx.guild.id)
player = self.get_player(ctx.guild)
player.enable_looping(True)
embed = discord.Embed(color=ctx.me.color)
embed.description = 'Looping turned on'
@@ -792,7 +809,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: MixPlayer = self.bot.lavalink.player_manager.get(ctx.guild.id)
player = self.get_player(ctx.guild)
player.enable_looping(False)
embed = discord.Embed(color=ctx.me.color)
embed.description = 'Looping turned off'
@@ -800,19 +817,21 @@ class Music(commands.Cog):
await ctx.send(embed=embed)
async def cog_unload(self):
self.bot.lavalink._event_hooks.clear()
self.lavalink._event_hooks.clear()
async def track_hook(self, event):
if isinstance(event, TrackEndEvent):
channel = self.bot.get_channel(event.player.fetch('channel'))
player: MixPlayer = self.bot.lavalink.player_manager.get(channel.guild.id)
player.skip_voters.clear()
if channel := self.bot.get_channel(event.player.fetch('channel')):
if isinstance(channel, VoiceChannel):
player = self.get_player(channel.guild)
player.skip_voters.clear()
if isinstance(event, TrackStartEvent):
pass
if isinstance(event, QueueEndEvent):
channel = self.bot.get_channel(event.player.fetch('channel'))
await self.check_leave_voice(channel.guild)
if channel := self.bot.get_channel(event.player.fetch('channel')):
if isinstance(channel, VoiceChannel):
await self.check_leave_voice(channel.guild)
if isinstance(event, PlayerUpdateEvent):
pass
if isinstance(event, NodeDisconnectedEvent):
@@ -827,16 +846,18 @@ class Music(commands.Cog):
@commands.Cog.listener()
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 self.bot.user is None:
return # Bot not logged in
if member.id == self.bot.user.id and after.channel is not None:
voice_channel = after.channel
player: MixPlayer = self.bot.lavalink.player_manager.get(member.guild.id)
player = self.get_player(member.guild)
player.clear_listeners()
for member in voice_channel.members:
if not member.bot:
player.update_listeners(member, member.voice)
if not member.bot:
player: MixPlayer = self.bot.lavalink.player_manager.get(member.guild.id)
player = self.get_player(member.guild)
if player is not None:
player.update_listeners(member, after)
await self.check_leave_voice(member.guild)
@@ -844,7 +865,7 @@ class Music(commands.Cog):
async def check_leave_voice(self, guild: discord.Guild):
""" Checks if the bot should leave the voice channel """
# TODO, disconnect timer?
player: MixPlayer = self.bot.lavalink.player_manager.get(guild.id)
player = self.get_player(guild)
if len(player.listeners) == 0 and player.is_connected:
if player.queue.empty and player.current is None:
await player.stop()
@@ -852,8 +873,9 @@ class Music(commands.Cog):
await voice_client.disconnect(force=False)
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))
for player_id in self.lavalink.player_manager.players:
if guild := self.bot.get_guild(player_id):
await self.check_leave_voice(guild)
@tasks.loop(seconds=10.0)
async def leave_timer(self):
+18 -45
View File
@@ -1,46 +1,16 @@
# Discord Packages
import discord
from discord.ext import commands
import asyncio
import functools
import inspect
import math
# Bot Utilities
from musicbot.utils.mixplayer.player import MixPlayer
from ...utils.checks import is_dj
from .music_errors import WrongVoiceChannelError
class BasicVoiceClient(discord.VoiceClient):
def __init__(self, client: discord.Client, channel: discord.abc.Connectable):
self.client = client
self.channel = channel
if hasattr(self.client, 'lavalink'):
self.lavalink = self.client.lavalink
else:
raise Exception("client did not have defined lavalink before connect")
async def on_voice_server_update(self, data):
await self.lavalink.voice_update_handler({'t': 'VOICE_SERVER_UPDATE', 'd': data})
async def on_voice_state_update(self, data):
await self.lavalink.voice_update_handler({'t': 'VOICE_STATE_UPDATE', 'd': data})
async def connect(self, *, timeout: float, reconnect: bool, self_deaf: bool = False,
self_mute: bool = False) -> None:
await self.channel.guild.change_voice_state(channel=self.channel, self_mute=self_mute, self_deaf=self_deaf)
async def disconnect(self, *, force: bool = False) -> None:
player = self.lavalink.player_manager.get(self.channel.guild.id)
# no need to disconnect if we are not connected
if not force and not player.is_connected:
return
# None means disconnect
await self.channel.guild.change_voice_state(channel=None)
player.channel_id = None
self.cleanup()
from . import music_errors
from .voice_client import BasicVoiceClient
def require_voice_connection(should_connect=False):
@@ -53,36 +23,39 @@ def require_voice_connection(should_connect=False):
async def ensure_voice_inner(self, ctx, *command_args, **kwargs):
""" This check ensures that the bot and command author are in the same voicechannel. """
player = self.bot.lavalink.player_manager.get(ctx.guild.id)
player: MixPlayer = self.bot.lavalink.player_manager.get(ctx.guild.id)
if not player:
raise music_errors.MusicError("ensure voice could not get lavalink player")
if not ctx.author.voice or not ctx.author.voice.channel:
raise commands.CommandInvokeError('Join a voicechannel first.')
raise music_errors.UserNotConnectedError('Join a voicechannel first.')
if not player.is_connected:
if not should_connect:
raise commands.CommandInvokeError('Not connected.')
raise music_errors.BotNotConnectedError('Not connected.')
user_voice_channel = ctx.author.voice.channel
permissions = user_voice_channel.permissions_for(ctx.me)
if not permissions.connect or not permissions.speak: # Check user limit too?
raise commands.CommandInvokeError('I need the `CONNECT` and `SPEAK` permissions.')
raise music_errors.MissingPermissionsError('I need the `CONNECT` and `SPEAK` permissions.')
if (len(user_voice_channel.members) == user_voice_channel.user_limit) and not permissions.administrator:
raise commands.CommandInvokeError('The channel is currently full')
raise music_errors.VoiceChannelFullError('The channel is currently full', user_voice_channel)
# Check against music channel restrictions in bot settings
if voice_channels := self.bot.settings.get(ctx.guild, 'channels.music', []):
if user_voice_channel.id not in voice_channels:
raise WrongVoiceChannelError(
raise music_errors.WrongVoiceChannelError(
'You need to be in the right voice channel', channels=voice_channels)
player.store('channel', ctx.channel.id)
await ctx.author.voice.channel.connect(cls=BasicVoiceClient)
elif int(player.channel_id) != ctx.author.voice.channel.id:
elif player.channel_id and int(player.channel_id) != ctx.author.voice.channel.id:
bot_channel = self.bot.get_channel(int(player.channel_id))
raise WrongVoiceChannelError(
'You need to be in my voice channel', channels=[bot_channel])
raise music_errors.UserInDifferentVoiceChannelError('You need to be in my voice channel',
channel=bot_channel)
await func(self, ctx, *command_args, **kwargs)
@@ -102,10 +75,10 @@ def require_playing(require_user_listening=False):
if player := self.bot.lavalink.player_manager.get(ctx.guild.id):
if not player.is_playing:
raise commands.CommandInvokeError('Not playing')
raise music_errors.RequirePlayingError('Not playing')
if require_user_listening and (ctx.author not in player.listeners):
raise commands.CommandInvokeError('Not listening')
raise music_errors.RequireListeningError('Not listening')
else:
raise Exception('Player object does not yet exist')
+63 -11
View File
@@ -1,20 +1,72 @@
# define Python user-defined exceptions
# Discord Packages
from discord.ext.commands import CommandError
from typing import Optional
class WrongChannelError(CommandError):
def __init__(self, e: str, channels=None):
self.original = e
class MusicError(CommandError):
def __init__(self, message: str, inner_error: Optional[Exception] = None):
self.inner_error = inner_error
super().__init__(message)
class MissingPermissionsError(MusicError):
def __init__(self, message: str, inner_error: Optional[Exception] = None):
super().__init__(message, inner_error=inner_error)
class BotNotConnectedError(MusicError):
def __init__(self, message: str, inner_error: Optional[Exception] = None):
super().__init__(message, inner_error=inner_error)
class UserNotConnectedError(MusicError):
def __init__(self, message: str, inner_error: Optional[Exception] = None):
super().__init__(message, inner_error=inner_error)
class PlayBackStatusError(MusicError):
def __init__(self, message: str, inner_error: Optional[Exception] = None):
super().__init__(message, inner_error=inner_error)
class RequirePlayingError(PlayBackStatusError):
def __init__(self, message: str, inner_error: Optional[Exception] = None):
super().__init__(message, inner_error=inner_error)
class RequireListeningError(PlayBackStatusError):
def __init__(self, message: str, inner_error: Optional[Exception] = None):
super().__init__(message, inner_error=inner_error)
# Channel errors
class ChannelError(MusicError):
def __init__(self, message: str, channels, inner_error: Optional[Exception] = None):
self.channels = channels
super().__init__()
super().__init__(message=message, inner_error=inner_error)
class WrongVoiceChannelError(WrongChannelError):
def __init__(self, e: str, channels=None):
super().__init__(e, channels=channels)
class VoiceChannelFullError(ChannelError):
def __init__(self, message: str, channel, inner_error: Optional[Exception] = None):
super().__init__(message, channels=[channel], inner_error=inner_error)
class WrongTextChannelError(WrongChannelError):
def __init__(self, e: str, channels=None):
super().__init__(e, channels=channels)
class UserInDifferentVoiceChannelError(ChannelError):
def __init__(self, message: str, channel, inner_error: Optional[Exception] = None):
super().__init__(message, channels=[channel], inner_error=inner_error)
class ConfiguredChannelsError(ChannelError):
def __init__(self, message: str, channels, inner_error: Optional[Exception] = None):
super().__init__(message, channels=channels, inner_error=inner_error)
class WrongVoiceChannelError(ConfiguredChannelsError):
def __init__(self, message: str, channels, inner_error: Optional[Exception] = None):
super().__init__(message, channels=channels, inner_error=inner_error)
class WrongTextChannelError(ConfiguredChannelsError):
def __init__(self, message: str, channels, inner_error: Optional[Exception] = None):
super().__init__(message, channels=channels, inner_error=inner_error)
+38
View File
@@ -0,0 +1,38 @@
# Discord Packages
import discord
from bot import MusicBot
from musicbot.cogs.music.music_errors import MusicError
class BasicVoiceClient(discord.VoiceClient):
def __init__(self, client: MusicBot, channel: discord.VoiceChannel):
self.music_bot = client
self.channel = channel
if self.music_bot.lavalink:
self.lavalink = self.music_bot.lavalink
else:
raise MusicError("client did not have defined lavalink before connect")
async def on_voice_server_update(self, data):
await self.lavalink.voice_update_handler({'t': 'VOICE_SERVER_UPDATE', 'd': data})
async def on_voice_state_update(self, data):
await self.lavalink.voice_update_handler({'t': 'VOICE_STATE_UPDATE', 'd': data})
async def connect(self, *, timeout: float, reconnect: bool, self_deaf: bool = False,
self_mute: bool = False) -> None:
await self.channel.guild.change_voice_state(channel=self.channel, self_mute=self_mute, self_deaf=self_deaf)
async def disconnect(self, *, force: bool = False) -> None:
player = self.lavalink.player_manager.get(self.channel.guild.id)
if player:
# no need to disconnect if we are not connected
if not force and not player.is_connected:
return
# None means disconnect
await self.channel.guild.change_voice_state(channel=None)
player.channel_id = None
self.cleanup()
+15 -6
View File
@@ -1,12 +1,15 @@
# Discord Packages
import discord
import lavalink
from discord.ext import commands
from lavalink import Client, Node
import codecs
import yaml
from bot import MusicBot
from musicbot.cogs.music.music_errors import MusicError
# Bot Utilities
from musicbot.utils.settingsmanager import Settings
from ..utils.mixplayer import MixPlayer
@@ -15,8 +18,8 @@ from .helpformatter import commandhelper
class NodeManager(commands.Cog):
def __init__(self, bot):
self.bot = bot
def __init__(self, bot: MusicBot):
self.bot: MusicBot = bot
self.settings: Settings = self.bot.settings
self.logger = self.bot.main_logger.bot_logger.getChild("NodeManager")
@@ -25,8 +28,8 @@ class NodeManager(commands.Cog):
'musicbot.cogs.music',
]
if not hasattr(self.bot, 'lavalink'):
self.bot.lavalink = Client(self.bot.user.id, player=MixPlayer)
if self.bot.lavalink is None and self.bot.user:
self.bot.lavalink = lavalink.Client(self.bot.user.id, player=MixPlayer)
self.load_nodes_from_file()
@@ -38,6 +41,9 @@ class NodeManager(commands.Cog):
await self.bot.load_extension(extension)
except Exception:
self.logger.exception("Loading of extension %s failed" % extension)
else:
self.logger.error("Failed to initialize lavalink client")
raise MusicError("Failed to initialize lavalink client")
def load_nodes_from_file(self):
with codecs.open(f"{self.bot.datadir}/config.yaml", 'r', encoding='utf8') as f:
@@ -46,6 +52,9 @@ class NodeManager(commands.Cog):
name_cache = []
new_nodes = []
if self.bot.lavalink is None:
return new_nodes
for node in self.bot.lavalink.node_manager.nodes:
name_cache.append(node.name)
@@ -93,7 +102,7 @@ class NodeManager(commands.Cog):
embed.add_field(name=f'{await self._regioner(n.region)} **Name:** {n.name}',
value=f'**Host:** {n.host}\n **Port:** {n.port}')
if isinstance(node, Node):
if isinstance(node, lavalink.Node):
embed.add_field(name=f'{await self._regioner(node.region)} **Name:** {node.name}',
value=f'**Host:** {node.host}\n **Port:** {node.port}')
+4 -2
View File
@@ -2,14 +2,16 @@
import discord
from discord.ext import commands
from bot import MusicBot
from ..utils import checks
from ..utils.userinteraction import ClearOn, Scroller
from .helpformatter import commandhelper
class Settings(commands.Cog):
def __init__(self, bot):
self.bot = bot
def __init__(self, bot: MusicBot):
self.bot: MusicBot = bot
self.settings = self.bot.settings
def format_prefixes(self, prefixes):
@@ -4,6 +4,7 @@ import discord
import codecs
import locale as localee
import os
from typing import Any
import yaml
@@ -53,7 +54,7 @@ class Settings():
with codecs.open(self._SETTINGS_PATH, 'w', encoding='utf8') as f:
yaml.dump(self.settings, f, indent=2)
def get(self, identifier, setting, default=''):
def get(self, identifier, setting, default: Any = '') -> Any:
""" Gets a value from the settings if a default return value is specified
it will return the default if no setting is found. If that default is an
attribute of the settings class, the value of the attribute will get returned."""
+3 -3
View File
@@ -2,12 +2,12 @@ from time import perf_counter
from bs4 import BeautifulSoup as bs4
from bot import Bot
from bot import MusicBot
class Thumbnailer(object):
def __init__(self, bot: Bot):
self.bot = bot
def __init__(self, bot: MusicBot):
self.bot: MusicBot = bot
self.logger = self.bot.main_logger.bot_logger.getChild("Thumbnailer")
async def get_html(self, url: str):
+3 -1
View File
@@ -7,6 +7,8 @@ import asyncio
from enum import Flag, auto
from typing import List, Optional
from bot import MusicBot
from .paginators import BasePaginator, CantScrollException
@@ -42,7 +44,7 @@ class Scroller:
if not self.paginator.pages:
raise Exception("Paginator contained no pages to display") # TODO: proper error
self.bot = ctx.bot
self.bot: MusicBot = ctx.bot
self.channel = ctx.channel
self.message: Optional[discord.Message] = None