Implemented untested IMC2 in the new system.

This commit is contained in:
Griatch 2014-02-27 19:48:48 +01:00
parent 97991a2238
commit 84f5c4dca5
9 changed files with 359 additions and 742 deletions

View file

@ -121,6 +121,8 @@ class Bot(Player):
# Bot implementations
# IRC
class IRCBot(Bot):
"""
Bot for handling IRC connections.
@ -194,6 +196,8 @@ class IRCBot(Bot):
self.ndb.ev_channel.msg(text, senders=self.dbobj.id)
# RSS
class RSSBot(Bot):
"""
An RSS relayer. The RSS protocol itself runs a ticker to update its feed at regular
@ -240,4 +244,75 @@ class RSSBot(Bot):
if self.ndb.ev_channel:
self.ndb.ev_channel.msg(text, senders=self.dbobj.id)
class IMC2Bot(Bot):
"""
IMC2 Bot
"""
def start(self, ev_channel=None, imc2_network=None, imc2_mudname=None,
imc2_port=None, imc2_client_pwd=None, imc2_server_pwd=None):
"""
Start by telling the portal to start a new session
ev_channel - key of the Evennia channel to connect to
imc2_network - IMC2 network name
imc2_mudname - registered mudname (if not given, use settings.SERVERNAME)
imc2_port - port number of IMC2 network
imc2_client_pwd - client password registered with IMC2 network
imc2_server_pwd - server password registered with IMC2 network
"""
global _SESSIONS
if not _SESSIONS:
from src.server.sessionhandler import SESSIONS as _SESSIONS
if ev_channel:
# connect to Evennia channel
channel = search.channel_search(ev_channel)
if not channel:
raise RuntimeError("Evennia Channel '%s' not found." % ev_channel)
channel = channel[0]
channel.connect(self)
self.db.ev_channel = channel
if imc2_network:
self.db.imc2_network = imc2_network
if imc2_port:
self.db.imc2_port = imc2_port
if imc2_mudname:
self.db.imc2_mudname = imc2_mudname
elif not self.db.imc2_mudname:
self.db.imc2_mudname = settings.SERVERNAME
# storing imc2 passwords in attributes - a possible
# security issue?
if imc2_server_pwd:
self.db.imc2_server_pwd = imc2_server_pwd
if imc2_client_pwd:
self.db.imc2_client_pwd = imc2_client_pwd
configdict = {"uid": self.dbid,
"mudname": self.db.imc2_mudname,
"network": self.db.imc2_network,
"port": self.db.imc2_port,
"client_pwd": self.db.client_pwd,
"server_pwd": self.db.server_pwd}
_SESSIONS.start_bot_session("src.server.portal.imc2.IMC2BotFactory", configdict)
def msg(self, text=None, **kwargs):
"""
Takes text from connected channel (only)
"""
if not self.ndb.ev_channel and self.db.ev_channel:
# cache channel lookup
self.ndb.ev_channel = self.db.ev_channel
if "from_channel" in kwargs and text and self.ndb.ev_channel.dbid == kwargs["from_channel"]:
if "from_obj" not in kwargs or kwargs["from_obj"] != [self.dbobj.id]:
text = "bot_data_out %s" % text
self.dbobj.msg(text=text)
def execute_cmd(self, text=None, sessid=None):
"""
Relay incoming data to connected channel.
"""
if not self.ndb.ev_channel and self.db.ev_channel:
# cache channel lookup
self.ndb.ev_channel = self.db.ev_channel
if self.ndb.ev_channel:
self.ndb.ev_channel.msg(text, senders=self.dbobj.id)

View file

@ -3,213 +3,186 @@ IMC2 client module. Handles connecting to and communicating with an IMC2 server.
"""
from time import time
from twisted.internet import task
from twisted.application import internet
from twisted.internet import protocol
from twisted.conch import telnet
from django.conf import settings
from src.utils import logger, create, search, utils
from src.server.sessionhandler import SESSIONS
from src.scripts.scripts import Script
from src.comms.models import ChannelDB, ExternalChannelConnection
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
from src.server.session import Session
from src.utils import logger, utils
from src.server.portal.imc2lib import imc2_packets as pck
from django.utils.translation import ugettext as _
# 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 = ChannelDB.objects.channel_search(settings.CHANNEL_MUDINFO[0])
# all linked channel connections
IMC2_CLIENT = None
# IMC2 debug mode
IMC2_DEBUG = False
# Use this instance to keep track of the other games on the network.
IMC2_MUDLIST = IMC2MudList()
# Tracks the list of available channels on the network.
IMC2_CHANLIST = IMC2ChanList()
# storage containers for IMC2 muds and channels
#
# Helper method
#
def msg_info(message):
class IMC2Mud(object):
"""
Send info to default info channel
Stores information about other games connected to our current IMC2 network.
"""
try:
INFOCHANNEL[0].msg(message)
message = '[%s][IMC2]: %s' % (INFOCHANNEL[0].key, message)
except Exception:
logger.log_infomsg("MUDinfo (imc2): %s" % message)
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()
#
# Regular scripts
#
class Send_IsAlive(Script):
class IMC2MudList(dict):
"""
Sends 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.
Keeps track of other MUDs connected to the IMC network.
"""
def at_script_creation(self):
self.key = 'IMC2_Send_IsAlive'
self.interval = 900
self.desc = _("Send an IMC2 is-alive packet")
self.persistent = True
def get_mud_list(self):
"""
Returns a sorted list of connected Muds.
"""
muds = self.items()
muds.sort()
return [value for key, value in muds]
def at_repeat(self):
IMC2_CLIENT.send_packet(pck.IMC2PacketIsAlive())
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.name] = mud
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_"))
def remove_mud_from_packet(self, packet):
"""
Removes a mud from the Mud list when given a packet.
"""
mud = IMC2Mud(packet)
try:
del self[mud.name]
except KeyError:
# No matching entry, no big deal.
pass
class Send_Keepalive_Request(Script):
class IMC2Channel(object):
"""
Event: Sends a keepalive-request to connected games in order to see who
is connected.
Stores information about channels available on the network.
"""
def at_script_creation(self):
self.key = "IMC2_Send_Keepalive_Request"
self.interval = 3500
self.desc = _("Send an IMC2 keepalive-request packet")
self.persistent = True
def __init__(self, packet):
self.localname = packet.optional_data.get('localname', None)
self.name = packet.optional_data.get('channel', None)
self.level = packet.optional_data.get('level', None)
self.owner = packet.optional_data.get('owner', None)
self.policy = packet.optional_data.get('policy', None)
self.last_updated = time()
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_"))
class Prune_Inactive_Muds(Script):
class IMC2ChanList(dict):
"""
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.
Keeps track of Channels on the IMC network.
"""
def at_script_creation(self):
self.key = "IMC2_Prune_Inactive_Muds"
self.interval = 1800
self.desc = _("Check IMC2 list for inactive games")
self.persistent = True
self.inactive_threshold = 3599
def at_repeat(self):
for name, mudinfo in IMC2_MUDLIST.mud_list.items():
if time() - mudinfo.last_updated > self.inactive_threshold:
del IMC2_MUDLIST.mud_list[name]
def get_channel_list(self):
"""
Returns a sorted list of cached channels.
"""
channels = self.items()
channels.sort()
return [value for key, value in channels]
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_"))
def update_channel_from_packet(self, packet):
"""
This grabs relevant info from the packet and stuffs it in the
channel list for later retrieval.
"""
channel = IMC2Channel(packet)
self[channel.name] = channel
def remove_channel_from_packet(self, packet):
"""
Removes a channel from the Channel list when given a packet.
"""
channel = IMC2Channel(packet)
try:
del self[channel.name]
except KeyError:
# No matching entry, no big deal.
pass
class Sync_Server_Channel_List(Script):
"""
Re-syncs the network's channel list. This will
cause a cascade of reply packets of a certain type
from the network. These are handled by the protocol,
gradually updating the channel cache.
"""
def at_script_creation(self):
self.key = "IMC2_Sync_Server_Channel_List"
self.interval = 24 * 3600 # once every day
self.desc = _("Re-sync IMC2 network channel list")
self.persistent = True
def at_repeat(self):
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_"))
#
# IMC2 protocol
#
class IMC2Protocol(telnet.StatefulTelnetProtocol):
class IMC2Bot(telnet.StatefulTelnetProtocol, Session):
"""
Provides the abstraction for the IMC2 protocol. Handles connection,
authentication, and all necessary packets.
"""
def __init__(self):
global IMC2_CLIENT
IMC2_CLIENT = self
self.is_authenticated = False
self.auth_type = None
self.server_name = None
self.network_name = None
self.sequence = None
def connectionMade(self):
"""
Triggered after connecting to the IMC2 network.
"""
# only support plaintext passwords
self.auth_type = "plaintext"
if IMC2_DEBUG:
logger.log_infomsg("IMC2: Connected to network server.")
logger.log_infomsg("IMC2: Sending authentication packet.")
self.send_packet(pck.IMC2PacketAuthPlaintext())
self.sequence = None
self.imc2_mudlist = IMC2MudList()
self.imc2_chanlist = IMC2ChanList()
def connectionLost(self, reason=None):
"""
This is executed when the connection is lost for
whatever reason.
"""
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
on its way to the IMC2 server.
Evennia -> IMC2
"""
if self.sequence:
# This gets incremented with every command.
self.sequence += 1
def _send_packet(self, packet):
"Helper function to send packets across the wire"
packet.imc2_protocol = self
packet_str = utils.to_str(packet.assemble(self.factory.mudname,
self.factory.client_pwd, self.factory.server_pwd))
if IMC2_DEBUG and not (hasattr(packet, 'packet_type') and
packet.packet_type == "is-alive"):
logger.log_infomsg("IMC2: SENT> %s" % packet_str)
logger.log_infomsg(str(packet))
self.factory.client_pwd, self.factory.server_pwd))
self.sendLine(packet_str)
def _parse_auth_response(self, line):
def _isalive(self):
"Send an isalive packet"
self._send_packet(pck.IMC2PacketIsAlive())
def _keepalive(self):
"Send a keepalive packet"
# send to channel?
self._send_packet(pck.IMC2PacketKeepAliveRequest())
def _channellist(self):
"Sync the network channel list"
checked_networks = []
if not self.network in checked_networks:
self._send_packet(pck.IMC2PacketIceRefresh())
checked_networks.append(self.network)
def _prune(self):
"Prune active channel list"
t0 = time()
for name, mudinfo in self.imc2_mudlist.items():
if t0 - mudinfo.last_updated > 3599:
del self.imc2_mudlist[name]
def _whois_reply(self, packet):
"handle reply from server from an imcwhois request"
# packet.target potentially contains the id of an character to target
# not using that here
response_text = imc2_ansi.parse_ansi(packet.optional_data.get('text', 'Unknown'))
string = _('Whois reply from %(origin)s: %(msg)s') % {"origin":packet.origin, "msg":response_text}
# somehow pass reply on to a given player
def _format_tell(self, packet):
"""
Parses the IMC2 network authentication packet.
Handle tells over IMC2 by formatting the text properly
"""
return _("{c%(sender)s@%(origin)s{n {wpages (over IMC):{n %(msg)s") % {"sender": packet.sender,
"origin": packet.origin,
"msg": packet.optional_data.get('text', 'ERROR: No text provided.')}
def _imc_login(self, line):
"Connect and identify to imc network"
if self.auth_type == "plaintext":
# Plain text passwords.
# Only support Plain text passwords.
# SERVER Sends: PW <servername> <serverpw> version=<version#> <networkname>
if IMC2_DEBUG:
logger.log_infomsg("IMC2: AUTH< %s" % line)
logger.log_infomsg("IMC2: AUTH< %s" % line)
line_split = line.split(' ')
pw_present = line_split[0] == 'PW'
@ -218,7 +191,6 @@ class IMC2Protocol(telnet.StatefulTelnetProtocol):
if "reject" in line_split:
auth_message = _("IMC2 server rejected connection.")
logger.log_infomsg(auth_message)
msg_info(auth_message)
return
if pw_present:
@ -232,266 +204,193 @@ class IMC2Protocol(telnet.StatefulTelnetProtocol):
self.sequence = int(time())
# Log to stdout and notify over MUDInfo.
auth_message = _("Successfully authenticated to the '%s' network.") % self.factory.network
logger.log_infomsg('IMC2: %s' % auth_message)
msg_info(auth_message)
logger.log_infomsg('IMC2: Authenticated to %s' % self.factory.network)
# Ask to see what other MUDs are connected.
self.send_packet(pck.IMC2PacketKeepAliveRequest())
self._send_packet(pck.IMC2PacketKeepAliveRequest())
# IMC2 protocol states that KeepAliveRequests should be followed
# up by the requester sending an IsAlive packet.
self.send_packet(pck.IMC2PacketIsAlive())
self._send_packet(pck.IMC2PacketIsAlive())
# Get a listing of channels.
self.send_packet(pck.IMC2PacketIceRefresh())
self._send_packet(pck.IMC2PacketIceRefresh())
def _msg_evennia(self, packet):
def connectionMade(self):
"""
Handle the sending of packet data to Evennia channel
(Message from IMC2 -> Evennia)
Triggered after connecting to the IMC2 network.
"""
conn_name = packet.optional_data.get('channel', None)
# 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
imc2_channel = conn_name.split(':', 1)[1]
# 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
# 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):
"""
Handle tells over IMC2 by formatting the text properly
"""
return _("{c%(sender)s@%(origin)s{n {wpages (over IMC):{n %(msg)s") % {"sender": packet.sender,
"origin": packet.origin,
"msg": packet.optional_data.get('text', 'ERROR: No text provided.')}
self.stopping = False
self.factory.bot = self
address = "%s@%s" % (self.mudname, self.network)
self.init_session("ircbot", address, self.factory.sessionhandler)
# link back and log in
self.uid = int(self.factory.uid)
self.logged_in = True
self.factory.sessionhandler.connect(self)
logger.log_infomsg("IMC2 bot connected to %s." % self.network)
# Send authentication packet. The reply will be caught by lineReceived
self._send_packet(pck.IMC2PacketAuthPlaintext())
def lineReceived(self, line):
"""
Triggered when text is received from the IMC2 network. Figures out
what to do with the packet.
IMC2 -> Evennia
Triggered when text is received from the IMC2 network. Figures out
what to do with the packet. This deals with the following
"""
line = line.strip()
if not self.is_authenticated:
self._parse_auth_response(line)
else:
if IMC2_DEBUG and not 'is-alive' in line:
# if IMC2_DEBUG mode is on, print the contents of the packet
# to stdout.
logger.log_infomsg("IMC2: RECV> %s" % line)
# we are not authenticated yet. Deal with this.
self._imc_login(line)
return
# Parse the packet and encapsulate it for easy access
packet = pck.IMC2Packet(self.factory.mudname, packet_str=line)
#logger.log_infomsg("IMC2: RECV> %s" % line)
if IMC2_DEBUG and packet.packet_type not in ('is-alive', 'keepalive-request'):
# Print the parsed packet's __str__ representation.
# is-alive and keepalive-requests happen pretty frequently.
# Don't bore us with them in stdout.
logger.log_infomsg(str(packet))
# Parse the packet and encapsulate it for easy access
packet = pck.IMC2Packet(self.mudname, packet_str=line)
# Figure out what kind of packet we're dealing with and hand it
# off to the correct handler.
# Figure out what kind of packet we're dealing with and hand it
# off to the correct handler.
if packet.packet_type == 'is-alive':
IMC2_MUDLIST.update_mud_from_packet(packet)
elif packet.packet_type == 'keepalive-request':
# Don't need to check the destination, we only receive these
# packets when they are intended for us.
self.send_packet(pck.IMC2PacketIsAlive())
elif packet.packet_type == 'ice-msg-b':
self._msg_evennia(packet)
elif packet.packet_type == 'whois-reply':
handle_whois_reply(packet)
elif packet.packet_type == 'close-notify':
IMC2_MUDLIST.remove_mud_from_packet(packet)
elif packet.packet_type == 'ice-update':
IMC2_CHANLIST.update_channel_from_packet(packet)
elif packet.packet_type == 'ice-destroy':
IMC2_CHANLIST.remove_channel_from_packet(packet)
elif packet.packet_type == 'tell':
player = search.players(packet.target)
if not player:
return
player[0].msg(self._format_tell(packet))
if packet.packet_type == 'is-alive':
self.imc2_mudlist.update_mud_from_packet(packet)
elif packet.packet_type == 'keepalive-request':
# Don't need to check the destination, we only receive these
# packets when they are intended for us.
self.send_packet(pck.IMC2PacketIsAlive())
elif packet.packet_type == 'ice-msg-b':
self.data_out(text=line, packettype="broadcast")
elif packet.packet_type == 'whois-reply':
# handle eventual whois reply
elif packet.packet_type == 'close-notify':
self.imc2_mudlist.remove_mud_from_packet(packet)
elif packet.packet_type == 'ice-update':
self.imc2_chanlist.update_channel_from_packet(packet)
elif packet.packet_type == 'ice-destroy':
self.imc2_chanlist.remove_channel_from_packet(packet)
elif packet.packet_type == 'tell':
# send message to identified player
pass
def msg_imc2(self, message, from_obj=None, packet_type="imcbroadcast", data=None):
def data_in(self, text=None, **kwargs):
"""
Called by Evennia to send a message through the imc2 connection
Data IMC2 -> Evennia
"""
if from_obj:
if hasattr(from_obj, 'key'):
from_name = from_obj.key
self.sessionhandler.data_in(self, text=text, **kwargs)
def data_out(self, text=None, **kwargs):
"""
Evennia -> IMC2
Keywords
packet_type:
broadcast - send to everyone on IMC channel
tell - send a tell (see target keyword)
whois - get whois information (see target keyword)
sender - used by tell to identify the sender
target - key identifier of target to tells or whois. If not
given "Unknown" will be used.
destination - used by tell to specify mud destination to send to
"""
if self.sequence:
# This gets incremented with every command.
self.sequence += 1
packet_type = kwargs.get("packet_type", "imcbroadcast")
if packet_type == "broadcast":
# broadcast to everyone on IMC channel
if text.startswith("bot_data_out"):
text = text.split(" ", 1)[1]
else:
from_name = from_obj
else:
from_name = self.factory.mudname
return
if packet_type == "imcbroadcast":
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:
target = data.get("target", "Unknown")
destination = data.get("destination", "Unknown")
self.send_packet(pck.IMC2PacketTell(from_name, target, destination, message))
# we remove the extra channel info since imc2 supplies this anyway
if ":" in text:
header, message = [part.strip() for part in text.split(":", 1)]
# Create imc2packet and send it
self._send_packet(pck.IMC2PacketIceMsgBroadcasted(self.servername,
self.channel,
header, text))
elif packet_type == "tell":
# send an IMC2 tell
sender = kwargs.get("sender", self.mudname)
target = kwargs.get("target", "Unknown")
destination = kwargs.get("destination", "Unknown")
self._send_packet(pck.IMC2PacketTell(sender, target, destination, text))
elif packet_type == "imcwhois":
elif packet_type == "whois":
# send a whois request
if type(data) == dict:
target = data.get("target", "Unknown")
self.send_packet(pck.IMC2PacketWhois(from_obj.id, target))
sender = kwargs.get("sender", self.mudname)
target = kwargs.get("target", "Unknown")
self._send_packet(pck.IMC2PacketWhois(sender, target))
class IMC2Factory(protocol.ClientFactory):
class IMC2BotFactory(protocol.ReconnectingClientFactory):
"""
Creates instances of the IMC2Protocol. Should really only ever
need to create one connection. Tied in via src/server.py.
"""
protocol = IMC2Protocol
initialDelay = 1
factor = 1.5
maxDelay = 60
def __init__(self, network, port, mudname, client_pwd, server_pwd):
self.pretty_key = "%s:%s(%s)" % (network, port, mudname)
def __init__(self, sessionhandler, uid=None, network=None, channel=None,
port=None, mudname=None, client_pwd=None, server_pwd=None):
self.uid = uid
self.network = network
sname, host = network.split(".", 1)
self.servername = sname.strip()
self.channel = channel
self.port = port
self.mudname = mudname
self.protocol_version = '2'
self.client_pwd = client_pwd
self.server_pwd = server_pwd
self.bot = None
self.task_isalive = None
self.task_keepalive = None
self.task_prune = None
self.task_channellist = None
def buildProtocol(self, addr):
"Build the protocol"
protocol = IMC2Bot()
protocol.factory = self
protocol.network = self.network
protocol.servername = self.servername
protocol.channel = self.channel
protocol.mudname = self.mudname
protocol.port = self.port
return protocol
def clientConnectionFailed(self, connector, reason):
message = _('Connection failed: %s') % reason.getErrorMessage()
msg_info(message)
logger.log_errmsg('IMC2: %s' % message)
self.retry(connector)
def clientConnectionLost(self, connector, reason):
message = _('Connection lost: %s') % reason.getErrorMessage()
msg_info(message)
logger.log_errmsg('IMC2: %s' % message)
if not self.bot.stopping:
self.retry(connector)
def start(self):
"Connect session to sessionhandler"
def errback(fail):
logger.log_errmsg(fail.value)
def build_connection_key(channel, imc2_channel):
"Build an id hash for the connection"
if hasattr(channel, "key"):
channel = channel.key
return "imc2_%s:%s(%s)%s<>%s" % (IMC2_NETWORK, IMC2_PORT,
IMC2_MUDNAME, imc2_channel, channel)
if self.port:
service = internet.TCPClient(self.network, int(self.port), self)
self.sessionhandler.portal.services.addService(service)
# start tasks
self.task_isalive = task.LoopingCall(self.bot._isalive)
self.task_keepalive = task.LoopingCall(self.bot._keepalive)
self.task_prune = task.LoopingCall(self.bot._prune)
self.task_channellist = task.LoopingCall(self.bot._channellist)
self.task_isalive.start(900, now=False)
self.task_keepalive.start(3500, now=False)
self.task_prune.start(1800, now=False)
self.task_channellist.start(3600 * 24, now=False)
def start_scripts(validate=False):
"""
Start all the needed scripts
"""
if validate:
from src.scripts.models import ScriptDB
ScriptDB.objects.validate()
return
if not search.scripts("IMC2_Send_IsAlive"):
create.create_script(Send_IsAlive)
if not search.scripts("IMC2_Send_Keepalive_Request"):
create.create_script(Send_Keepalive_Request)
if not search.scripts("IMC2_Prune_Inactive_Muds"):
create.create_script(Prune_Inactive_Muds)
if not search.scripts("IMC2_Sync_Server_Channel_List"):
create.create_script(Sync_Server_Channel_List)
def create_connection(channel, imc2_channel):
"""
This will create a new IMC2<->channel connection.
"""
if not type(channel) == ChannelDB:
new_channel = ChannelDB.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(",")
if imc2_channel in old_config:
return False # we already listen to this channel
else:
# 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, senders=[self])\n"
conn = ExternalChannelConnection(db_channel=channel, db_external_key=key, db_external_send_code=send_code,
db_external_config=config)
conn.save()
return True
def delete_connection(channel, imc2_channel):
"Destroy a connection"
if hasattr(channel, "key"):
channel = channel.key
key = build_connection_key(channel, imc2_channel)
try:
conn = ExternalChannelConnection.objects.get(db_external_key=key)
except ExternalChannelConnection.DoesNotExist:
return False
conn.delete()
return True
def connect_to_imc2():
"Create the imc instance and connect to the IMC2 network."
# connect
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():
"""
Activates the imc2 system. Called by the server if IMC2_ENABLED=True.
"""
connect_to_imc2()
start_scripts()

View file

@ -23,19 +23,15 @@ class IMC2Mud(object):
self.last_updated = time()
class IMC2MudList(object):
class IMC2MudList(dict):
"""
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 get_mud_list(self):
"""
Returns a sorted list of connected Muds.
"""
muds = self.mud_list.items()
muds = self.items()
muds.sort()
return [value for key, value in muds]
@ -45,7 +41,7 @@ class IMC2MudList(object):
Mud list for later retrieval.
"""
mud = IMC2Mud(packet)
self.mud_list[mud.name] = mud
self[mud.name] = mud
def remove_mud_from_packet(self, packet):
"""
@ -53,7 +49,7 @@ class IMC2MudList(object):
"""
mud = IMC2Mud(packet)
try:
del self.mud_list[mud.name]
del self[mud.name]
except KeyError:
# No matching entry, no big deal.
pass
@ -72,19 +68,16 @@ class IMC2Channel(object):
self.last_updated = time()
class IMC2ChanList(object):
class IMC2ChanList(dict):
"""
Keeps track of other MUDs connected to the IMC network.
Keeps track of Channels on the IMC network.
"""
def __init__(self):
# Chan list is stored in a dict, key being the IMC Mud name.
self.chan_list = {}
def get_channel_list(self):
"""
Returns a sorted list of cached channels.
"""
channels = self.chan_list.items()
channels = self.items()
channels.sort()
return [value for key, value in channels]
@ -94,7 +87,7 @@ class IMC2ChanList(object):
channel list for later retrieval.
"""
channel = IMC2Channel(packet)
self.chan_list[channel.name] = channel
self[channel.name] = channel
def remove_channel_from_packet(self, packet):
"""
@ -102,7 +95,7 @@ class IMC2ChanList(object):
"""
channel = IMC2Channel(packet)
try:
del self.chan_list[channel.name]
del self[channel.name]
except KeyError:
# No matching entry, no big deal.
pass

View file

@ -123,218 +123,3 @@ class IRCBotFactory(protocol.ReconnectingClientFactory):
if self.port:
service = internet.TCPClient(self.network, int(self.port), self)
self.sessionhandler.portal.services.addService(service)
#
#from twisted.application import internet
#from twisted.words.protocols import irc
#from twisted.internet import protocol
#from django.conf import settings
#from src.comms.models import ExternalChannelConnection, ChannelDB
#from src.utils import logger, utils
#from src.server.sessionhandler import SESSIONS
#
#from django.utils.translation import ugettext as _
#
#INFOCHANNEL = ChannelDB.objects.channel_search(settings.CHANNEL_MUDINFO[0])
#IRC_CHANNELS = []
#
#def msg_info(message):
# """
# Send info to default info channel
# """
# message = '[%s][IRC]: %s' % (INFOCHANNEL[0].key, message)
# try:
# INFOCHANNEL[0].msg(message)
# except AttributeError:
# logger.log_infomsg("MUDinfo (irc): %s" % message)
#
#
#class IRC_Bot(irc.IRCClient):
# """
# This defines an IRC bot that connects to an IRC channel
# and relays data to and from an evennia game.
# """
#
# def _get_nickname(self):
# "required for correct nickname setting"
# return self.factory.nickname
# nickname = property(_get_nickname)
#
# def signedOn(self):
# # This is the first point the protocol is instantiated.
# # add this protocol instance to the global list so we
# # can access it later to send data.
# global IRC_CHANNELS
# self.join(self.factory.channel)
#
# IRC_CHANNELS.append(self)
# #msg_info("Client connecting to %s.'" % (self.factory.channel))
#
# def joined(self, channel):
# msg = _("joined %s.") % self.factory.pretty_key
# msg_info(msg)
# logger.log_infomsg(msg)
#
# def get_mesg_info(self, user, irc_channel, msg):
# """
# Get basic information about a message posted in IRC.
# """
# #find irc->evennia channel mappings
# conns = ExternalChannelConnection.objects.filter(db_external_key=self.factory.key)
# if not conns:
# return
# #format message:
# user = user.split("!")[0]
# if user:
# user.strip()
# else:
# user = _("Unknown")
# msg = msg.strip()
# sender_strings = ["%s@%s" % (user, irc_channel)]
# return conns, msg, sender_strings
#
# def privmsg(self, user, irc_channel, msg):
# "Someone has written something in irc channel. Echo it to the evennia channel"
# conns, msg, sender_strings = self.get_mesg_info(user, irc_channel, msg)
# #logger.log_infomsg("<IRC: " + msg)
# for conn in conns:
# if conn.channel:
# conn.to_channel(msg, sender_strings=sender_strings)
#
# def action(self, user, irc_channel, msg):
# "Someone has performed an action, e.g. using /me <pose>"
# #find irc->evennia channel mappings
# conns = ExternalChannelConnection.objects.filter(db_external_key=self.factory.key)
# if not conns:
# return
# conns, msg, sender_strings = self.get_mesg_info(user, irc_channel, msg)
# # Transform this into a pose.
# msg = ':' + msg
# #logger.log_infomsg("<IRC: " + msg)
# for conn in conns:
# if conn.channel:
# conn.to_channel(msg, sender_strings=sender_strings)
#
# def msg_irc(self, msg, senders=None):
# """
# Called by evennia when sending something to mapped IRC channel.
#
# Note that this cannot simply be called msg() since that's the
# name of of the twisted irc hook as well, this leads to some
# initialization messages to be sent without checks, causing loops.
# """
# self.msg(utils.to_str(self.factory.channel), utils.to_str(msg))
#
#
#class IRCbotFactory(protocol.ClientFactory):
# protocol = IRC_Bot
#
# def __init__(self, key, channel, network, port, nickname, evennia_channel):
# self.key = key
# self.pretty_key = "%s:%s%s ('%s')" % (network, port, channel, nickname)
# self.network = network
# self.port = port
# self.channel = channel
# self.nickname = nickname
# self.evennia_channel = evennia_channel
#
# def clientConnectionLost(self, connector, reason):
# from twisted.internet.error import ConnectionDone
# if type(reason.type) == type(ConnectionDone):
# msg_info(_("Connection closed."))
# else:
# msg_info(_("Lost connection %(key)s. Reason: '%(reason)s'. Reconnecting.") % {"key":self.pretty_key, "reason":reason})
# connector.connect()
#
# def clientConnectionFailed(self, connector, reason):
# msg = _("Could not connect %(key)s Reason: '%(reason)s'") % {"key":self.pretty_key, "reason":reason}
# msg_info(msg)
# logger.log_errmsg(msg)
#
#
#def build_connection_key(channel, irc_network, irc_port, irc_channel, irc_bot_nick):
# "Build an id hash for the connection"
# if hasattr(channel, 'key'):
# channel = channel.key
# return "irc_%s:%s%s(%s)<>%s" % (irc_network, irc_port,
# irc_channel, irc_bot_nick, channel)
#
#
#def build_service_key(key):
# return "IRCbot:%s" % key
#
#
#def create_connection(channel, irc_network, irc_port,
# irc_channel, irc_bot_nick):
# """
# This will create a new IRC<->channel connection.
# """
# if not type(channel) == ChannelDB:
# new_channel = ChannelDB.objects.filter(db_key=channel)
# if not new_channel:
# logger.log_errmsg(_("Cannot attach IRC<->Evennia: Evennia Channel '%s' not found") % channel)
# return False
# channel = new_channel[0]
# key = build_connection_key(channel, irc_network, irc_port,
# irc_channel, irc_bot_nick)
#
# old_conns = ExternalChannelConnection.objects.filter(db_external_key=key)
# if old_conns:
# return False
# config = "%s|%s|%s|%s" % (irc_network, irc_port, irc_channel, irc_bot_nick)
# # how the channel will be able to contact this protocol
# send_code = "from src.comms.irc import IRC_CHANNELS\n"
# send_code += "matched_ircs = [irc for irc in IRC_CHANNELS if irc.factory.key == '%s']\n" % key
# send_code += "[irc.msg_irc(message, senders=[self]) for irc in matched_ircs]\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_irc(conn)
# return True
#
#def delete_connection(channel, irc_network, irc_port, irc_channel, irc_bot_nick):
# "Destroy a connection"
# if hasattr(channel, 'key'):
# channel = channel.key
#
# key = build_connection_key(channel, irc_network, irc_port, irc_channel, irc_bot_nick)
# service_key = build_service_key(key)
# try:
# conn = ExternalChannelConnection.objects.get(db_external_key=key)
# except Exception:
# return False
# conn.delete()
#
# try:
# service = SESSIONS.server.services.getServiceNamed(service_key)
# except Exception:
# return True
# if service.running:
# SESSIONS.server.services.removeService(service)
# return True
#
#def connect_to_irc(connection):
# "Create the bot instance and connect to the IRC network and channel."
# # get config
# key = utils.to_str(connection.external_key)
# service_key = build_service_key(key)
# irc_network, irc_port, irc_channel, irc_bot_nick = [utils.to_str(conf) for conf in connection.external_config.split('|')]
# # connect
# bot = internet.TCPClient(irc_network, int(irc_port), IRCbotFactory(key, irc_channel, irc_network, irc_port, irc_bot_nick,
# connection.channel.key))
# bot.setName(service_key)
# SESSIONS.server.services.addService(bot)
#
#def connect_all():
# """
# Activate all irc bots.
# """
# for connection in ExternalChannelConnection.objects.filter(db_external_key__startswith='irc_'):
# connect_to_irc(connection)
#
#

View file

@ -6,20 +6,13 @@ to the channel whenever the feed updates.
"""
import re
from twisted.internet import task, threads
from django.conf import settings
from src.comms.models import ExternalChannelConnection, ChannelDB
from src.server.session import Session
from src.utils import logger, utils
from src.utils import logger
RSS_ENABLED = settings.RSS_ENABLED
RSS_UPDATE_INTERVAL = settings.RSS_UPDATE_INTERVAL
INFOCHANNEL = ChannelDB.objects.channel_search(settings.CHANNEL_MUDINFO[0])
RETAG = re.compile(r'<[^>]*?>')
# holds rss readers they can be shut down at will.
RSS_READERS = {}
#RETAG = re.compile(r'<[^>]*?>')
if RSS_ENABLED:
try:
@ -27,7 +20,6 @@ if RSS_ENABLED:
except ImportError:
raise ImportError("RSS requires python-feedparser to be installed. Install or set RSS_ENABLED=False.")
class RSSReader(Session):
"""
A simple RSS reader using universal feedparser
@ -68,7 +60,7 @@ class RSSReader(Session):
def _errback(self, fail):
"Report error"
print "RSS feed error: %s" % fail.value
logger.log_errmsg("RSS feed error: %s" % fail.value)
def update(self, init=False):
"Request feed"
@ -93,143 +85,16 @@ class RSSBotFactory(object):
Called by portalsessionhandler
"""
def errback(fail):
print fail.value
logger.log_errmsg(fail.value)
# set up session and connect it to sessionhandler
self.bot.init_session("rssbot", self.url, self.sessionhandler)
self.bot.uid = self.uid
self.bot.logged_in = True
self.sessionhandler.connect(self.bot)
# start repeater task
#self.bot.update(init=True)
self.bot.update(init=True)
self.task = task.LoopingCall(self.bot.update)
if self.rate:
self.task.start(self.rate, now=False).addErrback(errback)
#class RSSReader(object):
# """
# Reader script used to connect to each individual RSS feed
# """
# def __init__(self, key, url, interval):
# """
# The reader needs an rss url and It also needs an interval
# for how often it is to check for new updates (defaults
# to 10 minutes)
# """
# self.key = key
# self.url = url
# self.interval = interval
# self.entries = {} # stored feeds
# self.task = None
# # first we do is to load the feed so we don't resend
# # old entries whenever the reader starts.
# self.update_feed()
# # start runner
# self.start()
#
# def update_feed(self):
# "Read the url for new updated data and determine what's new."
# feed = feedparser.parse(self.url)
# new = []
# for entry in (e for e in feed['entries'] if e['id'] not in self.entries):
# txt = "[RSS] %s: %s" % (RETAG.sub("", entry['title']),
# entry['link'].replace('\n','').encode('utf-8'))
# self.entries[entry['id']] = txt
# new.append(txt)
# return new
#
# def update(self):
# """
# Called every self.interval seconds - tries to get new feed entries,
# and if so, uses the appropriate ExternalChannelConnection to send the
# data to subscribing channels.
# """
# new = self.update_feed()
# if not new:
# return
# conns = ExternalChannelConnection.objects.filter(db_external_key=self.key)
# for conn in (conn for conn in conns if conn.channel):
# for txt in new:
# conn.to_channel("%s:%s" % (conn.channel.key, txt))
#
# def start(self):
# """
# Starting the update task and store a reference in the
# global variable so it can be found and shut down later.
# """
# global RSS_READERS
# self.task = task.LoopingCall(self.update)
# self.task.start(self.interval, now=False)
# RSS_READERS[self.key] = self
#
#
#def build_connection_key(channel, url):
# "This is used to id the connection"
# if hasattr(channel, 'key'):
# channel = channel.key
# return "rss_%s>%s" % (url, channel)
#
#
#def create_connection(channel, url, interval):
# """
# This will create a new RSS->channel connection
# """
# if not type(channel) == ChannelDB:
# new_channel = ChannelDB.objects.filter(db_key=channel)
# if not new_channel:
# logger.log_errmsg("Cannot attach RSS->Evennia: Evennia Channel '%s' not found." % channel)
# return False
# channel = new_channel[0]
# key = build_connection_key(channel, url)
# old_conns = ExternalChannelConnection.objects.filter(db_external_key=key)
# if old_conns:
# return False
# config = "%s|%i" % (url, interval)
# # There is no sendback from evennia to the rss, so we need not define
# # any sendback code.
# conn = ExternalChannelConnection(db_channel=channel,
# db_external_key=key,
# db_external_config=config)
# conn.save()
#
# connect_to_rss(conn)
# return True
#
#
#def delete_connection(channel, url):
# """
# Delete rss connection between channel and url
# """
# key = build_connection_key(channel, url)
# try:
# conn = ExternalChannelConnection.objects.get(db_external_key=key)
# except Exception:
# return False
# conn.delete()
# reader = RSS_READERS.get(key, None)
# if reader and reader.task:
# reader.task.stop()
# return True
#
#
#def connect_to_rss(connection):
# """
# Create the parser instance and connect to RSS feed and channel
# """
# global RSS_READERS
# key = utils.to_str(connection.external_key)
# url, interval = [utils.to_str(conf) for conf in connection.external_config.split('|')]
# # Create reader (this starts the running task and stores a reference in RSS_TASKS)
# RSSReader(key, url, int(interval))
#
#
#def connect_all():
# """
# Activate all rss feed parsers
# """
# if not RSS_ENABLED:
# return
# for connection in ExternalChannelConnection.objects.filter(db_external_key__startswith="rss_"):
# print "connecting RSS: %s" % connection
# connect_to_rss(connection)