rendered paste body# vim: ts=4 sw=4 noexpandtab
"""IRC Bot module.
This is a IRC Bot ModManager module
===== Config =====
# Sets option 1
mm_sample.myOption1 1
# Sets option 2
mm_sample.myOption2 "hello there"
===== History =====
v0.1 - 08/04/2005:
Initial version
Copyright (c)2005 Multiplay UK
Author: Steven 'Killing' Hartland
"""
import bf2
import host
import mm_utils
import re
# Set the version of your module here
__version__ = 0.1
# Set the required module versions here
__required_modules__ = {
'modmanager': 1.0
}
# Does this module support reload ( are all its reference closed on shutdown? )
__supports_reload__ = True
# Set the description of your module here
__description__ = "IRCBot v%s" % __version__
# Add all your configuration options here
configDefaults = {
'serverAddress': 'irc.efnet.net',
'serverPort': 6667,
'localIp': '0.0.0.0',
'localPort': 6668,
'channelName': '#b0tty',
'channelPassword': '',
'nick': 'mmbot',
}
class IRCBot( object ):
def __init__( self, modManager ):
# ModManager reference
self.mm = modManager
# Internal shutdown state
self.__state = 0
# Add any static initialisation here.
# Note: Handler registration should not be done here
# but instead in the init() method
# Your rcon commands go here:
self.__cmds = {
'sample': { 'method': self.cmdSample }
}
def cmdExec( self, ctx, cmd ):
"""Execute a IRCBot sub command."""
# Note: The Python doc above is used for help / description
# messages in rcon if not overriden
return mm_utils.exec_subcmd( self.mm, self.__cmds, ctx, cmd )
def cmdSample( self, ctx, cmd ):
"""Does XYZ.
Details about this function
"""
# Note: The Python doc above is used for help / description
# messages in rcon if not overriden
self.mm.debug( 2, "Running cmdSample '%s'" % cmd )
ctx.write( "Your arguments where '%s'" % cmd )
return 1
def onPlayerSpawn( self, player, soldier ):
"""Do something when a player spawns."""
if 1 != self.__state:
return 0
# Put your actions here
def init( self ):
"""Provides default initialisation."""
# Load the configuration
self.__config = self.mm.getModuleConfig( configDefaults )
if 0 == self.__state:
# Register your host handlers here
host.registerHandler( 'PlayerKilled', self.onPlayerKilled, 1 )
host.registerHandler( 'PlayerConnect', self.onPlayerConnect, 1 )
host.registerHandler( 'PlayerDisconnect', self.onPlayerDisconnect, 1 )
host.registerHandler( 'PlayerBanned', self.onPlayerBanned, 1 )
host.registerHandler( 'PlayerKicked', self.onPlayerKicked, 1 )
host.registerHandler( 'ControlPointChangedOwner', self.onControlPointChangedOwner, 1 )
host.registerHandler( 'TicketLimitReached', self.onTicketLimitReached, 1 )
host.registerHandler( 'TimeLimitReached', self.onTimeLimitReached, 1 )
# Create out IRC Server Connection
self.connectToServer()
# Register our rcon command handlers
#self.mm.registerRconCmdHandler( 'sample', { 'method': self.cmdExec, 'subcmds': self.__cmds } )
self.mm.registerUpdates( self.update )
# Update to the running state
self.__state = 1
def shutdown( self ):
"""Shutdown and stop processing."""
# Unregister game handlers and do any other
# other actions to ensure your module no longer affects
# the game in anyway
# Shutdown our server connection
if self.__socket:
self.__socket.close();
# Flag as shutdown as there is currently way to:
# host.unregisterHandler
self.__state = 2
def update( self ):
"""Process and update.
Note: This is called VERY often processing in here should
be kept to an absolute minimum.
"""
if self.__socket is None:
# No socket, just return
return
# without blocking, check for new connections
try:
sock, peeraddr = self.__socket.accept()
peer = AdminConnection( self, sock, peeraddr )
self.__peers.append( peer )
# inform the connect handlers of the new client
ctx = CommandContext( peer )
for handler in self.__connectHandlers:
try:
handler( ctx )
except:
self.mm.error( "Handler '%s' failed to process connect" % mm_utils.method_name( handler ), True )
except socket.error, detail:
if detail[0] != errno.EWOULDBLOCK:
self.mm.error( "Rcon accept error (%s)" % detail, True )
raise socket.error, detail
class IRCServer( object ):
"""Each TCP connection is represented by an object of this class."""
def __init__( self, bot ):
self.mm = bot.mm
self.__config = bot.__config
self.__outputBuffer = []
self.__inputBuffer = ''
self.__connected = False
self.__linesep_regexp = re.compile( "\r?\n" )
self.__rfc_1459_command_regexp = re.compile( "^(:(?P<prefix>[^ ]+) +)?(?P<command>[^ ]+)( *(?P<argument> .+))?" )
self.connect()
def send( self, string ):
"""Append an text to output."""
try:
self.__outputBuffer.append( string )
except Exception, e:
self.mm.error( "Failed to enqueue '%s' (%s)" % ( string, e ), True )
def connect( self ):
"""Connect to the IRC server."""
try:
self.__socket = socket.socket( socket.AF_INET, socket.SOCK_STREAM )
if self.__config['enableLinger']:
linger = struct.pack( "ii", self.__config['lingerFor'], 0 )
self.__socket.setsockopt( socket.SOL_SOCKET, socket.SO_LINGER, linger )
if self.__config['reuseAddress']:
self.__socket.setsockopt( socket.SOL_SOCKET, socket.SO_REUSEADDR, 1 )
except socket.error, detail:
self.mm.error( 'Failed setup IRC socket (%s)' % detail, True )
self.__socket = None
return False
try:
self.__socket.bind( ( self.__config['localIP'], self.__config['localPort'] ) )
self.__socket.connect( ( self.__config['serverAddress'], self.__config['serverPort'] ) )
self.__socket.setblocking( 0 )
except socket.error, detail:
self.__socket.close()
self.__socket = None
self.mm.error( "Couldn't connect to IRC server: %s" % detail, True )
return False
self.__connected = True
return True
def processInputLine( self, line ):
"""Process an input line."""
m = self.__rfc_1459_command_regexp.match( line )
if m.group( "prefix" ):
prefix = m.group( "prefix" )
if not self.real_server_name:
self.real_server_name = prefix
if m.group( "command" ):
cmd = string.lower( m.group( "command" ) )
if m.group( "argument" ):
a = string.split( m.group( "argument" ), " :", 1 )
args = string.split( a[0] )
if len( a ) == 2:
args.append( a[1] )
# Translate numerics into more readable strings.
if numeric_events.has_key( command ):
cmd = numeric_events[command]
if cmd == "nick":
if string.split(prefix, "!")[0] == self.__real_nick:
self.__real_nick = args[0]
elif cmd == "welcome":
# Record the nickname in case the client changed nick
# in a nicknameinuse callback.
self.__real_nick = args[0]
if "quit" == cmd:
args = [args[0]]
elif "ping" == cmd:
target = args[0]
else:
target = args[0]
args = args[1:]
if cmd == "mode":
if not is_channel( target ):
cmd = "umode"
self.mm.debug( 2, "cmd: %s, src: %s, target: %s, args: %s" % ( cmd, prefix, target, args ) )
#self._handle_event(Event(command, prefix, target, arguments))
def update( self ):
"""Receive from server then send output."""
if not self.__socket:
# socket already closed e.g. DOS protection
return False
try:
new_data = self.__socket.recv( 16384 )
except socket.error, detail:
# The server hung up.
if detail[0] != errno.EWOULDBLOCK:
self.disconnect( "Connection reset by peer" )
return False
else:
# just nothing atm
return True
if not new_data:
# Read nothing: connection must be down.
self.disconnect( "Connection reset by peer" )
return False
lines = self.__linesep_regexp.split( self.__inputBuffer + new_data )
# Save the last, unfinished line.
self.__inputBuffer = lines[-1]
lines = lines[:-1]
for line in lines[:-1]:
self.processInputLine( self, line )
# Send our queued output
while 0 < len( self__outputBuffer ):
try:
line = self__outputBuffer[0]
scount = self.socket.send( line )
if scount == len( line ):
# all sent remove
del self.__outputBuffer[0]
else:
# some remaining
self.__outputBuffer[0] = line[scount:]
break
except socket.error, detail:
if detail[0] != errno.EWOULDBLOCK:
self.mm.error( "Failed to send", True )
self.disconnect( detail[1] )
return False
return True
def disconnect( self, err = 'unknown' ):
"""Close the server connection."""
self.mm.debug( 1, 'IRCServer: closing (%s)' % ( err ) )
# ensure we close down as best we can
if self.__socket is not None:
try:
self.__socket.shutdown(2)
except:
self.mm.error( 'IRCServer: failed to shutdown connection (%s)' % ( err ) )
try:
self.__socket.close()
except:
self.mm.error( 'IRCServer: failed to close connection (%s)' % ( err ), True )
self.__socket = None
return True
# Numeric table mostly stolen from the Perl IRC module (Net::IRC).
numeric_events = {
"001": "welcome",
"002": "yourhost",
"003": "created",
"004": "myinfo",
"005": "featurelist", # XXX
"200": "tracelink",
"201": "traceconnecting",
"202": "tracehandshake",
"203": "traceunknown",
"204": "traceoperator",
"205": "traceuser",
"206": "traceserver",
"207": "traceservice",
"208": "tracenewtype",
"209": "traceclass",
"210": "tracereconnect",
"211": "statslinkinfo",
"212": "statscommands",
"213": "statscline",
"214": "statsnline",
"215": "statsiline",
"216": "statskline",
"217": "statsqline",
"218": "statsyline",
"219": "endofstats",
"221": "umodeis",
"231": "serviceinfo",
"232": "endofservices",
"233": "service",
"234": "servlist",
"235": "servlistend",
"241": "statslline",
"242": "statsuptime",
"243": "statsoline",
"244": "statshline",
"250": "luserconns",
"251": "luserclient",
"252": "luserop",
"253": "luserunknown",
"254": "luserchannels",
"255": "luserme",
"256": "adminme",
"257": "adminloc1",
"258": "adminloc2",
"259": "adminemail",
"261": "tracelog",
"262": "endoftrace",
"263": "tryagain",
"265": "n_local",
"266": "n_global",
"300": "none",
"301": "away",
"302": "userhost",
"303": "ison",
"305": "unaway",
"306": "nowaway",
"311": "whoisuser",
"312": "whoisserver",
"313": "whoisoperator",
"314": "whowasuser",
"315": "endofwho",
"316": "whoischanop",
"317": "whoisidle",
"318": "endofwhois",
"319": "whoischannels",
"321": "liststart",
"322": "list",
"323": "listend",
"324": "channelmodeis",
"329": "channelcreate",
"331": "notopic",
"332": "topic",
"333": "topicinfo",
"341": "inviting",
"342": "summoning",
"346": "invitelist",
"347": "endofinvitelist",
"348": "exceptlist",
"349": "endofexceptlist",
"351": "version",
"352": "whoreply",
"353": "namreply",
"361": "killdone",
"362": "closing",
"363": "closeend",
"364": "links",
"365": "endoflinks",
"366": "endofnames",
"367": "banlist",
"368": "endofbanlist",
"369": "endofwhowas",
"371": "info",
"372": "motd",
"373": "infostart",
"374": "endofinfo",
"375": "motdstart",
"376": "endofmotd",
"377": "motd2", # 1997-10-16 -- tkil
"381": "youreoper",
"382": "rehashing",
"384": "myportis",
"391": "time",
"392": "usersstart",
"393": "users",
"394": "endofusers",
"395": "nousers",
"401": "nosuchnick",
"402": "nosuchserver",
"403": "nosuchchannel",
"404": "cannotsendtochan",
"405": "toomanychannels",
"406": "wasnosuchnick",
"407": "toomanytargets",
"409": "noorigin",
"411": "norecipient",
"412": "notexttosend",
"413": "notoplevel",
"414": "wildtoplevel",
"421": "unknowncommand",
"422": "nomotd",
"423": "noadmininfo",
"424": "fileerror",
"431": "nonicknamegiven",
"432": "erroneusnickname", # Thiss iz how its speld in thee RFC.
"433": "nicknameinuse",
"436": "nickcollision",
"437": "unavailresource", # "Nick temporally unavailable"
"441": "usernotinchannel",
"442": "notonchannel",
"443": "useronchannel",
"444": "nologin",
"445": "summondisabled",
"446": "usersdisabled",
"451": "notregistered",
"461": "needmoreparams",
"462": "alreadyregistered",
"463": "nopermforhost",
"464": "passwdmismatch",
"465": "yourebannedcreep", # I love this one...
"466": "youwillbebanned",
"467": "keyset",
"471": "channelisfull",
"472": "unknownmode",
"473": "inviteonlychan",
"474": "bannedfromchan",
"475": "badchannelkey",
"476": "badchanmask",
"477": "nochanmodes", # "Channel doesn't supportmodes"
"478": "banlistfull",
"481": "noprivileges",
"482": "chanoprivsneeded",
"483": "cantkillserver",
"484": "restricted", # Connection is restricted
"485": "uniqopprivsneeded",
"491": "nooperhost",
"492": "noservicehost",
"501": "umodeunknownflag",
"502": "usersdontmatch",
}
generated_events = [
# Generated events
"dcc_connect",
"dcc_disconnect",
"dccmsg",
"disconnect",
"ctcp",
"ctcpreply",
]
protocol_events = [
# IRC protocol events
"error",
"join",
"kick",
"mode",
"part",
"ping",
"privmsg",
"privnotice",
"pubmsg",
"pubnotice",
"quit",
"invite",
"pong",
]
def mm_load( modManager ):
"""Creates and returns your object."""
return IRCBot( modManager )