diff options
| -rw-r--r-- | bot/constants.py | 4 | ||||
| -rw-r--r-- | bot/exts/evergreen/emoji_count.py | 91 | ||||
| -rw-r--r-- | bot/exts/evergreen/minesweeper.py | 12 | ||||
| -rw-r--r-- | bot/exts/evergreen/source.py | 109 | 
4 files changed, 210 insertions, 6 deletions
| diff --git a/bot/constants.py b/bot/constants.py index 7ec8ac27..e113428e 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -267,3 +267,7 @@ POSITIVE_REPLIES = [  class Wikipedia:      total_chance = 3 + +class Source: +    github = "https://github.com/python-discord/seasonalbot" +    github_avatar_url = "https://avatars1.githubusercontent.com/u/9919" diff --git a/bot/exts/evergreen/emoji_count.py b/bot/exts/evergreen/emoji_count.py new file mode 100644 index 00000000..ef900199 --- /dev/null +++ b/bot/exts/evergreen/emoji_count.py @@ -0,0 +1,91 @@ +import datetime +import logging +import random +from typing import Dict, Optional + +import discord +from discord.ext import commands + +from bot.constants import Colours, ERROR_REPLIES + +log = logging.getLogger(__name__) + + +class EmojiCount(commands.Cog): +    """Command that give random emoji based on category.""" + +    def __init__(self, bot: commands.Bot): +        self.bot = bot + +    def embed_builder(self, emoji: dict) -> discord.Embed: +        """Generates an embed with the emoji names and count.""" +        embed = discord.Embed( +            color=Colours.orange, +            title="Emoji Count", +            timestamp=datetime.datetime.utcnow() +        ) + +        if len(emoji) == 1: +            for key, value in emoji.items(): +                embed.description = f"There are **{len(value)}** emojis in the **{key}** category" +                embed.set_thumbnail(url=random.choice(value).url) +        else: +            msg = '' +            for key, value in emoji.items(): +                emoji_choice = random.choice(value) +                emoji_info = f'There are **{len(value)}** emojis in the **{key}** category\n' +                msg += f'<:{emoji_choice.name}:{emoji_choice.id}> {emoji_info}' +            embed.description = msg +        return embed + +    @staticmethod +    def generate_invalid_embed(ctx: commands.Context) -> discord.Embed: +        """Genrates error embed.""" +        embed = discord.Embed( +            color=Colours.soft_red, +            title=random.choice(ERROR_REPLIES) +        ) + +        emoji_dict = {} +        for emoji in ctx.guild.emojis: +            emoji_dict[emoji.name.split("_")[0]] = [] + +        error_comp = ', '.join(key for key in emoji_dict.keys()) +        embed.description = f"These are the valid categories\n```{error_comp}```" +        return embed + +    def emoji_list(self, ctx: commands.Context, categories: dict) -> Dict: +        """Generates an embed with the emoji names and count.""" +        out = {category: [] for category in categories} + +        for emoji in ctx.guild.emojis: +            category = emoji.name.split('_')[0] +            if category in out: +                out[category].append(emoji) +        return out + +    @commands.command(name="emoji_count", aliases=["ec"]) +    async def ec(self, ctx: commands.Context, *, emoji: str = None) -> Optional[str]: +        """Returns embed with emoji category and info given by the user.""" +        emoji_dict = {} + +        for a in ctx.guild.emojis: +            if emoji is None: +                log.trace("Emoji Category not provided by the user") +                emoji_dict.update({a.name.split("_")[0]: []}) +            elif a.name.split("_")[0] in emoji: +                log.trace("Emoji Category provided by the user") +                emoji_dict.update({a.name.split("_")[0]: []}) + +        emoji_dict = self.emoji_list(ctx, emoji_dict) + +        if len(emoji_dict) == 0: +            embed = self.generate_invalid_embed(ctx) +        else: +            embed = self.embed_builder(emoji_dict) +        await ctx.send(embed=embed) + + +def setup(bot: commands.Bot) -> None: +    """Emoji Count Cog load.""" +    bot.add_cog(EmojiCount(bot)) diff --git a/bot/exts/evergreen/minesweeper.py b/bot/exts/evergreen/minesweeper.py index 3e40f493..286ac7a5 100644 --- a/bot/exts/evergreen/minesweeper.py +++ b/bot/exts/evergreen/minesweeper.py @@ -120,14 +120,14 @@ class Minesweeper(commands.Cog):      def format_for_discord(board: GameBoard) -> str:          """Format the board as a string for Discord."""          discord_msg = ( -            ":stop_button:    :regional_indicator_a::regional_indicator_b::regional_indicator_c:" -            ":regional_indicator_d::regional_indicator_e::regional_indicator_f::regional_indicator_g:" -            ":regional_indicator_h::regional_indicator_i::regional_indicator_j:\n\n" +            ":stop_button:    :regional_indicator_a: :regional_indicator_b: :regional_indicator_c: " +            ":regional_indicator_d: :regional_indicator_e: :regional_indicator_f: :regional_indicator_g: " +            ":regional_indicator_h: :regional_indicator_i: :regional_indicator_j:\n\n"          )          rows = []          for row_number, row in enumerate(board):              new_row = f"{MESSAGE_MAPPING[row_number + 1]}    " -            new_row += "".join(MESSAGE_MAPPING[cell] for cell in row) +            new_row += " ".join(MESSAGE_MAPPING[cell] for cell in row)              rows.append(new_row)          discord_msg += "\n".join(rows) @@ -158,7 +158,7 @@ class Minesweeper(commands.Cog):          if ctx.guild:              await ctx.send(f"{ctx.author.mention} is playing Minesweeper") -            chat_msg = await ctx.send(f"Here's there board!\n{self.format_for_discord(revealed_board)}") +            chat_msg = await ctx.send(f"Here's their board!\n{self.format_for_discord(revealed_board)}")          else:              chat_msg = None @@ -176,7 +176,7 @@ class Minesweeper(commands.Cog):          await game.dm_msg.delete()          game.dm_msg = await ctx.author.send(f"Here's your board!\n{self.format_for_discord(game.revealed)}")          if game.activated_on_server: -            await game.chat_msg.edit(content=f"Here's there board!\n{self.format_for_discord(game.revealed)}") +            await game.chat_msg.edit(content=f"Here's their board!\n{self.format_for_discord(game.revealed)}")      @commands.dm_only()      @minesweeper_group.command(name="flag") diff --git a/bot/exts/evergreen/source.py b/bot/exts/evergreen/source.py new file mode 100644 index 00000000..0725714f --- /dev/null +++ b/bot/exts/evergreen/source.py @@ -0,0 +1,109 @@ +import inspect +from pathlib import Path +from typing import Optional, Tuple, Union + +from discord import Embed +from discord.ext import commands + +from bot.constants import Source + +SourceType = Union[commands.Command, commands.Cog, str, commands.ExtensionNotLoaded] + + +class SourceConverter(commands.Converter): +    """Convert an argument into a help command, tag, command, or cog.""" + +    async def convert(self, ctx: commands.Context, argument: str) -> SourceType: +        """Convert argument into source object.""" +        cog = ctx.bot.get_cog(argument) +        if cog: +            return cog + +        cmd = ctx.bot.get_command(argument) +        if cmd: +            return cmd + +        raise commands.BadArgument( +            f"Unable to convert `{argument}` to valid command or Cog." +        ) + + +class BotSource(commands.Cog): +    """Displays information about the bot's source code.""" + +    def __init__(self, bot: commands.Bot): +        self.bot = bot + +    @commands.command(name="source", aliases=("src",)) +    async def source_command(self, ctx: commands.Context, *, source_item: SourceConverter = None) -> None: +        """Display information and a GitHub link to the source code of a command, tag, or cog.""" +        if not source_item: +            embed = Embed(title="Seasonal Bot's GitHub Repository") +            embed.add_field(name="Repository", value=f"[Go to GitHub]({Source.github})") +            embed.set_thumbnail(url=Source.github_avatar_url) +            await ctx.send(embed=embed) +            return + +        embed = await self.build_embed(source_item) +        await ctx.send(embed=embed) + +    def get_source_link(self, source_item: SourceType) -> Tuple[str, str, Optional[int]]: +        """ +        Build GitHub link of source item, return this link, file location and first line number. + +        Raise BadArgument if `source_item` is a dynamically-created object (e.g. via internal eval). +        """ +        if isinstance(source_item, commands.Command): +            src = source_item.callback.__code__ +            filename = src.co_filename +        else: +            src = type(source_item) +            try: +                filename = inspect.getsourcefile(src) +            except TypeError: +                raise commands.BadArgument("Cannot get source for a dynamically-created object.") + +        if not isinstance(source_item, str): +            try: +                lines, first_line_no = inspect.getsourcelines(src) +            except OSError: +                raise commands.BadArgument("Cannot get source for a dynamically-created object.") + +            lines_extension = f"#L{first_line_no}-L{first_line_no+len(lines)-1}" +        else: +            first_line_no = None +            lines_extension = "" + +        file_location = Path(filename).relative_to(Path.cwd()).as_posix() + +        url = f"{Source.github}/blob/master/{file_location}{lines_extension}" + +        return url, file_location, first_line_no or None + +    async def build_embed(self, source_object: SourceType) -> Optional[Embed]: +        """Build embed based on source object.""" +        url, location, first_line = self.get_source_link(source_object) + +        if isinstance(source_object, commands.Command): +            if source_object.cog_name == 'Help': +                title = "Help Command" +                description = source_object.__doc__.splitlines()[1] +            else: +                description = source_object.short_doc +                title = f"Command: {source_object.qualified_name}" +        else: +            title = f"Cog: {source_object.qualified_name}" +            description = source_object.description.splitlines()[0] + +        embed = Embed(title=title, description=description) +        embed.set_thumbnail(url=Source.github_avatar_url) +        embed.add_field(name="Source Code", value=f"[Go to GitHub]({url})") +        line_text = f":{first_line}" if first_line else "" +        embed.set_footer(text=f"{location}{line_text}") + +        return embed + + +def setup(bot: commands.Bot) -> None: +    """Load the BotSource cog.""" +    bot.add_cog(BotSource(bot)) | 
