-
-
Notifications
You must be signed in to change notification settings - Fork 1
feat: allow guild setting apis (partial) #99
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
chore(ui): increase byte screen logo size
* feat: add guild configuration command * feat: add litestar office hours command * fix: centralize options for slashcmd and dropdown * ci: apply pre-commit * chore: dependency updates * chore: clean up utils * fix: move moved import * fix: attempt to fix broken modal submission * feat: finalize working config selection and sub selections * ci: for some reason pre-commit is stupid and changed every fucking file
Reviewer's GuideThis PR refactors and modularizes the Byte bot by reorganizing style assets, extracting and cleaning up core type logic, extending the Guild configuration API and interactive config UI, enhancing logging/settings, revamping embed views, improving join event handling, and updating registry to support new plugins and views. Sequence Diagram for Guild Join ProcesssequenceDiagram
actor DiscordServer as Discord Server
participant ByteBot as Byte Bot
participant BackendAPI as Backend API (Litestar)
participant DevChannel as Dev Discord Channel
DiscordServer->>ByteBot: Guild Join Event (guild)
ByteBot->>BackendAPI: POST /api/guilds/create?guild_id={id}&guild_name={name}
alt Creation Successful
BackendAPI-->>ByteBot: 201 CREATED
ByteBot->>DevChannel: Send Embed (Guild Joined Successfully)
else Creation Failed
BackendAPI-->>ByteBot: Error Response
ByteBot->>DevChannel: Send Embed (Guild Join Failed to Add to DB)
else API Connection Error
ByteBot-->>ByteBot: Log Exception
ByteBot->>DevChannel: Send Embed (Guild Join Failed - API Connect Error)
end
ByteBot->>ByteBot: Sync App Commands for Guild
Sequence Diagram for Member Join ProcesssequenceDiagram
actor DiscordUser as New User
participant ByteBot as Byte Bot
DiscordUser->>ByteBot: Member Join Event (member)
alt User is not a Bot
ByteBot->>DiscordUser: Send DM (Welcome Message)
end
Sequence Diagram for Help Thread View SetupsequenceDiagram
participant User as User
participant DiscordClient as Discord Client
participant HelpThreadView as HelpThreadView
participant GuildsService as GuildsService
participant Database as Database
User->>DiscordClient: Creates a help thread
DiscordClient->>HelpThreadView: Instantiate HelpThreadView(author, guild_id, bot)
HelpThreadView->>HelpThreadView: call setup()
HelpThreadView->>GuildsService: get(guild_id)
GuildsService->>Database: Fetch guild_settings for guild_id
Database-->>GuildsService: guild_settings
GuildsService-->>HelpThreadView: guild_settings
alt Guild settings and GitHub config found
HelpThreadView->>HelpThreadView: Add Button("Open GitHub Issue", url)
else No GitHub config
HelpThreadView->>HelpThreadView: Log warning
end
HelpThreadView-->>DiscordClient: View with/without button
DiscordClient-->>User: Display thread message with view
Sequence Diagram for Schedule Office Hours CommandsequenceDiagram
actor User
participant DiscordClient as Discord Client
participant LitestarCommands as LitestarCommands Cog
participant DiscordAPI as Discord API
User->>DiscordClient: Executes /schedule-office-hours command (delay?)
DiscordClient->>LitestarCommands: schedule_office_hours(interaction, delay)
LitestarCommands->>LitestarCommands: Calculate next Friday's start/end time (start_dt, end_dt)
LitestarCommands->>DiscordAPI: Get existing scheduled events for guild
DiscordAPI-->>LitestarCommands: existing_events
alt No existing Office Hours event for that day
LitestarCommands->>DiscordAPI: create_scheduled_event(name="Office Hours", start_time, end_time, ...)
DiscordAPI-->>LitestarCommands: Event created
LitestarCommands->>DiscordClient: Send Message("Office Hours event scheduled: ...")
else Event already exists
LitestarCommands->>DiscordClient: Send Message("An Office Hours event is already scheduled...", ephemeral=true)
end
Sequence Diagram for Create GitHub Issue Context MenusequenceDiagram
actor User
participant DiscordClient as Discord Client
participant GitHubCommands as GitHubCommands Cog
participant GitHubIssueModal as GitHubIssue Modal
participant GitHubAPI as GitHub API
User->>DiscordClient: Right-click message -> "Create GitHub Issue"
DiscordClient->>GitHubCommands: create_github_issue_modal(interaction, message)
GitHubCommands->>DiscordClient: interaction.response.send_modal(GitHubIssueModal(message))
User->>GitHubIssueModal: Fills out Title, Description, MCVE, Logs, Version
GitHubIssueModal->>User: Submits Modal
GitHubIssueModal->>GitHubAPI: POST /repos/{owner}/{repo}/issues (title, body)
alt Issue Creation Successful
GitHubAPI-->>GitHubIssueModal: Success Response (issue_url)
GitHubIssueModal->>DiscordClient: interaction.response.send_message("GitHub Issue created: {issue_url}")
else Issue Creation Failed
GitHubAPI-->>GitHubIssueModal: Error Response
GitHubIssueModal->>DiscordClient: interaction.response.send_message("Issue creation failed.", ephemeral=true)
else Exception
GitHubIssueModal->>GitHubIssueModal: Log Exception
GitHubIssueModal->>DiscordClient: interaction.response.send_message("An error occurred: {error}", ephemeral=true)
end
Class Diagram for New Configuration UI (byte_bot.byte.views.config)classDiagram
direction LR
class View~discord.ui.View~
class Modal~discord.ui.Modal~
class Button~discord.ui.Button~
class Select~discord.ui.Select~
class TextInput~discord.ui.TextInput~
class ConfigView {
+__init__(preselected: str | None)
}
ConfigView --|> View
ConfigView o-- ConfigSelect
ConfigView o-- FinishButton
ConfigView o-- CancelButton
class ConfigKeyView {
+__init__(option: dict)
}
ConfigKeyView --|> View
ConfigKeyView o-- ConfigKeySelect
ConfigKeyView o-- BackButton
ConfigKeyView o-- CancelButton
class ConfigModal {
+__init__(title: str, sub_setting: dict | None, sub_settings: list[dict] | None, option: dict | None)
+on_submit(interaction: Interaction)
+on_error(interaction: Interaction, error: Exception)
}
ConfigModal --|> Modal
ConfigModal o-- TextInput
class FinishButton {
+__init__()
+callback(interaction: Interaction)
}
FinishButton --|> Button
class BackButton {
+__init__()
+callback(interaction: Interaction)
}
BackButton --|> Button
class CancelButton {
+__init__()
+callback(interaction: Interaction)
}
CancelButton --|> Button
class ConfigSelect {
+__init__(preselected: str | None)
+callback(interaction: Interaction)
}
ConfigSelect --|> Select
class ConfigKeySelect {
+__init__(option: dict)
+callback(interaction: Interaction)
}
ConfigKeySelect --|> Select
Class Diagram for Guild Configuration Schemas (byte_bot.server.domain.guilds.schemas)classDiagram
class CamelizedBaseModel
class GitHubConfigSchema {
+guild_id: UUID
+discussion_sync: bool
+github_organization: str | None
+github_repository: str | None
}
GitHubConfigSchema --|> CamelizedBaseModel
class SOTagsConfigSchema {
+guild_id: UUID
+tag_name: str
}
SOTagsConfigSchema --|> CamelizedBaseModel
class AllowedUsersConfigSchema {
+guild_id: UUID
+user_id: UUID
}
AllowedUsersConfigSchema --|> CamelizedBaseModel
class ForumConfigSchema {
+guild_id: UUID
+help_forum: bool
+help_forum_category: str
+help_thread_auto_close: bool
+help_thread_auto_close_days: int
+help_thread_notify: bool
+help_thread_notify_roles: list[int]
+help_thread_notify_days: int
+help_thread_sync: bool
+showcase_forum: bool
+showcase_forum_category: str
+showcase_thread_auto_close: bool
+showcase_thread_auto_close_days: int
}
ForumConfigSchema --|> CamelizedBaseModel
class UpdateableGuildSetting {
+help_forum: bool
+help_forum_category: str
+help_thread_auto_close: bool
+help_thread_auto_close_days: int
+help_thread_notify: bool
+help_thread_notify_roles: list[int]
+help_thread_notify_days: int
+help_thread_sync: bool
+showcase_forum: bool
+showcase_forum_category: str
+showcase_thread_auto_close: bool
+showcase_thread_auto_close_days: int
}
UpdateableGuildSetting --|> CamelizedBaseModel
Class Diagram for Guild Configuration Services (byte_bot.server.domain.guilds.services)classDiagram
direction BT
class SQLAlchemyAsyncRepositoryService
class SQLAlchemyAsyncRepository
class SQLAlchemyAsyncSlugRepository
class GuildsService
GuildsService --|> SQLAlchemyAsyncRepositoryService
GuildsService o-- GuildsRepository
class GuildsRepository {
model_type: Guild
}
GuildsRepository --|> SQLAlchemyAsyncSlugRepository
class GitHubConfigService {
repository_type: GitHubConfigRepository
match_fields: list
+to_model(data, operation) GitHubConfig
}
GitHubConfigService --|> SQLAlchemyAsyncRepositoryService
GitHubConfigService o-- GitHubConfigRepository
class GitHubConfigRepository {
model_type: GitHubConfig
}
GitHubConfigRepository --|> SQLAlchemyAsyncRepository
class SOTagsConfigService {
repository_type: SOTagsConfigRepository
match_fields: list
+to_model(data, operation) SOTagsConfig
}
SOTagsConfigService --|> SQLAlchemyAsyncRepositoryService
SOTagsConfigService o-- SOTagsConfigRepository
class SOTagsConfigRepository {
model_type: SOTagsConfig
}
SOTagsConfigRepository --|> SQLAlchemyAsyncRepository
class AllowedUsersConfigService {
repository_type: AllowedUsersConfigRepository
match_fields: list
+to_model(data, operation) AllowedUsersConfig
}
AllowedUsersConfigService --|> SQLAlchemyAsyncRepositoryService
AllowedUsersConfigService o-- AllowedUsersConfigRepository
class AllowedUsersConfigRepository {
model_type: AllowedUsersConfig
}
AllowedUsersConfigRepository --|> SQLAlchemyAsyncRepository
class ForumConfigService {
repository_type: ForumConfigRepository
match_fields: list
+to_model(data, operation) ForumConfig
}
ForumConfigService --|> SQLAlchemyAsyncRepositoryService
ForumConfigService o-- ForumConfigRepository
class ForumConfigRepository {
model_type: ForumConfig
}
ForumConfigRepository --|> SQLAlchemyAsyncRepository
Class Diagram for Abstract and Plugin ViewsclassDiagram
class View~discord.ui.View~
class Embed~discord.Embed~
class Button~discord.ui.Button~
class Member~discord.Member~
class Bot~discord.ext.commands.Bot~
class ButtonEmbedView {
-author: int
-bot: Bot
-original_embed: Embed
-minified_embed: Embed
+__init__(author, bot, original_embed, minified_embed)
+delete_interaction_check(interaction: Interaction) bool
+delete_button_callback(interaction: Interaction)
+learn_more_button_callback(interaction: Interaction)
}
ButtonEmbedView --|> View
ButtonEmbedView o-- Button : delete_button
ButtonEmbedView o-- Button : learn_more_button
class Field~TypedDict~
class ExtendedEmbed {
+add_field_dict(field: Field) Self
+add_field_dicts(fields: list[Field]) Self
+from_field_dicts(...) Self
+deepcopy() Self
}
ExtendedEmbed --|> Embed
ExtendedEmbed ..> Field : uses
class RuffView
RuffView --|> ButtonEmbedView
class PEPView
PEPView --|> ButtonEmbedView
class HelpThreadView {
-author: Member
-bot: Bot
-guild_id: int
+__init__(author, guild_id, bot)
+setup()
+delete_interaction_check(interaction: Interaction) bool
+solve_button_callback(interaction: Interaction, button: Button)
+remove_button_callback(interaction: Interaction, button: Button)
}
HelpThreadView --|> View
HelpThreadView o-- Button : solve_button
HelpThreadView o-- Button : remove_button
HelpThreadView o-- Button : "may add Open GitHub Issue button"
Class Diagram for New Plugins (github.py, config.py)classDiagram
class Cog~discord.ext.commands.Cog~
class Modal~discord.ui.Modal~
class Bot~discord.ext.commands.Bot~
class ContextMenu~discord.app_commands.ContextMenu~
class ConfigView
class GitHubIssue {
+title_: TextInput
+description: TextInput
+mcve: TextInput
+logs: TextInput
+version: TextInput
+__init__(message: Message | None)
+on_submit(interaction: Interaction)
}
GitHubIssue --|> Modal
class GitHubCommands {
-bot: Bot
-context_menu: ContextMenu
+__init__(bot: Bot)
+create_github_issue_modal(interaction: Interaction, message: Message)
}
GitHubCommands --|> Cog
GitHubCommands o-- GitHubIssue : creates
class Config {
-bot: Bot
-config_options: list[dict]
+__init__(bot: Bot)
+_config_autocomplete(interaction: Interaction, current: str) list[Choice]
+config_rule(interaction: Interaction, setting: str | None)
}
Config --|> Cog
Config ..> ConfigView : uses
Class Diagram for Bot Core and Event Handler Changes (byte_bot.byte.bot)classDiagram
class Bot~discord.ext.commands.Bot~
class Member~discord.Member~
class Guild~discord.Guild~
class AsyncClient~httpx.AsyncClient~
class Byte {
+setup_hook()
+on_ready()
+on_command_error(ctx: Context, error: CommandError)
+on_member_join(member: Member)
+on_guild_join(guild: Guild)
}
Byte --|> Bot
Byte ..> AsyncClient : uses in on_guild_join
Class Diagram for LitestarCommands Plugin Changes (byte_bot.byte.plugins.custom.litestar)classDiagram
class Cog~discord.ext.commands.Cog~
class Bot~discord.ext.commands.Bot~
class LitestarCommands {
-bot: Bot
+__init__(bot: Bot)
+litestar_group()
+apply_role_embed(ctx: Context)
+schedule_office_hours(interaction: Interaction, delay: int | None)
}
LitestarCommands --|> Cog
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey @JacobCoffee - I've reviewed your changes - here's some feedback:
- You’ve defined get_guild_forum_config twice in GuildsController (once at the top and again at the bottom), which will shadow the first—please remove the duplicate or consolidate logic into a single handler.
- In ForumConfigService you set repository_type = AllowedUsersConfigRepository instead of ForumConfigRepository, causing the wrong repo to be used for forum configs—please correct that.
- The new except ConnectError in on_guild_join isn’t imported (will cause a NameError)—add
from httpx import ConnectErroror import from the appropriate module.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- You’ve defined get_guild_forum_config twice in GuildsController (once at the top and again at the bottom), which will shadow the first—please remove the duplicate or consolidate logic into a single handler.
- In ForumConfigService you set repository_type = AllowedUsersConfigRepository instead of ForumConfigRepository, causing the wrong repo to be used for forum configs—please correct that.
- The new except ConnectError in on_guild_join isn’t imported (will cause a NameError)—add `from httpx import ConnectError` or import from the appropriate module.
## Individual Comments
### Comment 1
<location> `byte_bot/server/domain/guilds/services.py:202` </location>
<code_context>
+class AllowedUsersConfigService(SQLAlchemyAsyncRepositoryService[AllowedUsersConfig]):
+ """Handles basic operations for the guilds' Allowed Users config."""
+
+ repository_type = AllowedUsersConfigRepository
+ match_fields = ["guild_id"]
+
</code_context>
<issue_to_address>
ForumConfigService uses the wrong repository_type
It should use ForumConfigRepository to handle ForumConfig models correctly.
</issue_to_address>
### Comment 2
<location> `byte_bot/server/domain/guilds/dependencies.py:79` </location>
<code_context>
GitHubConfigService: GitHubConfig-based service
"""
- async with GitHubConfigService.new(
+ async with GuildsService.new(
session=db_session,
- statement=select(GitHubConfig)
</code_context>
<issue_to_address>
provides_github_config_service uses GuildsService instead of GitHubConfigService
Replace GuildsService.new with GitHubConfigService.new to ensure the correct service is used.
</issue_to_address>
### Comment 3
<location> `byte_bot/server/domain/guilds/controllers.py:392` </location>
<code_context>
+ summary="Get forum config for a guild.",
+ path=urls.GUILD_FORUM_DETAIL,
+ )
+ async def get_guild_forum_config(
+ self,
+ forum_service: ForumConfigService,
</code_context>
<issue_to_address>
Duplicate get_guild_forum_config method detected
This will cause route conflicts. Please remove or rename one of the implementations.
</issue_to_address>
### Comment 4
<location> `byte_bot/byte/views/abstract_views.py:44` </location>
<code_context>
self.minified_embed = minified_embed
- async def interaction_check(self, interaction: Interaction) -> bool:
+ async def delete_interaction_check(self, interaction: Interaction) -> bool:
"""Check if the user is the author or a guild admin.
</code_context>
<issue_to_address>
Renamed interaction_check may break base behavior
Renaming the method means you no longer override the base `View.interaction_check`, which could leave other buttons unprotected. Override `interaction_check` and branch on `interaction.data["custom_id"]` to maintain proper checks.
Suggested implementation:
```python
async def interaction_check(self, interaction: Interaction) -> bool:
"""Override to check if the user is the author or a guild admin for delete, or allow for other buttons.
Args:
interaction (Interaction): The interaction to check.
Returns:
bool: True if the interaction is allowed, False otherwise.
"""
custom_id = interaction.data.get("custom_id")
# Replace 'delete_button_custom_id' with the actual custom_id for your delete button
if custom_id == "delete_button_custom_id":
# Check if the user is the author or a guild admin
if interaction.user.id == self.author:
return True
if interaction.guild and interaction.user.guild_permissions.administrator:
return True
return False
# Default: allow other buttons (or add more checks as needed)
return True
```
- Replace `"delete_button_custom_id"` with the actual custom_id string used for your delete button.
- If there are other buttons that need specific checks, add additional branches for their custom_ids.
- Remove any references to `delete_interaction_check` elsewhere in the codebase, as it is now merged into `interaction_check`.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
Description
This adds setting management through discord ui


then goes into modals
this is partially done but works (no linking into db or at least not fully workign)
going to merge though
Close Issue(s)
Summary by Sourcery
Provide full guild-setting management APIs, enhance bot configuration and scheduling features, refactor UI/embed utilities and CSS, and tighten logging and error handling.
New Features:
/configslash command with UI views and modals for guild administrators to configure bot settings/schedule-office-hoursslash command to schedule recurring 'Office Hours' eventsBug Fixes:
Enhancements:
ButtonEmbedViewandExtendedEmbedclassestypespackagesget_next_fridayutility for scheduling logicBuild:
Chores: