diff --git a/pyproject.toml b/pyproject.toml index 7848c20..5869d35 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "DiscordBot" -version = "3.0.4" +version = "3.0.5" description = "A simple Discord bot with OpenAI support and server administration tools" urls.Repository = "https://github.com/ddc/DiscordBot" urls.Homepage = "https://github.com/ddc/DiscordBot" @@ -36,7 +36,7 @@ dependencies = [ "ddcdatabases[postgres]>=3.0.10", "discord-py>=2.6.4", "gTTS>=2.5.4", - "openai>=2.23.0", + "openai>=2.24.0", "PyNaCl>=1.6.2", "pythonLogs>=6.0.2", ] diff --git a/src/bot/constants/messages.py b/src/bot/constants/messages.py index 745d47f..9d79fff 100644 --- a/src/bot/constants/messages.py +++ b/src/bot/constants/messages.py @@ -89,7 +89,7 @@ class CommandError: DM_CANNOT_EXECUTE = "Cannot execute action on a DM channel" PRIVILEGE_LOW = "Your Privilege is too low." DIRECT_MESSAGES_DISABLED = ( - "Direct messages are disable in your configuration.\n" + "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."' @@ -169,7 +169,7 @@ class BotUtils: LOADING_EXTENSIONS = "Loading Bot Extensions..." LOADING_EXTENSION_FAILED = "ERROR: FAILED to load extension" DISABLED_DM = ( - "Direct messages are disable in your configuration.\n" + "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' diff --git a/src/bot/tools/bot_utils.py b/src/bot/tools/bot_utils.py index 34f2f5e..c1e8aa7 100644 --- a/src/bot/tools/bot_utils.py +++ b/src/bot/tools/bot_utils.py @@ -127,20 +127,26 @@ async def send_embed(ctx, embed, dm=False): await ctx.author.send(embed=embed) elif dm: # Send to DM and notify in channel - await ctx.author.send(embed=embed) - notification_embed = discord.Embed(description="📬 Response sent to your DM", color=discord.Color.green()) - notification_embed.set_author( - name=ctx.author.display_name, - icon_url=ctx.author.avatar.url if ctx.author.avatar else ctx.author.default_avatar.url, - ) - await ctx.send(embed=notification_embed) + try: + await ctx.author.send(embed=embed) + notification_embed = discord.Embed( + description="📬 Response sent to your DM", color=discord.Color.green() + ) + notification_embed.set_author( + name=ctx.author.display_name, + 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): + # 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.logger.error(e) + ctx.bot.log.error(e) async def delete_message(ctx, warning=False): diff --git a/src/gw2/cogs/account.py b/src/gw2/cogs/account.py index 5a73a0c..f95ee73 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 521a276..ed849be 100644 --- a/src/gw2/cogs/sessions.py +++ b/src/gw2/cogs/sessions.py @@ -84,13 +84,13 @@ async def session(ctx): gw2_session_dal = Gw2SessionsDal(ctx.bot.db_session, ctx.bot.log) rs_session = await gw2_session_dal.get_user_last_session(user_id) if not rs_session: - return await bot_utils.send_error_msg(ctx, gw2_messages.USER_NO_SESSION_FOUND, True) + return await bot_utils.send_error_msg(ctx, gw2_messages.USER_NO_SESSION_FOUND) rs_start = rs_session[0]["start"] 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, True) + return await bot_utils.send_error_msg(ctx, gw2_messages.SESSION_SAVE_ERROR) await ctx.message.channel.typing() color = ctx.bot.settings["gw2"]["EmbedColor"] diff --git a/tests/unit/bot/tools/test_bot_utils_extra.py b/tests/unit/bot/tools/test_bot_utils_extra.py index d6c0576..2308913 100644 --- a/tests/unit/bot/tools/test_bot_utils_extra.py +++ b/tests/unit/bot/tools/test_bot_utils_extra.py @@ -227,8 +227,8 @@ async def test_send_embed_generic_exception(self, mock_ctx): await bot_utils.send_embed(mock_ctx, embed, dm=False) - mock_ctx.bot.logger.error.assert_called_once() - error_arg = mock_ctx.bot.logger.error.call_args[0][0] + mock_ctx.bot.log.error.assert_called_once() + error_arg = mock_ctx.bot.log.error.call_args[0][0] assert isinstance(error_arg, ValueError) assert str(error_arg) == "Some unexpected error" diff --git a/tests/unit/gw2/cogs/test_sessions.py b/tests/unit/gw2/cogs/test_sessions.py index 1d6df9b..b0dee07 100644 --- a/tests/unit/gw2/cogs/test_sessions.py +++ b/tests/unit/gw2/cogs/test_sessions.py @@ -267,7 +267,8 @@ async def test_session_no_session_found(self, mock_ctx, sample_api_key_data): with patch("src.gw2.cogs.sessions.bot_utils.send_error_msg") as mock_error: await session(mock_ctx) mock_error.assert_called_once() - assert mock_error.call_args[0][2] is True + # Error sent to channel (not via DM) + assert len(mock_error.call_args[0]) == 2 @pytest.mark.asyncio async def test_session_end_date_is_none(self, mock_ctx, sample_api_key_data): @@ -282,7 +283,8 @@ async def test_session_end_date_is_none(self, mock_ctx, sample_api_key_data): with patch("src.gw2.cogs.sessions.bot_utils.send_error_msg") as mock_error: await session(mock_ctx) mock_error.assert_called_once() - assert mock_error.call_args[0][2] is True + # Error sent to channel (not via DM) + assert len(mock_error.call_args[0]) == 2 @pytest.mark.asyncio async def test_session_time_passed_less_than_one_minute(self, mock_ctx, sample_api_key_data): diff --git a/uv.lock b/uv.lock index 69ec33b..7190b34 100644 --- a/uv.lock +++ b/uv.lock @@ -365,7 +365,7 @@ wheels = [ [[package]] name = "discordbot" -version = "3.0.4" +version = "3.0.5" source = { virtual = "." } dependencies = [ { name = "alembic" }, @@ -396,7 +396,7 @@ requires-dist = [ { name = "ddcdatabases", extras = ["postgres"], specifier = ">=3.0.10" }, { name = "discord-py", specifier = ">=2.6.4" }, { name = "gtts", specifier = ">=2.5.4" }, - { name = "openai", specifier = ">=2.23.0" }, + { name = "openai", specifier = ">=2.24.0" }, { name = "pynacl", specifier = ">=1.6.2" }, { name = "pythonlogs", specifier = ">=6.0.2" }, ] @@ -689,7 +689,7 @@ wheels = [ [[package]] name = "openai" -version = "2.23.0" +version = "2.24.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -701,9 +701,9 @@ dependencies = [ { name = "tqdm" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/61/4b/dc1d84b8237205ebe48a1b1c9c3a8e1ab9fd08b30811b6d787196df58fd6/openai-2.23.0.tar.gz", hash = "sha256:7d24cc8087d5e8eed58e98aaa823391d39d12f9a9a2755770f67c7bb2004d94c", size = 657323, upload-time = "2026-02-24T03:20:20.323Z" } +sdist = { url = "https://files.pythonhosted.org/packages/55/13/17e87641b89b74552ed408a92b231283786523edddc95f3545809fab673c/openai-2.24.0.tar.gz", hash = "sha256:1e5769f540dbd01cb33bc4716a23e67b9d695161a734aff9c5f925e2bf99a673", size = 658717, upload-time = "2026-02-24T20:02:07.958Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1d/5f/bcdf0fb510c24f021e485f920677da363cd59d6e0310171bf2cad6e052b5/openai-2.23.0-py3-none-any.whl", hash = "sha256:1041d40bebf845053fda1946104f8bf9c3e2df957a41c3878c55c72c352630e9", size = 1118971, upload-time = "2026-02-24T03:20:18.708Z" }, + { url = "https://files.pythonhosted.org/packages/c9/30/844dc675ee6902579b8eef01ed23917cc9319a1c9c0c14ec6e39340c96d0/openai-2.24.0-py3-none-any.whl", hash = "sha256:fed30480d7d6c884303287bde864980a4b137b60553ffbcf9ab4a233b7a73d94", size = 1120122, upload-time = "2026-02-24T20:02:05.669Z" }, ] [[package]]