diff --git a/src/players/bots.py b/src/players/bots.py index 3d567761ca..e76f7c7f8e 100644 --- a/src/players/bots.py +++ b/src/players/bots.py @@ -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) diff --git a/src/server/portal/imc2.py b/src/server/portal/imc2.py index 082ab526e7..5238012f49 100644 --- a/src/server/portal/imc2.py +++ b/src/server/portal/imc2.py @@ -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 version= - 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() diff --git a/src/comms/imc2lib/__init__.py b/src/server/portal/imc2lib/__init__.py similarity index 100% rename from src/comms/imc2lib/__init__.py rename to src/server/portal/imc2lib/__init__.py diff --git a/src/comms/imc2lib/imc2_ansi.py b/src/server/portal/imc2lib/imc2_ansi.py similarity index 100% rename from src/comms/imc2lib/imc2_ansi.py rename to src/server/portal/imc2lib/imc2_ansi.py diff --git a/src/comms/imc2lib/imc2_listeners.py b/src/server/portal/imc2lib/imc2_listeners.py similarity index 100% rename from src/comms/imc2lib/imc2_listeners.py rename to src/server/portal/imc2lib/imc2_listeners.py diff --git a/src/comms/imc2lib/imc2_packets.py b/src/server/portal/imc2lib/imc2_packets.py similarity index 100% rename from src/comms/imc2lib/imc2_packets.py rename to src/server/portal/imc2lib/imc2_packets.py diff --git a/src/comms/imc2lib/imc2_trackers.py b/src/server/portal/imc2lib/imc2_trackers.py similarity index 82% rename from src/comms/imc2lib/imc2_trackers.py rename to src/server/portal/imc2lib/imc2_trackers.py index cd053fde50..a276c54afa 100644 --- a/src/comms/imc2lib/imc2_trackers.py +++ b/src/server/portal/imc2lib/imc2_trackers.py @@ -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 diff --git a/src/server/portal/irc.py b/src/server/portal/irc.py index 396ba0dba9..7eb1e09879 100644 --- a/src/server/portal/irc.py +++ b/src/server/portal/irc.py @@ -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("" -# #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("%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) -# -# diff --git a/src/server/portal/rss.py b/src/server/portal/rss.py index 20fb22869c..534050d5f8 100644 --- a/src/server/portal/rss.py +++ b/src/server/portal/rss.py @@ -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)