diff --git a/.gitignore b/.gitignore index dfa51c3..e4da460 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ seen.json .vscode/settings.json .idea + +*.mcdr diff --git a/lang/en_us.yml b/lang/en_us.yml index 7150b58..870939a 100644 --- a/lang/en_us.yml +++ b/lang/en_us.yml @@ -28,6 +28,7 @@ mcd_seen.text.top_normal: §dplayer only§r mcd_seen.text.top_bot: §5bot only§r mcd_seen.text.top_merge: §7all players§r/§emerged§r mcd_seen.text.top_all: §7all players§r +mcd_seen.text.reloaded: Plugin reloaded # Hover texts mcd_seen.hover.help_msg_suggest: "Click to fill {}" @@ -35,10 +36,10 @@ mcd_seen.hover.query_player: "Click to query player {}" mcd_seen.hover.show_help: "Click to show help" # Format texts -mcd_seen.fmt.delta_time: "sec min hrs day" -mcd_seen.fmt.seen_top: "Here are the {} players §cofflined§r for the longest time({}):" -mcd_seen.fmt.seen_top_full: "Here are all the players' offline time data({})" -mcd_seen.fmt.liver_top: "Here are the players §acurrently online§r({}):" +mcd_seen.fmt.time_seen: "sec min hrs day" +mcd_seen.fmt.seen_top: "Here are the {num} players §cofflined§r for the longest time({arg}):" +mcd_seen.fmt.seen_top_full: "Here are all the players' offline time data({arg})" +mcd_seen.fmt.liver_top: "Here are the players §acurrently online§r({arg}):" # Error texts mcd_seen.error.player_data_not_found: Player data not found! Click here for help diff --git a/lang/zh_cn.yml b/lang/zh_cn.yml index 3592076..da37264 100644 --- a/lang/zh_cn.yml +++ b/lang/zh_cn.yml @@ -28,6 +28,7 @@ mcd_seen.text.top_normal: §d仅真人玩家§r mcd_seen.text.top_bot: §5仅假人§r mcd_seen.text.top_merge: §7所有玩家§r/§e合并显示§r mcd_seen.text.top_all: §7所有玩家§r +mcd_seen.text.reloaded: 插件已重载 # Hover texts mcd_seen.hover.help_msg_suggest: "点击以填入§7{}§r" @@ -35,10 +36,10 @@ mcd_seen.hover.query_player: "点击以查询玩家§e{}§r的数据" mcd_seen.hover.show_help: "点击以获取插件帮助信息" # Format texts -mcd_seen.fmt.delta_time: "秒 分 小时 天" -mcd_seen.fmt.seen_top: "摸鱼榜前{}的§c鸽子§r({})如下: " -mcd_seen.fmt.seen_top_full: "摸鱼榜全部§c鸽子§r({})如下: " -mcd_seen.fmt.liver_top: "当前在线的§a肝帝§r({})如下: " +mcd_seen.fmt.time_seen: "秒 分 小时 天" +mcd_seen.fmt.seen_top: "摸鱼榜前§6{num}§r的§c鸽子§r({arg})如下: " +mcd_seen.fmt.seen_top_full: "摸鱼榜全部§c鸽子§r({arg})如下: " +mcd_seen.fmt.liver_top: "当前在线的§a肝帝§r({arg})如下: " # Error texts mcd_seen.error.player_data_not_found: 没有该玩家的数据 diff --git a/mcd_seen/__init__.py b/mcd_seen/__init__.py index 6ae536c..e61ce42 100644 --- a/mcd_seen/__init__.py +++ b/mcd_seen/__init__.py @@ -2,8 +2,7 @@ from mcdreforged.api.types import Info, PluginServerInterface from mcdreforged.api.decorator import new_thread -from mcd_seen.constants import SEEN_PREFIX, META -from mcd_seen.utils import verify_player_name, bot_name, tr, logger +from mcd_seen.utils import verify_player_name, bot_name, tr, logger, psi from mcd_seen.storage import storage, bot_list from mcd_seen.config import config from mcd_seen.interface import register_command @@ -29,13 +28,18 @@ def on_server_stop(*args, **kwargs): storage.correct([]) -@new_thread(META.name + '_PluginLoad') +def on_unload(*args, **kwargs): + logger.unset_file() + + +@new_thread(psi.get_self_metadata().name + '_PluginLoad') def warn_first_load(): logger.warning('Load Seen plugin when server is empty is suggested to make sure all the datas are right') def on_load(server: PluginServerInterface, prev_module): - server.register_help_message(SEEN_PREFIX, tr('mcd_seen.text.reg_help_msg')) + for prefix in config.seen_prefix: + server.register_help_message(prefix, tr('mcd_seen.text.reg_help_msg')) register_command(server) if prev_module is not None: try: diff --git a/mcd_seen/config.py b/mcd_seen/config.py index b7f3c6a..cd3f298 100644 --- a/mcd_seen/config.py +++ b/mcd_seen/config.py @@ -1,17 +1,64 @@ import json -from mcd_seen.constants import CONFIG_FILE - +from typing import Union, List from mcdreforged.api.utils import Serializable from mcdreforged.api.types import ServerInterface +from mcd_seen.constants import CONFIG_FILE + + +psi = ServerInterface.get_instance().as_plugin_server_interface() + class Config(Serializable): + primary_prefix: Union[str, List[str]] = '!!seen' + primary_rank_prefix: Union[str, List[str]] = '!!seen-top' + secondary_rank_prefix: Union[str, List[str]] = '!!liver-top' seen_top_max: int = 10 player_prior_in_merge: bool = True log_seens: bool = True identify_bot: bool = True bot_list_delay: float = 0.3 + verbosity: bool + debug_commands: bool + debug_prefixes: Union[str, List[str]] + + @staticmethod + def get_iterable(original: Union[str, List[str]]) -> List[str]: + if isinstance(original, str): + return [original] + return original + + @property + def seen_prefix(self): + return self.get_iterable(self.primary_prefix) + + @property + def seen_top_prefix(self): + return self.get_iterable(self.primary_rank_prefix) + + @property + def liver_top_prefix(self): + return self.get_iterable(self.secondary_rank_prefix) + + @property + def debug_prefix(self): + return self.get_iterable(self.serialize().get('debug_prefixes', '!!liver')) + + @property + def prefixes(self): + result = [] + for item in [self.seen_prefix, self.seen_top_prefix, self.liver_top_prefix]: + result += item + return result + + @property + def verbose_mode(self): + return self.serialize().get('verbosity', False) + + @property + def debug(self): + return self.serialize().get('debug_commands', False) def save(self) -> None: with open(CONFIG_FILE, 'w', encoding='UTF-8') as f: @@ -19,8 +66,11 @@ def save(self) -> None: @classmethod def load(cls) -> 'Config': - return ServerInterface.get_instance().as_plugin_server_interface().load_config_simple( - CONFIG_FILE, default_config=cls.get_default().serialize(), in_data_folder=False, echo_in_console=True, + return psi.load_config_simple( + CONFIG_FILE, + default_config=cls.get_default().serialize(), + in_data_folder=False, + echo_in_console=True, target_class=cls ) diff --git a/mcd_seen/constants.py b/mcd_seen/constants.py index 69b8b05..197e222 100644 --- a/mcd_seen/constants.py +++ b/mcd_seen/constants.py @@ -1,11 +1,4 @@ import os -from mcdreforged.api.types import ServerInterface - -# Command prefixes -SEEN_PREFIX = '!!seen' -SEEN_TOP_PREFIX = '!!seen-top' -LIVER_TOP_PREFIX = '!!liver-top' -DEBUG_PREFIX = '!!liver' def ensure(folder: str): @@ -18,11 +11,7 @@ def ensure(folder: str): DATA_FOLDER = ensure('config/seen') CONFIG_FILE = os.path.join(DATA_FOLDER, 'config.json') SEENS_FILE = os.path.join(DATA_FOLDER, 'seen.json') -LOG_FILE = os.path.join(DATA_FOLDER, 'player_seens.log') +LOG_FILE = os.path.join(DATA_FOLDER, 'logs', 'seen.log') SEENS_PATH_OLD = ['seen.json', 'config/seen.json'] - -# Plugin Metadata -META = ServerInterface.get_instance().get_plugin_metadata('mcd_seen') - -# Debug mode -DEBUG_MODE = False +OLD_LOG_FILE = os.path.join(DATA_FOLDER, 'player_seens.log') +NEW_LOG_PATH = os.path.join(DATA_FOLDER, 'logs', 'old_seens.log') diff --git a/mcd_seen/interface.py b/mcd_seen/interface.py index fcff811..a45769e 100644 --- a/mcd_seen/interface.py +++ b/mcd_seen/interface.py @@ -1,15 +1,13 @@ -import re +from typing import Callable, Any, List, Union, Optional -from mcdreforged.api.types import CommandSource, PluginServerInterface from mcdreforged.api.command import * -from mcdreforged.api.utils import Serializable from mcdreforged.api.rtext import * -from typing import Callable, Any, List, Union, Optional +from mcdreforged.api.types import CommandSource, PluginServerInterface +from mcdreforged.api.utils import Serializable -from mcd_seen.storage import storage, PlayerSeen -from mcd_seen.constants import * from mcd_seen.config import config -from mcd_seen.utils import tr, delta_time, formatted_time, bot_name +from mcd_seen.storage import storage, PlayerSeen +from mcd_seen.utils import tr, delta_time, bot_name, psi, ntr, htr, fmt_time_tr TOP_OPTIONS = { '-bot': 'bot', @@ -36,7 +34,7 @@ def parse(cls, arg_string: Optional[str], liver: bool = False): data[mode] = True args.remove(arg) data = cls.deserialize(data) - # Confict check + # Conflict check if [data.all, data.merge, data.bot].count(True) > 1 or (data.full and liver) or (len(args) != 0): raise IllegalArgument(f'Illegal argument: {arg_string}', 1) if liver and not data.get_all: @@ -55,7 +53,12 @@ def text(self): ret.append(tr(f'text.top_{o}')) if len(ret) == 0: ret.append(tr('text.top_normal')) - return '/'.join(ret).strip('/') + return RTextBase.join('/', ret) + + +def reload_self(source: CommandSource): + psi.reload_plugin(psi.get_self_metadata().id) + source.reply(tr('text.reloaded')) def register_command(server: PluginServerInterface): @@ -66,25 +69,31 @@ def exe(func: Union[Callable[[CommandSource, str], Any], Callable[[CommandSource # !!seen server.register_command( - Literal(SEEN_PREFIX).on_child_error(CommandError, cmd_error, handled=True).runs(show_help).then( + Literal(config.seen_prefix).on_child_error( + CommandError, cmd_error, handled=True).runs(show_help).then( + Literal('reload').runs(reload_self) + ).then( QuotableText('player').runs(exe(seen)) ) ) # !!seen-top server.register_command( - Literal(SEEN_TOP_PREFIX).on_child_error(CommandError, cmd_error, handled=True).runs(exe(seen_top, True)).then( + Literal(config.seen_top_prefix).on_child_error( + CommandError, cmd_error, handled=True).runs(exe(seen_top, True)).then( GreedyText('exarg').runs(exe(seen_top)) ) ) # !!liver server.register_command( - Literal(LIVER_TOP_PREFIX).on_child_error(CommandError, cmd_error, handled=True).runs(exe(liver_top, True)).then( + Literal(config.liver_top_prefix).on_child_error( + CommandError, cmd_error, handled=True).runs(exe(liver_top, True)).then( QuotableText('exarg').runs(exe(liver_top)) ) ) - if DEBUG_MODE: + if config.debug: server.register_command( - Literal(DEBUG_PREFIX).requires(lambda src: src.has_permission(4), lambda: 'Permission denied').then( + Literal(config.debug_prefix).requires( + lambda src: src.has_permission(4), lambda: 'Permission denied').then( Literal('remove').then( GreedyText('players').runs(exe(__remove_player_data)) ) @@ -93,49 +102,51 @@ def exe(func: Union[Callable[[CommandSource, str], Any], Callable[[CommandSource def show_help(source: CommandSource): - help_message = tr( - 'help_msg', SEEN_PREFIX, SEEN_TOP_PREFIX, LIVER_TOP_PREFIX, META.name, str(META.version) - ).strip().splitlines() - help_msg_rtext = '' - for line in help_message: - if help_msg_rtext != '': - help_msg_rtext += '\n' - for PREFIX in [SEEN_PREFIX, SEEN_TOP_PREFIX, LIVER_TOP_PREFIX]: - result = re.search(r'(?<=§7){}[\S ]*?(?=§)'.format(PREFIX), line) - if result is not None: - break - if result is not None: - cmd = result.group().strip() + ' ' - help_msg_rtext += RText(line).c(RAction.suggest_command, cmd).h( - tr("hover.help_msg_suggest", cmd.strip())) - else: - help_msg_rtext += line - source.reply(help_msg_rtext) + meta = psi.get_self_metadata() + msg = htr( + 'help_msg', + config.seen_prefix[0], + config.seen_top_prefix[0], + config.liver_top_prefix[0], + meta.name, + str(meta.version) + ) + source.reply(msg) # Text layout def top(top_players: List[PlayerSeen], prefix: Union[RTextBase, str]): - ret, num = RTextList(prefix), 1 + ret, num = [prefix], 1 for p in top_players: - ret.append(f'\n{num}. ', seen_format(p)) + ret.append(RTextList(f'{num}. ', seen_format(p))) num += 1 - return ret + return RTextBase.join('\n', ret) def seen_format(player: PlayerSeen): - ret = '' + return tr('text', player=player).set_translator(seen_fmt_tr) + + +def seen_fmt_tr(translation_key: str, player: PlayerSeen, language: Optional[str] = None, allow_failure: bool = True): + def ttr(key: str, *args, **kwargs): + return ntr(f'{translation_key}.{key}', *args, language=language, allow_failure=allow_failure, **kwargs) + ret = [] # Bot/Player - ret += '§{}{}§e'.format('5' if player.is_bot else 'd', - tr(f'text.{"bot" if player.is_bot else "player"}').capitalize()) + color = '§5' if player.is_bot else '§d' + ret.append(f"{color}{ttr('bot' if player.is_bot else 'player').capitalize()}§r") # - ret += f' §e{player.actual_name}§r ' + ret.append(f'§e{player.actual_name}§r') # has been online/offline for - ret += tr(f'text.{"bot_liver" if player.is_bot else "player_liver"}') if player.online else tr('text.seen') + ret.append(ttr('bot_liver' if player.is_bot else 'player_liver') if player.online else ttr('seen')) # sec min hrs day - ret += formatted_time(delta_time(player.target)) - return RText(ret).h(tr('hover.query_player', player.actual_name)).c( - RAction.run_command, '{} {}'.format(SEEN_PREFIX, player.actual_name) + ret.append(fmt_time_tr( + 'mcd_seen.fmt.time_seen', t=delta_time(player.target), language=language, allow_failure=allow_failure)) + + ret = ' '.join(ret) + ret = RText(ret).h(tr('hover.query_player', player.actual_name)).c( + RAction.run_command, '{} {}'.format(config.seen_prefix[0], player.actual_name) ) + return ret def seen(source: CommandSource, player: str): @@ -163,7 +174,7 @@ def seen_top(source: CommandSource, exarg: str = None, liver: bool = False): # -full sorted_list = sorted_list if args.full else sorted_list[:config.seen_top_max] # get prefix - prefix = tr(f'fmt.seen_top{"_full" if args.full else ""}', config.seen_top_max, args.text) + prefix = tr(f'fmt.seen_top{"_full" if args.full else ""}', num=config.seen_top_max, arg=args.text) if liver: prefix = tr('fmt.liver_top', args.text) @@ -176,10 +187,8 @@ def liver_top(source: CommandSource, exarg: str = None): def cmd_error(source: CommandSource): source.reply( - RText( - tr('mcd_seen.error.cmd_error'), color=RColor.red - ).c( - RAction.run_command, SEEN_PREFIX + tr('mcd_seen.error.cmd_error').set_color(color=RColor.red).c( + RAction.run_command, config.seen_prefix[0] ).h( tr('mcd_seen.hover.show_help') ) @@ -188,10 +197,8 @@ def cmd_error(source: CommandSource): def player_data_not_found(source: CommandSource): source.reply( - RText( - tr('mcd_seen.error.player_data_not_found'), color=RColor.red - ).c( - RAction.run_command, SEEN_PREFIX + tr('mcd_seen.error.player_data_not_found').set_color(color=RColor.red).c( + RAction.run_command, config.seen_prefix[0] ).h( tr('mcd_seen.hover.show_help') ) diff --git a/mcd_seen/storage.py b/mcd_seen/storage.py index 45148da..a3dfc35 100644 --- a/mcd_seen/storage.py +++ b/mcd_seen/storage.py @@ -8,8 +8,8 @@ from mcdreforged.api.decorator import new_thread from mcdreforged.api.utils import Serializable -from mcd_seen.constants import SEENS_FILE, META, SEENS_PATH_OLD -from mcd_seen.utils import now_time, log_seen, logger, bot_name, is_bot +from mcd_seen.constants import SEENS_FILE, SEENS_PATH_OLD +from mcd_seen.utils import now_time, log_seen, logger, bot_name, is_bot, psi from mcd_seen.config import config bot_list = [] @@ -62,7 +62,7 @@ class SeenStorage: def __init__(self): self.data = {} # type: Dict[str, PlayerSeen] - @new_thread(META.name + '_PlayerJoin') + @new_thread(psi.get_self_metadata().name + '_PlayerJoin') def player_joined(self, name: str, save=True): self[name].join() log_seen(f'Player {name} joined the game') @@ -72,7 +72,7 @@ def player_joined(self, name: str, save=True): if save: self.save() - @new_thread(META.name + '_PlayerLeft') + @new_thread(psi.get_self_metadata().name + '_PlayerLeft') def player_left(self, name: str, save=True): if bot_name(name) in bot_list: name = bot_name(name) @@ -82,7 +82,7 @@ def player_left(self, name: str, save=True): if save: self.save() - @new_thread(META.name + '_Debug') + @new_thread(psi.get_self_metadata().name + '_Debug') def debug_remove(self, players: Iterable[str]): removed = [] for p in players: @@ -168,7 +168,7 @@ def merge(ite: Iterable[PlayerSeen]): def get(self, name: str) -> Optional[PlayerSeen]: return self.lower_data.get(name.lower()) - @new_thread(META.id + '_DataCorrect') + @new_thread(psi.get_self_metadata().id + '_DataCorrect') def correct(self, player_list: List[str]): for s in self.data.values(): if s.online and s.actual_name not in player_list: diff --git a/mcd_seen/utils.py b/mcd_seen/utils.py index 37418dc..5fd71cd 100644 --- a/mcd_seen/utils.py +++ b/mcd_seen/utils.py @@ -1,52 +1,46 @@ -import logging -import time -import types +import os.path import re -import os +import time +from typing import Optional, Union -from typing import Optional -from mcdreforged.api.types import MCDReforgedLogger, PluginServerInterface from mcdreforged.api.rtext import * +from mcdreforged.api.types import MCDReforgedLogger, ServerInterface -from mcd_seen.constants import LOG_FILE, META, DEBUG_MODE from mcd_seen.config import config +from mcd_seen.constants import LOG_FILE, NEW_LOG_PATH, OLD_LOG_FILE + +TextType = Union[str, RText] +psi = ServerInterface.get_instance().as_plugin_server_interface() class SeenLogger(MCDReforgedLogger): - __global_instance = None # type: Optional[PluginServerInterface] + __global_instance = None # type: Optional[SeenLogger] + __debug = False def debug(self, *args, **kwargs): - if DEBUG_MODE: + if self.__debug: super(MCDReforgedLogger, self).debug(*args, **kwargs) - def set_file(self, file_name: str): - if self.file_handler is not None: - self.removeHandler(self.file_handler) - if not os.path.isfile(LOG_FILE): - with open(LOG_FILE, 'w') as f: - f.write('') - self.file_handler = logging.FileHandler(file_name, encoding='UTF-8') - self.file_handler.setFormatter(self.FILE_FMT) - self.addHandler(self.file_handler) - - @classmethod - def inject(cls, server: PluginServerInterface) -> None: - server.logger.set_file = types.MethodType(cls.set_file, server.logger) - server.logger.debug = types.MethodType(cls.set_file, server.logger) - server.logger.set_file(LOG_FILE) - cls.__global_instance = server - @classmethod - def get_server(cls) -> PluginServerInterface: - if cls.__global_instance is None: - cls.inject(PluginServerInterface.get_instance().as_plugin_server_interface()) - return cls.__global_instance + def set_verbosity(cls): + cls.__debug = config.verbose_mode @classmethod def get_instance(cls) -> MCDReforgedLogger: if cls.__global_instance is None: - cls.inject(PluginServerInterface.get_instance().as_plugin_server_interface()) - return cls.__global_instance.logger + cls.set_verbosity() + cls.__global_instance = cls(plugin_id=psi.get_self_metadata().id) + old_converted = False + try: + if os.path.isfile(OLD_LOG_FILE): + os.rename(OLD_LOG_FILE, NEW_LOG_PATH) + old_converted = True + except Exception as exc: + cls.__global_instance.warning(f'Move old logs to new place failed: {str(exc)}') + cls.__global_instance.set_file(LOG_FILE) + if old_converted: + cls.__global_instance.info('Moved old log file to new place') + return cls.__global_instance logger = SeenLogger.get_instance() @@ -66,17 +60,98 @@ def verify_player_name(name: str) -> bool: return re.fullmatch(r'\w+', name) is not None -def tr(key: str, *fmt, lang: str = None): - if not key.startswith(f'{META.id}.'): - key = f'{META.id}.{key.strip(".")}' - return SeenLogger.get_server().tr(key, *fmt, language=lang) - - -def formatted_time(t: int or str): +def ntr(translation_key: str, *args, language: Optional[str] = None, + allow_failure: bool = True, **kwargs) -> TextType: + """ + Directly translate your keys to text + :param translation_key: Your translation key + :param args: Format args + :param language: Required language specified + :param allow_failure: Allow failure to be thrown + :param kwargs: Format kwargs + :return: Your message text that can be processed by MCDReforged + """ + try: + return psi.tr( + translation_key, *args, language=language, allow_failure=False, **kwargs + ) + except (KeyError, ValueError): + fallback_language = psi.get_mcdr_language() + try: + if fallback_language == 'en_us': + raise KeyError(translation_key) + return psi.tr( + translation_key, *args, language='en_us', allow_failure=allow_failure, **kwargs + ) + except (KeyError, ValueError): + languages = [] + for item in (language, fallback_language, 'en_us'): + if item not in languages: + languages.append(item) + languages = ', '.join(languages) + if allow_failure: + logger.error(f'Error translate text "{translation_key}" to language {languages}') + else: + raise KeyError(f'Translation key "{translation_key}" not found with language {languages}') + + +def tr(translation_key: str, *args, with_prefix=True, **kwargs) -> RTextMCDRTranslation: + """ + Return a translation object + :param translation_key: Your translation key + :param args: Format args + :param with_prefix: Auto fill translation key prefix + :param kwargs: Format kwargs + :return: Your message text that can be processed by MCDReforged + """ + plugin_id = psi.get_self_metadata().id + if with_prefix and not translation_key.startswith(plugin_id): + translation_key = f"{plugin_id}.{translation_key}" + return psi.rtr(translation_key, *args, **kwargs).set_translator(ntr) + + +def htr(translation_key: str, *args, **kwargs) -> RTextMCDRTranslation: + """ + Magic(xD)! Translate your help message to an advanced rich text + :param translation_key: Your translation key + :param args: Format args + :param kwargs: Format kwargs + :return: Your message text that can be processed by MCDReforged + """ + + def __get_regex_result(line: str) -> Optional["re.Match"]: + suggest_pattern = r'(?<=§7){}[\S ]*?(?=§)' + for prefix in config.prefixes: + result = re.search(suggest_pattern.format(prefix), line) + if result is not None: + return result + return None + + def __htr(key: str, *inner_args, language: Optional[str] = None, **inner_kwargs): + original, processed = ntr(key, *inner_args, language=language, **inner_kwargs), [] + if not isinstance(original, str): + return key + for line in original.splitlines(): + result = __get_regex_result(line) + action = RAction.suggest_command + hover = "hover.help_msg_suggest" + if result is not None: + command = result.group() + ' ' + processed.append(RText(line).c(action, command).h(tr(hover, command))) + else: + processed.append(line) + return RTextBase.join('\n', processed) + + return tr(translation_key, *args, **kwargs).set_translator(__htr) + + +def fmt_time_tr(translation_key: str, t: Union[int, str], language: Optional[str] = None, + allow_failure: bool = True) -> str: t = int(t) values = [] - units = tr("mcd_seen.fmt.delta_time").split(' ') + units = ntr(translation_key, language=language, allow_failure=allow_failure).split(' ') scales = [60, 60, 24] + for scale in scales: value = t % scale values.append(value) @@ -88,12 +163,13 @@ def formatted_time(t: int or str): # Time large enough values.append(t) - s = "" + s = [] for i in range(len(values)): value = values[i] unit = units[i] - s = "{v} {u} ".format(v=value, u=unit) + s - return f'§6{s.strip()}' + s.append("{v} {u}".format(v=value, u=unit)) + s.reverse() + return '§6' + ' '.join(s) + '§r' def now_time() -> int: diff --git a/mcdreforged.plugin.json b/mcdreforged.plugin.json index b804b50..c918ce0 100644 --- a/mcdreforged.plugin.json +++ b/mcdreforged.plugin.json @@ -1,6 +1,6 @@ { "id": "mcd_seen", - "version": "1.1.1", + "version": "1.2.0-alpha.1", "name": "Seen", "description": "Show laziness rank easily", "author": [