diff --git a/musicbot/cogs/cogmanager.py b/musicbot/cogs/cogmanager.py index 9610959..df4364d 100644 --- a/musicbot/cogs/cogmanager.py +++ b/musicbot/cogs/cogmanager.py @@ -3,7 +3,7 @@ from discord.ext import commands import traceback -from ..utils.userinteraction import Scroller, ClearOn +from ..utils.userinteraction import ClearOn, Scroller from .helpformatter import commandhelper diff --git a/musicbot/cogs/errors.py b/musicbot/cogs/errors.py index 1dbd44d..6da74e4 100644 --- a/musicbot/cogs/errors.py +++ b/musicbot/cogs/errors.py @@ -4,7 +4,7 @@ from discord.ext import commands import traceback -from ..utils.userinteraction import Scroller, ClearOn +from ..utils.userinteraction import ClearOn, Scroller from .helpformatter import commandhelper from .music.music_errors import WrongTextChannelError, WrongVoiceChannelError diff --git a/musicbot/cogs/helpformatter/help.py b/musicbot/cogs/helpformatter/help.py index b1f41da..11f7c82 100644 --- a/musicbot/cogs/helpformatter/help.py +++ b/musicbot/cogs/helpformatter/help.py @@ -1,7 +1,7 @@ # Discord Packages from discord.ext import commands -from ...utils.userinteraction.scroller import Scroller, ClearOn +from ...utils.userinteraction.scroller import ClearOn, Scroller from .helpformatter import coghelper, commandhelper, helper, prefix_cleaner diff --git a/musicbot/cogs/music/__init__.py b/musicbot/cogs/music/__init__.py index c7bd443..ee41d43 100644 --- a/musicbot/cogs/music/__init__.py +++ b/musicbot/cogs/music/__init__.py @@ -1,17 +1,19 @@ # Discord Packages -from os import remove import discord import lavalink -import asyncio from discord.ext import commands, tasks +import asyncio import re +import json +import urllib from bs4 import BeautifulSoup + from musicbot.utils.mixplayer.player import MixPlayer from ...utils import checks, thumbnailer, timeformatter -from ...utils.userinteraction import QueuePaginator, Scroller, Selector, TextPaginator, ClearOn +from ...utils.userinteraction import ClearOn, QueuePaginator, Scroller, Selector, TextPaginator from ...utils.userinteraction.selector import Selector2, SelectorButton, SelectorItem from .decorators import BasicVoiceClient, require_playing, require_queue, require_voice_connection, voteable from .music_errors import WrongTextChannelError @@ -135,33 +137,33 @@ class Music(commands.Cog): self.logger.debug("Query: %s" % query) player = self.bot.lavalink.player_manager.get(ctx.guild.id) query = query.strip('<>') - + if not url_rx.match(query): query = f'ytsearch:{query}' - + results = await player.node.get_tracks(query) - + if not results or not results.tracks: return await ctx.send(ctx.localizer.format_str("{nothing_found}")) - + embed = discord.Embed(color=ctx.me.color) - + if results.load_type == 'PLAYLIST_LOADED': numtracks = 0 for track in results.tracks: _, track_added = await self.enqueue(ctx, track, embed, silent=True) if track_added: numtracks += 1 - + embed.title = '{playlist_enqued}' embed.description = f'{results.playlist_info.name} - {numtracks} {{tracks}}' else: track = results.tracks[0] await self.enqueue(ctx, track, embed) - + embed = ctx.localizer.format_embed(embed) await ctx.send(embed=embed) - + if not player.is_playing: await player.play() @@ -190,32 +192,32 @@ class Music(commands.Cog): async def _skip(self, ctx): """ Skips the current track. """ player = self.bot.lavalink.player_manager.get(ctx.guild.id) - + await player.skip() if player.current: embed = self.get_current_song_embed(ctx) await ctx.send(ctx.localizer.format_str("{skip.skipped}"), embed=embed) else: await ctx.send(ctx.localizer.format_str("{skip.skipped}")) - + @commands.command(name='skipto') @checks.dj_or(alone=True) @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) - + # TODO: Do all queue out of range messages the same way if pos < 1: return await ctx.send(ctx.localizer.format_str("{skip_to.invalid_pos}")) if len(player.queue) < pos: return await ctx.send(ctx.localizer.format_str("{skip_to.exceeds_queue}")) - + await player.skip(pos - 1) - + msg = ctx.localizer.format_str("{skip_to.skipped_to}", _title=player.current.title, _pos=pos) await ctx.send(msg) - + @commands.command(name='stop') @require_voice_connection() @require_playing(require_user_listening=True) @@ -225,7 +227,7 @@ class Music(commands.Cog): player = self.bot.lavalink.player_manager.get(ctx.guild.id) player.queue.clear() await player.stop() - + embed = discord.Embed(color=ctx.me.color, title='{stop}') await ctx.send(embed=ctx.localizer.format_embed(embed)) @@ -279,9 +281,9 @@ class Music(commands.Cog): async def _move(self, ctx): """ Moves a song in your queue. """ player = self.bot.lavalink.player_manager.get(ctx.guild.id) - + user_queue = player.user_queue(ctx.author.id, dual=True) - + # Setup for selector identifiers = [] for index, temp in enumerate(user_queue): @@ -363,7 +365,7 @@ class Music(commands.Cog): selector_buttons.append( SelectorItem(f'`{index}` [{track.title}]({track.uri})', str(index), add_change_button_color(update_remove_list, tracks_to_remove, track))) - + remove_selector = Selector2(ctx, selector_buttons, terminate_on_select=False, use_tick_for_stop_emoji=True, color=ctx.me.color, title='Select songs to remove') await remove_selector.start_scrolling(ClearOn.AnyExit) @@ -444,7 +446,7 @@ class Music(commands.Cog): wrap_in_button_callback( self.enqueue, ctx, track, discord.Embed(color=ctx.me.color))) buttons.append(interaction) - + search_selector = Selector2(ctx, buttons, color=ctx.me.color, title=ctx.localizer.format_str('{results}')) message, callback_results = await search_selector.start_scrolling() @@ -629,10 +631,10 @@ class Music(commands.Cog): thumbnail_url = None # 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' + _uri=track.uri, _id=track.requester) + '\n' embed = discord.Embed(title=ctx.localizer.format_str('{history.title}'), color=ctx.me.color, - description=description) + description=description) if thumbnail_url: embed.set_thumbnail(url=thumbnail_url) @@ -651,7 +653,7 @@ class Music(commands.Cog): return await ctx.send('Missing API key') excluded_words = ['music', 'video', 'version', 'original', 'lyrics', 'lyric', - 'official', 'live', 'instrumental', 'audio', 'hd'] + 'official', 'live', 'instrumental', 'audio', 'hd'] query = ' '.join(query) @@ -729,7 +731,8 @@ class Music(commands.Cog): scrubber_controls = [ SelectorItem("", '\N{BLACK LEFT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}', seek_callback(player, -1000)), SelectorItem("", '\N{BLACK LEFT-POINTING DOUBLE TRIANGLE}', seek_callback(player, -15)), - SelectorItem("", '\N{Black Right-Pointing Triangle with Double Vertical Bar}', toggle_pause_callback(player)), + SelectorItem("", '\N{Black Right-Pointing Triangle with Double Vertical Bar}', + toggle_pause_callback(player)), SelectorItem("", '\N{BLACK RIGHT-POINTING DOUBLE TRIANGLE}', seek_callback(player, 15)), SelectorItem("", '\N{BLACK RIGHT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}', seek_callback(player, 1000)) ] @@ -743,7 +746,7 @@ class Music(commands.Cog): if ctx.invoked_subcommand is None: player = self.bot.lavalink.player_manager.get(ctx.guild.id) embed = discord.Embed(color=ctx.me.color, title='Loop status') - + if player.looping: embed.description = 'The bot is currently looping: ♾️' else: @@ -836,10 +839,12 @@ class Music(commands.Cog): except Exception as err: self.logger.debug("Error in leave_timer loop.\nTraceback: %s" % (err)) + def wrap_in_button_callback(func, *args): async def inner(_interaction, _button): return await func(*args) return inner + async def setup(bot): await bot.add_cog(Music(bot)) diff --git a/musicbot/cogs/nodemanager.py b/musicbot/cogs/nodemanager.py index f6f2b37..e233fee 100644 --- a/musicbot/cogs/nodemanager.py +++ b/musicbot/cogs/nodemanager.py @@ -9,7 +9,7 @@ import codecs import yaml from ..utils.mixplayer import MixPlayer -from ..utils.userinteraction import Scroller, ClearOn +from ..utils.userinteraction import ClearOn, Scroller from .helpformatter import commandhelper diff --git a/musicbot/cogs/settings.py b/musicbot/cogs/settings.py index f3af709..44142af 100644 --- a/musicbot/cogs/settings.py +++ b/musicbot/cogs/settings.py @@ -3,7 +3,7 @@ import discord from discord.ext import commands from ..utils import checks -from ..utils.userinteraction import Scroller, ClearOn +from ..utils.userinteraction import ClearOn, Scroller from .helpformatter import commandhelper diff --git a/musicbot/utils/mixplayer/mixqueue.py b/musicbot/utils/mixplayer/mixqueue.py index eb6e683..2a552ee 100644 --- a/musicbot/utils/mixplayer/mixqueue.py +++ b/musicbot/utils/mixplayer/mixqueue.py @@ -4,7 +4,7 @@ from lavalink import AudioTrack from collections import OrderedDict, deque from itertools import chain, cycle, islice from random import shuffle -from typing import Union, List +from typing import List, Union def roundrobin(*iterables): diff --git a/musicbot/utils/userinteraction/__init__.py b/musicbot/utils/userinteraction/__init__.py index 50a607f..68b276d 100644 --- a/musicbot/utils/userinteraction/__init__.py +++ b/musicbot/utils/userinteraction/__init__.py @@ -1,5 +1,5 @@ from .paginators import BasePaginator, CantScrollException, FieldPaginator, HelpPaginator, QueuePaginator, TextPaginator -from .scroller import Scroller, ClearOn +from .scroller import ClearOn, Scroller from .selector import Selector __all__ = ['Scroller', 'Selector', 'QueuePaginator', 'HelpPaginator', diff --git a/musicbot/utils/userinteraction/scroller.py b/musicbot/utils/userinteraction/scroller.py index 1285861..87d86bd 100644 --- a/musicbot/utils/userinteraction/scroller.py +++ b/musicbot/utils/userinteraction/scroller.py @@ -1,11 +1,14 @@ from __future__ import annotations -import discord -from enum import Flag, auto +# Discord Packages +import discord from lavalink.client import asyncio + +from enum import Flag, auto +from typing import List, Union + from .paginators import BasePaginator, CantScrollException -from typing import Union, List class ClearOn(Flag): Timeout = auto() @@ -26,8 +29,8 @@ class ScrollerNav(discord.ui.Select): class Scroller: - def __init__(self, ctx, paginator, timeout=20.0, - use_tick_for_stop_emoji: bool = False, show_cancel_for_single_page: bool = False): + def __init__(self, ctx, paginator, timeout=20.0, use_tick_for_stop_emoji: bool = False, + show_cancel_for_single_page: bool = False): if not isinstance(paginator, BasePaginator): raise TypeError('Paginator needs to be a subclass of BasePaginator.') @@ -51,23 +54,26 @@ class Scroller: self.use_nav_bar = len(self.paginator.pages) > 3 self.scrolling_done = asyncio.Event() - stop_emoji = '✔️' if use_tick_for_stop_emoji else '❌' + stop_emoji = '✔️' if use_tick_for_stop_emoji else '❌' self.control_buttons: List[ScrollerButton] = [] - self.forward_button = ScrollerButton(callback=self.next_page, label='\N{BLACK RIGHT-POINTING TRIANGLE}', row=3) - self.back_button = ScrollerButton(self.previous_page,'\N{BLACK LEFT-POINTING TRIANGLE}', row=3) - first_page_button = ScrollerButton(self.first_page,'\N{BLACK LEFT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}', row=3) - last_page_button = ScrollerButton(self.last_page,'\N{BLACK RIGHT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}', row=3) + self.forward_button = ScrollerButton(self.next_page, '\N{BLACK RIGHT-POINTING TRIANGLE}', row=3) + self.back_button = ScrollerButton(self.previous_page, '\N{BLACK LEFT-POINTING TRIANGLE}', row=3) + first_page_button = ScrollerButton(self.first_page, + '\N{BLACK LEFT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}', row=3) + last_page_button = ScrollerButton(self.last_page, + '\N{BLACK RIGHT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}', row=3) self.stop_button = ScrollerButton(self.stop, stop_emoji, row=3) # We start on the first page, can't go back self.back_button.disabled = True - + # Determine which buttons should be visible depending on the number of pages if (self.is_scrolling_paginator): if (len(self.paginator.pages) > 2): - self.control_buttons = [first_page_button, self.back_button, self.forward_button, last_page_button, self.stop_button] + self.control_buttons = [first_page_button, self.back_button, self.forward_button, + last_page_button, self.stop_button] else: self.control_buttons = [self.back_button, self.forward_button, self.stop_button] elif show_cancel_for_single_page: @@ -122,7 +128,7 @@ class Scroller: try: if self.ctx.message: await self.ctx.message.delete() - except: + except discord.HTTPException: pass else: await self.update_message() diff --git a/musicbot/utils/userinteraction/selector.py b/musicbot/utils/userinteraction/selector.py index 28fa709..14e8d4c 100644 --- a/musicbot/utils/userinteraction/selector.py +++ b/musicbot/utils/userinteraction/selector.py @@ -1,12 +1,11 @@ -# Discord Packages from __future__ import annotations -import discord +# Discord Packages +import discord import asyncio import inspect -from typing import List, Callable, Coroutine -from discord.ext.commands import check +from typing import Callable, Coroutine, List from .paginators import TextPaginator from .scroller import ClearOn, Scroller @@ -56,7 +55,8 @@ class Selector2(TextPaginator, Scroller): self.add_line(default_text) self.close_page() - Scroller.__init__(self, ctx, self, use_tick_for_stop_emoji=use_tick_for_stop_emoji, show_cancel_for_single_page=True) + Scroller.__init__(self, ctx, self, use_tick_for_stop_emoji=use_tick_for_stop_emoji, + show_cancel_for_single_page=True) # Contains a list of all buttons that can be interacted with, both # currently visible and not. @@ -87,7 +87,7 @@ class Selector2(TextPaginator, Scroller): return wrapped_callback for choice in self.selections: - self.buttons.append(SelectorButton(label=choice.button_label, callback=with_update_view(choice.callback), row=0)) + self.buttons.append(SelectorButton(choice.button_label, with_update_view(choice.callback), row=0)) def build_view(self): super().build_view() @@ -110,7 +110,7 @@ class Selector2(TextPaginator, Scroller): self.view.add_item(item=button) self.visible_buttons.append(button) - async def start_scrolling(self, clear_mode: ClearOn=ClearOn.Timeout): + async def start_scrolling(self, clear_mode: ClearOn = ClearOn.Timeout): message = await super().start_scrolling(clear_mode) return message, self.callback_results