Example #1
0
    async def typing_stats(self, ctx, user: discord.Member = None):
        """See your typing test 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.Info(
                ("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)
Example #2
0
    async def typing_history(self, ctx, user: discord.Member = None):
        """See your typing test history."""
        if user is None:
            user = 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
            """,
            user.id,
        )
        if not data:
            raise exceptions.Info(
                ctx,
                ("You haven't" if user is ctx.author else
                 f"**{user.name}** hasn't") + " taken any typing tests yet!",
            )

        content = discord.Embed(
            title=f":stopwatch: Typing history for {user.name}",
            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)
Example #3
0
    async def list(self, ctx):
        """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.Info("You have not set any notifications yet!")

        content = discord.Embed(title=":love_letter: Your notifications",
                                color=int("dd2e44", 16))

        rows = []
        for guild_id, keyword, times_triggered in words:
            if guild_id == 0:
                guild = "globally"
            else:
                guild = f"in {self.bot.get_guild(guild_id)}"

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

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

        if ctx.guild is not None:
            await ctx.send(
                f"Notification list sent to your DM {emojis.VIVISMIRK}")
Example #4
0
    async def remindme(self, ctx, 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 ")
        except ValueError:
            return await util.send_command_help(ctx)

        now = arrow.now()

        if pre == "on":
            # user inputs date
            date = arrow.get(reminder_time)
            seconds = date.timestamp - now.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.Info(
                "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}```"),
        ))
Example #5
0
    async def melon(self, ctx, timeframe):
        """Melon music charts."""
        if timeframe not in ["day", "month"]:
            if timeframe == "realtime":
                timeframe = ""
            elif timeframe == "rising":
                timeframe = "rise"
            else:
                raise exceptions.Info(
                    "Available timeframes: `[ day | month | realtime | rising ]`"
                )

        url = f"https://www.melon.com/chart/{timeframe}/index.htm"
        async with aiohttp.ClientSession() as session:
            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 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)
Example #6
0
    async def list(self, ctx):
        """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.Info(
                "No custom commands have been added on this server yet")
Example #7
0
    async def mediaonly_user(self, ctx, username, value: bool):
        """User level setting."""
        user_id = await self.bot.db.execute(
            """
            SELECT twitter_user.user_id
            FROM follow RIGHT JOIN twitter_user
            ON twitter_user.user_id = follow.twitter_user_id
            WHERE twitter_user.username = %s AND guild_id = %s""",
            username,
            ctx.guild.id,
            one_value=True,
        )
        if not user_id:
            raise exceptions.Info(f'No channel on this server is following "@{username}"')

        await queries.set_config_user(self.bot.db, ctx.guild.id, user_id, "media_only", value)
        await ctx.send(
            f":white_check_mark: User setting **Media only** is now **{value}** for **@{username}**"
        )
Example #8
0
    async def votechannel_list(self, ctx):
        """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.Info("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)
Example #9
0
    async def remove(self, ctx, channel: discord.TextChannel, *usernames):
        """Remove users from the follow list."""
        if not usernames:
            raise exceptions.Info("You must give at least one twitter user to remove!")

        rows = []
        current_users = await self.bot.db.execute(
            "SELECT twitter_user_id FROM follow WHERE channel_id = %s", channel.id
        )
        successes = 0
        for username in usernames:
            status = None
            try:
                user_id = (await self.api.get_user(username=username)).data.id
            except Exception as e:
                # user not found, maybe changed username
                # try finding username from cache
                user_id = await self.bot.db.execute(
                    "SELECT user_id FROM twitter_user WHERE username = %s", username
                )
                if user_id:
                    user_id = user_id[0][0]
                else:
                    status = f":x: Error {e.args[0][0]['code']}: {e.args[0][0]['message']}"

            if status is None:
                if (user_id,) not in current_users:
                    status = ":x: User is not being followed on this channel"
                else:
                    await self.unfollow(channel.id, user_id)
                    status = ":white_check_mark: Success"
                    successes += 1

            rows.append(f"**@{username}** {status}")

        content = discord.Embed(
            title=f":notepad_spiral: Removed {successes}/{len(usernames)} users from {channel.name}",
            color=self.bot.twitter_blue,
        )
        content.set_footer(text="Changes will take effect within a minute")
        pages = menus.Menu(source=menus.ListMenu(rows, embed=content), clear_reactions_after=True)
        await pages.start(ctx)
Example #10
0
    async def add(self, ctx, channel: discord.TextChannel, *usernames):
        """Add users to the follow list."""
        if not usernames:
            raise exceptions.Info("You must give at least one twitter user to follow!")

        rows = []
        time_now = arrow.now().datetime
        current_users = await self.bot.db.execute(
            "SELECT twitter_user_id FROM follow WHERE channel_id = %s", channel.id
        )
        guild_follow_current, guild_follow_limit = await queries.get_follow_limit(
            self.bot.db, channel.guild.id
        )
        successes = 0
        for username in usernames:
            status = None
            try:
                user = (await self.api.get_user(username=username)).data
            except Exception as e:
                status = f":x: Error {e}"
            else:
                if (user.id,) in current_users:
                    status = ":x: User already being followed on this channel"
                else:
                    if guild_follow_current >= guild_follow_limit:
                        status = f":lock: Guild follow count limit reached ({guild_follow_limit})"
                    else:
                        await self.follow(channel, user.id, user.username, time_now)
                        status = ":white_check_mark: Success"
                        successes += 1
                        guild_follow_current += 1

            rows.append(f"**@{user.username}** {status}")

        content = discord.Embed(
            title=f":notepad_spiral: Added {successes}/{len(usernames)} users to {channel.name}",
            color=self.bot.twitter_blue,
        )
        content.set_footer(text="Changes will take effect within a minute")
        pages = menus.Menu(source=menus.ListMenu(rows, embed=content), clear_reactions_after=True)
        await pages.start(ctx)
Example #11
0
    async def set(self, ctx, sign):
        """Save your zodiac sign."""
        sign = sign.lower()
        if self.hs.get(sign) is None:
            raise exceptions.Info(
                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']}"
        )
Example #12
0
    async def list(self, ctx):
        """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.Info("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.Warning(
                "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}")
Example #13
0
    async def send_hs(self, ctx, 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.Info(
                "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 aiohttp.ClientSession() as session:
            async with session.post("https://aztro.sameerkumar.website/",
                                    params=params) as response:
                data = await response.json()

        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)
Example #14
0
    async def leaderboard_fishy(self, ctx, 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.Info("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)
Example #15
0
    async def mute(self, ctx, 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.Warning(
                "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.Warning(f'Invalid mute duration "{duration}"')

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

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

        try:
            await member.add_roles(mute_role)
        except discord.errors.Forbidden:
            raise exceptions.Error(
                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
Example #16
0
    async def commandstats_single(self, ctx, command_name):
        """Stats of a single command."""
        command = self.bot.get_command(command_name)
        if command is None:
            raise exceptions.Info(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)
Example #17
0
    async def minecraft(self, ctx, 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.Info(
                    "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)