diff --git a/bot.py b/bot.py index ed5b359..036acf5 100644 --- a/bot.py +++ b/bot.py @@ -86,6 +86,7 @@ class Bot(commands.Bot): print(f'Version: {discord.__version__}\n') self.load_extension('cogs.music') + self.load_extension('cogs.musicevents') await self.change_presence(activity=discord.Game(type=0, name=config["playing"]), diff --git a/cogs/cogs.py b/cogs/cogs.py index 7b104c8..2ad85ce 100644 --- a/cogs/cogs.py +++ b/cogs/cogs.py @@ -2,6 +2,7 @@ import discord import os import json import codecs +import traceback from discord.ext import commands from cogs.utils import checks @@ -66,6 +67,7 @@ class Cogs: @checks.is_owner() async def _shutdown(self, ctx): """Logs out and stops.""" + self.bot.lavalink.players.clear() await self.bot.logout() diff --git a/cogs/misc.py b/cogs/misc.py index dc49309..fb8201a 100644 --- a/cogs/misc.py +++ b/cogs/misc.py @@ -14,7 +14,6 @@ class Misc: self.bot = bot @commands.command(name='ping', hidden=True) - @checks.is_mod() async def _ping(self, ctx): start = time.perf_counter() message = await ctx.send('Ping...') @@ -40,6 +39,23 @@ class Misc: for guild in self.bot.guilds: guilds += f"{guild.name}\n" await ctx.send(guilds) + + @commands.command() + async def musicinfo(self, ctx): + """ + Info om musikkspilleren + """ + embed = discord.Embed(title='Music info', color=ctx.me.color) + lavalink = self.bot.lavalink + + listeners = 0 + for guild, player in lavalink.players: + listeners += len(player.listeners) + + embed.add_field(name='Players', value=f'{len(lavalink.players)}') + embed.add_field(name='Listeners', value=f'{listeners}') + await ctx.send(embed=embed) + @commands.command(name="reloadlocale") @@ -72,12 +88,13 @@ class Misc: size=1024) uptimetext = f'{days}d {hours}t {minutes}m {seconds}s' - embed = discord.Embed(color=0xD9C04D) + embed = discord.Embed(color=ctx.me.color) embed.set_author(name=self.bot.user.name, icon_url=avatar) embed.set_thumbnail(url=avatar) + embed.set_image(url='https://cdn.discordapp.com/attachments/298524946454282250/368118192251469835/vintage1turntable.png') embed.add_field(name="{info.what}", value='{info.infotext}', inline=False) - embed.set_footer(icon_url="https://cdn.discordapp.com/icons/297798952538079233/0198dc7b01ccf288b985b8a394cb1d44.jpg?size=64", + embed.set_footer(icon_url="https://cdn.discordapp.com/icons/532176350019321917/92f43a1f67308a99a30c169db4b671dd.png?size=64", text="{info.footer_text}") embed.add_field(name="{info.how}", value='{info.spectext}') diff --git a/cogs/music.py b/cogs/music.py index 668f9be..7b115fd 100644 --- a/cogs/music.py +++ b/cogs/music.py @@ -1,6 +1,5 @@ """ -This is an example cog that shows how you would make use of Lavalink.py. -This example cog requires that you have python 3.6 or higher due to the f-strings. +Music commands """ import math import re @@ -33,24 +32,14 @@ class Music: bot.lavalink.add_node(host, port, password, region, 'default-node') # Host, Port, Password, Region, Name bot.add_listener(bot.lavalink.voice_update_handler, 'on_socket_response') - bot.lavalink.add_event_hook(self.track_hook) - - def __unload(self): - self.bot.lavalink._event_hooks.clear() - def getLocalizer(self, guild_id): lang = self.bot.settings.get_locale(guild_id) return LocalizerWrapper(self.bot.localizer, lang, "music.response") - async def track_hook(self, event): - if isinstance(event, lavalink.events.TrackEndEvent): - pass # Send track ended message to channel. - if isinstance(event, lavalink.events.QueueEndEvent): - channel = self.bot.get_channel(event.player.fetch('channel')) - await self.check_leave_voice(channel.guild) - async def __before_invoke(self, ctx): + # TODO: rewrite this thing. Probably remove ensure_voice. + guild_check = ctx.guild is not None # This is essentially the same as `@commands.guild_only()` # except it saves us repeating ourselves (and also a few lines). @@ -81,7 +70,7 @@ class Music: if not results or not results['tracks']: return await ctx.send(localizer.format_str("{nothing_found}")) - embed = discord.Embed(color=0xEFD26C) + embed = discord.Embed(color=ctx.me.color) if results['loadType'] == 'PLAYLIST_LOADED': tracks = results['tracks'] @@ -200,7 +189,7 @@ class Music: song = f'**[{player.current.title}]({player.current.uri})**\n({position}/{duration})' member = ctx.guild.get_member(player.current.requester) - embed = discord.Embed(color=0xEFD26C, description=song, title='{now}') + embed = discord.Embed(color=ctx.me.color, description=song, title='{now}') thumbnail_url = await RoxUtils.ThumbNailer.identify(self,player.current.identifier, player.current.uri) if thumbnail_url: embed.set_thumbnail(url=thumbnail_url) @@ -290,7 +279,7 @@ class Music: if not user_queue: return await ctx.send(localizer.format_str("{my_queue}")) - if not all(x in range(1,len(user_queue)+1) for x in [from_pos, to_pos]): + if not all(x in range(1,len(user_queue) + 1) for x in [from_pos, to_pos]): return await ctx.send(localizer.format_str("{out_of_range}", _len=len(user_queue))) moved = player.move_user_track(ctx.author.id, from_pos - 1, to_pos - 1) @@ -367,9 +356,11 @@ class Music: return player.remove_user_queue(user.id) - embed = discord.Embed(color=0x36393F) + + embed = discord.Embed(color=ctx.me.color) embed.description = "{dj_remove_user}" embed = localizer.format_embed(embed, _id=user.id) + await ctx.send(embed=embed) @commands.command() @@ -434,9 +425,11 @@ class Music: await result_msg.add_reaction('❌') embed.description = search_results + embed.title = '{results}' - embed.color = 0xEFD26C + embed.color = ctx.me.color embed = localizer.format_embed(embed) + await result_msg.edit(embed=embed) try: @@ -479,7 +472,7 @@ class Music: player.queue.clear() await player.stop() await self.connect_to(ctx.guild.id, None) - embed = discord.Embed(description='{disconnect.disconnected}', color=0x36393F) + embed = discord.Embed(description='{disconnect.disconnected}', color=ctx.me.color) embed = localizer.format_embed(embed) await ctx.send(embed=embed) @@ -498,7 +491,7 @@ class Music: return await ctx.send(localizer.format_str("{volume.out_of_range}")) await player.set_volume(volume) - embed = discord.Embed(color=0x36393F) + embed = discord.Embed(color=ctx.me.color) embed.description = "{volume.set_to}" embed = localizer.format_embed(embed, _volume=player.volume) await ctx.send(embed=embed) @@ -512,9 +505,11 @@ class Music: await player.set_volume(100) await player.bassboost(False) - embed = discord.Embed(color=0x36393F) + + embed = discord.Embed(color=ctx.me.color) embed.description = '{volume.reset}' embed = localizer.format_embed(embed) + await ctx.send(embed=embed) @commands.command(name='boost', aliases=['boo']) @@ -527,7 +522,7 @@ class Music: if boost is not None: await player.bassboost(boost) - embed = discord.Embed(color=0x36393F) + embed = discord.Embed(color=ctx.me.color) if player.boosted: embed.description = '{boost.on}' @@ -552,7 +547,7 @@ class Music: for index, track in enumerate(history[1:], start=1): description += localizer.format_str("{history.track}",_index=-index, _title=track.title, _uri=track.uri, _id=track.requester) + '\n' - embed = discord.Embed(title=localizer.format_str('{history.title}'), color=0xEFD26C, description=description) + embed = discord.Embed(title=localizer.format_str('{history.title}'), color=ctx.me.color, description=description) if thumb_url: embed.set_thumbnail(url=thumb_url) @@ -567,8 +562,9 @@ class Music: localizer = self.getLocalizer(ctx.guild.id) controls = '{scrub.controls}' - embed = discord.Embed(description='{nothing_playing}', color=0x36393F) + embed = discord.Embed(description='{nothing_playing}', color=ctx.me.color) embed = localizer.format_embed(embed) + if player.current is None: return await ctx.send(embed=embed) @@ -657,33 +653,10 @@ class Music: player.store('channel', ctx.channel.id) await self.connect_to(ctx.guild.id, str(ctx.author.voice.channel.id)) - # Get listeners - voice_channel = ctx.author.voice.channel - while not player.is_connected: - await asyncio.sleep(0.5) - - for member in voice_channel.members: - if not member.bot: - player.update_listeners(member, member.voice) - else: if int(player.channel_id) != ctx.author.voice.channel.id: raise commands.CommandInvokeError('You need to be in my voicechannel.') - async def on_voice_state_update(self, member, before, after): - if not member.bot: - player = self.bot.lavalink.players.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): - player = self.bot.lavalink.players.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 self.connect_to(guild.id, None) - def max_track_length(self, guild): player = self.bot.lavalink.players.get(guild.id) if len(player.listeners): diff --git a/cogs/musicevents.py b/cogs/musicevents.py new file mode 100644 index 0000000..1caa8a0 --- /dev/null +++ b/cogs/musicevents.py @@ -0,0 +1,81 @@ +""" +A cog to separate events from regular music commands +""" + +import asyncio + +import discord +import lavalink +from discord.ext import commands +import time + +from .utils.mixplayer import MixPlayer +from lavalink.events import * + +from lavasettings import * + + +class MusicEvents: + def __init__(self, bot): + self.bot = bot + # TODO: maybe only load when Music loads + if not hasattr(bot, 'lavalink'): # This ensures the client isn't overwritten during cog reloads. + bot.lavalink = lavalink.Client(bot.user.id, player=MixPlayer) + bot.lavalink.add_node(host, port, password, region, 'default-node') # Host, Port, Password, Region, Name + bot.add_listener(bot.lavalink.voice_update_handler, 'on_socket_response') + + bot.lavalink.add_event_hook(self.track_hook) + + def __unload(self): + self.bot.lavalink._event_hooks.clear() + + async def track_hook(self, event): + if isinstance(event, TrackEndEvent): + pass # Send track ended message to channel. + 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 isinstance(event, PlayerUpdateEvent): + pass + if isinstance(event, NodeDisconnectedEvent): + pass + if isinstance(event, NodeConnectedEvent): + pass + if isinstance(event, NodeChangedEvent): + pass + + async def connect_to(self, guild_id: int, channel_id: str): + """ Connects to the given voicechannel ID. A channel_id of `None` means disconnect. """ + ws = self.bot._connection._get_websocket(guild_id) + await ws.voice_state(str(guild_id), channel_id) + + async def on_voice_state_update(self, member, before, after): + """ 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.players.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.players.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): + """ Checks if the bot should leave the voice channel """ + # TODO, disconnect timer? + player = self.bot.lavalink.players.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 self.connect_to(guild.id, None) + + +def setup(bot): + bot.add_cog(MusicEvents(bot)) diff --git a/cogs/utils/mixplayer.py b/cogs/utils/mixplayer.py index bba731c..4643add 100644 --- a/cogs/utils/mixplayer.py +++ b/cogs/utils/mixplayer.py @@ -111,6 +111,9 @@ class MixPlayer(DefaultPlayer): else: self.listeners.add(member) + def clear_listeners(self): + self.listeners.clear() + def add_skipper(self, member): if member in self.listeners: self.skip_voters.add(member) diff --git a/localization/nb_no/info.json b/localization/nb_no/info.json index 535c317..59dcc40 100644 --- a/localization/nb_no/info.json +++ b/localization/nb_no/info.json @@ -2,7 +2,7 @@ "name": "hva?", "infotext": "Musikkbot skrevet for bruk på /r/Norge sine Discord Servere. Kildekoden er åpen! Den kan du finne [HER]({global.vars.repo})", "spectext": "**Python:** [{_python_v}](https://www.python.org/)\n**Discord.py:** [{_discord_v}](https://github.com/Rapptz/discord.py/tree/rewrite)\n**Lavalink:** [{_lavalink_v}](https://github.com/Devoxin/Lavalink.py)", - "stattext": "**Guilder:** {_guilds}\n**Brukere:** {_members}", + "stattext": "**Tenarar:** {_guilds}\n**Brukere:** {_members}", "what": "Hva?", "how": "Hvordan?", "how_many": "Hvor mange?",