From 75c283e95946bb0dae4dc21cdf0e1d46fb107670 Mon Sep 17 00:00:00 2001 From: Ev-1 <33662061+Ev-1@users.noreply.github.com> Date: Sun, 2 Oct 2022 21:39:00 +0200 Subject: [PATCH] Remove old selector implementation --- bot.py | 1 + localization/en_en/music.yaml | 2 +- musicbot/cogs/music/__init__.py | 134 ++++++----- musicbot/utils/bot_version.py | 2 +- musicbot/utils/userinteraction/scroller.py | 48 ++-- musicbot/utils/userinteraction/selector.py | 261 +++------------------ setup.cfg | 2 +- 7 files changed, 145 insertions(+), 305 deletions(-) diff --git a/bot.py b/bot.py index cde79c5..1a3e4db 100644 --- a/bot.py +++ b/bot.py @@ -12,6 +12,7 @@ from argparse import ArgumentParser, RawTextHelpFormatter import aiohttp import yaml +# Bot Utilities from musicbot.utils.localisation import Aliaser, LocalizedContext, Localizer, LocalizerWrapper from musicbot.utils.logger import BotLogger from musicbot.utils.settingsmanager import Settings diff --git a/localization/en_en/music.yaml b/localization/en_en/music.yaml index 5c5433a..14aea15 100644 --- a/localization/en_en/music.yaml +++ b/localization/en_en/music.yaml @@ -62,7 +62,7 @@ scrub: enqueue: position: position playing_in: Playing in - enqueued: Song enqueued + enqueued: Song queued estimated: estimated toolong: Track too long. `{_length}>{_max}` history: diff --git a/musicbot/cogs/music/__init__.py b/musicbot/cogs/music/__init__.py index c07cfb2..df256a6 100644 --- a/musicbot/cogs/music/__init__.py +++ b/musicbot/cogs/music/__init__.py @@ -12,11 +12,12 @@ import urllib from bs4 import BeautifulSoup +# Bot Utilities from musicbot.utils.mixplayer.player import MixPlayer - from ...utils import checks, thumbnailer, timeformatter -from ...utils.userinteraction import ClearOn, QueuePaginator, Scroller, Selector, TextPaginator -from ...utils.userinteraction.selector import Selector2, SelectorButton, SelectorItem +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 @@ -63,6 +64,7 @@ class Music(commands.Cog): return embed, False track.extra["thumbnail_url"] = await thumbnailer.ThumbNailer.identify(self, track.identifier, track.uri) + track.requester = ctx.author.id # Add to player track, pos_global, pos_local = player.add(requester=ctx.author.id, track=track) @@ -90,25 +92,27 @@ class Music(commands.Cog): return embed, True def get_current_song_embed(self, ctx, include_time=False): - player = self.bot.lavalink.player_manager.get(ctx.guild.id) + player: MixPlayer = self.bot.lavalink.player_manager.get(ctx.guild.id) + if not player.current: + return + + song = f'**[{player.current.title}]({player.current.uri})**' if include_time: position = timeformatter.format_ms(player.position) if player.current.stream: duration = '{live}' else: duration = timeformatter.format_ms(player.current.duration) - song = f'**[{player.current.title}]({player.current.uri})**\n({position}/{duration})' - else: - song = f'**[{player.current.title}]({player.current.uri})**' + song += f'\n({position}/{duration})' embed = discord.Embed(color=ctx.me.color, description=song, title='{now}') - member = ctx.guild.get_member(player.current.requester) if thumbnail_url := player.current.extra["thumbnail_url"]: embed.set_thumbnail(url=thumbnail_url) - embed.set_footer(text=f'{{requested_by}} {member.display_name}', icon_url=member.display_avatar.url) + if member := ctx.guild.get_member(player.current.requester): + embed.set_footer(text=f'{{requested_by}} {member.display_name}', icon_url=member.display_avatar.url) embed = ctx.localizer.format_embed(embed) return embed @@ -189,11 +193,8 @@ class Music(commands.Cog): 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}")) + embed = self.get_current_song_embed(ctx) + await ctx.send(ctx.localizer.format_str("{skip.skipped}"), embed=embed) @commands.command(name='skipto') @checks.dj_or(alone=True) @@ -229,9 +230,9 @@ class Music(commands.Cog): @commands.command(name='now') @require_playing() async def _now(self, ctx): - embed = self.get_current_song_embed(ctx, include_time=True) - embed = ctx.localizer.format_embed(embed) - await ctx.send(embed=embed) + if embed := self.get_current_song_embed(ctx, include_time=True): + embed = ctx.localizer.format_embed(embed) + await ctx.send(embed=embed) @commands.command(name='queue') @require_queue(require_member_queue=True) @@ -275,57 +276,63 @@ class Music(commands.Cog): @require_queue(require_author_queue=True) async def _move(self, ctx): """ Moves a song in your queue. """ - player = self.bot.lavalink.player_manager.get(ctx.guild.id) + player: MixPlayer = self.bot.lavalink.player_manager.get(ctx.guild.id) - user_queue = player.user_queue(ctx.author.id, dual=True) + # We create a new selector for each selection + def move_selector(ctx, queue, title, first: bool): + async def return_track(track): + return track - # Setup for selector - identifiers = [] - for index, temp in enumerate(user_queue): - track, globpos = temp - identifiers.append(ctx.localizer.format_str("{queue.usertrack}", _index=index+1, - _globalindex=globpos+1, _title=track.title, - _uri=track.uri)) + choices = [] + for index, (track, _) in enumerate(queue, start=1): + blank = " " * 6 + if first: + prefix = f'`{blank}`\n`{index:<6}`' + else: + prefix = f'`{index:<3}-> `\n`{blank}`' + label = f'{prefix} [{track.title}]({track.uri})' + selection = SelectorItem(label, str(index), wrap_in_button_callback(return_track, track)) + choices.append(selection) - def ret_first_arg(*args): - return args[0] + return Selector(ctx, choices, select_mode=SelectMode.SpanningMultiSelect, use_tick_for_stop_emoji=True, + color=ctx.me.color, title=ctx.localizer.format_str(title)) - functions = [ret_first_arg]*len(user_queue) - arguments = [(i,) for i in range(len(user_queue))] + message = None + page = 0 + while True: + # Prompt the user for which track to move + selector = move_selector(ctx, player.user_queue(ctx.author.id, dual=True), "{moved.choose_pos}", True) + message, track_to_move = await selector.start_scrolling(ClearOn.Timeout, message, page) + page = selector.page_number - # Set the different titles for the different selections - round_titles = ["{moved.choose_song}", "{moved.choose_pos}"] - round_titles = [ctx.localizer.format_str(i) for i in round_titles] + if not track_to_move: + break - selector = Selector(ctx, identifiers, functions, arguments, num_selections=5, round_titles=round_titles, - color=ctx.me.color, title=ctx.localizer.format_str("{moved.choose}")) + # Prompt the user for where to move it + selector = move_selector(ctx, player.user_queue(ctx.author.id, dual=True), "{moved.choose_song}", False) + message, track_to_replace = await selector.start_scrolling(ClearOn.Timeout, message, page) + page = selector.page_number - # Get the initial and final position - message, page, selections = await selector.start_scrolling() + if not track_to_replace: + break - try: - pos_initial, pos_final = selections[0], selections[1] - except (IndexError, TypeError): - return await message.delete() + # At this point the user has chosen two songs, we will move the first song to be in front of the second + # one in the queue. Find the index of both songs in the current queue - # Move the track - moved = player.move_user_track(ctx.author.id, pos_initial, pos_final) + # Keep updating the user queue in case it changes while the user is selecting songs + user_queue = player.user_queue(ctx.author.id, dual=True) + user_queue_identifiers = [track for (track, _) in user_queue] + try: + pos_initial = user_queue_identifiers.index(track_to_move[0]) + pos_final = user_queue_identifiers.index(track_to_replace[0]) + player.move_user_track(ctx.author.id, pos_initial, pos_final) + except ValueError: + # Track not found in queue. Queue could have changed during selection + # if a song was skipped or the current song finished + pass - # 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}') - member = ctx.guild.get_member(moved.requester) - - if thumbnail_url := player.current.extra["thumbnail_url"]: - embed.set_thumbnail(url=thumbnail_url) - - embed.add_field(name="{moved.local}", value=f"`{pos_initial + 1} → {pos_final + 1}`", inline=True) - embed.add_field(name="{moved.global}", value=f"`{player.queue._loc_to_glob(ctx.author.id, pos_initial) + 1}\ - → {player.queue._loc_to_glob(ctx.author.id, pos_final) + 1}`", inline=True) - embed.set_footer(text=f'{{requested_by}} {member.display_name}', icon_url=member.display_avatar.url) - embed = ctx.localizer.format_embed(embed) - - await message.edit(embed=embed) + if message: + await message.delete() @commands.command(name='remove') @require_voice_connection() @@ -361,8 +368,8 @@ class Music(commands.Cog): 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') + remove_selector = Selector(ctx, selector_buttons, select_mode=SelectMode.MultiSelect, + use_tick_for_stop_emoji=True, color=ctx.me.color, title='Select songs to remove') await remove_selector.start_scrolling(ClearOn.AnyExit) # If any tracks were removed create a scroller for navigating them @@ -442,7 +449,8 @@ class Music(commands.Cog): 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}')) + search_selector = Selector(ctx, buttons, select_mode=SelectMode.SingleSelect, + color=ctx.me.color, title=ctx.localizer.format_str('{results}')) message, callback_results = await search_selector.start_scrolling() if len(callback_results) == 0: @@ -732,8 +740,8 @@ class Music(commands.Cog): 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)) ] - scrubber = Selector2(ctx, scrubber_controls, terminate_on_select=False, use_tick_for_stop_emoji=True, - default_text=ctx.localizer.format_str(controls), color=ctx.me.color) + scrubber = Selector(ctx, scrubber_controls, select_mode=SelectMode.MultiSelect, use_tick_for_stop_emoji=True, + default_text=ctx.localizer.format_str(controls), color=ctx.me.color) await scrubber.start_scrolling(ClearOn.AnyExit) @commands.group(name='loop') diff --git a/musicbot/utils/bot_version.py b/musicbot/utils/bot_version.py index c37e33d..cff551c 100644 --- a/musicbot/utils/bot_version.py +++ b/musicbot/utils/bot_version.py @@ -1 +1 @@ -bot_version = "0.2.13" +bot_version = "0.3.0" diff --git a/musicbot/utils/userinteraction/scroller.py b/musicbot/utils/userinteraction/scroller.py index 24bc6a1..51b780a 100644 --- a/musicbot/utils/userinteraction/scroller.py +++ b/musicbot/utils/userinteraction/scroller.py @@ -2,8 +2,8 @@ from __future__ import annotations # Discord Packages import discord -from lavalink.client import asyncio +import asyncio from enum import Flag, auto from typing import List, Union @@ -88,21 +88,31 @@ class Scroller: if not self.permissions.send_messages: raise CantScrollException('Bot cannot send messages.') - async def start_scrolling(self, clear_mode: ClearOn) -> discord.Message: + async def start_scrolling(self, clear_mode: ClearOn, + message: Union[discord.Message, None] = None, + start_page: int = 0) -> discord.Message: self.clear_mode = clear_mode - self.current_page_number = 0 + self.page_number = start_page self.build_view() - self.message = await self.channel.send(embed=self.paginator.pages[0], view=self.view) + self.update_view() + if message: + self.message = message + await self.message.edit(embed=self.current_page_embed, view=self.view) + else: + self.message = await self.channel.send(embed=self.current_page_embed, view=self.view) await self.scrolling_done.wait() return self.message - def update_view(self, interaction: discord.Interaction): + def update_view_on_interaction(self, interaction: discord.Interaction): + self.update_view() + + def update_view(self): if self.use_nav_bar: - self.navigator.placeholder = f"Page: {self.current_page_number + 1}/{len(self.paginator.pages)}" + self.navigator.placeholder = f"Page: {self.page_number + 1}/{len(self.paginator.pages)}" if self.is_scrolling_paginator: - self.back_button.disabled = self.current_page_number == 0 - self.forward_button.disabled = self.current_page_number == len(self.paginator.pages)-1 + self.back_button.disabled = self.page_number == 0 + self.forward_button.disabled = self.page_number == len(self.paginator.pages)-1 def build_view(self): for button in self.control_buttons: @@ -114,10 +124,11 @@ class Scroller: for (i, _) in enumerate(self.paginator.pages): self.navigator.add_option(label=str(i+1), value=str(i)) - async def stop(self, user_stopped: bool): + async def stop(self, user_stopped: bool, clear_scroller_view: bool = True): self.is_scrolling_paginator = False self.view.stop() - self.view.clear_items() + if clear_scroller_view: + self.view.clear_items() if (user_stopped and self.clear_mode & ClearOn.ManualExit or not user_stopped and self.clear_mode & ClearOn.Timeout): if self.message: @@ -131,7 +142,8 @@ class Scroller: except discord.HTTPException: pass else: - await self.update_message() + if clear_scroller_view: + await self.update_message() # Notify the start_scrolling function that we're done self.scrolling_done.set() @@ -142,17 +154,17 @@ class Scroller: if page < 0 or page >= len(self.paginator.pages): return - self.current_page_number = page + self.page_number = page if interaction.message: # Update the view before we edit the message - self.update_view(interaction) + self.update_view_on_interaction(interaction) await self.update_message() await interaction.response.defer() async def update_message(self): if self.message: - await self.message.edit(embed=self.paginator.pages[self.current_page_number], view=self.view) + await self.message.edit(embed=self.current_page_embed, view=self.view) async def first_page(self, interaction: discord.Interaction): await self._scroll(0, interaction) @@ -161,10 +173,10 @@ class Scroller: await self._scroll(len(self.paginator.pages) - 1, interaction) async def next_page(self, interaction: discord.Interaction): - await self._scroll(self.current_page_number + 1, interaction) + await self._scroll(self.page_number + 1, interaction) async def previous_page(self, interaction: discord.Interaction): - await self._scroll(self.current_page_number - 1, interaction) + await self._scroll(self.page_number - 1, interaction) async def navigate(self, interaction: discord.Interaction): await self._scroll(int(self.navigator.values[0]), interaction) @@ -175,3 +187,7 @@ class Scroller: async def on_timeout(self): await self.stop(user_stopped=False) + + @property + def current_page_embed(self): + return self.paginator.pages[self.page_number] diff --git a/musicbot/utils/userinteraction/selector.py b/musicbot/utils/userinteraction/selector.py index b4944ae..ecc72ad 100644 --- a/musicbot/utils/userinteraction/selector.py +++ b/musicbot/utils/userinteraction/selector.py @@ -3,14 +3,19 @@ from __future__ import annotations # Discord Packages import discord -import asyncio -import inspect -from typing import Callable, Coroutine, List +from enum import Enum, auto +from typing import Callable, Coroutine, List, Union from .paginators import TextPaginator from .scroller import ClearOn, Scroller +class SelectMode(Enum): + SingleSelect = auto() + MultiSelect = auto() + SpanningMultiSelect = auto() # Used to keep the view around after select + + class SelectorButton(discord.ui.Button): def __init__(self, label, callback: Callable[[discord.Interaction, SelectorButton], Coroutine], style=discord.ButtonStyle.gray, **kwargs): @@ -34,15 +39,11 @@ class SelectorItem: self.callback = callback -class Selector2(TextPaginator, Scroller): - def __init__(self, ctx, choices: List[SelectorItem], round_titles=None, use_tick_for_stop_emoji: bool = False, - terminate_on_select: bool = True, max_size=2000, default_text=" ", **embed_base): - self.match = None - if round_titles is None: - round_titles = [] - self.match = None - self.terminate_on_select = terminate_on_select - self.round_titles = round_titles +class Selector(TextPaginator, Scroller): + def __init__(self, ctx, choices: List[SelectorItem], select_mode: SelectMode, + use_tick_for_stop_emoji: bool = False, max_size=2000, default_text=" ", **embed_base): + + self.selector_mode: SelectMode = select_mode self.selections_per_page = 5 self.selections = choices @@ -63,10 +64,10 @@ class Selector2(TextPaginator, Scroller): self.buttons = [] # The list of currently visible buttons, gets cleared upon scroll. - self.visible_buttons = [] + self.currently_visible_buttons = [] - # Depending on the selector we might want the result of callbacks - # after we have finished interacting with the scroller + # Depending on the selector we might want the result of calculations in + # the callbacks after we have finished interacting with the scroller self.callback_results = [] # Wrap the provided so that we can both update the view if needed @@ -78,12 +79,17 @@ class Selector2(TextPaginator, Scroller): result = await func(interaction, button) self.callback_results.append(result) - self.update_view(interaction) + self.update_view_on_interaction(interaction) await interaction.response.defer() - if terminate_on_select: - await self.stop(True) - else: - await self.update_message() + + match self.selector_mode: + case SelectMode.SingleSelect: + await self.stop(True, clear_scroller_view=True) + case SelectMode.SpanningMultiSelect: + await self.stop(True, clear_scroller_view=False) + case _: + await self.update_message() + return wrapped_callback for choice in self.selections: @@ -93,216 +99,25 @@ class Selector2(TextPaginator, Scroller): super().build_view() self.update_buttons() - def update_view(self, interaction: discord.Interaction): - super().update_view(interaction) + def update_view(self): + super().update_view() self.update_buttons() def update_buttons(self): - for button in self.visible_buttons: + for button in self.currently_visible_buttons: self.view.remove_item(item=button) - self.visible_buttons = [] - self.current_page_number - start = self.current_page_number * self.selections_per_page - end = min((self.current_page_number + 1) * self.selections_per_page, len(self.selections)) + self.currently_visible_buttons = [] + self.page_number + start = self.page_number * self.selections_per_page + end = min((self.page_number + 1) * self.selections_per_page, len(self.selections)) for button in self.buttons[start:end]: self.view.add_item(item=button) - self.visible_buttons.append(button) + self.currently_visible_buttons.append(button) - async def start_scrolling(self, clear_mode: ClearOn = ClearOn.Timeout): - message = await super().start_scrolling(clear_mode) + async def start_scrolling(self, clear_mode: ClearOn = ClearOn.Timeout, + message: Union[discord.Message, None] = None, + start_page: int = 0): + message = await super().start_scrolling(clear_mode, message, start_page) return message, self.callback_results - - -class Selector(TextPaginator): - def __init__(self, ctx, identifiers, functions, arguments, terminate_on_select=True, num_selections=3, - round_titles=None, max_size=2000, **embed_base): - """ - Selector, tool for making interactive menus. - :param ctx: discord.py context - :param identifiers: A list of strings, one for each menu item - :param functions: List of functions, one for each menu item. Gets run when the corresponding - menu item is selected - :param arguments: List of arguments to pass to the corresponding function in functions. - :param terminate_on_select: - :param num_selections: the number of selectable items per page - :param round_titles: A list of embed titles, updates each time a menu selection is made - :param max_size: The max amount of characters per page. - :param embed_base: Arguments that specify the look of the scroller embeds - """ - if round_titles is None: - round_titles = [] - self.match = None - self.current_page = 0 - self.ctx = ctx - self.bot = ctx.bot - self.cmdmsg = ctx.message - self.message = ctx.message - self.channel = ctx.channel - self.author = ctx.author - - self.localizer = ctx.localizer - - self.num_selections = num_selections - self.terminate_on_select = terminate_on_select - self.round_titles = round_titles - - assert len(identifiers) == len(functions) == len(arguments) - super().__init__(max_size=max_size, max_lines=num_selections, **embed_base) - - self.selections = list(zip(identifiers, functions, arguments)) - - self.max_selections = min(len(identifiers), num_selections) - for i, selection in enumerate(self.selections): - self.add_line(f"{i%num_selections+1}\N{combining enclosing keycap} {selection[0]}") - self.close_page() - - if len(self.pages) > 1: - self.multipage = True - else: - self.multipage = False - - assert num_selections <= 10 - self.select_emojis = [f"{i+1}\N{combining enclosing keycap}" for i in range(num_selections)] - - self.control_emojis = [ - ('\N{BLACK LEFT-POINTING TRIANGLE}', self.previous_page), - ('\N{BLACK RIGHT-POINTING TRIANGLE}', self.next_page), - ('❌', self.stop_scrolling), - ] - - self.timeout = 30 - self.args = None - self.control_input = False - self.scrolling = True - self.stopped = False - - self.add_page_indicator(self.localizer, "{queue.pageindicator}") - if self.round_titles: - self.update_embed_title(self.round_titles.pop(0)) - - async def scroll(self, page): - if page < 0 or page >= len(self.pages): - return - self.current_page = page - await self.message.edit(embed=self.pages[page]) - - async def next_page(self): - await self.scroll(self.current_page + 1) - - async def previous_page(self): - await self.scroll(self.current_page - 1) - - async def stop_scrolling(self): - self.scrolling = False - await self.message.clear_reactions() - - def update_embed_title(self, updated_title): - for page in self.pages: - page.title = updated_title - - def _react_check(self, reaction, user): - if user is None or user.id != self.author.id: - return False - - if reaction.message.id != self.message.id: - return False - - for i, emoji in enumerate(self.select_emojis): - if emoji == reaction.emoji: - choice = i + self.current_page * self.num_selections - if choice >= len(self.selections): - return True - self.match = self.selections[choice][1] - self.args = self.selections[choice][2] - return True - - for (emoji, func) in self.control_emojis: - if reaction.emoji == emoji: - self.match = func - self.control_input = True - return True - return False - - async def send(self): - # No embeds to scroll through - if not self.pages: - return - - self.message = await self.channel.send(embed=self.pages[0]) - - for reaction in self.select_emojis[:self.max_selections]: - if not self.stopped: - await self.message.add_reaction(reaction) - - if self.multipage: - for reaction in list(zip(*self.control_emojis))[0]: - if not self.stopped: - await self.message.add_reaction(reaction) - else: - if not self.stopped: - await self.message.add_reaction('❌') - - async def start_scrolling(self): - result = None - if not self.scrolling: - await self.send() - else: - self.bot.loop.create_task(self.send()) - - current_round = 0 - results = [] - while self.scrolling: - try: - reaction, user = await self.bot.wait_for('reaction_add', check=self._react_check, timeout=self.timeout) - except asyncio.TimeoutError: - self.scrolling = False - try: - await self.message.clear_reactions() - except (discord.Forbidden, discord.HTTPException): - pass - finally: - break - - try: - await self.message.remove_reaction(reaction, user) - except (discord.Forbidden, discord.HTTPException, discord.NotFound, discord.InvalidArgument): - pass - if self.match: - if inspect.iscoroutinefunction(self.match): - if self.args: - result = await self.match(*self.args) - else: - result = await self.match() - else: - if self.args: - result = self.match(*self.args) - else: - result = self.match() - else: - continue - - if self.terminate_on_select and not self.control_input: - current_round += 1 - results.append(result) - if not self.round_titles: - self.stopped = True - try: - await self.message.clear_reactions() - except (discord.Forbidden, discord.HTTPException): - pass - break - if self.round_titles: - self.update_embed_title(self.round_titles.pop(0)) - await self.scroll(self.current_page) - self.args = None - self.control_input = False - if len(results) == 1: - self.stopped = True - await self.message.clear_reactions() - return self.message, self.pages[self.current_page], results[0] - else: - self.stopped = True - await self.message.clear_reactions() - return self.message, self.pages[self.current_page], results diff --git a/setup.cfg b/setup.cfg index a14c0aa..e5640bc 100644 --- a/setup.cfg +++ b/setup.cfg @@ -5,7 +5,7 @@ statistics = True [isort] known_discord = discord,lavalink -known_utils = cogs.utils,cogs.helpformatter +known_utils = cogs.utils,cogs.helpformatter,musicbot.utils import_heading_discord = Discord Packages import_heading_utils = Bot Utilities sections = FUTURE,DISCORD,STDLIB,THIRDPARTY,FIRSTPARTY,UTILS,LOCALFOLDER