* Added the 'imclist' command to show other games connected to IMC.
* Added the automatic cleaning/pruning code to weed out entries that are probably disconnected. * Added 'imcwhois <player>' command. Still needs some sanitizing on the outgoing string. * Added the beginnings of a -reply packet handler through reply_listener.py. * Fleshed out a few more packets in packets.py. Next up: Make the ANSI system a little more modular. Create a class for ANSI tables so developers can pick and choose different tables on their own, but keep the same API. This will be used so we don't have to copy/paste src/ansi.py to src/imc2/ansi.py and duplicate stuff.
This commit is contained in:
parent
317a4f1532
commit
31a78f9fb6
7 changed files with 213 additions and 45 deletions
|
|
@ -1,7 +1,7 @@
|
|||
"""
|
||||
IMC2 user and administrative commands.
|
||||
"""
|
||||
import time
|
||||
from time import time
|
||||
from django.conf import settings
|
||||
from src.config.models import ConfigValue
|
||||
from src.objects.models import Object
|
||||
|
|
@ -11,25 +11,67 @@ from src.util import functions_general
|
|||
from src.cmdtable import GLOBAL_CMD_TABLE
|
||||
from src.imc2 import connection as imc2_conn
|
||||
from src.imc2.packets import *
|
||||
from src.imc2.trackers import IMC2_MUDLIST
|
||||
|
||||
def cmd_imctest(command):
|
||||
def cmd_imcwhois(command):
|
||||
"""
|
||||
Shows a player's inventory.
|
||||
"""
|
||||
source_object = command.source_object
|
||||
source_object.emit_to("Sending")
|
||||
packet = IMC2PacketWhois(source_object, 'Cratylus')
|
||||
imc2_conn.IMC2_PROTOCOL_INSTANCE.send_packet(packet)
|
||||
source_object.emit_to("Sent")
|
||||
GLOBAL_CMD_TABLE.add_command("imctest", cmd_imctest)
|
||||
if not command.command_argument:
|
||||
source_object.emit_to("Get what?")
|
||||
return
|
||||
else:
|
||||
source_object.emit_to("Sending IMC whois request. If you receive no response, no matches were found.")
|
||||
packet = IMC2PacketWhois(source_object, command.command_argument)
|
||||
imc2_conn.IMC2_PROTOCOL_INSTANCE.send_packet(packet)
|
||||
GLOBAL_CMD_TABLE.add_command("imcwhois", cmd_imcwhois)
|
||||
|
||||
def cmd_imckeepalive(command):
|
||||
"""
|
||||
Shows a player's inventory.
|
||||
Sends an is-alive packet to the network.
|
||||
"""
|
||||
source_object = command.source_object
|
||||
source_object.emit_to("Sending")
|
||||
packet = IMC2PacketIsAlive()
|
||||
imc2_conn.IMC2_PROTOCOL_INSTANCE.send_packet(packet)
|
||||
source_object.emit_to("Sent")
|
||||
GLOBAL_CMD_TABLE.add_command("imckeepalive", cmd_imckeepalive)
|
||||
GLOBAL_CMD_TABLE.add_command("imckeepalive", cmd_imckeepalive)
|
||||
|
||||
def cmd_imckeeprequest(command):
|
||||
"""
|
||||
Sends a keepalive-request packet to the network.
|
||||
"""
|
||||
source_object = command.source_object
|
||||
source_object.emit_to("Sending")
|
||||
packet = IMC2PacketKeepAliveRequest()
|
||||
imc2_conn.IMC2_PROTOCOL_INSTANCE.send_packet(packet)
|
||||
source_object.emit_to("Sent")
|
||||
GLOBAL_CMD_TABLE.add_command("imckeeprequest", cmd_imckeeprequest)
|
||||
|
||||
def cmd_imclist(command):
|
||||
"""
|
||||
Shows the list of cached games from the IMC2 Mud list.
|
||||
"""
|
||||
source_object = command.source_object
|
||||
|
||||
retval = 'Active MUDs on %s\n\r' % imc2_conn.IMC2_PROTOCOL_INSTANCE.network_name
|
||||
for name, mudinfo in IMC2_MUDLIST.mud_list.items():
|
||||
mudline = ' %-20s %s' % (name, mudinfo.versionid)
|
||||
retval += '%s\n\r' % mudline[:78]
|
||||
retval += '%s active MUDs found.' % len(IMC2_MUDLIST.mud_list)
|
||||
source_object.emit_to(retval)
|
||||
GLOBAL_CMD_TABLE.add_command("imclist", cmd_imclist)
|
||||
|
||||
def cmd_imclistupdated(command):
|
||||
"""
|
||||
Shows the list of cached games from the IMC2 Mud list.
|
||||
"""
|
||||
source_object = command.source_object
|
||||
|
||||
retval = 'Active MUDs on %s\n\r' % imc2_conn.IMC2_PROTOCOL_INSTANCE.network_name
|
||||
for name, mudinfo in IMC2_MUDLIST.mud_list.items():
|
||||
tdelta = time() - mudinfo.last_updated
|
||||
retval += ' %-20s %s\n\r' % (name, tdelta)
|
||||
source_object.emit_to(retval)
|
||||
GLOBAL_CMD_TABLE.add_command("imclistupdated", cmd_imclistupdated)
|
||||
|
|
@ -14,16 +14,7 @@ class IntervalEvent(object):
|
|||
"""
|
||||
Represents an event that is triggered periodically. Sub-class this and
|
||||
fill in the stub function.
|
||||
"""
|
||||
# This is what shows up on @ps in-game.
|
||||
name = None
|
||||
# An interval (in seconds) for execution.
|
||||
interval = None
|
||||
# A timestamp (int) for the last time the event was fired.
|
||||
time_last_executed = None
|
||||
# A reference to the task.LoopingCall object.
|
||||
looped_task = None
|
||||
|
||||
"""
|
||||
def __init__(self):
|
||||
"""
|
||||
Executed when the class is instantiated.
|
||||
|
|
@ -31,6 +22,12 @@ class IntervalEvent(object):
|
|||
# This is set to prevent a Nonetype exception on @ps before the
|
||||
# event is fired for the first time.
|
||||
self.time_last_executed = time.time()
|
||||
# This is what shows up on @ps in-game.
|
||||
self.name = None
|
||||
# An interval (in seconds) for execution.
|
||||
self.interval = None
|
||||
# A reference to the task.LoopingCall object.
|
||||
self.looped_task = None
|
||||
|
||||
def __unicode__(self):
|
||||
"""
|
||||
|
|
@ -78,9 +75,11 @@ class IEvt_Check_Sessions(IntervalEvent):
|
|||
"""
|
||||
Event: Check all of the connected sessions.
|
||||
"""
|
||||
name = 'IEvt_Check_Sessions'
|
||||
interval = 60
|
||||
description = "Session consistency checks."
|
||||
def __init__(self):
|
||||
super(IEvt_Check_Sessions, self).__init__()
|
||||
self.name = 'IEvt_Check_Sessions'
|
||||
self.interval = 60
|
||||
self.description = "Session consistency checks."
|
||||
|
||||
def event_function(self):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -2,14 +2,16 @@
|
|||
IMC2 client module. Handles connecting to and communicating with an IMC2 server.
|
||||
"""
|
||||
import telnetlib
|
||||
import time
|
||||
from time import time
|
||||
from twisted.internet.protocol import ClientFactory
|
||||
from twisted.protocols.basic import LineReceiver
|
||||
from twisted.internet import reactor, task
|
||||
from twisted.conch.telnet import StatefulTelnetProtocol
|
||||
from django.conf import settings
|
||||
from src.imc2.packets import *
|
||||
from src import logger
|
||||
from src.imc2.packets import *
|
||||
from src.imc2.trackers import *
|
||||
from src.imc2 import reply_listener
|
||||
|
||||
# The active instance of IMC2Protocol. Set at server startup.
|
||||
IMC2_PROTOCOL_INSTANCE = None
|
||||
|
|
@ -20,8 +22,9 @@ class IMC2Protocol(StatefulTelnetProtocol):
|
|||
authentication, and all necessary packets.
|
||||
"""
|
||||
def __init__(self):
|
||||
print "IMC2: Client connecting to %s:%s..." % (settings.IMC2_SERVER_ADDRESS,
|
||||
settings.IMC2_SERVER_PORT)
|
||||
logger.log_infomsg("IMC2: Client connecting to %s:%s..." % (
|
||||
settings.IMC2_SERVER_ADDRESS,
|
||||
settings.IMC2_SERVER_PORT))
|
||||
global IMC2_PROTOCOL_INSTANCE
|
||||
IMC2_PROTOCOL_INSTANCE = self
|
||||
self.is_authenticated = False
|
||||
|
|
@ -34,9 +37,9 @@ class IMC2Protocol(StatefulTelnetProtocol):
|
|||
"""
|
||||
Triggered after connecting to the IMC2 network.
|
||||
"""
|
||||
print "IMC2: Connected to network server."
|
||||
logger.log_infomsg("IMC2: Connected to network server.")
|
||||
self.auth_type = "plaintext"
|
||||
print "IMC2: Sending authentication packet."
|
||||
logger.log_infomsg("IMC2: Sending authentication packet.")
|
||||
self.send_packet(IMC2PacketAuthPlaintext())
|
||||
|
||||
def send_packet(self, packet):
|
||||
|
|
@ -50,7 +53,7 @@ class IMC2Protocol(StatefulTelnetProtocol):
|
|||
|
||||
packet.imc2_protocol = self
|
||||
packet_str = packet.assemble()
|
||||
print "IMC2: SENT> %s" % packet_str
|
||||
logger.log_infomsg("IMC2: SENT> %s" % packet_str)
|
||||
self.sendLine(packet_str)
|
||||
|
||||
def _parse_auth_response(self, line):
|
||||
|
|
@ -66,8 +69,11 @@ class IMC2Protocol(StatefulTelnetProtocol):
|
|||
self.server_name = line_split[1]
|
||||
self.network_name = line_split[4]
|
||||
self.is_authenticated = True
|
||||
self.sequence = int(time.time())
|
||||
print "IMC2: Successfully authenticated to the '%s' network." % self.network_name
|
||||
self.sequence = int(time())
|
||||
logger.log_infomsg("IMC2: Successfully authenticated to the '%s' network." % self.network_name)
|
||||
# Let everyone know we've arrived.
|
||||
#self.send_packet(IMC2PacketKeepAliveRequest())
|
||||
self.send_packet(IMC2PacketIsAlive())
|
||||
|
||||
def lineReceived(self, line):
|
||||
"""
|
||||
|
|
@ -78,8 +84,14 @@ class IMC2Protocol(StatefulTelnetProtocol):
|
|||
self._parse_auth_response(line)
|
||||
else:
|
||||
logger.log_infomsg("PACKET: %s" % line)
|
||||
logger.log_infomsg(IMC2Packet(packet_str = line))
|
||||
#print "receive:", line
|
||||
packet = IMC2Packet(packet_str = line)
|
||||
logger.log_infomsg(packet)
|
||||
if packet.packet_type == 'is-alive':
|
||||
IMC2_MUDLIST.update_mud_from_packet(packet)
|
||||
elif packet.packet_type == 'whois-reply':
|
||||
reply_listener.handle_whois_reply(packet)
|
||||
elif packet.packet_type == 'close-notify':
|
||||
IMC2_MUDLIST.remove_mud_from_packet(packet)
|
||||
|
||||
class IMC2ClientFactory(ClientFactory):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -3,30 +3,78 @@ This module contains all IMC2 events that are triggered periodically.
|
|||
Most of these are used to maintain the existing connection and keep various
|
||||
lists/caches up to date.
|
||||
"""
|
||||
from time import time
|
||||
from src import events
|
||||
from src import scheduler
|
||||
from src.imc2 import connection as imc2_conn
|
||||
from src.imc2.packets import *
|
||||
from src.imc2.trackers import IMC2_MUDLIST
|
||||
|
||||
class IEvt_IMC2_IsAlive(events.IntervalEvent):
|
||||
class IEvt_IMC2_Send_IsAlive(events.IntervalEvent):
|
||||
"""
|
||||
Event: Send periodic keepalives to network neighbors. This lets the other
|
||||
games know that our game is still up and connected to the network. Also
|
||||
provides some useful information about the client game.
|
||||
"""
|
||||
name = 'IEvt_IMC2_IsAlive'
|
||||
# Send keep-alive packets every 15 minutes.
|
||||
interval = 900
|
||||
description = "Send an IMC2 is-alive packet."
|
||||
def __init__(self):
|
||||
super(IEvt_IMC2_Send_IsAlive, self).__init__()
|
||||
self.name = 'IEvt_IMC2_Send_IsAlive'
|
||||
# Send keep-alive packets every 15 minutes.
|
||||
self.interval = 900
|
||||
self.description = "Send an IMC2 is-alive packet."
|
||||
|
||||
def event_function(self):
|
||||
"""
|
||||
This is the function that is fired every self.interval seconds.
|
||||
"""
|
||||
imc2_conn.IMC2_PROTOCOL_INSTANCE.send_packet(IMC2PacketIsAlive())
|
||||
|
||||
class IEvt_IMC2_Send_Keepalive_Request(events.IntervalEvent):
|
||||
"""
|
||||
Event: Sends a keepalive-request to connected games in order to see who
|
||||
is connected.
|
||||
"""
|
||||
def __init__(self):
|
||||
super(IEvt_IMC2_Send_Keepalive_Request, self).__init__()
|
||||
self.name = 'IEvt_IMC2_Send_Keepalive_Request'
|
||||
self.interval = 3500
|
||||
self.description = "Send an IMC2 keepalive-request packet."
|
||||
|
||||
def event_function(self):
|
||||
"""
|
||||
This is the function that is fired every self.interval seconds.
|
||||
"""
|
||||
imc2_conn.IMC2_PROTOCOL_INSTANCE.send_packet(IMC2PacketKeepAliveRequest())
|
||||
|
||||
class IEvt_IMC2_Prune_Inactive_Muds(events.IntervalEvent):
|
||||
"""
|
||||
Event: Prunes games that have not sent is-alive packets for a while. If
|
||||
we haven't heard from them, they're probably not connected or don't
|
||||
implement the protocol correctly. In either case, good riddance to them.
|
||||
"""
|
||||
def __init__(self):
|
||||
super(IEvt_IMC2_Prune_Inactive_Muds, self).__init__()
|
||||
self.name = 'IEvt_IMC2_Prune_Inactive_Muds'
|
||||
# Check every 30 minutes.
|
||||
self.interval = 1800
|
||||
self.description = "Check IMC2 list for inactive games."
|
||||
# Threshold for game inactivity (in seconds).
|
||||
self.inactive_thresh = 3599
|
||||
|
||||
def event_function(self):
|
||||
"""
|
||||
This is the function that is fired every self.interval seconds.
|
||||
"""
|
||||
for name, mudinfo in IMC2_MUDLIST.mud_list.items():
|
||||
# If we haven't heard from the game within our threshold time,
|
||||
# we assume that they're dead.
|
||||
if time() - mudinfo.last_updated > self.inactive_thresh:
|
||||
del IMC2_MUDLIST.mud_list[name]
|
||||
|
||||
def add_events():
|
||||
"""
|
||||
Adds the IMC2 events to the scheduler.
|
||||
"""
|
||||
scheduler.add_event(IEvt_IMC2_IsAlive())
|
||||
scheduler.add_event(IEvt_IMC2_Send_IsAlive())
|
||||
scheduler.add_event(IEvt_IMC2_Prune_Inactive_Muds())
|
||||
scheduler.add_event(IEvt_IMC2_Send_Keepalive_Request())
|
||||
|
|
@ -40,7 +40,7 @@ class IMC2Packet(object):
|
|||
self.target = None
|
||||
self.destination = None
|
||||
# Optional data.
|
||||
self.optional_data = []
|
||||
self.optional_data = {}
|
||||
# Reference to the IMC2Protocol object doing the sending.
|
||||
self.imc2_protocol = None
|
||||
|
||||
|
|
@ -56,7 +56,6 @@ class IMC2Packet(object):
|
|||
if counter == 0:
|
||||
# This is the sender@origin token.
|
||||
sender_origin = token
|
||||
print token
|
||||
split_sender_origin = sender_origin.split('@')
|
||||
self.sender = split_sender_origin[0].strip()
|
||||
self.origin = split_sender_origin[1]
|
||||
|
|
@ -84,8 +83,12 @@ class IMC2Packet(object):
|
|||
self.destination = split_target_destination[0]
|
||||
elif counter > 4:
|
||||
# Populate optional data.
|
||||
key, value = token.split('=', 1)
|
||||
self.optional_data.append((key, value))
|
||||
try:
|
||||
key, value = token.split('=', 1)
|
||||
self.optional_data[key] = value
|
||||
except ValueError:
|
||||
# Failed to split on equal sign, disregard.
|
||||
pass
|
||||
# Increment and continue to the next token (if applicable)
|
||||
counter += 1
|
||||
|
||||
|
|
@ -129,6 +132,8 @@ class IMC2Packet(object):
|
|||
if self.sender == '*':
|
||||
# Some packets have no sender.
|
||||
return '*'
|
||||
elif str(self.sender).isdigit():
|
||||
return self.sender
|
||||
elif self.sender:
|
||||
# Player object.
|
||||
name = self.sender.get_name(fullname=False, show_dbref=False,
|
||||
|
|
@ -199,7 +204,12 @@ class IMC2PacketKeepAliveRequest(IMC2Packet):
|
|||
Example of a sent keepalive-request:
|
||||
*@YourMUD 1234567890 YourMUD keepalive-request *@*
|
||||
"""
|
||||
pass
|
||||
def __init__(self):
|
||||
super(IMC2PacketKeepAliveRequest, self).__init__()
|
||||
self.sender = '*'
|
||||
self.packet_type = 'keepalive-request'
|
||||
self.target = '*'
|
||||
self.destination = '*'
|
||||
|
||||
class IMC2PacketIsAlive(IMC2Packet):
|
||||
"""
|
||||
|
|
@ -607,7 +617,8 @@ class IMC2PacketWhois(IMC2Packet):
|
|||
"""
|
||||
def __init__(self, pobject, whois_target):
|
||||
super(IMC2PacketWhois, self).__init__()
|
||||
self.sender = pobject
|
||||
# Use the dbref, it's easier to trace back for the whois-reply.
|
||||
self.sender = pobject.id
|
||||
self.packet_type = 'whois'
|
||||
self.target = whois_target
|
||||
self.destination = '*'
|
||||
|
|
|
|||
12
src/imc2/reply_listener.py
Normal file
12
src/imc2/reply_listener.py
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
"""
|
||||
This module handles some of the -reply packets like whois-reply.
|
||||
"""
|
||||
from src.objects.models import Object
|
||||
|
||||
def handle_whois_reply(packet):
|
||||
try:
|
||||
pobject = Object.objects.get(id=packet.target)
|
||||
pobject.emit_to('Whois reply: %s' % packet.optional_data.get('text', 'Unknown'))
|
||||
except Object.DoesNotExist:
|
||||
# No match found for whois sender. Ignore it.
|
||||
pass
|
||||
44
src/imc2/trackers.py
Normal file
44
src/imc2/trackers.py
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
"""
|
||||
Certain periodic packets are sent by connected MUDs (is-alive, user-cache,
|
||||
etc). The IMC2 protocol assumes that each connected MUD will capture these and
|
||||
populate/maintain their own lists of other servers connected. This module
|
||||
contains stuff like this.
|
||||
"""
|
||||
from time import time
|
||||
|
||||
class IMC2Mud(object):
|
||||
"""
|
||||
Stores information about other games connected to our current IMC2 network.
|
||||
"""
|
||||
def __init__(self, packet):
|
||||
self.name = packet.origin
|
||||
self.versionid = packet.optional_data.get('versionid', None)
|
||||
self.networkname = packet.optional_data.get('networkname', None)
|
||||
self.url = packet.optional_data.get('url', None)
|
||||
self.host = packet.optional_data.get('host', None)
|
||||
self.port = packet.optional_data.get('port', None)
|
||||
self.sha256 = packet.optional_data.get('sha256', None)
|
||||
# This is used to determine when a Mud has fallen into inactive status.
|
||||
self.last_updated = time()
|
||||
|
||||
class IMC2MudList(object):
|
||||
"""
|
||||
Keeps track of other MUDs connected to the IMC network.
|
||||
"""
|
||||
def __init__(self):
|
||||
# Mud list is stored in a dict, key being the IMC Mud name.
|
||||
self.mud_list = {}
|
||||
|
||||
def update_mud_from_packet(self, packet):
|
||||
# This grabs relevant info from the packet and stuffs it in the
|
||||
# Mud list for later retrieval.
|
||||
mud = IMC2Mud(packet)
|
||||
self.mud_list[mud.name] = mud
|
||||
|
||||
def remove_mud_from_packet(self, packet):
|
||||
# Removes a mud from the Mud list when given a packet.
|
||||
mud = IMC2Mud(packet)
|
||||
del self.mud_list[mud.name]
|
||||
|
||||
# Use this instance to keep track of the other games on the network.
|
||||
IMC2_MUDLIST = IMC2MudList()
|
||||
Loading…
Add table
Add a link
Reference in a new issue