Finished converting server/ and server/portal to google-style docstrings as per #709.

This commit is contained in:
Griatch 2015-06-23 15:20:32 +02:00
parent ccae355175
commit 19bfaae8a6
15 changed files with 906 additions and 268 deletions

View file

@ -21,6 +21,7 @@ from django.utils.translation import ugettext as _
class IMC2Mud(object): class IMC2Mud(object):
""" """
Stores information about other games connected to our current IMC2 network. Stores information about other games connected to our current IMC2 network.
""" """
def __init__(self, packet): def __init__(self, packet):
self.name = packet.origin self.name = packet.origin
@ -37,6 +38,7 @@ class IMC2Mud(object):
class IMC2MudList(dict): class IMC2MudList(dict):
""" """
Keeps track of other MUDs connected to the IMC network. Keeps track of other MUDs connected to the IMC network.
""" """
def get_mud_list(self): def get_mud_list(self):
""" """
@ -50,6 +52,10 @@ class IMC2MudList(dict):
""" """
This grabs relevant info from the packet and stuffs it in the This grabs relevant info from the packet and stuffs it in the
Mud list for later retrieval. Mud list for later retrieval.
Args:
packet (Packet): incoming packet.
""" """
mud = IMC2Mud(packet) mud = IMC2Mud(packet)
self[mud.name] = mud self[mud.name] = mud
@ -57,6 +63,10 @@ class IMC2MudList(dict):
def remove_mud_from_packet(self, packet): def remove_mud_from_packet(self, packet):
""" """
Removes a mud from the Mud list when given a packet. Removes a mud from the Mud list when given a packet.
Args:
packet (Packet): Incoming packet.
""" """
mud = IMC2Mud(packet) mud = IMC2Mud(packet)
try: try:
@ -71,6 +81,7 @@ class IMC2Channel(object):
Stores information about channels available on the network. Stores information about channels available on the network.
""" """
def __init__(self, packet): def __init__(self, packet):
"Initialize channel."
self.localname = packet.optional_data.get('localname', None) self.localname = packet.optional_data.get('localname', None)
self.name = packet.optional_data.get('channel', None) self.name = packet.optional_data.get('channel', None)
self.level = packet.optional_data.get('level', None) self.level = packet.optional_data.get('level', None)
@ -87,6 +98,7 @@ class IMC2ChanList(dict):
def get_channel_list(self): def get_channel_list(self):
""" """
Returns a sorted list of cached channels. Returns a sorted list of cached channels.
""" """
channels = self.items() channels = self.items()
channels.sort() channels.sort()
@ -96,6 +108,10 @@ class IMC2ChanList(dict):
""" """
This grabs relevant info from the packet and stuffs it in the This grabs relevant info from the packet and stuffs it in the
channel list for later retrieval. channel list for later retrieval.
Args:
packet (Packet): incoming packet.
""" """
channel = IMC2Channel(packet) channel = IMC2Channel(packet)
self[channel.name] = channel self[channel.name] = channel
@ -103,6 +119,10 @@ class IMC2ChanList(dict):
def remove_channel_from_packet(self, packet): def remove_channel_from_packet(self, packet):
""" """
Removes a channel from the Channel list when given a packet. Removes a channel from the Channel list when given a packet.
Args:
packet (Packet): incoming packet.
""" """
channel = IMC2Channel(packet) channel = IMC2Channel(packet)
try: try:
@ -120,8 +140,10 @@ class IMC2Bot(telnet.StatefulTelnetProtocol, Session):
""" """
Provides the abstraction for the IMC2 protocol. Handles connection, Provides the abstraction for the IMC2 protocol. Handles connection,
authentication, and all necessary packets. authentication, and all necessary packets.
""" """
def __init__(self): def __init__(self):
"Initialize bot."
self.is_authenticated = False self.is_authenticated = False
# only support plaintext passwords # only support plaintext passwords
self.auth_type = "plaintext" self.auth_type = "plaintext"
@ -130,37 +152,49 @@ class IMC2Bot(telnet.StatefulTelnetProtocol, Session):
self.imc2_chanlist = IMC2ChanList() self.imc2_chanlist = IMC2ChanList()
def _send_packet(self, packet): def _send_packet(self, packet):
"Helper function to send packets across the wire" """
Helper function to send packets across the wire.
Args:
packet (Packet): Outgoing packet.
"""
packet.imc2_protocol = self packet.imc2_protocol = self
packet_str = utils.to_str(packet.assemble(self.factory.mudname, packet_str = utils.to_str(packet.assemble(self.factory.mudname,
self.factory.client_pwd, self.factory.server_pwd)) self.factory.client_pwd, self.factory.server_pwd))
self.sendLine(packet_str) self.sendLine(packet_str)
def _isalive(self): def _isalive(self):
"Send an isalive packet" "Send an isalive packet."
self._send_packet(pck.IMC2PacketIsAlive()) self._send_packet(pck.IMC2PacketIsAlive())
def _keepalive(self): def _keepalive(self):
"Send a keepalive packet" "Send a keepalive packet."
# send to channel? # send to channel?
self._send_packet(pck.IMC2PacketKeepAliveRequest()) self._send_packet(pck.IMC2PacketKeepAliveRequest())
def _channellist(self): def _channellist(self):
"Sync the network channel list" "Sync the network channel list."
checked_networks = [] checked_networks = []
if not self.network in checked_networks: if not self.network in checked_networks:
self._send_packet(pck.IMC2PacketIceRefresh()) self._send_packet(pck.IMC2PacketIceRefresh())
checked_networks.append(self.network) checked_networks.append(self.network)
def _prune(self): def _prune(self):
"Prune active channel list" "Prune active channel list."
t0 = time() t0 = time()
for name, mudinfo in self.imc2_mudlist.items(): for name, mudinfo in self.imc2_mudlist.items():
if t0 - mudinfo.last_updated > 3599: if t0 - mudinfo.last_updated > 3599:
del self.imc2_mudlist[name] del self.imc2_mudlist[name]
def _whois_reply(self, packet): def _whois_reply(self, packet):
"handle reply from server from an imcwhois request" """
Handle reply from server from an imcwhois request.
Args:
packet (Packet): Data packet.
"""
# packet.target potentially contains the id of an character to target # packet.target potentially contains the id of an character to target
# not using that here # not using that here
response_text = imc2_ansi.parse_ansi(packet.optional_data.get('text', 'Unknown')) response_text = imc2_ansi.parse_ansi(packet.optional_data.get('text', 'Unknown'))
@ -171,13 +205,24 @@ class IMC2Bot(telnet.StatefulTelnetProtocol, Session):
def _format_tell(self, packet): def _format_tell(self, packet):
""" """
Handle tells over IMC2 by formatting the text properly Handle tells over IMC2 by formatting the text properly
Args:
packet (Packet): Data packet.
""" """
return _("{c%(sender)s@%(origin)s{n {wpages (over IMC):{n %(msg)s") % {"sender": packet.sender, return _("{c%(sender)s@%(origin)s{n {wpages (over IMC):{n %(msg)s") % {"sender": packet.sender,
"origin": packet.origin, "origin": packet.origin,
"msg": packet.optional_data.get('text', 'ERROR: No text provided.')} "msg": packet.optional_data.get('text', 'ERROR: No text provided.')}
def _imc_login(self, line): def _imc_login(self, line):
"Connect and identify to imc network" """
Connect and identify to imc network as per the
`self.auth_type` setting.
Args:
line (str): Incoming text.
"""
if self.auth_type == "plaintext": if self.auth_type == "plaintext":
# Only support Plain text passwords. # Only support Plain text passwords.
@ -218,6 +263,7 @@ class IMC2Bot(telnet.StatefulTelnetProtocol, Session):
def connectionMade(self): def connectionMade(self):
""" """
Triggered after connecting to the IMC2 network. Triggered after connecting to the IMC2 network.
""" """
self.stopping = False self.stopping = False
@ -239,6 +285,9 @@ class IMC2Bot(telnet.StatefulTelnetProtocol, Session):
Triggered when text is received from the IMC2 network. Figures out Triggered when text is received from the IMC2 network. Figures out
what to do with the packet. This deals with the following what to do with the packet. This deals with the following
Args:
line (str): Incoming text.
""" """
line = line.strip() line = line.strip()
@ -278,24 +327,31 @@ class IMC2Bot(telnet.StatefulTelnetProtocol, Session):
def data_in(self, text=None, **kwargs): def data_in(self, text=None, **kwargs):
""" """
Data IMC2 -> Evennia Data IMC2 -> Evennia.
Kwargs:
text (str): Incoming text.
kwargs (any): Other data from protocol.
""" """
text = "bot_data_in " + text text = "bot_data_in " + text
self.sessionhandler.data_in(self, text=text, **kwargs) self.sessionhandler.data_in(self, text=text, **kwargs)
def data_out(self, text=None, **kwargs): def data_out(self, text=None, **kwargs):
""" """
Evennia -> IMC2 Evennia -> IMC2.
Keywords Kwargs:
packet_type: text (str): Outgoing text.
broadcast - send to everyone on IMC channel packet_type (str):
tell - send a tell (see target keyword) - broadcast: Send to everyone on IMC channel.
whois - get whois information (see target keyword) - tell: Send a tell (see target keyword).
sender - used by tell to identify the sender - whois: Get whois information (see target keyword).
target - key identifier of target to tells or whois. If not sender (str): Used by tell to identify the mud sending.
target (str): Key identifier of target to tells or whois. If not
given "Unknown" will be used. given "Unknown" will be used.
destination - used by tell to specify mud destination to send to destination (str): Used by tell to specify mud
destination to send to.
""" """
@ -338,6 +394,7 @@ class IMC2BotFactory(protocol.ReconnectingClientFactory):
""" """
Creates instances of the IMC2Protocol. Should really only ever Creates instances of the IMC2Protocol. Should really only ever
need to create one connection. Tied in via evennia/server.py. need to create one connection. Tied in via evennia/server.py.
""" """
initialDelay = 1 initialDelay = 1
factor = 1.5 factor = 1.5
@ -345,6 +402,7 @@ class IMC2BotFactory(protocol.ReconnectingClientFactory):
def __init__(self, sessionhandler, uid=None, network=None, channel=None, def __init__(self, sessionhandler, uid=None, network=None, channel=None,
port=None, mudname=None, client_pwd=None, server_pwd=None): port=None, mudname=None, client_pwd=None, server_pwd=None):
"Initialize the bot factory."
self.uid = uid self.uid = uid
self.network = network self.network = network
sname, host = network.split(".", 1) sname, host = network.split(".", 1)
@ -362,7 +420,16 @@ class IMC2BotFactory(protocol.ReconnectingClientFactory):
self.task_channellist = None self.task_channellist = None
def buildProtocol(self, addr): def buildProtocol(self, addr):
"Build the protocol" """
Build the protocol.
Args:
addr (str): Protocl address.
Returns:
protocol (Protocol): The new protocol.
"""
protocol = IMC2Bot() protocol = IMC2Bot()
protocol.factory = self protocol.factory = self
protocol.network = self.network protocol.network = self.network
@ -373,9 +440,23 @@ class IMC2BotFactory(protocol.ReconnectingClientFactory):
return protocol return protocol
def clientConnectionFailed(self, connector, reason): def clientConnectionFailed(self, connector, reason):
"""
Called when Client could not connect.
Args:
connector (Connector): Reprsents the connection.
reason (str): Reason for the failure.
"""
self.retry(connector) self.retry(connector)
def clientConnectionLost(self, connector, reason): def clientConnectionLost(self, connector, reason):
"""
Called when Client looses connection.
Args:
connector (Connector): Reprsents the connection.
reason (str): Reason for the failure.
"""
if not self.bot.stopping: if not self.bot.stopping:
self.retry(connector) self.retry(connector)

