Added support for GMCP out-of-band messaging, as a backup handler to MSDP. Starting to rework the oob system to be more straightforward to follow and understand.
This commit is contained in:
parent
f1b6a4e212
commit
eda15ccc45
9 changed files with 416 additions and 555 deletions
|
|
@ -1,243 +0,0 @@
|
|||
"""
|
||||
|
||||
MSDP (Mud Server Data Protocol)
|
||||
|
||||
This implements the MSDP protocol as per
|
||||
http://tintin.sourceforge.net/msdp/. MSDP manages out-of-band
|
||||
communication between the client and server, for updating health bars
|
||||
etc.
|
||||
|
||||
"""
|
||||
import re
|
||||
from evennia.utils.utils import to_str
|
||||
|
||||
# MSDP-relevant telnet cmd/opt-codes
|
||||
MSDP = chr(69)
|
||||
MSDP_VAR = chr(1)
|
||||
MSDP_VAL = chr(2)
|
||||
MSDP_TABLE_OPEN = chr(3)
|
||||
MSDP_TABLE_CLOSE = chr(4)
|
||||
MSDP_ARRAY_OPEN = chr(5)
|
||||
MSDP_ARRAY_CLOSE = chr(6)
|
||||
|
||||
IAC = chr(255)
|
||||
SB = chr(250)
|
||||
SE = chr(240)
|
||||
|
||||
force_str = lambda inp: to_str(inp, force_string=True)
|
||||
|
||||
# pre-compiled regexes
|
||||
# returns 2-tuple
|
||||
regex_array = re.compile(r"%s(.*?)%s%s(.*?)%s" % (MSDP_VAR, MSDP_VAL,
|
||||
MSDP_ARRAY_OPEN,
|
||||
MSDP_ARRAY_CLOSE))
|
||||
# returns 2-tuple (may be nested)
|
||||
regex_table = re.compile(r"%s(.*?)%s%s(.*?)%s" % (MSDP_VAR, MSDP_VAL,
|
||||
MSDP_TABLE_OPEN,
|
||||
MSDP_TABLE_CLOSE))
|
||||
regex_var = re.compile(MSDP_VAR)
|
||||
regex_val = re.compile(MSDP_VAL)
|
||||
|
||||
|
||||
# Msdp object handler
|
||||
|
||||
class Msdp(object):
|
||||
"""
|
||||
Implements the MSDP protocol.
|
||||
"""
|
||||
|
||||
def __init__(self, protocol):
|
||||
"""
|
||||
Initiates by storing the protocol
|
||||
on itself and trying to determine
|
||||
if the client supports MSDP.
|
||||
"""
|
||||
self.protocol = protocol
|
||||
self.protocol.protocol_flags['MSDP'] = False
|
||||
self.protocol.negotiationMap[MSDP] = self.msdp_to_evennia
|
||||
self.protocol.will(MSDP).addCallbacks(self.do_msdp, self.no_msdp)
|
||||
self.msdp_reported = {}
|
||||
|
||||
def no_msdp(self, option):
|
||||
"No msdp supported or wanted"
|
||||
self.protocol.handshake_done()
|
||||
|
||||
def do_msdp(self, option):
|
||||
"""
|
||||
Called when client confirms that it can do MSDP.
|
||||
"""
|
||||
self.protocol.protocol_flags['MSDP'] = True
|
||||
self.protocol.handshake_done()
|
||||
|
||||
def evennia_to_msdp(self, cmdname, *args, **kwargs):
|
||||
"""
|
||||
handle return data from cmdname by converting it to
|
||||
a proper msdp structure. data can either be a single value (will be
|
||||
converted to a string), a list (will be converted to an MSDP_ARRAY),
|
||||
or a dictionary (will be converted to MSDP_TABLE).
|
||||
|
||||
OBS - there is no actual use of arrays and tables in the MSDP
|
||||
specification or default commands -- are returns are implemented
|
||||
as simple lists or named lists (our name for them here, these
|
||||
un-bounded structures are not named in the specification). So for
|
||||
now, this routine will not explicitly create arrays nor tables,
|
||||
although there are helper methods ready should it be needed in
|
||||
the future.
|
||||
"""
|
||||
|
||||
def make_table(name, **kwargs):
|
||||
"build a table that may be nested with other tables or arrays."
|
||||
string = MSDP_VAR + force_str(name) + MSDP_VAL + MSDP_TABLE_OPEN
|
||||
for key, val in kwargs.items():
|
||||
if isinstance(val, dict):
|
||||
string += make_table(string, key, **val)
|
||||
elif hasattr(val, '__iter__'):
|
||||
string += make_array(string, key, *val)
|
||||
else:
|
||||
string += MSDP_VAR + force_str(key) + MSDP_VAL + force_str(val)
|
||||
string += MSDP_TABLE_CLOSE
|
||||
return string
|
||||
|
||||
def make_array(name, *args):
|
||||
"build a array. Arrays may not nest tables by definition."
|
||||
string = MSDP_VAR + force_str(name) + MSDP_ARRAY_OPEN
|
||||
string += MSDP_VAL.join(force_str(arg) for arg in args)
|
||||
string += MSDP_ARRAY_CLOSE
|
||||
return string
|
||||
|
||||
def make_list(name, *args):
|
||||
"build a simple list - an array without start/end markers"
|
||||
string = MSDP_VAR + force_str(name)
|
||||
string += MSDP_VAL.join(force_str(arg) for arg in args)
|
||||
return string
|
||||
|
||||
def make_named_list(name, **kwargs):
|
||||
"build a named list - a table without start/end markers"
|
||||
string = MSDP_VAR + force_str(name)
|
||||
for key, val in kwargs.items():
|
||||
string += MSDP_VAR + force_str(key) + MSDP_VAL + force_str(val)
|
||||
return string
|
||||
|
||||
# Default MSDP commands
|
||||
|
||||
print "MSDP outgoing:", cmdname, args, kwargs
|
||||
|
||||
cupper = cmdname.upper()
|
||||
if cupper == "LIST":
|
||||
if args:
|
||||
args = list(args)
|
||||
mode = args.pop(0).upper()
|
||||
self.data_out(make_array(mode, *args))
|
||||
elif cupper == "REPORT":
|
||||
self.data_out(make_list("REPORT", *args))
|
||||
elif cupper == "UNREPORT":
|
||||
self.data_out(make_list("UNREPORT", *args))
|
||||
elif cupper == "RESET":
|
||||
self.data_out(make_list("RESET", *args))
|
||||
elif cupper == "SEND":
|
||||
self.data_out(make_named_list("SEND", **kwargs))
|
||||
else:
|
||||
# return list or named lists.
|
||||
msdp_string = ""
|
||||
if args:
|
||||
msdp_string += make_list(cupper, *args)
|
||||
if kwargs:
|
||||
msdp_string += make_named_list(cupper, **kwargs)
|
||||
self.data_out(msdp_string)
|
||||
|
||||
def msdp_to_evennia(self, data):
|
||||
"""
|
||||
Handle a client's requested negotiation, converting
|
||||
it into a function mapping - either one of the MSDP
|
||||
default functions (LIST, SEND etc) or a custom one
|
||||
in OOB_FUNCS dictionary. command names are case-insensitive.
|
||||
|
||||
varname, var --> mapped to function varname(var)
|
||||
arrayname, array --> mapped to function arrayname(*array)
|
||||
tablename, table --> mapped to function tablename(**table)
|
||||
|
||||
Note: Combinations of args/kwargs to one function is not supported
|
||||
in this implementation (it complicates the code for limited
|
||||
gain - arrayname(*array) is usually as complex as anyone should
|
||||
ever need to go anyway (I hope!).
|
||||
|
||||
"""
|
||||
tables = {}
|
||||
arrays = {}
|
||||
variables = {}
|
||||
|
||||
if hasattr(data, "__iter__"):
|
||||
data = "".join(data)
|
||||
|
||||
#logger.log_infomsg("MSDP SUBNEGOTIATION: %s" % data)
|
||||
|
||||
for key, table in regex_table.findall(data):
|
||||
tables[key] = {}
|
||||
for varval in regex_var.split(table):
|
||||
parts = regex_val.split(varval)
|
||||
tables[key].expand({parts[0]: tuple(parts[1:]) if len(parts) > 1 else ("",)})
|
||||
for key, array in regex_array.findall(data):
|
||||
arrays[key] = []
|
||||
for val in regex_val.split(array):
|
||||
arrays[key].append(val)
|
||||
arrays[key] = tuple(arrays[key])
|
||||
for varval in regex_var.split(regex_array.sub("", regex_table.sub("", data))):
|
||||
# get remaining varvals after cleaning away tables/arrays
|
||||
parts = regex_val.split(varval)
|
||||
variables[parts[0].upper()] = tuple(parts[1:]) if len(parts) > 1 else ("", )
|
||||
|
||||
#print "MSDP: table, array, variables:", tables, arrays, variables
|
||||
|
||||
# all variables sent through msdp to Evennia are considered commands
|
||||
# with arguments. There are three forms of commands possible
|
||||
# through msdp:
|
||||
#
|
||||
# VARNAME VAR -> varname(var)
|
||||
# ARRAYNAME VAR VAL VAR VAL VAR VAL ENDARRAY -> arrayname(val,val,val)
|
||||
# TABLENAME TABLE VARNAME VAL VARNAME VAL ENDTABLE ->
|
||||
# tablename(varname=val, varname=val)
|
||||
#
|
||||
|
||||
# default MSDP functions
|
||||
if "LIST" in variables:
|
||||
self.data_in("list", *variables.pop("LIST"))
|
||||
if "REPORT" in variables:
|
||||
self.data_in("report", *variables.pop("REPORT"))
|
||||
if "REPORT" in arrays:
|
||||
self.data_in("report", *(arrays.pop("REPORT")))
|
||||
if "UNREPORT" in variables:
|
||||
self.data_in("unreport", *(arrays.pop("UNREPORT")))
|
||||
if "RESET" in variables:
|
||||
self.data_in("reset", *variables.pop("RESET"))
|
||||
if "RESET" in arrays:
|
||||
self.data_in("reset", *(arrays.pop("RESET")))
|
||||
if "SEND" in variables:
|
||||
self.data_in("send", *variables.pop("SEND"))
|
||||
if "SEND" in arrays:
|
||||
self.data_in("send", *(arrays.pop("SEND")))
|
||||
|
||||
# if there are anything left consider it a call to a custom function
|
||||
|
||||
for varname, var in variables.items():
|
||||
# a simple function + argument
|
||||
self.data_in(varname, (var,))
|
||||
for arrayname, array in arrays.items():
|
||||
# we assume the array are multiple arguments to the function
|
||||
self.data_in(arrayname, *array)
|
||||
for tablename, table in tables.items():
|
||||
# we assume tables are keyword arguments to the function
|
||||
self.data_in(tablename, **table)
|
||||
|
||||
def data_out(self, msdp_string):
|
||||
"""
|
||||
Return a msdp-valid subnegotiation across the protocol.
|
||||
"""
|
||||
#print "msdp data_out (without IAC SE):", msdp_string
|
||||
self.protocol ._write(IAC + SB + MSDP + force_str(msdp_string) + IAC + SE)
|
||||
|
||||
def data_in(self, funcname, *args, **kwargs):
|
||||
"""
|
||||
Send oob data to Evennia
|
||||
"""
|
||||
#print "msdp data_in:", funcname, args, kwargs
|
||||
self.protocol.data_in(text=None, oob=(funcname, args, kwargs))
|
||||
|
|
@ -179,6 +179,71 @@ class PortalSessionHandler(SessionHandler):
|
|||
return [sess for sess in self.get_sessions(include_unloggedin=True)
|
||||
if hasattr(sess, 'suid') and sess.suid == suid]
|
||||
|
||||
def announce_all(self, message):
|
||||
"""
|
||||
Send message to all connection sessions
|
||||
"""
|
||||
for session in self.sessions.values():
|
||||
session.data_out(message)
|
||||
|
||||
def oobstruct_parser(self, oobstruct):
|
||||
"""
|
||||
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
|
||||
"""
|
||||
def _parse(oobstruct):
|
||||
slen = len(oobstruct)
|
||||
if not oobstruct:
|
||||
return tuple(None, (), {})
|
||||
elif not hasattr(oobstruct, "__iter__"):
|
||||
# a singular command name, without arguments or kwargs
|
||||
return (oobstruct, (), {})
|
||||
# regardless of number of args/kwargs, the first element must be
|
||||
# the function name. We will not catch this error if not, but
|
||||
# allow it to propagate.
|
||||
if slen == 1:
|
||||
return (oobstruct[0], (), {})
|
||||
elif slen == 2:
|
||||
if isinstance(oobstruct[1], dict):
|
||||
# cmdname, {kwargs}
|
||||
return (oobstruct[0], (), dict(oobstruct[1]))
|
||||
elif isinstance(oobstruct[1], (tuple, list)):
|
||||
# cmdname, (args,)
|
||||
return (oobstruct[0], list(oobstruct[1]), {})
|
||||
else:
|
||||
# cmdname, cmdname
|
||||
return ((oobstruct[0], (), {}), (oobstruct[1].lower(), (), {}))
|
||||
else:
|
||||
# cmdname, (args,), {kwargs}
|
||||
return (oobstruct[0], list(oobstruct[1]), dict(oobstruct[2]))
|
||||
|
||||
if hasattr(oobstruct, "__iter__"):
|
||||
# differentiate between (cmdname, cmdname),
|
||||
# (cmdname, (args), {kwargs}) and ((cmdname,(args),{kwargs}),
|
||||
# (cmdname,(args),{kwargs}), ...)
|
||||
|
||||
if oobstruct and isinstance(oobstruct[0], basestring):
|
||||
return (list(_parse(oobstruct)),)
|
||||
else:
|
||||
out = []
|
||||
for oobpart in oobstruct:
|
||||
out.append(_parse(oobpart))
|
||||
return (list(out),)
|
||||
return (_parse(oobstruct),)
|
||||
|
||||
|
||||
def data_in(self, session, text="", **kwargs):
|
||||
"""
|
||||
Called by portal sessions for relaying data coming
|
||||
|
|
@ -189,20 +254,17 @@ class PortalSessionHandler(SessionHandler):
|
|||
msg=text,
|
||||
data=kwargs)
|
||||
|
||||
def announce_all(self, message):
|
||||
"""
|
||||
Send message to all connection sessions
|
||||
"""
|
||||
for session in self.sessions.values():
|
||||
session.data_out(message)
|
||||
|
||||
def data_out(self, sessid, text=None, **kwargs):
|
||||
"""
|
||||
Called by server for having the portal relay messages and data
|
||||
to the correct session protocol.
|
||||
to the correct session protocol. We also convert oob input to
|
||||
a generic form here.
|
||||
"""
|
||||
session = self.sessions.get(sessid, None)
|
||||
if session:
|
||||
# convert oob to the generic format
|
||||
if "oob" in kwargs:
|
||||
kwargs["oob"] = self.oobstruct_parser(kwargs["oob"])
|
||||
session.data_out(text=text, **kwargs)
|
||||
|
||||
PORTAL_SESSIONS = PortalSessionHandler()
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ sessions etc.
|
|||
import re
|
||||
from twisted.conch.telnet import Telnet, StatefulTelnetProtocol, IAC, LINEMODE, GA, WILL, WONT, ECHO
|
||||
from evennia.server.session import Session
|
||||
from evennia.server.portal import ttype, mssp, msdp, naws
|
||||
from evennia.server.portal import ttype, mssp, telnet_oob, naws
|
||||
from evennia.server.portal.mccp import Mccp, mccp_compress, MCCP
|
||||
from evennia.server.portal.mxp import Mxp, mxp_parse
|
||||
from evennia.utils import utils, ansi, logger
|
||||
|
|
@ -35,7 +35,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, msdp, mxp
|
||||
self.handshakes = 6 # naws, ttype, mccp, mssp, oob, mxp
|
||||
self.init_session("telnet", client_address, self.factory.sessionhandler)
|
||||
|
||||
# negotiate client size
|
||||
|
|
@ -47,8 +47,8 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
|
|||
self.mccp = Mccp(self)
|
||||
# negotiate mssp (crawler communication)
|
||||
self.mssp = mssp.Mssp(self)
|
||||
# msdp
|
||||
self.msdp = msdp.Msdp(self)
|
||||
# oob communication (MSDP, GMCP)
|
||||
self.oob = telnet_oob.TelnetOOB(self)
|
||||
# mxp support
|
||||
self.mxp = Mxp(self)
|
||||
# keepalive watches for dead links
|
||||
|
|
@ -211,7 +211,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
|
|||
through the telnet connection.
|
||||
|
||||
valid telnet kwargs:
|
||||
oob=<string> - supply an Out-of-Band instruction.
|
||||
oob=[(cmdname,args,kwargs), ...] - supply an Out-of-Band instruction.
|
||||
xterm256=True/False - enforce xterm256 setting. If not
|
||||
given, ttype result is used. If
|
||||
client does not suport xterm256, the
|
||||
|
|
@ -237,13 +237,10 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
|
|||
self.sendLine(str(e))
|
||||
return
|
||||
if "oob" in kwargs:
|
||||
oobstruct = self.sessionhandler.oobstruct_parser(kwargs.pop("oob"))
|
||||
if "MSDP" in self.protocol_flags:
|
||||
for cmdname, args, kwargs in oobstruct:
|
||||
#print "cmdname, args, kwargs:", cmdname, args, kwargs
|
||||
msdp_string = self.msdp.evennia_to_msdp(cmdname, *args, **kwargs)
|
||||
#print "msdp_string:", msdp_string
|
||||
self.msdp.data_out(msdp_string)
|
||||
# oob is a list of [(cmdname, arg, kwarg), ...]
|
||||
if "OOB" in self.protocol_flags:
|
||||
for cmdname, args, kwargs in kwargs["oob"]:
|
||||
self.oob.data_out(cmdname, *args, **kwargs)
|
||||
|
||||
# parse **kwargs, falling back to ttype if nothing is given explicitly
|
||||
ttype = self.protocol_flags.get('TTYPE', {})
|
||||
|
|
|
|||
238
evennia/server/portal/telnet_oob.py
Normal file
238
evennia/server/portal/telnet_oob.py
Normal file
|
|
@ -0,0 +1,238 @@
|
|||
"""
|
||||
|
||||
Telnet OOB (Out of band communication)
|
||||
|
||||
This implements the following telnet oob protocols:
|
||||
MSDP (Mud Server Data Protocol)
|
||||
GMCP (Generic Mud Communication Protocol)
|
||||
|
||||
This implements the MSDP protocol as per
|
||||
http://tintin.sourceforge.net/msdp/ and the GMCP protocol as per
|
||||
http://www.ironrealms.com/rapture/manual/files/FeatGMCP-txt.html#Generic_MUD_Communication_Protocol%28GMCP%29
|
||||
|
||||
Following the lead of KaVir's protocol snippet, we first check if
|
||||
client supports MSDP and if not, we fallback to GMCP with a MSDP
|
||||
header where applicable.
|
||||
|
||||
OOB manages out-of-band
|
||||
communication between the client and server, for updating health bars
|
||||
etc. See also GMCP which is another standard doing the same thing.
|
||||
|
||||
"""
|
||||
import re
|
||||
import json
|
||||
from evennia.utils.utils import to_str
|
||||
|
||||
# MSDP-relevant telnet cmd/opt-codes
|
||||
MSDP = chr(69)
|
||||
MSDP_VAR = chr(1)
|
||||
MSDP_VAL = chr(2)
|
||||
MSDP_TABLE_OPEN = chr(3)
|
||||
MSDP_TABLE_CLOSE = chr(4)
|
||||
MSDP_ARRAY_OPEN = chr(5)
|
||||
MSDP_ARRAY_CLOSE = chr(6)
|
||||
|
||||
GMCP = chr(200)
|
||||
|
||||
IAC = chr(255)
|
||||
SB = chr(250)
|
||||
SE = chr(240)
|
||||
|
||||
force_str = lambda inp: to_str(inp, force_string=True)
|
||||
|
||||
# pre-compiled regexes
|
||||
# returns 2-tuple
|
||||
msdp_regex_array = re.compile(r"%s(.*?)%s%s(.*?)%s" % (MSDP_VAR, MSDP_VAL,
|
||||
MSDP_ARRAY_OPEN,
|
||||
MSDP_ARRAY_CLOSE))
|
||||
# returns 2-tuple (may be nested)
|
||||
msdp_regex_table = re.compile(r"%s(.*?)%s%s(.*?)%s" % (MSDP_VAR, MSDP_VAL,
|
||||
MSDP_TABLE_OPEN,
|
||||
MSDP_TABLE_CLOSE))
|
||||
msdp_regex_var = re.compile(MSDP_VAR)
|
||||
msdp_regex_val = re.compile(MSDP_VAL)
|
||||
|
||||
# Msdp object handler
|
||||
|
||||
class Telnet_OOB(object):
|
||||
"""
|
||||
Implements the MSDP and GMCP protocols.
|
||||
"""
|
||||
|
||||
def __init__(self, protocol):
|
||||
"""
|
||||
Initiates by storing the protocol
|
||||
on itself and trying to determine
|
||||
if the client supports MSDP.
|
||||
"""
|
||||
self.protocol = protocol
|
||||
self.protocol.protocol_flags['OOB'] = False
|
||||
self.MSDP = False
|
||||
self.GMCP = False
|
||||
# detect MSDP first
|
||||
self.protocol.negotiationMap[MSDP] = self.data_in
|
||||
self.protocol.will(MSDP).addCallbacks(self.do_msdp, self.no_msdp)
|
||||
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)
|
||||
|
||||
def do_msdp(self, option):
|
||||
"MSDP supported by client"
|
||||
self.MSDP = True
|
||||
self.protocol.protocol_flags['OOB'] = True
|
||||
self.protocol.handshake_done()
|
||||
|
||||
def no_gmcp(self, option):
|
||||
"Neither MSDP nor GMCP supported"
|
||||
self.protocol.handshake_done()
|
||||
|
||||
def do_gmcp(self, option):
|
||||
"""
|
||||
Called when client confirms that it can do MSDP or GMCP.
|
||||
"""
|
||||
self.GMCP = True
|
||||
self.protocol.protocol_flags['OOB'] = True
|
||||
self.protocol.handshake_done()
|
||||
|
||||
# encoders
|
||||
|
||||
def encode_msdp(self, cmdname, *args, **kwargs):
|
||||
"""
|
||||
handle return data from cmdname by converting it to
|
||||
a proper msdp structure. These are the combinations we
|
||||
support:
|
||||
|
||||
cmdname string -> cmdname string
|
||||
cmdname *args -> cmdname MSDP_ARRAY
|
||||
cmdname **kwargs -> cmdname MSDP_TABLE
|
||||
|
||||
OBS - whereas there are also definitions for making arrays and tables in
|
||||
the specification, these are not actually used in the default
|
||||
msdp commands -- returns are implemented as simple lists or
|
||||
named lists (our name for them here, these un-bounded
|
||||
structures are not named in the specification). So for now,
|
||||
this routine will not explicitly create arrays nor tables,
|
||||
although there are helper methods ready should it be needed in
|
||||
the future.
|
||||
"""
|
||||
msdp_string = ""
|
||||
if args:
|
||||
if len(args) == 1:
|
||||
msdp_string = "%s %s" % (cmdname.upper(), args[0])
|
||||
else:
|
||||
msdp_string = "%s%s%s%s" % (MSDP_VAR, cmdname.upper(), "".join(
|
||||
"%s%s" % (MSDP_VAL, val) for val in args))
|
||||
elif kwargs:
|
||||
msdp_string = "%s%s%s" % (MSDP_VAR. cmdname.upper(), "".join(
|
||||
["%s%s%s%s" % (MSDP_VAR, key, MSDP_VAL, val) for key, val in kwargs.items()]))
|
||||
return msdp_string
|
||||
|
||||
def encode_gmcp(self, cmdname, *args, **kwargs):
|
||||
"""
|
||||
Gmcp messages are on one of the following outgoing forms:
|
||||
|
||||
cmdname string -> cmdname string
|
||||
cmdname *args -> cmdname [arg, arg, arg, ...]
|
||||
cmdname **kwargs -> cmdname {key:arg, key:arg, ...}
|
||||
|
||||
cmdname is generally recommended to be a string on the form
|
||||
Module.Submodule.Function
|
||||
"""
|
||||
if args:
|
||||
gmcp_string = "%s %s" % (cmdname, json.dumps(args))
|
||||
elif kwargs:
|
||||
gmcp_string = "%s %s" % (cmdname, json.dumps(kwargs))
|
||||
return gmcp_string
|
||||
|
||||
def decode_msdp(self, data):
|
||||
"""
|
||||
Decodes incoming MSDP data
|
||||
|
||||
cmdname, var --> cmdname arg
|
||||
cmdname, array --> cmdname *args
|
||||
cmdname, table --> cmdname **kwargs
|
||||
|
||||
"""
|
||||
tables = {}
|
||||
arrays = {}
|
||||
variables = {}
|
||||
|
||||
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] = {}
|
||||
for varval in msdp_regex_var.split(table):
|
||||
parts = msdp_regex_val.split(varval)
|
||||
tables[key].expand({parts[0]: tuple(parts[1:]) if len(parts) > 1 else ("",)})
|
||||
for key, array in msdp_regex_array.findall(data):
|
||||
arrays[key] = []
|
||||
for val in msdp_regex_val.split(array):
|
||||
arrays[key].append(val)
|
||||
arrays[key] = tuple(arrays[key])
|
||||
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 ("", )
|
||||
|
||||
# send to the sessionhandler
|
||||
if data:
|
||||
for varname, var in variables.items():
|
||||
# a simple function + argument
|
||||
self.protocol.data_in(oob=(varname, var, {}))
|
||||
for arrayname, array in arrays.items():
|
||||
# we assume the array are multiple arguments to the function
|
||||
self.protocol.data_in(oob=(arrayname, array, {}))
|
||||
for tablename, table in tables.items():
|
||||
# we assume tables are keyword arguments to the function
|
||||
self.protocol.data_in(oob=(tablename, (), table))
|
||||
|
||||
def decode_gmcp(self, data):
|
||||
"""
|
||||
Decodes incoming GMCP data on the form 'varname <structure>'
|
||||
|
||||
cmdname string -> cmdname arg
|
||||
cmdname [arg, arg,...] -> cmdname *args
|
||||
cmdname {key:arg, key:arg, ...} -> cmdname **kwargs
|
||||
|
||||
"""
|
||||
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 {}))
|
||||
|
||||
# access methods
|
||||
|
||||
def data_out(self, cmdname, *args, **kwargs):
|
||||
"""
|
||||
Return a msdp-valid subnegotiation across the protocol.
|
||||
"""
|
||||
if self.MSDP:
|
||||
encoded_oob = force_str(self.encode_msdp(cmdname, *args, **kwargs))
|
||||
self.protocol._write(IAC + SB + MSDP + encoded_oob + IAC + SE)
|
||||
else:
|
||||
encoded_oob = force_str(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)
|
||||
Loading…
Add table
Add a link
Reference in a new issue