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):
"""
Stores information about other games connected to our current IMC2 network.
"""
def __init__(self, packet):
self.name = packet.origin
@ -37,6 +38,7 @@ class IMC2Mud(object):
class IMC2MudList(dict):
"""
Keeps track of other MUDs connected to the IMC network.
"""
def get_mud_list(self):
"""
@ -50,6 +52,10 @@ class IMC2MudList(dict):
"""
This grabs relevant info from the packet and stuffs it in the
Mud list for later retrieval.
Args:
packet (Packet): incoming packet.
"""
mud = IMC2Mud(packet)
self[mud.name] = mud
@ -57,6 +63,10 @@ class IMC2MudList(dict):
def remove_mud_from_packet(self, packet):
"""
Removes a mud from the Mud list when given a packet.
Args:
packet (Packet): Incoming packet.
"""
mud = IMC2Mud(packet)
try:
@ -71,6 +81,7 @@ class IMC2Channel(object):
Stores information about channels available on the network.
"""
def __init__(self, packet):
"Initialize channel."
self.localname = packet.optional_data.get('localname', None)
self.name = packet.optional_data.get('channel', None)
self.level = packet.optional_data.get('level', None)
@ -87,6 +98,7 @@ class IMC2ChanList(dict):
def get_channel_list(self):
"""
Returns a sorted list of cached channels.
"""
channels = self.items()
channels.sort()
@ -96,6 +108,10 @@ class IMC2ChanList(dict):
"""
This grabs relevant info from the packet and stuffs it in the
channel list for later retrieval.
Args:
packet (Packet): incoming packet.
"""
channel = IMC2Channel(packet)
self[channel.name] = channel
@ -103,6 +119,10 @@ class IMC2ChanList(dict):
def remove_channel_from_packet(self, packet):
"""
Removes a channel from the Channel list when given a packet.
Args:
packet (Packet): incoming packet.
"""
channel = IMC2Channel(packet)
try:
@ -120,8 +140,10 @@ class IMC2Bot(telnet.StatefulTelnetProtocol, Session):
"""
Provides the abstraction for the IMC2 protocol. Handles connection,
authentication, and all necessary packets.
"""
def __init__(self):
"Initialize bot."
self.is_authenticated = False
# only support plaintext passwords
self.auth_type = "plaintext"
@ -130,37 +152,49 @@ class IMC2Bot(telnet.StatefulTelnetProtocol, Session):
self.imc2_chanlist = IMC2ChanList()
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_str = utils.to_str(packet.assemble(self.factory.mudname,
self.factory.client_pwd, self.factory.server_pwd))
self.sendLine(packet_str)
def _isalive(self):
"Send an isalive packet"
"Send an isalive packet."
self._send_packet(pck.IMC2PacketIsAlive())
def _keepalive(self):
"Send a keepalive packet"
"Send a keepalive packet."
# send to channel?
self._send_packet(pck.IMC2PacketKeepAliveRequest())
def _channellist(self):
"Sync the network channel list"
"Sync the network channel list."
checked_networks = []
if not self.network in checked_networks:
self._send_packet(pck.IMC2PacketIceRefresh())
checked_networks.append(self.network)
def _prune(self):
"Prune active channel list"
"Prune active channel list."
t0 = time()
for name, mudinfo in self.imc2_mudlist.items():
if t0 - mudinfo.last_updated > 3599:
del self.imc2_mudlist[name]
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
# not using that here
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):
"""
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,
"origin": packet.origin,
"msg": packet.optional_data.get('text', 'ERROR: No text provided.')}
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":
# Only support Plain text passwords.
@ -218,6 +263,7 @@ class IMC2Bot(telnet.StatefulTelnetProtocol, Session):
def connectionMade(self):
"""
Triggered after connecting to the IMC2 network.
"""
self.stopping = False
@ -239,6 +285,9 @@ class IMC2Bot(telnet.StatefulTelnetProtocol, Session):
Triggered when text is received from the IMC2 network. Figures out
what to do with the packet. This deals with the following
Args:
line (str): Incoming text.
"""
line = line.strip()
@ -278,24 +327,31 @@ class IMC2Bot(telnet.StatefulTelnetProtocol, Session):
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
self.sessionhandler.data_in(self, text=text, **kwargs)
def data_out(self, text=None, **kwargs):
"""
Evennia -> IMC2
Evennia -> IMC2.
Keywords
packet_type:
broadcast - send to everyone on IMC channel
tell - send a tell (see target keyword)
whois - get whois information (see target keyword)
sender - used by tell to identify the sender
target - key identifier of target to tells or whois. If not
given "Unknown" will be used.
destination - used by tell to specify mud destination to send to
Kwargs:
text (str): Outgoing text.
packet_type (str):
- broadcast: Send to everyone on IMC channel.
- tell: Send a tell (see target keyword).
- whois: Get whois information (see target keyword).
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.
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
need to create one connection. Tied in via evennia/server.py.
"""
initialDelay = 1
factor = 1.5
@ -345,6 +402,7 @@ class IMC2BotFactory(protocol.ReconnectingClientFactory):
def __init__(self, sessionhandler, uid=None, network=None, channel=None,
port=None, mudname=None, client_pwd=None, server_pwd=None):
"Initialize the bot factory."
self.uid = uid
self.network = network
sname, host = network.split(".", 1)
@ -362,7 +420,16 @@ class IMC2BotFactory(protocol.ReconnectingClientFactory):
self.task_channellist = None
def buildProtocol(self, addr):
"Build the protocol"
"""
Build the protocol.
Args:
addr (str): Protocl address.
Returns:
protocol (Protocol): The new protocol.
"""
protocol = IMC2Bot()
protocol.factory = self
protocol.network = self.network
@ -373,9 +440,23 @@ class IMC2BotFactory(protocol.ReconnectingClientFactory):
return protocol
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)
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:
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)
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(), "")
def parse_irc_colors(string):
"""
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)
parsed_string = ""
@ -105,6 +122,7 @@ class IRCBot(irc.IRCClient, Session):
"""
An IRC bot that tracks actitivity in a channel as well
as sends text to it when prompted
"""
lineRate = 1
@ -117,9 +135,9 @@ class IRCBot(irc.IRCClient, Session):
def signedOn(self):
"""
This is called when we successfully connect to
the network. We make sure to now register with
the game as a full session.
This is called when we successfully connect to the network. We
make sure to now register with the game as a full session.
"""
self.join(self.channel)
self.stopping = False
@ -135,7 +153,11 @@ class IRCBot(irc.IRCClient, Session):
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!"
self.sessionhandler.disconnect(self)
@ -143,23 +165,53 @@ class IRCBot(irc.IRCClient, Session):
self.transport.loseConnection()
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('***'):
user = user.split('!', 1)[0]
self.data_in("bot_data_in %s@%s: %s" % (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('**'):
user = user.split('!', 1)[0]
self.data_in("bot_data_in %s@%s %s" % (user, channel, msg))
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)
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"):
text = text.split(" ", 1)[1]
text = parse_irc_colors(text)
@ -168,8 +220,9 @@ class IRCBot(irc.IRCClient, Session):
class IRCBotFactory(protocol.ReconnectingClientFactory):
"""
Creates instances of AnnounceBot, connecting with
a staggered increase in delay
Creates instances of AnnounceBot, connecting with a staggered
increase in delay
"""
# scaling reconnect time
initialDelay = 1
@ -177,7 +230,20 @@ class IRCBotFactory(protocol.ReconnectingClientFactory):
maxDelay = 60
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.uid = uid
self.nickname = str(botname)
@ -187,7 +253,13 @@ class IRCBotFactory(protocol.ReconnectingClientFactory):
self.bot = None
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.factory = self
protocol.nickname = self.nickname
@ -197,18 +269,43 @@ class IRCBotFactory(protocol.ReconnectingClientFactory):
return protocol
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)
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)
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:
self.retry(connector)
def start(self):
"Connect session to sessionhandler"
"""
Connect session to sessionhandler.
"""
if self.port:
service = internet.TCPClient(self.network, int(self.port), self)
self.sessionhandler.portal.services.addService(service)

View file

@ -22,7 +22,16 @@ FLUSH = zlib.Z_SYNC_FLUSH
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'):
return protocol.zlib.compress(data) + protocol.zlib.flush(FLUSH)
return data
@ -32,6 +41,7 @@ class Mccp(object):
"""
Implements the MCCP protocol. Add this to a
variable on the telnet protocol to set it up.
"""
def __init__(self, protocol):
@ -40,6 +50,10 @@ class Mccp(object):
ourselves and calling the client to see if
it supports MCCP. Sets callbacks to
start zlib compression in that case.
Args:
protocol (Protocol): The active protocol instance.
"""
self.protocol = protocol
@ -49,7 +63,11 @@ class Mccp(object):
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'):
del self.protocol.zlib
@ -60,6 +78,10 @@ class Mccp(object):
"""
The client supports MCCP. Set things up by
creating a zlib compression stream.
Args:
option (Option): Option dict (not used).
"""
self.protocol.protocol_flags['MCCP'] = True
self.protocol.requestNegotiation(MCCP, '')

