Improved achievement logic #268_achievements
This commit is contained in:
		@@ -7,6 +7,7 @@
 | 
			
		||||
      "bot-core": "src/bot_core/bot-core.json",
 | 
			
		||||
      "bot-data": "src/bot_data/bot-data.json",
 | 
			
		||||
      "bot-graphql": "src/bot_graphql/bot-graphql.json",
 | 
			
		||||
      "achievements": "src/modules/achievements/achievements.json",
 | 
			
		||||
      "auto-role": "src/modules/auto_role/auto-role.json",
 | 
			
		||||
      "base": "src/modules/base/base.json",
 | 
			
		||||
      "boot-log": "src/modules/boot_log/boot-log.json",
 | 
			
		||||
 
 | 
			
		||||
 Submodule kdb-bot/src/bot/config updated: e1c1efac98...35c9d4ecb4
									
								
							@@ -11,7 +11,6 @@ from bot_core.configuration.bot_logging_settings import BotLoggingSettings
 | 
			
		||||
from bot_core.configuration.bot_settings import BotSettings
 | 
			
		||||
from modules.base.configuration.base_settings import BaseSettings
 | 
			
		||||
from modules.boot_log.configuration.boot_log_settings import BootLogSettings
 | 
			
		||||
from modules.level.configuration.level_settings import LevelSettings
 | 
			
		||||
from modules.permission.configuration.permission_settings import PermissionSettings
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -37,7 +36,6 @@ class StartupSettingsExtension(StartupExtensionABC):
 | 
			
		||||
        self._configure_settings_with_sub_settings(configuration, BotSettings, lambda x: x.servers, lambda x: x.id)
 | 
			
		||||
        self._configure_settings_with_sub_settings(configuration, BaseSettings, lambda x: x.servers, lambda x: x.id)
 | 
			
		||||
        self._configure_settings_with_sub_settings(configuration, BootLogSettings, lambda x: x.servers, lambda x: x.id)
 | 
			
		||||
        self._configure_settings_with_sub_settings(configuration, LevelSettings, lambda x: x.servers, lambda x: x.id)
 | 
			
		||||
        self._configure_settings_with_sub_settings(
 | 
			
		||||
            configuration, PermissionSettings, lambda x: x.servers, lambda x: x.id
 | 
			
		||||
        )
 | 
			
		||||
