Added the command/irc.py directory that was not committed properly.

Added some more helper commands and changed a bit under the hood on those
previously committed for mapping irc/imc channels to each other.

There seems to be an issue with IMC2 users seeing an echo back to themselves when talking. Investigating.
/Griatch
This commit is contained in:
Griatch 2009-08-28 18:13:07 +00:00
parent 5f58c4384b
commit 72e55f417a
3 changed files with 314 additions and 212 deletions

View file

@ -122,7 +122,7 @@ GLOBAL_CMD_TABLE.add_command("imcstatus", cmd_imcstatus)
def cmd_IMC2chan(command): def cmd_IMC2chan(command):
""" """
@imc2chan IMCchannel channel @imc2chan IMCServer:IMCchannel channel
Links an IMC channel to an existing Links an IMC channel to an existing
evennia channel. You can link as many existing evennia channel. You can link as many existing
@ -130,7 +130,8 @@ def cmd_IMC2chan(command):
IMC channel this way. Running the command with an IMC channel this way. Running the command with an
existing mapping will re-map the channels. existing mapping will re-map the channels.
Use 'imcchanlist' to get a list of IMC channels. Use 'imcchanlist' to get a list of IMC channels and servers.
Note that both are case sensitive.
""" """
source_object = command.source_object source_object = command.source_object
if not settings.IMC2_ENABLED: if not settings.IMC2_ENABLED:
@ -139,16 +140,22 @@ def cmd_IMC2chan(command):
return return
args = command.command_argument args = command.command_argument
if not args or len(args.split()) != 2 : if not args or len(args.split()) != 2 :
source_object.emit_to("Usage: @imc2chan IMCchannel channel") source_object.emit_to("Usage: @imc2chan IMCServer:IMCchannel channel")
return return
imc_channel, channel = args.split() #identify the server-channel pair
imclist = IMC2_CHANLIST.get_channel_list() imcdata, channel = args.split()
if imc_channel not in [c.localname for c in imclist]: if not ":" in imcdata:
source_object.emit_to("IMC channel '%s' not found." % imc_channel) source_object.emit_to("You need to supply an IMC Server:Channel pair.")
return
imclist = IMC2_CHANLIST.get_channel_list()
imc_channels = filter(lambda c: c.name == imcdata, imclist)
if not imc_channels:
source_object.emit_to("IMC server and channel '%s' not found." % imcdata)
return return
else: else:
imc_channel = filter(lambda c: c.localname==imc_channel,imclist) imc_server_name, imc_channel_name = imcdata.split(":")
if imc_channel: imc_channel = imc_channel[0]
#find evennia channel
try: try:
chanobj = comsys.get_cobj_from_name(channel) chanobj = comsys.get_cobj_from_name(channel)
except CommChannel.DoesNotExist: except CommChannel.DoesNotExist:
@ -163,11 +170,9 @@ def cmd_IMC2chan(command):
outstring = "Replacing %s. New " % mapping outstring = "Replacing %s. New " % mapping
else: else:
mapping = IMC2ChannelMapping() mapping = IMC2ChannelMapping()
server,name = imc_channel.name.split(":")
mapping.imc2_server_name = server.strip() #settings.IMC2_SERVER_ADDRESS mapping.imc2_server_name = imc_server_name
mapping.imc2_channel_name = name.strip() #imc_channel.name mapping.imc2_channel_name = imc_channel_name
mapping.channel = chanobj mapping.channel = chanobj
mapping.save() mapping.save()
outstring += "Mapping set: %s." % mapping outstring += "Mapping set: %s." % mapping

97
src/commands/irc.py Normal file
View file

@ -0,0 +1,97 @@
"""
IRC-related functions
"""
from django.conf import settings
from src.irc.connection import IRC_CHANNELS
from src.irc.connection import connect_to_IRC
from src.irc.models import IRCChannelMapping
from src import comsys
from src.cmdtable import GLOBAL_CMD_TABLE
def cmd_IRC2chan(command):
"""
@irc2chan IRCchannel channel
Links an IRC channel (including #) to an existing
evennia channel. You can link as many existing
evennia channels as you like to the
IRC channel this way. Running the command with an
existing mapping will re-map the channels.
"""
source_object = command.source_object
if not settings.IRC_ENABLED:
s = """IRC is not enabled. You need to activate it in game/settings.py."""
source_object.emit_to(s)
return
args = command.command_argument
if not args or len(args.split()) != 2 :
source_object.emit_to("Usage: @irc2chan IRCchannel channel")
return
irc_channel, channel = args.split()
if irc_channel not in [o.factory.channel for o in IRC_CHANNELS]:
source_object.emit_to("IRC channel '%s' not found." % irc_channel)
return
try:
chanobj = comsys.get_cobj_from_name(channel)
except CommChannel.DoesNotExist:
source_object.emit_to("Local channel '%s' not found (use real name, not alias)." % channel)
return
#create the mapping.
outstring = ""
mapping = IRCChannelMapping.objects.filter(channel__name=channel)
if mapping:
mapping = mapping[0]
outstring = "Replacing %s. New " % mapping
else:
mapping = IRCChannelMapping()
mapping.irc_server_name = settings.IRC_NETWORK
mapping.irc_channel_name = irc_channel
mapping.channel = chanobj
mapping.save()
outstring += "Mapping set: %s." % mapping
source_object.emit_to(outstring)
GLOBAL_CMD_TABLE.add_command("@irc2chan",cmd_IRC2chan,auto_help=True,staff_help=True,
priv_tuple=("objects.add_commchannel",))
def cmd_IRCjoin(command):
"""
@ircjoin IRCchannel
Attempts to connect a bot to a new IRC channel (don't forget that
IRC channels begin with a #).
The bot uses the connection details defined in the main settings.
Observe that channels added using this command does not survive a reboot.
"""
source_object = command.source_object
arg = command.command_argument
if not arg:
source_object.emit_to("Usage: @ircjoin irc_channel")
return
channel = arg.strip()
if channel[0] != "#": channel = "#%s" % channel
connect_to_IRC(settings.IRC_NETWORK,
settings.IRC_PORT,
channel,settings.IRC_NICKNAME)
GLOBAL_CMD_TABLE.add_command("@ircjoin",cmd_IRCjoin,auto_help=True,
staff_help=True,
priv_tuple=("objects.add_commchannel",))
def cmd_IRCchanlist(command):
"""
ircchanlist
Lists all externally available IRC channels.
"""
source_object = command.source_object
s = "Available IRC channels:"
for c in IRC_CHANNELS:
s += "\n %s \t(nick '%s') on %s" % (c.factory.channel,c.factory.nickname,c.factory.network,)
source_object.emit_to(s)
GLOBAL_CMD_TABLE.add_command("ircchanlist", cmd_IRCchanlist, auto_help=True)

View file

@ -1,199 +1,199 @@
""" """
IMC2 client module. Handles connecting to and communicating with an IMC2 server. IMC2 client module. Handles connecting to and communicating with an IMC2 server.
""" """
import telnetlib import telnetlib
from time import time from time import time
from twisted.internet.protocol import ClientFactory from twisted.internet.protocol import ClientFactory
from twisted.protocols.basic import LineReceiver from twisted.protocols.basic import LineReceiver
from twisted.internet import reactor, task from twisted.internet import reactor, task
from twisted.conch.telnet import StatefulTelnetProtocol from twisted.conch.telnet import StatefulTelnetProtocol
from django.conf import settings from django.conf import settings
from src import logger from src import logger
from src import session_mgr from src import session_mgr
from src.imc2.packets import * from src.imc2.packets import *
from src.imc2.trackers import * from src.imc2.trackers import *
from src.imc2 import reply_listener from src.imc2 import reply_listener
from src.imc2.models import IMC2ChannelMapping from src.imc2.models import IMC2ChannelMapping
from src import comsys from src import comsys
# The active instance of IMC2Protocol. Set at server startup. # The active instance of IMC2Protocol. Set at server startup.
IMC2_PROTOCOL_INSTANCE = None IMC2_PROTOCOL_INSTANCE = None
def cemit_info(message): def cemit_info(message):
""" """
Channel emits info to the appropriate info channel. By default, this Channel emits info to the appropriate info channel. By default, this
is MUDInfo. is MUDInfo.
""" """
comsys.send_cmessage(settings.COMMCHAN_IMC2_INFO, 'IMC: %s' % message) comsys.send_cmessage(settings.COMMCHAN_IMC2_INFO, 'IMC: %s' % message,from_external="IMC2")
class IMC2Protocol(StatefulTelnetProtocol): class IMC2Protocol(StatefulTelnetProtocol):
""" """
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):
message = "Client connecting to %s:%s..." % ( message = "Client connecting to %s:%s..." % (
settings.IMC2_SERVER_ADDRESS, settings.IMC2_SERVER_ADDRESS,
settings.IMC2_SERVER_PORT) settings.IMC2_SERVER_PORT)
logger.log_infomsg('IMC2: %s' % message) logger.log_infomsg('IMC2: %s' % message)
cemit_info(message) cemit_info(message)
global IMC2_PROTOCOL_INSTANCE global IMC2_PROTOCOL_INSTANCE
IMC2_PROTOCOL_INSTANCE = self IMC2_PROTOCOL_INSTANCE = self
self.is_authenticated = False self.is_authenticated = False
self.auth_type = None self.auth_type = None
self.server_name = None self.server_name = None
self.network_name = None self.network_name = None
self.sequence = None self.sequence = None
def connectionMade(self): def connectionMade(self):
""" """
Triggered after connecting to the IMC2 network. Triggered after connecting to the IMC2 network.
""" """
logger.log_infomsg("IMC2: Connected to network server.") logger.log_infomsg("IMC2: Connected to network server.")
self.auth_type = "plaintext" self.auth_type = "plaintext"
logger.log_infomsg("IMC2: Sending authentication packet.") logger.log_infomsg("IMC2: Sending authentication packet.")
self.send_packet(IMC2PacketAuthPlaintext()) self.send_packet(IMC2PacketAuthPlaintext())
def send_packet(self, packet): def send_packet(self, packet):
""" """
Given a sub-class of IMC2Packet, assemble the packet and send it Given a sub-class of IMC2Packet, assemble the packet and send it
on its way. on its way.
""" """
if self.sequence: if self.sequence:
# This gets incremented with every command. # This gets incremented with every command.
self.sequence += 1 self.sequence += 1
packet.imc2_protocol = self packet.imc2_protocol = self
packet_str = str(packet.assemble()) packet_str = str(packet.assemble())
logger.log_infomsg("IMC2: SENT> %s" % packet_str) logger.log_infomsg("IMC2: SENT> %s" % packet_str)
self.sendLine(packet_str) self.sendLine(packet_str)
def _parse_auth_response(self, line): def _parse_auth_response(self, line):
""" """
Parses the IMC2 network authentication packet. Parses the IMC2 network authentication packet.
""" """
if self.auth_type == "plaintext": if self.auth_type == "plaintext":
""" """
SERVER Sends: PW <servername> <serverpw> version=<version#> <networkname> SERVER Sends: PW <servername> <serverpw> version=<version#> <networkname>
""" """
line_split = line.split(' ') line_split = line.split(' ')
pw_present = line_split[0] == 'PW' pw_present = line_split[0] == 'PW'
autosetup_present = line_split[0] == 'autosetup' autosetup_present = line_split[0] == 'autosetup'
if pw_present: if pw_present:
self.server_name = line_split[1] self.server_name = line_split[1]
self.network_name = line_split[4] self.network_name = line_split[4]
elif autosetup_present: elif autosetup_present:
logger.log_infomsg("IMC2: Autosetup response found.") logger.log_infomsg("IMC2: Autosetup response found.")
self.server_name = line_split[1] self.server_name = line_split[1]
self.network_name = line_split[3] self.network_name = line_split[3]
self.is_authenticated = True self.is_authenticated = True
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.network_name auth_message = "Successfully authenticated to the '%s' network." % self.network_name
logger.log_infomsg('IMC2: %s' % auth_message) logger.log_infomsg('IMC2: %s' % auth_message)
cemit_info(auth_message) cemit_info(auth_message)
# Ask to see what other MUDs are connected. # Ask to see what other MUDs are connected.
self.send_packet(IMC2PacketKeepAliveRequest()) self.send_packet(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(IMC2PacketIsAlive()) self.send_packet(IMC2PacketIsAlive())
# Get a listing of channels. # Get a listing of channels.
self.send_packet(IMC2PacketIceRefresh()) self.send_packet(IMC2PacketIceRefresh())
def _handle_channel_mappings(self, packet): def _handle_channel_mappings(self, packet):
""" """
Received a message. Look for an IMC2 channel mapping and Received a message. Look for an IMC2 channel mapping and
route it accordingly. route it accordingly.
""" """
chan_name = packet.optional_data.get('channel', None) chan_name = packet.optional_data.get('channel', None)
# If the packet lacks the 'echo' key, don't bother with it. # If the packet lacks the 'echo' key, don't bother with it.
has_echo = packet.optional_data.get('echo', None) has_echo = packet.optional_data.get('echo', None)
if chan_name and has_echo: if chan_name and has_echo:
# The second half of this is the channel name: Server:Channel # The second half of this is the channel name: Server:Channel
chan_name = chan_name.split(':', 1)[1] chan_name = chan_name.split(':', 1)[1]
try: try:
# Look for matching IMC2 channel maps. # Look for matching IMC2 channel maps.
mappings = IMC2ChannelMapping.objects.filter(imc2_channel_name=chan_name) mappings = IMC2ChannelMapping.objects.filter(imc2_channel_name=chan_name)
# Format the message to cemit to the local channel. # Format the message to cemit to the local channel.
message = '%s@%s: %s' % (packet.sender, message = '%s@%s: %s' % (packet.sender,
packet.origin, packet.origin,
packet.optional_data.get('text')) packet.optional_data.get('text'))
# Bombs away. # Bombs away.
for mapping in mappings: for mapping in mappings:
if mapping.channel: if mapping.channel:
comsys.send_cmessage(mapping.channel, message, comsys.send_cmessage(mapping.channel, message,
from_external="IMC2") from_external="IMC2")
except IMC2ChannelMapping.DoesNotExist: except IMC2ChannelMapping.DoesNotExist:
# No channel mapping found for this message, ignore it. # No channel mapping found for this message, ignore it.
pass pass
def lineReceived(self, line): def lineReceived(self, line):
""" """
Triggered when text is received from the IMC2 network. Figures out Triggered when text is received from the IMC2 network. Figures out
what to do with the packet. what to do with the packet.
""" """
if not self.is_authenticated: if not self.is_authenticated:
self._parse_auth_response(line) self._parse_auth_response(line)
else: else:
if settings.IMC2_DEBUG and 'is-alive' not in line: if settings.IMC2_DEBUG and 'is-alive' not in line:
# if IMC2_DEBUG mode is on, print the contents of the packet # if IMC2_DEBUG mode is on, print the contents of the packet
# to stdout. # to stdout.
logger.log_infomsg("PACKET: %s" % line) logger.log_infomsg("PACKET: %s" % line)
# Parse the packet and encapsulate it for easy access # Parse the packet and encapsulate it for easy access
packet = IMC2Packet(packet_str = line) packet = IMC2Packet(packet_str = line)
if settings.IMC2_DEBUG and packet.packet_type not in ['is-alive', 'keepalive-request']: if settings.IMC2_DEBUG and packet.packet_type not in ['is-alive', 'keepalive-request']:
# Print the parsed packet's __str__ representation. # Print the parsed packet's __str__ representation.
# is-alive and keepalive-requests happen pretty frequently. # is-alive and keepalive-requests happen pretty frequently.
# Don't bore us with them in stdout. # Don't bore us with them in stdout.
logger.log_infomsg(packet) logger.log_infomsg(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) 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(IMC2PacketIsAlive()) self.send_packet(IMC2PacketIsAlive())
elif packet.packet_type == 'ice-msg-b': elif packet.packet_type == 'ice-msg-b':
self._handle_channel_mappings(packet) self._handle_channel_mappings(packet)
elif packet.packet_type == 'whois-reply': elif packet.packet_type == 'whois-reply':
reply_listener.handle_whois_reply(packet) reply_listener.handle_whois_reply(packet)
elif packet.packet_type == 'close-notify': elif packet.packet_type == 'close-notify':
IMC2_MUDLIST.remove_mud_from_packet(packet) 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) 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) IMC2_CHANLIST.remove_channel_from_packet(packet)
elif packet.packet_type == 'tell': elif packet.packet_type == 'tell':
sessions = session_mgr.find_sessions_from_username(packet.target) sessions = session_mgr.find_sessions_from_username(packet.target)
for session in sessions: for session in sessions:
session.msg("%s@%s IMC tells: %s" % session.msg("%s@%s IMC tells: %s" %
(packet.sender, (packet.sender,
packet.origin, packet.origin,
packet.optional_data.get('text', packet.optional_data.get('text',
'ERROR: No text provided.'))) 'ERROR: No text provided.')))
class IMC2ClientFactory(ClientFactory): class IMC2ClientFactory(ClientFactory):
""" """
Creates instances of the IMC2Protocol. Should really only ever create one Creates instances of the IMC2Protocol. Should really only ever create one
in our particular instance. Tied in via src/server.py. in our particular instance. Tied in via src/server.py.
""" """
protocol = IMC2Protocol protocol = IMC2Protocol
def clientConnectionFailed(self, connector, reason): def clientConnectionFailed(self, connector, reason):
message = 'Connection failed: %s' % reason.getErrorMessage() message = 'Connection failed: %s' % reason.getErrorMessage()
cemit_info(message) cemit_info(message)
logger.log_errmsg('IMC2: %s' % message) logger.log_errmsg('IMC2: %s' % message)
def clientConnectionLost(self, connector, reason): def clientConnectionLost(self, connector, reason):
message = 'Connection lost: %s' % reason.getErrorMessage() message = 'Connection lost: %s' % reason.getErrorMessage()
cemit_info(message) cemit_info(message)
logger.log_errmsg('IMC2: %s' % message) logger.log_errmsg('IMC2: %s' % message)