Пример #1
0
 async def clear_cache_command(
     self,
     ctx: commands.Context,
     package_name: Union[PackageName, allowed_strings("*")]  # noqa: F722
 ) -> None:
     """Clear the persistent redis cache for `package`."""
     if await doc_cache.delete(package_name):
         await ctx.send(
             f"Successfully cleared the cache for `{package_name}`.")
     else:
         await ctx.send("No keys matching the package found.")
Пример #2
0
    async def metabase_extract(
            self,
            ctx: Context,
            question_id: int,
            extension: allowed_strings("csv", "json") = "csv") -> None:
        """
        Extract data from a metabase question.

        You can find the question_id at the end of the url on metabase.
        I.E. /question/{question_id}

        If, instead of an id, there is a long URL, make sure to save the question first.

        If you want to extract data from a question within a dashboard, click the
        question title at the top left of the chart to go directly to that page.

        Valid extensions are: csv and json.
        """
        await ctx.trigger_typing()

        # Make sure we have a session token before running anything
        await self.init_task

        url = f"{MetabaseConfig.base_url}/api/card/{question_id}/query/{extension}"

        async with self.bot.http_session.post(url,
                                              headers=self.headers,
                                              raise_for_status=True) as resp:
            if extension == "csv":
                out = await resp.text(encoding="utf-8")
                # Save the output for use with int e
                self.exports[question_id] = list(csv.DictReader(StringIO(out)))

            elif extension == "json":
                out = await resp.json(encoding="utf-8")
                # Save the output for use with int e
                self.exports[question_id] = out

                # Format it nicely for human eyes
                out = json.dumps(out, indent=4, sort_keys=True)

        paste_link = await send_to_paste_service(out, extension=extension)
        if paste_link:
            message = f":+1: {ctx.author.mention} Here's your link: {paste_link}"
        else:
            message = f":x: {ctx.author.mention} Link service is unavailible."
        await ctx.send(
            f"{message}\nYou can also access this data within internal eval by doing: "
            f"`bot.get_cog('Metabase').exports[{question_id}]`")
Пример #3
0
    async def infraction_edit(
            self,
            ctx: Context,
            infraction: Infraction,
            duration: t.Union[Expiry,
                              allowed_strings("p", "permanent"),
                              None],  # noqa: F821
            *,
            reason: str = None) -> None:
        """
        Edit the duration and/or the reason of an infraction.

        Durations are relative to the time of updating and should be appended with a unit of time.
        Units (∗case-sensitive):
        \u2003`y` - years
        \u2003`m` - months∗
        \u2003`w` - weeks
        \u2003`d` - days
        \u2003`h` - hours
        \u2003`M` - minutes∗
        \u2003`s` - seconds

        Use "l", "last", or "recent" as the infraction ID to specify that the most recent infraction
        authored by the command invoker should be edited.

        Use "p" or "permanent" to mark the infraction as permanent. Alternatively, an ISO 8601
        timestamp can be provided for the duration.
        """
        if duration is None and reason is None:
            # Unlike UserInputError, the error handler will show a specified message for BadArgument
            raise commands.BadArgument(
                "Neither a new expiry nor a new reason was specified.")

        infraction_id = infraction["id"]

        request_data = {}
        confirm_messages = []
        log_text = ""

        if duration is not None and not infraction['active']:
            if (infr_type := infraction['type']) in ('note', 'warning'):
                await ctx.send(
                    f":x: Cannot edit the expiration of a {infr_type}.")
            else:
                await ctx.send(
                    ":x: Cannot edit the expiration of an expired infraction.")
            return
Пример #4
0
    async def infraction_append(
            self,
            ctx: Context,
            infraction: Infraction,
            duration: t.Union[Expiry,
                              allowed_strings("p", "permanent"),
                              None],  # noqa: F821
            *,
            reason: str = None) -> None:
        """
        Append text and/or edit the duration of an infraction.

        Durations are relative to the time of updating and should be appended with a unit of time.
        Units (∗case-sensitive):
        \u2003`y` - years
        \u2003`m` - months∗
        \u2003`w` - weeks
        \u2003`d` - days
        \u2003`h` - hours
        \u2003`M` - minutes∗
        \u2003`s` - seconds

        Use "l", "last", or "recent" as the infraction ID to specify that the most recent infraction
        authored by the command invoker should be edited.

        Use "p" or "permanent" to mark the infraction as permanent. Alternatively, an ISO 8601
        timestamp can be provided for the duration.

        If a previous infraction reason does not end with an ending punctuation mark, this automatically
        adds a period before the amended reason.
        """
        old_reason = infraction["reason"]

        if old_reason is not None and reason is not None:
            add_period = not old_reason.endswith((".", "!", "?"))
            reason = old_reason + (". " if add_period else " ") + reason

        await self.infraction_edit(ctx, infraction, duration, reason=reason)
Пример #5
0
    async def infraction_edit(
            self,
            ctx: Context,
            infraction_id: t.Union[
                int, allowed_strings("l", "last", "recent")],  # noqa: F821
            duration: t.Union[Expiry,
                              allowed_strings("p", "permanent"),
                              None],  # noqa: F821
            *,
            reason: str = None) -> None:
        """
        Edit the duration and/or the reason of an infraction.

        Durations are relative to the time of updating and should be appended with a unit of time.
        Units (∗case-sensitive):
        \u2003`y` - years
        \u2003`m` - months∗
        \u2003`w` - weeks
        \u2003`d` - days
        \u2003`h` - hours
        \u2003`M` - minutes∗
        \u2003`s` - seconds

        Use "l", "last", or "recent" as the infraction ID to specify that the most recent infraction
        authored by the command invoker should be edited.

        Use "p" or "permanent" to mark the infraction as permanent. Alternatively, an ISO 8601
        timestamp can be provided for the duration.
        """
        if duration is None and reason is None:
            # Unlike UserInputError, the error handler will show a specified message for BadArgument
            raise commands.BadArgument(
                "Neither a new expiry nor a new reason was specified.")

        # Retrieve the previous infraction for its information.
        if isinstance(infraction_id, str):
            params = {"actor__id": ctx.author.id, "ordering": "-inserted_at"}
            infractions = await self.bot.api_client.get("bot/infractions",
                                                        params=params)

            if infractions:
                old_infraction = infractions[0]
                infraction_id = old_infraction["id"]
            else:
                await ctx.send(
                    ":x: Couldn't find most recent infraction; you have never given an infraction."
                )
                return
        else:
            old_infraction = await self.bot.api_client.get(
                f"bot/infractions/{infraction_id}")

        request_data = {}
        confirm_messages = []
        log_text = ""

        if duration is not None and not old_infraction['active']:
            if reason is None:
                await ctx.send(
                    ":x: Cannot edit the expiration of an expired infraction.")
                return
            confirm_messages.append(
                "expiry unchanged (infraction already expired)")
        elif isinstance(duration, str):
            request_data['expires_at'] = None
            confirm_messages.append("marked as permanent")
        elif duration is not None:
            request_data['expires_at'] = duration.isoformat()
            expiry = time.format_infraction_with_duration(
                request_data['expires_at'])
            confirm_messages.append(f"set to expire on {expiry}")
        else:
            confirm_messages.append("expiry unchanged")

        if reason:
            request_data['reason'] = reason
            confirm_messages.append("set a new reason")
            log_text += f"""
                Previous reason: {old_infraction['reason']}
                New reason: {reason}
            """.rstrip()
        else:
            confirm_messages.append("reason unchanged")

        # Update the infraction
        new_infraction = await self.bot.api_client.patch(
            f'bot/infractions/{infraction_id}',
            json=request_data,
        )

        # Re-schedule infraction if the expiration has been updated
        if 'expires_at' in request_data:
            # A scheduled task should only exist if the old infraction wasn't permanent
            if old_infraction['expires_at']:
                self.infractions_cog.cancel_task(new_infraction['id'])

            # If the infraction was not marked as permanent, schedule a new expiration task
            if request_data['expires_at']:
                self.infractions_cog.schedule_task(new_infraction['id'],
                                                   new_infraction)

            log_text += f"""
                Previous expiry: {old_infraction['expires_at'] or "Permanent"}
                New expiry: {new_infraction['expires_at'] or "Permanent"}
            """.rstrip()

        changes = ' & '.join(confirm_messages)
        await ctx.send(
            f":ok_hand: Updated infraction #{infraction_id}: {changes}")

        # Get information about the infraction's user
        user_id = new_infraction['user']
        user = ctx.guild.get_member(user_id)

        if user:
            user_text = f"{user.mention} (`{user.id}`)"
            thumbnail = user.avatar_url_as(static_format="png")
        else:
            user_text = f"`{user_id}`"
            thumbnail = None

        # The infraction's actor
        actor_id = new_infraction['actor']
        actor = ctx.guild.get_member(actor_id) or f"`{actor_id}`"

        await self.mod_log.send_log_message(icon_url=constants.Icons.pencil,
                                            colour=discord.Colour.blurple(),
                                            title="Infraction edited",
                                            thumbnail=thumbnail,
                                            text=textwrap.dedent(f"""
                Member: {user_text}
                Actor: {actor}
                Edited by: {ctx.message.author}{log_text}
            """))
