diff --git a/cogs/audio.py b/cogs/audio.py index c2af8a257..039ad0d91 100644 --- a/cogs/audio.py +++ b/cogs/audio.py @@ -19,6 +19,7 @@ import time import inspect import subprocess import urllib.parse +import datetime from enum import Enum __author__ = "tekulvw" @@ -148,6 +149,10 @@ class Song: self.duration = kwargs.pop('duration', 60) self.start_time = kwargs.pop('start_time', None) self.end_time = kwargs.pop('end_time', None) + self.thumbnail = kwargs.pop('thumbnail', None) + self.view_count = kwargs.pop('view_count', None) + self.rating = kwargs.pop('average_rating', None) + self.song_start_time = None class QueuedSong: def __init__(self, url, channel): @@ -310,8 +315,10 @@ class Audio: self.queue = {} # add deque's, repeat self.downloaders = {} # sid: object self.settings = dataIO.load_json("data/audio/settings.json") + self.settings_path = "data/audio/settings.json" self.server_specific_setting_keys = ["VOLUME", "VOTE_ENABLED", - "VOTE_THRESHOLD", "NOPPL_DISCONNECT"] + "VOTE_THRESHOLD", "NOPPL_DISCONNECT", + "NOTIFY", "NOTIFY_CHANNEL", "TIMER_DISCONNECT"] self.cache_path = "data/audio/cache" self.local_playlist_path = "data/audio/localtracks" self._old_game = False @@ -892,6 +899,7 @@ class Audio: except FileNotFoundError: raise + song.song_start_time = datetime.datetime.now() voice_client = await self._create_ffmpeg_player(server, song.id, local=local, start_time=song.start_time, @@ -1177,6 +1185,35 @@ class Audio: await self.bot.say("Maximum length is now {} seconds.".format(length)) self.save_settings() + @checks.mod_or_permissions(manage_messages=True) + @audioset.command(name="notifychannel", pass_context=True) + async def audioset_notifychannel(self, ctx, channel: discord.Channel): + """Sets the channel for the now playing announcement""" + server = ctx.message.server + if not server.me.permissions_in(channel).send_messages: + await self.bot.say("No permissions to speak in that channel.") + return + self.set_server_setting(server, "NOTIFY_CHANNEL", channel.id) + dataIO.save_json(self.settings_path, self.settings) + await self.bot.send_message(channel, "I will now announce new songs here.") + + @audioset.command(name="notify", pass_context=True) + @checks.mod_or_permissions(manage_messages=True) + async def audioset_notify(self, ctx): + """Sends a notification to the channel when the song changes""" + server = ctx.message.server + settings = self.get_server_settings(server.id) + notify = settings.get("NOTIFY", True) + self.set_server_setting(server, "NOTIFY", not notify) + if self.get_server_settings(server)["NOTIFY_CHANNEL"] is None: + self.set_server_setting(server, "NOTIFY_CHANNEL", ctx.message.channel.id) + dataIO.save_json(self.settings_path, self.settings) + if not notify: + await self.bot.say("Now notifying when a new track plays.") + else: + await self.bot.say("No longer notifying when a new track plays.") + self.save_settings() + @audioset.command(name="player") @checks.is_owner() async def audioset_player(self): @@ -1204,6 +1241,26 @@ class Audio: " status") self.save_settings() + @audioset.command(name="timerdisconnect", pass_context=True) + @checks.mod_or_permissions(manage_messages=True) + async def audioset_timerdisconnect(self, ctx): + """Toggles the disconnect timer""" + server = ctx.message.server + settings = self.get_server_settings(server.id) + timer_disconnect = settings.get("TIMER_DISCONNECT", True) + self.set_server_setting(server, "TIMER_DISCONNECT", + not timer_disconnect) + if not timer_disconnect: + await self.bot.say("The bot will automatically disconnect after" + " playback is stopped and five minutes have" + " elapsed. Disable this setting to stop the" + " bot from disconnecting with other music cogs" + " playing.") + else: + await self.bot.say("The bot will no longer auto disconnect" + " while other music cogs are playing.") + self.save_settings() + @audioset.command(pass_context=True, name="volume", no_pm=True) @checks.mod_or_permissions(manage_messages=True) async def audioset_volume(self, ctx, percent: int=None): @@ -1771,22 +1828,27 @@ class Audio: """Not a command, use `queue` with no args to call this.""" server = ctx.message.server channel = ctx.message.channel - if server.id not in self.queue: + now_playing = self._get_queue_nowplaying(server) + if server.id not in self.queue and now_playing is None: await self.bot.say("Nothing playing on this server!") return - elif len(self.queue[server.id][QueueKey.QUEUE]) == 0: + if len(self.queue[server.id][QueueKey.QUEUE]) == 0 and not self.is_playing(server): await self.bot.say("Nothing queued on this server.") return + colour = ''.join([choice('0123456789ABCDEF') for x in range(6)]) + em = discord.Embed(description="", colour=int(colour, 16)) msg = "" - now_playing = self._get_queue_nowplaying(server) + if self.is_playing(server): + msg += "\n***Currently playing:***\n{}\n".format(now_playing.title) + msg += self._draw_play(now_playing, server) + "\n" # draw play thing + if now_playing.thumbnail is None: + now_playing.thumbnail = (self.bot.user.avatar_url).replace('webp', 'png') + em.set_thumbnail(url=now_playing.thumbnail) - if now_playing is not None: - msg += "\n***Now playing:***\n{}\n".format(now_playing.title) - - queued_song_list = self._get_queue(server, 5) - tempqueued_song_list = self._get_queue_tempqueue(server, 5) + queued_song_list = self._get_queue(server, 10) + tempqueued_song_list = self._get_queue_tempqueue(server, 10) await self.bot.say("Gathering information...") @@ -1795,21 +1857,65 @@ class Audio: song_info = [] for num, song in enumerate(tempqueue_song_list, 1): + str_duration = str(datetime.timedelta(seconds=song.duration)) try: - song_info.append("{}. {.title}".format(num, song)) + if song.title is None: + song_info.append("**[{}]** {.webpage_url} ({})".format(num, song, str_duration)) + else: + song_info.append("**[{}]** {.title} ({})".format(num, song, str_duration)) except AttributeError: - song_info.append("{}. {.webpage_url}".format(num, song)) + song_info.append("**[{}]** {.webpage_url} ({})".format(num, song, str_duration)) for num, song in enumerate(queue_song_list, len(song_info) + 1): - if num > 5: + str_duration = str(datetime.timedelta(seconds=song.duration)) + if num > 10: break try: - song_info.append("{}. {.title}".format(num, song)) + if song.title is None: + song_info.append("**[{}]** {.webpage_url} ({})".format(num, song, str_duration)) + else: + song_info.append("**[{}]** {.title} ({})".format(num, song, str_duration)) except AttributeError: - song_info.append("{}. {.webpage_url}".format(num, song)) - msg += "\n***Next up:***\n" + "\n".join(song_info) + song_info.append("**[{}]** {.webpage_url} ({})".format(num, song, str_duration)) - await self.bot.say(msg) + if song_info: + msg += "\n***Next up:***\n" + "\n".join(song_info) + em.description = msg.replace('None', '-') + more_songs = len(self.queue[server.id][QueueKey.QUEUE]) - 10 + if more_songs > 0: + em.set_footer(text="And {} more songs...".format(more_songs)) + await self.bot.say(embed=em) + + def _draw_play(self, song, server): + song_start_time = song.song_start_time + total_time = datetime.timedelta(seconds=song.duration) + current_time = datetime.datetime.now() + elapsed_time = current_time - song_start_time + sections = 12 + loc_time = round((elapsed_time/total_time) * sections) # 10 sections + + bar_char = '\N{BOX DRAWINGS HEAVY HORIZONTAL}' + seek_char = '\N{RADIO BUTTON}' + play_char = '\N{BLACK RIGHT-POINTING TRIANGLE}' + + try: + if self.voice_client(server).audio_player.is_playing(): + play_char = '\N{BLACK RIGHT-POINTING TRIANGLE}' + else: + play_char = '\N{DOUBLE VERTICAL BAR}' + except AttributeError: + pass + + msg = "\n" + play_char + " " + + for i in range(sections): + if i == loc_time: + msg += seek_char + else: + msg += bar_char + + msg += " `{}`/`{}`".format(str(elapsed_time)[0:7],str(total_time)) + return msg @commands.group(pass_context=True, no_pm=True) async def repeat(self, ctx): @@ -1950,7 +2056,7 @@ class Audio: url = "https://www.youtube.com/watch?v={}".format(choice(ids)) await ctx.invoke(self.play, url_or_search_terms=url) - @commands.command(pass_context=True, no_pm=True) + @commands.command(pass_context=True, aliases=["np"], no_pm=True) async def song(self, ctx): """Info about the current song.""" server = ctx.message.server @@ -1966,6 +2072,10 @@ class Audio: song.view_count = None if not hasattr(song, 'uploader'): song.uploader = None + if song.rating is None: + song.rating = 0 + if song.thumbnail is None: + song.thumbnail = (self.bot.user.avatar_url).replace('webp', 'png') if hasattr(song, 'duration'): m, s = divmod(song.duration, 60) h, m = divmod(m, 60) @@ -1975,14 +2085,22 @@ class Audio: dur = "{0}:{1:0>2}".format(m, s) else: dur = None - msg = ("\n**Title:** {}\n**Author:** {}\n**Uploader:** {}\n" - "**Views:** {}\n**Duration:** {}\n\n<{}>".format( - song.title, song.creator, song.uploader, - song.view_count, dur, song.webpage_url)) - await self.bot.say(msg.replace("**Author:** None\n", "") - .replace("**Views:** None\n", "") - .replace("**Uploader:** None\n", "") - .replace("**Duration:** None\n", "")) + + msg = ("**Author:** `{}`\n**Uploader:** `{}`\n" + "**Duration:** `{}`\n**Rating: **`{:.2f}`\n**Views:** `{}`".format( + song.creator, song.uploader, str(datetime.timedelta(seconds=song.duration)), song.rating, + song.view_count)) + msg += self._draw_play(song, server) + "\n" + colour = ''.join([choice('0123456789ABCDEF') for x in range(6)]) + em = discord.Embed(description="", colour=int(colour, 16)) + if 'http' not in song.webpage_url: + em.set_author(name=song.title) + else: + em.set_author(name=song.title, url=song.webpage_url) + em.set_thumbnail(url=song.thumbnail) + em.description = msg.replace('None', '-') + + await self.bot.say("**Currently Playing:**", embed=em) else: await self.bot.say("Darude - Sandstorm.") @@ -2072,10 +2190,13 @@ class Audio: if stop_times[server] and \ int(time.time()) - stop_times[server] > 300: # 5 min not playing to d/c - log.debug("dcing from sid {} after 300s".format(server.id)) - self._clear_queue(server) - await self._stop_and_disconnect(server) - stop_times[server] = None + timer_disconnect = self.get_server_settings(server) + timer_disconnect = timer_disconnect.get("TIMER_DISCONNECT", True) + if timer_disconnect: + log.debug("dcing from sid {} after 300s".format(server.id)) + self._clear_queue(server) + await self._stop_and_disconnect(server) + stop_times[server] = None await asyncio.sleep(5) def get_server_settings(self, server): @@ -2093,6 +2214,15 @@ class Audio: if "NOPPL_DISCONNECT" not in ret: ret["NOPPL_DISCONNECT"] = True + if "NOTIFY" not in ret: + ret["NOTIFY"] = False + + if "NOTIFY_CHANNEL" not in ret: + ret["NOTIFY_CHANNEL"] = None + + if "TIMER_DISCONNECT" not in ret: + ret["TIMER_DISCONNECT"] = True + for setting in self.server_specific_setting_keys: if setting not in ret: # Add the default @@ -2131,6 +2261,10 @@ class Audio: """This function assumes that there's something in the queue for us to play""" server = self.bot.get_server(sid) + if self.get_server_settings(server)["NOTIFY"] is True: + notify_channel = self.settings["SERVERS"][server.id]["NOTIFY_CHANNEL"] + if self.get_server_settings(server)["NOTIFY"] is False: + notify_channel = None max_length = self.settings["MAX_LENGTH"] # This is a reference, or should be at least @@ -2158,6 +2292,7 @@ class Audio: url = queued_song.url channel = queued_song.channel song = await self._play(sid, url, channel) + await self.display_now_playing(server, song, notify_channel) except MaximumLength: return elif len(queue) > 0: # We're in the normal queue @@ -2167,6 +2302,7 @@ class Audio: log.debug("calling _play on the normal queue") try: song = await self._play(sid, url, channel) + await self.display_now_playing(server, song, notify_channel) except MaximumLength: return if repeat and last_song: @@ -2210,6 +2346,47 @@ class Audio: message = escape(message, mass_mentions=True) await self.bot.send_message(next_channel, message) + async def display_now_playing(self, server, song, notify_channel:int): + channel = discord.utils.get(server.channels, id=notify_channel) + if channel is None: + return + if song.title is None: + return + def to_delete(m): + if "Now Playing" in m.content and m.author == self.bot.user: + return True + else: + return False + try: + await self.bot.purge_from(channel, limit=50, check=to_delete) + except discord.errors.Forbidden: + await self.bot.say("I need permissions to manage messages in this channel.") + + if song: + if not hasattr(song, 'creator'): + song.creator = None + if not hasattr(song, 'uploader'): + song.uploader = None + if song.rating is None: + song.rating = 0 + if song.thumbnail is None: + song.thumbnail = (self.bot.user.avatar_url).replace('webp', 'png') + + msg = ("**Author:** `{}`\n**Uploader:** `{}`\n" + "**Duration:** `{}`\n**Rating: **`{:.2f}`\n**Views:** `{}`".format( + song.creator, song.uploader, str(datetime.timedelta(seconds=song.duration)), song.rating, song.view_count)) + + colour = ''.join([choice('0123456789ABCDEF') for x in range(6)]) + em = discord.Embed(description="", colour=int(colour, 16)) + if 'http' not in song.webpage_url: + em.set_author(name=song.title) + else: + em.set_author(name=song.title, url=song.webpage_url) + em.set_thumbnail(url=song.thumbnail) + em.description = msg.replace('None', '-') + + await self.bot.send_message(channel, "**Now Playing:**", embed=em) + async def queue_scheduler(self): while self == self.bot.get_cog('Audio'): tasks = []