diff --git a/CHANGELOG.md b/CHANGELOG.md index 9050969d28..6a6cd9b600 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -93,18 +93,17 @@ Web/Django standard initiative (@strikaco) + `validate_password`: Mechanism for validating a password based on predefined Django validators. + `set_password`: Apply password to account, using validation checks. +### Server + +- Convert ServerConf model to store its values as a Picklefield (same as Attributes) instead of using a custom solution. +- OOB: Add support for MSDP LIST, REPORT, UNREPORT commands (re-mapped to `msdp_list`, + `msdp_report`, `msdp_unreport` inlinefuncs_) + ### Utils - `evennia` launcher now fully handles all django-admin commands, like running tests in parallel. - `evennia.utils.create.account` now also takes `tags` and `attrs` keywords. - Added many more unit tests. - -### Server - -- Convert ServerConf model to store its values as a Picklefield (same as Attributes) instead of using a custom solution. - -### Utils - - Swap argument order of `evennia.set_trace` to `set_trace(term_size=(140, 40), debugger='auto')` since the size is more likely to be changed on the command line. - `utils.to_str(text, session=None)` now acts as the old `utils.to_unicode` (which was removed). @@ -113,7 +112,8 @@ Web/Django standard initiative (@strikaco) `force_string` flag was removed and assumed always set). - `utils.to_bytes(text, session=None)` replaces the old `utils.to_str()` functionality and converts str to bytes. - +- `evennia.MONITOR_HANDLER.all` now takes keyword argument `obj` to only retrieve monitors from that specific + Object (rather than all monitors in the entire handler). ### Contribs diff --git a/evennia/scripts/monitorhandler.py b/evennia/scripts/monitorhandler.py index c3027ff773..64af43a1ae 100644 --- a/evennia/scripts/monitorhandler.py +++ b/evennia/scripts/monitorhandler.py @@ -172,16 +172,21 @@ class MonitorHandler(object): """ self.monitors = defaultdict(lambda: defaultdict(dict)) - def all(self): + def all(self, obj=None): """ - List all monitors. + List all monitors or all monitors of a given object. + + Args: + obj (Object): The object on which to list all monitors. Returns: monitors (list): The handled monitors. """ output = [] - for obj in self.monitors: + objs = [obj] if obj else self.monitors + + for obj in objs: for fieldname in self.monitors[obj]: for idstring, (callback, persistent, kwargs) in self.monitors[obj][fieldname].items(): output.append((obj, fieldname, idstring, persistent, kwargs)) diff --git a/evennia/server/inputfuncs.py b/evennia/server/inputfuncs.py index 3e3816c4f2..4eee991188 100644 --- a/evennia/server/inputfuncs.py +++ b/evennia/server/inputfuncs.py @@ -3,7 +3,8 @@ Functions for processing input commands. All global functions in this module whose name does not start with "_" is considered an inputfunc. Each function must have the following -callsign: +callsign (where inputfunc name is always lower-case, no matter what the +OOB input name looked like): inputfunc(session, *args, **kwargs) @@ -138,6 +139,11 @@ def default(session, cmdname, *args, **kwargs): log_err(err) +_CLIENT_OPTIONS = \ + ("ANSI", "XTERM256", "MXP", "UTF-8", "SCREENREADER", "ENCODING", "MCCP", + "SCREENHEIGHT", "SCREENWIDTH", "INPUTDEBUG", "RAW", "NOCOLOR", "NOGOAHEAD") + + def client_options(session, *args, **kwargs): """ This allows the client an OOB way to inform us about its name and capabilities. @@ -165,12 +171,7 @@ def client_options(session, *args, **kwargs): if not kwargs or kwargs.get("get", False): # return current settings options = dict((key, old_flags[key]) for key in old_flags - if key.upper() in ("ANSI", "XTERM256", "MXP", - "UTF-8", "SCREENREADER", "ENCODING", - "MCCP", "SCREENHEIGHT", - "SCREENWIDTH", "INPUTDEBUG", - "RAW", "NOCOLOR", - "NOGOAHEAD")) + if key.upper() in _CLIENT_OPTIONS) session.msg(client_options=options) return @@ -239,11 +240,6 @@ def client_options(session, *args, **kwargs): {session.sessid: {"protocol_flags": flags}}) -# GMCP alias -hello = client_options -supports_set = client_options - - def get_client_options(session, *args, **kwargs): """ Alias wrapper for getting options. @@ -368,10 +364,14 @@ def _on_monitor_change(**kwargs): obj = kwargs["obj"] name = kwargs["name"] session = kwargs["session"] + outputfunc_name = kwargs['outputfunc_name'] + # the session may be None if the char quits and someone # else then edits the object + if session: - session.msg(monitor={"name": name, "value": _GA(obj, fieldname)}) + callsign = {outputfunc_name: {"name": name, "value": _GA(obj, fieldname)}} + session.msg(**callsign) def monitor(session, *args, **kwargs): @@ -384,10 +384,14 @@ def monitor(session, *args, **kwargs): in the _monitorable dict earlier in this module are accepted. stop (bool): Stop monitoring the above name. + outputfunc_name (str, optional): Change the name of + the outputfunc name. This is used e.g. by MSDP which + has its own specific output format. """ from evennia.scripts.monitorhandler import MONITOR_HANDLER name = kwargs.get("name", None) + outputfunc_name = kwargs("outputfunc_name", "monitor") if name and name in _monitorable and session.puppet: field_name = _monitorable[name] obj = session.puppet @@ -396,7 +400,8 @@ def monitor(session, *args, **kwargs): else: # the handler will add fieldname and obj to the kwargs automatically MONITOR_HANDLER.add(obj, field_name, _on_monitor_change, idstring=session.sessid, - persistent=False, name=name, session=session) + persistent=False, name=name, session=session, + outputfunc_name=outputfunc_name) def unmonitor(session, *args, **kwargs): @@ -407,6 +412,17 @@ def unmonitor(session, *args, **kwargs): monitor(session, *args, **kwargs) +def monitored(session, *args, **kwargs): + """ + Report on what is being monitored + + """ + from evennia.scripts.monitorhandler import MONITOR_HANDLER + obj = session.puppet + monitors = MONITOR_HANDLER.all(obj=obj) + session.msg(monitored=(monitors, {})) + + def _on_webclient_options_change(**kwargs): """ Called when the webclient options stored on the account changes. @@ -469,3 +485,60 @@ def webclient_options(session, *args, **kwargs): # kwargs provided: persist them to the account object for key, value in kwargs.items(): clientoptions[key] = value + + +# OOB protocol-specific aliases and wrappers + +# GMCP aliases +hello = client_options +supports_set = client_options + + +# MSDP aliases (some of the the generic MSDP commands defined in the MSDP spec are prefixed +# by msdp_ at the protocol level) +# See https://tintin.sourceforge.io/protocols/msdp/ + + +def msdp_list(session, *args, **kwargs): + """ + MSDP LIST command + + """ + from evennia.scripts.monitorhandler import MONITOR_HANDLER + args_lower = [arg.lower() for arg in args] + if "commands" in args_lower: + inputfuncs = [key[5:] if key.startswith("msdp_") else key + for key in session.sessionhandler.get_inputfuncs().keys()] + session.msg(commands=(inputfuncs, {})) + if "lists" in args_lower: + session.msg(lists=(['commands', 'lists', 'configurable_variables', 'reportable_variables', + 'reported_variables', 'sendable_variables'], {})) + if "configurable_variables" in args_lower: + session.msg(configurable_variables=(_CLIENT_OPTIONS, {})) + if "reportable_variables" in args_lower: + session.msg(reportable_variables=(_monitorable, {})) + if "reported_variables" in args_lower: + obj = session.puppet + monitor_infos = MONITOR_HANDLER.all(obj=obj) + fieldnames = [tup[1] for tup in monitor_infos] + session.msg(reported_variables=(fieldnames, {})) + if "sendable_variables" in args_lower: + # no default sendable variables + session.msg(sendable_variables=([], {})) + + +def msdp_report(session, *args, **kwargs): + """ + MSDP REPORT command + + """ + kwargs['outputfunc_name': 'report'] + monitor(session, *args, **kwargs) + + +def msdp_unreport(session, *args, **kwargs): + """ + MSDP UNREPORT command + + """ + unmonitor(session, *args, **kwargs) diff --git a/evennia/server/portal/telnet_oob.py b/evennia/server/portal/telnet_oob.py index c777f5ee38..f8a11d1bee 100644 --- a/evennia/server/portal/telnet_oob.py +++ b/evennia/server/portal/telnet_oob.py @@ -350,6 +350,13 @@ class TelnetOOB(object): for key, var in variables.items(): cmds[key] = [[var], {}] + # remap the 'generic msdp commands' to avoid colliding with builtins etc + # by prepending "msdp_" + lower_case = {key.lower(): key for key in cmds} + for remap in ("list", "report", "reset", "send", "unreport"): + if remap in lower_case: + cmds["msdp_{}".format(remap)] = cmds.pop(lower_case[remap]) + # print("msdp data in:", cmds) # DEBUG self.protocol.data_in(**cmds)