View file

@ -23,29 +23,50 @@ MSSPTable_CUSTOM = utils.variable_from_module(settings.MSSP_META_MODULE, "MSSPTa
class Mssp(object):
"""
Implements the MSSP protocol. Add this to a
variable on the telnet protocol to set it up.
Implements the MSSP protocol. Add this to a variable on the telnet
protocol to set it up.
"""
def __init__(self, protocol):
"""
initialize MSSP by storing protocol on ourselves
and calling the client to see if it supports
MSSP.
initialize MSSP by storing protocol on ourselves and calling
the client to see if it supports MSSP.
Args:
protocol (Protocol): The active protocol instance.
"""
self.protocol = protocol
self.protocol.will(MSSP).addCallbacks(self.do_mssp, self.no_mssp)
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())
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)
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()
pass
@ -53,6 +74,10 @@ class Mssp(object):
def do_mssp(self, option):
"""
Negotiate all the information.
Args:
option (Option): Not used.
"""
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.mushclient.com/mushclient/mxp.htm
http://www.gammon.com.au/mushclient/addingservermxp.htm
"""
import re
@ -27,6 +28,13 @@ MXP_SEND = MXP_TEMPSECURE + \
def mxp_parse(text):
"""
Replaces links to the correct format for MXP.
Args:
text (str): The text to parse.
Returns:
parsed (str): The parsed text.
"""
text = text.replace("&", "&") \
.replace("<", "&lt;") \
@ -38,24 +46,39 @@ def mxp_parse(text):
class Mxp(object):
"""
Implements the MXP 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_flags["MXP"] = False
self.protocol.will(MXP).addCallbacks(self.do_mxp, self.no_mxp)
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.handshake_done()
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.handshake_done()

View file

@ -5,9 +5,8 @@ NAWS - Negotiate About Window Size
This implements the NAWS telnet option as per
https://www.ietf.org/rfc/rfc1073.txt
NAWS allows telnet clients to report their
current window size to the client and update
it when the size changes
NAWS allows telnet clients to report their current window size to the
client and update it when the size changes
"""
from django.conf import settings
@ -22,14 +21,18 @@ DEFAULT_HEIGHT = settings.CLIENT_DEFAULT_HEIGHT
class Naws(object):
"""
Implements the MSSP protocol. Add this to a
variable on the telnet protocol to set it up.
Implements the NAWS protocol. Add this to a variable on the telnet
protocol to set it up.
"""
def __init__(self, protocol):
"""
initialize NAWS by storing protocol on ourselves
and calling the client to see if it supports
NAWS.
initialize NAWS by storing protocol on ourselves and calling
the client to see if it supports NAWS.
Args:
protocol (Protocol): The active protocol instance.
"""
self.naws_step = 0
self.protocol = protocol
@ -40,17 +43,33 @@ class Naws(object):
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()
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()
def negotiate_sizes(self, options):
"""
Step through the NAWS handshake.
Args:
option (list): The incoming NAWS options.
"""
if len(options) == 4:
# NAWS is negotiated with 16bit words
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
def _portal_maintenance():
"""
The maintenance function handles repeated checks and updates
that the server needs to do. It is called every minute.
The maintenance function handles repeated checks and updates that
the server needs to do. It is called every minute.
"""
# check for idle sessions
now = time.time()
@ -85,6 +86,7 @@ def _portal_maintenance():
if (now - sess.cmd_last) > _IDLE_TIMEOUT]:
session.data_out(reason)
PORTAL_SESSIONS.disconnect(session)
if _IDLE_TIMEOUT > 0:
# only start the maintenance task if we care about idling.
_maintenance_task = LoopingCall(_portal_maintenance)
@ -97,16 +99,18 @@ if _IDLE_TIMEOUT > 0:
class Portal(object):
"""
The main Portal server handler. This object sets up the database and
tracks and interlinks all the twisted network services that make up
Portal.
The main Portal server handler. This object sets up the database
and tracks and interlinks all the twisted network services that
make up Portal.
"""
def __init__(self, application):
"""
Setup the server.
application - an instantiated Twisted application
Args:
application (Application): An instantiated Twisted application
"""
sys.path.append('.')
@ -125,9 +129,13 @@ class Portal(object):
def set_restart_mode(self, mode=None):
"""
This manages the flag file that tells the runner if the server should
be restarted or is shutting down. Valid modes are True/False and None.
If mode is None, no change will be done to the flag file.
This manages the flag file that tells the runner if the server
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:
return
@ -139,15 +147,19 @@ class Portal(object):
"""
Shuts down the server from inside it.
restart - True/False sets the flags so the server will be
restarted or not. If None, the current flag setting
(set at initialization or previous runs) is used.
_reactor_stopping - this is set if server is already in the process of
shutting down; in this case we don't need to stop it again.
Args:
restart (bool or None, optional): True/False sets the
flags so the server will be restarted or not. If None, the
current flag setting (set at initialization or previous
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
if the Portal is currently running in daemon mode. In that
case it always needs to be restarted manually.
"""
if _reactor_stopping and hasattr(self, "shutdown_complete"):
# we get here due to us calling reactor.stop below. No need

