Further updates to the IMC2 system, making it a bit easier to use.
This commit is contained in:
parent
c70d59045a
commit
23462c626c
5 changed files with 147 additions and 183 deletions
|
|
@ -16,11 +16,17 @@ from src.comms.imc2lib import imc2_packets as pck
|
|||
from src.comms.imc2lib.imc2_trackers import IMC2MudList, IMC2ChanList
|
||||
from src.comms.imc2lib.imc2_listeners import handle_whois_reply
|
||||
|
||||
# IMC2 network setup
|
||||
IMC2_MUDNAME = settings.SERVERNAME
|
||||
IMC2_NETWORK = settings.IMC2_NETWORK
|
||||
IMC2_PORT = settings.IMC2_PORT
|
||||
IMC2_CLIENT_PWD = settings.IMC2_CLIENT_PWD
|
||||
IMC2_SERVER_PWD = settings.IMC2_SERVER_PWD
|
||||
|
||||
# channel to send info to
|
||||
INFOCHANNEL = Channel.objects.channel_search(settings.CHANNEL_MUDINFO[0])
|
||||
# all linked channel connections
|
||||
IMC2_CONNECTIONS = []
|
||||
IMC2_CLIENT = None
|
||||
# IMC2 debug mode
|
||||
IMC2_DEBUG = False
|
||||
# Use this instance to keep track of the other games on the network.
|
||||
|
|
@ -58,11 +64,10 @@ class Send_IsAlive(Script):
|
|||
self.desc = "Send an IMC2 is-alive packet"
|
||||
self.persistent = True
|
||||
def at_repeat(self):
|
||||
for channel in IMC2_CONNECTIONS:
|
||||
channel.send_packet(pck.IMC2PacketIsAlive())
|
||||
IMC2_CLIENT.send_packet(pck.IMC2PacketIsAlive())
|
||||
def is_valid(self):
|
||||
"Is only valid as long as there are channels to update"
|
||||
return any(service for service in SESSIONS.server.services if service.name.startswith("IMC2:"))
|
||||
return any(service for service in SESSIONS.server.services if service.name.startswith("imc2_"))
|
||||
|
||||
class Send_Keepalive_Request(Script):
|
||||
"""
|
||||
|
|
@ -74,12 +79,11 @@ class Send_Keepalive_Request(Script):
|
|||
self.interval = 3500
|
||||
self.desc = "Send an IMC2 keepalive-request packet"
|
||||
self.persistent = True
|
||||
def at_repeat(self):
|
||||
for channel in IMC2_CONNECTIONS:
|
||||
channel.send_packet(pck.IMC2PacketKeepAliveRequest())
|
||||
def at_repeat(self):
|
||||
IMC2_CLIENT.channel.send_packet(pck.IMC2PacketKeepAliveRequest())
|
||||
def is_valid(self):
|
||||
"Is only valid as long as there are channels to update"
|
||||
return any(service for service in SESSIONS.server.services if service.name.startswith("IMC2:"))
|
||||
return any(service for service in SESSIONS.server.services if service.name.startswith("imc2_"))
|
||||
|
||||
class Prune_Inactive_Muds(Script):
|
||||
"""
|
||||
|
|
@ -99,7 +103,7 @@ class Prune_Inactive_Muds(Script):
|
|||
del IMC2_MUDLIST.mud_list[name]
|
||||
def is_valid(self):
|
||||
"Is only valid as long as there are channels to update"
|
||||
return any(service for service in SESSIONS.server.services if service.name.startswith("IMC2:"))
|
||||
return any(service for service in SESSIONS.server.services if service.name.startswith("imc2_"))
|
||||
|
||||
class Sync_Server_Channel_List(Script):
|
||||
"""
|
||||
|
|
@ -114,14 +118,13 @@ class Sync_Server_Channel_List(Script):
|
|||
self.desc = "Re-sync IMC2 network channel list"
|
||||
self.persistent = True
|
||||
def at_repeat(self):
|
||||
checked_networks = []
|
||||
for channel in self.IMC2_CONNECTIONS:
|
||||
network = channel.factory.network
|
||||
if not network in checked_networks:
|
||||
channel.send_packet(pkg.IMC2PacketIceRefresh())
|
||||
checked_networks.append(network)
|
||||
checked_networks = []
|
||||
network = IMC2_CLIENT.factory.network
|
||||
if not network in checked_networks:
|
||||
channel.send_packet(pkg.IMC2PacketIceRefresh())
|
||||
checked_networks.append(network)
|
||||
def is_valid(self):
|
||||
return any(service for service in SESSIONS.server.services if service.name.startswith("IMC2:"))
|
||||
return any(service for service in SESSIONS.server.services if service.name.startswith("imc2_"))
|
||||
#
|
||||
# IMC2 protocol
|
||||
#
|
||||
|
|
@ -132,8 +135,8 @@ class IMC2Protocol(telnet.StatefulTelnetProtocol):
|
|||
authentication, and all necessary packets.
|
||||
"""
|
||||
def __init__(self):
|
||||
global IMC2_CONNECTIONS
|
||||
IMC2_CONNECTIONS.append(self)
|
||||
global IMC2_CLIENT
|
||||
IMC2_CLIENT = self
|
||||
self.is_authenticated = False
|
||||
self.auth_type = None
|
||||
self.server_name = None
|
||||
|
|
@ -146,8 +149,9 @@ class IMC2Protocol(telnet.StatefulTelnetProtocol):
|
|||
Triggered after connecting to the IMC2 network.
|
||||
"""
|
||||
self.auth_type = "plaintext"
|
||||
logger.log_infomsg("IMC2: Connected to network server.")
|
||||
logger.log_infomsg("IMC2: Sending authentication packet.")
|
||||
if IMC2_DEBUG:
|
||||
logger.log_infomsg("IMC2: Connected to network server.")
|
||||
logger.log_infomsg("IMC2: Sending authentication packet.")
|
||||
self.send_packet(pck.IMC2PacketAuthPlaintext())
|
||||
|
||||
def connectionLost(self, reason=None):
|
||||
|
|
@ -155,8 +159,13 @@ class IMC2Protocol(telnet.StatefulTelnetProtocol):
|
|||
This is executed when the connection is lost for
|
||||
whatever reason.
|
||||
"""
|
||||
pass # we don't need to do anything, it's cleaned up automatically.
|
||||
|
||||
try:
|
||||
service = SESSIONS.server.services.getServiceNamed("imc2_%s:%s(%s)" % (IMC2_NETWORK, IMC2_PORT, IMC2_MUDNAME))
|
||||
except Exception:
|
||||
return
|
||||
if service.running:
|
||||
service.stopService()
|
||||
|
||||
def send_packet(self, packet):
|
||||
"""
|
||||
Given a sub-class of IMC2Packet, assemble the packet and send it
|
||||
|
|
@ -206,7 +215,7 @@ class IMC2Protocol(telnet.StatefulTelnetProtocol):
|
|||
self.sequence = int(time())
|
||||
|
||||
# Log to stdout and notify over MUDInfo.
|
||||
auth_message = "Successfully authenticated to the '%s' network." % self.network_name
|
||||
auth_message = "Successfully authenticated to the '%s' network." % self.factory.network
|
||||
logger.log_infomsg('IMC2: %s' % auth_message)
|
||||
msg_info(auth_message)
|
||||
|
||||
|
|
@ -228,23 +237,18 @@ class IMC2Protocol(telnet.StatefulTelnetProtocol):
|
|||
# If the packet lacks the 'echo' key, don't bother with it.
|
||||
if not conn_name or not packet.optional_data.get('echo', None):
|
||||
return
|
||||
chan_name = conn_name.split(':', 1)[1]
|
||||
imc2_channel = conn_name.split(':', 1)[1]
|
||||
|
||||
key = "imc2_%s" % conn_name
|
||||
# Look for matching IMC2 channel maps.
|
||||
conns = ExternalChannelConnection.objects.filter(db_external_key=self.factory.key)
|
||||
# Look for matching IMC2 channel maps mapping to this imc2 channel.
|
||||
conns = ExternalChannelConnection.objects.filter(db_external_key__startswith="imc2_")
|
||||
conns = [conn for conn in conns if imc2_channel in conn.db_external_config.split(",")]
|
||||
if not conns:
|
||||
# we are not listening to this imc2 channel.
|
||||
return
|
||||
|
||||
# get channel subscriptions
|
||||
chansubs = conns[0].db_external_config.split("|")[2].split(",")
|
||||
if not chan_name in chansubs:
|
||||
# we are not listening to this channel.
|
||||
return
|
||||
|
||||
# Format the message to send to local channel.
|
||||
message = '[%s] %s@%s: %s' % (self.factory.evennia_channel, packet.sender, packet.origin, packet.optional_data.get('text'))
|
||||
for conn in (conn for conn in conns if conn.channel):
|
||||
|
||||
# Format the message to send to local channel(s).
|
||||
for conn in conns:
|
||||
message = '[%s] %s@%s: %s' % (conn.channel.key, packet.sender, packet.origin, packet.optional_data.get('text'))
|
||||
conn.to_channel(message)
|
||||
|
||||
def _format_tell(self, packet):
|
||||
|
|
@ -252,7 +256,7 @@ class IMC2Protocol(telnet.StatefulTelnetProtocol):
|
|||
Handle tells over IMC2 by formatting the text properly
|
||||
"""
|
||||
return "{c%s@%s{n {wpages (over IMC):{n %s" % (packet.sender, packet.origin,
|
||||
packet.optional_data.get('text', 'ERROR: No text provided.'))
|
||||
packet.optional_data.get('text', 'ERROR: No text provided.'))
|
||||
|
||||
def lineReceived(self, line):
|
||||
"""
|
||||
|
|
@ -308,9 +312,6 @@ class IMC2Protocol(telnet.StatefulTelnetProtocol):
|
|||
"""
|
||||
Called by Evennia to send a message through the imc2 connection
|
||||
"""
|
||||
conns = ExternalChannelConnection.objects.filter(db_external_key=self.factory.key)
|
||||
if not conns:
|
||||
return
|
||||
if from_obj:
|
||||
if hasattr(from_obj, 'key'):
|
||||
from_name = from_obj.key
|
||||
|
|
@ -318,11 +319,20 @@ class IMC2Protocol(telnet.StatefulTelnetProtocol):
|
|||
from_name = from_obj
|
||||
else:
|
||||
from_name = self.factory.mudname
|
||||
|
||||
|
||||
if packet_type == "imcbroadcast":
|
||||
# send the packet
|
||||
self.send_packet(pck.IMC2PacketIceMsgBroadcasted(self.factory.servername, self.factory.channel[0],
|
||||
from_name, message))
|
||||
if type(data) == dict:
|
||||
conns = ExternalChannelConnection.objects.filter(db_external_key__startswith="imc2_",
|
||||
db_channel=data.get("channel", "Unknown"))
|
||||
if not conns:
|
||||
return
|
||||
# we remove the extra channel info since imc2 supplies this anyway
|
||||
if ":" in message:
|
||||
header, message = [part.strip() for part in message.split(":", 1)]
|
||||
# send the packet
|
||||
imc2_channel = conns[0].db_external_config.split(',')[0] # only send to the first channel
|
||||
self.send_packet(pck.IMC2PacketIceMsgBroadcasted(self.factory.servername, imc2_channel,
|
||||
from_name, message))
|
||||
elif packet_type == "imctell":
|
||||
# send a tell
|
||||
if type(data) == dict:
|
||||
|
|
@ -338,24 +348,21 @@ class IMC2Protocol(telnet.StatefulTelnetProtocol):
|
|||
|
||||
class IMC2Factory(protocol.ClientFactory):
|
||||
"""
|
||||
Creates instances of the IMC2Protocol. Should really only ever create one
|
||||
in our particular instance. Tied in via src/server.py.
|
||||
Creates instances of the IMC2Protocol. Should really only ever
|
||||
need to create one connection. Tied in via src/server.py.
|
||||
"""
|
||||
protocol = IMC2Protocol
|
||||
|
||||
def __init__(self, key, channel, network, port, mudname, client_pwd, server_pwd, evennia_channel):
|
||||
self.key = key
|
||||
self.mudname = mudname
|
||||
self.channel = channel # this is a list!
|
||||
self.pretty_key = "%s:%s/%s (%s)" % (network, port, channel, mudname)
|
||||
def __init__(self, network, port, mudname, client_pwd, server_pwd):
|
||||
self.pretty_key = "%s:%s(%s)" % (network, port, mudname)
|
||||
self.network = network
|
||||
sname, host = network.split(".", 1)
|
||||
self.servername = sname.strip()
|
||||
self.port = port
|
||||
self.mudname = mudname
|
||||
self.protocol_version = '2'
|
||||
self.client_pwd = client_pwd
|
||||
self.server_pwd = server_pwd
|
||||
self.evennia_channel = evennia_channel
|
||||
|
||||
|
||||
def clientConnectionFailed(self, connector, reason):
|
||||
message = 'Connection failed: %s' % reason.getErrorMessage()
|
||||
|
|
@ -368,12 +375,11 @@ class IMC2Factory(protocol.ClientFactory):
|
|||
logger.log_errmsg('IMC2: %s' % message)
|
||||
|
||||
|
||||
def build_connection_key(imc2_network, imc2_port, imc2_mudname):
|
||||
def build_connection_key(channel, imc2_channel):
|
||||
"Build an id hash for the connection"
|
||||
return "imc2_%s:%s(%s)<>Evennia" % (imc2_network, imc2_port, imc2_mudname)
|
||||
|
||||
def build_service_key(key):
|
||||
return "IMC2:%s" % key
|
||||
if hasattr(channel, "key"):
|
||||
channel = channel.key
|
||||
return "imc2_%s:%s(%s)%s<>%s" % (IMC2_NETWORK, IMC2_PORT, IMC2_MUDNAME, imc2_channel, channel)
|
||||
|
||||
def start_scripts(validate=False):
|
||||
"""
|
||||
|
|
@ -393,97 +399,69 @@ def start_scripts(validate=False):
|
|||
if not search.scripts("IMC2_Sync_Server_Channel_List"):
|
||||
create.create_script(Sync_Server_Channel_List)
|
||||
|
||||
def create_connection(channel, imc2_network, imc2_port, imc2_channel, imc2_mudname, imc2_client_pwd, imc2_server_pwd):
|
||||
def create_connection(channel, imc2_channel):
|
||||
"""
|
||||
This will create a new IMC2<->channel connection.
|
||||
"""
|
||||
|
||||
key = build_connection_key(imc2_network, imc2_port, imc2_mudname)
|
||||
|
||||
if not type(channel) == Channel:
|
||||
new_channel = Channel.objects.filter(db_key=channel)
|
||||
if not new_channel:
|
||||
logger.log_errmsg("Cannot attach IMC2<->Evennia: Evennia Channel '%s' not found" % channel)
|
||||
return False
|
||||
channel = new_channel[0]
|
||||
|
||||
key = build_connection_key(channel, imc2_channel)
|
||||
|
||||
old_conns = ExternalChannelConnection.objects.filter(db_external_key=key)
|
||||
if old_conns:
|
||||
# this evennia channel is already connected to imc. Check if imc2_channel is different.
|
||||
# connection already exists. We try to only connect a new channel
|
||||
old_config = old_conns[0].db_external_config.split('|',5)
|
||||
old_chan_subs = old_config[2].split(',')
|
||||
if imc2_channel in old_chan_subs:
|
||||
old_config = old_conns[0].db_external_config.split(",")
|
||||
if imc2_channel in old_config:
|
||||
return False # we already listen to this channel
|
||||
else:
|
||||
# We add thew new channel to the connection instead of creating a new connection.
|
||||
old_chan_subs.append(imc2_channel)
|
||||
old_chan_subs = ",".join(old_chan_subs)
|
||||
old_config[2] = old_chan_subs # add a channel subscription to old config
|
||||
old_conns[0].db_external_config = "|".join(old_config)
|
||||
# We add a new imc2_channel to listen to
|
||||
old_config.append(imc2_channel)
|
||||
old_conns[0].db_external_config = ",".join(old_config)
|
||||
old_conns[0].save()
|
||||
return True
|
||||
else:
|
||||
# no old connection found; create a new one.
|
||||
config = imc2_channel
|
||||
# how the evennia channel will be able to contact this protocol in reverse
|
||||
send_code = "from src.comms.imc2 import IMC2_CLIENT\n"
|
||||
send_code += "data={'channel':from_channel}\n"
|
||||
send_code += "IMC2_CLIENT.msg_imc2(message, from_obj=from_obj, data=data)\n"
|
||||
conn = ExternalChannelConnection(db_channel=channel, db_external_key=key, db_external_send_code=send_code,
|
||||
db_external_config=config)
|
||||
conn.save()
|
||||
return True
|
||||
|
||||
# new connection
|
||||
config = "%s|%s|%s|%s|%s|%s" % (imc2_network, imc2_port, imc2_channel, imc2_mudname, imc2_client_pwd, imc2_server_pwd)
|
||||
# how the channel will be able to contact this protocol
|
||||
send_code = "from src.comms.imc2 import IMC2_CONNECTIONS\n"
|
||||
send_code += "matched_imc2s = [imc2 for imc2 in IMC2_CONNECTIONS if imc2.factory.key == '%s']\n" % key
|
||||
send_code += "[imc2.msg_imc2(message, from_obj=from_obj) for imc2 in matched_imc2s]\n"
|
||||
conn = ExternalChannelConnection(db_channel=channel, db_external_key=key, db_external_send_code=send_code,
|
||||
db_external_config=config)
|
||||
conn.save()
|
||||
|
||||
# connect
|
||||
connect_to_imc2(conn)
|
||||
# start scripts (if needed)
|
||||
start_scripts()
|
||||
return True
|
||||
|
||||
def delete_connection(imc2_network, imc2_port, mudname):
|
||||
def delete_connection(channel, imc2_channel):
|
||||
"Destroy a connection"
|
||||
|
||||
key = build_connection_key(imc2_network, imc2_port, mudname)
|
||||
service_key = build_service_key(key)
|
||||
if hasattr(channel, "key"):
|
||||
channel = channel.key
|
||||
key = build_connection_key(channel, imc2_channel)
|
||||
|
||||
try:
|
||||
conn = ExternalChannelConnection.objects.get(db_external_key=key)
|
||||
except Exception:
|
||||
except ExternalChannelConnection.DoesNotExist:
|
||||
return False
|
||||
conn.delete()
|
||||
|
||||
try:
|
||||
service = SESSIONS.server.services.getServiceNamed(service_key)
|
||||
except Exception:
|
||||
return True
|
||||
if service.running:
|
||||
SESSIONS.server.services.removeService(service)
|
||||
# validate scripts
|
||||
start_scripts(validate=True)
|
||||
return True
|
||||
|
||||
def connect_to_imc2(connection):
|
||||
"Create the imc instance and connect to the IMC2 network and channel."
|
||||
def connect_to_imc2():
|
||||
"Create the imc instance and connect to the IMC2 network."
|
||||
|
||||
# get config
|
||||
key = utils.to_str(connection.external_key)
|
||||
service_key = build_service_key(key)
|
||||
imc2_network, imc2_port, imc2_channel, imc2_mudname, imc2_client_pwd, imc2_server_pwd = \
|
||||
[utils.to_str(conf) for conf in connection.external_config.split('|')]
|
||||
imc2_channel = imc2_channel.split(",")
|
||||
# connect
|
||||
imc = internet.TCPClient(imc2_network, int(imc2_port), IMC2Factory(key, imc2_channel, imc2_network, imc2_port, imc2_mudname,
|
||||
imc2_client_pwd, imc2_server_pwd, connection.channel.key))
|
||||
imc.setName(service_key)
|
||||
imc = internet.TCPClient(IMC2_NETWORK, int(IMC2_PORT), IMC2Factory(IMC2_NETWORK, IMC2_PORT, IMC2_MUDNAME,
|
||||
IMC2_CLIENT_PWD, IMC2_SERVER_PWD))
|
||||
imc.setName("imc2_%s:%s(%s)" % (IMC2_NETWORK, IMC2_PORT, IMC2_MUDNAME))
|
||||
SESSIONS.server.services.addService(imc)
|
||||
|
||||
def connect_all():
|
||||
"""
|
||||
Activate all imc2 bots.
|
||||
|
||||
Returns a list of (key, TCPClient) tuples for server to properly set services.
|
||||
Activates the imc2 system. Called by the server if IMC2_ENABLED=True.
|
||||
"""
|
||||
connections = ExternalChannelConnection.objects.filter(db_external_key__startswith='imc2_')
|
||||
for connection in connections:
|
||||
connect_to_imc2(connection)
|
||||
if connections:
|
||||
start_scripts()
|
||||
connect_to_imc2()
|
||||
start_scripts()
|
||||
|
|
|
|||
|
|
@ -561,7 +561,7 @@ class Channel(SharedMemoryModel):
|
|||
conn.player.msg(msg, from_obj)
|
||||
except AttributeError:
|
||||
try:
|
||||
conn.to_external(msg, from_obj)
|
||||
conn.to_external(msg, from_obj, from_channel=self)
|
||||
except Exception:
|
||||
logger.log_trace("Cannot send msg to connection '%s'" % conn)
|
||||
return True
|
||||
|
|
@ -780,7 +780,7 @@ class ExternalChannelConnection(SharedMemoryModel):
|
|||
from_obj = self.external_key
|
||||
self.channel.msg(message, from_obj=from_obj)
|
||||
|
||||
def to_external(self, message, from_obj=None):
|
||||
def to_external(self, message, from_obj=None, from_channel=None):
|
||||
"Send channel -> external"
|
||||
|
||||
# make sure we are not echoing back our own message to ourselves
|
||||
|
|
@ -792,7 +792,7 @@ class ExternalChannelConnection(SharedMemoryModel):
|
|||
# we execute the code snippet that should make it possible for the
|
||||
# connection to contact the protocol correctly (as set by the protocol).
|
||||
# Note that the code block has access to the variables here, such
|
||||
# as message and from_obj.
|
||||
# as message, from_obj and from_channel.
|
||||
exec(to_str(self.external_send_code))
|
||||
except Exception:
|
||||
logger.log_trace("Channel %s could not send to External %s" % (self.channel, self.external_key))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue