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):
|
class IMC2Mud(object):
|
||||||
"""
|
"""
|
||||||
Stores information about other games connected to our current IMC2 network.
|
Stores information about other games connected to our current IMC2 network.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def __init__(self, packet):
|
def __init__(self, packet):
|
||||||
self.name = packet.origin
|
self.name = packet.origin
|
||||||
|
|
@ -37,6 +38,7 @@ class IMC2Mud(object):
|
||||||
class IMC2MudList(dict):
|
class IMC2MudList(dict):
|
||||||
"""
|
"""
|
||||||
Keeps track of other MUDs connected to the IMC network.
|
Keeps track of other MUDs connected to the IMC network.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def get_mud_list(self):
|
def get_mud_list(self):
|
||||||
"""
|
"""
|
||||||
|
|
@ -50,6 +52,10 @@ class IMC2MudList(dict):
|
||||||
"""
|
"""
|
||||||
This grabs relevant info from the packet and stuffs it in the
|
This grabs relevant info from the packet and stuffs it in the
|
||||||
Mud list for later retrieval.
|
Mud list for later retrieval.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
packet (Packet): incoming packet.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
mud = IMC2Mud(packet)
|
mud = IMC2Mud(packet)
|
||||||
self[mud.name] = mud
|
self[mud.name] = mud
|
||||||
|
|
@ -57,6 +63,10 @@ class IMC2MudList(dict):
|
||||||
def remove_mud_from_packet(self, packet):
|
def remove_mud_from_packet(self, packet):
|
||||||
"""
|
"""
|
||||||
Removes a mud from the Mud list when given a packet.
|
Removes a mud from the Mud list when given a packet.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
packet (Packet): Incoming packet.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
mud = IMC2Mud(packet)
|
mud = IMC2Mud(packet)
|
||||||
try:
|
try:
|
||||||
|
|
@ -71,6 +81,7 @@ class IMC2Channel(object):
|
||||||
Stores information about channels available on the network.
|
Stores information about channels available on the network.
|
||||||
"""
|
"""
|
||||||
def __init__(self, packet):
|
def __init__(self, packet):
|
||||||
|
"Initialize channel."
|
||||||
self.localname = packet.optional_data.get('localname', None)
|
self.localname = packet.optional_data.get('localname', None)
|
||||||
self.name = packet.optional_data.get('channel', None)
|
self.name = packet.optional_data.get('channel', None)
|
||||||
self.level = packet.optional_data.get('level', None)
|
self.level = packet.optional_data.get('level', None)
|
||||||
|
|
@ -87,6 +98,7 @@ class IMC2ChanList(dict):
|
||||||
def get_channel_list(self):
|
def get_channel_list(self):
|
||||||
"""
|
"""
|
||||||
Returns a sorted list of cached channels.
|
Returns a sorted list of cached channels.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
channels = self.items()
|
channels = self.items()
|
||||||
channels.sort()
|
channels.sort()
|
||||||
|
|
@ -96,6 +108,10 @@ class IMC2ChanList(dict):
|
||||||
"""
|
"""
|
||||||
This grabs relevant info from the packet and stuffs it in the
|
This grabs relevant info from the packet and stuffs it in the
|
||||||
channel list for later retrieval.
|
channel list for later retrieval.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
packet (Packet): incoming packet.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
channel = IMC2Channel(packet)
|
channel = IMC2Channel(packet)
|
||||||
self[channel.name] = channel
|
self[channel.name] = channel
|
||||||
|
|
@ -103,6 +119,10 @@ class IMC2ChanList(dict):
|
||||||
def remove_channel_from_packet(self, packet):
|
def remove_channel_from_packet(self, packet):
|
||||||
"""
|
"""
|
||||||
Removes a channel from the Channel list when given a packet.
|
Removes a channel from the Channel list when given a packet.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
packet (Packet): incoming packet.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
channel = IMC2Channel(packet)
|
channel = IMC2Channel(packet)
|
||||||
try:
|
try:
|
||||||
|
|
@ -120,8 +140,10 @@ class IMC2Bot(telnet.StatefulTelnetProtocol, Session):
|
||||||
"""
|
"""
|
||||||
Provides the abstraction for the IMC2 protocol. Handles connection,
|
Provides the abstraction for the IMC2 protocol. Handles connection,
|
||||||
authentication, and all necessary packets.
|
authentication, and all necessary packets.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
"Initialize bot."
|
||||||
self.is_authenticated = False
|
self.is_authenticated = False
|
||||||
# only support plaintext passwords
|
# only support plaintext passwords
|
||||||
self.auth_type = "plaintext"
|
self.auth_type = "plaintext"
|
||||||
|
|
@ -130,37 +152,49 @@ class IMC2Bot(telnet.StatefulTelnetProtocol, Session):
|
||||||
self.imc2_chanlist = IMC2ChanList()
|
self.imc2_chanlist = IMC2ChanList()
|
||||||
|
|
||||||
def _send_packet(self, packet):
|
def _send_packet(self, packet):
|
||||||
"Helper function to send packets across the wire"
|
"""
|
||||||
|
Helper function to send packets across the wire.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
packet (Packet): Outgoing packet.
|
||||||
|
|
||||||
|
"""
|
||||||
packet.imc2_protocol = self
|
packet.imc2_protocol = self
|
||||||
packet_str = utils.to_str(packet.assemble(self.factory.mudname,
|
packet_str = utils.to_str(packet.assemble(self.factory.mudname,
|
||||||
self.factory.client_pwd, self.factory.server_pwd))
|
self.factory.client_pwd, self.factory.server_pwd))
|
||||||
self.sendLine(packet_str)
|
self.sendLine(packet_str)
|
||||||
|
|
||||||
def _isalive(self):
|
def _isalive(self):
|
||||||
"Send an isalive packet"
|
"Send an isalive packet."
|
||||||
self._send_packet(pck.IMC2PacketIsAlive())
|
self._send_packet(pck.IMC2PacketIsAlive())
|
||||||
|
|
||||||
def _keepalive(self):
|
def _keepalive(self):
|
||||||
"Send a keepalive packet"
|
"Send a keepalive packet."
|
||||||
# send to channel?
|
# send to channel?
|
||||||
self._send_packet(pck.IMC2PacketKeepAliveRequest())
|
self._send_packet(pck.IMC2PacketKeepAliveRequest())
|
||||||
|
|
||||||
def _channellist(self):
|
def _channellist(self):
|
||||||
"Sync the network channel list"
|
"Sync the network channel list."
|
||||||
checked_networks = []
|
checked_networks = []
|
||||||
if not self.network in checked_networks:
|
if not self.network in checked_networks:
|
||||||
self._send_packet(pck.IMC2PacketIceRefresh())
|
self._send_packet(pck.IMC2PacketIceRefresh())
|
||||||
checked_networks.append(self.network)
|
checked_networks.append(self.network)
|
||||||
|
|
||||||
def _prune(self):
|
def _prune(self):
|
||||||
"Prune active channel list"
|
"Prune active channel list."
|
||||||
t0 = time()
|
t0 = time()
|
||||||
for name, mudinfo in self.imc2_mudlist.items():
|
for name, mudinfo in self.imc2_mudlist.items():
|
||||||
if t0 - mudinfo.last_updated > 3599:
|
if t0 - mudinfo.last_updated > 3599:
|
||||||
del self.imc2_mudlist[name]
|
del self.imc2_mudlist[name]
|
||||||
|
|
||||||
def _whois_reply(self, packet):
|
def _whois_reply(self, packet):
|
||||||
"handle reply from server from an imcwhois request"
|
"""
|
||||||
|
Handle reply from server from an imcwhois request.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
packet (Packet): Data packet.
|
||||||
|
|
||||||
|
"""
|
||||||
# packet.target potentially contains the id of an character to target
|
# packet.target potentially contains the id of an character to target
|
||||||
# not using that here
|
# not using that here
|
||||||
response_text = imc2_ansi.parse_ansi(packet.optional_data.get('text', 'Unknown'))
|
response_text = imc2_ansi.parse_ansi(packet.optional_data.get('text', 'Unknown'))
|
||||||
|
|
@ -171,13 +205,24 @@ class IMC2Bot(telnet.StatefulTelnetProtocol, Session):
|
||||||
def _format_tell(self, packet):
|
def _format_tell(self, packet):
|
||||||
"""
|
"""
|
||||||
Handle tells over IMC2 by formatting the text properly
|
Handle tells over IMC2 by formatting the text properly
|
||||||
|
|
||||||
|
Args:
|
||||||
|
packet (Packet): Data packet.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return _("{c%(sender)s@%(origin)s{n {wpages (over IMC):{n %(msg)s") % {"sender": packet.sender,
|
return _("{c%(sender)s@%(origin)s{n {wpages (over IMC):{n %(msg)s") % {"sender": packet.sender,
|
||||||
"origin": packet.origin,
|
"origin": packet.origin,
|
||||||
"msg": packet.optional_data.get('text', 'ERROR: No text provided.')}
|
"msg": packet.optional_data.get('text', 'ERROR: No text provided.')}
|
||||||
|
|
||||||
def _imc_login(self, line):
|
def _imc_login(self, line):
|
||||||
"Connect and identify to imc network"
|
"""
|
||||||
|
Connect and identify to imc network as per the
|
||||||
|
`self.auth_type` setting.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
line (str): Incoming text.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
if self.auth_type == "plaintext":
|
if self.auth_type == "plaintext":
|
||||||
# Only support Plain text passwords.
|
# Only support Plain text passwords.
|
||||||
|
|
@ -218,6 +263,7 @@ class IMC2Bot(telnet.StatefulTelnetProtocol, Session):
|
||||||
def connectionMade(self):
|
def connectionMade(self):
|
||||||
"""
|
"""
|
||||||
Triggered after connecting to the IMC2 network.
|
Triggered after connecting to the IMC2 network.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.stopping = False
|
self.stopping = False
|
||||||
|
|
@ -239,6 +285,9 @@ class IMC2Bot(telnet.StatefulTelnetProtocol, Session):
|
||||||
Triggered when text is received from the IMC2 network. Figures out
|
Triggered when text is received from the IMC2 network. Figures out
|
||||||
what to do with the packet. This deals with the following
|
what to do with the packet. This deals with the following
|
||||||
|
|
||||||
|
Args:
|
||||||
|
line (str): Incoming text.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
line = line.strip()
|
line = line.strip()
|
||||||
|
|
||||||
|
|
@ -278,24 +327,31 @@ class IMC2Bot(telnet.StatefulTelnetProtocol, Session):
|
||||||
|
|
||||||
def data_in(self, text=None, **kwargs):
|
def data_in(self, text=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
Data IMC2 -> Evennia
|
Data IMC2 -> Evennia.
|
||||||
|
|
||||||
|
Kwargs:
|
||||||
|
text (str): Incoming text.
|
||||||
|
kwargs (any): Other data from protocol.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
text = "bot_data_in " + text
|
text = "bot_data_in " + text
|
||||||
self.sessionhandler.data_in(self, text=text, **kwargs)
|
self.sessionhandler.data_in(self, text=text, **kwargs)
|
||||||
|
|
||||||
def data_out(self, text=None, **kwargs):
|
def data_out(self, text=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
Evennia -> IMC2
|
Evennia -> IMC2.
|
||||||
|
|
||||||
Keywords
|
Kwargs:
|
||||||
packet_type:
|
text (str): Outgoing text.
|
||||||
broadcast - send to everyone on IMC channel
|
packet_type (str):
|
||||||
tell - send a tell (see target keyword)
|
- broadcast: Send to everyone on IMC channel.
|
||||||
whois - get whois information (see target keyword)
|
- tell: Send a tell (see target keyword).
|
||||||
sender - used by tell to identify the sender
|
- whois: Get whois information (see target keyword).
|
||||||
target - key identifier of target to tells or whois. If not
|
sender (str): Used by tell to identify the mud sending.
|
||||||
|
target (str): Key identifier of target to tells or whois. If not
|
||||||
given "Unknown" will be used.
|
given "Unknown" will be used.
|
||||||
destination - used by tell to specify mud destination to send to
|
destination (str): Used by tell to specify mud
|
||||||
|
destination to send to.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
@ -338,6 +394,7 @@ class IMC2BotFactory(protocol.ReconnectingClientFactory):
|
||||||
"""
|
"""
|
||||||
Creates instances of the IMC2Protocol. Should really only ever
|
Creates instances of the IMC2Protocol. Should really only ever
|
||||||
need to create one connection. Tied in via evennia/server.py.
|
need to create one connection. Tied in via evennia/server.py.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
initialDelay = 1
|
initialDelay = 1
|
||||||
factor = 1.5
|
factor = 1.5
|
||||||
|
|
@ -345,6 +402,7 @@ class IMC2BotFactory(protocol.ReconnectingClientFactory):
|
||||||
|
|
||||||
def __init__(self, sessionhandler, uid=None, network=None, channel=None,
|
def __init__(self, sessionhandler, uid=None, network=None, channel=None,
|
||||||
port=None, mudname=None, client_pwd=None, server_pwd=None):
|
port=None, mudname=None, client_pwd=None, server_pwd=None):
|
||||||
|
"Initialize the bot factory."
|
||||||
self.uid = uid
|
self.uid = uid
|
||||||
self.network = network
|
self.network = network
|
||||||
sname, host = network.split(".", 1)
|
sname, host = network.split(".", 1)
|
||||||
|
|
@ -362,7 +420,16 @@ class IMC2BotFactory(protocol.ReconnectingClientFactory):
|
||||||
self.task_channellist = None
|
self.task_channellist = None
|
||||||
|
|
||||||
def buildProtocol(self, addr):
|
def buildProtocol(self, addr):
|
||||||
"Build the protocol"
|
"""
|
||||||
|
Build the protocol.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
addr (str): Protocl address.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
protocol (Protocol): The new protocol.
|
||||||
|
|
||||||
|
"""
|
||||||
protocol = IMC2Bot()
|
protocol = IMC2Bot()
|
||||||
protocol.factory = self
|
protocol.factory = self
|
||||||
protocol.network = self.network
|
protocol.network = self.network
|
||||||
|
|
@ -373,9 +440,23 @@ class IMC2BotFactory(protocol.ReconnectingClientFactory):
|
||||||
return protocol
|
return protocol
|
||||||
|
|
||||||
def clientConnectionFailed(self, connector, reason):
|
def clientConnectionFailed(self, connector, reason):
|
||||||
|
"""
|
||||||
|
Called when Client could not connect.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
connector (Connector): Reprsents the connection.
|
||||||
|
reason (str): Reason for the failure.
|
||||||
|
"""
|
||||||
self.retry(connector)
|
self.retry(connector)
|
||||||
|
|
||||||
def clientConnectionLost(self, connector, reason):
|
def clientConnectionLost(self, connector, reason):
|
||||||
|
"""
|
||||||
|
Called when Client looses connection.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
connector (Connector): Reprsents the connection.
|
||||||
|
reason (str): Reason for the failure.
|
||||||
|
"""
|
||||||
if not self.bot.stopping:
|
if not self.bot.stopping:
|
||||||
self.retry(connector)
|
self.retry(connector)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -83,11 +83,28 @@ RE_MXP = re.compile(r'\{lc(.*?)\{lt(.*?)\{le', re.DOTALL)
|
||||||
RE_ANSI_ESCAPES = re.compile(r"(%s)" % "|".join(("{{", "%%", "\\\\")), re.DOTALL)
|
RE_ANSI_ESCAPES = re.compile(r"(%s)" % "|".join(("{{", "%%", "\\\\")), re.DOTALL)
|
||||||
|
|
||||||
def sub_irc(ircmatch):
|
def sub_irc(ircmatch):
|
||||||
|
"""
|
||||||
|
Substitute irc color info. Used by re.sub.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
ircmatch (Match): The match from regex.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
colored (str): A string with converted IRC colors.
|
||||||
|
|
||||||
|
"""
|
||||||
return IRC_COLOR_MAP.get(ircmatch.group(), "")
|
return IRC_COLOR_MAP.get(ircmatch.group(), "")
|
||||||
|
|
||||||
def parse_irc_colors(string):
|
def parse_irc_colors(string):
|
||||||
"""
|
"""
|
||||||
Parse {-type syntax and replace with IRC color markers
|
Parse {-type syntax and replace with IRC color markers
|
||||||
|
|
||||||
|
Args:
|
||||||
|
string (str): String to parse for IRC colors.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
parsed_string (str): String with replaced IRC colors.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
in_string = utils.to_str(string)
|
in_string = utils.to_str(string)
|
||||||
parsed_string = ""
|
parsed_string = ""
|
||||||
|
|
@ -105,6 +122,7 @@ class IRCBot(irc.IRCClient, Session):
|
||||||
"""
|
"""
|
||||||
An IRC bot that tracks actitivity in a channel as well
|
An IRC bot that tracks actitivity in a channel as well
|
||||||
as sends text to it when prompted
|
as sends text to it when prompted
|
||||||
|
|
||||||
"""
|
"""
|
||||||
lineRate = 1
|
lineRate = 1
|
||||||
|
|
||||||
|
|
@ -117,9 +135,9 @@ class IRCBot(irc.IRCClient, Session):
|
||||||
|
|
||||||
def signedOn(self):
|
def signedOn(self):
|
||||||
"""
|
"""
|
||||||
This is called when we successfully connect to
|
This is called when we successfully connect to the network. We
|
||||||
the network. We make sure to now register with
|
make sure to now register with the game as a full session.
|
||||||
the game as a full session.
|
|
||||||
"""
|
"""
|
||||||
self.join(self.channel)
|
self.join(self.channel)
|
||||||
self.stopping = False
|
self.stopping = False
|
||||||
|
|
@ -135,7 +153,11 @@ class IRCBot(irc.IRCClient, Session):
|
||||||
|
|
||||||
def disconnect(self, reason=None):
|
def disconnect(self, reason=None):
|
||||||
"""
|
"""
|
||||||
Called by sessionhandler to disconnect this protocol
|
Called by sessionhandler to disconnect this protocol.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
reason (str): Motivation for the disconnect.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
print "irc disconnect called!"
|
print "irc disconnect called!"
|
||||||
self.sessionhandler.disconnect(self)
|
self.sessionhandler.disconnect(self)
|
||||||
|
|
@ -143,23 +165,53 @@ class IRCBot(irc.IRCClient, Session):
|
||||||
self.transport.loseConnection()
|
self.transport.loseConnection()
|
||||||
|
|
||||||
def privmsg(self, user, channel, msg):
|
def privmsg(self, user, channel, msg):
|
||||||
"A message was sent to channel"
|
"""
|
||||||
|
Called when the connected channel receives a message.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
user (str): User name sending the message.
|
||||||
|
channel (str): Channel name seeing the message.
|
||||||
|
msg (str): The message arriving from channel.
|
||||||
|
|
||||||
|
"""
|
||||||
if not msg.startswith('***'):
|
if not msg.startswith('***'):
|
||||||
user = user.split('!', 1)[0]
|
user = user.split('!', 1)[0]
|
||||||
self.data_in("bot_data_in %s@%s: %s" % (user, channel, msg))
|
self.data_in("bot_data_in %s@%s: %s" % (user, channel, msg))
|
||||||
|
|
||||||
def action(self, user, channel, msg):
|
def action(self, user, channel, msg):
|
||||||
"An action was done in channel"
|
"""
|
||||||
|
Called when an action is detected in channel.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
user (str): User name sending the message.
|
||||||
|
channel (str): Channel name seeing the message.
|
||||||
|
msg (str): The message arriving from channel.
|
||||||
|
|
||||||
|
"""
|
||||||
if not msg.startswith('**'):
|
if not msg.startswith('**'):
|
||||||
user = user.split('!', 1)[0]
|
user = user.split('!', 1)[0]
|
||||||
self.data_in("bot_data_in %s@%s %s" % (user, channel, msg))
|
self.data_in("bot_data_in %s@%s %s" % (user, channel, msg))
|
||||||
|
|
||||||
def data_in(self, text=None, **kwargs):
|
def data_in(self, text=None, **kwargs):
|
||||||
"Data IRC -> Server"
|
"""
|
||||||
|
Data IRC -> Server.
|
||||||
|
|
||||||
|
Kwargs:
|
||||||
|
text (str): Ingoing text.
|
||||||
|
kwargs (any): Other data from protocol.
|
||||||
|
|
||||||
|
"""
|
||||||
self.sessionhandler.data_in(self, text=text, **kwargs)
|
self.sessionhandler.data_in(self, text=text, **kwargs)
|
||||||
|
|
||||||
def data_out(self, text=None, **kwargs):
|
def data_out(self, text=None, **kwargs):
|
||||||
"Data from server-> IRC"
|
"""
|
||||||
|
Data from server-> IRC.
|
||||||
|
|
||||||
|
Kwargs:
|
||||||
|
text (str): Outgoing text.
|
||||||
|
kwargs (any): Other data to protocol.
|
||||||
|
|
||||||
|
"""
|
||||||
if text.startswith("bot_data_out"):
|
if text.startswith("bot_data_out"):
|
||||||
text = text.split(" ", 1)[1]
|
text = text.split(" ", 1)[1]
|
||||||
text = parse_irc_colors(text)
|
text = parse_irc_colors(text)
|
||||||
|
|
@ -168,8 +220,9 @@ class IRCBot(irc.IRCClient, Session):
|
||||||
|
|
||||||
class IRCBotFactory(protocol.ReconnectingClientFactory):
|
class IRCBotFactory(protocol.ReconnectingClientFactory):
|
||||||
"""
|
"""
|
||||||
Creates instances of AnnounceBot, connecting with
|
Creates instances of AnnounceBot, connecting with a staggered
|
||||||
a staggered increase in delay
|
increase in delay
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# scaling reconnect time
|
# scaling reconnect time
|
||||||
initialDelay = 1
|
initialDelay = 1
|
||||||
|
|
@ -177,7 +230,20 @@ class IRCBotFactory(protocol.ReconnectingClientFactory):
|
||||||
maxDelay = 60
|
maxDelay = 60
|
||||||
|
|
||||||
def __init__(self, sessionhandler, uid=None, botname=None, channel=None, network=None, port=None):
|
def __init__(self, sessionhandler, uid=None, botname=None, channel=None, network=None, port=None):
|
||||||
"Storing some important protocol properties"
|
"""
|
||||||
|
Storing some important protocol properties.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
sessionhandler (SessionHandler): Reference to the main Sessionhandler.
|
||||||
|
|
||||||
|
Kwargs:
|
||||||
|
uid (int): Bot user id.
|
||||||
|
botname (str): Bot name (seen in IRC channel).
|
||||||
|
channel (str): IRC channel to connect to.
|
||||||
|
network (str): Network address to connect to.
|
||||||
|
port (str): Port of the network.
|
||||||
|
|
||||||
|
"""
|
||||||
self.sessionhandler = sessionhandler
|
self.sessionhandler = sessionhandler
|
||||||
self.uid = uid
|
self.uid = uid
|
||||||
self.nickname = str(botname)
|
self.nickname = str(botname)
|
||||||
|
|
@ -187,7 +253,13 @@ class IRCBotFactory(protocol.ReconnectingClientFactory):
|
||||||
self.bot = None
|
self.bot = None
|
||||||
|
|
||||||
def buildProtocol(self, addr):
|
def buildProtocol(self, addr):
|
||||||
"Build the protocol and assign it some properties"
|
"""
|
||||||
|
Build the protocol and assign it some properties.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
addr (str): Not used; using factory data.
|
||||||
|
|
||||||
|
"""
|
||||||
protocol = IRCBot()
|
protocol = IRCBot()
|
||||||
protocol.factory = self
|
protocol.factory = self
|
||||||
protocol.nickname = self.nickname
|
protocol.nickname = self.nickname
|
||||||
|
|
@ -197,18 +269,43 @@ class IRCBotFactory(protocol.ReconnectingClientFactory):
|
||||||
return protocol
|
return protocol
|
||||||
|
|
||||||
def startedConnecting(self, connector):
|
def startedConnecting(self, connector):
|
||||||
"Tracks reconnections for debugging"
|
"""
|
||||||
|
Tracks reconnections for debugging.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
connector (Connector): Represents the connection.
|
||||||
|
|
||||||
|
"""
|
||||||
logger.log_infomsg("(re)connecting to %s" % self.channel)
|
logger.log_infomsg("(re)connecting to %s" % self.channel)
|
||||||
|
|
||||||
def clientConnectionFailed(self, connector, reason):
|
def clientConnectionFailed(self, connector, reason):
|
||||||
|
"""
|
||||||
|
Called when Client failed to connect.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
connector (Connection): Represents the connection.
|
||||||
|
reason (str): The reason for the failure.
|
||||||
|
|
||||||
|
"""
|
||||||
self.retry(connector)
|
self.retry(connector)
|
||||||
|
|
||||||
def clientConnectionLost(self, connector, reason):
|
def clientConnectionLost(self, connector, reason):
|
||||||
|
"""
|
||||||
|
Called when Client looses connection.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
connector (Connection): Represents the connection.
|
||||||
|
reason (str): The reason for the failure.
|
||||||
|
|
||||||
|
"""
|
||||||
if not self.bot.stopping:
|
if not self.bot.stopping:
|
||||||
self.retry(connector)
|
self.retry(connector)
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
"Connect session to sessionhandler"
|
"""
|
||||||
|
Connect session to sessionhandler.
|
||||||
|
|
||||||
|
"""
|
||||||
if self.port:
|
if self.port:
|
||||||
service = internet.TCPClient(self.network, int(self.port), self)
|
service = internet.TCPClient(self.network, int(self.port), self)
|
||||||
self.sessionhandler.portal.services.addService(service)
|
self.sessionhandler.portal.services.addService(service)
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,16 @@ FLUSH = zlib.Z_SYNC_FLUSH
|
||||||
|
|
||||||
|
|
||||||
def mccp_compress(protocol, data):
|
def mccp_compress(protocol, data):
|
||||||
"Handles zlib compression, if applicable"
|
"""
|
||||||
|
Handles zlib compression, if applicable.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
data (str): Incoming data to compress.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
stream (binary): Zlib-compressed data.
|
||||||
|
|
||||||
|
"""
|
||||||
if hasattr(protocol, 'zlib'):
|
if hasattr(protocol, 'zlib'):
|
||||||
return protocol.zlib.compress(data) + protocol.zlib.flush(FLUSH)
|
return protocol.zlib.compress(data) + protocol.zlib.flush(FLUSH)
|
||||||
return data
|
return data
|
||||||
|
|
@ -32,6 +41,7 @@ class Mccp(object):
|
||||||
"""
|
"""
|
||||||
Implements the MCCP protocol. Add this to a
|
Implements the MCCP protocol. Add this to a
|
||||||
variable on the telnet protocol to set it up.
|
variable on the telnet protocol to set it up.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, protocol):
|
def __init__(self, protocol):
|
||||||
|
|
@ -40,6 +50,10 @@ class Mccp(object):
|
||||||
ourselves and calling the client to see if
|
ourselves and calling the client to see if
|
||||||
it supports MCCP. Sets callbacks to
|
it supports MCCP. Sets callbacks to
|
||||||
start zlib compression in that case.
|
start zlib compression in that case.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
protocol (Protocol): The active protocol instance.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.protocol = protocol
|
self.protocol = protocol
|
||||||
|
|
@ -49,7 +63,11 @@ class Mccp(object):
|
||||||
|
|
||||||
def no_mccp(self, option):
|
def no_mccp(self, option):
|
||||||
"""
|
"""
|
||||||
Called if client doesn't support mccp or chooses to turn it off
|
Called if client doesn't support mccp or chooses to turn it off.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
option (Option): Option dict (not used).
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if hasattr(self.protocol, 'zlib'):
|
if hasattr(self.protocol, 'zlib'):
|
||||||
del self.protocol.zlib
|
del self.protocol.zlib
|
||||||
|
|
@ -60,6 +78,10 @@ class Mccp(object):
|
||||||
"""
|
"""
|
||||||
The client supports MCCP. Set things up by
|
The client supports MCCP. Set things up by
|
||||||
creating a zlib compression stream.
|
creating a zlib compression stream.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
option (Option): Option dict (not used).
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.protocol.protocol_flags['MCCP'] = True
|
self.protocol.protocol_flags['MCCP'] = True
|
||||||
self.protocol.requestNegotiation(MCCP, '')
|
self.protocol.requestNegotiation(MCCP, '')
|
||||||
|
|
|
||||||
|
|
@ -23,29 +23,50 @@ MSSPTable_CUSTOM = utils.variable_from_module(settings.MSSP_META_MODULE, "MSSPTa
|
||||||
|
|
||||||
class Mssp(object):
|
class Mssp(object):
|
||||||
"""
|
"""
|
||||||
Implements the MSSP protocol. Add this to a
|
Implements the MSSP protocol. Add this to a variable on the telnet
|
||||||
variable on the telnet protocol to set it up.
|
protocol to set it up.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def __init__(self, protocol):
|
def __init__(self, protocol):
|
||||||
"""
|
"""
|
||||||
initialize MSSP by storing protocol on ourselves
|
initialize MSSP by storing protocol on ourselves and calling
|
||||||
and calling the client to see if it supports
|
the client to see if it supports MSSP.
|
||||||
MSSP.
|
|
||||||
|
Args:
|
||||||
|
protocol (Protocol): The active protocol instance.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.protocol = protocol
|
self.protocol = protocol
|
||||||
self.protocol.will(MSSP).addCallbacks(self.do_mssp, self.no_mssp)
|
self.protocol.will(MSSP).addCallbacks(self.do_mssp, self.no_mssp)
|
||||||
|
|
||||||
def get_player_count(self):
|
def get_player_count(self):
|
||||||
"Get number of logged-in players"
|
"""
|
||||||
|
Get number of logged-in players.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
count (int): The number of players in the MUD.
|
||||||
|
|
||||||
|
"""
|
||||||
return str(self.protocol.sessionhandler.count_loggedin())
|
return str(self.protocol.sessionhandler.count_loggedin())
|
||||||
|
|
||||||
def get_uptime(self):
|
def get_uptime(self):
|
||||||
"Get how long the portal has been online (reloads are not counted)"
|
"""
|
||||||
|
Get how long the portal has been online (reloads are not counted).
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
uptime (int): Number of seconds of uptime.
|
||||||
|
|
||||||
|
"""
|
||||||
return str(self.protocol.sessionhandler.uptime)
|
return str(self.protocol.sessionhandler.uptime)
|
||||||
|
|
||||||
def no_mssp(self, option):
|
def no_mssp(self, option):
|
||||||
"""
|
"""
|
||||||
This is the normal operation.
|
Called when mssp is not requested. This is the normal
|
||||||
|
operation.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
option (Option): Not used.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.protocol.handshake_done()
|
self.protocol.handshake_done()
|
||||||
pass
|
pass
|
||||||
|
|
@ -53,6 +74,10 @@ class Mssp(object):
|
||||||
def do_mssp(self, option):
|
def do_mssp(self, option):
|
||||||
"""
|
"""
|
||||||
Negotiate all the information.
|
Negotiate all the information.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
option (Option): Not used.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.mssp_table = {
|
self.mssp_table = {
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ More information can be found on the following links:
|
||||||
http://www.zuggsoft.com/zmud/mxp.htm
|
http://www.zuggsoft.com/zmud/mxp.htm
|
||||||
http://www.mushclient.com/mushclient/mxp.htm
|
http://www.mushclient.com/mushclient/mxp.htm
|
||||||
http://www.gammon.com.au/mushclient/addingservermxp.htm
|
http://www.gammon.com.au/mushclient/addingservermxp.htm
|
||||||
|
|
||||||
"""
|
"""
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
|
@ -27,6 +28,13 @@ MXP_SEND = MXP_TEMPSECURE + \
|
||||||
def mxp_parse(text):
|
def mxp_parse(text):
|
||||||
"""
|
"""
|
||||||
Replaces links to the correct format for MXP.
|
Replaces links to the correct format for MXP.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
text (str): The text to parse.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
parsed (str): The parsed text.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
text = text.replace("&", "&") \
|
text = text.replace("&", "&") \
|
||||||
.replace("<", "<") \
|
.replace("<", "<") \
|
||||||
|
|
@ -38,24 +46,39 @@ def mxp_parse(text):
|
||||||
class Mxp(object):
|
class Mxp(object):
|
||||||
"""
|
"""
|
||||||
Implements the MXP protocol.
|
Implements the MXP protocol.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, protocol):
|
def __init__(self, protocol):
|
||||||
"""Initializes the protocol by checking if the client supports it."""
|
"""
|
||||||
|
Initializes the protocol by checking if the client supports it.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
protocol (Protocol): The active protocol instance.
|
||||||
|
|
||||||
|
"""
|
||||||
self.protocol = protocol
|
self.protocol = protocol
|
||||||
self.protocol.protocol_flags["MXP"] = False
|
self.protocol.protocol_flags["MXP"] = False
|
||||||
self.protocol.will(MXP).addCallbacks(self.do_mxp, self.no_mxp)
|
self.protocol.will(MXP).addCallbacks(self.do_mxp, self.no_mxp)
|
||||||
|
|
||||||
def no_mxp(self, option):
|
def no_mxp(self, option):
|
||||||
"""
|
"""
|
||||||
Client does not support MXP.
|
Called when the Client reports to not support MXP.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
option (Option): Not used.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.protocol.protocol_flags["MXP"] = False
|
self.protocol.protocol_flags["MXP"] = False
|
||||||
self.protocol.handshake_done()
|
self.protocol.handshake_done()
|
||||||
|
|
||||||
def do_mxp(self, option):
|
def do_mxp(self, option):
|
||||||
"""
|
"""
|
||||||
Client does support MXP.
|
Called when the Client reports to support MXP.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
option (Option): Not used.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.protocol.protocol_flags["MXP"] = True
|
self.protocol.protocol_flags["MXP"] = True
|
||||||
self.protocol.handshake_done()
|
self.protocol.handshake_done()
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,8 @@ NAWS - Negotiate About Window Size
|
||||||
This implements the NAWS telnet option as per
|
This implements the NAWS telnet option as per
|
||||||
https://www.ietf.org/rfc/rfc1073.txt
|
https://www.ietf.org/rfc/rfc1073.txt
|
||||||
|
|
||||||
NAWS allows telnet clients to report their
|
NAWS allows telnet clients to report their current window size to the
|
||||||
current window size to the client and update
|
client and update it when the size changes
|
||||||
it when the size changes
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
@ -22,14 +21,18 @@ DEFAULT_HEIGHT = settings.CLIENT_DEFAULT_HEIGHT
|
||||||
|
|
||||||
class Naws(object):
|
class Naws(object):
|
||||||
"""
|
"""
|
||||||
Implements the MSSP protocol. Add this to a
|
Implements the NAWS protocol. Add this to a variable on the telnet
|
||||||
variable on the telnet protocol to set it up.
|
protocol to set it up.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def __init__(self, protocol):
|
def __init__(self, protocol):
|
||||||
"""
|
"""
|
||||||
initialize NAWS by storing protocol on ourselves
|
initialize NAWS by storing protocol on ourselves and calling
|
||||||
and calling the client to see if it supports
|
the client to see if it supports NAWS.
|
||||||
NAWS.
|
|
||||||
|
Args:
|
||||||
|
protocol (Protocol): The active protocol instance.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.naws_step = 0
|
self.naws_step = 0
|
||||||
self.protocol = protocol
|
self.protocol = protocol
|
||||||
|
|
@ -40,17 +43,33 @@ class Naws(object):
|
||||||
|
|
||||||
def no_naws(self, option):
|
def no_naws(self, option):
|
||||||
"""
|
"""
|
||||||
This is the normal operation.
|
Called when client is not reporting NAWS. This is the normal
|
||||||
|
operation.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
option (Option): Not used.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.protocol.handshake_done()
|
self.protocol.handshake_done()
|
||||||
|
|
||||||
def do_naws(self, option):
|
def do_naws(self, option):
|
||||||
"""
|
"""
|
||||||
Negotiate all the information.
|
Client wants to negotiate all the NAWS information.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
option (Option): Not used.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.protocol.handshake_done()
|
self.protocol.handshake_done()
|
||||||
|
|
||||||
def negotiate_sizes(self, options):
|
def negotiate_sizes(self, options):
|
||||||
|
"""
|
||||||
|
Step through the NAWS handshake.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
option (list): The incoming NAWS options.
|
||||||
|
|
||||||
|
"""
|
||||||
if len(options) == 4:
|
if len(options) == 4:
|
||||||
# NAWS is negotiated with 16bit words
|
# NAWS is negotiated with 16bit words
|
||||||
width = options[0] + options[1]
|
width = options[0] + options[1]
|
||||||
|
|
|
||||||
|
|
@ -74,8 +74,9 @@ AMP_ENABLED = AMP_HOST and AMP_PORT and AMP_INTERFACE
|
||||||
_IDLE_TIMEOUT = settings.IDLE_TIMEOUT
|
_IDLE_TIMEOUT = settings.IDLE_TIMEOUT
|
||||||
def _portal_maintenance():
|
def _portal_maintenance():
|
||||||
"""
|
"""
|
||||||
The maintenance function handles repeated checks and updates
|
The maintenance function handles repeated checks and updates that
|
||||||
that the server needs to do. It is called every minute.
|
the server needs to do. It is called every minute.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# check for idle sessions
|
# check for idle sessions
|
||||||
now = time.time()
|
now = time.time()
|
||||||
|
|
@ -85,6 +86,7 @@ def _portal_maintenance():
|
||||||
if (now - sess.cmd_last) > _IDLE_TIMEOUT]:
|
if (now - sess.cmd_last) > _IDLE_TIMEOUT]:
|
||||||
session.data_out(reason)
|
session.data_out(reason)
|
||||||
PORTAL_SESSIONS.disconnect(session)
|
PORTAL_SESSIONS.disconnect(session)
|
||||||
|
|
||||||
if _IDLE_TIMEOUT > 0:
|
if _IDLE_TIMEOUT > 0:
|
||||||
# only start the maintenance task if we care about idling.
|
# only start the maintenance task if we care about idling.
|
||||||
_maintenance_task = LoopingCall(_portal_maintenance)
|
_maintenance_task = LoopingCall(_portal_maintenance)
|
||||||
|
|
@ -97,16 +99,18 @@ if _IDLE_TIMEOUT > 0:
|
||||||
class Portal(object):
|
class Portal(object):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
The main Portal server handler. This object sets up the database and
|
The main Portal server handler. This object sets up the database
|
||||||
tracks and interlinks all the twisted network services that make up
|
and tracks and interlinks all the twisted network services that
|
||||||
Portal.
|
make up Portal.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, application):
|
def __init__(self, application):
|
||||||
"""
|
"""
|
||||||
Setup the server.
|
Setup the server.
|
||||||
|
|
||||||
application - an instantiated Twisted application
|
Args:
|
||||||
|
application (Application): An instantiated Twisted application
|
||||||
|
|
||||||
"""
|
"""
|
||||||
sys.path.append('.')
|
sys.path.append('.')
|
||||||
|
|
@ -125,9 +129,13 @@ class Portal(object):
|
||||||
|
|
||||||
def set_restart_mode(self, mode=None):
|
def set_restart_mode(self, mode=None):
|
||||||
"""
|
"""
|
||||||
This manages the flag file that tells the runner if the server should
|
This manages the flag file that tells the runner if the server
|
||||||
be restarted or is shutting down. Valid modes are True/False and None.
|
should be restarted or is shutting down.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
mode (bool or None): Valid modes are True/False and None.
|
||||||
If mode is None, no change will be done to the flag file.
|
If mode is None, no change will be done to the flag file.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if mode is None:
|
if mode is None:
|
||||||
return
|
return
|
||||||
|
|
@ -139,15 +147,19 @@ class Portal(object):
|
||||||
"""
|
"""
|
||||||
Shuts down the server from inside it.
|
Shuts down the server from inside it.
|
||||||
|
|
||||||
restart - True/False sets the flags so the server will be
|
Args:
|
||||||
restarted or not. If None, the current flag setting
|
restart (bool or None, optional): True/False sets the
|
||||||
(set at initialization or previous runs) is used.
|
flags so the server will be restarted or not. If None, the
|
||||||
_reactor_stopping - this is set if server is already in the process of
|
current flag setting (set at initialization or previous
|
||||||
shutting down; in this case we don't need to stop it again.
|
runs) is used.
|
||||||
|
_reactor_stopping (bool, optional): This is set if server
|
||||||
|
is already in the process of shutting down; in this case
|
||||||
|
we don't need to stop it again.
|
||||||
|
|
||||||
Note that restarting (regardless of the setting) will not work
|
Note that restarting (regardless of the setting) will not work
|
||||||
if the Portal is currently running in daemon mode. In that
|
if the Portal is currently running in daemon mode. In that
|
||||||
case it always needs to be restarted manually.
|
case it always needs to be restarted manually.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if _reactor_stopping and hasattr(self, "shutdown_complete"):
|
if _reactor_stopping and hasattr(self, "shutdown_complete"):
|
||||||
# we get here due to us calling reactor.stop below. No need
|
# we get here due to us calling reactor.stop below. No need
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ class PortalSessionHandler(SessionHandler):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
"""
|
"""
|
||||||
Init the handler
|
Init the handler
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.portal = None
|
self.portal = None
|
||||||
self.sessions = {}
|
self.sessions = {}
|
||||||
|
|
@ -43,21 +44,26 @@ class PortalSessionHandler(SessionHandler):
|
||||||
|
|
||||||
def at_server_connection(self):
|
def at_server_connection(self):
|
||||||
"""
|
"""
|
||||||
Called when the Portal establishes connection with the
|
Called when the Portal establishes connection with the Server.
|
||||||
Server. At this point, the AMP connection is already
|
At this point, the AMP connection is already established.
|
||||||
established.
|
|
||||||
"""
|
"""
|
||||||
self.connection_time = time()
|
self.connection_time = time()
|
||||||
|
|
||||||
def connect(self, session):
|
def connect(self, session):
|
||||||
"""
|
"""
|
||||||
Called by protocol at first connect. This adds a not-yet
|
Called by protocol at first connect. This adds a not-yet
|
||||||
authenticated session using an ever-increasing counter for sessid.
|
authenticated session using an ever-increasing counter for
|
||||||
|
sessid.
|
||||||
|
|
||||||
We implement a throttling mechanism here to limit the speed at which
|
Args:
|
||||||
new connections are accepted - this is both a stop against DoS attacks
|
session (PortalSession): The Session connecting.
|
||||||
as well as helps using the Dummyrunner tester with a large number of
|
|
||||||
connector dummies.
|
Notes:
|
||||||
|
We implement a throttling mechanism here to limit the speed at
|
||||||
|
which new connections are accepted - this is both a stop
|
||||||
|
against DoS attacks as well as helps using the Dummyrunner
|
||||||
|
tester with a large number of connector dummies.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
session.server_connected = False
|
session.server_connected = False
|
||||||
|
|
@ -95,8 +101,12 @@ class PortalSessionHandler(SessionHandler):
|
||||||
def sync(self, session):
|
def sync(self, session):
|
||||||
"""
|
"""
|
||||||
Called by the protocol of an already connected session. This
|
Called by the protocol of an already connected session. This
|
||||||
can be used to sync the session info in a delayed manner,
|
can be used to sync the session info in a delayed manner, such
|
||||||
such as when negotiation and handshakes are delayed.
|
as when negotiation and handshakes are delayed.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
session (PortalSession): Session to sync.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if session.sessid and session.server_connected:
|
if session.sessid and session.server_connected:
|
||||||
# only use if session already has sessid and has already connected
|
# only use if session already has sessid and has already connected
|
||||||
|
|
@ -119,8 +129,12 @@ class PortalSessionHandler(SessionHandler):
|
||||||
|
|
||||||
def disconnect(self, session):
|
def disconnect(self, session):
|
||||||
"""
|
"""
|
||||||
Called from portal side when the connection is closed
|
Called from portal when the connection is closed from the
|
||||||
from the portal side.
|
portal side.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
session (PortalSession): Session to disconnect.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
sessid = session.sessid
|
sessid = session.sessid
|
||||||
self.portal.amp_protocol.call_remote_ServerAdmin(sessid,
|
self.portal.amp_protocol.call_remote_ServerAdmin(sessid,
|
||||||
|
|
@ -128,19 +142,25 @@ class PortalSessionHandler(SessionHandler):
|
||||||
|
|
||||||
def server_connect(self, protocol_path="", config=dict()):
|
def server_connect(self, protocol_path="", config=dict()):
|
||||||
"""
|
"""
|
||||||
Called by server to force the initialization of a new
|
Called by server to force the initialization of a new protocol
|
||||||
protocol instance. Server wants this instance to get
|
instance. Server wants this instance to get a unique sessid
|
||||||
a unique sessid and to be connected back as normal. This
|
and to be connected back as normal. This is used to initiate
|
||||||
is used to initiate irc/imc2/rss etc connections.
|
irc/imc2/rss etc connections.
|
||||||
|
|
||||||
protocol_path - full python path to the class factory
|
Args:
|
||||||
|
protocol_path (st): Full python path to the class factory
|
||||||
for the protocol used, eg
|
for the protocol used, eg
|
||||||
'evennia.server.portal.irc.IRCClientFactory'
|
'evennia.server.portal.irc.IRCClientFactory'
|
||||||
config - dictionary of configuration options, fed as **kwarg
|
config (dict): Dictionary of configuration options, fed as
|
||||||
to protocol class' __init__ method.
|
**kwarg to protocol class' __init__ method.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
RuntimeError: If The correct factory class is not found.
|
||||||
|
|
||||||
|
Notes:
|
||||||
The called protocol class must have a method start()
|
The called protocol class must have a method start()
|
||||||
that calls the portalsession.connect() as a normal protocol.
|
that calls the portalsession.connect() as a normal protocol.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
global _MOD_IMPORT
|
global _MOD_IMPORT
|
||||||
if not _MOD_IMPORT:
|
if not _MOD_IMPORT:
|
||||||
|
|
@ -154,7 +174,12 @@ class PortalSessionHandler(SessionHandler):
|
||||||
|
|
||||||
def server_disconnect(self, sessid, reason=""):
|
def server_disconnect(self, sessid, reason=""):
|
||||||
"""
|
"""
|
||||||
Called by server to force a disconnect by sessid
|
Called by server to force a disconnect by sessid.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
sessid (int): Session id to disconnect.
|
||||||
|
reason (str, optional): Motivation for disconect.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
session = self.sessions.get(sessid, None)
|
session = self.sessions.get(sessid, None)
|
||||||
if session:
|
if session:
|
||||||
|
|
@ -167,6 +192,10 @@ class PortalSessionHandler(SessionHandler):
|
||||||
def server_disconnect_all(self, reason=""):
|
def server_disconnect_all(self, reason=""):
|
||||||
"""
|
"""
|
||||||
Called by server when forcing a clean disconnect for everyone.
|
Called by server when forcing a clean disconnect for everyone.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
reason (str, optional): Motivation for disconnect.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
for session in self.sessions.values():
|
for session in self.sessions.values():
|
||||||
session.disconnect(reason)
|
session.disconnect(reason)
|
||||||
|
|
@ -175,21 +204,29 @@ class PortalSessionHandler(SessionHandler):
|
||||||
|
|
||||||
def server_logged_in(self, sessid, data):
|
def server_logged_in(self, sessid, data):
|
||||||
"""
|
"""
|
||||||
The server tells us that the session has been
|
The server tells us that the session has been authenticated.
|
||||||
authenticated. Updated it.
|
Update it. Called by the Server.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
sessid (int): Session id logging in.
|
||||||
|
data (dict): The session sync data.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
sess = self.get_session(sessid)
|
sess = self.get_session(sessid)
|
||||||
sess.load_sync_data(data)
|
sess.load_sync_data(data)
|
||||||
|
|
||||||
def server_session_sync(self, serversessions):
|
def server_session_sync(self, serversessions):
|
||||||
"""
|
"""
|
||||||
Server wants to save data to the portal, maybe because it's about
|
Server wants to save data to the portal, maybe because it's
|
||||||
to shut down. We don't overwrite any sessions here, just update
|
about to shut down. We don't overwrite any sessions here, just
|
||||||
them in-place and remove any that are out of sync (which should
|
update them in-place and remove any that are out of sync
|
||||||
normally not be the case)
|
(which should normally not be the case)
|
||||||
|
|
||||||
serversessions - dictionary {sessid:{property:value},...} describing
|
Args:
|
||||||
the properties to sync on all sessions
|
serversessions (dict): This is a dictionary
|
||||||
|
|
||||||
|
`{sessid:{property:value},...}` describing
|
||||||
|
the properties to sync on all sessions.
|
||||||
"""
|
"""
|
||||||
to_save = [sessid for sessid in serversessions if sessid in self.sessions]
|
to_save = [sessid for sessid in serversessions if sessid in self.sessions]
|
||||||
to_delete = [sessid for sessid in self.sessions if sessid not in to_save]
|
to_delete = [sessid for sessid in self.sessions if sessid not in to_save]
|
||||||
|
|
@ -203,6 +240,14 @@ class PortalSessionHandler(SessionHandler):
|
||||||
def count_loggedin(self, include_unloggedin=False):
|
def count_loggedin(self, include_unloggedin=False):
|
||||||
"""
|
"""
|
||||||
Count loggedin connections, alternatively count all connections.
|
Count loggedin connections, alternatively count all connections.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
include_unloggedin (bool): Also count sessions that have
|
||||||
|
not yet authenticated.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
count (int): Number of sessions.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return len(self.get_sessions(include_unloggedin=include_unloggedin))
|
return len(self.get_sessions(include_unloggedin=include_unloggedin))
|
||||||
|
|
||||||
|
|
@ -210,13 +255,24 @@ class PortalSessionHandler(SessionHandler):
|
||||||
"""
|
"""
|
||||||
Given a session id, retrieve the session (this is primarily
|
Given a session id, retrieve the session (this is primarily
|
||||||
intended to be called by web clients)
|
intended to be called by web clients)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
suid (int): Session id.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
session (list): The matching session, if found.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return [sess for sess in self.get_sessions(include_unloggedin=True)
|
return [sess for sess in self.get_sessions(include_unloggedin=True)
|
||||||
if hasattr(sess, 'suid') and sess.suid == suid]
|
if hasattr(sess, 'suid') and sess.suid == suid]
|
||||||
|
|
||||||
def announce_all(self, message):
|
def announce_all(self, message):
|
||||||
"""
|
"""
|
||||||
Send message to all connection sessions
|
Send message to all connected sessions.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
message (str): Message to relay.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
for session in self.sessions.values():
|
for session in self.sessions.values():
|
||||||
session.data_out(message)
|
session.data_out(message)
|
||||||
|
|
@ -226,7 +282,8 @@ class PortalSessionHandler(SessionHandler):
|
||||||
Helper method for each session to use to parse oob structures
|
Helper method for each session to use to parse oob structures
|
||||||
(The 'oob' kwarg of the msg() method).
|
(The 'oob' kwarg of the msg() method).
|
||||||
|
|
||||||
Args: oobstruct (str or iterable): A structure representing
|
Args:
|
||||||
|
oobstruct (str or iterable): A structure representing
|
||||||
an oob command on one of the following forms:
|
an oob command on one of the following forms:
|
||||||
- "cmdname"
|
- "cmdname"
|
||||||
- "cmdname", "cmdname"
|
- "cmdname", "cmdname"
|
||||||
|
|
@ -242,6 +299,7 @@ class PortalSessionHandler(SessionHandler):
|
||||||
structure (tuple): A generic OOB structure on the form
|
structure (tuple): A generic OOB structure on the form
|
||||||
`((cmdname, (args,), {kwargs}), ...)`, where the two last
|
`((cmdname, (args,), {kwargs}), ...)`, where the two last
|
||||||
args and kwargs may be empty
|
args and kwargs may be empty
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def _parse(oobstruct):
|
def _parse(oobstruct):
|
||||||
slen = len(oobstruct)
|
slen = len(oobstruct)
|
||||||
|
|
@ -287,8 +345,17 @@ class PortalSessionHandler(SessionHandler):
|
||||||
def data_in(self, session, text="", **kwargs):
|
def data_in(self, session, text="", **kwargs):
|
||||||
"""
|
"""
|
||||||
Called by portal sessions for relaying data coming
|
Called by portal sessions for relaying data coming
|
||||||
in from the protocol to the server. data is
|
in from the protocol to the server.
|
||||||
serialized before passed on.
|
|
||||||
|
Args:
|
||||||
|
session (PortalSession): Session receiving data.
|
||||||
|
|
||||||
|
Kwargs:
|
||||||
|
text (str): Text from protocol.
|
||||||
|
kwargs (any): Other data from protocol.
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
Data is serialized before passed on.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# data throttle (anti DoS measure)
|
# data throttle (anti DoS measure)
|
||||||
|
|
@ -307,6 +374,14 @@ class PortalSessionHandler(SessionHandler):
|
||||||
Called by server for having the portal relay messages and data
|
Called by server for having the portal relay messages and data
|
||||||
to the correct session protocol. We also convert oob input to
|
to the correct session protocol. We also convert oob input to
|
||||||
a generic form here.
|
a generic form here.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
sessid (int): Session id sending data.
|
||||||
|
|
||||||
|
Kwargs:
|
||||||
|
text (str): Text from protocol.
|
||||||
|
kwargs (any): Other data from protocol.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
session = self.sessions.get(sessid, None)
|
session = self.sessions.get(sessid, None)
|
||||||
if session:
|
if session:
|
||||||
|
|
|
||||||
|
|
@ -22,16 +22,29 @@ if RSS_ENABLED:
|
||||||
|
|
||||||
class RSSReader(Session):
|
class RSSReader(Session):
|
||||||
"""
|
"""
|
||||||
A simple RSS reader using universal feedparser
|
A simple RSS reader using the feedparser module.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def __init__(self, factory, url, rate):
|
def __init__(self, factory, url, rate):
|
||||||
|
"""
|
||||||
|
Initialize the reader.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
factory (RSSFactory): The protocol factory.
|
||||||
|
url (str): The RSS url.
|
||||||
|
rate (int): The seconds between RSS lookups.
|
||||||
|
|
||||||
|
"""
|
||||||
self.url = url
|
self.url = url
|
||||||
self.rate = rate
|
self.rate = rate
|
||||||
self.factory = factory
|
self.factory = factory
|
||||||
self.old_entries = {}
|
self.old_entries = {}
|
||||||
|
|
||||||
def get_new(self):
|
def get_new(self):
|
||||||
"""Returns list of new items."""
|
"""
|
||||||
|
Returns list of new items.
|
||||||
|
|
||||||
|
"""
|
||||||
feed = feedparser.parse(self.url)
|
feed = feedparser.parse(self.url)
|
||||||
new_entries = []
|
new_entries = []
|
||||||
for entry in feed['entries']:
|
for entry in feed['entries']:
|
||||||
|
|
@ -42,20 +55,41 @@ class RSSReader(Session):
|
||||||
return new_entries
|
return new_entries
|
||||||
|
|
||||||
def disconnect(self, reason=None):
|
def disconnect(self, reason=None):
|
||||||
"Disconnect from feed"
|
"""
|
||||||
|
Disconnect from feed.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
reason (str, optional): Motivation for the disconnect.
|
||||||
|
|
||||||
|
"""
|
||||||
if self.factory.task and self.factory.task.running:
|
if self.factory.task and self.factory.task.running:
|
||||||
self.factory.task.stop()
|
self.factory.task.stop()
|
||||||
self.sessionhandler.disconnect(self)
|
self.sessionhandler.disconnect(self)
|
||||||
|
|
||||||
def _callback(self, new_entries, init):
|
def _callback(self, new_entries, init):
|
||||||
"Called when RSS returns (threaded)"
|
"""
|
||||||
|
Called when RSS returns.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
new_entries (list): List of new RSS entries since last.
|
||||||
|
init (bool): If this is a startup operation (at which
|
||||||
|
point all entries are considered new).
|
||||||
|
|
||||||
|
"""
|
||||||
if not init:
|
if not init:
|
||||||
# for initialization we just ignore old entries
|
# for initialization we just ignore old entries
|
||||||
for entry in reversed(new_entries):
|
for entry in reversed(new_entries):
|
||||||
self.data_in("bot_data_in " + entry)
|
self.data_in("bot_data_in " + entry)
|
||||||
|
|
||||||
def data_in(self, text=None, **kwargs):
|
def data_in(self, text=None, **kwargs):
|
||||||
"Data RSS -> Server"
|
"""
|
||||||
|
Data RSS -> Evennia.
|
||||||
|
|
||||||
|
Kwargs:
|
||||||
|
text (str): Incoming text
|
||||||
|
kwargs (any): Options from protocol.
|
||||||
|
|
||||||
|
"""
|
||||||
self.sessionhandler.data_in(self, text=text, **kwargs)
|
self.sessionhandler.data_in(self, text=text, **kwargs)
|
||||||
|
|
||||||
def _errback(self, fail):
|
def _errback(self, fail):
|
||||||
|
|
@ -63,16 +97,36 @@ class RSSReader(Session):
|
||||||
logger.log_errmsg("RSS feed error: %s" % fail.value)
|
logger.log_errmsg("RSS feed error: %s" % fail.value)
|
||||||
|
|
||||||
def update(self, init=False):
|
def update(self, init=False):
|
||||||
"Request feed"
|
"""
|
||||||
|
Request the latest version of feed.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
init (bool, optional): If this is an initialization call
|
||||||
|
or not (during init, all entries are conidered new).
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
This call is done in a separate thread to avoid blocking
|
||||||
|
on slow connections.
|
||||||
|
|
||||||
|
"""
|
||||||
return threads.deferToThread(self.get_new).addCallback(self._callback, init).addErrback(self._errback)
|
return threads.deferToThread(self.get_new).addCallback(self._callback, init).addErrback(self._errback)
|
||||||
|
|
||||||
class RSSBotFactory(object):
|
class RSSBotFactory(object):
|
||||||
"""
|
"""
|
||||||
Initializes new bots
|
Initializes new bots.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, sessionhandler, uid=None, url=None, rate=None):
|
def __init__(self, sessionhandler, uid=None, url=None, rate=None):
|
||||||
"Initialize"
|
"""
|
||||||
|
Initialize the bot.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
sessionhandler (PortalSessionHandler): The main sessionhandler object.
|
||||||
|
uid (int): User id for the bot.
|
||||||
|
url (str): The RSS URL.
|
||||||
|
rate (int): How often for the RSS to request the latest RSS entries.
|
||||||
|
|
||||||
|
"""
|
||||||
self.sessionhandler = sessionhandler
|
self.sessionhandler = sessionhandler
|
||||||
self.url = url
|
self.url = url
|
||||||
self.rate = rate
|
self.rate = rate
|
||||||
|
|
@ -82,7 +136,7 @@ class RSSBotFactory(object):
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
"""
|
"""
|
||||||
Called by portalsessionhandler
|
Called by portalsessionhandler. Starts te bot.
|
||||||
"""
|
"""
|
||||||
def errback(fail):
|
def errback(fail):
|
||||||
logger.log_errmsg(fail.value)
|
logger.log_errmsg(fail.value)
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,8 @@
|
||||||
This module implements the ssh (Secure SHell) protocol for encrypted
|
This module implements the ssh (Secure SHell) protocol for encrypted
|
||||||
connections.
|
connections.
|
||||||
|
|
||||||
This depends on a generic session module that implements
|
This depends on a generic session module that implements the actual
|
||||||
the actual login procedure of the game, tracks
|
login procedure of the game, tracks sessions etc.
|
||||||
sessions etc.
|
|
||||||
|
|
||||||
Using standard ssh client,
|
Using standard ssh client,
|
||||||
|
|
||||||
|
|
@ -41,11 +40,16 @@ class SshProtocol(Manhole, session.Session):
|
||||||
Each player connecting over ssh gets this protocol assigned to
|
Each player connecting over ssh gets this protocol assigned to
|
||||||
them. All communication between game and player goes through
|
them. All communication between game and player goes through
|
||||||
here.
|
here.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def __init__(self, starttuple):
|
def __init__(self, starttuple):
|
||||||
"""
|
"""
|
||||||
For setting up the player. If player is not None then we'll
|
For setting up the player. If player is not None then we'll
|
||||||
login automatically.
|
login automatically.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
starttuple (tuple): A (player, factory) tuple.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.authenticated_player = starttuple[0]
|
self.authenticated_player = starttuple[0]
|
||||||
# obs must not be called self.factory, that gets overwritten!
|
# obs must not be called self.factory, that gets overwritten!
|
||||||
|
|
@ -54,6 +58,11 @@ class SshProtocol(Manhole, session.Session):
|
||||||
def terminalSize(self, width, height):
|
def terminalSize(self, width, height):
|
||||||
"""
|
"""
|
||||||
Initialize the terminal and connect to the new session.
|
Initialize the terminal and connect to the new session.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
width (int): Width of terminal.
|
||||||
|
height (int): Height of terminal.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# Clear the previous input line, redraw it at the new
|
# Clear the previous input line, redraw it at the new
|
||||||
# cursor position
|
# cursor position
|
||||||
|
|
@ -74,8 +83,8 @@ class SshProtocol(Manhole, session.Session):
|
||||||
|
|
||||||
def connectionMade(self):
|
def connectionMade(self):
|
||||||
"""
|
"""
|
||||||
This is called when the connection is first
|
This is called when the connection is first established.
|
||||||
established.
|
|
||||||
"""
|
"""
|
||||||
recvline.HistoricRecvLine.connectionMade(self)
|
recvline.HistoricRecvLine.connectionMade(self)
|
||||||
self.keyHandlers[CTRL_C] = self.handle_INT
|
self.keyHandlers[CTRL_C] = self.handle_INT
|
||||||
|
|
@ -87,8 +96,9 @@ class SshProtocol(Manhole, session.Session):
|
||||||
|
|
||||||
def handle_INT(self):
|
def handle_INT(self):
|
||||||
"""
|
"""
|
||||||
Handle ^C as an interrupt keystroke by resetting the current input
|
Handle ^C as an interrupt keystroke by resetting the current
|
||||||
variables to their initial state.
|
input variables to their initial state.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.lineBuffer = []
|
self.lineBuffer = []
|
||||||
self.lineBufferIndex = 0
|
self.lineBufferIndex = 0
|
||||||
|
|
@ -100,6 +110,7 @@ class SshProtocol(Manhole, session.Session):
|
||||||
def handle_EOF(self):
|
def handle_EOF(self):
|
||||||
"""
|
"""
|
||||||
Handles EOF generally used to exit.
|
Handles EOF generally used to exit.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if self.lineBuffer:
|
if self.lineBuffer:
|
||||||
self.terminal.write('\a')
|
self.terminal.write('\a')
|
||||||
|
|
@ -110,6 +121,7 @@ class SshProtocol(Manhole, session.Session):
|
||||||
"""
|
"""
|
||||||
Handle a 'form feed' byte - generally used to request a screen
|
Handle a 'form feed' byte - generally used to request a screen
|
||||||
refresh/redraw.
|
refresh/redraw.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.terminal.eraseDisplay()
|
self.terminal.eraseDisplay()
|
||||||
self.terminal.cursorHome()
|
self.terminal.cursorHome()
|
||||||
|
|
@ -117,14 +129,18 @@ class SshProtocol(Manhole, session.Session):
|
||||||
def handle_QUIT(self):
|
def handle_QUIT(self):
|
||||||
"""
|
"""
|
||||||
Quit, end, and lose the connection.
|
Quit, end, and lose the connection.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.terminal.loseConnection()
|
self.terminal.loseConnection()
|
||||||
|
|
||||||
def connectionLost(self, reason=None):
|
def connectionLost(self, reason=None):
|
||||||
"""
|
"""
|
||||||
This is executed when the connection is lost for
|
This is executed when the connection is lost for whatever
|
||||||
whatever reason. It can also be called directly,
|
reason. It can also be called directly, from the disconnect
|
||||||
from the disconnect method.
|
method.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
reason (str): Motivation for loosing connection.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
insults.TerminalProtocol.connectionLost(self, reason)
|
insults.TerminalProtocol.connectionLost(self, reason)
|
||||||
|
|
@ -133,25 +149,35 @@ class SshProtocol(Manhole, session.Session):
|
||||||
|
|
||||||
def getClientAddress(self):
|
def getClientAddress(self):
|
||||||
"""
|
"""
|
||||||
Returns the client's address and port in a tuple. For example
|
Get client address.
|
||||||
('127.0.0.1', 41917)
|
|
||||||
|
Returns:
|
||||||
|
address_and_port (tuple): The client's address and port in
|
||||||
|
a tuple. For example `('127.0.0.1', 41917)`.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return self.terminal.transport.getPeer()
|
return self.terminal.transport.getPeer()
|
||||||
|
|
||||||
def lineReceived(self, string):
|
def lineReceived(self, string):
|
||||||
"""
|
"""
|
||||||
Communication Player -> Evennia. Any line return indicates a
|
Communication User -> Evennia. Any line return indicates a
|
||||||
command for the purpose of the MUD. So we take the user input
|
command for the purpose of the MUD. So we take the user input
|
||||||
and pass it on to the game engine.
|
and pass it on to the game engine.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
string (str): Input text.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.sessionhandler.data_in(self, string)
|
self.sessionhandler.data_in(self, string)
|
||||||
|
|
||||||
def lineSend(self, string):
|
def lineSend(self, string):
|
||||||
"""
|
"""
|
||||||
Communication Evennia -> Player
|
Communication Evennia -> User. Any string sent should
|
||||||
Any string sent should already have been
|
already have been properly formatted and processed before
|
||||||
properly formatted and processed
|
reaching this point.
|
||||||
before reaching this point.
|
|
||||||
|
Args:
|
||||||
|
string (str): Output text.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
for line in string.split('\n'):
|
for line in string.split('\n'):
|
||||||
|
|
@ -163,7 +189,11 @@ class SshProtocol(Manhole, session.Session):
|
||||||
|
|
||||||
def disconnect(self, reason="Connection closed. Goodbye for now."):
|
def disconnect(self, reason="Connection closed. Goodbye for now."):
|
||||||
"""
|
"""
|
||||||
Disconnect from server
|
Disconnect from server.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
reason (str): Motivation for disconnect.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if reason:
|
if reason:
|
||||||
self.data_out(reason)
|
self.data_out(reason)
|
||||||
|
|
@ -171,12 +201,13 @@ class SshProtocol(Manhole, session.Session):
|
||||||
|
|
||||||
def data_out(self, text=None, **kwargs):
|
def data_out(self, text=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
Data Evennia -> Player access hook. 'data' argument is a dict
|
Data Evennia -> User access hook. 'data' argument is a dict
|
||||||
parsed for string settings.
|
parsed for string settings.
|
||||||
|
|
||||||
ssh flags:
|
Kwargs:
|
||||||
raw=True - leave all ansi markup and tokens unparsed
|
text (str): Text to send.
|
||||||
nomarkup=True - remove all ansi markup
|
raw (bool): Leave all ansi markup and tokens unparsed
|
||||||
|
nomarkup (bool): Remove all ansi markup.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
|
|
@ -199,6 +230,10 @@ class ExtraInfoAuthServer(SSHUserAuthServer):
|
||||||
|
|
||||||
Used mostly for setting up the transport so we can query
|
Used mostly for setting up the transport so we can query
|
||||||
username and password later.
|
username and password later.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
packet (Packet): Auth packet.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
password = common.getNS(packet[1:])[0]
|
password = common.getNS(packet[1:])[0]
|
||||||
c = credentials.UsernamePassword(self.user, password)
|
c = credentials.UsernamePassword(self.user, password)
|
||||||
|
|
@ -212,15 +247,26 @@ class PlayerDBPasswordChecker(object):
|
||||||
Checks the django db for the correct credentials for
|
Checks the django db for the correct credentials for
|
||||||
username/password otherwise it returns the player or None which is
|
username/password otherwise it returns the player or None which is
|
||||||
useful for the Realm.
|
useful for the Realm.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
credentialInterfaces = (credentials.IUsernamePassword,)
|
credentialInterfaces = (credentials.IUsernamePassword,)
|
||||||
|
|
||||||
def __init__(self, factory):
|
def __init__(self, factory):
|
||||||
|
"""
|
||||||
|
Initialize the factory.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
factory (SSHFactory): Checker factory.
|
||||||
|
|
||||||
|
"""
|
||||||
self.factory = factory
|
self.factory = factory
|
||||||
super(PlayerDBPasswordChecker, self).__init__()
|
super(PlayerDBPasswordChecker, self).__init__()
|
||||||
|
|
||||||
def requestAvatarId(self, c):
|
def requestAvatarId(self, c):
|
||||||
"Generic credentials"
|
"""
|
||||||
|
Generic credentials.
|
||||||
|
|
||||||
|
"""
|
||||||
up = credentials.IUsernamePassword(c, None)
|
up = credentials.IUsernamePassword(c, None)
|
||||||
username = up.username
|
username = up.username
|
||||||
password = up.password
|
password = up.password
|
||||||
|
|
@ -235,6 +281,7 @@ class PassAvatarIdTerminalRealm(TerminalRealm):
|
||||||
"""
|
"""
|
||||||
Returns an avatar that passes the avatarId through to the
|
Returns an avatar that passes the avatarId through to the
|
||||||
protocol. This is probably not the best way to do it.
|
protocol. This is probably not the best way to do it.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def _getAvatar(self, avatarId):
|
def _getAvatar(self, avatarId):
|
||||||
|
|
@ -255,6 +302,7 @@ class TerminalSessionTransport_getPeer:
|
||||||
"""
|
"""
|
||||||
Taken from twisted's TerminalSessionTransport which doesn't
|
Taken from twisted's TerminalSessionTransport which doesn't
|
||||||
provide getPeer to the transport. This one does.
|
provide getPeer to the transport. This one does.
|
||||||
|
j
|
||||||
"""
|
"""
|
||||||
def __init__(self, proto, chainedProtocol, avatar, width, height):
|
def __init__(self, proto, chainedProtocol, avatar, width, height):
|
||||||
self.proto = proto
|
self.proto = proto
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
|
||||||
def connectionMade(self):
|
def connectionMade(self):
|
||||||
"""
|
"""
|
||||||
This is called when the connection is first established.
|
This is called when the connection is first established.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# initialize the session
|
# initialize the session
|
||||||
self.iaw_mode = False
|
self.iaw_mode = False
|
||||||
|
|
@ -88,8 +89,14 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
|
||||||
def enableRemote(self, option):
|
def enableRemote(self, option):
|
||||||
"""
|
"""
|
||||||
This sets up the remote-activated options we allow for this protocol.
|
This sets up the remote-activated options we allow for this protocol.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
option (char): The telnet option to enable.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
enable (bool): If this option should be enabled.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
pass
|
|
||||||
return (option == LINEMODE or
|
return (option == LINEMODE or
|
||||||
option == ttype.TTYPE or
|
option == ttype.TTYPE or
|
||||||
option == naws.NAWS or
|
option == naws.NAWS or
|
||||||
|
|
@ -99,12 +106,23 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
|
||||||
def enableLocal(self, option):
|
def enableLocal(self, option):
|
||||||
"""
|
"""
|
||||||
Call to allow the activation of options for this protocol
|
Call to allow the activation of options for this protocol
|
||||||
|
|
||||||
|
Args:
|
||||||
|
option (char): The telnet option to enable locally.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
enable (bool): If this option should be enabled.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return (option == MCCP or option==ECHO)
|
return (option == MCCP or option==ECHO)
|
||||||
|
|
||||||
def disableLocal(self, option):
|
def disableLocal(self, option):
|
||||||
"""
|
"""
|
||||||
Disable a given option
|
Disable a given option locally.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
option (char): The telnet option to disable locally.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if option == ECHO:
|
if option == ECHO:
|
||||||
return True
|
return True
|
||||||
|
|
@ -116,23 +134,33 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
|
||||||
|
|
||||||
def connectionLost(self, reason):
|
def connectionLost(self, reason):
|
||||||
"""
|
"""
|
||||||
this is executed when the connection is lost for
|
this is executed when the connection is lost for whatever
|
||||||
whatever reason. it can also be called directly, from
|
reason. it can also be called directly, from the disconnect
|
||||||
the disconnect method
|
method
|
||||||
|
|
||||||
|
Args:
|
||||||
|
reason (str): Motivation for losing connection.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.sessionhandler.disconnect(self)
|
self.sessionhandler.disconnect(self)
|
||||||
self.transport.loseConnection()
|
self.transport.loseConnection()
|
||||||
|
|
||||||
def dataReceived(self, data):
|
def dataReceived(self, data):
|
||||||
"""
|
"""
|
||||||
|
Handle incoming data over the wire.
|
||||||
|
|
||||||
This method will split the incoming data depending on if it
|
This method will split the incoming data depending on if it
|
||||||
starts with IAC (a telnet command) or not. All other data will
|
starts with IAC (a telnet command) or not. All other data will
|
||||||
be handled in line mode. Some clients also sends an erroneous
|
be handled in line mode. Some clients also sends an erroneous
|
||||||
line break after IAC, which we must watch out for.
|
line break after IAC, which we must watch out for.
|
||||||
|
|
||||||
OOB protocols (MSDP etc) already intercept subnegotiations
|
Args:
|
||||||
on their own, never entering this method. They will relay
|
data (str): Incoming data.
|
||||||
their parsed data directly to self.data_in.
|
|
||||||
|
Notes:
|
||||||
|
OOB protocols (MSDP etc) already intercept subnegotiations on
|
||||||
|
their own, never entering this method. They will relay their
|
||||||
|
parsed data directly to self.data_in.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
@ -180,7 +208,13 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
|
||||||
super(TelnetProtocol, self)._write(mccp_compress(self, data))
|
super(TelnetProtocol, self)._write(mccp_compress(self, data))
|
||||||
|
|
||||||
def sendLine(self, line):
|
def sendLine(self, line):
|
||||||
"hook overloading the one used by linereceiver"
|
"""
|
||||||
|
Hook overloading the one used by linereceiver.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
line (str): Line to send.
|
||||||
|
|
||||||
|
"""
|
||||||
#print "sendLine (%s):\n%s" % (self.state, line)
|
#print "sendLine (%s):\n%s" % (self.state, line)
|
||||||
#escape IAC in line mode, and correctly add \r\n
|
#escape IAC in line mode, and correctly add \r\n
|
||||||
line += self.delimiter
|
line += self.delimiter
|
||||||
|
|
@ -191,6 +225,10 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
|
||||||
"""
|
"""
|
||||||
Telnet method called when data is coming in over the telnet
|
Telnet method called when data is coming in over the telnet
|
||||||
connection. We pass it on to the game engine directly.
|
connection. We pass it on to the game engine directly.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
string (str): Incoming data.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.data_in(text=string)
|
self.data_in(text=string)
|
||||||
|
|
||||||
|
|
@ -200,6 +238,10 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
|
||||||
"""
|
"""
|
||||||
generic hook for the engine to call in order to
|
generic hook for the engine to call in order to
|
||||||
disconnect this protocol.
|
disconnect this protocol.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
reason (str): Reason for disconnecting.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if reason:
|
if reason:
|
||||||
self.data_out(reason)
|
self.data_out(reason)
|
||||||
|
|
@ -207,36 +249,46 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
|
||||||
|
|
||||||
def data_in(self, text=None, **kwargs):
|
def data_in(self, text=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
Data Telnet -> Server
|
Data User -> Evennia
|
||||||
|
|
||||||
|
Kwargs:
|
||||||
|
text (str): Incoming text.
|
||||||
|
kwargs (any): Options from the protocol.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.sessionhandler.data_in(self, text=text, **kwargs)
|
self.sessionhandler.data_in(self, text=text, **kwargs)
|
||||||
|
|
||||||
def data_out(self, text=None, **kwargs):
|
def data_out(self, text=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
Data Evennia -> Player.
|
Data Evennia -> User. A generic hook method for engine to call
|
||||||
generic hook method for engine to call in order to send data
|
in order to send data through the telnet connection.
|
||||||
through the telnet connection.
|
|
||||||
|
|
||||||
valid telnet kwargs:
|
Kwargs:
|
||||||
oob=[(cmdname,args,kwargs), ...] - supply an Out-of-Band instruction.
|
text (str): Text to send.
|
||||||
xterm256=True/False - enforce xterm256 setting. If not
|
oob (list): `[(cmdname,args,kwargs), ...]`, supply an
|
||||||
given, ttype result is used. If
|
Out-of-Band instruction.
|
||||||
client does not suport xterm256, the
|
xterm256 (bool): Enforce xterm256 setting. If not given,
|
||||||
ansi fallback will be used
|
ttype result is used. If client does not suport xterm256,
|
||||||
mxp=True/False - enforce mxp setting. If not given, enables if we
|
the ansi fallback will be used
|
||||||
detected client support for it
|
mxp (bool): Enforce mxp setting. If not given, enables if
|
||||||
ansi=True/False - enforce ansi setting. If not given,
|
we detected client support for it
|
||||||
ttype result is used.
|
ansi (bool): Enforce ansi setting. If not given, ttype
|
||||||
nomarkup=True - strip all ansi markup (this is the same as
|
result is used.
|
||||||
xterm256=False, ansi=False)
|
nomarkup (bool): If True, strip all ansi markup (this is
|
||||||
raw=True - pass string through without any ansi
|
the same as ´xterm256=False, ansi=False`)
|
||||||
processing (i.e. include Evennia ansi markers but do
|
raw (bool):Pass string through without any ansi processing
|
||||||
not convert them into ansi tokens)
|
(i.e. include Evennia ansi markers but do not convert them
|
||||||
prompt=<string> - supply a prompt text which gets sent without a
|
into ansi tokens)
|
||||||
newline added to the end
|
prompt (str): Supply a prompt text which gets sent without
|
||||||
echo=True/False
|
a newline added to the end.
|
||||||
The telnet ttype negotiation flags, if any, are used if no kwargs
|
echo (str): Turn on/off line echo on the client, if the
|
||||||
|
client supports it (e.g. for password input). Remember
|
||||||
|
that you must manually activate it again later.
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
The telnet TTYPE negotiation flags, if any, are used if no kwargs
|
||||||
are given.
|
are given.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
text = utils.to_str(text if text else "", encoding=self.encoding)
|
text = utils.to_str(text if text else "", encoding=self.encoding)
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,8 @@
|
||||||
|
|
||||||
Telnet OOB (Out of band communication)
|
Telnet OOB (Out of band communication)
|
||||||
|
|
||||||
This implements the following telnet oob protocols:
|
This implements the following telnet oob protocols: MSDP (Mud Server
|
||||||
MSDP (Mud Server Data Protocol)
|
Data Protocol) GMCP (Generic Mud Communication Protocol)
|
||||||
GMCP (Generic Mud Communication Protocol)
|
|
||||||
|
|
||||||
This implements the MSDP protocol as per
|
This implements the MSDP protocol as per
|
||||||
http://tintin.sourceforge.net/msdp/ and the GMCP protocol as per
|
http://tintin.sourceforge.net/msdp/ and the GMCP protocol as per
|
||||||
|
|
@ -14,9 +13,9 @@ Following the lead of KaVir's protocol snippet, we first check if
|
||||||
client supports MSDP and if not, we fallback to GMCP with a MSDP
|
client supports MSDP and if not, we fallback to GMCP with a MSDP
|
||||||
header where applicable.
|
header where applicable.
|
||||||
|
|
||||||
OOB manages out-of-band
|
OOB manages out-of-band communication between the client and server,
|
||||||
communication between the client and server, for updating health bars
|
for updating health bars etc. See also GMCP which is another standard
|
||||||
etc. See also GMCP which is another standard doing the same thing.
|
doing the same thing.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
import re
|
import re
|
||||||
|
|
@ -63,9 +62,12 @@ class TelnetOOB(object):
|
||||||
|
|
||||||
def __init__(self, protocol):
|
def __init__(self, protocol):
|
||||||
"""
|
"""
|
||||||
Initiates by storing the protocol
|
Initiates by storing the protocol on itself and trying to
|
||||||
on itself and trying to determine
|
determine if the client supports MSDP.
|
||||||
if the client supports MSDP.
|
|
||||||
|
Args:
|
||||||
|
protocol (Protocol): The active protocol.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.protocol = protocol
|
self.protocol = protocol
|
||||||
self.protocol.protocol_flags['OOB'] = False
|
self.protocol.protocol_flags['OOB'] = False
|
||||||
|
|
@ -80,23 +82,46 @@ class TelnetOOB(object):
|
||||||
self.oob_reported = {}
|
self.oob_reported = {}
|
||||||
|
|
||||||
def no_msdp(self, option):
|
def no_msdp(self, option):
|
||||||
"No msdp supported or wanted"
|
"""
|
||||||
|
Client reports No msdp supported or wanted.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
options (Option): Not used.
|
||||||
|
|
||||||
|
"""
|
||||||
# no msdp, check GMCP
|
# no msdp, check GMCP
|
||||||
self.protocol.handshake_done()
|
self.protocol.handshake_done()
|
||||||
|
|
||||||
def do_msdp(self, option):
|
def do_msdp(self, option):
|
||||||
"MSDP supported by client"
|
"""
|
||||||
|
Client reports that it supports msdp.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
option (Option): Not used.
|
||||||
|
|
||||||
|
"""
|
||||||
self.MSDP = True
|
self.MSDP = True
|
||||||
self.protocol.protocol_flags['OOB'] = True
|
self.protocol.protocol_flags['OOB'] = True
|
||||||
self.protocol.handshake_done()
|
self.protocol.handshake_done()
|
||||||
|
|
||||||
def no_gmcp(self, option):
|
def no_gmcp(self, option):
|
||||||
"Neither MSDP nor GMCP supported"
|
"""
|
||||||
|
If this is reached, it means neither MSDP nor GMCP is
|
||||||
|
supported.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
option (Option): Not used.
|
||||||
|
|
||||||
|
"""
|
||||||
self.protocol.handshake_done()
|
self.protocol.handshake_done()
|
||||||
|
|
||||||
def do_gmcp(self, option):
|
def do_gmcp(self, option):
|
||||||
"""
|
"""
|
||||||
Called when client confirms that it can do MSDP or GMCP.
|
Called when client confirms that it can do MSDP or GMCP.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
option (Option): Not used.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.GMCP = True
|
self.GMCP = True
|
||||||
self.protocol.protocol_flags['OOB'] = True
|
self.protocol.protocol_flags['OOB'] = True
|
||||||
|
|
@ -106,15 +131,17 @@ class TelnetOOB(object):
|
||||||
|
|
||||||
def encode_msdp(self, cmdname, *args, **kwargs):
|
def encode_msdp(self, cmdname, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
handle return data from cmdname by converting it to
|
handle return data from cmdname by converting it to a proper
|
||||||
a proper msdp structure. These are the combinations we
|
msdp structure. These are the combinations we support:
|
||||||
support:
|
|
||||||
|
|
||||||
|
Args:
|
||||||
|
cmdname (str): Name of OOB command.
|
||||||
|
args, kwargs (any): Arguments to OOB command.
|
||||||
|
|
||||||
|
Examples:
|
||||||
cmdname string -> cmdname string
|
cmdname string -> cmdname string
|
||||||
cmdname *args -> cmdname MSDP_ARRAY
|
cmdname *args -> cmdname MSDP_ARRAY
|
||||||
cmdname **kwargs -> cmdname MSDP_TABLE
|
cmdname **kwargs -> cmdname MSDP_TABLE
|
||||||
|
|
||||||
# send 'raw' data structures
|
|
||||||
MSDP_ARRAY *args -> MSDP_ARRAY
|
MSDP_ARRAY *args -> MSDP_ARRAY
|
||||||
MSDP_TABLE **kwargs -> MSDP_TABLE
|
MSDP_TABLE **kwargs -> MSDP_TABLE
|
||||||
|
|
||||||
|
|
@ -138,11 +165,18 @@ class TelnetOOB(object):
|
||||||
|
|
||||||
def encode_gmcp(self, cmdname, *args, **kwargs):
|
def encode_gmcp(self, cmdname, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
|
Encode GMCP messages.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
cmdname (str): GMCP OOB command name.
|
||||||
|
args, kwargs (any): Arguments to OOB command.
|
||||||
|
|
||||||
|
Notes:
|
||||||
Gmcp messages are on one of the following outgoing forms:
|
Gmcp messages are on one of the following outgoing forms:
|
||||||
|
|
||||||
cmdname string -> cmdname string
|
- cmdname string -> cmdname string
|
||||||
cmdname *args -> cmdname [arg, arg, arg, ...]
|
- cmdname *args -> cmdname [arg, arg, arg, ...]
|
||||||
cmdname **kwargs -> cmdname {key:arg, key:arg, ...}
|
- cmdname **kwargs -> cmdname {key:arg, key:arg, ...}
|
||||||
|
|
||||||
cmdname is generally recommended to be a string on the form
|
cmdname is generally recommended to be a string on the form
|
||||||
Module.Submodule.Function
|
Module.Submodule.Function
|
||||||
|
|
@ -165,8 +199,12 @@ class TelnetOOB(object):
|
||||||
|
|
||||||
def decode_msdp(self, data):
|
def decode_msdp(self, data):
|
||||||
"""
|
"""
|
||||||
Decodes incoming MSDP data
|
Decodes incoming MSDP data.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
data (str or list): MSDP data.
|
||||||
|
|
||||||
|
Notes:
|
||||||
cmdname var --> cmdname arg
|
cmdname var --> cmdname arg
|
||||||
cmdname array --> cmdname *args
|
cmdname array --> cmdname *args
|
||||||
cmdname table --> cmdname **kwargs
|
cmdname table --> cmdname **kwargs
|
||||||
|
|
@ -211,8 +249,12 @@ class TelnetOOB(object):
|
||||||
|
|
||||||
def decode_gmcp(self, data):
|
def decode_gmcp(self, data):
|
||||||
"""
|
"""
|
||||||
Decodes incoming GMCP data on the form 'varname <structure>'
|
Decodes incoming GMCP data on the form 'varname <structure>'.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
data (str or list): GMCP data.
|
||||||
|
|
||||||
|
Notes:
|
||||||
cmdname string -> cmdname arg
|
cmdname string -> cmdname arg
|
||||||
cmdname [arg, arg,...] -> cmdname *args
|
cmdname [arg, arg,...] -> cmdname *args
|
||||||
cmdname {key:arg, key:arg, ...} -> cmdname **kwargs
|
cmdname {key:arg, key:arg, ...} -> cmdname **kwargs
|
||||||
|
|
@ -248,6 +290,11 @@ class TelnetOOB(object):
|
||||||
def data_out(self, cmdname, *args, **kwargs):
|
def data_out(self, cmdname, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Return a msdp-valid subnegotiation across the protocol.
|
Return a msdp-valid subnegotiation across the protocol.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
cmdname (str): OOB-command name.
|
||||||
|
args, kwargs (any): Arguments to OOB command.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
#print "data_out:", encoded_oob
|
#print "data_out:", encoded_oob
|
||||||
if self.MSDP:
|
if self.MSDP:
|
||||||
|
|
|
||||||
|
|
@ -30,14 +30,20 @@ class Ttype(object):
|
||||||
"""
|
"""
|
||||||
Handles ttype negotiations. Called and initiated by the
|
Handles ttype negotiations. Called and initiated by the
|
||||||
telnet protocol.
|
telnet protocol.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def __init__(self, protocol):
|
def __init__(self, protocol):
|
||||||
"""
|
"""
|
||||||
initialize ttype by storing protocol on ourselves and calling
|
Initialize ttype by storing protocol on ourselves and calling
|
||||||
the client to see if it supporst ttype.
|
the client to see if it supporst ttype.
|
||||||
|
|
||||||
the ttype_step indicates how far in the data retrieval we've
|
Args:
|
||||||
gotten.
|
protocol (Protocol): The protocol instance.
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
The `self.ttype_step` indicates how far in the data
|
||||||
|
retrieval we've gotten.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.ttype_step = 0
|
self.ttype_step = 0
|
||||||
self.protocol = protocol
|
self.protocol = protocol
|
||||||
|
|
@ -52,19 +58,27 @@ class Ttype(object):
|
||||||
def wont_ttype(self, option):
|
def wont_ttype(self, option):
|
||||||
"""
|
"""
|
||||||
Callback if ttype is not supported by client.
|
Callback if ttype is not supported by client.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
option (Option): Not used.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.protocol.protocol_flags['TTYPE']["init_done"] = True
|
self.protocol.protocol_flags['TTYPE']["init_done"] = True
|
||||||
self.protocol.handshake_done()
|
self.protocol.handshake_done()
|
||||||
|
|
||||||
def will_ttype(self, option):
|
def will_ttype(self, option):
|
||||||
"""
|
"""
|
||||||
Handles negotiation of the ttype protocol once the
|
Handles negotiation of the ttype protocol once the client has
|
||||||
client has confirmed that it will respond with the ttype
|
confirmed that it will respond with the ttype protocol.
|
||||||
protocol.
|
|
||||||
|
|
||||||
|
Args:
|
||||||
|
option (Option): Not used.
|
||||||
|
|
||||||
|
Notes:
|
||||||
The negotiation proceeds in several steps, each returning a
|
The negotiation proceeds in several steps, each returning a
|
||||||
certain piece of information about the client. All data is
|
certain piece of information about the client. All data is
|
||||||
stored on protocol.protocol_flags under the TTYPE key.
|
stored on protocol.protocol_flags under the TTYPE key.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
options = self.protocol.protocol_flags.get('TTYPE')
|
options = self.protocol.protocol_flags.get('TTYPE')
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,7 @@ def jsonify(obj):
|
||||||
class WebClient(resource.Resource):
|
class WebClient(resource.Resource):
|
||||||
"""
|
"""
|
||||||
An ajax/comet long-polling transport
|
An ajax/comet long-polling transport
|
||||||
|
|
||||||
"""
|
"""
|
||||||
isLeaf = True
|
isLeaf = True
|
||||||
allowedMethods = ('POST',)
|
allowedMethods = ('POST',)
|
||||||
|
|
@ -80,8 +81,17 @@ class WebClient(resource.Resource):
|
||||||
|
|
||||||
def lineSend(self, suid, string, data=None):
|
def lineSend(self, suid, string, data=None):
|
||||||
"""
|
"""
|
||||||
This adds the data to the buffer and/or sends it to
|
This adds the data to the buffer and/or sends it to the client
|
||||||
the client as soon as possible.
|
as soon as possible.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
suid (int): Session id.
|
||||||
|
string (str): The text to send.
|
||||||
|
data (dict): Optional data.
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
The `data` keyword is deprecated.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
request = self.requests.get(suid)
|
request = self.requests.get(suid)
|
||||||
if request:
|
if request:
|
||||||
|
|
@ -98,6 +108,10 @@ class WebClient(resource.Resource):
|
||||||
def client_disconnect(self, suid):
|
def client_disconnect(self, suid):
|
||||||
"""
|
"""
|
||||||
Disconnect session with given suid.
|
Disconnect session with given suid.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
suid (int): Session id.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if suid in self.requests:
|
if suid in self.requests:
|
||||||
self.requests[suid].finish()
|
self.requests[suid].finish()
|
||||||
|
|
@ -107,8 +121,12 @@ class WebClient(resource.Resource):
|
||||||
|
|
||||||
def mode_init(self, request):
|
def mode_init(self, request):
|
||||||
"""
|
"""
|
||||||
This is called by render_POST when the client
|
This is called by render_POST when the client requests an init
|
||||||
requests an init mode operation (at startup)
|
mode operation (at startup)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request (Request): Incoming request.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
#csess = request.getSession() # obs, this is a cookie, not
|
#csess = request.getSession() # obs, this is a cookie, not
|
||||||
# an evennia session!
|
# an evennia session!
|
||||||
|
|
@ -133,6 +151,10 @@ class WebClient(resource.Resource):
|
||||||
"""
|
"""
|
||||||
This is called by render_POST when the client
|
This is called by render_POST when the client
|
||||||
is sending data to the server.
|
is sending data to the server.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request (Request): Incoming request.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
suid = request.args.get('suid', ['0'])[0]
|
suid = request.args.get('suid', ['0'])[0]
|
||||||
if suid == '0':
|
if suid == '0':
|
||||||
|
|
@ -148,10 +170,13 @@ class WebClient(resource.Resource):
|
||||||
def mode_receive(self, request):
|
def mode_receive(self, request):
|
||||||
"""
|
"""
|
||||||
This is called by render_POST when the client is telling us
|
This is called by render_POST when the client is telling us
|
||||||
that it is ready to receive data as soon as it is
|
that it is ready to receive data as soon as it is available.
|
||||||
available. This is the basis of a long-polling (comet)
|
This is the basis of a long-polling (comet) mechanism: the
|
||||||
mechanism: the server will wait to reply until data is
|
server will wait to reply until data is available.
|
||||||
available.
|
|
||||||
|
Args:
|
||||||
|
request (Request): Incoming request.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
suid = request.args.get('suid', ['0'])[0]
|
suid = request.args.get('suid', ['0'])[0]
|
||||||
if suid == '0':
|
if suid == '0':
|
||||||
|
|
@ -170,6 +195,10 @@ class WebClient(resource.Resource):
|
||||||
"""
|
"""
|
||||||
This is called by render_POST when the client is signalling
|
This is called by render_POST when the client is signalling
|
||||||
that it is about to be closed.
|
that it is about to be closed.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request (Request): Incoming request.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
suid = request.args.get('suid', ['0'])[0]
|
suid = request.args.get('suid', ['0'])[0]
|
||||||
if suid == '0':
|
if suid == '0':
|
||||||
|
|
@ -191,6 +220,10 @@ class WebClient(resource.Resource):
|
||||||
initializing or sending/receving data through the request. It
|
initializing or sending/receving data through the request. It
|
||||||
uses a long-polling mechanism to avoid sending data unless
|
uses a long-polling mechanism to avoid sending data unless
|
||||||
there is actual data available.
|
there is actual data available.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request (Request): Incoming request.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
dmode = request.args.get('mode', [None])[0]
|
dmode = request.args.get('mode', [None])[0]
|
||||||
if dmode == 'init':
|
if dmode == 'init':
|
||||||
|
|
@ -222,7 +255,10 @@ class WebClientSession(session.Session):
|
||||||
|
|
||||||
def disconnect(self, reason=None):
|
def disconnect(self, reason=None):
|
||||||
"""
|
"""
|
||||||
Disconnect from server
|
Disconnect from server.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
reason (str): Motivation for the disconnect.
|
||||||
"""
|
"""
|
||||||
if reason:
|
if reason:
|
||||||
self.client.lineSend(self.suid, reason)
|
self.client.lineSend(self.suid, reason)
|
||||||
|
|
@ -230,11 +266,11 @@ class WebClientSession(session.Session):
|
||||||
|
|
||||||
def data_out(self, text=None, **kwargs):
|
def data_out(self, text=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
Data Evennia -> Player access hook.
|
Data Evennia -> User access hook.
|
||||||
|
|
||||||
webclient flags checked are
|
Kwargs:
|
||||||
raw=True - no parsing at all (leave ansi-to-html markers unparsed)
|
raw (bool): No parsing at all (leave ansi-to-html markers unparsed).
|
||||||
nomarkup=True - clean out all ansi/html markers and tokens
|
nomarkup (bool): Clean out all ansi/html markers and tokens.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# string handling is similar to telnet
|
# string handling is similar to telnet
|
||||||
|
|
|
||||||
|
|
@ -13,9 +13,9 @@ the client must send data on the following form:
|
||||||
|
|
||||||
OOB{"func1":[args], "func2":[args], ...}
|
OOB{"func1":[args], "func2":[args], ...}
|
||||||
|
|
||||||
where the dict is JSON encoded. The initial OOB-prefix
|
where the dict is JSON encoded. The initial OOB-prefix is used to
|
||||||
is used to identify this type of communication, all other data
|
identify this type of communication, all other data is considered
|
||||||
is considered plain text (command input).
|
plain text (command input).
|
||||||
|
|
||||||
Example of call from a javascript client:
|
Example of call from a javascript client:
|
||||||
|
|
||||||
|
|
@ -43,6 +43,7 @@ class WebSocketClient(Protocol, Session):
|
||||||
def connectionMade(self):
|
def connectionMade(self):
|
||||||
"""
|
"""
|
||||||
This is called when the connection is first established.
|
This is called when the connection is first established.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
client_address = self.transport.client
|
client_address = self.transport.client
|
||||||
self.init_session("websocket", client_address, self.factory.sessionhandler)
|
self.init_session("websocket", client_address, self.factory.sessionhandler)
|
||||||
|
|
@ -52,8 +53,12 @@ class WebSocketClient(Protocol, Session):
|
||||||
|
|
||||||
def disconnect(self, reason=None):
|
def disconnect(self, reason=None):
|
||||||
"""
|
"""
|
||||||
generic hook for the engine to call in order to
|
Generic hook for the engine to call in order to
|
||||||
disconnect this protocol.
|
disconnect this protocol.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
reason (str): Motivation for the disconnection.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if reason:
|
if reason:
|
||||||
self.data_out(text=reason)
|
self.data_out(text=reason)
|
||||||
|
|
@ -61,24 +66,29 @@ class WebSocketClient(Protocol, Session):
|
||||||
|
|
||||||
def connectionLost(self, reason):
|
def connectionLost(self, reason):
|
||||||
"""
|
"""
|
||||||
this is executed when the connection is lost for
|
This is executed when the connection is lost for whatever
|
||||||
whatever reason. it can also be called directly, from
|
reason. it can also be called directly, from the disconnect
|
||||||
the disconnect method
|
method
|
||||||
|
|
||||||
|
Args:
|
||||||
|
reason (str): Motivation for the lost connection.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.sessionhandler.disconnect(self)
|
self.sessionhandler.disconnect(self)
|
||||||
self.transport.close()
|
self.transport.close()
|
||||||
|
|
||||||
def dataReceived(self, string):
|
def dataReceived(self, string):
|
||||||
"""
|
"""
|
||||||
Method called when data is coming in over
|
Method called when data is coming in over the websocket
|
||||||
the websocket connection.
|
connection.
|
||||||
|
|
||||||
Type of data is identified by a 3-character
|
Args:
|
||||||
prefix.
|
string (str): Type of data is identified by a 3-character
|
||||||
OOB - This is an Out-of-band instruction. If so,
|
prefix:
|
||||||
|
- "OOB" This is an Out-of-band instruction. If so,
|
||||||
the remaining string should be a json-packed
|
the remaining string should be a json-packed
|
||||||
string on the form {oobfuncname: [args, ], ...}
|
string on the form {oobfuncname: [args, ], ...}
|
||||||
CMD - plain text data, to be treated like a game
|
- "CMD" plain text data, to be treated like a game
|
||||||
input command.
|
input command.
|
||||||
"""
|
"""
|
||||||
mode = string[:3]
|
mode = string[:3]
|
||||||
|
|
@ -92,13 +102,26 @@ class WebSocketClient(Protocol, Session):
|
||||||
self.data_in(text=data)
|
self.data_in(text=data)
|
||||||
|
|
||||||
def sendLine(self, line):
|
def sendLine(self, line):
|
||||||
"send data to client"
|
"""
|
||||||
|
Send data to client.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
line (str): Text to send.
|
||||||
|
|
||||||
|
"""
|
||||||
return self.transport.write(line)
|
return self.transport.write(line)
|
||||||
|
|
||||||
def json_decode(self, data):
|
def json_decode(self, data):
|
||||||
"""
|
"""
|
||||||
Decodes incoming data from the client
|
Decodes incoming data from the client.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
data (JSON): JSON object to unpack.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
Exception: If receiving a malform OOB request.
|
||||||
|
|
||||||
|
Notes:
|
||||||
[cmdname, [args],{kwargs}] -> cmdname *args **kwargs
|
[cmdname, [args],{kwargs}] -> cmdname *args **kwargs
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
@ -111,8 +134,13 @@ class WebSocketClient(Protocol, Session):
|
||||||
|
|
||||||
def json_encode(self, cmdname, *args, **kwargs):
|
def json_encode(self, cmdname, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Encode OOB data for sending to client
|
Encode OOB data for sending to client.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
cmdname (str): OOB command name.
|
||||||
|
args, kwargs (any): Arguments to oob command.
|
||||||
|
|
||||||
|
Notes:
|
||||||
cmdname *args -> cmdname [json array]
|
cmdname *args -> cmdname [json array]
|
||||||
cmdname **kwargs -> cmdname {json object}
|
cmdname **kwargs -> cmdname {json object}
|
||||||
|
|
||||||
|
|
@ -122,20 +150,25 @@ class WebSocketClient(Protocol, Session):
|
||||||
|
|
||||||
def data_in(self, text=None, **kwargs):
|
def data_in(self, text=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
Data Websocket -> Server
|
Data User > Evennia.
|
||||||
|
|
||||||
|
Kwargs:
|
||||||
|
text (str): Incoming text.
|
||||||
|
kwargs (any): Options from protocol.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.sessionhandler.data_in(self, text=text, **kwargs)
|
self.sessionhandler.data_in(self, text=text, **kwargs)
|
||||||
|
|
||||||
def data_out(self, text=None, **kwargs):
|
def data_out(self, text=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
Data Evennia -> Player.
|
Data Evennia -> User. A generic hook method for engine to call
|
||||||
generic hook method for engine to call in order to send data
|
in order to send data through the websocket connection.
|
||||||
through the websocket connection.
|
|
||||||
|
Kwargs:
|
||||||
|
oob (str or tuple): Supply an Out-of-Band instruction.
|
||||||
|
raw (bool): No parsing at all (leave ansi-to-html markers unparsed).
|
||||||
|
nomarkup (bool): Clean out all ansi/html markers and tokens.
|
||||||
|
|
||||||
valid webclient kwargs:
|
|
||||||
oob=<string> - supply an Out-of-Band instruction.
|
|
||||||
raw=True - no parsing at all (leave ansi-to-html markers unparsed)
|
|
||||||
nomarkup=True - clean out all ansi/html markers and tokens
|
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
text = to_str(text if text else "", encoding=self.encoding)
|
text = to_str(text if text else "", encoding=self.encoding)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue