diff --git a/evennia/scripts/tickerhandler.py b/evennia/scripts/tickerhandler.py index b537597d6..58e7fc6ec 100644 --- a/evennia/scripts/tickerhandler.py +++ b/evennia/scripts/tickerhandler.py @@ -79,7 +79,7 @@ class Ticker(object): appropriately. """ for key, (obj, args, kwargs) in self.subscriptions.items(): - hook_key = yield kwargs.get("hook_key", "at_tick") + hook_key = yield kwargs.get("_hook_key", "at_tick") if not obj: # object was deleted between calls self.validate() @@ -256,21 +256,48 @@ class TickerHandler(object): def add(self, obj, interval, idstring="", hook_key="at_tick", *args, **kwargs): """ - Add object to tickerhandler. The object must have an at_tick - method. This will be called every interval seconds until the - object is unsubscribed from the ticker. + Add object to tickerhandler + + Args: + obj (Object): The object to subscribe to the ticker. + interval (int): Interval in seconds between calling + `hook_key` below. + idstring (str, optional): Identifier for separating + this ticker-subscription from others with the same + interval. Allows for managing multiple calls with + the same time interval + hook_key (str, optional): The name of the hook method + on `obj` to call every `interval` seconds. Defaults to + `at_tick()`. + args, kwargs (optional): These will be passed into the + method given by `hook_key` every time it is called. + + Notes: + The combination of `obj`, `interval` and `idstring` + together uniquely defines the ticker subscription. They + must all be supplied in order to unsubscribe from it + later. + """ isdb, store_key = self._store_key(obj, interval, idstring) if isdb: self.ticker_storage[store_key] = (args, kwargs) self.save() - kwargs["hook_key"] = hook_key + kwargs["_hook_key"] = hook_key self.ticker_pool.add(store_key, obj, interval, *args, **kwargs) def remove(self, obj, interval=None, idstring=""): """ - Remove object from ticker, or only this object ticking - at a given interval. + Remove object from ticker or only remove it from tickers with + a given interval. + + Args: + obj (Object): The object subscribing to the ticker. + interval (int, optional): Interval of ticker to remove. If + `None`, all tickers on this object matching `idstring` + will be removed, regardless of their `interval` setting. + idstring (str, optional): Identifier id of ticker to remove. + """ if interval: isdb, store_key = self._store_key(obj, interval, idstring) diff --git a/evennia/server/amp.py b/evennia/server/amp.py index bd5fc04a6..6afc12935 100644 --- a/evennia/server/amp.py +++ b/evennia/server/amp.py @@ -373,10 +373,10 @@ class AMPProtocol(amp.AMP): """ Relays message to Portal. This method is executed on the Portal. """ - #print "msg server->portal (portal side):", sessid, msg ret = self.safe_recv(MsgServer2Portal, sessid, ipart, nparts, text=msg, data=data) if ret is not None: + print "msg server->portal (portal side):", sessid, ret["text"], loads(ret["data"]) self.factory.portal.sessions.data_out(sessid, text=ret["text"], **loads(ret["data"])) diff --git a/evennia/server/oob_cmds.py b/evennia/server/oob_cmds.py index 19d9f2ce8..103082f53 100644 --- a/evennia/server/oob_cmds.py +++ b/evennia/server/oob_cmds.py @@ -89,7 +89,7 @@ def oob_error(oobhandler, session, errmsg, *args, **kwargs): management. """ - session.msg(oob=("error", ("OOB ERROR: %s" % errmsg))) + session.msg(oob=("error", ("OOB ERROR: %s" % errmsg,))) def oob_echo(oobhandler, session, *args, **kwargs): """ @@ -107,7 +107,7 @@ def oob_echo(oobhandler, session, *args, **kwargs): ##OOB{"repeat":10} def oob_repeat(oobhandler, session, oobfuncname, interval, *args, **kwargs): """ - Called as REPEAT + Called as REPEAT Repeats a given OOB command with a certain frequency. Args: @@ -120,8 +120,14 @@ def oob_repeat(oobhandler, session, oobfuncname, interval, *args, **kwargs): The command checks so that it cannot repeat itself. """ - if oobfuncname != "REPEAT": - oobhandler.add_repeat(None, session.sessid, oobfuncname, interval, *args, **kwargs) + if not oobfuncname: + oob_error(oobhandler, session, "Usage: REPEAT , ") + return + # limit repeat actions to minimum 5 seconds interval + interval = 20 if not interval else (max(5, interval)) + obj = session.get_puppet_or_player() + if obj and oobfuncname != "REPEAT": + oobhandler.add_repeater(obj, session.sessid, oobfuncname, interval, *args, **kwargs) ##OOB{"UNREPEAT":10} @@ -132,16 +138,18 @@ def oob_unrepeat(oobhandler, session, oobfuncname, interval): Args: oobhandler (OOBHandler): main OOB handler. - session (Session): Session controlling the repeat + session (Session): Session controlling the repeater oobfuncname (str): OOB function called every interval seconds - interval (int): Interval of repeat, in seconds. + interval (int): Interval of repeater, in seconds. Notes: The command checks so that it cannot repeat itself. """ - oobhandler.remove_repeat(None, session.sessid, oobfuncname, interval) + obj = session.get_puppet_or_player() + if obj: + oobhandler.remove_repeater(obj, session.sessid, oobfuncname, interval) # @@ -194,7 +202,7 @@ def oob_send(oobhandler, session, *args, **kwargs): if obj: for name in (a.upper() for a in args if a): try: - print "MSDP SEND inp:", name + #print "MSDP SEND inp:", name value = OOB_SENDABLE.get(name, _NA)(obj) ret[name] = value except Exception, e: @@ -253,7 +261,7 @@ def oob_report(oobhandler, session, *args, **kwargs): else: oobhandler.add_attribute_monitor(obj, session.sessid, propname, "return_attribute_report") ret.append(_GA(obj, "db_value")) - print "ret:", ret + #print "ret:", ret session.msg(oob=("MSDP_ARRAY", ret)) else: oob_error(oobhandler, session, "You must log in first.") @@ -336,11 +344,6 @@ def oob_list(oobhandler, session, mode, *args, **kwargs): "UNREPORT", # "RESET", "SEND"))) - elif mode == "LISTS": - session.msg(oob=("LISTS",("REPORTABLE_VARIABLES", - "REPORTED_VARIABLES", - # "CONFIGURABLE_VARIABLES", - "SENDABLE_VARIABLES"))) elif mode == "REPORTABLE_VARIABLES": session.msg(oob=("REPORTABLE_VARIABLES", tuple(key for key in OOB_REPORTABLE.keys()))) elif mode == "REPORTED_VARIABLES": @@ -356,7 +359,11 @@ def oob_list(oobhandler, session, mode, *args, **kwargs): # Not implemented (game specific) oob_error(oobhandler, session, "Not implemented (game specific)") else: - oob_error(oobhandler, session, "Unsupported mode") + # mode == "LISTS" or not given + session.msg(oob=("LISTS",("REPORTABLE_VARIABLES", + "REPORTED_VARIABLES", + # "CONFIGURABLE_VARIABLES", + "SENDABLE_VARIABLES"))) # # Cmd mapping @@ -368,8 +375,8 @@ def oob_list(oobhandler, session, mode, *args, **kwargs): CMD_MAP = {"oob_error": oob_error, # will get error messages "return_field_report": oob_return_field_report, "return_attribute_report": oob_return_attribute_report, - "repeat": oob_repeat, - "unrepeat": oob_unrepeat, + "REPEAT": oob_repeat, + "UNREPEAT": oob_unrepeat, "SEND": oob_send, "ECHO": oob_echo, "REPORT": oob_report, diff --git a/evennia/server/oobhandler.py b/evennia/server/oobhandler.py index d03b679b7..4f5aee7e9 100644 --- a/evennia/server/oobhandler.py +++ b/evennia/server/oobhandler.py @@ -1,36 +1,14 @@ """ OOBHandler - Out Of Band Handler -The OOBHandler.execute_cmd is called by the sessionhandler when it detects -an OOB instruction (exactly how this looked depends on the protocol; at this -point all oob calls should look the same) +The OOBHandler.execute_cmd is called by the sessionhandler when it +detects an `oob` keyword in the outgoing data (usually called via +`msg(oob=...)` -The handler pieces of functionality: - - function execution - the oob protocol can execute a function directly on - the server. The available functions must be defined - as global functions in settings.OOB_PLUGIN_MODULES. - repeat func execution - the oob protocol can request a given function be - executed repeatedly at a regular interval. This - uses an internal script pool. - tracking - the oob protocol can request Evennia to track changes to - fields on objects, as well as changes in Attributes. This is - done by dynamically adding tracker-objects on entities. The - behaviour of those objects can be customized by adding new - tracker classes in settings.OOB_PLUGIN_MODULES. - -What goes into the OOB_PLUGIN_MODULES is a (list of) modules that contains -the working server-side code available to the OOB system: oob functions and -tracker classes. - -oob functions have the following call signature: - function(caller, session, *args, **kwargs) - -oob trackers should inherit from the OOBTracker class (in this - module) and implement a minimum of the same functionality. - -If a function named "oob_error" is given, this will be called with error -messages. +How this works is that the handler executes an oobfunction, which is +defined in a user-supplied module. This function can then make use of +the oobhandler's functionality to return data, register a monitor on +an object's properties or start a repeating action. """ @@ -132,17 +110,21 @@ class OOBFieldMonitor(object): self.subscribers.pop(sessid, None) -class OOBAtRepeat(object): +class OOBAtRepeater(object): """ - This object should be stored on a target object, named - as the hook to call repeatedly, e.g. + This object is created and used by the `OOBHandler.repeat` method. + It will be assigned to a target object as a custom variable, e.g.: + + `obj._oob_ECHO_every_20s_for_sessid_1 = AtRepater()` + + It will be called every interval seconds by the OOBHandler, + triggering whatever OOB function it is set to use. - _oob_listen_every_20s_for_sessid_1 = AtRepat() """ - def __call__(self, sessid, oobfuncname, *args, **kwargs): + def __call__(self, *args, **kwargs): "Called at regular intervals. Calls the oob function" - OOB_HANDLER.execute_cmd(sessid, oobfuncname, *args, **kwargs) + OOB_HANDLER.execute_cmd(kwargs["_sessid"], kwargs["_oobfuncname"], *args, **kwargs) # Main OOB Handler @@ -153,13 +135,13 @@ class OOBHandler(TickerHandler): """ def __init__(self, *args, **kwargs): + super(OOBHandler, self).__init__(*args, **kwargs) self.save_name = "oob_ticker_storage" self.oob_save_name = "oob_monitor_storage" self.oob_monitor_storage = {} - super(OOBHandler, self).__init__(*args, **kwargs) - def _get_repeat_hook_name(self, oobfuncname, interval, sessid): - "Return the unique repeat call hook name for this object" + def _get_repeater_hook_name(self, oobfuncname, interval, sessid): + "Return the unique repeater call hook name for this object" return "_oob_%s_every_%ss_for_sessid_%s" % (oobfuncname, interval, sessid) def _get_fieldmonitor_name(self, fieldname): @@ -220,7 +202,7 @@ class OOBHandler(TickerHandler): by the Server process as part of the reload upstart. Here we overload the tickerhandler's restore method completely to make sure we correctly re-apply and re-initialize the correct - monitor and repeat objects on all saved objects. + monitor and repeater objecth on all saved objects. """ # load the oob monitors and initialize them oob_storage = ServerConfig.objects.conf(key=self.oob_save_name) @@ -232,7 +214,7 @@ class OOBHandler(TickerHandler): obj = unpack_dbobj(obj) self._add_monitor(obj, sessid, fieldname, oobfuncname, *args, **kwargs) # handle the tickers (same as in TickerHandler except we call - # the add_repeat method which makes sure to add the hooks before + # the add_repeater method which makes sure to add the hooks before # starting the tickerpool) ticker_storage = ServerConfig.objects.conf(key=self.save_name) if ticker_storage: @@ -240,12 +222,12 @@ class OOBHandler(TickerHandler): for store_key, (args, kwargs) in self.ticker_storage.items(): obj, interval, idstring = store_key obj = unpack_dbobj(obj) - # we saved these in add_repeat before, can now retrieve them - sessid = kwargs["sessid"] - oobfuncname = kwargs["oobfuncname"] - self.add_repeat(obj, sessid, oobfuncname, interval, *args, **kwargs) + # we saved these in add_repeater before, can now retrieve them + sessid = kwargs["_sessid"] + oobfuncname = kwargs["_oobfuncname"] + self.add_repeater(obj, sessid, oobfuncname, interval, *args, **kwargs) - def add_repeat(self, obj, sessid, oobfuncname, interval=20, *args, **kwargs): + def add_repeater(self, obj, sessid, oobfuncname, interval=20, *args, **kwargs): """ Set an oob function to be repeatedly called. @@ -261,22 +243,20 @@ class OOBHandler(TickerHandler): if not isinstance(sessid, int): sessid = sessid.sessid - hook = OOBAtRepeat() - hookname = self._get_repeat_hook_name(oobfuncname, interval, sessid) + hook = OOBAtRepeater() + hookname = self._get_repeater_hook_name(oobfuncname, interval, sessid) _SA(obj, hookname, hook) - kwargs.update({"sessid":sessid, "oobfuncname":oobfuncname}) # we store these in kwargs so that tickerhandler saves them with the rest - kwargs["sessid"] = sessid - kwargs["oobfuncbame"] = oobfuncname - self.add(obj, interval, idstring=oobfuncname, hook_key=hookname, *args, **kwargs) + kwargs.update({"_sessid":sessid, "_oobfuncname":oobfuncname}) + super(OOBHandler, self).add(obj, int(interval), oobfuncname, hookname, *args, **kwargs) - def remove_repeat(self, obj, sessid, oobfuncname, interval=20): + def remove_repeater(self, obj, sessid, oobfuncname, interval=20): """ Remove the repeatedly calling oob function Args: obj (Object): The object on which the repeater sits - sessid (int): Session id of the Session that registered the repeat + sessid (int): Session id of the Session that registered the repeater oobfuncname (str): Name of oob function to call at repeat interval (int, optional): Number of seconds between repeats @@ -284,8 +264,8 @@ class OOBHandler(TickerHandler): # check so we didn't get a session instead of a sessid if not isinstance(sessid, int): sessid = sessid.sessid - self.remove(obj, interval, idstring=oobfuncname) - hookname = self._get_repeat_hook_name(oobfuncname, interval, sessid) + super(OOBHandler, self).remove(obj, interval, idstring=oobfuncname) + hookname = self._get_repeater_hook_name(oobfuncname, interval, sessid) try: _DA(obj, hookname) except AttributeError: @@ -406,8 +386,8 @@ class OOBHandler(TickerHandler): oobfuncname (str): The name of the oob command (case sensitive) Notes: - If the oobfuncname is a valid oob function, the `*args` and - `**kwargs` are passed into the oob command. + If the oobfuncname is a valid oob function, `args` and + `kwargs` are passed into the oob command. """ if isinstance(session, int): @@ -418,13 +398,19 @@ class OOBHandler(TickerHandler): (session, oobfuncname, args, kwargs) raise RuntimeError(errmsg) - print "execute_oob:", session, oobfuncname, args, kwargs - # don't catch this, wrong oobfuncname should be reported - oobfunc = _OOB_FUNCS[oobfuncname] + #print "execute_oob:", session, oobfuncname, args, kwargs + try: + oobfunc = _OOB_FUNCS[oobfuncname] + except Exception: + errmsg = "'%s' is not a valid OOB command. Commands available:\n %s" % (oobfuncname, ", ".join(_OOB_FUNCS)) + if _OOB_ERROR: + _OOB_ERROR(self, session, errmsg, *args, **kwargs) + errmsg = "OOB ERROR: %s" % errmsg + logger.log_trace(errmsg) + raise # we found an oob command. Execute it. try: - #print "OOB execute_cmd:", session, func_key, args, kwargs, _OOB_FUNCS.keys() oobfunc(self, session, *args, **kwargs) except Exception, err: errmsg = "Exception in %s(*%s, **%s):\n%s" % (oobfuncname, args, kwargs, err) diff --git a/evennia/server/portal/portalsessionhandler.py b/evennia/server/portal/portalsessionhandler.py index f714e0cec..7a1c1c091 100644 --- a/evennia/server/portal/portalsessionhandler.py +++ b/evennia/server/portal/portalsessionhandler.py @@ -191,17 +191,22 @@ class PortalSessionHandler(SessionHandler): Helper method for each session to use to parse oob structures (The 'oob' kwarg of the msg() method). - Allowed input oob structures are: - cmdname - ((cmdname,), (cmdname,)) - (cmdname,(arg, )) - (cmdname,(arg1,arg2)) - (cmdname,{key:val,key2:val2}) - (cmdname, (args,), {kwargs}) - ((cmdname, (arg1,arg2)), cmdname, (cmdname, (arg1,))) - outputs an ordered structure on the form - ((cmdname, (args,), {kwargs}), ...), where the two last - parts of each tuple may be empty + Args: oobstruct (str or iterable): A structure representing + an oob command on one of the following forms: + - "cmdname" + - "cmdname", "cmdname" + - ("cmdname", arg) + - ("cmdname",(args)) + - ("cmdname",{kwargs} + - ("cmdname", (args), {kwargs}) + - (("cmdname", (args,), {kwargs}), ("cmdname", (args,), {kwargs})) + and any combination of argument-less commands or commands with only + args, only kwargs or both. + + Returns: + structure (tuple): A generic OOB structure on the form + `((cmdname, (args,), {kwargs}), ...)`, where the two last + args and kwargs may be empty """ def _parse(oobstruct): slen = len(oobstruct) @@ -217,17 +222,17 @@ class PortalSessionHandler(SessionHandler): return (oobstruct[0], (), {}) elif slen == 2: if isinstance(oobstruct[1], dict): - # cmdname, {kwargs} + # (cmdname, {kwargs}) return (oobstruct[0], (), dict(oobstruct[1])) elif isinstance(oobstruct[1], (tuple, list)): - # cmdname, (args,) - return (oobstruct[0], list(oobstruct[1]), {}) + # (cmdname, (args,)) + return (oobstruct[0], tuple(oobstruct[1]), {}) else: - # cmdname, cmdname - return ((oobstruct[0], (), {}), (oobstruct[1].lower(), (), {})) + # (cmdname, arg) + return (oobstruct[0], (oobstruct[1],), {}) else: - # cmdname, (args,), {kwargs} - return (oobstruct[0], list(oobstruct[1]), dict(oobstruct[2])) + # (cmdname, (args,), {kwargs}) + return (oobstruct[0], tuple(oobstruct[1]), dict(oobstruct[2])) if hasattr(oobstruct, "__iter__"): # differentiate between (cmdname, cmdname), @@ -264,7 +269,9 @@ class PortalSessionHandler(SessionHandler): if session: # convert oob to the generic format if "oob" in kwargs: + print "oobstruct_parser in:", kwargs["oob"] kwargs["oob"] = self.oobstruct_parser(kwargs["oob"]) + print "oobstruct_parser out:", kwargs["oob"] session.data_out(text=text, **kwargs) PORTAL_SESSIONS = PortalSessionHandler() diff --git a/evennia/server/portal/telnet.py b/evennia/server/portal/telnet.py index d5adef8bd..ecdc783a5 100644 --- a/evennia/server/portal/telnet.py +++ b/evennia/server/portal/telnet.py @@ -26,8 +26,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session): """ def connectionMade(self): """ - This is called when the connection is first - established. + This is called when the connection is first established. """ # initialize the session self.iaw_mode = False @@ -35,7 +34,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session): client_address = self.transport.client # this number is counted down for every handshake that completes. # when it reaches 0 the portal/server syncs their data - self.handshakes = 6 # naws, ttype, mccp, mssp, oob, mxp + self.handshakes = 7 # naws, ttype, mccp, mssp, msdp, gmcp, mxp self.init_session("telnet", client_address, self.factory.sessionhandler) # negotiate client size @@ -47,7 +46,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session): self.mccp = Mccp(self) # negotiate mssp (crawler communication) self.mssp = mssp.Mssp(self) - # oob communication (MSDP, GMCP) + # oob communication (MSDP, GMCP) - two handshake calls! self.oob = telnet_oob.TelnetOOB(self) # mxp support self.mxp = Mxp(self) @@ -67,7 +66,6 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session): When all have reported, a sync with the server is performed. The system will force-call this sync after a small time to handle clients that don't reply to handshakes at all. - info - debug text from the protocol calling """ if self.handshakes > 0: if force: @@ -239,7 +237,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session): if "oob" in kwargs and "OOB" in self.protocol_flags: # oob is a list of [(cmdname, arg, kwarg), ...] for cmdname, args, kwargs in kwargs["oob"]: - print "telnet oob data_out:", cmdname, args, kwargs + #print "telnet oob data_out:", cmdname, args, kwargs self.oob.data_out(cmdname, *args, **kwargs) # parse **kwargs, falling back to ttype if nothing is given explicitly diff --git a/evennia/server/portal/telnet_oob.py b/evennia/server/portal/telnet_oob.py index faa144fa4..9bfc0adf1 100644 --- a/evennia/server/portal/telnet_oob.py +++ b/evennia/server/portal/telnet_oob.py @@ -33,7 +33,7 @@ MSDP_ARRAY_OPEN = chr(5) MSDP_ARRAY_CLOSE = chr(6) # GMCP -GMCP = chr(200) +GMCP = chr(201) # General Telnet IAC = chr(255) @@ -71,16 +71,18 @@ class TelnetOOB(object): self.protocol.protocol_flags['OOB'] = False self.MSDP = False self.GMCP = False - # detect MSDP first - self.protocol.negotiationMap[MSDP] = self.data_in + # ask for the available protocols and assign decoders + # (note that handshake_done() will be called twice!) + self.protocol.negotiationMap[MSDP] = self.decode_msdp + self.protocol.negotiationMap[GMCP] = self.decode_gmcp self.protocol.will(MSDP).addCallbacks(self.do_msdp, self.no_msdp) + self.protocol.will(GMCP).addCallbacks(self.do_gmcp, self.no_gmcp) self.oob_reported = {} def no_msdp(self, option): "No msdp supported or wanted" # no msdp, check GMCP - self.protocol.negotiationMap[GMCP] = self.data_in - self.protocol.will(GMCP).addCallbacks(self.do_oob, self.no_oob) + self.protocol.handshake_done() def do_msdp(self, option): "MSDP supported by client" @@ -122,16 +124,16 @@ class TelnetOOB(object): if cmdname == "MSDP_ARRAY": msdp_string = "".join(["%s%s" % (MSDP_VAL, val) for val in args]) else: - msdp_string = "%s%s%s" % (MSDP_VAR, cmdname.upper(), "".join( + msdp_string = "%s%s%s" % (MSDP_VAR, cmdname, "".join( "%s%s" % (MSDP_VAL, val) for val in args)) elif kwargs: if cmdname == "MSDP_TABLE": msdp_string = "".join(["%s%s%s%s" % (MSDP_VAR, key, MSDP_VAL, val) for key, val in kwargs.items()]) else: - msdp_string = "%s%s%s" % (MSDP_VAR. cmdname.upper(), "".join( + msdp_string = "%s%s%s" % (MSDP_VAR. cmdname, "".join( ["%s%s%s%s" % (MSDP_VAR, key, MSDP_VAL, val) for key, val in kwargs.items()])) - print "encode msdp result:", cmdname, args, kwargs, "->", msdp_string + #print "encode msdp result:", cmdname, args, kwargs, "->", msdp_string return force_str(msdp_string) def encode_gmcp(self, cmdname, *args, **kwargs): @@ -145,8 +147,8 @@ class TelnetOOB(object): cmdname is generally recommended to be a string on the form Module.Submodule.Function """ - if cmdname in ("SEND", "ECHO", "REPORT", "UNREPORT", "LIST"): - # we wrap the standard MSDP commands in a MSDP. submodule + if cmdname in ("SEND", "REPORT", "UNREPORT", "LIST"): + # we wrap the standard MSDP commands in a MSDP.submodule # here as far as GMCP is concerned. cmdname = "MSDP.%s" % cmdname elif cmdname in ("MSDP_ARRAY", "MSDP_TABLE"): @@ -156,15 +158,16 @@ class TelnetOOB(object): gmcp_string = "%s %s" % (cmdname, json.dumps(args)) elif kwargs: gmcp_string = "%s %s" % (cmdname, json.dumps(kwargs)) + print "gmcp_encode", cmdname, args, kwargs, "->", gmcp_string return force_str(gmcp_string).strip() def decode_msdp(self, data): """ Decodes incoming MSDP data - cmdname, var --> cmdname arg - cmdname, array --> cmdname *args - cmdname, table --> cmdname **kwargs + cmdname var --> cmdname arg + cmdname array --> cmdname *args + cmdname table --> cmdname **kwargs """ tables = {} @@ -174,8 +177,6 @@ class TelnetOOB(object): if hasattr(data, "__iter__"): data = "".join(data) - #logger.log_infomsg("MSDP SUBNEGOTIATION: %s" % data) - # decode for key, table in msdp_regex_table.findall(data): tables[key] = {} @@ -190,9 +191,9 @@ class TelnetOOB(object): for varval in msdp_regex_var.split(msdp_regex_array.sub("", msdp_regex_table.sub("", data))): # get remaining varvals after cleaning away tables/arrays parts = msdp_regex_val.split(varval) - variables[parts[0].upper()] = tuple(parts[1:]) if len(parts) > 1 else ("", ) + variables[parts[0]] = tuple(parts[1:]) if len(parts) > 1 else ("", ) - print "OOB: MSDP decode:", data, "->", variables, arrays, tables + #print "OOB: MSDP decode:", data, "->", variables, arrays, tables # send to the sessionhandler if data: @@ -215,16 +216,27 @@ class TelnetOOB(object): cmdname {key:arg, key:arg, ...} -> cmdname **kwargs """ + if hasattr(data, "__iter__"): + data = "".join(data) + + print "decode_gmcp:", data if data: splits = data.split(" ", 1) cmdname = splits[0] if len(splits) < 2: self.protocol.data_in(oob=(cmdname, (), {})) - else: - struct = json.loads(splits[1]) - self.protocol.data_in(oob=(cmdname, - struct if isinstance(struct, list) else (), - struct if isinstance(struct, dict) else {})) + elif splits[1]: + struct = json.loads(json.dumps(splits[1])) + args, kwargs = (), {} + if hasattr(struct, "__iter__"): + if isinstance(struct, dict): + kwargs = struct + else: + args = tuple(struct) + else: + args = (struct,) + print "gmcp decode:", data, "->", cmdname, args, kwargs + self.protocol.data_in(oob=(cmdname, args, kwargs)) # access methods @@ -232,20 +244,10 @@ class TelnetOOB(object): """ Return a msdp-valid subnegotiation across the protocol. """ + #print "data_out:", encoded_oob if self.MSDP: encoded_oob = self.encode_msdp(cmdname, *args, **kwargs) - print "data_out:", encoded_oob self.protocol._write(IAC + SB + MSDP + encoded_oob + IAC + SE) - else: + if self.GMCP: encoded_oob = self.encode_gmcp(cmdname, *args, **kwargs) self.protocol._write(IAC + SB + GMCP + encoded_oob + IAC + SE) - - def data_in(self, data): - """ - Send oob data to Evennia. The self.decode_* methods send to - protocol.data_in() themselves. - """ - if self.MSDP: - self.decode_msdp(data) - else: - self.decode_gmcp(data) diff --git a/evennia/server/sessionhandler.py b/evennia/server/sessionhandler.py index d26f619b1..9d80a7663 100644 --- a/evennia/server/sessionhandler.py +++ b/evennia/server/sessionhandler.py @@ -438,6 +438,7 @@ class ServerSessionHandler(SessionHandler): if not _OOB_HANDLER: from evennia.server.oobhandler import OOB_HANDLER as _OOB_HANDLER funcname, args, kwargs = kwargs.pop("oob") + print "OOB session.data_in:", funcname, args, kwargs if funcname: _OOB_HANDLER.execute_cmd(session, funcname, *args, **kwargs)