Пример #6
0
    async def metabase_extract(
            self,
            ctx: Context,
            question_id: int,
            extension: allowed_strings("csv", "json") = "csv") -> None:
        """
        Extract data from a metabase question.

        You can find the question_id at the end of the url on metabase.
        I.E. /question/{question_id}

        If, instead of an id, there is a long URL, make sure to save the question first.

        If you want to extract data from a question within a dashboard, click the
        question title at the top left of the chart to go directly to that page.

        Valid extensions are: csv and json.
        """
        async with ctx.typing():

            # Make sure we have a session token before running anything
            await self.init_task

            url = f"{MetabaseConfig.url}/card/{question_id}/query/{extension}"
            try:
                async with self.bot.http_session.post(
                        url, headers=self.headers,
                        raise_for_status=True) as resp:
                    if extension == "csv":
                        out = await resp.text()
                        # Save the output for use with int e
                        self.exports[question_id] = list(
                            csv.DictReader(StringIO(out)))

                    elif extension == "json":
                        out = await resp.json()
                        # Save the output for use with int e
                        self.exports[question_id] = out

                        # Format it nicely for human eyes
                        out = json.dumps(out, indent=4, sort_keys=True)
            except ClientResponseError as e:
                if e.status == 403:
                    # User doesn't have access to the given question
                    log.warning(
                        f"Failed to auth with Metabase for question {question_id}."
                    )
                    await ctx.send(
                        f":x: {ctx.author.mention} Failed to auth with Metabase for that question."
                    )
                else:
                    # User credentials are invalid, or the refresh failed.
                    # Delete the expiry time, to force a refresh on next startup.
                    await self.session_info.delete("session_expiry")
                    log.exception(
                        "Session token is invalid or refresh failed.")
                    await ctx.send(
                        f":x: {ctx.author.mention} Session token is invalid or refresh failed."
                    )
                return

            paste_link = await send_to_paste_service(out, extension=extension)
            if paste_link:
                message = f":+1: {ctx.author.mention} Here's your link: {paste_link}"
            else:
                message = f":x: {ctx.author.mention} Link service is unavailible."
            await ctx.send(
                f"{message}\nYou can also access this data within internal eval by doing: "
                f"`bot.get_cog('Metabase').exports[{question_id}]`")