Пример #1
0
    async def unban(self, ctx: commands.Context, *discord_users):
        """Unban user(s)"""
        if not discord_users:
            return await util.send_command_help(ctx)

        if len(discord_users) == 1:
            user = await util.get_user(ctx, discord_users[0])
            if user is None:
                try:
                    user = await self.bot.fetch_user(int(user))
                except (ValueError, discord.NotFound):
                    raise exceptions.CommandError(
                        f"Invalid user or id `{user}`")
            try:
                await ctx.guild.unban(user)
            except discord.errors.Forbidden:
                raise exceptions.CommandError(
                    f"It seems I don't have the permission to unban **{user}** {user.mention}"
                )
            except discord.errors.NotFound:
                raise exceptions.CommandWarning(
                    f"Unable to unban. **{user}** is not banned")
            else:
                return await util.send_success(
                    ctx, f"Unbanned **{user}** {user.mention}")

        success = []
        failure = []
        for discord_user in discord_users:
            user = await util.get_user(ctx, discord_user)
            if user is None:
                try:
                    user = await self.bot.fetch_user(int(discord_user))
                except (ValueError, discord.NotFound):
                    failure.append(f"`{discord_user}` Invalid user or id")
                    continue

            try:
                await ctx.guild.unban(user)
            except discord.errors.Forbidden:
                failure.append(f"`{user}` No permission to unban")
            except discord.errors.NotFound:
                failure.append(f"`{user}` is not banned")
            else:
                success.append(f"`{user}` Unbanned")

        await util.send_tasks_result_list(
            ctx, success, failure,
            f":memo: Attempting to unban {len(discord_users)} users...")
Пример #2
0
    async def unmute(self, ctx: commands.Context, member: discord.Member):
        """Unmute 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.")
        try:
            await member.remove_roles(mute_role)
        except discord.errors.Forbidden:
            raise exceptions.CommandError(
                f"It seems I don't have permission to unmute {member.mention}")

        await util.send_success(ctx, f"Unmuted {member.mention}")
        await self.bot.db.execute(
            """
            DELETE FROM muted_user WHERE guild_id = %s AND user_id = %s
            """,
            ctx.guild.id,
            member.id,
        )
        self.cache_needs_refreshing = True
Пример #3
0
    async def purge(self, ctx: commands.Context, amount: int):
        """
        Delete given amount of messages in the current channel

        Optionally if users are mentioned, only messages by those users are deleted.

        Usage:
            >purge <amount> [mentions...]
        """
        if amount > 100:
            raise exceptions.CommandWarning(
                "You cannot delete more than 100 messages at a time.")

        await ctx.message.delete()

        if ctx.message.mentions:
            deleted = []
            async for message in ctx.channel.history(limit=100,
                                                     oldest_first=False):
                if message.author in ctx.message.mentions:
                    deleted.append(message)
                    if len(deleted) >= amount:
                        break
            try:
                await ctx.channel.delete_messages(deleted)
            except discord.errors.HTTPException:
                raise exceptions.CommandError(
                    "You can only delete messages that are under 14 days old.")
        else:
            deleted = await ctx.channel.purge(limit=amount)

        await ctx.send(
            f":put_litter_in_its_place: Deleted `{len(deleted)}` messages.",
            delete_after=5,
        )
Пример #4
0
    async def youtube(self, ctx: commands.Context, *, query):
        """Search for videos from youtube"""
        url = "https://www.googleapis.com/youtube/v3/search"
        params = {
            "key": GOOGLE_API_KEY,
            "part": "snippet",
            "type": "video",
            "maxResults": 25,
            "q": query,
        }
        async with self.bot.session.get(url, params=params) as response:
            if response.status == 403:
                raise exceptions.CommandError(
                    "Daily youtube api quota reached.")

            data = await response.json(loads=orjson.loads)

        if not data.get("items"):
            return await ctx.send("No results found!")

        await util.paginate_list(
            ctx,
            [
                f"https://youtube.com/watch?v={item['id']['videoId']}"
                for item in data.get("items")
            ],
            use_locking=True,
            only_author=True,
            index_entries=True,
        )
Пример #5
0
 async def executemany(self, statement, params):
     if await self.wait_for_pool():
         async with self.pool.acquire() as conn:
             async with conn.cursor() as cur:
                 await cur.executemany(statement, params)
                 await conn.commit()
         return ()
     raise exceptions.CommandError(
         "Could not connect to the local MariaDB instance!")
Пример #6
0
 async def advice(self, ctx: commands.Context):
     """Get some life advice"""
     # for some reason the api is missing content for these ids
     missing_ids = {0, 22, 48, 67}
     slip_id = 0
     while slip_id in missing_ids:
         slip_id = random.randint(1, 224)
     url = f"https://api.adviceslip.com/advice/{slip_id}"
     async with self.bot.session.get(url) as response:
         data = orjson.loads(await response.text())
     try:
         await ctx.send(f"*{data['slip']['advice']}*")
     except KeyError:
         raise exceptions.CommandError(f"slip_id={slip_id} :: {data}")
Пример #7
0
    async def random(self, ctx: commands.Context, gender=None):
        """Get a random kpop idol"""
        gender = get_gender(gender)

        idol_id_list = await self.bot.db.execute(
            "SELECT idol_id FROM kpop_idol WHERE gender IN %s",
            gender,
            as_list=True,
        )
        if not idol_id_list:
            raise exceptions.CommandError("Looks like there are no idols in the database!")

        chosen_id = random.choice(idol_id_list)
        await self.send_idol(ctx, chosen_id)
Пример #8
0
    async def notification_test(self,
                                ctx: commands.Context,
                                message: discord.Message = None):
        """
        Test if Miso can send you a notification
        If supplied with a message id, will check if you would have been notified by it.
        """
        if message is None:
            try:
                await self.send_notification(ctx.author,
                                             message or ctx.message, ["test"],
                                             test=True)
                await ctx.send(":ok_hand: Check your DM")
            except discord.errors.Forbidden:
                raise exceptions.CommandWarning(
                    "I was unable to send you a DM! Please check your privacy settings."
                )
        else:
            if ctx.author not in message.channel.members:
                raise exceptions.CommandError("You cannot see this message.")

            keywords = await self.bot.db.execute(
                "SELECT keyword FROM notification WHERE user_id = %s",
                ctx.author.id,
                as_list=True,
            )

            pattern = regex.compile(self.keyword_regex,
                                    words=keywords,
                                    flags=regex.IGNORECASE)

            finds = pattern.findall(message.content)
            if not finds:
                await ctx.send(":x: This message would not notify you")
            else:
                keywords = list(set(finds))
                await self.send_notification(ctx.author,
                                             message,
                                             keywords,
                                             test=True)
                await ctx.send(":ok_hand: Check your DM")
Пример #9
0
 async def execute(self,
                   statement,
                   *params,
                   one_row=False,
                   one_value=False,
                   as_list=False):
     if await self.wait_for_pool():
         async with self.pool.acquire() as conn:
             async with conn.cursor() as cur:
                 await cur.execute(statement, params)
                 data = await cur.fetchall()
         if data is None:
             return ()
         if data:
             if one_value:
                 return data[0][0]
             if one_row:
                 return data[0]
             if as_list:
                 return [row[0] for row in data]
             return data
         return ()
     raise exceptions.CommandError(
         "Could not connect to the local MariaDB instance!")
Пример #10
0
    async def send_idol(self, ctx: commands.Context, idol_id):
        idol_data = await self.bot.db.execute(
            """
            SELECT idol_id, full_name, stage_name, korean_name, korean_stage_name,
                   date_of_birth, country, group_name, height, weight, gender, image_url
                FROM kpop_idol
            WHERE idol_id = %s
            """,
            idol_id,
            one_row=True,
        )
        if not idol_data:
            raise exceptions.CommandError("There was an error getting idol data.")

        (
            idol_id,
            full_name,
            stage_name,
            korean_name,
            korean_stage_name,
            date_of_birth,
            country,
            group_name,
            height,
            weight,
            gender,
            image_url,
        ) = idol_data

        if group_name is None:
            search_term = full_name
        else:
            search_term = f"{group_name} {stage_name} kpop"

        if image_url is None:
            image_url = await self.google_image_search(search_term)
            if image_url != "":
                await self.bot.db.execute(
                    "UPDATE kpop_idol SET image_url = %s WHERE idol_id = %s",
                    image_url,
                    idol_id,
                )

        content = discord.Embed()
        if gender == "F":
            content.colour = int("e7586d", 16)
        elif gender == "M":
            content.colour = int("226699", 16)

        content.title = (
            self.gender_icon.get(gender, "")
            + (f"{group_name} " if group_name is not None else "")
            + stage_name
        )
        today = datetime.date.today()
        age = (
            today.year
            - date_of_birth.year
            - ((today.month, today.day) < (date_of_birth.month, date_of_birth.day))
        )
        content.set_image(url=image_url)
        content.add_field(name="Full name", value=full_name)
        content.add_field(name="Korean name", value=f"{korean_stage_name} ({korean_name})")
        content.add_field(
            name="Birthday",
            value=arrow.get(date_of_birth).format("YYYY-MM-DD") + f" (age {age})",
        )
        content.add_field(name="Country", value=country)
        content.add_field(name="Height", value=f"{height} cm" if height else "unknown")
        content.add_field(name="Weight", value=f"{weight} kg" if weight else "unknown")

        await ctx.send(embed=content)
Пример #11
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
Пример #12
0
    async def stock(self, ctx: commands.Context, *, symbol):
        """
        Get price data for the US stock market

        Example:
            >stock $TSLA
        """
        FINNHUB_API_QUOTE_URL = "https://finnhub.io/api/v1/quote"
        FINNHUB_API_PROFILE_URL = "https://finnhub.io/api/v1/stock/profile2"

        symbol = symbol.upper()
        params = {"symbol": symbol.strip("$"), "token": FINNHUB_TOKEN}
        async with self.bot.session.get(FINNHUB_API_QUOTE_URL,
                                        params=params) as response:
            quote_data = await response.json(loads=orjson.loads)

        error = quote_data.get("error")
        if error:
            raise exceptions.CommandError(error)

        if quote_data["c"] == 0:
            raise exceptions.CommandWarning("Company not found")

        async with self.bot.session.get(FINNHUB_API_PROFILE_URL,
                                        params=params) as response:
            company_profile = await response.json(loads=orjson.loads)

        change = float(quote_data["c"]) - float(quote_data["pc"])
        GAINS = change > 0

        arrow_emoji = emojis.GREEN_UP if GAINS else emojis.RED_DOWN
        percentage = (
            (float(quote_data["c"]) / float(quote_data["pc"])) - 1) * 100
        market_cap = int(company_profile["marketCapitalization"])

        def get_money(key):
            return f"${quote_data[key]}"

        if company_profile.get("name") is not None:
            content = discord.Embed(
                title=
                f"${company_profile['ticker']} | {company_profile['name']}", )
            content.set_thumbnail(url=company_profile.get("logo"))
            content.set_footer(text=company_profile["exchange"])
        else:
            content = discord.Embed(title=f"${symbol}")

        content.add_field(
            name="Change",
            value=
            f"{'+' if GAINS else '-'}${abs(change):.2f}{arrow_emoji}\n({percentage:.2f}%)",
        )
        content.add_field(name="Open", value=get_money("o"))
        content.add_field(name="Previous close", value=get_money("pc"))
        content.add_field(name="Current price", value=get_money("c"))
        content.add_field(name="Today's high", value=get_money("h"))
        content.add_field(name="Today's low", value=get_money("l"))
        content.add_field(name="Market capitalization",
                          value=f"${market_cap:,}",
                          inline=False)

        content.colour = discord.Color.green() if GAINS else discord.Color.red(
        )
        content.timestamp = arrow.get(quote_data["t"]).datetime

        await ctx.send(embed=content)
Пример #13
0
    async def instagram(self, ctx: commands.Context, *links):
        """Retrieve images from one or more instagram posts"""
        urls = []
        download = False

        for link in links:
            if link.lower() in ["-d", "--download"]:
                download = True
            else:
                urls.append(link)

        if len(urls) > 5:
            raise exceptions.CommandWarning("Only 5 links at a time please!")

        for post_url in urls:
            result = regex.findall("/(p|reel|tv)/(.*?)(/|\\Z)", post_url)
            if result:
                shortcode = result[0][1]
            else:
                shortcode = post_url.strip("/").split("/")[0]

            post_url = f"https://www.instagram.com/p/{shortcode}"
            try:
                post = await self.ig.extract(shortcode)
            except instagram.ExpiredCookie:
                raise exceptions.CommandError(
                    "The Instagram login cookie has expired, please ask my developer to reauthenticate!"
                )
            except instagram.InstagramError as e:
                raise exceptions.CommandError(e.message)

            if download:
                # send as files
                caption = "\n".join([
                    f":bust_in_silhouette: **@{post.user.username}**",
                    f":calendar: <t:{post.timestamp}:d>",
                    f":link: <{post_url}>",
                ])
                files = []
                # discord normally has 8MB file size limit, but it can be increased in some guilds
                max_filesize = getattr(ctx.guild, "filesize_limit", 8388608)
                for n, media in enumerate(post.media, start=1):
                    ext = "mp4" if media.media_type == instagram.MediaType.VIDEO else "jpg"
                    dateformat = arrow.get(post.timestamp).format("YYMMDD")
                    filename = f"{dateformat}-@{post.user.username}-{shortcode}-{n}.{ext}"

                    # The url params are unescaped by aiohttp's built-in yarl
                    # This causes problems with the hash-based request signing that instagram uses
                    # Thankfully you can plug your own yarl.URL into session.get(), with encoded=True
                    async with self.bot.session.get(
                        yarl.URL(media.url, encoded=True)) as response:
                        if (int(
                                response.headers.get("content-length",
                                                     max_filesize)) >
                                max_filesize):
                            caption += f"\n{media.url}"
                        else:
                            buffer = io.BytesIO(await response.read())
                            files.append(
                                discord.File(fp=buffer, filename=filename))

                # send files to discord
                await ctx.send(caption, files=files)

            else:
                # send as embeds
                content = discord.Embed(color=random.choice(self.ig_colors))
                content.set_author(name=f"@{post.user.username}",
                                   icon_url=post.user.avatar_url,
                                   url=post_url)
                for n, media in enumerate(post.media, start=1):
                    if n == len(post.media):
                        content.timestamp = arrow.get(post.timestamp).datetime
                    if media.media_type == instagram.MediaType.VIDEO:
                        await ctx.send(media.url)
                    else:
                        content.set_image(url=media.url)
                        await ctx.send(embed=content)
                    content._author = None

        # finally delete discord automatic embed
        try:
            await ctx.message.edit(suppress=True)
        except (discord.Forbidden, discord.NotFound):
            pass