Added realm management #481

This commit is contained in:
Sven Heidemann 2025-01-07 16:40:06 +01:00
parent 3abd145ce2
commit 405055edf5
22 changed files with 1254 additions and 4 deletions

View File

@ -15,7 +15,7 @@ __title__ = "bot"
__author__ = "Sven Heidemann" __author__ = "Sven Heidemann"
__license__ = "MIT" __license__ = "MIT"
__copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de" __copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de"
__version__ = "1.2.8" __version__ = "1.2.9"
from collections import namedtuple from collections import namedtuple
@ -23,4 +23,4 @@ from collections import namedtuple
# imports: # imports:
VersionInfo = namedtuple("VersionInfo", "major minor micro") VersionInfo = namedtuple("VersionInfo", "major minor micro")
version_info = VersionInfo(major="1", minor="2", micro="8") version_info = VersionInfo(major="1", minor="2", micro="9")

View File

@ -4,7 +4,7 @@
"Version": { "Version": {
"Major": "1", "Major": "1",
"Minor": "2", "Minor": "2",
"Micro": "8" "Micro": "9"
}, },
"Author": "Sven Heidemann", "Author": "Sven Heidemann",
"AuthorEmail": "sven.heidemann@sh-edraft.de", "AuthorEmail": "sven.heidemann@sh-edraft.de",
@ -71,6 +71,7 @@
"../modules/config/config.json", "../modules/config/config.json",
"../modules/database/database.json", "../modules/database/database.json",
"../modules/level/level.json", "../modules/level/level.json",
"../modules/realms/realms.json",
"../modules/short_role_name/short-role-name.json", "../modules/short_role_name/short-role-name.json",
"../modules/special_offers/special-offers.json", "../modules/special_offers/special-offers.json",
"../modules/technician/technician.json" "../modules/technician/technician.json"

@ -1 +1 @@
Subproject commit eeebd13f80c6ceecc922ede5771e55212a884019 Subproject commit 40df176141b233f73a9f702b1e8a598ed305dce1

View File

@ -12,6 +12,7 @@ from modules.boot_log.boot_log_module import BootLogModule
from modules.config.config_module import ConfigModule from modules.config.config_module import ConfigModule
from modules.database.database_module import DatabaseModule from modules.database.database_module import DatabaseModule
from modules.level.level_module import LevelModule from modules.level.level_module import LevelModule
from modules.realms.reams_module import RealmsModule
from modules.short_role_name.short_role_name_module import ShortRoleNameModule from modules.short_role_name.short_role_name_module import ShortRoleNameModule
from modules.special_offers.special_offers_module import SteamSpecialOffersModule from modules.special_offers.special_offers_module import SteamSpecialOffersModule
from modules.technician.technician_module import TechnicianModule from modules.technician.technician_module import TechnicianModule
@ -37,6 +38,7 @@ class ModuleList:
AchievementsModule, AchievementsModule,
ShortRoleNameModule, ShortRoleNameModule,
SteamSpecialOffersModule, SteamSpecialOffersModule,
RealmsModule,
# has to be last! # has to be last!
BootLogModule, BootLogModule,
CoreExtensionModule, CoreExtensionModule,

View File

@ -95,6 +95,56 @@
} }
}, },
"modules": { "modules": {
"realm": {
"list": {
"title": "Level:",
"description": "Konfigurierte Level:",
"realm_names": "Realm-Namen",
"error": {
"nothing_found": "Keine Einträge gefunden."
}
},
"add": {
"error": {
"role_exists": "Die Rolle {} existiert bereits.",
"category_exists": "Die Kategorie {} existiert bereits.",
"realm_exists": "Das Realm {} existiert bereits.",
"realm_create": "Fehler beim Erstellen des Realms {}."
},
"success": "Das Realm {} wurde erfolgreich erstellt."
},
"edit": {
"error": {
"realm_not_found": "Das Realm {} wurde nicht gefunden."
},
"success": "Das Realm {} wurde erfolgreich in {} umbenannt."
},
"error": {
"not_moderator": "Du bist kein Moderator des Realms {}."
},
"delete": {
"error": {
"realm_not_found": "Das Realm {} wurde nicht gefunden."
},
"success": "Das Realm {} wurde erfolgreich gelöscht."
},
"member": {
"add": {
"success": "{} wurde erfolgreich zum Realm {} hinzugefügt."
},
"remove": {
"success": "{} wurde erfolgreich aus dem Realm {} entfernt."
}
},
"moderator": {
"add": {
"success": "{} wurde erfolgreich zum Moderator des Realms {} hinzugefügt."
},
"remove": {
"success": "{} wurde erfolgreich als Moderator des Realms {} entfernt."
}
}
},
"special_offers": { "special_offers": {
"price": "Preis", "price": "Preis",
"discount": "Rabatt", "discount": "Rabatt",

View File

@ -17,6 +17,7 @@ class FeatureFlagsEnum(Enum):
moderator_module = "ModeratorModule" moderator_module = "ModeratorModule"
short_role_name_module = "ShortRoleNameModule" short_role_name_module = "ShortRoleNameModule"
steam_special_offers_module = "SteamSpecialOffersModule" steam_special_offers_module = "SteamSpecialOffersModule"
realm_module = "RealmModule"
# features # features
api_only = "ApiOnly" api_only = "ApiOnly"
presence = "Presence" presence = "Presence"

View File

@ -19,6 +19,7 @@ class FeatureFlagsSettings(ConfigurationModelABC):
FeatureFlagsEnum.config_module.value: True, # 19.07.2023 #127 FeatureFlagsEnum.config_module.value: True, # 19.07.2023 #127
FeatureFlagsEnum.short_role_name_module.value: True, # 28.09.2023 #378 FeatureFlagsEnum.short_role_name_module.value: True, # 28.09.2023 #378
FeatureFlagsEnum.steam_special_offers_module.value: True, # 11.10.2023 #188 FeatureFlagsEnum.steam_special_offers_module.value: True, # 11.10.2023 #188
FeatureFlagsEnum.realm_module.value: True, # 07.01.2025 #481
# features # features
FeatureFlagsEnum.api_only.value: False, # 13.10.2022 #70 FeatureFlagsEnum.api_only.value: False, # 13.10.2022 #70
FeatureFlagsEnum.presence.value: True, # 03.10.2022 #56 FeatureFlagsEnum.presence.value: True, # 03.10.2022 #56

View File

@ -0,0 +1,69 @@
from abc import ABC, abstractmethod
from typing import Optional
from cpl_query.extension import List
from bot_data.model.realm import Realm
from bot_data.model.realm_moderator import RealmModerator
class RealmRepositoryABC(ABC):
@abstractmethod
def __init__(self):
pass
@abstractmethod
def get_all(self) -> List[Realm]:
pass
@abstractmethod
def get_realm_by_id(self, id: int) -> Realm:
pass
@abstractmethod
def find_realm_by_id(self, id: int) -> Optional[Realm]:
pass
@abstractmethod
def find_realm_by_name(self, name: str) -> Optional[Realm]:
pass
@abstractmethod
def get_realms_by_server_id(self, id: int) -> List[Realm]:
pass
@abstractmethod
def add_realm(self, realm: Realm):
pass
@abstractmethod
def update_realm(self, realm: Realm):
pass
@abstractmethod
def delete_realm(self, realm: Realm):
pass
@abstractmethod
def get_realm_moderators(self) -> List[RealmModerator]:
pass
@abstractmethod
def get_realm_moderator_by_id(self, id: int) -> RealmModerator:
pass
@abstractmethod
def get_realm_moderators_by_realm_id(self, id: int) -> List[RealmModerator]:
pass
@abstractmethod
def add_realm_moderator(self, realm: RealmModerator):
pass
@abstractmethod
def update_realm_moderator(self, realm: RealmModerator):
pass
@abstractmethod
def delete_realm_moderator(self, realm: RealmModerator):
pass

View File

@ -14,6 +14,7 @@ from bot_data.abc.data_seeder_abc import DataSeederABC
from bot_data.abc.game_server_repository_abc import GameServerRepositoryABC from bot_data.abc.game_server_repository_abc import GameServerRepositoryABC
from bot_data.abc.known_user_repository_abc import KnownUserRepositoryABC from bot_data.abc.known_user_repository_abc import KnownUserRepositoryABC
from bot_data.abc.level_repository_abc import LevelRepositoryABC from bot_data.abc.level_repository_abc import LevelRepositoryABC
from bot_data.abc.realm_repository_abc import RealmRepositoryABC
from bot_data.abc.scheduled_event_repository_abc import ScheduledEventRepositoryABC from bot_data.abc.scheduled_event_repository_abc import ScheduledEventRepositoryABC
from bot_data.abc.server_config_repository_abc import ServerConfigRepositoryABC from bot_data.abc.server_config_repository_abc import ServerConfigRepositoryABC
from bot_data.abc.server_repository_abc import ServerRepositoryABC from bot_data.abc.server_repository_abc import ServerRepositoryABC
@ -46,6 +47,7 @@ from bot_data.service.client_repository_service import ClientRepositoryService
from bot_data.service.game_server_repository_service import GameServerRepositoryService from bot_data.service.game_server_repository_service import GameServerRepositoryService
from bot_data.service.known_user_repository_service import KnownUserRepositoryService from bot_data.service.known_user_repository_service import KnownUserRepositoryService
from bot_data.service.level_repository_service import LevelRepositoryService from bot_data.service.level_repository_service import LevelRepositoryService
from bot_data.service.realm_repository_service import RealmRepositoryService
from bot_data.service.scheduled_event_repository_service import ScheduledEventRepositoryService from bot_data.service.scheduled_event_repository_service import ScheduledEventRepositoryService
from bot_data.service.seeder_service import SeederService from bot_data.service.seeder_service import SeederService
from bot_data.service.server_config_repository_service import ( from bot_data.service.server_config_repository_service import (
@ -118,6 +120,7 @@ class DataModule(ModuleABC):
services.add_transient(ShortRoleNameRepositoryABC, ShortRoleNameRepositoryService) services.add_transient(ShortRoleNameRepositoryABC, ShortRoleNameRepositoryService)
services.add_transient(SteamSpecialOfferRepositoryABC, SteamSpecialOfferRepositoryService) services.add_transient(SteamSpecialOfferRepositoryABC, SteamSpecialOfferRepositoryService)
services.add_transient(ScheduledEventRepositoryABC, ScheduledEventRepositoryService) services.add_transient(ScheduledEventRepositoryABC, ScheduledEventRepositoryService)
services.add_transient(RealmRepositoryABC, RealmRepositoryService)
services.add_transient(SeederService) services.add_transient(SeederService)
services.add_transient(DataSeederABC, TechnicianConfigSeeder) services.add_transient(DataSeederABC, TechnicianConfigSeeder)

View File

@ -0,0 +1,120 @@
from datetime import datetime
from typing import Optional
from cpl_core.database import TableABC
from bot_data.model.server import Server
class Realm(TableABC):
def __init__(
self,
name: str,
role_id: int,
server: Optional[Server],
created_at: datetime = None,
modified_at: datetime = None,
id=0,
):
self._id = id
self._name = name
self._role_id = role_id
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 name(self) -> str:
return self._name
@name.setter
def name(self, value: str):
self._name = value
@property
def role_id(self) -> int:
return self._role_id
@role_id.setter
def role_id(self, value: int):
self._role_id = value
@property
def server(self) -> Server:
return self._server
@staticmethod
def get_select_all_string() -> str:
return str(
f"""
SELECT * FROM `Realms`;
"""
)
@staticmethod
def get_select_by_id_string(id: int) -> str:
return str(
f"""
SELECT * FROM `Realms`
WHERE `RealmId` = {id};
"""
)
@staticmethod
def get_select_by_server_id_string(id: int) -> str:
return str(
f"""
SELECT * FROM `Realms`
WHERE `ServerId` = {id};
"""
)
@staticmethod
def get_select_by_name_string(name: str) -> str:
return str(
f"""
SELECT * FROM `Realms`
WHERE `Name` = '{name}';
"""
)
@property
def insert_string(self) -> str:
return str(
f"""
INSERT INTO `Realms` (
`ServerId`, `Name`, `RoleId`
) VALUES (
{self._server.id},
'{self._name}',
{self._role_id}
);
"""
)
@property
def udpate_string(self) -> str:
return str(
f"""
UPDATE `Realms`
SET `ServerId` = {self._server.id},
`Name` = '{self._name}',
`RoleId` = {self._role_id}
WHERE `RealmId` = {self._id};
"""
)
@property
def delete_string(self) -> str:
return str(
f"""
DELETE FROM `Realms`
WHERE `RealmId` = {self._id};
"""
)

View File

@ -0,0 +1,43 @@
from typing import Optional
from bot_data.abc.history_table_abc import HistoryTableABC
from bot_data.model.server import Server
class Realm(HistoryTableABC):
def __init__(
self,
server: Optional[Server],
name: str,
role_id: int,
deleted: bool,
date_from: str,
date_to: str,
id=0,
):
HistoryTableABC.__init__(self)
self._id = id
self._server = server
self._name = name
self._role_id = role_id
self._deleted = deleted
self._date_from = date_from
self._date_to = date_to
@property
def id(self) -> int:
return self._id
@property
def server(self) -> Server:
return self._server
@property
def name(self) -> str:
return self._name
@property
def role_id(self) -> int:
return self._role_id

View File

@ -0,0 +1,106 @@
from datetime import datetime
from cpl_core.database import TableABC
from bot_data.model.realm import Realm
from bot_data.model.user import User
class RealmModerator(TableABC):
def __init__(
self,
realm: Realm,
user: User,
created_at: datetime = None,
modified_at: datetime = None,
id=0,
):
self._join_id = id
self._realm = realm
self._user = user
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._join_id
@property
def realm(self) -> Realm:
return self._realm
@property
def user(self) -> User:
return self._user
@staticmethod
def get_select_all_string() -> str:
return str(
f"""
SELECT * FROM `RealmModerators`;
"""
)
@staticmethod
def get_select_by_id_string(id: int) -> str:
return str(
f"""
SELECT * FROM `RealmModerators`
WHERE `Id` = {id};
"""
)
@staticmethod
def get_select_by_realm_id_string(id: int) -> str:
return str(
f"""
SELECT * FROM `RealmModerators`
WHERE `RealmId` = {id};
"""
)
@staticmethod
def get_select_by_user_id_string(id: int) -> str:
return str(
f"""
SELECT * FROM `RealmModerators`
WHERE `UserId` = {id};
"""
)
@property
def insert_string(self) -> str:
return str(
f"""
INSERT INTO `RealmModerators` (
`UserId`, `RealmId`
) VALUES (
{self._user.id},
{self._realm.id}
);
"""
)
@property
def udpate_string(self) -> str:
return str(f"""""")
@property
def delete_string(self) -> str:
return str(
f"""
DELETE FROM `RealmModerators`
WHERE `Id` = {self._join_id};
"""
)
@staticmethod
def delete_by_user_id_string(id: int) -> str:
return str(
f"""
DELETE FROM `RealmModerators`
WHERE `UserId` = {id}
"""
)

View File

@ -0,0 +1,36 @@
from bot_data.abc.history_table_abc import HistoryTableABC
from bot_data.model.realm import Realm
from bot_data.model.user import User
class RealmModerator(HistoryTableABC):
def __init__(
self,
realm: Realm,
user: User,
deleted: bool,
date_from: str,
date_to: str,
id=0,
):
HistoryTableABC.__init__(self)
self._join_id = id
self._realm = realm
self._user = user
self._deleted = deleted
self._date_from = date_from
self._date_to = date_to
@property
def id(self) -> int:
return self._join_id
@property
def realm(self) -> Realm:
return self._realm
@property
def user(self) -> User:
return self._user

View File

@ -0,0 +1,4 @@
DROP TABLE IF EXISTS `Realms`;
DROP TABLE IF EXISTS `RealmsHistory`;
DROP TABLE IF EXISTS `RealmModerators`;
DROP TABLE IF EXISTS `RealmModeratorsHistory`;

View File

@ -0,0 +1,97 @@
CREATE TABLE IF NOT EXISTS `Realms`
(
`RealmId` BIGINT NOT NULL AUTO_INCREMENT,
`Name` VARCHAR(255) NOT NULL,
`RoleId` BIGINT NOT NULL,
`ServerId` BIGINT,
`CreatedAt` DATETIME(6) NULL DEFAULT CURRENT_TIMESTAMP(6),
`LastModifiedAt` DATETIME(6) NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
PRIMARY KEY (`RealmId`),
FOREIGN KEY (`ServerId`) REFERENCES `Servers` (`ServerId`)
);
CREATE TABLE IF NOT EXISTS `RealmsHistory`
(
`RealmId` BIGINT(20) NOT NULL,
`Name` VARCHAR(255) NOT NULL,
`RoleId` BIGINT NOT NULL,
`ServerId` BIGINT(20) DEFAULT NULL,
`Deleted` BOOL DEFAULT FALSE,
`DateFrom` DATETIME(6) NOT NULL,
`DateTo` DATETIME(6) NOT NULL
);
DROP TRIGGER IF EXISTS `TR_RealmsUpdate`;
CREATE TRIGGER `TR_RealmsUpdate`
AFTER UPDATE
ON `Realms`
FOR EACH ROW
BEGIN
INSERT INTO `RealmsHistory` (`RealmId`, `Name`, `RoleId`, `ServerId`, `DateFrom`, `DateTo`)
VALUES (OLD.RealmId, OLD.Name, OLD.RoleId, OLD.ServerId, OLD.LastModifiedAt, CURRENT_TIMESTAMP(6));
END;
DROP TRIGGER IF EXISTS `TR_RealmsDelete`;
CREATE TRIGGER `TR_RealmsDelete`
AFTER DELETE
ON `Realms`
FOR EACH ROW
BEGIN
INSERT INTO `RealmsHistory` (`RealmId`, `Name`, `RoleId`, `ServerId`, `Deleted`, `DateFrom`, `DateTo`)
VALUES (OLD.RealmId, OLD.Name, OLD.RoleId, OLD.ServerId, TRUE, OLD.LastModifiedAt, CURRENT_TIMESTAMP(6));
END;
-- Realm moderators
CREATE TABLE IF NOT EXISTS `RealmModerators`
(
`Id` BIGINT NOT NULL AUTO_INCREMENT,
`RealmId` BIGINT NOT NULL,
`UserId` BIGINT NOT NULL,
`ServerId` 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 (`RealmId`) REFERENCES `Realms` (`RealmId`),
FOREIGN KEY (`UserId`) REFERENCES `Users` (`UserId`),
FOREIGN KEY (`ServerId`) REFERENCES `Servers` (`ServerId`)
);
CREATE TABLE IF NOT EXISTS `RealmModeratorsHistory`
(
`Id` BIGINT(20) NOT NULL,
`RealmId` BIGINT NOT NULL,
`UserId` BIGINT NOT NULL,
`ServerId` BIGINT(20) DEFAULT NULL,
`Deleted` BOOL DEFAULT FALSE,
`DateFrom` DATETIME(6) NOT NULL,
`DateTo` DATETIME(6) NOT NULL
);
DROP TRIGGER IF EXISTS `TR_RealmModeratorsUpdate`;
CREATE TRIGGER `TR_RealmModeratorsUpdate`
AFTER UPDATE
ON `RealmModerators`
FOR EACH ROW
BEGIN
INSERT INTO `RealmModeratorsHistory` (`Id`, `RealmId`, `UserId`, `ServerId`, `DateFrom`, `DateTo`)
VALUES (OLD.Id, OLD.RealmId, OLD.UserId, OLD.ServerId, OLD.LastModifiedAt, CURRENT_TIMESTAMP(6));
END;
DROP TRIGGER IF EXISTS `TR_RealmModeratorsDelete`;
CREATE TRIGGER `TR_RealmModeratorsDelete`
AFTER DELETE
ON `RealmModerators`
FOR EACH ROW
BEGIN
INSERT INTO `RealmModeratorsHistory` (`Id`, `RealmId`, `UserId`, `ServerId`, `Deleted`, `DateFrom`, `DateTo`)
VALUES (OLD.Id, OLD.RealmId, OLD.UserId, OLD.ServerId, TRUE, OLD.LastModifiedAt, CURRENT_TIMESTAMP(6));
END;

View File

@ -0,0 +1,127 @@
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.realm_repository_abc import RealmRepositoryABC
from bot_data.abc.server_repository_abc import ServerRepositoryABC
from bot_data.abc.user_repository_abc import UserRepositoryABC
from bot_data.model.realm import Realm
from bot_data.model.realm_moderator import RealmModerator
class RealmRepositoryService(RealmRepositoryABC):
def __init__(
self,
logger: DatabaseLogger,
db_context: DatabaseContextABC,
servers: ServerRepositoryABC,
users: UserRepositoryABC,
):
self._logger = logger
self._context = db_context
self._servers = servers
self._users = users
RealmRepositoryABC.__init__(self)
def _from_result(self, result: tuple) -> Realm:
return Realm(
result[1],
result[2],
self._servers.get_server_by_id(result[3]),
result[4],
result[5],
id=result[0],
)
def get_all(self) -> List[Realm]:
self._logger.trace(__name__, f"Send SQL command: {Realm.get_select_all_string()}")
return List(
Realm, [self._from_result(result) for result in self._context.select(Realm.get_select_all_string())]
)
def get_realm_by_id(self, id: int) -> Realm:
self._logger.trace(__name__, f"Send SQL command: {Realm.get_select_by_id_string(id)}")
return self._from_result(self._context.select(Realm.get_select_by_id_string(id))[0])
def find_realm_by_id(self, id: int) -> Optional[Realm]:
self._logger.trace(__name__, f"Send SQL command: {Realm.get_select_by_id_string(id)}")
result = self._context.select(Realm.get_select_by_id_string(id))
if result is None or len(result) == 0:
return None
return self._from_result(result[0])
def find_realm_by_name(self, name: str) -> Optional[Realm]:
self._logger.trace(__name__, f"Send SQL command: {Realm.get_select_by_name_string(name)}")
result = self._context.select(Realm.get_select_by_name_string(name))
if result is None or len(result) == 0:
return None
return self._from_result(result[0])
def get_realms_by_server_id(self, id: int) -> List[Realm]:
self._logger.trace(__name__, f"Send SQL command: {Realm.get_select_by_server_id_string(id)}")
return List(
Realm,
[self._from_result(result) for result in self._context.select(Realm.get_select_by_server_id_string(id))],
)
def add_realm(self, realm: Realm):
self._logger.trace(__name__, f"Send SQL command: {realm.insert_string}")
self._context.cursor.execute(realm.insert_string)
def update_realm(self, realm: Realm):
self._logger.trace(__name__, f"Send SQL command: {realm.udpate_string}")
self._context.cursor.execute(realm.udpate_string)
def delete_realm(self, realm: Realm):
self._logger.trace(__name__, f"Send SQL command: {realm.delete_string}")
self._context.cursor.execute(realm.delete_string)
def _rule_from_result(self, result: tuple) -> RealmModerator:
return RealmModerator(
self.get_realm_by_id(result[1]),
self._users.get_user_by_id(result[2]),
result[4],
result[5],
id=result[0],
)
def get_realm_moderators(self) -> List[RealmModerator]:
self._logger.trace(__name__, f"Send SQL command: {RealmModerator.get_select_all_string()}")
return List(
RealmModerator,
[self._rule_from_result(result) for result in self._context.select(RealmModerator.get_select_all_string())],
)
def get_realm_moderator_by_id(self, id: int) -> RealmModerator:
self._logger.trace(__name__, f"Send SQL command: {RealmModerator.get_select_by_id_string(id)}")
return self._rule_from_result(self._context.select(RealmModerator.get_select_by_id_string(id))[0])
def get_realm_moderators_by_realm_id(self, id: int) -> List[RealmModerator]:
self._logger.trace(
__name__,
f"Send SQL command: {RealmModerator.get_select_by_realm_id_string(id)}",
)
return List(
RealmModerator,
[
self._rule_from_result(result)
for result in self._context.select(RealmModerator.get_select_by_realm_id_string(id))
],
)
def add_realm_moderator(self, realm_moderator: RealmModerator):
self._logger.trace(__name__, f"Send SQL command: {realm_moderator.insert_string}")
self._context.cursor.execute(realm_moderator.insert_string)
def update_realm_moderator(self, realm_moderator: RealmModerator):
self._logger.trace(__name__, f"Send SQL command: {realm_moderator.udpate_string}")
self._context.cursor.execute(realm_moderator.udpate_string)
def delete_realm_moderator(self, realm_moderator: RealmModerator):
self._logger.trace(__name__, f"Send SQL command: {realm_moderator.delete_string}")
self._context.cursor.execute(realm_moderator.delete_string)

View File

View File

@ -0,0 +1,446 @@
from typing import List as TList
from typing import Optional
import discord
from cpl_discord.command import DiscordCommandABC
from cpl_discord.service import DiscordBotServiceABC
from cpl_translation import TranslatePipe
from discord import app_commands, Member
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.abc.permission_service_abc import PermissionServiceABC
from bot_core.helper.command_checks import CommandChecks
from bot_core.logging.command_logger import CommandLogger
from bot_data.abc.realm_repository_abc import RealmRepositoryABC
from bot_data.abc.server_repository_abc import ServerRepositoryABC
from bot_data.abc.user_repository_abc import UserRepositoryABC
from bot_data.model.realm import Realm
from bot_data.model.realm_moderator import RealmModerator
from bot_data.model.technician_config import TechnicianConfig
from modules.realms.realm_utils import RealmUtils
class RealmGroup(DiscordCommandABC):
def __init__(
self,
logger: CommandLogger,
message_service: MessageServiceABC,
bot: DiscordBotServiceABC,
client_utils: ClientUtilsABC,
translate: TranslatePipe,
permissions: PermissionServiceABC,
servers: ServerRepositoryABC,
users: UserRepositoryABC,
realms: RealmRepositoryABC,
settings: TechnicianConfig,
realm_utils: RealmUtils,
):
DiscordCommandABC.__init__(self)
self._logger = logger
self._message_service = message_service
self._bot = bot
self._client_utils = client_utils
self._t = translate
self._permissions = permissions
self._servers = servers
self._users = users
self._realms = realms
self._settings = settings
self._realm_utils = realm_utils
self._logger.trace(__name__, f"Loaded command service: {type(self).__name__}")
async def _auto_realm_complete(
self, interaction: discord.Interaction, current: str
) -> TList[app_commands.Choice[str]]:
server = self._servers.get_server_by_discord_id(interaction.guild.id)
realms = self._realms.get_realms_by_server_id(server.id).select(lambda x: x.name)
return [
app_commands.Choice(name=realm, value=realm)
for realm in self._client_utils.get_auto_complete_list(realms, current, lambda x: x.name)
]
@commands.hybrid_group(name="realm")
@commands.guild_only()
async def realm(self, ctx: Context):
pass
@realm.command()
@commands.guild_only()
@CommandChecks.check_is_ready()
@CommandChecks.check_is_member_moderator()
async def list(self, ctx: Context):
self._logger.debug(__name__, f"Received command realm list {ctx}")
realms = self._realms.get_all()
if len(realms) == 0:
await self._message_service.send_ctx_msg(ctx, self._t.transform("modules.realm.list.error.nothing_found"))
self._logger.trace(__name__, f"Finished command realm list")
return
embed = discord.Embed(
title=self._t.transform("modules.realm.list.title"),
description=self._t.transform("modules.realm.list.description"),
color=int("ef9d0d", 16),
)
realm_names = ""
for realm in realms:
realm_names += f"\n{realm.name}"
embed.add_field(
name=self._t.transform("modules.realm.list.realm_names"),
value=realm_names,
inline=True,
)
await self._message_service.send_ctx_msg(ctx, embed)
self._logger.trace(__name__, f"Finished command realm list")
@realm.command()
@commands.guild_only()
@CommandChecks.check_is_ready()
@CommandChecks.check_is_member_moderator()
async def add(self, ctx: Context, name: str, leader: Optional[discord.Member] = None):
self._logger.debug(__name__, f"Received command realm list {ctx}")
if self._realm_utils.find_role_by_name(ctx.guild, name) is not None:
await self._message_service.send_ctx_msg(
ctx,
self._t.transform("modules.realm.add.error.role_exists").format(name),
)
return
if name in [x.name for x in ctx.guild.categories]:
await self._message_service.send_ctx_msg(
ctx,
self._t.transform("modules.realm.add.error.category_exists").format(name),
)
return
try:
role = await ctx.guild.create_role(name=name)
server = self._servers.get_server_by_discord_id(ctx.guild.id)
if self._realms.find_realm_by_name(name) is not None:
await role.delete()
await self._message_service.send_ctx_msg(
ctx,
self._t.transform("modules.realm.add.error.realm_exists").format(name),
)
return
realm = Realm(name, role.id, server)
self._realms.add_realm(realm)
realm = self._realms.find_realm_by_name(name)
if realm is None:
raise ValueError("Realm not found after creation")
if leader is not None:
self._realms.add_realm_moderator(
RealmModerator(realm, self._users.get_user_by_discord_id_and_server_id(leader.id, server.id))
)
await self._realm_utils.create_category(ctx.guild, name, role, leader)
await self._message_service.send_ctx_msg(
ctx,
self._t.transform("modules.realm.add.success").format(name),
)
except Exception as e:
role = self._realm_utils.find_role_by_name(ctx.guild, name)
if role is not None:
await role.delete()
self._logger.error(__name__, f"Error creating realm: {e}")
await self._message_service.send_ctx_msg(
ctx,
self._t.transform("modules.realm.add.error.realm_create").format(name),
)
return
self._logger.trace(__name__, f"Finished realm list command")
@realm.command()
@commands.guild_only()
@CommandChecks.check_is_ready()
@CommandChecks.check_is_member_moderator()
async def edit(self, ctx: Context, realm_name: str, name: str):
self._logger.debug(__name__, f"Received command realm list {ctx}")
realm = self._realms.find_realm_by_name(realm_name)
if realm is None:
await self._message_service.send_ctx_msg(
ctx,
self._t.transform("modules.realm.edit.error.realm_not_found").format(realm_name),
)
return
realm.name = name
self._realms.update_realm(realm)
role = self._realm_utils.find_role_by_name(ctx.guild, realm_name)
if role is None and self._realm_utils.find_role_by_name(ctx.guild, name) is None:
role = await ctx.guild.create_role(name=name)
realm.role_id = role.id
self._realms.update_realm(realm)
elif role is not None:
await role.edit(name=name)
category = self._realm_utils.find_category_by_name(ctx.guild, realm_name)
if category is None:
mods = self._realms.get_realm_moderators_by_realm_id(realm.id)
leader = mods[0].user.discord_id if len(mods) > 0 else None
await self._realm_utils.create_category(ctx.guild, name, role, leader)
await category.edit(name=name)
await self._message_service.send_ctx_msg(
ctx,
self._t.transform("modules.realm.edit.success").format(realm_name, name),
)
self._logger.trace(__name__, f"Finished realm list command")
@edit.autocomplete("realm_name")
async def remove_autocomplete(
self, interaction: discord.Interaction, current: str
) -> TList[app_commands.Choice[str]]:
return await self._auto_realm_complete(interaction, current)
@realm.command()
@commands.guild_only()
@CommandChecks.check_is_ready()
@CommandChecks.check_is_member_moderator()
async def delete(self, ctx: Context, realm_name: str):
self._logger.debug(__name__, f"Received command realm list {ctx}")
realm = self._realms.find_realm_by_name(realm_name)
if realm is None:
await self._message_service.send_ctx_msg(
ctx,
self._t.transform("modules.realm.delete.error.realm_not_found").format(realm_name),
)
return
role = self._realm_utils.find_role_by_name(ctx.guild, realm_name)
if role is not None:
await role.delete()
category = self._realm_utils.find_category_by_name(ctx.guild, realm_name)
if category is not None:
for c in category.channels:
await c.delete()
await category.delete()
mods = self._realms.get_realm_moderators_by_realm_id(realm.id)
for mod in mods:
self._realms.delete_realm_moderator(mod)
self._realms.delete_realm(realm)
await self._message_service.send_ctx_msg(
ctx,
self._t.transform("modules.realm.delete.success").format(realm_name),
)
self._logger.trace(__name__, f"Finished realm list command")
@delete.autocomplete("realm_name")
async def remove_autocomplete(
self, interaction: discord.Interaction, current: str
) -> TList[app_commands.Choice[str]]:
return await self._auto_realm_complete(interaction, current)
@realm.group(name="member")
@commands.guild_only()
async def member(self, ctx: Context):
pass
@member.command("add")
@commands.guild_only()
@CommandChecks.check_is_ready()
async def member_add(self, ctx: Context, realm: str, member: Member):
self._logger.debug(__name__, f"Received command realm member add {ctx}")
realm = self._realms.find_realm_by_name(realm)
if realm is None:
await self._message_service.send_ctx_msg(
ctx,
self._t.transform("modules.realm.add.error.realm_not_found").format(realm),
)
return
if not self._permissions.is_member_moderator(ctx.author) and not self._realm_utils.is_realm_moderator(
ctx.author.id, realm.id
):
await self._message_service.send_ctx_msg(
ctx,
self._t.transform("modules.realm.error.not_moderator").format(realm.name),
)
return
role = self._realm_utils.find_role_by_name(ctx.guild, realm.name)
if role is None:
await self._message_service.send_ctx_msg(
ctx,
self._t.transform("modules.realm.add.error.role_not_found").format(realm.name),
)
return
await member.add_roles(role)
await self._message_service.send_ctx_msg(
ctx,
self._t.transform("modules.realm.member.add.success").format(member.name, realm.name),
)
self._logger.trace(__name__, f"Finished realm list command")
@member_add.autocomplete("realm")
async def add_autocomplete(self, interaction: discord.Interaction, current: str) -> TList[app_commands.Choice[str]]:
return await self._auto_realm_complete(interaction, current)
@member.command("remove")
@commands.guild_only()
@CommandChecks.check_is_ready()
async def member_remove(self, ctx: Context, realm: str, member: Member):
self._logger.debug(__name__, f"Received command realm member remove {ctx}")
realm = self._realms.find_realm_by_name(realm)
if realm is None:
await self._message_service.send_ctx_msg(
ctx,
self._t.transform("modules.realm.add.error.realm_not_found").format(realm),
)
return
if not self._permissions.is_member_moderator(ctx.author) and not self._realm_utils.is_realm_moderator(
ctx.author.id, realm.id
):
await self._message_service.send_ctx_msg(
ctx,
self._t.transform("modules.realm.error.not_moderator").format(realm.name),
)
return
role = self._realm_utils.find_role_by_name(ctx.guild, realm.name)
if role is None:
await self._message_service.send_ctx_msg(
ctx,
self._t.transform("modules.realm.add.error.role_not_found").format(realm.name),
)
return
await member.remove_roles(role)
await self._message_service.send_ctx_msg(
ctx,
self._t.transform("modules.realm.member.remove.success").format(member.name, realm.name),
)
self._logger.trace(__name__, f"Finished realm remove command")
@member_remove.autocomplete("realm")
async def remove_autocomplete(
self, interaction: discord.Interaction, current: str
) -> TList[app_commands.Choice[str]]:
return await self._auto_realm_complete(interaction, current)
@realm.group(name="moderator")
@commands.guild_only()
async def moderator(self, ctx: Context):
pass
@moderator.command("add")
@commands.guild_only()
@CommandChecks.check_is_ready()
async def moderator_add(self, ctx: Context, realm: str, moderator: Member):
self._logger.debug(__name__, f"Received command realm moderator add {ctx}")
realm = self._realms.find_realm_by_name(realm)
if realm is None:
await self._message_service.send_ctx_msg(
ctx,
self._t.transform("modules.realm.add.error.realm_not_found").format(realm),
)
return
if not self._permissions.is_member_moderator(ctx.author) and not self._realm_utils.is_realm_moderator(ctx.author.id, realm.id):
await self._message_service.send_ctx_msg(
ctx,
self._t.transform("modules.realm.error.not_moderator").format(realm.name),
)
return
role = self._realm_utils.find_role_by_name(ctx.guild, realm.name)
if role is None:
await self._message_service.send_ctx_msg(
ctx,
self._t.transform("modules.realm.add.error.role_not_found").format(realm.name),
)
return
category = self._realm_utils.find_category_by_name(ctx.guild, realm.name)
await self._realm_utils.assign_moderator(category, moderator)
await self._message_service.send_ctx_msg(
ctx,
self._t.transform("modules.realm.moderator.add.success").format(moderator.name, realm.name),
)
self._logger.trace(__name__, f"Finished realm list command")
@moderator_add.autocomplete("realm")
async def add_autocomplete(self, interaction: discord.Interaction, current: str) -> TList[app_commands.Choice[str]]:
return await self._auto_realm_complete(interaction, current)
@moderator.command("remove")
@commands.guild_only()
@CommandChecks.check_is_ready()
async def moderator_remove(self, ctx: Context, realm: str, moderator: Member):
self._logger.debug(__name__, f"Received command realm moderator remove {ctx}")
realm = self._realms.find_realm_by_name(realm)
if realm is None:
await self._message_service.send_ctx_msg(
ctx,
self._t.transform("modules.realm.add.error.realm_not_found").format(realm),
)
return
if not self._permissions.is_member_moderator(ctx.author) and not self._realm_utils.is_realm_moderator(ctx.author.id, realm.id):
await self._message_service.send_ctx_msg(
ctx,
self._t.transform("modules.realm.error.not_moderator").format(realm.name),
)
return
role = self._realm_utils.find_role_by_name(ctx.guild, realm.name)
if role is None:
await self._message_service.send_ctx_msg(
ctx,
self._t.transform("modules.realm.add.error.role_not_found").format(realm.name),
)
return
category = self._realm_utils.find_category_by_name(ctx.guild, realm.name)
if category is None:
await self._message_service.send_ctx_msg(
ctx,
self._t.transform("modules.realm.add.error.category_not_found").format(realm.name),
)
return
await self._realm_utils.remove_moderator(category, moderator)
await self._message_service.send_ctx_msg(
ctx,
self._t.transform("modules.realm.moderator.remove.success").format(moderator.name, realm.name),
)
self._logger.trace(__name__, f"Finished realm remove command")
@moderator_remove.autocomplete("realm")
async def remove_autocomplete(
self, interaction: discord.Interaction, current: str
) -> TList[app_commands.Choice[str]]:
return await self._auto_realm_complete(interaction, current)

View File

@ -0,0 +1,79 @@
from typing import Optional
import discord
from cpl_discord.container import Role, CategoryChannel
from discord import Guild, Member
from bot_data.abc.realm_repository_abc import RealmRepositoryABC
class RealmUtils:
def __init__(self, realms: RealmRepositoryABC):
self._realms = realms
@staticmethod
def find_role_by_name(guild: Guild, name: str) -> Optional[Role]:
for role in guild.roles:
if role.name == name:
return Role(role)
return None
@staticmethod
def find_category_by_name(guild: Guild, name: str) -> Optional[CategoryChannel]:
for category in guild.categories:
if category.name == name:
return CategoryChannel(category)
return None
@staticmethod
def check_realm_role(guild: Guild, role: Role) -> bool:
return role in guild.roles
@staticmethod
async def create_category(guild: Guild, name: str, role: Role, leader: Optional[Member]) -> CategoryChannel:
overwrites = {
guild.default_role: discord.PermissionOverwrite(view_channel=False, connect=False),
role: discord.PermissionOverwrite(view_channel=True, connect=True),
}
if leader is not None:
overwrites[leader] = discord.PermissionOverwrite(
view_channel=True,
connect=True,
manage_channels=True,
manage_permissions=True,
create_instant_invite=True,
)
if leader.get_role(role.id) is None:
await leader.add_roles(role)
return await guild.create_category(name, overwrites=overwrites)
@staticmethod
async def assign_moderator(category: CategoryChannel, member: Member):
await category.set_permissions(
member,
view_channel=True,
connect=True,
manage_channels=True,
manage_permissions=True,
create_instant_invite=True,
)
@staticmethod
async def remove_moderator(category: CategoryChannel, member: Member):
await category.set_permissions(
member,
view_channel=True,
connect=True,
manage_channels=False,
manage_permissions=False,
create_instant_invite=False,
)
def is_realm_moderator(self, member_id: int, realm_id: int) -> bool:
return (
self._realms.get_realm_moderators_by_realm_id(realm_id)
.select(lambda x: x.user.discord_id)
.contains(member_id)
)

View File

@ -0,0 +1,44 @@
{
"ProjectSettings": {
"Name": "private-categories",
"Version": {
"Major": "1",
"Minor": "2",
"Micro": "8"
},
"Author": "",
"AuthorEmail": "",
"Description": "",
"LongDescription": "",
"URL": "",
"CopyrightDate": "",
"CopyrightName": "",
"LicenseName": "",
"LicenseDescription": "",
"Dependencies": [
"cpl-core==2022.12.0"
],
"DevDependencies": [
"cpl-cli==2022.12.0"
],
"PythonVersion": ">=3.10.6",
"PythonPath": {},
"Classifiers": []
},
"BuildSettings": {
"ProjectType": "library",
"SourcePath": "",
"OutputPath": "../../dist",
"Main": "private_categories.main",
"EntryPoint": "private_categories",
"IncludePackageData": false,
"Included": [],
"Excluded": [
"*/__pycache__",
"*/logs",
"*/tests"
],
"PackageData": {},
"ProjectReferences": []
}
}

View File

@ -0,0 +1,21 @@
from cpl_core.configuration import ConfigurationABC
from cpl_core.dependency_injection import ServiceCollectionABC
from cpl_core.environment import ApplicationEnvironmentABC
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.realms.command.realm_group import RealmGroup
from modules.realms.realm_utils import RealmUtils
class RealmsModule(ModuleABC):
def __init__(self, dc: DiscordCollectionABC):
ModuleABC.__init__(self, dc, FeatureFlagsEnum.realm_module)
def configure_configuration(self, config: ConfigurationABC, env: ApplicationEnvironmentABC):
pass
def configure_services(self, services: ServiceCollectionABC, env: ApplicationEnvironmentABC):
services.add_transient(RealmGroup)
services.add_transient(RealmUtils)