Пример #1
0
    async def typing_history(self,
                             ctx: commands.Context,
                             member: discord.Member = None):
        """See your typing test history"""
        if member is None:
            member = ctx.author

        data = await self.bot.db.execute(
            """
            SELECT test_date, wpm, accuracy, word_count, test_language FROM typing_stats
            WHERE user_id = %s ORDER BY test_date DESC
            """,
            member.id,
        )
        if not data:
            raise exceptions.CommandInfo(("You haven't" if member is ctx.author
                                          else f"**{member.name}** hasn't") +
                                         " taken any typing tests yet!", )

        content = discord.Embed(
            title=f":stopwatch: {util.displayname(member)} Typing test history",
            color=int("dd2e44", 16),
        )
        content.set_footer(text=f"Total {len(data)} typing tests taken")
        rows = []
        for test_date, wpm, accuracy, word_count, test_language in data:
            rows.append(
                f"**{wpm}** WPM, **{int(accuracy)}%** ACC, "
                f"**{word_count}** words, *{test_language}* ({arrow.get(test_date).to('utc').humanize()})"
            )

        await util.send_as_pages(ctx, content, rows)
Пример #2
0
    async def send_hs(self, ctx: commands.Context, day):
        sunsign = await self.bot.db.execute(
            "SELECT sunsign FROM user_settings WHERE user_id = %s",
            ctx.author.id,
            one_value=True,
        )
        if not sunsign or sunsign is None:
            raise exceptions.CommandInfo(
                "Please save your zodiac sign using `>horoscope set <sign>`\n"
                "Use `>horoscope list` if you don't know which one you are."
            )
        params = {"sign": sunsign, "day": day}
        async with self.bot.session.post(
            "https://aztro.sameerkumar.website/", params=params
        ) as response:
            data = await response.json(loads=orjson.loads)

        sign = self.hs.get(sunsign)
        content = discord.Embed(
            color=int("9266cc", 16),
            title=f"{sign['emoji']} {sign['name']} - {data['current_date']}",
            description=data["description"],
        )

        content.add_field(name="Mood", value=data["mood"], inline=True)
        content.add_field(name="Compatibility", value=data["compatibility"], inline=True)
        content.add_field(name="Color", value=data["color"], inline=True)
        content.add_field(name="Lucky number", value=data["lucky_number"], inline=True)
        content.add_field(name="Lucky time", value=data["lucky_time"], inline=True)
        content.add_field(name="Date range", value=data["date_range"], inline=True)

        await ctx.send(embed=content)
Пример #3
0
    async def remindme(self, ctx: commands.Context, pre, *, arguments):
        """
        Set a reminder

        Usage:
            >remindme in <some time> to <something>
            >remindme on <YYYY/MM/DD> [HH:mm:ss] to <something>
        """
        try:
            reminder_time, content = arguments.split(" to ", 1)
        except ValueError:
            return await util.send_command_help(ctx)

        now = arrow.utcnow()

        if pre == "on":
            # user inputs date
            date = arrow.get(reminder_time)
            seconds = date.int_timestamp - now.int_timestamp

        elif pre == "in":
            # user inputs time delta
            seconds = util.timefromstring(reminder_time)
            date = now.shift(seconds=+seconds)

        else:
            return await ctx.send(
                f"Invalid operation `{pre}`\nUse `on` for date and `in` for time delta"
            )

        if seconds < 1:
            raise exceptions.CommandInfo(
                "You must give a valid time at least 1 second in the future!")

        await self.bot.db.execute(
            """
            INSERT INTO reminder (user_id, guild_id, created_on, reminder_date, content, original_message_url)
                VALUES(%s, %s, %s, %s, %s, %s)
            """,
            ctx.author.id,
            ctx.guild.id,
            now.datetime,
            date.datetime,
            content,
            ctx.message.jump_url,
        )

        self.cache_needs_refreshing = True
        await ctx.send(embed=discord.Embed(
            color=int("ccd6dd", 16),
            description=
            (f":pencil: I'll message you on **{date.to('utc').format('DD/MM/YYYY HH:mm:ss')}"
             f" UTC** to remind you of:\n```{content}```"),
        ))
Пример #4
0
    async def command_list(self, ctx: commands.Context):
        """List all commands on this server"""
        rows = []
        for command in await self.custom_command_list(ctx.guild.id):
            rows.append(f"{ctx.prefix}{command}")

        if rows:
            content = discord.Embed(title=f"{ctx.guild.name} custom commands")
            await util.send_as_pages(ctx, content, rows)
        else:
            raise exceptions.CommandInfo(
                "No custom commands have been added on this server yet")
Пример #5
0
    async def melon(self, ctx: commands.Context, timeframe):
        """Melon music charts"""
        if timeframe not in ["day", "month"]:
            if timeframe == "realtime":
                timeframe = ""
            elif timeframe == "rising":
                timeframe = "rise"
            else:
                raise exceptions.CommandInfo(
                    "Available timeframes: `[ day | month | realtime | rising ]`"
                )

        url = f"https://www.melon.com/chart/{timeframe}/index.htm"
        headers = {
            "Accept":
            "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
            "User-Agent":
            "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:65.0) Gecko/20100101 Firefox/65.0",
        }
        async with self.bot.session.get(url, headers=headers) as response:
            soup = BeautifulSoup(await response.text(), "html.parser")

        song_titles = [
            util.escape_md(x.find("span").find("a").text)
            for x in soup.find_all("div", {"class": "ellipsis rank01"})
        ]
        artists = [
            util.escape_md(x.find("a").text)
            for x in soup.find_all("div", {"class": "ellipsis rank02"})
        ]
        # albums = [
        #     util.escape_md(x.find("a").text)
        #     for x in soup.find_all("div", {"class": "ellipsis rank03"})
        # ]
        image = soup.find("img", {
            "onerror": "WEBPOCIMG.defaultAlbumImg(this);"
        }).get("src")

        content = discord.Embed(color=discord.Color.from_rgb(0, 205, 60))
        content.set_author(
            name=f"Melon top {len(song_titles)}" +
            ("" if timeframe == "" else f" - {timeframe.capitalize()}"),
            url=url,
            icon_url="https://i.imgur.com/hm9xzPz.png",
        )
        content.set_thumbnail(url=image)
        content.timestamp = ctx.message.created_at

        rows = []
        for i, (song, artist) in enumerate(zip(song_titles, artists), start=1):
            rows.append(f"`#{i:2}` **{artist}** — ***{song}***")

        await util.send_as_pages(ctx, content, rows)
Пример #6
0
    async def ban(self, ctx: commands.Context, *discord_users):
        """Ban user(s)"""
        if not discord_users:
            return await util.send_command_help(ctx)

        if len(discord_users) > 4:
            raise exceptions.CommandInfo(
                f"It seems you are trying to ban a lot of users at once.\nPlease use `{ctx.prefix}massban ...` instead"
            )

        for discord_user in discord_users:
            user = await util.get_member(ctx, discord_user)
            if user is None:
                try:
                    user = await self.bot.fetch_user(int(discord_user))
                except (ValueError, discord.NotFound):
                    await ctx.send(embed=discord.Embed(
                        description=
                        f":warning: Invalid user or id `{discord_user}`",
                        color=int("be1931", 16),
                    ))
                    continue

            if user.id == 133311691852218378:
                return await ctx.send("no.")

            # confirmation dialog for guild members
            if isinstance(user, discord.Member):
                await self.send_ban_confirmation(ctx, user)

            elif isinstance(user, discord.User):
                try:
                    await ctx.guild.ban(user, delete_message_days=0)
                except discord.errors.Forbidden:
                    await ctx.send(embed=discord.Embed(
                        description=
                        f":no_entry: It seems I don't have the permission to ban **{user}**",
                        color=int("be1931", 16),
                    ))
                else:
                    await ctx.send(embed=discord.Embed(
                        description=f":hammer: Banned `{user}`",
                        color=int("f4900c", 16),
                    ))
            else:
                await ctx.send(embed=discord.Embed(
                    description=
                    f":warning: Invalid user or id `{discord_user}`",
                    color=int("be1931", 16),
                ))
Пример #7
0
    async def horoscope_set(self, ctx: commands.Context, sign):
        """Save your zodiac sign"""
        sign = sign.lower()
        if self.hs.get(sign) is None:
            raise exceptions.CommandInfo(
                f"`{sign}` is not a valid zodiac! Use `>horoscope list` for a list of zodiacs."
            )

        await ctx.bot.db.execute(
            """
            INSERT INTO user_settings (user_id, sunsign)
                VALUES (%s, %s)
            ON DUPLICATE KEY UPDATE
                sunsign = VALUES(sunsign)
            """,
            ctx.author.id,
            sign,
        )
        await ctx.send(f"Zodiac saved as **{sign.capitalize()}** {self.hs.get(sign)['emoji']}")
Пример #8
0
    async def votechannel_list(self, ctx: commands.Context):
        """List all current voting channels on this server"""
        channels = await self.bot.db.execute(
            """
            SELECT channel_id, voting_type FROM voting_channel WHERE guild_id = %s
            """,
            ctx.guild.id,
        )
        if not channels:
            raise exceptions.CommandInfo(
                "There are no voting channels on this server yet!")

        rows = []
        for channel_id, voting_type in channels:
            rows.append(f"<#{channel_id}> - `{voting_type}`")

        content = discord.Embed(
            title=f":1234: Voting channels in {ctx.guild.name}",
            color=int("3b88c3", 16))
        await util.send_as_pages(ctx, content, rows)
Пример #9
0
    async def notification_list(self, ctx: commands.Context):
        """List your current notifications"""
        words = await self.bot.db.execute(
            """
            SELECT guild_id, keyword, times_triggered FROM notification WHERE user_id = %s ORDER BY keyword
            """,
            ctx.author.id,
        )

        if not words:
            raise exceptions.CommandInfo(
                "You have not set any notifications yet!")

        content = discord.Embed(
            title=f":love_letter: You have {len(words)} notifications",
            color=int("dd2e44", 16),
        )

        rows = []
        for guild_id, keyword, times_triggered in sorted(words):
            guild = self.bot.get_guild(guild_id)
            if guild is None:
                guild = f"[Unknown server `{guild_id}`]"

            rows.append(
                f"**{guild}** : `{keyword}` - Triggered **{times_triggered}** times"
            )

        try:
            await util.send_as_pages(ctx.author,
                                     content,
                                     rows,
                                     maxpages=1,
                                     maxrows=50)
        except discord.errors.Forbidden:
            raise exceptions.CommandWarning(
                "I was unable to send you a DM! Please change your settings.")

        if ctx.guild is not None:
            await util.send_success(
                ctx, f"Notification list sent to your DM {emojis.VIVISMIRK}")
Пример #10
0
    async def leaderboard_fishy(self, ctx: commands.Context, scope=""):
        """Fishy leaderboard"""
        global_data = scope.lower() == "global"
        data = await self.bot.db.execute(
            "SELECT user_id, fishy_count FROM fishy ORDER BY fishy_count DESC")

        rows = []
        medal_emoji = [":first_place:", ":second_place:", ":third_place:"]
        i = 1
        for user_id, fishy_count in data:
            if global_data:
                user = self.bot.get_user(user_id)
            else:
                user = ctx.guild.get_member(user_id)

            if user is None or fishy_count == 0:
                continue

            if i <= len(medal_emoji):
                ranking = medal_emoji[i - 1]
            else:
                ranking = f"`#{i:2}`"

            rows.append(
                f"{ranking} **{util.displayname(user)}** — **{fishy_count}** fishy"
            )
            i += 1

        if not rows:
            raise exceptions.CommandInfo("Nobody has any fish yet!")

        content = discord.Embed(
            title=
            f":fish: {'Global' if global_data else ctx.guild.name} fishy leaderboard",
            color=int("55acee", 16),
        )
        await util.send_as_pages(ctx, content, rows)
Пример #11
0
    async def typing_stats(self,
                           ctx: commands.Context,
                           user: discord.Member = None):
        """See your typing statistics"""
        if user is None:
            user = ctx.author

        data = await self.bot.db.execute(
            """
            SELECT COUNT(test_date), MAX(wpm), AVG(wpm), AVG(accuracy), race_count, win_count
                FROM typing_stats LEFT JOIN typing_race
                ON typing_stats.user_id = typing_race.user_id
            WHERE typing_stats.user_id = %s
            GROUP BY typing_stats.user_id
            """,
            user.id,
            one_row=True,
        )
        if not data:
            raise exceptions.CommandInfo(
                ("You haven't" if user is ctx.author else
                 f"**{user.name}** hasn't") + " taken any typing tests yet!", )

        test_count, max_wpm, avg_wpm, avg_acc, race_count, win_count = data
        content = discord.Embed(
            title=f":bar_chart: Typing stats for {user.name}",
            color=int("3b94d9", 16))
        content.description = (
            f"Best WPM: **{max_wpm}**\n"
            f"Average WPM: **{int(avg_wpm)}**\n"
            f"Average Accuracy: **{avg_acc:.2f}%**\n"
            f"Tests taken: **{test_count}** of which **{race_count}** were races\n"
            f"Races won: **{win_count or 0}** " +
            (f"(**{(win_count/race_count)*100:.1f}%** win rate)"
             if race_count is not None else ""))

        await ctx.send(embed=content)
