From 135a3090d9724157803f9de6e4c2a7ef7939faf1 Mon Sep 17 00:00:00 2001 From: Drapersniper <27962761+drapersniper@users.noreply.github.com> Date: Tue, 3 Dec 2019 20:18:26 +0000 Subject: [PATCH 01/19] Merge branch 'V3/develop' of https://github.com/Cog-Creators/Red-DiscordBot into refactoring Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> # Conflicts: # redbot/cogs/audio/audio.py --- redbot/cogs/audio/audio.py | 64 +++++++++++++++++++++++++++++++++++--- 1 file changed, 59 insertions(+), 5 deletions(-) diff --git a/redbot/cogs/audio/audio.py b/redbot/cogs/audio/audio.py index 07cbeb91abe..e99f6dba89d 100644 --- a/redbot/cogs/audio/audio.py +++ b/redbot/cogs/audio/audio.py @@ -5,12 +5,11 @@ import heapq import json import logging -import os import random import re import time import traceback -from collections import namedtuple +from collections import namedtuple, Counter from io import StringIO from typing import List, Optional, Tuple, Union, cast @@ -98,7 +97,7 @@ def __init__(self, bot): status=False, use_external_lavalink=False, restrict=True, - current_version=redbot.core.VersionInfo.from_str("3.0.0a0").to_json(), + current_version=redbot.core.VersionInfo.from_str(__version__).to_json(), localpath=str(cog_data_path(raw_name="Audio")), **self._default_lavalink_settings, ) @@ -139,6 +138,9 @@ def __init__(self, bot): self.config.register_guild(**default_guild) self.config.register_global(**default_global) self.music_cache = MusicCache(bot, self.session, path=str(cog_data_path(raw_name="Audio"))) + self._error_counter = Counter() + self._error_timer = {} + self._disconnected_players = {} self.play_lock = {} self._manager: Optional[ServerManager] = None @@ -367,6 +369,23 @@ async def attempt_connect(self, timeout: int = 50): "tracebacks for details." ) + def disconnect_cache_handler(self, player: lavalink.Player): + guild = player.channel.guild + now = time.time() + seconds_allowed = 10 + last_error = self._error_timer.setdefault(guild.id, now) + if now - seconds_allowed > last_error: + self._error_timer[guild.id] = 0 + self._error_counter[guild.id] = 0 + return now + + def error_handler(self, now: float, player: lavalink.Player) -> bool: + guild = player.channel.guild + if self._error_timer[guild.id] == 0: + self._error_timer[guild.id] = now + self._error_counter[guild.id] = self._error_counter[guild.id] + 1 + return self._error_counter[guild.id] > 5 + async def event_handler( self, player: lavalink.Player, event_type: lavalink.LavalinkEvents, extra ): @@ -417,6 +436,42 @@ async def _status_check(playing_servers): ) ) + time_now = self.disconnect_cache_handler(player) + if event_type == lavalink.LavalinkEvents.TRACK_EXCEPTION: + self._error_counter.setdefault(player.channel.guild.id, 0) + if player.channel.guild.id not in self._error_counter: + self._error_counter[player.channel.guild.id] = 0 + early_exit = self.error_handler(time_now, player) + if early_exit: + self._disconnected_players[player.channel.guild.id] = True + self.bot.dispatch("red_audio_audio_disconnect", player.channel.guild) + self.play_lock[player.channel.guild.id] = False + message_channel = player.fetch("channel") + eq = player.fetch("eq") + player.queue = [] + player.store("playing_song", None) + if eq: + await self.config.custom("EQUALIZER", player.channel.guild.id).eq_bands.set( + eq.bands + ) + if message_channel: + message_channel = self.bot.get_channel(message_channel) + embed = discord.Embed( + colour=(await self.bot.get_embed_color(message_channel)), + title=_("Multiple errors detected"), + description=_( + "Closing the audio player " + "due to multiple errors being detected. " + "If this persists, please inform the bot owner " + "as the Audio cog may be temporally unavailable." + ), + ) + await message_channel.send(embed=embed) + await player.stop() + await player.disconnect() + if self._disconnected_players.get(player.channel.guild.id): + return + if event_type == lavalink.LavalinkEvents.TRACK_START: self.skip_votes[player.channel.guild] = [] playing_song = player.fetch("playing_song") @@ -984,7 +1039,7 @@ async def dj(self, ctx: commands.Context): try: pred = MessagePredicate.valid_role(ctx) await ctx.bot.wait_for("message", timeout=15.0, check=pred) - await ctx.invoke(self.role, pred.result) + await ctx.invoke(self.role, role_name=pred.result) except asyncio.TimeoutError: return await self._embed_msg(ctx, _("Response timed out, try again later.")) @@ -6020,7 +6075,6 @@ async def _build_search_page(ctx: commands.Context, tracks, page_num): search_track_num, track.title, track.uri ) except AttributeError: - # query = Query.process_input(track) track = audio_dataclasses.Query.process_input(track) if track.is_local and command != "search": search_list += "`{}.` **{}**\n".format( From 8e06629d792594be2711f7e381ec02da727da51f Mon Sep 17 00:00:00 2001 From: Drapersniper <27962761+drapersniper@users.noreply.github.com> Date: Tue, 3 Dec 2019 20:24:53 +0000 Subject: [PATCH 02/19] :eyes: + chore Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> --- changelog.d/audio/3165.bugfix.1.rst | 1 + changelog.d/audio/3165.enhance.1.rst | 1 + redbot/cogs/audio/audio.py | 6 +----- 3 files changed, 3 insertions(+), 5 deletions(-) create mode 100644 changelog.d/audio/3165.bugfix.1.rst create mode 100644 changelog.d/audio/3165.enhance.1.rst diff --git a/changelog.d/audio/3165.bugfix.1.rst b/changelog.d/audio/3165.bugfix.1.rst new file mode 100644 index 00000000000..2c29f9902ee --- /dev/null +++ b/changelog.d/audio/3165.bugfix.1.rst @@ -0,0 +1 @@ +Fixed an error that was thrown when running ``[p]audioset dj``. \ No newline at end of file diff --git a/changelog.d/audio/3165.enhance.1.rst b/changelog.d/audio/3165.enhance.1.rst new file mode 100644 index 00000000000..00771829716 --- /dev/null +++ b/changelog.d/audio/3165.enhance.1.rst @@ -0,0 +1 @@ +Better error handling the player is unable to play multiple tracks in sequence. \ No newline at end of file diff --git a/redbot/cogs/audio/audio.py b/redbot/cogs/audio/audio.py index e99f6dba89d..a157641a98b 100644 --- a/redbot/cogs/audio/audio.py +++ b/redbot/cogs/audio/audio.py @@ -140,7 +140,6 @@ def __init__(self, bot): self.music_cache = MusicCache(bot, self.session, path=str(cog_data_path(raw_name="Audio"))) self._error_counter = Counter() self._error_timer = {} - self._disconnected_players = {} self.play_lock = {} self._manager: Optional[ServerManager] = None @@ -372,7 +371,7 @@ async def attempt_connect(self, timeout: int = 50): def disconnect_cache_handler(self, player: lavalink.Player): guild = player.channel.guild now = time.time() - seconds_allowed = 10 + seconds_allowed = 5 last_error = self._error_timer.setdefault(guild.id, now) if now - seconds_allowed > last_error: self._error_timer[guild.id] = 0 @@ -443,7 +442,6 @@ async def _status_check(playing_servers): self._error_counter[player.channel.guild.id] = 0 early_exit = self.error_handler(time_now, player) if early_exit: - self._disconnected_players[player.channel.guild.id] = True self.bot.dispatch("red_audio_audio_disconnect", player.channel.guild) self.play_lock[player.channel.guild.id] = False message_channel = player.fetch("channel") @@ -469,8 +467,6 @@ async def _status_check(playing_servers): await message_channel.send(embed=embed) await player.stop() await player.disconnect() - if self._disconnected_players.get(player.channel.guild.id): - return if event_type == lavalink.LavalinkEvents.TRACK_START: self.skip_votes[player.channel.guild] = [] From 39787fbf8afb0440cea9ddaf27b37a546d1a272c Mon Sep 17 00:00:00 2001 From: Drapersniper <27962761+drapersniper@users.noreply.github.com> Date: Tue, 3 Dec 2019 21:30:43 +0000 Subject: [PATCH 03/19] facepalm Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> --- redbot/cogs/audio/audio.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/redbot/cogs/audio/audio.py b/redbot/cogs/audio/audio.py index a157641a98b..28c8df5e729 100644 --- a/redbot/cogs/audio/audio.py +++ b/redbot/cogs/audio/audio.py @@ -140,6 +140,7 @@ def __init__(self, bot): self.music_cache = MusicCache(bot, self.session, path=str(cog_data_path(raw_name="Audio"))) self._error_counter = Counter() self._error_timer = {} + self._disconnected_players = {} self.play_lock = {} self._manager: Optional[ServerManager] = None @@ -467,6 +468,8 @@ async def _status_check(playing_servers): await message_channel.send(embed=embed) await player.stop() await player.disconnect() + if self._disconnected_players.get(player.channel.guild.id): + return if event_type == lavalink.LavalinkEvents.TRACK_START: self.skip_votes[player.channel.guild] = [] From 35d156609b4f31bed173dd4a2fd55657401e27da Mon Sep 17 00:00:00 2001 From: Drapersniper <27962761+drapersniper@users.noreply.github.com> Date: Tue, 3 Dec 2019 21:48:54 +0000 Subject: [PATCH 04/19] facepalm... again y u h8 me bruh Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> --- redbot/cogs/audio/audio.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/redbot/cogs/audio/audio.py b/redbot/cogs/audio/audio.py index 28c8df5e729..3994ce2455f 100644 --- a/redbot/cogs/audio/audio.py +++ b/redbot/cogs/audio/audio.py @@ -369,22 +369,19 @@ async def attempt_connect(self, timeout: int = 50): "tracebacks for details." ) - def disconnect_cache_handler(self, player: lavalink.Player): + async def error_reset(self, player: lavalink.Player): guild = player.channel.guild now = time.time() - seconds_allowed = 5 + seconds_allowed = 10 last_error = self._error_timer.setdefault(guild.id, now) if now - seconds_allowed > last_error: self._error_timer[guild.id] = 0 self._error_counter[guild.id] = 0 - return now - def error_handler(self, now: float, player: lavalink.Player) -> bool: + async def error_handler(self, player: lavalink.Player) -> bool: guild = player.channel.guild - if self._error_timer[guild.id] == 0: - self._error_timer[guild.id] = now - self._error_counter[guild.id] = self._error_counter[guild.id] + 1 - return self._error_counter[guild.id] > 5 + self._error_counter[guild.id] += 1 + return self._error_counter[guild.id] >= 5 async def event_handler( self, player: lavalink.Player, event_type: lavalink.LavalinkEvents, extra @@ -436,13 +433,15 @@ async def _status_check(playing_servers): ) ) - time_now = self.disconnect_cache_handler(player) + await self.error_reset(player) if event_type == lavalink.LavalinkEvents.TRACK_EXCEPTION: self._error_counter.setdefault(player.channel.guild.id, 0) if player.channel.guild.id not in self._error_counter: self._error_counter[player.channel.guild.id] = 0 - early_exit = self.error_handler(time_now, player) + + early_exit = await self.error_handler(player) if early_exit: + self._disconnected_players[player.channel.guild.id] = True self.bot.dispatch("red_audio_audio_disconnect", player.channel.guild) self.play_lock[player.channel.guild.id] = False message_channel = player.fetch("channel") @@ -468,8 +467,8 @@ async def _status_check(playing_servers): await message_channel.send(embed=embed) await player.stop() await player.disconnect() - if self._disconnected_players.get(player.channel.guild.id): - return + if self._disconnected_players.get(player.channel.guild.id): + return if event_type == lavalink.LavalinkEvents.TRACK_START: self.skip_votes[player.channel.guild] = [] From 57180c62d526a230e0e857dc0a6e12923705fd3f Mon Sep 17 00:00:00 2001 From: Drapersniper <27962761+drapersniper@users.noreply.github.com> Date: Tue, 3 Dec 2019 22:03:17 +0000 Subject: [PATCH 05/19] fuk this fuk u tube fuck python fuck all Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> --- redbot/cogs/audio/audio.py | 68 ++++++++++++++++++-------------------- 1 file changed, 33 insertions(+), 35 deletions(-) diff --git a/redbot/cogs/audio/audio.py b/redbot/cogs/audio/audio.py index 3994ce2455f..987a4175576 100644 --- a/redbot/cogs/audio/audio.py +++ b/redbot/cogs/audio/audio.py @@ -434,41 +434,6 @@ async def _status_check(playing_servers): ) await self.error_reset(player) - if event_type == lavalink.LavalinkEvents.TRACK_EXCEPTION: - self._error_counter.setdefault(player.channel.guild.id, 0) - if player.channel.guild.id not in self._error_counter: - self._error_counter[player.channel.guild.id] = 0 - - early_exit = await self.error_handler(player) - if early_exit: - self._disconnected_players[player.channel.guild.id] = True - self.bot.dispatch("red_audio_audio_disconnect", player.channel.guild) - self.play_lock[player.channel.guild.id] = False - message_channel = player.fetch("channel") - eq = player.fetch("eq") - player.queue = [] - player.store("playing_song", None) - if eq: - await self.config.custom("EQUALIZER", player.channel.guild.id).eq_bands.set( - eq.bands - ) - if message_channel: - message_channel = self.bot.get_channel(message_channel) - embed = discord.Embed( - colour=(await self.bot.get_embed_color(message_channel)), - title=_("Multiple errors detected"), - description=_( - "Closing the audio player " - "due to multiple errors being detected. " - "If this persists, please inform the bot owner " - "as the Audio cog may be temporally unavailable." - ), - ) - await message_channel.send(embed=embed) - await player.stop() - await player.disconnect() - if self._disconnected_players.get(player.channel.guild.id): - return if event_type == lavalink.LavalinkEvents.TRACK_START: self.skip_votes[player.channel.guild] = [] @@ -625,6 +590,39 @@ async def _status_check(playing_servers): break if repeat: player.current = None + self._error_counter.setdefault(player.channel.guild.id, 0) + if player.channel.guild.id not in self._error_counter: + self._error_counter[player.channel.guild.id] = 0 + + early_exit = await self.error_handler(player) + if early_exit: + self._disconnected_players[player.channel.guild.id] = True + self.bot.dispatch("red_audio_audio_disconnect", player.channel.guild) + self.play_lock[player.channel.guild.id] = False + eq = player.fetch("eq") + player.queue = [] + player.store("playing_song", None) + if eq: + await self.config.custom("EQUALIZER", player.channel.guild.id).eq_bands.set( + eq.bands + ) + if message_channel: + message_channel = self.bot.get_channel(message_channel) + embed = discord.Embed( + colour=(await self.bot.get_embed_color(message_channel)), + title=_("Multiple errors detected"), + description=_( + "Closing the audio player " + "due to multiple errors being detected. " + "If this persists, please inform the bot owner " + "as the Audio cog may be temporally unavailable." + ), + ) + await message_channel.send(embed=embed) + await player.stop() + await player.disconnect() + if self._disconnected_players.get(player.channel.guild.id): + return await player.skip() async def play_query( From 6877d0a0ec62bad86361f7a634eb3f30de220264 Mon Sep 17 00:00:00 2001 From: Drapersniper <27962761+drapersniper@users.noreply.github.com> Date: Tue, 3 Dec 2019 22:04:43 +0000 Subject: [PATCH 06/19] awyehfqwajefhnqeffawefqa eqewarfqaesf qwef qaf qwfr Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> --- redbot/cogs/audio/audio.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/redbot/cogs/audio/audio.py b/redbot/cogs/audio/audio.py index 987a4175576..2f5f8dfcbcb 100644 --- a/redbot/cogs/audio/audio.py +++ b/redbot/cogs/audio/audio.py @@ -621,8 +621,8 @@ async def _status_check(playing_servers): await message_channel.send(embed=embed) await player.stop() await player.disconnect() - if self._disconnected_players.get(player.channel.guild.id): - return + if self._disconnected_players.get(player.channel.guild.id): + return await player.skip() async def play_query( From 757ec9d72bd6f5fed6829df275668913f95c4eee Mon Sep 17 00:00:00 2001 From: Drapersniper <27962761+drapersniper@users.noreply.github.com> Date: Tue, 3 Dec 2019 22:10:27 +0000 Subject: [PATCH 07/19] fuck everything Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> --- redbot/cogs/audio/audio.py | 64 +++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/redbot/cogs/audio/audio.py b/redbot/cogs/audio/audio.py index 2f5f8dfcbcb..e801232c89b 100644 --- a/redbot/cogs/audio/audio.py +++ b/redbot/cogs/audio/audio.py @@ -590,38 +590,38 @@ async def _status_check(playing_servers): break if repeat: player.current = None - self._error_counter.setdefault(player.channel.guild.id, 0) - if player.channel.guild.id not in self._error_counter: - self._error_counter[player.channel.guild.id] = 0 - - early_exit = await self.error_handler(player) - if early_exit: - self._disconnected_players[player.channel.guild.id] = True - self.bot.dispatch("red_audio_audio_disconnect", player.channel.guild) - self.play_lock[player.channel.guild.id] = False - eq = player.fetch("eq") - player.queue = [] - player.store("playing_song", None) - if eq: - await self.config.custom("EQUALIZER", player.channel.guild.id).eq_bands.set( - eq.bands - ) - if message_channel: - message_channel = self.bot.get_channel(message_channel) - embed = discord.Embed( - colour=(await self.bot.get_embed_color(message_channel)), - title=_("Multiple errors detected"), - description=_( - "Closing the audio player " - "due to multiple errors being detected. " - "If this persists, please inform the bot owner " - "as the Audio cog may be temporally unavailable." - ), - ) - await message_channel.send(embed=embed) - await player.stop() - await player.disconnect() - if self._disconnected_players.get(player.channel.guild.id): + with contextlib.suppress(Exception): + self._error_counter.setdefault(player.channel.guild.id, 0) + if player.channel.guild.id not in self._error_counter: + self._error_counter[player.channel.guild.id] = 0 + + early_exit = await self.error_handler(player) + if early_exit: + self._disconnected_players[player.channel.guild.id] = True + self.bot.dispatch("red_audio_audio_disconnect", player.channel.guild) + self.play_lock[player.channel.guild.id] = False + eq = player.fetch("eq") + player.queue = [] + player.store("playing_song", None) + if eq: + await self.config.custom("EQUALIZER", player.channel.guild.id).eq_bands.set( + eq.bands + ) + if message_channel: + message_channel = self.bot.get_channel(message_channel) + embed = discord.Embed( + colour=(await self.bot.get_embed_color(message_channel)), + title=_("Multiple errors detected"), + description=_( + "Closing the audio player " + "due to multiple errors being detected. " + "If this persists, please inform the bot owner " + "as the Audio cog may be temporally unavailable." + ), + ) + await message_channel.send(embed=embed) + await player.stop() + await player.disconnect() return await player.skip() From 68b34253f52df00ec857bc4cd39ba41749e58723 Mon Sep 17 00:00:00 2001 From: Drapersniper <27962761+drapersniper@users.noreply.github.com> Date: Tue, 3 Dec 2019 22:18:32 +0000 Subject: [PATCH 08/19] oh lord saviour resus i love you just make this work Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> --- redbot/cogs/audio/audio.py | 81 ++++++++++++++++++++------------------ 1 file changed, 43 insertions(+), 38 deletions(-) diff --git a/redbot/cogs/audio/audio.py b/redbot/cogs/audio/audio.py index e801232c89b..a79df934cf0 100644 --- a/redbot/cogs/audio/audio.py +++ b/redbot/cogs/audio/audio.py @@ -563,26 +563,7 @@ async def _status_check(playing_servers): if event_type == lavalink.LavalinkEvents.TRACK_EXCEPTION: message_channel = player.fetch("channel") - if message_channel: - message_channel = self.bot.get_channel(message_channel) - query = audio_dataclasses.Query.process_input(player.current.uri) - if player.current and query.is_local: - query = audio_dataclasses.Query.process_input(player.current.uri) - if player.current.title == "Unknown title": - description = "{}".format(query.track.to_string_hidden()) - else: - song = bold("{} - {}").format(player.current.author, player.current.title) - description = "{}\n{}".format(song, query.track.to_string_hidden()) - else: - description = bold("[{}]({})").format(player.current.title, player.current.uri) - - embed = discord.Embed( - colour=(await self.bot.get_embed_color(message_channel)), - title=_("Track Error"), - description="{}\n{}".format(extra, description), - ) - embed.set_footer(text=_("Skipping...")) - await message_channel.send(embed=embed) + early_exit = False while True: if player.current in player.queue: player.queue.remove(player.current) @@ -594,7 +575,6 @@ async def _status_check(playing_servers): self._error_counter.setdefault(player.channel.guild.id, 0) if player.channel.guild.id not in self._error_counter: self._error_counter[player.channel.guild.id] = 0 - early_exit = await self.error_handler(player) if early_exit: self._disconnected_players[player.channel.guild.id] = True @@ -604,25 +584,50 @@ async def _status_check(playing_servers): player.queue = [] player.store("playing_song", None) if eq: - await self.config.custom("EQUALIZER", player.channel.guild.id).eq_bands.set( - eq.bands - ) - if message_channel: - message_channel = self.bot.get_channel(message_channel) - embed = discord.Embed( - colour=(await self.bot.get_embed_color(message_channel)), - title=_("Multiple errors detected"), - description=_( - "Closing the audio player " - "due to multiple errors being detected. " - "If this persists, please inform the bot owner " - "as the Audio cog may be temporally unavailable." - ), - ) - await message_channel.send(embed=embed) + await self.config.custom( + "EQUALIZER", player.channel.guild.id + ).eq_bands.set(eq.bands) await player.stop() await player.disconnect() - return + if message_channel: + message_channel = self.bot.get_channel(message_channel) + if early_exit: + embed = discord.Embed( + colour=(await self.bot.get_embed_color(message_channel)), + title=_("Multiple errors detected"), + description=_( + "Closing the audio player " + "due to multiple errors being detected. " + "If this persists, please inform the bot owner " + "as the Audio cog may be temporally unavailable." + ), + ) + await message_channel.send(embed=embed) + else: + query = audio_dataclasses.Query.process_input(player.current.uri) + if player.current and query.is_local: + query = audio_dataclasses.Query.process_input(player.current.uri) + if player.current.title == "Unknown title": + description = "{}".format(query.track.to_string_hidden()) + else: + song = bold("{} - {}").format( + player.current.author, player.current.title + ) + description = "{}\n{}".format(song, query.track.to_string_hidden()) + else: + description = bold("[{}]({})").format( + player.current.title, player.current.uri + ) + + embed = discord.Embed( + colour=(await self.bot.get_embed_color(message_channel)), + title=_("Track Error"), + description="{}\n{}".format(extra, description), + ) + embed.set_footer(text=_("Skipping...")) + await message_channel.send(embed=embed) + if early_exit: + return await player.skip() async def play_query( From fa0f267af727d323504beaee10573f088390096f Mon Sep 17 00:00:00 2001 From: Draper <27962761+Drapersniper@users.noreply.github.com> Date: Wed, 4 Dec 2019 09:51:09 +0000 Subject: [PATCH 09/19] Change logic to be no errors within last 10 seconds... this should be a valid work around discord ratelimits caused by the spam --- redbot/cogs/audio/audio.py | 153 ++++++++++++++++++------------------- 1 file changed, 76 insertions(+), 77 deletions(-) diff --git a/redbot/cogs/audio/audio.py b/redbot/cogs/audio/audio.py index a79df934cf0..8a2a226a611 100644 --- a/redbot/cogs/audio/audio.py +++ b/redbot/cogs/audio/audio.py @@ -378,11 +378,53 @@ async def error_reset(self, player: lavalink.Player): self._error_timer[guild.id] = 0 self._error_counter[guild.id] = 0 - async def error_handler(self, player: lavalink.Player) -> bool: + async def increase_error_counter(self, player: lavalink.Player) -> bool: guild = player.channel.guild + now = time.time() self._error_counter[guild.id] += 1 + self._error_timer[guild.id] = now return self._error_counter[guild.id] >= 5 + @staticmethod + async def _players_check(): + try: + get_single_title = lavalink.active_players()[0].current.title + query = audio_dataclasses.Query.process_input(lavalink.active_players()[0].current.uri) + if get_single_title == "Unknown title": + get_single_title = lavalink.active_players()[0].current.uri + if not get_single_title.startswith("http"): + get_single_title = get_single_title.rsplit("/", 1)[-1] + elif query.is_local: + get_single_title = "{} - {}".format( + lavalink.active_players()[0].current.author, + lavalink.active_players()[0].current.title, + ) + else: + get_single_title = lavalink.active_players()[0].current.title + playing_servers = len(lavalink.active_players()) + except IndexError: + get_single_title = None + playing_servers = 0 + return get_single_title, playing_servers + + async def _status_check(self, playing_servers): + if playing_servers == 0: + await self.bot.change_presence(activity=None) + if playing_servers == 1: + single_title = await self._players_check() + await self.bot.change_presence( + activity=discord.Activity( + name=single_title[0], type=discord.ActivityType.listening + ) + ) + if playing_servers > 1: + await self.bot.change_presence( + activity=discord.Activity( + name=_("music in {} servers").format(playing_servers), + type=discord.ActivityType.playing, + ) + ) + async def event_handler( self, player: lavalink.Player, event_type: lavalink.LavalinkEvents, extra ): @@ -392,47 +434,6 @@ async def event_handler( status = await self.config.status() repeat = await self.config.guild(player.channel.guild).repeat() - async def _players_check(): - try: - get_single_title = lavalink.active_players()[0].current.title - query = audio_dataclasses.Query.process_input( - lavalink.active_players()[0].current.uri - ) - if get_single_title == "Unknown title": - get_single_title = lavalink.active_players()[0].current.uri - if not get_single_title.startswith("http"): - get_single_title = get_single_title.rsplit("/", 1)[-1] - elif query.is_local: - get_single_title = "{} - {}".format( - lavalink.active_players()[0].current.author, - lavalink.active_players()[0].current.title, - ) - else: - get_single_title = lavalink.active_players()[0].current.title - playing_servers = len(lavalink.active_players()) - except IndexError: - get_single_title = None - playing_servers = 0 - return get_single_title, playing_servers - - async def _status_check(playing_servers): - if playing_servers == 0: - await self.bot.change_presence(activity=None) - if playing_servers == 1: - single_title = await _players_check() - await self.bot.change_presence( - activity=discord.Activity( - name=single_title[0], type=discord.ActivityType.listening - ) - ) - if playing_servers > 1: - await self.bot.change_presence( - activity=discord.Activity( - name=_("music in {} servers").format(playing_servers), - type=discord.ActivityType.playing, - ) - ) - await self.error_reset(player) if event_type == lavalink.LavalinkEvents.TRACK_START: @@ -534,16 +535,16 @@ async def _status_check(playing_servers): player.store("notify_message", notify_message) if event_type == lavalink.LavalinkEvents.TRACK_START and status: - player_check = await _players_check() - await _status_check(player_check[1]) + player_check = await self._players_check() + await self._status_check(player_check[1]) if event_type == lavalink.LavalinkEvents.TRACK_END and status: await asyncio.sleep(1) if not player.is_playing: - player_check = await _players_check() - await _status_check(player_check[1]) + player_check = await self._players_check() + await self._status_check(player_check[1]) - if event_type == lavalink.LavalinkEvents.QUEUE_END and notify and not autoplay: + if not autoplay and event_type == lavalink.LavalinkEvents.QUEUE_END and notify: notify_channel = player.fetch("channel") if notify_channel: notify_channel = self.bot.get_channel(notify_channel) @@ -553,17 +554,16 @@ async def _status_check(playing_servers): ) await notify_channel.send(embed=embed) - elif event_type == lavalink.LavalinkEvents.QUEUE_END and disconnect and not autoplay: - self.bot.dispatch("red_audio_audio_disconnect", player.channel.guild) + elif not autoplay and event_type == lavalink.LavalinkEvents.QUEUE_END and disconnect: await player.disconnect() + self.bot.dispatch("red_audio_audio_disconnect", player.channel.guild) if event_type == lavalink.LavalinkEvents.QUEUE_END and status: - player_check = await _players_check() - await _status_check(player_check[1]) + player_check = await self._players_check() + await self._status_check(player_check[1]) if event_type == lavalink.LavalinkEvents.TRACK_EXCEPTION: message_channel = player.fetch("channel") - early_exit = False while True: if player.current in player.queue: player.queue.remove(player.current) @@ -571,24 +571,23 @@ async def _status_check(playing_servers): break if repeat: player.current = None - with contextlib.suppress(Exception): - self._error_counter.setdefault(player.channel.guild.id, 0) - if player.channel.guild.id not in self._error_counter: - self._error_counter[player.channel.guild.id] = 0 - early_exit = await self.error_handler(player) - if early_exit: - self._disconnected_players[player.channel.guild.id] = True - self.bot.dispatch("red_audio_audio_disconnect", player.channel.guild) - self.play_lock[player.channel.guild.id] = False - eq = player.fetch("eq") - player.queue = [] - player.store("playing_song", None) - if eq: - await self.config.custom( - "EQUALIZER", player.channel.guild.id - ).eq_bands.set(eq.bands) - await player.stop() - await player.disconnect() + self._error_counter.setdefault(player.channel.guild.id, 0) + if player.channel.guild.id not in self._error_counter: + self._error_counter[player.channel.guild.id] = 0 + early_exit = await self.increase_error_counter(player) + if early_exit: + self._disconnected_players[player.channel.guild.id] = True + self.play_lock[player.channel.guild.id] = False + eq = player.fetch("eq") + player.queue = [] + player.store("playing_song", None) + if eq: + await self.config.custom("EQUALIZER", player.channel.guild.id).eq_bands.set( + eq.bands + ) + await player.stop() + await player.disconnect() + self.bot.dispatch("red_audio_audio_disconnect", player.channel.guild) if message_channel: message_channel = self.bot.get_channel(message_channel) if early_exit: @@ -604,6 +603,9 @@ async def _status_check(playing_servers): ) await message_channel.send(embed=embed) else: + if player.fetch("error_message") is not None: + with contextlib.suppress(discord.HTTPException): + await player.fetch("error_message").delete() query = audio_dataclasses.Query.process_input(player.current.uri) if player.current and query.is_local: query = audio_dataclasses.Query.process_input(player.current.uri) @@ -625,10 +627,9 @@ async def _status_check(playing_servers): description="{}\n{}".format(extra, description), ) embed.set_footer(text=_("Skipping...")) - await message_channel.send(embed=embed) - if early_exit: - return - await player.skip() + error_message = await message_channel.send(embed=embed) + player.store("error_message", error_message) + await player.skip() async def play_query( self, @@ -3979,9 +3980,7 @@ async def _playlist_download( ] playlist_data[ "playlist" - ] = ( - playlist_songs_backwards_compatible - ) # TODO: Keep new playlists backwards compatible, Remove me in a few releases + ] = playlist_songs_backwards_compatible # TODO: Keep new playlists backwards compatible, Remove me in a few releases playlist_data[ "link" ] = ( From 89afd690aad6c22cee9f162d2492bb6d3c620f6b Mon Sep 17 00:00:00 2001 From: Drapersniper <27962761+drapersniper@users.noreply.github.com> Date: Wed, 4 Dec 2019 17:54:34 +0000 Subject: [PATCH 10/19] Remove auto deletion Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> --- redbot/cogs/audio/audio.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/redbot/cogs/audio/audio.py b/redbot/cogs/audio/audio.py index 8a2a226a611..a17a524075e 100644 --- a/redbot/cogs/audio/audio.py +++ b/redbot/cogs/audio/audio.py @@ -603,9 +603,6 @@ async def event_handler( ) await message_channel.send(embed=embed) else: - if player.fetch("error_message") is not None: - with contextlib.suppress(discord.HTTPException): - await player.fetch("error_message").delete() query = audio_dataclasses.Query.process_input(player.current.uri) if player.current and query.is_local: query = audio_dataclasses.Query.process_input(player.current.uri) @@ -627,8 +624,7 @@ async def event_handler( description="{}\n{}".format(extra, description), ) embed.set_footer(text=_("Skipping...")) - error_message = await message_channel.send(embed=embed) - player.store("error_message", error_message) + await message_channel.send(embed=embed) await player.skip() async def play_query( From 4f7dbaa457487ae4907d2f18565118ea086ed2a7 Mon Sep 17 00:00:00 2001 From: Drapersniper <27962761+drapersniper@users.noreply.github.com> Date: Wed, 4 Dec 2019 19:56:19 +0000 Subject: [PATCH 11/19] black Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> --- redbot/cogs/audio/audio.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/redbot/cogs/audio/audio.py b/redbot/cogs/audio/audio.py index a17a524075e..0e87a5d7b58 100644 --- a/redbot/cogs/audio/audio.py +++ b/redbot/cogs/audio/audio.py @@ -3976,7 +3976,9 @@ async def _playlist_download( ] playlist_data[ "playlist" - ] = playlist_songs_backwards_compatible # TODO: Keep new playlists backwards compatible, Remove me in a few releases + ] = ( + playlist_songs_backwards_compatible + ) # TODO: Keep new playlists backwards compatible, Remove me in a few releases playlist_data[ "link" ] = ( From 4af33cff171d4daded7148ceba8a540826c4f7e6 Mon Sep 17 00:00:00 2001 From: Drapersniper <27962761+drapersniper@users.noreply.github.com> Date: Tue, 17 Dec 2019 22:16:59 +0000 Subject: [PATCH 12/19] Migrate Playlist to DB 3 TODO 1 Migrate Config to Schema 3 without playlists and update get_playlist methods Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> --- redbot/cogs/audio/playlists.py | 235 ++++++++++++++++++++++++++++++--- 1 file changed, 219 insertions(+), 16 deletions(-) diff --git a/redbot/cogs/audio/playlists.py b/redbot/cogs/audio/playlists.py index 1ac62b7fd7a..a96a87637a5 100644 --- a/redbot/cogs/audio/playlists.py +++ b/redbot/cogs/audio/playlists.py @@ -1,18 +1,23 @@ +import json +import os from collections import namedtuple from enum import Enum, unique -from typing import List, Optional, Union +from typing import List, Optional, Union, Tuple +import apsw import discord import lavalink from redbot.core import Config, commands from redbot.core.bot import Red +from redbot.core.data_manager import cog_data_path from redbot.core.i18n import Translator from redbot.core.utils.chat_formatting import humanize_list from .errors import InvalidPlaylistScope, MissingAuthor, MissingGuild, NotAllowed -_config = None -_bot = None +_config: Config = None +_bot: Red = None +_database: "Database" = None __all__ = [ "Playlist", @@ -31,6 +36,105 @@ _ = Translator("Audio", __file__) +_PRAGMA_UPDATE_temp_store = """ +PRAGMA temp_store = 2; +""" +_PRAGMA_UPDATE_journal_mode = """ +PRAGMA journal_mode = wal; +""" +_PRAGMA_UPDATE_wal_autocheckpoint = """ +PRAGMA wal_autocheckpoint; +""" +_PRAGMA_UPDATE_read_uncommitted = """ +PRAGMA read_uncommitted = 1; +""" +_PRAGMA_UPDATE_optimize = """ +PRAGMA optimize = 1; +""" + +_CREATE_TABLE = """ +CREATE TABLE IF NOT EXISTS GLOBAL ( +playlist_id INTEGER PRIMARY KEY, +playlist_name TEXT NOT NULL, +scope_id INTEGER NOT NULL, +author_id INTEGER NOT NULL, +playlist_url TEXT, +tracks BLOB); +""" + +_DROP = """ +DROP TABLE {table}; +""" +_DELETE = """ +DELETE FROM {table} +WHERE + ( + playlist_id = :playlist_id + AND + scope_id = :scope_id + ) +; +""" +_FETCH_ALL = """ +SELECT +playlist_id, +playlist_name, +scope_id, +author_id, +playlist_url, +tracks +FROM {table}; +""" + +_FETCH = """ +SELECT +playlist_id, +playlist_name, +scope_id, +author_id, +playlist_url, +tracks +FROM {table} +WHERE + ( + playlist_id = :playlist_id + AND + scope_id = :scope_id + ) +""" + +_UPSET = """INSERT INTO +{table} + ( + playlist_id, + playlist_name, + scope_id, + author_id, + playlist_url, + tracks + ) +VALUES + ( + :playlist_id, + :playlist_name, + :scope_id, + :author_id, + :playlist_url, + :tracks + ) +ON CONFLICT + ( + playlist_id, + scope_id + ) +DO UPDATE + SET + playlist_name = :playlist_name, + playlist_url = :playlist_url, + tracks = :tracks +; +""" + @unique class PlaylistScope(Enum): @@ -46,12 +150,89 @@ def list(): return list(map(lambda c: c.value, PlaylistScope)) +class Database: + def __init__(self): + self._database = apsw.Connection( + str(cog_data_path(_bot.get_cog("Audio")) / "playlists.db") + ) + self.cursor = self._database.cursor() + self.cursor.execute(_PRAGMA_UPDATE_temp_store) + self.cursor.execute(_PRAGMA_UPDATE_journal_mode) + self.cursor.execute(_PRAGMA_UPDATE_wal_autocheckpoint) + self.cursor.execute(_PRAGMA_UPDATE_read_uncommitted) + for t in ["GLOBAL", "GUILD", "USER"]: + self.cursor.execute(_CREATE_TABLE.format(table=t)) + + @staticmethod + def parse_query(scope: PlaylistScope, query: str): + if scope == PlaylistScope.GLOBAL.value: + table = "GLOBAL" + elif scope == PlaylistScope.GUILD.value: + table = "GUILD" + elif scope == PlaylistScope.USER.value: + table = "USER" + else: + raise + return query.format(table=table) + + def fetch( + self, scope: PlaylistScope, playlist_id: int, scope_id: int + ) -> Tuple[int, str, int, int, str, str]: + query = self.parse_query(scope, _FETCH) + return self.cursor.execute( + query, ({"playlist_id": playlist_id, "scope_id": scope_id}) + ).fetchone() + + def delete(self, scope: PlaylistScope, playlist_id: int, scope_id: int): + query = self.parse_query(scope, _DELETE) + return self.cursor.execute(query, ({"playlist_id": playlist_id, "scope_id": scope_id})) + + def fetch_all(self, scope: PlaylistScope) -> List[Tuple[int, str, int, int, str, str]]: + query = self.parse_query(scope, _FETCH_ALL) + return self.cursor.execute(query).fetchall() + + def drop(self, scope: PlaylistScope): + query = self.parse_query(scope, _DROP) + return self.cursor.execute(query) + + def create_table(self, scope: PlaylistScope): + query = self.parse_query(scope, _CREATE_TABLE) + return self.cursor.execute(query) + + def upsert( + self, + scope: PlaylistScope, + playlist_id: int, + playlist_name: str, + scope_id: int, + author_id: int, + playlist_url: str, + tracks: List[dict], + ): + query = self.parse_query(scope, _UPSET) + self.cursor.execute( + query, + ( + { + "playlist_id": playlist_id, + "playlist_name": playlist_name, + "scope_id": scope_id, + "author_id": author_id, + "playlist_url": playlist_url, + "tracks": json.dumps(tracks), + } + ), + ) + + def _pass_config_to_playlist(config: Config, bot: Red): - global _config, _bot + global _config, _bot, _database if _config is None: _config = config if _bot is None: _bot = bot + if _database is None: + _database = Database() def standardize_scope(scope) -> str: @@ -92,15 +273,15 @@ def _prepare_config_scope( scope = standardize_scope(scope) if scope == PlaylistScope.GLOBAL.value: - config_scope = [PlaylistScope.GLOBAL.value] + config_scope = [PlaylistScope.GLOBAL.value, _bot.user.id] elif scope == PlaylistScope.USER.value: if author is None: raise MissingAuthor("Invalid author for user scope.") - config_scope = [PlaylistScope.USER.value, str(getattr(author, "id", author))] + config_scope = [PlaylistScope.USER.value, getattr(author, "id", author)] else: if guild is None: raise MissingGuild("Invalid guild for guild scope.") - config_scope = [PlaylistScope.GUILD.value, str(getattr(guild, "id", guild))] + config_scope = [PlaylistScope.GUILD.value, getattr(guild, "id", guild)] return config_scope @@ -132,6 +313,14 @@ def __init__( self.tracks = tracks or [] self.tracks_obj = [lavalink.Track(data=track) for track in self.tracks] + def _get_scope_id(self): + if self.scope == PlaylistScope.GLOBAL.value: + return self.bot.user.id + elif self.scope == PlaylistScope.USER.value: + return self.author + else: + return self.guild.id + async def edit(self, data: dict): """ Edits a Playlist. @@ -146,8 +335,22 @@ async def edit(self, data: dict): for item in list(data.keys()): setattr(self, item, data[item]) + await self.save() - await _config.custom(*self.config_scope, str(self.id)).set(self.to_json()) + async def save(self): + """ + Saves a Playlist. + """ + scope, scope_id = self.config_scope + _database.upsert( + scope, + playlist_id=int(self.id), + playlist_name=self.name, + scope_id=scope_id, + author_id=self.author, + playlist_url=self.url, + tracks=self.tracks, + ) def to_json(self) -> dict: """Transform the object to a dict. @@ -216,7 +419,7 @@ async def from_json(cls, bot: Red, scope: str, playlist_number: int, data: dict, ) -async def get_playlist( +async def get_playlist( # TODO: convert to SQL playlist_number: int, scope: str, bot: Red, @@ -262,7 +465,7 @@ async def get_playlist( ) -async def get_all_playlist( +async def get_all_playlist( # TODO: convert to SQL scope: str, bot: Red, guild: Union[discord.Guild, int] = None, @@ -360,10 +563,7 @@ async def create_playlist( playlist = Playlist( ctx.bot, scope, author.id, ctx.message.id, playlist_name, playlist_url, tracks, ctx.guild ) - - await _config.custom(*_prepare_config_scope(scope, author, guild), str(ctx.message.id)).set( - playlist.to_json() - ) + await playlist.save() return playlist @@ -393,7 +593,9 @@ async def reset_playlist( `MissingAuthor` Trying to access the User scope without an user id. """ - await _config.custom(*_prepare_config_scope(scope, author, guild)).clear() + scope, scope_id = _prepare_config_scope(scope, author, guild) + _database.drop(scope) + _database.create_table(scope) async def delete_playlist( @@ -425,4 +627,5 @@ async def delete_playlist( `MissingAuthor` Trying to access the User scope without an user id. """ - await _config.custom(*_prepare_config_scope(scope, author, guild), str(playlist_id)).clear() + scope, scope_id = _prepare_config_scope(scope, author, guild) + _database.delete(scope, int(playlist_id), scope_id) From 6bce47c5a40a501cc3c4ce0221810aa5abe2e2d3 Mon Sep 17 00:00:00 2001 From: Drapersniper <27962761+drapersniper@users.noreply.github.com> Date: Tue, 17 Dec 2019 22:17:55 +0000 Subject: [PATCH 13/19] Revert "Migrate Playlist to DB 3 TODO 1 Migrate Config to Schema 3 without playlists and update get_playlist methods" This reverts commit 4af33cff Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> --- redbot/cogs/audio/playlists.py | 235 +++------------------------------ 1 file changed, 16 insertions(+), 219 deletions(-) diff --git a/redbot/cogs/audio/playlists.py b/redbot/cogs/audio/playlists.py index a96a87637a5..1ac62b7fd7a 100644 --- a/redbot/cogs/audio/playlists.py +++ b/redbot/cogs/audio/playlists.py @@ -1,23 +1,18 @@ -import json -import os from collections import namedtuple from enum import Enum, unique -from typing import List, Optional, Union, Tuple +from typing import List, Optional, Union -import apsw import discord import lavalink from redbot.core import Config, commands from redbot.core.bot import Red -from redbot.core.data_manager import cog_data_path from redbot.core.i18n import Translator from redbot.core.utils.chat_formatting import humanize_list from .errors import InvalidPlaylistScope, MissingAuthor, MissingGuild, NotAllowed -_config: Config = None -_bot: Red = None -_database: "Database" = None +_config = None +_bot = None __all__ = [ "Playlist", @@ -36,105 +31,6 @@ _ = Translator("Audio", __file__) -_PRAGMA_UPDATE_temp_store = """ -PRAGMA temp_store = 2; -""" -_PRAGMA_UPDATE_journal_mode = """ -PRAGMA journal_mode = wal; -""" -_PRAGMA_UPDATE_wal_autocheckpoint = """ -PRAGMA wal_autocheckpoint; -""" -_PRAGMA_UPDATE_read_uncommitted = """ -PRAGMA read_uncommitted = 1; -""" -_PRAGMA_UPDATE_optimize = """ -PRAGMA optimize = 1; -""" - -_CREATE_TABLE = """ -CREATE TABLE IF NOT EXISTS GLOBAL ( -playlist_id INTEGER PRIMARY KEY, -playlist_name TEXT NOT NULL, -scope_id INTEGER NOT NULL, -author_id INTEGER NOT NULL, -playlist_url TEXT, -tracks BLOB); -""" - -_DROP = """ -DROP TABLE {table}; -""" -_DELETE = """ -DELETE FROM {table} -WHERE - ( - playlist_id = :playlist_id - AND - scope_id = :scope_id - ) -; -""" -_FETCH_ALL = """ -SELECT -playlist_id, -playlist_name, -scope_id, -author_id, -playlist_url, -tracks -FROM {table}; -""" - -_FETCH = """ -SELECT -playlist_id, -playlist_name, -scope_id, -author_id, -playlist_url, -tracks -FROM {table} -WHERE - ( - playlist_id = :playlist_id - AND - scope_id = :scope_id - ) -""" - -_UPSET = """INSERT INTO -{table} - ( - playlist_id, - playlist_name, - scope_id, - author_id, - playlist_url, - tracks - ) -VALUES - ( - :playlist_id, - :playlist_name, - :scope_id, - :author_id, - :playlist_url, - :tracks - ) -ON CONFLICT - ( - playlist_id, - scope_id - ) -DO UPDATE - SET - playlist_name = :playlist_name, - playlist_url = :playlist_url, - tracks = :tracks -; -""" - @unique class PlaylistScope(Enum): @@ -150,89 +46,12 @@ def list(): return list(map(lambda c: c.value, PlaylistScope)) -class Database: - def __init__(self): - self._database = apsw.Connection( - str(cog_data_path(_bot.get_cog("Audio")) / "playlists.db") - ) - self.cursor = self._database.cursor() - self.cursor.execute(_PRAGMA_UPDATE_temp_store) - self.cursor.execute(_PRAGMA_UPDATE_journal_mode) - self.cursor.execute(_PRAGMA_UPDATE_wal_autocheckpoint) - self.cursor.execute(_PRAGMA_UPDATE_read_uncommitted) - for t in ["GLOBAL", "GUILD", "USER"]: - self.cursor.execute(_CREATE_TABLE.format(table=t)) - - @staticmethod - def parse_query(scope: PlaylistScope, query: str): - if scope == PlaylistScope.GLOBAL.value: - table = "GLOBAL" - elif scope == PlaylistScope.GUILD.value: - table = "GUILD" - elif scope == PlaylistScope.USER.value: - table = "USER" - else: - raise - return query.format(table=table) - - def fetch( - self, scope: PlaylistScope, playlist_id: int, scope_id: int - ) -> Tuple[int, str, int, int, str, str]: - query = self.parse_query(scope, _FETCH) - return self.cursor.execute( - query, ({"playlist_id": playlist_id, "scope_id": scope_id}) - ).fetchone() - - def delete(self, scope: PlaylistScope, playlist_id: int, scope_id: int): - query = self.parse_query(scope, _DELETE) - return self.cursor.execute(query, ({"playlist_id": playlist_id, "scope_id": scope_id})) - - def fetch_all(self, scope: PlaylistScope) -> List[Tuple[int, str, int, int, str, str]]: - query = self.parse_query(scope, _FETCH_ALL) - return self.cursor.execute(query).fetchall() - - def drop(self, scope: PlaylistScope): - query = self.parse_query(scope, _DROP) - return self.cursor.execute(query) - - def create_table(self, scope: PlaylistScope): - query = self.parse_query(scope, _CREATE_TABLE) - return self.cursor.execute(query) - - def upsert( - self, - scope: PlaylistScope, - playlist_id: int, - playlist_name: str, - scope_id: int, - author_id: int, - playlist_url: str, - tracks: List[dict], - ): - query = self.parse_query(scope, _UPSET) - self.cursor.execute( - query, - ( - { - "playlist_id": playlist_id, - "playlist_name": playlist_name, - "scope_id": scope_id, - "author_id": author_id, - "playlist_url": playlist_url, - "tracks": json.dumps(tracks), - } - ), - ) - - def _pass_config_to_playlist(config: Config, bot: Red): - global _config, _bot, _database + global _config, _bot if _config is None: _config = config if _bot is None: _bot = bot - if _database is None: - _database = Database() def standardize_scope(scope) -> str: @@ -273,15 +92,15 @@ def _prepare_config_scope( scope = standardize_scope(scope) if scope == PlaylistScope.GLOBAL.value: - config_scope = [PlaylistScope.GLOBAL.value, _bot.user.id] + config_scope = [PlaylistScope.GLOBAL.value] elif scope == PlaylistScope.USER.value: if author is None: raise MissingAuthor("Invalid author for user scope.") - config_scope = [PlaylistScope.USER.value, getattr(author, "id", author)] + config_scope = [PlaylistScope.USER.value, str(getattr(author, "id", author))] else: if guild is None: raise MissingGuild("Invalid guild for guild scope.") - config_scope = [PlaylistScope.GUILD.value, getattr(guild, "id", guild)] + config_scope = [PlaylistScope.GUILD.value, str(getattr(guild, "id", guild))] return config_scope @@ -313,14 +132,6 @@ def __init__( self.tracks = tracks or [] self.tracks_obj = [lavalink.Track(data=track) for track in self.tracks] - def _get_scope_id(self): - if self.scope == PlaylistScope.GLOBAL.value: - return self.bot.user.id - elif self.scope == PlaylistScope.USER.value: - return self.author - else: - return self.guild.id - async def edit(self, data: dict): """ Edits a Playlist. @@ -335,22 +146,8 @@ async def edit(self, data: dict): for item in list(data.keys()): setattr(self, item, data[item]) - await self.save() - async def save(self): - """ - Saves a Playlist. - """ - scope, scope_id = self.config_scope - _database.upsert( - scope, - playlist_id=int(self.id), - playlist_name=self.name, - scope_id=scope_id, - author_id=self.author, - playlist_url=self.url, - tracks=self.tracks, - ) + await _config.custom(*self.config_scope, str(self.id)).set(self.to_json()) def to_json(self) -> dict: """Transform the object to a dict. @@ -419,7 +216,7 @@ async def from_json(cls, bot: Red, scope: str, playlist_number: int, data: dict, ) -async def get_playlist( # TODO: convert to SQL +async def get_playlist( playlist_number: int, scope: str, bot: Red, @@ -465,7 +262,7 @@ async def get_playlist( # TODO: convert to SQL ) -async def get_all_playlist( # TODO: convert to SQL +async def get_all_playlist( scope: str, bot: Red, guild: Union[discord.Guild, int] = None, @@ -563,7 +360,10 @@ async def create_playlist( playlist = Playlist( ctx.bot, scope, author.id, ctx.message.id, playlist_name, playlist_url, tracks, ctx.guild ) - await playlist.save() + + await _config.custom(*_prepare_config_scope(scope, author, guild), str(ctx.message.id)).set( + playlist.to_json() + ) return playlist @@ -593,9 +393,7 @@ async def reset_playlist( `MissingAuthor` Trying to access the User scope without an user id. """ - scope, scope_id = _prepare_config_scope(scope, author, guild) - _database.drop(scope) - _database.create_table(scope) + await _config.custom(*_prepare_config_scope(scope, author, guild)).clear() async def delete_playlist( @@ -627,5 +425,4 @@ async def delete_playlist( `MissingAuthor` Trying to access the User scope without an user id. """ - scope, scope_id = _prepare_config_scope(scope, author, guild) - _database.delete(scope, int(playlist_id), scope_id) + await _config.custom(*_prepare_config_scope(scope, author, guild), str(playlist_id)).clear() From bce139dae524a41f28bc0fb46fb6a76c3d3d3dad Mon Sep 17 00:00:00 2001 From: Drapersniper <27962761+drapersniper@users.noreply.github.com> Date: Thu, 19 Dec 2019 20:53:11 +0000 Subject: [PATCH 14/19] Allow removing tracks from queue by URL Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> --- redbot/cogs/audio/audio.py | 57 +++++++++++++++++++++++++------------- 1 file changed, 38 insertions(+), 19 deletions(-) diff --git a/redbot/cogs/audio/audio.py b/redbot/cogs/audio/audio.py index 209a571bbc7..04a256eb48e 100644 --- a/redbot/cogs/audio/audio.py +++ b/redbot/cogs/audio/audio.py @@ -5699,8 +5699,8 @@ async def repeat(self, ctx: commands.Context): @commands.command() @commands.guild_only() @commands.bot_has_permissions(embed_links=True) - async def remove(self, ctx: commands.Context, index: int): - """Remove a specific track number from the queue.""" + async def remove(self, ctx: commands.Context, index_or_url: Union[int, str]): + """Remove a specific track number or url from the queue.""" dj_enabled = await self.config.guild(ctx.guild).dj_enabled() if not self._player_check(ctx): return await self._embed_msg(ctx, _("Nothing playing.")) @@ -5711,29 +5711,48 @@ async def remove(self, ctx: commands.Context, index: int): if not await self._can_instaskip(ctx, ctx.author): return await self._embed_msg(ctx, _("You need the DJ role to remove tracks.")) if ( - not ctx.author.voice or ctx.author.voice.channel != player.channel + not ctx.author.voice or ctx.author.voice.channel != player.channel ) and not await self._can_instaskip(ctx, ctx.author): return await self._embed_msg( ctx, _("You must be in the voice channel to manage the queue.") ) - if index > len(player.queue) or index < 1: - return await self._embed_msg( - ctx, _("Song number must be greater than 1 and within the queue limit.") - ) - index -= 1 - removed = player.queue.pop(index) - query = audio_dataclasses.Query.process_input(removed.uri) - if query.is_local: - local_path = audio_dataclasses.LocalPath(removed.uri).to_string_hidden() - if removed.title == "Unknown title": - removed_title = local_path + if isinstance(index_or_url, int): + if index_or_url > len(player.queue) or index_or_url < 1: + return await self._embed_msg( + ctx, _("Song number must be greater than 1 and within the queue limit.") + ) + index_or_url -= 1 + removed = player.queue.pop(index_or_url) + query = audio_dataclasses.Query.process_input(removed.uri) + if query.is_local: + local_path = audio_dataclasses.LocalPath(removed.uri).to_string_hidden() + if removed.title == "Unknown title": + removed_title = local_path + else: + removed_title = "{} - {}\n{}".format(removed.author, removed.title, local_path) else: - removed_title = "{} - {}\n{}".format(removed.author, removed.title, local_path) + removed_title = removed.title + await self._embed_msg( + ctx, _("Removed {track} from the queue.").format(track=removed_title) + ) else: - removed_title = removed.title - await self._embed_msg( - ctx, _("Removed {track} from the queue.").format(track=removed_title) - ) + clean_tracks = [] + removed_tracks = 0 + for track in player.queue: + if track.uri != index_or_url: + clean_tracks.append(track) + else: + removed_tracks += 1 + player.queue = clean_tracks + if removed_tracks == 0: + await self._embed_msg(ctx, _("Removed 0 tracks.")) + else: + await self._embed_msg( + ctx, + _("Removed {removed_tracks} tracks from queue.").format( + removed_tracks=removed_tracks, member=ctx.author + ), + ) @commands.command() @commands.guild_only() From 4783e832964bb218e56a8cf9b63030a9a21c5b43 Mon Sep 17 00:00:00 2001 From: Drapersniper <27962761+drapersniper@users.noreply.github.com> Date: Thu, 19 Dec 2019 20:59:15 +0000 Subject: [PATCH 15/19] Words matter Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> --- redbot/cogs/audio/audio.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/redbot/cogs/audio/audio.py b/redbot/cogs/audio/audio.py index 04a256eb48e..9b9d79546c0 100644 --- a/redbot/cogs/audio/audio.py +++ b/redbot/cogs/audio/audio.py @@ -5711,7 +5711,7 @@ async def remove(self, ctx: commands.Context, index_or_url: Union[int, str]): if not await self._can_instaskip(ctx, ctx.author): return await self._embed_msg(ctx, _("You need the DJ role to remove tracks.")) if ( - not ctx.author.voice or ctx.author.voice.channel != player.channel + not ctx.author.voice or ctx.author.voice.channel != player.channel ) and not await self._can_instaskip(ctx, ctx.author): return await self._embed_msg( ctx, _("You must be in the voice channel to manage the queue.") @@ -5745,13 +5745,16 @@ async def remove(self, ctx: commands.Context, index_or_url: Union[int, str]): removed_tracks += 1 player.queue = clean_tracks if removed_tracks == 0: - await self._embed_msg(ctx, _("Removed 0 tracks.")) + await self._embed_msg( + ctx, _("Removed 0 tracks, nothing matches the URL provided.") + ) else: await self._embed_msg( ctx, - _("Removed {removed_tracks} tracks from queue.").format( - removed_tracks=removed_tracks, member=ctx.author - ), + _( + "Removed {removed_tracks} tracks from queue " + "which matched the URL provided." + ).format(removed_tracks=removed_tracks, member=ctx.author), ) @commands.command() From 28fec7b8a728adfa47e6f81a4b46e2f534fbec39 Mon Sep 17 00:00:00 2001 From: Drapersniper <27962761+drapersniper@users.noreply.github.com> Date: Thu, 19 Dec 2019 21:03:30 +0000 Subject: [PATCH 16/19] *sigh* Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> --- redbot/cogs/audio/audio.py | 58 ++++++++++++-------------------------- 1 file changed, 18 insertions(+), 40 deletions(-) diff --git a/redbot/cogs/audio/audio.py b/redbot/cogs/audio/audio.py index 9b9d79546c0..209a571bbc7 100644 --- a/redbot/cogs/audio/audio.py +++ b/redbot/cogs/audio/audio.py @@ -5699,8 +5699,8 @@ async def repeat(self, ctx: commands.Context): @commands.command() @commands.guild_only() @commands.bot_has_permissions(embed_links=True) - async def remove(self, ctx: commands.Context, index_or_url: Union[int, str]): - """Remove a specific track number or url from the queue.""" + async def remove(self, ctx: commands.Context, index: int): + """Remove a specific track number from the queue.""" dj_enabled = await self.config.guild(ctx.guild).dj_enabled() if not self._player_check(ctx): return await self._embed_msg(ctx, _("Nothing playing.")) @@ -5716,46 +5716,24 @@ async def remove(self, ctx: commands.Context, index_or_url: Union[int, str]): return await self._embed_msg( ctx, _("You must be in the voice channel to manage the queue.") ) - if isinstance(index_or_url, int): - if index_or_url > len(player.queue) or index_or_url < 1: - return await self._embed_msg( - ctx, _("Song number must be greater than 1 and within the queue limit.") - ) - index_or_url -= 1 - removed = player.queue.pop(index_or_url) - query = audio_dataclasses.Query.process_input(removed.uri) - if query.is_local: - local_path = audio_dataclasses.LocalPath(removed.uri).to_string_hidden() - if removed.title == "Unknown title": - removed_title = local_path - else: - removed_title = "{} - {}\n{}".format(removed.author, removed.title, local_path) - else: - removed_title = removed.title - await self._embed_msg( - ctx, _("Removed {track} from the queue.").format(track=removed_title) + if index > len(player.queue) or index < 1: + return await self._embed_msg( + ctx, _("Song number must be greater than 1 and within the queue limit.") ) - else: - clean_tracks = [] - removed_tracks = 0 - for track in player.queue: - if track.uri != index_or_url: - clean_tracks.append(track) - else: - removed_tracks += 1 - player.queue = clean_tracks - if removed_tracks == 0: - await self._embed_msg( - ctx, _("Removed 0 tracks, nothing matches the URL provided.") - ) + index -= 1 + removed = player.queue.pop(index) + query = audio_dataclasses.Query.process_input(removed.uri) + if query.is_local: + local_path = audio_dataclasses.LocalPath(removed.uri).to_string_hidden() + if removed.title == "Unknown title": + removed_title = local_path else: - await self._embed_msg( - ctx, - _( - "Removed {removed_tracks} tracks from queue " - "which matched the URL provided." - ).format(removed_tracks=removed_tracks, member=ctx.author), - ) + removed_title = "{} - {}\n{}".format(removed.author, removed.title, local_path) + else: + removed_title = removed.title + await self._embed_msg( + ctx, _("Removed {track} from the queue.").format(track=removed_title) + ) @commands.command() @commands.guild_only() From 46ba8ff49b693b0a04ee05e036f7a0085a249f68 Mon Sep 17 00:00:00 2001 From: Drapersniper <27962761+drapersniper@users.noreply.github.com> Date: Thu, 19 Dec 2019 21:11:56 +0000 Subject: [PATCH 17/19] chore Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> --- changelog.d/audio/3201.feature.1.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/audio/3201.feature.1.rst diff --git a/changelog.d/audio/3201.feature.1.rst b/changelog.d/audio/3201.feature.1.rst new file mode 100644 index 00000000000..7c5beb4ca35 --- /dev/null +++ b/changelog.d/audio/3201.feature.1.rst @@ -0,0 +1 @@ +``[p]remove`` command now accepts an URL or Index, if an URL is used it will remove all tracks in the queue with that URL. \ No newline at end of file From 1c7b7dee264e33ea365107915de2ab794655aa7d Mon Sep 17 00:00:00 2001 From: Drapersniper <27962761+drapersniper@users.noreply.github.com> Date: Fri, 20 Dec 2019 08:14:39 +0000 Subject: [PATCH 18/19] arghhh CONFLICTS Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> --- redbot/cogs/audio/audio.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/redbot/cogs/audio/audio.py b/redbot/cogs/audio/audio.py index 381e0bb8d7b..3fd62961c5d 100644 --- a/redbot/cogs/audio/audio.py +++ b/redbot/cogs/audio/audio.py @@ -16,6 +16,7 @@ import aiohttp import discord import lavalink +import math from fuzzywuzzy import process import redbot.core @@ -6351,7 +6352,8 @@ async def _can_instaskip(self, ctx: commands.Context, member: discord.Member): return False - async def _is_alone(self, ctx: commands.Context): + @staticmethod + async def _is_alone(ctx: commands.Context): channel_members = rgetattr(ctx, "guild.me.voice.channel.members", []) nonbots = sum(m.id != ctx.author.id for m in channel_members if not m.bot) return nonbots < 1 From 224e47fd393b2ddb140ef11f98bb9b021262c0b9 Mon Sep 17 00:00:00 2001 From: Drapersniper <27962761+drapersniper@users.noreply.github.com> Date: Sun, 22 Dec 2019 13:16:55 +0000 Subject: [PATCH 19/19] Remove unused config default Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> --- redbot/cogs/audio/audio.py | 1 - 1 file changed, 1 deletion(-) diff --git a/redbot/cogs/audio/audio.py b/redbot/cogs/audio/audio.py index 3fd62961c5d..27fd58d1e16 100644 --- a/redbot/cogs/audio/audio.py +++ b/redbot/cogs/audio/audio.py @@ -122,7 +122,6 @@ def __init__(self, bot): status=False, use_external_lavalink=False, restrict=True, - current_version=redbot.core.VersionInfo.from_str(__version__).to_json(), localpath=str(cog_data_path(raw_name="Audio")), **self._default_lavalink_settings, )