147 lines
4.2 KiB
Python
147 lines
4.2 KiB
Python
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}}
|
|
)
|