[3.2]Audio] some hotfixes to avoid crashing the bot (#3286)

* Add a command to toggle daily queues, and  restrict playlist length to 10k tracks and try to avoid some blocking calls

Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com>

* Add a command to toggle daily queues, and  restrict playlist length to 10k tracks and try to avoid some blocking calls

Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com>

* indents

Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com>

* *sigh* forgot single tracks

Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com>

* formatting plus some other fixes

Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com>

* formatting plus some other fixes

Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com>
This commit is contained in:
Draper 2020-01-08 22:04:52 +00:00 committed by Michael H
parent 965416de73
commit 44e680ee41
4 changed files with 312 additions and 127 deletions

View File

@ -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).

View File

@ -318,6 +318,7 @@ class MusicCache:
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 @@ class MusicCache:
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

View File

@ -94,6 +94,7 @@ class Audio(commands.Cog):
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 @@ class Audio(commands.Cog):
disconnect=False,
dj_enabled=False,
dj_role=None,
daily_playlists=False,
emptydc_enabled=False,
emptydc_timer=0,
emptypause_enabled=False,
@ -183,6 +185,9 @@ class Audio(commands.Cog):
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 @@ class Audio(commands.Cog):
"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 @@ class Audio(commands.Cog):
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 @@ class Audio(commands.Cog):
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 @@ class Audio(commands.Cog):
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 @@ class Audio(commands.Cog):
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 @@ class Audio(commands.Cog):
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 @@ class Audio(commands.Cog):
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 @@ class Audio(commands.Cog):
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 @@ class Audio(commands.Cog):
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 @@ class Audio(commands.Cog):
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 @@ class Audio(commands.Cog):
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 @@ class Audio(commands.Cog):
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 @@ class Audio(commands.Cog):
# 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 @@ class Audio(commands.Cog):
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 @@ class Audio(commands.Cog):
# this is in the case of [p]play <query>, 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 @@ class Audio(commands.Cog):
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 @@ class Audio(commands.Cog):
).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 @@ class Audio(commands.Cog):
)
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="<id_or_name> [args]")
async def _playlist_copy(
self,
@ -4142,9 +4218,11 @@ class Audio(commands.Cog):
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 @@ class Audio(commands.Cog):
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 @@ class Audio(commands.Cog):
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 @@ class Audio(commands.Cog):
),
)
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 @@ class Audio(commands.Cog):
),
)
@commands.cooldown(1, 30, commands.BucketType.member)
@playlist.command(name="dedupe", usage="<playlist_name_OR_id> [args]")
async def _playlist_remdupe(
self,
@ -4407,8 +4489,10 @@ class Audio(commands.Cog):
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 @@ class Audio(commands.Cog):
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 @@ class Audio(commands.Cog):
),
)
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 @@ class Audio(commands.Cog):
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 @@ class Audio(commands.Cog):
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 @@ class Audio(commands.Cog):
),
)
except MissingGuild:
ctx.command.reset_cooldown(ctx)
return await self._embed_msg(
ctx,
title=_("Missing Arguments"),
@ -4567,6 +4658,7 @@ class Audio(commands.Cog):
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 @@ class Audio(commands.Cog):
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 @@ class Audio(commands.Cog):
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="<playlist_name_OR_id> [args]")
async def _playlist_info(
self,
@ -4672,8 +4766,10 @@ class Audio(commands.Cog):
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 @@ class Audio(commands.Cog):
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 @@ class Audio(commands.Cog):
),
)
except MissingGuild:
ctx.command.reset_cooldown(ctx)
return await self._embed_msg(
ctx,
title=_("Missing Arguments"),
@ -4722,6 +4820,7 @@ class Audio(commands.Cog):
msg += "`{}.` **[{}]({})**\n".format(
track_idx, track["info"]["title"], track["info"]["uri"]
)
await asyncio.sleep(0)
else:
msg = "No tracks."
@ -4753,6 +4852,7 @@ class Audio(commands.Cog):
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 @@ class Audio(commands.Cog):
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 @@ class Audio(commands.Cog):
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 @@ class Audio(commands.Cog):
),
)
elif not playlists:
ctx.command.reset_cooldown(ctx)
return await self._embed_msg(
ctx,
title=_("Playlist Not Found"),
@ -4838,6 +4941,7 @@ class Audio(commands.Cog):
)
)
)
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 @@ class Audio(commands.Cog):
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 @@ class Audio(commands.Cog):
):
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 @@ class Audio(commands.Cog):
return embed
@playlist.command(name="queue", usage="<name> [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 @@ class Audio(commands.Cog):
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 @@ class Audio(commands.Cog):
).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="<playlist_name_OR_id> <url> [args]")
@ -5073,7 +5189,7 @@ class Audio(commands.Cog):
)
@playlist.command(name="save", usage="<name> <url> [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 @@ class Audio(commands.Cog):
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 @@ class Audio(commands.Cog):
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="<playlist_name_OR_id> [args]")
async def _playlist_start(
self,
@ -5203,6 +5329,7 @@ class Audio(commands.Cog):
)
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 @@ class Audio(commands.Cog):
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 @@ class Audio(commands.Cog):
)
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 @@ class Audio(commands.Cog):
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 @@ class Audio(commands.Cog):
"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 @@ class Audio(commands.Cog):
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 @@ class Audio(commands.Cog):
),
)
except MissingGuild:
ctx.command.reset_cooldown(ctx)
return await self._embed_msg(
ctx,
title=_("Missing Arguments"),
@ -5314,6 +5450,7 @@ class Audio(commands.Cog):
if playlist:
return await ctx.invoke(self.play, query=playlist.url)
@commands.cooldown(1, 60, commands.BucketType.member)
@playlist.command(name="update", usage="<playlist_name_OR_id> [args]")
async def _playlist_update(
self,
@ -5361,9 +5498,11 @@ class Audio(commands.Cog):
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 @@ class Audio(commands.Cog):
)
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 @@ class Audio(commands.Cog):
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 @@ class Audio(commands.Cog):
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 @@ class Audio(commands.Cog):
scope_data=(scope, author, guild, specified_user),
)
@commands.cooldown(1, 60, commands.BucketType.member)
@playlist.command(name="rename", usage="<playlist_name_OR_id> <new_name> [args]")
async def _playlist_rename(
self,
@ -5629,6 +5773,7 @@ class Audio(commands.Cog):
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 @@ class Audio(commands.Cog):
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 @@ class Audio(commands.Cog):
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 @@ class Audio(commands.Cog):
),
)
except MissingGuild:
ctx.command.reset_cooldown(ctx)
return await self._embed_msg(
ctx,
title=_("Missing Arguments"),
@ -5669,6 +5818,7 @@ class Audio(commands.Cog):
)
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 @@ class Audio(commands.Cog):
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 @@ class Audio(commands.Cog):
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 @@ class Audio(commands.Cog):
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 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)
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:
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)
task = start_adding_reactions(message, expected[:4], ctx.bot.loop)
else:
task = None
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}")
try:
(r, u) = await self.bot.wait_for(
"reaction_add",
check=ReactionPredicate.with_emojis(expected, message, ctx.author),
timeout=30.0,
)
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
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 @@ class Audio(commands.Cog):
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 @@ class Audio(commands.Cog):
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 @@ class Audio(commands.Cog):
).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 @@ class Audio(commands.Cog):
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 @@ class Audio(commands.Cog):
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 @@ class Audio(commands.Cog):
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 @@ class Audio(commands.Cog):
)
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 @@ class Audio(commands.Cog):
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 @@ class Audio(commands.Cog):
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 @@ class Audio(commands.Cog):
)
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 @@ class Audio(commands.Cog):
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 @@ class Audio(commands.Cog):
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 @@ class Audio(commands.Cog):
)
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 @@ class Audio(commands.Cog):
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()))

View File

@ -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(