@@ -50,9 +48,9 @@ class StartupSettingsExtension(StartupExtensionABC):
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def _configure_settings_with_sub_settings(
 | 
			
		||||
        config: ConfigurationABC, settings: Type, list_atr: Callable, atr: Callable
 | 
			
		||||
        config: ConfigurationABC, settings_type: Type, list_atr: Callable, atr: Callable
 | 
			
		||||
    ):
 | 
			
		||||
        settings: Optional[settings] = config.get_configuration(settings)
 | 
			
		||||
        settings: Optional[settings_type] = config.get_configuration(settings_type)
 | 
			
		||||
        if settings is None:
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -93,6 +93,9 @@
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  "modules": {
 | 
			
		||||
    "achievements": {
 | 
			
		||||
      "got_new_achievement": "{} hat die Errungenschaft {} freigeschaltet :D"
 | 
			
		||||
    },
 | 
			
		||||
    "auto_role": {
 | 
			
		||||
      "add": {
 | 
			
		||||
        "error": {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,21 +1,30 @@
 | 
			
		||||
import traceback
 | 
			
		||||
 | 
			
		||||
from cpl_core.configuration import ConfigurationModelABC
 | 
			
		||||
from cpl_core.console import Console
 | 
			
		||||
from cpl_query.extension import List
 | 
			
		||||
 | 
			
		||||
from bot_api.json_processor import JSONProcessor
 | 
			
		||||
from bot_core.configuration.server_settings import ServerSettings
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class BotSettings(ConfigurationModelABC):
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
    def __init__(
 | 
			
		||||
        self,
 | 
			
		||||
        technicians: list = None,
 | 
			
		||||
        wait_for_restart: int = 2,
 | 
			
		||||
        wait_for_shutdown: int = 2,
 | 
			
		||||
        cache_max_messages: int = 1000,
 | 
			
		||||
        server_settings: dict = None,
 | 
			
		||||
    ):
 | 
			
		||||
        ConfigurationModelABC.__init__(self)
 | 
			
		||||
 | 
			
		||||
        self._technicians: List[int] = List(int) if technicians is None else technicians
 | 
			
		||||
        self._wait_for_restart = wait_for_restart
 | 
			
		||||
        self._wait_for_shutdown = wait_for_shutdown
 | 
			
		||||
        self._cache_max_messages = cache_max_messages
 | 
			
		||||
 | 
			
		||||
        self._servers: List[ServerSettings] = List(ServerSettings)
 | 
			
		||||
        self._technicians: List[int] = List(int)
 | 
			
		||||
        self._wait_for_restart = 2
 | 
			
		||||
        self._wait_for_shutdown = 2
 | 
			
		||||
        self._cache_max_messages = 1000
 | 
			
		||||
 | 
			
		||||
        if server_settings is not None:
 | 
			
		||||
            self._servers_from_dict(server_settings)
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def servers(self) -> List[ServerSettings]:
 | 
			
		||||
@@ -37,26 +46,34 @@ class BotSettings(ConfigurationModelABC):
 | 
			
		||||
    def cache_max_messages(self) -> int:
 | 
			
		||||
        return self._cache_max_messages
 | 
			
		||||
 | 
			
		||||
    def from_dict(self, settings: dict):
 | 
			
		||||
        try:
 | 
			
		||||
            self._technicians = settings["Technicians"]
 | 
			
		||||
            self._wait_for_restart = settings["WaitForRestart"]
 | 
			
		||||
            self._wait_for_shutdown = settings["WaitForShutdown"]
 | 
			
		||||
            settings.pop("Technicians")
 | 
			
		||||
            settings.pop("WaitForRestart")
 | 
			
		||||
            settings.pop("WaitForShutdown")
 | 
			
		||||
    def _servers_from_dict(self, settings: dict):
 | 
			
		||||
        servers = List(ServerSettings)
 | 
			
		||||
        for s in settings:
 | 
			
		||||
            settings[s]["id"] = int(s)
 | 
			
		||||
            st = JSONProcessor.process(ServerSettings, settings[s])
 | 
			
		||||
            servers.append(st)
 | 
			
		||||
        self._servers = servers
 | 
			
		||||
 | 
			
		||||
            if "CacheMaxMessages" in settings:
 | 
			
		||||
                self._cache_max_messages = settings["CacheMaxMessages"]
 | 
			
		||||
                settings.pop("CacheMaxMessages")
 | 
			
		||||
 | 
			
		||||
            servers = List(ServerSettings)
 | 
			
		||||
            for s in settings:
 | 
			
		||||
                st = ServerSettings()
 | 
			
		||||
                settings[s]["Id"] = s
 | 
			
		||||
                st.from_dict(settings[s])
 | 
			
		||||
                servers.append(st)
 | 
			
		||||
            self._servers = servers
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            Console.error(f"[ ERROR ] [ {__name__} ]: Reading error in {type(self).__name__} settings")
 | 
			
		||||
            Console.error(f"[ EXCEPTION ] [ {__name__} ]: {e} -> {traceback.format_exc()}")
 | 
			
		||||
    # def from_dict(self, settings: dict):
 | 
			
		||||
    #     try:
 | 
			
		||||
    #         self._technicians = settings["Technicians"]
 | 
			
		||||
    #         self._wait_for_restart = settings["WaitForRestart"]
 | 
			
		||||
    #         self._wait_for_shutdown = settings["WaitForShutdown"]
 | 
			
		||||
    #         settings.pop("Technicians")
 | 
			
		||||
    #         settings.pop("WaitForRestart")
 | 
			
		||||
    #         settings.pop("WaitForShutdown")
 | 
			
		||||
    #
 | 
			
		||||
    #         if "CacheMaxMessages" in settings:
 | 
			
		||||
    #             self._cache_max_messages = settings["CacheMaxMessages"]
 | 
			
		||||
    #             settings.pop("CacheMaxMessages")
 | 
			
		||||
    #
 | 
			
		||||
    #         servers = List(ServerSettings)
 | 
			
		||||
    #         for s in settings:
 | 
			
		||||
    #             st = ServerSettings()
 | 
			
		||||
    #             settings[s]["Id"] = s
 | 
			
		||||
    #             st.from_dict(settings[s])
 | 
			
		||||
    #             servers.append(st)
 | 
			
		||||
    #         self._servers = servers
 | 
			
		||||
    #     except Exception as e:
 | 
			
		||||
    #         Console.error(f"[ ERROR ] [ {__name__} ]: Reading error in {type(self).__name__} settings")
 | 
			
		||||
    #         Console.error(f"[ EXCEPTION ] [ {__name__} ]: {e} -> {traceback.format_exc()}")
 | 
			
		||||
 
 | 
			
		||||
@@ -1,15 +1,18 @@
 | 
			
		||||
import traceback
 | 
			
		||||
 | 
			
		||||
from cpl_core.configuration.configuration_model_abc import ConfigurationModelABC
 | 
			
		||||
from cpl_core.console import Console
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ServerSettings(ConfigurationModelABC):
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
    def __init__(
 | 
			
		||||
        self,
 | 
			
		||||
        id: int = None,
 | 
			
		||||
        message_delete_timer: int = None,
 | 
			
		||||
        notification_chat_id: int = None,
 | 
			
		||||
    ):
 | 
			
		||||
        ConfigurationModelABC.__init__(self)
 | 
			
		||||
 | 
			
		||||
        self._id: int = 0
 | 
			
		||||
        self._message_delete_timer: int = 0
 | 
			
		||||
        self._id: int = id
 | 
			
		||||
        self._message_delete_timer: int = message_delete_timer
 | 
			
		||||
        self._notification_chat_id: int = notification_chat_id
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def id(self) -> int:
 | 
			
		||||
@@ -19,10 +22,15 @@ class ServerSettings(ConfigurationModelABC):
 | 
			
		||||
    def message_delete_timer(self) -> int:
 | 
			
		||||
        return self._message_delete_timer
 | 
			
		||||
 | 
			
		||||
    def from_dict(self, settings: dict):
 | 
			
		||||
        try:
 | 
			
		||||
            self._id = int(settings["Id"])
 | 
			
		||||
            self._message_delete_timer = int(settings["MessageDeleteTimer"])
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            Console.error(f"[ ERROR ] [ {__name__} ]: Reading error in settings")
 | 
			
		||||
            Console.error(f"[ EXCEPTION ] [ {__name__} ]: {e} -> {traceback.format_exc()}")
 | 
			
		||||
    @property
 | 
			
		||||
    def notification_chat_id(self) -> int:
 | 
			
		||||
        return self._notification_chat_id
 | 
			
		||||
 | 
			
		||||
    # def from_dict(self, settings: dict):
 | 
			
		||||
    #     try:
 | 
			
		||||
    #         self._id = int(settings["Id"])
 | 
			
		||||
    #         self._message_delete_timer = int(settings["MessageDeleteTimer"])
 | 
			
		||||
    #         self._notification_chat_id = int(settings["NotificationChatId"])
 | 
			
		||||
    #     except Exception as e:
 | 
			
		||||
    #         Console.error(f"[ ERROR ] [ {__name__} ]: Reading error in settings")
 | 
			
		||||
    #         Console.error(f"[ EXCEPTION ] [ {__name__} ]: {e} -> {traceback.format_exc()}")
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,7 @@ from abc import ABC, abstractmethod
 | 
			
		||||
from cpl_query.extension import List
 | 
			
		||||
 | 
			
		||||
from bot_data.model.achievement import Achievement
 | 
			
		||||
from bot_data.model.user_got_achievement import UserGotAchievement
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AchievementRepositoryABC(ABC):
 | 
			
		||||
@@ -22,6 +23,10 @@ class AchievementRepositoryABC(ABC):
 | 
			
		||||
    def get_achievements_by_server_id(self, server_id: int) -> List[Achievement]:
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    @abstractmethod
 | 
			
		||||
    def get_achievements_by_user_id(self, user_id: int) -> List[Achievement]:
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    @abstractmethod
 | 
			
		||||
    def add_achievement(self, achievement: Achievement):
 | 
			
		||||
        pass
 | 
			
		||||
@@ -33,3 +38,11 @@ class AchievementRepositoryABC(ABC):
 | 
			
		||||
    @abstractmethod
 | 
			
		||||
    def delete_achievement(self, achievement: Achievement):
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    @abstractmethod
 | 
			
		||||
    def add_user_got_achievement(self, join: UserGotAchievement):
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    @abstractmethod
 | 
			
		||||
    def delete_user_got_achievement(self, join: UserGotAchievement):
 | 
			
		||||
        pass
 | 
			
		||||
 
 | 
			
		||||
@@ -45,7 +45,7 @@ class AchievementsMigration(MigrationABC):
 | 
			
		||||
                        `Attribute` VARCHAR(255) NOT NULL,
 | 
			
		||||
                        `Operator` VARCHAR(2) NOT NULL,
 | 
			
		||||
                        `Value` VARCHAR(255) NOT NULL,
 | 
			
		||||
                        `Deleted`   BOOL                 DEFAULT FALSE,
 | 
			
		||||
                        `Deleted`   BOOL DEFAULT FALSE,
 | 
			
		||||
                        `DateFrom`  DATETIME(6) NOT NULL,
 | 
			
		||||
                        `DateTo`    DATETIME(6) NOT NULL
 | 
			
		||||
                    );
 | 
			
		||||
@@ -53,6 +53,25 @@ class AchievementsMigration(MigrationABC):
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        self._cursor.execute(
 | 
			
		||||
            str(
 | 
			
		||||
                f"""
 | 
			
		||||
                    CREATE TABLE IF NOT EXISTS `UserGotAchievements` (
 | 
			
		||||
                        `Id` BIGINT NOT NULL AUTO_INCREMENT,
 | 
			
		||||
                        `UserId` BIGINT,
 | 
			
		||||
                        `AchievementId` BIGINT,
 | 
			
		||||
                        `CreatedAt` DATETIME(6) NULL DEFAULT CURRENT_TIMESTAMP(6),
 | 
			
		||||
                        `LastModifiedAt` DATETIME(6) NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
 | 
			
		||||
                        PRIMARY KEY(`Id`),
 | 
			
		||||
                        FOREIGN KEY (`UserId`) REFERENCES `Users`(`UserId`),
 | 
			
		||||
                        FOREIGN KEY (`AchievementId`) REFERENCES `Achievements`(`Id`)
 | 
			
		||||
                    );
 | 
			
		||||
                """
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        # A join table history between users and achievements is not necessary.
 | 
			
		||||
 | 
			
		||||
        self._cursor.execute(str(f"""ALTER TABLE Users ADD MessageCount BIGINT NOT NULL DEFAULT 0 AFTER XP;"""))
 | 
			
		||||
        self._cursor.execute(str(f"""ALTER TABLE Users ADD ReactionCount BIGINT NOT NULL DEFAULT 0 AFTER XP;"""))
 | 
			
		||||
        self._cursor.execute(str(f"""ALTER TABLE UsersHistory ADD MessageCount BIGINT NOT NULL DEFAULT 0 AFTER XP;"""))
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										99
									
								
								kdb-bot/src/bot_data/model/user_got_achievement.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								kdb-bot/src/bot_data/model/user_got_achievement.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,99 @@
 | 
			
		||||
from datetime import datetime
 | 
			
		||||
from typing import Optional
 | 
			
		||||
 | 
			
		||||
from cpl_core.database import TableABC
 | 
			
		||||
 | 
			
		||||
from bot_data.model.achievement import Achievement
 | 
			
		||||
from bot_data.model.server import Server
 | 
			
		||||
from bot_data.model.user import User
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class UserGotAchievement(TableABC):
 | 
			
		||||
    def __init__(
 | 
			
		||||
        self,
 | 
			
		||||
        user: Optional[User],
 | 
			
		||||
        achievement: Optional[Achievement],
 | 
			
		||||
        server: Optional[Server],
 | 
			
		||||
        created_at: datetime = None,
 | 
			
		||||
        modified_at: datetime = None,
 | 
			
		||||
        id=0,
 | 
			
		||||
    ):
 | 
			
		||||
        self._id = id
 | 
			
		||||
        self._user = user
 | 
			
		||||
        self._achievement = achievement
 | 
			
		||||
        self._server = server
 | 
			
		||||
 | 
			
		||||
        TableABC.__init__(self)
 | 
			
		||||
        self._created_at = created_at if created_at is not None else self._created_at
 | 
			
		||||
        self._modified_at = modified_at if modified_at is not None else self._modified_at
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def id(self) -> int:
 | 
			
		||||
        return self._id
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def user(self) -> User:
 | 
			
		||||
        return self._user
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def achievement(self) -> Achievement:
 | 
			
		||||
        return self._achievement
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def get_select_all_string() -> str:
 | 
			
		||||
        return str(
 | 
			
		||||
            f"""
 | 
			
		||||
                SELECT * FROM `UserGotAchievements`;
 | 
			
		||||
            """
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def get_select_by_id_string(id: int) -> str:
 | 
			
		||||
        return str(
 | 
			
		||||
            f"""
 | 
			
		||||
                SELECT * FROM `UserGotAchievements`
 | 
			
		||||
                WHERE `Id` = {id};
 | 
			
		||||
            """
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def get_select_by_user_id_string(id: int) -> str:
 | 
			
		||||
        return str(
 | 
			
		||||
            f"""
 | 
			
		||||
                SELECT * FROM `UserGotAchievements`
 | 
			
		||||
                WHERE `UserId` = {id};
 | 
			
		||||
            """
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def insert_string(self) -> str:
 | 
			
		||||
        return str(
 | 
			
		||||
            f"""
 | 
			
		||||
            INSERT INTO `UserGotAchievements` (
 | 
			
		||||
                `UserId`, `AchievementId`
 | 
			
		||||
            ) VALUES (
 | 
			
		||||
                {self._user.id},
 | 
			
		||||
                {self._achievement.id}
 | 
			
		||||
            );
 | 
			
		||||
        """
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def udpate_string(self) -> str:
 | 
			
		||||
        return str(
 | 
			
		||||
            f"""
 | 
			
		||||
                UPDATE `UserGotAchievements`
 | 
			
		||||
                SET `UserId` = '{self._user.id}',
 | 
			
		||||
                `AchievementId` = '{self._achievement.id}'
 | 
			
		||||
                WHERE `Id` = {self._id};
 | 
			
		||||
            """
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def delete_string(self) -> str:
 | 
			
		||||
        return str(
 | 
			
		||||
            f"""
 | 
			
		||||
                DELETE FROM `UserGotAchievements`
 | 
			
		||||
                WHERE `Id` = {self._id};
 | 
			
		||||
            """
 | 
			
		||||
        )
 | 
			
		||||
@@ -4,7 +4,9 @@ from cpl_query.extension import List
 | 
			
		||||
from bot_core.logging.database_logger import DatabaseLogger
 | 
			
		||||
from bot_data.abc.achievement_repository_abc import AchievementRepositoryABC
 | 
			
		||||
from bot_data.abc.server_repository_abc import ServerRepositoryABC
 | 
			
		||||
from bot_data.abc.user_repository_abc import UserRepositoryABC
 | 
			
		||||
from bot_data.model.achievement import Achievement
 | 
			
		||||
from bot_data.model.user_got_achievement import UserGotAchievement
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AchievementRepositoryService(AchievementRepositoryABC):
 | 
			
		||||
@@ -13,11 +15,13 @@ class AchievementRepositoryService(AchievementRepositoryABC):
 | 
			
		||||
        logger: DatabaseLogger,
 | 
			
		||||
        db_context: DatabaseContextABC,
 | 
			
		||||
        servers: ServerRepositoryABC,
 | 
			
		||||
        users: UserRepositoryABC,
 | 
			
		||||
    ):
 | 
			
		||||
        self._logger = logger
 | 
			
		||||
        self._context = db_context
 | 
			
		||||
 | 
			
		||||
        self._servers = servers
 | 
			
		||||
        self._users = users
 | 
			
		||||
 | 
			
		||||
        AchievementRepositoryABC.__init__(self)
 | 
			
		||||
 | 
			
		||||
@@ -33,6 +37,15 @@ class AchievementRepositoryService(AchievementRepositoryABC):
 | 
			
		||||
            id=result[0],
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def _join_from_result(self, result: tuple):
 | 
			
		||||
        return UserGotAchievement(
 | 
			
		||||
            self._users.get_user_by_id(result[1]),
 | 
			
		||||
            self.get_achievement_by_id(result[2]),
 | 
			
		||||
            self._servers.get_server_by_id(result[3]),
 | 
			
		||||
            result[5],
 | 
			
		||||
            id=result[0],
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def get_achievements(self) -> List[Achievement]:
 | 
			
		||||
        achievements = List(Achievement)
 | 
			
		||||
        self._logger.trace(__name__, f"Send SQL command: {Achievement.get_select_all_string()}")
 | 
			
		||||
@@ -59,6 +72,23 @@ class AchievementRepositoryService(AchievementRepositoryABC):
 | 
			
		||||
 | 
			
		||||
        return achievements
 | 
			
		||||
 | 
			
		||||
    def get_achievements_by_user_id(self, user_id: int) -> List[Achievement]:
 | 
			
		||||
        achievements = List(Achievement)
 | 
			
		||||
        achievements_joins = List(UserGotAchievement)
 | 
			
		||||
        self._logger.trace(__name__, f"Send SQL command: {UserGotAchievement.get_select_by_user_id_string(user_id)}")
 | 
			
		||||
        results = self._context.select(UserGotAchievement.get_select_all_string())
 | 
			
		||||
        for result in results:
 | 
			
		||||
            self._logger.trace(__name__, f"Got UserGotAchievement with id {result[0]}")
 | 
			
		||||
            achievements_joins.append(self._join_from_result(result))
 | 
			
		||||
 | 
			
		||||
        for achievements_join in achievements_joins:
 | 
			
		||||
            results = self._context.select(Achievement.get_select_by_id_string(achievements_join.achievement.id))
 | 
			
		||||
            for result in results:
 | 
			
		||||
                self._logger.trace(__name__, f"Got Achievement with id {result[0]}")
 | 
			
		||||
                achievements.append(self._from_result(result))
 | 
			
		||||
 | 
			
		||||
        return achievements
 | 
			
		||||
 | 
			
		||||
    def add_achievement(self, achievement: Achievement):
 | 
			
		||||
        self._logger.trace(__name__, f"Send SQL command: {achievement.insert_string}")
 | 
			
		||||
        self._context.cursor.execute(achievement.insert_string)
 | 
			
		||||
@@ -70,3 +100,11 @@ class AchievementRepositoryService(AchievementRepositoryABC):
 | 
			
		||||
    def delete_achievement(self, achievement: Achievement):
 | 
			
		||||
        self._logger.trace(__name__, f"Send SQL command: {achievement.delete_string}")
 | 
			
		||||
        self._context.cursor.execute(achievement.delete_string)
 | 
			
		||||
 | 
			
		||||
    def add_user_got_achievement(self, join: UserGotAchievement):
 | 
			
		||||
        self._logger.trace(__name__, f"Send SQL command: {join.insert_string}")
 | 
			
		||||
        self._context.cursor.execute(join.insert_string)
 | 
			
		||||
 | 
			
		||||
    def delete_user_got_achievement(self, join: UserGotAchievement):
 | 
			
		||||
        self._logger.trace(__name__, f"Send SQL command: {join.delete_string}")
 | 
			
		||||
        self._context.cursor.execute(join.delete_string)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										78
									
								
								kdb-bot/src/modules/achievements/achievement_service.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								kdb-bot/src/modules/achievements/achievement_service.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,78 @@
 | 
			
		||||
from cpl_core.configuration import ConfigurationABC
 | 
			
		||||
from cpl_core.database.context import DatabaseContextABC
 | 
			
		||||
from cpl_core.logging import LoggerABC
 | 
			
		||||
from cpl_discord.service import DiscordBotServiceABC
 | 
			
		||||
from cpl_translation import TranslatePipe
 | 
			
		||||
 | 
			
		||||
from bot_core.configuration.server_settings import ServerSettings
 | 
			
		||||
from bot_core.service.message_service import MessageService
 | 
			
		||||
from bot_data.abc.achievement_repository_abc import AchievementRepositoryABC
 | 
			
		||||
from bot_data.model.achievement import Achievement
 | 
			
		||||
from bot_data.model.user import User
 | 
			
		||||
from bot_data.model.user_got_achievement import UserGotAchievement
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AchievementService:
 | 
			
		||||
    def __init__(
 | 
			
		||||
        self,
 | 
			
		||||
        config: ConfigurationABC,
 | 
			
		||||
        logger: LoggerABC,
 | 
			
		||||
        bot: DiscordBotServiceABC,
 | 
			
		||||
        achievements: AchievementRepositoryABC,
 | 
			
		||||
        db: DatabaseContextABC,
 | 
			
		||||
        message_service: MessageService,
 | 
			
		||||
        t: TranslatePipe,
 | 
			
		||||
    ):
 | 
			
		||||
        self._config = config
 | 
			
		||||
        self._logger = logger
 | 
			
		||||
        self._bot = bot
 | 
			
		||||
        self._achievements = achievements
 | 
			
		||||
        self._db = db
 | 
			
		||||
        self._message_service = message_service
 | 
			
		||||
        self._t = t
 | 
			
		||||
 | 
			
		||||
    def _match(self, value: str, operator: str, expected_value: str) -> bool:
 | 
			
		||||
        match operator:
 | 
			
		||||
            case "==":
 | 
			
		||||
                return value == expected_value
 | 
			
		||||
            case "!=":
 | 
			
		||||
                return value != expected_value
 | 
			
		||||
            case "<=":
 | 
			
		||||
                return value <= expected_value
 | 
			
		||||
            case ">=":
 | 
			
		||||
                return value >= expected_value
 | 
			
		||||
            case "<":
 | 
			
		||||
                return value < expected_value
 | 
			
		||||
            case ">":
 | 
			
		||||
                return value > expected_value
 | 
			
		||||
            case _:
 | 
			
		||||
                raise ValueError(f"Invalid operator: ${operator}")
 | 
			
		||||
 | 
			
		||||
    def has_user_achievement_already(self, user: User, achievement: Achievement) -> bool:
 | 
			
		||||
        user_achievements = self._achievements.get_achievements_by_user_id(user.id)
 | 
			
		||||
        return user_achievements.where(lambda x: x.name == achievement.name).count() > 0
 | 
			
		||||
 | 
			
		||||
    def has_user_achievement(self, user: User, achievement: Achievement) -> bool:
 | 
			
		||||
        return self._match(str(getattr(user, achievement.attribute)), achievement.operator, achievement.value)
 | 
			
		||||
 | 
			
		||||
    async def validate_achievements_for_user(self, user: User):
 | 
			
		||||
        achievements = self._achievements.get_achievements_by_server_id(user.server.id)
 | 
			
		||||
        for achievement in achievements:
 | 
			
		||||
            if self.has_user_achievement_already(user, achievement) and not self.has_user_achievement(
 | 
			
		||||
                user, achievement
 | 
			
		||||
            ):
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
            self._achievements.add_user_got_achievement(UserGotAchievement(user, achievement, user.server))
 | 
			
		||||
            self._db.save_changes()
 | 
			
		||||
            await self._send_achievement_notification(user.server.discord_id, user.discord_id, achievement.name)
 | 
			
		||||
 | 
			
		||||
    async def _send_achievement_notification(self, guild_id: int, member_id: int, achievement_name: str):
 | 
			
		||||
        member = self._bot.get_guild(guild_id).get_member(member_id)
 | 
			
		||||
 | 
			
		||||
        settings: ServerSettings = self._config.get_configuration(f"ServerSettings_{guild_id}")
 | 
			
		||||
        await self._message_service.send_channel_message(
 | 
			
		||||
            self._bot.get_channel(settings.notification_chat_id),
 | 
			
		||||
            self._t.transform("modules.achievements.got_new_achievement").format(member.mention, achievement_name),
 | 
			
		||||
            is_persistent=True,
 | 
			
		||||
        )
 | 
			
		||||
@@ -1,10 +1,13 @@
 | 
			
		||||
from cpl_core.configuration import ConfigurationABC
 | 
			
		||||
from cpl_core.dependency_injection import ServiceCollectionABC
 | 
			
		||||
from cpl_core.environment import ApplicationEnvironmentABC
 | 
			
		||||
from cpl_discord.discord_event_types_enum import DiscordEventTypesEnum
 | 
			
		||||
from cpl_discord.service.discord_collection_abc import DiscordCollectionABC
 | 
			
		||||
 | 
			
		||||
from bot_core.abc.module_abc import ModuleABC
 | 
			
		||||
from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum
 | 
			
		||||
from modules.achievements.achievement_service import AchievementService
 | 
			
		||||
from modules.achievements.events.achievement_on_message_event import AchievementOnMessageEvent
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AchievementsModule(ModuleABC):
 | 
			
		||||
@@ -15,4 +18,5 @@ class AchievementsModule(ModuleABC):
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    def configure_services(self, services: ServiceCollectionABC, env: ApplicationEnvironmentABC):
 | 
			
		||||
        pass
 | 
			
		||||
        services.add_transient(AchievementService)
 | 
			
		||||
        self._dc.add_event(DiscordEventTypesEnum.on_message.value, AchievementOnMessageEvent)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1
									
								
								kdb-bot/src/modules/achievements/events/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								kdb-bot/src/modules/achievements/events/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
# imports
 | 
			
		||||
@@ -0,0 +1,43 @@
 | 
			
		||||
import discord
 | 
			
		||||
from cpl_core.database.context import DatabaseContextABC
 | 
			
		||||
from cpl_core.logging import LoggerABC
 | 
			
		||||
from cpl_discord.events import OnMessageABC
 | 
			
		||||
from cpl_discord.service import DiscordBotServiceABC
 | 
			
		||||
 | 
			
		||||
from bot_core.helper.event_checks import EventChecks
 | 
			
		||||
from bot_data.abc.server_repository_abc import ServerRepositoryABC
 | 
			
		||||
from bot_data.abc.user_repository_abc import UserRepositoryABC
 | 
			
		||||
from modules.achievements.achievement_service import AchievementService
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AchievementOnMessageEvent(OnMessageABC):
 | 
			
		||||
    def __init__(
 | 
			
		||||
        self,
 | 
			
		||||
        logger: LoggerABC,
 | 
			
		||||
        bot: DiscordBotServiceABC,
 | 
			
		||||
        achievements: AchievementService,
 | 
			
		||||
        db: DatabaseContextABC,
 | 
			
		||||
        servers: ServerRepositoryABC,
 | 
			
		||||
        users: UserRepositoryABC,
 | 
			
		||||
    ):
 | 
			
		||||
        OnMessageABC.__init__(self)
 | 
			
		||||
 | 
			
		||||
        self._logger = logger
 | 
			
		||||
        self._bot = bot
 | 
			
		||||
        self._achievements = achievements
 | 
			
		||||
        self._db = db
 | 
			
		||||
        self._servers = servers
 | 
			
		||||
        self._users = users
 | 
			
		||||
 | 
			
		||||
    @EventChecks.check_is_ready()
 | 
			
		||||
    async def on_message(self, message: discord.Message):
 | 
			
		||||
        if message.author.bot:
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        server = self._servers.get_server_by_discord_id(message.guild.id)
 | 
			
		||||
        user = self._users.get_user_by_discord_id_and_server_id(message.author.id, server.id)
 | 
			
		||||
 | 
			
		||||
        user.message_count += 1
 | 
			
		||||
        self._db.save_changes()
 | 
			
		||||
 | 
			
		||||
        await self._achievements.validate_achievements_for_user(user)
 | 
			
		||||
@@ -1,28 +0,0 @@
 | 
			
		||||
import traceback
 | 
			
		||||
 | 
			
		||||
from cpl_core.configuration.configuration_model_abc import ConfigurationModelABC
 | 
			
		||||
from cpl_core.console import Console
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class LevelServerSettings(ConfigurationModelABC):
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        ConfigurationModelABC.__init__(self)
 | 
			
		||||
 | 
			
		||||
        self._id: int = 0
 | 
			
		||||
        self._changed_level_notification_channel = 0
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def id(self) -> int:
 | 
			
		||||
        return self._id
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def changed_level_notification_channel(self) -> int:
 | 
			
		||||
        return self._changed_level_notification_channel
 | 
			
		||||
 | 
			
		||||
    def from_dict(self, settings: dict):
 | 
			
		||||
        try:
 | 
			
		||||
            self._id = int(settings["Id"])
 | 
			
		||||
            self._changed_level_notification_channel = int(settings["ChangedLevelNotificationChannelId"])
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            Console.error(f"[ ERROR ] [ {__name__} ]: Reading error in {type(self).__name__} settings")
 | 
			
		||||
            Console.error(f"[ EXCEPTION ] [ {__name__} ]: {e} -> {traceback.format_exc()}")
 | 
			
		||||
@@ -1,31 +0,0 @@
 | 
			
		||||
import traceback
 | 
			
		||||
 | 
			
		||||
from cpl_core.configuration.configuration_model_abc import ConfigurationModelABC
 | 
			
		||||
from cpl_core.console import Console
 | 
			
		||||
from cpl_query.extension import List
 | 
			
		||||
 | 
			
		||||
from modules.level.configuration.level_server_settings import LevelServerSettings
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class LevelSettings(ConfigurationModelABC):
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        ConfigurationModelABC.__init__(self)
 | 
			
		||||
 | 
			
		||||
        self._servers: List[LevelServerSettings] = List()
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def servers(self) -> List[LevelServerSettings]:
 | 
			
		||||
        return self._servers
 | 
			
		||||
 | 
			
		||||
    def from_dict(self, settings: dict):
 | 
			
		||||
        try:
 | 
			
		||||
            servers = List(LevelServerSettings)
 | 
			
		||||
            for s in settings:
 | 
			
		||||
                st = LevelServerSettings()
 | 
			
		||||
                settings[s]["Id"] = s
 | 
			
		||||
                st.from_dict(settings[s])
 | 
			
		||||
                servers.append(st)
 | 
			
		||||
            self._servers = servers
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            Console.error(f"[ ERROR ] [ {__name__} ]: Reading error in {type(self).__name__} settings")
 | 
			
		||||
            Console.error(f"[ EXCEPTION ] [ {__name__} ]: {e} -> {traceback.format_exc()}")
 | 
			
		||||
@@ -6,13 +6,13 @@ from cpl_discord.container import Guild, Role, Member
 | 
			
		||||
from cpl_discord.service import DiscordBotServiceABC
 | 
			
		||||
from cpl_translation import TranslatePipe
 | 
			
		||||
 | 
			
		||||
from bot_core.configuration.server_settings import ServerSettings
 | 
			
		||||
from bot_core.service.message_service import MessageService
 | 
			
		||||
from bot_data.model.level import Level
 | 
			
		||||
from bot_data.model.user import User
 | 
			
		||||
from bot_data.service.level_repository_service import LevelRepositoryService
 | 
			
		||||
from bot_data.service.server_repository_service import ServerRepositoryService
 | 
			
		||||
from bot_data.service.user_repository_service import UserRepositoryService
 | 
			
		||||
from modules.level.configuration.level_server_settings import LevelServerSettings
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class LevelService:
 | 
			
		||||
@@ -75,9 +75,9 @@ class LevelService:
 | 
			
		||||
            self._logger.error(__name__, f"Adding role {level_role.name} to {member.name} failed!", e)
 | 
			
		||||
 | 
			
		||||
        if notification_needed:
 | 
			
		||||
            level_settings: LevelServerSettings = self._config.get_configuration(f"LevelServerSettings_{guild.id}")
 | 
			
		||||
            settings: ServerSettings = self._config.get_configuration(f"ServerSettings_{guild.id}")
 | 
			
		||||
            await self._message_service.send_channel_message(
 | 
			
		||||
                self._bot.get_channel(level_settings.changed_level_notification_channel),
 | 
			
		||||
                self._bot.get_channel(settings.notification_chat_id),
 | 
			
		||||
                self._t.transform("modules.level.new_level_message").format(member.mention, level.name),
 | 
			
		||||
                is_persistent=True,
 | 
			
		||||
            )
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user