예제 #1
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",
                                 user.id)

        # See if they have anything available
        plant_names = sorted([(i['plant_name'], i['plant_type'],
                               i['plant_nourishment']) for i in user_rows])
        if not plant_names:
            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")
        for i in plant_names:
            if i[2] >= 0:
                embed.add_field(
                    i[0],
                    f"{i[1].replace('_', ' ')}, nourishment level {i[2]}/{self.bot.plants[i[1]].max_nourishment_level}"
                )
            else:
                embed.add_field(i[0], f"{i[1].replace('_', ' ')}, dead :c")

        # Return to user
        return await ctx.send(embed=embed)
예제 #2
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:
            with utils.Embed(use_random_colour=True) as embed:
                plants = sorted(self.bot.plants.values(),
                                key=lambda x: (x.plant_level, x.name))
                embed.description = '\n'.join([
                    f"**{i.display_name.capitalize()}** - level {i.plant_level}"
                    for i in plants
                ])
            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[plant.artist].copy()
        discord_id = artist_info.pop('discord', None)
        if discord_id:
            description_list.append(f"**Artist `{plant.artist}`**")
            description_list.append(
                f"Discord: <@{discord_id}> (`{discord_id}`)")
        else:
            description_list.append(f"**Artist `{plant.artist}`**")
        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")
        display_cog = self.bot.get_cog("PlantDisplayCommands")
        plant_image_bytes = display_cog.image_to_bytes(
            display_cog.get_plant_image(plant.name, 0, 21, "clay",
                                        random.randint(0, 360)))
        await ctx.send(embed=embed,
                       file=discord.File(plant_image_bytes,
                                         filename="plant.png"))
예제 #3
0
    async def giveaway(self, ctx: utils.Context, duration: utils.TimeValue,
                       winner_count: int, *, giveaway_name: str):
        """Start a giveaway"""

        # Validate winner count
        if winner_count < 1:
            return await ctx.send(
                "You need to give a winner count larger than 0.")
        if winner_count > 200:
            return await ctx.send("You can't give more than 200 winners.")

        # Create the embed
        with utils.Embed(colour=0x00ff00) as embed:
            embed.title = giveaway_name
            embed.description = "Click the reaction below to enter!"
            embed.set_footer(text=f"{winner_count} winners | Ends at")
            embed.timestamp = dt.utcnow() + duration.delta
        giveaway_message = await ctx.send(embed=embed)

        # Add to database
        async with self.bot.database() as db:
            await db(
                """INSERT INTO giveaways (channel_id, message_id, winner_count, ending_time, description)
                VALUES ($1, $2, $3, $4, $5)""",
                giveaway_message.channel.id,
                giveaway_message.id,
                winner_count,
                dt.utcnow() + duration.delta,
                giveaway_name,
            )

        # Add reaction
        await giveaway_message.add_reaction("\N{PARTY POPPER}")
예제 #4
0
    async def stats(self, ctx: utils.Context):
        """Gives you the stats for the bot"""

        # Get creator info
        creator_id = self.bot.config["owners"][0]
        creator = self.bot.get_user(creator_id) or await self.bot.fetch_user(
            creator_id)

        # Make embed
        with utils.Embed(colour=0x1e90ff) as embed:
            embed.set_footer(str(self.bot.user),
                             icon_url=self.bot.user.avatar_url)
            embed.add_field("Creator", f"{creator!s}\n{creator_id}")
            embed.add_field("Library", f"Discord.py {discord.__version__}")
            if self.bot.shard_count != len(self.bot.shard_ids):
                embed.add_field(
                    "Average Guild Count",
                    int((len(self.bot.guilds) / len(self.bot.shard_ids)) *
                        self.bot.shard_count))
            else:
                embed.add_field("Guild Count", len(self.bot.guilds))
            embed.add_field("Shard Count", self.bot.shard_count)
            embed.add_field("Average WS Latency",
                            f"{(self.bot.latency * 1000):.2f}ms")
            embed.add_field(
                "Coroutines",
                f"{len([i for i in asyncio.Task.all_tasks() if not i.done()])} running, {len(asyncio.Task.all_tasks())} total."
            )

        # Send it out wew let's go
        await ctx.send(embed=embed)
예제 #5
0
파일: big_ben.py 프로젝트: fadedmax/BigBen
    async def bongdist(self, ctx: utils.Context, user: discord.Member = None):
        """Gives you the bong leaderboard"""

        user = user or ctx.author
        async with self.bot.database() as db:
            rows = await db(
                "SELECT timestamp - message_timestamp AS reaction_time FROM bong_log WHERE user_id=$1 AND guild_id=$2",
                user.id, ctx.guild.id)
        if not rows:
            return await ctx.send(
                f"{user.mention} has reacted to the bong message yet on your server."
            )

        # Build our output graph
        fig = plt.figure()
        ax = fig.subplots()
        bplot = ax.boxplot([i['reaction_time'].total_seconds() for i in rows],
                           patch_artist=True)
        ax.axis([0, 2, 0, 10])

        for i in bplot['boxes']:
            i.set_facecolor('lightblue')

        # Fix axies
        # ax.axis('off')
        ax.grid(True)

        # Tighten border
        fig.tight_layout()

        # Output to user baybeeee
        fig.savefig('activity.png', bbox_inches='tight', pad_inches=0)
        with utils.Embed() as embed:
            embed.set_image(url="attachment://activity.png")
        await ctx.send(embed=embed, file=discord.File("activity.png"))
예제 #6
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))
            user_rows = await db(
                "SELECT * FROM user_settings WHERE user_id=$1", user.id)

        # Filter into variables
        if plant_rows and user_rows:
            display_data = self.get_display_data(plant_rows[0], user_rows[0])
        elif plant_rows:
            display_data = self.get_display_data(plant_rows[0], None, user.id)
        elif user_rows:
            display_data = self.get_display_data(None, user_rows[0])
        else:
            display_data = self.get_display_data(None, None, 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 = self.image_to_bytes(self.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")
        await ctx.send(embed=embed, file=file)
예제 #7
0
    async def update_shop_message(self, guild: discord.Guild):
        """Edit the shop message to to be pretty good"""

        # Get the shop message
        self.logger.info(f"Shop message update dispatched (G{guild.id})")
        try:
            shop_channel = self.bot.get_channel(
                self.bot.guild_settings[guild.id]['shop_channel_id'])
            shop_message = await shop_channel.fetch_message(
                self.bot.guild_settings[guild.id]['shop_message_id'])
            if shop_message is None:
                self.logger.info(
                    f"Can't update shop message - no message found (G{guild.id})"
                )
                raise AttributeError()
        except (discord.NotFound, AttributeError):
            self.logger.info(
                f"Can't update shop message - no message/channel found (G{guild.id})"
            )
            return

        # Generate embed
        coin_emoji = self.bot.guild_settings[guild.id].get("coin_emoji",
                                                           None) or "coins"
        emojis = []
        with utils.Embed() as embed:
            for emoji, data in self.get_shop_items(guild).items():
                item_price = self.bot.guild_settings[guild.id].get(
                    data['price_key'], data['amount'])
                if item_price <= 0:
                    continue
                embed.add_field(
                    f"{data['name']} ({emoji}) - {item_price} {coin_emoji}",
                    data['description'],
                    inline=False)
                emojis.append(emoji)

        # See if we need to edit the message
        try:
            current_embed = shop_message.embeds[0]
        except IndexError:
            current_embed = None
        if embed == current_embed:
            self.logger.info(
                f"Not updating shop message - no changes presented (G{guild.id})"
            )
            return

        # Edit message
        self.logger.info(f"Updating shop message (G{guild.id})")
        await shop_message.edit(content=None, embed=embed)

        # Add reactions
        await shop_message.clear_reactions()
        for e in emojis:
            await shop_message.add_reaction(e)
예제 #8
0
    async def cat(self, ctx:utils.Context):
        """Gives you some cats innit"""

        await ctx.channel.trigger_typing()
        headers = {"User-Agent": "MarriageBot/1.0.0 - Discord@Caleb#2831"}
        async with self.bot.session.get("https://api.thecatapi.com/v1/images/search", headers=headers) as r:
            data = await r.json()
        with utils.Embed(use_random_colour=True) as embed:
            embed.set_image(url=data[0]['url'])
        await ctx.send(embed=embed)
예제 #9
0
    async def coins(self, ctx: utils.Context, user: discord.Member = None):
        """Gives you the content of your inventory"""

        # Make sure there's money set up
        if self.bot.guild_settings[ctx.guild.id].get(
                'shop_message_id') is None:
            return await ctx.send(
                "There's no shop set up for this server, so there's no point in you getting money. Sorry :/"
            )

        # Grab the user
        user = user or ctx.author
        if user.id == ctx.guild.me.id:
            return await ctx.send("Obviously, I'm rich beyond belief.")
        if user.bot:
            return await ctx.send(
                "Robots have no need for money as far as I'm aware.")

        # Get the data
        async with self.bot.database() as db:
            coin_rows = await db(
                "SELECT * FROM user_money WHERE guild_id=$1 AND user_id=$2",
                user.guild.id, user.id)
            if not coin_rows:
                coin_rows = [{'amount': 0}]
            inv_rows = await db(
                "SELECT * FROM user_inventory WHERE guild_id=$1 AND user_id=$2",
                user.guild.id, user.id)

        # Throw it into an embed
        coin_emoji = self.bot.guild_settings[ctx.guild.id].get(
            "coin_emoji", None) or "coins"
        with utils.Embed(use_random_colour=True) as embed:
            embed.set_author_to_user(user)
            inventory_string_rows = []
            for row in inv_rows:
                if row['amount'] == 0:
                    continue
                try:
                    item_data = [
                        i for i in self.bot.get_cog(
                            "ShopHandler").get_shop_items(ctx.guild).values()
                        if i['name'] == row['item_name']
                    ][0]
                except IndexError:
                    continue
                inventory_string_rows.append(
                    f"{row['amount']}x {item_data['emoji'] or item_data['name']}"
                )
            embed.description = f"**{coin_rows[0]['amount']:,} {coin_emoji}**\n" + "\n".join(
                inventory_string_rows)
        return await ctx.send(embed=embed)
예제 #10
0
    def format_page(self, menu: menus.Menu, entries: list) -> utils.Embed:
        """Format the infraction entries into an embed"""

        with utils.Embed(use_random_colour=True) as embed:
            for row in entries:
                # TODO add timestamp
                embed.add_field(
                    f"{row['infraction_type']} - {row['infraction_id']}",
                    f"<@{row['moderator_id']}> :: {row['infraction_reason']}",
                    inline=False)
            embed.set_footer(
                f"Page {menu.current_page + 1}/{self.get_max_pages()}")
        return embed
예제 #11
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="")

        # 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)
예제 #12
0
    async def on_message_edit(self, before: discord.Message,
                              after: discord.Message):
        """Logs edited messages"""

        # Filter
        if after.guild is None:
            return
        if before.content == after.content:
            return
        if not before.content or not after.content:
            return
        if after.author.bot:
            return

        # Create embed
        with utils.Embed(colour=0x0000ff) as embed:
            embed.set_author_to_user(user=after.author)
            embed.description = f"[Message edited]({after.jump_url}) in {after.channel.mention}"
            before_content = before.content
            if len(before.content) > 1000:
                before_content = before.content[:1000] + '...'
            after_content = after.content
            if len(after.content) > 1000:
                after_content = after.content[:1000] + '...'
            embed.add_field(name="Old Message",
                            value=before_content,
                            inline=False)
            embed.add_field(name="New Message",
                            value=after_content,
                            inline=False)
            embed.set_footer(f"User ID {after.author.id}")
            embed.timestamp = after.edited_at
            if after.attachments:
                embed.add_field("Attachments",
                                '\n'.join([i.url for i in after.attachments]))

        # Get channel
        channel_id = self.bot.guild_settings[after.guild.id].get(
            "edited_message_modlog_channel_id")
        channel = self.bot.get_channel(channel_id)
        if channel is None:
            return

        # Send log
        try:
            m = await channel.send(embed=embed)
            self.logger.info(
                f"Logging edited message (G{m.guild.id}/C{m.channel.id})")
        except discord.Forbidden:
            pass
예제 #13
0
    async def fakesona(self, ctx: utils.Context):
        """Grabs you a fake sona from ThisFursonaDoesNotExist.com"""

        seed = random.randint(1, 99999)
        with utils.Embed(use_random_colour=True) as embed:
            embed.set_author(
                name="Click here to it larger",
                url=
                f"https://thisfursonadoesnotexist.com/v2/jpgs-2x/seed{seed:0>5}.jpg"
            )
            embed.set_image(
                f"https://thisfursonadoesnotexist.com/v2/jpgs/seed{seed:0>5}.jpg"
            )
            embed.set_footer(text="Provided by ThisFursonaDoesNotExist.com")
        await ctx.send(embed=embed)
예제 #14
0
    async def interactions(self,
                           ctx: utils.Context,
                           user: discord.Member = None):
        """Shows you your interaction statistics"""

        if ctx.invoked_subcommand is not None:
            return

        # Get the interaction numbers
        user = user or ctx.author
        async with self.bot.database() as db:
            valid_interaction_rows = await db(
                "SELECT DISTINCT interaction_name FROM interaction_text WHERE guild_id=ANY($1::BIGINT[])",
                [0, ctx.guild.id])
            given_rows = await db(
                "SELECT interaction, SUM(amount) AS amount FROM interaction_counter WHERE user_id=$1 AND guild_id=$2 GROUP BY guild_id, user_id, interaction",
                user.id, ctx.guild.id)
            received_rows = await db(
                "SELECT interaction, SUM(amount) AS amount FROM interaction_counter WHERE target_id=$1 AND guild_id=$2 GROUP BY guild_id, target_id, interaction",
                user.id, ctx.guild.id)
        valid_interactions = sorted(
            [i['interaction_name'] for i in valid_interaction_rows])

        # Sort them into useful dicts
        given_interactions = collections.defaultdict(int)
        for row in given_rows:
            given_interactions[row['interaction']] = row['amount']
        received_interactions = collections.defaultdict(int)
        for row in received_rows:
            received_interactions[row['interaction']] = row['amount']

        # And now into an embed
        with utils.Embed(use_random_colour=True) as embed:
            embed.set_author_to_user(user=user)
            for i in valid_interactions:
                given = given_interactions[i]
                received = received_interactions[i]
                if given > 0 and received > 0:
                    fraction = fractions.Fraction(given, received)
                    embed.add_field(
                        i.title(),
                        f"Given {given}, received {received} ({fraction.numerator:.0f}:{fraction.denominator:.0f})"
                    )
                else:
                    embed.add_field(i.title(),
                                    f"Given {given}, received {received}")
        await ctx.send(embed=embed)
예제 #15
0
    async def stats(self, ctx: utils.Context):
        """Gives you the stats for the bot"""

        # Get creator info
        creator_id = self.bot.config["owners"][0]
        creator = self.bot.get_user(creator_id) or await self.bot.fetch_user(
            creator_id)

        # Make embed
        embed = utils.Embed(colour=0x1e90ff)
        embed.set_footer(str(self.bot.user), icon_url=self.bot.user.avatar_url)
        embed.add_field("Creator", f"{creator!s}\n{creator_id}")
        embed.add_field("Library", f"Discord.py {discord.__version__}")
        if self.bot.shard_count != len(self.bot.shard_ids):
            embed.add_field(
                "Approximate Guild Count",
                int((len(self.bot.guilds) / len(self.bot.shard_ids)) *
                    self.bot.shard_count))
        else:
            embed.add_field("Guild Count", len(self.bot.guilds))
        embed.add_field("Shard Count", self.bot.shard_count)
        embed.add_field("Average WS Latency",
                        f"{(self.bot.latency * 1000):.2f}ms")
        embed.add_field(
            "Coroutines",
            f"{len([i for i in asyncio.Task.all_tasks() if not i.done()])} running, {len(asyncio.Task.all_tasks())} total."
        )
        if self.bot.config.get("topgg_token"):
            params = {"fields": "points,monthlyPoints"}
            headers = {"Authorization": self.bot.config['topgg_token']}
            async with self.bot.session.get(
                    f"https://top.gg/api/bots/{self.bot.user.id}",
                    params=params,
                    headers=headers) as r:
                try:
                    data = await r.json()
                except Exception:
                    data = {}
            if "points" in data and "monthlyPoints" in data:
                embed.add_field(
                    "Top.gg Points",
                    f"{data['points']} ({data['monthlyPoints']} this month)")

        # Send it out wew let's go
        await ctx.send(embed=embed)
예제 #16
0
    async def on_moderation_action(self, moderator: discord.Member,
                                   user: discord.User, reason: str,
                                   action: str):
        """Looks for moderator actions being done and logs them into the relevant channel"""

        # Save to database
        db_reason = None if reason == '<No reason provided>' else reason
        async with self.bot.database() as db:
            code = await self.get_unused_infraction_id(db)
            await db(
                """INSERT INTO infractions (infraction_id, guild_id, user_id, moderator_id, infraction_type,
                infraction_reason, timestamp) VALUES ($1, $2, $3, $4, $5, $6, $7)""",
                code,
                moderator.guild.id,
                user.id,
                moderator.id,
                action,
                db_reason,
                dt.utcnow(),
            )

        # Get log channel
        log_channel_id = self.bot.guild_settings[moderator.guild.id].get(
            f"{action.lower()}_modlog_channel_id", None)
        log_channel = self.bot.get_channel(log_channel_id)
        if log_channel is None:
            return

        # Make info embed
        with utils.Embed() as embed:
            embed.title = action
            embed.add_field("Moderator",
                            f"{moderator.mention} (`{moderator.id}`)")
            embed.add_field("User", f"<@{user.id}> (`{user.id}`)")
            if reason:
                embed.add_field("Reason", reason, inline=False)

        # Send to channel
        try:
            await log_channel.send(embed=embed)
        except discord.Forbidden:
            pass
예제 #17
0
파일: vc_logs.py 프로젝트: schlopp/Honey
    async def on_voice_state_update(self, member: discord.Member,
                                    before: discord.VoiceState,
                                    after: discord.VoiceState):
        """Log users joining or leaving VCs"""

        if before.channel == after.channel:
            return
        update_log_id = self.bot.guild_settings[
            member.guild.id]['voice_update_modlog_channel_id']
        if not update_log_id:
            return
        channel = self.bot.get_channel(update_log_id)
        if not channel:
            return
        try:
            if before.channel is None:
                text = f"{member.mention} joined the **{after.channel.name}** VC."
            elif after.channel is None:
                text = f"{member.mention} left the **{before.channel.name}** VC."
            else:
                text = f"{member.mention} moved from the **{before.channel.name}** VC to the **{after.channel.name}** VC."
            if channel.permissions_for(channel.guild.me).embed_links:
                data = {
                    "embed": utils.Embed(use_random_colour=True,
                                         description=text)
                }
            else:
                data = {
                    "content": text,
                    "allowed_mentions": discord.AllowedMentions(users=False)
                }
            await channel.send(**data)
            self.logger.info(
                f"Logging updated VC user (G{member.guild.id}/U{member.id})")
        except discord.Forbidden:
            pass
예제 #18
0
파일: big_ben.py 프로젝트: fadedmax/BigBen
 def format_page(self, menu, entries):
     with utils.Embed(use_random_colour=True) as embed:
         embed.description = '\n'.join(entries)
     return embed
예제 #19
0
    async def shop(self, ctx: utils.Context):
        """Shows you the available plants"""

        # Get the experience
        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']
        else:
            user_experience = 0
            plant_limit = 1
        available_item_count = 0  # Used to make sure we can continue the command
        embed = utils.Embed(use_random_colour=True, description="")

        # See what we wanna get to doing
        embed.description += f"What would you like to spend your experience to buy, {ctx.author.mention}? 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 plants to the embed
        plant_text = []
        for plant in sorted(available_plants.values()):
            if plant.required_experience <= user_experience and len(
                    plant_level_rows) < plant_limit:
                plant_text.append(
                    f"{plant.display_name.capitalize()} - `{plant.required_experience} exp`"
                )
                available_item_count += 1
            else:
                plant_text.append(
                    f"~~{plant.display_name.capitalize()} - `{plant.required_experience} exp`~~"
                )
        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)

        # Add items to the embed
        item_text = []
        if user_experience >= self.get_points_for_plant_pot(
                plant_limit) and plant_limit < self.HARD_PLANT_CAP:
            item_text.append(
                f"Pot - `{self.get_points_for_plant_pot(plant_limit)} exp`")
            available_item_count += 1
        else:
            item_text.append(
                f"~~Pot - `{self.get_points_for_plant_pot(plant_limit)} exp`~~"
            )
        for item in self.bot.items.values():
            if user_experience >= item.price:
                item_text.append(
                    f"{item.display_name.capitalize()} - `{item.price} exp`")
                available_item_count += 1
            else:
                item_text.append(
                    f"~~{item.display_name.capitalize()} - `{item.price} exp`~~"
                )
        embed.add_field("Available Items", '\n'.join(item_text), inline=True)

        # See if we should cancel
        if available_item_count == 0:
            embed.description += "\n**There is currently nothing available which you can purchase.**\n"
            return await ctx.send(embed=embed)

        # Wait for them to respond
        await ctx.send(embed=embed)
        try:
            plant_type_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 type {ctx.author.mention}.")
        given_response = plant_type_message.content.lower().replace(' ', '_')

        # See if they want a plant pot
        if given_response == "pot":
            if plant_limit >= self.HARD_PLANT_CAP:
                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 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!")
            elif '\n' in plant_name:
                await ctx.send(
                    "You can't have names with multiple lines in them! Please give another one instead!"
                )
            else:
                break

        # Save that 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)
                VALUES ($1, $2, $3, 0, $4) 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 WHERE user_id=$1",
                ctx.author.id,
                plant_type.required_experience,
            )
        await ctx.send(f"Planted your **{plant_type.display_name}** seeds!")
예제 #20
0
    async def make_graph(self, ctx, users:typing.List[int], window_days:int, *, colours:dict=None, segments:int=None):
        """Makes the actual graph for the thing innit mate"""

        # Make sure there's people
        if not users:
            return await ctx.send("You can't make a graph of 0 users.")
        if len(users) > 10:
            return await ctx.send("There's more than 10 people in that graph - it would take too long for me to generate.")

        # Pick up colours
        if colours is None:
            colours = {}

        # This takes a lil bit so let's gooooooo
        await ctx.channel.trigger_typing()

        # Set up our most used vars
        original = window_days
        truncation = None
        if window_days > 365:
            window_days = 365
            truncation = f"shortened from your original request of {original} days for going over the 365 day max"
        if window_days > (dt.utcnow() - ctx.guild.me.joined_at).days:
            window_days = (dt.utcnow() - ctx.guild.me.joined_at).days
            truncation = f"shortened from your original request of {original} days as I haven't been in the guild that long"

        # Make sure there's actually a day
        if window_days == 0:
            window_days = 1

        # Go through each day and work out how many points it has
        points_per_week_base = [0 for _ in range(window_days)]  # A list of the amount of points the user have in each given day (index)
        points_per_week = collections.defaultdict(points_per_week_base.copy)
        async with self.bot.database() as db:
            for user_id in users:
                message_rows = await db(
                    """SELECT COUNT(timestamp) AS count, generate_series
                    FROM user_messages, generate_series(1, $3)
                    WHERE
                        user_id=$1 AND guild_id=$2
                        AND timestamp > TIMEZONE('UTC', NOW()) - CAST(CONCAT($3, ' days') AS INTERVAL) + (INTERVAL '1 day' * generate_series) - INTERVAL '7 days'
                        AND timestamp <= TIMEZONE('UTC', NOW()) - CAST(CONCAT($3, ' days') AS INTERVAL) + (INTERVAL '1 day' * generate_series)
                    GROUP BY generate_series ORDER BY generate_series ASC""",
                    user_id, ctx.guild.id, window_days,
                )
                for row in message_rows:
                    points_per_week[user_id][row['generate_series'] - 1] += row['count']
                vc_rows = await db(
                    """SELECT COUNT(timestamp) AS count, generate_series
                    FROM user_vc_activity, generate_series(1, $3)
                    WHERE
                        user_id=$1 AND guild_id=$2
                        AND timestamp > TIMEZONE('UTC', NOW()) - CAST(CONCAT($3, ' days') AS INTERVAL) + (INTERVAL '1 day' * generate_series) - INTERVAL '7 days'
                        AND timestamp <= TIMEZONE('UTC', NOW()) - CAST(CONCAT($3, ' days') AS INTERVAL) + (INTERVAL '1 day' * generate_series)
                    GROUP BY generate_series ORDER BY generate_series ASC""",
                    user_id, ctx.guild.id, window_days,
                )
                for row in vc_rows:
                    points_per_week[user_id][row['generate_series'] - 1] += row['count'] // 5
                self.logger.info(points_per_week[user_id])

        # Don't bother uploading if they've not got any data
        if sum([sum(user_points) for user_points in points_per_week.values()]) == 0:
            return await ctx.send("They've not sent any messages that I can graph.")

        # Get roles
        role_data = self.bot.guild_settings[ctx.guild.id]['role_gain']
        role_object_data = sorted([(threshold, ctx.guild.get_role(role_id)) for role_id, threshold in role_data.items() if ctx.guild.get_role(role_id)], key=lambda x: x[0])

        # Build our output graph
        fig = plt.figure()
        ax = fig.subplots()

        # Plot data
        for user, i in points_per_week.items():
            if user in colours:
                colour = colours.get(user)
            else:
                colour = format(hex(random.randint(0, 0xffffff))[2:], "0>6")
            rgb_colour = tuple(int(colour[i:i + 2], 16) / 255 for i in (0, 2, 4))
            ax.plot(list(range(window_days)), i, 'k-', label=str(self.bot.get_user(user)) or user, color=rgb_colour)
        fig.legend(loc="upper left")

        # Set size
        MINOR_AXIS_STOP = 50
        if role_object_data:
            graph_height = max([role_object_data[-1][0] + MINOR_AXIS_STOP, math.ceil((max([max(i) for i in points_per_week.values()]) + 1) / MINOR_AXIS_STOP) * MINOR_AXIS_STOP])
        else:
            graph_height = math.ceil((max([max(i) for i in points_per_week.values()]) + 1) / MINOR_AXIS_STOP) * MINOR_AXIS_STOP
        ax.axis([0, window_days, 0, graph_height])

        # Fix axies
        ax.axis('off')
        ax.grid(True)

        # Add background colour
        for zorder, tier in zip(range(-100, -100 + (len(role_object_data) * 2), 2), role_object_data):
            plt.axhspan(tier[0], graph_height, facecolor=f"#{tier[1].colour.value or 0xffffff:0>6X}", zorder=zorder)
            plt.axhspan(tier[0], tier[0] + 1, facecolor="#000000", zorder=zorder + 1)

        # Tighten border
        fig.tight_layout()

        # Output to user baybeeee
        fig.savefig('activity.png', bbox_inches='tight', pad_inches=0)
        with utils.Embed() as embed:
            embed.set_image(url="attachment://activity.png")
        await ctx.send(f"Activity graph in a {window_days} day window{(' (' + truncation + ')') if truncation else ''}, showing average activity over each 7 day period.", embed=embed, file=discord.File("activity.png"))
