From 16e5b87b929c6624f64f4568070dbb08638f2180 Mon Sep 17 00:00:00 2001 From: maxime1907 <19607336+maxime1907@users.noreply.github.com> Date: Sun, 25 Feb 2024 15:53:22 +0100 Subject: [PATCH] feat: map sm flags to torchlight levels in config --- VERSION | 2 +- config/access.json | 6 -- config/admins.json | 9 +++ config/config.json | 53 +++++++++++--- config/flags.json | 106 ++++++++++++++++++++++++++++ src/torchlight/AccessManager.py | 52 +++++++++----- src/torchlight/Admin.py | 72 ------------------- src/torchlight/AntiSpam.py | 2 +- src/torchlight/AudioClip.py | 2 +- src/torchlight/AudioManager.py | 8 +-- src/torchlight/CommandHandler.py | 2 +- src/torchlight/Commands.py | 86 +++++++++++----------- src/torchlight/Config.py | 11 +-- src/torchlight/Player.py | 74 +++++++------------ src/torchlight/PlayerManager.py | 11 ++- src/torchlight/SourceRCONClient.py | 10 ++- src/torchlight/Sourcemod.py | 88 +++++++++++++++++++++++ src/torchlight/Torchlight.py | 4 +- src/torchlight/TorchlightHandler.py | 11 ++- src/torchlight/cli.py | 1 + 20 files changed, 382 insertions(+), 228 deletions(-) delete mode 100644 config/access.json create mode 100644 config/admins.json create mode 100644 config/flags.json delete mode 100644 src/torchlight/Admin.py create mode 100644 src/torchlight/Sourcemod.py diff --git a/VERSION b/VERSION index 26ca594..dc1e644 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.5.1 +1.6.0 diff --git a/config/access.json b/config/access.json deleted file mode 100644 index 668221d..0000000 --- a/config/access.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "[U:1:51174697]": { - "name": "BotoX", - "level": 100 - } -} \ No newline at end of file diff --git a/config/admins.json b/config/admins.json new file mode 100644 index 0000000..7a56d23 --- /dev/null +++ b/config/admins.json @@ -0,0 +1,9 @@ +{ + "admins": [ + { + "name": "BotoX", + "unique_id": "[U:1:51174697]", + "level": 100 + } + ] +} \ No newline at end of file diff --git a/config/config.json b/config/config.json index 3bf1db7..5e3edcf 100644 --- a/config/config.json +++ b/config/config.json @@ -47,15 +47,50 @@ "AdStop": 10 }, - "AccessLevel": - { - "Root": 6, - "EventManager": 4, - "Admin": 3, - "DonatedAdmin": 2, - "VIP": 2, - "Player": 0 - }, + "SourcemodGroups": + [ + { + "name": "Root", + "flags": [ + "z", + "m" + ], + "level": 6 + }, + { + "name": "Event Manager", + "flags": [ + "r" + ], + "level": 4 + }, + { + "name": "Server admin", + "flags": [ + "d" + ], + "level": 3 + }, + { + "name": "Donated admin", + "flags": [ + "b" + ], + "level": 2 + }, + { + "name": "VIP", + "flags": [ + "o" + ], + "level": 2 + }, + { + "name": "Player", + "flags": [], + "level": 0 + } + ], "Command": { diff --git a/config/flags.json b/config/flags.json new file mode 100644 index 0000000..c7a2e76 --- /dev/null +++ b/config/flags.json @@ -0,0 +1,106 @@ +{ + "SM_RESERVED_SLOT": { + "value": "a", + "display": "Reserved Slot" + }, + + "SM_GENERIC": { + "value": "b", + "display": "Generic Admin" + }, + + "SM_KICK": { + "value": "c", + "display": "Kick" + }, + + "SM_BAN": { + "value": "d", + "display": "Ban" + }, + + "SM_UNBAN": { + "value": "e", + "display": "Unban" + }, + + "SM_SLAY": { + "value": "f", + "display": "Slay" + }, + + "SM_MAP": { + "value": "g", + "display": "Map Change" + }, + + "SM_CVAR": { + "value": "h", + "display": "Change ConVars" + }, + + "SM_CONFIG": { + "value": "i", + "display": "Run Configs" + }, + + "SM_CHAT": { + "value": "j", + "display": "Admin Chat" + }, + + "SM_VOTE": { + "value": "k", + "display": "Start Votes" + }, + + "SM_PASSWORD": { + "value": "l", + "display": "Password Server" + }, + + "SM_RCON": { + "value": "m", + "display": "RCON" + }, + + "SM_CHEATS": { + "value": "n", + "display": "Enable Cheats" + }, + + "SM_ROOT": { + "value": "z", + "display": "ALL Permissions (Root)" + }, + + "SM_CUSTOM1": { + "value": "o", + "display": "Custom Flag 1" + }, + + "SM_CUSTOM2": { + "value": "p", + "display": "Custom Flag 2" + }, + + "SM_CUSTOM3": { + "value": "q", + "display": "Custom Flag 3" + }, + + "SM_CUSTOM4": { + "value": "r", + "display": "Custom Flag 4" + }, + + "SM_CUSTOM5": { + "value": "s", + "display": "Custom Flag 5" + }, + + "SM_CUSTOM6": { + "value": "t", + "display": "Custom Flag 6" + } +} \ No newline at end of file diff --git a/src/torchlight/AccessManager.py b/src/torchlight/AccessManager.py index 7bae730..71d8272 100644 --- a/src/torchlight/AccessManager.py +++ b/src/torchlight/AccessManager.py @@ -1,15 +1,16 @@ +import copy import json import logging import os from collections import OrderedDict -from torchlight.Config import ConfigAccess from torchlight.Player import Player +from torchlight.Sourcemod import SourcemodAdmin class AccessManager: def __init__( - self, config_folder: str, config_filename: str = "access.json" + self, config_folder: str, config_filename: str = "admins.json" ) -> None: self.logger = logging.getLogger(self.__class__.__name__) self.config_folder = os.path.abspath(config_folder) @@ -18,30 +19,36 @@ def __init__( os.path.join(config_folder, config_filename) ) self.access_dict: OrderedDict = OrderedDict() - self.config_access_list: dict[str, ConfigAccess] = {} + self.admins: list[SourcemodAdmin] = [] def Load(self) -> None: self.logger.info(f"Loading access from {self.config_filepath}") with open(self.config_filepath) as fp: self.access_dict = json.load(fp, object_pairs_hook=OrderedDict) - for unique_id, access in self.access_dict.items(): - self.config_access_list[unique_id] = ConfigAccess( - name=access["name"], - level=access["level"], - uniqueid=unique_id, + for admin_dict in self.access_dict["admins"]: + self.admins.append( + SourcemodAdmin( + name=admin_dict["name"], + level=admin_dict["level"], + unique_id=admin_dict["unique_id"], + flag_bits=0, + groups=[], + ) ) - self.logger.info(f"Loaded {self.config_access_list}") + self.logger.info(f"Loaded {self.admins}") def Save(self) -> None: self.logger.info(f"Saving access to {self.config_filepath}") - for unique_id, access in self.config_access_list.items(): - self.access_dict[unique_id] = { - "name": access.name, - "level": access.level, - } + for index, admin_dict in enumerate(self.access_dict["admins"]): + for admin in self.admins: + if admin.unique_id == admin_dict["unique_id"]: + self.access_dict[index] = { + "name": admin.name, + "level": admin.level, + } self.access_dict = OrderedDict( sorted( @@ -54,8 +61,15 @@ def Save(self) -> None: with open(self.config_filepath, "w") as fp: json.dump(self.access_dict, fp, indent="\t") - def get_access(self, player: Player) -> ConfigAccess: - for unique_id, access in self.config_access_list.items(): - if unique_id == player.unique_id: - return access - return player.access + def get_admin(self, player: Player) -> SourcemodAdmin: + for admin in self.admins: + if admin.unique_id == player.unique_id: + return admin + return player.admin + + def set_admin(self, unique_id: str, admin: SourcemodAdmin) -> bool: + for index, admin in enumerate(self.admins): + if admin.unique_id == unique_id: + self.admins[index] = copy.deepcopy(admin) + return True + return False diff --git a/src/torchlight/Admin.py b/src/torchlight/Admin.py deleted file mode 100644 index ee85cd8..0000000 --- a/src/torchlight/Admin.py +++ /dev/null @@ -1,72 +0,0 @@ -from torchlight.Constants import AdminFlagBits - - -class Admin: - def __init__(self) -> None: - self._flag_bits = 0 - - def FlagBits(self) -> int: - return self._flag_bits - - def Reservation(self) -> int: - return self._flag_bits & AdminFlagBits.ADMFLAG_RESERVATION - - def Generic(self) -> int: - return self._flag_bits & AdminFlagBits.ADMFLAG_GENERIC - - def Kick(self) -> int: - return self._flag_bits & AdminFlagBits.ADMFLAG_KICK - - def Ban(self) -> int: - return self._flag_bits & AdminFlagBits.ADMFLAG_BAN - - def Unban(self) -> int: - return self._flag_bits & AdminFlagBits.ADMFLAG_UNBAN - - def Slay(self) -> int: - return self._flag_bits & AdminFlagBits.ADMFLAG_SLAY - - def Changemap(self) -> int: - return self._flag_bits & AdminFlagBits.ADMFLAG_CHANGEMAP - - def Convars(self) -> int: - return self._flag_bits & AdminFlagBits.ADMFLAG_CONVARS - - def Config(self) -> int: - return self._flag_bits & AdminFlagBits.ADMFLAG_CONFIG - - def Chat(self) -> int: - return self._flag_bits & AdminFlagBits.ADMFLAG_CHAT - - def Vote(self) -> int: - return self._flag_bits & AdminFlagBits.ADMFLAG_VOTE - - def Password(self) -> int: - return self._flag_bits & AdminFlagBits.ADMFLAG_PASSWORD - - def RCON(self) -> int: - return self._flag_bits & AdminFlagBits.ADMFLAG_RCON - - def Cheats(self) -> int: - return self._flag_bits & AdminFlagBits.ADMFLAG_CHEATS - - def Root(self) -> int: - return self._flag_bits & AdminFlagBits.ADMFLAG_ROOT - - def Custom1(self) -> int: - return self._flag_bits & AdminFlagBits.ADMFLAG_CUSTOM1 - - def Custom2(self) -> int: - return self._flag_bits & AdminFlagBits.ADMFLAG_CUSTOM2 - - def Custom3(self) -> int: - return self._flag_bits & AdminFlagBits.ADMFLAG_CUSTOM3 - - def Custom4(self) -> int: - return self._flag_bits & AdminFlagBits.ADMFLAG_CUSTOM4 - - def Custom5(self) -> int: - return self._flag_bits & AdminFlagBits.ADMFLAG_CUSTOM5 - - def Custom6(self) -> int: - return self._flag_bits & AdminFlagBits.ADMFLAG_CUSTOM6 diff --git a/src/torchlight/AntiSpam.py b/src/torchlight/AntiSpam.py index 22c38ee..a99071e 100644 --- a/src/torchlight/AntiSpam.py +++ b/src/torchlight/AntiSpam.py @@ -21,7 +21,7 @@ def CheckAntiSpam(self, player: Player) -> bool: if ( self.disabled_time and self.disabled_time > self.torchlight.loop.time() - and player.access.level < self.config["ImmunityLevel"] + and player.admin.level < self.config["ImmunityLevel"] ): self.torchlight.SayPrivate( player, diff --git a/src/torchlight/AudioClip.py b/src/torchlight/AudioClip.py index cb1936c..373a1ab 100644 --- a/src/torchlight/AudioClip.py +++ b/src/torchlight/AudioClip.py @@ -24,7 +24,7 @@ def __init__( self.last_position: int = 0 self.stops: set[int] = set() - self.level = self.player.access.level + self.level = self.player.admin.level self.audio_player.AddCallback("Play", self.OnPlay) self.audio_player.AddCallback("Stop", self.OnStop) diff --git a/src/torchlight/AudioManager.py b/src/torchlight/AudioManager.py index e17cddc..d9a40d7 100644 --- a/src/torchlight/AudioManager.py +++ b/src/torchlight/AudioManager.py @@ -22,7 +22,7 @@ def __del__(self) -> None: self.logger.info("~AudioManager()") def CheckLimits(self, player: Player) -> bool: - level = player.access.level + level = player.admin.level if str(level) in self.anti_spam.config: if ( @@ -71,7 +71,7 @@ def CheckLimits(self, player: Player) -> bool: return True def Stop(self, player: Player, extra: str) -> None: - level = player.access.level + level = player.admin.level for audio_clip in self.audio_clips[:]: if extra and not extra.lower() in audio_clip.player.name.lower(): @@ -122,7 +122,7 @@ def AudioClip( uri: str, _type: AudioPlayerType = AudioPlayerType.AUDIOPLAYER_FFMPEG, ) -> AudioClip | None: - level = player.access.level + level = player.admin.level if self.torchlight.disabled and self.torchlight.disabled > level: self.torchlight.SayPrivate( @@ -143,7 +143,7 @@ def AudioClip( self.audio_clips.append(clip) audio_player.AddCallback("Stop", lambda: self.audio_clips.remove(clip)) - if player.access.level < self.anti_spam.config["ImmunityLevel"]: + if player.admin.level < self.anti_spam.config["ImmunityLevel"]: clip.audio_player.AddCallback( "Play", lambda *args: self.anti_spam.OnPlay(clip, *args) ) diff --git a/src/torchlight/CommandHandler.py b/src/torchlight/CommandHandler.py index 6eb8f83..1cb1f05 100644 --- a/src/torchlight/CommandHandler.py +++ b/src/torchlight/CommandHandler.py @@ -85,7 +85,7 @@ async def HandleCommand(self, line: str, player: Player) -> int | None: message[1] = message[1].replace("!last", self.torchlight.last_url) line = message[0] + " " + message[1] - level = player.access.level + level = player.admin.level self.logger.debug(f"Command: {message}") ret_message: str | None = None diff --git a/src/torchlight/Commands.py b/src/torchlight/Commands.py index cb8622b..ffbbc77 100644 --- a/src/torchlight/Commands.py +++ b/src/torchlight/Commands.py @@ -83,7 +83,7 @@ def check_chat_cooldown(self, player: Player) -> bool: return False def check_disabled(self, player: Player) -> bool: - level = player.access.level + level = player.admin.level disabled = self.torchlight.disabled if disabled and ( @@ -182,8 +182,8 @@ async def _rfunc( def FormatAccess(config: Config, player: Player) -> str: answer = f'#{player.user_id} "{player.name}"({player.unique_id}) is ' - level = str(player.access.level) - answer += f"level {level!s} as {player.access.name}." + level = str(player.admin.level) + answer += f"level {level!s} as {player.admin.name}." if level in config["AudioLimits"]: uses = config["AudioLimits"][level]["Uses"] @@ -241,13 +241,10 @@ async def _func(self, message: list[str], player: Player) -> int: break elif message[0] == "!whois": - for ( - unique_id, - access, - ) in self.access_manager.config_access_list.items(): - if access.name.lower().find(message[1].lower()) != -1: + for admin in self.access_manager.admins: + if admin.name.lower().find(message[1].lower()) != -1: targeted_player = self.player_manager.FindUniqueID( - unique_id + admin.unique_id ) if targeted_player is not None: self.torchlight.SayChat( @@ -258,7 +255,7 @@ async def _func(self, message: list[str], player: Player) -> int: else: self.torchlight.SayChat( '#? "{}"({}) is level {!s} is currently offline.'.format( - access.name, unique_id, access.level + admin.name, admin.unique_id, admin.level ) ) @@ -701,7 +698,7 @@ async def _func(self, message: list[str], player: Player) -> int: def get_sound_path( self, player: Player, voice_trigger: str, trigger_number: str ) -> str | None: - level = player.access.level + level = player.admin.level if ( voice_trigger[0] != "!" @@ -1099,7 +1096,7 @@ async def _func(self, message: list[str], player: Player) -> int: self.logger.debug(sys._getframe().f_code.co_name + " " + str(message)) if self.torchlight.disabled: - if self.torchlight.disabled > player.access.level: + if self.torchlight.disabled > player.admin.level: self.torchlight.SayPrivate( player, "You don't have access to enable torchlight, since it was disabled by a higher level user.", @@ -1119,7 +1116,7 @@ async def _func(self, message: list[str], player: Player) -> int: class Disable(BaseCommand): async def _func(self, message: list[str], player: Player) -> int: if not self.torchlight.disabled: - if self.torchlight.disabled > player.access.level: + if self.torchlight.disabled > player.admin.level: self.torchlight.SayPrivate( player, "You don't have access to disable torchlight, since it was already disabled by a higher level user.", @@ -1128,7 +1125,7 @@ async def _func(self, message: list[str], player: Player) -> int: self.torchlight.SayChat( "Torchlight has been disabled for the duration of this map - Type !enable to enable it again." ) - self.torchlight.disabled = player.access.level + self.torchlight.disabled = player.admin.level else: self.torchlight.SayChat("Torchlight is already disabled.") return 0 @@ -1139,7 +1136,7 @@ def ReloadValidUsers(self) -> None: self.access_manager.Load() for player in self.player_manager.players: if player: - player.access = self.access_manager.get_access(player) + player.admin = self.access_manager.get_admin(player) async def _func(self, message: list[str], admin_player: Player) -> int: self.logger.debug(sys._getframe().f_code.co_name + " " + str(message)) @@ -1150,7 +1147,7 @@ async def _func(self, message: list[str], admin_player: Player) -> int: self.ReloadValidUsers() self.torchlight.SayChat( "Loaded access list with {} users".format( - len(self.access_manager.config_access_list) + len(self.access_manager.admins) ) ) @@ -1158,7 +1155,7 @@ async def _func(self, message: list[str], admin_player: Player) -> int: self.access_manager.Save() self.torchlight.SayChat( "Saved access list with {} users".format( - len(self.access_manager.config_access_list) + len(self.access_manager.admins) ) ) @@ -1217,22 +1214,22 @@ async def _func(self, message: list[str], admin_player: Player) -> int: ): level = int(level_parsed) - if level >= admin_player.access.level: + if level >= admin_player.admin.level: self.torchlight.SayChat( "Trying to assign level {}, which is higher or equal than your level ({})".format( - level, admin_player.access.level + level, admin_player.admin.level ) ) return 4 if ( - targeted_player.access.level >= admin_player.access.level + targeted_player.admin.level >= admin_player.admin.level and admin_player.user_id != targeted_player.user_id ): self.torchlight.SayChat( "Trying to modify level {}, which is higher or equal than your level ({})".format( - targeted_player.access.level, - admin_player.access.level, + targeted_player.admin.level, + admin_player.admin.level, ) ) return 5 @@ -1242,38 +1239,36 @@ async def _func(self, message: list[str], admin_player: Player) -> int: 'Changed "{}"({}) as {} level/name from {} to {} as {}'.format( targeted_player.name, targeted_player.unique_id, - targeted_player.access.name, - targeted_player.access.level, + targeted_player.admin.name, + targeted_player.admin.level, level, reg_name, ) ) - targeted_player.access.name = reg_name + targeted_player.admin.name = reg_name else: self.torchlight.SayChat( 'Changed "{}"({}) as {} level from {} to {}'.format( targeted_player.name, targeted_player.unique_id, - targeted_player.access.name, - targeted_player.access.level, + targeted_player.admin.name, + targeted_player.admin.level, level, ) ) - targeted_player.access.level = level - self.access_manager.config_access_list[ - targeted_player.unique_id - ] = targeted_player.access + targeted_player.admin.level = level + self.access_manager.set_admin( + unique_id=targeted_player.unique_id, + admin=targeted_player.admin, + ) else: if level_parsed == "revoke": - if ( - targeted_player.access.level - >= admin_player.access.level - ): + if targeted_player.admin.level >= admin_player.admin.level: self.torchlight.SayChat( "Trying to revoke level {}, which is higher or equal than your level ({})".format( - targeted_player.access.level, - admin_player.access.level, + targeted_player.admin.level, + admin_player.admin.level, ) ) return 6 @@ -1282,18 +1277,19 @@ async def _func(self, message: list[str], admin_player: Player) -> int: 'Removed "{}"({}) from access list (was {} with level {})'.format( targeted_player.name, targeted_player.unique_id, - targeted_player.access.name, - targeted_player.access.level, + targeted_player.admin.name, + targeted_player.admin.level, ) ) - targeted_player.access.name = "Player" - targeted_player.access.level = self.torchlight.config[ + targeted_player.admin.name = "Player" + targeted_player.admin.level = self.torchlight.config[ "AccessLevel" ]["Player"] - targeted_player.access.uniqueid = targeted_player.unique_id - self.access_manager.config_access_list[ - targeted_player.unique_id - ] = targeted_player.access + targeted_player.admin.unique_id = targeted_player.unique_id + self.access_manager.set_admin( + unique_id=targeted_player.unique_id, + admin=targeted_player.admin, + ) return 0 diff --git a/src/torchlight/Config.py b/src/torchlight/Config.py index eaae7ee..12b8956 100644 --- a/src/torchlight/Config.py +++ b/src/torchlight/Config.py @@ -2,17 +2,9 @@ import logging import os import sys -from dataclasses import dataclass from typing import Any -@dataclass -class ConfigAccess: - name: str - level: int - uniqueid: str - - class Config: def __init__( self, @@ -26,9 +18,8 @@ def __init__( os.path.join(config_folder, config_filename) ) self.config: dict[str, Any] = {} - self.Load() - def Load(self) -> int: + def load(self) -> int: try: with open(self.config_filepath) as fp: self.config = json.load(fp) diff --git a/src/torchlight/Player.py b/src/torchlight/Player.py index 6906771..44baf56 100644 --- a/src/torchlight/Player.py +++ b/src/torchlight/Player.py @@ -1,7 +1,6 @@ import logging -from torchlight.Admin import Admin -from torchlight.Config import Config, ConfigAccess +from torchlight.Sourcemod import SourcemodAdmin, SourcemodConfig class Player: @@ -9,20 +8,23 @@ def __init__( self, index: int, userid: int, - uniqueid: str, + unique_id: str, address: str, name: str, ): self.logger = logging.getLogger(self.__class__.__name__) self.index = index self.user_id = userid - self.unique_id = uniqueid + self.unique_id = unique_id self.address = address self.name = name - self.access: ConfigAccess = ConfigAccess( - name=self.name, level=0, uniqueid=self.unique_id + self.admin = SourcemodAdmin( + name=self.name, + unique_id=self.unique_id, + flag_bits=0, + groups=[], + level=0, ) - self.admin = Admin() self.storage: dict = {} self.active = False self.chat_cooldown = 0 @@ -41,56 +43,28 @@ def OnConnect(self) -> None: def OnActivate(self) -> None: self.active = True - def OnClientPostAdminCheck(self, flag_bits: int, config: Config) -> None: - self.admin._flag_bits = flag_bits + def OnClientPostAdminCheck( + self, + *, + flag_bits: int, + sourcemod_config: SourcemodConfig, + ) -> None: self.logger.info( '#{} "{}"({}) FlagBits: {}'.format( - self.user_id, self.name, self.unique_id, self.admin._flag_bits + self.user_id, self.name, self.unique_id, flag_bits ) ) - player_access = ConfigAccess( - name="Player", - level=config["AccessLevel"]["Player"], - uniqueid=self.unique_id, + self.admin.flag_bits = flag_bits + self.admin.groups = sourcemod_config.get_sourcemod_groups_by_flags( + flagbits=flag_bits ) - if self.admin.RCON() or self.admin.Root(): - player_access = ConfigAccess( - level=config["AccessLevel"]["Root"], - name="SAdmin", - uniqueid=self.unique_id, - ) - elif self.admin.Custom4(): - player_access = ConfigAccess( - level=config["AccessLevel"]["EventManager"], - name="EManager", - uniqueid=self.unique_id, - ) - elif self.admin.Ban(): - player_access = ConfigAccess( - level=config["AccessLevel"]["Admin"], - name="Admin", - uniqueid=self.unique_id, - ) - elif self.admin.Generic(): - player_access = ConfigAccess( - level=config["AccessLevel"]["DonatedAdmin"], - name="DAdmin", - uniqueid=self.unique_id, - ) - elif self.admin.Custom1(): - player_access = ConfigAccess( - level=config["AccessLevel"]["VIP"], - name="VIP", - uniqueid=self.unique_id, - ) - - if ( - player_access is not None - and self.access.level < player_access.level - ): - self.access = player_access + group = sourcemod_config.get_highest_group_level( + sm_groups=self.admin.groups + ) + if group is not None and self.admin.level < group.level: + self.admin.level = group.level def OnInfo(self, name: str) -> None: self.name = name diff --git a/src/torchlight/PlayerManager.py b/src/torchlight/PlayerManager.py index a693017..a4aa2dd 100644 --- a/src/torchlight/PlayerManager.py +++ b/src/torchlight/PlayerManager.py @@ -5,6 +5,7 @@ from torchlight.AudioManager import AudioManager from torchlight.Constants import Clients from torchlight.Player import Player +from torchlight.Sourcemod import SourcemodConfig from torchlight.Torchlight import Torchlight @@ -14,11 +15,13 @@ def __init__( torchlight: Torchlight, audio_manager: AudioManager, access_manager: AccessManager, + sourcemod_config: SourcemodConfig, ) -> None: self.logger = logging.getLogger(self.__class__.__name__) self.torchlight = torchlight self.audio_manager = audio_manager self.access_manager = access_manager + self.sourcemod_config = sourcemod_config self.audio_storage: dict[str, dict] = {} self.players: list[Player | None] = [None] * (Clients.MAXPLAYERS + 1) @@ -66,7 +69,7 @@ def Event_PlayerConnect( player = Player(index, userid, networkid, address, name) - player.access = self.access_manager.get_access(player) + player.admin = self.access_manager.get_admin(player) for unique_id, audio_stored in self.audio_storage.items(): if player.unique_id == unique_id: @@ -102,7 +105,9 @@ async def OnClientPostAdminCheckAsync(self, player: Player) -> None: flag_bits: int = ( await self.torchlight.sourcemod_api.GetUserFlagBits(player.index) )["result"] - player.OnClientPostAdminCheck(flag_bits, self.torchlight.config) + player.OnClientPostAdminCheck( + flag_bits=flag_bits, sourcemod_config=self.sourcemod_config + ) def Event_PlayerInfo( self, name: str, index: int, userid: int, networkid: str, bot: int @@ -170,7 +175,7 @@ def Event_ServerSpawn( if self.audio_manager.anti_spam.config["StopOnMapChange"]: self.audio_manager.OnDisconnect(player) player.OnDisconnect("mapchange") - player.access = self.access_manager.get_access(player) + player.admin = self.access_manager.get_admin(player) player.OnConnect() self.audio_storage[player.unique_id] = player.storage diff --git a/src/torchlight/SourceRCONClient.py b/src/torchlight/SourceRCONClient.py index 9c957e4..d43e3db 100644 --- a/src/torchlight/SourceRCONClient.py +++ b/src/torchlight/SourceRCONClient.py @@ -7,8 +7,8 @@ from typing import Any from torchlight.CommandHandler import CommandHandler -from torchlight.Config import ConfigAccess from torchlight.Player import Player +from torchlight.Sourcemod import SourcemodAdmin class SourceRCONClient: @@ -97,8 +97,12 @@ def ParsePacket(self, data_raw: bytes) -> None: "127.0.0.1", "CONSOLE", ) - player.access = ConfigAccess( - name="CONSOLE", level=9001, uniqueid="CONSOLE" + player.admin = SourcemodAdmin( + name="CONSOLE", + unique_id=player.unique_id, + level=100, + flag_bits=0, + groups=[], ) player.storage = dict( { diff --git a/src/torchlight/Sourcemod.py b/src/torchlight/Sourcemod.py new file mode 100644 index 0000000..e466d3e --- /dev/null +++ b/src/torchlight/Sourcemod.py @@ -0,0 +1,88 @@ +import copy +import json +import logging +import os +import sys +from dataclasses import dataclass +from typing import Any + +from torchlight.Config import Config + + +@dataclass +class SourcemodGroup: + name: str + level: int + flags: list[str] + + +@dataclass +class SourcemodAdmin: + name: str + unique_id: str + flag_bits: int + groups: list[SourcemodGroup] + level: int + + +class SourcemodConfig: + def __init__( + self, + config_folder: str, + config: Config, + config_filename: str = "flags.json", + ) -> None: + self.logger = logging.getLogger(self.__class__.__name__) + self.config = config + self.config_folder = os.path.abspath(config_folder) + self.config_filename = config_filename + self.config_filepath = os.path.abspath( + os.path.join(config_folder, config_filename) + ) + self.sm_flags: dict[str, Any] = {} + self.sm_groups: list[SourcemodGroup] = [] + + def Load(self) -> int: + try: + with open(self.config_filepath) as fp: + self.sm_flags = json.load(fp) + except ValueError as e: + self.logger.error(sys._getframe().f_code.co_name + " " + str(e)) + return 1 + for sm_group in self.config["SourcemodGroups"]: + self.sm_groups.append( + SourcemodGroup( + name=sm_group["name"], + level=sm_group["level"], + flags=sm_group["flags"], + ) + ) + return 0 + + def flagbits_to_flags(self, *, flagbits: int) -> list[str]: + flags: list[str] = [] + for sm_flag in self.sm_flags.values(): + bit_shifting_right_value = ord("a") - ord(sm_flag["value"]) + if flagbits & (1 << bit_shifting_right_value): + flags.append(sm_flag["value"]) + return flags + + def get_sourcemod_groups_by_flags( + self, *, flagbits: int + ) -> list[SourcemodGroup]: + groups = [] + for sm_flag in self.sm_flags.values(): + if sm_flag["value"] in self.flagbits_to_flags(flagbits=flagbits): + for sm_group in self.sm_groups: + if sm_flag["value"] in sm_group.flags: + groups.append(copy.deepcopy(sm_group)) + return groups + + def get_highest_group_level( + self, *, sm_groups: list[SourcemodGroup] + ) -> (SourcemodGroup | None): + highest_group: SourcemodGroup | None = None + for sm_group in sm_groups: + if highest_group is None or sm_group.level > highest_group.level: + highest_group = sm_group + return highest_group diff --git a/src/torchlight/Torchlight.py b/src/torchlight/Torchlight.py index da79973..dac75b6 100644 --- a/src/torchlight/Torchlight.py +++ b/src/torchlight/Torchlight.py @@ -37,7 +37,7 @@ def __init__( self.callbacks: list[tuple[str, Callable]] = [] def Reload(self) -> None: - self.config.Load() + self.config.load() self.Callback("OnReload") def AddCallback(self, cbtype: str, cbfunc: Callable) -> bool: @@ -70,7 +70,7 @@ def SayChat(self, message: str, player: Player | None = None) -> None: asyncio.ensure_future(self.sourcemod_api.CPrintToChatAll(line)) if player: - level = player.access.level + level = player.admin.level if level < self.config["AntiSpam"]["ImmunityLevel"]: cooldown = len(lines) * self.config["AntiSpam"]["ChatCooldown"] diff --git a/src/torchlight/TorchlightHandler.py b/src/torchlight/TorchlightHandler.py index 4e9d229..12c7ae3 100644 --- a/src/torchlight/TorchlightHandler.py +++ b/src/torchlight/TorchlightHandler.py @@ -7,6 +7,7 @@ from torchlight.CommandHandler import CommandHandler from torchlight.Config import Config from torchlight.PlayerManager import PlayerManager +from torchlight.Sourcemod import SourcemodConfig from torchlight.Torchlight import Torchlight from torchlight.TriggerManager import TriggerManager @@ -50,6 +51,11 @@ def Init(self) -> None: ) self.trigger_manager.Load() + self.sourcemod_config = SourcemodConfig( + config_folder=self.config.config_folder, config=self.config + ) + self.sourcemod_config.Load() + self.async_client = AsyncClient( self.loop, self.config["SMAPIServer"], @@ -63,7 +69,10 @@ def Init(self) -> None: self.audio_manager = AudioManager(self.torchlight) self.player_manager = PlayerManager( - self.torchlight, self.audio_manager, self.access_manager + self.torchlight, + self.audio_manager, + self.access_manager, + self.sourcemod_config, ) self.command_handler = CommandHandler( diff --git a/src/torchlight/cli.py b/src/torchlight/cli.py index 37e86e8..10ac5fb 100644 --- a/src/torchlight/cli.py +++ b/src/torchlight/cli.py @@ -18,6 +18,7 @@ def cli(config_folder: str) -> None: config = Config(config_folder) + config.load() logging.basicConfig( level=logging.getLevelName(config["Logging"]["level"]),