Beispiel #1
0
def test_options_list_base_command(command):
    command.name = "name"
    clazz = SlashCommand(command=command,
                         root=None,
                         name=None,
                         description="description",
                         options=[Option(name="opt", description="desc")])
    assert clazz.options == [Option(name="opt", description="desc")]
Beispiel #2
0
def test_options_empty_base_command_group_options(command):
    command.name = "name"
    clazz = SlashCommand(command=command,
                         root=None,
                         name=None,
                         description="description",
                         options=[])
    clazz.append_options([Option(name="opt", description="desc")])
    assert clazz.options == [Option(name="opt", description="desc")]
Beispiel #3
0
class EightBall(commands.Cog):
    def __init__(self, bot):
        self.bot = bot

    @slash_command(options=[
        Option(name="question",
               description="The question to ask the magic 8 ball.")
    ])
    @commands.command(name="eightball",
                      aliases=["8ball"],
                      description="Ask the magic 8 ball a question!")
    async def eightball_command(self, context, *, question: str = None):
        await self.eightball(context, question)

    async def eightball(self, context: commands.Context, question: str):
        if question is None:
            await context.send(
                "You need to ask a question to get an answer. :unamused:")
        elif len(question.replace("?", "")) == 0:
            await context.send(
                "Who do you think you are? I AM!\nhttps://youtu.be/gKQOXYB2cd8?t=10"
            )
        elif not question.endswith("?"):
            await context.send("I can't tell if that's a question, brother.")
        else:
            async with context.typing():
                if random.random() < 3.0 / 10.0:
                    await asyncio.sleep(3.0)
                    await context.send(random.choice(joke_phrases))
                await asyncio.sleep(5.0)
                await context.send(embed=Embed(colour=Colour.purple(
                )).add_field(
                    name=
                    f"{context.author.display_name}, my :crystal_ball: says:",
                    value=f"_{random.choice(phrases)}_"))
Beispiel #4
0
def test_to_dict():
    assert Option(name="n", description="d", type=OptionType.STRING, required=True).to_dict() == {
        "name": "n",
        "description": "d",
        "type": OptionType.STRING,
        "required": True,
        "options": [],
    }
def test_slash_command_slash_options_subcommand():
    options = [Option(name="opt", description="desc")]
    slash = slash_command(root="root", name="name",
                          options=options)(command()(func))
    assert slash.slash_ext.options == [
        SubCommand(name="name",
                   description=None,
                   type=OptionType.SUB_COMMAND,
                   options=options)
    ]
async def test_upsert_slash_commands_create_subcommand_group_not_prod(
        bot, http, guild, command, autospec):
    command2 = autospec.of("discord.ext.commands.Command")
    create_slash_command(command,
                         "first",
                         name="1",
                         root="root",
                         options=[Option(name="1", description="d1")])
    create_slash_command(command2,
                         "second",
                         name="2",
                         root="root",
                         options=[Option(name="2", description="d2")])
    expected = command.slash_ext
    expected.append_options(command2.slash_ext.options)
    bot.walk_commands.return_value = [command, command2]
    bot.guilds = [guild]
    clazz = SlashCommandHandler(bot)
    await clazz.upsert_slash_commands()
    bot.http.bulk_upsert_global_commands.assert_called_once_with(
        bot.user.id, [expected.to_dict()])
    bot.http.bulk_upsert_guild_commands.assert_called_once_with(
        bot.user.id, guild.id, [expected.to_dict()])
async def test_upsert_slash_commands_create_subcommand_not_prod(
        bot, http, guild, command):
    create_slash_command(command,
                         "command_name",
                         name="name",
                         root="root",
                         options=[Option(name="opt", description="d")])
    bot.walk_commands.return_value = [command]
    bot.guilds = [guild]
    clazz = SlashCommandHandler(bot)
    await clazz.upsert_slash_commands()
    bot.http.bulk_upsert_global_commands.assert_called_once_with(
        bot.user.id, [command.slash_ext.to_dict()])
    bot.http.bulk_upsert_guild_commands.assert_called_once_with(
        bot.user.id, guild.id, [command.slash_ext.to_dict()])
Beispiel #8
0
class DogPhotos(commands.Cog):
    def __init__(self, bot):
        self.bot = bot

    @slash_command(options=[
        Option(name="breed",
               description=
               "The specific breed of dog to show. Defaults to any breed.")
    ])
    @commands.command(name="dog",
                      aliases=["doge"],
                      description="Show a random dog you probably don't know")
    async def dog_command(self, context, *, breed: Optional[str] = None):
        await self.dog(context, breed)

    async def dog(self, context, breed: Optional[str]):
        if breed and breed in self.get_breeds():
            await context.send(self.get_dog_image(breed))
        else:
            await context.send(self.get_dog_image(None))

    def get_dog_image(self, breed: Optional[str] = None) -> str:
        if breed:
            if " " in breed:
                path = f"breed/{'/'.join(reversed(breed.split()))}/images"
            else:
                path = f"breed/{breed}/images"
        else:
            path = f"breed/{breed.replace(' ', '/')}/images" if breed else "breeds/image"
        result = requests.get(f"https://dog.ceo/api/{path}/random").json()
        if result.get("status", "ded") != "success" or not result.get(
                "message", None):
            raise RuntimeError(f"could not fetch a puppy; breed = {breed}")
        else:
            return result.get("message")

    def get_breeds(self) -> List[str]:
        result = requests.get("https://dog.ceo/api/breeds/list/all").json()
        if result.get("status", "ded") != "success" or not result.get(
                "message", None):
            raise RuntimeError("could not fetch a puppy")
        else:
            breeds = []
            for breed, sub_breeds in result.get("message").items():
                breeds.append(breed)
                for sub in sub_breeds:
                    breeds.append(f"{sub} {breed}")
            return breeds
Beispiel #9
0
class Dice(commands.Cog):
    def __init__(self, bot):
        self.bot = bot

    @slash_command(options=[
        Option(
            name="expression",
            description="The number and type of dice to roll. Default is 1d20")
    ])
    @commands.command(name="roll",
                      aliases=["r"],
                      description="Roll some Dungeons & Dragons style dice!")
    async def roll_command(self, context, *, expression: str = "1d20"):
        await self.roll(context, expression)

    async def roll(self, context, expression: str):
        max_length = MAX_MESSAGE_LENGTH - 50  # max-50 as a buffer for the added text
        try:
            roller = self.make_roller(100_000)
            result = roller.roll(expression,
                                 allow_comments=True,
                                 stringifier=DiceStringifier())
            text = f"{result.result[:max_length]}..." if len(
                result.result) > max_length else result.result
            await context.send(f"**Rolls**: {text}\n**Total**: {result.total}")
        except d20.errors.TooManyRolls:
            await context.send(
                f"I can only roll up to {roller.context.max_rolls} dice.",
                delete_after=30)
        except d20.errors.RollError as e:
            await context.send(
                f"Oh... :nauseated_face: I don't feel so good... :face_vomiting:\n```{e}```",
                delete_after=30)

    def make_roller(self, max_rolls: int):
        return d20.Roller(d20.RollContext(max_rolls))
Beispiel #10
0
def test_name_contains_whitespace_throws(name):
    with pytest.raises(ValueError):
        Option(name=name, description="d")
def test_slash_command_slash_options_is_provided():
    options = [Option(name="opt", description="desc")]
    slash = slash_command(options=options)(command()(func))
    assert slash.slash_ext.options == options
Beispiel #12
0
import pytest

from duckbot.slash import Option, OptionType
from duckbot.slash.option import SubCommand

option = Option(name="a",
                description="b",
                type=OptionType.STRING,
                required=True)


@pytest.mark.parametrize("name", ["a name", "some\nname", "bruh\tname"])
def test_name_contains_whitespace_throws(name):
    with pytest.raises(ValueError):
        SubCommand(name=name, description="d", options=[option])


def test_to_dict():
    assert SubCommand(name="n",
                      description="d",
                      type=OptionType.SUB_COMMAND,
                      options=[option]).to_dict() == {
                          "name": "n",
                          "description": "d",
                          "type": OptionType.SUB_COMMAND,
                          "required": False,
                          "options": [option.to_dict()],
                      }
Beispiel #13
0
class Recipe(commands.Cog):
    def __init__(self, bot):
        self.bot = bot

    @staticmethod
    def select_recipe(recipe_list):
        """Given a list of recipes, select a random one."""
        return random.choice(recipe_list)

    @staticmethod
    def parse_recipes(html_content):
        """Parse raw HTML from allrecipes to find a list of recipes."""
        recipe_list = []
        soup = BeautifulSoup(html_content, "html.parser")

        articles = soup.findAll(
            "div",
            {"class": "component card card__recipe card__facetedSearchResult"})

        for article in articles:
            data = {}
            try:
                data["name"] = article.find("h3", {
                    "class": "card__title"
                }).get_text().strip(" \t\n\r")
                data["description"] = article.find("div", {
                    "class": "card__summary"
                }).get_text().strip(" \t\n\r")
                data["url"] = article.find(
                    "a",
                    href=re.compile(
                        r"^https://www\.allrecipes\.com/recipe/"))["href"]
                data["rating"] = len(
                    article.findAll("span", {"class": "rating-star active"}))

                recipe_list.append(data)
            except Exception:
                pass

        return recipe_list

    @staticmethod
    def search_recipes(search_term):
        """Search allrecipes with a given search term then return html data."""

        html_content = ""
        for page in range(1, 6):
            url = "https://www.allrecipes.com/element-api/content-proxy/faceted-searches-load-more"
            result = requests.get(url,
                                  params={
                                      "search": search_term,
                                      "page": page
                                  },
                                  headers={
                                      "Cookie": "euConsent=true"
                                  }).json()
            html_content += result.get("html", "")

            if not result.get("hasNext", False):
                break

        return html_content

    async def recipe(self, context, search_term):
        # clean up the arguments to make a valid recipe search
        search_term = re.sub(r"[^\w\s]", "", search_term)

        try:
            # search for recipes on allrecipes.com
            html_content = self.search_recipes(search_term)

            # parse the html to get all recipes from the search
            recipe_list = self.parse_recipes(html_content)

            if len(recipe_list) == 0:
                response = f"I am terribly sorry. There doesn't seem to be any recipes for {search_term}."
            else:
                recipe = self.select_recipe(recipe_list)
                response = f"How about a nice {recipe['name']}. {recipe['description']} This recipe has a {recipe['rating']}/5 rating! {recipe['url']}"
        except Exception:
            response = "I am terribly sorry. I am having problems reading All Recipes for you."

        await context.send(response)

    @slash_command(options=[
        Option(name="search", description="Search terms for the recipe")
    ])
    @commands.command(name="recipe",
                      description="Get a random recipe for something.")
    async def recipe_command(self, context, *, search_term: str = ""):
        async with context.typing():
            await self.recipe(context, search_term)
Beispiel #14
0
class Dictionary(commands.Cog):
    def __init__(self, bot):
        self.bot = bot
        self.headers = {"app_id": os.getenv("OXFORD_DICTIONARY_ID"), "app_key": os.getenv("OXFORD_DICTIONARY_KEY")}
        self.url = "https://od-api.oxforddictionaries.com/api/v2"

    @slash_command(options=[Option(name="word", description="The word to define.", required=True)])
    @commands.command(name="define", description="Define a brother, word.")
    async def define_command(self, context, *, word: str = "taco"):
        await self.define(context, word)

    async def define(self, context, word: str):
        roots = self.get_root_words(word.lower()) or ["why"]
        await context.send(embeds=[self.get_definition(x) for x in roots])

    def get_root_words(self, word: str) -> List[str]:
        """Returns all roots of the given word."""
        response = requests.get(f"{self.url}/lemmas/en/{word}", headers=self.headers).json()
        results = response.get("results", [])
        inflections = [i.get("id") for r in results for lex in r.get("lexicalEntries", []) for i in lex.get("inflectionOf", [])]
        return sorted(set(inflections))

    def get_definition(self, word: str) -> discord.Embed:
        """Returns the full definition of the word as an embed."""
        embed = discord.Embed(title=word)
        response = requests.get(f"{self.url}/entries/en-us/{word}", headers=self.headers).json()
        results = response.get("results", [])
        categories = sorted(set(lex.get("lexicalCategory", {}).get("id", None) for r in results for lex in r.get("lexicalEntries", [])))
        for category in categories:
            text, pronunciation, lines = self.category_group_data(word, category, results)
            embed.add_field(name=f"{category}: **{text}**  /{pronunciation}/", value="\n".join(lines), inline=False)

        return embed

    def category_group_data(self, word: str, category: str, results: List[dict]) -> Tuple[str, str, List[str]]:
        lines = []
        n = 0
        text = word
        pronunciation = "screw flanders"
        for result in results:
            for lex in result.get("lexicalEntries", []):
                if lex.get("lexicalCategory", {}).get("id", None) == category:
                    text, pronunciation, lex_lines, count = self.definition_data(word, lex, n)
                    n += count
                    lines = lines + lex_lines
        return text, pronunciation, lines

    def definition_data(self, word: str, lexical_entry: dict, entry_number: int) -> Tuple[str, str, List[str], int]:
        lines = []
        text = lexical_entry.get("text", word)
        pronunciation = "screw flanders"
        n = entry_number
        for entry in lexical_entry.get("entries", []):
            pronunciation, entry_lines, count = self.entry_data(word, entry, n)
            n += count
            lines = lines + entry_lines
        return text, pronunciation, lines, n

    def entry_data(self, word: str, entry: dict, entry_number: int) -> Tuple[str, List[str], int]:
        lines = []
        n = entry_number
        pronunciation = next((x.get("phoneticSpelling", "") for x in entry.get("pronunciations", []) if x.get("phoneticNotation", "") == "respell"), "")
        for sense in entry.get("senses", []):
            n += 1
            definition = next(iter(sense.get("definitions", [])), "it means things")
            example = next(iter(sense.get("examples", [])), {}).get("text", f"this is where I'd use {word} in a sentence... IF I HAD ONE")
            lines.append(f"{n}. {definition}\n_{example}_") if example else lines.append(f"{n}. {definition}")
            for sub in sense.get("subsenses", []):
                definition = next(iter(sub.get("definitions", [])), "")
                example = next(iter(sub.get("examples", [])), {}).get("text", "")
                if definition:
                    lines.append(f"  • {definition}\n    _{example}_") if example else lines.append(f"  • {definition}")
            lines.append("")
        return pronunciation, lines, n