From 9a5f66d7877f55b304ad7b1c4877ef46c55874cc Mon Sep 17 00:00:00 2001 From: woweiseng <168587256+woweiseng@users.noreply.github.com> Date: Fri, 14 Nov 2025 22:32:35 -0500 Subject: [PATCH 01/16] added implementation to connect User id with Guild and Guild id with user.-testing still in progress --- .../frontend/cogs/features/profile_cog.py | 104 ++++++++++++++++++ 1 file changed, 104 insertions(+) diff --git a/src/capy_app/frontend/cogs/features/profile_cog.py b/src/capy_app/frontend/cogs/features/profile_cog.py index 2a02885..a275e8a 100644 --- a/src/capy_app/frontend/cogs/features/profile_cog.py +++ b/src/capy_app/frontend/cogs/features/profile_cog.py @@ -713,6 +713,7 @@ async def _save_profile( Database.add_document(new_user) user = new_user self.logger.info(f"Successfully created new profile for {interaction.user}") + await self._link_user_to_guilds(interaction) else: updates = {f"profile__{k}": v for k, v in profile_data_dict.items()} Database.update_document(user, updates) @@ -729,6 +730,109 @@ async def _save_profile( ephemeral=True, ) + def _get_target_guild_ids(self, interaction: discord.Interaction) -> list[int]: + """Determine which guild IDs to assign to the user. + + Args: + interaction: The Discord interaction + + Returns: + List of guild IDs to assign to the user + """ + user_id = interaction.user.id + + # If interaction is in a guild, return that guild ID + if interaction.guild_id: + self.logger.info(f"Profile created in guild {interaction.guild_id}") + return [int(interaction.guild_id)] + + # If in DMs, find all mutual guilds where the bot sees the user as a member + mutual_guild_ids = [] + for guild in self.bot.guilds: + member = guild.get_member(user_id) + if member and not member.bot: + mutual_guild_ids.append(int(guild.id)) + self.logger.debug(f"Found user {user_id} in guild {guild.id} ({guild.name})") + + self.logger.info(f"Profile created in DMs, found {len(mutual_guild_ids)} mutual guilds") + return mutual_guild_ids + + def _ensure_guild_exists(self, guild_id: int) -> Guild: + """Ensure a Guild document exists in the database. + + Args: + guild_id: The guild ID to ensure exists + + Returns: + The Guild document + """ + guild_doc = Database.get_document(Guild, guild_id) + if not guild_doc: + guild_doc = Guild(_id=guild_id) + Database.add_document(guild_doc) + self.logger.info(f"Created Guild document for {guild_id}") + return guild_doc + + def _add_user_to_guild(self, guild_id: int, user_id: int) -> None: + """Add a user to a guild's user list. + + Args: + guild_id: The guild ID + user_id: The user ID to add + """ + try: + guild_doc = self._ensure_guild_exists(guild_id) + + existing_users = getattr(guild_doc, "users", []) or [] + if user_id not in existing_users: + existing_users.append(user_id) + Database.update_document(guild_doc, {"users": sorted(existing_users)}) + self.logger.info(f"Added user {user_id} to Guild.users for {guild_id}") + else: + self.logger.debug(f"User {user_id} already in Guild.users for {guild_id}") + except Exception as e: + # Non-fatal: log and continue + self.logger.error(f"Failed to add user {user_id} to Guild.users for {guild_id}: {e}") + + async def _link_user_to_guilds(self, interaction: discord.Interaction) -> None: + """Link a user to their guilds after profile creation. + + This handles both server and DM initiation paths: + - In servers: links to the current guild + - In DMs: links to all mutual guilds where the bot sees the user + + Args: + interaction: The Discord interaction + """ + user_id = interaction.user.id + + try: + # Determine which guilds to link + target_guild_ids = self._get_target_guild_ids(interaction) + + if not target_guild_ids: + self.logger.warning(f"No guilds found for user {user_id}") + return + + # Update User.guilds with add-to-set semantics (no duplicates) + for guild_id in target_guild_ids: + try: + Database.bulk_update_attr(User, [user_id], "guilds", guild_id) + self.logger.info(f"Added guild {guild_id} to User.guilds for {user_id}") + except Exception as e: + # Non-fatal: log and continue with other guilds + self.logger.error(f"Failed to add guild {guild_id} to User.guilds for {user_id}: {e}") + + # Ensure Guild documents exist and add user to Guild.users + for guild_id in target_guild_ids: + self._add_user_to_guild(guild_id, user_id) + + self.logger.info(f"Successfully linked user {user_id} to {len(target_guild_ids)} guild(s)") + + except Exception as e: + # Non-fatal: profile is more important than guild links + self.logger.error(f"Error linking user {user_id} to guilds: {e}", exc_info=True) + async def show_profile_embed( self, message_or_interaction: discord.Message | discord.Interaction, From ffb49e1bbf15e0fbbf2acae0f0d470c1efcf198d Mon Sep 17 00:00:00 2001 From: woweiseng <168587256+woweiseng@users.noreply.github.com> Date: Tue, 18 Nov 2025 15:45:07 -0500 Subject: [PATCH 02/16] fixed issues with profile delete --- src/capy_app/frontend/bot.py | 2 +- .../frontend/cogs/features/profile_cog.py | 40 ++++++++++++++----- .../interactions/bases/button_base.py | 10 +++-- 3 files changed, 38 insertions(+), 14 deletions(-) diff --git a/src/capy_app/frontend/bot.py b/src/capy_app/frontend/bot.py index 77c6d1f..939fc57 100644 --- a/src/capy_app/frontend/bot.py +++ b/src/capy_app/frontend/bot.py @@ -18,8 +18,8 @@ from discord.ext import commands, tasks from discord.ext.commands import Context -from capy_app.stats import Statistics from config import settings +from stats import Statistics # type: ignore[attr-defined] class Bot(commands.AutoShardedBot): diff --git a/src/capy_app/frontend/cogs/features/profile_cog.py b/src/capy_app/frontend/cogs/features/profile_cog.py index a275e8a..70f4ca0 100644 --- a/src/capy_app/frontend/cogs/features/profile_cog.py +++ b/src/capy_app/frontend/cogs/features/profile_cog.py @@ -88,8 +88,11 @@ async def retry_button(self, interaction: discord.Interaction, _button: discord. async def delete_profile_from_events(user): - user = Database.get_document(User, user.id) + """Remove user from all events they're registered for. + Args: + user: The User document to remove from events + """ for event_id in user.events: event = Database.get_document(Event, event_id) # TODO remove the frontend RSVP reactions of the deleted user @@ -104,16 +107,20 @@ async def delete_profile_from_events(user): if user.id in event.maybe_users: event.maybe_users.remove(user.id) event.details.reactions.modify("maybe", -1) - event.save() + event.save() async def delete_profile_from_guilds(user): - user = Database.get_document(User, user.id) + """Remove user from all guilds they're in. + Args: + user: The User document to remove from guilds + """ for guild_id in user.guilds: guild = Database.get_document(Guild, guild_id) if guild and user.id in guild.users: guild.users.remove(user.id) + guild.save() class ProfileCog(commands.Cog): @@ -916,21 +923,36 @@ async def delete_profile(self, interaction: discord.Interaction) -> None: await interaction.edit_original_response(content="You don't have a profile to delete.") return - view = ConfirmDeleteView() + view = ConfirmDeleteView(timeout=60) await interaction.edit_original_response( content="⚠️ Are you sure you want to delete your profile? This action cannot be undone.", view=view, ) - await view.wait() - if view.value: - Database.delete_document(user) + # Wait for button press + timed_out = await view.wait() + + if view.value and view.interaction: + # Use the button interaction to update the message - this acknowledges it + await view.interaction.response.edit_message(content="⏳ Deleting your profile...", view=None) + + # Remove user from events and guilds BEFORE deleting the user document await delete_profile_from_events(user) await delete_profile_from_guilds(user) - await interaction.edit_original_response(content="Your profile has been deleted.", view=None) + # Now delete the user document + Database.delete_document(user) + + # Show success message + await interaction.edit_original_response(content="✅ Your profile has been deleted.", view=None) + elif timed_out: + await interaction.edit_original_response(content="❌ Profile deletion timed out.", view=None) + elif view.interaction: + # User cancelled - use button interaction to respond + await view.interaction.response.edit_message(content="❌ Profile deletion cancelled.", view=None) else: - await interaction.edit_original_response(content="Profile deletion cancelled.", view=None) + # Fallback if no interaction (shouldn't happen) + await interaction.edit_original_response(content="❌ Profile deletion cancelled.", view=None) async def setup(bot: commands.Bot) -> None: diff --git a/src/capy_app/frontend/interactions/bases/button_base.py b/src/capy_app/frontend/interactions/bases/button_base.py index 98598e6..7d9584c 100644 --- a/src/capy_app/frontend/interactions/bases/button_base.py +++ b/src/capy_app/frontend/interactions/bases/button_base.py @@ -24,6 +24,7 @@ def __init__(self, ephemeral: bool = True, **options) -> None: self._completed: bool = False self._timed_out: bool = False self.value: bool | None = None + self.interaction: Interaction | None = None async def _send_status_message(self, content: str) -> None: """Update status message.""" @@ -77,19 +78,20 @@ class AcceptCancelView(BaseButtonView): @discord.ui.button(label="Accept", style=ButtonStyle.success) async def accept(self, interaction: Interaction, _button: discord.ui.Button[Any]) -> None: """Handle accept button press.""" - await interaction.response.defer() + # Store the interaction so the caller can respond to it + self.interaction = interaction self.value = True self._completed = True - await self._send_status_message("Operation accepted") self.stop() @discord.ui.button(label="Cancel", style=ButtonStyle.danger) async def cancel(self, interaction: Interaction, _button: discord.ui.Button[Any]) -> None: """Handle cancel button press.""" - await interaction.response.defer() + # Store the interaction so the caller can respond to it + self.interaction = interaction self.value = False self._completed = True - await self._send_status_message("Operation cancelled") + self.stop() self.stop() From 0f22599d7260882907f9e32bb601c00d7665f26b Mon Sep 17 00:00:00 2001 From: tagciccone Date: Tue, 18 Nov 2025 17:04:10 -0500 Subject: [PATCH 03/16] Ensure a user has events & guilds before deleting them from said events & guilds --- src/capy_app/frontend/cogs/features/profile_cog.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/capy_app/frontend/cogs/features/profile_cog.py b/src/capy_app/frontend/cogs/features/profile_cog.py index a275e8a..8da8e01 100644 --- a/src/capy_app/frontend/cogs/features/profile_cog.py +++ b/src/capy_app/frontend/cogs/features/profile_cog.py @@ -90,6 +90,9 @@ async def retry_button(self, interaction: discord.Interaction, _button: discord. async def delete_profile_from_events(user): user = Database.get_document(User, user.id) + if not hasattr(user, "events"): + return + for event_id in user.events: event = Database.get_document(Event, event_id) # TODO remove the frontend RSVP reactions of the deleted user @@ -110,6 +113,9 @@ async def delete_profile_from_events(user): async def delete_profile_from_guilds(user): user = Database.get_document(User, user.id) + if not hasattr(user, "events"): + return + for guild_id in user.guilds: guild = Database.get_document(Guild, guild_id) if guild and user.id in guild.users: From 9dea1778d86259ad6134c038a244e8b60634f388 Mon Sep 17 00:00:00 2001 From: tagciccone Date: Tue, 18 Nov 2025 17:05:22 -0500 Subject: [PATCH 04/16] Whoops, make sure guild deletion checks for guild attr --- src/capy_app/frontend/cogs/features/profile_cog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/capy_app/frontend/cogs/features/profile_cog.py b/src/capy_app/frontend/cogs/features/profile_cog.py index 8da8e01..a532085 100644 --- a/src/capy_app/frontend/cogs/features/profile_cog.py +++ b/src/capy_app/frontend/cogs/features/profile_cog.py @@ -113,7 +113,7 @@ async def delete_profile_from_events(user): async def delete_profile_from_guilds(user): user = Database.get_document(User, user.id) - if not hasattr(user, "events"): + if not hasattr(user, "guilds"): return for guild_id in user.guilds: From c5d27eef2a914b7abfe5de7a0aac913b5b73bf50 Mon Sep 17 00:00:00 2001 From: woweiseng <168587256+woweiseng@users.noreply.github.com> Date: Tue, 18 Nov 2025 17:43:02 -0500 Subject: [PATCH 05/16] auto fix --- src/capy_app/frontend/cogs/features/profile_cog.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/capy_app/frontend/cogs/features/profile_cog.py b/src/capy_app/frontend/cogs/features/profile_cog.py index 7ae41d2..c302a53 100644 --- a/src/capy_app/frontend/cogs/features/profile_cog.py +++ b/src/capy_app/frontend/cogs/features/profile_cog.py @@ -88,6 +88,7 @@ async def retry_button(self, interaction: discord.Interaction, _button: discord. async def delete_profile_from_events(user): + # Delete user profile from events if not hasattr(user, "events"): return From 27faf0cf03fd0dc39f11a67e8a9afb895debb717 Mon Sep 17 00:00:00 2001 From: Shamik Karkhanis Date: Tue, 18 Nov 2025 17:52:46 -0500 Subject: [PATCH 06/16] [Feature] - updated pre-commit config --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index aa3b859..a309409 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.12.8 hooks: - - id: ruff + - id: ruff-check args: [--fix, --exit-non-zero-on-fix] - id: ruff-format - repo: local From 7ea8386d9f4e45b4d4d6cb698a0d1fc01336c7ea Mon Sep 17 00:00:00 2001 From: Shamik Karkhanis Date: Tue, 18 Nov 2025 17:55:25 -0500 Subject: [PATCH 07/16] [Feature] - updated pre commit once again --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a309409..ddb6a5d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -14,7 +14,7 @@ repos: rev: v0.12.8 hooks: - id: ruff-check - args: [--fix, --exit-non-zero-on-fix] + args: [--fix] - id: ruff-format - repo: local hooks: From 47757e8ad24e16506a5a8f89e540cb1055d22b38 Mon Sep 17 00:00:00 2001 From: Shamik Karkhanis Date: Fri, 21 Nov 2025 11:58:18 -0800 Subject: [PATCH 08/16] update yaml again --- .pre-commit-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ddb6a5d..7b0483b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,8 +13,8 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.12.8 hooks: - - id: ruff-check - args: [--fix] + - id: ruff + args: [--fix, --exit-non-zero-on-fix] - id: ruff-format - repo: local hooks: @@ -46,7 +46,7 @@ repos: files: \.py$ - id: pytest name: pytest - entry: pytest + entry: pytest . language: system pass_filenames: false always_run: true From 23c01e4d2a20cdaf7c6c6099a071f2ee83f5d297 Mon Sep 17 00:00:00 2001 From: Shamik Karkhanis Date: Fri, 21 Nov 2025 12:03:51 -0800 Subject: [PATCH 09/16] pre-commit no fix --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7b0483b..f94245d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -14,7 +14,7 @@ repos: rev: v0.12.8 hooks: - id: ruff - args: [--fix, --exit-non-zero-on-fix] + args: [--diff] - id: ruff-format - repo: local hooks: From bc05fd391b83c2c842a05f72ed338389f84391f1 Mon Sep 17 00:00:00 2001 From: Shamik Karkhanis Date: Fri, 21 Nov 2025 12:23:32 -0800 Subject: [PATCH 10/16] ruff fixes --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f94245d..fa2818e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,10 +11,10 @@ repos: require_serial: true verbose: true - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.12.8 + rev: v0.14.6 # latest as of 11/21/2025 hooks: - id: ruff - args: [--diff] + args: [--fix, --unsafe-fixes] - id: ruff-format - repo: local hooks: From c0d7b48608512d66e0d7eb8aea0378f22cefa1c0 Mon Sep 17 00:00:00 2001 From: Shamik Karkhanis Date: Fri, 21 Nov 2025 12:26:09 -0800 Subject: [PATCH 11/16] ruff fixes again --- src/capy_app/frontend/cogs/features/event_cog.py | 2 +- src/capy_app/frontend/cogs/features/profile_cog.py | 2 +- tests/capy_app/frontend/test_auto_vs_suggest.py | 4 ++-- tests/capy_app/frontend/test_major_handler.py | 2 +- tests/capy_app/frontend/test_typo_checker.py | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/capy_app/frontend/cogs/features/event_cog.py b/src/capy_app/frontend/cogs/features/event_cog.py index fe6ae52..4a8142d 100644 --- a/src/capy_app/frontend/cogs/features/event_cog.py +++ b/src/capy_app/frontend/cogs/features/event_cog.py @@ -1381,7 +1381,7 @@ async def on_raw_reaction_remove(self, payload): """ Handle reaction removal from event announcements. """ - valid, channel, message = await self.is_valid_reaction(payload) + valid, _channel, message = await self.is_valid_reaction(payload) if not valid: return diff --git a/src/capy_app/frontend/cogs/features/profile_cog.py b/src/capy_app/frontend/cogs/features/profile_cog.py index c302a53..d050aa8 100644 --- a/src/capy_app/frontend/cogs/features/profile_cog.py +++ b/src/capy_app/frontend/cogs/features/profile_cog.py @@ -389,7 +389,7 @@ async def _process_and_validate_majors( processed_majors = self.process_majors_from_text(majors_text) # Validate and normalize majors with fuzzy matching for typos - all_valid, validated_majors, invalid_majors, auto_corrections, suggestions = ( + _all_valid, validated_majors, invalid_majors, auto_corrections, suggestions = ( self.major_handler.validate_majors_with_corrections(processed_majors) ) diff --git a/tests/capy_app/frontend/test_auto_vs_suggest.py b/tests/capy_app/frontend/test_auto_vs_suggest.py index d4cddf3..acf84e9 100644 --- a/tests/capy_app/frontend/test_auto_vs_suggest.py +++ b/tests/capy_app/frontend/test_auto_vs_suggest.py @@ -48,7 +48,7 @@ def test_suggestion_medium_confidence(major_handler): # We'll need to find a string that scores between 60-90% # For now, let's create an artificially low-scoring match input_majors = ["comp sci"] # Very short - likely won't match well - all_valid, valid_majors, invalid_majors, auto_corrections, suggestions = ( + all_valid, _valid_majors, invalid_majors, auto_corrections, suggestions = ( major_handler.validate_majors_with_corrections(input_majors) ) @@ -69,7 +69,7 @@ def test_mixed_auto_and_suggest(major_handler): # "Compter Science" should auto-correct (high score) # Let's try to find something that scores medium input_majors = ["Compter Science", "Physics"] - all_valid, valid_majors, invalid_majors, auto_corrections, suggestions = ( + _all_valid, valid_majors, _invalid_majors, auto_corrections, suggestions = ( major_handler.validate_majors_with_corrections(input_majors) ) diff --git a/tests/capy_app/frontend/test_major_handler.py b/tests/capy_app/frontend/test_major_handler.py index fb7ec89..d18f11a 100644 --- a/tests/capy_app/frontend/test_major_handler.py +++ b/tests/capy_app/frontend/test_major_handler.py @@ -228,7 +228,7 @@ def test_validate_majors_with_abbreviations(major_handler): """Test that abbreviations require user confirmation via suggestions.""" # Test common abbreviations - they should NOT be auto-expanded input_majors = ["CS", "me", "PHYS"] - all_valid, valid_majors, invalid_majors = major_handler.validate_majors(input_majors) + all_valid, valid_majors, _invalid_majors = major_handler.validate_majors(input_majors) # validate_majors doesn't handle abbreviations, so they'll be invalid or fuzzy matched # This is expected - abbreviations only work with validate_majors_with_corrections diff --git a/tests/capy_app/frontend/test_typo_checker.py b/tests/capy_app/frontend/test_typo_checker.py index b78148c..275fc42 100644 --- a/tests/capy_app/frontend/test_typo_checker.py +++ b/tests/capy_app/frontend/test_typo_checker.py @@ -79,7 +79,7 @@ def test_fuzzy_match_rejects_too_different(major_handler): ] for invalid_input in test_cases: - all_valid, valid_majors, invalid_majors = major_handler.validate_majors([invalid_input]) + all_valid, _valid_majors, invalid_majors = major_handler.validate_majors([invalid_input]) assert all_valid is False assert invalid_input in invalid_majors From cecfc3289eda2b3b2a92a24984f0f359d8e62d3f Mon Sep 17 00:00:00 2001 From: Shamik Karkhanis Date: Fri, 21 Nov 2025 14:03:13 -0800 Subject: [PATCH 12/16] removed ruff args --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index fa2818e..c7f8324 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -14,7 +14,7 @@ repos: rev: v0.14.6 # latest as of 11/21/2025 hooks: - id: ruff - args: [--fix, --unsafe-fixes] + args: [] - id: ruff-format - repo: local hooks: From c19fbc0b63c4f981fd85e31f170220738cc067e4 Mon Sep 17 00:00:00 2001 From: Shamik Karkhanis Date: Fri, 21 Nov 2025 14:20:18 -0800 Subject: [PATCH 13/16] more ruff fixes --- .pre-commit-config.yaml | 2 +- src/capy_app/frontend/bot.py | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c7f8324..fa2818e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -14,7 +14,7 @@ repos: rev: v0.14.6 # latest as of 11/21/2025 hooks: - id: ruff - args: [] + args: [--fix, --unsafe-fixes] - id: ruff-format - repo: local hooks: diff --git a/src/capy_app/frontend/bot.py b/src/capy_app/frontend/bot.py index ad7203b..4a7c5b3 100644 --- a/src/capy_app/frontend/bot.py +++ b/src/capy_app/frontend/bot.py @@ -1,8 +1,6 @@ """Discord bot module for handling discord-related functionality.""" import json - -# Standard library imports import logging import pathlib import typing @@ -10,10 +8,7 @@ from datetime import datetime from pathlib import Path -# Third-party imports import discord - -# Local imports from backend.db.database import Database from discord.ext import commands, tasks from discord.ext.commands import Context From 3993ee589bfb3d0f9e9b135a81c4f70635047650 Mon Sep 17 00:00:00 2001 From: Shamik Karkhanis Date: Fri, 21 Nov 2025 14:24:08 -0800 Subject: [PATCH 14/16] Remove Python 3.13 from CI workflow --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index dde50d9..fee1215 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -10,7 +10,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, windows-latest] - python-version: ["3.12", "3.13"] + python-version: ["3.12"] steps: - uses: actions/checkout@v4 From 56a3c839c920dffbf318d363c990c788ac67de1d Mon Sep 17 00:00:00 2001 From: woweiseng <168587256+woweiseng@users.noreply.github.com> Date: Thu, 4 Dec 2025 16:27:57 -0500 Subject: [PATCH 15/16] fixing try again not working issue --- src/capy_app/frontend/cogs/features/profile_cog.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/capy_app/frontend/cogs/features/profile_cog.py b/src/capy_app/frontend/cogs/features/profile_cog.py index c302a53..a7e6e21 100644 --- a/src/capy_app/frontend/cogs/features/profile_cog.py +++ b/src/capy_app/frontend/cogs/features/profile_cog.py @@ -46,8 +46,8 @@ def __init__(self, parent_cog, action, invalid_data=None): @discord.ui.button(label="Try Again", style=discord.ButtonStyle.primary) async def retry_button(self, interaction: discord.Interaction, _: discord.ui.Button[Any]): - # Acknowledge the interaction to avoid timeouts/jitter - await interaction.response.defer(ephemeral=True) + # Don't defer - we need to pass the interaction to handle_profile + # which will use it to show the modal await self.parent_cog.handle_profile(interaction, self.action, retry_data=self.invalid_data) self.stop() @@ -78,8 +78,7 @@ async def accept_button(self, interaction: discord.Interaction, _button: discord @discord.ui.button(label="Try Again", style=discord.ButtonStyle.primary) async def retry_button(self, interaction: discord.Interaction, _button: discord.ui.Button[Any]): """Reject suggestions and return to form with all data except majors.""" - # Acknowledge the interaction to avoid timeouts/jitter - await interaction.response.defer(ephemeral=True) + # Don't defer - we need to show a modal, which requires an unacknowledged interaction self.accepted = False # Keep all profile data EXCEPT the major field - user needs to re-enter majors retry_data = {k: v for k, v in self.profile_data.items() if k != "major(s)"} @@ -418,7 +417,10 @@ async def _process_and_validate_majors( if invalid_majors: error_msg = self.major_handler.get_validation_error_message(invalid_majors) error_msg += "\nPlease check your spelling and try again." - await message.edit(content=error_msg) + # Keep all profile data EXCEPT the major field - user needs to re-enter majors + retry_data = {k: v for k, v in profile_data.items() if k != "major(s)"} + view = TryAgainView(self, action, retry_data) + await message.edit(content=error_msg, view=view) return None return majors_string From 0a468e5407614c54a293574ec73273e6406969f1 Mon Sep 17 00:00:00 2001 From: woweiseng <168587256+woweiseng@users.noreply.github.com> Date: Thu, 4 Dec 2025 16:43:53 -0500 Subject: [PATCH 16/16] auto fix --- src/capy_app/frontend/cogs/features/profile_cog.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/capy_app/frontend/cogs/features/profile_cog.py b/src/capy_app/frontend/cogs/features/profile_cog.py index a8a5610..78a2e07 100644 --- a/src/capy_app/frontend/cogs/features/profile_cog.py +++ b/src/capy_app/frontend/cogs/features/profile_cog.py @@ -47,7 +47,6 @@ def __init__(self, parent_cog, action, invalid_data=None): @discord.ui.button(label="Try Again", style=discord.ButtonStyle.primary) async def retry_button(self, interaction: discord.Interaction, _: discord.ui.Button[Any]): # Don't defer - we need to pass the interaction to handle_profile - # which will use it to show the modal await self.parent_cog.handle_profile(interaction, self.action, retry_data=self.invalid_data) self.stop()