import logging from dataclasses import dataclass from re import IGNORECASE, Match, compile from typing import List, Union from bson import ObjectId, Regex from modules.database import col_challenges logger = logging.getLogger(__name__) @dataclass class PycordChallenge: """Dataclass of DB entry of a captcha challenge""" __slots__ = ( "_id", "guild", "enabled", "archived", "challenge", "answers", ) _id: ObjectId guild: ObjectId enabled: bool archived: bool challenge: str answers: List[Regex] @classmethod async def create( cls, guild: ObjectId, challenge: str, answers: List[Regex], enabled: bool = True, archived: bool = False, ): """Create new challenge entry in a database. ### Args: * guild (`ObjectId`): Guild's ID * challenge (`str`): Challenge text * answers (`List[Regex]`): List of regex patterns that are answers to the challenge * enabled (`bool`, *optional*): Whether the challenge is enabled. Defaults to `True`. * archived (`bool`, *optional*): Whether the challenge is archived. Defaults to `False`. ### Returns: * `PycordChallenge`: The challenge object """ challenge_entry = { "guild": guild, "enabled": enabled, "archived": archived, "challenge": challenge, "answers": answers, } inserted = await col_challenges.insert_one(challenge_entry) challenge_entry["_id"] = inserted.inserted_id return cls(**challenge_entry) def solve(self, expression: str) -> bool: """Check if the expression is an answer to the challenge ### Args: * expression (`str`): Some expression to be matched against the challenge's answers. ### Returns: * `bool`: `True` if the answer is correct and `False` if not. """ return bool(self.match(expression)) def match(self, expression: str) -> Union[Match, None]: """Match the expression against challenge's answers ### Args: * expression (`str`): Some expression to be matched against the challenge's answers. ### Returns: * `Union[Match, None]`: Regex `Match` if a match is found, otherwise `None`. """ for answer in self.answers: if re_match := compile(answer.pattern, IGNORECASE).match( expression.strip() ): return re_match return None async def enable(self, unarchive: bool = True) -> None: """Enable and unarchive the challenge ### Args: * unarchive (`bool`, *optional*): Whether to unarchive the challenge as well. Defaults to `True`. """ self.enabled = True self.archived = unarchive if self.archived else False await col_challenges.update_one( {"_id": self._id}, { "$set": { "enabled": True, "archived": unarchive if self.archived else False, } }, ) async def disable(self) -> None: """Disable the challenge""" self.enabled = False await col_challenges.update_one({"_id": self._id}, {"$set": {"enabled": False}}) async def archive(self, disable: bool = True) -> None: """Archive and disable the challenge ### Args: * disable (`bool`, *optional*): Whether to disable the challenge as well. Defaults to `True`. """ self.enabled = not disable if self.enabled else False self.archived = True await col_challenges.update_one( {"_id": self._id}, { "$set": { "enabled": not disable if self.enabled else False, "archived": True, } }, ) async def unarchive(self) -> None: """Unarchive the challenge""" self.archived = False await col_challenges.update_one( {"_id": self._id}, {"$set": {"archived": False}} )