From 3f0ef5cfeb84d8fc56bf26bc7cbb337d24d9acc4 Mon Sep 17 00:00:00 2001
From: ddc <34492089+ddc@users.noreply.github.com>
Date: Thu, 26 Mar 2026 13:33:29 -0300
Subject: [PATCH 1/2] v3.0.10
---
.github/FUNDING.yml | 2 +-
.github/PULL_REQUEST_TEMPLATE | 3 -
.github/workflows/workflow.yml | 4 +-
.gitignore | 11 +-
README.md | 4 +-
pyproject.toml | 6 +-
src/bot/constants/messages.py | 214 +++++++++++++++---------------
src/bot/constants/settings.py | 32 ++---
src/gw2/constants/gw2_messages.py | 108 +++++++--------
src/gw2/constants/gw2_teams.py | 4 +-
src/gw2/tools/gw2_client.py | 3 +-
uv.lock | 22 +--
12 files changed, 208 insertions(+), 205 deletions(-)
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
index 6505644f..b909b3c4 100644
--- a/.github/FUNDING.yml
+++ b/.github/FUNDING.yml
@@ -1,3 +1,3 @@
github: ddc
-ko_fi: ddcsta
+ko_fi: ddc
custom: "https://www.paypal.com/ncp/payment/6G9Z78QHUD4RJ"
diff --git a/.github/PULL_REQUEST_TEMPLATE b/.github/PULL_REQUEST_TEMPLATE
index eba1f84f..63a08014 100644
--- a/.github/PULL_REQUEST_TEMPLATE
+++ b/.github/PULL_REQUEST_TEMPLATE
@@ -1,6 +1,3 @@
-## Summary
-
-
## Changes Made
diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml
index 96d9b968..a38f7723 100644
--- a/.github/workflows/workflow.yml
+++ b/.github/workflows/workflow.yml
@@ -34,7 +34,7 @@ jobs:
run: uv python install ${{ env.LATEST_PYTHON_VERSION }}
- name: Install dependencies
- run: uv sync --locked --all-extras --group dev
+ run: uv sync --locked --all-extras --all-groups
- name: Run tests with coverage
uses: nick-fields/retry@v3
@@ -68,7 +68,7 @@ jobs:
run: uv python install ${{ env.LATEST_PYTHON_VERSION }}
- name: Install dependencies
- run: uv sync --locked --all-extras --group dev
+ run: uv sync --locked --all-extras --all-groups
- name: Run integration tests
uses: nick-fields/retry@v3
diff --git a/.gitignore b/.gitignore
index 0c39f7c3..726e6c0f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -122,12 +122,11 @@ celerybeat.pid
# Environments
.env
.env.prod
-.venv
-env/
-venv/
-ENV/
-env.bak/
-venv.bak/
+.env.dev
+.venv*
+env*/
+venv*/
+ENV*/
# Spyder project settings
.spyderproject
diff --git a/README.md b/README.md
index 2c8ea6b5..c5e8195b 100644
--- a/README.md
+++ b/README.md
@@ -11,10 +11,10 @@
-
+
@@ -266,7 +266,7 @@ Requires [UV](https://docs.astral.sh/uv/getting-started/installation/) to be ins
## Setup
```shell
-uv sync --all-extras --all-groups
+uv lock --upgrade && uv sync --all-extras --all-groups
```
## Running Tests
diff --git a/pyproject.toml b/pyproject.toml
index b355ec43..3327da2a 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "DiscordBot"
-version = "3.0.9"
+version = "3.0.10"
description = "A simple Discord bot with OpenAI support and server administration tools"
urls.Repository = "https://github.com/ddc/DiscordBot"
urls.Homepage = "https://ddc.github.io/DiscordBot"
@@ -36,7 +36,7 @@ dependencies = [
"ddcdatabases[postgres]>=4.0.1",
"discord-py>=2.7.1",
"gTTS>=2.5.4",
- "openai>=2.29.0",
+ "openai>=2.30.0",
"PyNaCl>=1.6.2",
"pythonLogs>=7.0.0",
"uuid-utils>=0.14.1",
@@ -63,7 +63,7 @@ test-integration = "uv run pytest tests/integration"
hadolint.shell = "docker run --rm -i -v $(pwd)/.hadolint.yml:/.config/hadolint.yml:ro hadolint/hadolint < Dockerfile"
test-docker = "uv run pytest tests/docker"
tests.sequence = ["linter", "hadolint", "test-docker", "test", "test-integration"]
-updatedev.sequence = ["linter", {shell = "uv lock --upgrade && uv sync --all-extras --group dev"}]
+updatedev.sequence = ["linter", {shell = "uv lock --upgrade && uv sync --all-extras --all-groups"}]
migration = "uv run --frozen alembic upgrade head"
[tool.pytest.ini_options]
diff --git a/src/bot/constants/messages.py b/src/bot/constants/messages.py
index fddcfa7f..68689749 100644
--- a/src/bot/constants/messages.py
+++ b/src/bot/constants/messages.py
@@ -1,3 +1,5 @@
+from typing import Final
+
"""Bot message constants organized by domain."""
@@ -225,17 +227,17 @@ class Owner:
# ============================================================================
# Bot
-BOT_TOKEN_NOT_FOUND = Bot.MISSING_ENV_VAR
-BOT_TERMINATED = Bot.TERMINATED
-BOT_STOPPED_CTRTC = Bot.STOPPED_CTRTC
-BOT_FATAL_ERROR_MAIN = Bot.FATAL_ERROR_MAIN
-BOT_CRASHED = Bot.CRASHED
-BOT_CLOSING = Bot.CLOSING
-BOT_LOGIN_FAILED = Bot.LOGIN_FAILED
-BOT_INIT_PREFIX_FAILED = Bot.INIT_PREFIX_FAILED
-BOT_LOAD_SETTINGS_FAILED = Bot.LOAD_SETTINGS_FAILED
-BOT_LOAD_COGS_FAILED = Bot.LOAD_COGS_FAILED
-BOT_LOADED_ALL_COGS_SUCCESS = Bot.LOADED_ALL_COGS_SUCCESS
+BOT_TOKEN_NOT_FOUND: Final = Bot.MISSING_ENV_VAR
+BOT_TERMINATED: Final = Bot.TERMINATED
+BOT_STOPPED_CTRTC: Final = Bot.STOPPED_CTRTC
+BOT_FATAL_ERROR_MAIN: Final = Bot.FATAL_ERROR_MAIN
+BOT_CRASHED: Final = Bot.CRASHED
+BOT_CLOSING: Final = Bot.CLOSING
+BOT_LOGIN_FAILED: Final = Bot.LOGIN_FAILED
+BOT_INIT_PREFIX_FAILED: Final = Bot.INIT_PREFIX_FAILED
+BOT_LOAD_SETTINGS_FAILED: Final = Bot.LOAD_SETTINGS_FAILED
+BOT_LOAD_COGS_FAILED: Final = Bot.LOAD_COGS_FAILED
+BOT_LOADED_ALL_COGS_SUCCESS: Final = Bot.LOADED_ALL_COGS_SUCCESS
bot_online = Bot.online
bot_starting = Bot.starting
bot_disconnected = Bot.disconnected
@@ -245,129 +247,129 @@ class Owner:
bg_task_warning = Admin.bg_task_warning
# Config
-CONFIG_JOIN = Config.JOIN
-CONFIG_LEAVE = Config.LEAVE
-CONFIG_SERVER = Config.SERVER
-CONFIG_MEMBER = Config.MEMBER
-CONFIG_BLOCK_INVIS_MEMBERS = Config.BLOCK_INVIS_MEMBERS
-CONFIG_BOT_WORD_REACTIONS = Config.BOT_WORD_REACTIONS
-CONFIG_PFILTER_CHANNELS = Config.PFILTER_CHANNELS
+CONFIG_JOIN: Final = Config.JOIN
+CONFIG_LEAVE: Final = Config.LEAVE
+CONFIG_SERVER: Final = Config.SERVER
+CONFIG_MEMBER: Final = Config.MEMBER
+CONFIG_BLOCK_INVIS_MEMBERS: Final = Config.BLOCK_INVIS_MEMBERS
+CONFIG_BOT_WORD_REACTIONS: Final = Config.BOT_WORD_REACTIONS
+CONFIG_PFILTER_CHANNELS: Final = Config.PFILTER_CHANNELS
config_pfilter = Config.pfilter
-CONFIG_CHANNEL_ID_INSTEAD_NAME = Config.CHANNEL_ID_INSTEAD_NAME
-CONFIG_NOT_ACTIVATED_ERROR = Config.NOT_ACTIVATED_ERROR
-MISING_REUIRED_ARGUMENT = Config.MISSING_REQUIRED_ARGUMENT
-CHANNEL_ID_NOT_FOUND = Config.CHANNEL_ID_NOT_FOUND
-BOT_MISSING_MANAGE_MESSAGES_PERMISSION = Config.BOT_MISSING_MANAGE_MESSAGES
-NO_CHANNELS_LISTED = Config.NO_CHANNELS_LISTED
+CONFIG_CHANNEL_ID_INSTEAD_NAME: Final = Config.CHANNEL_ID_INSTEAD_NAME
+CONFIG_NOT_ACTIVATED_ERROR: Final = Config.NOT_ACTIVATED_ERROR
+MISING_REUIRED_ARGUMENT: Final = Config.MISSING_REQUIRED_ARGUMENT
+CHANNEL_ID_NOT_FOUND: Final = Config.CHANNEL_ID_NOT_FOUND
+BOT_MISSING_MANAGE_MESSAGES_PERMISSION: Final = Config.BOT_MISSING_MANAGE_MESSAGES
+NO_CHANNELS_LISTED: Final = Config.NO_CHANNELS_LISTED
# Custom Command
-ALREADY_A_STANDARD_COMMAND = CustomCommand.ALREADY_A_STANDARD_COMMAND
-COMMAND_LENGHT_ERROR = CustomCommand.LENGTH_ERROR
-CUSTOM_COMMAND_ADDED = CustomCommand.ADDED
-CUSTOM_COMMAND_EDITED = CustomCommand.EDITED
-CUSTOM_COMMAND_REMOVED = CustomCommand.REMOVED
-CUSTOM_COMMAND_ALL_REMOVED = CustomCommand.ALL_REMOVED
-COMMAND_ALREADY_EXISTS = CustomCommand.ALREADY_EXISTS
-NO_CUSTOM_COMMANDS_FOUND = CustomCommand.NO_COMMANDS_FOUND
-CUSTOM_COMMAND_UNABLE_REMOVE = CustomCommand.UNABLE_REMOVE
-CUSTOM_COMMANDS_SERVER = CustomCommand.COMMANDS_SERVER
-GET_CONFIGS_ERROR = CustomCommand.GET_CONFIGS_ERROR
+ALREADY_A_STANDARD_COMMAND: Final = CustomCommand.ALREADY_A_STANDARD_COMMAND
+COMMAND_LENGHT_ERROR: Final = CustomCommand.LENGTH_ERROR
+CUSTOM_COMMAND_ADDED: Final = CustomCommand.ADDED
+CUSTOM_COMMAND_EDITED: Final = CustomCommand.EDITED
+CUSTOM_COMMAND_REMOVED: Final = CustomCommand.REMOVED
+CUSTOM_COMMAND_ALL_REMOVED: Final = CustomCommand.ALL_REMOVED
+COMMAND_ALREADY_EXISTS: Final = CustomCommand.ALREADY_EXISTS
+NO_CUSTOM_COMMANDS_FOUND: Final = CustomCommand.NO_COMMANDS_FOUND
+CUSTOM_COMMAND_UNABLE_REMOVE: Final = CustomCommand.UNABLE_REMOVE
+CUSTOM_COMMANDS_SERVER: Final = CustomCommand.COMMANDS_SERVER
+GET_CONFIGS_ERROR: Final = CustomCommand.GET_CONFIGS_ERROR
# Command Error
-MISSING_REQUIRED_ARGUMENT_HELP_MESSAGE = CommandError.MISSING_REQUIRED_ARGUMENT_HELP
-COMMAND_NOT_FOUND = CommandError.NOT_FOUND
-COMMAND_ERROR = CommandError.ERROR
-COMMAND_RAISED_EXCEPTION = CommandError.RAISED_EXCEPTION
-NOT_ADMIN_USE_COMMAND = CommandError.NOT_ADMIN
-BOT_OWNERS_ONLY_COMMAND = CommandError.OWNERS_ONLY
-PREFIXES_CHOICE = CommandError.PREFIXES_CHOICE
-MORE_INFO = CommandError.MORE_INFO
-UNKNOWN_OPTION = CommandError.UNKNOWN_OPTION
-HELP_COMMAND_MORE_INFO = CommandError.HELP_MORE_INFO
-NO_OPTION_FOUND = CommandError.NO_OPTION_FOUND
-NO_PERMISSION_EXECUTE_COMMAND = CommandError.NO_PERMISSION
-INVALID_MESSAGE = CommandError.INVALID_MESSAGE
-COMMAND_INTERNAL_ERROR = CommandError.INTERNAL_ERROR
-CONTACT_ADMIN = CommandError.CONTACT_ADMIN
-DM_CANNOT_EXECUTE_COMMAND = CommandError.DM_CANNOT_EXECUTE
-PRIVILEGE_LOW = CommandError.PRIVILEGE_LOW
-DIRECT_MESSAGES_DISABLED = CommandError.DIRECT_MESSAGES_DISABLED
+MISSING_REQUIRED_ARGUMENT_HELP_MESSAGE: Final = CommandError.MISSING_REQUIRED_ARGUMENT_HELP
+COMMAND_NOT_FOUND: Final = CommandError.NOT_FOUND
+COMMAND_ERROR: Final = CommandError.ERROR
+COMMAND_RAISED_EXCEPTION: Final = CommandError.RAISED_EXCEPTION
+NOT_ADMIN_USE_COMMAND: Final = CommandError.NOT_ADMIN
+BOT_OWNERS_ONLY_COMMAND: Final = CommandError.OWNERS_ONLY
+PREFIXES_CHOICE: Final = CommandError.PREFIXES_CHOICE
+MORE_INFO: Final = CommandError.MORE_INFO
+UNKNOWN_OPTION: Final = CommandError.UNKNOWN_OPTION
+HELP_COMMAND_MORE_INFO: Final = CommandError.HELP_MORE_INFO
+NO_OPTION_FOUND: Final = CommandError.NO_OPTION_FOUND
+NO_PERMISSION_EXECUTE_COMMAND: Final = CommandError.NO_PERMISSION
+INVALID_MESSAGE: Final = CommandError.INVALID_MESSAGE
+COMMAND_INTERNAL_ERROR: Final = CommandError.INTERNAL_ERROR
+CONTACT_ADMIN: Final = CommandError.CONTACT_ADMIN
+DM_CANNOT_EXECUTE_COMMAND: Final = CommandError.DM_CANNOT_EXECUTE
+PRIVILEGE_LOW: Final = CommandError.PRIVILEGE_LOW
+DIRECT_MESSAGES_DISABLED: Final = CommandError.DIRECT_MESSAGES_DISABLED
# Guild Join
guild_join_bot_message = GuildJoin.bot_message
# Guild Update
-NEW_SERVER_SETTINGS = GuildUpdate.NEW_SERVER_SETTINGS
-NEW_SERVER_ICON = GuildUpdate.NEW_SERVER_ICON
-NEW_SERVER_NAME = GuildUpdate.NEW_SERVER_NAME
-PREVIOUS_NAME = GuildUpdate.PREVIOUS_NAME
-PREVIOUS_SERVER_OWNER = GuildUpdate.PREVIOUS_SERVER_OWNER
-NEW_SERVER_OWNER = GuildUpdate.NEW_SERVER_OWNER
+NEW_SERVER_SETTINGS: Final = GuildUpdate.NEW_SERVER_SETTINGS
+NEW_SERVER_ICON: Final = GuildUpdate.NEW_SERVER_ICON
+NEW_SERVER_NAME: Final = GuildUpdate.NEW_SERVER_NAME
+PREVIOUS_NAME: Final = GuildUpdate.PREVIOUS_NAME
+PREVIOUS_SERVER_OWNER: Final = GuildUpdate.PREVIOUS_SERVER_OWNER
+NEW_SERVER_OWNER: Final = GuildUpdate.NEW_SERVER_OWNER
# Member Join
-JOINED_THE_SERVER = MemberJoin.JOINED_THE_SERVER
+JOINED_THE_SERVER: Final = MemberJoin.JOINED_THE_SERVER
# Member Remove
-LEFT_THE_SERVER = MemberRemove.LEFT_THE_SERVER
+LEFT_THE_SERVER: Final = MemberRemove.LEFT_THE_SERVER
# Member Update
-PROFILE_CHANGES = MemberUpdate.PROFILE_CHANGES
-PREVIOUS_NICKNAME = MemberUpdate.PREVIOUS_NICKNAME
-NEW_NICKNAME = MemberUpdate.NEW_NICKNAME
-PREVIOUS_ROLES = MemberUpdate.PREVIOUS_ROLES
-NEW_ROLES = MemberUpdate.NEW_ROLES
+PROFILE_CHANGES: Final = MemberUpdate.PROFILE_CHANGES
+PREVIOUS_NICKNAME: Final = MemberUpdate.PREVIOUS_NICKNAME
+NEW_NICKNAME: Final = MemberUpdate.NEW_NICKNAME
+PREVIOUS_ROLES: Final = MemberUpdate.PREVIOUS_ROLES
+NEW_ROLES: Final = MemberUpdate.NEW_ROLES
# Messages
-BOT_REACT_EMOJIS = Messages.BOT_REACT_EMOJIS
-OWNER_DM_BOT_MESSAGE = Messages.OWNER_DM_BOT_MESSAGE
-NO_DM_MESSAGES = Messages.NO_DM_MESSAGES
-DM_COMMAND_NOT_ALLOWED = Messages.DM_COMMAND_NOT_ALLOWED
-DM_COMMANDS_ALLOW_LIST = Messages.DM_COMMANDS_ALLOW_LIST
-BOT_REACT_STUPID = Messages.BOT_REACT_STUPID
-BOT_REACT_RETARD = Messages.BOT_REACT_RETARD
-MESSAGE_CENSURED = Messages.MESSAGE_CENSURED
-PRIVATE_BOT_MESSAGE = Messages.PRIVATE_BOT_MESSAGE
+BOT_REACT_EMOJIS: Final = Messages.BOT_REACT_EMOJIS
+OWNER_DM_BOT_MESSAGE: Final = Messages.OWNER_DM_BOT_MESSAGE
+NO_DM_MESSAGES: Final = Messages.NO_DM_MESSAGES
+DM_COMMAND_NOT_ALLOWED: Final = Messages.DM_COMMAND_NOT_ALLOWED
+DM_COMMANDS_ALLOW_LIST: Final = Messages.DM_COMMANDS_ALLOW_LIST
+BOT_REACT_STUPID: Final = Messages.BOT_REACT_STUPID
+BOT_REACT_RETARD: Final = Messages.BOT_REACT_RETARD
+MESSAGE_CENSURED: Final = Messages.MESSAGE_CENSURED
+PRIVATE_BOT_MESSAGE: Final = Messages.PRIVATE_BOT_MESSAGE
blocked_invis_message = Messages.blocked_invis
# User Update
-NEW_AVATAR = UserUpdate.NEW_AVATAR
-NEW_NAME = UserUpdate.NEW_NAME
-PREVIOUS_DISCRIMINATOR = UserUpdate.PREVIOUS_DISCRIMINATOR
-NEW_DISCRIMINATOR = UserUpdate.NEW_DISCRIMINATOR
+NEW_AVATAR: Final = UserUpdate.NEW_AVATAR
+NEW_NAME: Final = UserUpdate.NEW_NAME
+PREVIOUS_DISCRIMINATOR: Final = UserUpdate.PREVIOUS_DISCRIMINATOR
+NEW_DISCRIMINATOR: Final = UserUpdate.NEW_DISCRIMINATOR
# Bot Utils
-LOADING_EXTENSIONS = BotUtils.LOADING_EXTENSIONS
-LOADING_EXTENSION_FAILED = BotUtils.LOADING_EXTENSION_FAILED
-DISABLED_DM = BotUtils.DISABLED_DM
-MESSAGE_REMOVED_FOR_PRIVACY = BotUtils.MESSAGE_REMOVED_FOR_PRIVACY
-DELETE_MESSAGE_NO_PERMISSION = BotUtils.DELETE_MESSAGE_NO_PERMISSION
-SEND_MESSAGE_FAILED = BotUtils.SEND_MESSAGE_FAILED
+LOADING_EXTENSIONS: Final = BotUtils.LOADING_EXTENSIONS
+LOADING_EXTENSION_FAILED: Final = BotUtils.LOADING_EXTENSION_FAILED
+DISABLED_DM: Final = BotUtils.DISABLED_DM
+MESSAGE_REMOVED_FOR_PRIVACY: Final = BotUtils.MESSAGE_REMOVED_FOR_PRIVACY
+DELETE_MESSAGE_NO_PERMISSION: Final = BotUtils.DELETE_MESSAGE_NO_PERMISSION
+SEND_MESSAGE_FAILED: Final = BotUtils.SEND_MESSAGE_FAILED
# Dice Rolls
-DICE_SIZE_NOT_VALID = DiceRolls.SIZE_NOT_VALID
-MEMBER_HIGHEST_ROLL_ANOUNCE = DiceRolls.MEMBER_HIGHEST_ROLL_ANNOUNCE
-SERVER_HIGHEST_ROLL_ANOUNCE = DiceRolls.SERVER_HIGHEST_ROLL_ANNOUNCE
-MEMBER_SERVER_WINNER_ANOUNCE = DiceRolls.MEMBER_SERVER_WINNER_ANNOUNCE
-MEMBER_HIGHEST_ROLL = DiceRolls.MEMBER_HIGHEST_ROLL
-MEMBER_HAS_HIGHEST_ROLL = DiceRolls.MEMBER_HAS_HIGHEST_ROLL
-DICE_SIZE_HIGHER_ONE = DiceRolls.SIZE_HIGHER_ONE
-RESET_ALL_ROLLS = DiceRolls.RESET_ALL
-DELETED_ALL_ROLLS = DiceRolls.DELETED_ALL
+DICE_SIZE_NOT_VALID: Final = DiceRolls.SIZE_NOT_VALID
+MEMBER_HIGHEST_ROLL_ANOUNCE: Final = DiceRolls.MEMBER_HIGHEST_ROLL_ANNOUNCE
+SERVER_HIGHEST_ROLL_ANOUNCE: Final = DiceRolls.SERVER_HIGHEST_ROLL_ANNOUNCE
+MEMBER_SERVER_WINNER_ANOUNCE: Final = DiceRolls.MEMBER_SERVER_WINNER_ANNOUNCE
+MEMBER_HIGHEST_ROLL: Final = DiceRolls.MEMBER_HIGHEST_ROLL
+MEMBER_HAS_HIGHEST_ROLL: Final = DiceRolls.MEMBER_HAS_HIGHEST_ROLL
+DICE_SIZE_HIGHER_ONE: Final = DiceRolls.SIZE_HIGHER_ONE
+RESET_ALL_ROLLS: Final = DiceRolls.RESET_ALL
+DELETED_ALL_ROLLS: Final = DiceRolls.DELETED_ALL
no_dice_size_rolls = DiceRolls.no_size_rolls
# Misc
-PEPE_DOWNLOAD_ERROR = Misc.PEPE_DOWNLOAD_ERROR
-INVITE_TITLE = Misc.INVITE_TITLE
-UNLIMITED_INVITES = Misc.UNLIMITED_INVITES
-TEMPORARY_INVITES = Misc.TEMPORARY_INVITES
-REVOKED_INVITES = Misc.REVOKED_INVITES
-NO_INVITES = Misc.NO_INVITES
-DO_NOT_DISTURB = Misc.DO_NOT_DISTURB
-JOINED_DISCORD_ON = Misc.JOINED_DISCORD_ON
-JOINED_THIS_SERVER_ON = Misc.JOINED_THIS_SERVER_ON
-LIST_COMMAND_CATEGORIES = Misc.LIST_COMMAND_CATEGORIES
+PEPE_DOWNLOAD_ERROR: Final = Misc.PEPE_DOWNLOAD_ERROR
+INVITE_TITLE: Final = Misc.INVITE_TITLE
+UNLIMITED_INVITES: Final = Misc.UNLIMITED_INVITES
+TEMPORARY_INVITES: Final = Misc.TEMPORARY_INVITES
+REVOKED_INVITES: Final = Misc.REVOKED_INVITES
+NO_INVITES: Final = Misc.NO_INVITES
+DO_NOT_DISTURB: Final = Misc.DO_NOT_DISTURB
+JOINED_DISCORD_ON: Final = Misc.JOINED_DISCORD_ON
+JOINED_THIS_SERVER_ON: Final = Misc.JOINED_THIS_SERVER_ON
+LIST_COMMAND_CATEGORIES: Final = Misc.LIST_COMMAND_CATEGORIES
dev_info_msg = Misc.dev_info
# Owner
-BOT_PREFIX_CHANGED = Owner.PREFIX_CHANGED
-BOT_DESCRIPTION_CHANGED = Owner.DESCRIPTION_CHANGED
+BOT_PREFIX_CHANGED: Final = Owner.PREFIX_CHANGED
+BOT_DESCRIPTION_CHANGED: Final = Owner.DESCRIPTION_CHANGED
diff --git a/src/bot/constants/settings.py b/src/bot/constants/settings.py
index eefede7f..1756f16f 100644
--- a/src/bot/constants/settings.py
+++ b/src/bot/constants/settings.py
@@ -13,30 +13,30 @@ class BotSettings(BaseSettings):
model_config = SettingsConfigDict(env_prefix="BOT_", env_file=".env", extra="allow")
# Bot
- prefix: str | None = Field(default="!")
+ prefix: str = Field(default="!")
token: str | None = Field(default=None)
- bg_activity_timer: int | None = Field(default=0)
- allowed_dm_commands: str | None = Field(default="owner, about, gw2")
- bot_reaction_words: str | None = Field(default="stupid, retard, retarded, noob")
- embed_color: str | None = Field(default="green")
- embed_owner_color: str | None = Field(default="dark_purple")
- exclusive_users: str | None = Field(default="")
+ bg_activity_timer: int = Field(default=0)
+ allowed_dm_commands: str = Field(default="owner, about, gw2")
+ bot_reaction_words: str = Field(default="stupid, retard, retarded, noob")
+ embed_color: str = Field(default="green")
+ embed_owner_color: str = Field(default="dark_purple")
+ exclusive_users: str = Field(default="")
# OpenAi
- openai_model: str | None = Field(default="gpt-5.2")
+ openai_model: str = Field(default="gpt-5.4", description="https://developers.openai.com/api/docs/models")
openai_api_key: str | None = Field(default=None)
# Cooldowns
- admin_cooldown: int | None = Field(default=20)
- config_cooldown: int | None = Field(default=20)
- custom_cmd_cooldown: int | None = Field(default=20)
- dice_rolls_cooldown: int | None = Field(default=10)
- misc_cooldown: int | None = Field(default=20)
- openai_cooldown: int | None = Field(default=10)
- owner_cooldown: int | None = Field(default=5)
+ admin_cooldown: int = Field(default=20)
+ config_cooldown: int = Field(default=20)
+ custom_cmd_cooldown: int = Field(default=20)
+ dice_rolls_cooldown: int = Field(default=10)
+ misc_cooldown: int = Field(default=20)
+ openai_cooldown: int = Field(default=10)
+ owner_cooldown: int = Field(default=5)
# Alembic migration settings
- alembic_version_table_name: str | None = Field(default="alembic_version")
+ alembic_version_table_name: str = Field(default="alembic_version")
@lru_cache(maxsize=1)
diff --git a/src/gw2/constants/gw2_messages.py b/src/gw2/constants/gw2_messages.py
index 02ad5e46..68eb9eda 100644
--- a/src/gw2/constants/gw2_messages.py
+++ b/src/gw2/constants/gw2_messages.py
@@ -1,3 +1,5 @@
+from typing import Final
+
GW2_FULL_NAME = "Guild Wars 2"
#################################
# EVENT ON COMMAND ERROR
@@ -7,24 +9,24 @@
#################################
# GW2 API
#################################
-INVALID_APIKEY_MSG = "This API Key is INVALID or no longer exists in gw2 api database"
-API_ERROR = "GW2 API ERROR"
-API_DOWN = "GW2 API is currently down. Try again later."
-API_NOT_FOUND = "GW2 API Not found."
-API_REQUEST_REACHED = "GW2 API Requests limit has been saturated. Try again later."
-API_ACCESS_DENIED = "Access denied with your GW2 API key."
+INVALID_APIKEY_MSG: Final = "This API Key is INVALID or no longer exists in gw2 api database"
+API_ERROR: Final = "GW2 API ERROR"
+API_DOWN: Final = "GW2 API is currently down. Try again later."
+API_NOT_FOUND: Final = "GW2 API Not found."
+API_REQUEST_REACHED: Final = "GW2 API Requests limit has been saturated. Try again later."
+API_ACCESS_DENIED: Final = "Access denied with your GW2 API key."
#################################
# GW2 UTILS
#################################
-API_KEY_MESSAGE_REMOVED = "Your message with your API Key was removed for privacy."
-API_KEY_MESSAGE_REMOVED_DENIED = (
+API_KEY_MESSAGE_REMOVED: Final = "Your message with your API Key was removed for privacy."
+API_KEY_MESSAGE_REMOVED_DENIED: Final = (
"Bot does not have permission to delete the message with your API key.\nMissing bot permission: `Manage Messages`"
)
#################################
# GW2 ACCOUNT/CHARACTERS
#################################
-NO_API_KEY = "You dont have an API key registered.\n"
-INVALID_API_KEY_HELP_MESSAGE = "This API Key is INVALID or no longer exists in gw2 api database.\n"
+NO_API_KEY: Final = "You dont have an API key registered.\n"
+INVALID_API_KEY_HELP_MESSAGE: Final = "This API Key is INVALID or no longer exists in gw2 api database.\n"
def key_add_info_help(prefix: str) -> str:
@@ -35,27 +37,27 @@ def key_more_info_help(prefix: str) -> str:
return f"To get info about your api key: `{prefix}gw2 key info`"
-API_KEY_NO_PERMISSION = (
+API_KEY_NO_PERMISSION: Final = (
"Your API key doesnt have permission to access your gw2 account.\nPlease add one key with account permission."
)
#################################
# GW2 CONFIG
#################################
-CONFIG_TITLE = "Guild Wars 2 configurations for"
+CONFIG_TITLE: Final = "Guild Wars 2 configurations for"
def config_more_info(prefix: str) -> str:
return f"For more info: {prefix}help gw2 config"
-USER_SESSION_TITLE = "GW2 Users Session"
-SESSION_ACTIVATED = "Last session `ACTIVATED`\nBot will now record Gw2 users last sessions."
-SESSION_DEACTIVATED = "Last session `DEACTIVATED`\nBot will `NOT` record Gw2 users last sessions."
+USER_SESSION_TITLE: Final = "GW2 Users Session"
+SESSION_ACTIVATED: Final = "Last session `ACTIVATED`\nBot will now record Gw2 users last sessions."
+SESSION_DEACTIVATED: Final = "Last session `DEACTIVATED`\nBot will `NOT` record Gw2 users last sessions."
#################################
# GW2 KEY
#################################
-KEY_ALREADY_IN_USE = "That API key is already in use by someone else."
-KEY_REMOVED_SUCCESSFULLY = "Your GW2 API Key has been deleted successfully."
+KEY_ALREADY_IN_USE: Final = "That API key is already in use by someone else."
+KEY_REMOVED_SUCCESSFULLY: Final = "Your GW2 API Key has been deleted successfully."
def key_replaced_successfully(old: str, new: str, server: str) -> str:
@@ -69,63 +71,63 @@ def key_added_successfully(key_name: str, server_name: str) -> str:
#################################
# GW2 MISC
#################################
-LONG_SEARCH = "Search too long"
-WIKI_SEARCH_RESULTS = "Wiki Search Results"
-NO_RESULTS = "No results!"
-CLICK_HERE = "Click here"
+LONG_SEARCH: Final = "Search too long"
+WIKI_SEARCH_RESULTS: Final = "Wiki Search Results"
+NO_RESULTS: Final = "No results!"
+CLICK_HERE: Final = "Click here"
def displaying_wiki_search_title(count: int, keyword: str) -> str:
return f"Displaying **{count}** closest titles that matches **{keyword}**"
-CLICK_ON_LINK = "Click on link above for more info !!!"
+CLICK_ON_LINK: Final = "Click on link above for more info !!!"
#################################
# GW2 SESSIONS
#################################
-SESSION_TITLE = "GW2 Last Session"
+SESSION_TITLE: Final = "GW2 Last Session"
def session_not_active(prefix: str) -> str:
return f"Last session is not active on this server.\nTo activate use: `{prefix}gw2 config session on`"
-SESSION_MISSING_PERMISSIONS_TITLE = "To use this command your API key needs to have the following permissions"
-ADD_RIGHT_API_KEY_PERMISSIONS = (
+SESSION_MISSING_PERMISSIONS_TITLE: Final = "To use this command your API key needs to have the following permissions"
+ADD_RIGHT_API_KEY_PERMISSIONS: Final = (
"Please add another API key with permissions that are MISSING if you want to use this command."
)
-SESSION_BOT_STILL_UPDATING = "Bot still updating your stats!"
-SESSION_END_PROCESSING = (
+SESSION_BOT_STILL_UPDATING: Final = "Bot still updating your stats!"
+SESSION_END_PROCESSING: Final = (
"Your session is still being processed.\n"
"The bot waits a few minutes after you stop playing to ensure accurate data from the GW2 API.\n"
"Please try again shortly."
)
-SESSION_USER_STILL_PLAYING = "You are playing Guild Wars 2 at the moment.\nYour stats may NOT be accurate."
-WAITING_TIME = "Waiting time"
-ACCOUNT_NAME = "Account Name"
-SERVER = "Server"
-PLAY_TIME = "Play time"
-TIMES_YOU_DIED = "Times you died"
-WVW_RANKS = "WvW ranks"
-YAKS_KILLED = "Yaks killed"
-YAKS_SCORTED = "Yaks escorted"
-PLAYERS_KILLED = "Players killed"
-KEEPS_CAPTURED = "Keeps captured"
-TOWERS_CAPTURED = "Towers captured"
-CAMPS_CAPTURED = "Camps captured"
-SMC_CAPTURED = "SMC captured"
-SESSION_IN_PROGRESS = (
+SESSION_USER_STILL_PLAYING: Final = "You are playing Guild Wars 2 at the moment.\nYour stats may NOT be accurate."
+WAITING_TIME: Final = "Waiting time"
+ACCOUNT_NAME: Final = "Account Name"
+SERVER: Final = "Server"
+PLAY_TIME: Final = "Play time"
+TIMES_YOU_DIED: Final = "Times you died"
+WVW_RANKS: Final = "WvW ranks"
+YAKS_KILLED: Final = "Yaks killed"
+YAKS_SCORTED: Final = "Yaks escorted"
+PLAYERS_KILLED: Final = "Players killed"
+KEEPS_CAPTURED: Final = "Keeps captured"
+TOWERS_CAPTURED: Final = "Towers captured"
+CAMPS_CAPTURED: Final = "Camps captured"
+SMC_CAPTURED: Final = "SMC captured"
+SESSION_IN_PROGRESS: Final = (
"Your Guild Wars 2 session is still in progress.\nSession stats will be available after you stop playing."
)
-SESSION_SAVE_ERROR = (
+SESSION_SAVE_ERROR: Final = (
"There was a problem trying to record your last finished session.\n"
"Please, do not close discord when the game is running."
)
-SESSION_API_DOWN_DM = (
+SESSION_API_DOWN_DM: Final = (
"The GW2 API was unreachable while recording your session.\n"
"Your session data may be incomplete. Please try again later."
)
-USER_NO_SESSION_FOUND = (
+USER_NO_SESSION_FOUND: Final = (
"No records were found in your name.\n"
"You are probably trying to execute this command without playing the game.\n"
"Make sure your status is NOT set to invisible in discord.\n"
@@ -135,20 +137,20 @@ def session_not_active(prefix: str) -> str:
#################################
# GW2 WORLDS
#################################
-NA_SERVERS_TITLE = "~~~~~ NA Servers ~~~~~"
-EU_SERVERS_TITLE = "~~~~~ EU Servers ~~~~~"
+NA_SERVERS_TITLE: Final = "~~~~~ NA Servers ~~~~~"
+EU_SERVERS_TITLE: Final = "~~~~~ EU Servers ~~~~~"
#################################
# GW2 WVW
#################################
-INVALID_WORLD_NAME = "Invalid world name"
-MISSING_WORLD_NAME = "Missing World Name"
-WORLD_COLOR_ERROR = "Could not resolve world's color"
+INVALID_WORLD_NAME: Final = "Invalid world name"
+MISSING_WORLD_NAME: Final = "Missing World Name"
+WORLD_COLOR_ERROR: Final = "Could not resolve world's color"
def match_world_name_help(prefix: str) -> str:
return f"Use `{prefix}gw2 match `\nOr register an API key on your account.\n"
-WVW_KDR_TITLE = "WvW Kills/Death Ratings"
-NA_TIER_TITLE = "North America Tier"
-EU_TIER_TITLE = "Europe Tier"
+WVW_KDR_TITLE: Final = "WvW Kills/Death Ratings"
+NA_TIER_TITLE: Final = "North America Tier"
+EU_TIER_TITLE: Final = "Europe Tier"
diff --git a/src/gw2/constants/gw2_teams.py b/src/gw2/constants/gw2_teams.py
index a92fa6de..f25a3476 100644
--- a/src/gw2/constants/gw2_teams.py
+++ b/src/gw2/constants/gw2_teams.py
@@ -5,8 +5,10 @@
API, so names must be hardcoded.
"""
+from typing import Final
+
# NA teams: 11001-11012, EU teams: 12001-12015
-WR_TEAM_NAMES: dict[int, str] = {
+WR_TEAM_NAMES: Final[dict[int, str]] = {
# North America
11001: "Team 1 (NA)",
11002: "Team 2 (NA)",
diff --git a/src/gw2/tools/gw2_client.py b/src/gw2/tools/gw2_client.py
index 5ee8236c..c52bfe01 100644
--- a/src/gw2/tools/gw2_client.py
+++ b/src/gw2/tools/gw2_client.py
@@ -10,9 +10,10 @@
APIInvalidKey,
APINotFound,
)
+from typing import Final
_gw2_settings = get_gw2_settings()
-_RETRYABLE_STATUSES = (502, 503, 504)
+_RETRYABLE_STATUSES: Final = (502, 503, 504)
class Gw2Client:
diff --git a/uv.lock b/uv.lock
index a1aea700..d97c993b 100644
--- a/uv.lock
+++ b/uv.lock
@@ -99,14 +99,14 @@ wheels = [
[[package]]
name = "anyio"
-version = "4.12.1"
+version = "4.13.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "idna" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/96/f0/5eb65b2bb0d09ac6776f2eb54adee6abe8228ea05b20a5ad0e4945de8aac/anyio-4.12.1.tar.gz", hash = "sha256:41cfcc3a4c85d3f05c932da7c26d0201ac36f72abd4435ba90d0464a3ffed703", size = 228685, upload-time = "2026-01-06T11:45:21.246Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/19/14/2c5dd9f512b66549ae92767a9c7b330ae88e1932ca57876909410251fe13/anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc", size = 231622, upload-time = "2026-03-24T12:59:09.671Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl", hash = "sha256:d405828884fc140aa80a3c667b8beed277f1dfedec42ba031bd6ac3db606ab6c", size = 113592, upload-time = "2026-01-06T11:45:19.497Z" },
+ { url = "https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708", size = 114353, upload-time = "2026-03-24T12:59:08.246Z" },
]
[[package]]
@@ -381,7 +381,7 @@ wheels = [
[[package]]
name = "discordbot"
-version = "3.0.9"
+version = "3.0.10"
source = { virtual = "." }
dependencies = [
{ name = "alembic" },
@@ -413,7 +413,7 @@ requires-dist = [
{ name = "ddcdatabases", extras = ["postgres"], specifier = ">=4.0.1" },
{ name = "discord-py", specifier = ">=2.7.1" },
{ name = "gtts", specifier = ">=2.5.4" },
- { name = "openai", specifier = ">=2.29.0" },
+ { name = "openai", specifier = ">=2.30.0" },
{ name = "pynacl", specifier = ">=1.6.2" },
{ name = "pythonlogs", specifier = ">=7.0.0" },
{ name = "uuid-utils", specifier = ">=0.14.1" },
@@ -707,7 +707,7 @@ wheels = [
[[package]]
name = "openai"
-version = "2.29.0"
+version = "2.30.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyio" },
@@ -719,9 +719,9 @@ dependencies = [
{ name = "tqdm" },
{ name = "typing-extensions" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/b4/15/203d537e58986b5673e7f232453a2a2f110f22757b15921cbdeea392e520/openai-2.29.0.tar.gz", hash = "sha256:32d09eb2f661b38d3edd7d7e1a2943d1633f572596febe64c0cd370c86d52bec", size = 671128, upload-time = "2026-03-17T17:53:49.599Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/88/15/52580c8fbc16d0675d516e8749806eda679b16de1e4434ea06fb6feaa610/openai-2.30.0.tar.gz", hash = "sha256:92f7661c990bda4b22a941806c83eabe4896c3094465030dd882a71abe80c885", size = 676084, upload-time = "2026-03-25T22:08:59.96Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/d0/b1/35b6f9c8cf9318e3dbb7146cc82dab4cf61182a8d5406fc9b50864362895/openai-2.29.0-py3-none-any.whl", hash = "sha256:b7c5de513c3286d17c5e29b92c4c98ceaf0d775244ac8159aeb1bddf840eb42a", size = 1141533, upload-time = "2026-03-17T17:53:47.348Z" },
+ { url = "https://files.pythonhosted.org/packages/2a/9e/5bfa2270f902d5b92ab7d41ce0475b8630572e71e349b2a4996d14bdda93/openai-2.30.0-py3-none-any.whl", hash = "sha256:9a5ae616888eb2748ec5e0c5b955a51592e0b201a11f4262db920f2a78c5231d", size = 1146656, upload-time = "2026-03-25T22:08:58.2Z" },
]
[[package]]
@@ -1046,7 +1046,7 @@ wheels = [
[[package]]
name = "requests"
-version = "2.32.5"
+version = "2.33.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "certifi" },
@@ -1054,9 +1054,9 @@ dependencies = [
{ name = "idna" },
{ name = "urllib3" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/34/64/8860370b167a9721e8956ae116825caff829224fbca0ca6e7bf8ddef8430/requests-2.33.0.tar.gz", hash = "sha256:c7ebc5e8b0f21837386ad0e1c8fe8b829fa5f544d8df3b2253bff14ef29d7652", size = 134232, upload-time = "2026-03-25T15:10:41.586Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" },
+ { url = "https://files.pythonhosted.org/packages/56/5d/c814546c2333ceea4ba42262d8c4d55763003e767fa169adc693bd524478/requests-2.33.0-py3-none-any.whl", hash = "sha256:3324635456fa185245e24865e810cecec7b4caf933d7eb133dcde67d48cee69b", size = 65017, upload-time = "2026-03-25T15:10:40.382Z" },
]
[[package]]
From d9f95a80fc70d8576ab384717be01edf78f54597 Mon Sep 17 00:00:00 2001
From: ddc <34492089+ddc@users.noreply.github.com>
Date: Thu, 26 Mar 2026 13:41:35 -0300
Subject: [PATCH 2/2] v3.0.10
---
tests/unit/bot/constants/test_settings.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tests/unit/bot/constants/test_settings.py b/tests/unit/bot/constants/test_settings.py
index bcdbf8ff..78b73b66 100644
--- a/tests/unit/bot/constants/test_settings.py
+++ b/tests/unit/bot/constants/test_settings.py
@@ -96,7 +96,7 @@ def test_partial_env_var_overrides(self):
assert settings.admin_cooldown == 35
# Default values for non-overridden fields
- assert settings.openai_model == "gpt-5.2"
+ assert settings.openai_model == "gpt-5.4"
# Note: openai_api_key might have a value from actual env, so we'll check it's set
assert settings.embed_color == "green"
assert settings.config_cooldown == 20