async def command_equalizer(self, ctx: commands.Context):
        """Equalizer management."""
        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)
Exemple #2
0
    async def _confirm(ctx: commands.Context) -> bool:
        """Ask "Are you sure?" and get the response as a bool."""
        if ctx.guild is None or ctx.guild.me.permissions_in(
                ctx.channel).add_reactions:
            msg = await ctx.send(_("Are you sure?"))
            # noinspection PyAsyncCall
            task = start_adding_reactions(msg,
                                          ReactionPredicate.YES_OR_NO_EMOJIS)
            pred = ReactionPredicate.yes_or_no(msg, ctx.author)
            try:
                await ctx.bot.wait_for("reaction_add", check=pred, timeout=30)
            except asyncio.TimeoutError:
                await ctx.send(_("Response timed out."))
                return False
            else:
                task.cancel()
                agreed = pred.result
            finally:
                await msg.delete()
        else:
            await ctx.send(_("Are you sure? (y/n)"))
            pred = MessagePredicate.yes_or_no(ctx)
            try:
                await ctx.bot.wait_for("message", check=pred, timeout=30)
            except asyncio.TimeoutError:
                await ctx.send(_("Response timed out."))
                return False
            else:
                agreed = pred.result

        if agreed is False:
            await ctx.send(_("Action cancelled."))
        return agreed
    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)
        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()
Exemple #4
0
    async def get_playlist_match(
        self,
        context: commands.Context,
        matches: MutableMapping,
        scope: str,
        author: discord.User,
        guild: discord.Guild,
        specified_user: bool = False,
    ) -> Tuple[Optional[Playlist], str, str]:
        """
        Parameters
        ----------
        context: commands.Context
            The context in which this is being called.
        matches: dict
            A dict of the matches found where key is scope and value is matches.
        scope:str
            The custom config scope. A value from :code:`PlaylistScope`.
        author: discord.User
            The user.
        guild: discord.Guild
            The guild.
        specified_user: bool
            Whether or not a user ID was specified via argparse.
        Returns
        -------
        Tuple[Optional[Playlist], str, str]
            Tuple of Playlist or None if none found, original user input and scope.
        Raises
        ------
        `TooManyMatches`
            When more than 10 matches are found or
            When multiple matches are found but none is selected.

        """
        correct_scope_matches: List[Playlist]
        original_input = matches.get("arg")
        lazy_match = False
        if scope is None:
            correct_scope_matches_temp: MutableMapping = matches.get("all")
            lazy_match = True
        else:
            correct_scope_matches_temp: MutableMapping = matches.get(scope)
        guild_to_query = guild.id
        user_to_query = author.id
        correct_scope_matches_user = []
        correct_scope_matches_guild = []
        correct_scope_matches_global = []
        if not correct_scope_matches_temp:
            return None, original_input, scope or PlaylistScope.GUILD.value
        if lazy_match or (scope == PlaylistScope.USER.value):
            correct_scope_matches_user = [
                p for p in matches.get(PlaylistScope.USER.value)
                if user_to_query == p.scope_id
            ]
        if lazy_match or (scope == PlaylistScope.GUILD.value
                          and not correct_scope_matches_user):
            if specified_user:
                correct_scope_matches_guild = [
                    p for p in matches.get(PlaylistScope.GUILD.value) if
                    guild_to_query == p.scope_id and p.author == user_to_query
                ]
            else:
                correct_scope_matches_guild = [
                    p for p in matches.get(PlaylistScope.GUILD.value)
                    if guild_to_query == p.scope_id
                ]
        if lazy_match or (scope == PlaylistScope.GLOBAL.value
                          and not correct_scope_matches_user
                          and not correct_scope_matches_guild):
            if specified_user:
                correct_scope_matches_global = [
                    p for p in matches.get(PlaylistScope.GLOBAL.value)
                    if p.author == user_to_query
                ]
            else:
                correct_scope_matches_global = [
                    p for p in matches.get(PlaylistScope.GLOBAL.value)
                ]

        correct_scope_matches = [
            *correct_scope_matches_global,
            *correct_scope_matches_guild,
            *correct_scope_matches_user,
        ]
        match_count = len(correct_scope_matches)
        if match_count > 1:
            correct_scope_matches2 = [
                p for p in correct_scope_matches
                if p.name == str(original_input).strip()
            ]
            if correct_scope_matches2:
                correct_scope_matches = correct_scope_matches2
            elif original_input.isnumeric():
                arg = int(original_input)
                correct_scope_matches3 = [
                    p for p in correct_scope_matches if p.id == arg
                ]
                if correct_scope_matches3:
                    correct_scope_matches = correct_scope_matches3
        match_count = len(correct_scope_matches)
        # We done all the trimming we can with the info available time to ask the user
        if match_count > 10:
            if original_input.isnumeric():
                arg = int(original_input)
                correct_scope_matches = [
                    p for p in correct_scope_matches if p.id == arg
                ]
            if match_count > 10:
                raise TooManyMatches(
                    _("{match_count} playlists match {original_input}: "
                      "Please try to be more specific, or use the playlist ID."
                      ).format(match_count=match_count,
                               original_input=original_input))
        elif match_count == 1:
            return correct_scope_matches[
                0], original_input, correct_scope_matches[0].scope
        elif match_count == 0:
            return None, original_input, scope or PlaylistScope.GUILD.value

        # TODO : Convert this section to a new paged reaction menu when Toby Menus are Merged
        pos_len = 3
        playlists = f"{'#':{pos_len}}\n"
        number = 0
        correct_scope_matches = sorted(correct_scope_matches,
                                       key=lambda x: x.name.lower())
        async for number, playlist in AsyncIter(
                correct_scope_matches).enumerate(start=1):
            author = self.bot.get_user(
                playlist.author) or playlist.author or _("Unknown")
            line = _("{number}."
                     "    <{playlist.name}>\n"
                     " - Scope:  < {scope} >\n"
                     " - ID:     < {playlist.id} >\n"
                     " - Tracks: < {tracks} >\n"
                     " - Author: < {author} >\n\n").format(
                         number=number,
                         playlist=playlist,
                         scope=self.humanize_scope(playlist.scope),
                         tracks=len(playlist.tracks),
                         author=author,
                     )
            playlists += line

        embed = discord.Embed(
            title=_("{playlists} playlists found, which one would you like?").
            format(playlists=number),
            description=box(playlists, lang="md"),
            colour=await context.embed_colour(),
        )
        msg = await context.send(embed=embed)
        avaliable_emojis = ReactionPredicate.NUMBER_EMOJIS[1:]
        avaliable_emojis.append("🔟")
        emojis = avaliable_emojis[:len(correct_scope_matches)]
        emojis.append("\N{CROSS MARK}")
        start_adding_reactions(msg, emojis)
        pred = ReactionPredicate.with_emojis(emojis, msg, user=context.author)
        try:
            await context.bot.wait_for("reaction_add", check=pred, timeout=60)
        except asyncio.TimeoutError:
            with contextlib.suppress(discord.HTTPException):
                await msg.delete()
            raise TooManyMatches(
                _("Too many matches found and you did not select which one you wanted."
                  ))
        if emojis[pred.result] == "\N{CROSS MARK}":
            with contextlib.suppress(discord.HTTPException):
                await msg.delete()
            raise TooManyMatches(
                _("Too many matches found and you did not select which one you wanted."
                  ))
        with contextlib.suppress(discord.HTTPException):
            await msg.delete()
        return (
            correct_scope_matches[pred.result],
            original_input,
            correct_scope_matches[pred.result].scope,
        )
    async def command_equalizer_save(self,
                                     ctx: commands.Context,
                                     eq_preset: str = None):
        """Save the current eq settings to a 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())
        if dj_enabled and not await self._can_instaskip(ctx, ctx.author):
            ctx.command.reset_cooldown(ctx)
            return await self.send_embed_msg(
                ctx,
                title=_("Unable To Save Preset"),
                description=_(
                    "You need the DJ role to save equalizer presets."),
            )
        if not eq_preset:
            await self.send_embed_msg(
                ctx, title=_("Please enter a name for this equalizer preset."))
            try:
                eq_name_msg = await self.bot.wait_for(
                    "message",
                    timeout=15.0,
                    check=MessagePredicate.regex(
                        fr"^(?!{re.escape(ctx.prefix)})", ctx),
                )
                eq_preset = eq_name_msg.content.split(" ")[0].strip(
                    '"').lower()
            except asyncio.TimeoutError:
                ctx.command.reset_cooldown(ctx)
                return await self.send_embed_msg(
                    ctx,
                    title=_("Unable To Save Preset"),
                    description=
                    _("No equalizer preset name entered, try the command again later."
                      ),
                )
        eq_preset = eq_preset or ""
        eq_exists_msg = None
        eq_preset = eq_preset.lower().lstrip(ctx.prefix)
        eq_presets = await self.config.custom("EQUALIZER",
                                              ctx.guild.id).eq_presets()
        eq_list = list(eq_presets.keys())

        if len(eq_preset) > 20:
            ctx.command.reset_cooldown(ctx)
            return await self.send_embed_msg(
                ctx,
                title=_("Unable To Save Preset"),
                description=_("Try the command again with a shorter name."),
            )
        if eq_preset in eq_list:
            eq_exists_msg = await self.send_embed_msg(
                ctx,
                title=_(
                    "Preset name already exists, do you want to replace it?"))
            start_adding_reactions(eq_exists_msg,
                                   ReactionPredicate.YES_OR_NO_EMOJIS)
            pred = ReactionPredicate.yes_or_no(eq_exists_msg, ctx.author)
            await self.bot.wait_for("reaction_add", check=pred)
            if not pred.result:
                await self._clear_react(eq_exists_msg)
                embed2 = discord.Embed(colour=await ctx.embed_colour(),
                                       title=_("Not saving preset."))
                ctx.command.reset_cooldown(ctx)
                return await eq_exists_msg.edit(embed=embed2)

        player = lavalink.get_player(ctx.guild.id)
        eq = player.fetch("eq", Equalizer())
        to_append = {eq_preset: {"author": ctx.author.id, "bands": eq.bands}}
        new_eq_presets = {**eq_presets, **to_append}
        await self.config.custom("EQUALIZER",
                                 ctx.guild.id).eq_presets.set(new_eq_presets)
        embed3 = discord.Embed(
            colour=await ctx.embed_colour(),
            title=_("Current equalizer saved to the {preset_name} preset.").
            format(preset_name=eq_preset),
        )
        if eq_exists_msg:
            await self._clear_react(eq_exists_msg)
            await eq_exists_msg.edit(embed=embed3)
        else:
            await self.send_embed_msg(ctx, embed=embed3)
Exemple #6
0
    async def command_queue(self, ctx: commands.Context, *, page: int = 1):
        """List the songs in the queue."""
        async def _queue_menu(
            ctx: commands.Context,
            pages: list,
            controls: MutableMapping,
            message: discord.Message,
            page: int,
            timeout: float,
            emoji: str,
        ):
            if message:
                await ctx.send_help(self.command_queue)
                with contextlib.suppress(discord.HTTPException):
                    await message.delete()
                return None

        queue_controls = {
            "\N{LEFTWARDS BLACK ARROW}\N{VARIATION SELECTOR-16}": prev_page,
            "\N{CROSS MARK}": close_menu,
            "\N{BLACK RIGHTWARDS ARROW}\N{VARIATION SELECTOR-16}": next_page,
            "\N{INFORMATION SOURCE}\N{VARIATION SELECTOR-16}": _queue_menu,
        }

        if not self._player_check(ctx):
            return await self.send_embed_msg(
                ctx, title=_("There's nothing in the queue."))
        player = lavalink.get_player(ctx.guild.id)

        if player.current and not player.queue:
            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 += f"\n\n{arrow}`{pos}`/`{dur}`"
            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}"))
            embed.set_footer(text=text)
            message = await self.send_embed_msg(ctx, embed=embed)
            dj_enabled = self._dj_status_cache.setdefault(
                ctx.guild.id, guild_data["dj_enabled"])
            vote_enabled = guild_data["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

            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())
            if not player.queue and not autoplay:
                expected = (emoji["stop"], emoji["pause"], emoji["close"])
            if player.current:
                task: Optional[asyncio.Task] = start_adding_reactions(
                    message, expected[:5])
            else:
                task: Optional[asyncio.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()
            return
        elif not player.current and not player.queue:
            return await self.send_embed_msg(
                ctx, title=_("There's nothing in the queue."))

        async with ctx.typing():
            limited_queue = player.queue[:
                                         500]  # TODO: Improve when Toby menu's are merged
            len_queue_pages = math.ceil(len(limited_queue) / 10)
            queue_page_list = []
            async for page_num in AsyncIter(range(1, len_queue_pages + 1)):
                embed = await self._build_queue_page(ctx, limited_queue,
                                                     player, page_num)
                queue_page_list.append(embed)
            if page > len_queue_pages:
                page = len_queue_pages
        return await menu(ctx,
                          queue_page_list,
                          queue_controls,
                          page=(page - 1))
Exemple #7
0
    async def _save_trivia_list(self, ctx: commands.Context,
                                attachment: discord.Attachment) -> None:
        """Checks and saves a trivia list to data folder.

        Parameters
        ----------
        file : discord.Attachment
            A discord message attachment.

        Returns
        -------
        None
        """
        filename = attachment.filename.rsplit(".", 1)[0].casefold()

        # Check if trivia filename exists in core files or if it is a command
        if filename in self.trivia.all_commands or any(
                filename == item.stem for item in get_core_lists()):
            await ctx.send(
                _("{filename} is a reserved trivia name and cannot be replaced.\n"
                  "Choose another name.").format(filename=filename))
            return

        file = cog_data_path(self) / f"{filename}.yaml"
        if file.exists():
            overwrite_message = _(
                "{filename} already exists. Do you wish to overwrite?").format(
                    filename=filename)

            can_react = ctx.channel.permissions_for(ctx.me).add_reactions
            if not can_react:
                overwrite_message += " (y/n)"

            overwrite_message_object: discord.Message = await ctx.send(
                overwrite_message)
            if can_react:
                # noinspection PyAsyncCall
                start_adding_reactions(overwrite_message_object,
                                       ReactionPredicate.YES_OR_NO_EMOJIS)
                pred = ReactionPredicate.yes_or_no(overwrite_message_object,
                                                   ctx.author)
                event = "reaction_add"
            else:
                pred = MessagePredicate.yes_or_no(ctx=ctx)
                event = "message"
            try:
                await ctx.bot.wait_for(event, check=pred, timeout=30)
            except asyncio.TimeoutError:
                await ctx.send(_("You took too long answering."))
                return

            if pred.result is False:
                await ctx.send(_("I am not replacing the existing file."))
                return

        buffer = io.BytesIO(await attachment.read())
        yaml.safe_load(buffer)
        buffer.seek(0)

        with file.open("wb") as fp:
            fp.write(buffer.read())
        await ctx.send(
            _("Saved Trivia list as {filename}.").format(filename=filename))