def load_languages(self): self.translations = {} tool.touch_folder(self.language_folder) for file in tool.list_file(self.language_folder, constant.LANGUAGE_FILE_SUFFIX): language = tool.remove_suffix(os.path.basename(file), constant.LANGUAGE_FILE_SUFFIX) with open(file, encoding='utf8') as f: self.translations[language] = yaml.round_trip_load(f)
def list_plugin(self, info): file_list_all = self.server.plugin_manager.get_plugin_file_list_all() file_list_disabled = self.server.plugin_manager.get_plugin_file_list_disabled() file_list_loaded = self.server.plugin_manager.get_loaded_plugin_file_name_list() file_list_not_loaded = [file_name for file_name in file_list_all if file_name not in file_list_loaded] self.send_message(info, self.t('command_manager.list_plugin.info_loaded_plugin', len(file_list_loaded))) for file_name in file_list_loaded: self.send_message( info, '§7-§r {}'.format(file_name) + SText(' [×]', color=SColor.gray) .set_click_event(SAction.run_command, '!!MCDR plugin disable {}'.format(file_name)) .set_hover_text(self.t('command_manager.list_plugin.suggest_disable', file_name)) + SText(' [▷]', color=SColor.gray) .set_click_event(SAction.run_command, '!!MCDR plugin load {}'.format(file_name)) .set_hover_text(self.t('command_manager.list_plugin.suggest_reload', file_name)) ) self.send_message(info, self.t('command_manager.list_plugin.info_disabled_plugin', len(file_list_disabled))) for file_name in file_list_disabled: file_name = tool.remove_suffix(file_name, constant.DISABLED_PLUGIN_FILE_SUFFIX) self.send_message( info, '§7-§r {}'.format(file_name) + SText(' [✔]', color=SColor.gray) .set_click_event(SAction.run_command, '!!MCDR plugin enable {}'.format(file_name)) .set_hover_text(self.t('command_manager.list_plugin.suggest_enable', file_name)) ) self.send_message(info, self.t('command_manager.list_plugin.info_not_loaded_plugin', len(file_list_not_loaded))) for file_name in file_list_not_loaded: self.send_message(info, '§7-§r {}'.format(file_name) + SText(' [✔]', color=SColor.gray) .set_click_event(SAction.run_command, '!!MCDR plugin load {}'.format(file_name)) .set_hover_text(self.t('command_manager.list_plugin.suggest_load', file_name)) )
def enable_plugin(self, file_name) -> bool: file_name = tool.format_plugin_file_name_disabled(file_name) file_path = os.path.join(self.plugin_folder, file_name) new_file_path = tool.remove_suffix(file_path, constant.DISABLED_PLUGIN_FILE_SUFFIX) if os.path.isfile(file_path): os.rename(file_path, new_file_path) return bool(self.load_plugin(os.path.basename(new_file_path))) return False
class WaterfallParser(BungeecordParser): # The logging format of waterfall server is spigot like NAME = tool.remove_suffix(os.path.basename(__file__), '.py') def parse_server_stdout(self, text): return bukkit_parser.get_parser( self.parser_manager).parse_server_stdout(text)
def __init__(self, server, file_path): self.server = server self.file_path = file_path self.file_name = os.path.basename(file_path) self.plugin_name = tool.remove_suffix(self.file_name, '.py') self.module = None self.old_module = None self.help_messages = [] self.file_hash = None
def __init__(self, server, file_path): self.server = server self.file_path = file_path self.file_name = os.path.basename(file_path) self.plugin_name = tool.remove_suffix(self.file_name, '.py') self.module = None self.old_module = None self.help_messages = [] self.file_hash = None self.loaded_modules = [] self.thread_pool = self.server.plugin_manager.thread_pool self.load_lock = self.server.plugin_manager.plugin_load_lock
class BukkitParser14(VanillaParser): # 1.14.4+ bukkit / spigot change it's console logger into vanilla like format # idk why they did this # paper is not included NAME = tool.remove_suffix(os.path.basename(__file__), '.py') def __init__(self, parser_manager): super().__init__(parser_manager) def parse_player_joined(self, info): return bukkit_parser.get_parser( self.parser_manager).parse_player_joined(info)
class CatServerParser(BukkitParser): # https://github.com/Luohuayu/CatServer # CatServer uses vanilla logging format but spigot like player joined message # And has color code around the player left message NAME = tool.remove_suffix(os.path.basename(__file__), '.py') def parse_server_stdout(self, text): return VanillaParser.parse_server_stdout(self, text) def parse_player_left(self, info): # §eSteve left the game§r return super().parse_player_left(cleaned_info(info)) def parse_player_made_advancement(self, info): # § before advancement name return super().parse_player_made_advancement(cleaned_info(info))
class ForgeParser(VanillaParser): NAME = tool.remove_suffix(os.path.basename(__file__), '.py') LOGGER_NAME_CHAR_SET = VanillaParser.LOGGER_NAME_CHAR_SET + r'.' def __init__(self, parser_manager): super().__init__(parser_manager) def parse_server_stdout(self, text): result = self._parse_server_stdout_raw(text) # [18:26:03] [Server thread/INFO] [FML]: Unloading dimension 1 # [18:26:03] [Server thread/INFO] [minecraft/DedicatedServer]: Done (9.855s)! For help, type "help" or "?" # [18:29:30] [Server thread/INFO] [minecraft/DedicatedServer]: <Steve> test time_data = re.search(r'\[[0-9]*:[0-9]*:[0-9.]*\] ', text).group() elements = time_data[1:-2].split(':') result.hour = int(elements[0]) result.min = int(elements[1]) result.sec = int(float(elements[2])) text = text.replace(time_data, '', 1) # [Server thread/INFO] [FML]: Unloading dimension 1 # [Server thread/INFO] [minecraft/DedicatedServer]: Done (9.855s)! For help, type "help" or "?" # [Server thread/INFO] [minecraft/DedicatedServer]: <Steve> test logging = re.match(r'^\[[{}]*?\] '.format(self.LOGGER_NAME_CHAR_SET), text).group() result.logging_level = re.search(r'(?<=/)\w+(?=\] )', logging).group() text = text.replace(logging, '', 1) # [FML]: Unloading dimension 1 # [minecraft/DedicatedServer]: Done (9.855s)! For help, type "help" or "?" # [minecraft/DedicatedServer]: <Steve> test matched = re.match(r'^\[[{}]*\]: '.format(self.LOGGER_NAME_CHAR_SET), text).group() text = text.replace(matched, '', 1) # Unloading dimension 1 # Done (9.855s)! For help, type "help" or "?" # <Steve> test result.player = re.match(r'<\w+> ', text) if result.player is None: result.content = text else: result.player = result.player.group()[1:-2] result.content = text.replace(f'<{result.player}> ', '', 1) return result
class BukkitParser(VanillaParser): NAME = tool.remove_suffix(os.path.basename(__file__), '.py') # Fallen_Breath[/127.0.0.1:50099] logged in with entity id 11 at ([lobby]0.7133817548136454, 4.0, 5.481879061970788) # Fake_player[local] logged in with entity id 11 at ([lobby]100.19, 22.33, 404.0) PLAYER_JOINED_PATTERN = re.compile(r'\w{1,16}\[(?:/[\d.:]+|local)\] logged in with entity id \d+ at \((\[\w+\])?[\dE\-., ]+\)') def parse_server_stdout(self, text): result = self._parse_server_stdout_raw(text) # [09:00:01 INFO]: <Steve> hi # [09:00:03 WARN]: Alex moved too quickly! # [09:00:04 INFO]: [world_nether]<Alex> hello time_data = re.search(r'\[[0-9]*:[0-9]*:[0-9]* \w*\]: ', text).group() elements = time_data[1:-2].split(':') result.hour = int(elements[0]) result.min = int(elements[1]) result.sec = int(elements[2].split(' ')[0]) result.logging_level = re.search(r'(?<= )\w+(?=\])', elements[2]).group() text = text.replace(time_data, '', 1) # <Steve> hi # Alex moved too quickly! # [world_nether]<Alex> hello dim = re.match(r'\[\w+\](?=<\w+> )', text) if dim is not None: text = text.replace(dim.group(), '', 1) # <Steve> hi # Alex moved too quickly! # <Alex> hello result.player = re.match(r'<\w+> ', text) if result.player is None: result.content = text else: result.player = result.player.group()[1:-2] result.content = text.replace(f'<{result.player}> ', '', 1) return result
class BasicParser(AbstractParser): NAME = tool.remove_suffix(os.path.basename(__file__), '.py') def parse_server_stdout(self, text): return self._parse_server_stdout_raw(text) def parse_player_joined(self, info): return None def parse_player_left(self, info): return None def parse_player_made_advancement(self, info): return None def parse_server_startup_done(self, info): return False def parse_rcon_started(self, info: Info): return False def parse_server_stopping(self, info: Info): return False
class AbstractParser: NAME = tool.remove_suffix(os.path.basename(__file__), '.py') STOP_COMMAND = '' def __init__(self, parser_manager): self.parser_manager = parser_manager @staticmethod def _parse_server_stdout_raw(text: str): """ Base raw parsing, returns an almost un-parsed Info instance Use as the first step of the parsing process, or as the return value if you give up parsing this text :param str text: A line of the server stdout to be parsed :return: An Info instance :rtype: Info """ if type(text) is not str: raise TypeError('The text to parse should be a string') result = Info() result.source = InfoSource.SERVER result.content = result.raw_content = text return result def pre_parse_server_stdout(self, text): """ Remove useless things like console color code in the text before parse :param str text: A line of the server stdout to be parsed :return: Trimmed line :rtype: str """ return tool.clean_console_color_code(text) @staticmethod def parse_console_command(text): """ Base parsing, returns an almost un-parsed Info instance Don't use this unless :param str text: A line of the server stdout to be parsed :return: An Info instance :rtype: Info """ if type(text) is not str: raise TypeError('The text to parse should be a string') result = Info() result.raw_content = text t = time.localtime(time.time()) result.hour = t.tm_hour result.min = t.tm_min result.sec = t.tm_sec result.content = text result.source = InfoSource.CONSOLE return result def parse_death_message(self, info): """ Check if the info matches a death message and return a bool It will search all death message format from itself to its all base class and try to match the info with them :param Info info: The info instance that will be checked :return: If the info matches a death message :rtype: bool """ if info.is_user: return False re_list = self.parser_manager.get_death_message_list(type(self)) for re_exp in re_list: if re.fullmatch(re_exp, info.content): return True return False # ---------------------------------------- # Things that need to be implemented # ---------------------------------------- def parse_server_stdout(self, text): """ Main parsing operation. Parse a string from the stdout of the server It may raise any exceptions if the format of the input string is not correct :param str text: A line of the server stdout to be parsed :return: An parsed Info instance :rtype: Info """ raise NotImplementedError() def parse_player_joined(self, info): """ Check if the info indicating a player joined message If it is, returns the name of the player, otherwise returns None :param Info info: The info instance that will be checked :return: The name of the player or None :rtype: str or None """ raise NotImplementedError() def parse_player_left(self, info): """ Check if the info indicates a player left message If it is, returns the name of the player, otherwise returns None :param Info info: The info instance that will be checked :return: The name of the player or None :rtype: str or None """ raise NotImplementedError() def parse_player_made_advancement(self, info): """ Check if the info indicates a player made advancement message If it is, returns a tuple of two str: the name of the player and the name of the advancement, otherwise returns None :param Info info: The info instance that will be checked :return: (player_name, advancement_name), or None :rtype: tuple[str, str] or None """ raise NotImplementedError() def parse_server_startup_done(self, info): """ Check if the info indicates a server startup message and return a bool :param Info info: The info instance that will be checked :return: If the info indicates a server startup message :rtype: bool """ raise NotImplementedError() def parse_rcon_started(self, info: Info): """ Check if rcon has started and return a bool :param Info info: The info instance that will be checked :return: If rcon has started :rtype: bool """ raise NotImplementedError() def parse_server_stopping(self, info: Info): """ Check if the server is stopping and return a bool :param Info info: The info instance that will be checked :return: If the server is stopping :rtype: bool """ raise NotImplementedError()
class VanillaParser(AbstractParser): NAME = tool.remove_suffix(os.path.basename(__file__), '.py') PLAYER_JOINED_PATTERN = re.compile(r'\w{1,16}\[(?:/[\d.:]+|local)\] logged in with entity id \d+ at \([\dE\-., ]+\)') STOP_COMMAND = 'stop' LOGGER_NAME_CHAR_SET = r'\w /\#\-' def parse_server_stdout(self, text): result = self._parse_server_stdout_raw(text) # [09:00:00] [Server thread/INFO]: <Steve> Hello # [09:00:01] [Server thread/WARN]: Can't keep up! time_data = re.search(r'\[[0-9]*:[0-9]*:[0-9]*\] ', text).group() elements = time_data[1:-2].split(':') result.hour = int(elements[0]) result.min = int(elements[1]) result.sec = int(elements[2]) text = text.replace(time_data, '', 1) # [Server thread/INFO]: <Steve> Hello # [Server thread/WARN]: Can't keep up! logging = re.match(r'^\[[{}]*?\]: '.format(self.LOGGER_NAME_CHAR_SET), text).group() result.logging_level = re.search(r'(?<=/)\w+(?=\]: )', logging).group() text = text.replace(logging, '', 1) # <Steve> Hello # Can't keep up! result.player = re.match(r'<(.*?)> ', text) if result.player is None: result.content = text else: result.player = result.player.group()[1:-2] result.content = text.replace(f'<{result.player}> ', '', 1) result.player = re.sub('\[(.*?)+\]','',result.player) return result def parse_player_joined(self, info): # Steve[/127.0.0.1:9864] logged in with entity id 131 at (187.2703, 146.79014, 404.84718) if not info.is_user and self.PLAYER_JOINED_PATTERN.fullmatch(info.content): return info.content.split('[', 1)[0] return None def parse_player_left(self, info): # Steve left the game if not info.is_user and re.fullmatch(r'\w{1,16} left the game', info.content): return info.content.split(' ')[0] return None def parse_player_made_advancement(self, info): # Steve has made the advancement [Stone Age] # Steve has completed the challenge [Uneasy Alliance] # Steve has reached the goal [Sky's the Limit] if info.is_user: return None for action in ['made the advancement', 'completed the challenge', 'reached the goal']: match = re.fullmatch(r'\w{1,16} has %s \[.+\]' % action, info.content) if match is not None: player, rest = info.content.split(' ', 1) adv = re.search(r'(?<=%s \[).+(?=\])' % action, rest).group() return player, adv return None def parse_server_startup_done(self, info): # 1.13+ Done (3.500s)! For help, type "help" # 1.13- Done (3.500s)! For help, type "help" or "?" if info.is_user: return False match = re.fullmatch(r'Done \([0-9.]*s\)! For help, type "help"( or "\?")?', info.content) return match is not None def parse_rcon_started(self, info): # RCON running on 0.0.0.0:25575 if info.is_user: return False match = re.fullmatch(r'RCON running on [\w.]+:\d+', info.content) return match is not None def parse_server_stopping(self, info): # Stopping server if info.is_user: return False return info.content == 'Stopping server'
def list_plugin(self, info): file_list_all = self.server.plugin_manager.get_plugin_file_list_all() file_list_disabled = self.server.plugin_manager.get_plugin_file_list_disabled( ) file_list_loaded = self.server.plugin_manager.get_loaded_plugin_file_name_list( ) file_list_not_loaded = [ file_name for file_name in file_list_all if file_name not in file_list_loaded ] self.send_message( info, self.t('command_manager.list_plugin.info_loaded_plugin', len(file_list_loaded))) for file_name in file_list_loaded: texts = RText('§7-§r {}'.format(file_name)) if self.display_buttons(info): texts += RTextList( RText(' [×]', color=RColor.gray).c( RAction.run_command, '!!MCDR plugin disable {}'.format(file_name)).h( self.t( 'command_manager.list_plugin.suggest_disable', file_name)), RText(' [↻]', color=RColor.gray).c( RAction.run_command, '!!MCDR plugin load {}'.format(file_name)).h( self.t( 'command_manager.list_plugin.suggest_reload', file_name))) self.send_message(info, texts) self.send_message( info, self.t('command_manager.list_plugin.info_disabled_plugin', len(file_list_disabled))) for file_name in file_list_disabled: file_name = tool.remove_suffix( file_name, constant.DISABLED_PLUGIN_FILE_SUFFIX) texts = RText('§7-§r {}'.format(file_name)) if self.display_buttons(info): texts += (RText(' [✔]', color=RColor.gray).c( RAction.run_command, '!!MCDR plugin enable {}'.format(file_name)).h( self.t('command_manager.list_plugin.suggest_enable', file_name))) self.send_message(info, texts) self.send_message( info, self.t('command_manager.list_plugin.info_not_loaded_plugin', len(file_list_not_loaded))) for file_name in file_list_not_loaded: texts = RText('§7-§r {}'.format(file_name)) if self.display_buttons(info): texts += (RText(' [✔]', color=RColor.gray).c( RAction.run_command, '!!MCDR plugin load {}'.format(file_name)).h( self.t('command_manager.list_plugin.suggest_load', file_name))) self.send_message(info, texts)
class BungeecordParser(AbstractParser): NAME = tool.remove_suffix(os.path.basename(__file__), '.py') STOP_COMMAND = 'end' def __init__(self, parser_manager): super().__init__(parser_manager) def parse_server_stdout(self, text): result = self._parse_server_stdout_raw(text) # 09:00:02 [信息] Listening on /0.0.0.0:25565 # 09:00:01 [信息] [Steve] -> UpstreamBridge has disconnected time_data = re.search(r'[0-9]*:[0-9]*:[0-9]* ', text).group() elements = time_data[0:-1].split(':') result.hour = int(elements[0]) result.min = int(elements[1]) result.sec = int(elements[2]) text = text.replace(time_data, '', 1) result.logging_level = text.split(' ')[0][1:-1] # [信息] Listening on /0.0.0.0:25565 # [信息] [Steve] -> UpstreamBridge has disconnected text = text.replace(re.match(r'\[\w+?\] ', text).group(), '', 1) # Listening on /0.0.0.0:25565 # [Steve] -> UpstreamBridge has disconnected result.content = text return result def pre_parse_server_stdout(self, text): text = super().pre_parse_server_stdout(text) match = re.match(r'>*\r', text) if match is not None: text = text.replace(match.group(), '', 1) return text def parse_player_joined(self, info): # [Steve,/127.0.0.1:3631] <-> InitialHandler has connected if not info.is_user and re.fullmatch( r'\[\w{1,16},/[0-9.]+:[0-9]+\] <-> InitialHandler has connected', info.content): return info.content[1:].split(',/')[0] return None def parse_player_left(self, info): # [Steve] -> UpstreamBridge has disconnected if not info.is_user and re.fullmatch( r'\[\w{1,16}\] -> UpstreamBridge has disconnected', info.content): return info.content[1:].split('] -> ')[0] return None def parse_server_startup_done(self, info): # Listening on /0.0.0.0:25577 return not info.is_user and re.fullmatch( r'Listening on /[0-9.]+:[0-9]+', info.content) is not None def parse_rcon_started(self, info): return self.parse_server_startup_done(info) def parse_server_stopping(self, info) -> bool: # Closing listener [id: 0x3acae0b0, L:/0:0:0:0:0:0:0:0:25565] return not info.is_user and re.fullmatch( r'Closing listener \[id: .+, L:[\d:/]+\]', info.content) is not None def parse_death_message(self, info: Info) -> bool: return False def parse_player_made_advancement(self, info: Info): return None