diff --git a/changelog.d/audio/3286.bugfix.1.rst b/changelog.d/audio/3286.bugfix.1.rst new file mode 100644 index 00000000000..8150f98b0b6 --- /dev/null +++ b/changelog.d/audio/3286.bugfix.1.rst @@ -0,0 +1 @@ +Add more cooldown to playlist commands and restrict queue and playlist to 10k songs to avoid DOS attacks(User crashing your bot on purpose). \ No newline at end of file diff --git a/redbot/cogs/audio/apis.py b/redbot/cogs/audio/apis.py index d15fb13c757..eb6dbd7c03d 100644 --- a/redbot/cogs/audio/apis.py +++ b/redbot/cogs/audio/apis.py @@ -318,6 +318,7 @@ async def _spotify_first_time_query( youtube_urls.append(val) else: youtube_urls.append(track_info) + await asyncio.sleep(0) track_count += 1 if notifier and ((track_count % 2 == 0) or (track_count == total_tracks)): await notifier.notify_user(current=track_count, total=total_tracks, key="youtube") @@ -615,6 +616,8 @@ async def spotify_enqueue( continue track_list.append(single_track) if enqueue: + if len(player.queue) >= 10000: + continue if guild_data["maxlength"] > 0: if track_limit(single_track, guild_data["maxlength"]): enqueued_tracks += 1 diff --git a/redbot/cogs/audio/audio.py b/redbot/cogs/audio/audio.py index df7f0376d0a..f29da5c6d09 100644 --- a/redbot/cogs/audio/audio.py +++ b/redbot/cogs/audio/audio.py @@ -94,6 +94,7 @@ def __init__(self, bot): self.config: Config = Config.get_conf(self, 2711759130, force_registration=True) self.skip_votes: MutableMapping[discord.Guild, List[discord.Member]] = {} self.play_lock: MutableMapping[int, bool] = {} + self._daily_playlist_cache: MutableMapping[int, bool] = {} self._dj_status_cache: MutableMapping[int, Optional[bool]] = {} self._dj_role_cache: MutableMapping[int, Optional[int]] = {} self.session: aiohttp.ClientSession = aiohttp.ClientSession() @@ -121,6 +122,7 @@ def __init__(self, bot): disconnect=False, dj_enabled=False, dj_role=None, + daily_playlists=False, emptydc_enabled=False, emptydc_timer=0, emptypause_enabled=False, @@ -183,6 +185,9 @@ async def cog_before_invoke(self, ctx: commands.Context): dj_enabled = self._dj_status_cache.setdefault( ctx.guild.id, await self.config.guild(ctx.guild).dj_enabled() ) + daily_cache = self._daily_playlist_cache.setdefault( + ctx.guild.id, await self.config.guild(ctx.guild).daily_playlists() + ) if dj_enabled: dj_role = self._dj_role_cache.setdefault( ctx.guild.id, await self.config.guild(ctx.guild).dj_role() @@ -249,8 +254,10 @@ async def _migrate_config(self, from_version: int, to_version: int) -> None: "last_fetched": time_now, } ) + await asyncio.sleep(0) if guild_playlist: all_playlist[str(guild_id)] = guild_playlist + await asyncio.sleep(0) await self.config.custom(PlaylistScope.GUILD.value).set(all_playlist) # new schema is now in place await self.config.schema_version.set(_SCHEMA_VERSION) @@ -658,6 +665,26 @@ async def play_query( async def audioset(self, ctx: commands.Context): """Music configuration options.""" + @audioset.command(name="dailyqueue") + @checks.admin() + async def _audioset_historical_queue(self, ctx: commands.Context): + """Toggle daily queues. + + Daily queues creates a playlist for all tracks played today. + """ + daily_playlists = self._daily_playlist_cache.setdefault( + ctx.guild.id, await self.config.guild(ctx.guild).daily_playlists() + ) + await self.config.guild(ctx.guild).daily_playlists.set(not daily_playlists) + self._daily_playlist_cache[ctx.guild.id] = not daily_playlists + await self._embed_msg( + ctx, + title=_("Setting Changed"), + description=_("Daily queues: {true_or_false}.").format( + true_or_false=_("Enabled") if not daily_playlists else _("Disabled") + ), + ) + @audioset.command() @checks.mod_or_permissions(manage_messages=True) async def dc(self, ctx: commands.Context): @@ -2493,6 +2520,7 @@ async def _build_local_search_list(to_search, search_words): search_list.extend( [i.track.to_string_user() for i in to_search if i.track.name == track_match] ) + await asyncio.sleep(0) return search_list @commands.command() @@ -2658,6 +2686,7 @@ async def _usercount(req_username): for track in queue_tracks: req_username = "{}#{}".format(track.requester.name, track.requester.discriminator) await _usercount(req_username) + await asyncio.sleep(0) try: req_username = "{}#{}".format( @@ -2672,6 +2701,7 @@ async def _usercount(req_username): requesters["total"] ) requesters["users"][req_username]["percent"] = round(percentage * 100, 1) + await asyncio.sleep(0) top_queue_users = heapq.nlargest( 20, @@ -2771,6 +2801,11 @@ async def play(self, ctx: commands.Context, *, query: str): query=query.to_string_user() ), ) + if len(player.queue) >= 10000: + return await self._embed_msg( + ctx, title=_("Unable To Play Tracks"), description=_("Queue size limit reached.") + ) + if not await self._currency_check(ctx, guild_data["jukebox_price"]): return if query.is_spotify: @@ -2872,6 +2907,11 @@ async def bumpplay( query=query.to_string_user() ), ) + if len(player.queue) >= 10000: + return await self._embed_msg( + ctx, title=_("Unable To Play Tracks"), description=_("Queue size limit reached.") + ) + if not await self._currency_check(ctx, guild_data["jukebox_price"]): return try: @@ -3127,6 +3167,7 @@ async def _playlist_search_menu( ctx, category_list, page_num, _("Categories") ) category_search_page_list.append(embed) + await asyncio.sleep(0) cat_menu_output = await menu(ctx, category_search_page_list, category_search_controls) if not cat_menu_output: return await self._embed_msg(ctx, title=_("No categories selected, try again later.")) @@ -3147,10 +3188,15 @@ async def _playlist_search_menu( playlist=True, ) playlists_search_page_list.append(embed) + await asyncio.sleep(0) playlists_pick = await menu(ctx, playlists_search_page_list, playlist_search_controls) query = audio_dataclasses.Query.process_input(playlists_pick) if not query.valid: return await self._embed_msg(ctx, title=_("No tracks to play.")) + if len(player.queue) >= 10000: + return await self._embed_msg( + ctx, title=_("Unable To Play Tracks"), description=_("Queue size limit reached.") + ) if not await self._currency_check(ctx, guild_data["jukebox_price"]): return if query.is_spotify: @@ -3206,6 +3252,7 @@ async def _build_genre_search_page( else: name = f"{list(entry.keys())[0]}" search_list += "`{}.` {}\n".format(search_track_num, name) + await asyncio.sleep(0) embed = discord.Embed( colour=await ctx.embed_colour(), title=title, description=search_list @@ -3278,9 +3325,12 @@ async def autoplay(self, ctx: commands.Context): title=_("Unable To Play Tracks"), description=_("You must be in the voice channel to use the autoplay command."), ) + if len(player.queue) >= 10000: + return await self._embed_msg( + ctx, title=_("Unable To Play Tracks"), description=_("Queue size limit reached.") + ) if not await self._currency_check(ctx, guild_data["jukebox_price"]): return - try: await self.music_cache.autoplay(player) except DatabaseError: @@ -3436,6 +3486,7 @@ async def _enqueue_tracks( self._play_lock(ctx, True) guild_data = await self.config.guild(ctx.guild).all() first_track_only = False + single_track = None index = None playlist_data = None playlist_url = None @@ -3500,9 +3551,13 @@ async def _enqueue_tracks( # this is a Spotify playlist already made into a list of Tracks or a # url where Lavalink handles providing all Track objects to use, like a # YouTube or Soundcloud playlist + if len(player.queue) >= 10000: + return await self._embed_msg(ctx, title=_("Queue size limit reached.")) track_len = 0 empty_queue = not player.queue for track in tracks: + if len(player.queue) >= 10000: + continue if not await is_allowed( ctx.guild, ( @@ -3526,6 +3581,7 @@ async def _enqueue_tracks( self.bot.dispatch( "red_audio_track_enqueue", player.channel.guild, track, ctx.author ) + await asyncio.sleep(0) player.maybe_shuffle(0 if empty_queue else 1) if len(tracks) > track_len: @@ -3563,6 +3619,10 @@ async def _enqueue_tracks( # this is in the case of [p]play , a single Spotify url/code # or this is a localtrack item try: + if len(player.queue) >= 10000: + + return await self._embed_msg(ctx, title=_("Queue size limit reached.")) + single_track = ( tracks if isinstance(tracks, lavalink.rest_api.Track) @@ -4013,8 +4073,14 @@ async def _playlist_append( ctx, title=_("Could not find a track matching your query.") ) track_list = playlist.tracks - tracks_obj_list = playlist.tracks_obj + current_count = len(track_list) to_append_count = len(to_append) + tracks_obj_list = playlist.tracks_obj + not_added = 0 + if current_count + to_append_count > 10000: + to_append = to_append[: 10000 - current_count] + not_added = to_append_count - len(to_append) + to_append_count = len(to_append) scope_name = humanize_scope( scope, ctx=guild if scope == PlaylistScope.GUILD.value else author ) @@ -4031,6 +4097,9 @@ async def _playlist_append( ).format( track=to.title, playlist=playlist.name, id=playlist.id, scope=scope_name ), + footer=_("Playlist limit reached: Could not add track.").format(not_added) + if not_added > 0 + else None, ) else: appended += 1 @@ -4067,8 +4136,15 @@ async def _playlist_append( ) embed = discord.Embed(title=_("Playlist Modified"), description=desc) - await self._embed_msg(ctx, embed=embed) + await self._embed_msg( + ctx, + embed=embed, + footer=_("Playlist limit reached: Could not add track.").format(not_added) + if not_added > 0 + else None, + ) + @commands.cooldown(1, 300, commands.BucketType.member) @playlist.command(name="copy", usage=" [args]") async def _playlist_copy( self, @@ -4142,9 +4218,11 @@ async def _playlist_copy( ctx, playlist_matches, from_scope, from_author, from_guild, specified_from_user ) except TooManyMatches as e: + ctx.command.reset_cooldown(ctx) return await self._embed_msg(ctx, title=str(e)) if playlist_id is None: + ctx.command.reset_cooldown(ctx) return await self._embed_msg( ctx, title=_("Playlist Not Found"), @@ -4153,6 +4231,7 @@ async def _playlist_copy( temp_playlist = FakePlaylist(to_author.id, to_scope) if not await self.can_manage_playlist(to_scope, temp_playlist, ctx, to_author, to_guild): + ctx.command.reset_cooldown(ctx) return try: @@ -4160,6 +4239,7 @@ async def _playlist_copy( playlist_id, from_scope, self.bot, from_guild, from_author.id ) except RuntimeError: + ctx.command.reset_cooldown(ctx) return await self._embed_msg( ctx, title=_("Playlist Not Found"), @@ -4168,6 +4248,7 @@ async def _playlist_copy( ), ) except MissingGuild: + ctx.command.reset_cooldown(ctx) return await self._embed_msg( ctx, title=_("You need to specify the Guild ID for the guild to lookup.") ) @@ -4356,6 +4437,7 @@ async def _playlist_delete( ), ) + @commands.cooldown(1, 30, commands.BucketType.member) @playlist.command(name="dedupe", usage=" [args]") async def _playlist_remdupe( self, @@ -4407,8 +4489,10 @@ async def _playlist_remdupe( ctx, playlist_matches, scope, author, guild, specified_user ) except TooManyMatches as e: + ctx.command.reset_cooldown(ctx) return await self._embed_msg(ctx, title=str(e)) if playlist_id is None: + ctx.command.reset_cooldown(ctx) return await self._embed_msg( ctx, title=_("Playlist Not Found"), @@ -4420,6 +4504,7 @@ async def _playlist_remdupe( try: playlist = await get_playlist(playlist_id, scope, self.bot, guild, author) except RuntimeError: + ctx.command.reset_cooldown(ctx) return await self._embed_msg( ctx, title=_("Playlist Not Found"), @@ -4428,12 +4513,14 @@ async def _playlist_remdupe( ), ) except MissingGuild: + ctx.command.reset_cooldown(ctx) return await self._embed_msg( ctx, title=_("Missing Arguments"), description=_("You need to specify the Guild ID for the guild to lookup."), ) if not await self.can_manage_playlist(scope, playlist, ctx, author, guild): + ctx.command.reset_cooldown(ctx) return track_objects = playlist.tracks_obj @@ -4538,8 +4625,10 @@ async def _playlist_download( ctx, playlist_matches, scope, author, guild, specified_user ) except TooManyMatches as e: + ctx.command.reset_cooldown(ctx) return await self._embed_msg(ctx, title=str(e)) if playlist_id is None: + ctx.command.reset_cooldown(ctx) return await self._embed_msg( ctx, title=_("Playlist Not Found"), @@ -4549,6 +4638,7 @@ async def _playlist_download( try: playlist = await get_playlist(playlist_id, scope, self.bot, guild, author) except RuntimeError: + ctx.command.reset_cooldown(ctx) return await self._embed_msg( ctx, title=_("Playlist Not Found"), @@ -4557,6 +4647,7 @@ async def _playlist_download( ), ) except MissingGuild: + ctx.command.reset_cooldown(ctx) return await self._embed_msg( ctx, title=_("Missing Arguments"), @@ -4567,6 +4658,7 @@ async def _playlist_download( version = "v3" if v2 is False else "v2" if not playlist.tracks: + ctx.command.reset_cooldown(ctx) return await self._embed_msg(ctx, title=_("That playlist has no tracks.")) if version == "v2": v2_valid_urls = ["https://www.youtube.com/watch?v=", "https://soundcloud.com/"] @@ -4574,6 +4666,7 @@ async def _playlist_download( for track in playlist.tracks: if track["info"]["uri"].startswith(tuple(v2_valid_urls)): song_list.append(track["info"]["uri"]) + await asyncio.sleep(0) playlist_data = { "author": playlist.author, "link": playlist.url, @@ -4622,6 +4715,7 @@ async def _playlist_download( await ctx.send(file=discord.File(to_write, filename=f"{file_name}.txt")) to_write.close() + @commands.cooldown(1, 20, commands.BucketType.member) @playlist.command(name="info", usage=" [args]") async def _playlist_info( self, @@ -4672,8 +4766,10 @@ async def _playlist_info( ctx, playlist_matches, scope, author, guild, specified_user ) except TooManyMatches as e: + ctx.command.reset_cooldown(ctx) return await self._embed_msg(ctx, title=str(e)) if playlist_id is None: + ctx.command.reset_cooldown(ctx) return await self._embed_msg( ctx, title=_("Playlist Not Found"), @@ -4683,6 +4779,7 @@ async def _playlist_info( try: playlist = await get_playlist(playlist_id, scope, self.bot, guild, author) except RuntimeError: + ctx.command.reset_cooldown(ctx) return await self._embed_msg( ctx, title=_("Playlist Not Found"), @@ -4691,6 +4788,7 @@ async def _playlist_info( ), ) except MissingGuild: + ctx.command.reset_cooldown(ctx) return await self._embed_msg( ctx, title=_("Missing Arguments"), @@ -4722,6 +4820,7 @@ async def _playlist_info( msg += "`{}.` **[{}]({})**\n".format( track_idx, track["info"]["title"], track["info"]["uri"] ) + await asyncio.sleep(0) else: msg = "No tracks." @@ -4753,6 +4852,7 @@ async def _playlist_info( page_list.append(embed) await menu(ctx, page_list, DEFAULT_CONTROLS) + @commands.cooldown(1, 30, commands.BucketType.guild) @playlist.command(name="list", usage="[args]") @commands.bot_has_permissions(add_reactions=True) async def _playlist_list(self, ctx: commands.Context, *, scope_data: ScopeParser = None): @@ -4793,6 +4893,7 @@ async def _playlist_list(self, ctx: commands.Context, *, scope_data: ScopeParser try: playlists = await get_all_playlist(scope, self.bot, guild, author, specified_user) except MissingGuild: + ctx.command.reset_cooldown(ctx) return await self._embed_msg( ctx, title=_("Missing Arguments"), @@ -4807,6 +4908,7 @@ async def _playlist_list(self, ctx: commands.Context, *, scope_data: ScopeParser name = "Global" if not playlists and specified_user: + ctx.command.reset_cooldown(ctx) return await self._embed_msg( ctx, title=_("Playlist Not Found"), @@ -4815,6 +4917,7 @@ async def _playlist_list(self, ctx: commands.Context, *, scope_data: ScopeParser ), ) elif not playlists: + ctx.command.reset_cooldown(ctx) return await self._embed_msg( ctx, title=_("Playlist Not Found"), @@ -4838,6 +4941,7 @@ async def _playlist_list(self, ctx: commands.Context, *, scope_data: ScopeParser ) ) ) + await asyncio.sleep(0) abc_names = sorted(playlist_list, key=str.lower) len_playlist_list_pages = math.ceil(len(abc_names) / 5) playlist_embeds = [] @@ -4845,6 +4949,7 @@ async def _playlist_list(self, ctx: commands.Context, *, scope_data: ScopeParser for page_num in range(1, len_playlist_list_pages + 1): embed = await self._build_playlist_list_page(ctx, page_num, abc_names, name) playlist_embeds.append(embed) + await asyncio.sleep(0) await menu(ctx, playlist_embeds, DEFAULT_CONTROLS) @staticmethod @@ -4858,6 +4963,7 @@ async def _build_playlist_list_page(ctx: commands.Context, page_num, abc_names, ): item_idx = i + 1 plist += "`{}.` {}".format(item_idx, playlist_info) + await asyncio.sleep(0) embed = discord.Embed( colour=await ctx.embed_colour(), title=_("Playlists for {scope}:").format(scope=scope), @@ -4871,7 +4977,7 @@ async def _build_playlist_list_page(ctx: commands.Context, page_num, abc_names, return embed @playlist.command(name="queue", usage=" [args]") - @commands.cooldown(1, 15, commands.BucketType.guild) + @commands.cooldown(1, 600, commands.BucketType.member) async def _playlist_queue( self, ctx: commands.Context, playlist_name: str, *, scope_data: ScopeParser = None ): @@ -4938,16 +5044,23 @@ async def _playlist_queue( tracklist = [] np_song = track_creator(player, "np") tracklist.append(np_song) - for i, track in enumerate(player.queue, start=1): + queue_length = len(player.queue) + to_add = player.queue + not_added = 0 + if queue_length > 10000: + to_add = player.queue[:10000] + not_added = queue_length - 10000 + + for i, track in enumerate(to_add, start=1): if i % 500 == 0: # TODO: Improve when Toby menu's are merged await asyncio.sleep(0.02) queue_idx = player.queue.index(track) track_obj = track_creator(player, queue_idx) tracklist.append(track_obj) - - playlist = await create_playlist( - ctx, scope, playlist_name, None, tracklist, author, guild - ) + playlist = await create_playlist( + ctx, scope, playlist_name, None, tracklist, author, guild + ) + await asyncio.sleep(0) await self._embed_msg( ctx, title=_("Playlist Created"), @@ -4957,6 +5070,9 @@ async def _playlist_queue( ).format( name=playlist.name, num=len(playlist.tracks), id=playlist.id, scope=scope_name ), + footer=_("Playlist limit reached: Could not add {} tracks.").format(not_added) + if not_added > 0 + else None, ) @playlist.command(name="remove", usage=" [args]") @@ -5073,7 +5189,7 @@ async def _playlist_remove( ) @playlist.command(name="save", usage=" [args]") - @commands.cooldown(1, 15, commands.BucketType.guild) + @commands.cooldown(1, 120, commands.BucketType.member) async def _playlist_save( self, ctx: commands.Context, @@ -5146,6 +5262,12 @@ async def _playlist_save( if isinstance(tracklist, discord.Message): return None if tracklist is not None: + playlist_length = len(tracklist) + not_added = 0 + if playlist_length > 10000: + tracklist = tracklist[:10000] + not_added = playlist_length - 10000 + playlist = await create_playlist( ctx, scope, playlist_name, playlist_url, tracklist, author, guild ) @@ -5155,8 +5277,12 @@ async def _playlist_save( description=_( "Playlist {name} (`{id}`) [**{scope}**] saved: {num} tracks added." ).format(name=playlist.name, num=len(tracklist), id=playlist.id, scope=scope_name), + footer=_("Playlist limit reached: Could not add {} tracks.").format(not_added) + if not_added > 0 + else None, ) + @commands.cooldown(1, 60, commands.BucketType.member) @playlist.command(name="start", aliases=["play"], usage=" [args]") async def _playlist_start( self, @@ -5203,6 +5329,7 @@ async def _playlist_start( ) if dj_enabled: if not await self._can_instaskip(ctx, ctx.author): + ctx.command.reset_cooldown(ctx) await self._embed_msg( ctx, title=_("Unable To Play Tracks"), @@ -5215,8 +5342,10 @@ async def _playlist_start( ctx, playlist_matches, scope, author, guild, specified_user ) except TooManyMatches as e: + ctx.command.reset_cooldown(ctx) return await self._embed_msg(ctx, title=str(e)) if playlist_id is None: + ctx.command.reset_cooldown(ctx) return await self._embed_msg( ctx, title=_("Playlist Not Found"), @@ -5224,9 +5353,11 @@ async def _playlist_start( ) if not await self._playlist_check(ctx): + ctx.command.reset_cooldown(ctx) return jukebox_price = await self.config.guild(ctx.guild).jukebox_price() if not await self._currency_check(ctx, jukebox_price): + ctx.command.reset_cooldown(ctx) return maxlength = await self.config.guild(ctx.guild).maxlength() author_obj = self.bot.get_user(ctx.author.id) @@ -5240,6 +5371,8 @@ async def _playlist_start( for i, track in enumerate(tracks, start=1): if i % 500 == 0: # TODO: Improve when Toby menu's are merged await asyncio.sleep(0.02) + if len(player.queue) >= 10000: + continue if not await is_allowed( ctx.guild, ( @@ -5265,6 +5398,7 @@ async def _playlist_start( "red_audio_track_enqueue", player.channel.guild, track, ctx.author ) track_len += 1 + await asyncio.sleep(0) player.maybe_shuffle(0 if empty_queue else 1) if len(tracks) > track_len: maxlength_msg = " {bad_tracks} tracks cannot be queued.".format( @@ -5297,6 +5431,7 @@ async def _playlist_start( await player.play() return except RuntimeError: + ctx.command.reset_cooldown(ctx) return await self._embed_msg( ctx, title=_("Playlist Not Found"), @@ -5305,6 +5440,7 @@ async def _playlist_start( ), ) except MissingGuild: + ctx.command.reset_cooldown(ctx) return await self._embed_msg( ctx, title=_("Missing Arguments"), @@ -5314,6 +5450,7 @@ async def _playlist_start( if playlist: return await ctx.invoke(self.play, query=playlist.url) + @commands.cooldown(1, 60, commands.BucketType.member) @playlist.command(name="update", usage=" [args]") async def _playlist_update( self, @@ -5361,9 +5498,11 @@ async def _playlist_update( ctx, playlist_matches, scope, author, guild, specified_user ) except TooManyMatches as e: + ctx.command.reset_cooldown(ctx) return await self._embed_msg(ctx, title=str(e)) if playlist_id is None: + ctx.command.reset_cooldown(ctx) return await self._embed_msg( ctx, title=_("Playlist Not Found"), @@ -5371,6 +5510,7 @@ async def _playlist_update( ) if not await self._playlist_check(ctx): + ctx.command.reset_cooldown(ctx) return try: playlist = await get_playlist(playlist_id, scope, self.bot, guild, author) @@ -5380,12 +5520,14 @@ async def _playlist_update( player = lavalink.get_player(ctx.guild.id) added, removed, playlist = await self._maybe_update_playlist(ctx, player, playlist) else: + ctx.command.reset_cooldown(ctx) return await self._embed_msg( ctx, title=_("Invalid Playlist"), description=_("Custom playlists cannot be updated."), ) except RuntimeError: + ctx.command.reset_cooldown(ctx) return await self._embed_msg( ctx, title=_("Playlist Not Found"), @@ -5544,7 +5686,8 @@ async def _playlist_upload(self, ctx: commands.Context, *, scope_data: ScopePars else: uploaded_playlist_url = uploaded_playlist.get("link", None) track_list = uploaded_playlist.get("playlist", []) - + if len(track_list) > 10000: + return await self._embed_msg(ctx, title=_("This playlist is too large.")) uploaded_playlist_name = uploaded_playlist.get( "name", (file_url.split("/")[6]).split(".")[0] ) @@ -5584,6 +5727,7 @@ async def _playlist_upload(self, ctx: commands.Context, *, scope_data: ScopePars scope_data=(scope, author, guild, specified_user), ) + @commands.cooldown(1, 60, commands.BucketType.member) @playlist.command(name="rename", usage=" [args]") async def _playlist_rename( self, @@ -5629,6 +5773,7 @@ async def _playlist_rename( new_name = new_name.split(" ")[0].strip('"')[:32] if new_name.isnumeric(): + ctx.command.reset_cooldown(ctx) return await self._embed_msg( ctx, title=_("Invalid Playlist Name"), @@ -5643,8 +5788,10 @@ async def _playlist_rename( ctx, playlist_matches, scope, author, guild, specified_user ) except TooManyMatches as e: + ctx.command.reset_cooldown(ctx) return await self._embed_msg(ctx, title=str(e)) if playlist_id is None: + ctx.command.reset_cooldown(ctx) return await self._embed_msg( ctx, title=_("Playlist Not Found"), @@ -5654,6 +5801,7 @@ async def _playlist_rename( try: playlist = await get_playlist(playlist_id, scope, self.bot, guild, author) except RuntimeError: + ctx.command.reset_cooldown(ctx) return await self._embed_msg( ctx, title=_("Playlist Not Found"), @@ -5662,6 +5810,7 @@ async def _playlist_rename( ), ) except MissingGuild: + ctx.command.reset_cooldown(ctx) return await self._embed_msg( ctx, title=_("Missing Arguments"), @@ -5669,6 +5818,7 @@ async def _playlist_rename( ) if not await self.can_manage_playlist(scope, playlist, ctx, author, guild): + ctx.command.reset_cooldown(ctx) return scope_name = humanize_scope( scope, ctx=guild if scope == PlaylistScope.GUILD.value else author @@ -5940,6 +6090,7 @@ async def _playlist_tracks( for track in tracks: track_obj = track_creator(player, other_track=track) tracklist.append(track_obj) + await asyncio.sleep(0) self._play_lock(ctx, False) elif query.is_search: try: @@ -5988,6 +6139,7 @@ async def _playlist_tracks( for track in tracks: track_obj = track_creator(player, other_track=track) tracklist.append(track_obj) + await asyncio.sleep(0) elif len(tracklist) == 0: track_obj = track_creator(player, other_track=tracks[0]) tracklist.append(track_obj) @@ -6080,83 +6232,84 @@ async def _queue_menu( if not self._player_check(ctx): return await self._embed_msg(ctx, title=_("There's nothing in the queue.")) player = lavalink.get_player(ctx.guild.id) - if not player.queue: - if player.current: - arrow = await draw_time(ctx) - pos = lavalink.utils.format_time(player.position) - if player.current.is_stream: - dur = "LIVE" - else: - dur = lavalink.utils.format_time(player.current.length) - song = get_track_description(player.current) - song += _("\n Requested by: **{track.requester}**") - song += "\n\n{arrow}`{pos}`/`{dur}`" - song = song.format(track=player.current, arrow=arrow, pos=pos, dur=dur) - embed = discord.Embed(title=_("Now Playing"), description=song) - if await self.config.guild(ctx.guild).thumbnail() and player.current: - if player.current.thumbnail: - embed.set_thumbnail(url=player.current.thumbnail) - - shuffle = await self.config.guild(ctx.guild).shuffle() - repeat = await self.config.guild(ctx.guild).repeat() - autoplay = await self.config.guild(ctx.guild).auto_play() - text = "" - text += ( - _("Auto-Play") - + ": " - + ("\N{WHITE HEAVY CHECK MARK}" if autoplay else "\N{CROSS MARK}") - ) - text += ( - (" | " if text else "") - + _("Shuffle") - + ": " - + ("\N{WHITE HEAVY CHECK MARK}" if shuffle else "\N{CROSS MARK}") - ) - text += ( - (" | " if text else "") - + _("Repeat") - + ": " - + ("\N{WHITE HEAVY CHECK MARK}" if repeat else "\N{CROSS MARK}") - ) - embed.set_footer(text=text) - message = await self._embed_msg(ctx, embed=embed) - dj_enabled = self._dj_status_cache.setdefault( - ctx.guild.id, await self.config.guild(ctx.guild).dj_enabled() - ) - vote_enabled = await self.config.guild(ctx.guild).vote_enabled() - if dj_enabled or vote_enabled: - if not await self._can_instaskip(ctx, ctx.author) and not await self._is_alone( - ctx - ): - return - expected = ("⏹", "⏯") - emoji = {"stop": "⏹", "pause": "⏯"} - if player.current: - task = start_adding_reactions(message, expected[:4], ctx.bot.loop) - else: - task = None + if player.current and not player.queue: + arrow = await draw_time(ctx) + pos = lavalink.utils.format_time(player.position) + if player.current.is_stream: + dur = "LIVE" + else: + dur = lavalink.utils.format_time(player.current.length) + song = get_track_description(player.current) + song += _("\n Requested by: **{track.requester}**") + song += "\n\n{arrow}`{pos}`/`{dur}`" + song = song.format(track=player.current, arrow=arrow, pos=pos, dur=dur) + embed = discord.Embed(title=_("Now Playing"), description=song) + if await self.config.guild(ctx.guild).thumbnail() and player.current: + if player.current.thumbnail: + embed.set_thumbnail(url=player.current.thumbnail) - try: - (r, u) = await self.bot.wait_for( - "reaction_add", - check=ReactionPredicate.with_emojis(expected, message, ctx.author), - timeout=30.0, - ) - except asyncio.TimeoutError: - return await self._clear_react(message, emoji) - else: - if task is not None: - task.cancel() - reacts = {v: k for k, v in emoji.items()} - react = reacts[r.emoji] - if react == "stop": - await self._clear_react(message, emoji) - return await ctx.invoke(self.stop) - elif react == "pause": - await self._clear_react(message, emoji) - return await ctx.invoke(self.pause) - return + shuffle = await self.config.guild(ctx.guild).shuffle() + repeat = await self.config.guild(ctx.guild).repeat() + autoplay = await self.config.guild(ctx.guild).auto_play() + text = "" + text += ( + _("Auto-Play") + + ": " + + ("\N{WHITE HEAVY CHECK MARK}" if autoplay else "\N{CROSS MARK}") + ) + text += ( + (" | " if text else "") + + _("Shuffle") + + ": " + + ("\N{WHITE HEAVY CHECK MARK}" if shuffle else "\N{CROSS MARK}") + ) + text += ( + (" | " if text else "") + + _("Repeat") + + ": " + + ("\N{WHITE HEAVY CHECK MARK}" if repeat else "\N{CROSS MARK}") + ) + embed.set_footer(text=text) + message = await self._embed_msg(ctx, embed=embed) + dj_enabled = self._dj_status_cache.setdefault( + ctx.guild.id, await self.config.guild(ctx.guild).dj_enabled() + ) + vote_enabled = await self.config.guild(ctx.guild).vote_enabled() + if dj_enabled or vote_enabled: + if not await self._can_instaskip(ctx, ctx.author) and not await self._is_alone( + ctx + ): + return + + expected = ("⏹", "⏯") + emoji = {"stop": "⏹", "pause": "⏯"} + if player.current: + task = start_adding_reactions(message, expected[:4], ctx.bot.loop) + else: + task = None + + try: + (r, u) = await self.bot.wait_for( + "reaction_add", + check=ReactionPredicate.with_emojis(expected, message, ctx.author), + timeout=30.0, + ) + except asyncio.TimeoutError: + return await self._clear_react(message, emoji) + else: + if task is not None: + task.cancel() + reacts = {v: k for k, v in emoji.items()} + react = reacts[r.emoji] + if react == "stop": + await self._clear_react(message, emoji) + return await ctx.invoke(self.stop) + elif react == "pause": + await self._clear_react(message, emoji) + return await ctx.invoke(self.pause) + return + elif not player.current and not player.queue: return await self._embed_msg(ctx, title=_("There's nothing in the queue.")) async with ctx.typing(): @@ -6166,6 +6319,7 @@ async def _queue_menu( for page_num in range(1, len_queue_pages + 1): embed = await self._build_queue_page(ctx, limited_queue, player, page_num) queue_page_list.append(embed) + await asyncio.sleep(0) if page > len_queue_pages: page = len_queue_pages return await menu(ctx, queue_page_list, queue_controls, page=(page - 1)) @@ -6258,6 +6412,7 @@ async def _build_queue_page( else: queue_list += f"`{track_idx}.` **[{track_title}]({track.uri})**, " queue_list += _("requested by **{user}**\n").format(user=req_user) + await asyncio.sleep(0) embed = discord.Embed( colour=await ctx.embed_colour(), @@ -6273,7 +6428,7 @@ async def _build_queue_page( ).format( page_num=humanize_number(page_num), total_pages=humanize_number(queue_num_pages), - num_tracks=len(player.queue) + 1, + num_tracks=len(player.queue), num_remaining=queue_total_duration, ) text += ( @@ -6315,6 +6470,7 @@ async def _build_queue_search_list(queue_list, search_words): song_info = {str(queue_idx): track_title} track_list.append(song_info) + await asyncio.sleep(0) search_results = process.extract(search_words, track_list, limit=50) search_list = [] for search, percent_match in search_results: @@ -6340,6 +6496,7 @@ async def _build_queue_search_page(ctx: commands.Context, page_num, search_list) track_match += "`{}.` **{}**\n".format(track_idx, track_location) else: track_match += "`{}.` **{}**\n".format(track[0], track[1]) + await asyncio.sleep(0) embed = discord.Embed( colour=await ctx.embed_colour(), title=_("Matching Tracks:"), description=track_match ) @@ -6797,6 +6954,8 @@ async def _search_menu( track_len = 0 empty_queue = not player.queue for track in tracks: + if len(player.queue) >= 10000: + continue if not await is_allowed( ctx.guild, ( @@ -6821,6 +6980,7 @@ async def _search_menu( ) if not player.current: await player.play() + await asyncio.sleep(0) player.maybe_shuffle(0 if empty_queue else 1) if len(tracks) > track_len: maxlength_msg = " {bad_tracks} tracks cannot be queued.".format( @@ -6856,7 +7016,8 @@ async def _search_menu( ctx, title=_("Unable to Get Track"), description=_( - "I'm unable get a track from Lavalink at the moment, try again in a few minutes." + "I'm unable get a track from Lavalink at the moment," + "try again in a few minutes." ), ) tracks = result.tracks @@ -6891,6 +7052,7 @@ async def _search_menu( for page_num in range(1, len_search_pages + 1): embed = await self._build_search_page(ctx, tracks, page_num) search_page_list.append(embed) + await asyncio.sleep(0) if dj_enabled and not await self._can_instaskip(ctx, ctx.author): return await menu(ctx, search_page_list, DEFAULT_CONTROLS) @@ -6917,6 +7079,10 @@ async def _search_button_action(self, ctx: commands.Context, tracks, emoji, page ) player = lavalink.get_player(ctx.guild.id) guild_data = await self.config.guild(ctx.guild).all() + if len(player.queue) >= 10000: + return await self._embed_msg( + ctx, title=_("Unable To Play Tracks"), description=_("Queue size limit reached.") + ) if not await self._currency_check(ctx, guild_data["jukebox_price"]): return try: @@ -7035,6 +7201,7 @@ async def _build_search_page(ctx: commands.Context, tracks, page_num): search_list += "`{}.` **{}**\n".format( search_track_num, track.to_string_user() ) + await asyncio.sleep(0) if hasattr(tracks[0], "uri") and hasattr(tracks[0], "track_identifier"): title = _("Tracks Found:") footer = _("search results") @@ -7513,6 +7680,7 @@ async def summon(self, ctx: commands.Context): can_skip = await self._can_instaskip(ctx, ctx.author) if vote_enabled or (vote_enabled and dj_enabled): if not can_skip and not is_alone: + ctx.command.reset_cooldown(ctx) return await self._embed_msg( ctx, title=_("Unable To Join Voice Channel"), @@ -7520,6 +7688,7 @@ async def summon(self, ctx: commands.Context): ) if dj_enabled and not vote_enabled: if not (can_skip or is_requester) and not is_alone: + ctx.command.reset_cooldown(ctx) return await self._embed_msg( ctx, title=_("Unable To Join Voice Channel"), @@ -8048,41 +8217,45 @@ def _player_check(self, ctx: commands.Context): async def on_red_audio_track_start( self, guild: discord.Guild, track: lavalink.Track, requester: discord.Member ): + daily_cache = self._daily_playlist_cache.setdefault( + guild.id, await self.config.guild(guild).daily_playlists() + ) scope = PlaylistScope.GUILD.value today = datetime.date.today() midnight = datetime.datetime.combine(today, datetime.datetime.min.time()) - name = f"Daily playlist - {today}" - today_id = int(time.mktime(today.timetuple())) - track_identifier = track.track_identifier - track = track_to_json(track) + if daily_cache: + name = f"Daily playlist - {today}" + today_id = int(time.mktime(today.timetuple())) + track_identifier = track.track_identifier + track = track_to_json(track) + try: + playlist = await get_playlist( + playlist_number=today_id, + scope=PlaylistScope.GUILD.value, + bot=self.bot, + guild=guild, + author=self.bot.user, + ) + except RuntimeError: + playlist = None - try: - playlist = await get_playlist( - playlist_number=today_id, - scope=PlaylistScope.GUILD.value, - bot=self.bot, - guild=guild, - author=self.bot.user, - ) - except RuntimeError: - playlist = None + if playlist: + tracks = playlist.tracks + tracks.append(track) + await playlist.edit({"tracks": tracks}) + else: + playlist = Playlist( + bot=self.bot, + scope=scope, + author=self.bot.user.id, + playlist_id=today_id, + name=name, + playlist_url=None, + tracks=[track], + guild=guild, + ) + await playlist.save() - if playlist: - tracks = playlist.tracks - tracks.append(track) - await playlist.edit({"tracks": tracks}) - else: - playlist = Playlist( - bot=self.bot, - scope=scope, - author=self.bot.user.id, - playlist_id=today_id, - name=name, - playlist_url=None, - tracks=[track], - guild=guild, - ) - await playlist.save() with contextlib.suppress(Exception): too_old = midnight - datetime.timedelta(days=8) too_old_id = int(time.mktime(too_old.timetuple())) diff --git a/redbot/cogs/audio/playlists.py b/redbot/cogs/audio/playlists.py index 82a1ee2cdd0..98af9be3ae4 100644 --- a/redbot/cogs/audio/playlists.py +++ b/redbot/cogs/audio/playlists.py @@ -1,3 +1,4 @@ +import asyncio from collections import namedtuple from typing import List, MutableMapping, Optional, Union, TYPE_CHECKING @@ -478,12 +479,16 @@ async def get_all_playlist( playlists = await database.fetch_all(scope_standard, scope_id, author_id=user_id) else: playlists = await database.fetch_all(scope_standard, scope_id) - return [ - await Playlist.from_json( - bot, scope, playlist.playlist_id, playlist, guild=guild, author=author + + playlist_list = [] + for playlist in playlists: + playlist_list.append( + await Playlist.from_json( + bot, scope, playlist.playlist_id, playlist, guild=guild, author=author + ) ) - for playlist in playlists - ] + await asyncio.sleep(0) + return playlist_list async def get_all_playlist_converter( @@ -524,12 +529,15 @@ async def get_all_playlist_converter( playlists = await database.fetch_all_converter( scope_standard, playlist_name=arg, playlist_id=arg ) - return [ - await Playlist.from_json( - bot, scope, playlist.playlist_id, playlist, guild=guild, author=author + playlist_list = [] + for playlist in playlists: + playlist_list.append( + await Playlist.from_json( + bot, scope, playlist.playlist_id, playlist, guild=guild, author=author + ) ) - for playlist in playlists - ] + await asyncio.sleep(0) + return playlist_list async def create_playlist(