예제 #21
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.PLANT_WATER_COOLDOWN) > 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.PLANT_WATER_COOLDOWN)) - 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
        plant_nourishment = plant_level_row[0]['plant_nourishment']
        gained_experience = 0
        original_gained_experience = 0
        multipliers = []  # List[Tuple[float, "reason"]]
        additional_text = []  # List[str]
        topgg_voted = False
        if 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.PLANT_WATER_COOLDOWN) <= timedelta(seconds=30):
                gained_experience = int(gained_experience * 1.5)
                multipliers.append((1.5, "You watered within 30 seconds of your plant's cooldown resetting"))

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

            # Update db
            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 plant_nourishment < 0:
            return await ctx.send("You sadly pour water into the dry soil of your silently wilting plant :c")

        # Send our SPECIAL outputs
        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(plant_nourishment) > plant_data.get_nourishment_display_level(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)

        # Let's embed the thing, f**k it
        embed = None
        if ctx.guild is None or ctx.channel.permissions_for(ctx.guild.me).embed_links:
            embed = utils.Embed(
                use_random_colour=True, description=output_lines[0]
            )
            if len(output_lines) > 1:
                embed.add_field(
                    "Multipliers", "\n".join([i.strip('') for i in output_lines[1:]]), inline=False
                )
            if self.bot.config.get('topgg_token') and topgg_voted is False:
                embed.set_footer(f"Get a 1.2x exp multiplier by voting on Top.gg! ({ctx.prefix}vote)")
            output_lines.clear()
        return await ctx.send("\n".join(output_lines), embed=embed)
예제 #22
0
    async def displayall(self, ctx: utils.Context,
                         user: typing.Optional[utils.converters.UserID]):
        """Show you all of your plants"""

        # 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", user.id)
            if not plant_rows:
                return await ctx.send(f"<@{user.id}> has no available plants.",
                                      allowed_mentions=discord.AllowedMentions(
                                          users=[ctx.author]))
            user_rows = await db(
                "SELECT * FROM user_settings WHERE user_id=$1", user.id)
        await ctx.trigger_typing()

        # Filter into variables
        images = []
        for plant_row in plant_rows:
            if plant_row and user_rows:
                display_data = self.get_display_data(plant_row, user_rows[0])
            elif plant_row:
                display_data = self.get_display_data(plant_row, None)
            elif user_rows:
                display_data = self.get_display_data(None, user_rows[0])
            else:
                display_data = self.get_display_data(None, None)
            images.append(self.get_plant_image(**display_data))

        # Work out our numbers
        max_height = max([i.size[1] for i in images])
        total_width = sum([i.size[0] for i in images])

        # Create the new image
        new_image = Image.new("RGBA", (
            total_width,
            max_height,
        ))
        width_offset = 0
        for index, image in enumerate(images):
            if random.randint(0, 1):
                image = ImageOps.mirror(image)
            new_image.paste(image, (
                width_offset,
                max_height - image.size[1],
            ), image)
            width_offset += image.size[0]

        # And Discord it up
        image = self.crop_image_to_content(
            new_image.resize((
                new_image.size[0] * 5,
                new_image.size[1] * 5,
            ), Image.NEAREST))
        image_to_send = self.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")
        await ctx.send(embed=embed, file=file)
예제 #23
0
    async def suggest(self, ctx: utils.Context, *, suggestion: str):
        """Send in a suggestion to the server"""

        if not suggestion:
            raise utils.errors.MissingRequiredArgumentString("suggestion")

        # Ask where the suggestion is for
        user = ctx.author
        try:
            m = await user.send(
                "Is this a _bot_ suggestion (0\N{COMBINING ENCLOSING KEYCAP}) or a _server_ suggestion (1\N{COMBINING ENCLOSING KEYCAP})?"
            )
        except discord.Forbidden:
            return await ctx.send("I couldn't send you a DM.")
        await ctx.send("Sent you a DM!")
        await m.add_reaction("0\N{COMBINING ENCLOSING KEYCAP}")
        await m.add_reaction("1\N{COMBINING ENCLOSING KEYCAP}")

        # See what they're talkin about
        try:
            reaction, _ = await self.bot.wait_for(
                "reaction_add",
                check=lambda r, u: r.message.id == m.id and u.id == user.id,
                timeout=120)
        except asyncio.TimeoutError:
            return await user.send("Timed out asking about your suggestion.")

        # Generate the embed
        with utils.Embed(use_random_colour=True) as embed:
            embed.description = suggestion
            if ctx.message.attachments:
                embed.add_field(
                    "Attachments",
                    ", ".join([i.url for i in ctx.message.attachments]))
            embed.set_author_to_user(ctx.author)
            embed.timestamp = ctx.message.created_at
            embed.set_footer(f"User ID {ctx.author.id}")

        # See how they reacted - assume it's a server suggestion if it was an invalid emoji
        if str(reaction.emoji) == "0\N{COMBINING ENCLOSING KEYCAP}":
            suggestion_channel_id = self.bot.config['channels'][
                'suggestion_channel']
            suggestion_channel = self.bot.get_channel(suggestion_channel_id)
            try:
                await suggestion_channel.send(embed=embed)
            except (discord.HTTPException, AttributeError) as e:
                return await user.send(
                    f"Your suggestion could not be sent in to the development team - {e}"
                )
            return await user.send(
                "Your suggestion has been successfully sent in to the bot development team."
            )

        # Send the suggestion to the server
        if ctx.guild is None:
            return await user.send(
                "You can't run this command in DMs if you're trying to make a server suggestion."
            )
        suggestion_channel_id = self.bot.guild_settings[
            ctx.guild.id]['suggestion_channel_id']
        if not suggestion_channel_id:
            return await user.send(
                f"**{ctx.guild.name}** hasn't set up a suggestion channel for me to send suggestions to."
            )
        suggestion_channel = self.bot.get_channel(suggestion_channel_id)
        if not suggestion_channel:
            return await user.send(
                f"**{ctx.guild.name}** has set up an invalid suggestion channel for me to send suggestions to."
            )
        try:
            embed.set_footer("Send in a suggestion with the 'suggest' command")
            await suggestion_channel.send(embed=embed)
        except (discord.HTTPException, AttributeError) as e:
            return await user.send(
                f"I couldn't send in your suggestion into {suggestion_channel.mention} - {e}"
            )
        return await user.send(
            "Your suggestion has been successfully sent in to the server's suggestions channel."
        )
