async def on_command_error(self, ctx: utils.Context,
                               error: commands.CommandError):
        """
        Listens for command not found errors and tries to run them as interactions.
        """

        if not isinstance(error, commands.CommandNotFound):
            return

        # Deal with common aliases
        command_name = ctx.invoked_with.lower()
        for aliases in COMMON_COMMAND_ALIASES:
            if command_name in aliases:
                command_name = aliases[0]

        # See if we wanna deal with it
        guild_ids = [0] if ctx.guild is None else [0, ctx.guild.id]
        async with self.bot.database() as db:
            rows = await db(
                "SELECT response FROM interaction_text WHERE interaction_name=$1 AND guild_id=ANY($2::BIGINT[]) ORDER BY RANDOM() LIMIT 1",
                command_name.lower(), guild_ids)
        if not rows:
            self.logger.info("Nothing found")
            return  # No responses found

        # Create a command we can invoke
        ctx.interaction_response = rows[0]['response']
        ctx.interaction_name = command_name
        ctx.invoke_meta = True
        ctx.command = self.bot.get_command("interaction_command_meta")
        await self.bot.invoke(ctx)
Esempio n. 2
0
    async def displayplant(self, ctx: utils.Context,
                           user: typing.Optional[utils.converters.UserID], *,
                           plant_name: str):
        """
        Shows you your plant status.
        """

        # Get data from database
        user = discord.Object(user) if user else ctx.author
        async with self.bot.database() as db:
            plant_rows = await db(
                "SELECT * FROM plant_levels WHERE user_id=$1 AND LOWER(plant_name)=LOWER($2)",
                user.id, plant_name)
            if not plant_rows:
                return await ctx.send(
                    f"You have no plant named **{plant_name.capitalize()}**",
                    allowed_mentions=discord.AllowedMentions(users=False,
                                                             roles=False,
                                                             everyone=False))

        # Filter into variables
        display_utils = self.bot.get_cog("PlantDisplayUtils")
        if plant_rows:
            display_data = display_utils.get_display_data(plant_rows[0],
                                                          user_id=user.id)
        else:
            display_data = display_utils.get_display_data(None,
                                                          user_id=user.id)

        # Generate text
        if display_data['plant_type'] is None:
            if ctx.author.id == user.id:
                text = f"You don't have a plant yet, {ctx.author.mention}! Run `{ctx.prefix}getplant` to get one!"
            else:
                text = f"<@{user.id}> doesn't have a plant yet!"
        else:
            text = f"<@{user.id}>'s {display_data['plant_type'].replace('_', ' ')} - **{plant_rows[0]['plant_name']}**"
            if int(display_data['plant_nourishment']) > 0:
                if ctx.author.id == user.id:
                    text += "!"
            elif int(display_data['plant_nourishment']) < 0:
                if ctx.author.id == user.id:
                    text += f". It's looking a tad... dead. Run `{ctx.prefix}deleteplant {plant_name}` to plant some new seeds."
                else:
                    text += ". It looks a bit... worse for wear, to say the least."
            elif int(display_data['plant_nourishment']) == 0:
                if ctx.author.id == user.id:
                    text += f". There are some seeds there, but you need to `{ctx.prefix}water {plant_rows[0]['plant_name']}` them to get them to grow."
                else:
                    text += ". There are some seeds there I think, but they need to be watered."

        # Send image
        image_data = display_utils.image_to_bytes(
            display_utils.get_plant_image(**display_data))
        file = discord.File(image_data, filename="plant.png")
        embed = utils.Embed(
            use_random_colour=True,
            description=text).set_image("attachment://plant.png")
        ctx._set_footer(embed)
        await ctx.send(embed=embed, file=file)
Esempio n. 3
0
    async def plants(self, ctx: utils.Context,
                     user: typing.Optional[discord.User]):
        """
        Shows you all the plants that a given user has.
        """

        # Grab the plant data
        user = user or ctx.author
        async with self.bot.database() as db:
            user_rows = await db(
                "SELECT * FROM plant_levels WHERE user_id=$1 ORDER BY plant_name DESC",
                user.id)

        # See if they have anything available
        plant_data = sorted([
            (i['plant_name'], i['plant_type'], i['plant_nourishment'],
             i['last_water_time'], i['plant_adoption_time']) for i in user_rows
        ])
        if not plant_data:
            embed = utils.Embed(use_random_colour=True,
                                description=f"<@{user.id}> has no plants :c")
            return await ctx.send(embed=embed)

        # Add the plant information
        embed = utils.Embed(use_random_colour=True,
                            description=f"<@{user.id}>'s plants")
        ctx._set_footer(embed)
        for plant_name, plant_type, plant_nourishment, last_water_time, plant_adoption_time in plant_data:
            plant_type_display = plant_type.replace('_', ' ').capitalize()
            plant_death_time = last_water_time + timedelta(
                **self.bot.config.get('plants', {}).get(
                    'death_timeout', {'days': 3}))
            plant_death_humanize_time = utils.TimeValue(
                (plant_death_time - dt.utcnow()).total_seconds()).clean_full
            plant_life_humanize_time = utils.TimeValue(
                (dt.utcnow() - plant_adoption_time).total_seconds()).clean_full
            if plant_nourishment == 0:
                text = f"{plant_type_display}, nourishment level {plant_nourishment}/{self.bot.plants[plant_type].max_nourishment_level}."
            elif plant_nourishment > 0:
                text = (
                    f"**{plant_type_display}**, nourishment level {plant_nourishment}/{self.bot.plants[plant_type].max_nourishment_level}.\n"
                    f"If not watered, this plant will die in **{plant_death_humanize_time}**.\n"
                    f"This plant has been alive for **{plant_life_humanize_time}**.\n"
                )
            else:
                text = f"{plant_type_display}, dead :c"
            embed.add_field(plant_name, text, inline=False)

        # Return to user
        v = await ctx.send(embed=embed)
        try:
            await self.bot.add_delete_button(v, (
                ctx.author,
                user,
            ),
                                             wait=False)
        except discord.HTTPException:
            pass
Esempio n. 4
0
    async def inventory(self,
                        ctx: utils.Context,
                        user: utils.converters.UserID = None):
        """
        Show you the inventory of a user.
        """

        # Get user info
        user = discord.Object(user) if user else ctx.author
        async with self.bot.database() as db:
            user_rows = await db(
                "SELECT * FROM user_settings WHERE user_id=$1", user.id)
            plant_rows = await db(
                "SELECT * FROM plant_levels WHERE user_id=$1", user.id)
            user_inventory_rows = await db(
                "SELECT * FROM user_inventory WHERE user_id=$1 AND amount > 0",
                user.id)

        # Start our embed
        embed = utils.Embed(use_random_colour=True, description="")
        ctx._set_footer(embed)

        # Format exp into a string
        if user_rows:
            exp_value = user_rows[0]['user_experience']
        else:
            exp_value = 0
        embed.description += f"<@{user.id}> has **{exp_value:,}** experience.\n"

        # Format plant limit into a string
        if user_rows:
            plant_limit = user_rows[0]['plant_limit']
        else:
            plant_limit = 1
        they_you = {True: "you", False: "they"}.get(user.id == ctx.author.id)
        their_your = {
            True: "your",
            False: "their"
        }.get(user.id == ctx.author.id)
        if plant_limit == len(plant_rows):
            embed.description += f"{they_you.capitalize()} are currently using all of {their_your} available {plant_limit} plant pots.\n"
        else:
            embed.description += f"{they_you.capitalize()} are currently using {len(plant_rows)} of {their_your} available {plant_limit} plant pots.\n"

        # Format inventory into a string
        if user_inventory_rows:
            inventory_string = "\n".join([
                f"{row['item_name'].replace('_', ' ').capitalize()} x{row['amount']:,}"
                for row in user_inventory_rows
            ])
            embed.add_field("Inventory", inventory_string)

        # Return to user
        return await ctx.send(embed=embed)
Esempio n. 5
0
    async def on_command_error(self, ctx: utils.Context,
                               error: commands.CommandError):
        """
        CommandNotFound handler so the bot can search for that custom command.
        """

        # Filter out DMs
        if isinstance(ctx.channel, discord.DMChannel):
            return  # Fail silently on DM invocation

        # Handle commandnotfound which is really just handling the set/get/delete/etc commands
        if not isinstance(error, commands.CommandNotFound):
            return

        # Get the command and used template
        prefixless_content = ctx.message.content[len(ctx.prefix):]
        matches = self.COMMAND_REGEX.search(prefixless_content)
        if matches is None:
            matches = self.OLD_COMMAND_REGEX.search(prefixless_content)
            if matches is None:
                return
        command_operator = matches.group("command")  # get/get/delete/edit
        template_name = matches.group("template")  # template name

        # Find the template they asked for on their server
        async with self.bot.database() as db:
            template = await localutils.Template.fetch_template_by_name(
                db, ctx.guild.id, template_name, fetch_fields=False)
        if not template:
            self.logger.info(
                f"Failed at getting template '{template_name}' in guild {ctx.guild.id}"
            )
            return  # Fail silently on template doesn't exist

        # Invoke command
        metacommand: utils.Command = self.bot.get_command(
            f'{command_operator.lower()}_profile_meta')
        ctx.command = metacommand
        ctx.template = template
        ctx.invoke_meta = True
        ctx.invoked_with = f"{matches.group('template')} {matches.group('command')}"
        ctx.view = commands.view.StringView(matches.group('args'))
        try:
            self.bot.dispatch("command", ctx)
            await metacommand.invoke(
                ctx)  # This converts the args for me, which is nice
        except (commands.CommandInvokeError, commands.CommandError) as e:
            self.bot.dispatch(
                "command_error", ctx, e
            )  # Throw any errors we get in this command into its own error handler
Esempio n. 6
0
    async def plants(self,
                     ctx: utils.Context,
                     user: utils.converters.UserID = None):
        """
        Shows you all the plants that a given user has.
        """

        # Grab the plant data
        user = discord.Object(user) if user else ctx.author
        async with self.bot.database() as db:
            user_rows = await db(
                "SELECT * FROM plant_levels WHERE user_id=$1 ORDER BY plant_name DESC",
                user.id)

        # See if they have anything available
        plant_data = sorted([(i['plant_name'], i['plant_type'],
                              i['plant_nourishment'], i['last_water_time'])
                             for i in user_rows])
        if not plant_data:
            embed = utils.Embed(use_random_colour=True,
                                description=f"<@{user.id}> has no plants :c")
            return await ctx.send(embed=embed)

        # Add the plant information
        embed = utils.Embed(use_random_colour=True,
                            description=f"<@{user.id}>'s plants")
        ctx._set_footer(embed)
        for plant_name, plant_type, plant_nourishment, last_water_time in plant_data:
            plant_type_display = plant_type.replace('_', ' ').capitalize()
            # plant_name_display = re.sub(r"([\_*`])", r"\\\1", plant_name)
            plant_death_time = last_water_time + timedelta(
                **self.bot.config.get('plants', {}).get(
                    'death_timeout', {'days': 3}))
            plant_death_humanize_time = arrow.get(plant_death_time).humanize(
                granularity=["day", "hour", "minute"], only_distance=True)
            if plant_nourishment == 0:
                text = f"{plant_type_display}, nourishment level {plant_nourishment}/{self.bot.plants[plant_type].max_nourishment_level}."
            elif plant_nourishment > 0:
                text = (
                    f"{plant_type_display}, nourishment level {plant_nourishment}/{self.bot.plants[plant_type].max_nourishment_level}.\n"
                    f"If not watered, this plant will die in *{plant_death_humanize_time}*."
                )
            else:
                text = f"{plant_type_display}, dead :c"
            embed.add_field(plant_name, text)

        # Return to user
        return await ctx.send(embed=embed)
Esempio n. 7
0
    async def dnd_class(self, ctx: vbu.Context, *, class_name: str):
        """
        Gives you information on a D&D class.
        """

        async with ctx.typing():
            data = await self.send_web_request("classes", class_name)
        if not data:
            return await ctx.send(
                "I couldn't find any information for that class.", wait=False)
        embed = vbu.Embed(
            use_random_colour=True,
            title=data['name'],
        ).add_field(
            "Proficiencies",
            ", ".join([i['name'] for i in data['proficiencies']]),
        ).add_field(
            "Saving Throws",
            ", ".join([i['name'] for i in data['saving_throws']]),
        ).add_field(
            "Starting Equipment",
            "\n".join([
                f"{i['quantity']}x {i['equipment']['name']}"
                for i in data['starting_equipment']
            ]),
        )
        return await ctx.send(embed=embed, wait=False)
Esempio n. 8
0
    async def runstartupmethod(self, ctx: vbu.Context):
        """
        Runs the bot startup method, recaching everything of interest.
        """

        async with ctx.typing():
            await self.bot.startup()
        await ctx.okay()
Esempio n. 9
0
    async def waterplant(self, ctx: utils.Context, *, plant_name: str):
        """
        Increase the growth level of your plant.
        """

        # Let's run all the bullshit
        item = await self.water_plant_backend(ctx.author.id, plant_name)
        if item['success'] is False:
            return await ctx.send(item['text'])
        output_lines = item['text'].split("\n")

        # Try and embed the message
        embed = None
        if ctx.guild is None or ctx.channel.permissions_for(
                ctx.guild.me).embed_links:

            # Make initial embed
            embed = utils.Embed(use_random_colour=True,
                                description=output_lines[0])

            # Add multipliers
            if len(output_lines) > 1:
                embed.add_field("Multipliers",
                                "\n".join(
                                    [i.strip('') for i in output_lines[1:]]),
                                inline=False)

            # Add "please vote for Flower" footer
            counter = 0
            ctx._set_footer(embed)

            def check(footer_text) -> bool:
                if item['voted_on_topgg']:
                    return 'vote' not in footer_text
                return 'vote' in footer_text

            while counter < 100 and check(embed.footer.text.lower()):
                ctx._set_footer(embed)
                counter += 1

            # Clear the text we would otherwise output
            output_lines.clear()

        # Send message
        return await ctx.send("\n".join(output_lines), embed=embed)
Esempio n. 10
0
    async def leaderboard(self, ctx: utils.Context, pages: int = 1):
        """
        Gives you the leaderboard users for the server.
        """

        # This takes a while
        async with ctx.typing():

            # Get all their valid user IDs
            async with self.bot.database() as db:
                message_rows = await db(
                    """SELECT user_id, COUNT(timestamp) FROM user_messages WHERE guild_id=$1 AND
                    timestamp > TIMEZONE('UTC', NOW()) - INTERVAL '7 days' GROUP BY user_id
                    ORDER BY COUNT(timestamp) DESC LIMIT 30;""",
                    ctx.guild.id,
                )
                vc_rows = await db(
                    """SELECT user_id, COUNT(timestamp) FROM user_vc_activity WHERE guild_id=$1 AND
                    timestamp > TIMEZONE('UTC', NOW()) - INTERVAL '7 days' GROUP BY user_id
                    ORDER BY COUNT(timestamp) DESC LIMIT 30;""",
                    ctx.guild.id,
                )

            # Sort that into more formattable data
            user_data_dict = collections.defaultdict({
                'message_count': 0,
                'vc_minute_count': 0
            }.copy)  # uid: {message_count: int, vc_minute_count: int}
            for row in message_rows:
                user_data_dict[row['user_id']]['message_count'] = row['count']
            for row in vc_rows:
                user_data_dict[
                    row['user_id']]['vc_minute_count'] = row['count']

            # And now make it into something we can sort
            guild_user_data = [(uid, d['message_count'], d['vc_minute_count'])
                               for uid, d in user_data_dict.items()]
            valid_guild_user_data = []
            for i in guild_user_data:
                try:
                    if ctx.guild.get_member(
                            i[0]) or await ctx.guild.fetch_member(i[0]):
                        valid_guild_user_data.append(i)
                except discord.HTTPException:
                    pass
            ordered_guild_user_data = sorted(valid_guild_user_data,
                                             key=lambda k: k[1] + (k[2] // 5),
                                             reverse=True)

        # Make menu
        pages = menus.MenuPages(source=LeaderboardSource(
            self.bot, ordered_guild_user_data, "Tracked Points over 7 days"),
                                clear_reactions_after=True)
        return await pages.start(ctx)
Esempio n. 11
0
    async def displayall(self, ctx: utils.Context,
                         user: typing.Optional[discord.User]):
        """
        Show you all of your plants.
        """

        # Get data from database
        user = user or ctx.author
        async with self.bot.database() as db:
            plant_rows = await db(
                "SELECT * FROM plant_levels WHERE user_id=$1 ORDER BY plant_name DESC",
                user.id)
            if not plant_rows:
                return await ctx.send(f"<@{user.id}> has no available plants.",
                                      allowed_mentions=discord.AllowedMentions(
                                          users=[ctx.author]))
        await ctx.trigger_typing()

        # Filter into variables
        display_utils = self.bot.get_cog("PlantDisplayUtils")
        plant_rows = display_utils.sort_plant_rows(plant_rows)
        images = []
        for plant_row in plant_rows:
            if plant_row:
                display_data = display_utils.get_display_data(plant_row,
                                                              user_id=user.id)
            else:
                display_data = display_utils.get_display_data(None,
                                                              user_id=user.id)
            images.append(display_utils.get_plant_image(**display_data))

        # Get our images
        image = display_utils.compile_plant_images(*images)
        image_to_send = display_utils.image_to_bytes(image)
        text = f"Here are all of <@{user.id}>'s plants!"
        file = discord.File(image_to_send, filename="plant.png")
        embed = utils.Embed(
            use_random_colour=True,
            description=text).set_image("attachment://plant.png")
        ctx._set_footer(embed)
        await ctx.send(embed=embed, file=file)
Esempio n. 12
0
    async def volunteer(self, ctx: utils.Context):
        """
        Get the information for volunteering.
        """

        VOLUNTEER_INFORMATION = (
            "Want to help out with Flower, and watch it grow? Heck yeah! There's a few ways you can help out:\n\n"
            "**Art**\n"
            "Flower takes a lot of art, being a bot entirely about watching things grow. Unfortunately, I'm awful at art. Anything you can help out "
            "with would be amazing, if you had some kind of artistic talent yourself. If you [go here](https://github.com/Voxel-Fox-Ltd/Flower/blob/master/images/pots/clay/full.png) "
            "you can get an empty pot image you can use as a base. Every plant in Flower has a minimum of 6 distinct growth stages "
            "(which you can [see here](https://github.com/Voxel-Fox-Ltd/Flower/tree/master/images/plants/blue_daisy/alive) if you need an example).\n"
            "If this is the kind of thing you're interested in, I suggest you join [the support server](https://discord.gg/vfl) to ask for more information, or "
            "[email Kae](mailto://[email protected]) - the bot's developer.\n"
            "\n"
            "**Programming**\n"
            "If you're any good at programming, you can help out on [the bot's Github](https://github.com/Voxel-Fox-Ltd/Flower)! Ideas are discussed on "
            "[the support server](https://discord.gg/vfl) if you want to do that, but otherwise you can PR fixes, add issues, etc from there as you would "
            "with any other git repository.\n"
            "\n"
            "**Ideas**\n"
            "Flower is in constant need of feedback from the people who like to use it, and that's where you can shine. Even if you don't want to "
            "help out with art, programming, or anything else: it would be _absolutely amazing_ if you could give your experiences, gripes, and suggestions for "
            "Flower via the `{ctx.clean_prefix}suggest` command. That way I know where to change things, what to do to add new stuff, etcetc. If you want to "
            "discuss in more detail, I'm always around on [the support server](https://discord.gg/vfl)."
        ).format(ctx=ctx)
        embed = utils.Embed(
            use_random_colour=True,
            description=VOLUNTEER_INFORMATION,
        )
        ctx._set_footer(embed)
        try:
            await ctx.author.send(embed=embed)
        except discord.HTTPException:
            return await ctx.send("I wasn't able to send you a DM :<")
        if ctx.guild is not None:
            return await ctx.send("Sent you a DM!")
Esempio n. 13
0
    async def dnd_condition(self, ctx:utils.Context, *, condition_name:str):
        """
        Gives you information on a D&D condition.
        """

        async with ctx.typing():
            data = await self.send_web_request("conditions", condition_name)
        if not data:
            return await ctx.send("I couldn't find any information for that condition.")
        embed = utils.Embed(
            use_random_colour=True,
            title=data['name'],
            description="\n".join(data['desc']),
        )
        return await ctx.send(embed=embed)
Esempio n. 14
0
    async def relationship(self,
                           ctx: vbu.Context,
                           user: vbu.converters.UserID,
                           other: vbu.converters.UserID = None):
        """
        Gets the relationship between the two specified users.
        """

        # Fix up the arguments
        if other is None:
            user_id, other_id = ctx.author.id, user
        else:
            user_id, other_id = user, other

        # See if they're the same person
        if user_id == other_id:
            if user_id == ctx.author.id:
                return await ctx.send(
                    "Unsurprisingly, you're pretty closely related to yourself.",
                    wait=False)
            return await ctx.send(
                "Unsurprisingly, they're pretty closely related to themselves.",
                wait=False)

        # Get their relation
        user_info, other_info = utils.FamilyTreeMember.get_multiple(
            user_id, other_id, guild_id=utils.get_family_guild_id(ctx))
        async with ctx.typing():
            relation = user_info.get_relation(other_info)

        # Get names
        user_name = await utils.DiscordNameManager.fetch_name_by_id(
            self.bot, user_id)
        other_name = await utils.DiscordNameManager.fetch_name_by_id(
            self.bot, other_id)

        # Output
        if relation is None:
            output = f"**{utils.escape_markdown(user_name)}** is not related to **{utils.escape_markdown(other_name)}**."
            if user_id == ctx.author.id:
                output = f"You're not related to **{utils.escape_markdown(other_name)}**."
        else:
            output = f"**{utils.escape_markdown(other_name)}** is **{utils.escape_markdown(user_name)}**'s {relation}."
            if user_id == ctx.author.id:
                output = f"**{utils.escape_markdown(other_name)}** is your {relation}."
        return await ctx.send(output,
                              allowed_mentions=discord.AllowedMentions.none(),
                              wait=False)
Esempio n. 15
0
    async def issue_comment(self, ctx:utils.Context, repo:GitRepo, issue:GitIssueNumber, *, comment:str):
        """
        Comment on a git issue.
        """

        # Get the database because whatever why not
        async with self.bot.database() as db:
            user_rows = await db("SELECT * FROM user_settings WHERE user_id=$1", ctx.author.id)
            if not user_rows or not user_rows[0][f'{repo.host.lower()}_username']:
                return await ctx.send(f"You need to link your {repo.host} account to Discord to run this command - see `{ctx.clean_prefix}website`.")

        # Add attachments
        attachment_urls = []
        for i in ctx.message.attachments:
            async with ctx.typing():
                try:
                    async with self.bot.session.get(i.url) as r:
                        data = await r.read()
                    file = discord.File(io.BytesIO(data), filename=i.filename)
                    cache_message = await ctx.author.send(file=file)
                    attachment_urls.append((file.filename, cache_message.attachments[0].url))
                except discord.HTTPException:
                    break

        # Get the headers
        if repo.host == "Github":
            headers = {'Accept': 'application/vnd.github.v3+json','Authorization': f"token {user_rows[0]['github_access_token']}",}
        elif repo.host == "Gitlab":
            headers = {'Authorization': f"Bearer {user_rows[0]['gitlab_bearer_token']}"}
        json = {'body': (comment + "\n\n" + "\n".join([f"![{name}]({url})" for name, url in attachment_urls])).strip()}
        headers.update({'User-Agent': self.bot.user_agent})

        # Create comment
        async with self.bot.session.post(repo.issue_comments_api_url.format(issue=issue), json=json, headers=headers) as r:
            data = await r.json()
            self.logger.info(f"Received data from git {r.url!s} - {data!s}")
            if r.status == 404:
                return await ctx.send("I was unable to find that issue.")
            if 200 <= r.status < 300:
                pass
            else:
                return await ctx.send(f"I was unable to create a comment on that issue - `{data}`.")

        # Output
        if repo.host == "Github":
            return await ctx.send(f"Comment added! <{data['html_url']}>")
        return await ctx.send(f"Comment added! <https://gitlab.com/{repo.owner}/{repo.repo}/-/issues/{issue}#note_{data['id']}>")
Esempio n. 16
0
    async def copulate(self, ctx: vbu.Context, target: discord.Member):
        """
        Lets you... um... heck someone.
        """

        # Variables we're gonna need for later
        family_guild_id = utils.get_family_guild_id(ctx)
        author_tree, target_tree = utils.FamilyTreeMember.get_multiple(
            ctx.author.id, target.id, guild_id=family_guild_id)

        # Check they're not a bot
        if target.id == self.bot.user.id:
            return await ctx.send("Ew. No. Thanks.", wait=False)
        if target.id == ctx.author.id:
            return

        # See if they're already related
        async with ctx.typing():
            relation = author_tree.get_relation(target_tree)
        if relation and relation != "partner" and utils.guild_allows_incest(
                ctx) is False:
            return await ctx.send(
                f"Woah woah woah, it looks like you guys are related! {target.mention} is your {relation}!",
                allowed_mentions=utils.only_mention(ctx.author),
                wait=False,
            )

        # Set up the proposal
        if target.id != ctx.author.id:
            try:
                result = await utils.send_proposal_message(
                    ctx,
                    target,
                    f"Hey, {target.mention}, {ctx.author.mention} do you wanna... smash? \N{SMIRKING FACE}",
                    allow_bots=True,
                )
            except Exception:
                result = None
        if result is None:
            return

        # Respond
        await result.ctx.send(
            random.choice(utils.random_text.Copulate.VALID).format(
                author=ctx.author, target=target),
            wait=False,
        )
Esempio n. 17
0
    async def dnd_spell(self, ctx:utils.Context, *, spell_name:str):
        """
        Gives you information on a D&D spell.
        """

        async with ctx.typing():
            data = await self.send_web_request("spells", spell_name)
        if not data:
            return await ctx.send("I couldn't find any information for that spell.")
        embed = utils.Embed(
            use_random_colour=True,
            title=data['name'],
            description=data['desc'][0],
        ).add_field(
            "Casting Time", data['casting_time'],
        ).add_field(
            "Range", data['range'],
        ).add_field(
            "Components", ', '.join(data['components']),
        ).add_field(
            "Material", data.get('material', 'N/A'),
        ).add_field(
            "Duration", data['duration'],
        ).add_field(
            "Classes", ', '.join([i['name'] for i in data['classes']]),
        ).add_field(
            "Ritual", data['ritual'],
        ).add_field(
            "Concentration", data['concentration'],
        )
        if data.get('higher_level'):
            embed.add_field(
                "Higher Level", "\n".join(data['higher_level']), inline=False,
            )
        elif data.get('damage'):
            text = ""
            if data['damage'].get('damage_at_character_level'):
                text += "\nCharacter level " + ", ".join([f"{i}: {o}" for i, o in data['damage']['damage_at_character_level'].items()])
            if data['damage'].get('damage_at_slot_level'):
                text += "\nSlot level " + ", ".join([f"{i}: {o}" for i, o in data['damage']['damage_at_slot_level'].items()])
            embed.add_field(
                "Damage", text.strip(), inline=False,
            )
        return await ctx.send(embed=embed)
Esempio n. 18
0
    async def setupsupportguild(self, ctx:utils.Context):
        """
        Sends some sexy new messages into the support guild.
        """

        # Make sure we're in the right guild
        if ctx.guild is None or ctx.guild.id != SUPPORT_GUILD_ID:
            return await ctx.send("This can only be run on the set support guild.")

        # This could take a while
        async with ctx.typing():

            # Remake the FAQ channel for each channel
            for channel_id_str, embed_lines in FAQ_MESSAGES.items():

                # Get the category object
                channel = self.bot.get_channel(int(channel_id_str))
                category = channel.category

                # Get the faq channel and delete the old message
                faq_channel = category.channels[0]
                if faq_channel.name != "faqs":
                    return await ctx.send(
                        f"The first channel in the **{category_name}** category isn't called **faqs**.",
                        allowed_mentions=discord.AllowedMentions.none(),
                    )

                # Make the embed
                emoji_lines = [f"{index}\N{COMBINING ENCLOSING KEYCAP} **{string}**" for index, string in enumerate(embed_lines, start=1)]
                description = "\n".join(emoji_lines + ["\N{BLACK QUESTION MARK ORNAMENT} **Other**"])
                new_embed = utils.Embed(title="What issue are you having?", description=description, colour=0x1)

                # See if it's anything new
                current_messages = await faq_channel.history(limit=1).flatten()
                if current_messages and current_messages[0].embeds and current_messages[0].embeds[0].to_dict() == new_embed.to_dict():
                    continue
                await current_messages[0].delete()
                new_message = await faq_channel.send(embed=new_embed)
                for emoji, item in [i.strip().split(" ", 1) for i in new_message.embeds[0].description.strip().split("\n")]:
                    await new_message.add_reaction(emoji)

        # And we should be done at this point
        await ctx.okay()
Esempio n. 19
0
    async def dnd_monster(self, ctx:utils.Context, *, monster_name:str):
        """
        Gives you information on a D&D monster.
        """

        async with ctx.typing():
            data = await self.send_web_request("monsters", monster_name)
        if not data:
            return await ctx.send("I couldn't find any information for that monster.")
        embed = utils.Embed(
            use_random_colour=True,
            title=data['name'],
            description="\n".join([
                f"{data['size'].capitalize()} | {data['type']} | {data['hit_points']:,} ({data['hit_dice']}) HP | {data['xp']:,} XP",
                ", ".join([f"{o} {data[i]}" for i, o in self.ATTRIBUTES.items()]),
            ])
        ).add_field(
            "Proficiencies", ", ".join([f"{i['proficiency']['name']} {i['value']}" for i in data['proficiencies']]) or "None",
        ).add_field(
            "Damage Vulnerabilities", "\n".join(data['damage_vulnerabilities']).capitalize() or "None",
        ).add_field(
            "Damage Resistances", "\n".join(data['damage_resistances']).capitalize() or "None",
        ).add_field(
            "Damage Immunities", "\n".join(data['damage_immunities']).capitalize() or "None",
        ).add_field(
            "Condition Immunities", "\n".join([i['name'] for i in data['condition_immunities']]).capitalize() or "None",
        ).add_field(
            "Senses", "\n".join([f"{i.replace('_', ' ').capitalize()} {o}" for i, o in data['senses'].items()]) or "None",
        )
        self.group_field_descriptions(embed, "Actions", data['actions'])
        self.group_field_descriptions(embed, "Legendary Actions", data.get('legendary_actions', list()))
        if data.get('special_abilities'):
            embed.add_field(
                "Special Abilities", "\n".join([f"**{i['name']}**\n{i['desc']}" for i in data['special_abilities'] if i['name'] != 'Spellcasting']) or "None", inline=False,
            )
        spellcasting = [i for i in data.get('special_abilities', list()) if i['name'] == 'Spellcasting']
        if spellcasting:
            spellcasting = spellcasting[0]
            embed.add_field(
                "Spellcasting", spellcasting['desc'].replace('\n\n', '\n'), inline=False,
            )
        return await ctx.send(embed=embed)
Esempio n. 20
0
    async def httpcat(self, ctx: vbu.Context, errorcode: str):
        """
        Gives you a cat based on an HTTP error code.
        """

        standard_errorcodes = [error.value for error in http.HTTPStatus]

        if errorcode in ('random', 'rand', 'r'):
            errorcode = random.choice(standard_errorcodes)
        else:
            try:
                errorcode = int(errorcode)
            except ValueError:
                return ctx.send(
                    'Converting to "int" failed for parameter "errorcode".',
                    wait=False)

        await ctx.trigger_typing()
        headers = {"User-Agent": self.bot.user_agent}
        async with self.bot.session.get(f"https://http.cat/{errorcode}",
                                        headers=headers) as r:
            if r.status == 404:
                if errorcode not in standard_errorcodes:
                    await ctx.send("That HTTP code doesn't exist.", wait=False)
                else:
                    await ctx.send(
                        'Image for HTTP code not found on provider.',
                        wait=False)
                return
            if r.status != 200:
                await ctx.send(
                    f'Something went wrong, try again later. ({r.status})',
                    wait=False)
                return
        with vbu.Embed(use_random_colour=True) as embed:
            embed.set_image(url=f'https://http.cat/{errorcode}')
        await ctx.send(embed=embed, wait=False)
Esempio n. 21
0
    async def makeparent(self, ctx: vbu.Context, *,
                         target: utils.converters.UnblockedMember):
        """
        Picks a user that you want to be your parent.
        """

        # Variables we're gonna need for later
        family_guild_id = utils.get_family_guild_id(ctx)
        author_tree, target_tree = utils.FamilyTreeMember.get_multiple(
            ctx.author.id, target.id, guild_id=family_guild_id)

        # Check they're not themselves
        if target.id == ctx.author.id:
            return await ctx.send(
                "That's you. You can't make yourself your parent.", wait=False)

        # Check they're not a bot
        if target.id == self.bot.user.id:
            return await ctx.send(
                "I think I could do better actually, but thank you!",
                wait=False)

        # Lock those users
        re = await self.bot.redis.get_connection()
        try:
            lock = await utils.ProposalLock.lock(re, ctx.author.id, target.id)
        except utils.ProposalInProgress:
            return await ctx.send(
                "Aren't you popular! One of you is already waiting on a proposal - please try again later.",
                wait=False)

        # See if the *target* is already married
        if author_tree.parent:
            await lock.unlock()
            return await ctx.send(
                f"Hey! {ctx.author.mention}, you already have a parent \N{ANGRY FACE}",
                allowed_mentions=utils.only_mention(ctx.author),
                wait=False,
            )

        # See if we're already married
        if ctx.author.id in target_tree._children:
            await lock.unlock()
            return await ctx.send(
                f"Hey isn't {target.mention} already your child? \N{FACE WITH ROLLING EYES}",
                allowed_mentions=utils.only_mention(ctx.author),
                wait=False,
            )

        # See if they're already related
        async with ctx.typing():
            relation = author_tree.get_relation(target_tree)
        if relation and utils.guild_allows_incest(ctx) is False:
            await lock.unlock()
            return await ctx.send(
                f"Woah woah woah, it looks like you guys are already related! {target.mention} is your {relation}!",
                allowed_mentions=utils.only_mention(ctx.author),
                wait=False,
            )

        # Manage children
        children_amount = await self.get_max_children_for_member(
            ctx.guild, target)
        if len(target_tree._children) >= children_amount:
            return await ctx.send(
                f"They're currently at the maximum amount of children they can have - see `{ctx.prefix}perks` for more information.",
                wait=False,
            )

        # Check the size of their trees
        # TODO I can make this a util because I'm going to use it a couple times
        max_family_members = utils.get_max_family_members(ctx)
        async with ctx.typing():
            family_member_count = 0
            for i in author_tree.span(add_parent=True, expand_upwards=True):
                if family_member_count >= max_family_members:
                    break
                family_member_count += 1
            for i in target_tree.span(add_parent=True, expand_upwards=True):
                if family_member_count >= max_family_members:
                    break
                family_member_count += 1
            if family_member_count >= max_family_members:
                await lock.unlock()
                return await ctx.send(
                    f"If you added {target.mention} to your family, you'd have over {max_family_members} in your family. Sorry!",
                    allowed_mentions=utils.only_mention(ctx.author),
                    wait=False,
                )

        # Set up the proposal
        try:
            result = await utils.send_proposal_message(
                ctx,
                target,
                f"Hey, {target.mention}, {ctx.author.mention} wants to be your child! What do you think?",
                allow_bots=True,
            )
        except Exception:
            result = None
        if result is None:
            return await lock.unlock()

        # Database it up
        async with self.bot.database() as db:
            try:
                await db(
                    """INSERT INTO parents (parent_id, child_id, guild_id, timestamp) VALUES ($1, $2, $3, $4)""",
                    target.id,
                    ctx.author.id,
                    family_guild_id,
                    dt.utcnow(),
                )
            except asyncpg.UniqueViolationError:
                await lock.unlock()
                return await result.ctx.send(
                    "I ran into an error saving your family data - please try again later."
                )
        await result.ctx.send(
            f"I'm happy to introduce {ctx.author.mention} as your child, {target.mention}!",
            wait=False,
        )

        # And we're done
        target_tree._children.append(author_tree.id)
        author_tree._parent = target.id
        await re.publish('TreeMemberUpdate', author_tree.to_json())
        await re.publish('TreeMemberUpdate', target_tree.to_json())
        await re.disconnect()
        await lock.unlock()
Esempio n. 22
0
    async def currency_create(self, ctx: utils.Context):
        """
        Add a new currency to your guild.
        """

        # Make sure they only have 3 currencies already
        async with self.bot.database() as db:
            currency_rows = await db("""SELECT * FROM guild_currencies WHERE guild_id=$1""", ctx.guild.id)
        if len(currency_rows) >= self.MAX_GUILD_CURRENCIES:
            return await ctx.send(f"You can only have **{self.MAX_GUILD_CURRENCIES}** currencies per guild.")
        boolean_emojis = ["\N{HEAVY CHECK MARK}", "\N{HEAVY MULTIPLICATION X}"]

        # Set up the wait_for check here because we're gonna use it multiple times
        def check(message):
            return all([
                message.channel.id == ctx.channel.id,
                message.author.id == ctx.author.id,
            ])

        def reaction_check(message):
            def wrapper(payload):
                return all([
                    payload.message_id == message.id,
                    payload.user_id == ctx.author.id,
                    str(payload.emoji) in boolean_emojis,
                ])
            return wrapper

        # Ask what they want the name of the currency to be
        await ctx.send("""What do you want the _name_ of the currency to be? Examples: "dollars", "pounds", "krona", etc.""")
        for _ in range(3):
            try:
                currency_name_message = await self.bot.wait_for("message", check=check, timeout=60)
                assert currency_name_message.content
            except asyncio.TimeoutError:
                return await ctx.send("Timed out on adding a new currency to the guild.")
            except AssertionError:
                await currency_name_message.reply("This isn't a valid currency name - please provide another one.")
                continue

            # Check that their provided name is valid
            async with self.bot.database() as db:
                check_rows = await db(
                    """SELECT * FROM guild_currencies WHERE guild_id=$1 AND LOWER(currency_name)=LOWER($2)""",
                    ctx.guild.id, currency_name_message.content,
                )
            if check_rows:
                await currency_name_message.reply(
                    f"You're already using a currency with the name **{currency_name_message.content}** - please provide another one.",
                    allowed_mentions=discord.AllowedMentions.none(),
                )
                continue
            break
        else:
            return await ctx.send("You failed giving a valid currency name too many times - please try again later.")

        # Ask what they want the short form of the currency to be
        await ctx.send("""What do you want the _short form_ of the currency to be? Examples: "USD", "GBP", "RS3", etc.""")
        for _ in range(3):
            try:
                currency_short_message = await self.bot.wait_for("message", check=check, timeout=60)
                assert currency_short_message.content
                break
            except asyncio.TimeoutError:
                return await ctx.send("Timed out on adding a new currency to the guild.")
            except AssertionError:
                await currency_short_message.reply("This isn't a valid currency name - please provide another one.")

            # Check that their provided name is valid
            async with self.bot.database() as db:
                check_rows = await db(
                    """SELECT * FROM guild_currencies WHERE guild_id=$1 AND LOWER(short_form)=LOWER($2)""",
                    ctx.guild.id, currency_short_message.content,
                )
            if check_rows:
                await currency_name_message.reply(
                    f"You're already using a currency with the short name **{currency_name_message.content}** - please provide another one.",
                    allowed_mentions=discord.AllowedMentions.none(),
                )
                continue
            break
        else:
            return await ctx.send("You failed giving a valid currency short name too many times - please try again later.")

        # Ask if we should add a daily command
        m = await ctx.send("""Do you want there to be a "daily" command available for this currency, where users can get between 9k and 13k every day?""")
        for e in boolean_emojis:
            self.bot.loop.create_task(m.add_reaction(e))
        try:
            currency_daily_payload = await self.bot.wait_for("raw_reaction_add", check=reaction_check(m), timeout=60)
        except asyncio.TimeoutError:
            return await ctx.send("Timed out on adding a new currency to the guild.")

        # Add the new currency to the server
        async with ctx.typing():
            async with self.bot.database() as db:
                await db(
                    """INSERT INTO guild_currencies (guild_id, currency_name, short_form, allow_daily_command)
                    VALUES ($1, $2, $3, $4)""",
                    ctx.guild.id, currency_name_message.content, currency_short_message.content,
                    str(currency_daily_payload.emoji) == "\N{HEAVY CHECK MARK}",
                )
        return await ctx.send("Added a new currency to your server!")
Esempio n. 23
0
    async def shop(self, ctx:utils.Context):
        """
        Shows you the available plants.
        """

        # Get data from the user
        async with self.bot.database() as db:
            user_rows = await db("SELECT * FROM user_settings WHERE user_id=$1", ctx.author.id)
            plant_level_rows = await db("SELECT * FROM plant_levels WHERE user_id=$1", ctx.author.id)
        if user_rows:
            user_experience = user_rows[0]['user_experience']
            plant_limit = user_rows[0]['plant_limit']
            last_plant_shop_time = user_rows[0]['last_plant_shop_time'] or dt(2000, 1, 1)
        else:
            user_experience = 0
            plant_limit = 1
            last_plant_shop_time = dt(2000, 1, 1)
        can_purchase_new_plants = dt.utcnow() > last_plant_shop_time + timedelta(**self.bot.config.get('plants', {}).get('water_cooldown', {'minutes': 15}))
        buy_plant_cooldown_delta = None
        if can_purchase_new_plants is False:
            buy_plant_cooldown_delta = utils.TimeValue(
                ((last_plant_shop_time + timedelta(**self.bot.config.get('plants', {}).get('water_cooldown', {'minutes': 15}))) - dt.utcnow()).total_seconds()
            )

        # Set up our initial items
        available_item_count = 0  # Used to make sure we can continue the command
        embed = utils.Embed(use_random_colour=True, description="")
        ctx._set_footer(embed)

        # See what we wanna get to doing
        embed.description += (
            f"What would you like to spend your experience to buy, {ctx.author.mention}? "
            f"You currently have **{user_experience:,} exp**, and you're using {len(plant_level_rows):,} of your {plant_limit:,} available plant pots.\n"
        )
        available_plants = await self.get_available_plants(ctx.author.id)

        # Add "can't purchase new plant" to the embed
        if can_purchase_new_plants is False:
            embed.description += f"\nYou can't purchase new plants for another **{buy_plant_cooldown_delta.clean}**.\n"

        # Add plants to the embed
        plant_text = []
        for plant in sorted(available_plants.values()):
            modifier = lambda x: x
            text = f"{plant.display_name.capitalize()} - `{plant.required_experience:,} exp`"
            if can_purchase_new_plants and plant.required_experience <= user_experience and len(plant_level_rows) < plant_limit:
                available_item_count += 1
            else:
                modifier = strikethrough
            plant_text.append(modifier(text))

        # Say when the plants will change
        now = dt.utcnow()
        remaining_time = utils.TimeValue((dt(now.year if now.month < 12 else now.year + 1, now.month + 1 if now.month < 12 else 1, 1) - now).total_seconds())
        plant_text.append(f"These plants will change in {remaining_time.clean_spaced}.")
        embed.add_field("Available Plants", '\n'.join(plant_text), inline=True)

        # Set up items to be added to the embed
        item_text = []

        # Add pots
        modifier = lambda x: x
        text = f"Pot - `{self.get_points_for_plant_pot(plant_limit):,} exp`"
        if user_experience >= self.get_points_for_plant_pot(plant_limit) and plant_limit < self.bot.config.get('plants', {}).get('hard_plant_cap', 10):
            available_item_count += 1
        else:
            modifier = strikethrough
        item_text.append(modifier(text))

        # Add variable items
        for item in self.bot.items.values():
            modifier = lambda x: x
            text = f"{item.display_name.capitalize()} - `{item.price:,} exp`"
            if user_experience >= item.price:
                available_item_count += 1
            else:
                modifier = strikethrough
            item_text.append(modifier(text))

        # Add all our items to the embed
        embed.add_field("Available Items", '\n'.join(item_text), inline=True)

        # Cancel if they don't have anything available
        if available_item_count == 0:
            embed.description += "\n**There is currently nothing available which you can purchase.**\n"
            return await ctx.send(embed=embed)
        else:
            embed.description += "\n**Say the name of the item you want to purchase, or type `cancel` to exit the shop with nothing.**\n"

        # Wait for them to respond
        shop_menu_message = await ctx.send(embed=embed)
        try:
            done, pending = await asyncio.wait([
                self.bot.wait_for("message", check=lambda m: m.author.id == ctx.author.id and m.channel == ctx.channel and m.content),
                self.bot.wait_for("raw_message_delete", check=lambda m: m.message_id == shop_menu_message.id),
            ], timeout=120, return_when=asyncio.FIRST_COMPLETED)
        except asyncio.TimeoutError:
            pass

        # See how they responded
        for future in pending:
            future.cancel()
        try:
            done = done.pop().result()
        except KeyError:
            return await ctx.send(f"Timed out asking for plant type {ctx.author.mention}.")
        if isinstance(done, discord.RawMessageDeleteEvent):
            return
        plant_type_message = done
        given_response = plant_type_message.content.lower().replace(' ', '_')

        # See if they want to cancel
        if given_response == "cancel":
            try:
                await plant_type_message.add_reaction("\N{OK HAND SIGN}")
            except discord.HTTPException:
                pass
            return

        # See if they want a plant pot
        if given_response == "pot":
            if plant_limit >= self.bot.config.get('plants', {}).get('hard_plant_cap', 10):
                return await ctx.send(f"You're already at the maximum amount of pots, {ctx.author.mention}! :c")
            if user_experience >= self.get_points_for_plant_pot(plant_limit):
                async with self.bot.database() as db:
                    await db(
                        """INSERT INTO user_settings (user_id, plant_limit, user_experience) VALUES ($1, 2, $2) ON CONFLICT (user_id) DO UPDATE
                        SET plant_limit=user_settings.plant_limit+1, user_experience=user_settings.user_experience-excluded.user_experience""",
                        ctx.author.id, self.get_points_for_plant_pot(plant_limit)
                    )
                return await ctx.send(f"Given you another plant pot, {ctx.author.mention}!")
            else:
                return await ctx.send(f"You don't have the required experience to get a new plant pot, {ctx.author.mention} :c")

        # See if they want a revival token
        item_type = self.bot.items.get(given_response)
        if item_type is not None:
            if user_experience >= item_type.price:
                async with self.bot.database() as db:
                    await db.start_transaction()
                    await db(
                        """INSERT INTO user_settings (user_id, user_experience) VALUES ($1, $2) ON CONFLICT (user_id) DO UPDATE
                        SET user_experience=user_settings.user_experience-excluded.user_experience""",
                        ctx.author.id, item_type.price
                    )
                    await db(
                        """INSERT INTO user_inventory (user_id, item_name, amount) VALUES ($1, $2, 1)
                        ON CONFLICT (user_id, item_name) DO UPDATE SET amount=user_inventory.amount+excluded.amount""",
                        ctx.author.id, item_type.name
                    )
                    await db.commit_transaction()
                return await ctx.send(f"Given you a **{item_type.display_name}**, {ctx.author.mention}!")
            else:
                return await ctx.send(f"You don't have the required experience to get a **{item_type.display_name}**, {ctx.author.mention} :c")

        # See if they want a plant
        try:
            plant_type = self.bot.plants[given_response]
        except KeyError:
            return await ctx.send(f"`{plant_type_message.content}` isn't an available plant name, {ctx.author.mention}!", allowed_mentions=discord.AllowedMentions(users=[ctx.author], roles=False, everyone=False))
        if can_purchase_new_plants is False:
            return await ctx.send(f"You can't purchase new plants for another **{buy_plant_cooldown_delta.clean}**.")
        if plant_type not in available_plants.values():
            return await ctx.send(f"**{plant_type.display_name.capitalize()}** isn't available in your shop this month, {ctx.author.mention} :c")
        if plant_type.required_experience > user_experience:
            return await ctx.send(f"You don't have the required experience to get a **{plant_type.display_name}**, {ctx.author.mention} (it requires {plant_type.required_experience}, you have {user_experience}) :c")
        if len(plant_level_rows) >= plant_limit:
            return await ctx.send(f"You don't have enough plant pots to be able to get a **{plant_type.display_name}**, {ctx.author.mention} :c")

        # Get a name for the plant
        await ctx.send("What name do you want to give your plant?")
        while True:
            try:
                plant_name_message = await self.bot.wait_for("message", check=lambda m: m.author.id == ctx.author.id and m.channel == ctx.channel and m.content, timeout=120)
            except asyncio.TimeoutError:
                return await ctx.send(f"Timed out asking for plant name {ctx.author.mention}.")
            plant_name = self.bot.get_cog("PlantCareCommands").validate_name(plant_name_message.content)
            if len(plant_name) > 50 or len(plant_name) == 0:
                await ctx.send("That name is too long! Please give another one instead!")
            else:
                break

        # Save the enw plant to database
        async with self.bot.database() as db:
            plant_name_exists = await db(
                "SELECT * FROM plant_levels WHERE user_id=$1 AND LOWER(plant_name)=LOWER($2)",
                ctx.author.id, plant_name
            )
            if plant_name_exists:
                return await ctx.send(f"You've already used the name `{plant_name}` for one of your other plants - please run this command again to give a new one!", allowed_mentions=discord.AllowedMentions(everyone=False, users=False, roles=False))
            await db(
                """INSERT INTO plant_levels (user_id, plant_name, plant_type, plant_nourishment, last_water_time, original_owner_id, plant_adoption_time, plant_pot_hue)
                VALUES ($1::BIGINT, $2, $3, 0, $4, $1::BIGINT, TIMEZONE('UTC', NOW()), CAST($1::BIGINT % 360 AS SMALLINT)) ON CONFLICT (user_id, plant_name) DO UPDATE
                SET plant_nourishment=0, last_water_time=$4""",
                ctx.author.id, plant_name, plant_type.name, dt(2000, 1, 1),
            )
            await db(
                "UPDATE user_settings SET user_experience=user_settings.user_experience-$2, last_plant_shop_time=TIMEZONE('UTC', NOW()) WHERE user_id=$1",
                ctx.author.id, plant_type.required_experience,
            )
        await ctx.send(f"Planted your **{plant_type.display_name}** seeds!")
Esempio n. 24
0
    async def edittemplate(self, ctx: utils.Context,
                           template: localutils.Template):
        """
        Edits a template for your guild.
        """

        # See if they're already editing that template
        if self.template_editing_locks[ctx.guild.id].locked():
            return await ctx.send("You're already editing a template.")

        # See if they're bot support
        is_bot_support = await self.user_is_bot_support(ctx)

        # Grab the template edit lock
        async with self.template_editing_locks[ctx.guild.id]:

            # Get the template fields
            async with self.bot.database() as db:
                await template.fetch_fields(db)
                guild_settings_rows = await db(
                    """SELECT * FROM guild_settings WHERE guild_id=$1 OR guild_id=0 ORDER BY guild_id DESC""",
                    ctx.guild.id,
                )
                perks = await localutils.get_perks_for_guild(db, ctx.guild.id)
            ctx.guild_perks = perks
            guild_settings = guild_settings_rows[0]

            # Set up our initial vars so we can edit them later
            template_display_edit_message = await ctx.send(
                "Loading template...")  # The message with the template
            components = utils.MessageComponents.add_buttons_with_rows(
                utils.Button("Template name",
                             custom_id="1\N{COMBINING ENCLOSING KEYCAP}"),
                utils.Button("Profile verification channel",
                             custom_id="2\N{COMBINING ENCLOSING KEYCAP}"),
                utils.Button("Profile archive channel",
                             custom_id="3\N{COMBINING ENCLOSING KEYCAP}"),
                utils.Button("Profile completion role",
                             custom_id="4\N{COMBINING ENCLOSING KEYCAP}"),
                utils.Button("Template fields",
                             custom_id="5\N{COMBINING ENCLOSING KEYCAP}"),
                utils.Button("Profile count per user",
                             custom_id="6\N{COMBINING ENCLOSING KEYCAP}"),
                utils.Button("Done",
                             custom_id="DONE",
                             style=utils.ButtonStyle.SUCCESS),
            )
            template_options_edit_message = None
            should_edit = True  # Whether or not the template display message should be edited

            # Start our edit loop
            while True:

                # Ask what they want to edit
                if should_edit:
                    try:
                        await template_display_edit_message.edit(
                            content=None,
                            embed=template.build_embed(self.bot, brief=True),
                            allowed_mentions=discord.AllowedMentions(
                                roles=False),
                        )
                    except discord.HTTPException:
                        return
                    should_edit = False

                # Wait for a response from the user
                try:
                    if template_options_edit_message:
                        await template_options_edit_message.edit(
                            components=components.enable_components())
                    else:
                        template_options_edit_message = await ctx.send(
                            "What would you like to edit?",
                            components=components)
                    payload = await template_options_edit_message.wait_for_button_click(
                        check=lambda p: p.user.id == ctx.author.id,
                        timeout=120)
                    await payload.ack()
                    reaction = payload.component.custom_id
                except asyncio.TimeoutError:
                    try:
                        await template_options_edit_message.edit(
                            content="Timed out waiting for edit response.",
                            components=None)
                    except discord.HTTPException:
                        pass
                    return

                # See what they reacted with
                try:
                    available_reactions = {
                        "1\N{COMBINING ENCLOSING KEYCAP}": ("name", str),
                        "2\N{COMBINING ENCLOSING KEYCAP}":
                        ("verification_channel_id",
                         commands.TextChannelConverter()),
                        "3\N{COMBINING ENCLOSING KEYCAP}":
                        ("archive_channel_id",
                         commands.TextChannelConverter()),
                        "4\N{COMBINING ENCLOSING KEYCAP}":
                        ("role_id", commands.RoleConverter()),
                        "5\N{COMBINING ENCLOSING KEYCAP}":
                        (None,
                         self.edit_field(ctx, template, guild_settings,
                                         is_bot_support)),
                        "6\N{COMBINING ENCLOSING KEYCAP}":
                        ("max_profile_count", int),
                        "DONE":
                        None,
                    }
                    attr, converter = available_reactions[reaction]
                except TypeError:
                    break  # They're done

                # Disable the components
                await template_options_edit_message.edit(
                    components=components.disable_components())

                # If they want to edit a field, we go through this section
                if attr is None:

                    # Let them change the fields
                    fields_have_changed = await converter

                    # If the fields have changed then we should update the message
                    if fields_have_changed:
                        async with self.bot.database() as db:
                            await template.fetch_fields(db)
                        should_edit = True

                    # And we're done with this round, so continue upwards
                    continue

                # Change the given attribute
                should_edit = await self.change_template_attribute(
                    ctx, template, guild_settings, is_bot_support, attr,
                    converter)

        # Tell them it's done
        await template_options_edit_message.edit(
            content=
            (f"Finished editing template. Users can create profiles with `{ctx.clean_prefix}{template.name.lower()} set`, "
             f"edit with `{ctx.clean_prefix}{template.name.lower()} edit`, and show them with "
             f"`{ctx.clean_prefix}{template.name.lower()} get`."),
            components=None,
        )
Esempio n. 25
0
    async def leaderboard(self, ctx: utils.Context, days: int = None):
        """
        Gives you the leaderboard users for the server.
        """

        if days is None:
            days = self.bot.guild_settings[
                ctx.guild.id]['activity_window_days']
        elif days <= 0:
            days = 7
        elif days > 365:
            days = 365

        # This takes a while
        async with ctx.typing():

            # Get all their valid user IDs
            async with self.bot.database() as db:
                message_rows = await db(
                    """SELECT user_id, COUNT(timestamp) FROM user_messages WHERE guild_id=$1 AND
                    timestamp > TIMEZONE('UTC', NOW()) - MAKE_INTERVAL(days => $2) GROUP BY user_id
                    ORDER BY COUNT(timestamp) DESC;""",
                    ctx.guild.id,
                    days,
                )
                vc_rows = await db(
                    """SELECT user_id, COUNT(timestamp) FROM user_vc_activity WHERE guild_id=$1 AND
                    timestamp > TIMEZONE('UTC', NOW()) - MAKE_INTERVAL(days => $2) GROUP BY user_id
                    ORDER BY COUNT(timestamp) DESC;""",
                    ctx.guild.id,
                    days,
                )
                if self.bot.guild_settings[
                        ctx.guild.id]['minecraft_srv_authorization']:
                    minecraft_rows = await db(
                        """SELECT user_id, COUNT(timestamp) FROM minecraft_server_activity WHERE guild_id=$1 AND
                        timestamp > TIMEZONE('UTC', NOW()) - MAKE_INTERVAL(days => $2) GROUP BY user_id
                        ORDER BY COUNT(timestamp) DESC;""",
                        ctx.guild.id,
                        days,
                    )
                else:
                    minecraft_rows = []

            # Sort that into more formattable data
            user_data_dict = collections.defaultdict({
                'message_count':
                0,
                'vc_minute_count':
                0,
                'minecraft_minute_count':
                0
            }.copy)  # uid: {message_count: int, vc_minute_count: int}
            for row in message_rows:
                user_data_dict[row['user_id']]['message_count'] = row['count']
            for row in vc_rows:
                user_data_dict[
                    row['user_id']]['vc_minute_count'] = row['count']
            for row in minecraft_rows:
                user_data_dict[
                    row['user_id']]['minecraft_minute_count'] = row['count']

            # And now make it into something we can sort
            valid_guild_user_data = [{
                'id': uid,
                'm': d['message_count'],
                'vc': d['vc_minute_count'],
                'mc': d['minecraft_minute_count']
            } for uid, d in user_data_dict.items()
                                     if ctx.guild.get_member(uid)]
            ordered_guild_user_data = sorted(valid_guild_user_data,
                                             key=lambda k: k['m'] +
                                             (k['vc'] // 5) + (k['mc'] // 5),
                                             reverse=True)

            # And now make it into strings
            ordered_guild_user_strings = []
            for d in ordered_guild_user_data:
                total_points = d['m'] + (d['vc'] // 5) + (d['mc'] // 5)
                vc_time = utils.TimeValue(d['vc'] * 60).clean_spaced or '0m'
                if self.bot.guild_settings[
                        ctx.guild.id]['minecraft_srv_authorization']:
                    ordered_guild_user_strings.append(
                        f"**<@{d['id']}>** - **{total_points:,}** (**{d['m']:,}** text, **{vc_time}** VC, **{d['mc']:,}** Minecraft)"
                    )
                else:
                    ordered_guild_user_strings.append(
                        f"**<@{d['id']}>** - **{total_points:,}** (**{d['m']:,}** text, **{vc_time}** VC)"
                    )

        # Make menu
        return await utils.Paginator(
            ordered_guild_user_strings,
            formatter=utils.Paginator.default_ranked_list_formatter).start(ctx)
Esempio n. 26
0
    async def marry(self, ctx: vbu.Context, *, target: utils.converters.UnblockedMember):
        """
        Lets you propose to another Discord user.
        """

        # Get the family tree member objects
        family_guild_id = utils.get_family_guild_id(ctx)
        author_tree, target_tree = utils.FamilyTreeMember.get_multiple(ctx.author.id, target.id, guild_id=family_guild_id)

        # Check they're not themselves
        if target.id == ctx.author.id:
            return await ctx.send("That's you. You can't marry yourself.", wait=False)

        # Check they're not a bot
        if target.bot:
            if target.id == self.bot.user.id:
                return await ctx.send("I think I could do better actually, but thank you!", wait=False)
            return await ctx.send("That is a robot. Robots cannot consent to marriage.", wait=False)

        # Lock those users
        re = await self.bot.redis.get_connection()
        try:
            lock = await utils.ProposalLock.lock(re, ctx.author.id, target.id)
        except utils.ProposalInProgress:
            return await ctx.send("Aren't you popular! One of you is already waiting on a proposal - please try again later.", wait=False)

        # See if we're already married
        if author_tree._partner:
            await lock.unlock()
            return await ctx.send(
                f"Hey, {ctx.author.mention}, you're already married! Try divorcing your partner first \N{FACE WITH ROLLING EYES}",
                allowed_mentions=utils.only_mention(ctx.author),
                wait=False,
            )

        # See if the *target* is already married
        if target_tree._partner:
            await lock.unlock()
            return await ctx.send(
                f"Sorry, {ctx.author.mention}, it looks like {target.mention} is already married \N{PENSIVE FACE}",
                allowed_mentions=utils.only_mention(ctx.author),
                wait=False,
            )

        # See if they're already related
        async with ctx.typing():
            relation = author_tree.get_relation(target_tree)
        if relation and utils.guild_allows_incest(ctx) is False:
            await lock.unlock()
            return await ctx.send(
                f"Woah woah woah, it looks like you guys are already related! {target.mention} is your {relation}!",
                allowed_mentions=utils.only_mention(ctx.author),
                wait=False,
            )

        # Check the size of their trees
        # TODO I can make this a util because I'm going to use it a couple times
        max_family_members = utils.get_max_family_members(ctx)
        async with ctx.typing():
            family_member_count = 0
            for i in author_tree.span(add_parent=True, expand_upwards=True):
                if family_member_count >= max_family_members:
                    break
                family_member_count += 1
            for i in target_tree.span(add_parent=True, expand_upwards=True):
                if family_member_count >= max_family_members:
                    break
                family_member_count += 1
            if family_member_count >= max_family_members:
                await lock.unlock()
                return await ctx.send(
                    f"If you added {target.mention} to your family, you'd have over {max_family_members} in your family. Sorry!",
                    allowed_mentions=utils.only_mention(ctx.author),
                    wait=False,
                )

        # Set up the proposal
        try:
            result = await utils.send_proposal_message(
                ctx, target,
                f"Hey, {target.mention}, it would make {ctx.author.mention} really happy if you would marry them. What do you say?",
            )
        except Exception:
            result = None
        if result is None:
            return await lock.unlock()

        # They said yes!
        async with self.bot.database() as db:
            try:
                await db.start_transaction()
                await db(
                    "INSERT INTO marriages (user_id, partner_id, guild_id, timestamp) VALUES ($1, $2, $3, $4), ($2, $1, $3, $4)",
                    ctx.author.id, target.id, family_guild_id, dt.utcnow(),
                )
                await db.commit_transaction()
            except asyncpg.UniqueViolationError:
                await lock.unlock()
                return await result.ctx.send("I ran into an error saving your family data.", wait=False)
        await result.ctx.send(
            f"I'm happy to introduce {target.mention} into the family of {ctx.author.mention}!",
            wait=False,
        )  # Keep allowed mentions on

        # Ping over redis
        author_tree._partner = target.id
        target_tree._partner = ctx.author.id
        await re.publish('TreeMemberUpdate', author_tree.to_json())
        await re.publish('TreeMemberUpdate', target_tree.to_json())
        await re.disconnect()
        await lock.unlock()
Esempio n. 27
0
    async def setsona(self, ctx: utils.Context):
        """
        Stores your fursona information in the bot.
        """

        # See if the user already has a fursona stored
        async with self.bot.database() as db:
            rows = await db(
                "SELECT * FROM fursonas WHERE guild_id=$1 AND user_id=$2",
                ctx.guild.id, ctx.author.id)
        current_sona_names = [row['name'].lower() for row in rows]
        ctx.current_sona_names = current_sona_names

        # See if they're at the limit
        try:
            sona_limit = max(o for i, o in self.bot.guild_settings[
                ctx.guild.id].setdefault('role_sona_count', dict()).items()
                             if int(i) in ctx.author._roles)
        except ValueError:
            sona_limit = 1
        if len(current_sona_names) >= sona_limit:
            return await ctx.send(
                "You're already at the sona limit - you have to delete one to be able to set another."
            )

        # See if they're setting one up already
        if ctx.author.id in self.currently_setting_sonas:
            return await ctx.send(
                "You're already setting up a sona! Please finish that one off first!"
            )

        # Try and send them an initial DM
        user = ctx.author
        start_message = f"Now taking you through setting up your sona on **{ctx.guild.name}**!\n"
        if not self.bot.guild_settings[ctx.guild.id]["nsfw_is_allowed"]:
            start_message += f"NSFW fursonas are not allowed for **{ctx.guild.name}** and will be automatically declined.\n"
        try:
            await user.send(start_message.strip())
        except discord.Forbidden:
            return await ctx.send(
                "I couldn't send you a DM! Please open your DMs for this server and try again."
            )
        self.currently_setting_sonas.add(user.id)
        await ctx.send("Sent you a DM!")

        # Ask about name
        name_message = await self.send_verification_message(
            user, "What is the name of your sona?")
        if name_message is None:
            return self.currently_setting_sonas.remove(user.id)
        if name_message.content.lower() in current_sona_names:
            self.currently_setting_sonas.remove(user.id)
            return await user.send(
                f"You already have a sona with the name `{name_message.content}`. Please start your setup again and provide a different name."
            )

        # Ask about gender
        gender_message = await self.send_verification_message(
            user, "What's your sona's gender?")
        if gender_message is None:
            return self.currently_setting_sonas.remove(user.id)

        # Ask about age
        age_message = await self.send_verification_message(
            user, "How old is your sona?")
        if age_message is None:
            return self.currently_setting_sonas.remove(user.id)

        # Ask about species
        species_message = await self.send_verification_message(
            user, "What species is your sona?")
        if species_message is None:
            return self.currently_setting_sonas.remove(user.id)

        # Ask about orientation
        orientation_message = await self.send_verification_message(
            user, "What's your sona's orientation?")
        if orientation_message is None:
            return self.currently_setting_sonas.remove(user.id)

        # Ask about height
        height_message = await self.send_verification_message(
            user, "How tall is your sona?")
        if height_message is None:
            return self.currently_setting_sonas.remove(user.id)

        # Ask about weight
        weight_message = await self.send_verification_message(
            user, "What's the weight of your sona?")
        if weight_message is None:
            return self.currently_setting_sonas.remove(user.id)

        # Ask about bio
        bio_message = await self.send_verification_message(
            user, "What's the bio of your sona?", max_length=1000)
        if bio_message is None:
            return self.currently_setting_sonas.remove(user.id)

        # Ask about image
        def check(m) -> bool:
            return all([
                isinstance(m.channel, discord.DMChannel),
                m.author.id == user.id,
                any([
                    m.content.lower() == "no",
                    self.get_image_from_message(m)
                ]),
            ])

        image_message = await self.send_verification_message(
            user,
            "Do you have an image for your sona? Please post it if you have one (as a link or an attachment), or say `no` to continue without.",
            check=check)
        if image_message is None:
            return self.currently_setting_sonas.remove(user.id)

        # Ask about NSFW
        if self.bot.guild_settings[ctx.guild.id]["nsfw_is_allowed"]:
            check = lambda m: isinstance(
                m.channel, discord.DMChannel
            ) and m.author.id == user.id and m.content.lower(
            ) in ["yes", "no"]
            nsfw_message = await self.send_verification_message(
                user,
                "Is your sona NSFW? Please either say `yes` or `no`.",
                check=check)
            if nsfw_message is None:
                return self.currently_setting_sonas.remove(user.id)
            else:
                nsfw_content = nsfw_message.content.lower()
        else:
            nsfw_content = "no"

        # Format that into data
        image_content = None if image_message.content.lower(
        ) == "no" else self.get_image_from_message(image_message)
        information = {
            'name': name_message.content,
            'gender': gender_message.content,
            'age': age_message.content,
            'species': species_message.content,
            'orientation': orientation_message.content,
            'height': height_message.content,
            'weight': weight_message.content,
            'bio': bio_message.content,
            'image': image_content,
            'nsfw': nsfw_content == "yes",
        }
        self.currently_setting_sonas.remove(user.id)
        ctx.information = information
        if information['nsfw'] and not self.bot.guild_settings[
                ctx.guild.id]["nsfw_is_allowed"]:
            return await user.send(
                "Your fursona has been automatically declined as it is NSFW")
        await self.bot.get_command("setsonabyjson").invoke(ctx)
Esempio n. 28
0
    async def importsona(self, ctx: utils.Context):
        """
        Get your sona from another server.
        """

        # See if they're setting one up already
        if ctx.author.id in self.currently_setting_sonas:
            return await ctx.send(
                "You're already setting up a sona! Please finish that one off first!"
            )

        # Try and send them an initial DM
        try:
            await ctx.author.send(
                f"Now taking you through importing your sona to **{ctx.guild.name}**!"
            )
        except discord.Forbidden:
            return await ctx.send(
                "I couldn't send you a DM! Please open your DMs for this server and try again."
            )
        self.currently_setting_sonas.add(ctx.author.id)
        await ctx.send("Sent you a DM!")

        # Get sona data
        async with self.bot.database() as db:
            database_rows = await db("SELECT * FROM fursonas WHERE user_id=$1",
                                     ctx.author.id)

        # Format that into a list
        all_user_sonas = []
        for row in database_rows:
            try:
                guild = self.bot.get_guild(
                    row['guild_id']) or await self.bot.fetch_guild(
                        row['guild_id'])
            except discord.Forbidden:
                guild = None

            # Add to the all sona list
            menu_data = dict(row)
            if guild:
                menu_data.update({"guild_name": guild.name})
            else:
                menu_data.update({"guild_name": "Unknown Guild Name"})
            all_user_sonas.append(menu_data)

        # Let's add our other servers via their APIs
        for api_data in self.OTHER_FURRY_GUILD_DATA[::-1]:

            # Format data
            url = api_data['url']
            params = {
                i: o.format(user=ctx.author, guild=ctx.guild, bot=self.bot)
                for i, o in api_data.get('params', dict()).copy().items()
            }
            headers = {
                i: o.format(user=ctx.author, guild=ctx.guild, bot=self.bot)
                for i, o in api_data.get('headers', dict()).copy().items()
            }

            # Run request
            try:
                async with self.bot.session.get(url,
                                                params=params,
                                                headers=headers) as r:
                    grabbed_sona_data = await r.json()
            except Exception:
                grabbed_sona_data = {'data': []}

            # Add to lists
            if grabbed_sona_data['data']:
                guild_id = api_data['guild_id']
                guild_name = api_data['name']

                # Add to the all sona list
                for sona in grabbed_sona_data['data']:
                    menu_data = sona.copy()
                    menu_data.update({
                        "guild_name": guild_name,
                        "guild_id": guild_id
                    })
                    all_user_sonas.append(menu_data)

        # Filter the list
        all_user_sonas = [
            i for i in all_user_sonas if i['guild_id'] != ctx.guild.id
        ]
        if not self.bot.guild_settings[ctx.guild.id]["nsfw_is_allowed"]:
            all_user_sonas = [i for i in all_user_sonas if i['nsfw'] is False]
        if not all_user_sonas:
            self.currently_setting_sonas.remove(ctx.author.id)
            return await ctx.send(
                "You have no sonas available to import from other servers.")

        # Send it off to the user
        pages = menus.MenuPages(
            source=FursonaPageSource(all_user_sonas, per_page=1))
        await pages.start(ctx, channel=ctx.author, wait=True)

        # Ask if the user wants to import the sona they stopped on
        sona_data = pages.raw_sona_data
        ask_import_message = await ctx.author.send(
            f"Do you want to import your sona from **{sona_data['guild_name']}**?"
        )
        await ask_import_message.add_reaction(self.CHECK_MARK_EMOJI)
        await ask_import_message.add_reaction(self.CROSS_MARK_EMOJI)
        try:
            check = lambda r, u: r.message.id == ask_import_message.id and u.id == ctx.author.id
            reaction, _ = await self.bot.wait_for("reaction_add",
                                                  check=check,
                                                  timeout=120)
        except asyncio.TimeoutError:
            self.currently_setting_sonas.remove(ctx.author.id)
            return await ctx.author.send("Timed out asking about sona import.")

        # Import data
        self.currently_setting_sonas.remove(ctx.author.id)
        emoji = str(reaction.emoji)
        if emoji == self.CROSS_MARK_EMOJI:
            return await ctx.author.send(
                "Alright, cancelled importing your sona.")
        command = self.bot.get_command("setsonabyjson")
        ctx.information = sona_data
        return await command.invoke(ctx)
Esempio n. 29
0
    async def herbiary(self, ctx: utils.Context, *, plant_name: str = None):
        """
        Get the information for a given plant.
        """

        # See if a name was given
        if plant_name is None:
            plant_dict = collections.defaultdict(list)
            for plant in self.bot.plants.values():
                plant_dict[plant.plant_level].append(
                    plant.display_name.capitalize())
            embed_fields = []
            embed = utils.Embed(use_random_colour=True)
            for plant_level, plants in plant_dict.items():
                plants.sort()
                embed_fields.append(
                    (f"Level {plant_level}", "\n".join(plants)))
            for field in sorted(embed_fields):
                embed.add_field(*field, inline=True)
            ctx._set_footer(embed)
            return await ctx.send(embed=embed)

        # See if the given name is valid
        plant_name = plant_name.replace(' ', '_').lower()
        if plant_name not in self.bot.plants:
            return await ctx.send(
                f"There's no plant with the name **{plant_name.replace('_', ' ')}** :c",
                allowed_mentions=discord.AllowedMentions(users=False,
                                                         roles=False,
                                                         everyone=False))
        plant = self.bot.plants[plant_name]

        # Work out our artist info to be displayed
        description_list = []
        artist_info = self.artist_info.get(plant.artist, {}).copy()
        discord_id = artist_info.pop('discord', None)
        description_list.append(f"**Artist `{plant.artist}`**")
        if discord_id:
            description_list.append(
                f"Discord: <@{discord_id}> (`{discord_id}`)")
        for i, o in sorted(artist_info.items()):
            description_list.append(f"{i.capitalize()}: [Link]({o})")
        description_list.append("")

        # Work out some other info we want displayed
        description_list.append(f"Plant level {plant.plant_level}")
        description_list.append(f"Costs {plant.required_experience} exp")
        description_list.append(
            f"Gives between {plant.experience_gain['minimum']} and {plant.experience_gain['maximum']} exp"
        )

        # Embed the data
        with utils.Embed(use_random_colour=True) as embed:
            embed.title = plant.display_name.capitalize()
            embed.description = '\n'.join(description_list)
            embed.set_image("attachment://plant.png")
            ctx._set_footer(embed)
        display_utils = self.bot.get_cog("PlantDisplayUtils")
        plant_image_bytes = display_utils.image_to_bytes(
            display_utils.get_plant_image(plant.name, 21, "clay",
                                          random.randint(0, 360)))
        await ctx.send(embed=embed,
                       file=discord.File(plant_image_bytes,
                                         filename="plant.png"))
Esempio n. 30
0
    async def waterplant(self, ctx: utils.Context, *, plant_name: str):
        """
        Increase the growth level of your plant.
        """

        # Decide on our plant type - will be ignored if there's already a plant
        db = await self.bot.database.get_connection()

        # See if they have a plant available
        plant_level_row = await db(
            "SELECT * FROM plant_levels WHERE user_id=$1 AND LOWER(plant_name)=LOWER($2)",
            ctx.author.id, plant_name)
        if not plant_level_row:
            await db.disconnect()
            return await ctx.send(
                f"You don't have a plant with the name **{plant_name}**! Run `{ctx.prefix}getplant` to plant some new seeds, or `{ctx.prefix}plants` to see the list of plants you have already!",
                allowed_mentions=discord.AllowedMentions(users=False,
                                                         roles=False,
                                                         everyone=False))
        plant_data = self.bot.plants[plant_level_row[0]['plant_type']]

        # See if they're allowed to water things
        if plant_level_row[0]['last_water_time'] + timedelta(
                **self.bot.config.get('plants', {}).get(
                    'water_cooldown', {'minutes': 15})) > dt.utcnow(
                    ) and ctx.author.id not in self.bot.owner_ids:
            await db.disconnect()
            timeout = utils.TimeValue(
                ((plant_level_row[0]['last_water_time'] +
                  timedelta(**self.bot.config.get('plants', {}).get(
                      'water_cooldown', {'minutes': 15}))) -
                 dt.utcnow()).total_seconds())
            return await ctx.send(
                f"You need to wait another {timeout.clean_spaced} to be able water your {plant_level_row[0]['plant_type'].replace('_', ' ')}."
            )
        last_water_time = plant_level_row[0]['last_water_time']

        # See if the plant should be dead
        if plant_level_row[0]['plant_nourishment'] < 0:
            plant_level_row = await db(
                """UPDATE plant_levels SET
                plant_nourishment=LEAST(-plant_levels.plant_nourishment, plant_levels.plant_nourishment), last_water_time=$3
                WHERE user_id=$1 AND LOWER(plant_name)=LOWER($2) RETURNING *""",
                ctx.author.id,
                plant_name,
                dt.utcnow(),
            )

        # Increase the nourishment otherwise
        else:
            plant_level_row = await db(
                """UPDATE plant_levels
                SET plant_nourishment=LEAST(plant_levels.plant_nourishment+1, $4), last_water_time=$3
                WHERE user_id=$1 AND LOWER(plant_name)=LOWER($2) RETURNING *""",
                ctx.author.id,
                plant_name,
                dt.utcnow(),
                plant_data.max_nourishment_level,
            )

        # Add to the user exp if the plant is alive
        user_plant_data = plant_level_row[0]
        gained_experience = 0
        original_gained_experience = 0
        multipliers = []  # List[Tuple[float, "reason"]]
        additional_text = []  # List[str]
        voted_on_topgg = False

        # And now let's water the damn thing
        if user_plant_data['plant_nourishment'] > 0:

            # Get the experience that they should have gained
            gained_experience = plant_data.get_experience()
            original_gained_experience = gained_experience

            # See if we want to give them a 30 second water-time bonus
            if dt.utcnow() - last_water_time - timedelta(
                    **self.bot.config.get('plants', {}).get(
                        'water_cooldown', {'minutes': 15})) <= timedelta(
                            seconds=30):
                multipliers.append((
                    1.5,
                    "You watered within 30 seconds of your plant's cooldown resetting."
                ))

            # See if we want to give the new owner bonus
            if plant_level_row[0]['user_id'] != plant_level_row[0][
                    'original_owner_id']:
                multipliers.append(
                    (1.05, "You watered a plant that you got from a trade."))

            # See if we want to give them the voter bonus
            if self.bot.config.get(
                    'bot_listing_api_keys',
                {}).get('topgg_token') and await self.get_user_voted(
                    ctx.author.id):
                multipliers.append((
                    1.1,
                    f"You [voted for the bot](https://top.gg/bot/{self.bot.user.id}/vote) on Top.gg."
                ))
                voted_on_topgg = True

            # See if we want to give them the plant longevity bonus
            if user_plant_data['plant_adoption_time'] < dt.utcnow(
            ) - timedelta(days=7):
                multipliers.append(
                    (1.2, "Your plant has been alive for longer than a week."))

            # Add the actual multiplier values
            for multiplier, _ in multipliers:
                gained_experience *= multiplier

            # Update db
            gained_experience = int(gained_experience)
            await db(
                """INSERT INTO user_settings (user_id, user_experience) VALUES ($1, $2) ON CONFLICT (user_id)
                DO UPDATE SET user_experience=user_settings.user_experience+$2""",
                ctx.author.id,
                gained_experience,
            )

        # Send an output
        await db.disconnect()
        if user_plant_data['plant_nourishment'] < 0:
            return await ctx.send(
                "You sadly pour water into the dry soil of your silently wilting plant :c"
            )

        # Set up our output text
        gained_exp_string = f"**{gained_experience}**" if gained_experience == original_gained_experience else f"~~{original_gained_experience}~~ **{gained_experience}**"
        output_lines = []
        if plant_data.get_nourishment_display_level(
                user_plant_data['plant_nourishment']
        ) > plant_data.get_nourishment_display_level(
                user_plant_data['plant_nourishment'] - 1):
            output_lines.append(
                f"You gently pour water into **{plant_level_row[0]['plant_name']}**'s soil, gaining you {gained_exp_string} experience, watching your plant grow!~"
            )
        else:
            output_lines.append(
                f"You gently pour water into **{plant_level_row[0]['plant_name']}**'s soil, gaining you {gained_exp_string} experience~"
            )
        for m, t in multipliers:
            output_lines.append(f"**{m}x**: {t}")
        for t in additional_text:
            output_lines.append(t)

        # Try and embed the message
        embed = None
        if ctx.guild is None or ctx.channel.permissions_for(
                ctx.guild.me).embed_links:

            # Make initial embed
            embed = utils.Embed(use_random_colour=True,
                                description=output_lines[0])

            # Add multipliers
            if len(output_lines) > 1:
                embed.add_field("Multipliers",
                                "\n".join(
                                    [i.strip('') for i in output_lines[1:]]),
                                inline=False)

            # Add "please vote for Flower" footer
            counter = 0
            ctx._set_footer(embed)
            check = lambda text: 'vote' in text if voted_on_topgg else False  # Return True to change again - force to "vote for flower" if they haven't voted, else anything but
            while counter < 100 and check(embed.footer.text.lower()):
                ctx._set_footer(embed)
                counter += 1

            # Clear the text we would otherwise output
            output_lines.clear()

        # Send message
        return await ctx.send("\n".join(output_lines), embed=embed)