Skip to content

Feature/capr 21 purge branch update - #74

Merged
shamikkarkhanis merged 4 commits into
developfrom
feature/capr-21-purge-branch-update
Feb 10, 2026
Merged

Feature/capr 21 purge branch update#74
shamikkarkhanis merged 4 commits into
developfrom
feature/capr-21-purge-branch-update

Conversation

@shamikkarkhanis

@shamikkarkhanis shamikkarkhanis commented Feb 10, 2026

Copy link
Copy Markdown
Member

Summary by Sourcery

Add a moderation purge command cog and update modal type constraints.

New Features:

  • Introduce a PurgeCog providing a /purge slash command to delete messages by count or time-based duration.

Enhancements:

  • Tighten the generic type constraint on CallbackModal to be explicitly bound to the CallbackModal class.

@sourcery-ai

sourcery-ai Bot commented Feb 10, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Implements a new /purge moderation slash command with count- and duration-based deletion modes, tightens type constraints for CallbackModal, and wires the purge flow through shared success/error embed helpers for consistent user feedback.

Class diagram for PurgeCog and updated CallbackModal

classDiagram
    class BaseModal

    class CallbackModal~T~ {
    }

    class PurgeCog {
        - commands.Bot bot
        - logging.Logger log
        + __init__(bot: commands.Bot) None
        + parse_duration(duration: str) timedelta | None
        + _handle_purge_count(amount: int, channel: discord.TextChannel) tuple[bool, discord.Embed]
        + _handle_purge_duration(duration: str, channel: discord.TextChannel) tuple[bool, discord.Embed]
        + purge(interaction: discord.Interaction, amount: int | None, duration: str | None) None
    }

    class discord_TextChannel {
        + purge(limit: int) list[discord.Message]
        + purge(after: datetime) list[discord.Message]
    }

    class embeds_helpers {
        + error_embed(description: str) discord.Embed
        + success_embed(title: str, description: str) discord.Embed
    }

    BaseModal <|-- CallbackModal
    commands_Cog <|-- PurgeCog
    PurgeCog --> discord_TextChannel : uses
    PurgeCog --> embeds_helpers : uses

    note for CallbackModal "T is now constrained to CallbackModal"
Loading

File-Level Changes

Change Details Files
Tighten the generic type bound on CallbackModal to improve typing for modal callbacks.
  • Update CallbackModal generic declaration to constrain T to CallbackModal for better type inference and static checking
capy_discord/ui/modal.py
Add a new PurgeCog providing a /purge slash command that deletes messages by count or duration with standardized success/error embeds.
  • Introduce PurgeCog cog class that depends on a discord.ext.commands.Bot instance
  • Implement parse_duration helper to convert duration strings like 1d2h3m into timedeltas
  • Add internal helpers to handle purge-by-count and purge-by-duration with validation and feedback embeds
  • Implement the /purge app command with permissions checks, argument validation, ephemeral responses, and use of shared error_embed/success_embed utilities
  • Register the PurgeCog via an async setup function for extension loading
capy_discord/exts/tools/purge.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@shamikkarkhanis
shamikkarkhanis changed the base branch from main to develop February 10, 2026 04:44

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 2 issues, and left some high level feedback:

  • The new CallbackModal[T: "CallbackModal"] type parameter constraint in ui/modal.py is self-referential and likely invalid in practice; consider reverting to a standard TypeVar bound or a Generic-based approach that doesn’t refer to the class being defined.
  • In the purge cog, the duration help text suggests formats like "1h 30m", but parse_duration only accepts contiguous strings like "1h30m"; either relax the regex to allow spaces or update the user-facing description to match the accepted format.
  • Both _handle_purge_count and _handle_purge_duration return a (bool, Embed) tuple, but the boolean is never used by callers; consider returning just the embed or raising a user-friendly error to simplify the API.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The new `CallbackModal[T: "CallbackModal"]` type parameter constraint in `ui/modal.py` is self-referential and likely invalid in practice; consider reverting to a standard `TypeVar` bound or a `Generic`-based approach that doesn’t refer to the class being defined.
- In the purge cog, the `duration` help text suggests formats like `"1h 30m"`, but `parse_duration` only accepts contiguous strings like `"1h30m"`; either relax the regex to allow spaces or update the user-facing description to match the accepted format.
- Both `_handle_purge_count` and `_handle_purge_duration` return a `(bool, Embed)` tuple, but the boolean is never used by callers; consider returning just the embed or raising a user-friendly error to simplify the API.

## Individual Comments

### Comment 1
<location> `capy_discord/ui/modal.py:25` </location>
<code_context>


-class CallbackModal[T](BaseModal):
+class CallbackModal[T: "CallbackModal"](BaseModal):
     """A modal that delegates submission logic to a callback function.

</code_context>

<issue_to_address>
**issue:** Revisit generic type parameter bound on `CallbackModal`.

`T: "CallbackModal"` makes the type parameter self-referential to the class being declared, which is probably not intended and can confuse type checkers. If you’re trying to model methods that return the instance type, consider using `Self` (PEP 673) where available, or keep `T = TypeVar("T", bound="CallbackModal")` defined externally and use `T` only in method signatures. The class-level type parameter doesn’t seem to add type-safety here and instead complicates the type model.
</issue_to_address>

### Comment 2
<location> `capy_discord/exts/tools/purge.py:92-98` </location>
<code_context>
+            return
+
+        channel = interaction.channel
+        if not isinstance(channel, discord.TextChannel):
+            await interaction.response.send_message(
+                embed=error_embed(description="This command can only be used in text channels."),
</code_context>

<issue_to_address>
**suggestion:** Channel type check excludes threads and other text-based channels.

Limiting this to `discord.TextChannel` prevents the command from working in text threads and other messageable channels that also support purging. To support those, check against a broader, message-capable type (e.g. `discord.abc.Messageable` or a union of `TextChannel`/`Thread` types) instead of only `TextChannel`.

```suggestion
        channel = interaction.channel
        if not isinstance(channel, (discord.TextChannel, discord.Thread)):
            await interaction.response.send_message(
                embed=error_embed(description="This command can only be used in text-based channels (e.g. channels and threads)."),
                ephemeral=True,
            )
            return
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread capy_discord/ui/modal.py


class CallbackModal[T](BaseModal):
class CallbackModal[T: "CallbackModal"](BaseModal):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: Revisit generic type parameter bound on CallbackModal.

T: "CallbackModal" makes the type parameter self-referential to the class being declared, which is probably not intended and can confuse type checkers. If you’re trying to model methods that return the instance type, consider using Self (PEP 673) where available, or keep T = TypeVar("T", bound="CallbackModal") defined externally and use T only in method signatures. The class-level type parameter doesn’t seem to add type-safety here and instead complicates the type model.

Comment on lines +92 to +98
channel = interaction.channel
if not isinstance(channel, discord.TextChannel):
await interaction.response.send_message(
embed=error_embed(description="This command can only be used in text channels."),
ephemeral=True,
)
return

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Channel type check excludes threads and other text-based channels.

Limiting this to discord.TextChannel prevents the command from working in text threads and other messageable channels that also support purging. To support those, check against a broader, message-capable type (e.g. discord.abc.Messageable or a union of TextChannel/Thread types) instead of only TextChannel.

Suggested change
channel = interaction.channel
if not isinstance(channel, discord.TextChannel):
await interaction.response.send_message(
embed=error_embed(description="This command can only be used in text channels."),
ephemeral=True,
)
return
channel = interaction.channel
if not isinstance(channel, (discord.TextChannel, discord.Thread)):
await interaction.response.send_message(
embed=error_embed(description="This command can only be used in text-based channels (e.g. channels and threads)."),
ephemeral=True,
)
return

@shamikkarkhanis
shamikkarkhanis merged commit e144173 into develop Feb 10, 2026
4 checks passed
@shamikkarkhanis
shamikkarkhanis deleted the feature/capr-21-purge-branch-update branch February 10, 2026 05:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant