diff --git a/bot.py b/bot.py index 04ed455..8501ada 100644 --- a/bot.py +++ b/bot.py @@ -66,7 +66,7 @@ async def process_command(ctx): command_usage['member_usage'] = total_member_usage # Add one to the server's usage for this command - if message.server is not None: + if ctx.message.server is not None: total_server_usage = command_usage.get('server_usage', {}) server_usage = total_server_usage.get(server.id, 0) + 1 total_server_usage[server.id] = server_usage diff --git a/cogs/core.py b/cogs/core.py index 6258744..194f358 100644 --- a/cogs/core.py +++ b/cogs/core.py @@ -172,13 +172,13 @@ class Core: cmd = None page = 1 - - perms = self.bot.permissions_in(ctx.message.channel) - if not (perms.embed_links and perms.add_reactions): - fmt = "I need the permissions `embed_links` and `add_reactions` to send my help message! " \ - " Otherwise you can use this link to view available commands {}".format( - "http://bonfirebot.readthedocs.io/en/latest/") - await self.bot.say(fmt) + if ctx.message.server: + perms = ctx.message.server.me.permissions_in(ctx.message.channel) + if not (perms.embed_links and perms.add_reactions): + fmt = "I need the permissions `embed_links` and `add_reactions` to send my help message! " \ + " Otherwise you can use this link to view available commands {}".format( + "http://bonfirebot.readthedocs.io/en/latest/") + await self.bot.say(fmt) if message is not None: # If something is provided, it can either be the page number or a command diff --git a/cogs/disabled_playlist.py b/cogs/disabled_playlist.py index 48df5df..90703bc 100644 --- a/cogs/disabled_playlist.py +++ b/cogs/disabled_playlist.py @@ -55,7 +55,7 @@ class Music: This command automatically searches as well from YouTube. The list of supported sites can be found here: https://rg3.github.io/youtube-dl/supportedsites.html - + EXAMPLE: !play Song by Band RESULT: Song by Band will be queued to play! """ @@ -110,7 +110,12 @@ class Music: @commands.command(pass_context=True, no_pm=True, enabled=False) @checks.custom_perms(send_messages=True) async def queue(self, ctx): - """Provides a printout of the songs that are in the queue + """Provides a printout of the songs that are in the queue. + \N{LEFTWARDS BLACK ARROW}: Goes to the previous page + \N{BLACK RIGHTWARDS ARROW}: Goes to the next page + \N{DOWNWARDS BLACK ARROW}: Moves the current song showing back in the queue + \N{UPWARDS BLACK ARROW}: Moves the current song showing up in the queue + \N{CROSS MARK}: Removes the current song showing from the queue EXAMPLE: !queue RESULT: A list of shitty songs you probably don't wanna listen to""" diff --git a/cogs/interaction.py b/cogs/interaction.py index ae7bc49..de7c644 100644 --- a/cogs/interaction.py +++ b/cogs/interaction.py @@ -138,7 +138,11 @@ class Interaction: if file is None: await self.bot.say(url) else: - await self.bot.upload(file, filename='avatar.jpg') + if '.gif' in url: + filename = 'avatar.gif' + else: + filename = 'avatar.webp' + await self.bot.upload(file, filename=filename) else: await self.bot.say(url) diff --git a/cogs/music.py b/cogs/music.py index a942cbc..8e3e64f 100644 --- a/cogs/music.py +++ b/cogs/music.py @@ -9,6 +9,8 @@ import time import asyncio import re import os +import glob +import socket if not discord.opus.is_loaded(): discord.opus.load_opus('/usr/lib64/libopus.so.0') @@ -33,6 +35,7 @@ class VoiceState: } self.volume = 50 self.downloader = download + self.file_names = [] def is_playing(self): # If our VoiceClient or current VoiceEntry do not exist, then we are not playing a song @@ -58,8 +61,6 @@ class VoiceState: self.player.stop() def toggle_next(self): - # Delete the old file (screw caching, when on 5000+ Guilds this takes up too much space) - os.remove(self.current.filename) # Set the Event so that the next song in the queue can be played self.bot.loop.call_soon_threadsafe(self.play_next_song.set) @@ -74,6 +75,7 @@ class VoiceState: # Make sure we find a song while self.current is None: + self.clear_audio_files() await asyncio.sleep(1) self.current = await self.songs.get_next_entry() @@ -82,6 +84,10 @@ class VoiceState: print("Downloading...") await asyncio.sleep(1) + # Now add this file to our list of filenames, so that it can be deleted later + if self.current.filename not in self.file_names: + self.file_names.append(self.current.filename) + # Create the player object self.current.player = self.voice.create_ffmpeg_player( self.current.filename, @@ -100,6 +106,11 @@ class VoiceState: # Wait till the Event has been set, before doing our task again await self.play_next_song.wait() + def clear_audio_files(self): + """Deletes all the audio files this guild has created""" + for f in self.file_names: + os.remove(f) + self.file_names = [] class Music: """Voice related commands. @@ -112,6 +123,12 @@ class Music: down = Downloader(download_folder='audio_tmp') self.downloader = down self.bot.downloader = down + self.clear_audio_tmp() + + def clear_audio_tmp(self): + files = glob.glob('audio_tmp/*') + for f in files: + os.remove(f) def get_voice_state(self, server): state = self.voice_states.get(server.id) @@ -132,17 +149,22 @@ class Music: server = channel.server state = self.get_voice_state(server) voice = self.bot.voice_client_in(server) + # Attempt 3 times + for i in range(3): + try: + if voice is None: + state.voice = await self.bot.join_voice_channel(channel) + return True + elif voice.channel == channel: + state.voice = voice + return True + else: + await voice.disconnect() + state.voice = await self.bot.join_voice_channel(channel) + return True + except (discord.ClientException, socket.gaierror): + continue - if voice is None: - state.voice = await self.bot.join_voice_channel(channel) - return True - elif voice.channel == channel: - state.voice = voice - return True - else: - await voice.disconnect() - state.voice = await self.bot.join_voice_channel(channel) - return True async def remove_voice_client(self, server): """Removes any voice clients from a server @@ -171,11 +193,126 @@ class Music: if state.voice is None: return voice_channel = state.voice.channel - if voice_channel != before.voice.voice_channel and voice_channel != after.voice.voice_channel: - return num_members = len(voice_channel.voice_members) state.required_skips = math.ceil((num_members + 1) / 3) + async def queue_embed_task(self, state, channel, author): + index = 0 + message = None + fmt = None + # Our check to ensure the only one who reacts is the bot + def check(reaction, user): + return user == author + possible_reactions = ['\u27A1', '\u2B05', '\u2b06', '\u2b07', '\u274c'] + while True: + # Get the current queue (It might change while we're doing this) + # So do this in the while loop + queue = state.songs.entries + count = len(queue) + # This means the last song was removed + if count == 0: + await self.bot.send_message(channel, "Nothing currently in the queue") + break + # Get the current entry + entry = queue[index] + # Get the entry's embed + embed = entry.to_embed() + # Set the embed's title to indicate the amount of things in the queue + count = len(queue) + embed.title = "Current Queue [{}/{}]".format(index+1, count) + # Now we need to send the embed, so check if the message is already set + # If not, then we need to send a new one (i.e. this is the first time called) + if message: + message = await self.bot.edit_message(message, fmt, embed=embed) + # There's only one reaction we want to make sure we remove in the circumstances + # If the member doesn't have kick_members permissions, and isn't the requester + # Then they can't remove the song, otherwise they can + if not author.server_permissions.kick_members and author != entry.requester: + try: + await self.bot.remove_reaction(message, '\u274c', channel.server.me) + except: + pass + elif not author.server_permissions.kick_members and author == entry.requester: + try: + await self.bot.add_reaction(message, '\u274c') + except: + pass + else: + message = await self.bot.send_message(channel, embed=embed) + await self.bot.add_reaction(message, '\N{LEFTWARDS BLACK ARROW}') + await self.bot.add_reaction(message, '\N{BLACK RIGHTWARDS ARROW}') + # The moderation tools that can be used + if author.server_permissions.kick_members: + await self.bot.add_reaction(message, '\N{DOWNWARDS BLACK ARROW}') + await self.bot.add_reaction(message, '\N{UPWARDS BLACK ARROW}') + await self.bot.add_reaction(message, '\N{CROSS MARK}') + elif author == entry.requester: + await self.bot.add_reaction(message, '\N{CROSS MARK}') + # Reset the fmt message + fmt = None + # Now we wait for the next reaction + res = await self.bot.wait_for_reaction(possible_reactions, message=message, check=check, timeout=180) + if res is None: + break + else: + reaction, user = res + # Now we can prepare for the next embed to be sent + # If right is clicked + if '\u27A1' in reaction.emoji: + index += 1 + if index >= count: + index = 0 + # If left is clicked + elif '\u2B05' in reaction.emoji: + index -= 1 + if index < 0: + index = count - 1 + # If up is clicked + elif '\u2b06' in reaction.emoji: + # A second check just to make sure, as well as ensuring index is higher than 0 + if author.server_permissions.kick_members and index > 0: + if entry != queue[index]: + fmt = "`Error: Position of this entry has changed, cannot complete your action`" + else: + # Remove the current entry + del queue[index] + # Add it one position higher + queue.insert(index - 1, entry) + # Lets move the index to look at the new place of the entry + index -= 1 + # If down is clicked + elif '\u2b07' in reaction.emoji: + # A second check just to make sure, as well as ensuring index is lower than last + if author.server_permissions.kick_members and index < (count - 1): + if entry != queue[index]: + fmt = "`Error: Position of this entry has changed, cannot complete your action`" + else: + # Remove the current entry + del queue[index] + # Add it one position lower + queue.insert(index + 1, entry) + # Lets move the index to look at the new place of the entry + index += 1 + # If x is clicked + elif '\u274c' in reaction.emoji: + # A second check just to make sure + if author.server_permissions.kick_members or author == entry.requester: + if entry != queue[index]: + fmt = "`Error: Position of this entry has changed, cannot complete your action`" + else: + # Simply remove the entry in place + del queue[index] + # This is the only check we need to make, to ensure index is now not more than last + new_count = count - 1 + if index >= new_count: + index = new_count - 1 + try: + await self.bot.remove_reaction(message, reaction.emoji, user) + except discord.Forbidden: + pass + await self.bot.delete_message(message) + + @commands.command(pass_context=True, no_pm=True) @checks.custom_perms(send_messages=True) async def progress(self, ctx): @@ -281,6 +418,10 @@ class Music: await self.bot.say("You are not currently in the channel; please join before trying to request a song.") return + # Set the number of required skips to start + num_members = len(my_channel.voice_members) + state.required_skips = math.ceil((num_members + 1) / 3) + # Create the player, and check if this was successful # Here all we want is to get the information of the player song = re.sub('[<>\[\]]', '', song) @@ -289,8 +430,8 @@ class Music: _entry, position = await state.songs.add_entry(song, ctx.message.author) except WrongEntryTypeError: # This means that a song was attempted to be searched, instead of a link provided - info = await self.downloader.extract_info(self.bot.loop, song, download=False, process=True) try: + info = await self.downloader.extract_info(self.bot.loop, song, download=False, process=True) song = info.get('entries', [])[0]['webpage_url'] except IndexError: await self.bot.send_message(ctx.message.channel, "No results found for {}!".format(song)) @@ -310,6 +451,7 @@ class Music: fmt = "Sorry but I couldn't download that! Either you provided a playlist, a streamed link, or " \ "a page that is not supported to download." await self.bot.send_message(ctx.message.channel, fmt) + return except ExtractionError as e: # This gets the youtube_dl error, instead of our error raised error = str(e).split("\n\n")[1] @@ -376,8 +518,9 @@ class Music: # Then erase the voice_state entirely, and disconnect from the channel try: state.audio_player.cancel() + state.clear_audio_files() + await self.remove_voice_client(ctx.message.server) del self.voice_states[server.id] - await state.voice.disconnect() except: pass @@ -433,10 +576,9 @@ class Music: # Asyncio provides no non-private way to access the queue, so we have to use _queue queue = state.songs.entries if len(queue) == 0: - fmt = "Nothing currently in the queue" + await self.bot.say("Nothing currently in the queue") else: - fmt = "\n\n".join(str(x) for x in queue) - await self.bot.say("Current songs in the queue:```\n{}```".format(fmt)) + self.bot.loop.create_task(self.queue_embed_task(state, ctx.message.channel, ctx.message.author)) @commands.command(pass_context=True, no_pm=True) @checks.custom_perms(send_messages=True) @@ -498,8 +640,23 @@ class Music: if not state.is_playing(): await self.bot.say('Not playing anything.') else: + # Create the embed object we'll use + embed = discord.Embed() + # Fill in the simple things + embed.add_field(name='Title', value=state.current.title, inline=False) + embed.add_field(name='Requester', value=state.current.requester.display_name, inline=False) + # Get the amount of current skips, and display how many have been skipped/how many required skip_count = len(state.skip_votes) - await self.bot.say('Now playing {} [skips: {}/{}]'.format(state.current, skip_count, state.required_skips)) + embed.add_field(name='Skip Count', value='{}/{}'.format(skip_count, state.required_skips), inline=False) + # Get the current progress and display this + progress = state.current.progress + length = state.current.length + progress = divmod(round(progress, 0), 60) + length = divmod(round(length, 0), 60) + fmt = "{0[0]}m {0[1]}s/{1[0]}m {1[1]}s".format(progress, length) + embed.add_field(name='Progress', value=fmt,inline=False) + # And send the embed + await self.bot.say(embed=embed) def setup(bot): diff --git a/cogs/picarto.py b/cogs/picarto.py index 7f76348..9f4fbe2 100644 --- a/cogs/picarto.py +++ b/cogs/picarto.py @@ -7,8 +7,8 @@ import traceback import logging from discord.ext import commands -from .utils import config -from .utils import checks + +from . import utils log = logging.getLogger() base_url = 'https://ptvappapi.picarto.tv' @@ -24,7 +24,7 @@ async def online_users(): # In place of requesting for /channel and checking if that is online currently, for each channel # This method is in place to just return all online_users url = '{}/online/all?key={}'.format(base_url, key) - with aiohttp.ClientSession(headers={"User-Agent": config.user_agent}) as s: + with aiohttp.ClientSession(headers={"User-Agent": utils.user_agent}) as s: async with s.get(url) as response: return await response.json() except: @@ -43,7 +43,7 @@ def check_online(online_channels, channel): class Picarto: def __init__(self, bot): self.bot = bot - self.headers = {"User-Agent": config.user_agent} + self.headers = {"User-Agent": utils.user_agent} self.session = aiohttp.ClientSession() async def check_channels(self): @@ -52,7 +52,7 @@ class Picarto: try: while not self.bot.is_closed: r_filter = {'notifications_on': 1} - picarto = await config.get_content('picarto', r_filter) + picarto = await utils.get_content('picarto', r_filter) # Get all online users before looping, so that only one request is needed online_users_list = await online_users() old_online_users = {data['member_id']: data for data in picarto if data['live']} @@ -69,7 +69,7 @@ class Picarto: server = self.bot.get_server(server_id) if server is None: continue - server_alerts = await config.get_content('server_alerts', {'server_id': server_id}) + server_alerts = await utils.get_content('server_alerts', {'server_id': server_id}) try: channel_id = server_alerts[0]['channel_id'] except (IndexError, TypeError): @@ -82,7 +82,7 @@ class Picarto: fmt = "{} has just gone live! View their stream at {}".format(member.display_name, url) await self.bot.send_message(channel, fmt) - await config.update_content('picarto', {'live': 1}, {'member_id': m_id}) + await utils.update_content('picarto', {'live': 1}, {'member_id': m_id}) for m_id, result in old_online_users.items(): # Get their url and their user based on that url url = result['picarto_url'] @@ -94,7 +94,7 @@ class Picarto: server = self.bot.get_server(server_id) if server is None: continue - server_alerts = await config.get_content('server_alerts', {'server_id': server_id}) + server_alerts = await utils.get_content('server_alerts', {'server_id': server_id}) try: channel_id = server_alerts[0]['channel_id'] except IndexError: @@ -105,7 +105,7 @@ class Picarto: fmt = "{} has just gone offline! Catch them next time they stream at {}".format( member.display_name, url) await self.bot.send_message(channel, fmt) - await config.update_content('picarto', {'live': 0}, {'member_id': m_id}) + await utils.update_content('picarto', {'live': 0}, {'member_id': m_id}) await asyncio.sleep(30) except Exception as e: tb = traceback.format_exc() @@ -113,7 +113,7 @@ class Picarto: log.error(fmt) @commands.group(pass_context=True, invoke_without_command=True, no_pm=True) - @checks.custom_perms(send_messages=True) + @utils.custom_perms(send_messages=True) async def picarto(self, ctx, member: discord.Member = None): """This command can be used to view Picarto stats about a certain member @@ -122,7 +122,7 @@ class Picarto: # If member is not given, base information on the author member = member or ctx.message.author r_filter = {'member_id': member.id} - picarto_entry = await config.get_content('picarto', r_filter) + picarto_entry = await utils.get_content('picarto', r_filter) if picarto_entry is None: await self.bot.say("That user does not have a picarto url setup!") return @@ -151,7 +151,7 @@ class Picarto: await self.bot.say("Picarto stats for {}: ```\n{}```".format(member.display_name, fmt)) @picarto.command(name='add', pass_context=True, no_pm=True) - @checks.custom_perms(send_messages=True) + @utils.custom_perms(send_messages=True) async def add_picarto_url(self, ctx, url: str): """Saves your user's picarto URL @@ -187,20 +187,20 @@ class Picarto: 'notifications_on': 1, 'live': 0, 'member_id': ctx.message.author.id} - if await config.add_content('picarto', entry, r_filter): + if await utils.add_content('picarto', entry, r_filter): await self.bot.say( "I have just saved your Picarto URL {}, this server will now be notified when you go live".format( ctx.message.author.mention)) else: - await config.update_content('picarto', {'picarto_url': url}, r_filter) + await utils.update_content('picarto', {'picarto_url': url}, r_filter) await self.bot.say("I have just updated your Picarto URL") @picarto.command(name='remove', aliases=['delete'], pass_context=True, no_pm=True) - @checks.custom_perms(send_messages=True) + @utils.custom_perms(send_messages=True) async def remove_picarto_url(self, ctx): """Removes your picarto URL""" r_filter = {'member_id': ctx.message.author.id} - if await config.remove_content('picarto', r_filter): + if await utils.remove_content('picarto', r_filter): await self.bot.say("I am no longer saving your picarto URL {}".format(ctx.message.author.mention)) else: await self.bot.say( @@ -208,7 +208,7 @@ class Picarto: ctx.message.author.mention, ctx.prefix)) @picarto.group(pass_context=True, no_pm=True, invoke_without_command=True) - @checks.custom_perms(send_messages=True) + @utils.custom_perms(send_messages=True) async def notify(self, ctx): """This can be used to turn picarto notifications on or off Call this command by itself, to add this server to the list of servers to be notified @@ -216,7 +216,7 @@ class Picarto: EXAMPLE: !picarto notify RESULT: This server will now be notified of you going live""" r_filter = {'member_id': ctx.message.author.id} - result = await config.get_content('picarto', r_filter) + result = await utils.get_content('picarto', r_filter) # Check if this user is saved at all if result is None: await self.bot.say( @@ -226,30 +226,30 @@ class Picarto: elif ctx.message.server.id in result[0]['servers']: await self.bot.say("I am already set to notify in this server...") else: - await config.update_content('picarto', {'servers': r.row['servers'].append(ctx.message.server.id)}, + await utils.update_content('picarto', {'servers': r.row['servers'].append(ctx.message.server.id)}, r_filter) @notify.command(name='on', aliases=['start,yes'], pass_context=True, no_pm=True) - @checks.custom_perms(send_messages=True) + @utils.custom_perms(send_messages=True) async def notify_on(self, ctx): """Turns picarto notifications on EXAMPLE: !picarto notify on RESULT: Notifications are sent when you go live""" r_filter = {'member_id': ctx.message.author.id} - await config.update_content('picarto', {'notifications_on': 1}, r_filter) + await utils.update_content('picarto', {'notifications_on': 1}, r_filter) await self.bot.say("I will notify if you go live {}, you'll get a bajillion followers I promise c:".format( ctx.message.author.mention)) @notify.command(name='off', aliases=['stop,no'], pass_context=True, no_pm=True) - @checks.custom_perms(send_messages=True) + @utils.custom_perms(send_messages=True) async def notify_off(self, ctx): """Turns picarto notifications off EXAMPLE: !picarto notify off RESULT: No more notifications sent when you go live""" r_filter = {'member_id': ctx.message.author.id} - await config.update_content('picarto', {'notifications_on': 0}, r_filter) + await utils.update_content('picarto', {'notifications_on': 0}, r_filter) await self.bot.say( "I will not notify if you go live anymore {}, " "are you going to stream some lewd stuff you don't want people to see?~".format( diff --git a/cogs/raffle.py b/cogs/raffle.py index 131181f..411ad6f 100644 --- a/cogs/raffle.py +++ b/cogs/raffle.py @@ -75,7 +75,10 @@ class Raffle: # We don't have to wait for it however, so create a task for it r_filter = {'id': raffle_id} self.bot.loop.create_task(config.remove_content('raffles', r_filter)) - await self.bot.send_message(server, fmt) + try: + await self.bot.send_message(server, fmt) + except discord.Forbidden: + pass @commands.command(pass_context=True, no_pm=True) @checks.custom_perms(send_messages=True) diff --git a/cogs/twitch.py b/cogs/twitch.py index 80d37c8..1f2446b 100644 --- a/cogs/twitch.py +++ b/cogs/twitch.py @@ -46,8 +46,8 @@ class Twitch: # Loop through as long as the bot is connected try: while not self.bot.is_closed: - twitch = await config.get_content('twitch', {'notifications_on': 1}) - # Online/offline is based on whether they are set to such, in the config file + twitch = await utils.get_content('twitch', {'notifications_on': 1}) + # Online/offline is based on whether they are set to such, in the utils file # This means they were detected as online/offline before and we check for a change online_users = {data['member_id']: data for data in twitch if data['live']} offline_users = {data['member_id']: data for data in twitch if not data['live']} @@ -62,7 +62,7 @@ class Twitch: server = self.bot.get_server(server_id) if server is None: continue - server_alerts = await config.get_content('server_alerts', {'server_id': server_id}) + server_alerts = await utils.get_content('server_alerts', {'server_id': server_id}) try: channel_id = server_alerts[0]['channel_id'] except (IndexError, TypeError): @@ -75,7 +75,7 @@ class Twitch: fmt = "{} has just gone live! View their stream at {}".format(member.display_name, url) await self.bot.send_message(channel, fmt) - await config.update_content('twitch', {'live': 1}, {'member_id': m_id}) + await utils.update_content('twitch', {'live': 1}, {'member_id': m_id}) for m_id, result in online_users.items(): # Get their url and their user based on that url url = result['twitch_url'] @@ -87,7 +87,7 @@ class Twitch: server = self.bot.get_server(server_id) if server is None: continue - server_alerts = await config.get_content('server_alerts', {'server_id': server_id}) + server_alerts = await utils.get_content('server_alerts', {'server_id': server_id}) channel_id = server_id if len(server_alerts) > 0: channel_id = server_alerts[0].get('channel_id') @@ -97,7 +97,7 @@ class Twitch: fmt = "{} has just gone offline! Catch them next time they stream at {}".format( member.display_name, url) await self.bot.send_message(channel, fmt) - await config.update_content('twitch', {'live': 0}, {'member_id': m_id}) + await utils.update_content('twitch', {'live': 0}, {'member_id': m_id}) await asyncio.sleep(30) except Exception as e: tb = traceback.format_exc() diff --git a/cogs/utils/utilities.py b/cogs/utils/utilities.py index eda5701..4669fe1 100644 --- a/cogs/utils/utilities.py +++ b/cogs/utils/utilities.py @@ -61,7 +61,7 @@ async def download_image(url): return None # Then wrap it in a BytesIO object, to be used like an actual file - image = io.BytesIO(await r.read()) + image = io.BytesIO(bts) return image async def request(url, *, headers=None, payload=None, method='GET', attr='json'): diff --git a/cogs/voice_utilities/entry.py b/cogs/voice_utilities/entry.py index 300481c..3af1966 100644 --- a/cogs/voice_utilities/entry.py +++ b/cogs/voice_utilities/entry.py @@ -3,6 +3,7 @@ import json import os import traceback import time +import discord from hashlib import md5 from .exceptions import ExtractionError @@ -270,5 +271,16 @@ class URLPlaylistEntry(BasePlaylistEntry): else: # Move the temporary file to it's final location. os.rename(unhashed_fname, self.filename) - - + def to_embed(self): + """Returns an embed that can be used to display information about this particular song""" + # Create the embed object we'll use + embed = discord.Embed() + # Fill in the simple things + embed.add_field(name='Title', value=self.title, inline=False) + embed.add_field(name='Requester', value=self.requester.display_name, inline=False) + # Get the current length of the song and display this + length = divmod(round(self.length, 0), 60) + fmt = "{0[0]}m {0[1]}s".format(length) + embed.add_field(name='Duration', value=fmt,inline=False) + # And return the embed we created + return embed