Implemented untested IMC2 in the new system.
This commit is contained in:
parent
97991a2238
commit
84f5c4dca5
9 changed files with 359 additions and 742 deletions
|
|
@ -121,6 +121,8 @@ class Bot(Player):
|
||||||
|
|
||||||
# Bot implementations
|
# Bot implementations
|
||||||
|
|
||||||
|
# IRC
|
||||||
|
|
||||||
class IRCBot(Bot):
|
class IRCBot(Bot):
|
||||||
"""
|
"""
|
||||||
Bot for handling IRC connections.
|
Bot for handling IRC connections.
|
||||||
|
|
@ -194,6 +196,8 @@ class IRCBot(Bot):
|
||||||
self.ndb.ev_channel.msg(text, senders=self.dbobj.id)
|
self.ndb.ev_channel.msg(text, senders=self.dbobj.id)
|
||||||
|
|
||||||
|
|
||||||
|
# RSS
|
||||||
|
|
||||||
class RSSBot(Bot):
|
class RSSBot(Bot):
|
||||||
"""
|
"""
|
||||||
An RSS relayer. The RSS protocol itself runs a ticker to update its feed at regular
|
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:
|
if self.ndb.ev_channel:
|
||||||
self.ndb.ev_channel.msg(text, senders=self.dbobj.id)
|
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)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,213 +3,186 @@ IMC2 client module. Handles connecting to and communicating with an IMC2 server.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from time import time
|
from time import time
|
||||||
|
from twisted.internet import task
|
||||||
from twisted.application import internet
|
from twisted.application import internet
|
||||||
from twisted.internet import protocol
|
from twisted.internet import protocol
|
||||||
from twisted.conch import telnet
|
from twisted.conch import telnet
|
||||||
from django.conf import settings
|
|
||||||
|
|
||||||
from src.utils import logger, create, search, utils
|
from src.server.session import Session
|
||||||
from src.server.sessionhandler import SESSIONS
|
from src.utils import logger, utils
|
||||||
from src.scripts.scripts import Script
|
from src.server.portal.imc2lib import imc2_packets as pck
|
||||||
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 django.utils.translation import ugettext as _
|
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
|
# storage containers for IMC2 muds and channels
|
||||||
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()
|
|
||||||
|
|
||||||
#
|
class IMC2Mud(object):
|
||||||
# Helper method
|
|
||||||
#
|
|
||||||
|
|
||||||
def msg_info(message):
|
|
||||||
"""
|
"""
|
||||||
Send info to default info channel
|
Stores information about other games connected to our current IMC2 network.
|
||||||
"""
|
"""
|
||||||
try:
|
def __init__(self, packet):
|
||||||
INFOCHANNEL[0].msg(message)
|
self.name = packet.origin
|
||||||
message = '[%s][IMC2]: %s' % (INFOCHANNEL[0].key, message)
|
self.versionid = packet.optional_data.get('versionid', None)
|
||||||
except Exception:
|
self.networkname = packet.optional_data.get('networkname', None)
|
||||||
logger.log_infomsg("MUDinfo (imc2): %s" % message)
|
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
|
Keeps track of other MUDs connected to the IMC network.
|
||||||
games know that our game is still up and connected to the network. Also
|
|
||||||
provides some useful information about the client game.
|
|
||||||
"""
|
"""
|
||||||
def at_script_creation(self):
|
def get_mud_list(self):
|
||||||
self.key = 'IMC2_Send_IsAlive'
|
"""
|
||||||
self.interval = 900
|
Returns a sorted list of connected Muds.
|
||||||
self.desc = _("Send an IMC2 is-alive packet")
|
"""
|
||||||
self.persistent = True
|
muds = self.items()
|
||||||
|
muds.sort()
|
||||||
|
return [value for key, value in muds]
|
||||||
|
|
||||||
def at_repeat(self):
|
def update_mud_from_packet(self, packet):
|
||||||
IMC2_CLIENT.send_packet(pck.IMC2PacketIsAlive())
|
"""
|
||||||
|
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):
|
def remove_mud_from_packet(self, packet):
|
||||||
"Is only valid as long as there are channels to update"
|
"""
|
||||||
return any(service for service in SESSIONS.server.services
|
Removes a mud from the Mud list when given a packet.
|
||||||
if service.name.startswith("imc2_"))
|
"""
|
||||||
|
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
|
Stores information about channels available on the network.
|
||||||
is connected.
|
|
||||||
"""
|
"""
|
||||||
def at_script_creation(self):
|
def __init__(self, packet):
|
||||||
self.key = "IMC2_Send_Keepalive_Request"
|
self.localname = packet.optional_data.get('localname', None)
|
||||||
self.interval = 3500
|
self.name = packet.optional_data.get('channel', None)
|
||||||
self.desc = _("Send an IMC2 keepalive-request packet")
|
self.level = packet.optional_data.get('level', None)
|
||||||
self.persistent = True
|
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):
|
class IMC2ChanList(dict):
|
||||||
"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):
|
|
||||||
"""
|
"""
|
||||||
Prunes games that have not sent is-alive packets for a while. If
|
Keeps track of Channels on the IMC network.
|
||||||
we haven't heard from them, they're probably not connected or don't
|
|
||||||
implement the protocol correctly. In either case, good riddance to them.
|
|
||||||
"""
|
"""
|
||||||
def 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):
|
def get_channel_list(self):
|
||||||
for name, mudinfo in IMC2_MUDLIST.mud_list.items():
|
"""
|
||||||
if time() - mudinfo.last_updated > self.inactive_threshold:
|
Returns a sorted list of cached channels.
|
||||||
del IMC2_MUDLIST.mud_list[name]
|
"""
|
||||||
|
channels = self.items()
|
||||||
|
channels.sort()
|
||||||
|
return [value for key, value in channels]
|
||||||
|
|
||||||
def is_valid(self):
|
def update_channel_from_packet(self, packet):
|
||||||
"Is only valid as long as there are channels to update"
|
"""
|
||||||
return any(service for service in SESSIONS.server.services
|
This grabs relevant info from the packet and stuffs it in the
|
||||||
if service.name.startswith("imc2_"))
|
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
|
# IMC2 protocol
|
||||||
#
|
#
|
||||||
class IMC2Protocol(telnet.StatefulTelnetProtocol):
|
class IMC2Bot(telnet.StatefulTelnetProtocol, Session):
|
||||||
"""
|
"""
|
||||||
Provides the abstraction for the IMC2 protocol. Handles connection,
|
Provides the abstraction for the IMC2 protocol. Handles connection,
|
||||||
authentication, and all necessary packets.
|
authentication, and all necessary packets.
|
||||||
"""
|
"""
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
global IMC2_CLIENT
|
|
||||||
IMC2_CLIENT = self
|
|
||||||
self.is_authenticated = False
|
self.is_authenticated = False
|
||||||
self.auth_type = None
|
# only support plaintext passwords
|
||||||
self.server_name = None
|
|
||||||
self.network_name = None
|
|
||||||
self.sequence = None
|
|
||||||
|
|
||||||
def connectionMade(self):
|
|
||||||
"""
|
|
||||||
Triggered after connecting to the IMC2 network.
|
|
||||||
"""
|
|
||||||
self.auth_type = "plaintext"
|
self.auth_type = "plaintext"
|
||||||
if IMC2_DEBUG:
|
self.sequence = None
|
||||||
logger.log_infomsg("IMC2: Connected to network server.")
|
self.imc2_mudlist = IMC2MudList()
|
||||||
logger.log_infomsg("IMC2: Sending authentication packet.")
|
self.imc2_chanlist = IMC2ChanList()
|
||||||
self.send_packet(pck.IMC2PacketAuthPlaintext())
|
|
||||||
|
|
||||||
def connectionLost(self, reason=None):
|
def _send_packet(self, packet):
|
||||||
"""
|
"Helper function to send packets across the wire"
|
||||||
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
|
|
||||||
packet.imc2_protocol = self
|
packet.imc2_protocol = self
|
||||||
packet_str = utils.to_str(packet.assemble(self.factory.mudname,
|
packet_str = utils.to_str(packet.assemble(self.factory.mudname,
|
||||||
self.factory.client_pwd, self.factory.server_pwd))
|
self.factory.client_pwd, self.factory.server_pwd))
|
||||||
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.sendLine(packet_str)
|
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":
|
if self.auth_type == "plaintext":
|
||||||
# Plain text passwords.
|
# Only support Plain text passwords.
|
||||||
# SERVER Sends: PW <servername> <serverpw> version=<version#> <networkname>
|
# 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(' ')
|
line_split = line.split(' ')
|
||||||
pw_present = line_split[0] == 'PW'
|
pw_present = line_split[0] == 'PW'
|
||||||
|
|
@ -218,7 +191,6 @@ class IMC2Protocol(telnet.StatefulTelnetProtocol):
|
||||||
if "reject" in line_split:
|
if "reject" in line_split:
|
||||||
auth_message = _("IMC2 server rejected connection.")
|
auth_message = _("IMC2 server rejected connection.")
|
||||||
logger.log_infomsg(auth_message)
|
logger.log_infomsg(auth_message)
|
||||||
msg_info(auth_message)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
if pw_present:
|
if pw_present:
|
||||||
|
|
@ -232,266 +204,193 @@ class IMC2Protocol(telnet.StatefulTelnetProtocol):
|
||||||
self.sequence = int(time())
|
self.sequence = int(time())
|
||||||
|
|
||||||
# Log to stdout and notify over MUDInfo.
|
# Log to stdout and notify over MUDInfo.
|
||||||
auth_message = _("Successfully authenticated to the '%s' network.") % self.factory.network
|
logger.log_infomsg('IMC2: Authenticated to %s' % self.factory.network)
|
||||||
logger.log_infomsg('IMC2: %s' % auth_message)
|
|
||||||
msg_info(auth_message)
|
|
||||||
|
|
||||||
# Ask to see what other MUDs are connected.
|
# 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
|
# IMC2 protocol states that KeepAliveRequests should be followed
|
||||||
# up by the requester sending an IsAlive packet.
|
# up by the requester sending an IsAlive packet.
|
||||||
self.send_packet(pck.IMC2PacketIsAlive())
|
self._send_packet(pck.IMC2PacketIsAlive())
|
||||||
# Get a listing of channels.
|
# 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
|
Triggered after connecting to the IMC2 network.
|
||||||
(Message from IMC2 -> Evennia)
|
|
||||||
"""
|
"""
|
||||||
conn_name = packet.optional_data.get('channel', None)
|
|
||||||
|
|
||||||
# If the packet lacks the 'echo' key, don't bother with it.
|
self.stopping = False
|
||||||
if not conn_name or not packet.optional_data.get('echo', None):
|
self.factory.bot = self
|
||||||
return
|
address = "%s@%s" % (self.mudname, self.network)
|
||||||
imc2_channel = conn_name.split(':', 1)[1]
|
self.init_session("ircbot", address, self.factory.sessionhandler)
|
||||||
|
# link back and log in
|
||||||
# Look for matching IMC2 channel maps mapping to this imc2 channel.
|
self.uid = int(self.factory.uid)
|
||||||
conns = ExternalChannelConnection.objects.filter(db_external_key__startswith="imc2_")
|
self.logged_in = True
|
||||||
conns = [conn for conn in conns if imc2_channel in conn.db_external_config.split(",")]
|
self.factory.sessionhandler.connect(self)
|
||||||
if not conns:
|
logger.log_infomsg("IMC2 bot connected to %s." % self.network)
|
||||||
# we are not listening to this imc2 channel.
|
# Send authentication packet. The reply will be caught by lineReceived
|
||||||
return
|
self._send_packet(pck.IMC2PacketAuthPlaintext())
|
||||||
|
|
||||||
# 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.')}
|
|
||||||
|
|
||||||
def lineReceived(self, line):
|
def lineReceived(self, line):
|
||||||
"""
|
"""
|
||||||
Triggered when text is received from the IMC2 network. Figures out
|
|
||||||
what to do with the packet.
|
|
||||||
IMC2 -> Evennia
|
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()
|
line = line.strip()
|
||||||
|
|
||||||
if not self.is_authenticated:
|
if not self.is_authenticated:
|
||||||
self._parse_auth_response(line)
|
# we are not authenticated yet. Deal with this.
|
||||||
else:
|
self._imc_login(line)
|
||||||
if IMC2_DEBUG and not 'is-alive' in line:
|
return
|
||||||
# if IMC2_DEBUG mode is on, print the contents of the packet
|
|
||||||
# to stdout.
|
|
||||||
logger.log_infomsg("IMC2: RECV> %s" % line)
|
|
||||||
|
|
||||||
# Parse the packet and encapsulate it for easy access
|
#logger.log_infomsg("IMC2: RECV> %s" % line)
|
||||||
packet = pck.IMC2Packet(self.factory.mudname, packet_str=line)
|
|
||||||
|
|
||||||
if IMC2_DEBUG and packet.packet_type not in ('is-alive', 'keepalive-request'):
|
# Parse the packet and encapsulate it for easy access
|
||||||
# Print the parsed packet's __str__ representation.
|
packet = pck.IMC2Packet(self.mudname, packet_str=line)
|
||||||
# is-alive and keepalive-requests happen pretty frequently.
|
|
||||||
# Don't bore us with them in stdout.
|
|
||||||
logger.log_infomsg(str(packet))
|
|
||||||
|
|
||||||
# Figure out what kind of packet we're dealing with and hand it
|
# Figure out what kind of packet we're dealing with and hand it
|
||||||
# off to the correct handler.
|
# off to the correct handler.
|
||||||
|
|
||||||
if packet.packet_type == 'is-alive':
|
if packet.packet_type == 'is-alive':
|
||||||
IMC2_MUDLIST.update_mud_from_packet(packet)
|
self.imc2_mudlist.update_mud_from_packet(packet)
|
||||||
elif packet.packet_type == 'keepalive-request':
|
elif packet.packet_type == 'keepalive-request':
|
||||||
# Don't need to check the destination, we only receive these
|
# Don't need to check the destination, we only receive these
|
||||||
# packets when they are intended for us.
|
# packets when they are intended for us.
|
||||||
self.send_packet(pck.IMC2PacketIsAlive())
|
self.send_packet(pck.IMC2PacketIsAlive())
|
||||||
elif packet.packet_type == 'ice-msg-b':
|
elif packet.packet_type == 'ice-msg-b':
|
||||||
self._msg_evennia(packet)
|
self.data_out(text=line, packettype="broadcast")
|
||||||
elif packet.packet_type == 'whois-reply':
|
elif packet.packet_type == 'whois-reply':
|
||||||
handle_whois_reply(packet)
|
# handle eventual whois reply
|
||||||
elif packet.packet_type == 'close-notify':
|
elif packet.packet_type == 'close-notify':
|
||||||
IMC2_MUDLIST.remove_mud_from_packet(packet)
|
self.imc2_mudlist.remove_mud_from_packet(packet)
|
||||||
elif packet.packet_type == 'ice-update':
|
elif packet.packet_type == 'ice-update':
|
||||||
IMC2_CHANLIST.update_channel_from_packet(packet)
|
self.imc2_chanlist.update_channel_from_packet(packet)
|
||||||
elif packet.packet_type == 'ice-destroy':
|
elif packet.packet_type == 'ice-destroy':
|
||||||
IMC2_CHANLIST.remove_channel_from_packet(packet)
|
self.imc2_chanlist.remove_channel_from_packet(packet)
|
||||||
elif packet.packet_type == 'tell':
|
elif packet.packet_type == 'tell':
|
||||||
player = search.players(packet.target)
|
# send message to identified player
|
||||||
if not player:
|
pass
|
||||||
return
|
|
||||||
player[0].msg(self._format_tell(packet))
|
|
||||||
|
|
||||||
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:
|
self.sessionhandler.data_in(self, text=text, **kwargs)
|
||||||
if hasattr(from_obj, 'key'):
|
|
||||||
from_name = from_obj.key
|
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:
|
else:
|
||||||
from_name = from_obj
|
return
|
||||||
else:
|
|
||||||
from_name = self.factory.mudname
|
|
||||||
|
|
||||||
if packet_type == "imcbroadcast":
|
# we remove the extra channel info since imc2 supplies this anyway
|
||||||
if type(data) == dict:
|
if ":" in text:
|
||||||
conns = ExternalChannelConnection.objects.filter(db_external_key__startswith="imc2_",
|
header, message = [part.strip() for part in text.split(":", 1)]
|
||||||
db_channel=data.get("channel", "Unknown"))
|
# Create imc2packet and send it
|
||||||
if not conns:
|
self._send_packet(pck.IMC2PacketIceMsgBroadcasted(self.servername,
|
||||||
return
|
self.channel,
|
||||||
# we remove the extra channel info since imc2 supplies this anyway
|
header, text))
|
||||||
if ":" in message:
|
elif packet_type == "tell":
|
||||||
header, message = [part.strip() for part in message.split(":", 1)]
|
# send an IMC2 tell
|
||||||
# send the packet
|
sender = kwargs.get("sender", self.mudname)
|
||||||
imc2_channel = conns[0].db_external_config.split(',')[0] # only send to the first channel
|
target = kwargs.get("target", "Unknown")
|
||||||
self.send_packet(pck.IMC2PacketIceMsgBroadcasted(self.factory.servername, imc2_channel,
|
destination = kwargs.get("destination", "Unknown")
|
||||||
from_name, message))
|
self._send_packet(pck.IMC2PacketTell(sender, target, destination, text))
|
||||||
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))
|
|
||||||
|
|
||||||
elif packet_type == "imcwhois":
|
elif packet_type == "whois":
|
||||||
# send a whois request
|
# send a whois request
|
||||||
if type(data) == dict:
|
sender = kwargs.get("sender", self.mudname)
|
||||||
target = data.get("target", "Unknown")
|
target = kwargs.get("target", "Unknown")
|
||||||
self.send_packet(pck.IMC2PacketWhois(from_obj.id, target))
|
self._send_packet(pck.IMC2PacketWhois(sender, target))
|
||||||
|
|
||||||
|
|
||||||
class IMC2Factory(protocol.ClientFactory):
|
class IMC2BotFactory(protocol.ReconnectingClientFactory):
|
||||||
"""
|
"""
|
||||||
Creates instances of the IMC2Protocol. Should really only ever
|
Creates instances of the IMC2Protocol. Should really only ever
|
||||||
need to create one connection. Tied in via src/server.py.
|
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):
|
def __init__(self, sessionhandler, uid=None, network=None, channel=None,
|
||||||
self.pretty_key = "%s:%s(%s)" % (network, port, mudname)
|
port=None, mudname=None, client_pwd=None, server_pwd=None):
|
||||||
|
self.uid = uid
|
||||||
self.network = network
|
self.network = network
|
||||||
sname, host = network.split(".", 1)
|
sname, host = network.split(".", 1)
|
||||||
self.servername = sname.strip()
|
self.servername = sname.strip()
|
||||||
|
self.channel = channel
|
||||||
self.port = port
|
self.port = port
|
||||||
self.mudname = mudname
|
self.mudname = mudname
|
||||||
self.protocol_version = '2'
|
self.protocol_version = '2'
|
||||||
self.client_pwd = client_pwd
|
self.client_pwd = client_pwd
|
||||||
self.server_pwd = server_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):
|
def clientConnectionFailed(self, connector, reason):
|
||||||
message = _('Connection failed: %s') % reason.getErrorMessage()
|
self.retry(connector)
|
||||||
msg_info(message)
|
|
||||||
logger.log_errmsg('IMC2: %s' % message)
|
|
||||||
|
|
||||||
def clientConnectionLost(self, connector, reason):
|
def clientConnectionLost(self, connector, reason):
|
||||||
message = _('Connection lost: %s') % reason.getErrorMessage()
|
if not self.bot.stopping:
|
||||||
msg_info(message)
|
self.retry(connector)
|
||||||
logger.log_errmsg('IMC2: %s' % message)
|
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
"Connect session to sessionhandler"
|
||||||
|
def errback(fail):
|
||||||
|
logger.log_errmsg(fail.value)
|
||||||
|
|
||||||
def build_connection_key(channel, imc2_channel):
|
if self.port:
|
||||||
"Build an id hash for the connection"
|
service = internet.TCPClient(self.network, int(self.port), self)
|
||||||
if hasattr(channel, "key"):
|
self.sessionhandler.portal.services.addService(service)
|
||||||
channel = channel.key
|
# start tasks
|
||||||
return "imc2_%s:%s(%s)%s<>%s" % (IMC2_NETWORK, IMC2_PORT,
|
self.task_isalive = task.LoopingCall(self.bot._isalive)
|
||||||
IMC2_MUDNAME, imc2_channel, channel)
|
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()
|
|
||||||
|
|
|
||||||
|
|
@ -23,19 +23,15 @@ class IMC2Mud(object):
|
||||||
self.last_updated = time()
|
self.last_updated = time()
|
||||||
|
|
||||||
|
|
||||||
class IMC2MudList(object):
|
class IMC2MudList(dict):
|
||||||
"""
|
"""
|
||||||
Keeps track of other MUDs connected to the IMC network.
|
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):
|
def get_mud_list(self):
|
||||||
"""
|
"""
|
||||||
Returns a sorted list of connected Muds.
|
Returns a sorted list of connected Muds.
|
||||||
"""
|
"""
|
||||||
muds = self.mud_list.items()
|
muds = self.items()
|
||||||
muds.sort()
|
muds.sort()
|
||||||
return [value for key, value in muds]
|
return [value for key, value in muds]
|
||||||
|
|
||||||
|
|
@ -45,7 +41,7 @@ class IMC2MudList(object):
|
||||||
Mud list for later retrieval.
|
Mud list for later retrieval.
|
||||||
"""
|
"""
|
||||||
mud = IMC2Mud(packet)
|
mud = IMC2Mud(packet)
|
||||||
self.mud_list[mud.name] = mud
|
self[mud.name] = mud
|
||||||
|
|
||||||
def remove_mud_from_packet(self, packet):
|
def remove_mud_from_packet(self, packet):
|
||||||
"""
|
"""
|
||||||
|
|
@ -53,7 +49,7 @@ class IMC2MudList(object):
|
||||||
"""
|
"""
|
||||||
mud = IMC2Mud(packet)
|
mud = IMC2Mud(packet)
|
||||||
try:
|
try:
|
||||||
del self.mud_list[mud.name]
|
del self[mud.name]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
# No matching entry, no big deal.
|
# No matching entry, no big deal.
|
||||||
pass
|
pass
|
||||||
|
|
@ -72,19 +68,16 @@ class IMC2Channel(object):
|
||||||
self.last_updated = time()
|
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):
|
def get_channel_list(self):
|
||||||
"""
|
"""
|
||||||
Returns a sorted list of cached channels.
|
Returns a sorted list of cached channels.
|
||||||
"""
|
"""
|
||||||
channels = self.chan_list.items()
|
channels = self.items()
|
||||||
channels.sort()
|
channels.sort()
|
||||||
return [value for key, value in channels]
|
return [value for key, value in channels]
|
||||||
|
|
||||||
|
|
@ -94,7 +87,7 @@ class IMC2ChanList(object):
|
||||||
channel list for later retrieval.
|
channel list for later retrieval.
|
||||||
"""
|
"""
|
||||||
channel = IMC2Channel(packet)
|
channel = IMC2Channel(packet)
|
||||||
self.chan_list[channel.name] = channel
|
self[channel.name] = channel
|
||||||
|
|
||||||
def remove_channel_from_packet(self, packet):
|
def remove_channel_from_packet(self, packet):
|
||||||
"""
|
"""
|
||||||
|
|
@ -102,7 +95,7 @@ class IMC2ChanList(object):
|
||||||
"""
|
"""
|
||||||
channel = IMC2Channel(packet)
|
channel = IMC2Channel(packet)
|
||||||
try:
|
try:
|
||||||
del self.chan_list[channel.name]
|
del self[channel.name]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
# No matching entry, no big deal.
|
# No matching entry, no big deal.
|
||||||
pass
|
pass
|
||||||
|
|
@ -123,218 +123,3 @@ class IRCBotFactory(protocol.ReconnectingClientFactory):
|
||||||
if self.port:
|
if self.port:
|
||||||
service = internet.TCPClient(self.network, int(self.port), self)
|
service = internet.TCPClient(self.network, int(self.port), self)
|
||||||
self.sessionhandler.portal.services.addService(service)
|
self.sessionhandler.portal.services.addService(service)
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
#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)
|
|
||||||
#
|
|
||||||
#
|
|
||||||
|
|
|
||||||
|
|
@ -6,20 +6,13 @@ to the channel whenever the feed updates.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import re
|
|
||||||
from twisted.internet import task, threads
|
from twisted.internet import task, threads
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from src.comms.models import ExternalChannelConnection, ChannelDB
|
|
||||||
from src.server.session import Session
|
from src.server.session import Session
|
||||||
from src.utils import logger, utils
|
from src.utils import logger
|
||||||
|
|
||||||
RSS_ENABLED = settings.RSS_ENABLED
|
RSS_ENABLED = settings.RSS_ENABLED
|
||||||
RSS_UPDATE_INTERVAL = settings.RSS_UPDATE_INTERVAL
|
#RETAG = re.compile(r'<[^>]*?>')
|
||||||
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 = {}
|
|
||||||
|
|
||||||
if RSS_ENABLED:
|
if RSS_ENABLED:
|
||||||
try:
|
try:
|
||||||
|
|
@ -27,7 +20,6 @@ if RSS_ENABLED:
|
||||||
except ImportError:
|
except ImportError:
|
||||||
raise ImportError("RSS requires python-feedparser to be installed. Install or set RSS_ENABLED=False.")
|
raise ImportError("RSS requires python-feedparser to be installed. Install or set RSS_ENABLED=False.")
|
||||||
|
|
||||||
|
|
||||||
class RSSReader(Session):
|
class RSSReader(Session):
|
||||||
"""
|
"""
|
||||||
A simple RSS reader using universal feedparser
|
A simple RSS reader using universal feedparser
|
||||||
|
|
@ -68,7 +60,7 @@ class RSSReader(Session):
|
||||||
|
|
||||||
def _errback(self, fail):
|
def _errback(self, fail):
|
||||||
"Report error"
|
"Report error"
|
||||||
print "RSS feed error: %s" % fail.value
|
logger.log_errmsg("RSS feed error: %s" % fail.value)
|
||||||
|
|
||||||
def update(self, init=False):
|
def update(self, init=False):
|
||||||
"Request feed"
|
"Request feed"
|
||||||
|
|
@ -93,143 +85,16 @@ class RSSBotFactory(object):
|
||||||
Called by portalsessionhandler
|
Called by portalsessionhandler
|
||||||
"""
|
"""
|
||||||
def errback(fail):
|
def errback(fail):
|
||||||
print fail.value
|
logger.log_errmsg(fail.value)
|
||||||
|
|
||||||
# set up session and connect it to sessionhandler
|
# set up session and connect it to sessionhandler
|
||||||
self.bot.init_session("rssbot", self.url, self.sessionhandler)
|
self.bot.init_session("rssbot", self.url, self.sessionhandler)
|
||||||
self.bot.uid = self.uid
|
self.bot.uid = self.uid
|
||||||
self.bot.logged_in = True
|
self.bot.logged_in = True
|
||||||
self.sessionhandler.connect(self.bot)
|
self.sessionhandler.connect(self.bot)
|
||||||
|
|
||||||
# start repeater task
|
# start repeater task
|
||||||
#self.bot.update(init=True)
|
|
||||||
self.bot.update(init=True)
|
self.bot.update(init=True)
|
||||||
self.task = task.LoopingCall(self.bot.update)
|
self.task = task.LoopingCall(self.bot.update)
|
||||||
if self.rate:
|
if self.rate:
|
||||||
self.task.start(self.rate, now=False).addErrback(errback)
|
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)
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue