diff --git a/.pylintrc b/.pylintrc index 87b2e65..044025f 100644 --- a/.pylintrc +++ b/.pylintrc @@ -6,7 +6,8 @@ min-public-methods=0 [MESSAGES CONTROL] # Disable the message "C0114: Missing module docstring" # Disabled the message "W0511: fixme" -disable=C0114, W0511 +# Disabled the message "R0913: Too many arguments" +disable=C0114, W0511, R0913 [FORMAT] # Disabled the message "W0603: Using the global statement" diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 6a7a6fc..a5ec046 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,7 +1,8 @@ { - "recommendations": [ - "ms-python.python", - "ms-python.pylint", - "sonarsource.sonarlint-vscode" - ] -} \ No newline at end of file + "recommendations": [ + "ms-python.python", + "ms-python.pylint", + "ms-python.black", + "sonarsource.sonarlint-vscode" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json index d26be36..4612ab7 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,12 +1,6 @@ { - "python.analysis.importFormat": "absolute", - "python.formatting.provider": "black", - "python.formatting.blackArgs": [ - "--line-length", - "120", - "--target-version", - "py11", - ], - "python.linting.pylintEnabled": true, - "editor.formatOnSave": true, -} \ No newline at end of file + "python.analysis.importFormat": "absolute", + "editor.defaultFormatter": "ms-python.black-formatter", + "editor.formatOnSave": true, + "black-formatter.args": ["--line-length", "120", "--target-version", "py311"] +} diff --git a/base_configs.py b/base_configs.py index ce1e8b5..da33879 100644 --- a/base_configs.py +++ b/base_configs.py @@ -7,6 +7,7 @@ PREFIX = "!" # Database Configs +DB_PROTOCOL = "mongodb" DB_HOST = "localhost" DB_NAME = "database_name" DB_USER = "Cool Name" diff --git a/bot/botcommands/bot_configs.py b/bot/botcommands/bot_configs.py index 8c55006..779d94a 100644 --- a/bot/botcommands/bot_configs.py +++ b/bot/botcommands/bot_configs.py @@ -21,7 +21,7 @@ async def get_spam_configs(self, ctx: commands.Context): """Affiche la configuration du spam.""" spam_config = self.configs_service.find_or_create_spam_configs() - await ctx.send(spam_config.__dict__) + await ctx.send(spam_config.__getstate__()) @commands.command(name="editspamconfig", aliases=["esc"]) @commands.guild_only() diff --git a/bot/botcommands/member.py b/bot/botcommands/member.py index 55c8631..be62bb6 100644 --- a/bot/botcommands/member.py +++ b/bot/botcommands/member.py @@ -55,7 +55,7 @@ async def create_ticket(self, ctx: Context): """ await ctx.channel.send(configs.TICKET_VIEW_MESSAGE, view=TicketOpeningInteraction()) - @commands.command() + @commands.command(aliases=["stalk"]) @has_at_least_role(configs.ADMIN_ROLE) async def search(self, ctx: Context, user: discord.User): """ diff --git a/bot/db/models/bot_configs.py b/bot/db/models/bot_configs.py index 11eae25..f673efc 100644 --- a/bot/db/models/bot_configs.py +++ b/bot/db/models/bot_configs.py @@ -12,7 +12,6 @@ class GlobalConfig: """ __slots__ = ("_id",) - _id: str def __init__(self, _id: str) -> None: """ @@ -28,6 +27,15 @@ def config_id(self): """The id of the config.""" return self._id + def __getstate__(self): + state = {} + for cls in self.__class__.__mro__: + if hasattr(cls, "__slots__"): + for slot in cls.__slots__: + if hasattr(self, slot): + state[slot] = getattr(self, slot) + return state + class SpamConfigs(GlobalConfig): """ diff --git a/bot/db/models/entity.py b/bot/db/models/entity.py index 91675f2..28ea169 100644 --- a/bot/db/models/entity.py +++ b/bot/db/models/entity.py @@ -20,6 +20,8 @@ class Entity: The time the entity was last updated. """ + __slots__ = ("_id", "created_at", "updated_at") + def __init__(self, _id: int, created_at: datetime = None, updated_at: datetime = None): self._id = _id self.created_at = created_at @@ -43,7 +45,7 @@ def save(self, upsert=True): else: self.updated_at = datetime.utcnow() - return self.service.update_one({"_id": self._id}, self.__dict__, upsert=upsert) + return self.service.update_one({"_id": self._id}, self.__getstate__(), upsert=upsert) def delete(self): """Delete the entity from the database.""" @@ -57,6 +59,15 @@ def _load(self): self.created_at = entity.get("created_at") self.updated_at = entity.get("updated_at") + def __getstate__(self): + state = {} + for cls in self.__class__.__mro__: + if hasattr(cls, "__slots__"): + for slot in cls.__slots__: + if hasattr(self, slot): + state[slot] = getattr(self, slot) + return state + @property @abstractmethod def service(self) -> BaseService: diff --git a/bot/db/models/user.py b/bot/db/models/user.py index a3c5029..2f3660c 100644 --- a/bot/db/models/user.py +++ b/bot/db/models/user.py @@ -26,13 +26,15 @@ class AdeptMember(Entity): The program of the member. """ - name: str - email: str - is_student: bool - is_teacher: bool - is_it_student: bool - student_id: int - program: str + __slots__ = ( + "name", + "email", + "is_student", + "is_teacher", + "is_it_student", + "student_id", + "program", + ) def __init__( self, diff --git a/bot/db/services/base_service.py b/bot/db/services/base_service.py index 37d08e9..624783b 100644 --- a/bot/db/services/base_service.py +++ b/bot/db/services/base_service.py @@ -42,10 +42,10 @@ def conn(self) -> Database: """Return a connection to the database.""" if self.__conn is None: self.__conn = MongoClient( - "mongodb+srv://" + - f"{configs.DB_USER}:{configs.DB_PASSWORD}" + - f"@{configs.DB_HOST}/" + - "?retryWrites=true&w=majority&ssl=true", + f"{configs.DB_PROTOCOL}://" + + f"{configs.DB_USER}:{configs.DB_PASSWORD}" + + f"@{configs.DB_HOST}/" + + "?retryWrites=true&w=majority", ).get_database(configs.DB_NAME) return self.__conn diff --git a/bot/db/services/configs_service.py b/bot/db/services/configs_service.py index 1b4bd60..125639e 100644 --- a/bot/db/services/configs_service.py +++ b/bot/db/services/configs_service.py @@ -16,7 +16,7 @@ def find_or_create_spam_configs(self) -> SpamConfigs: spam_config = SpamConfigs() if data is None: - self.insert_one(spam_config.__dict__) + self.insert_one(spam_config.__getstate__()) return spam_config @@ -34,7 +34,7 @@ def update_configs(self, config: GlobalConfig): `config` : GlobalConfig The config to update. """ - return self.update_one({"_id": config.config_id}, config.__dict__, upsert=True) + return self.update_one({"_id": config.config_id}, config.__getstate__(), upsert=True) @property def collection_name(self): diff --git a/bot/management/welcome.py b/bot/management/welcome.py index 180f486..e35cc51 100644 --- a/bot/management/welcome.py +++ b/bot/management/welcome.py @@ -5,11 +5,15 @@ import configs from bot import util, welcome +from bot.db.services import UserService class WelcomeCog(commands.Cog): """This class contains the events related to welcome.""" + def __init__(self) -> None: + self.user_service = UserService() + @commands.command() @commands.guild_only() async def setup(self, ctx: commands.Context): @@ -26,6 +30,10 @@ async def setup(self, ctx: commands.Context): @commands.Cog.listener() async def on_member_join(self, member: discord.Member): """This event is called when a member joins the server.""" + adept_member = await self.user_service.find_by_id(member.id) + if adept_member: + return await welcome.process_welcome_result(member, adept_member) + await util.say(configs.WELCOME_CHANNEL, configs.WELCOME_SERVER.format(name=member.mention)) result = await welcome.walk_through_welcome(member) await welcome.process_welcome_result(member, result) diff --git a/bot/permissions.py b/bot/permissions.py index b1a9b66..4d5f37c 100644 --- a/bot/permissions.py +++ b/bot/permissions.py @@ -27,6 +27,7 @@ def __init__( ) -> None: self.channel = channel self.message = message + super().__init__(self.message) @total_ordering diff --git a/bot/util.py b/bot/util.py index d8bce5f..1dbeba7 100644 --- a/bot/util.py +++ b/bot/util.py @@ -12,7 +12,9 @@ CLIENT: discord.Client = None logging.basicConfig( - level=logging.INFO, format="%(asctime)s - %(levelname)s - %(name)s - %(message)s", datefmt="%Y-%m-%d %H:%M:%S" + level=logging.INFO, + format="%(asctime)s - %(levelname)s - %(name)s - %(message)s", + datefmt="%Y-%m-%d %H:%M:%S", ) logger = logging.getLogger("ADEPT-BOT") @@ -29,6 +31,7 @@ class AdeptBotException(Exception): def __init__(self, message: str) -> None: self.message = f":bangbang: **{message}**" + super().__init__(self.message) def get_member(guild_id: int, member_id: int): @@ -183,7 +186,10 @@ async def archive_ticket(member: discord.Member, channel: discord.TextChannel): category = guild.get_channel(configs.TICKET_ARCHIVE_CATEGORY) overwrites = discord.PermissionOverwrite( - view_channel=True, read_messages=True, send_messages=False, read_message_history=True + view_channel=True, + read_messages=True, + send_messages=False, + read_message_history=True, ) await channel.set_permissions(member, overwrite=overwrites) @@ -201,7 +207,9 @@ def get_welcome_instruction(instruction: str): The instruction to display. """ welcome_embed = discord.Embed( - title=configs.WELCOME_TITLE, description=configs.WELCOME_MESSAGE.format(content=instruction), color=0x1DE203 + title=configs.WELCOME_TITLE, + description=configs.WELCOME_MESSAGE.format(content=instruction), + color=0x1DE203, ) welcome_embed.set_thumbnail(url="https://www.adeptinfo.ca/img/badge.png") welcome_embed.set_footer(text=configs.WELCOME_FOOTER) diff --git a/bot/welcome.py b/bot/welcome.py index e4a7c9c..db09c24 100644 --- a/bot/welcome.py +++ b/bot/welcome.py @@ -68,7 +68,8 @@ async def walk_through_welcome(member: discord.Member): """ is_student_view = YesNoInteraction() original_message: discord.Message = await member.send( - embed=util.get_welcome_instruction("Êtes-vous un étudiant?"), view=is_student_view + embed=util.get_welcome_instruction("Êtes-vous un étudiant?"), + view=is_student_view, ) is_student = await is_student_view.start() @@ -93,7 +94,11 @@ async def walk_through_welcome(member: discord.Member): welcome_user.program = process_student_result.program welcome_user.is_it_student = process_student_result.is_it_student - confirmation_embed.add_field(name="Numéro étudiant:", value=process_student_result.student_number, inline=False) + confirmation_embed.add_field( + name="Numéro étudiant:", + value=process_student_result.student_number, + inline=False, + ) if process_student_result.program is not None: confirmation_embed.add_field(name="Programme:", value=process_student_result.program, inline=False) else: @@ -170,7 +175,10 @@ async def __process_email(member: discord.Member, original_message: discord.Mess async def __process_teacher(member: discord.Member, original_message: discord.Message): current_view = YesNoInteraction() - await original_message.edit(embed=util.get_welcome_instruction("Êtes-vous un professeur?"), view=current_view) + await original_message.edit( + embed=util.get_welcome_instruction("Êtes-vous un professeur?"), + view=current_view, + ) is_teacher = await current_view.start() if is_teacher is None: @@ -198,7 +206,8 @@ async def __process_student(member: discord.Member, original_message: discord.Me is_info_view = YesNoInteraction() await original_message.edit( - embed=util.get_welcome_instruction("Êtes-vous un étudiant en informatique?"), view=is_info_view + embed=util.get_welcome_instruction("Êtes-vous un étudiant en informatique?"), + view=is_info_view, ) is_it_student = await is_info_view.start() @@ -209,7 +218,10 @@ async def __process_student(member: discord.Member, original_message: discord.Me if is_it_student: student_view = StudentInteraction() - await original_message.edit(embed=util.get_welcome_instruction("Quel est votre programme?"), view=student_view) + await original_message.edit( + embed=util.get_welcome_instruction("Quel est votre programme?"), + view=student_view, + ) program = await student_view.start() await original_message.edit(view=None) @@ -233,16 +245,26 @@ async def create_welcome_embed(member: discord.User, adept_member: AdeptMember): The new member's information. """ embed = discord.Embed( - title="Nouveau membre dans ADEPT-Informatique", color=0xF9E18B, timestamp=discord.utils.utcnow() + title="Nouveau membre dans ADEPT-Informatique", + color=0xF9E18B, + timestamp=discord.utils.utcnow(), ) embed.add_field(name="Nom:", value=adept_member.name, inline=False) embed.add_field(name="Email:", value=adept_member.email, inline=False) embed.add_field(name="Numéro étudiant:", value=adept_member.student_id, inline=False) - embed.add_field(name="Étudiant:", value="Oui" if adept_member.is_student else "Non", inline=False) - embed.add_field(name="Professeur:", value="Oui" if adept_member.is_teacher else "Non", inline=False) + embed.add_field( + name="Étudiant:", + value="Oui" if adept_member.is_student else "Non", + inline=False, + ) + embed.add_field( + name="Professeur:", + value="Oui" if adept_member.is_teacher else "Non", + inline=False, + ) embed.add_field( name="Programme:", - value=adept_member.program if adept_member.is_it_student else "N'est pas en informatique", + value=(adept_member.program if adept_member.is_it_student else "N'est pas en informatique"), inline=False, ) embed.set_footer(text=f"ID: {member.id}") @@ -279,7 +301,13 @@ async def process_welcome_result(member: discord.Member, result: AdeptMember): roles = [ role for role in member.roles - if role.id not in (configs.PROG_ROLE, configs.NETWORK_ROLE, configs.DECBAC_ROLE, configs.TEACHER_ROLE) + if role.id + not in ( + configs.PROG_ROLE, + configs.NETWORK_ROLE, + configs.DECBAC_ROLE, + configs.TEACHER_ROLE, + ) ] if role is not None: roles.append(role) diff --git a/dockerfile b/dockerfile index 0dd41a9..7f003a8 100644 --- a/dockerfile +++ b/dockerfile @@ -1,12 +1,15 @@ -FROM python:3.11-slim +FROM python:3.12-slim WORKDIR /usr/src/app # Copy project files to the container -COPY . . +COPY requirements.txt . # Install the dependencies RUN python3 -m pip install -r ./requirements.txt +# Copy project files to the container +COPY . . + # Start the application -CMD ["python3", "run.py"] +CMD ["python3", "-O", "run.py"]