diff --git a/kdb-bot/src/bot/bot.json b/kdb-bot/src/bot/bot.json index a0297264..e5666f6a 100644 --- a/kdb-bot/src/bot/bot.json +++ b/kdb-bot/src/bot/bot.json @@ -19,7 +19,6 @@ "cpl-core==2022.12.1.post3", "cpl-translation==2022.12.1", "cpl-query==2022.12.2.post1", - "cpl-discord==2022.12.1.post2", "Flask==2.2.2", "Flask-Classful==0.14.2", "Flask-Cors==3.0.10", @@ -29,7 +28,8 @@ "eventlet==0.33.3", "requests-oauthlib==1.3.1", "icmplib==3.0.3", - "ariadne==0.17.1" + "ariadne==0.17.1", + "cpl-discord==2022.12.2" ], "DevDependencies": [ "cpl-cli==2022.12.1.post3" diff --git a/kdb-bot/src/bot/config b/kdb-bot/src/bot/config index b0ae8762..ac704682 160000 --- a/kdb-bot/src/bot/config +++ b/kdb-bot/src/bot/config @@ -1 +1 @@ -Subproject commit b0ae87621bbe54fd9c5650071ec8c1c9ec32df48 +Subproject commit ac7046820f3410f55e779797126d9410c6bcdc9f diff --git a/kdb-bot/src/bot/startup_migration_extension.py b/kdb-bot/src/bot/startup_migration_extension.py index 415c8344..a6b13ddd 100644 --- a/kdb-bot/src/bot/startup_migration_extension.py +++ b/kdb-bot/src/bot/startup_migration_extension.py @@ -11,6 +11,7 @@ from bot_data.migration.auto_role_migration import AutoRoleMigration from bot_data.migration.initial_migration import InitialMigration from bot_data.migration.level_migration import LevelMigration from bot_data.migration.stats_migration import StatsMigration +from bot_data.migration.user_joined_game_server_migration import UserJoinedGameServerMigration from bot_data.migration.user_message_count_per_hour_migration import ( UserMessageCountPerHourMigration, ) @@ -34,3 +35,4 @@ class StartupMigrationExtension(StartupExtensionABC): services.add_transient(MigrationABC, AutoRoleFix1Migration) # 30.12.2022 #151 - 0.3.0 services.add_transient(MigrationABC, UserMessageCountPerHourMigration) # 11.01.2023 #168 - 0.3.1 services.add_transient(MigrationABC, ApiKeyMigration) # 09.02.2023 #162 - 1.0.0 + services.add_transient(MigrationABC, UserJoinedGameServerMigration) # 12.02.2023 #181 - 1.0.0 diff --git a/kdb-bot/src/bot/translation/de.json b/kdb-bot/src/bot/translation/de.json index 25c1301e..756ffb76 100644 --- a/kdb-bot/src/bot/translation/de.json +++ b/kdb-bot/src/bot/translation/de.json @@ -200,6 +200,13 @@ "error": { "atr_not_found": "Das Attribut {} konnte nicht gefunden werden :(" } + }, + "register": { + "success": "Spieler wurde mit dem Mitglied verlinkt :D", + "not_found": "Benutzer nicht gefunden!" + }, + "unregister": { + "success": "Verlinkung wurde entfernt :D" } }, "boot_log": { diff --git a/kdb-bot/src/bot_api/config b/kdb-bot/src/bot_api/config index 27289afd..3c2a8630 160000 --- a/kdb-bot/src/bot_api/config +++ b/kdb-bot/src/bot_api/config @@ -1 +1 @@ -Subproject commit 27289afd5e2d020cd9fcec62a682204a93688f25 +Subproject commit 3c2a863022e6656927b9360875695a6bcdc70ac9 diff --git a/kdb-bot/src/bot_data/abc/user_joined_game_server_repository_abc.py b/kdb-bot/src/bot_data/abc/user_joined_game_server_repository_abc.py new file mode 100644 index 00000000..97a0358b --- /dev/null +++ b/kdb-bot/src/bot_data/abc/user_joined_game_server_repository_abc.py @@ -0,0 +1,52 @@ +from abc import ABC, abstractmethod +from typing import Optional + +from cpl_query.extension import List + +from bot_data.model.user_joined_game_server import UserJoinedGameServer + + +class UserJoinedGameServerRepositoryABC(ABC): + @abstractmethod + def __init__(self): + pass + + @abstractmethod + def get_user_joined_game_servers(self) -> List[UserJoinedGameServer]: + pass + + @abstractmethod + def get_user_joined_game_server_by_id(self, id: int) -> UserJoinedGameServer: + pass + + @abstractmethod + def get_user_joined_game_servers_by_user_id(self, user_id: int) -> List[UserJoinedGameServer]: + pass + + @abstractmethod + def get_active_user_joined_game_server_by_user_id(self, user_id: int) -> UserJoinedGameServer: + pass + + @abstractmethod + def find_active_user_joined_game_server_by_user_id(self, user_id: int) -> Optional[UserJoinedGameServer]: + pass + + @abstractmethod + def find_active_user_joined_game_servers_by_user_id(self, user_id: int) -> List[Optional[UserJoinedGameServer]]: + pass + + @abstractmethod + def add_user_joined_game_server(self, user_joined_game_server: UserJoinedGameServer): + pass + + @abstractmethod + def update_user_joined_game_server(self, user_joined_game_server: UserJoinedGameServer): + pass + + @abstractmethod + def delete_user_joined_game_server(self, user_joined_game_server: UserJoinedGameServer): + pass + + @abstractmethod + def delete_user_joined_game_server_by_user_id(self, user_id: int): + pass diff --git a/kdb-bot/src/bot_data/data_module.py b/kdb-bot/src/bot_data/data_module.py index 6e8c8000..16c751a9 100644 --- a/kdb-bot/src/bot_data/data_module.py +++ b/kdb-bot/src/bot_data/data_module.py @@ -13,6 +13,7 @@ from bot_data.abc.known_user_repository_abc import KnownUserRepositoryABC from bot_data.abc.level_repository_abc import LevelRepositoryABC from bot_data.abc.server_repository_abc import ServerRepositoryABC from bot_data.abc.statistic_repository_abc import StatisticRepositoryABC +from bot_data.abc.user_joined_game_server_repository_abc import UserJoinedGameServerRepositoryABC from bot_data.abc.user_joined_server_repository_abc import UserJoinedServerRepositoryABC from bot_data.abc.user_joined_voice_channel_repository_abc import ( UserJoinedVoiceChannelRepositoryABC, @@ -30,6 +31,7 @@ from bot_data.service.level_repository_service import LevelRepositoryService from bot_data.service.seeder_service import SeederService from bot_data.service.server_repository_service import ServerRepositoryService from bot_data.service.statistic_repository_service import StatisticRepositoryService +from bot_data.service.user_joined_game_server_repository_service import UserJoinedGameServerRepositoryService from bot_data.service.user_joined_server_repository_service import ( UserJoinedServerRepositoryService, ) @@ -58,6 +60,7 @@ class DataModule(ModuleABC): services.add_transient(KnownUserRepositoryABC, KnownUserRepositoryService) services.add_transient(UserJoinedServerRepositoryABC, UserJoinedServerRepositoryService) services.add_transient(UserJoinedVoiceChannelRepositoryABC, UserJoinedVoiceChannelRepositoryService) + services.add_transient(UserJoinedGameServerRepositoryABC, UserJoinedGameServerRepositoryService) services.add_transient(AutoRoleRepositoryABC, AutoRoleRepositoryService) services.add_transient(LevelRepositoryABC, LevelRepositoryService) services.add_transient(StatisticRepositoryABC, StatisticRepositoryService) diff --git a/kdb-bot/src/bot_data/migration/user_joined_game_server_migration.py b/kdb-bot/src/bot_data/migration/user_joined_game_server_migration.py new file mode 100644 index 00000000..e29854aa --- /dev/null +++ b/kdb-bot/src/bot_data/migration/user_joined_game_server_migration.py @@ -0,0 +1,52 @@ +from bot_core.logging.database_logger import DatabaseLogger +from bot_data.abc.migration_abc import MigrationABC +from bot_data.db_context import DBContext + + +class UserJoinedGameServerMigration(MigrationABC): + name = "1.0_UserJoinedGameServerMigration" + + def __init__(self, logger: DatabaseLogger, db: DBContext): + MigrationABC.__init__(self) + self._logger = logger + self._db = db + self._cursor = db.cursor + + def upgrade(self): + self._logger.debug(__name__, "Running upgrade") + + self._cursor.execute( + str( + f""" + CREATE TABLE IF NOT EXISTS `UserJoinedGameServer` ( + `Id` BIGINT NOT NULL AUTO_INCREMENT, + `UserId` BIGINT NOT NULL, + `GameServer` VARCHAR(255) NOT NULL, + `JoinedOn` DATETIME(6) NOT NULL, + `LeavedOn` DATETIME(6), + `CreatedAt` DATETIME(6), + `LastModifiedAt` DATETIME(6), + FOREIGN KEY (`UserId`) REFERENCES Users(`UserId`), + PRIMARY KEY(`Id`) + ); + """ + ) + ) + + self._cursor.execute( + str( + f""" + ALTER TABLE Users ADD MinecraftId VARCHAR(255) NULL AFTER XP; + """ + ) + ) + + def downgrade(self): + self._cursor.execute("DROP TABLE `UserJoinedGameServer`;") + self._cursor.execute( + str( + f""" + ALTER TABLE Users DROP COLUMN MinecraftId; + """ + ) + ) diff --git a/kdb-bot/src/bot_data/model/user.py b/kdb-bot/src/bot_data/model/user.py index ab97ecd1..a2f4de81 100644 --- a/kdb-bot/src/bot_data/model/user.py +++ b/kdb-bot/src/bot_data/model/user.py @@ -11,6 +11,7 @@ class User(TableABC): self, dc_id: int, xp: int, + minecraft_id: Optional[str], server: Optional[Server], created_at: datetime = None, modified_at: datetime = None, @@ -19,6 +20,7 @@ class User(TableABC): self._user_id = id self._discord_id = dc_id self._xp = xp + self._minecraft_id = minecraft_id self._server = server TableABC.__init__(self) @@ -42,6 +44,14 @@ class User(TableABC): self._modified_at = datetime.now().isoformat() self._xp = value + @property + def minecraft_id(self) -> Optional[str]: + return self._minecraft_id + + @minecraft_id.setter + def minecraft_id(self, value: str): + self._minecraft_id = value + @property def server(self) -> Optional[Server]: return self._server @@ -96,10 +106,11 @@ class User(TableABC): return str( f""" INSERT INTO `Users` ( - `DiscordId`, `XP`, `ServerId`, `CreatedAt`, `LastModifiedAt` + `DiscordId`, `XP`, `MinecraftId`, `ServerId`, `CreatedAt`, `LastModifiedAt` ) VALUES ( {self._discord_id}, {self._xp}, + '{"NULL" if self._minecraft_id is None else self._minecraft_id}', {self._server.server_id}, '{self._created_at}', '{self._modified_at}' @@ -113,6 +124,7 @@ class User(TableABC): f""" UPDATE `Users` SET `XP` = {self._xp}, + `MinecraftId` = '{"NULL" if self._minecraft_id is None else self._minecraft_id}', `LastModifiedAt` = '{self._modified_at}' WHERE `UserId` = {self._user_id}; """ diff --git a/kdb-bot/src/bot_data/model/user_joined_game_server.py b/kdb-bot/src/bot_data/model/user_joined_game_server.py new file mode 100644 index 00000000..d13beed0 --- /dev/null +++ b/kdb-bot/src/bot_data/model/user_joined_game_server.py @@ -0,0 +1,154 @@ +from datetime import datetime + +from cpl_core.database import TableABC + +from bot_data.model.user import User + + +class UserJoinedGameServer(TableABC): + def __init__( + self, + user: User, + game_server: str, + joined_on: datetime, + leaved_on: datetime = None, + created_at: datetime = None, + modified_at: datetime = None, + id=0, + ): + self._id = id + self._user = user + self._game_server = game_server + self._joined_on = joined_on + self._leaved_on = leaved_on + + 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 game_server(self) -> str: + return self._game_server + + @property + def joined_on(self) -> datetime: + return self._joined_on + + @joined_on.setter + def joined_on(self, value: datetime): + self._modified_at = datetime.now() + self.joined_on = value + + @property + def leaved_on(self) -> datetime: + return self._leaved_on + + @leaved_on.setter + def leaved_on(self, value: datetime): + self._modified_at = datetime.now() + self._leaved_on = value + + @staticmethod + def get_select_all_string() -> str: + return str( + f""" + SELECT * FROM `UserJoinedGameServer`; + """ + ) + + @staticmethod + def get_select_by_id_string(id: int) -> str: + return str( + f""" + SELECT * FROM `UserJoinedGameServer` + WHERE `Id` = {id}; + """ + ) + + @staticmethod + def get_select_by_user_id_string(id: int) -> str: + return str( + f""" + SELECT * FROM `UserJoinedGameServer` + WHERE `UserId` = {id}; + """ + ) + + @staticmethod + def get_select_active_by_user_id_string(id: int) -> str: + return str( + f""" + SELECT * FROM `UserJoinedGameServer` + WHERE `UserId` = {id} + AND `LeavedOn` IS NULL; + """ + ) + + @property + def insert_string(self) -> str: + if self._leaved_on is not None: + return str( + f""" + INSERT INTO `UserJoinedGameServer` ( + `UserId`, `GameServer`, `JoinedOn`, `LeavedOn`, `CreatedAt`, `LastModifiedAt` + ) VALUES ( + {self._user.user_id}, + '{self._game_server}', + '{self._joined_on}', + '{self._leaved_on}', + '{self._created_at}', + '{self._modified_at}' + ); + """ + ) + else: + return str( + f""" + INSERT INTO `UserJoinedGameServer` ( + `UserId`, `GameServer`, `JoinedOn`, `CreatedAt`, `LastModifiedAt` + ) VALUES ( + {self._user.user_id}, + '{self._game_server}', + '{self._joined_on}', + '{self._created_at}', + '{self._modified_at}' + ); + """ + ) + + @property + def udpate_string(self) -> str: + return str( + f""" + UPDATE `UserJoinedGameServer` + SET `LeavedOn` = '{self._leaved_on}', + `LastModifiedAt` = '{self._modified_at}' + WHERE `Id` = {self._id}; + """ + ) + + @property + def delete_string(self) -> str: + return str( + f""" + DELETE FROM `UserJoinedGameServer` + WHERE `Id` = {self._id}; + """ + ) + + @staticmethod + def delete_by_user_id_string(id: int) -> str: + return str( + f""" + DELETE FROM `UserJoinedGameServer` + WHERE `UserId` = {id} + """ + ) diff --git a/kdb-bot/src/bot_data/model/user_role_enum.py b/kdb-bot/src/bot_data/model/user_role_enum.py index edfaf85d..ad612e1d 100644 --- a/kdb-bot/src/bot_data/model/user_role_enum.py +++ b/kdb-bot/src/bot_data/model/user_role_enum.py @@ -2,7 +2,6 @@ from enum import Enum class UserRoleEnum(Enum): - member = 0 moderator = 1 admin = 2 diff --git a/kdb-bot/src/bot_data/service/user_joined_game_server_repository_service.py b/kdb-bot/src/bot_data/service/user_joined_game_server_repository_service.py new file mode 100644 index 00000000..4d8099f6 --- /dev/null +++ b/kdb-bot/src/bot_data/service/user_joined_game_server_repository_service.py @@ -0,0 +1,160 @@ +from typing import Optional + +from cpl_core.database.context import DatabaseContextABC +from cpl_query.extension import List + +from bot_core.logging.database_logger import DatabaseLogger +from bot_data.abc.user_joined_game_server_repository_abc import ( + UserJoinedGameServerRepositoryABC, +) +from bot_data.abc.user_repository_abc import UserRepositoryABC +from bot_data.model.user_joined_game_server import UserJoinedGameServer + + +class UserJoinedGameServerRepositoryService(UserJoinedGameServerRepositoryABC): + def __init__( + self, + logger: DatabaseLogger, + db_context: DatabaseContextABC, + users: UserRepositoryABC, + ): + self._logger = logger + self._context = db_context + + self._users = users + + UserJoinedGameServerRepositoryABC.__init__(self) + + def get_user_joined_game_servers(self) -> List[UserJoinedGameServer]: + joins = List(UserJoinedGameServer) + self._logger.trace( + __name__, + f"Send SQL command: {UserJoinedGameServer.get_select_all_string()}", + ) + results = self._context.select(UserJoinedGameServer.get_select_all_string()) + for result in results: + self._logger.trace(__name__, f"Get user-joined-game-server with id {result[0]}") + joins.append( + UserJoinedGameServer( + self._users.get_user_by_id(result[1]), + result[2], + result[3], + result[4], + result[5], + id=result[0], + ) + ) + + return joins + + def get_user_joined_game_server_by_id(self, id: int) -> UserJoinedGameServer: + self._logger.trace( + __name__, + f"Send SQL command: {UserJoinedGameServer.get_select_by_id_string(id)}", + ) + result = self._context.select(UserJoinedGameServer.get_select_by_id_string(id))[0] + return UserJoinedGameServer( + self._users.get_user_by_id(result[1]), + result[2], + result[3], + result[4], + result[5], + id=result[0], + ) + + def get_user_joined_game_servers_by_user_id(self, user_id: int) -> List[UserJoinedGameServer]: + joins = List(UserJoinedGameServer) + self._logger.trace( + __name__, + f"Send SQL command: {UserJoinedGameServer.get_select_by_user_id_string(user_id)}", + ) + results = self._context.select(UserJoinedGameServer.get_select_by_user_id_string(user_id)) + for result in results: + joins.append( + UserJoinedGameServer( + self._users.get_user_by_id(result[1]), + result[2], + result[3], + result[4], + result[5], + id=result[0], + ) + ) + + return joins + + def get_active_user_joined_game_server_by_user_id(self, user_id: int) -> UserJoinedGameServer: + self._logger.trace( + __name__, + f"Send SQL command: {UserJoinedGameServer.get_select_by_user_id_string(user_id)}", + ) + result = self._context.select(UserJoinedGameServer.get_select_active_by_user_id_string(user_id))[0] + return UserJoinedGameServer( + self._users.get_user_by_id(result[1]), + result[2], + result[3], + result[4], + result[5], + id=result[0], + ) + + def find_active_user_joined_game_server_by_user_id(self, user_id: int) -> Optional[UserJoinedGameServer]: + self._logger.trace( + __name__, + f"Send SQL command: {UserJoinedGameServer.get_select_by_user_id_string(user_id)}", + ) + result = self._context.select(UserJoinedGameServer.get_select_active_by_user_id_string(user_id)) + if result is None or len(result) == 0: + return None + + result = result[0] + + return UserJoinedGameServer( + self._users.get_user_by_id(result[1]), + result[2], + result[3], + result[4], + result[5], + id=result[0], + ) + + def find_active_user_joined_game_servers_by_user_id(self, user_id: int) -> List[Optional[UserJoinedGameServer]]: + self._logger.trace( + __name__, + f"Send SQL command: {UserJoinedGameServer.get_select_active_by_user_id_string(user_id)}", + ) + result = List(UserJoinedGameServer) + db_results = self._context.select(UserJoinedGameServer.get_select_active_by_user_id_string(user_id)) + + for db_result in db_results: + result.append( + UserJoinedGameServer( + self._users.get_user_by_id(db_result[1]), + db_result[2], + db_result[3], + db_result[4], + db_result[5], + id=db_result[0], + ) + ) + + return result + + def add_user_joined_game_server(self, user_joined_game_server: UserJoinedGameServer): + self._logger.trace(__name__, f"Send SQL command: {user_joined_game_server.insert_string}") + self._context.cursor.execute(user_joined_game_server.insert_string) + + def update_user_joined_game_server(self, user_joined_game_server: UserJoinedGameServer): + self._logger.trace(__name__, f"Send SQL command: {user_joined_game_server.udpate_string}") + self._context.cursor.execute(user_joined_game_server.udpate_string) + + def delete_user_joined_game_server(self, user_joined_game_server: UserJoinedGameServer): + self._logger.trace(__name__, f"Send SQL command: {user_joined_game_server.delete_string}") + self._context.cursor.execute(user_joined_game_server.delete_string) + + def delete_user_joined_game_server_by_user_id(self, user_id: int): + self._logger.trace( + __name__, + f"Send SQL command: {UserJoinedGameServer.delete_by_user_id_string}", + ) + self._context.cursor.execute(UserJoinedGameServer.delete_by_user_id_string(user_id)) diff --git a/kdb-bot/src/bot_data/service/user_repository_service.py b/kdb-bot/src/bot_data/service/user_repository_service.py index 9d44c0ba..44207824 100644 --- a/kdb-bot/src/bot_data/service/user_repository_service.py +++ b/kdb-bot/src/bot_data/service/user_repository_service.py @@ -33,7 +33,10 @@ class UserRepositoryService(UserRepositoryABC): User( result[1], result[2], - self._servers.get_server_by_id(result[3]), + result[3], + self._servers.get_server_by_id(result[4]), + result[5], + result[6], id=result[0], ) ) @@ -47,7 +50,10 @@ class UserRepositoryService(UserRepositoryABC): return User( result[1], result[2], - self._servers.get_server_by_id(result[3]), + result[3], + self._servers.get_server_by_id(result[4]), + result[5], + result[6], id=result[0], ) @@ -62,7 +68,10 @@ class UserRepositoryService(UserRepositoryABC): return User( result[1], result[2], - self._servers.get_server_by_id(result[3]), + result[3], + self._servers.get_server_by_id(result[4]), + result[5], + result[6], id=result[0], ) @@ -78,7 +87,10 @@ class UserRepositoryService(UserRepositoryABC): User( result[1], result[2], - self._servers.get_server_by_id(result[3]), + result[3], + self._servers.get_server_by_id(result[4]), + result[5], + result[6], id=result[0], ) ) @@ -97,7 +109,10 @@ class UserRepositoryService(UserRepositoryABC): User( result[1], result[2], - self._servers.get_server_by_id(result[3]), + result[3], + self._servers.get_server_by_id(result[4]), + result[5], + result[6], id=result[0], ) ) @@ -114,7 +129,10 @@ class UserRepositoryService(UserRepositoryABC): return User( result[1], result[2], - self._servers.get_server_by_id(result[3]), + result[3], + self._servers.get_server_by_id(result[4]), + result[5], + result[6], id=result[0], ) @@ -132,9 +150,10 @@ class UserRepositoryService(UserRepositoryABC): return User( result[1], result[2], - self._servers.get_server_by_id(result[3]), - result[4], + result[3], + self._servers.get_server_by_id(result[4]), result[5], + result[6], id=result[0], ) diff --git a/kdb-bot/src/bot_graphql/abc/query_abc.py b/kdb-bot/src/bot_graphql/abc/query_abc.py index fd652ec0..b9e9f444 100644 --- a/kdb-bot/src/bot_graphql/abc/query_abc.py +++ b/kdb-bot/src/bot_graphql/abc/query_abc.py @@ -17,6 +17,7 @@ from bot_data.model.known_user import KnownUser from bot_data.model.level import Level from bot_data.model.server import Server from bot_data.model.user import User +from bot_data.model.user_joined_game_server import UserJoinedGameServer from bot_data.model.user_joined_server import UserJoinedServer from bot_data.model.user_joined_voice_channel import UserJoinedVoiceChannel from bot_data.model.user_role_enum import UserRoleEnum @@ -49,15 +50,15 @@ class QueryABC(ObjectType): sort.from_dict(kwargs["sort"]) kwargs["sort"] = sort - collection = get_collection(*args) + collection: List = get_collection(*args) user = Route.get_user() if user == "system" or user.auth_role == AuthRoleEnum.admin: return self._resolve_collection(collection, *args, **kwargs) - for x in collection: + for x in collection.to_list(): if not self._can_user_see_element(user, x): - return List() + collection.remove(x) return self._resolve_collection(collection, *args, **kwargs) @@ -141,6 +142,13 @@ class QueryABC(ObjectType): access = True break + elif type(element) == UserJoinedGameServer: + for u in user.users: + u: User = u + if u.user_id == element.user.user_id: + access = True + break + return access @ServiceProviderABC.inject diff --git a/kdb-bot/src/bot_graphql/filter/user_filter.py b/kdb-bot/src/bot_graphql/filter/user_filter.py index b0028674..cf1a9bb6 100644 --- a/kdb-bot/src/bot_graphql/filter/user_filter.py +++ b/kdb-bot/src/bot_graphql/filter/user_filter.py @@ -30,8 +30,10 @@ class UserFilter(FilterABC): self._discord_id = None self._name = None self._xp = None + self._minecraft_id = None self._ontime = None self._level: Optional[LevelFilter] = None + self._server = None def from_dict(self, values: dict): if "id" in values: @@ -46,6 +48,9 @@ class UserFilter(FilterABC): if "xp" in values: self._xp = int(values["xp"]) + if "minecraftId" in values: + self._minecraft_id = values["minecraftId"] + if "ontime" in values: self._ontime = int(values["ontime"]) @@ -75,6 +80,9 @@ class UserFilter(FilterABC): if self._xp is not None: query = query.where(lambda x: x.xp == self._xp) + if self._minecraft_id is not None: + query = query.where(lambda x: x.minecraft_id == self._minecraft_id) + if self._ontime is not None: query = query.where(lambda x: self._client_utils.get_ontime_for_user(x) == self._ontime) diff --git a/kdb-bot/src/bot_graphql/filter/user_joined_game_server_filter.py b/kdb-bot/src/bot_graphql/filter/user_joined_game_server_filter.py new file mode 100644 index 00000000..1cdb4d4e --- /dev/null +++ b/kdb-bot/src/bot_graphql/filter/user_joined_game_server_filter.py @@ -0,0 +1,48 @@ +from cpl_core.dependency_injection import ServiceProviderABC +from cpl_discord.service import DiscordBotServiceABC +from cpl_query.extension import List + +from bot_data.model.user_joined_game_server import UserJoinedGameServer +from bot_graphql.abc.filter_abc import FilterABC + + +class UserJoinedGameServerFilter(FilterABC): + def __init__( + self, + services: ServiceProviderABC, + bot: DiscordBotServiceABC, + ): + FilterABC.__init__(self) + + self._services = services + self._bot = bot + + self._id = None + self._game_server = None + self._user = None + + def from_dict(self, values: dict): + if "id" in values: + self._id = int(values["id"]) + + if "gameServer" in values: + self._game_server = values["gameServer"] + + if "user" in values: + from bot_graphql.filter.user_filter import UserFilter + + self._user: UserFilter = self._services.get_service(UserFilter) + self._user.from_dict(values["user"]) + + def filter(self, query: List[UserJoinedGameServer]) -> List[UserJoinedGameServer]: + if self._id is not None: + query = query.where(lambda x: x.id == self._id) + + if self._game_server is not None: + query = query.where(lambda x: x.game_server == self._game_server) + + if self._user is not None: + users = self._user.filter(query.select(lambda x: x.user)).select(lambda x: x.user_id) + query = query.where(lambda x: x.user.user_id in users) + + return query diff --git a/kdb-bot/src/bot_graphql/graphql_module.py b/kdb-bot/src/bot_graphql/graphql_module.py index 43da66e0..14c7a841 100644 --- a/kdb-bot/src/bot_graphql/graphql_module.py +++ b/kdb-bot/src/bot_graphql/graphql_module.py @@ -14,6 +14,7 @@ from bot_graphql.filter.client_filter import ClientFilter from bot_graphql.filter.level_filter import LevelFilter from bot_graphql.filter.server_filter import ServerFilter from bot_graphql.filter.user_filter import UserFilter +from bot_graphql.filter.user_joined_game_server_filter import UserJoinedGameServerFilter from bot_graphql.filter.user_joined_server_filter import UserJoinedServerFilter from bot_graphql.filter.user_joined_voice_channel_filter import UserJoinedVoiceChannelFilter from bot_graphql.graphql_service import GraphQLService @@ -21,6 +22,7 @@ from bot_graphql.mutation import Mutation from bot_graphql.mutations.auto_role_mutation import AutoRoleMutation from bot_graphql.mutations.auto_role_rule_mutation import AutoRoleRuleMutation from bot_graphql.mutations.level_mutation import LevelMutation +from bot_graphql.mutations.user_joined_game_server_mutation import UserJoinedGameServerMutation from bot_graphql.mutations.user_mutation import UserMutation from bot_graphql.queries.auto_role_query import AutoRoleQuery from bot_graphql.queries.auto_role_rule_query import AutoRoleRuleQuery @@ -28,6 +30,7 @@ from bot_graphql.queries.client_query import ClientQuery from bot_graphql.queries.known_user_query import KnownUserQuery from bot_graphql.queries.level_query import LevelQuery from bot_graphql.queries.server_query import ServerQuery +from bot_graphql.queries.user_joined_game_server_query import UserJoinedGameServerQuery from bot_graphql.queries.user_joined_server_query import UserJoinedServerQuery from bot_graphql.queries.user_joined_voice_channel_query import UserJoinedVoiceChannelQuery from bot_graphql.queries.user_query import UserQuery @@ -58,6 +61,7 @@ class GraphQLModule(ModuleABC): services.add_transient(QueryABC, UserQuery) services.add_transient(QueryABC, UserJoinedServerQuery) services.add_transient(QueryABC, UserJoinedVoiceChannelQuery) + services.add_transient(QueryABC, UserJoinedGameServerQuery) # filters services.add_singleton(FilterABC, AutoRoleFilter) @@ -68,11 +72,13 @@ class GraphQLModule(ModuleABC): services.add_singleton(FilterABC, UserFilter) services.add_singleton(FilterABC, UserJoinedServerFilter) services.add_singleton(FilterABC, UserJoinedVoiceChannelFilter) + services.add_singleton(FilterABC, UserJoinedGameServerFilter) # mutations services.add_transient(QueryABC, AutoRoleMutation) services.add_transient(QueryABC, AutoRoleRuleMutation) services.add_transient(QueryABC, LevelMutation) services.add_transient(QueryABC, UserMutation) + services.add_transient(QueryABC, UserJoinedGameServerMutation) services.add_transient(SeederService) diff --git a/kdb-bot/src/bot_graphql/model/mutation.gql b/kdb-bot/src/bot_graphql/model/mutation.gql index 5290dd1c..9631fd8d 100644 --- a/kdb-bot/src/bot_graphql/model/mutation.gql +++ b/kdb-bot/src/bot_graphql/model/mutation.gql @@ -3,4 +3,5 @@ type Mutation { autoRoleRule: AutoRoleRuleMutation level: LevelMutation user: UserMutation + userJoinedGameServer: UserJoinedGameServerMutation } \ No newline at end of file diff --git a/kdb-bot/src/bot_graphql/model/query.gql b/kdb-bot/src/bot_graphql/model/query.gql index 46f80381..2f597800 100644 --- a/kdb-bot/src/bot_graphql/model/query.gql +++ b/kdb-bot/src/bot_graphql/model/query.gql @@ -18,10 +18,13 @@ type Query { servers(filter: ServerFilter, page: Page, sort: Sort): [Server] userJoinedServerCount: Int - userJoinedServers(filter: UserJoinedServerFilter, page: Page, sort: Sort): [User] + userJoinedServers(filter: UserJoinedServerFilter, page: Page, sort: Sort): [UserJoinedServer] userJoinedVoiceChannelCount: Int - userJoinedVoiceChannels(filter: UserJoinedVoiceChannelFilter, page: Page, sort: Sort): [User] + userJoinedVoiceChannels(filter: UserJoinedVoiceChannelFilter, page: Page, sort: Sort): [UserJoinedVoiceChannel] + + userJoinedGameServerCount: Int + userJoinedGameServers(filter: UserJoinedGameServerFilter, page: Page, sort: Sort): [UserJoinedGameServer] userCount: Int users(filter: UserFilter, page: Page, sort: Sort): [User] diff --git a/kdb-bot/src/bot_graphql/model/user.gql b/kdb-bot/src/bot_graphql/model/user.gql index 07f7acc4..853b1ad4 100644 --- a/kdb-bot/src/bot_graphql/model/user.gql +++ b/kdb-bot/src/bot_graphql/model/user.gql @@ -3,6 +3,7 @@ type User implements TableQuery { discordId: String name: String xp: Int + minecraftId: String ontime: Float level: Level @@ -12,6 +13,9 @@ type User implements TableQuery { joinedVoiceChannels(filter: UserJoinedVoiceChannelFilter, page: Page, sort: Sort): [UserJoinedVoiceChannel] joinedVoiceChannelCount: Int + userJoinedGameServerCount: Int + userJoinedGameServers(filter: UserJoinedGameServerFilter, page: Page, sort: Sort): [UserJoinedGameServer] + server: Server createdAt: String @@ -23,6 +27,7 @@ input UserFilter { discordId: String name: String xp: Int + minecraftId: String ontime: Float level: LevelFilter server: ServerFilter diff --git a/kdb-bot/src/bot_graphql/model/userJoinedGameServer.gql b/kdb-bot/src/bot_graphql/model/userJoinedGameServer.gql new file mode 100644 index 00000000..63d66daa --- /dev/null +++ b/kdb-bot/src/bot_graphql/model/userJoinedGameServer.gql @@ -0,0 +1,28 @@ +type UserJoinedGameServer implements TableQuery { + id: ID + gameServer: String + user: User + joinedOn: String + leavedOn: String + + createdAt: String + modifiedAt: String +} + +input UserJoinedGameServerFilter { + id: ID + gameServer: String + user: UserFilter + joinedOn: String + leavedOn: String +} + +type UserJoinedGameServerMutation { + userJoined(input: UserJoinedGameServerInput!): UserJoinedGameServer + userLeaved(input: UserJoinedGameServerInput!): UserJoinedGameServer +} + +input UserJoinedGameServerInput { + gameServer: String! + userId: ID! +} \ No newline at end of file diff --git a/kdb-bot/src/bot_graphql/mutation.py b/kdb-bot/src/bot_graphql/mutation.py index 5b980c14..56bfa7c5 100644 --- a/kdb-bot/src/bot_graphql/mutation.py +++ b/kdb-bot/src/bot_graphql/mutation.py @@ -1,8 +1,10 @@ from ariadne import MutationType +from bot_data.model.user_joined_game_server import UserJoinedGameServer from bot_graphql.mutations.auto_role_mutation import AutoRoleMutation from bot_graphql.mutations.auto_role_rule_mutation import AutoRoleRuleMutation from bot_graphql.mutations.level_mutation import LevelMutation +from bot_graphql.mutations.user_joined_game_server_mutation import UserJoinedGameServerMutation from bot_graphql.mutations.user_mutation import UserMutation @@ -13,27 +15,12 @@ class Mutation(MutationType): auto_role_rule_mutation: AutoRoleRuleMutation, level_mutation: LevelMutation, user_mutation: UserMutation, + user_joined_game_server: UserJoinedGameServerMutation, ): MutationType.__init__(self) - self._auto_role_mutation = auto_role_mutation - self._auto_role_rule_mutation = auto_role_rule_mutation - self._level_mutation = level_mutation - self._user_mutation = user_mutation - - self.set_field("autoRole", self.resolve_auto_role) - self.set_field("autoRoleRule", self.resolve_auto_role_rule) - self.set_field("level", self.resolve_level) - self.set_field("user", self.resolve_user) - - def resolve_auto_role(self, *_): - return self._auto_role_mutation - - def resolve_auto_role_rule(self, *_): - return self._auto_role_rule_mutation - - def resolve_level(self, *_): - return self._level_mutation - - def resolve_user(self, *_): - return self._user_mutation + self.set_field("autoRole", lambda *_: auto_role_mutation) + self.set_field("autoRoleRule", lambda *_: auto_role_rule_mutation) + self.set_field("level", lambda *_: level_mutation) + self.set_field("user", lambda *_: user_mutation) + self.set_field("userJoinedGameServer", lambda *_: user_joined_game_server) diff --git a/kdb-bot/src/bot_graphql/mutations/user_joined_game_server_mutation.py b/kdb-bot/src/bot_graphql/mutations/user_joined_game_server_mutation.py new file mode 100644 index 00000000..d36487a9 --- /dev/null +++ b/kdb-bot/src/bot_graphql/mutations/user_joined_game_server_mutation.py @@ -0,0 +1,84 @@ +from datetime import datetime + +from cpl_core.database.context import DatabaseContextABC +from cpl_core.logging import LoggerABC +from cpl_discord.service import DiscordBotServiceABC + +from bot_data.abc.server_repository_abc import ServerRepositoryABC +from bot_data.abc.user_joined_game_server_repository_abc import UserJoinedGameServerRepositoryABC +from bot_data.abc.user_repository_abc import UserRepositoryABC +from bot_data.model.user_joined_game_server import UserJoinedGameServer +from bot_data.model.user_role_enum import UserRoleEnum +from bot_graphql.abc.query_abc import QueryABC +from modules.base.abc.base_helper_abc import BaseHelperABC +from modules.base.configuration.base_server_settings import BaseServerSettings +from modules.permission.service.permission_service import PermissionService + + +class UserJoinedGameServerMutation(QueryABC): + def __init__( + self, + logger: LoggerABC, + base_helper: BaseHelperABC, + servers: ServerRepositoryABC, + users: UserRepositoryABC, + user_joined_game_servers: UserJoinedGameServerRepositoryABC, + bot: DiscordBotServiceABC, + db: DatabaseContextABC, + permissions: PermissionService, + ): + QueryABC.__init__(self, "UserJoinedGameServerMutation") + + self._logger = logger + self._base_helper = base_helper + self._servers = servers + self._users = users + self._user_joined_game_servers = user_joined_game_servers + self._bot = bot + self._db = db + self._permissions = permissions + + self.set_field("userJoined", self.resolve_user_joined) + self.set_field("userLeaved", self.resolve_user_leaved) + + def resolve_user_joined(self, *_, input: dict): + user = self._users.get_user_by_id(input["userId"]) + self._can_user_mutate_data(user.server, UserRoleEnum.admin) + + active = self._user_joined_game_servers.find_active_user_joined_game_server_by_user_id(user.user_id) + if active is not None: + self._logger.debug( + __name__, + f"Skip UserJoinedGameServer for user {user.user_id}. User already plays on {active.game_server}.", + ) + return + + new = UserJoinedGameServer(user, input["gameServer"], datetime.now()) + self._user_joined_game_servers.add_user_joined_game_server(new) + self._db.save_changes() + + return self._user_joined_game_servers.get_active_user_joined_game_server_by_user_id(user.user_id) + + def resolve_user_leaved(self, *_, input: dict): + user = self._users.get_user_by_id(input["userId"]) + self._can_user_mutate_data(user.server, UserRoleEnum.admin) + + active = self._user_joined_game_servers.find_active_user_joined_game_server_by_user_id(user.user_id) + if active is None: + return None + active.leaved_on = datetime.now() + + settings: BaseServerSettings = self._base_helper.get_config(user.server.discord_server_id) + + ontime = round((active.leaved_on - active.joined_on).total_seconds() / 3600, 2) + old_xp = user.xp + user.xp += round(ontime * settings.xp_per_ontime_hour) + + self._user_joined_game_servers.update_user_joined_game_server(active) + self._db.save_changes() + self._logger.debug( + __name__, + f"User {user} leaved_on {active.leaved_on}. Ontime: {ontime}h | xp: from {old_xp} to {user.xp}", + ) + + return active diff --git a/kdb-bot/src/bot_graphql/queries/user_joined_game_server_query.py b/kdb-bot/src/bot_graphql/queries/user_joined_game_server_query.py new file mode 100644 index 00000000..877fa8b7 --- /dev/null +++ b/kdb-bot/src/bot_graphql/queries/user_joined_game_server_query.py @@ -0,0 +1,37 @@ +from cpl_discord.service import DiscordBotServiceABC + +from bot_data.model.user_joined_game_server import UserJoinedGameServer +from bot_graphql.abc.data_query_abc import DataQueryABC + + +class UserJoinedGameServerQuery(DataQueryABC): + def __init__(self, bot: DiscordBotServiceABC): + DataQueryABC.__init__(self, "UserJoinedGameServer") + + self._bot = bot + + self.set_field("id", self.resolve_id) + self.set_field("gameServer", self.resolve_game_server) + self.set_field("user", self.resolve_user) + self.set_field("joinedOn", self.resolve_joined_on) + self.set_field("leavedOn", self.resolve_leaved_on) + + @staticmethod + def resolve_id(x: UserJoinedGameServer, *_): + return x.id + + @staticmethod + def resolve_game_server(x: UserJoinedGameServer, *_): + return x.game_server + + @staticmethod + def resolve_user(x: UserJoinedGameServer, *_): + return x.user + + @staticmethod + def resolve_joined_on(x: UserJoinedGameServer, *_): + return x.joined_on + + @staticmethod + def resolve_leaved_on(x: UserJoinedGameServer, *_): + return x.leaved_on diff --git a/kdb-bot/src/bot_graphql/queries/user_query.py b/kdb-bot/src/bot_graphql/queries/user_query.py index eca30928..ddcda7b9 100644 --- a/kdb-bot/src/bot_graphql/queries/user_query.py +++ b/kdb-bot/src/bot_graphql/queries/user_query.py @@ -1,10 +1,12 @@ from cpl_discord.service import DiscordBotServiceABC from bot_core.abc.client_utils_abc import ClientUtilsABC +from bot_data.abc.user_joined_game_server_repository_abc import UserJoinedGameServerRepositoryABC from bot_data.abc.user_joined_server_repository_abc import UserJoinedServerRepositoryABC from bot_data.abc.user_joined_voice_channel_repository_abc import UserJoinedVoiceChannelRepositoryABC from bot_data.model.user import User from bot_graphql.abc.data_query_abc import DataQueryABC +from bot_graphql.filter.user_joined_game_server_filter import UserJoinedGameServerFilter from bot_graphql.filter.user_joined_server_filter import UserJoinedServerFilter from bot_graphql.filter.user_joined_voice_channel_filter import UserJoinedVoiceChannelFilter from modules.level.service.level_service import LevelService @@ -18,12 +20,14 @@ class UserQuery(DataQueryABC): client_utils: ClientUtilsABC, ujs: UserJoinedServerRepositoryABC, ujvs: UserJoinedVoiceChannelRepositoryABC, + user_joined_game_server: UserJoinedGameServerRepositoryABC, ): DataQueryABC.__init__(self, "User") self._bot = bot self._levels = levels self._client_utils = client_utils + self._user_joined_game_server = user_joined_game_server self._ujs = ujs self._ujvs = ujvs @@ -31,6 +35,7 @@ class UserQuery(DataQueryABC): self.set_field("discordId", self.resolve_discord_id) self.set_field("name", self.resolve_name) self.set_field("xp", self.resolve_xp) + self.set_field("minecraftId", self.resolve_minecraft_id) self.set_field("ontime", self.resolve_ontime) self.set_field("level", self.resolve_level) self.add_collection( @@ -43,6 +48,11 @@ class UserQuery(DataQueryABC): lambda user, *_: self._ujvs.get_user_joined_voice_channels_by_user_id(user.user_id), UserJoinedVoiceChannelFilter, ) + self.add_collection( + "userJoinedGameServer", + lambda user, *_: self._user_joined_game_server.get_user_joined_game_servers_by_user_id(user.user_id), + UserJoinedGameServerFilter, + ) self.set_field("server", self.resolve_server) @staticmethod @@ -62,6 +72,10 @@ class UserQuery(DataQueryABC): def resolve_xp(user: User, *_): return user.xp + @staticmethod + def resolve_minecraft_id(user: User, *_): + return user.minecraft_id + def resolve_ontime(self, user: User, *_): return self._client_utils.get_ontime_for_user(user) diff --git a/kdb-bot/src/bot_graphql/query.py b/kdb-bot/src/bot_graphql/query.py index aa5948d9..252a416b 100644 --- a/kdb-bot/src/bot_graphql/query.py +++ b/kdb-bot/src/bot_graphql/query.py @@ -3,6 +3,7 @@ from bot_data.abc.client_repository_abc import ClientRepositoryABC from bot_data.abc.known_user_repository_abc import KnownUserRepositoryABC from bot_data.abc.level_repository_abc import LevelRepositoryABC from bot_data.abc.server_repository_abc import ServerRepositoryABC +from bot_data.abc.user_joined_game_server_repository_abc import UserJoinedGameServerRepositoryABC from bot_data.abc.user_joined_server_repository_abc import UserJoinedServerRepositoryABC from bot_data.abc.user_joined_voice_channel_repository_abc import UserJoinedVoiceChannelRepositoryABC from bot_data.abc.user_repository_abc import UserRepositoryABC @@ -13,6 +14,7 @@ from bot_graphql.filter.client_filter import ClientFilter from bot_graphql.filter.level_filter import LevelFilter from bot_graphql.filter.server_filter import ServerFilter from bot_graphql.filter.user_filter import UserFilter +from bot_graphql.filter.user_joined_game_server_filter import UserJoinedGameServerFilter from bot_graphql.filter.user_joined_server_filter import UserJoinedServerFilter from bot_graphql.filter.user_joined_voice_channel_filter import UserJoinedVoiceChannelFilter @@ -27,6 +29,7 @@ class Query(QueryABC): servers: ServerRepositoryABC, user_joined_servers: UserJoinedServerRepositoryABC, user_joined_voice_channel: UserJoinedVoiceChannelRepositoryABC, + user_joined_game_server: UserJoinedGameServerRepositoryABC, users: UserRepositoryABC, ): QueryABC.__init__(self, "Query") @@ -37,6 +40,7 @@ class Query(QueryABC): self._servers = servers self._user_joined_servers = user_joined_servers self._user_joined_voice_channels = user_joined_voice_channel + self._user_joined_game_server = user_joined_game_server self._users = users self.add_collection("autoRole", lambda *_: self._auto_roles.get_auto_roles(), AutoRoleFilter) @@ -53,4 +57,9 @@ class Query(QueryABC): lambda *_: self._user_joined_voice_channels.get_user_joined_voice_channels(), UserJoinedVoiceChannelFilter, ) + self.add_collection( + "userJoinedGameServer", + lambda *_: self._user_joined_game_server.get_user_joined_game_servers(), + UserJoinedGameServerFilter, + ) self.add_collection("user", lambda *_: self._users.get_users(), UserFilter) diff --git a/kdb-bot/src/modules/base/base_module.py b/kdb-bot/src/modules/base/base_module.py index 9377c6e9..492a5ee4 100644 --- a/kdb-bot/src/modules/base/base_module.py +++ b/kdb-bot/src/modules/base/base_module.py @@ -14,6 +14,8 @@ from modules.base.command.mass_move_command import MassMoveCommand from modules.base.command.ping_command import PingCommand from modules.base.command.presence_command import PresenceCommand from modules.base.command.purge_command import PurgeCommand +from modules.base.command.register_group import RegisterGroup +from modules.base.command.unregister_group import UnregisterGroup from modules.base.command.user_group import UserGroup from modules.base.events.base_on_command_error_event import BaseOnCommandErrorEvent from modules.base.events.base_on_command_event import BaseOnCommandEvent @@ -23,14 +25,19 @@ from modules.base.events.base_on_message_delete_event import BaseOnMessageDelete from modules.base.events.base_on_message_event import BaseOnMessageEvent from modules.base.events.base_on_raw_reaction_add import BaseOnRawReactionAddEvent from modules.base.events.base_on_raw_reaction_remove import BaseOnRawReactionRemoveEvent +from modules.base.events.base_on_scheduled_event_update_event import BaseOnScheduledEventUpdateEvent from modules.base.events.base_on_voice_state_update_event import ( BaseOnVoiceStateUpdateEvent, ) from modules.base.events.base_on_voice_state_update_event_help_channel import ( BaseOnVoiceStateUpdateEventHelpChannel, ) +from modules.base.events.base_on_voice_state_update_event_scheduled_event_bonus import ( + BaseOnVoiceStateUpdateEventScheduledEventBonus, +) from modules.base.helper.base_reaction_handler import BaseReactionHandler from modules.base.service.base_helper_service import BaseHelperService +from modules.base.service.event_service import EventService class BaseModule(ModuleABC): @@ -43,6 +50,8 @@ class BaseModule(ModuleABC): def configure_services(self, services: ServiceCollectionABC, env: ApplicationEnvironmentABC): services.add_transient(BaseHelperABC, BaseHelperService) services.add_transient(BaseReactionHandler) + services.add_singleton(EventService) + # commands self._dc.add_command(AFKCommand) self._dc.add_command(HelpCommand) @@ -53,6 +62,8 @@ class BaseModule(ModuleABC): self._dc.add_command(PurgeCommand) self._dc.add_command(UserGroup) + self._dc.add_command(RegisterGroup) + self._dc.add_command(UnregisterGroup) # events self._dc.add_event(DiscordEventTypesEnum.on_command.value, BaseOnCommandEvent) self._dc.add_event(DiscordEventTypesEnum.on_command_error.value, BaseOnCommandErrorEvent) @@ -73,3 +84,11 @@ class BaseModule(ModuleABC): DiscordEventTypesEnum.on_voice_state_update.value, BaseOnVoiceStateUpdateEventHelpChannel, ) + self._dc.add_event( + DiscordEventTypesEnum.on_voice_state_update.value, + BaseOnVoiceStateUpdateEventScheduledEventBonus, + ) + self._dc.add_event( + DiscordEventTypesEnum.on_scheduled_event_update.value, + BaseOnScheduledEventUpdateEvent, + ) diff --git a/kdb-bot/src/modules/base/command/register_group.py b/kdb-bot/src/modules/base/command/register_group.py new file mode 100644 index 00000000..b47329cb --- /dev/null +++ b/kdb-bot/src/modules/base/command/register_group.py @@ -0,0 +1,85 @@ +import discord +import requests +from cpl_core.database.context import DatabaseContextABC +from cpl_discord.command import DiscordCommandABC +from cpl_discord.service import DiscordBotServiceABC +from cpl_translation import TranslatePipe +from discord.ext import commands +from discord.ext.commands import Context + +from bot_core.abc.client_utils_abc import ClientUtilsABC +from bot_core.abc.message_service_abc import MessageServiceABC +from bot_core.helper.command_checks import CommandChecks +from bot_core.logging.command_logger import CommandLogger +from bot_data.abc.server_repository_abc import ServerRepositoryABC +from bot_data.abc.user_repository_abc import UserRepositoryABC + + +class RegisterGroup(DiscordCommandABC): + def __init__( + self, + logger: CommandLogger, + message_service: MessageServiceABC, + bot: DiscordBotServiceABC, + client_utils: ClientUtilsABC, + servers: ServerRepositoryABC, + users: UserRepositoryABC, + db: DatabaseContextABC, + t: TranslatePipe, + ): + DiscordCommandABC.__init__(self) + + self._logger = logger + self._message_service = message_service + self._bot = bot + self._client_utils = client_utils + self._servers = servers + self._users = users + self._db = db + self._t = t + + self._logger.trace(__name__, f"Loaded command service: {type(self).__name__}") + + @commands.hybrid_group() + @commands.guild_only() + async def register(self, ctx: Context): + pass + + @register.command() + @commands.guild_only() + @CommandChecks.check_is_ready() + @CommandChecks.check_is_member_moderator() + async def minecraft(self, ctx: Context, member: discord.Member, name: str): + self._logger.debug(__name__, f"Received command register minecraft {ctx}") + + minecraft_id = None + try: + self._logger.debug(__name__, f"Try to get minecraft id for {name}") + response = requests.get(url=f"https://api.mojang.com/users/profiles/minecraft/{name}") + if len(response.content) == 0: + await self._message_service.send_interaction_msg( + ctx.interaction, self._t.transform("modules.base.register.not_found") + ) + return + + minecraft_id = response.json()["id"] + except Exception as e: + self._logger.error(__name__, f"Get minecraft id for {name} failed", e) + await self._message_service.send_interaction_msg( + ctx.interaction, self._t.transform("modules.base.register.not_found") + ) + + if minecraft_id is None: + return + + server = self._servers.get_server_by_discord_id(ctx.guild.id) + user = self._users.get_user_by_discord_id_and_server_id(member.id, server.server_id) + user.minecraft_id = minecraft_id + self._users.update_user(user) + self._db.save_changes() + + await self._message_service.send_interaction_msg( + ctx.interaction, self._t.transform("modules.base.register.success") + ) + + self._logger.trace(__name__, f"Finished register minecraft command") diff --git a/kdb-bot/src/modules/base/command/unregister_group.py b/kdb-bot/src/modules/base/command/unregister_group.py new file mode 100644 index 00000000..c929f934 --- /dev/null +++ b/kdb-bot/src/modules/base/command/unregister_group.py @@ -0,0 +1,64 @@ +import discord +from cpl_core.database.context import DatabaseContextABC +from cpl_discord.command import DiscordCommandABC +from cpl_discord.service import DiscordBotServiceABC +from cpl_translation import TranslatePipe +from discord.ext import commands +from discord.ext.commands import Context + +from bot_core.abc.client_utils_abc import ClientUtilsABC +from bot_core.abc.message_service_abc import MessageServiceABC +from bot_core.helper.command_checks import CommandChecks +from bot_core.logging.command_logger import CommandLogger +from bot_data.abc.server_repository_abc import ServerRepositoryABC +from bot_data.abc.user_repository_abc import UserRepositoryABC + + +class UnregisterGroup(DiscordCommandABC): + def __init__( + self, + logger: CommandLogger, + message_service: MessageServiceABC, + bot: DiscordBotServiceABC, + client_utils: ClientUtilsABC, + servers: ServerRepositoryABC, + users: UserRepositoryABC, + db: DatabaseContextABC, + t: TranslatePipe, + ): + DiscordCommandABC.__init__(self) + + self._logger = logger + self._message_service = message_service + self._bot = bot + self._client_utils = client_utils + self._servers = servers + self._users = users + self._db = db + self._t = t + + self._logger.trace(__name__, f"Loaded command service: {type(self).__name__}") + + @commands.hybrid_group() + @commands.guild_only() + async def unregister(self, ctx: Context): + pass + + @unregister.command() + @commands.guild_only() + @CommandChecks.check_is_ready() + @CommandChecks.check_is_member_moderator() + async def minecraft(self, ctx: Context, member: discord.Member): + self._logger.debug(__name__, f"Received command register minecraft {ctx}") + + server = self._servers.get_server_by_discord_id(ctx.guild.id) + user = self._users.get_user_by_discord_id_and_server_id(member.id, server.server_id) + user.minecraft_id = None + self._users.update_user(user) + self._db.save_changes() + + await self._message_service.send_interaction_msg( + ctx.interaction, self._t.transform("modules.base.unregister.success") + ) + + self._logger.trace(__name__, f"Finished register minecraft command") diff --git a/kdb-bot/src/modules/base/configuration/base_server_settings.py b/kdb-bot/src/modules/base/configuration/base_server_settings.py index 531e44c9..c6a7e9c4 100644 --- a/kdb-bot/src/modules/base/configuration/base_server_settings.py +++ b/kdb-bot/src/modules/base/configuration/base_server_settings.py @@ -15,6 +15,7 @@ class BaseServerSettings(ConfigurationModelABC): self._xp_per_reaction: int = 0 self._max_message_xp_per_hour: int = 0 self._xp_per_ontime_hour: int = 0 + self._xp_per_event_participation: int = 0 self._afk_channel_ids: List[int] = List(int) self._afk_command_channel_id: int = 0 self._help_command_reference_url: str = "" @@ -45,6 +46,10 @@ class BaseServerSettings(ConfigurationModelABC): def xp_per_ontime_hour(self) -> int: return self._xp_per_ontime_hour + @property + def xp_per_event_participation(self) -> int: + return self._xp_per_event_participation + @property def afk_channel_ids(self) -> List[int]: return self._afk_channel_ids @@ -73,6 +78,9 @@ class BaseServerSettings(ConfigurationModelABC): self._xp_per_reaction = int(settings["XpPerReaction"]) self._max_message_xp_per_hour = int(settings["MaxMessageXpPerHour"]) self._xp_per_ontime_hour = int(settings["XpPerOntimeHour"]) + self._xp_per_event_participation = ( + 0 if "XpPerEventParticipation" not in settings else settings["XpPerEventParticipation"] + ) for index in settings["AFKChannelIds"]: self._afk_channel_ids.append(int(index)) self._afk_command_channel_id = settings["AFKCommandChannelId"] diff --git a/kdb-bot/src/modules/base/events/base_on_scheduled_event_update_event.py b/kdb-bot/src/modules/base/events/base_on_scheduled_event_update_event.py new file mode 100644 index 00000000..5b9cf901 --- /dev/null +++ b/kdb-bot/src/modules/base/events/base_on_scheduled_event_update_event.py @@ -0,0 +1,39 @@ +import discord +from cpl_core.logging import LoggerABC +from cpl_discord.events.on_scheduled_event_update_abc import OnScheduledEventUpdateABC +from cpl_discord.service import DiscordBotServiceABC +from discord import EventStatus + +from modules.base.model.active_event import ActiveEvent +from modules.base.service.event_service import EventService + + +class BaseOnScheduledEventUpdateEvent(OnScheduledEventUpdateABC): + def __init__( + self, + logger: LoggerABC, + bot: DiscordBotServiceABC, + events: EventService, + ): + OnScheduledEventUpdateABC.__init__(self) + + self._logger = logger + self._bot = bot + self._events = events + + async def on_scheduled_event_update(self, before: discord.ScheduledEvent, after: discord.ScheduledEvent): + self._logger.debug(__name__, f"Module {type(self)} started") + + # save started event + if before.status != after.status and after.status == EventStatus.active: + self._events.add_event(ActiveEvent(after)) + # delete stopped event + if before.status != after.status and ( + after.status.value == EventStatus.cancelled.value or after.status.value == EventStatus.completed.value + ): + event = self._events.get_active_event(after) + if event is None: + return + self._events.remove_event(event) + + self._logger.debug(__name__, f"Module {type(self)} stopped") diff --git a/kdb-bot/src/modules/base/events/base_on_voice_state_update_event_scheduled_event_bonus.py b/kdb-bot/src/modules/base/events/base_on_voice_state_update_event_scheduled_event_bonus.py new file mode 100644 index 00000000..54050851 --- /dev/null +++ b/kdb-bot/src/modules/base/events/base_on_voice_state_update_event_scheduled_event_bonus.py @@ -0,0 +1,66 @@ +import discord +from cpl_core.configuration import ConfigurationABC +from cpl_core.database.context import DatabaseContextABC +from cpl_core.logging import LoggerABC +from cpl_discord.events import OnVoiceStateUpdateABC + +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.base.abc.base_helper_abc import BaseHelperABC +from modules.base.configuration.base_server_settings import BaseServerSettings +from modules.base.service.event_service import EventService + + +class BaseOnVoiceStateUpdateEventScheduledEventBonus(OnVoiceStateUpdateABC): + def __init__( + self, + config: ConfigurationABC, + logger: LoggerABC, + base_helper: BaseHelperABC, + servers: ServerRepositoryABC, + users: UserRepositoryABC, + events: EventService, + db: DatabaseContextABC, + ): + OnVoiceStateUpdateABC.__init__(self) + self._config = config + self._logger = logger + self._base_helper = base_helper + self._servers = servers + self._users = users + self._events = events + self._db = db + + self._logger.info(__name__, f"Module {type(self)} loaded") + + @EventChecks.check_is_ready() + async def on_voice_state_update( + self, + member: discord.Member, + before: discord.VoiceState, + after: discord.VoiceState, + ): + self._logger.debug(__name__, f"Module {type(self)} started") + if member.bot or after.channel is None: + self._logger.debug(__name__, f"Module {type(self)} stopped") + return + + active_event = self._events.get_active_event_by_channel_id(after.channel.id) + if active_event is None: + self._logger.debug(__name__, f"Module {type(self)} stopped") + return + + server = self._servers.get_server_by_discord_id(member.guild.id) + user = self._users.get_user_by_discord_id_and_server_id(member.id, server.server_id) + if active_event.participants.any(lambda x: x.user_id == user.user_id): + self._logger.debug(__name__, f"Module {type(self)} stopped") + return + + settings: BaseServerSettings = self._base_helper.get_config(server.discord_server_id) + user.xp += settings.xp_per_event_participation + self._users.update_user(user) + self._db.save_changes() + active_event.participants.append(user) + + self._logger.debug(__name__, f"Module {type(self)} stopped") diff --git a/kdb-bot/src/modules/base/model/__init__.py b/kdb-bot/src/modules/base/model/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/kdb-bot/src/modules/base/model/active_event.py b/kdb-bot/src/modules/base/model/active_event.py new file mode 100644 index 00000000..63861e0c --- /dev/null +++ b/kdb-bot/src/modules/base/model/active_event.py @@ -0,0 +1,18 @@ +import discord +from cpl_query.extension import List + +from bot_data.model.user import User + + +class ActiveEvent: + def __init__(self, event: discord.ScheduledEvent): + self._event = event + self._participants = List(User) + + @property + def event(self) -> discord.ScheduledEvent: + return self._event + + @property + def participants(self) -> List[User]: + return self._participants diff --git a/kdb-bot/src/modules/base/service/event_service.py b/kdb-bot/src/modules/base/service/event_service.py new file mode 100644 index 00000000..8073b3d3 --- /dev/null +++ b/kdb-bot/src/modules/base/service/event_service.py @@ -0,0 +1,31 @@ +from typing import Optional + +import discord +from cpl_query.extension import List + +from modules.base.model.active_event import ActiveEvent + + +class EventService: + def __init__(self): + self._active_events = List(ActiveEvent) + + def add_event(self, event: ActiveEvent): + if self._active_events.contains(event): + return + + self._active_events.add(event) + + def get_active_event(self, event: discord.ScheduledEvent) -> Optional[ActiveEvent]: + return self._active_events.where(lambda x: x.event.id == event.id).single_or_default() + + def get_active_event_by_channel_id(self, channel_id: int) -> Optional[ActiveEvent]: + return self._active_events.where( + lambda x: x.event.channel is not None and x.event.channel.id == channel_id + ).single_or_default() + + def remove_event(self, event: ActiveEvent): + if not self._active_events.contains(event): + return + + self._active_events.remove(event) diff --git a/kdb-bot/src/modules/database/database_on_ready_event.py b/kdb-bot/src/modules/database/database_on_ready_event.py index 08aa8177..72f35442 100644 --- a/kdb-bot/src/modules/database/database_on_ready_event.py +++ b/kdb-bot/src/modules/database/database_on_ready_event.py @@ -11,6 +11,7 @@ from bot_core.logging.database_logger import DatabaseLogger from bot_core.pipes.date_time_offset_pipe import DateTimeOffsetPipe from bot_data.abc.client_repository_abc import ClientRepositoryABC from bot_data.abc.known_user_repository_abc import KnownUserRepositoryABC +from bot_data.abc.user_joined_game_server_repository_abc import UserJoinedGameServerRepositoryABC from bot_data.abc.user_joined_server_repository_abc import UserJoinedServerRepositoryABC from bot_data.abc.user_joined_voice_channel_repository_abc import ( UserJoinedVoiceChannelRepositoryABC, @@ -41,6 +42,7 @@ class DatabaseOnReadyEvent(OnReadyABC): known_users: KnownUserRepositoryABC, user_joins: UserJoinedServerRepositoryABC, user_joins_vc: UserJoinedVoiceChannelRepositoryABC, + user_joined_gs: UserJoinedGameServerRepositoryABC, dtp: DateTimeOffsetPipe, ): self._config = config @@ -55,6 +57,7 @@ class DatabaseOnReadyEvent(OnReadyABC): self._known_users = known_users self._user_joins = user_joins self._user_joins_vc = user_joins_vc + self._user_joined_gs = user_joined_gs self._dtp = dtp OnReadyABC.__init__(self) @@ -327,6 +330,48 @@ class DatabaseOnReadyEvent(OnReadyABC): except Exception as e: self._logger.error(__name__, f"Cannot get UserJoinedVoiceChannel", e) + def _check_user_joined_gs(self): + self._logger.debug(__name__, f"Start checking UserJoinedVoiceChannel table") + for guild in self._bot.guilds: + guild: discord.Guild = guild + + server = self._servers.find_server_by_discord_id(guild.id) + if server is None: + self._logger.fatal(__name__, f"Server not found in database: {guild.id}") + + try: + for member in guild.members: + if member.bot: + self._logger.trace(__name__, f"User {member.id} is ignored, because its a bot") + continue + + user = self._users.find_user_by_discord_id_and_server_id(member.id, server.server_id) + if user is None: + self._logger.fatal(__name__, f"User not found in database: {member.id}") + + joins = self._user_joined_gs.find_active_user_joined_game_servers_by_user_id(user.user_id) + if joins is None or len(joins) == 0: + continue + + for join in joins: + self._logger.warn( + __name__, + f"Active UserJoinedGameServer found in database: {guild.id}:{member.id}@{join.joined_on}", + ) + join.leaved_on = datetime.now() + settings: BaseServerSettings = self._config.get_configuration(f"BaseServerSettings_{guild.id}") + + if ( + (join.leaved_on - join.joined_on).total_seconds() / 60 / 60 + ) > settings.max_voice_state_hours: + join.leaved_on = join.joined_on + timedelta(hours=settings.max_voice_state_hours) + + self._user_joined_gs.update_user_joined_game_server(join) + # todo: maybe add XP + self._db_context.save_changes() + except Exception as e: + self._logger.error(__name__, f"Cannot get UserJoinedVoiceChannel", e) + async def on_ready(self): self._logger.debug(__name__, f"Module {type(self)} started") @@ -338,6 +383,7 @@ class DatabaseOnReadyEvent(OnReadyABC): self._check_users() self._check_user_joins() self._check_user_joins_vc() + self._check_user_joined_gs() self._validate_init_time() self._logger.trace(__name__, f"Module {type(self)} stopped")