mirror of
https://github.com/evennia/evennia.git
synced 2026-03-22 15:56:30 +01:00
Finished converting server/ and server/portal to google-style docstrings as per #709.
This commit is contained in:
parent
ccae355175
commit
19bfaae8a6
15 changed files with 906 additions and 268 deletions
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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, '')
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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("<", "<") \
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue