async def determine_music_source(self, ctx: commands.Context, song_str: Optional[str]): try: player = lavalink.get_player(ctx.guild.id) except (KeyError, IndexError): player = None if not player and song_str: return BOT_SONG_RE.sub("", song_str) if not (player or song_str): return None if player and song_str: return BOT_SONG_RE.sub("", song_str) possible_music = player.current if not possible_music: return None return BOT_SONG_RE.sub("", possible_music.title)
async def command_stop(self, ctx: commands.Context): """Stop playback and clear the queue.""" dj_enabled = self._dj_status_cache.setdefault( ctx.guild.id, await self.config.guild(ctx.guild).dj_enabled() ) vote_enabled = await self.config.guild(ctx.guild).vote_enabled() if not self._player_check(ctx): return await self.send_embed_msg(ctx, title=_("Nothing playing.")) player = lavalink.get_player(ctx.guild.id) can_skip = await self._can_instaskip(ctx, ctx.author) is_alone = await self.is_requester_alone(ctx) if (not ctx.author.voice or ctx.author.voice.channel != player.channel) and not can_skip: return await self.send_embed_msg( ctx, title=_("Unable To Stop Player"), description=_("You must be in the voice channel to stop the music."), ) if (vote_enabled or (vote_enabled and dj_enabled)) and not can_skip and not is_alone: return await self.send_embed_msg( ctx, title=_("Unable To Stop Player"), description=_("There are other people listening - vote to skip instead."), ) if dj_enabled and not vote_enabled and not can_skip: return await self.send_embed_msg( ctx, title=_("Unable To Stop Player"), description=_("You need the DJ role to stop the music."), ) if ( player.is_playing or (not player.is_playing and player.paused) or player.queue or getattr(player.current, "extras", {}).get("autoplay") ): eq = player.fetch("eq") if eq: await self.config.custom("EQUALIZER", ctx.guild.id).eq_bands.set(eq.bands) player.queue = [] player.store("playing_song", None) player.store("prev_requester", None) player.store("prev_song", None) player.store("requester", None) await player.stop() await self.send_embed_msg(ctx, title=_("Stopping..."))
async def command_disconnect(self, ctx: commands.Context): """Disconnect from the voice channel.""" if not self._player_check(ctx): return await self.send_embed_msg(ctx, title=_("Nothing playing.")) else: dj_enabled = self._dj_status_cache.setdefault( ctx.guild.id, await self.config.guild(ctx.guild).dj_enabled()) vote_enabled = await self.config.guild(ctx.guild).vote_enabled() player = lavalink.get_player(ctx.guild.id) can_skip = await self._can_instaskip(ctx, ctx.author) if ((vote_enabled or (vote_enabled and dj_enabled)) and not can_skip and not await self.is_requester_alone(ctx)): return await self.send_embed_msg( ctx, title=_("Unable To Disconnect"), description= _("There are other people listening - vote to skip instead." ), ) if dj_enabled and not vote_enabled and not can_skip: return await self.send_embed_msg( ctx, title=_("Unable To Disconnect"), description=_("You need the DJ role to disconnect."), ) if dj_enabled and not can_skip: return await self.send_embed_msg( ctx, title=_("Unable to Disconnect"), description=_("You need the DJ role to disconnect."), ) await self.send_embed_msg(ctx, title=_("Disconnecting...")) self.bot.dispatch("red_audio_audio_disconnect", ctx.guild) self.update_player_lock(ctx, False) eq = player.fetch("eq") player.queue = [] player.store("playing_song", None) if eq: await self.config.custom("EQUALIZER", ctx.guild.id).eq_bands.set(eq.bands) await player.stop() await player.disconnect() await self.api_interface.persistent_queue_api.drop(ctx.guild.id)
async def command_bump(self, ctx: commands.Context, index: int): """Bump a track number to the top of the queue.""" dj_enabled = self._dj_status_cache.setdefault( ctx.guild.id, await self.config.guild(ctx.guild).dj_enabled() ) if not self._player_check(ctx): return await self.send_embed_msg(ctx, title=_("Nothing playing.")) player = lavalink.get_player(ctx.guild.id) can_skip = await self._can_instaskip(ctx, ctx.author) if ( not ctx.author.voice or ctx.author.voice.channel != player.channel ) and not can_skip: return await self.send_embed_msg( ctx, title=_("Unable To Bump Track"), description=_("You must be in the voice channel to bump a track."), ) if dj_enabled and not can_skip: return await self.send_embed_msg( ctx, title=_("Unable To Bump Track"), description=_("You need the DJ role to bump tracks."), ) if index > len(player.queue) or index < 1: return await self.send_embed_msg( ctx, title=_("Unable To Bump Track"), description=_( "Song number must be greater than 1 and within the queue limit." ), ) bump_index = index - 1 bump_song = player.queue[bump_index] bump_song.extras["bumped"] = True player.queue.insert(0, bump_song) removed = player.queue.pop(index) description = await self.get_track_description( removed, self.local_folder_current_path ) await self.send_embed_msg( ctx, title=_("Moved track to the top of the queue."), description=description, )
async def command_volume(self, ctx: commands.Context, vol: int = None): """Set the volume, 1% - 150%.""" dj_enabled = self._dj_status_cache.setdefault( ctx.guild.id, await self.config.guild(ctx.guild).dj_enabled()) can_skip = await self._can_instaskip(ctx, ctx.author) if not vol: vol = await self.config.guild(ctx.guild).volume() embed = discord.Embed(title=_("Current Volume:"), description=str(vol) + "%") if not self._player_check(ctx): embed.set_footer(text=_("Nothing playing.")) return await self.send_embed_msg(ctx, embed=embed) if self._player_check(ctx): player = lavalink.get_player(ctx.guild.id) if (not ctx.author.voice or ctx.author.voice.channel != player.channel) and not can_skip: return await self.send_embed_msg( ctx, title=_("Unable To Change Volume"), description=_( "You must be in the voice channel to change the volume." ), ) if dj_enabled and not can_skip and not await self._has_dj_role( ctx, ctx.author): return await self.send_embed_msg( ctx, title=_("Unable To Change Volume"), description=_("You need the DJ role to change the volume."), ) if vol < 0: vol = 0 if vol > 150: vol = 150 await self.config.guild(ctx.guild).volume.set(vol) if self._player_check(ctx): await lavalink.get_player(ctx.guild.id).set_volume(vol) else: await self.config.guild(ctx.guild).volume.set(vol) if self._player_check(ctx): await lavalink.get_player(ctx.guild.id).set_volume(vol) embed = discord.Embed(title=_("Volume:"), description=str(vol) + "%") if not self._player_check(ctx): embed.set_footer(text=_("Nothing playing.")) await self.send_embed_msg(ctx, embed=embed)
async def queue_duration(ctx): player = lavalink.get_player(ctx.guild.id) duration = [] for i in range(len(player.queue)): if not player.queue[i].is_stream: duration.append(player.queue[i].length) queue_dur = sum(duration) if not player.queue: queue_dur = 0 try: if not player.current.is_stream: remain = player.current.length - player.position else: remain = 0 except AttributeError: remain = 0 queue_total_duration = remain + queue_dur return queue_total_duration
async def queue_duration(self, ctx: commands.Context) -> int: player = lavalink.get_player(ctx.guild.id) dur = [ i.length async for i in AsyncIter(player.queue, steps=50).filter(lambda x: not x.is_stream) ] queue_dur = sum(dur) if not player.queue: queue_dur = 0 try: if not player.current.is_stream: remain = player.current.length - player.position else: remain = 0 except AttributeError: remain = 0 queue_total_duration = remain + queue_dur return queue_total_duration
async def command_queue_clean(self, ctx: commands.Context): """Removes songs from the queue if the requester is not in the voice channel.""" try: player = lavalink.get_player(ctx.guild.id) except KeyError: return await self.send_embed_msg(ctx, title=_("There's nothing in the queue.")) dj_enabled = self._dj_status_cache.setdefault( ctx.guild.id, await self.config.guild(ctx.guild).dj_enabled() ) if not self._player_check(ctx) or not player.queue: return await self.send_embed_msg(ctx, title=_("There's nothing in the queue.")) if ( dj_enabled and not await self._can_instaskip(ctx, ctx.author) and not await self.is_requester_alone(ctx) ): return await self.send_embed_msg( ctx, title=_("Unable To Clean Queue"), description=_("You need the DJ role to clean the queue."), ) clean_tracks = [] removed_tracks = 0 listeners = player.channel.members async for track in AsyncIter(player.queue): if track.requester in listeners: clean_tracks.append(track) else: await self.api_interface.persistent_queue_api.played( ctx.guild.id, track.extras.get("enqueue_time") ) removed_tracks += 1 player.queue = clean_tracks if removed_tracks == 0: await self.send_embed_msg(ctx, title=_("Removed 0 tracks.")) else: await self.send_embed_msg( ctx, title=_("Removed Tracks From The Queue"), description=_( "Removed {removed_tracks} tracks queued by members " "outside of the voice channel." ).format(removed_tracks=removed_tracks), )
async def command_queue_search(self, ctx: commands.Context, *, search_words: str): """Search the queue.""" try: player = lavalink.get_player(ctx.guild.id) except KeyError: return await self.send_embed_msg(ctx, title=_("There's nothing in the queue.")) if not self._player_check(ctx) or not player.queue: return await self.send_embed_msg(ctx, title=_("There's nothing in the queue.")) search_list = await self._build_queue_search_list(player.queue, search_words) if not search_list: return await self.send_embed_msg(ctx, title=_("No matches.")) len_search_pages = math.ceil(len(search_list) / 10) search_page_list = [] async for page_num in AsyncIter(range(1, len_search_pages + 1)): embed = await self._build_queue_search_page(ctx, page_num, search_list) search_page_list.append(embed) await menu(ctx, search_page_list, DEFAULT_CONTROLS)
async def draw_time(ctx): player = lavalink.get_player(ctx.guild.id) paused = player.paused pos = player.position dur = player.current.length sections = 12 loc_time = round((pos / dur) * sections) bar = "\N{BOX DRAWINGS HEAVY HORIZONTAL}" seek = "\N{RADIO BUTTON}" if paused: msg = "\N{DOUBLE VERTICAL BAR}" else: msg = "\N{BLACK RIGHT-POINTING TRIANGLE}" for i in range(sections): if i == loc_time: msg += seek else: msg += bar return msg
async def command_pause(self, ctx: commands.Context): """Pause or resume a playing track.""" dj_enabled = self._dj_status_cache.setdefault( ctx.guild.id, await self.config.guild(ctx.guild).dj_enabled()) if not self._player_check(ctx): return await self.send_embed_msg(ctx, title=_("Nothing playing.")) player = lavalink.get_player(ctx.guild.id) can_skip = await self._can_instaskip(ctx, ctx.author) if (not ctx.author.voice or ctx.author.voice.channel != player.channel) and not can_skip: return await self.send_embed_msg( ctx, title=_("Unable To Manage Tracks"), description=_( "You must be in the voice channel to pause or resume."), ) if dj_enabled and not can_skip and not await self.is_requester_alone( ctx): return await self.send_embed_msg( ctx, title=_("Unable To Manage Tracks"), description=_( "You need the DJ role to pause or resume tracks."), ) player.store("channel", ctx.channel.id) player.store("guild", ctx.guild.id) if not player.current: return await self.send_embed_msg(ctx, title=_("Nothing playing.")) description = await self.get_track_description( player.current, self.local_folder_current_path) if player.current and not player.paused: await player.pause() return await self.send_embed_msg(ctx, title=_("Track Paused"), description=description) if player.current and player.paused: await player.pause(False) return await self.send_embed_msg(ctx, title=_("Track Resumed"), description=description) await self.send_embed_msg(ctx, title=_("Nothing playing."))
async def draw_time(self, ctx) -> str: player = lavalink.get_player(ctx.guild.id) paused = player.paused pos = player.position or 1 dur = getattr(player.current, "length", player.position or 1) sections = 12 loc_time = round((pos / dur if dur != 0 else pos) * sections) bar = "\N{BOX DRAWINGS HEAVY HORIZONTAL}" seek = "\N{RADIO BUTTON}" if paused: msg = "\N{DOUBLE VERTICAL BAR}\N{VARIATION SELECTOR-16}" else: msg = "\N{BLACK RIGHT-POINTING TRIANGLE}\N{VARIATION SELECTOR-16}" for i in range(sections): if i == loc_time: msg += seek else: msg += bar return msg
async def command_equalizer_load(self, ctx: commands.Context, eq_preset: str): """Load a saved eq preset.""" eq_preset = eq_preset.lower() eq_presets = await self.config.custom("EQUALIZER", ctx.guild.id).eq_presets() try: eq_values = eq_presets[eq_preset]["bands"] except KeyError: return await self.send_embed_msg( ctx, title=_("No Preset Found"), description=_( "Preset named {eq_preset} does not exist.".format(eq_preset=eq_preset) ), ) except TypeError: eq_values = eq_presets[eq_preset] if not self._player_check(ctx): return await self.send_embed_msg(ctx, title=_("Nothing playing.")) dj_enabled = self._dj_status_cache.setdefault( ctx.guild.id, await self.config.guild(ctx.guild).dj_enabled() ) player = lavalink.get_player(ctx.guild.id) if dj_enabled and not await self._can_instaskip(ctx, ctx.author): return await self.send_embed_msg( ctx, title=_("Unable To Load Preset"), description=_("You need the DJ role to load equalizer presets."), ) player.store("notify_channel", ctx.channel.id) await self.config.custom("EQUALIZER", ctx.guild.id).eq_bands.set(eq_values) await self._eq_check(ctx, player) eq = player.fetch("eq", Equalizer()) await self._eq_msg_clear(player.fetch("eq_message")) message = await ctx.send( content=box(eq.visualise(), lang="ini"), embed=discord.Embed( colour=await ctx.embed_colour(), title=_("The {eq_preset} preset was loaded.".format(eq_preset=eq_preset)), ), ) player.store("eq_message", message)
async def on_voice_state_update(self, member: discord.Member, before: discord.VoiceState, after: discord.VoiceState) -> None: await self.cog_ready_event.wait() if after.channel != before.channel: try: self.skip_votes[before.channel.guild].remove(member.id) except (ValueError, KeyError, AttributeError): pass channel = self.rgetattr(member, "voice.channel", None) bot_voice_state = self.rgetattr(member, "guild.me.voice.self_deaf", None) if channel and bot_voice_state is False: try: player = lavalink.get_player(channel.guild.id) except (KeyError, AttributeError): pass else: if player.channel.id == channel.id: await self.self_deafen(player)
async def command_shuffle_bumpped(self, ctx: commands.Context): """Toggle bumped track shuffle. Set this to disabled if you wish to avoid bumped songs being shuffled. This takes priority over `[p]shuffle`. """ dj_enabled = self._dj_status_cache.setdefault( ctx.guild.id, await self.config.guild(ctx.guild).dj_enabled() ) can_skip = await self._can_instaskip(ctx, ctx.author) if dj_enabled and not can_skip: return await self.send_embed_msg( ctx, title=_("Unable To Toggle Shuffle"), description=_("You need the DJ role to toggle shuffle."), ) if self._player_check(ctx): await self.set_player_settings(ctx) player = lavalink.get_player(ctx.guild.id) if ( not ctx.author.voice or ctx.author.voice.channel != player.channel ) and not can_skip: return await self.send_embed_msg( ctx, title=_("Unable To Toggle Shuffle"), description=_("You must be in the voice channel to toggle shuffle."), ) player.store("channel", ctx.channel.id) player.store("guild", ctx.guild.id) bumped = await self.config.guild(ctx.guild).shuffle_bumped() await self.config.guild(ctx.guild).shuffle_bumped.set(not bumped) await self.send_embed_msg( ctx, title=_("Setting Changed"), description=_("Shuffle bumped tracks: {true_or_false}.").format( true_or_false=_("Enabled") if not bumped else _("Disabled") ), ) if self._player_check(ctx): await self.set_player_settings(ctx)
async def command_repeat(self, ctx: commands.Context): """Toggle repeat.""" dj_enabled = self._dj_status_cache.setdefault( ctx.guild.id, await self.config.guild(ctx.guild).dj_enabled() ) can_skip = await self._can_instaskip(ctx, ctx.author) if dj_enabled and not can_skip and not await self._has_dj_role(ctx, ctx.author): return await self.send_embed_msg( ctx, title=_("Unable To Toggle Repeat"), description=_("You need the DJ role to toggle repeat."), ) if self._player_check(ctx): await self.set_player_settings(ctx) player = lavalink.get_player(ctx.guild.id) if ( not ctx.author.voice or ctx.author.voice.channel != player.channel ) and not can_skip: return await self.send_embed_msg( ctx, title=_("Unable To Toggle Repeat"), description=_("You must be in the voice channel to toggle repeat."), ) player.store("channel", ctx.channel.id) player.store("guild", ctx.guild.id) autoplay = await self.config.guild(ctx.guild).auto_play() repeat = await self.config.guild(ctx.guild).repeat() msg = "" msg += _("Repeat tracks: {true_or_false}.").format( true_or_false=_("Enabled") if not repeat else _("Disabled") ) await self.config.guild(ctx.guild).repeat.set(not repeat) if repeat is not True and autoplay is True: msg += _("\nAuto-play has been disabled.") await self.config.guild(ctx.guild).auto_play.set(False) embed = discord.Embed(title=_("Setting Changed"), description=msg) await self.send_embed_msg(ctx, embed=embed) if self._player_check(ctx): await self.set_player_settings(ctx)
async def on_red_audio_track_start(self, guild: discord.Guild, track: lavalink.Track, requester: discord.Member): if not (guild and track): return if track.author.lower() not in track.title.lower(): title = f"{track.title}" else: title = track.title self._cache[guild.id] = title auto_lyrics = await self.config.guild(guild).auto_lyrics() if auto_lyrics is True: notify_channel = lavalink.get_player(guild.id).fetch("channel") if not notify_channel: return notify_channel = self.bot.get_channel(notify_channel) botsong = BOT_SONG_RE.sub('', self._cache[guild.id]).strip() try: async with notify_channel.typing(): title, artist, lyrics, source = await getlyrics(botsong) paged_embeds = [] paged_content = [ p for p in pagify(lyrics, page_length=900) ] for index, page in enumerate(paged_content): e = discord.Embed( title='{} by {}'.format(title, artist), description=page, colour=await self.bot.get_embed_color(notify_channel)) e.set_footer( text='Requested by {} | Source: {} | Page: {}/{}'. format(track.requester, source, index, len(paged_content))) paged_embeds.append(e) await menu(notify_channel, paged_embeds, controls=DEFAULT_CONTROLS, timeout=180.0) except discord.Forbidden: return await notify_channel.send("Missing embed permissions..")
async def on_voice_state_update( self, member: discord.Member, before: discord.VoiceState, after: discord.VoiceState ) -> None: if await self.bot.cog_disabled_in_guild(self, member.guild): return await self.cog_ready_event.wait() if after.channel != before.channel: try: self.skip_votes[before.channel.guild.id].discard(member.id) except (ValueError, KeyError, AttributeError): pass # if ( # member == member.guild.me # and before.channel # and after.channel # and after.channel.id != before.channel.id # ): # try: # player = lavalink.get_player(member.guild.id) # if player.is_playing: # await player.resume(player.current, start=player.position, replace=False) # log.debug("Bot changed channel - Resume playback") # except: # log.debug("Bot changed channel - Unable to resume playback") channel = self.rgetattr(member, "voice.channel", None) bot_voice_state = self.rgetattr(member, "guild.me.voice.self_deaf", None) if ( channel and bot_voice_state is False and await self.config.guild(member.guild).auto_deafen() ): try: player = lavalink.get_player(channel.guild.id) except (KeyError, AttributeError): pass else: if player.channel.id == channel.id: await self.self_deafen(player)
async def command_equalizer(self, ctx: commands.Context): """Equalizer management. Band positions are 1-15 and values have a range of -0.25 to 1.0. Band names are 25, 40, 63, 100, 160, 250, 400, 630, 1k, 1.6k, 2.5k, 4k, 6.3k, 10k, and 16k Hz. Setting a band value to -0.25 nullifies it while +0.25 is double. """ if not self._player_check(ctx): ctx.command.reset_cooldown(ctx) return await self.send_embed_msg(ctx, title=_("Nothing playing.")) dj_enabled = self._dj_status_cache.setdefault( ctx.guild.id, await self.config.guild(ctx.guild).dj_enabled() ) player = lavalink.get_player(ctx.guild.id) eq = player.fetch("eq", Equalizer()) reactions = [ "\N{BLACK LEFT-POINTING TRIANGLE}\N{VARIATION SELECTOR-16}", "\N{LEFTWARDS BLACK ARROW}\N{VARIATION SELECTOR-16}", "\N{BLACK UP-POINTING DOUBLE TRIANGLE}", "\N{UP-POINTING SMALL RED TRIANGLE}", "\N{DOWN-POINTING SMALL RED TRIANGLE}", "\N{BLACK DOWN-POINTING DOUBLE TRIANGLE}", "\N{BLACK RIGHTWARDS ARROW}\N{VARIATION SELECTOR-16}", "\N{BLACK RIGHT-POINTING TRIANGLE}\N{VARIATION SELECTOR-16}", "\N{BLACK CIRCLE FOR RECORD}\N{VARIATION SELECTOR-16}", "\N{INFORMATION SOURCE}\N{VARIATION SELECTOR-16}", ] await self._eq_msg_clear(player.fetch("eq_message")) eq_message = await ctx.send(box(eq.visualise(), lang="ini")) if dj_enabled and not await self._can_instaskip(ctx, ctx.author): with contextlib.suppress(discord.HTTPException): await eq_message.add_reaction("\N{INFORMATION SOURCE}\N{VARIATION SELECTOR-16}") else: start_adding_reactions(eq_message, reactions) eq_msg_with_reacts = await ctx.fetch_message(eq_message.id) player.store("eq_message", eq_msg_with_reacts) await self._eq_interact(ctx, player, eq, eq_msg_with_reacts, 0)
async def command_shuffle(self, ctx: commands.Context): """Toggle shuffle.""" if ctx.invoked_subcommand is None: dj_enabled = self._dj_status_cache.setdefault( ctx.guild.id, await self.config.guild(ctx.guild).dj_enabled()) can_skip = await self._can_instaskip(ctx, ctx.author) if dj_enabled and not can_skip: return await self.send_embed_msg( ctx, title=_("Unable To Toggle Shuffle"), description=_("You need the DJ role to toggle shuffle."), ) if self._player_check(ctx): await self.set_player_settings(ctx) player = lavalink.get_player(ctx.guild.id) if (not ctx.author.voice or ctx.author.voice.channel != player.channel) and not can_skip: return await self.send_embed_msg( ctx, title=_("Unable To Toggle Shuffle"), description= _("You must be in the voice channel to toggle shuffle." ), ) player.store("channel", ctx.channel.id) player.store("guild", ctx.guild.id) shuffle = await self.config.guild(ctx.guild).shuffle() await self.config.guild(ctx.guild).shuffle.set(not shuffle) await self.send_embed_msg( ctx, title=_("Setting Changed"), description=_("Shuffle tracks: {true_or_false}.").format( true_or_false=_("Enabled") if not shuffle else _("Disabled" )), ) if self._player_check(ctx): await self.set_player_settings(ctx)
async def maybe_move_player(self, ctx: commands.Context) -> bool: try: player = lavalink.get_player(ctx.guild.id) except KeyError: return False try: in_channel = sum(not m.bot for m in ctx.guild.get_member( self.bot.user.id).voice.channel.members) except AttributeError: return False if not ctx.author.voice: user_channel = None else: user_channel = ctx.author.voice.channel if in_channel == 0 and user_channel: if ((player.channel != user_channel) and not player.current and player.position == 0 and len(player.queue) == 0): await player.move_to(user_channel) return True else: return False
async def command_queue_clear(self, ctx: commands.Context): """Clears the queue.""" try: player = lavalink.get_player(ctx.guild.id) except KeyError: return await self.send_embed_msg( ctx, title=_("There's nothing in the queue.")) dj_enabled = self._dj_status_cache.setdefault( ctx.guild.id, await self.config.guild(ctx.guild).dj_enabled()) if not self._player_check(ctx) or not player.queue: return await self.send_embed_msg( ctx, title=_("There's nothing in the queue.")) if (dj_enabled and not await self._can_instaskip(ctx, ctx.author) and not await self.is_requester_alone(ctx)): return await self.send_embed_msg( ctx, title=_("Unable To Clear Queue"), description=_("You need the DJ role to clear the queue."), ) player.queue.clear() await self.send_embed_msg(ctx, title=_("Queue Modified"), description=_("The queue has been cleared."))
async def on_voice_state_update(self, member: discord.Member, before: discord.VoiceState, after: discord.VoiceState) -> None: if await self.bot.cog_disabled_in_guild(self, member.guild): return await self.cog_ready_event.wait() if after.channel != before.channel: try: self.skip_votes[before.channel.guild.id].discard(member.id) except (ValueError, KeyError, AttributeError): pass channel = self.rgetattr(member, "voice.channel", None) bot_voice_state = self.rgetattr(member, "guild.me.voice.self_deaf", None) if (channel and bot_voice_state is False and await self.config.guild(member.guild).auto_deafen()): try: player = lavalink.get_player(channel.guild.id) except (KeyError, AttributeError): pass else: if player.channel.id == channel.id: await self.self_deafen(player)
async def command_now(self, ctx: commands.Context): """Now playing.""" if not self._player_check(ctx): return await self.send_embed_msg(ctx, title=_("Nothing playing.")) emoji = { "prev": "\N{BLACK LEFT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}\N{VARIATION SELECTOR-16}", "stop": "\N{BLACK SQUARE FOR STOP}\N{VARIATION SELECTOR-16}", "pause": "\N{BLACK RIGHT-POINTING TRIANGLE WITH DOUBLE VERTICAL BAR}\N{VARIATION SELECTOR-16}", "next": "\N{BLACK RIGHT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}\N{VARIATION SELECTOR-16}", "close": "\N{CROSS MARK}", } expected = tuple(emoji.values()) player = lavalink.get_player(ctx.guild.id) player.store("notify_channel", ctx.channel.id) if player.current: arrow = await self.draw_time(ctx) pos = self.format_time(player.position) if player.current.is_stream: dur = "LIVE" else: dur = self.format_time(player.current.length) song = (await self.get_track_description( player.current, self.local_folder_current_path) or "") song += _("\n Requested by: **{track.requester}**").format( track=player.current) song += "\n\n{arrow}`{pos}`/`{dur}`".format(arrow=arrow, pos=pos, dur=dur) else: song = _("Nothing.") if player.fetch("np_message") is not None: with contextlib.suppress(discord.HTTPException): await player.fetch("np_message").delete() embed = discord.Embed(title=_("Now Playing"), description=song) guild_data = await self.config.guild(ctx.guild).all() if guild_data[ "thumbnail"] and player.current and player.current.thumbnail: embed.set_thumbnail(url=player.current.thumbnail) shuffle = guild_data["shuffle"] repeat = guild_data["repeat"] autoplay = guild_data["auto_play"] text = "" text += ( _("Auto-Play") + ": " + ("\N{WHITE HEAVY CHECK MARK}" if autoplay else "\N{CROSS MARK}")) text += ( (" | " if text else "") + _("Shuffle") + ": " + ("\N{WHITE HEAVY CHECK MARK}" if shuffle else "\N{CROSS MARK}")) text += ( (" | " if text else "") + _("Repeat") + ": " + ("\N{WHITE HEAVY CHECK MARK}" if repeat else "\N{CROSS MARK}")) message = await self.send_embed_msg(ctx, embed=embed, footer=text) player.store("np_message", message) dj_enabled = self._dj_status_cache.setdefault( ctx.guild.id, await self.config.guild(ctx.guild).dj_enabled()) vote_enabled = await self.config.guild(ctx.guild).vote_enabled() if ((dj_enabled or vote_enabled) and not await self._can_instaskip(ctx, ctx.author) and not await self.is_requester_alone(ctx)): return if not player.queue and not autoplay: expected = (emoji["stop"], emoji["pause"], emoji["close"]) task: Optional[asyncio.Task] if player.current: task = start_adding_reactions(message, expected[:5]) else: task = None try: (r, u) = await self.bot.wait_for( "reaction_add", check=ReactionPredicate.with_emojis(expected, message, ctx.author), timeout=30.0, ) except asyncio.TimeoutError: return await self._clear_react(message, emoji) else: if task is not None: task.cancel() reacts = {v: k for k, v in emoji.items()} react = reacts[r.emoji] if react == "prev": await self._clear_react(message, emoji) await ctx.invoke(self.command_prev) elif react == "stop": await self._clear_react(message, emoji) await ctx.invoke(self.command_stop) elif react == "pause": await self._clear_react(message, emoji) await ctx.invoke(self.command_pause) elif react == "next": await self._clear_react(message, emoji) await ctx.invoke(self.command_skip) elif react == "close": await message.delete()
async def _playlist_check(self, ctx: commands.Context) -> bool: if not self._player_check(ctx): if self.lavalink_connection_aborted: msg = _("Connection to Lavalink has failed") desc = EmptyEmbed if await self.bot.is_owner(ctx.author): desc = _("Please check your console or logs for details.") await self.send_embed_msg(ctx, title=msg, description=desc) return False try: if (not ctx.author.voice.channel.permissions_for( ctx.me).connect or not ctx.author.voice.channel.permissions_for( ctx.me).move_members and self.is_vc_full(ctx.author.voice.channel)): await self.send_embed_msg( ctx, title=_("Unable To Get Playlists"), description= _("I don't have permission to connect to your channel." ), ) return False await lavalink.connect( ctx.author.voice.channel, deafen=await self.config.guild_from_id(ctx.guild.id).auto_deafen(), ) player = lavalink.get_player(ctx.guild.id) player.store("connect", datetime.datetime.utcnow()) player.store("channel", ctx.channel.id) player.store("guild", ctx.guild.id) except IndexError: await self.send_embed_msg( ctx, title=_("Unable To Get Playlists"), description=_( "Connection to Lavalink has not yet been established." ), ) return False except AttributeError: await self.send_embed_msg( ctx, title=_("Unable To Get Playlists"), description=_("Connect to a voice channel first."), ) return False player = lavalink.get_player(ctx.guild.id) player.store("channel", ctx.channel.id) player.store("guild", ctx.guild.id) if (not ctx.author.voice or ctx.author.voice.channel != player.channel ) and not await self._can_instaskip(ctx, ctx.author): await self.send_embed_msg( ctx, title=_("Unable To Get Playlists"), description= _("You must be in the voice channel to use the playlist command." ), ) return False await self._eq_check(ctx, player) await self.set_player_settings(ctx) return True
async def _skip_action(self, ctx: commands.Context, skip_to_track: int = None) -> None: player = lavalink.get_player(ctx.guild.id) autoplay = await self.config.guild(player.channel.guild).auto_play() if not player.current or (not player.queue and not autoplay): try: pos, dur = player.position, player.current.length except AttributeError: await self.send_embed_msg(ctx, title=_("There's nothing in the queue.")) return time_remain = self.format_time(dur - pos) if player.current.is_stream: embed = discord.Embed(title=_("There's nothing in the queue.")) embed.set_footer( text=_("Currently livestreaming {track}").format(track=player.current.title) ) else: embed = discord.Embed(title=_("There's nothing in the queue.")) embed.set_footer( text=_("{time} left on {track}").format( time=time_remain, track=player.current.title ) ) await self.send_embed_msg(ctx, embed=embed) return elif autoplay and not player.queue: embed = discord.Embed( title=_("Track Skipped"), description=await self.get_track_description( player.current, self.local_folder_current_path ), ) await self.send_embed_msg(ctx, embed=embed) await player.skip() return queue_to_append = [] if skip_to_track is not None and skip_to_track != 1: if skip_to_track < 1: await self.send_embed_msg( ctx, title=_("Track number must be equal to or greater than 1.") ) return elif skip_to_track > len(player.queue): await self.send_embed_msg( ctx, title=_("There are only {queuelen} songs currently queued.").format( queuelen=len(player.queue) ), ) return embed = discord.Embed( title=_("{skip_to_track} Tracks Skipped").format(skip_to_track=skip_to_track) ) await self.send_embed_msg(ctx, embed=embed) if player.repeat: queue_to_append = player.queue[0 : min(skip_to_track - 1, len(player.queue) - 1)] player.queue = player.queue[ min(skip_to_track - 1, len(player.queue) - 1) : len(player.queue) ] else: embed = discord.Embed( title=_("Track Skipped"), description=await self.get_track_description( player.current, self.local_folder_current_path ), ) await self.send_embed_msg(ctx, embed=embed) self.bot.dispatch("red_audio_skip_track", player.channel.guild, player.current, ctx.author) await player.play() player.queue += queue_to_append
async def player_automated_timer(self) -> None: stop_times: Dict = {} pause_times: Dict = {} while True: async for p in AsyncIter(lavalink.all_players()): server = p.channel.guild if await self.bot.cog_disabled_in_guild(self, server): continue if [self.bot.user] == p.channel.members: stop_times.setdefault(server.id, time.time()) pause_times.setdefault(server.id, time.time()) else: stop_times.pop(server.id, None) if p.paused and server.id in pause_times: try: await p.pause(False) except Exception as err: debug_exc_log( log, err, f"Exception raised in Audio's unpausing player for {server.id}.", ) pause_times.pop(server.id, None) servers = stop_times.copy() servers.update(pause_times) async for sid in AsyncIter(servers, steps=5): server_obj = self.bot.get_guild(sid) if sid in stop_times and await self.config.guild( server_obj).emptydc_enabled(): emptydc_timer = await self.config.guild(server_obj ).emptydc_timer() if (time.time() - stop_times[sid]) >= emptydc_timer: stop_times.pop(sid) try: player = lavalink.get_player(sid) await self.api_interface.persistent_queue_api.drop( sid) await player.stop() await player.disconnect() except Exception as err: if "No such player for that guild" in str(err): stop_times.pop(sid, None) debug_exc_log( log, err, f"Exception raised in Audio's emptydc_timer for {sid}." ) elif (sid in pause_times and await self.config.guild(server_obj).emptypause_enabled()): emptypause_timer = await self.config.guild( server_obj).emptypause_timer() if (time.time() - pause_times.get(sid, 0)) >= emptypause_timer: try: await lavalink.get_player(sid).pause() except Exception as err: if "No such player for that guild" in str(err): pause_times.pop(sid, None) debug_exc_log( log, err, f"Exception raised in Audio's pausing for {sid}." ) await asyncio.sleep(5)
async def _get_spotify_tracks( self, ctx: commands.Context, query: Query, forced: bool = False ) -> Union[discord.Message, List[lavalink.Track], lavalink.Track]: if ctx.invoked_with in ["play", "genre"]: enqueue_tracks = True else: enqueue_tracks = False player = lavalink.get_player(ctx.guild.id) api_data = await self._check_api_tokens() if any([not api_data["spotify_client_id"], not api_data["spotify_client_secret"]]): return await self.send_embed_msg( ctx, title=_("Invalid Environment"), description=_( "The owner needs to set the Spotify client ID and Spotify client secret, " "before Spotify URLs or codes can be used. " "\nSee `{prefix}audioset spotifyapi` for instructions." ).format(prefix=ctx.prefix), ) elif not api_data["youtube_api"]: return await self.send_embed_msg( ctx, title=_("Invalid Environment"), description=_( "The owner needs to set the YouTube API key before Spotify URLs or " "codes can be used.\nSee `{prefix}audioset youtubeapi` for instructions." ).format(prefix=ctx.prefix), ) try: if self.play_lock[ctx.message.guild.id]: return await self.send_embed_msg( ctx, title=_("Unable To Get Tracks"), description=_("Wait until the playlist has finished loading."), ) except KeyError: pass if query.single_track: try: res = await self.api_interface.spotify_query( ctx, "track", query.id, skip_youtube=True, notifier=None ) if not res: title = _("Nothing found.") embed = discord.Embed(title=title) if query.is_local and query.suffix in _PARTIALLY_SUPPORTED_MUSIC_EXT: title = _("Track is not playable.") description = _( "**{suffix}** is not a fully supported " "format and some tracks may not play." ).format(suffix=query.suffix) embed = discord.Embed(title=title, description=description) return await self.send_embed_msg(ctx, embed=embed) except SpotifyFetchError as error: self.update_player_lock(ctx, False) return await self.send_embed_msg( ctx, title=error.message.format(prefix=ctx.prefix) ) except Exception as e: self.update_player_lock(ctx, False) raise e self.update_player_lock(ctx, False) try: if enqueue_tracks: new_query = Query.process_input(res[0], self.local_folder_current_path) new_query.start_time = query.start_time return await self._enqueue_tracks(ctx, new_query) else: query = Query.process_input(res[0], self.local_folder_current_path) try: result, called_api = await self.api_interface.fetch_track( ctx, player, query ) except TrackEnqueueError: self.update_player_lock(ctx, False) return await self.send_embed_msg( ctx, title=_("Unable to Get Track"), description=_( "I'm unable to get a track from Lavalink at the moment, " "try again in a few minutes." ), ) tracks = result.tracks if not tracks: embed = discord.Embed(title=_("Nothing found.")) if query.is_local and query.suffix in _PARTIALLY_SUPPORTED_MUSIC_EXT: embed = discord.Embed(title=_("Track is not playable.")) embed.description = _( "**{suffix}** is not a fully supported format and some " "tracks may not play." ).format(suffix=query.suffix) return await self.send_embed_msg(ctx, embed=embed) single_track = tracks[0] single_track.start_timestamp = query.start_time * 1000 single_track = [single_track] return single_track except KeyError: self.update_player_lock(ctx, False) return await self.send_embed_msg( ctx, title=_("Invalid Environment"), description=_( "The Spotify API key or client secret has not been set properly. " "\nUse `{prefix}audioset spotifyapi` for instructions." ).format(prefix=ctx.prefix), ) except Exception as e: self.update_player_lock(ctx, False) raise e elif query.is_album or query.is_playlist: try: self.update_player_lock(ctx, True) track_list = await self.fetch_spotify_playlist( ctx, "album" if query.is_album else "playlist", query, enqueue_tracks, forced=forced, ) finally: self.update_player_lock(ctx, False) return track_list else: return await self.send_embed_msg( ctx, title=_("Unable To Find Tracks"), description=_("This doesn't seem to be a supported Spotify URL or code."), )
async def fetch_spotify_playlist( self, ctx: commands.Context, stype: str, query: Query, enqueue: bool = False, forced: bool = False, ): player = lavalink.get_player(ctx.guild.id) try: embed1 = discord.Embed(title=_("Please wait, finding tracks...")) playlist_msg = await self.send_embed_msg(ctx, embed=embed1) notifier = Notifier( ctx, playlist_msg, { "spotify": _("Getting track {num}/{total}..."), "youtube": _("Matching track {num}/{total}..."), "lavalink": _("Loading track {num}/{total}..."), "lavalink_time": _("Approximate time remaining: {seconds}"), }, ) track_list = await self.api_interface.spotify_enqueue( ctx, stype, query.id, enqueue=enqueue, player=player, lock=self.update_player_lock, notifier=notifier, forced=forced, query_global=await self.config.global_db_enabled(), ) except SpotifyFetchError as error: self.update_player_lock(ctx, False) return await self.send_embed_msg( ctx, title=_("Invalid Environment"), description=error.message.format(prefix=ctx.prefix), ) except TrackEnqueueError: self.update_player_lock(ctx, False) return await self.send_embed_msg( ctx, title=_("Unable to Get Track"), description=_( "I'm unable to get a track from Lavalink at the moment, " "try again in a few minutes." ), error=True, ) except (RuntimeError, aiohttp.ServerDisconnectedError): self.update_player_lock(ctx, False) error_embed = discord.Embed( title=_("The connection was reset while loading the playlist.") ) await self.send_embed_msg(ctx, embed=error_embed) return None except Exception as e: self.update_player_lock(ctx, False) raise e finally: self.update_player_lock(ctx, False) return track_list
async def _enqueue_tracks( self, ctx: commands.Context, query: Union[Query, list], enqueue: bool = True ) -> Union[discord.Message, List[lavalink.Track], lavalink.Track]: player = lavalink.get_player(ctx.guild.id) try: if self.play_lock[ctx.message.guild.id]: return await self.send_embed_msg( ctx, title=_("Unable To Get Tracks"), description=_("Wait until the playlist has finished loading."), ) except KeyError: self.update_player_lock(ctx, True) guild_data = await self.config.guild(ctx.guild).all() first_track_only = False single_track = None index = None playlist_data = None playlist_url = None seek = 0 if type(query) is not list: if not await self.is_query_allowed(self.config, ctx, f"{query}", query_obj=query): raise QueryUnauthorized( _("{query} is not an allowed query.").format(query=query.to_string_user()) ) if query.single_track: first_track_only = True index = query.track_index if query.start_time: seek = query.start_time try: result, called_api = await self.api_interface.fetch_track(ctx, player, query) except TrackEnqueueError: self.update_player_lock(ctx, False) return await self.send_embed_msg( ctx, title=_("Unable to Get Track"), description=_( "I'm unable to get a track from Lavalink at the moment, " "try again in a few minutes." ), ) except Exception as e: self.update_player_lock(ctx, False) raise e tracks = result.tracks playlist_data = result.playlist_info if not enqueue: return tracks if not tracks: self.update_player_lock(ctx, False) title = _("Nothing found.") embed = discord.Embed(title=title) if result.exception_message: if "Status Code" in result.exception_message: embed.set_footer(text=result.exception_message[:2000]) else: embed.set_footer(text=result.exception_message[:2000].replace("\n", "")) if await self.config.use_external_lavalink() and query.is_local: embed.description = _( "Local tracks will not work " "if the `Lavalink.jar` cannot see the track.\n" "This may be due to permissions or because Lavalink.jar is being run " "in a different machine than the local tracks." ) elif query.is_local and query.suffix in _PARTIALLY_SUPPORTED_MUSIC_EXT: title = _("Track is not playable.") embed = discord.Embed(title=title) embed.description = _( "**{suffix}** is not a fully supported format and some " "tracks may not play." ).format(suffix=query.suffix) return await self.send_embed_msg(ctx, embed=embed) else: tracks = query queue_dur = await self.queue_duration(ctx) queue_total_duration = self.format_time(queue_dur) before_queue_length = len(player.queue) if not first_track_only and len(tracks) > 1: # a list of Tracks where all should be enqueued # this is a Spotify playlist already made into a list of Tracks or a # url where Lavalink handles providing all Track objects to use, like a # YouTube or Soundcloud playlist if len(player.queue) >= 10000: return await self.send_embed_msg(ctx, title=_("Queue size limit reached.")) track_len = 0 empty_queue = not player.queue async for track in AsyncIter(tracks): if len(player.queue) >= 10000: continue query = Query.process_input(track, self.local_folder_current_path) if not await self.is_query_allowed( self.config, ctx, f"{track.title} {track.author} {track.uri} " f"{str(query)}", query_obj=query, ): if IS_DEBUG: log.debug(f"Query is not allowed in {ctx.guild} ({ctx.guild.id})") continue elif guild_data["maxlength"] > 0: if self.is_track_length_allowed(track, guild_data["maxlength"]): track_len += 1 track.extras.update( { "enqueue_time": int(time.time()), "vc": player.channel.id, "requester": ctx.author.id, } ) player.add(ctx.author, track) self.bot.dispatch( "red_audio_track_enqueue", player.channel.guild, track, ctx.author ) else: track_len += 1 track.extras.update( { "enqueue_time": int(time.time()), "vc": player.channel.id, "requester": ctx.author.id, } ) player.add(ctx.author, track) self.bot.dispatch( "red_audio_track_enqueue", player.channel.guild, track, ctx.author ) player.maybe_shuffle(0 if empty_queue else 1) if len(tracks) > track_len: maxlength_msg = _(" {bad_tracks} tracks cannot be queued.").format( bad_tracks=(len(tracks) - track_len) ) else: maxlength_msg = "" playlist_name = escape( playlist_data.name if playlist_data else _("No Title"), formatting=True ) embed = discord.Embed( description=bold(f"[{playlist_name}]({playlist_url})") if playlist_url else playlist_name, title=_("Playlist Enqueued"), ) embed.set_footer( text=_("Added {num} tracks to the queue.{maxlength_msg}").format( num=track_len, maxlength_msg=maxlength_msg ) ) if not guild_data["shuffle"] and queue_dur > 0: embed.set_footer( text=_( "{time} until start of playlist playback: starts at #{position} in queue" ).format(time=queue_total_duration, position=before_queue_length + 1) ) if not player.current: await player.play() self.update_player_lock(ctx, False) message = await self.send_embed_msg(ctx, embed=embed) return tracks or message else: single_track = None # a ytsearch: prefixed item where we only need the first Track returned # this is in the case of [p]play <query>, a single Spotify url/code # or this is a localtrack item try: if len(player.queue) >= 10000: return await self.send_embed_msg(ctx, title=_("Queue size limit reached.")) single_track = ( tracks if isinstance(tracks, lavalink.rest_api.Track) else tracks[index] if index else tracks[0] ) if seek and seek > 0: single_track.start_timestamp = seek * 1000 query = Query.process_input(single_track, self.local_folder_current_path) if not await self.is_query_allowed( self.config, ctx, ( f"{single_track.title} {single_track.author} {single_track.uri} " f"{str(query)}" ), query_obj=query, ): if IS_DEBUG: log.debug(f"Query is not allowed in {ctx.guild} ({ctx.guild.id})") self.update_player_lock(ctx, False) return await self.send_embed_msg( ctx, title=_("This track is not allowed in this server.") ) elif guild_data["maxlength"] > 0: if self.is_track_length_allowed(single_track, guild_data["maxlength"]): single_track.extras.update( { "enqueue_time": int(time.time()), "vc": player.channel.id, "requester": ctx.author.id, } ) player.add(ctx.author, single_track) player.maybe_shuffle() self.bot.dispatch( "red_audio_track_enqueue", player.channel.guild, single_track, ctx.author, ) else: self.update_player_lock(ctx, False) return await self.send_embed_msg( ctx, title=_("Track exceeds maximum length.") ) else: single_track.extras.update( { "enqueue_time": int(time.time()), "vc": player.channel.id, "requester": ctx.author.id, } ) player.add(ctx.author, single_track) player.maybe_shuffle() self.bot.dispatch( "red_audio_track_enqueue", player.channel.guild, single_track, ctx.author ) except IndexError: self.update_player_lock(ctx, False) title = _("Nothing found") desc = EmptyEmbed if await self.bot.is_owner(ctx.author): desc = _("Please check your console or logs for details.") return await self.send_embed_msg(ctx, title=title, description=desc) except Exception as e: self.update_player_lock(ctx, False) raise e description = await self.get_track_description( single_track, self.local_folder_current_path ) embed = discord.Embed(title=_("Track Enqueued"), description=description) if not guild_data["shuffle"] and queue_dur > 0: embed.set_footer( text=_("{time} until track playback: #{position} in queue").format( time=queue_total_duration, position=before_queue_length + 1 ) ) if not player.current: await player.play() self.update_player_lock(ctx, False) message = await self.send_embed_msg(ctx, embed=embed) return single_track or message