diff --git a/src/bot/constants/messages.py b/src/bot/constants/messages.py index 9d79fff..ae27006 100644 --- a/src/bot/constants/messages.py +++ b/src/bot/constants/messages.py @@ -90,9 +90,8 @@ class CommandError: PRIVILEGE_LOW = "Your Privilege is too low." DIRECT_MESSAGES_DISABLED = ( "Direct messages are disabled in your configuration.\n" - "If you want to receive messages from Bots, " - "you need to enable this option under Privacy & Safety:" - '"Allow direct messages from server members."' + "To receive messages from Bots, enable **Direct messages** " + "under **Settings > Content & Social > Social permissions**." ) @@ -170,9 +169,8 @@ class BotUtils: LOADING_EXTENSION_FAILED = "ERROR: FAILED to load extension" DISABLED_DM = ( "Direct messages are disabled in your configuration.\n" - "If you want to receive messages from Bots, " - "you need to enable this option under Privacy & Safety:\n" - '"Allow direct messages from server members."\n' + "To receive messages from Bots, enable **Direct messages** " + "under **Settings > Content & Social > Social permissions**." ) MESSAGE_REMOVED_FOR_PRIVACY = "Your message was removed for privacy." DELETE_MESSAGE_NO_PERMISSION = "Bot does not have permission to delete messages." diff --git a/src/bot/tools/bot_utils.py b/src/bot/tools/bot_utils.py index c1e8aa7..c996b68 100644 --- a/src/bot/tools/bot_utils.py +++ b/src/bot/tools/bot_utils.py @@ -137,13 +137,13 @@ async def send_embed(ctx, embed, dm=False): icon_url=ctx.author.avatar.url if ctx.author.avatar else ctx.author.default_avatar.url, ) await ctx.send(embed=notification_embed) - except (discord.Forbidden, discord.HTTPException): + except discord.Forbidden, discord.HTTPException: # DM failed, fall back to sending in the channel await ctx.send(embed=embed) else: # Send to channel await ctx.send(embed=embed) - except (discord.Forbidden, discord.HTTPException): + except discord.Forbidden, discord.HTTPException: await send_error_msg(ctx, messages.DISABLED_DM) except Exception as e: ctx.bot.log.error(e) diff --git a/src/gw2/cogs/account.py b/src/gw2/cogs/account.py index f95ee73..5a73a0c 100644 --- a/src/gw2/cogs/account.py +++ b/src/gw2/cogs/account.py @@ -20,7 +20,7 @@ async def _keep_typing_alive(ctx, stop_event): await asyncio.sleep(4) # Renew every 4 seconds (Discord typing lasts ~5s) except asyncio.CancelledError: raise # Re-raise CancelledError - except (discord.HTTPException, discord.Forbidden): + except discord.HTTPException, discord.Forbidden: # Handle Discord API errors gracefully and stop the loop break except asyncio.CancelledError: @@ -265,7 +265,7 @@ async def limited_guild_fetch(task): try: stop_typing.set() typing_task.cancel() - except (AttributeError, RuntimeError): + except AttributeError, RuntimeError: # Handle cases where task is already done or event is invalid pass await bot_utils.send_error_msg(ctx, e) diff --git a/src/gw2/cogs/sessions.py b/src/gw2/cogs/sessions.py index ed849be..e1b6399 100644 --- a/src/gw2/cogs/sessions.py +++ b/src/gw2/cogs/sessions.py @@ -90,7 +90,17 @@ async def session(ctx): rs_end = rs_session[0]["end"] if rs_end is None or rs_end.get("date") is None: - return await bot_utils.send_error_msg(ctx, gw2_messages.SESSION_SAVE_ERROR) + # Check if the user is currently playing GW2 + is_playing = ( + not isinstance(ctx.channel, discord.DMChannel) + and hasattr(ctx.message.author, "activity") + and ctx.message.author.activity is not None + and "guild wars 2" in str(ctx.message.author.activity.name).lower() + ) + if is_playing: + return await gw2_utils.send_msg(ctx, gw2_messages.SESSION_IN_PROGRESS) + # Game stopped but end data not saved yet — bot may still be updating + return await gw2_utils.send_msg(ctx, gw2_messages.SESSION_BOT_STILL_UPDATING) await ctx.message.channel.typing() color = ctx.bot.settings["gw2"]["EmbedColor"] diff --git a/src/gw2/constants/gw2_messages.py b/src/gw2/constants/gw2_messages.py index ac357b5..b35a192 100644 --- a/src/gw2/constants/gw2_messages.py +++ b/src/gw2/constants/gw2_messages.py @@ -108,6 +108,9 @@ def session_not_active(prefix: str) -> str: TOWERS_CAPTURED = "Towers captured" CAMPS_CAPTURED = "Camps captured" SMC_CAPTURED = "SMC captured" +SESSION_IN_PROGRESS = ( + "Your Guild Wars 2 session is still in progress.\nSession stats will be available after you stop playing." +) SESSION_SAVE_ERROR = ( "There was a problem trying to record your last finished session.\n" "Please, do not close discord when the game is running." diff --git a/tests/unit/gw2/cogs/test_sessions.py b/tests/unit/gw2/cogs/test_sessions.py index b0dee07..43e8c03 100644 --- a/tests/unit/gw2/cogs/test_sessions.py +++ b/tests/unit/gw2/cogs/test_sessions.py @@ -11,6 +11,7 @@ session, setup, ) +from src.gw2.constants import gw2_messages from unittest.mock import AsyncMock, MagicMock, patch @@ -272,7 +273,7 @@ async def test_session_no_session_found(self, mock_ctx, sample_api_key_data): @pytest.mark.asyncio async def test_session_end_date_is_none(self, mock_ctx, sample_api_key_data): - """Test session command when session end date is None.""" + """Test session command when session end date is None and user not playing.""" session_data = [{"acc_name": "TestUser.1234", "start": {"date": "2024-01-15 10:00:00"}, "end": {"date": None}}] with patch("src.gw2.cogs.sessions.Gw2KeyDal") as mock_dal: mock_dal.return_value.get_api_key_by_user = AsyncMock(return_value=sample_api_key_data) @@ -280,11 +281,27 @@ async def test_session_end_date_is_none(self, mock_ctx, sample_api_key_data): mock_configs.return_value.get_gw2_server_configs = AsyncMock(return_value=[{"session": True}]) with patch("src.gw2.cogs.sessions.Gw2SessionsDal") as mock_sessions_dal: mock_sessions_dal.return_value.get_user_last_session = AsyncMock(return_value=session_data) - with patch("src.gw2.cogs.sessions.bot_utils.send_error_msg") as mock_error: + with patch("src.gw2.cogs.sessions.gw2_utils.send_msg") as mock_send: await session(mock_ctx) - mock_error.assert_called_once() - # Error sent to channel (not via DM) - assert len(mock_error.call_args[0]) == 2 + mock_send.assert_called_once() + assert gw2_messages.SESSION_BOT_STILL_UPDATING in mock_send.call_args[0][1] + + @pytest.mark.asyncio + async def test_session_end_date_is_none_while_playing(self, mock_ctx, sample_api_key_data): + """Test session command when end date is None and user is currently playing GW2.""" + session_data = [{"acc_name": "TestUser.1234", "start": {"date": "2024-01-15 10:00:00"}, "end": {"date": None}}] + mock_ctx.message.author.activity = MagicMock() + mock_ctx.message.author.activity.name = "Guild Wars 2" + with patch("src.gw2.cogs.sessions.Gw2KeyDal") as mock_dal: + mock_dal.return_value.get_api_key_by_user = AsyncMock(return_value=sample_api_key_data) + with patch("src.gw2.cogs.sessions.Gw2ConfigsDal") as mock_configs: + mock_configs.return_value.get_gw2_server_configs = AsyncMock(return_value=[{"session": True}]) + with patch("src.gw2.cogs.sessions.Gw2SessionsDal") as mock_sessions_dal: + mock_sessions_dal.return_value.get_user_last_session = AsyncMock(return_value=session_data) + with patch("src.gw2.cogs.sessions.gw2_utils.send_msg") as mock_send: + await session(mock_ctx) + mock_send.assert_called_once() + assert gw2_messages.SESSION_IN_PROGRESS in mock_send.call_args[0][1] @pytest.mark.asyncio async def test_session_time_passed_less_than_one_minute(self, mock_ctx, sample_api_key_data):