Conversation
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.15.0 to 0.15.1. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](astral-sh/ruff@0.15.0...0.15.1) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.15.1 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com>
build(deps-dev): bump ruff from 0.15.0 to 0.15.1
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 6. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](actions/checkout@v4...v6) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 5 to 6. - [Release notes](https://github.com/docker/build-push-action/releases) - [Commits](docker/build-push-action@v5...v6) --- updated-dependencies: - dependency-name: docker/build-push-action dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [uv](https://github.com/astral-sh/uv) from 0.10.1 to 0.10.4. - [Release notes](https://github.com/astral-sh/uv/releases) - [Changelog](https://github.com/astral-sh/uv/blob/main/CHANGELOG.md) - [Commits](astral-sh/uv@0.10.1...0.10.4) --- updated-dependencies: - dependency-name: uv dependency-version: 0.10.4 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.15.1 to 0.15.2. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](astral-sh/ruff@0.15.1...0.15.2) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.15.2 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [pydantic-settings](https://github.com/pydantic/pydantic-settings) from 2.12.0 to 2.13.1. - [Release notes](https://github.com/pydantic/pydantic-settings/releases) - [Commits](pydantic/pydantic-settings@v2.12.0...v2.13.1) --- updated-dependencies: - dependency-name: pydantic-settings dependency-version: 2.13.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [ty](https://github.com/astral-sh/ty) from 0.0.15 to 0.0.18. - [Release notes](https://github.com/astral-sh/ty/releases) - [Changelog](https://github.com/astral-sh/ty/blob/main/CHANGELOG.md) - [Commits](astral-sh/ty@0.0.15...0.0.18) --- updated-dependencies: - dependency-name: ty dependency-version: 0.0.18 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com>
…s/checkout-6 build(deps): bump actions/checkout from 4 to 6
build(deps-dev): bump uv from 0.10.1 to 0.10.4
build(deps-dev): bump ruff from 0.15.1 to 0.15.2
…2.13.1 build(deps): bump pydantic-settings from 2.12.0 to 2.13.1
…/build-push-action-6 build(deps): bump docker/build-push-action from 5 to 6
Bumps [ty](https://github.com/astral-sh/ty) from 0.0.15 to 0.0.18. - [Release notes](https://github.com/astral-sh/ty/releases) - [Changelog](https://github.com/astral-sh/ty/blob/main/CHANGELOG.md) - [Commits](astral-sh/ty@0.0.15...0.0.18) --- updated-dependencies: - dependency-name: ty dependency-version: 0.0.18 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com>
…/capy-discord into dependabot/uv/ty-0.0.18
build(deps-dev): bump ty from 0.0.15 to 0.0.18
Bumps [uv](https://github.com/astral-sh/uv) from 0.10.4 to 0.10.5. - [Release notes](https://github.com/astral-sh/uv/releases) - [Changelog](https://github.com/astral-sh/uv/blob/main/CHANGELOG.md) - [Commits](astral-sh/uv@0.10.4...0.10.5) --- updated-dependencies: - dependency-name: uv dependency-version: 0.10.5 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com>
build(deps-dev): bump uv from 0.10.4 to 0.10.5
Bumps [uv](https://github.com/astral-sh/uv) from 0.10.5 to 0.10.6. - [Release notes](https://github.com/astral-sh/uv/releases) - [Changelog](https://github.com/astral-sh/uv/blob/main/CHANGELOG.md) - [Commits](astral-sh/uv@0.10.5...0.10.6) --- updated-dependencies: - dependency-name: uv dependency-version: 0.10.6 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [ty](https://github.com/astral-sh/ty) from 0.0.18 to 0.0.19. - [Release notes](https://github.com/astral-sh/ty/releases) - [Changelog](https://github.com/astral-sh/ty/blob/main/CHANGELOG.md) - [Commits](astral-sh/ty@0.0.18...0.0.19) --- updated-dependencies: - dependency-name: ty dependency-version: 0.0.19 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.15.2 to 0.15.4. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](astral-sh/ruff@0.15.2...0.15.4) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.15.4 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com>
build(deps-dev): bump ruff from 0.15.2 to 0.15.4
build(deps-dev): bump ty from 0.0.18 to 0.0.19
Bumps [uv](https://github.com/astral-sh/uv) from 0.10.6 to 0.10.9. - [Release notes](https://github.com/astral-sh/uv/releases) - [Changelog](https://github.com/astral-sh/uv/blob/main/CHANGELOG.md) - [Commits](astral-sh/uv@0.10.6...0.10.9) --- updated-dependencies: - dependency-name: uv dependency-version: 0.10.9 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [ty](https://github.com/astral-sh/ty) from 0.0.19 to 0.0.22. - [Release notes](https://github.com/astral-sh/ty/releases) - [Changelog](https://github.com/astral-sh/ty/blob/main/CHANGELOG.md) - [Commits](astral-sh/ty@0.0.19...0.0.22) --- updated-dependencies: - dependency-name: ty dependency-version: 0.0.22 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.15.4 to 0.15.6. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](astral-sh/ruff@0.15.4...0.15.6) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.15.6 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com>
build(deps-dev): bump ruff from 0.15.4 to 0.15.6
build(deps-dev): bump ty from 0.0.19 to 0.0.22
build(deps-dev): bump uv from 0.10.6 to 0.10.9
…r/metadata-action-6 build(deps): bump docker/metadata-action from 5 to 6
…r/build-push-action-7 build(deps): bump docker/build-push-action from 6 to 7
…r/setup-buildx-action-4 build(deps): bump docker/setup-buildx-action from 3 to 4
build(deps): bump discord-py from 2.6.4 to 2.7.1
…r/login-action-4 build(deps): bump docker/login-action from 3 to 4
Bumps [uv](https://github.com/astral-sh/uv) from 0.10.9 to 0.10.10. - [Release notes](https://github.com/astral-sh/uv/releases) - [Changelog](https://github.com/astral-sh/uv/blob/main/CHANGELOG.md) - [Commits](astral-sh/uv@0.10.9...0.10.10) --- updated-dependencies: - dependency-name: uv dependency-version: 0.10.10 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [ty](https://github.com/astral-sh/ty) from 0.0.22 to 0.0.23. - [Release notes](https://github.com/astral-sh/ty/releases) - [Changelog](https://github.com/astral-sh/ty/blob/main/CHANGELOG.md) - [Commits](astral-sh/ty@0.0.22...0.0.23) --- updated-dependencies: - dependency-name: ty dependency-version: 0.0.23 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com>
build(deps-dev): bump ty from 0.0.22 to 0.0.23
Bumps [coverage](https://github.com/coveragepy/coveragepy) from 7.13.4 to 7.13.5. - [Release notes](https://github.com/coveragepy/coveragepy/releases) - [Changelog](https://github.com/coveragepy/coveragepy/blob/main/CHANGES.rst) - [Commits](coveragepy/coveragepy@7.13.4...7.13.5) --- updated-dependencies: - dependency-name: coverage dependency-version: 7.13.5 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com>
build(deps-dev): bump coverage from 7.13.4 to 7.13.5
build(deps-dev): bump uv from 0.10.9 to 0.10.10
Reviewer's GuideImplements a new in-memory guild setup and onboarding flow with verification, extends the profile system into a two-step modal with additional fields, introduces a whois-style profile lookup command, centralizes user-facing error handling, tightens UI callback typing, and updates CI Docker GitHub Actions along with serialization and tests. Sequence diagram for two-step profile edit modal flowsequenceDiagram
actor User
participant Discord
participant ProfileCog as Profile
participant Modal1 as ModelModalIdentity
participant Modal2 as ModelModalDetails
participant ProfileModalLauncherView
participant Pydantic
User->>Discord: /profile action=edit
Discord-->>ProfileCog: handle_edit_action(interaction, action)
ProfileCog->>ProfileCog: current_profile = profiles.get(user.id)
ProfileCog->>ProfileCog: initial_data = current_profile.model_dump() or None
ProfileCog->>ProfileCog: _open_profile_identity_modal(interaction, action, initial_data)
ProfileCog->>Modal1: create ModelModal(model_cls=UserProfileIdentitySchema, callback=_handle_profile_identity_submit, title="Edit Your Profile (1/2)")
ProfileCog->>Discord: interaction.response.send_modal(Modal1)
User->>Discord: Submit identity fields
Discord-->>Modal1: on_submit(interaction)
Modal1->>Pydantic: validate UserProfileIdentitySchema
Pydantic-->>Modal1: identity instance
Modal1->>ProfileCog: _handle_profile_identity_submit(interaction, identity, action)
ProfileCog->>ProfileCog: profile_data = current_profile.model_dump() or {}
ProfileCog->>ProfileCog: profile_data.update(identity.model_dump())
ProfileCog->>ProfileCog: create ProfileModalLauncherView(callback=_open_profile_details_modal)
ProfileCog->>Discord: interaction.response.send_message("Step 1 of 2 complete…", view=ProfileModalLauncherView)
User->>Discord: Click "Finish Profile"
Discord-->>ProfileModalLauncherView: _button_callback(interaction)
ProfileModalLauncherView->>ProfileCog: _open_profile_details_modal(interaction, action, profile_data)
ProfileCog->>Modal2: create ModelModal(model_cls=UserProfileDetailsSchema, callback=_handle_profile_details_submit, title="Edit Your Profile (2/2)")
ProfileCog->>Discord: interaction.response.send_modal(Modal2)
User->>Discord: Submit details fields
Discord-->>Modal2: on_submit(interaction)
Modal2->>Pydantic: validate UserProfileDetailsSchema
Pydantic-->>Modal2: details instance
Modal2->>ProfileCog: _handle_profile_details_submit(interaction, details, profile_data)
ProfileCog->>ProfileCog: combined_data = {**profile_data, **details.model_dump()}
ProfileCog->>Pydantic: UserProfileSchema(**combined_data)
alt Validation succeeds
Pydantic-->>ProfileCog: profile instance
ProfileCog->>ProfileCog: _handle_profile_submit(interaction, profile)
ProfileCog->>ProfileCog: profiles[user.id] = profile
ProfileCog->>Discord: respond (success embed, etc.)
else ValidationError
Pydantic-->>ProfileCog: ValidationError
ProfileCog->>Discord: interaction.response.send_message(error_embed("Profile Validation Failed"), ephemeral=True)
end
Updated class diagram for setup, profile, whois, and related schemasclassDiagram
direction LR
class Bot {
+_format_missing_permissions(permissions list~str~) str
+_get_app_command_error_message(error app_commands.AppCommandError) str
+_get_prefix_error_message(error commands.CommandError) str
}
class Setup {
+setup app_commands.Group
-bot commands.Bot
-_setup_store dict~int, GuildSetupConfig~
-_user_state_store dict~str, UserOnboardingState~
-_grace_tasks dict~str, asyncio.Task~None~~
+_state_key(guild_id int, user_id int) str
+_ensure_setup(guild_id int) GuildSetupConfig
+_get_user_state(guild_id int, user_id int) UserOnboardingState
+_cancel_grace_task(guild_id int, user_id int) None
+_schedule_grace_period_check(guild_id int, user_id int) None
+_get_bot_member(guild discord.Guild) discord.Member
+_first_public_text_channel(guild discord.Guild) discord.TextChannel
+_format_role_mentions(guild discord.Guild, role_ids list~int~) str
+_format_channel_mention(guild discord.Guild, channel_id int) str
+_missing_items(config GuildSetupConfig) list~str~
+_build_setup_message(guild discord.Guild) str
+_parse_role_ids(raw str, guild discord.Guild) list~int~
+_send_log_message(guild discord.Guild, config GuildSetupConfig, message str) None
+_mark_pending(guild_id int, user_id int) None
+_mark_timed_out(guild_id int, user_id int) None
+_enforce_grace_period(guild_id int, user_id int) None
+_handle_accept(interaction discord.Interaction, target_user_id int) None
+on_guild_join(guild discord.Guild) None
+on_member_join(member discord.Member) None
+setup_summary(interaction discord.Interaction) None
+setup_roles(interaction discord.Interaction, admin_roles str, moderator_roles str, member_role discord.Role) None
+setup_channels(interaction discord.Interaction, log_channel discord.TextChannel, announcement_channel discord.TextChannel, welcome_channel discord.TextChannel, support_channel discord.TextChannel) None
+setup_onboarding(interaction discord.Interaction, enabled bool, welcome_dm_enabled bool, auto_kick_unverified bool, grace_period_hours int, log_events bool, rules_location str, message str) None
+setup_reset(interaction discord.Interaction) None
}
class VerifyView {
+target_user_id int
-_on_accept Callable~discord.Interaction, int -\> Awaitable~None~~
-_on_timeout_callback Callable~int -\> Awaitable~None~~
+accept(interaction discord.Interaction, _button ui.Button) None
+on_timeout() None
}
class Profile {
-bot commands.Bot
-profiles dict~int, UserProfileSchema~
+handle_edit_action(interaction discord.Interaction, action str) None
+_open_profile_identity_modal(interaction discord.Interaction, action str, initial_data dict~str, Any~) None
+_handle_profile_identity_submit(interaction discord.Interaction, identity UserProfileIdentitySchema, action str) None
+_open_profile_details_modal(interaction discord.Interaction, action str, profile_data dict~str, Any~) None
+_handle_profile_details_submit(interaction discord.Interaction, details UserProfileDetailsSchema, profile_data dict~str, Any~) None
+_handle_profile_submit(interaction discord.Interaction, profile UserProfileSchema) None
+handle_show_action(interaction discord.Interaction) None
+_create_profile_embed(user discord.User, profile UserProfileSchema) discord.Embed
}
class ProfileModalLauncherView {
-_callback Callable~discord.Interaction -\> Any~
+ProfileModalLauncherView(callback Callable~discord.Interaction -\> Any~, button_label str, button_emoji str, button_style discord.ButtonStyle)
+_button_callback(interaction discord.Interaction) None
}
class WhoIs {
-bot commands.Bot
+profile(interaction discord.Interaction, member discord.Member) None
+_create_profile_embed(member discord.Member, profile UserProfileSchema) discord.Embed
}
class BaseView {
+message discord.InteractionMessage|discord.Message|None
+on_error(interaction discord.Interaction, error Exception, item ui.Item) None
}
class ModelModal {
+model_cls type~BaseModel~
+callback Callable~discord.Interaction, BaseModel -\> Awaitable~Any~~
+title str
+initial_data dict~str, Any~
}
class UserProfileIdentitySchema {
+preferred_name str
+student_id str
+school_email str
+graduation_year int
+major str
}
class UserProfileDetailsSchema {
+minor str
+description str
}
class UserProfileSchema {
+preferred_name str
+student_id str
+school_email str
+graduation_year int
+major str
+minor str
+description str
}
class GuildSetupConfig {
+enabled bool
+admin_role_ids list~int~
+moderator_role_ids list~int~
+log_channel_id int
+announcement_channel_id int
+welcome_channel_id int
+welcome_dm_enabled bool
+auto_kick_unverified bool
+grace_period_hours int
+log_events bool
+support_channel_id int
+rules_location str
+verification_acceptance str
+member_role_id int
+onboarding_message_template str
}
class UserOnboardingState {
+status str
+started_at_utc datetime
+completed_at_utc datetime
+attempts int
}
Bot <|-- Setup
Bot <|-- Profile
Bot <|-- WhoIs
Setup o--> GuildSetupConfig
Setup o--> UserOnboardingState
Setup o--> VerifyView
VerifyView --|> BaseView
ProfileModalLauncherView --|> BaseView
Profile o--> UserProfileSchema
Profile o--> UserProfileIdentitySchema
Profile o--> UserProfileDetailsSchema
WhoIs o--> UserProfileSchema
ModelModal <.. Profile : uses
VerifyView <.. Setup : created_by
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've found 2 issues, and left some high level feedback:
- In
ProfileModalLauncherView, assigning the button callback viaself.children[0]is brittle if additional items are ever added; consider storing the created button in a local variable and attaching the callback directly to that instance instead of relying on index ordering. - The change to
Telemetry._serialize_valuenow coerces all non-list/dict values tostr, which will also stringify plain primitives like ints and bools that were previously preserved as-is; if you rely on numeric/boolean JSON types downstream, consider adding an explicit branch to pass through JSON-serializable primitives untouched before falling back tostr(value).
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In `ProfileModalLauncherView`, assigning the button callback via `self.children[0]` is brittle if additional items are ever added; consider storing the created button in a local variable and attaching the callback directly to that instance instead of relying on index ordering.
- The change to `Telemetry._serialize_value` now coerces all non-list/dict values to `str`, which will also stringify plain primitives like ints and bools that were previously preserved as-is; if you rely on numeric/boolean JSON types downstream, consider adding an explicit branch to pass through JSON-serializable primitives untouched before falling back to `str(value)`.
## Individual Comments
### Comment 1
<location path="capy_discord/exts/core/telemetry.py" line_range="561-564" />
<code_context>
+ type JsonPrimitive = str | int | float | bool | None
+ type JsonValue = JsonPrimitive | list["JsonValue"] | dict[str, "JsonValue"]
+
+ def _serialize_value(self, value: object) -> JsonValue:
"""Convert complex Discord objects to simple serializable types.
</code_context>
<issue_to_address>
**issue (bug_risk):** Serializing all non-container values to strings may unintentionally change data semantics.
With this change, any value not matched earlier (including ints/floats/bools/None) is now coerced to `str`, so data that previously passed through as proper JSON primitives will be logged as strings. That can break downstream consumers or analytics that depend on numeric/boolean types. Consider returning JSON primitives (int/float/bool/None) as-is and only `str()`-casting truly unknown or complex objects.
</issue_to_address>
### Comment 2
<location path="capy_discord/exts/setup/setup.py" line_range="411-416" />
<code_context>
+ timeout=1800,
+ )
+
+ sent = await welcome_channel.send(
+ rendered,
+ allowed_mentions=discord.AllowedMentions(users=True, roles=False, everyone=False),
+ view=view,
+ )
+ view.message = sent
+ self._schedule_grace_period_check(member.guild.id, member.id)
+
</code_context>
<issue_to_address>
**issue:** The welcome message send in `on_member_join` is not guarded against HTTP errors, which can break onboarding silently.
Unlike `_send_log_message` and the setup checklist, this `welcome_channel.send(...)` isn’t wrapped in `try/except discord.HTTPException`. If the bot loses `Send Messages` permission or the channel is misconfigured, this await will raise, aborting the handler with no logging or fallback. Please catch `discord.HTTPException`, log a warning, and skip scheduling the grace-period task when the welcome message fails to send.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| def _serialize_value(self, value: object) -> JsonValue: | ||
| """Convert complex Discord objects to simple serializable types. | ||
|
|
||
| Args: |
There was a problem hiding this comment.
issue (bug_risk): Serializing all non-container values to strings may unintentionally change data semantics.
With this change, any value not matched earlier (including ints/floats/bools/None) is now coerced to str, so data that previously passed through as proper JSON primitives will be logged as strings. That can break downstream consumers or analytics that depend on numeric/boolean types. Consider returning JSON primitives (int/float/bool/None) as-is and only str()-casting truly unknown or complex objects.
| sent = await welcome_channel.send( | ||
| rendered, | ||
| allowed_mentions=discord.AllowedMentions(users=True, roles=False, everyone=False), | ||
| view=view, | ||
| ) | ||
| view.message = sent |
There was a problem hiding this comment.
issue: The welcome message send in on_member_join is not guarded against HTTP errors, which can break onboarding silently.
Unlike _send_log_message and the setup checklist, this welcome_channel.send(...) isn’t wrapped in try/except discord.HTTPException. If the bot loses Send Messages permission or the channel is misconfigured, this await will raise, aborting the handler with no logging or fallback. Please catch discord.HTTPException, log a warning, and skip scheduling the grace-period task when the welcome message fails to send.
Summary by Sourcery
Introduce a configurable in-memory guild onboarding and setup system, expand user profiles into a multi-step flow with richer fields, and add a whois command for viewing other members’ profiles.
New Features:
Bug Fixes:
Enhancements:
Build:
Tests:
Chores: