feat: Suggestion Command Draft PR
Description
When the command is run makes a post in a defined channel which has two buttons, approve and deny, those buttons can only be interacted with by admins. It also will add thumps up and down reaction for user votes and create a thread attached to it.
Guidelines
-
My code follows the style guidelines of this project (formatted with Ruff)
-
I have performed a self-review of my own code
-
I have commented my code, particularly in hard-to-understand areas
-
I have made corresponding changes to the documentation if needed
-
My changes generate no new warnings
-
I have tested this change
-
Any dependent changes have been merged and published in downstream modules
-
I have added all appropriate labels to this PR
-
[X] I have followed all of these guidelines.
Additional Information
Most of it has been completed, the major missing component is button persistence on restart.
Summary by Sourcery
Implement a suggestion submission feature allowing users to submit suggestions via a slash command, log them to a configurable channel, and enable admin-only approve/deny interactions with vote tracking and threaded discussions.
New Features:
- Add /suggest slash command that opens a SuggestionModal for users to submit suggestions
- Introduce SuggestionModal and ButtonView to post suggestions as embeds with Approve/Deny buttons for admins
- Enable users to vote on suggestions via 👍/👎 reactions and automatically create a discussion thread for each suggestion
Enhancements:
- Add suggestion_log_id field to guild configuration with corresponding getters and setters
- Implement SuggestionController for ensuring guild records and creating suggestion entries in the database
- Register persistent ButtonView in bot startup to handle suggestion approval/denial interactions
Reviewer's Guide
Introduces a new suggestion command flow: a modal for user submissions, configuration UI for setting the suggestion channel, database support for persisting suggestions and channel settings, and interactive approve/deny buttons with reactions and thread creation.
Sequence Diagram: Admin Reviewing a Suggestion
sequenceDiagram
actor Admin
participant DiscordClient as Discord Client
participant SuggestionMessage as Suggestion Message (in Channel)
participant ButtonView as ButtonView (Tux Bot Component)
participant DiscordAPI as Discord API
Admin->>DiscordClient: Clicks "Approve" or "Deny" button on SuggestionMessage
DiscordClient->>ButtonView: Button interaction received
ButtonView->>ButtonView: Perform permission check (e.g., checks.has_pl(5))
alt Admin has required permissions
ButtonView->>ButtonView: Update internal embed state (e.g., status to "Accepted"/"Rejected", add reviewer)
ButtonView->>DiscordAPI: Edit original SuggestionMessage with updated embed and buttons
else Admin lacks permissions
ButtonView->>Admin: (Interaction fails or ephemeral error - not explicitly shown in diff)
end
Class Diagram: New UI Components - SuggestionModal and ButtonView
classDiagram
class SuggestionModal {
<<UI Modal>>
+bot: commands.Bot
+config: GuildConfigController
+suggestion_title: discord.ui.TextInput
+suggestion_description: discord.ui.TextInput
+__init__(title: str, bot: commands.Bot)
+on_submit(interaction: discord.Interaction) void
}
note for SuggestionModal "New modal for users to submit suggestions."
class ButtonView {
<<UI View>>
+embed: discord.Embed
+__init__(embed: discord.Embed)
+green_button(interaction: discord.Interaction, button: discord.ui.Button) void
note for green_button "Handles 'Accept' button. Requires @checks.has_pl(5)."
+red_button(interaction: discord.Interaction, button: discord.ui.Button) void
note for red_button "Handles 'Deny' button. Requires @checks.has_pl(5)."
}
note for ButtonView "New view with 'Accept' and 'Deny' buttons for suggestion messages."
SuggestionModal --|> discord.ui.Modal
ButtonView --|> discord.ui.View
SuggestionModal ..> GuildConfigController : uses
SuggestionModal ..> discord.Embed : creates
ButtonView ..> discord.Embed : manipulates
Class Diagram: New Backend Components - Suggestion Cog and SuggestionController
classDiagram
class Suggestion {
<<Cog>>
+bot: commands.Bot
+__init__(bot: commands.Bot)
+suggestion(interaction: discord.Interaction) void
note for suggestion "App command '/suggest'. Sends SuggestionModal to the user."
}
note for Suggestion "New Cog to handle the '/suggest' application command."
class SuggestionController {
<<Database Controller>>
+table: PrismaTable_Suggestion "prisma.suggestion table accessor"
+guild_table: PrismaTable_Guild "prisma.guild table accessor"
+__init__()
+ensure_guild_exists(guild_id: int) Guild
+create_suggestion(suggestion_title: str, suggestion_description: str, suggestion_status: SuggestionStatus, suggestion_user_id: int, guild_id: int) Suggestion
}
note for SuggestionController "New controller for creating and managing suggestion records in the database."
Suggestion --|> discord.ext.commands.Cog
Suggestion ..> SuggestionModal : creates and sends
SuggestionController ..> prisma.models.Guild : uses
SuggestionController ..> prisma.models.Suggestion : uses
SuggestionController ..> prisma.enums.SuggestionStatus : uses
Class Diagram: Updates to GuildConfigController for Suggestions
classDiagram
class GuildConfigController {
<<Existing Controller>>
#get_guild_config_field_value(guild_id, field_name)
#ensure_guild_exists(guild_id)
#table
+get_log_channel(guild_id: int, log_type: str) int | None
note for get_log_channel "Internal 'log_channel_ids' map updated to include 'suggestion'."
+get_suggestion_log_channel(guild_id: int) int | None
note for get_suggestion_log_channel "New method to retrieve the suggestion log channel ID."
+update_suggestion_log_id(guild_id: int, suggestion_log_id: int) GuildConfig | None
note for update_suggestion_log_id "New method to set or update the suggestion log channel ID."
}
note for GuildConfigController "Modified to manage configuration for suggestion log channels."
Class Diagram: Updates to ConfigSetLogChannel View for Suggestions
classDiagram
class ConfigSetLogChannel {
<<Existing UI View>>
+db: GuildConfigController
+__init__(timeout: float)
%% Other select handlers like _set_join_log(...)
+_set_suggestion_log(interaction: discord.Interaction, select: discord.ui.ChannelSelect) void
note for _set_suggestion_log "New discord.ui.ChannelSelect element and handler added to set the suggestion channel."
}
note for ConfigSetLogChannel "Modified to include a UI ChannelSelect dropdown for configuring the suggestion log channel."
ConfigSetLogChannel --|> discord.ui.View
ConfigSetLogChannel ..> GuildConfigController : uses (via self.db)
Class Diagram: Updates to DatabaseController and Tux Bot for Suggestions
classDiagram
class DatabaseController {
<<Existing Controller Aggregator>>
+note: NoteController
+reminder: ReminderController
+snippet: SnippetController
+guild: GuildController
+guild_config: GuildConfigController
+suggestion: SuggestionController
note for suggestion "New SuggestionController instance added for suggestion data management."
+__init__()
}
note for DatabaseController "Modified to integrate the new SuggestionController."
class Tux {
<<Bot Class>>
%% Other attributes and methods
+on_ready() void
note for on_ready "Modified to add ButtonView persistently using self.add_view(). This is a step towards button persistence on restart."
}
note for Tux "Bot class updated to register the new ButtonView for suggestions."
Tux --|> discord.ext.commands.Bot
DatabaseController ..> SuggestionController : aggregates
Tux ..> ButtonView : registers globally
File-Level Changes
| Change | Details | Files |
|---|---|---|
| Add suggestion channel configuration support |
|
tux/database/controllers/guild_config.pytux/ui/views/config.py |
| Register persistent suggestion button view on bot startup |
|
tux/bot.py |
| Implement suggestion modal and interactive button view |
|
tux/ui/modals/suggestion.py |
| Add SuggestionController for database operations |
|
tux/database/controllers/suggestion.py |
| Add slash command Cog for suggestions |
|
tux/cogs/misc/suggestions.py |
Possibly linked issues
- #1: Implements the /suggest command, adding channel posting, reactions, admin buttons, and discussion threads as requested.
Tips and commands
Interacting with Sourcery
- Trigger a new review: Comment
@sourcery-ai reviewon 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 issueto create an issue from it. - Generate a pull request title: Write
@sourcery-aianywhere in the pull request title to generate a title at any time. You can also comment@sourcery-ai titleon the pull request to (re-)generate the title at any time. - Generate a pull request summary: Write
@sourcery-ai summaryanywhere in the pull request body to generate a PR summary at any time exactly where you want it. You can also comment@sourcery-ai summaryon the pull request to (re-)generate the summary at any time. - Generate reviewer's guide: Comment
@sourcery-ai guideon the pull request to (re-)generate the reviewer's guide at any time. - Resolve all Sourcery comments: Comment
@sourcery-ai resolveon 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 dismisson 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 reviewto 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
- Contact our support team for questions or feedback.
- Visit our documentation for detailed guides and information.
- Keep in touch with the Sourcery team by following us on X/Twitter, LinkedIn or GitHub.
will need rewrites following completion of #853