예제 #24
0
    async def on_message_delete(self, message: discord.Message):
        """Logs edited messages"""

        # Filter
        if message.guild is None:
            return
        if message.author.bot:
            return

        # Create embed
        with utils.Embed(colour=0xff0000) as embed:
            embed.set_author_to_user(user=message.author)
            embed.description = f"Message deleted in {message.channel.mention}"
            if message.content:
                if len(message.content) > 1000:
                    embed.add_field(name="Message",
                                    value=message.content[:1000] + '...',
                                    inline=False)
                else:
                    embed.add_field(name="Message",
                                    value=message.content,
                                    inline=False)
            embed.set_footer(f"User ID {message.author.id}")
            embed.timestamp = dt.utcnow()
            if message.attachments:
                embed.add_field(
                    "Attachments", '\n'.join([
                        f"[Attachment {index}]({i.url}) ([attachment {index} proxy]({i.proxy_url}))"
                        for index, i in enumerate(message.attachments, start=1)
                    ]))

        # See if we can get who it was deleted by
        delete_time = dt.utcnow()
        if message.guild.me.guild_permissions.view_audit_log:
            changed = False
            async for entry in message.guild.audit_logs(
                    action=discord.AuditLogAction.message_delete, limit=1):
                if entry.extra.channel.id != message.channel.id:
                    break
                if entry.target.id != message.author.id:
                    break
                if entry.extra.count == 1 and delete_time > entry.created_at + timedelta(
                        seconds=0.1):
                    break  # I want the entry to be within 0.1 seconds of the message deletion
                elif entry.extra.count > 1:
                    last_delete_entry = self.last_audit_delete_entry_id.get(
                        message.guild.id, (
                            0,
                            -1,
                        ))
                    if last_delete_entry[0] != entry.id:
                        break  # Last cached entry is DIFFERENT to this entry
                    if last_delete_entry[1] == entry.extra.count:
                        break  # Unchanged from last cached log
                self.last_audit_delete_entry_id[message.guild.id] = (
                    entry.id, entry.extra.count)
                changed = True
                embed.description = f"Message deleted in {message.channel.mention} (deleted by {entry.user.mention})"
            if changed is False:
                embed.description = f"Message deleted in {message.channel.mention} (deleted by user or a bot)"

        # Get channel
        channel_id = self.bot.guild_settings[message.guild.id].get(
            "deleted_message_modlog_channel_id")
        channel = self.bot.get_channel(channel_id)
        if channel is None:
            return

        # Send log
        try:
            m = await channel.send(embed=embed)
            self.logger.info(
                f"Logging deleted message (G{m.guild.id}/C{m.channel.id})")
        except discord.Forbidden:
            pass
예제 #25
0
    async def make_graph(self, ctx, users:typing.List[discord.Member], window_days:int, colours:dict=None):
        """Makes the actual graph for the thing innit mate"""

        # Make sure there's people
        if not users:
            return await ctx.send("You can't make a graph of 0 users.")
        if len(users) > 10:
            return await ctx.send("There's more than 10 people in that graph - it would take too long for me to generate.")

        # Pick up colours
        if colours is None:
            colours = {}

        # This takes a lil bit so let's gooooooo
        await ctx.channel.trigger_typing()

        # Set up our most used vars
        original = window_days
        truncation = None
        if window_days > 365:
            window_days = 365
            truncation = f"shortened from your original request of {original} days for going over the 365 day max"
        if window_days > (dt.utcnow() - min([i.joined_at for i in users])).days:
            window_days = (dt.utcnow() - min([i.joined_at for i in users])).days
            truncation = f"shortened from your original request of {original} days as {'someone you pinged has not' if len(users) > 1 else 'they have not'} been in the guild that long"
        if window_days > (dt.utcnow() - ctx.guild.me.joined_at).days:
            window_days = (dt.utcnow() - ctx.guild.me.joined_at).days
            truncation = f"shortened from your original request of {original} days as I haven't been in the guild that long"

        # Make sure there's actually a day
        if window_days == 0:
            window_days = 1

        # Check our window, see if we can make it a lil bigger for them
        # if window_days <= 1:
        #     window = 'minutes', window_days * 24 * 60, 24 * 60
        if window_days <= 10:
            window = 'hours', window_days * 24, 24
        else:
            window = 'days', window_days, 1

        # Go through each day and work out how many points it has
        points_per_week_base = [0] * window[1]  # A list of the amount of points the user have in each given day (index)
        points_per_week = collections.defaultdict(points_per_week_base.copy)
        for user in users:
            for index in range(window[1]):
                between = (7 * window[2]) + window[1] - index - 1, window[1] - index - 1
                points_per_week[user][index] = len(utils.CachedMessage.get_messages_between(
                    user.id, ctx.guild.id, after={window[0]: between[0]}, before={window[0]: between[1]}
                ))

        # Don't bother uploading if they've not got any data
        if sum([sum(user_points) for user_points in points_per_week.values()]) == 0:
            return await ctx.send("They've not sent any messages that I can graph.")

        # Get roles
        async with self.bot.database() as db:
            role_data = await db("SELECT role_id, threshold FROM role_gain WHERE guild_id=$1", ctx.guild.id)
        role_object_data = sorted([(row['threshold'], ctx.guild.get_role(row['role_id'])) for row in role_data if ctx.guild.get_role(row['role_id'])], key=lambda x: x[0])

        # Build our output graph
        fig = plt.figure()
        ax = fig.subplots()

        # Plot data
        for user, i in points_per_week.items():
            if user.id in colours:
                colour = colours.get(user.id)
            else:
                colour = format(hex(random.randint(0, 0xffffff))[2:], "0>6")
            rgb_colour = tuple(int(colour[i:i + 2], 16) / 255 for i in (0, 2, 4))
            ax.plot(list(range(window[1])), i, 'k-', label=(user.nick or user.name), color=rgb_colour)
        fig.legend(loc="upper left")

        # Set size
        MINOR_AXIS_STOP = 50
        if role_object_data:
            graph_height = max([role_object_data[-1][0] + MINOR_AXIS_STOP, math.ceil((max([max(i) for i in points_per_week.values()]) + 1) / MINOR_AXIS_STOP) * MINOR_AXIS_STOP])
        else:
            graph_height = math.ceil((max([max(i) for i in points_per_week.values()]) + 1) / MINOR_AXIS_STOP) * MINOR_AXIS_STOP
        ax.axis([0, window[1], 0, graph_height])

        # Fix axies
        ax.axis('off')
        ax.grid(True)

        # Add background colour
        for zorder, tier in zip(range(-100, -100 + (len(role_object_data) * 2), 2), role_object_data):
            plt.axhspan(tier[0], graph_height, facecolor=f"#{tier[1].colour.value or 0xffffff:0>6X}", zorder=zorder)
            plt.axhspan(tier[0], tier[0] + 1, facecolor=f"#000000", zorder=zorder + 1)

        # Tighten border
        fig.tight_layout()

        # Output to user baybeeee
        fig.savefig('activity.png', bbox_inches='tight', pad_inches=0)
        with utils.Embed() as embed:
            embed.set_image(url="attachment://activity.png")
        await ctx.send(f"Activity graph in a {window_days} day window{(' (' + truncation + ')') if truncation else ''}, showing average activity over each 7 day period.", embed=embed, file=discord.File("activity.png"))