From fd6c791b05a8aa82ba6d831b899eb901fe8b687b Mon Sep 17 00:00:00 2001 From: TheerapakG Date: Sat, 15 Sep 2018 00:07:37 +0700 Subject: [PATCH 1/9] minor typo fix (#1729) --- musicbot/bot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/musicbot/bot.py b/musicbot/bot.py index 8c5b90e088..12a10b5161 100644 --- a/musicbot/bot.py +++ b/musicbot/bot.py @@ -1000,7 +1000,7 @@ async def on_ready(self): self.config.autojoin_channels.difference_update(invalids) if chlist: - log.info("Autojoining voice chanels:") + log.info("Autojoining voice channels:") [log.info(' - {}/{}'.format(ch.guild.name.strip(), ch.name.strip())) for ch in chlist if ch] else: log.info("Not autojoining any voice channels") From c35adf26eb2cd946f134e1400bbb662a33e65b9d Mon Sep 17 00:00:00 2001 From: TheerapakG Date: Mon, 17 Sep 2018 04:31:57 +0700 Subject: [PATCH 2/9] Various things implemented, I guess? (see desc) different modes for different guilds can now exist and now it save mode, too! and a redundant check got remove and nothing else I don't even know what what I'm saying right now. --- musicbot/bot.py | 360 ++++++++++++++++++++++++++------------------- musicbot/player.py | 1 + 2 files changed, 213 insertions(+), 148 deletions(-) diff --git a/musicbot/bot.py b/musicbot/bot.py index 30b8680f67..c31fae2d57 100644 --- a/musicbot/bot.py +++ b/musicbot/bot.py @@ -1,5 +1,6 @@ import os import sys +import json import time import shlex import shutil @@ -77,9 +78,6 @@ def __init__(self, config_file=None, perms_file=None): self.autoplaylist = load_file(self.config.auto_playlist_file) self.autostream = load_file(self.config.auto_stream_file) - # TODO: [TheerapakG] load this / save this - self.auto_toggle = 'playlist' - self.aiolocks = defaultdict(asyncio.Lock) self.downloader = downloader.Downloader(download_folder='audio_cache') @@ -315,7 +313,8 @@ def _autopause(player): if not player.playlist.entries: await self.on_player_finished_playing(player) - except Exception: + except Exception as e: + log.debug(str(e)) log.debug("Error joining {0.guild.name}/{0.name}".format(channel), exc_info=True) log.error("Failed to join {0.guild.name}/{0.name}".format(channel)) @@ -451,6 +450,7 @@ async def get_player(self, channel, create=False, *, deserialize=False) -> Music if deserialize: voice_client = await self.get_voice_client(channel) player = await self.deserialize_queue(guild, voice_client) + player.auto_mode = await self.deserialize_json(guild, dir = 'data/%s/mode.json') if player: log.debug("Created player via deserialization for guild %s with %s entries", guild.id, len(player.playlist)) @@ -559,174 +559,186 @@ def _autopause(player): player.pause() self.server_specific_data[player.voice_client.channel.guild]['auto_paused'] = True + if not player.auto_mode: + player.auto_mode = dict() + player.auto_mode['mode'] = self.config.auto_mode + if(player.auto_mode['mode'] == 'toggle'): + player.auto_mode['auto_toggle'] = self.playlisttype[0] + await self.serialize_json(player.auto_mode, player.voice_client.channel.guild, dir = 'data/%s/mode.json') + if not player.playlist.entries and not player.current_entry and (self.config.auto_playlist or self.config.auto_stream): - if self.config.auto_playlist or self.config.auto_stream: - if not player.autoplaylist: - player.autoplaylist = list() - if self.config.auto_playlist: - if not self.autoplaylist: - # TODO: When I add playlist expansion, make sure that's not happening during this check - log.warning("No playable songs in the autoplaylist, disabling.") - if self.auto_toggle == 'playlist' and len(self.playlisttype) > 1: + if not player.autoplaylist: + player.autoplaylist = list() + if self.config.auto_playlist: + if not self.autoplaylist: + # TODO: When I add playlist expansion, make sure that's not happening during this check + log.warning("No playable songs in the autoplaylist, disabling.") + if(player.auto_mode['mode'] == 'toggle'): + if player.auto_mode['auto_toggle'] == 'playlist' and len(self.playlisttype) > 1: try: - i = self.playlisttype.index(self.auto_toggle) + 1 + i = self.playlisttype.index(player.auto_mode['auto_toggle']) + 1 if i == len(self.playlisttype): i = 0 except ValueError: i = 0 - self.auto_toggle = self.playlisttype[i] - self.playlisttype.remove('playlist') - self.config.auto_playlist = False - else: - if self.config.auto_mode == 'merge' or (self.config.auto_mode == 'toggle' and self.auto_toggle == 'playlist'): - log.debug("No content in current autoplaylist. Filling with new music...") - player.autoplaylist.extend([(url, "default") for url in list(self.autoplaylist)]) - if self.config.auto_stream: - if not self.autostream: - log.warning("No playable songs in the autostream, disabling.") - if self.auto_toggle == 'stream' and len(self.playlisttype) > 1: + player.auto_mode['auto_toggle'] = self.playlisttype[i] + self.playlisttype.remove('playlist') + await self.serialize_json(player.auto_mode, player.voice_client.channel.guild, dir = 'data/%s/mode.json') + self.config.auto_playlist = False + else: + if player.auto_mode['mode'] == 'merge' or (player.auto_mode['mode'] == 'toggle' and player.auto_mode['auto_toggle'] == 'playlist'): + log.debug("No content in current autoplaylist. Filling with new music...") + player.autoplaylist.extend([(url, "default") for url in list(self.autoplaylist)]) + if self.config.auto_stream: + if not self.autostream: + log.warning("No playable songs in the autostream, disabling.") + if(player.auto_mode['mode'] == 'toggle'): + if player.auto_mode['auto_toggle'] == 'stream' and len(self.playlisttype) > 1: try: - i = self.playlisttype.index(self.auto_toggle) + 1 + i = self.playlisttype.index(player.auto_mode['auto_toggle']) + 1 if i == len(self.playlisttype): i = 0 except ValueError: i = 0 - self.auto_toggle = self.playlisttype[i] - self.playlisttype.remove('stream') - self.config.auto_stream = False - else: - if self.config.auto_mode == 'merge' or (self.config.auto_mode == 'toggle' and self.auto_toggle == 'stream'): - log.debug("No content in current autostream. Filling with new music...") - player.autoplaylist.extend([(url, "stream") for url in list(self.autostream)]) - - while player.autoplaylist: - if self.config.auto_playlist_stream_random: - random.shuffle(player.autoplaylist) - song_url = random.choice(player.autoplaylist) + player.auto_mode['auto_toggle'] = self.playlisttype[i] + self.playlisttype.remove('stream') + await self.serialize_json(player.auto_mode, player.voice_client.channel.guild, dir = 'data/%s/mode.json') + self.config.auto_stream = False else: - song_url = player.autoplaylist[0] - player.autoplaylist.remove(song_url) - - if song_url[1] == "default": + if player.auto_mode['mode'] == 'merge' or (player.auto_mode['mode'] == 'toggle' and player.auto_mode['auto_toggle'] == 'stream'): + log.debug("No content in current autostream. Filling with new music...") + player.autoplaylist.extend([(url, "stream") for url in list(self.autostream)]) + + while player.autoplaylist: + if self.config.auto_playlist_stream_random: + random.shuffle(player.autoplaylist) + song_url = random.choice(player.autoplaylist) + else: + song_url = player.autoplaylist[0] + player.autoplaylist.remove(song_url) - info = {} + if song_url[1] == "default": - try: - info = await self.downloader.extract_info(player.playlist.loop, song_url[0], download=False, process=False) - except downloader.youtube_dl.utils.DownloadError as e: - if 'YouTube said:' in e.args[0]: - # url is bork, remove from list and put in removed list - log.error("Error processing youtube url:\n{}".format(e.args[0])) + info = {} - else: - # Probably an error from a different extractor, but I've only seen youtube's - log.error("Error processing \"{url}\": {ex}".format(url=song_url[0], ex=e)) - - await self.remove_from_autoplaylist(song_url[0], ex=e, delete_from_ap=self.config.remove_ap) - continue + try: + info = await self.downloader.extract_info(player.playlist.loop, song_url[0], download=False, process=False) + except downloader.youtube_dl.utils.DownloadError as e: + if 'YouTube said:' in e.args[0]: + # url is bork, remove from list and put in removed list + log.error("Error processing youtube url:\n{}".format(e.args[0])) - except Exception as e: + else: + # Probably an error from a different extractor, but I've only seen youtube's log.error("Error processing \"{url}\": {ex}".format(url=song_url[0], ex=e)) - log.exception() - self.autoplaylist.remove(song_url[0]) - continue + await self.remove_from_autoplaylist(song_url[0], ex=e, delete_from_ap=self.config.remove_ap) + continue + + except Exception as e: + log.error("Error processing \"{url}\": {ex}".format(url=song_url[0], ex=e)) + log.exception() - if info.get('entries', None): # or .get('_type', '') == 'playlist' - log.debug("Playlist found but is unsupported at this time, skipping.") - # TODO: Playlist expansion + self.autoplaylist.remove(song_url[0]) + continue - # Do I check the initial conditions again? - # not (not player.playlist.entries and not player.current_entry and self.config.auto_playlist) + if info.get('entries', None): # or .get('_type', '') == 'playlist' + log.debug("Playlist found but is unsupported at this time, skipping.") + # TODO: Playlist expansion - if self.config.auto_pause: - player.once('play', lambda player, **_: _autopause(player)) + # Do I check the initial conditions again? + # not (not player.playlist.entries and not player.current_entry and self.config.auto_playlist) - try: - player.auto_state.once('change-entry', 'finishedentry') - player.auto_state.once('play', 'autoentry') - await player.playlist.add_entry(song_url[0], channel=None, author=None) - except exceptions.ExtractionError as e: - log.error("Error adding song from autoplaylist: {}".format(e)) - log.debug('', exc_info=True) - continue + if self.config.auto_pause: + player.once('play', lambda player, **_: _autopause(player)) - break + try: + player.auto_state.once('change-entry', 'finishedentry') + player.auto_state.once('play', 'autoentry') + await player.playlist.add_entry(song_url[0], channel=None, author=None) + except exceptions.ExtractionError as e: + log.error("Error adding song from autoplaylist: {}".format(e)) + log.debug('', exc_info=True) + continue + + break - elif song_url[1] == "stream": + elif song_url[1] == "stream": - info = {'extractor': None} + info = {'extractor': None} - try: - info = await self.downloader.extract_info(player.playlist.loop, song_url[0], download=False, process=False) - except downloader.youtube_dl.utils.DownloadError as e: + try: + info = await self.downloader.extract_info(player.playlist.loop, song_url[0], download=False, process=False) + except downloader.youtube_dl.utils.DownloadError as e: - if e.exc_info[0] == URLError: - if os.path.exists(os.path.abspath(song_url[0])): - await self.remove_from_autostream(song_url[0], ex=downloader.youtube_dl.utils.ExtractionError("This is not a stream, this is a file path."), delete_from_as=self.config.remove_as) - continue - - else: # it might be a file path that just doesn't exist - await self.remove_from_autostream(song_url[0], ex=downloader.youtube_dl.utils.ExtractionError("Invalid input: {0.exc_info[0]}: {0.exc_info[1].reason}".format(e)), delete_from_as=self.config.remove_as) - continue - - else: - # traceback.print_exc() - await self.remove_from_autostream(song_url[0], ex=downloader.youtube_dl.utils.ExtractionError("Unknown error: {}".format(e)), delete_from_as=self.config.remove_as) + if e.exc_info[0] == URLError: + if os.path.exists(os.path.abspath(song_url[0])): + await self.remove_from_autostream(song_url[0], ex=downloader.youtube_dl.utils.ExtractionError("This is not a stream, this is a file path."), delete_from_as=self.config.remove_as) + continue + + else: # it might be a file path that just doesn't exist + await self.remove_from_autostream(song_url[0], ex=downloader.youtube_dl.utils.ExtractionError("Invalid input: {0.exc_info[0]}: {0.exc_info[1].reason}".format(e)), delete_from_as=self.config.remove_as) continue - - except Exception as e: - log.error('Could not extract information from {} ({}), falling back to direct'.format(song_url[0], e), exc_info=True) - if info.get('is_live') is None and info.get('extractor', None) is not 'generic': # wew hacky - await self.remove_from_autostream(song_url[0], ex=downloader.youtube_dl.utils.ExtractionError("This is not a stream."), delete_from_as=self.config.remove_as) + else: + # traceback.print_exc() + await self.remove_from_autostream(song_url[0], ex=downloader.youtube_dl.utils.ExtractionError("Unknown error: {}".format(e)), delete_from_as=self.config.remove_as) continue + + except Exception as e: + log.error('Could not extract information from {} ({}), falling back to direct'.format(song_url[0], e), exc_info=True) - if self.config.auto_pause: - player.once('play', lambda player, **_: _autopause(player)) + if info.get('is_live') is None and info.get('extractor', None) is not 'generic': # wew hacky + await self.remove_from_autostream(song_url[0], ex=downloader.youtube_dl.utils.ExtractionError("This is not a stream."), delete_from_as=self.config.remove_as) + continue - try: - player.auto_state.once('change-entry', 'finishedentry') - player.auto_state.once('play', 'autoentry') - await player.playlist.add_stream_entry(song_url[0], info=None) - except exceptions.ExtractionError as e: - log.error("Error adding song from autostream: {}".format(e)) - log.debug('', exc_info=True) - continue + if self.config.auto_pause: + player.once('play', lambda player, **_: _autopause(player)) - break + try: + player.auto_state.once('change-entry', 'finishedentry') + player.auto_state.once('play', 'autoentry') + await player.playlist.add_stream_entry(song_url[0], info=None) + except exceptions.ExtractionError as e: + log.error("Error adding song from autostream: {}".format(e)) + log.debug('', exc_info=True) + continue + + break - else: - log.error("autoplaylist type undefined: {}".format(song_url[1])) + else: + log.error("autoplaylist type undefined: {}".format(song_url[1])) - if self.config.auto_playlist: - if not self.autoplaylist: - # TODO: When I add playlist expansion, make sure that's not happening during this check - log.warning("No playable songs in the autoplaylist, disabling.") - self.config.auto_playlist = False - if self.auto_toggle == 'playlist' and len(self.playlisttype) > 1: - try: - i = self.playlisttype.index(self.auto_toggle) + 1 - if i == len(self.playlisttype): - i = 0 - except ValueError: + if self.config.auto_playlist: + if not self.autoplaylist: + # TODO: When I add playlist expansion, make sure that's not happening during this check + log.warning("No playable songs in the autoplaylist, disabling.") + self.config.auto_playlist = False + if player.auto_mode['auto_toggle'] == 'playlist' and len(self.playlisttype) > 1: + try: + i = self.playlisttype.index(player.auto_mode['auto_toggle']) + 1 + if i == len(self.playlisttype): i = 0 - self.auto_toggle = self.playlisttype[i] - self.playlisttype.remove('playlist') - - if self.config.auto_stream: - if not self.autostream: - log.warning("No playable songs in the autostream, disabling.") - self.config.auto_stream = False - if self.auto_toggle == 'stream' and len(self.playlisttype) > 1: - try: - i = self.playlisttype.index(self.auto_toggle) + 1 - if i == len(self.playlisttype): - i = 0 - except ValueError: + except ValueError: + i = 0 + player.auto_mode['auto_toggle'] = self.playlisttype[i] + await self.serialize_json(player.auto_mode, player.voice_client.channel.guild, dir = 'data/%s/mode.json') + self.playlisttype.remove('playlist') + + if self.config.auto_stream: + if not self.autostream: + log.warning("No playable songs in the autostream, disabling.") + self.config.auto_stream = False + if player.auto_mode['auto_toggle'] == 'stream' and len(self.playlisttype) > 1: + try: + i = self.playlisttype.index(player.auto_mode['auto_toggle']) + 1 + if i == len(self.playlisttype): i = 0 - self.auto_toggle = self.playlisttype[i] - self.playlisttype.remove('stream') + except ValueError: + i = 0 + player.auto_mode['auto_toggle'] = self.playlisttype[i] + await self.serialize_json(player.auto_mode, player.voice_client.channel.guild, dir = 'data/%s/mode.json') + self.playlisttype.remove('stream') else: # Don't serialize for autoplaylist events await self.serialize_queue(player.voice_client.channel.guild) @@ -848,6 +860,41 @@ async def deserialize_queue(self, guild, voice_client, playlist=None, *, dir=Non return MusicPlayer.from_json(data, self, voice_client, playlist) + async def serialize_json(self, data, guild, *, dir=None): + """ + Serialize the current queue for a server's player to json. + """ + + dir = dir.replace('%s', str(guild.id)) + + async with self.aiolocks['json_serialization' + ':' + str(guild.id)]: + log.debug("Serializing json for %s at %s", guild.id, dir) + + with open(dir, 'w', encoding='utf8') as f: + f.write(json.dumps(data)) + + async def deserialize_json(self, guild, *, dir=None): + """ + Deserialize arbitrary json. + """ + + dir = dir.replace('%s', str(guild.id)) + + async with self.aiolocks['json_serialization' + ':' + str(guild.id)]: + if not os.path.isfile(dir): + return None + + log.debug("Deserializing json for %s at %s", guild.id, dir) + + try: + with open(dir, 'r', encoding='utf8') as f: + data = f.read() + + except FileNotFoundError: + return None + + return json.loads(data) + async def write_current_song(self, guild, entry, *, dir=None): """ Writes the current song to file @@ -1231,40 +1278,49 @@ async def cmd_resetplaylist(self, player, channel): Resets all songs in the server's autoplaylist and autostream with no randomization """ + if not player.autoplaylist_mode: + player.auto_mode = dict() + player.auto_mode['mode'] = self.config.auto_playlist + if(player.auto_mode['mode'] == 'toggle'): + player.auto_mode['auto_toggle'] = self.playlisttype[0] + await self.serialize_json(player.auto_mode, player.voice_client.channel.guild, dir = 'data/%s/mode.json') + player.autoplaylist = list() if self.config.auto_playlist: if not self.autoplaylist: # TODO: When I add playlist expansion, make sure that's not happening during this check log.warning("No playable songs in the autoplaylist, disabling.") self.config.auto_playlist = False - if self.auto_toggle == 'playlist' and len(self.playlisttype) > 1: + if player.auto_mode['auto_toggle'] == 'playlist' and len(self.playlisttype) > 1: try: - i = self.playlisttype.index(self.auto_toggle) + 1 + i = self.playlisttype.index(player.auto_mode['auto_toggle']) + 1 if i == len(self.playlisttype): i = 0 except ValueError: i = 0 - self.auto_toggle = self.playlisttype[i] + player.auto_mode['auto_toggle'] = self.playlisttype[i] + await self.serialize_json(player.auto_mode, player.voice_client.channel.guild, dir = 'data/%s/mode.json') self.playlisttype.remove('playlist') else: - if self.config.auto_mode == 'merge' or (self.config.auto_mode == 'toggle' and self.auto_toggle == 'playlist'): + if player.auto_mode['mode'] == 'merge' or (player.auto_mode['mode'] == 'toggle' and player.auto_mode['auto_toggle'] == 'playlist'): log.debug("resetting current autoplaylist...") player.autoplaylist.extend([(url, "default") for url in list(self.autoplaylist)]) if self.config.auto_stream: if not self.autostream: log.warning("No playable songs in the autostream, disabling.") self.config.auto_stream = False - if self.auto_toggle == 'stream' and len(self.playlisttype) > 1: + if player.auto_mode['auto_toggle'] == 'stream' and len(self.playlisttype) > 1: try: - i = self.playlisttype.index(self.auto_toggle) + 1 + i = self.playlisttype.index(player.auto_mode['auto_toggle']) + 1 if i == len(self.playlisttype): i = 0 except ValueError: i = 0 - self.auto_toggle = self.playlisttype[i] + player.auto_mode['auto_toggle'] = self.playlisttype[i] + await self.serialize_json(player.auto_mode, player.voice_client.channel.guild, dir = 'data/%s/mode.json') self.playlisttype.remove('stream') else: - if self.config.auto_mode == 'merge' or (self.config.auto_mode == 'toggle' and self.auto_toggle == 'stream'): + if player.auto_mode['mode'] == 'merge' or (player.auto_mode['mode'] == 'toggle' and player.auto_mode['auto_toggle'] == 'stream'): log.debug("resetting current autostream...") player.autoplaylist.extend([(url, "stream") for url in list(self.autostream)]) return Response(self.str.get('cmd-resetplaylist-response', '\N{OK HAND SIGN}'), delete_after=15) @@ -1276,7 +1332,14 @@ async def cmd_toggleplaylist(self, author, permissions, player, channel): Toggle between autoplaylist and autostream """ - if self.config.auto_mode == 'toggle': + if not player.auto_mode: + player.auto_mode = dict() + player.auto_mode['mode'] = self.config.auto_mode + if(player.auto_mode['mode'] == 'toggle'): + player.auto_mode['auto_toggle'] = self.playlisttype[0] + await self.serialize_json(player.auto_mode, player.voice_client.channel.guild, dir = 'data/%s/mode.json') + + if player.auto_mode['mode'] == 'toggle': if not permissions.toggle_playlists and not author.id == self.config.owner_id and not author == user: raise exceptions.PermissionsError( self.str.get('cmd-toggleplaylist-noperm', 'You have no permission to toggle autoplaylist'), @@ -1286,15 +1349,16 @@ async def cmd_toggleplaylist(self, author, permissions, player, channel): if len(self.playlisttype) == 0: return Response(self.str.get('cmd-toggleplaylist-nolist', 'There is not any autoplaylist to toggle to'), delete_after=15) try: - i = self.playlisttype.index(self.auto_toggle) + 1 + i = self.playlisttype.index(player.auto_mode['auto_toggle']) + 1 if i == len(self.playlisttype): i = 0 except ValueError: i = 0 - if self.playlisttype[i] == self.auto_toggle: + if self.playlisttype[i] == player.auto_mode['auto_toggle']: return Response(self.str.get('cmd-toggleplaylist-nolist', 'There is not any autoplaylist to toggle to'), delete_after=15) else: - self.auto_toggle = self.playlisttype[i] + player.auto_mode['auto_toggle'] = self.playlisttype[i] + await self.serialize_json(player.auto_mode, player.voice_client.channel.guild, dir = 'data/%s/mode.json') # reset playlist player.autoplaylist = list() # if autoing then switch @@ -1302,7 +1366,7 @@ async def cmd_toggleplaylist(self, author, permissions, player, channel): player.skip() # on_player_finished_playing should fill in the music # done! - return Response(self.str.get('cmd-toggleplaylist-success', 'Switched autoplaylist to {0}').format(self.auto_toggle), delete_after=15) + return Response(self.str.get('cmd-toggleplaylist-success', 'Switched autoplaylist to {0}').format(player.auto_mode['auto_toggle']), delete_after=15) else: return Response(self.str.get('cmd-toggleplaylist-wrongmode', 'Mode for dealing with autoplaylists is not set to \'toggle\', currently set to {0}').format(self.config.auto_mode), delete_after=15) diff --git a/musicbot/player.py b/musicbot/player.py index 724b520b5e..b36686731b 100644 --- a/musicbot/player.py +++ b/musicbot/player.py @@ -104,6 +104,7 @@ def __init__(self, bot, voice_client, playlist): self.loop = bot.loop self.voice_client = voice_client self.playlist = playlist + self.auto_mode = None self.autoplaylist = None self.state = MusicPlayerState.STOPPED self.skip_state = None From b06c8b37a24bb471417fea231ea46cbd2a60076b Mon Sep 17 00:00:00 2001 From: TheerapakG Date: Mon, 17 Sep 2018 10:54:54 +0700 Subject: [PATCH 3/9] ability to remove stream entry from autostream --- config/i18n/en.json | 9 ++++++--- musicbot/bot.py | 42 ++++++++++++++++++++++++++++++++++++------ 2 files changed, 42 insertions(+), 9 deletions(-) diff --git a/config/i18n/en.json b/config/i18n/en.json index b53ec72636..f4a7583230 100644 --- a/config/i18n/en.json +++ b/config/i18n/en.json @@ -13,9 +13,12 @@ "cmd-save-exists": "This song is already in the autoplaylist.", "cmd-save-invalid": "There is no valid song playing.", "cmd-save-success": "Added <{0}> to the autoplaylist.", - "cmd-savestream-exists": "This stream is already in the autostream.", - "cmd-savestream-invalid": "There is no valid stream playing.", - "cmd-savestram-success": "Added <{0}> to the autostream.", + "cmd-addstream-exists": "This stream is already in the autostream.", + "cmd-addstream-success": "Added <{0}> to the autostream.", + "cmd-removestream-notexists": "This stream is already not in the autostream.", + "cmd-removestream-success": "Removed <{0}> from the autostream.", + "cmd-autostream-invalid": "There is no valid stream playing.", + "cmd-autostream-nooption", "Check your specified option argument. It needs to be +, -, add or remove.", "cmd-toggleplaylist-wrongmode": "Mode for dealing with autoplaylists is not set to 'toggle', currently set to {0}", "cmd-toggleplaylist-nolist": "There is not any autoplaylist to toggle to", "cmd-toggleplaylist-success": "Switched autoplaylist to {0}", diff --git a/musicbot/bot.py b/musicbot/bot.py index c31fae2d57..477ac33340 100644 --- a/musicbot/bot.py +++ b/musicbot/bot.py @@ -1497,28 +1497,58 @@ async def cmd_save(self, player, url=None): else: raise exceptions.CommandError(self.str.get('cmd-save-invalid', 'There is no valid song playing.')) - async def cmd_savestream(self, player, url=None): + async def cmd_autostream(self, player, option, url=None): """ Usage: - {command_prefix}savestream [url] + {command_prefix}autostream [+, -, add, remove] [url] - Saves the specified stream or current stream if not specified to the autostream. + Add or remove the specified stream or current stream if not specified to/from the autostream. """ if url or (player.current_entry and isinstance(player.current_entry, StreamPlaylistEntry)): if not url: url = player.current_entry.url + else: + raise exceptions.CommandError(self.str.get('cmd-autostream-invalid', 'There is no valid stream playing.')) + + if option in ['+', 'add']: if url not in self.autostream: self.autostream.append(url) write_file(self.config.auto_stream_file, self.autostream) if 'stream' not in self.playlisttype: self.playlisttype.append('stream') log.debug("Appended {} to autostream".format(url)) - return Response(self.str.get('cmd-savestream-success', 'Added <{0}> to the autostream.').format(url)) + return Response(self.str.get('cmd-addstream-success', 'Added <{0}> to the autostream.').format(url)) else: - raise exceptions.CommandError(self.str.get('cmd-savestream-exists', 'This stream is already in the autostream.')) + raise exceptions.CommandError(self.str.get('cmd-addstream-exists', 'This stream is already in the autostream.')) + + elif option in ['-', 'remove']: + if song_url not in self.autostream: + log.debug("URL \"{}\" not in autostream, ignoring".format(song_url)) + raise exceptions.CommandError(self.str.get('cmd-removestream-notexists', 'This stream is already not in the autostream.')) + + async with self.aiolocks['remove_from_autostream']: + self.autostream.remove(song_url) + log.info("Removing song from session autostream: %s" % song_url) + + with open(self.config.auto_stream_removed_file, 'a', encoding='utf8') as f: + f.write( + '# Entry removed {ctime}\n' + '# Reason: {re}\n' + '{url}\n\n{sep}\n\n'.format( + ctime=time.ctime(), + re='\n#' + ' ' * 10 + 'removed by user', # 10 spaces to line up with # Reason: + url=song_url, + sep='#' * 32 + )) + + log.info("Updating autostream") + write_file(self.config.auto_stream_file, self.autostream) + + return Response(self.str.get('cmd-removestream-success', 'Removed <{0}> from the autostream.').format(url)) + else: - raise exceptions.CommandError(self.str.get('cmd-savestream-invalid', 'There is no valid stream playing.')) + raise exceptions.CommandError(self.str.get('cmd-autostream-nooption', 'Check your specified option argument. It needs to be +, -, add or remove.')) @owner_only async def cmd_joinserver(self, message, server_link=None): From 354c05a9ff72aeaf302eec3900476b197ba28096 Mon Sep 17 00:00:00 2001 From: TheerapakG Date: Mon, 17 Sep 2018 11:00:17 +0700 Subject: [PATCH 4/9] I need to be better at checking JSON files --- config/i18n/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/i18n/en.json b/config/i18n/en.json index f4a7583230..de623ed25c 100644 --- a/config/i18n/en.json +++ b/config/i18n/en.json @@ -18,7 +18,7 @@ "cmd-removestream-notexists": "This stream is already not in the autostream.", "cmd-removestream-success": "Removed <{0}> from the autostream.", "cmd-autostream-invalid": "There is no valid stream playing.", - "cmd-autostream-nooption", "Check your specified option argument. It needs to be +, -, add or remove.", + "cmd-autostream-nooption": "Check your specified option argument. It needs to be +, -, add or remove.", "cmd-toggleplaylist-wrongmode": "Mode for dealing with autoplaylists is not set to 'toggle', currently set to {0}", "cmd-toggleplaylist-nolist": "There is not any autoplaylist to toggle to", "cmd-toggleplaylist-success": "Switched autoplaylist to {0}", From 71e54cbc2311f64a6fc7251117648526185cde03 Mon Sep 17 00:00:00 2001 From: TheerapakG Date: Mon, 17 Sep 2018 22:19:45 +0700 Subject: [PATCH 5/9] fix position msg when skipping auto --- musicbot/bot.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/musicbot/bot.py b/musicbot/bot.py index 477ac33340..0f805490c5 100644 --- a/musicbot/bot.py +++ b/musicbot/bot.py @@ -1741,6 +1741,9 @@ async def cmd_play(self, message, player, channel, author, permissions, leftover info = await self.downloader.extract_info(player.playlist.loop, song_url, download=False, process=False) # Now I could just do: return await self.cmd_play(player, channel, author, song_url) # But this is probably fine + + # For checking if the list now contain auto and we will skip it, which would bring our position to first position + afterauto = False # TODO: Possibly add another check here to see about things like the bandcamp issue # TODO: Where ytdl gets the generic extractor version with no processing, but finds two different urls @@ -1816,6 +1819,10 @@ async def cmd_play(self, message, player, channel, author, permissions, leftover expire_in=30 ) + if self.config.skip_if_auto and player.auto_state.current_value and not player.is_stopped: + player.skip() + afterauto = True + reply_text = self.str.get('cmd-play-playlist-reply', "Enqueued **%s** songs to be played. Position in queue: %s") btext = str(listlen - drop_count) @@ -1836,6 +1843,7 @@ async def cmd_play(self, message, player, channel, author, permissions, leftover entry, position = await player.playlist.add_entry(song_url, channel=channel, author=author) if self.config.skip_if_auto and player.auto_state.current_value and not player.is_stopped: player.skip() + afterauto = True except exceptions.WrongEntryTypeError as e: if e.use_url == song_url: @@ -1849,11 +1857,10 @@ async def cmd_play(self, message, player, channel, author, permissions, leftover reply_text = self.str.get('cmd-play-song-reply', "Enqueued `%s` to be played. Position in queue: %s") btext = entry.title - - if position == 1 and player.is_stopped: + if (position == 1 and player.is_stopped) or afterauto: position = self.str.get('cmd-play-next', 'Up next!') reply_text %= (btext, position) - + else: try: time_until = await player.playlist.estimate_time_until(position, player) From 75a8adf1765d0ac6e13e3c6360889c43640bf905 Mon Sep 17 00:00:00 2001 From: TheerapakG Date: Thu, 20 Sep 2018 23:45:17 +0700 Subject: [PATCH 6/9] now it's real "permissive" --- musicbot/bot.py | 9 ++++----- musicbot/permissions.py | 35 +++++++++++++++++++++++++++++------ 2 files changed, 33 insertions(+), 11 deletions(-) diff --git a/musicbot/bot.py b/musicbot/bot.py index 12a10b5161..84ce15c9a0 100644 --- a/musicbot/bot.py +++ b/musicbot/bot.py @@ -1951,7 +1951,7 @@ async def cmd_remove(self, user_mentions, message, author, permissions, channel, if user_mentions: for user in user_mentions: - if author.id == self.config.owner_id or permissions.remove or author == user: + if permissions.remove or author == user: try: entry_indexes = [e for e in player.playlist.entries if e.meta.get('author', None) == user] for entry in entry_indexes: @@ -1978,7 +1978,7 @@ async def cmd_remove(self, user_mentions, message, author, permissions, channel, if index > len(player.playlist.entries): raise exceptions.CommandError(self.str.get('cmd-remove-invalid', "Invalid number. Use {}queue to find queue positions.").format(self.config.command_prefix), expire_in=20) - if author.id == self.config.owner_id or permissions.remove or author == player.playlist.get_entry_at_index(index - 1).meta.get('author', None): + if permissions.remove or author == player.playlist.get_entry_at_index(index - 1).meta.get('author', None): entry = player.playlist.delete_entry_at_index((index - 1)) await self._manual_delete_check(message) if entry.meta.get('channel', False) and entry.meta.get('author', False): @@ -2019,9 +2019,8 @@ async def cmd_skip(self, player, channel, author, message, permissions, voice_ch current_entry = player.current_entry if (param.lower() in ['force', 'f']) or self.config.legacy_skip: - if author.id == self.config.owner_id \ - or permissions.instaskip \ - or (self.config.allow_author_skip and author == player.current_entry.meta.get('author', None)): + if permissions.instaskip \ + or (self.config.allow_author_skip and author == player.current_entry.meta.get('author', None)): player.skip() # TODO: check autopause stuff here await self._manual_delete_check(message) diff --git a/musicbot/permissions.py b/musicbot/permissions.py index 1ea0901892..2f7fee4e4f 100644 --- a/musicbot/permissions.py +++ b/musicbot/permissions.py @@ -10,15 +10,15 @@ class PermissionsDefaults: perms_file = 'config/permissions.ini' - + #now it's unpermissive by default for most CommandWhiteList = set() CommandBlackList = set() IgnoreNonVoice = set() GrantToRoles = set() UserList = set() - MaxSongs = 0 - MaxSongLength = 0 + MaxSongs = 8 + MaxSongLength = 210 MaxPlaylistLength = 0 MaxSearchItems = 10 @@ -28,11 +28,34 @@ class PermissionsDefaults: SkipWhenAbsent = True BypassKaraokeMode = False - Extractors = set() - + Extractors = "youtube youtube:playlist" class Permissions: def __init__(self, config_file, grant_all=None): + + # TODO: [TheerapakG] pretty this + + configPermissive = configparser.ConfigParser(interpolation=None) + configPermissive['PermissionsPermissive'] = {} + configPermissive['PermissionsPermissive']['CommandWhiteList'] = '' + configPermissive['PermissionsPermissive']['CommandBlackList'] = '' + configPermissive['PermissionsPermissive']['IgnoreNonVoice'] = '' + configPermissive['PermissionsPermissive']['GrantToRoles'] = '' + configPermissive['PermissionsPermissive']['UserList'] = '' + + configPermissive['PermissionsPermissive']['MaxSongs'] = '0' + configPermissive['PermissionsPermissive']['MaxSongLength'] = '0' + configPermissive['PermissionsPermissive']['MaxPlaylistLength'] = '0' + configPermissive['PermissionsPermissive']['MaxSearchItems'] = '20' + + configPermissive['PermissionsPermissive']['AllowPlaylists'] = 'yes' + configPermissive['PermissionsPermissive']['InstaSkip'] = 'yes' + configPermissive['PermissionsPermissive']['Remove'] = 'yes' + configPermissive['PermissionsPermissive']['SkipWhenAbsent'] = 'no' + configPermissive['PermissionsPermissive']['BypassKaraokeMode'] = 'yes' + + configPermissive['PermissionsPermissive']['Extractors'] = '' + self.config_file = config_file self.config = configparser.ConfigParser(interpolation=None) @@ -55,7 +78,7 @@ def __init__(self, config_file, grant_all=None): # Create a fake section to fallback onto the permissive default values to grant to the owner # noinspection PyTypeChecker - owner_group = PermissionGroup("Owner (auto)", configparser.SectionProxy(self.config, None)) + owner_group = PermissionGroup("Owner (auto)", configPermissive['PermissionsPermissive']) if hasattr(grant_all, '__iter__'): owner_group.user_list = set(grant_all) From d535a805ce72851b8747038226489491c3b8252a Mon Sep 17 00:00:00 2001 From: TheerapakG Date: Fri, 21 Sep 2018 07:18:07 +0700 Subject: [PATCH 7/9] Does it more pretty? --- musicbot/permissions.py | 45 +++++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/musicbot/permissions.py b/musicbot/permissions.py index 2f7fee4e4f..87b2e02682 100644 --- a/musicbot/permissions.py +++ b/musicbot/permissions.py @@ -31,31 +31,32 @@ class PermissionsDefaults: Extractors = "youtube youtube:playlist" class Permissions: - def __init__(self, config_file, grant_all=None): - - # TODO: [TheerapakG] pretty this + def gen_permissive(): configPermissive = configparser.ConfigParser(interpolation=None) configPermissive['PermissionsPermissive'] = {} - configPermissive['PermissionsPermissive']['CommandWhiteList'] = '' - configPermissive['PermissionsPermissive']['CommandBlackList'] = '' - configPermissive['PermissionsPermissive']['IgnoreNonVoice'] = '' - configPermissive['PermissionsPermissive']['GrantToRoles'] = '' - configPermissive['PermissionsPermissive']['UserList'] = '' - - configPermissive['PermissionsPermissive']['MaxSongs'] = '0' - configPermissive['PermissionsPermissive']['MaxSongLength'] = '0' - configPermissive['PermissionsPermissive']['MaxPlaylistLength'] = '0' - configPermissive['PermissionsPermissive']['MaxSearchItems'] = '20' - - configPermissive['PermissionsPermissive']['AllowPlaylists'] = 'yes' - configPermissive['PermissionsPermissive']['InstaSkip'] = 'yes' - configPermissive['PermissionsPermissive']['Remove'] = 'yes' - configPermissive['PermissionsPermissive']['SkipWhenAbsent'] = 'no' - configPermissive['PermissionsPermissive']['BypassKaraokeMode'] = 'yes' + sectionPermissive = configPermissive['PermissionsPermissive'] + sectionPermissive['CommandWhiteList'] = '' + sectionPermissive['CommandBlackList'] = '' + sectionPermissive['IgnoreNonVoice'] = '' + sectionPermissive['GrantToRoles'] = '' + sectionPermissive['UserList'] = '' + + sectionPermissive['MaxSongs'] = '0' + sectionPermissive['MaxSongLength'] = '0' + sectionPermissive['MaxPlaylistLength'] = '0' + sectionPermissive['MaxSearchItems'] = '20' + + sectionPermissive['AllowPlaylists'] = 'yes' + sectionPermissive['InstaSkip'] = 'yes' + sectionPermissive['Remove'] = 'yes' + sectionPermissive['SkipWhenAbsent'] = 'no' + sectionPermissive['BypassKaraokeMode'] = 'yes' - configPermissive['PermissionsPermissive']['Extractors'] = '' - + sectionPermissive['Extractors'] = '' + return sectionPermissive + + def __init__(self, config_file, grant_all=None): self.config_file = config_file self.config = configparser.ConfigParser(interpolation=None) @@ -78,7 +79,7 @@ def __init__(self, config_file, grant_all=None): # Create a fake section to fallback onto the permissive default values to grant to the owner # noinspection PyTypeChecker - owner_group = PermissionGroup("Owner (auto)", configPermissive['PermissionsPermissive']) + owner_group = PermissionGroup("Owner (auto)", Permissions.gen_permissive()) if hasattr(grant_all, '__iter__'): owner_group.user_list = set(grant_all) From 8f6b837d27d09c7802eeb2d9cde99472978eb659 Mon Sep 17 00:00:00 2001 From: TheerapakG Date: Wed, 26 Sep 2018 07:55:58 +0700 Subject: [PATCH 8/9] because it could be None and I forget it --- musicbot/bot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/musicbot/bot.py b/musicbot/bot.py index 0f805490c5..4c5e574dd2 100644 --- a/musicbot/bot.py +++ b/musicbot/bot.py @@ -450,10 +450,10 @@ async def get_player(self, channel, create=False, *, deserialize=False) -> Music if deserialize: voice_client = await self.get_voice_client(channel) player = await self.deserialize_queue(guild, voice_client) - player.auto_mode = await self.deserialize_json(guild, dir = 'data/%s/mode.json') if player: log.debug("Created player via deserialization for guild %s with %s entries", guild.id, len(player.playlist)) + player.auto_mode = await self.deserialize_json(guild, dir = 'data/%s/mode.json') # Since deserializing only happens when the bot starts, I should never need to reconnect return self._init_player(player, guild=guild) From 157d62ebe9dc7491461452c22ed50000215d91e8 Mon Sep 17 00:00:00 2001 From: TheerapakG Date: Wed, 26 Sep 2018 13:38:51 +0700 Subject: [PATCH 9/9] Customizable owner perms (but who would do that?) --- config/example_permissions.ini | 14 ++++++++ musicbot/permissions.py | 62 ++++++++++++++++++++++------------ 2 files changed, 55 insertions(+), 21 deletions(-) diff --git a/config/example_permissions.ini b/config/example_permissions.ini index 7c04804638..c0f1c13cc7 100644 --- a/config/example_permissions.ini +++ b/config/example_permissions.ini @@ -82,6 +82,20 @@ ; ;;;;;;;;;;;;;;;;;;; +; This group is for owner. Any options not specified will fallback to permissive default value. Don't remove/rename this group. +; You cannot assign users or roles to this group. Those options are ignored. +[Owner (auto)] +; MaxSongLength = 0 +; MaxSongs = 0 +; MaxPlaylistLength = 0 +; AllowPlaylists = yes +; InstaSkip = yes +; Remove = yes +; SkipWhenAbsent = no +; BypassKaraokeMode = yes +; ToggleAutoPlaylists = yes +; Extractors = + ; This is the fallback group for any users that don't get assigned to another group. Don't remove/rename this group. ; You cannot assign users or roles to this group. Those options are ignored. [Default] diff --git a/musicbot/permissions.py b/musicbot/permissions.py index 87b2e02682..5404e41576 100644 --- a/musicbot/permissions.py +++ b/musicbot/permissions.py @@ -79,7 +79,7 @@ def __init__(self, config_file, grant_all=None): # Create a fake section to fallback onto the permissive default values to grant to the owner # noinspection PyTypeChecker - owner_group = PermissionGroup("Owner (auto)", Permissions.gen_permissive()) + owner_group = PermissionGroup('Owner (auto)', self.config['Owner (auto)'], fallback=Permissions.gen_permissive()) if hasattr(grant_all, '__iter__'): owner_group.user_list = set(grant_all) @@ -126,27 +126,47 @@ def create_group(self, name, **kwargs): class PermissionGroup: - def __init__(self, name, section_data): + def __init__(self, name, section_data, fallback=None): self.name = name - - self.command_whitelist = section_data.get('CommandWhiteList', fallback=PermissionsDefaults.CommandWhiteList) - self.command_blacklist = section_data.get('CommandBlackList', fallback=PermissionsDefaults.CommandBlackList) - self.ignore_non_voice = section_data.get('IgnoreNonVoice', fallback=PermissionsDefaults.IgnoreNonVoice) - self.granted_to_roles = section_data.get('GrantToRoles', fallback=PermissionsDefaults.GrantToRoles) - self.user_list = section_data.get('UserList', fallback=PermissionsDefaults.UserList) - - self.max_songs = section_data.get('MaxSongs', fallback=PermissionsDefaults.MaxSongs) - self.max_song_length = section_data.get('MaxSongLength', fallback=PermissionsDefaults.MaxSongLength) - self.max_playlist_length = section_data.get('MaxPlaylistLength', fallback=PermissionsDefaults.MaxPlaylistLength) - self.max_search_items = section_data.get('MaxSearchItems', fallback=PermissionsDefaults.MaxSearchItems) - - self.allow_playlists = section_data.get('AllowPlaylists', fallback=PermissionsDefaults.AllowPlaylists) - self.instaskip = section_data.get('InstaSkip', fallback=PermissionsDefaults.InstaSkip) - self.remove = section_data.get('Remove', fallback=PermissionsDefaults.Remove) - self.skip_when_absent = section_data.get('SkipWhenAbsent', fallback=PermissionsDefaults.SkipWhenAbsent) - self.bypass_karaoke_mode = section_data.get('BypassKaraokeMode', fallback=PermissionsDefaults.BypassKaraokeMode) - - self.extractors = section_data.get('Extractors', fallback=PermissionsDefaults.Extractors) + if fallback == None: + self.command_whitelist = section_data.get('CommandWhiteList', fallback=PermissionsDefaults.CommandWhiteList) + self.command_blacklist = section_data.get('CommandBlackList', fallback=PermissionsDefaults.CommandBlackList) + self.ignore_non_voice = section_data.get('IgnoreNonVoice', fallback=PermissionsDefaults.IgnoreNonVoice) + self.granted_to_roles = section_data.get('GrantToRoles', fallback=PermissionsDefaults.GrantToRoles) + self.user_list = section_data.get('UserList', fallback=PermissionsDefaults.UserList) + + self.max_songs = section_data.get('MaxSongs', fallback=PermissionsDefaults.MaxSongs) + self.max_song_length = section_data.get('MaxSongLength', fallback=PermissionsDefaults.MaxSongLength) + self.max_playlist_length = section_data.get('MaxPlaylistLength', fallback=PermissionsDefaults.MaxPlaylistLength) + self.max_search_items = section_data.get('MaxSearchItems', fallback=PermissionsDefaults.MaxSearchItems) + + self.allow_playlists = section_data.get('AllowPlaylists', fallback=PermissionsDefaults.AllowPlaylists) + self.instaskip = section_data.get('InstaSkip', fallback=PermissionsDefaults.InstaSkip) + self.remove = section_data.get('Remove', fallback=PermissionsDefaults.Remove) + self.skip_when_absent = section_data.get('SkipWhenAbsent', fallback=PermissionsDefaults.SkipWhenAbsent) + self.bypass_karaoke_mode = section_data.get('BypassKaraokeMode', fallback=PermissionsDefaults.BypassKaraokeMode) + + self.extractors = section_data.get('Extractors', fallback=PermissionsDefaults.Extractors) + + else: + self.command_whitelist = section_data.get('CommandWhiteList', fallback=fallback['CommandWhiteList']) + self.command_blacklist = section_data.get('CommandBlackList', fallback=fallback['CommandBlackList']) + self.ignore_non_voice = section_data.get('IgnoreNonVoice', fallback=fallback['IgnoreNonVoice']) + self.granted_to_roles = section_data.get('GrantToRoles', fallback=fallback['GrantToRoles']) + self.user_list = section_data.get('UserList', fallback=fallback['UserList']) + + self.max_songs = section_data.get('MaxSongs', fallback=fallback['MaxSongs']) + self.max_song_length = section_data.get('MaxSongLength', fallback=fallback['MaxSongLength']) + self.max_playlist_length = section_data.get('MaxPlaylistLength', fallback=fallback['MaxPlaylistLength']) + self.max_search_items = section_data.get('MaxSearchItems', fallback=fallback['MaxSearchItems']) + + self.allow_playlists = section_data.get('AllowPlaylists', fallback=fallback['AllowPlaylists']) + self.instaskip = section_data.get('InstaSkip', fallback=fallback['InstaSkip']) + self.remove = section_data.get('Remove', fallback=fallback['Remove']) + self.skip_when_absent = section_data.get('SkipWhenAbsent', fallback=fallback['SkipWhenAbsent']) + self.bypass_karaoke_mode = section_data.get('BypassKaraokeMode', fallback=fallback['BypassKaraokeMode']) + + self.extractors = section_data.get('Extractors', fallback=fallback['Extractors']) self.validate()