WIP: Added consent durations and modified default embed colors (#51)

This commit is contained in:
2025-07-24 19:52:12 +02:00
parent 558b12bdbd
commit 352f8c97ec
10 changed files with 102 additions and 16 deletions

View File

@@ -1,6 +1,6 @@
from dataclasses import dataclass
from datetime import datetime
from typing import Literal, Any, Dict, Optional
from typing import Any, Dict, Optional
from zoneinfo import ZoneInfo
from bson import ObjectId
@@ -33,11 +33,15 @@ class Consent(BaseCacheable):
_id: ObjectId
user_id: int
guild_id: int
scope: Literal[ConsentScope.GENERAL, ConsentScope.INTEGRATION_DEEPL]
scope: ConsentScope
consent_date: datetime
expiration_date: datetime | None
withdrawal_date: datetime | None
@staticmethod
def get_cache_key(user_id: int, guild_id: int, scope: ConsentScope) -> str:
return f"{Consent.__short_name__}_{user_id}_{guild_id}_{scope.value}"
# TODO Implement this method
@classmethod
async def from_id(cls, id: ObjectId, cache: Optional[Cache] = None, **kwargs: Any) -> Any:
@@ -53,7 +57,7 @@ class Consent(BaseCacheable):
cache: Optional[Cache] = None,
) -> Any:
cached_entry: Dict[str, Any] | None = restore_from_cache(
cls.__short_name__, f"{user_id}_{guild_id}_{scope.value}", cache=cache
cls.__short_name__, Consent.get_cache_key(user_id, guild_id, scope), cache=cache
)
if cached_entry is not None:
@@ -73,7 +77,7 @@ class Consent(BaseCacheable):
if cache is not None:
cache.set_json(
f"{cls.__short_name__}_{user_id}_{guild_id}_{scope.value}",
Consent.get_cache_key(user_id, guild_id, scope),
cls._entry_to_cache(db_entry),
)
@@ -81,6 +85,8 @@ class Consent(BaseCacheable):
@classmethod
def from_entry(cls, db_entry: Dict[str, Any]) -> "Consent":
db_entry["scope"] = ConsentScope(db_entry["scope"])
return cls(**db_entry)
# TODO Add documentation
@@ -108,7 +114,7 @@ class Consent(BaseCacheable):
if cache is not None:
cache.set_json(
f"{cls.__short_name__}_{user_id}_{guild_id}_{scope.value}",
Consent.get_cache_key(user_id, guild_id, scope),
cls._entry_to_cache(db_entry),
)
@@ -143,7 +149,7 @@ class Consent(BaseCacheable):
)
def _get_cache_key(self) -> str:
return f"{self.__short_name__}_{self.user_id}_{self.guild_id}_{self.scope.value}"
return self.get_cache_key(self.user_id, self.guild_id, self.scope)
@staticmethod
def _entry_to_cache(db_entry: Dict[str, Any]) -> Dict[str, Any]:

View File

@@ -209,7 +209,7 @@ class PycordUser(BaseCacheable):
cache: Optional[Cache] = None,
) -> None:
# TODO Test this query
async for consent_entry in Consent.__collection__.find(
await Consent.__collection__.update_many(
{
"user_id": self.id,
"guild_id": self.guild_id,
@@ -219,6 +219,9 @@ class PycordUser(BaseCacheable):
{"expiration_date": {"$gte": datetime.now(tz=ZoneInfo("UTC"))}},
{"expiration_date": None},
],
}
):
await Consent.from_entry(consent_entry).withdraw(cache)
},
{"$set": {"withdrawal_date": datetime.now(tz=ZoneInfo("UTC"))}},
)
if cache is not None:
cache.delete(Consent.get_cache_key(self.id, self.guild_id, scope))

View File

@@ -1,6 +1,8 @@
import logging
from datetime import datetime
from logging import Logger
from typing import Dict, Any, List
from zoneinfo import ZoneInfo
from discord import (
SlashCommandGroup,
@@ -12,10 +14,11 @@ from discord import (
from discord.ext import commands
from discord.ext.commands import guild_only
from libbot.i18n import _, in_every_locale
from tempora import parse_timedelta
from classes import PycordUser
from classes.pycord_bot import PycordBot
from enums import ConsentScope
from enums import ConsentScope, ConsentDuration
logger: Logger = logging.getLogger(__name__)
@@ -43,6 +46,23 @@ class CogConsent(commands.Cog):
return choices
@staticmethod
def _get_consent_durations() -> List[OptionChoice]:
choices: List[OptionChoice] = []
for duration in ConsentDuration._member_map_.values():
duration_value: str = duration.value
choices.append(
OptionChoice(
_(duration_value, "data_control", "consent_durations"),
duration_value,
in_every_locale(duration_value, "data_control", "consent_durations"),
)
)
return choices
# /consent terms <scope>
# Will provide information about terms
# TODO Implement i18n
@@ -107,7 +127,14 @@ class CogConsent(commands.Cog):
description="Scope of the consent",
choices=_get_scope_choices(),
)
async def command_consent_give(self, ctx: ApplicationContext, scope: str) -> None:
@option(
"duration",
description="Duration of the consent",
choices=_get_consent_durations(),
)
async def command_consent_give(
self, ctx: ApplicationContext, scope: str, duration: str = ConsentDuration.NORMAL.value
) -> None:
scopes_config: Dict[str, Dict[str, Any]] = self.bot.config["modules"]["consent"][
"scopes"
]
@@ -124,9 +151,18 @@ class CogConsent(commands.Cog):
user: PycordUser = await self.bot.find_user(ctx.user, ctx.guild_id)
is_scope_third_party: bool = self.bot.config["modules"]["consent"]["scopes"][scope][
"is_third_party"
]
expiration_date: datetime = datetime.now(tz=ZoneInfo("UTC")) + parse_timedelta(
self.bot.config["modules"]["consent"]["durations"][
"third_party" if is_scope_third_party else "first_party"
][duration]
)
# TODO Implement consent duration
try:
await user.give_consent(ConsentScope(scope), None, cache=self.bot.cache)
await user.give_consent(ConsentScope(scope), expiration_date, cache=self.bot.cache)
await ctx.respond(
embed=self.bot.create_embed_success("Success", "Consent has been given."),
@@ -187,6 +223,11 @@ class CogConsent(commands.Cog):
name="give_all",
description="Give consent to all scopes",
)
@option(
"duration",
description="Duration of the consent",
choices=_get_consent_durations(),
)
@guild_only()
@option(
"confirm",

View File

@@ -47,9 +47,9 @@
"colors": {
"primary": "#A4A4A6",
"secondary": "#595351",
"success": "#4CAF50",
"warning": "#FFC107",
"error": "#F44336"
"success": "#57F287",
"warning": "#FEE75C",
"error": "#ED4245"
},
"modules": {
"consent": {
@@ -67,6 +67,18 @@
"terms_url": "https://www.deepl.com/en/terms-of-use",
"privacy_url": "https://www.deepl.com/en/privacy"
}
},
"durations": {
"first_party": {
"short": "1 month",
"normal": "12 months",
"long": "24 months"
},
"third_party": {
"short": "1 month",
"normal": "6 months",
"long": "12 months"
}
}
},
"leveling": {

View File

@@ -1,4 +1,5 @@
from .cache_ttl import CacheTTL
from .consent_duration import ConsentDuration
from .consent_scope import ConsentScope
from .embed_color import EmbedColor
from .health_status import HealthStatus

View File

@@ -0,0 +1,7 @@
from enum import Enum
class ConsentDuration(Enum):
SHORT = "short"
NORMAL = "normal"
LONG = "long"

View File

@@ -53,6 +53,11 @@
"name": "Integration: DeepL",
"description": ""
}
},
"consent_durations": {
"short": "Short",
"normal": "Normal",
"long": "Long"
}
},
"commands": {

View File

@@ -53,6 +53,11 @@
"name": "Integration: DeepL",
"description": ""
}
},
"consent_durations": {
"short": "Short",
"normal": "Normal",
"long": "Long"
}
},
"commands": {

View File

@@ -67,6 +67,11 @@
"name": "Інтеграція: DeepL",
"description": ""
}
},
"consent_durations": {
"short": "Короткий",
"normal": "Звичаний",
"long": "Довгий"
}
},
"commands": {

View File

@@ -6,6 +6,7 @@ libbot[speed,pycord,cache]==4.4.0
mongodb-migrations==1.3.1
pynacl~=1.5.0
pytz~=2025.1
tempora~=5.8.1
# Temporarily disabled because
# these are still unused for now