Пример #12
0
    async def timeout(self,
                      ctx: commands.Context,
                      member: discord.Member,
                      *,
                      duration="1 hour"):
        """Timeout user. Pass 'remove' as the duration to remove"""
        if member.timeout is not None:
            seconds = member.timeout.timestamp() - arrow.now().int_timestamp
            if duration and duration.strip().lower() == "remove":
                await member.edit(timeout=None)
                return await util.send_success(
                    ctx, f"Removed timeout from {member.mention}")
            else:
                raise exceptions.CommandInfo(
                    f"{member.mention} is already timed out (**{util.stringfromtime(seconds)}** remaining)",
                )

        seconds = util.timefromstring(duration)

        await member.edit(timeout=arrow.now().shift(seconds=+seconds).datetime)
        await util.send_success(
            ctx,
            f"Timed out {member.mention} for **{util.stringfromtime(seconds)}**"
        )
Пример #13
0
    async def minecraft(self, ctx: commands.Context, address=None, port=None):
        """Get the status of a minecraft server"""
        if address == "set":
            if port is None:
                return await ctx.send(
                    f"Save minecraft server address for this discord server:\n"
                    f"`{ctx.prefix}minecraft set <address>` (port defaults to 25565)\n"
                    f"`{ctx.prefix}minecraft set <address>:<port>`"
                )

            address = port.split(":")[0]
            try:
                port = int(port.split(":")[1])
            except IndexError:
                port = 25565

            await self.bot.db.execute(
                """
                INSERT INTO minecraft_server (guild_id, server_address, port)
                    VALUES (%s, %s, %s)
                ON DUPLICATE KEY UPDATE
                    server_address = VALUES(server_address),
                    port = VALUES(port)
                """,
                ctx.guild.id,
                address,
                port,
            )
            return await util.send_success(
                ctx,
                f"Default Minecraft server of this discord saved as `{address}:{port}`",
            )

        if address is None:
            data = await self.bot.db.execute(
                "SELECT server_address, port FROM minecraft_server WHERE guild_id = %s",
                ctx.guild.id,
                one_row=True,
            )
            if not data:
                raise exceptions.CommandInfo(
                    "No default Minecraft server saved for this discord server!"
                    f"Use `{ctx.prefix}minecraft set` to save one or"
                    f"`{ctx.prefix}minecraft <address> <port>` to see any server"
                )

            address, port = data

        if port is None:
            port = 25565

        server = await self.bot.loop.run_in_executor(
            None, lambda: minestat.MineStat(address, int(port))
        )
        content = discord.Embed(color=discord.Color.green())
        if server.online:
            content.add_field(name="Server Address", value=f"`{server.address}`")
            content.add_field(name="Version", value=server.version)
            content.add_field(
                name="Players", value=f"{server.current_players}/{server.max_players}"
            )
            content.add_field(name="Latency", value=f"{server.latency}ms")
            content.set_footer(text=f"Message of the day: {server.motd}")
        else:
            content.title = f"{address}:{port}"
            content.description = ":warning: **Server is offline**"
        content.set_thumbnail(url="https://i.imgur.com/P1IxD0Q.png")
        await ctx.send(embed=content)
Пример #14
0
    async def mute(self,
                   ctx: commands.Context,
                   member: discord.Member,
                   *,
                   duration=None):
        """Mute user"""
        mute_role_id = await self.bot.db.execute(
            """
            SELECT mute_role_id FROM guild_settings WHERE guild_id = %s
            """,
            ctx.guild.id,
            one_value=True,
        )
        mute_role = ctx.guild.get_role(mute_role_id)
        if not mute_role:
            raise exceptions.CommandWarning(
                "Mute role for this server has been deleted or is not set, "
                f"please use `{ctx.prefix}muterole <role>` to set it.")

        if member.id == 133311691852218378:
            return await ctx.send("no.")

        seconds = None
        if duration is not None:
            seconds = util.timefromstring(duration)

            if seconds is None or seconds == 0:
                raise exceptions.CommandWarning(
                    f'Invalid mute duration "{duration}"')

            if seconds < 60:
                raise exceptions.CommandInfo(
                    "The minimum duration of a mute is **1 minute**")

            if seconds > 604800:
                raise exceptions.CommandInfo(
                    "The maximum duration of a mute is **1 week**")

        try:
            await member.add_roles(mute_role)
        except discord.errors.Forbidden:
            raise exceptions.CommandError(
                f"It seems I don't have permission to mute {member.mention}")

        await util.send_success(
            ctx,
            f"Muted {member.mention}" +
            (f" for **{util.stringfromtime(seconds)}**"
             if seconds is not None else ""),
        )

        if seconds is not None:
            unmute_on = arrow.now().shift(seconds=seconds).datetime
        else:
            unmute_on = None

        await self.bot.db.execute(
            """
            INSERT INTO muted_user (guild_id, user_id, channel_id, unmute_on)
                VALUES (%s, %s, %s, %s)
            ON DUPLICATE KEY UPDATE
                unmute_on = VALUES(unmute_on)
            """,
            ctx.guild.id,
            member.id,
            ctx.channel.id,
            unmute_on,
        )
        self.cache_needs_refreshing = True
Пример #15
0
    async def commandstats_single(self, ctx: commands.Context, command_name):
        """Stats of a single command"""
        command = self.bot.get_command(command_name)
        if command is None:
            raise exceptions.CommandInfo(f"Command `{ctx.prefix}{command_name}` does not exist!")

        content = discord.Embed(title=f":bar_chart: `{ctx.prefix}{command.qualified_name}`")

        # set command name to be tuple of subcommands if this is a command group
        group = hasattr(command, "commands")
        if group:
            command_name = tuple(
                [f"{command.name} {x.name}" for x in command.commands] + [command_name]
            )
        else:
            command_name = command.qualified_name

        (total_uses, most_used_by_user_id, most_used_by_user_amount,) = await self.bot.db.execute(
            f"""
            SELECT SUM(use_sum) as total, user_id, MAX(use_sum) FROM (
                SELECT SUM(uses) as use_sum, user_id FROM command_usage
                    WHERE command_type = 'internal'
                      AND command_name {'IN %s' if group else '= %s'}
                GROUP BY user_id
            ) as subq
            """,
            command_name,
            one_row=True,
        )

        most_used_by_guild_id, most_used_by_guild_amount = await self.bot.db.execute(
            f"""
            SELECT guild_id, MAX(use_sum) FROM (
                SELECT guild_id, SUM(uses) as use_sum FROM command_usage
                    WHERE command_type = 'internal'
                      AND command_name {'IN %s' if group else '= %s'}
                GROUP BY guild_id
            ) as subq
            """,
            command_name,
            one_row=True,
        )

        uses_in_this_server = (
            await self.bot.db.execute(
                f"""
                SELECT SUM(uses) FROM command_usage
                    WHERE command_type = 'internal'
                      AND command_name {'IN %s' if group else '= %s'}
                      AND guild_id = %s
                GROUP BY guild_id
                """,
                command_name,
                ctx.guild.id,
                one_value=True,
            )
            or 0
        )

        # show the data in embed fields
        content.add_field(name="Uses", value=total_uses or 0)
        content.add_field(name="on this server", value=uses_in_this_server)
        content.add_field(
            name="Server most used in",
            value=f"{self.bot.get_guild(most_used_by_guild_id)} ({most_used_by_guild_amount or 0})",
            inline=False,
        )
        content.add_field(
            name="Most total uses by",
            value=f"{self.bot.get_user(most_used_by_user_id)} ({most_used_by_user_amount or 0})",
        )

        # additional data for command groups
        if group:
            content.description = "Command Group"
            subcommands_tuple = tuple([f"{command.name} {x.name}" for x in command.commands])
            subcommand_usage = await self.bot.db.execute(
                """
                SELECT command_name, SUM(uses) FROM command_usage
                    WHERE command_type = 'internal'
                      AND command_name IN %s
                GROUP BY command_name ORDER BY SUM(uses) DESC
                """,
                subcommands_tuple,
            )
            content.add_field(
                name="Subcommand usage",
                value="\n".join(f"{s[0]} - **{s[1]}**" for s in subcommand_usage),
                inline=False,
            )

        await ctx.send(embed=content)