Reduce code duplication in music cog.

This commit is contained in:
Ev-1 committed 2022-10-19 23:36:06 +02:00
1 parent 71c45ff037
commit 2860ae0668
12 files changed
+177 -175

No files matched your search

+2 -2
View File
@@ -50,11 +50,11 @@ move:
description: 'Moves a track in your queue.'
remove:
aliases: [rem]
args: '<pos>'
args: '<member>'
description: 'Removes a song from your queue.'
DJremove:
aliases: [DJremove, djremove]
args: '<pos>'
args: '<member>'
description: 'Removes a song from the global queue.'
removeuser:
aliases: [removeuser]
+2 -2
View File
@@ -54,7 +54,7 @@ remove:
description: 'Fjerner en sang fra personlige køen din.'
DJremove:
aliases: [DJfjern, djfjern]
args: '<posisjon>'
args: '<servermedlem>'
description: 'Fjerner en sang fra køen.'
removeuser:
aliases: [fjernbruker]
@@ -229,4 +229,4 @@ cogmanager:
shutdown:
aliases: [shutdown]
description: 'Stopper båtten.'
+1 -1
View File
@@ -54,7 +54,7 @@ remove:
description: 'Wemoves a sonyg fwom youw queue.'
DJremove:
aliases: [DJwemove, djwemove]
args: '<pos>'
args: '<member>'
description: 'Wemoves a sonyg fwom the gwobaw queue.'
removeuser:
aliases: [wemoveusew]
+106 -152
View File
@@ -12,7 +12,7 @@ import asyncio
import json
import re
import urllib.parse as urlparse
from typing import List, Optional
from typing import List, Optional, Tuple
from bs4 import BeautifulSoup
@@ -66,20 +66,19 @@ class Music(commands.Cog):
raise PlayerNotAvailableError(f"Tried to get player for guild {guild.id} but got None")
return player
async def enqueue(self, ctx, track, embed, silent=False, check_max_length=True) -> tuple[discord.Embed, bool]:
async def enqueue(self, ctx, track: AudioTrack, embed, standalone: bool = False,
check_max_length: bool = True) -> bool:
player = self.get_player(ctx.guild)
track_duration_str = timeformatter.format_track_duration(track)
# Only add tracks that don't exceed the max track length
if maxlength := self.max_track_length(ctx.guild, player):
if track.duration > maxlength and check_max_length:
if track.stream:
length = '{live}'
else:
length = timeformatter.format_ms(track.duration)
embed.description = ctx.localizer.format_str("{enqueue.toolong}",
_length=length,
_max=timeformatter.format_ms(maxlength))
return embed, False
maxlength = self.max_track_length(ctx.guild, player)
if (track.stream or track.duration > maxlength) and check_max_length:
embed.description = ctx.localizer.format_str("{enqueue.toolong}",
_length=track_duration_str,
_max=timeformatter.format_ms(maxlength))
return False
track.extra["thumbnail_url"] = await self.thumbnailer.identify(track.identifier, track.uri)
track.requester = ctx.author.id
@@ -87,7 +86,7 @@ class Music(commands.Cog):
# Add to player
track, pos_global, pos_local = player.add(requester=ctx.author, track=track)
if player.current is not None and not silent:
if player.current is not None and not standalone:
if player.current.stream:
until_play = '--:--'
else:
@@ -101,13 +100,8 @@ class Music(commands.Cog):
if thumbnail_url := track.extra["thumbnail_url"]:
embed.set_thumbnail(url=thumbnail_url)
if track.stream:
duration = '{live}'
else:
duration = timeformatter.format_ms(int(track.duration))
embed.description = f'[{track.title}]({track.uri})\n**{duration}**'
return embed, True
embed.description = f'[{track.title}]({track.uri})\n**{track_duration_str}**'
return True
def get_current_song_embed(self, ctx, include_time=False):
player = self.get_player(ctx.guild)
@@ -118,10 +112,7 @@ class Music(commands.Cog):
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)
duration = timeformatter.format_track_duration(player.current)
song += f'\n({position}/{duration})'
embed = discord.Embed(color=ctx.me.color, description=song, title='{now}')
@@ -135,22 +126,20 @@ class Music(commands.Cog):
embed = ctx.localizer.format_embed(embed)
return embed
def max_track_length(self, guild, player):
if maxlength := self.bot.settings.get(guild, 'duration.max', None):
is_dynamic = self.bot.settings.get(guild, 'duration.is_dynamic', 'default_duration_type')
listeners = max(1, len(player.listeners)) # Avoid division by 0
def max_track_length(self, guild, player) -> float:
# Numeric constants
minimum_dynamic_length = 10
minutes_to_milliseconds = 60 * 1000
configured_max = self.bot.settings.get(guild, 'duration.max', float('inf'))
is_dynamic = self.bot.settings.get(guild, 'duration.is_dynamic', 'default_duration_type')
listeners = max(1, len(player.listeners)) # Avoid division by 0
if maxlength > 10 and is_dynamic:
return max(maxlength*60*1000/listeners, 60*10*1000)
else:
return maxlength*60*1000
if configured_max > minimum_dynamic_length and is_dynamic:
return minutes_to_milliseconds * max(configured_max/listeners, minimum_dynamic_length)
else:
return None
return minutes_to_milliseconds * configured_max
@commands.command(name='play')
@require_voice_connection(should_connect=True)
async def _play(self, ctx, *, query: str):
""" Searches and plays a song from a given query. """
async def _search_and_play_query(self, ctx, query: str, check_max_length: bool):
self.logger.debug("Query: %s" % query)
player = self.get_player(ctx.guild)
query = query.strip('<>')
@@ -158,7 +147,7 @@ class Music(commands.Cog):
if not url_rx.match(query):
query = f'ytsearch:{query}'
results = await player.node.get_tracks(query)
results: lavalink.LoadResult = await player.node.get_tracks(query)
if not results or not results.tracks:
return await ctx.send(ctx.localizer.format_str("{nothing_found}"))
@@ -166,17 +155,16 @@ class Music(commands.Cog):
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}}'
tracks = [track for track in results.tracks if
await self.enqueue(ctx, track, embed, True, check_max_length)]
if tracks:
embed.title = '{playlist_enqued}'
embed.description = f'{results.playlist_info.name} - {len(tracks)} {{tracks}}'
else:
# TODO: This really means no tracks passed the max_length requirement
return await ctx.send(ctx.localizer.format_str("{nothing_found}"))
else:
track = results.tracks[0]
await self.enqueue(ctx, track, embed)
await self.enqueue(ctx, results.tracks[0], embed, check_max_length=check_max_length)
embed = ctx.localizer.format_embed(embed)
await ctx.send(embed=embed)
@@ -184,6 +172,19 @@ class Music(commands.Cog):
if not player.is_playing:
await player.play()
@commands.command(name='play')
@require_voice_connection(should_connect=True)
async def _play(self, ctx, *, query: str):
""" Searches and plays a song from a given query. """
await self._search_and_play_query(ctx, query, check_max_length=True)
@commands.command(name='forceplay')
@require_voice_connection(should_connect=True)
@voteable(DJ_override=True, react_to_vote=True)
async def _forceplay(self, ctx, *, query: str):
""" Searches and plays a song from a given query, ignoring configured max duration. """
await self._search_and_play_query(ctx, query, check_max_length=False)
@commands.command(name='seek')
@checks.dj_or(alone=True, track_requester=True)
@require_voice_connection()
@@ -300,7 +301,7 @@ class Music(commands.Cog):
player = self.get_player(ctx.guild)
# We create a new selector for each selection
def move_selector(ctx, queue: List[AudioTrack], title: str, first: bool):
def build_move_selector(ctx, queue: List[AudioTrack], title: str, first: bool):
async def return_track(track):
return track
@@ -322,19 +323,19 @@ class Music(commands.Cog):
page = 0
while True:
# Prompt the user for which track to move
selector = move_selector(ctx, player.user_queue(ctx.author), "{moved.choose_pos}", True)
message, track_to_move = await selector.start_scrolling(ClearOn.Timeout, message, page)
selector = build_move_selector(ctx, player.user_queue(ctx.author), "{moved.choose_pos}", True)
message, timed_out, track_to_move = await selector.start_scrolling(ClearOn.Timeout, message, page)
page = selector.page_number
if not track_to_move:
if not track_to_move or timed_out:
break
# Prompt the user for where to move it
selector = move_selector(ctx, player.user_queue(ctx.author), "{moved.choose_song}", False)
message, track_to_replace = await selector.start_scrolling(ClearOn.Timeout, message, page)
selector = build_move_selector(ctx, player.user_queue(ctx.author), "{moved.choose_song}", False)
message, timed_out, track_to_replace = await selector.start_scrolling(ClearOn.Timeout, message, page)
page = selector.page_number
if not track_to_replace:
if not track_to_replace or timed_out:
break
# At this point the user has chosen two songs, we will move the first song to be in front of the second
@@ -354,17 +355,19 @@ class Music(commands.Cog):
if message:
await message.delete()
@commands.command(name='remove')
@require_voice_connection()
@require_queue(require_author_queue=True)
async def _remove(self, ctx):
async def _interactive_remove(self, ctx, queue: List[AudioTrack]):
"""
Helper function for creating an interactive selector over a given queue
in which will remove the selected tracks on exit.
"""
player = self.get_player(ctx.guild)
user_queue: List[AudioTrack] = player.user_queue(ctx.author)
tracks_to_remove = []
async def update_remove_list(tracks_list, track):
async def update_remove_list(tracks_list, track: AudioTrack):
# It seems duplicate songs still don't satisfy equality
# which meas remove is sufficient to preserve order
# of similar items
if track in tracks_list:
tracks_list.remove(track)
else:
@@ -383,65 +386,55 @@ class Music(commands.Cog):
return inner
selector_buttons = []
for index, track in enumerate(user_queue, start=1):
# Build each selection from the queue, a visible string and a callback.
for index, track in enumerate(queue, start=1):
requester = self.bot.get_user(track.requester)
selector_buttons.append(
SelectorItem(f'`{index}` [{track.title}]({track.uri})', str(index),
add_change_button_color(update_remove_list, tracks_to_remove, track)))
SelectorItem(f'`{index}` [{track.title}]({track.uri}) - {requester.mention if requester else ""}',
str(index), add_change_button_color(update_remove_list, tracks_to_remove, track)))
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)
_, timed_out, _ = await remove_selector.start_scrolling(ClearOn.AnyExit)
# If any tracks were removed create a scroller for navigating them
if tracks_to_remove:
if tracks_to_remove and not timed_out:
paginator = TextPaginator(color=ctx.me.color, title="Removed")
# If the user spent a long time selecting songs the queue might have changed
user_queue = player.user_queue(ctx.author)
track_indexes_to_remove = [i for i, track in enumerate(user_queue) if track in tracks_to_remove]
tracks_removed: List[Tuple[int, str]] = []
for track in tracks_to_remove:
if remove_result := player.remove_track(track):
(position, removed_track) = remove_result
if requester := self.bot.get_user(removed_track.requester):
tracks_removed.append((position, f"{removed_track.title} - {requester.mention}"))
# Sort in reverse order since songs shift index when deleting
tracks_removed = []
for track in sorted(track_indexes_to_remove, reverse=True):
if removed_track := player.remove_user_track(ctx.author, track):
tracks_removed.append(f"{removed_track.title}")
# We want to display the removed tracks in the original queue order, so reverse the list once again
for removed in reversed(tracks_removed):
paginator.add_line(removed)
# remove_track returns the global queue index of the track,
# so we display the removed tracks in "queue order"
for removed in sorted(tracks_removed, key=lambda x: x[0]):
paginator.add_line(removed[1])
paginator.close_page()
scroller = Scroller(ctx, paginator)
await scroller.start_scrolling(ClearOn.ManualExit | ClearOn.Timeout)
@commands.command(name='remove')
@require_voice_connection()
@require_queue(require_author_queue=True)
async def _remove(self, ctx):
""" Remove a song from your queue. """
player = self.get_player(ctx.guild)
return await self._interactive_remove(ctx, player.user_queue(ctx.author))
@commands.command(name="DJremove")
@checks.dj_or()
@require_voice_connection()
@require_queue()
async def _djremove(self, ctx, pos: int, member: Optional[discord.Member] = None):
async def _djremove(self, ctx, member: Optional[discord.Member] = None):
""" Remove a song from either the global queue or a users queue"""
player = self.get_player(ctx.guild)
# TODO: add way to allow "member" as regular arg in require_queue() instead of only kwargs
queue = player.user_queue(member) if member else player.queue
# TODO: Do all queue out of range messages the same way
if pos <= len(queue) and pos >= 1:
if member is None:
removed = player.remove_global_track(pos - 1)
else:
removed = player.remove_user_track(member, pos - 1)
if removed:
if requester := self.bot.get_user(removed.requester):
await ctx.send(ctx.localizer.format_str("{dj_removed}", _title=removed.title,
_user=requester.display_name))
else:
self.logger.error(f"Did not get a valid user for track requester {removed.requester}")
else:
self.logger.error(f"Did not remove a track when trying to remove index {pos}, despite bounds check")
else:
await ctx.send(ctx.localizer.format_str("{out_of_range}", _len=len(queue)))
queue = player.user_queue(member) if member else player.global_queue()
return await self._interactive_remove(ctx, queue)
@commands.command(name='removeuser')
@checks.dj_or(alone=True)
@@ -468,35 +461,32 @@ class Music(commands.Cog):
results = await player.node.get_tracks(query)
embed = discord.Embed(description='{nothing_found}', color=0x36393F)
if not results or not results['tracks']:
embed = discord.Embed(description='{nothing_found}', color=0x36393F)
embed = ctx.localizer.format_embed(embed)
return await ctx.send(embed=embed)
# This is the base embed that will be modified by the selector
embed = discord.Embed(color=ctx.me.color)
buttons = []
for i, track in enumerate(results['tracks'], start=1):
duration = timeformatter.format_ms(int(track.duration))
interaction = SelectorItem(f'`{i}` [{track.title}]({track.uri}) `{duration}`', str(i),
wrap_in_button_callback(
self.enqueue, ctx, track, discord.Embed(color=ctx.me.color)))
self.enqueue, ctx, track, embed))
buttons.append(interaction)
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()
message, timed_out, _ = await search_selector.start_scrolling(ClearOn.Timeout)
if len(callback_results) == 0:
if message:
await message.delete()
if timed_out:
return
song_embed, song_added = callback_results[0]
if song_added:
embed = ctx.localizer.format_embed(song_embed)
await message.edit(embed=embed, view=None)
await message.edit(embed=ctx.localizer.format_embed(embed), view=None)
if not player.is_playing:
await player.play()
if not player.is_playing:
await player.play()
@commands.command(name='disconnect')
@require_voice_connection()
@@ -520,7 +510,7 @@ class Music(commands.Cog):
player = self.get_player(ctx.guild)
current_channel = player.channel_id
async def recon():
async def inner_reconnect():
await player.stop()
if ctx.voice_client:
await ctx.voice_client.disconnect()
@@ -531,11 +521,11 @@ class Music(commands.Cog):
if player.current:
track = player.current
start_time = player.position
await recon()
await inner_reconnect()
if track:
await player.play(track, start_time=int(start_time))
else:
await recon()
await inner_reconnect()
@commands.command(name='volume')
@checks.dj_or(alone=True, track_requester=True)
@@ -558,43 +548,6 @@ class Music(commands.Cog):
embed = ctx.localizer.format_embed(embed, _volume=player.volume)
await ctx.send(embed=embed)
@commands.command(name='forceplay')
@require_voice_connection(should_connect=True)
@voteable(DJ_override=True, react_to_vote=True)
async def _forceplay(self, ctx, *, query: str):
""" Searches and plays a song from a given query. """
player = self.get_player(ctx.guild)
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, check_max_length=False)
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, check_max_length=False)
embed = ctx.localizer.format_embed(embed)
await ctx.send(embed=embed)
if not player.is_playing:
await player.play()
@commands.command(name='forcedisconnect')
@checks.dj_or(alone=True)
async def _forcedisconnect(self, ctx):
@@ -620,6 +573,7 @@ class Music(commands.Cog):
await player.set_volume(100)
await player.bassboost(False)
await player.nightcoreify(False)
embed = discord.Embed(description='{volume.reset}', color=ctx.me.color)
embed = ctx.localizer.format_embed(embed)
-1
View File
@@ -22,7 +22,6 @@ def require_voice_connection(should_connect=False):
@functools.wraps(func)
async def ensure_voice_inner(self, ctx, *command_args, **kwargs):
""" This check ensures that the bot and command author are in the same voicechannel. """
player: MixPlayer = self.bot.lavalink.player_manager.get(ctx.guild.id)
if not player:
raise music_errors.MusicError("ensure voice could not get lavalink player")
-1
View File
@@ -57,7 +57,6 @@ class NodeManager(commands.Cog):
for node in self.lavalink.node_manager.nodes:
name_cache.append(node.name)
self.logger.debug("BEFORE")
for node in conf['lavalink nodes'] + self.settings.get('lavalink', 'nodes', []):
if node['name'] in name_cache:
continue
+13
View File
@@ -153,6 +153,19 @@ class MixQueue(Generic[T]):
self._clear_empty()
return track
def remove_track(self, track: T) -> Optional[Tuple[int, T]]:
"""
Removes a track by identity
"""
for queue in self.queues.values():
for i, t in enumerate(queue):
if t is track:
global_pos = self._loc_to_glob(t.requester, i)
removed = queue.pop(i)
self._clear_empty()
return global_pos, removed
return None
def remove_global_track(self, pos: int) -> Optional[T]:
# Get the actual index of the song
index = list(self).index(self.get_queue()[pos])
+5 -2
View File
@@ -71,6 +71,9 @@ class MixPlayer(DefaultPlayer):
self.logger.info(f"Track {track.title} for requester {requester.display_name} removed.")
return track
def remove_track(self, track: AudioTrack) -> Optional[Tuple[int, AudioTrack]]:
return self.queue.remove_track(track)
def remove_global_track(self, pos: int) -> Optional[AudioTrack]:
""" Removes the song at <pos> in the global queue """
return self.queue.remove_global_track(pos)
@@ -213,7 +216,7 @@ class MixPlayer(DefaultPlayer):
votes.clear()
await self.play()
async def bassboost(self, boost: bool = False):
async def bassboost(self, boost: bool):
self.boosted = boost
if boost:
await self.set_filter(self.bass_boost_filter)
@@ -221,7 +224,7 @@ class MixPlayer(DefaultPlayer):
self.logger.info("Disabling bass boost")
await self.remove_filter(self.bass_boost_filter)
async def nightcoreify(self, nightcore: bool = False):
async def nightcoreify(self, nightcore: bool):
self.nightcore_enabled = nightcore
if nightcore:
self.logger.info("Enabling nightcore mode")
+25
View File
@@ -121,3 +121,28 @@ class TestMixQueue():
assert item == queue.pop_first()
assert list(queue) == []
def test_remove_by_identity(self):
queue = MixQueue()
queue.add_track(1, TrackMock(1, "a"))
queue.add_track(1, TrackMock(1, "a"))
queue.add_track(1, TrackMock(1, "b"))
queue.add_track(1, TrackMock(1, "a"))
queue.add_track(1, TrackMock(1, "b"))
queue.add_track(1, TrackMock(1, "a"))
queue.add_track(1, TrackMock(1, "b"))
queue.add_track(1, TrackMock(1, "b"))
assert queue.remove_track(TrackMock(1, "a")) is None
to_remove = queue.get_user_queue(1)[3]
first_remove = queue.remove_track(to_remove)
assert first_remove is not None
pos, removed = first_remove
assert to_remove is removed
assert pos == 3
assert queue.remove_track(to_remove) is None
queue_after_id_remove = self.list_to_requests(["1a", "1a", "1b", "1b", "1a", "1b", "1b"])
for item in queue_after_id_remove:
assert item == queue.pop_first()
+8
View File
@@ -1,3 +1,7 @@
# Discord Packages
import lavalink
def format_ms(milliseconds):
seconds = int(milliseconds)/1000
formatted = ''
@@ -17,3 +21,7 @@ def unformat_ms(formatted):
if len(hms) == 3:
seconds += hms[2] * 3600
return seconds * 1000
def format_track_duration(track: lavalink.AudioTrack) -> str:
return '{live}' if track.stream else format_ms(track.duration)
+11 -10
View File
@@ -5,7 +5,7 @@ import discord
import asyncio
from enum import Flag, auto
from typing import List, Optional
from typing import List, Optional, Tuple
from bot import MusicBot
@@ -66,7 +66,7 @@ class Scroller:
'\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)
self.stop_button = ScrollerButton(self.stop_scrolling, stop_emoji, row=3)
# We start on the first page, can't go back
self.back_button.disabled = True
@@ -92,9 +92,9 @@ class Scroller:
async def start_scrolling(self, clear_mode: ClearOn,
message: Optional[discord.Message] = None,
start_page: int = 0) -> discord.Message:
start_page: int = 0) -> Tuple[discord.Message, bool]:
self.clear_mode = clear_mode
self.page_number = start_page
self.page_number = min(start_page, len(self.paginator.pages))
self.build_view()
self.update_view()
if message:
@@ -103,7 +103,7 @@ class Scroller:
else:
self.message = await self.channel.send(embed=self.current_page_embed, view=self.view)
await self.scrolling_done.wait()
return self.message
return self.message, self.timed_out
def update_view_on_interaction(self, _: discord.Interaction):
self.update_view()
@@ -126,13 +126,14 @@ 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, clear_scroller_view: bool = True):
async def stop(self, was_timeout: bool, clear_scroller_view: bool = True):
self.is_scrolling_paginator = False
self.view.stop()
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):
self.timed_out = was_timeout
if (not was_timeout and self.clear_mode & ClearOn.ManualExit or
was_timeout and self.clear_mode & ClearOn.Timeout):
if self.message:
await self.message.delete()
self.message = None
@@ -185,10 +186,10 @@ class Scroller:
async def stop_scrolling(self, interaction: discord.Interaction):
await interaction.response.defer()
await self.stop(user_stopped=True)
await self.stop(was_timeout=False)
async def on_timeout(self):
await self.stop(user_stopped=False)
await self.stop(was_timeout=True)
@property
def current_page_embed(self):
+4 -4
View File
@@ -84,9 +84,9 @@ class Selector(TextPaginator, Scroller):
match self.selector_mode:
case SelectMode.SingleSelect:
await self.stop(True, clear_scroller_view=True)
await self.stop(was_timeout=False, clear_scroller_view=True)
case SelectMode.SpanningMultiSelect:
await self.stop(True, clear_scroller_view=False)
await self.stop(was_timeout=False, clear_scroller_view=False)
case _:
await self.update_message()
@@ -119,5 +119,5 @@ class Selector(TextPaginator, Scroller):
async def start_scrolling(self, clear_mode: ClearOn = ClearOn.Timeout,
message: Optional[discord.Message] = None,
start_page: int = 0):
message = await super().start_scrolling(clear_mode, message, start_page)
return message, self.callback_results
message, timed_out = await super().start_scrolling(clear_mode, message, start_page)
return message, timed_out, self.callback_results