Ejemplo n.º 1
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
Ejemplo n.º 2
0
    async def reminder(self, ctx: utils.Context):
        """
        Shows you your reminders.
        """

        # Get the guild ID
        try:
            guild_id = ctx.guild.id
        except AttributeError:
            guild_id = 0

        # Grab their remidners
        async with self.bot.database() as db:
            rows = await db(
                "SELECT * FROM reminders WHERE user_id=$1 and guild_id=$2",
                ctx.author.id, guild_id)

        # Format an output string
        reminders = ""
        for reminder in rows:
            expiry = utils.TimeValue(
                (reminder['timestamp'] -
                 dt.utcnow()).total_seconds()).clean_spaced or 'now'
            reminders += f"\n`{reminder['reminder_id']}` - {reminder['message'][:70]} ({expiry})"
        message = f"Your reminders: {reminders}"

        # Send to the user
        await ctx.send(message or "You have no reminders.",
                       allowed_mentions=discord.AllowedMentions.none())
Ejemplo n.º 3
0
    async def daily(self, ctx: utils.Context):
        """
        Get money on the daily.
        """

        async with self.bot.database() as db:

            # See which currencies allow faily command
            all_guild_currencies = await db(
                """SELECT currency_name FROM guild_currencies WHERE guild_id=$1 AND allow_daily_command=true""",
                ctx.guild.id,
            )

            # Check out last run commands
            allowed_daily_currencies = await db(
                """SELECT currency_name, last_daily_command FROM user_money WHERE user_money.guild_id=$1 AND
                user_money.user_id=$2 AND currency_name=ANY($3::TEXT[])""",
                ctx.guild.id, ctx.author.id, [i['currency_name'] for i in all_guild_currencies],
            )

            # Work out when each thing was last run
            allowed_daily_dict = {}
            for row in all_guild_currencies:
                allowed_daily_dict[row['currency_name']] = dt(2000, 1, 1)
            for row in allowed_daily_currencies:
                allowed_daily_dict[row['currency_name']] = row['last_daily_command']
            if not allowed_daily_dict:
                return await ctx.send("There's nothing available for use with the daily command right now.")

            # Work out how much we're adding
            changed_daily = {}
            for currency_name, last_run_time in allowed_daily_dict.items():
                if last_run_time > dt.utcnow() - self.DAILY_COMMAND_TIMEOUT:
                    continue
                amount = random.randint(9_000, 13_000)
                await db(
                    """INSERT INTO user_money (user_id, guild_id, currency_name, money_amount, last_daily_command)
                    VALUES ($1, $2, $3, $4, $5) ON CONFLICT (user_id, guild_id, currency_name) DO UPDATE SET
                    money_amount=user_money.money_amount+excluded.money_amount, last_daily_command=excluded.last_daily_command""",
                    ctx.author.id, ctx.guild.id, currency_name, amount, ctx.message.created_at,
                )
                self.bot.dispatch("transaction", ctx.author, currency_name, amount, "DAILY_COMMAND")
                changed_daily[row['currency_name']] = amount

        # Make them into an embed
        if not changed_daily:
            soonest_allow_daily = max(allowed_daily_dict.values())
            soonest_tv = utils.TimeValue((soonest_allow_daily - (dt.utcnow() - self.DAILY_COMMAND_TIMEOUT)).total_seconds())
            return await ctx.send(f"You can't get anything with the daily command for another **{soonest_tv.clean_full}**.")
        embed = utils.Embed(use_random_colour=True)
        description_list = []
        for currency, amount in changed_daily.items():
            currency_name = currency.title() if currency.lower() == currency else currency
            description_list.append(f"**{currency_name}** - {amount}")
        embed.description = "\n".join(description_list)
        return await ctx.send(embed=embed)
Ejemplo n.º 4
0
    async def whois(self, ctx: utils.Context, user: discord.Member = None):
        """
        Give you some information about a user.
        """

        user = user or ctx.author
        with utils.Embed(use_random_colour=True) as embed:
            embed.set_author_to_user(user)
            account_creation_time_humanized = utils.TimeValue(
                (dt.utcnow() - user.created_at).total_seconds()).clean_full
            create_value = f"{user.created_at.strftime('%A %B %d %Y %I:%M:%S%p')}\n{account_creation_time_humanized} ago"
            embed.add_field("Account Creation Time",
                            create_value,
                            inline=False)
            guild_join_time_humanized = utils.TimeValue(
                (dt.utcnow() - user.joined_at).total_seconds()).clean_full
            join_value = f"{user.joined_at.strftime('%A %B %d %Y %I:%M:%S%p')}\n{guild_join_time_humanized} ago"
            embed.add_field("Guild Join Time", join_value, inline=False)
            embed.set_thumbnail(user.avatar_url_as(size=1024))
        return await ctx.send(embed=embed)
Ejemplo n.º 5
0
 async def format_page(self, menu, entries):
     text = ""
     for row in entries:
         total_points = row[1] + (row[3] // 5)
         vc_time = utils.TimeValue(row[3] * 60).clean or '0m'
         text += f"**<@{row[0]}>** - `{total_points:,}` (`{row[2]:,}` text, `{vc_time}` VC)\n"
     max_page = math.ceil(len(self.entries) / self.per_page)
     return {
         "content":
         f"""__{self.header}:__\n{text}\n\nPage {menu.current_page + 1} of {max_page}""",
         "allowed_mentions": discord.AllowedMentions.none()
     }
Ejemplo n.º 6
0
    async def partner(self,
                      ctx: utils.Context,
                      user: utils.converters.UserID = None):
        """
        Tells you who a user is married to.
        """

        # Get the user's info
        user_id = user or ctx.author.id
        user_name = await localutils.DiscordNameManager.fetch_name_by_id(
            self.bot, user_id)
        user_info = localutils.FamilyTreeMember.get(
            user_id, localutils.get_family_guild_id(ctx))

        # Check they have a partner
        if user_info._partner is None:
            if user_id == ctx.author.id:
                return await ctx.send(
                    f"You're not currently married.",
                    allowed_mentions=discord.AllowedMentions.none())
            return await ctx.send(
                f"**{localutils.escape_markdown(user_name)}** is not currently married.",
                allowed_mentions=discord.AllowedMentions.none(),
            )
        partner_name = await localutils.DiscordNameManager.fetch_name_by_id(
            self.bot, user_info._partner)

        # Get timestamp
        async with self.bot.database() as db:
            if self.bot.config.get('is_server_specific', False):
                data = await db(
                    "SELECT * FROM marriages WHERE user_id=$1 AND guild_id=$2",
                    user_id, user_info._guild_id)
            else:
                data = await db(
                    "SELECT * FROM marriages WHERE user_id=$1 AND guild_id=0",
                    user_id)
        try:
            timestamp = data[0]['timestamp']
        except Exception:
            timestamp = None

        # Output
        text = f"**{localutils.escape_markdown(user_name)}** is currently married to **{localutils.escape_markdown(partner_name)}** (`{user_info._partner}`). "
        if user_id == ctx.author.id:
            text = f"You're currently married to **{localutils.escape_markdown(partner_name)}** (`{user_info._partner}`). "
        if timestamp:
            duration = utils.TimeValue(
                (dt.utcnow() - timestamp).total_seconds())
            text += f"{'You' if user_id == ctx.author.id else 'They'}'ve been married for {duration.clean_days}."
        await ctx.send(text, allowed_mentions=discord.AllowedMentions.none())
Ejemplo n.º 7
0
    async def getitem(self, ctx: utils.Context, *,
                      item_name: commands.clean_content):
        """
        Gets you an item from the server.
        """

        # Get the item from the db
        item_name = item_name.lower()
        db = await self.bot.database.get_connection()
        acquire_information = await db(
            "SELECT * FROM guild_item_acquire_methods WHERE guild_id=$1 AND item_name=$2 AND acquired_by='Command'",
            ctx.guild.id, item_name)
        if not acquire_information:
            await db.disconnect()
            return await ctx.send(
                f"You can't acquire **{item_name}** items via the `getitem` command."
            )
        acquire_information = acquire_information[0]

        # See if they hit the timeout
        last_run = self.last_command_run[(ctx.guild.id, ctx.author.id,
                                          item_name)]
        if last_run + timedelta(
                seconds=acquire_information['acquire_per']) > dt.utcnow():
            cooldown_seconds = (
                (last_run +
                 timedelta(seconds=acquire_information['acquire_per'])) -
                dt.utcnow()).total_seconds()
            cooldown_timevalue = utils.TimeValue(cooldown_seconds)
            await db.disconnect()
            return await ctx.send(
                f"You can't run this command again for another `{cooldown_timevalue.clean_spaced}`."
            )
        self.last_command_run[(ctx.guild.id, ctx.author.id,
                               item_name)] = dt.utcnow()

        # Add to database
        amount = random.randint(acquire_information['min_acquired'],
                                acquire_information['max_acquired'])
        await db(
            """INSERT INTO user_inventories (guild_id, user_id, item_name, amount)
            VALUES ($1, $2, $3, $4) ON CONFLICT (guild_id, user_id, item_name)
            DO UPDATE SET amount=user_inventories.amount+excluded.amount""",
            ctx.guild.id,
            ctx.author.id,
            item_name,
            amount,
        )
        await db.disconnect()
        return await ctx.send(f"You've received `{amount:,}x {item_name}`.")
Ejemplo n.º 8
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:
            plant_data = await db(
                """SELECT * FROM plant_levels WHERE user_id=$1 ORDER BY plant_name DESC,
                plant_type DESC, plant_nourishment DESC, last_water_time DESC,
                plant_adoption_time DESC""",
                user.id,
            )

        # See if they have anything available
        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.bot.set_footer_from_config(embed)
        for plant in plant_data:
            plant_type_display = plant['plant_type'].replace('_',
                                                             ' ').capitalize()

            # Get the time when the plant will die
            if plant['immortal']:
                plant_death_time, plant_death_humanize_time = None, None
            else:
                death_timeout = timedelta(
                    **self.bot.config['plants']['death_timeout'])
                plant_death_time = plant['last_water_time'] + death_timeout
                plant_death_humanize_time = utils.TimeValue(
                    (plant_death_time -
                     dt.utcnow()).total_seconds()).clean_full

            # See how long the plant has been alive
            plant_life_humanize_time = utils.TimeValue(
                (dt.utcnow() -
                 plant['plant_adoption_time']).total_seconds()).clean_full

            # Make the text to put in the embed
            if plant['plant_nourishment'] == 0 or plant['immortal']:
                text = f"{plant_type_display}, nourishment level {plant['plant_nourishment']}/{self.bot.plants[plant['plant_type']].max_nourishment_level}."
            elif plant['plant_nourishment'] > 0 or plant['immortal']:
                text = f"**{plant_type_display}**, nourishment level {plant['plant_nourishment']}/{self.bot.plants[plant['plant_type']].max_nourishment_level}.\n"
                if not plant['immortal']:
                    text += f"If not watered, this plant will die in **{plant_death_humanize_time}**.\n"
                text += f"This plant has been alive for **{plant_life_humanize_time}**.\n"
            else:
                text = f"{plant_type_display}, dead :c"

            # And add the field
            embed.add_field(plant['plant_name'], text, inline=False)

        # Return to user
        return await ctx.send(embed=embed)
Ejemplo n.º 9
0
    async def water_plant_backend(self, user_id: int, plant_name: str):
        """
        Run the backend for the plant watering.

        Returns a sexy lil dictionary in format:
            {
                "text": str,
                "success": bool,
                "new_nourishment_level": int,
                "voted_on_topgg": bool,
                "new_user_experience": int,
                "multipliers": [
                    {
                        "multiplier": float,
                        "text": str
                    },
                    ...
                ]
            }
        """

        # 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)",
            user_id, plant_name)
        if not plant_level_row:
            await db.disconnect()
            return self.get_water_plant_dict(
                f"You don't have a plant with the name **{plant_name}**! Run the `shop` command to plant some new seeds, or `plants` to see the list of plants you have already!"
            )
        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 user_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 self.get_water_plant_dict(
                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 *""",
                user_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, notification_sent=FALSE
                WHERE user_id=$1 AND LOWER(plant_name)=LOWER($2) RETURNING *""",
                user_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[dict]
        additional_text = []  # List[str]
        voted_on_topgg = False

        # Disconnect from the database so we don't have hanging connections open while
        # making our Top.gg web request
        await db.disconnect()

        # 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({
                    "multiplier":
                    1.5,
                    "text":
                    "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({
                    "multiplier":
                    1.05,
                    "text":
                    "You watered a plant that you got from a trade."
                })

            # See if we want to give them the voter bonus
            user_voted_api_request = False
            try:
                user_voted_api_request = await asyncio.wait_for(
                    self.get_user_voted(user_id), timeout=2.0)
            except asyncio.TimeoutError:
                pass
            if self.bot.config.get(
                    'bot_listing_api_keys',
                {}).get('topgg_token') and user_voted_api_request:
                multipliers.append({
                    "multiplier":
                    1.1,
                    "text":
                    f"You [voted for the bot](https://top.gg/bot/{self.bot.config['oauth']['client_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({
                    "multiplier":
                    1.2,
                    "text":
                    "Your plant has been alive for longer than a week."
                })

            # Add the actual multiplier values
            for obj in multipliers:
                gained_experience *= obj['multiplier']

            # Update db
            gained_experience = int(gained_experience)
            async with self.bot.database() as db:
                user_experience_row = 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 RETURNING *""",
                    user_id,
                    gained_experience,
                )

        # Send an output
        if user_plant_data['plant_nourishment'] < 0:
            return self.get_water_plant_dict(
                "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 obj in multipliers:
            output_lines.append(f"**{obj['multiplier']}x**: {obj['text']}")
        for t in additional_text:
            output_lines.append(t)

        # And now we output ALL the information that we need for this to be an API route
        return self.get_water_plant_dict(
            text="\n".join(output_lines),
            success=True,
            gained_experience=gained_experience,
            new_nourishment_level=plant_level_row[0]['plant_nourishment'],
            new_user_experience=user_experience_row[0]['user_experience'],
            voted_on_topgg=voted_on_topgg,
            multipliers=multipliers,
        )
Ejemplo n.º 10
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!")
Ejemplo n.º 11
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)
Ejemplo n.º 12
0
    async def useitem(self,
                      ctx: utils.Context,
                      item_name: str,
                      user: typing.Optional[discord.Member],
                      *,
                      args: str = None):
        """
        Use an item that you purchased from the shop.
        """

        # Get the items they own
        try:
            item_data = [
                i for i in self.bot.get_cog("ShopHandler").get_shop_items(
                    ctx.guild).values()
                if item_name.lower().replace(' ', '') == i['name'].lower(
                ).replace(' ', '') or item_name.lower().replace(' ', '') in
                [o.lower().replace(' ', '') for o in i['aliases']]
            ][0]
        except IndexError:
            return await ctx.send("That isn't an item that exists.")
        db = await self.bot.database.get_connection()
        rows = await db(
            "SELECT * FROM user_inventory WHERE guild_id=$1 AND user_id=$2 AND LOWER(item_name)=LOWER($3)",
            ctx.guild.id,
            ctx.author.id,
            item_data['name'],
        )
        if (not rows or rows[0]['amount'] <= 0
            ) and ctx.original_author_id not in self.bot.owner_ids:
            await ctx.send(
                f"You don't have any **{item_data['name']}** items in this server."
            )
            await db.disconnect()
            return

        # Use the item
        user = user or ctx.author
        await ctx.trigger_typing()

        # Paint
        if item_data['name'] == 'Paintbrush':
            async with self.paintbrush_locks[ctx.guild.id]:
                data = await self.use_paintbrush(ctx, args, db=db, user=user)

        # Cooldown tokens
        elif item_data['name'] == 'Cooldown Token':
            data = await self.use_cooldown_token(ctx, db=db, user=user)

        # Cooldown tokens
        elif item_data['name'].startswith('Buyable Role'):
            role_search = re.search(r"<@&(?P<roleid>[0-9]{13,23})",
                                    item_data['description'])
            role_id = role_search.group("roleid")
            self.logger.info(role_id)
            try:
                await ctx.author.add_roles(ctx.guild.get_role(int(role_id)),
                                           reason="Role purchased")
            except Exception as e:
                await ctx.send(f"I couldn't add the role - {e}")
                data = False
            else:
                await ctx.send("Added role.")
                data = True

        # Cooldown tokens
        elif item_data['name'].startswith('Buyable Temporary Role'):
            role_search = re.search(r"<@&(?P<roleid>[0-9]{13,23})",
                                    item_data['description'])
            role_id = role_search.group("roleid")
            self.logger.info(role_id)
            try:
                await ctx.author.add_roles(ctx.guild.get_role(int(role_id)),
                                           reason="Role purchased")
            except Exception as e:
                await ctx.send(f"I couldn't add the role - {e}")
                data = False
            else:
                await ctx.send("Added role.")
                duration_rows = await db(
                    "SELECT * FROM buyable_temporary_roles WHERE guild_id=$1 AND role_id=$2",
                    ctx.guild.id, int(role_id))
                await db(
                    """INSERT INTO temporary_roles (guild_id, role_id, user_id, remove_timestamp, key, dm_user)
                    VALUES ($1, $2, $3, $4, 'Buyable Temp Role', true) ON CONFLICT (guild_id, role_id, user_id) DO UPDATE
                    SET remove_timestamp=excluded.remove_timestamp, key=excluded.key, dm_user=excluded.dm_user""",
                    ctx.guild.id, int(role_id), ctx.author.id,
                    dt.utcnow() +
                    utils.TimeValue(duration_rows[0]['duration']).delta)
                data = True

        # It's nothing else
        else:
            await ctx.send("No use method set in the code for that item.")
            await db.disconnect()
            return

        # Unpack data
        try:
            success, amount = data
        except TypeError:
            success, amount = data, 1

        # Disconnect from the DB if the item failed
        if success is False:
            await db.disconnect()
            return

        # Alter their inventory
        await db(
            "UPDATE user_inventory SET amount=user_inventory.amount - $4 WHERE guild_id=$1 AND user_id=$2 AND item_name=$3",
            ctx.guild.id, ctx.author.id, item_data['name'], amount)
        self.logger.info(
            f"Remove item ({item_data['name']}) from user (G{ctx.guild.id}/U{ctx.author.id})"
        )
        await db.disconnect()
Ejemplo n.º 13
0
    async def water_plant_backend(self,
                                  user_id: int,
                                  plant_name: str,
                                  waterer_id: int = None):
        """
        Run the backend for the plant watering.

        Returns a sexy lil dictionary in format:
            {
                "text": str,
                "success": bool,
                "new_nourishment_level": int,
                "voted_on_topgg": bool,
                "new_user_experience": int,
                "multipliers": [
                    {
                        "multiplier": float,
                        "text": str
                    },
                    ...
                ]
            }
        """

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

        # Get friend watering status
        waterer_id = waterer_id or user_id
        waterer_is_owner = user_id == waterer_id
        they_you = {True: "you", False: "they"}.get(waterer_is_owner)
        their_your = {True: "your", False: "their"}.get(waterer_is_owner)

        # See if they can water this person's plant
        if not waterer_is_owner:
            given_key = await db(
                """SELECT * FROM user_garden_access WHERE garden_owner=$1 AND garden_access=$2""",
                user_id,
                waterer_id,
            )
            if not given_key:
                await db.disconnect()
                return self.get_water_plant_dict(
                    f"You don't have access to <@{user_id}>'s garden!")

        # 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)""",
            user_id,
            plant_name,
        )
        if not plant_level_row:
            await db.disconnect()
            shop_note = (
                "Run the `shop` command to plant some new seeds, or `plants` "
                "to see the list of plants you have already!")
            return self.get_water_plant_dict((
                f"{they_you.capitalize()} don't have a plant with the name **{plant_name}**! "
                f"{shop_note if waterer_is_owner else ''}"))
        plant_data = self.bot.plants[plant_level_row[0]['plant_type']]

        # See if the user running the command is the owner of the plant and give a cooldown period properly
        if waterer_is_owner:
            water_cooldown_period = timedelta(
                **self.bot.config['plants']['water_cooldown'])
        else:
            water_cooldown_period = timedelta(
                **self.bot.config['plants']['guest_water_cooldown'])

        # See if they're allowed to water things
        last_water_time = plant_level_row[0]['last_water_time']
        if (last_water_time + water_cooldown_period
            ) > dt.utcnow() and user_id not in self.bot.owner_ids:
            await db.disconnect()
            timeout = utils.TimeValue(
                ((plant_level_row[0]['last_water_time'] +
                  water_cooldown_period) - dt.utcnow()).total_seconds())
            return self.get_water_plant_dict((
                f"You need to wait another {timeout.clean_spaced} to be able to water "
                f"{their_your} {plant_level_row[0]['plant_type'].replace('_', ' ')}."
            ))

        # 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 *""",
                user_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, notification_sent=FALSE
                WHERE user_id=$1 AND LOWER(plant_name)=LOWER($2) RETURNING *""",
                user_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[dict]
        additional_text = []  # List[str]
        voted_on_topgg = False
        user_is_premium = False

        # See if the user is premium
        try:
            await localutils.checks.has_premium().predicate(
                utils.web.WebContext(self.bot, waterer_id))
            user_is_premium = True
        except commands.CheckFailure:
            pass

        # Disconnect from the database so we don't have hanging connections open while
        # making our Top.gg web request
        await db.disconnect()

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

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

            # See if we want to give them a premium bonus
            if user_is_premium:
                multipliers.append({
                    "multiplier":
                    2.0,
                    "text":
                    f"You're subscribed to Flower Premium! :D",
                })

            # See if we want to give them a 30 second water-time bonus
            if dt.utcnow(
            ) - last_water_time - water_cooldown_period <= timedelta(
                    seconds=30):
                multipliers.append({
                    "multiplier":
                    1.5,
                    "text":
                    f"You watered within 30 seconds of {their_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({
                    "multiplier":
                    1.05,
                    "text":
                    f"You watered a plant that {they_you} got from a trade.",
                })

            # See if we want to give them the voter bonus
            user_voted_api_request = False
            try:
                user_voted_api_request = await asyncio.wait_for(
                    self.get_user_voted(waterer_id), timeout=2.0)
            except asyncio.TimeoutError:
                pass
            if self.bot.config.get(
                    'bot_listing_api_keys',
                {}).get('topgg_token') and user_voted_api_request:
                bot_client_id = self.bot.config['oauth']['client_id']
                multipliers.append({
                    "multiplier":
                    1.1,
                    "text":
                    f"You [voted for the bot](https://top.gg/bot/{bot_client_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({
                    "multiplier":
                    1.2,
                    "text":
                    f"{their_your.title()} plant has been alive for longer than a week.",
                })

            # See if we want to give them the plant longevity bonus
            if user_plant_data['immortal']:
                multipliers.append({
                    "multiplier": 0.5,
                    "text": f"{their_your} plant is immortal.",
                })

            # Add the actual multiplier values
            for obj in multipliers:
                total_experience *= obj['multiplier']

            # Update db
            total_experience = int(total_experience)
            async with self.bot.database() as db:

                # Give exp to everyone we care about
                if waterer_is_owner:
                    gained_experience = total_experience
                    user_experience_row = 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 RETURNING *""",
                        user_id,
                        gained_experience,
                    )
                else:
                    gained_experience = int(total_experience * 0.8)
                    owner_gained_experience = int(total_experience -
                                                  gained_experience)
                    await db.start_transaction()
                    user_experience_row = 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 RETURNING *""",
                        waterer_id,
                        gained_experience,
                    )
                    owner_experience_row = 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 RETURNING *""",
                        user_id,
                        owner_gained_experience,
                    )
                    await db.commit_transaction()

                # Update the user achievements
                await db(
                    """INSERT INTO plant_achievement_counts (user_id, plant_type, max_plant_nourishment) VALUES ($1, $2, $3)
                    ON CONFLICT (user_id, plant_type) DO UPDATE SET
                    max_plant_nourishment=GREATEST(plant_achievement_counts.max_plant_nourishment,
                    excluded.max_plant_nourishment)""", user_id,
                    user_plant_data['plant_type'],
                    user_plant_data['plant_nourishment'])

        # Send an output
        if user_plant_data['plant_nourishment'] < 0:
            return self.get_water_plant_dict(
                f"You sadly pour water into the dry soil of {their_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 {their_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 obj in multipliers:
            output_lines.append(f"**{obj['multiplier']}x**: {obj['text']}")
        for t in additional_text:
            output_lines.append(t)

        # And now we output ALL the information that we need for this to be an API route
        return self.get_water_plant_dict(
            text="\n".join(output_lines),
            success=True,
            gained_experience=gained_experience,
            new_nourishment_level=plant_level_row[0]['plant_nourishment'],
            new_user_experience=user_experience_row[0]['user_experience'],
            voted_on_topgg=voted_on_topgg,
            multipliers=multipliers,
        )
Ejemplo n.º 14
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)
Ejemplo n.º 15
0
    async def shop(self, ctx: utils.Context):
        """
        Shows you the available plants.
        """

        # Get data from the user and set up our variables to be used later
        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']
            user_plant_limit = user_rows[0]['plant_limit']
            last_plant_shop_time = user_rows[0]['last_plant_shop_time'] or dt(
                2000, 1, 1)
            plant_pot_hue = user_rows[0]['plant_pot_hue'] or ctx.author.id % 360
        else:
            user_experience = 0
            user_plant_limit = 1
            last_plant_shop_time = dt(2000, 1, 1)
            plant_pot_hue = ctx.author.id % 360

        # Work out if the user's cooldown is expired for purchasing new plants
        water_cooldown = timedelta(
            **self.bot.config['plants']['water_cooldown'])
        can_purchase_new_plants = dt.utcnow(
        ) > last_plant_shop_time + water_cooldown
        can_purchase_new_plants = can_purchase_new_plants or ctx.author.id in self.bot.owner_ids
        buy_plant_cooldown = None
        if can_purchase_new_plants is False:
            buy_plant_cooldown = utils.TimeValue(
                ((last_plant_shop_time + water_cooldown) -
                 dt.utcnow()).total_seconds())

        # Set up our initial embed
        all_plants = []  # Used to make sure we can continue the command
        all_items = []  # Used to make sure we can continue the command
        embed = utils.Embed(use_random_colour=True, description="")
        self.bot.set_footer_from_config(embed)

        # Make the initial embed
        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 "
            f"{user_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.clean}**.\n"

        # Add plants to the embed
        for plant in sorted(available_plants.values()):
            text = plant.display_name.capitalize()
            disabled = not all([
                can_purchase_new_plants,
                plant.required_experience <= user_experience,
                len(plant_level_rows) < user_plant_limit,
            ])
            all_plants.append({
                "label": text,
                "custom_id": plant.name,
                "disabled": disabled
            })

        # 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())
        embed.description += f"Your plants will change in {remaining_time.clean_spaced}.\n"

        # See if the user has premium
        user_has_premium = False
        try:
            await localutils.checks.has_premium().predicate(ctx)
            user_has_premium = True
        except commands.CommandError:
            pass

        # See how many pots the user is allowed
        bot_plant_cap = self.bot.config['plants']['hard_plant_cap']
        non_subscriber_plant_cap = self.bot.config['plants'][
            'non_subscriber_plant_cap']
        user_plant_cap = bot_plant_cap if user_has_premium else non_subscriber_plant_cap

        # Add pots
        text = f"Pot ({self.get_points_for_plant_pot(user_plant_limit):,} exp)"
        if user_experience >= self.get_points_for_plant_pot(
                user_plant_limit) and user_plant_limit < user_plant_cap:
            all_items.append({
                "label": text,
                "custom_id": "pot",
                "disabled": False
            })
        elif user_plant_limit >= bot_plant_cap:
            all_items.append({
                "label": "Pot (maximum pots reached)",
                "custom_id": "pot",
                "disabled": True,
                "style": utils.ButtonStyle.SECONDARY
            })
        elif user_plant_limit >= user_plant_cap:
            all_items.append({
                "label": "Pot (maximum free pots reached)",
                "custom_id": "pot_free",
                "disabled": False,
                "style": utils.ButtonStyle.SECONDARY
            })
        else:
            all_items.append({
                "label": text,
                "custom_id": "pot",
                "disabled": True,
                "style": utils.ButtonStyle.SECONDARY
            })

        # Add variable items
        for item in self.bot.items.values():
            text = f"{item.display_name.capitalize()} ({item.price:,} exp)"
            if user_experience >= item.price:
                all_items.append({
                    "label": text,
                    "custom_id": item.name,
                    "disabled": False,
                    "style": utils.ButtonStyle.SECONDARY
                })
            else:
                all_items.append({
                    "label": text,
                    "custom_id": item.name,
                    "disabled": True,
                    "style": utils.ButtonStyle.SECONDARY
                })

        # Add all our items to the embed
        components = utils.MessageComponents(
            utils.ActionRow(*[utils.Button(**i) for i in all_plants]),
            utils.ActionRow(*[utils.Button(**i) for i in all_items]),
        )

        # Cancel if they don't have anything available
        if not [i for i in all_plants + all_items if not i['disabled']]:
            embed.description += "\n**There is currently nothing available which you can purchase.**\n"
            return await ctx.send(ctx.author.mention,
                                  embed=embed,
                                  components=components)
        components.components.append(
            utils.ActionRow(
                utils.Button("Cancel",
                             "cancel",
                             style=utils.ButtonStyle.DANGER)))

        # Wait for them to respond
        shop_menu_message = await ctx.send(
            ctx.author.mention,
            embed=embed,
            components=components,
        )
        try:
            done, pending = await asyncio.wait(
                [
                    self.bot.wait_for(
                        "raw_message_delete",
                        check=lambda m: m.message_id == shop_menu_message.id,
                    ),
                    self.bot.wait_for(
                        "component_interaction",
                        check=lambda p: p.message.id == shop_menu_message.id
                        and ctx.author.id == p.user.id,
                    ),
                ],
                timeout=120,
                return_when=asyncio.FIRST_COMPLETED)
        except asyncio.TimeoutError:
            await shop_menu_message.edit(components=None)
            return await ctx.send(
                f"Timed out asking for plant type {ctx.author.mention}.")

        # 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
        payload = done
        given_response = payload.component.custom_id.lower(
        )  # .replace(' ', '_')
        await payload.ack()

        # See if they want to cancel
        if given_response == "cancel":
            return await payload.message.delete()
        await payload.message.edit(components=components.disable_components())

        # See if they want a pot but they're not subbed
        if given_response == "pot_free":
            return await payload.send((
                f"You're already at the maximum amount of pots you can have without "
                f"subscribing - see `{ctx.clean_prefix}donate` for more information."
            ))

        # See if they want a plant pot
        if given_response == "pot":
            if user_plant_limit >= self.bot.config['plants']['hard_plant_cap']:
                return await payload.send(
                    f"You're already at the maximum amount of pots, {ctx.author.mention}! :c"
                )
            if user_experience >= self.get_points_for_plant_pot(
                    user_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(user_plant_limit))
                return await payload.send(
                    f"Given you another plant pot, {ctx.author.mention}!")
            else:
                return await payload.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.replace(' ', '_'))
        if item_type is None:
            try:
                item_type = [
                    i for i in self.bot.items.values()
                    if i.display_name == given_response
                ][0]
            except IndexError:
                item_type = None
        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 payload.send((
                    f"Given you a **{item_type.display_name}**, {ctx.author.mention}! You can use it "
                    f"with `{item_type.usage.format(ctx=ctx)}`."))
            else:
                return await payload.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.replace(' ', '_')]
        except KeyError:
            return await payload.send(
                f"`{given_response}` 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 payload.send(
                f"You can't purchase new plants for another **{buy_plant_cooldown.clean}**.",
            )
        if plant_type not in available_plants.values():
            return await payload.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 payload.send((
                f"You don't have the required experience to get a **{plant_type.display_name}**, {ctx.author.mention} "
                f"(it requires {plant_type.required_experience}, you have {user_experience}) :c"
            ))
        if len(plant_level_rows) >= user_plant_limit:
            return await payload.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 payload.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 payload.send(
                    f"Timed out asking for plant name {ctx.author.mention}.")
            plant_name = localutils.PlantType.validate_name(
                plant_name_message.content)
            if len(plant_name) > 50 or len(plant_name) == 0:
                await plant_name_message.reply(
                    "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 plant_name_message.reply(
                    (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.none(),
                )
            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, $2, $3, 0, $4, $1, TIMEZONE('UTC', NOW()), $5) 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),
                plant_pot_hue,
            )
            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 db(
                """INSERT INTO plant_achievement_counts (user_id, plant_type, plant_count) VALUES ($1, $2, 1)
                ON CONFLICT (user_id, plant_type) DO UPDATE SET
                plant_count=plant_achievement_counts.plant_count+excluded.plant_count""",
                ctx.author.id,
                plant_type.name,
            )
        await plant_name_message.reply(
            f"Planted your **{plant_type.display_name}** seeds!")