View file

@ -33,6 +33,7 @@ class PortalSessionHandler(SessionHandler):
def __init__(self):
"""
Init the handler
"""
self.portal = None
self.sessions = {}
@ -43,21 +44,26 @@ class PortalSessionHandler(SessionHandler):
def at_server_connection(self):
"""
Called when the Portal establishes connection with the
Server. At this point, the AMP connection is already
established.
Called when the Portal establishes connection with the Server.
At this point, the AMP connection is already established.
"""
self.connection_time = time()
def connect(self, session):
"""
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
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.
Args:
session (PortalSession): The Session connecting.
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
@ -95,8 +101,12 @@ class PortalSessionHandler(SessionHandler):
def sync(self, session):
"""
Called by the protocol of an already connected session. This
can be used to sync the session info in a delayed manner,
such as when negotiation and handshakes are delayed.
can be used to sync the session info in a delayed manner, such
as when negotiation and handshakes are delayed.
Args:
session (PortalSession): Session to sync.
"""
if session.sessid and session.server_connected:
# only use if session already has sessid and has already connected
@ -119,8 +129,12 @@ class PortalSessionHandler(SessionHandler):
def disconnect(self, session):
"""
Called from portal side when the connection is closed
from the portal side.
Called from portal when the connection is closed from the
portal side.
Args:
session (PortalSession): Session to disconnect.
"""
sessid = session.sessid
self.portal.amp_protocol.call_remote_ServerAdmin(sessid,
@ -128,19 +142,25 @@ class PortalSessionHandler(SessionHandler):
def server_connect(self, protocol_path="", config=dict()):
"""
Called by server to force the initialization of a new
protocol instance. Server wants this instance to get
a unique sessid and to be connected back as normal. This
is used to initiate irc/imc2/rss etc connections.
Called by server to force the initialization of a new protocol
instance. Server wants this instance to get a unique sessid
and to be connected back as normal. This is used to initiate
irc/imc2/rss etc connections.
protocol_path - full python path to the class factory
for the protocol used, eg
'evennia.server.portal.irc.IRCClientFactory'
config - dictionary of configuration options, fed as **kwarg
to protocol class' __init__ method.
Args:
protocol_path (st): Full python path to the class factory
for the protocol used, eg
'evennia.server.portal.irc.IRCClientFactory'
config (dict): Dictionary of configuration options, fed as
**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()
that calls the portalsession.connect() as a normal protocol.
The called protocol class must have a method start()
that calls the portalsession.connect() as a normal protocol.
"""
global _MOD_IMPORT
if not _MOD_IMPORT:
@ -154,7 +174,12 @@ class PortalSessionHandler(SessionHandler):
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)
if session:
@ -167,6 +192,10 @@ class PortalSessionHandler(SessionHandler):
def server_disconnect_all(self, reason=""):
"""
Called by server when forcing a clean disconnect for everyone.
Args:
reason (str, optional): Motivation for disconnect.
"""
for session in self.sessions.values():
session.disconnect(reason)
@ -175,21 +204,29 @@ class PortalSessionHandler(SessionHandler):
def server_logged_in(self, sessid, data):
"""
The server tells us that the session has been
authenticated. Updated it.
The server tells us that the session has been authenticated.
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.load_sync_data(data)
def server_session_sync(self, serversessions):
"""
Server wants to save data to the portal, maybe because it's about
to shut down. We don't overwrite any sessions here, just update
them in-place and remove any that are out of sync (which should
normally not be the case)
Server wants to save data to the portal, maybe because it's
about to shut down. We don't overwrite any sessions here, just
update them in-place and remove any that are out of sync
(which should normally not be the case)
serversessions - dictionary {sessid:{property:value},...} describing
the properties to sync on all sessions
Args:
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_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):
"""
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))
@ -210,13 +255,24 @@ class PortalSessionHandler(SessionHandler):
"""
Given a session id, retrieve the session (this is primarily
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)
if hasattr(sess, 'suid') and sess.suid == suid]
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():
session.data_out(message)
@ -226,22 +282,24 @@ class PortalSessionHandler(SessionHandler):
Helper method for each session to use to parse oob structures
(The 'oob' kwarg of the msg() method).
Args: oobstruct (str or iterable): A structure representing
an oob command on one of the following forms:
- "cmdname"
- "cmdname", "cmdname"
- ("cmdname", arg)
- ("cmdname",(args))
- ("cmdname",{kwargs}
- ("cmdname", (args), {kwargs})
- (("cmdname", (args,), {kwargs}), ("cmdname", (args,), {kwargs}))
Args:
oobstruct (str or iterable): A structure representing
an oob command on one of the following forms:
- "cmdname"
- "cmdname", "cmdname"
- ("cmdname", arg)
- ("cmdname",(args))
- ("cmdname",{kwargs}
- ("cmdname", (args), {kwargs})
- (("cmdname", (args,), {kwargs}), ("cmdname", (args,), {kwargs}))
and any combination of argument-less commands or commands with only
args, only kwargs or both.
Returns:
structure (tuple): A generic OOB structure on the form
`((cmdname, (args,), {kwargs}), ...)`, where the two last
args and kwargs may be empty
`((cmdname, (args,), {kwargs}), ...)`, where the two last
args and kwargs may be empty
"""
def _parse(oobstruct):
slen = len(oobstruct)
@ -287,8 +345,17 @@ class PortalSessionHandler(SessionHandler):
def data_in(self, session, text="", **kwargs):
"""
Called by portal sessions for relaying data coming
in from the protocol to the server. data is
serialized before passed on.
in from the protocol to the server.
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)
@ -307,6 +374,14 @@ class PortalSessionHandler(SessionHandler):
Called by server for having the portal relay messages and data
to the correct session protocol. We also convert oob input to
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)
if session:

View file

@ -22,16 +22,29 @@ if RSS_ENABLED:
class RSSReader(Session):
"""
A simple RSS reader using universal feedparser
A simple RSS reader using the feedparser module.
"""
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.rate = rate
self.factory = factory
self.old_entries = {}
def get_new(self):
"""Returns list of new items."""
"""
Returns list of new items.
"""
feed = feedparser.parse(self.url)
new_entries = []
for entry in feed['entries']:
@ -42,20 +55,41 @@ class RSSReader(Session):
return new_entries
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:
self.factory.task.stop()
self.sessionhandler.disconnect(self)
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:
# for initialization we just ignore old entries
for entry in reversed(new_entries):
self.data_in("bot_data_in " + entry)
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)
def _errback(self, fail):
@ -63,16 +97,36 @@ class RSSReader(Session):
logger.log_errmsg("RSS feed error: %s" % fail.value)
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)
class RSSBotFactory(object):
"""
Initializes new bots
Initializes new bots.
"""
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.url = url
self.rate = rate
@ -82,7 +136,7 @@ class RSSBotFactory(object):
def start(self):
"""
Called by portalsessionhandler
Called by portalsessionhandler. Starts te bot.
"""
def errback(fail):
logger.log_errmsg(fail.value)

View file

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

View file

@ -31,6 +31,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
def connectionMade(self):
"""
This is called when the connection is first established.
"""
# initialize the session
self.iaw_mode = False
@ -88,8 +89,14 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
def enableRemote(self, option):
"""
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
option == ttype.TTYPE or
option == naws.NAWS or
@ -99,12 +106,23 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
def enableLocal(self, option):
"""
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)
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:
return True
@ -116,23 +134,33 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
def connectionLost(self, reason):
"""
this is executed when the connection is lost for
whatever reason. it can also be called directly, from
the disconnect method
this is executed when the connection is lost for whatever
reason. it can also be called directly, from the disconnect
method
Args:
reason (str): Motivation for losing connection.
"""
self.sessionhandler.disconnect(self)
self.transport.loseConnection()
def dataReceived(self, data):
"""
Handle incoming data over the wire.
This method will split the incoming data depending on if it
starts with IAC (a telnet command) or not. All other data will
be handled in line mode. Some clients also sends an erroneous
line break after IAC, which we must watch out for.
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.
Args:
data (str): Incoming data.
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))
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)
#escape IAC in line mode, and correctly add \r\n
line += self.delimiter
@ -191,6 +225,10 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
"""
Telnet method called when data is coming in over the telnet
connection. We pass it on to the game engine directly.
Args:
string (str): Incoming data.
"""
self.data_in(text=string)
@ -200,6 +238,10 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
"""
generic hook for the engine to call in order to
disconnect this protocol.
Args:
reason (str): Reason for disconnecting.
"""
if reason:
self.data_out(reason)
@ -207,36 +249,46 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
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)
def data_out(self, text=None, **kwargs):
"""
Data Evennia -> Player.
generic hook method for engine to call in order to send data
through the telnet connection.
Data Evennia -> User. A generic hook method for engine to call
in order to send data through the telnet connection.
Kwargs:
text (str): Text to send.
oob (list): `[(cmdname,args,kwargs), ...]`, supply an
Out-of-Band instruction.
xterm256 (bool): Enforce xterm256 setting. If not given,
ttype result is used. If client does not suport xterm256,
the ansi fallback will be used
mxp (bool): Enforce mxp setting. If not given, enables if
we detected client support for it
ansi (bool): Enforce ansi setting. If not given, ttype
result is used.
nomarkup (bool): If True, strip all ansi markup (this is
the same as ´xterm256=False, ansi=False`)
raw (bool):Pass string through without any ansi processing
(i.e. include Evennia ansi markers but do not convert them
into ansi tokens)
prompt (str): Supply a prompt text which gets sent without
a newline added to the end.
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.
valid telnet kwargs:
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
ansi fallback will be used
mxp=True/False - enforce mxp setting. If not given, enables if we
detected client support for it
ansi=True/False - enforce ansi setting. If not given,
ttype result is used.
nomarkup=True - strip all ansi markup (this is the same as
xterm256=False, ansi=False)
raw=True - pass string through without any ansi
processing (i.e. include Evennia ansi markers but do
not convert them into ansi tokens)
prompt=<string> - supply a prompt text which gets sent without a
newline added to the end
echo=True/False
The telnet ttype negotiation flags, if any, are used if no kwargs
are given.
"""
try:
text = utils.to_str(text if text else "", encoding=self.encoding)

View file

@ -2,9 +2,8 @@
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 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
@ -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
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.
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
@ -63,9 +62,12 @@ class TelnetOOB(object):
def __init__(self, protocol):
"""
Initiates by storing the protocol
on itself and trying to determine
if the client supports MSDP.
Initiates by storing the protocol on itself and trying to
determine if the client supports MSDP.
Args:
protocol (Protocol): The active protocol.
"""
self.protocol = protocol
self.protocol.protocol_flags['OOB'] = False
@ -80,23 +82,46 @@ class TelnetOOB(object):
self.oob_reported = {}
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
self.protocol.handshake_done()
def do_msdp(self, option):
"MSDP supported by client"
"""
Client reports that it supports msdp.
Args:
option (Option): Not used.
"""
self.MSDP = True
self.protocol.protocol_flags['OOB'] = True
self.protocol.handshake_done()
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()
def do_gmcp(self, option):
"""
Called when client confirms that it can do MSDP or GMCP.
Args:
option (Option): Not used.
"""
self.GMCP = True
self.protocol.protocol_flags['OOB'] = True
@ -106,17 +131,19 @@ class TelnetOOB(object):
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:
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
Args:
cmdname (str): Name of OOB command.
args, kwargs (any): Arguments to OOB command.
# send 'raw' data structures
MSDP_ARRAY *args -> MSDP_ARRAY
MSDP_TABLE **kwargs -> MSDP_TABLE
Examples:
cmdname string -> cmdname string
cmdname *args -> cmdname MSDP_ARRAY
cmdname **kwargs -> cmdname MSDP_TABLE
MSDP_ARRAY *args -> MSDP_ARRAY
MSDP_TABLE **kwargs -> MSDP_TABLE
"""
msdp_string = ""
@ -138,14 +165,21 @@ class TelnetOOB(object):
def encode_gmcp(self, cmdname, *args, **kwargs):
"""
Gmcp messages are on one of the following outgoing forms:
Encode GMCP messages.
cmdname string -> cmdname string
cmdname *args -> cmdname [arg, arg, arg, ...]
cmdname **kwargs -> cmdname {key:arg, key:arg, ...}
Args:
cmdname (str): GMCP OOB command name.
args, kwargs (any): Arguments to OOB command.
cmdname is generally recommended to be a string on the form
Module.Submodule.Function
Notes:
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 cmdname in ("SEND", "REPORT", "UNREPORT", "LIST"):
# we wrap the standard MSDP commands in a MSDP.submodule
@ -165,11 +199,15 @@ class TelnetOOB(object):
def decode_msdp(self, data):
"""
Decodes incoming MSDP data
Decodes incoming MSDP data.
cmdname var --> cmdname arg
cmdname array --> cmdname *args
cmdname table --> cmdname **kwargs
Args:
data (str or list): MSDP data.
Notes:
cmdname var --> cmdname arg
cmdname array --> cmdname *args
cmdname table --> cmdname **kwargs
"""
tables = {}
@ -211,11 +249,15 @@ class TelnetOOB(object):
def decode_gmcp(self, data):
"""
Decodes incoming GMCP data on the form 'varname <structure>'
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
Args:
data (str or list): GMCP data.
Notes:
cmdname string -> cmdname arg
cmdname [arg, arg,...] -> cmdname *args
cmdname {key:arg, key:arg, ...} -> cmdname **kwargs
"""
if hasattr(data, "__iter__"):
@ -248,6 +290,11 @@ class TelnetOOB(object):
def data_out(self, cmdname, *args, **kwargs):
"""
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
if self.MSDP:

View file

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

View file

@ -57,6 +57,7 @@ def jsonify(obj):
class WebClient(resource.Resource):
"""
An ajax/comet long-polling transport
"""
isLeaf = True
allowedMethods = ('POST',)
@ -80,8 +81,17 @@ class WebClient(resource.Resource):
def lineSend(self, suid, string, data=None):
"""
This adds the data to the buffer and/or sends it to
the client as soon as possible.
This adds the data to the buffer and/or sends it to the client
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)
if request:
@ -98,6 +108,10 @@ class WebClient(resource.Resource):
def client_disconnect(self, suid):
"""
Disconnect session with given suid.
Args:
suid (int): Session id.
"""
if suid in self.requests:
self.requests[suid].finish()
@ -107,8 +121,12 @@ class WebClient(resource.Resource):
def mode_init(self, request):
"""
This is called by render_POST when the client
requests an init mode operation (at startup)
This is called by render_POST when the client requests an init
mode operation (at startup)
Args:
request (Request): Incoming request.
"""
#csess = request.getSession() # obs, this is a cookie, not
# an evennia session!
@ -133,6 +151,10 @@ class WebClient(resource.Resource):
"""
This is called by render_POST when the client
is sending data to the server.
Args:
request (Request): Incoming request.
"""
suid = request.args.get('suid', ['0'])[0]
if suid == '0':
@ -148,10 +170,13 @@ class WebClient(resource.Resource):
def mode_receive(self, request):
"""
This is called by render_POST when the client is telling us
that it is ready to receive data as soon as it is
available. This is the basis of a long-polling (comet)
mechanism: the server will wait to reply until data is
available.
that it is ready to receive data as soon as it is available.
This is the basis of a long-polling (comet) mechanism: the
server will wait to reply until data is available.
Args:
request (Request): Incoming request.
"""
suid = request.args.get('suid', ['0'])[0]
if suid == '0':
@ -170,6 +195,10 @@ class WebClient(resource.Resource):
"""
This is called by render_POST when the client is signalling
that it is about to be closed.
Args:
request (Request): Incoming request.
"""
suid = request.args.get('suid', ['0'])[0]
if suid == '0':
@ -191,6 +220,10 @@ class WebClient(resource.Resource):
initializing or sending/receving data through the request. It
uses a long-polling mechanism to avoid sending data unless
there is actual data available.
Args:
request (Request): Incoming request.
"""
dmode = request.args.get('mode', [None])[0]
if dmode == 'init':
@ -222,7 +255,10 @@ class WebClientSession(session.Session):
def disconnect(self, reason=None):
"""
Disconnect from server
Disconnect from server.
Args:
reason (str): Motivation for the disconnect.
"""
if reason:
self.client.lineSend(self.suid, reason)
@ -230,11 +266,11 @@ class WebClientSession(session.Session):
def data_out(self, text=None, **kwargs):
"""
Data Evennia -> Player access hook.
Data Evennia -> User access hook.
webclient flags checked are
raw=True - no parsing at all (leave ansi-to-html markers unparsed)
nomarkup=True - clean out all ansi/html markers and tokens
Kwargs:
raw (bool): No parsing at all (leave ansi-to-html markers unparsed).
nomarkup (bool): Clean out all ansi/html markers and tokens.
"""
# 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], ...}
where the dict is JSON encoded. The initial OOB-prefix
is used to identify this type of communication, all other data
is considered plain text (command input).
where the dict is JSON encoded. The initial OOB-prefix is used to
identify this type of communication, all other data is considered
plain text (command input).
Example of call from a javascript client:
@ -43,6 +43,7 @@ class WebSocketClient(Protocol, Session):
def connectionMade(self):
"""
This is called when the connection is first established.
"""
client_address = self.transport.client
self.init_session("websocket", client_address, self.factory.sessionhandler)
@ -52,8 +53,12 @@ class WebSocketClient(Protocol, Session):
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.
Args:
reason (str): Motivation for the disconnection.
"""
if reason:
self.data_out(text=reason)
@ -61,25 +66,30 @@ class WebSocketClient(Protocol, Session):
def connectionLost(self, reason):
"""
this is executed when the connection is lost for
whatever reason. it can also be called directly, from
the disconnect method
This is executed when the connection is lost for whatever
reason. it can also be called directly, from the disconnect
method
Args:
reason (str): Motivation for the lost connection.
"""
self.sessionhandler.disconnect(self)
self.transport.close()
def dataReceived(self, string):
"""
Method called when data is coming in over
the websocket connection.
Method called when data is coming in over the websocket
connection.
Type of data is identified by a 3-character
prefix.
OOB - This is an Out-of-band instruction. If so,
the remaining string should be a json-packed
string on the form {oobfuncname: [args, ], ...}
CMD - plain text data, to be treated like a game
input command.
Args:
string (str): Type of data is identified by a 3-character
prefix:
- "OOB" This is an Out-of-band instruction. If so,
the remaining string should be a json-packed
string on the form {oobfuncname: [args, ], ...}
- "CMD" plain text data, to be treated like a game
input command.
"""
mode = string[:3]
data = string[3:]
@ -92,14 +102,27 @@ class WebSocketClient(Protocol, Session):
self.data_in(text=data)
def sendLine(self, line):
"send data to client"
"""
Send data to client.
Args:
line (str): Text to send.
"""
return self.transport.write(line)
def json_decode(self, data):
"""
Decodes incoming data from the client
Decodes incoming data from the client.
[cmdname, [args],{kwargs}] -> cmdname *args **kwargs
Args:
data (JSON): JSON object to unpack.
Raises:
Exception: If receiving a malform OOB request.
Notes:
[cmdname, [args],{kwargs}] -> cmdname *args **kwargs
"""
try:
@ -111,10 +134,15 @@ class WebSocketClient(Protocol, Session):
def json_encode(self, cmdname, *args, **kwargs):
"""
Encode OOB data for sending to client
Encode OOB data for sending to client.
cmdname *args -> cmdname [json array]
cmdname **kwargs -> cmdname {json object}
Args:
cmdname (str): OOB command name.
args, kwargs (any): Arguments to oob command.
Notes:
cmdname *args -> cmdname [json array]
cmdname **kwargs -> cmdname {json object}
"""
cmdtuple = [cmdname, list(args), kwargs]
@ -122,20 +150,25 @@ class WebSocketClient(Protocol, Session):
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)
def data_out(self, text=None, **kwargs):
"""
Data Evennia -> Player.
generic hook method for engine to call in order to send data
through the websocket connection.
Data Evennia -> User. A generic hook method for engine to call
in order to send data 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:
text = to_str(text if text else "", encoding=self.encoding)