View file

@ -83,11 +83,28 @@ RE_MXP = re.compile(r'\{lc(.*?)\{lt(.*?)\{le', re.DOTALL)
RE_ANSI_ESCAPES = re.compile(r"(%s)" % "|".join(("{{", "%%", "\\\\")), re.DOTALL) RE_ANSI_ESCAPES = re.compile(r"(%s)" % "|".join(("{{", "%%", "\\\\")), re.DOTALL)
def sub_irc(ircmatch): def sub_irc(ircmatch):
"""
Substitute irc color info. Used by re.sub.
Args:
ircmatch (Match): The match from regex.
Returns:
colored (str): A string with converted IRC colors.
"""
return IRC_COLOR_MAP.get(ircmatch.group(), "") return IRC_COLOR_MAP.get(ircmatch.group(), "")
def parse_irc_colors(string): def parse_irc_colors(string):
""" """
Parse {-type syntax and replace with IRC color markers Parse {-type syntax and replace with IRC color markers
Args:
string (str): String to parse for IRC colors.
Returns:
parsed_string (str): String with replaced IRC colors.
""" """
in_string = utils.to_str(string) in_string = utils.to_str(string)
parsed_string = "" parsed_string = ""
@ -105,6 +122,7 @@ class IRCBot(irc.IRCClient, Session):
""" """
An IRC bot that tracks actitivity in a channel as well An IRC bot that tracks actitivity in a channel as well
as sends text to it when prompted as sends text to it when prompted
""" """
lineRate = 1 lineRate = 1
@ -117,9 +135,9 @@ class IRCBot(irc.IRCClient, Session):
def signedOn(self): def signedOn(self):
""" """
This is called when we successfully connect to This is called when we successfully connect to the network. We
the network. We make sure to now register with make sure to now register with the game as a full session.
the game as a full session.
""" """
self.join(self.channel) self.join(self.channel)
self.stopping = False self.stopping = False
@ -135,7 +153,11 @@ class IRCBot(irc.IRCClient, Session):
def disconnect(self, reason=None): def disconnect(self, reason=None):
""" """
Called by sessionhandler to disconnect this protocol Called by sessionhandler to disconnect this protocol.
Args:
reason (str): Motivation for the disconnect.
""" """
print "irc disconnect called!" print "irc disconnect called!"
self.sessionhandler.disconnect(self) self.sessionhandler.disconnect(self)
@ -143,23 +165,53 @@ class IRCBot(irc.IRCClient, Session):
self.transport.loseConnection() self.transport.loseConnection()
def privmsg(self, user, channel, msg): def privmsg(self, user, channel, msg):
"A message was sent to channel" """
Called when the connected channel receives a message.
Args:
user (str): User name sending the message.
channel (str): Channel name seeing the message.
msg (str): The message arriving from channel.
"""
if not msg.startswith('***'): if not msg.startswith('***'):
user = user.split('!', 1)[0] user = user.split('!', 1)[0]
self.data_in("bot_data_in %s@%s: %s" % (user, channel, msg)) self.data_in("bot_data_in %s@%s: %s" % (user, channel, msg))
def action(self, user, channel, msg): def action(self, user, channel, msg):
"An action was done in channel" """
Called when an action is detected in channel.
Args:
user (str): User name sending the message.
channel (str): Channel name seeing the message.
msg (str): The message arriving from channel.
"""
if not msg.startswith('**'): if not msg.startswith('**'):
user = user.split('!', 1)[0] user = user.split('!', 1)[0]
self.data_in("bot_data_in %s@%s %s" % (user, channel, msg)) self.data_in("bot_data_in %s@%s %s" % (user, channel, msg))
def data_in(self, text=None, **kwargs): def data_in(self, text=None, **kwargs):
"Data IRC -> Server" """
Data IRC -> Server.
Kwargs:
text (str): Ingoing text.
kwargs (any): Other data from protocol.
"""
self.sessionhandler.data_in(self, text=text, **kwargs) self.sessionhandler.data_in(self, text=text, **kwargs)
def data_out(self, text=None, **kwargs): def data_out(self, text=None, **kwargs):
"Data from server-> IRC" """
Data from server-> IRC.
Kwargs:
text (str): Outgoing text.
kwargs (any): Other data to protocol.
"""
if text.startswith("bot_data_out"): if text.startswith("bot_data_out"):
text = text.split(" ", 1)[1] text = text.split(" ", 1)[1]
text = parse_irc_colors(text) text = parse_irc_colors(text)
@ -168,8 +220,9 @@ class IRCBot(irc.IRCClient, Session):
class IRCBotFactory(protocol.ReconnectingClientFactory): class IRCBotFactory(protocol.ReconnectingClientFactory):
""" """
Creates instances of AnnounceBot, connecting with Creates instances of AnnounceBot, connecting with a staggered
a staggered increase in delay increase in delay
""" """
# scaling reconnect time # scaling reconnect time
initialDelay = 1 initialDelay = 1
@ -177,7 +230,20 @@ class IRCBotFactory(protocol.ReconnectingClientFactory):
maxDelay = 60 maxDelay = 60
def __init__(self, sessionhandler, uid=None, botname=None, channel=None, network=None, port=None): def __init__(self, sessionhandler, uid=None, botname=None, channel=None, network=None, port=None):
"Storing some important protocol properties" """
Storing some important protocol properties.
Args:
sessionhandler (SessionHandler): Reference to the main Sessionhandler.
Kwargs:
uid (int): Bot user id.
botname (str): Bot name (seen in IRC channel).
channel (str): IRC channel to connect to.
network (str): Network address to connect to.
port (str): Port of the network.
"""
self.sessionhandler = sessionhandler self.sessionhandler = sessionhandler
self.uid = uid self.uid = uid
self.nickname = str(botname) self.nickname = str(botname)
@ -187,7 +253,13 @@ class IRCBotFactory(protocol.ReconnectingClientFactory):
self.bot = None self.bot = None
def buildProtocol(self, addr): def buildProtocol(self, addr):
"Build the protocol and assign it some properties" """
Build the protocol and assign it some properties.
Args:
addr (str): Not used; using factory data.
"""
protocol = IRCBot() protocol = IRCBot()
protocol.factory = self protocol.factory = self
protocol.nickname = self.nickname protocol.nickname = self.nickname
@ -197,18 +269,43 @@ class IRCBotFactory(protocol.ReconnectingClientFactory):
return protocol return protocol
def startedConnecting(self, connector): def startedConnecting(self, connector):
"Tracks reconnections for debugging" """
Tracks reconnections for debugging.
Args:
connector (Connector): Represents the connection.
"""
logger.log_infomsg("(re)connecting to %s" % self.channel) logger.log_infomsg("(re)connecting to %s" % self.channel)
def clientConnectionFailed(self, connector, reason): def clientConnectionFailed(self, connector, reason):
"""
Called when Client failed to connect.
Args:
connector (Connection): Represents the connection.
reason (str): The reason for the failure.
"""
self.retry(connector) self.retry(connector)
def clientConnectionLost(self, connector, reason): def clientConnectionLost(self, connector, reason):
"""
Called when Client looses connection.
Args:
connector (Connection): Represents the connection.
reason (str): The reason for the failure.
"""
if not self.bot.stopping: if not self.bot.stopping:
self.retry(connector) self.retry(connector)
def start(self): def start(self):
"Connect session to sessionhandler" """
Connect session to sessionhandler.
"""
if self.port: if self.port:
service = internet.TCPClient(self.network, int(self.port), self) service = internet.TCPClient(self.network, int(self.port), self)
self.sessionhandler.portal.services.addService(service) self.sessionhandler.portal.services.addService(service)

View file

@ -22,7 +22,16 @@ FLUSH = zlib.Z_SYNC_FLUSH
def mccp_compress(protocol, data): def mccp_compress(protocol, data):
"Handles zlib compression, if applicable" """
Handles zlib compression, if applicable.
Args:
data (str): Incoming data to compress.
Returns:
stream (binary): Zlib-compressed data.
"""
if hasattr(protocol, 'zlib'): if hasattr(protocol, 'zlib'):
return protocol.zlib.compress(data) + protocol.zlib.flush(FLUSH) return protocol.zlib.compress(data) + protocol.zlib.flush(FLUSH)
return data return data
@ -32,6 +41,7 @@ class Mccp(object):
""" """
Implements the MCCP protocol. Add this to a Implements the MCCP protocol. Add this to a
variable on the telnet protocol to set it up. variable on the telnet protocol to set it up.
""" """
def __init__(self, protocol): def __init__(self, protocol):
@ -40,6 +50,10 @@ class Mccp(object):
ourselves and calling the client to see if ourselves and calling the client to see if
it supports MCCP. Sets callbacks to it supports MCCP. Sets callbacks to
start zlib compression in that case. start zlib compression in that case.
Args:
protocol (Protocol): The active protocol instance.
""" """
self.protocol = protocol self.protocol = protocol
@ -49,7 +63,11 @@ class Mccp(object):
def no_mccp(self, option): def no_mccp(self, option):
""" """
Called if client doesn't support mccp or chooses to turn it off Called if client doesn't support mccp or chooses to turn it off.
Args:
option (Option): Option dict (not used).
""" """
if hasattr(self.protocol, 'zlib'): if hasattr(self.protocol, 'zlib'):
del self.protocol.zlib del self.protocol.zlib
@ -60,6 +78,10 @@ class Mccp(object):
""" """
The client supports MCCP. Set things up by The client supports MCCP. Set things up by
creating a zlib compression stream. creating a zlib compression stream.
Args:
option (Option): Option dict (not used).
""" """
self.protocol.protocol_flags['MCCP'] = True self.protocol.protocol_flags['MCCP'] = True
self.protocol.requestNegotiation(MCCP, '') self.protocol.requestNegotiation(MCCP, '')

View file

@ -23,29 +23,50 @@ MSSPTable_CUSTOM = utils.variable_from_module(settings.MSSP_META_MODULE, "MSSPTa
class Mssp(object): class Mssp(object):
""" """
Implements the MSSP protocol. Add this to a Implements the MSSP protocol. Add this to a variable on the telnet
variable on the telnet protocol to set it up. protocol to set it up.
""" """
def __init__(self, protocol): def __init__(self, protocol):
""" """
initialize MSSP by storing protocol on ourselves initialize MSSP by storing protocol on ourselves and calling
and calling the client to see if it supports the client to see if it supports MSSP.
MSSP.
Args:
protocol (Protocol): The active protocol instance.
""" """
self.protocol = protocol self.protocol = protocol
self.protocol.will(MSSP).addCallbacks(self.do_mssp, self.no_mssp) self.protocol.will(MSSP).addCallbacks(self.do_mssp, self.no_mssp)
def get_player_count(self): def get_player_count(self):
"Get number of logged-in players" """
Get number of logged-in players.
Returns:
count (int): The number of players in the MUD.
"""
return str(self.protocol.sessionhandler.count_loggedin()) return str(self.protocol.sessionhandler.count_loggedin())
def get_uptime(self): def get_uptime(self):
"Get how long the portal has been online (reloads are not counted)" """
Get how long the portal has been online (reloads are not counted).
Returns:
uptime (int): Number of seconds of uptime.
"""
return str(self.protocol.sessionhandler.uptime) return str(self.protocol.sessionhandler.uptime)
def no_mssp(self, option): def no_mssp(self, option):
""" """
This is the normal operation. Called when mssp is not requested. This is the normal
operation.
Args:
option (Option): Not used.
""" """
self.protocol.handshake_done() self.protocol.handshake_done()
pass pass
@ -53,6 +74,10 @@ class Mssp(object):
def do_mssp(self, option): def do_mssp(self, option):
""" """
Negotiate all the information. Negotiate all the information.
Args:
option (Option): Not used.
""" """
self.mssp_table = { self.mssp_table = {

View file

@ -11,6 +11,7 @@ More information can be found on the following links:
http://www.zuggsoft.com/zmud/mxp.htm http://www.zuggsoft.com/zmud/mxp.htm
http://www.mushclient.com/mushclient/mxp.htm http://www.mushclient.com/mushclient/mxp.htm
http://www.gammon.com.au/mushclient/addingservermxp.htm http://www.gammon.com.au/mushclient/addingservermxp.htm
""" """
import re import re
@ -27,6 +28,13 @@ MXP_SEND = MXP_TEMPSECURE + \
def mxp_parse(text): def mxp_parse(text):
""" """
Replaces links to the correct format for MXP. Replaces links to the correct format for MXP.
Args:
text (str): The text to parse.
Returns:
parsed (str): The parsed text.
""" """
text = text.replace("&", "&") \ text = text.replace("&", "&") \
.replace("<", "&lt;") \ .replace("<", "&lt;") \
@ -38,24 +46,39 @@ def mxp_parse(text):
class Mxp(object): class Mxp(object):
""" """
Implements the MXP protocol. Implements the MXP protocol.
""" """
def __init__(self, protocol): def __init__(self, protocol):
"""Initializes the protocol by checking if the client supports it.""" """
Initializes the protocol by checking if the client supports it.
Args:
protocol (Protocol): The active protocol instance.
"""
self.protocol = protocol self.protocol = protocol
self.protocol.protocol_flags["MXP"] = False self.protocol.protocol_flags["MXP"] = False
self.protocol.will(MXP).addCallbacks(self.do_mxp, self.no_mxp) self.protocol.will(MXP).addCallbacks(self.do_mxp, self.no_mxp)
def no_mxp(self, option): def no_mxp(self, option):
""" """
Client does not support MXP. Called when the Client reports to not support MXP.
Args:
option (Option): Not used.
""" """
self.protocol.protocol_flags["MXP"] = False self.protocol.protocol_flags["MXP"] = False
self.protocol.handshake_done() self.protocol.handshake_done()
def do_mxp(self, option): def do_mxp(self, option):
""" """
Client does support MXP. Called when the Client reports to support MXP.
Args:
option (Option): Not used.
""" """
self.protocol.protocol_flags["MXP"] = True self.protocol.protocol_flags["MXP"] = True
self.protocol.handshake_done() self.protocol.handshake_done()

View file

@ -5,9 +5,8 @@ NAWS - Negotiate About Window Size
This implements the NAWS telnet option as per This implements the NAWS telnet option as per
https://www.ietf.org/rfc/rfc1073.txt https://www.ietf.org/rfc/rfc1073.txt
NAWS allows telnet clients to report their NAWS allows telnet clients to report their current window size to the
current window size to the client and update client and update it when the size changes
it when the size changes
""" """
from django.conf import settings from django.conf import settings
@ -22,14 +21,18 @@ DEFAULT_HEIGHT = settings.CLIENT_DEFAULT_HEIGHT
class Naws(object): class Naws(object):
""" """
Implements the MSSP protocol. Add this to a Implements the NAWS protocol. Add this to a variable on the telnet
variable on the telnet protocol to set it up. protocol to set it up.
""" """
def __init__(self, protocol): def __init__(self, protocol):
""" """
initialize NAWS by storing protocol on ourselves initialize NAWS by storing protocol on ourselves and calling
and calling the client to see if it supports the client to see if it supports NAWS.
NAWS.
Args:
protocol (Protocol): The active protocol instance.
""" """
self.naws_step = 0 self.naws_step = 0
self.protocol = protocol self.protocol = protocol
@ -40,17 +43,33 @@ class Naws(object):
def no_naws(self, option): def no_naws(self, option):
""" """
This is the normal operation. Called when client is not reporting NAWS. This is the normal
operation.
Args:
option (Option): Not used.
""" """
self.protocol.handshake_done() self.protocol.handshake_done()
def do_naws(self, option): def do_naws(self, option):
""" """
Negotiate all the information. Client wants to negotiate all the NAWS information.
Args:
option (Option): Not used.
""" """
self.protocol.handshake_done() self.protocol.handshake_done()
def negotiate_sizes(self, options): def negotiate_sizes(self, options):
"""
Step through the NAWS handshake.
Args:
option (list): The incoming NAWS options.
"""
if len(options) == 4: if len(options) == 4:
# NAWS is negotiated with 16bit words # NAWS is negotiated with 16bit words
width = options[0] + options[1] width = options[0] + options[1]

View file

@ -74,8 +74,9 @@ AMP_ENABLED = AMP_HOST and AMP_PORT and AMP_INTERFACE
_IDLE_TIMEOUT = settings.IDLE_TIMEOUT _IDLE_TIMEOUT = settings.IDLE_TIMEOUT
def _portal_maintenance(): def _portal_maintenance():
""" """
The maintenance function handles repeated checks and updates The maintenance function handles repeated checks and updates that
that the server needs to do. It is called every minute. the server needs to do. It is called every minute.
""" """
# check for idle sessions # check for idle sessions
now = time.time() now = time.time()
@ -85,6 +86,7 @@ def _portal_maintenance():
if (now - sess.cmd_last) > _IDLE_TIMEOUT]: if (now - sess.cmd_last) > _IDLE_TIMEOUT]:
session.data_out(reason) session.data_out(reason)
PORTAL_SESSIONS.disconnect(session) PORTAL_SESSIONS.disconnect(session)
if _IDLE_TIMEOUT > 0: if _IDLE_TIMEOUT > 0:
# only start the maintenance task if we care about idling. # only start the maintenance task if we care about idling.
_maintenance_task = LoopingCall(_portal_maintenance) _maintenance_task = LoopingCall(_portal_maintenance)
@ -97,16 +99,18 @@ if _IDLE_TIMEOUT > 0:
class Portal(object): class Portal(object):
""" """
The main Portal server handler. This object sets up the database and The main Portal server handler. This object sets up the database
tracks and interlinks all the twisted network services that make up and tracks and interlinks all the twisted network services that
Portal. make up Portal.
""" """
def __init__(self, application): def __init__(self, application):
""" """
Setup the server. Setup the server.
application - an instantiated Twisted application Args:
application (Application): An instantiated Twisted application
""" """
sys.path.append('.') sys.path.append('.')
@ -125,9 +129,13 @@ class Portal(object):
def set_restart_mode(self, mode=None): def set_restart_mode(self, mode=None):
""" """
This manages the flag file that tells the runner if the server should This manages the flag file that tells the runner if the server
be restarted or is shutting down. Valid modes are True/False and None. should be restarted or is shutting down.
Args:
mode (bool or None): Valid modes are True/False and None.
If mode is None, no change will be done to the flag file. If mode is None, no change will be done to the flag file.
""" """
if mode is None: if mode is None:
return return
@ -139,15 +147,19 @@ class Portal(object):
""" """
Shuts down the server from inside it. Shuts down the server from inside it.
restart - True/False sets the flags so the server will be Args:
restarted or not. If None, the current flag setting restart (bool or None, optional): True/False sets the
(set at initialization or previous runs) is used. flags so the server will be restarted or not. If None, the
_reactor_stopping - this is set if server is already in the process of current flag setting (set at initialization or previous
shutting down; in this case we don't need to stop it again. runs) is used.
_reactor_stopping (bool, optional): This is set if server
is already in the process of shutting down; in this case
we don't need to stop it again.
Note that restarting (regardless of the setting) will not work Note that restarting (regardless of the setting) will not work
if the Portal is currently running in daemon mode. In that if the Portal is currently running in daemon mode. In that
case it always needs to be restarted manually. case it always needs to be restarted manually.
""" """
if _reactor_stopping and hasattr(self, "shutdown_complete"): if _reactor_stopping and hasattr(self, "shutdown_complete"):
# we get here due to us calling reactor.stop below. No need # we get here due to us calling reactor.stop below. No need

View file

@ -33,6 +33,7 @@ class PortalSessionHandler(SessionHandler):
def __init__(self): def __init__(self):
""" """
Init the handler Init the handler
""" """
self.portal = None self.portal = None
self.sessions = {} self.sessions = {}
@ -43,21 +44,26 @@ class PortalSessionHandler(SessionHandler):
def at_server_connection(self): def at_server_connection(self):
""" """
Called when the Portal establishes connection with the Called when the Portal establishes connection with the Server.
Server. At this point, the AMP connection is already At this point, the AMP connection is already established.
established.
""" """
self.connection_time = time() self.connection_time = time()
def connect(self, session): def connect(self, session):
""" """
Called by protocol at first connect. This adds a not-yet Called by protocol at first connect. This adds a not-yet
authenticated session using an ever-increasing counter for sessid. authenticated session using an ever-increasing counter for
sessid.
We implement a throttling mechanism here to limit the speed at which Args:
new connections are accepted - this is both a stop against DoS attacks session (PortalSession): The Session connecting.
as well as helps using the Dummyrunner tester with a large number of
connector dummies. Notes:
We implement a throttling mechanism here to limit the speed at
which new connections are accepted - this is both a stop
against DoS attacks as well as helps using the Dummyrunner
tester with a large number of connector dummies.
""" """
session.server_connected = False session.server_connected = False
@ -95,8 +101,12 @@ class PortalSessionHandler(SessionHandler):
def sync(self, session): def sync(self, session):
""" """
Called by the protocol of an already connected session. This Called by the protocol of an already connected session. This
can be used to sync the session info in a delayed manner, can be used to sync the session info in a delayed manner, such
such as when negotiation and handshakes are delayed. as when negotiation and handshakes are delayed.
Args:
session (PortalSession): Session to sync.
""" """
if session.sessid and session.server_connected: if session.sessid and session.server_connected:
# only use if session already has sessid and has already connected # only use if session already has sessid and has already connected
@ -119,8 +129,12 @@ class PortalSessionHandler(SessionHandler):
def disconnect(self, session): def disconnect(self, session):
""" """
Called from portal side when the connection is closed Called from portal when the connection is closed from the
from the portal side. portal side.
Args:
session (PortalSession): Session to disconnect.
""" """
sessid = session.sessid sessid = session.sessid
self.portal.amp_protocol.call_remote_ServerAdmin(sessid, self.portal.amp_protocol.call_remote_ServerAdmin(sessid,
@ -128,19 +142,25 @@ class PortalSessionHandler(SessionHandler):
def server_connect(self, protocol_path="", config=dict()): def server_connect(self, protocol_path="", config=dict()):
""" """
Called by server to force the initialization of a new Called by server to force the initialization of a new protocol
protocol instance. Server wants this instance to get instance. Server wants this instance to get a unique sessid
a unique sessid and to be connected back as normal. This and to be connected back as normal. This is used to initiate
is used to initiate irc/imc2/rss etc connections. irc/imc2/rss etc connections.
protocol_path - full python path to the class factory Args:
protocol_path (st): Full python path to the class factory
for the protocol used, eg for the protocol used, eg
'evennia.server.portal.irc.IRCClientFactory' 'evennia.server.portal.irc.IRCClientFactory'
config - dictionary of configuration options, fed as **kwarg config (dict): Dictionary of configuration options, fed as
to protocol class' __init__ method. **kwarg to protocol class' __init__ method.
Raises:
RuntimeError: If The correct factory class is not found.
Notes:
The called protocol class must have a method start() The called protocol class must have a method start()
that calls the portalsession.connect() as a normal protocol. that calls the portalsession.connect() as a normal protocol.
""" """
global _MOD_IMPORT global _MOD_IMPORT
if not _MOD_IMPORT: if not _MOD_IMPORT:
@ -154,7 +174,12 @@ class PortalSessionHandler(SessionHandler):
def server_disconnect(self, sessid, reason=""): def server_disconnect(self, sessid, reason=""):
""" """
Called by server to force a disconnect by sessid Called by server to force a disconnect by sessid.
Args:
sessid (int): Session id to disconnect.
reason (str, optional): Motivation for disconect.
""" """
session = self.sessions.get(sessid, None) session = self.sessions.get(sessid, None)
if session: if session:
@ -167,6 +192,10 @@ class PortalSessionHandler(SessionHandler):
def server_disconnect_all(self, reason=""): def server_disconnect_all(self, reason=""):
""" """
Called by server when forcing a clean disconnect for everyone. Called by server when forcing a clean disconnect for everyone.
Args:
reason (str, optional): Motivation for disconnect.
""" """
for session in self.sessions.values(): for session in self.sessions.values():
session.disconnect(reason) session.disconnect(reason)
@ -175,21 +204,29 @@ class PortalSessionHandler(SessionHandler):
def server_logged_in(self, sessid, data): def server_logged_in(self, sessid, data):
""" """
The server tells us that the session has been The server tells us that the session has been authenticated.
authenticated. Updated it. Update it. Called by the Server.
Args:
sessid (int): Session id logging in.
data (dict): The session sync data.
""" """
sess = self.get_session(sessid) sess = self.get_session(sessid)
sess.load_sync_data(data) sess.load_sync_data(data)
def server_session_sync(self, serversessions): def server_session_sync(self, serversessions):
""" """
Server wants to save data to the portal, maybe because it's about Server wants to save data to the portal, maybe because it's
to shut down. We don't overwrite any sessions here, just update about to shut down. We don't overwrite any sessions here, just
them in-place and remove any that are out of sync (which should update them in-place and remove any that are out of sync
normally not be the case) (which should normally not be the case)
serversessions - dictionary {sessid:{property:value},...} describing Args:
the properties to sync on all sessions serversessions (dict): This is a dictionary
`{sessid:{property:value},...}` describing
the properties to sync on all sessions.
""" """
to_save = [sessid for sessid in serversessions if sessid in self.sessions] to_save = [sessid for sessid in serversessions if sessid in self.sessions]
to_delete = [sessid for sessid in self.sessions if sessid not in to_save] to_delete = [sessid for sessid in self.sessions if sessid not in to_save]
@ -203,6 +240,14 @@ class PortalSessionHandler(SessionHandler):
def count_loggedin(self, include_unloggedin=False): def count_loggedin(self, include_unloggedin=False):
""" """
Count loggedin connections, alternatively count all connections. Count loggedin connections, alternatively count all connections.
Args:
include_unloggedin (bool): Also count sessions that have
not yet authenticated.
Returns:
count (int): Number of sessions.
""" """
return len(self.get_sessions(include_unloggedin=include_unloggedin)) return len(self.get_sessions(include_unloggedin=include_unloggedin))
@ -210,13 +255,24 @@ class PortalSessionHandler(SessionHandler):
""" """
Given a session id, retrieve the session (this is primarily Given a session id, retrieve the session (this is primarily
intended to be called by web clients) intended to be called by web clients)
Args:
suid (int): Session id.
Returns:
session (list): The matching session, if found.
""" """
return [sess for sess in self.get_sessions(include_unloggedin=True) return [sess for sess in self.get_sessions(include_unloggedin=True)
if hasattr(sess, 'suid') and sess.suid == suid] if hasattr(sess, 'suid') and sess.suid == suid]
def announce_all(self, message): def announce_all(self, message):
""" """
Send message to all connection sessions Send message to all connected sessions.
Args:
message (str): Message to relay.
""" """
for session in self.sessions.values(): for session in self.sessions.values():
session.data_out(message) session.data_out(message)
@ -226,7 +282,8 @@ class PortalSessionHandler(SessionHandler):
Helper method for each session to use to parse oob structures Helper method for each session to use to parse oob structures
(The 'oob' kwarg of the msg() method). (The 'oob' kwarg of the msg() method).
Args: oobstruct (str or iterable): A structure representing Args:
oobstruct (str or iterable): A structure representing
an oob command on one of the following forms: an oob command on one of the following forms:
- "cmdname" - "cmdname"
- "cmdname", "cmdname" - "cmdname", "cmdname"
@ -242,6 +299,7 @@ class PortalSessionHandler(SessionHandler):
structure (tuple): A generic OOB structure on the form structure (tuple): A generic OOB structure on the form
`((cmdname, (args,), {kwargs}), ...)`, where the two last `((cmdname, (args,), {kwargs}), ...)`, where the two last
args and kwargs may be empty args and kwargs may be empty
""" """
def _parse(oobstruct): def _parse(oobstruct):
slen = len(oobstruct) slen = len(oobstruct)
@ -287,8 +345,17 @@ class PortalSessionHandler(SessionHandler):
def data_in(self, session, text="", **kwargs): def data_in(self, session, text="", **kwargs):
""" """
Called by portal sessions for relaying data coming Called by portal sessions for relaying data coming
in from the protocol to the server. data is in from the protocol to the server.
serialized before passed on.
Args:
session (PortalSession): Session receiving data.
Kwargs:
text (str): Text from protocol.
kwargs (any): Other data from protocol.
Notes:
Data is serialized before passed on.
""" """
# data throttle (anti DoS measure) # data throttle (anti DoS measure)
@ -307,6 +374,14 @@ class PortalSessionHandler(SessionHandler):
Called by server for having the portal relay messages and data Called by server for having the portal relay messages and data
to the correct session protocol. We also convert oob input to to the correct session protocol. We also convert oob input to
a generic form here. a generic form here.
Args:
sessid (int): Session id sending data.
Kwargs:
text (str): Text from protocol.
kwargs (any): Other data from protocol.
""" """
session = self.sessions.get(sessid, None) session = self.sessions.get(sessid, None)
if session: if session:

View file

@ -22,16 +22,29 @@ if RSS_ENABLED:
class RSSReader(Session): class RSSReader(Session):
""" """
A simple RSS reader using universal feedparser A simple RSS reader using the feedparser module.
""" """
def __init__(self, factory, url, rate): def __init__(self, factory, url, rate):
"""
Initialize the reader.
Args:
factory (RSSFactory): The protocol factory.
url (str): The RSS url.
rate (int): The seconds between RSS lookups.
"""
self.url = url self.url = url
self.rate = rate self.rate = rate
self.factory = factory self.factory = factory
self.old_entries = {} self.old_entries = {}
def get_new(self): def get_new(self):
"""Returns list of new items.""" """
Returns list of new items.
"""
feed = feedparser.parse(self.url) feed = feedparser.parse(self.url)
new_entries = [] new_entries = []
for entry in feed['entries']: for entry in feed['entries']:
@ -42,20 +55,41 @@ class RSSReader(Session):
return new_entries return new_entries
def disconnect(self, reason=None): def disconnect(self, reason=None):
"Disconnect from feed" """
Disconnect from feed.
Args:
reason (str, optional): Motivation for the disconnect.
"""
if self.factory.task and self.factory.task.running: if self.factory.task and self.factory.task.running:
self.factory.task.stop() self.factory.task.stop()
self.sessionhandler.disconnect(self) self.sessionhandler.disconnect(self)
def _callback(self, new_entries, init): def _callback(self, new_entries, init):
"Called when RSS returns (threaded)" """
Called when RSS returns.
Args:
new_entries (list): List of new RSS entries since last.
init (bool): If this is a startup operation (at which
point all entries are considered new).
"""
if not init: if not init:
# for initialization we just ignore old entries # for initialization we just ignore old entries
for entry in reversed(new_entries): for entry in reversed(new_entries):
self.data_in("bot_data_in " + entry) self.data_in("bot_data_in " + entry)
def data_in(self, text=None, **kwargs): def data_in(self, text=None, **kwargs):
"Data RSS -> Server" """
Data RSS -> Evennia.
Kwargs:
text (str): Incoming text
kwargs (any): Options from protocol.
"""
self.sessionhandler.data_in(self, text=text, **kwargs) self.sessionhandler.data_in(self, text=text, **kwargs)
def _errback(self, fail): def _errback(self, fail):
@ -63,16 +97,36 @@ class RSSReader(Session):
logger.log_errmsg("RSS feed error: %s" % fail.value) logger.log_errmsg("RSS feed error: %s" % fail.value)
def update(self, init=False): def update(self, init=False):
"Request feed" """
Request the latest version of feed.
Args:
init (bool, optional): If this is an initialization call
or not (during init, all entries are conidered new).
Notes:
This call is done in a separate thread to avoid blocking
on slow connections.
"""
return threads.deferToThread(self.get_new).addCallback(self._callback, init).addErrback(self._errback) return threads.deferToThread(self.get_new).addCallback(self._callback, init).addErrback(self._errback)
class RSSBotFactory(object): class RSSBotFactory(object):
""" """
Initializes new bots Initializes new bots.
""" """
def __init__(self, sessionhandler, uid=None, url=None, rate=None): def __init__(self, sessionhandler, uid=None, url=None, rate=None):
"Initialize" """
Initialize the bot.
Args:
sessionhandler (PortalSessionHandler): The main sessionhandler object.
uid (int): User id for the bot.
url (str): The RSS URL.
rate (int): How often for the RSS to request the latest RSS entries.
"""
self.sessionhandler = sessionhandler self.sessionhandler = sessionhandler
self.url = url self.url = url
self.rate = rate self.rate = rate
@ -82,7 +136,7 @@ class RSSBotFactory(object):
def start(self): def start(self):
""" """
Called by portalsessionhandler Called by portalsessionhandler. Starts te bot.
""" """
def errback(fail): def errback(fail):
logger.log_errmsg(fail.value) logger.log_errmsg(fail.value)

View file

@ -2,9 +2,8 @@
This module implements the ssh (Secure SHell) protocol for encrypted This module implements the ssh (Secure SHell) protocol for encrypted
connections. connections.
This depends on a generic session module that implements This depends on a generic session module that implements the actual
the actual login procedure of the game, tracks login procedure of the game, tracks sessions etc.
sessions etc.
Using standard ssh client, Using standard ssh client,
@ -41,11 +40,16 @@ class SshProtocol(Manhole, session.Session):
Each player connecting over ssh gets this protocol assigned to Each player connecting over ssh gets this protocol assigned to
them. All communication between game and player goes through them. All communication between game and player goes through
here. here.
""" """
def __init__(self, starttuple): def __init__(self, starttuple):
""" """
For setting up the player. If player is not None then we'll For setting up the player. If player is not None then we'll
login automatically. login automatically.
Args:
starttuple (tuple): A (player, factory) tuple.
""" """
self.authenticated_player = starttuple[0] self.authenticated_player = starttuple[0]
# obs must not be called self.factory, that gets overwritten! # obs must not be called self.factory, that gets overwritten!
@ -54,6 +58,11 @@ class SshProtocol(Manhole, session.Session):
def terminalSize(self, width, height): def terminalSize(self, width, height):
""" """
Initialize the terminal and connect to the new session. Initialize the terminal and connect to the new session.
Args:
width (int): Width of terminal.
height (int): Height of terminal.
""" """
# Clear the previous input line, redraw it at the new # Clear the previous input line, redraw it at the new
# cursor position # cursor position
@ -74,8 +83,8 @@ class SshProtocol(Manhole, session.Session):
def connectionMade(self): def connectionMade(self):
""" """
This is called when the connection is first This is called when the connection is first established.
established.
""" """
recvline.HistoricRecvLine.connectionMade(self) recvline.HistoricRecvLine.connectionMade(self)
self.keyHandlers[CTRL_C] = self.handle_INT self.keyHandlers[CTRL_C] = self.handle_INT
@ -87,8 +96,9 @@ class SshProtocol(Manhole, session.Session):
def handle_INT(self): def handle_INT(self):
""" """
Handle ^C as an interrupt keystroke by resetting the current input Handle ^C as an interrupt keystroke by resetting the current
variables to their initial state. input variables to their initial state.
""" """
self.lineBuffer = [] self.lineBuffer = []
self.lineBufferIndex = 0 self.lineBufferIndex = 0
@ -100,6 +110,7 @@ class SshProtocol(Manhole, session.Session):
def handle_EOF(self): def handle_EOF(self):
""" """
Handles EOF generally used to exit. Handles EOF generally used to exit.
""" """
if self.lineBuffer: if self.lineBuffer:
self.terminal.write('\a') self.terminal.write('\a')
@ -110,6 +121,7 @@ class SshProtocol(Manhole, session.Session):
""" """
Handle a 'form feed' byte - generally used to request a screen Handle a 'form feed' byte - generally used to request a screen
refresh/redraw. refresh/redraw.
""" """
self.terminal.eraseDisplay() self.terminal.eraseDisplay()
self.terminal.cursorHome() self.terminal.cursorHome()
@ -117,14 +129,18 @@ class SshProtocol(Manhole, session.Session):
def handle_QUIT(self): def handle_QUIT(self):
""" """
Quit, end, and lose the connection. Quit, end, and lose the connection.
""" """
self.terminal.loseConnection() self.terminal.loseConnection()
def connectionLost(self, reason=None): def connectionLost(self, reason=None):
""" """
This is executed when the connection is lost for This is executed when the connection is lost for whatever
whatever reason. It can also be called directly, reason. It can also be called directly, from the disconnect
from the disconnect method. method.
Args:
reason (str): Motivation for loosing connection.
""" """
insults.TerminalProtocol.connectionLost(self, reason) insults.TerminalProtocol.connectionLost(self, reason)
@ -133,25 +149,35 @@ class SshProtocol(Manhole, session.Session):
def getClientAddress(self): def getClientAddress(self):
""" """
Returns the client's address and port in a tuple. For example Get client address.
('127.0.0.1', 41917)
Returns:
address_and_port (tuple): The client's address and port in
a tuple. For example `('127.0.0.1', 41917)`.
""" """
return self.terminal.transport.getPeer() return self.terminal.transport.getPeer()
def lineReceived(self, string): def lineReceived(self, string):
""" """
Communication Player -> Evennia. Any line return indicates a Communication User -> Evennia. Any line return indicates a
command for the purpose of the MUD. So we take the user input command for the purpose of the MUD. So we take the user input
and pass it on to the game engine. and pass it on to the game engine.
Args:
string (str): Input text.
""" """
self.sessionhandler.data_in(self, string) self.sessionhandler.data_in(self, string)
def lineSend(self, string): def lineSend(self, string):
""" """
Communication Evennia -> Player Communication Evennia -> User. Any string sent should
Any string sent should already have been already have been properly formatted and processed before
properly formatted and processed reaching this point.
before reaching this point.
Args:
string (str): Output text.
""" """
for line in string.split('\n'): for line in string.split('\n'):
@ -163,7 +189,11 @@ class SshProtocol(Manhole, session.Session):
def disconnect(self, reason="Connection closed. Goodbye for now."): def disconnect(self, reason="Connection closed. Goodbye for now."):
""" """
Disconnect from server Disconnect from server.
Args:
reason (str): Motivation for disconnect.
""" """
if reason: if reason:
self.data_out(reason) self.data_out(reason)
@ -171,12 +201,13 @@ class SshProtocol(Manhole, session.Session):
def data_out(self, text=None, **kwargs): def data_out(self, text=None, **kwargs):
""" """
Data Evennia -> Player access hook. 'data' argument is a dict Data Evennia -> User access hook. 'data' argument is a dict
parsed for string settings. parsed for string settings.
ssh flags: Kwargs:
raw=True - leave all ansi markup and tokens unparsed text (str): Text to send.
nomarkup=True - remove all ansi markup raw (bool): Leave all ansi markup and tokens unparsed
nomarkup (bool): Remove all ansi markup.
""" """
try: try:
@ -199,6 +230,10 @@ class ExtraInfoAuthServer(SSHUserAuthServer):
Used mostly for setting up the transport so we can query Used mostly for setting up the transport so we can query
username and password later. username and password later.
Args:
packet (Packet): Auth packet.
""" """
password = common.getNS(packet[1:])[0] password = common.getNS(packet[1:])[0]
c = credentials.UsernamePassword(self.user, password) c = credentials.UsernamePassword(self.user, password)
@ -212,15 +247,26 @@ class PlayerDBPasswordChecker(object):
Checks the django db for the correct credentials for Checks the django db for the correct credentials for
username/password otherwise it returns the player or None which is username/password otherwise it returns the player or None which is
useful for the Realm. useful for the Realm.
""" """
credentialInterfaces = (credentials.IUsernamePassword,) credentialInterfaces = (credentials.IUsernamePassword,)
def __init__(self, factory): def __init__(self, factory):
"""
Initialize the factory.
Args:
factory (SSHFactory): Checker factory.
"""
self.factory = factory self.factory = factory
super(PlayerDBPasswordChecker, self).__init__() super(PlayerDBPasswordChecker, self).__init__()
def requestAvatarId(self, c): def requestAvatarId(self, c):
"Generic credentials" """
Generic credentials.
"""
up = credentials.IUsernamePassword(c, None) up = credentials.IUsernamePassword(c, None)
username = up.username username = up.username
password = up.password password = up.password
@ -235,6 +281,7 @@ class PassAvatarIdTerminalRealm(TerminalRealm):
""" """
Returns an avatar that passes the avatarId through to the Returns an avatar that passes the avatarId through to the
protocol. This is probably not the best way to do it. protocol. This is probably not the best way to do it.
""" """
def _getAvatar(self, avatarId): def _getAvatar(self, avatarId):
@ -255,6 +302,7 @@ class TerminalSessionTransport_getPeer:
""" """
Taken from twisted's TerminalSessionTransport which doesn't Taken from twisted's TerminalSessionTransport which doesn't
provide getPeer to the transport. This one does. provide getPeer to the transport. This one does.
j
""" """
def __init__(self, proto, chainedProtocol, avatar, width, height): def __init__(self, proto, chainedProtocol, avatar, width, height):
self.proto = proto self.proto = proto

View file

@ -31,6 +31,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
def connectionMade(self): def connectionMade(self):
""" """
This is called when the connection is first established. This is called when the connection is first established.
""" """
# initialize the session # initialize the session
self.iaw_mode = False self.iaw_mode = False
@ -88,8 +89,14 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
def enableRemote(self, option): def enableRemote(self, option):
""" """
This sets up the remote-activated options we allow for this protocol. This sets up the remote-activated options we allow for this protocol.
Args:
option (char): The telnet option to enable.
Returns:
enable (bool): If this option should be enabled.
""" """
pass
return (option == LINEMODE or return (option == LINEMODE or
option == ttype.TTYPE or option == ttype.TTYPE or
option == naws.NAWS or option == naws.NAWS or
@ -99,12 +106,23 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
def enableLocal(self, option): def enableLocal(self, option):
""" """
Call to allow the activation of options for this protocol Call to allow the activation of options for this protocol
Args:
option (char): The telnet option to enable locally.
Returns:
enable (bool): If this option should be enabled.
""" """
return (option == MCCP or option==ECHO) return (option == MCCP or option==ECHO)
def disableLocal(self, option): def disableLocal(self, option):
""" """
Disable a given option Disable a given option locally.
Args:
option (char): The telnet option to disable locally.
""" """
if option == ECHO: if option == ECHO:
return True return True
@ -116,23 +134,33 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
def connectionLost(self, reason): def connectionLost(self, reason):
""" """
this is executed when the connection is lost for this is executed when the connection is lost for whatever
whatever reason. it can also be called directly, from reason. it can also be called directly, from the disconnect
the disconnect method method
Args:
reason (str): Motivation for losing connection.
""" """
self.sessionhandler.disconnect(self) self.sessionhandler.disconnect(self)
self.transport.loseConnection() self.transport.loseConnection()
def dataReceived(self, data): def dataReceived(self, data):
""" """
Handle incoming data over the wire.
This method will split the incoming data depending on if it This method will split the incoming data depending on if it
starts with IAC (a telnet command) or not. All other data will starts with IAC (a telnet command) or not. All other data will
be handled in line mode. Some clients also sends an erroneous be handled in line mode. Some clients also sends an erroneous
line break after IAC, which we must watch out for. line break after IAC, which we must watch out for.
OOB protocols (MSDP etc) already intercept subnegotiations Args:
on their own, never entering this method. They will relay data (str): Incoming data.
their parsed data directly to self.data_in.
Notes:
OOB protocols (MSDP etc) already intercept subnegotiations on
their own, never entering this method. They will relay their
parsed data directly to self.data_in.
""" """
@ -180,7 +208,13 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
super(TelnetProtocol, self)._write(mccp_compress(self, data)) super(TelnetProtocol, self)._write(mccp_compress(self, data))
def sendLine(self, line): def sendLine(self, line):
"hook overloading the one used by linereceiver" """
Hook overloading the one used by linereceiver.
Args:
line (str): Line to send.
"""
#print "sendLine (%s):\n%s" % (self.state, line) #print "sendLine (%s):\n%s" % (self.state, line)
#escape IAC in line mode, and correctly add \r\n #escape IAC in line mode, and correctly add \r\n
line += self.delimiter line += self.delimiter
@ -191,6 +225,10 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
""" """
Telnet method called when data is coming in over the telnet Telnet method called when data is coming in over the telnet
connection. We pass it on to the game engine directly. connection. We pass it on to the game engine directly.
Args:
string (str): Incoming data.
""" """
self.data_in(text=string) self.data_in(text=string)
@ -200,6 +238,10 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
""" """
generic hook for the engine to call in order to generic hook for the engine to call in order to
disconnect this protocol. disconnect this protocol.
Args:
reason (str): Reason for disconnecting.
""" """
if reason: if reason:
self.data_out(reason) self.data_out(reason)
@ -207,36 +249,46 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
def data_in(self, text=None, **kwargs): def data_in(self, text=None, **kwargs):
""" """
Data Telnet -> Server Data User -> Evennia
Kwargs:
text (str): Incoming text.
kwargs (any): Options from the protocol.
""" """
self.sessionhandler.data_in(self, text=text, **kwargs) self.sessionhandler.data_in(self, text=text, **kwargs)
def data_out(self, text=None, **kwargs): def data_out(self, text=None, **kwargs):
""" """
Data Evennia -> Player. Data Evennia -> User. A generic hook method for engine to call
generic hook method for engine to call in order to send data in order to send data through the telnet connection.
through the telnet connection.
valid telnet kwargs: Kwargs:
oob=[(cmdname,args,kwargs), ...] - supply an Out-of-Band instruction. text (str): Text to send.
xterm256=True/False - enforce xterm256 setting. If not oob (list): `[(cmdname,args,kwargs), ...]`, supply an
given, ttype result is used. If Out-of-Band instruction.
client does not suport xterm256, the xterm256 (bool): Enforce xterm256 setting. If not given,
ansi fallback will be used ttype result is used. If client does not suport xterm256,
mxp=True/False - enforce mxp setting. If not given, enables if we the ansi fallback will be used
detected client support for it mxp (bool): Enforce mxp setting. If not given, enables if
ansi=True/False - enforce ansi setting. If not given, we detected client support for it
ttype result is used. ansi (bool): Enforce ansi setting. If not given, ttype
nomarkup=True - strip all ansi markup (this is the same as result is used.
xterm256=False, ansi=False) nomarkup (bool): If True, strip all ansi markup (this is
raw=True - pass string through without any ansi the same as ´xterm256=False, ansi=False`)
processing (i.e. include Evennia ansi markers but do raw (bool):Pass string through without any ansi processing
not convert them into ansi tokens) (i.e. include Evennia ansi markers but do not convert them
prompt=<string> - supply a prompt text which gets sent without a into ansi tokens)
newline added to the end prompt (str): Supply a prompt text which gets sent without
echo=True/False a newline added to the end.
The telnet ttype negotiation flags, if any, are used if no kwargs echo (str): Turn on/off line echo on the client, if the
client supports it (e.g. for password input). Remember
that you must manually activate it again later.
Notes:
The telnet TTYPE negotiation flags, if any, are used if no kwargs
are given. are given.
""" """
try: try:
text = utils.to_str(text if text else "", encoding=self.encoding) text = utils.to_str(text if text else "", encoding=self.encoding)

View file

@ -2,9 +2,8 @@
Telnet OOB (Out of band communication) Telnet OOB (Out of band communication)
This implements the following telnet oob protocols: This implements the following telnet oob protocols: MSDP (Mud Server
MSDP (Mud Server Data Protocol) Data Protocol) GMCP (Generic Mud Communication Protocol)
GMCP (Generic Mud Communication Protocol)
This implements the MSDP protocol as per This implements the MSDP protocol as per
http://tintin.sourceforge.net/msdp/ and the GMCP protocol as per http://tintin.sourceforge.net/msdp/ and the GMCP protocol as per
@ -14,9 +13,9 @@ 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 client supports MSDP and if not, we fallback to GMCP with a MSDP
header where applicable. header where applicable.
OOB manages out-of-band OOB manages out-of-band communication between the client and server,
communication between the client and server, for updating health bars for updating health bars etc. See also GMCP which is another standard
etc. See also GMCP which is another standard doing the same thing. doing the same thing.
""" """
import re import re
@ -63,9 +62,12 @@ class TelnetOOB(object):
def __init__(self, protocol): def __init__(self, protocol):
""" """
Initiates by storing the protocol Initiates by storing the protocol on itself and trying to
on itself and trying to determine determine if the client supports MSDP.
if the client supports MSDP.
Args:
protocol (Protocol): The active protocol.
""" """
self.protocol = protocol self.protocol = protocol
self.protocol.protocol_flags['OOB'] = False self.protocol.protocol_flags['OOB'] = False
@ -80,23 +82,46 @@ class TelnetOOB(object):
self.oob_reported = {} self.oob_reported = {}
def no_msdp(self, option): def no_msdp(self, option):
"No msdp supported or wanted" """
Client reports No msdp supported or wanted.
Args:
options (Option): Not used.
"""
# no msdp, check GMCP # no msdp, check GMCP
self.protocol.handshake_done() self.protocol.handshake_done()
def do_msdp(self, option): def do_msdp(self, option):
"MSDP supported by client" """
Client reports that it supports msdp.
Args:
option (Option): Not used.
"""
self.MSDP = True self.MSDP = True
self.protocol.protocol_flags['OOB'] = True self.protocol.protocol_flags['OOB'] = True
self.protocol.handshake_done() self.protocol.handshake_done()
def no_gmcp(self, option): def no_gmcp(self, option):
"Neither MSDP nor GMCP supported" """
If this is reached, it means neither MSDP nor GMCP is
supported.
Args:
option (Option): Not used.
"""
self.protocol.handshake_done() self.protocol.handshake_done()
def do_gmcp(self, option): def do_gmcp(self, option):
""" """
Called when client confirms that it can do MSDP or GMCP. Called when client confirms that it can do MSDP or GMCP.
Args:
option (Option): Not used.
""" """
self.GMCP = True self.GMCP = True
self.protocol.protocol_flags['OOB'] = True self.protocol.protocol_flags['OOB'] = True
@ -106,15 +131,17 @@ class TelnetOOB(object):
def encode_msdp(self, cmdname, *args, **kwargs): def encode_msdp(self, cmdname, *args, **kwargs):
""" """
handle return data from cmdname by converting it to handle return data from cmdname by converting it to a proper
a proper msdp structure. These are the combinations we msdp structure. These are the combinations we support:
support:
Args:
cmdname (str): Name of OOB command.
args, kwargs (any): Arguments to OOB command.
Examples:
cmdname string -> cmdname string cmdname string -> cmdname string
cmdname *args -> cmdname MSDP_ARRAY cmdname *args -> cmdname MSDP_ARRAY
cmdname **kwargs -> cmdname MSDP_TABLE cmdname **kwargs -> cmdname MSDP_TABLE
# send 'raw' data structures
MSDP_ARRAY *args -> MSDP_ARRAY MSDP_ARRAY *args -> MSDP_ARRAY
MSDP_TABLE **kwargs -> MSDP_TABLE MSDP_TABLE **kwargs -> MSDP_TABLE
@ -138,11 +165,18 @@ class TelnetOOB(object):
def encode_gmcp(self, cmdname, *args, **kwargs): def encode_gmcp(self, cmdname, *args, **kwargs):
""" """
Encode GMCP messages.
Args:
cmdname (str): GMCP OOB command name.
args, kwargs (any): Arguments to OOB command.
Notes:
Gmcp messages are on one of the following outgoing forms: Gmcp messages are on one of the following outgoing forms:
cmdname string -> cmdname string - cmdname string -> cmdname string
cmdname *args -> cmdname [arg, arg, arg, ...] - cmdname *args -> cmdname [arg, arg, arg, ...]
cmdname **kwargs -> cmdname {key:arg, key:arg, ...} - cmdname **kwargs -> cmdname {key:arg, key:arg, ...}
cmdname is generally recommended to be a string on the form cmdname is generally recommended to be a string on the form
Module.Submodule.Function Module.Submodule.Function
@ -165,8 +199,12 @@ class TelnetOOB(object):
def decode_msdp(self, data): def decode_msdp(self, data):
""" """
Decodes incoming MSDP data Decodes incoming MSDP data.
Args:
data (str or list): MSDP data.
Notes:
cmdname var --> cmdname arg cmdname var --> cmdname arg
cmdname array --> cmdname *args cmdname array --> cmdname *args
cmdname table --> cmdname **kwargs cmdname table --> cmdname **kwargs
@ -211,8 +249,12 @@ class TelnetOOB(object):
def decode_gmcp(self, data): def decode_gmcp(self, data):
""" """
Decodes incoming GMCP data on the form 'varname <structure>' Decodes incoming GMCP data on the form 'varname <structure>'.
Args:
data (str or list): GMCP data.
Notes:
cmdname string -> cmdname arg cmdname string -> cmdname arg
cmdname [arg, arg,...] -> cmdname *args cmdname [arg, arg,...] -> cmdname *args
cmdname {key:arg, key:arg, ...} -> cmdname **kwargs cmdname {key:arg, key:arg, ...} -> cmdname **kwargs
@ -248,6 +290,11 @@ class TelnetOOB(object):
def data_out(self, cmdname, *args, **kwargs): def data_out(self, cmdname, *args, **kwargs):
""" """
Return a msdp-valid subnegotiation across the protocol. Return a msdp-valid subnegotiation across the protocol.
Args:
cmdname (str): OOB-command name.
args, kwargs (any): Arguments to OOB command.
""" """
#print "data_out:", encoded_oob #print "data_out:", encoded_oob
if self.MSDP: if self.MSDP:

View file

@ -30,14 +30,20 @@ class Ttype(object):
""" """
Handles ttype negotiations. Called and initiated by the Handles ttype negotiations. Called and initiated by the
telnet protocol. telnet protocol.
""" """
def __init__(self, protocol): def __init__(self, protocol):
""" """
initialize ttype by storing protocol on ourselves and calling Initialize ttype by storing protocol on ourselves and calling
the client to see if it supporst ttype. the client to see if it supporst ttype.
the ttype_step indicates how far in the data retrieval we've Args:
gotten. protocol (Protocol): The protocol instance.
Notes:
The `self.ttype_step` indicates how far in the data
retrieval we've gotten.
""" """
self.ttype_step = 0 self.ttype_step = 0
self.protocol = protocol self.protocol = protocol
@ -52,19 +58,27 @@ class Ttype(object):
def wont_ttype(self, option): def wont_ttype(self, option):
""" """
Callback if ttype is not supported by client. Callback if ttype is not supported by client.
Args:
option (Option): Not used.
""" """
self.protocol.protocol_flags['TTYPE']["init_done"] = True self.protocol.protocol_flags['TTYPE']["init_done"] = True
self.protocol.handshake_done() self.protocol.handshake_done()
def will_ttype(self, option): def will_ttype(self, option):
""" """
Handles negotiation of the ttype protocol once the Handles negotiation of the ttype protocol once the client has
client has confirmed that it will respond with the ttype confirmed that it will respond with the ttype protocol.
protocol.
Args:
option (Option): Not used.
Notes:
The negotiation proceeds in several steps, each returning a The negotiation proceeds in several steps, each returning a
certain piece of information about the client. All data is certain piece of information about the client. All data is
stored on protocol.protocol_flags under the TTYPE key. stored on protocol.protocol_flags under the TTYPE key.
""" """
options = self.protocol.protocol_flags.get('TTYPE') options = self.protocol.protocol_flags.get('TTYPE')

View file

@ -57,6 +57,7 @@ def jsonify(obj):
class WebClient(resource.Resource): class WebClient(resource.Resource):
""" """
An ajax/comet long-polling transport An ajax/comet long-polling transport
""" """
isLeaf = True isLeaf = True
allowedMethods = ('POST',) allowedMethods = ('POST',)
@ -80,8 +81,17 @@ class WebClient(resource.Resource):
def lineSend(self, suid, string, data=None): def lineSend(self, suid, string, data=None):
""" """
This adds the data to the buffer and/or sends it to This adds the data to the buffer and/or sends it to the client
the client as soon as possible. as soon as possible.
Args:
suid (int): Session id.
string (str): The text to send.
data (dict): Optional data.
Notes:
The `data` keyword is deprecated.
""" """
request = self.requests.get(suid) request = self.requests.get(suid)
if request: if request:
@ -98,6 +108,10 @@ class WebClient(resource.Resource):
def client_disconnect(self, suid): def client_disconnect(self, suid):
""" """
Disconnect session with given suid. Disconnect session with given suid.
Args:
suid (int): Session id.
""" """
if suid in self.requests: if suid in self.requests:
self.requests[suid].finish() self.requests[suid].finish()
@ -107,8 +121,12 @@ class WebClient(resource.Resource):
def mode_init(self, request): def mode_init(self, request):
""" """
This is called by render_POST when the client This is called by render_POST when the client requests an init
requests an init mode operation (at startup) mode operation (at startup)
Args:
request (Request): Incoming request.
""" """
#csess = request.getSession() # obs, this is a cookie, not #csess = request.getSession() # obs, this is a cookie, not
# an evennia session! # an evennia session!
@ -133,6 +151,10 @@ class WebClient(resource.Resource):
""" """
This is called by render_POST when the client This is called by render_POST when the client
is sending data to the server. is sending data to the server.
Args:
request (Request): Incoming request.
""" """
suid = request.args.get('suid', ['0'])[0] suid = request.args.get('suid', ['0'])[0]
if suid == '0': if suid == '0':
@ -148,10 +170,13 @@ class WebClient(resource.Resource):
def mode_receive(self, request): def mode_receive(self, request):
""" """
This is called by render_POST when the client is telling us This is called by render_POST when the client is telling us
that it is ready to receive data as soon as it is that it is ready to receive data as soon as it is available.
available. This is the basis of a long-polling (comet) This is the basis of a long-polling (comet) mechanism: the
mechanism: the server will wait to reply until data is server will wait to reply until data is available.
available.
Args:
request (Request): Incoming request.
""" """
suid = request.args.get('suid', ['0'])[0] suid = request.args.get('suid', ['0'])[0]
if suid == '0': if suid == '0':
@ -170,6 +195,10 @@ class WebClient(resource.Resource):
""" """
This is called by render_POST when the client is signalling This is called by render_POST when the client is signalling
that it is about to be closed. that it is about to be closed.
Args:
request (Request): Incoming request.
""" """
suid = request.args.get('suid', ['0'])[0] suid = request.args.get('suid', ['0'])[0]
if suid == '0': if suid == '0':
@ -191,6 +220,10 @@ class WebClient(resource.Resource):
initializing or sending/receving data through the request. It initializing or sending/receving data through the request. It
uses a long-polling mechanism to avoid sending data unless uses a long-polling mechanism to avoid sending data unless
there is actual data available. there is actual data available.
Args:
request (Request): Incoming request.
""" """
dmode = request.args.get('mode', [None])[0] dmode = request.args.get('mode', [None])[0]
if dmode == 'init': if dmode == 'init':
@ -222,7 +255,10 @@ class WebClientSession(session.Session):
def disconnect(self, reason=None): def disconnect(self, reason=None):
""" """
Disconnect from server Disconnect from server.
Args:
reason (str): Motivation for the disconnect.
""" """
if reason: if reason:
self.client.lineSend(self.suid, reason) self.client.lineSend(self.suid, reason)
@ -230,11 +266,11 @@ class WebClientSession(session.Session):
def data_out(self, text=None, **kwargs): def data_out(self, text=None, **kwargs):
""" """
Data Evennia -> Player access hook. Data Evennia -> User access hook.
webclient flags checked are Kwargs:
raw=True - no parsing at all (leave ansi-to-html markers unparsed) raw (bool): No parsing at all (leave ansi-to-html markers unparsed).
nomarkup=True - clean out all ansi/html markers and tokens nomarkup (bool): Clean out all ansi/html markers and tokens.
""" """
# string handling is similar to telnet # string handling is similar to telnet

View file

@ -13,9 +13,9 @@ the client must send data on the following form:
OOB{"func1":[args], "func2":[args], ...} OOB{"func1":[args], "func2":[args], ...}
where the dict is JSON encoded. The initial OOB-prefix where the dict is JSON encoded. The initial OOB-prefix is used to
is used to identify this type of communication, all other data identify this type of communication, all other data is considered
is considered plain text (command input). plain text (command input).
Example of call from a javascript client: Example of call from a javascript client:
@ -43,6 +43,7 @@ class WebSocketClient(Protocol, Session):
def connectionMade(self): def connectionMade(self):
""" """
This is called when the connection is first established. This is called when the connection is first established.
""" """
client_address = self.transport.client client_address = self.transport.client
self.init_session("websocket", client_address, self.factory.sessionhandler) self.init_session("websocket", client_address, self.factory.sessionhandler)
@ -52,8 +53,12 @@ class WebSocketClient(Protocol, Session):
def disconnect(self, reason=None): def disconnect(self, reason=None):
""" """
generic hook for the engine to call in order to Generic hook for the engine to call in order to
disconnect this protocol. disconnect this protocol.
Args:
reason (str): Motivation for the disconnection.
""" """
if reason: if reason:
self.data_out(text=reason) self.data_out(text=reason)
@ -61,24 +66,29 @@ class WebSocketClient(Protocol, Session):
def connectionLost(self, reason): def connectionLost(self, reason):
""" """
this is executed when the connection is lost for This is executed when the connection is lost for whatever
whatever reason. it can also be called directly, from reason. it can also be called directly, from the disconnect
the disconnect method method
Args:
reason (str): Motivation for the lost connection.
""" """
self.sessionhandler.disconnect(self) self.sessionhandler.disconnect(self)
self.transport.close() self.transport.close()
def dataReceived(self, string): def dataReceived(self, string):
""" """
Method called when data is coming in over Method called when data is coming in over the websocket
the websocket connection. connection.
Type of data is identified by a 3-character Args:
prefix. string (str): Type of data is identified by a 3-character
OOB - This is an Out-of-band instruction. If so, prefix:
- "OOB" This is an Out-of-band instruction. If so,
the remaining string should be a json-packed the remaining string should be a json-packed
string on the form {oobfuncname: [args, ], ...} string on the form {oobfuncname: [args, ], ...}
CMD - plain text data, to be treated like a game - "CMD" plain text data, to be treated like a game
input command. input command.
""" """
mode = string[:3] mode = string[:3]
@ -92,13 +102,26 @@ class WebSocketClient(Protocol, Session):
self.data_in(text=data) self.data_in(text=data)
def sendLine(self, line): def sendLine(self, line):
"send data to client" """
Send data to client.
Args:
line (str): Text to send.
"""
return self.transport.write(line) return self.transport.write(line)
def json_decode(self, data): def json_decode(self, data):
""" """
Decodes incoming data from the client Decodes incoming data from the client.
Args:
data (JSON): JSON object to unpack.
Raises:
Exception: If receiving a malform OOB request.
Notes:
[cmdname, [args],{kwargs}] -> cmdname *args **kwargs [cmdname, [args],{kwargs}] -> cmdname *args **kwargs
""" """
@ -111,8 +134,13 @@ class WebSocketClient(Protocol, Session):
def json_encode(self, cmdname, *args, **kwargs): def json_encode(self, cmdname, *args, **kwargs):
""" """
Encode OOB data for sending to client Encode OOB data for sending to client.
Args:
cmdname (str): OOB command name.
args, kwargs (any): Arguments to oob command.
Notes:
cmdname *args -> cmdname [json array] cmdname *args -> cmdname [json array]
cmdname **kwargs -> cmdname {json object} cmdname **kwargs -> cmdname {json object}
@ -122,20 +150,25 @@ class WebSocketClient(Protocol, Session):
def data_in(self, text=None, **kwargs): def data_in(self, text=None, **kwargs):
""" """
Data Websocket -> Server Data User > Evennia.
Kwargs:
text (str): Incoming text.
kwargs (any): Options from protocol.
""" """
self.sessionhandler.data_in(self, text=text, **kwargs) self.sessionhandler.data_in(self, text=text, **kwargs)
def data_out(self, text=None, **kwargs): def data_out(self, text=None, **kwargs):
""" """
Data Evennia -> Player. Data Evennia -> User. A generic hook method for engine to call
generic hook method for engine to call in order to send data in order to send data through the websocket connection.
through the websocket connection.
Kwargs:
oob (str or tuple): Supply an Out-of-Band instruction.
raw (bool): No parsing at all (leave ansi-to-html markers unparsed).
nomarkup (bool): Clean out all ansi/html markers and tokens.
valid webclient kwargs:
oob=<string> - supply an Out-of-Band instruction.
raw=True - no parsing at all (leave ansi-to-html markers unparsed)
nomarkup=True - clean out all ansi/html markers and tokens
""" """
try: try:
text = to_str(text if text else "", encoding=self.encoding) text = to_str(text if text else "", encoding=self.encoding)