From acbad1a61a0c8d3a24d595bd6fc2a465cad9ed72 Mon Sep 17 00:00:00 2001 From: Ev-1 <33662061+Ev-1@users.noreply.github.com> Date: Sat, 12 Sep 2020 18:16:51 +0200 Subject: [PATCH] Moved decorators added node change command --- .vscode/settings.json | 12 +- cogs/music/__init__.py | 6 +- cogs/music/basic_commands.py | 231 +++------------------------- cogs/{utils => music}/decorators.py | 0 cogs/music/events.py | 2 + cogs/music/extra_commands.py | 215 ++++++++++++++++++++++++++ cogs/nodemanager.py | 20 ++- 7 files changed, 271 insertions(+), 215 deletions(-) rename cogs/{utils => music}/decorators.py (100%) create mode 100644 cogs/music/extra_commands.py diff --git a/.vscode/settings.json b/.vscode/settings.json index 5978804..8500405 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -10,6 +10,14 @@ "[Dockerfile]": { "editor.formatOnSave": false }, - "editor.tabSize": 4 - + "editor.tabSize": 4, + "files.exclude": { + "**/.git": true, + "**/.svn": true, + "**/.hg": true, + "**/CVS": true, + "**/.DS_Store": true, + "**/*.pyc": {"when": "$(basename).py"}, + "**/__pycache__": true, + }, } \ No newline at end of file diff --git a/cogs/music/__init__.py b/cogs/music/__init__.py index d12546c..e72d0f3 100644 --- a/cogs/music/__init__.py +++ b/cogs/music/__init__.py @@ -94,10 +94,12 @@ class Music(commands.Cog): # commands from .basic_commands import ( - _boost, _disconnect, _djremove, _history, _lyrics, _move, _myqueue, _normalize, _now, _pause, _play, _queue, - _reconnect, _remove, _scrub, _search, _seek, _shuffle, _skip, _skip_to, _stop, _user_queue_remove, _volume) + _disconnect, _djremove, _move, _myqueue, _now, _pause, _play, _queue, _reconnect, _remove, _search, _seek, + _shuffle, _skip, _skip_to, _stop, _user_queue_remove, _volume) # events from .events import check_leave_voice, cog_unload, leave_check, leave_timer, on_voice_state_update, track_hook + # commands + from .extra_commands import _boost, _history, _lyrics, _normalize, _scrub def setup(bot): diff --git a/cogs/music/basic_commands.py b/cogs/music/basic_commands.py index 61eee23..b6c7169 100644 --- a/cogs/music/basic_commands.py +++ b/cogs/music/basic_commands.py @@ -1,20 +1,20 @@ +""" +Commands that interacti with just discord and lavalink and only present the basic commands needed for a music bot +""" + # Discord Packages import discord import lavalink from discord.ext import commands import asyncio -import json import math import re -import urllib - -from bs4 import BeautifulSoup from ..utils import checks, thumbnailer, timeformatter -from ..utils.decorators import require_playing, require_queue, require_voice_connection -from ..utils.paginator import QueuePaginator, Scroller, TextPaginator +from ..utils.paginator import QueuePaginator, Scroller from ..utils.selector import Selector +from .decorators import require_playing, require_queue, require_voice_connection time_rx = re.compile('[0-9]+') url_rx = re.compile('https?:\\/\\/(?:www\\.)?.+') @@ -340,7 +340,7 @@ async def _djremove(self, ctx, pos: int, member: discord.Member = None): removed = player.remove_user_track(member.id, pos - 1) requester = self.bot.get_user(removed.requester) - await ctx.send(ctx.localizer.format_str("{dj_removed}", _title=removed.title, _user=requester.name)) + await ctx.send(ctx.localizer.format_str("{dj_removed}", _title=removed.title, _user=requester.display_name)) else: await ctx.send(ctx.localizer.format_str("{out_of_range}", _len=len(queue))) @@ -426,11 +426,20 @@ async def _reconnect(self, ctx): player = self.bot.lavalink.player_manager.get(ctx.guild.id) current_channel = player.channel_id - await player.stop() - await self.connect_to(ctx.guild.id, None) - await asyncio.sleep(1) # Pretend stuff is happening/give everything some time to reset. - await self.connect_to(ctx.guild.id, current_channel) - await player.play() + async def recon(): + await player.stop() + await self.connect_to(ctx.guild.id, None) + await asyncio.sleep(1) # Pretend stuff is happening/give everything some time to reset. + await self.connect_to(ctx.guild.id, current_channel) + + if player.current: + track = player.current + start_time = player.position + await recon() + if track: + await player.play(track, start_time=start_time) + else: + recon() @commands.command(name='volume') @@ -453,201 +462,3 @@ async def _volume(self, ctx, volume: int = None): embed = discord.Embed(description="{volume.set_to}", color=ctx.me.color) embed = ctx.localizer.format_embed(embed, _volume=player.volume) await ctx.send(embed=embed) - - -@commands.command(name='normalize') -@checks.dj_or(alone=True) -@require_voice_connection() -async def _normalize(self, ctx): - """ Reset the equalizer and volume """ - player = self.bot.lavalink.player_manager.get(ctx.guild.id) - - await player.set_volume(100) - await player.bassboost(False) - - embed = discord.Embed(description='{volume.reset}', color=ctx.me.color) - embed = ctx.localizer.format_embed(embed) - await ctx.send(embed=embed) - - -@commands.command(name='boost') -@checks.dj_or(alone=True) -async def _boost(self, ctx, boost: bool = None): - """ Set the equalizer to bass boost the music """ - player = self.bot.lavalink.player_manager.get(ctx.guild.id) - - if boost: - await player.bassboost(boost) - - embed = discord.Embed(color=ctx.me.color) - embed.description = '{boost.on}' if player.boosted else '{boost.off}' - - embed = ctx.localizer.format_embed(embed) - await ctx.send(embed=embed) - - -@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) - history = player.get_history() - if not history: - embed = discord.Embed(description='{history.empty}', color=ctx.me.color) - embed = ctx.localizer.format_embed(embed) - return await ctx.send(embed=embed) - track = history[0] - 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' - 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' - - embed = discord.Embed(title=ctx.localizer.format_str('{history.title}'), color=ctx.me.color, - description=description) - - if thumbnail_url: - embed.set_thumbnail(url=thumbnail_url) - await ctx.send(embed=embed) - - -@commands.cooldown(1, 5, commands.BucketType.guild) -@commands.command(name='lyrics') -async def _lyrics(self, ctx, *query: str): - - # TODO: - player = self.bot.lavalink.player_manager.get(ctx.guild.id) - - genius_access_token = self.bot.APIkeys.get('genius', None) - - if genius_access_token is None: - return await ctx.send('Missing API key') - - excluded_words = ['music', 'video', 'version', 'original', 'lyrics', 'lyric', - 'official', 'live', 'instrumental', 'audio', 'hd'] - - query = ' '.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) - - async def get_site_content(url): - async with self.bot.session.get(url) as resp: - response = await resp.read() - return response.decode('utf-8') - - embed = discord.Embed(description=':mag_right:') - status_msg = await ctx.send(embed=embed) - - try: - url = 'https://api.genius.com/search?' + urllib.parse.urlencode( - {'access_token': genius_access_token, 'q': query}) - result = await get_site_content(url) - response = json.loads(result) - song_id = response['response']['hits'][0]['result']['id'] - except Exception: - embed = discord.Embed(description=':x:', color=0xFF0000) - return await status_msg.edit(embed=embed) - - result = await get_site_content(f'https://api.genius.com/songs/{song_id}?access_token={genius_access_token}') - song = json.loads(result)['response']['song'] - - response = await get_site_content(song['url']) - scraped_data = BeautifulSoup(response, 'html.parser') - lyrics = scraped_data.find(class_='lyrics').get_text() - - paginator = TextPaginator(max_size=2000, max_lines=50, color=0xFFFF64) - for line in lyrics.split('\n'): - paginator.add_line(line) - - await status_msg.delete() - - paginator.pages[0].url = song['url'] - paginator.pages[0].title = song['full_title'] - paginator.pages[0].set_thumbnail(url=song['header_image_thumbnail_url']) - paginator.pages[0].set_author(name='Genius', icon_url='https://i.imgur.com/NmCTsoF.png') - - if len(paginator.pages) < 4: - for page in paginator.pages: - await ctx.send(embed=page) - else: - paginator.add_page_indicator(ctx.localizer) - await Scroller(ctx, paginator).start_scrolling() - - -@commands.command(name='scrub') -@checks.dj_or(alone=True) -@require_voice_connection() -@require_playing(require_user_listening=True) -async def _scrub(self, ctx): - """ Lists the first 10 search results from a given query. """ - player = self.bot.lavalink.player_manager.get(ctx.guild.id) - - controls = '{scrub.controls}' - - scrubber = [ - ('\N{BLACK LEFT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}', player.seek, -1000), - ('\N{BLACK LEFT-POINTING DOUBLE TRIANGLE}', player.seek, -15), - ('\N{DOUBLE VERTICAL BAR}', player.set_pause, True), - ('\N{BLACK RIGHT-POINTING TRIANGLE}', player.set_pause, False), - ('\N{BLACK RIGHT-POINTING DOUBLE TRIANGLE}', player.seek, 15), - ('\N{BLACK RIGHT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}', player.seek, 1000), - ] - - selection = None - arg = None - - def check(reaction, user): - if user is None or user.id != ctx.author.id: - return False - - if reaction.message.id != msg.id: - return False - - for (emoji, func, _arg) in scrubber: - if emoji == reaction.emoji: - nonlocal selection - nonlocal arg - selection = func - arg = _arg - return True - return False - - embed = discord.Embed(description='{scrub.add}', color=ctx.me.color) - embed = ctx.localizer.format_embed(embed) - msg = await ctx.send(embed=embed) - - for (emoji, _, _) in scrubber: - await msg.add_reaction(emoji) - - embed.description = controls - embed = ctx.localizer.format_embed(embed) - await msg.edit(embed=embed) - scrubbing = True - while scrubbing: - try: - reaction, user = await self.bot.wait_for('reaction_add', timeout=15.0, check=check) - except asyncio.TimeoutError: - # PEP8EDIT scrolling = False - try: - await msg.delete() - await ctx.message.delete() - except discord.Forbidden: - pass - except discord.HTTPException: - pass - finally: - break - - try: - await msg.remove_reaction(reaction, user) - except discord.Forbidden: - pass - if selection is not None: - if not isinstance(arg, bool): - arg = player.position + arg * 1000 - await selection(arg) diff --git a/cogs/utils/decorators.py b/cogs/music/decorators.py similarity index 100% rename from cogs/utils/decorators.py rename to cogs/music/decorators.py diff --git a/cogs/music/events.py b/cogs/music/events.py index 5ddd5bf..c1fbc69 100644 --- a/cogs/music/events.py +++ b/cogs/music/events.py @@ -28,6 +28,8 @@ async def track_hook(self, event): pass if isinstance(event, lavalink.events.NodeChangedEvent): pass + if isinstance(event, lavalink.events.TrackStuckEvent): + print(event) @commands.Cog.listener() diff --git a/cogs/music/extra_commands.py b/cogs/music/extra_commands.py new file mode 100644 index 0000000..779540c --- /dev/null +++ b/cogs/music/extra_commands.py @@ -0,0 +1,215 @@ +""" +Commands that add to the music bot experience, like equalizers, commands that access the web, like lyrics, and +commands that are complex and mostly for show, like scrubber. +""" + +# Discord Packages +import discord +from discord.ext import commands + +import asyncio +import json +import re +import urllib + +from bs4 import BeautifulSoup + +from ..utils import checks +from ..utils.paginator import Scroller, TextPaginator +from .decorators import require_playing, require_voice_connection + + +@commands.command(name='normalize') +@checks.dj_or(alone=True) +@require_voice_connection() +async def _normalize(self, ctx): + """ Reset the equalizer and volume """ + player = self.bot.lavalink.player_manager.get(ctx.guild.id) + + await player.set_volume(100) + await player.bassboost(False) + + embed = discord.Embed(description='{volume.reset}', color=ctx.me.color) + embed = ctx.localizer.format_embed(embed) + await ctx.send(embed=embed) + + +@commands.command(name='boost') +@checks.dj_or(alone=True) +async def _boost(self, ctx, boost: bool = None): + """ Set the equalizer to bass boost the music """ + player = self.bot.lavalink.player_manager.get(ctx.guild.id) + + if boost: + await player.bassboost(boost) + + embed = discord.Embed(color=ctx.me.color) + embed.description = '{boost.on}' if player.boosted else '{boost.off}' + + embed = ctx.localizer.format_embed(embed) + await ctx.send(embed=embed) + + +@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) + history = player.get_history() + if not history: + embed = discord.Embed(description='{history.empty}', color=ctx.me.color) + embed = ctx.localizer.format_embed(embed) + return await ctx.send(embed=embed) + track = history[0] + 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' + 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' + + embed = discord.Embed(title=ctx.localizer.format_str('{history.title}'), color=ctx.me.color, + description=description) + + if thumbnail_url: + embed.set_thumbnail(url=thumbnail_url) + await ctx.send(embed=embed) + + +@commands.cooldown(1, 5, commands.BucketType.guild) +@commands.command(name='lyrics') +async def _lyrics(self, ctx, *query: str): + + # TODO: + player = self.bot.lavalink.player_manager.get(ctx.guild.id) + + genius_access_token = self.bot.APIkeys.get('genius', None) + + if genius_access_token is None: + return await ctx.send('Missing API key') + + excluded_words = ['music', 'video', 'version', 'original', 'lyrics', 'lyric', + 'official', 'live', 'instrumental', 'audio', 'hd'] + + query = ' '.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) + + async def get_site_content(url): + async with self.bot.session.get(url) as resp: + response = await resp.read() + return response.decode('utf-8') + + embed = discord.Embed(description=':mag_right:') + status_msg = await ctx.send(embed=embed) + + try: + url = 'https://api.genius.com/search?' + urllib.parse.urlencode( + {'access_token': genius_access_token, 'q': query}) + result = await get_site_content(url) + response = json.loads(result) + song_id = response['response']['hits'][0]['result']['id'] + except Exception: + embed = discord.Embed(description=':x:', color=0xFF0000) + return await status_msg.edit(embed=embed) + + result = await get_site_content(f'https://api.genius.com/songs/{song_id}?access_token={genius_access_token}') + song = json.loads(result)['response']['song'] + + response = await get_site_content(song['url']) + scraped_data = BeautifulSoup(response, 'html.parser') + lyrics = scraped_data.find(class_='lyrics').get_text() + + paginator = TextPaginator(max_size=2000, max_lines=50, color=0xFFFF64) + for line in lyrics.split('\n'): + paginator.add_line(line) + + await status_msg.delete() + + paginator.pages[0].url = song['url'] + paginator.pages[0].title = song['full_title'] + paginator.pages[0].set_thumbnail(url=song['header_image_thumbnail_url']) + paginator.pages[0].set_author(name='Genius', icon_url='https://i.imgur.com/NmCTsoF.png') + + if len(paginator.pages) < 4: + for page in paginator.pages: + await ctx.send(embed=page) + else: + paginator.add_page_indicator(ctx.localizer) + await Scroller(ctx, paginator).start_scrolling() + + +@commands.command(name='scrub') +@checks.dj_or(alone=True) +@require_voice_connection() +@require_playing(require_user_listening=True) +async def _scrub(self, ctx): + """ Lists the first 10 search results from a given query. """ + player = self.bot.lavalink.player_manager.get(ctx.guild.id) + + controls = '{scrub.controls}' + + scrubber = [ + ('\N{BLACK LEFT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}', player.seek, -1000), + ('\N{BLACK LEFT-POINTING DOUBLE TRIANGLE}', player.seek, -15), + ('\N{DOUBLE VERTICAL BAR}', player.set_pause, True), + ('\N{BLACK RIGHT-POINTING TRIANGLE}', player.set_pause, False), + ('\N{BLACK RIGHT-POINTING DOUBLE TRIANGLE}', player.seek, 15), + ('\N{BLACK RIGHT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}', player.seek, 1000), + ] + + selection = None + arg = None + + def check(reaction, user): + if user is None or user.id != ctx.author.id: + return False + + if reaction.message.id != msg.id: + return False + + for (emoji, func, _arg) in scrubber: + if emoji == reaction.emoji: + nonlocal selection + nonlocal arg + selection = func + arg = _arg + return True + return False + + embed = discord.Embed(description='{scrub.add}', color=ctx.me.color) + embed = ctx.localizer.format_embed(embed) + msg = await ctx.send(embed=embed) + + for (emoji, _, _) in scrubber: + await msg.add_reaction(emoji) + + embed.description = controls + embed = ctx.localizer.format_embed(embed) + await msg.edit(embed=embed) + scrubbing = True + while scrubbing: + try: + reaction, user = await self.bot.wait_for('reaction_add', timeout=15.0, check=check) + except asyncio.TimeoutError: + # PEP8EDIT scrolling = False + try: + await msg.delete() + await ctx.message.delete() + except (discord.Forbidden, discord.HTTPException): + pass + finally: + break + + try: + await msg.remove_reaction(reaction, user) + except discord.Forbidden: + pass + if selection is not None: + if not isinstance(arg, bool): + arg = player.position + arg * 1000 + await selection(arg) diff --git a/cogs/nodemanager.py b/cogs/nodemanager.py index 58c4265..fce427a 100644 --- a/cogs/nodemanager.py +++ b/cogs/nodemanager.py @@ -119,7 +119,7 @@ class NodeManager(commands.Cog): @_node.command(name='add') @commands.is_owner() async def _add(self, ctx, host, port, password, region, name=None): - self.bot.lavalink.add_node(host, port, password, region, name) + self.bot.lavalink.add_node(host, port, password, region, name=name) self.logger.debug("Adding Lavalink node: %s on %s with the port %s in %s" % (host, port, region, name,)) embed = await self._node_presenter(ctx, {'host': host, 'port': port, 'password': password, 'region': region, 'name': name}) @@ -152,6 +152,24 @@ class NodeManager(commands.Cog): if not sent_feedback: await ctx.send('No node found') + @_node.command(name='change') + @commands.is_owner() + async def _nodechange(self, ctx, node=None): + player = self.bot.lavalink.player_manager.get(ctx.guild.id) + + if node is None: + await player.change_node(player.node) + + else: + newnode = None + for _node in self.bot.lavalink.node_manager.nodes: + if (_node.name or _node.host) == node: + newnode = _node + + if not newnode: + return + await player.change_node(newnode) + def setup(bot): bot.add_cog(NodeManager(bot))