mirror of
https://github.com/UiO-Gaming/ShiteMusicBot.git
synced 2026-11-05 13:56:51 +01:00
Remove old selector implementation
This commit is contained in:
1 parent
8ccab9afea
commit
75c283e959
7 files changed
+145
-305
No files matched your search
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -1 +1 @@
|
||||
bot_version = "0.2.13"
|
||||
bot_version = "0.3.0"
|
||||
@@ -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]
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user