From 595a38c7410b1d3364fccb9c750ca5fb4cc45752 Mon Sep 17 00:00:00 2001 From: dpedu Date: Tue, 16 Jun 2015 23:36:23 -0700 Subject: [PATCH] Abstract protocol handling code from bot logic. This will likely break a few modules. --- pyircbot/core/irccore.py | 372 +++++++++++++++++++++++++++++ pyircbot/core/pyircbot.py | 385 +++--------------------------- pyircbot/main.py | 3 +- pyircbot/modules/PingResponder.py | 2 +- 4 files changed, 409 insertions(+), 353 deletions(-) create mode 100644 pyircbot/core/irccore.py mode change 100644 => 100755 pyircbot/modules/PingResponder.py diff --git a/pyircbot/core/irccore.py b/pyircbot/core/irccore.py new file mode 100644 index 0000000..a0d1ae9 --- /dev/null +++ b/pyircbot/core/irccore.py @@ -0,0 +1,372 @@ +""" +.. module:: IRCCore + :synopsis: IRC protocol class + +.. moduleauthor:: Dave Pedu + +""" + +import socket +import asynchat +import asyncore +import logging +import traceback +from socket import SHUT_RDWR + +try: + from cStringIO import StringIO +except: + from io import BytesIO as StringIO + +class IRCCore(asynchat.async_chat): + """:param coreconfig: The core configuration of the bot. Passed by main.py. + :type coreconfig: dict + :param botconfig: The configuration of this instance of the bot. Passed by main.py. + :type botconfig: dict + """ + + def __init__(self): + asynchat.async_chat.__init__(self) + + self.connected=False + """If we're connected or not""" + + self.log = logging.getLogger('IRCCore') + """Reference to logger object""" + + self.buffer = StringIO() + """cSTringIO used as a buffer""" + + self.alive = True + """ True if we should try to stay connected""" + + # Connection details + self.server = None + self.port = 0 + self.ipv6 = False + + # IRC Messages are terminated with \r\n + self.set_terminator(b"\r\n") + + # Set up hooks for modules + self.initHooks() + + + def loop(self): + asyncore.loop() + + def kill(self): + """TODO close the socket""" + pass + + " Net related code here on down " + + def getBuf(self): + """Return the network buffer and clear it""" + self.buffer.seek(0) + data = self.buffer.read() + self.buffer = StringIO() + return data + + def collect_incoming_data(self, data): + """Recieve data from the IRC server, append it to the buffer + + :param data: the data that was recieved + :type data: str""" + #self.log.debug("<< %(message)s", {"message":repr(data)}) + self.buffer.write(data) + + def found_terminator(self): + """A complete command was pushed through, so clear the buffer and process it.""" + line = None + buf = self.getBuf() + try: + line = buf.decode("UTF-8") + except UnicodeDecodeError as ude: + self.log.error("found_terminator(): could not decode input as UTF-8") + self.log.error("found_terminator(): data: %s" % line) + self.log.error("found_terminator(): repr(data): %s" % repr(line)) + self.log.error("found_terminator(): error: %s" % str(ude)) + return + self.process_data(line) + + def handle_close(self): + """Called when the socket is disconnected. We will want to reconnect. """ + self.log.debug("handle_close") + self.connected=False + self.close() + self.fire_hook("_DISCONNECT") + + def handle_error(self, *args, **kwargs): + """Called on fatal network errors.""" + self.log.warning("Connection failed.") + + def _connect(self): + """Connect to IRC""" + self.log.debug("Connecting to %(server)s:%(port)i", {"server":self.server, "port":self.port}) + socket_type = socket.AF_INET + if self.ipv6: + self.log.info("IPv6 is enabled.") + socket_type = socket.AF_INET6 + socketInfo = socket.getaddrinfo(self.server, self.port, socket_type) + self.create_socket(socket_type, socket.SOCK_STREAM) + + self.connect(socketInfo[0][4]) + + def handle_connect(self): + """When asynchat indicates our socket is connected, fire the connect hook""" + self.connected=True + # TODO move to an event + self.log.debug("handle_connect: setting USER and NICK") + self.fire_hook("_CONNECT") + self.log.debug("handle_connect: complete") + + def sendRaw(self, text): + """Send a raw string to the IRC server + + :param text: the string to send + :type text: str""" + if self.connected: + #self.log.debug(">> "+text) + self.send( (text+"\r\n").encode("UTF-8").decode().encode("UTF-8")) + else: + self.log.warning("Send attempted while disconnected. >> "+text) + + def process_data(self, data): + """Process one line of tet irc sent us + + :param data: the data to process + :type data: str""" + if data.strip() == "": + return + + prefix = None + command = None + args=[] + trailing=None + + if data[0]==":": + prefix=data.split(" ")[0][1:] + data=data[data.find(" ")+1:] + command = data.split(" ")[0] + data=data[data.find(" ")+1:] + if(data[0]==":"): + # no args + trailing = data[1:].strip() + else: + trailing = data[data.find(" :")+2:].strip() + data = data[:data.find(" :")] + args = data.split(" ") + for index,arg in enumerate(args): + args[index]=arg.strip() + if not command in self.hookcalls: + self.log.warning("Unknown command: cmd='%s' prefix='%s' args='%s' trailing='%s'" % (command, prefix, args, trailing)) + else: + self.fire_hook(command, args=args, prefix=prefix, trailing=trailing) + + + " Module related code " + def initHooks(self): + """Defines hooks that modules can listen for events of""" + self.hooks = [ + '_CONNECT', # Called when the bot connects to IRC on the socket level + '_DISCONNECT', # Called when the irc socket is forcibly closed + 'NOTICE', # :irc.129irc.com NOTICE AUTH :*** Looking up your hostname... + 'MODE', # :CloneABCD MODE CloneABCD :+iwx + 'PING', # PING :irc.129irc.com + 'JOIN', # :CloneA!dave@hidden-B4F6B1AA.rit.edu JOIN :#clonea + 'QUIT', # :HCSMPBot!~HCSMPBot@108.170.48.18 QUIT :Quit: Disconnecting! + 'NICK', # :foxiAway!foxi@irc.hcsmp.com NICK :foxi + 'PART', # :CloneA!dave@hidden-B4F6B1AA.rit.edu PART #clonea + 'PRIVMSG', # :CloneA!dave@hidden-B4F6B1AA.rit.edu PRIVMSG #clonea :aaa + 'KICK', # :xMopxShell!~rduser@host KICK #xMopx2 xBotxShellTest :xBotxShellTest + 'INVITE', # :gmx!~gmxgeek@irc.hcsmp.com INVITE Tyrone :#hcsmp' + '001', # :irc.129irc.com 001 CloneABCD :Welcome to the 129irc IRC Network CloneABCD!CloneABCD@djptwc-laptop1.rit.edu + '002', # :irc.129irc.com 002 CloneABCD :Your host is irc.129irc.com, running version Unreal3.2.8.1 + '003', # :irc.129irc.com 003 CloneABCD :This server was created Mon Jul 19 2010 at 03:12:01 EDT + '004', # :irc.129irc.com 004 CloneABCD irc.129irc.com Unreal3.2.8.1 iowghraAsORTVSxNCWqBzvdHtGp lvhopsmntikrRcaqOALQbSeIKVfMCuzNTGj + '005', # :irc.129irc.com 005 CloneABCD CMDS=KNOCK,MAP,DCCALLOW,USERIP UHNAMES NAMESX SAFELIST HCN MAXCHANNELS=10 CHANLIMIT=#:10 MAXLIST=b:60,e:60,I:60 NICKLEN=30 CHANNELLEN=32 TOPICLEN=307 KICKLEN=307 AWAYLEN=307 :are supported by this server + '250', # :chaos.esper.net 250 xBotxShellTest :Highest connection count: 1633 (1632 clients) (186588 connections received) + '251', # :irc.129irc.com 251 CloneABCD :There are 1 users and 48 invisible on 2 servers + '252', # :irc.129irc.com 252 CloneABCD 9 :operator(s) online + '254', # :irc.129irc.com 254 CloneABCD 6 :channels formed + '255', # :irc.129irc.com 255 CloneABCD :I have 42 clients and 1 servers + '265', # :irc.129irc.com 265 CloneABCD :Current Local Users: 42 Max: 47 + '266', # :irc.129irc.com 266 CloneABCD :Current Global Users: 49 Max: 53 + '332', # :chaos.esper.net 332 xBotxShellTest #xMopx2 :/ #XMOPX2 / https://code.google.com/p/pyircbot/ (Channel Topic) + '333', # :chaos.esper.net 333 xBotxShellTest #xMopx2 xMopxShell!~rduser@108.170.60.242 1344370109 + '353', # :irc.129irc.com 353 CloneABCD = #clonea :CloneABCD CloneABC + '366', # :irc.129irc.com 366 CloneABCD #clonea :End of /NAMES list. + '372', # :chaos.esper.net 372 xBotxShell :motd text here + '375', # :chaos.esper.net 375 xBotxShellTest :- chaos.esper.net Message of the Day - + '376', # :chaos.esper.net 376 xBotxShell :End of /MOTD command. + '422', # :irc.129irc.com 422 CloneABCD :MOTD File is missing + '433', # :nova.esper.net 433 * pyircbot3 :Nickname is already in use. + ] + " mapping of hooks to methods " + self.hookcalls = {} + for command in self.hooks: + self.hookcalls[command]=[] + + def fire_hook(self, command, args=None, prefix=None, trailing=None): + """Run any listeners for a specific hook + + :param command: the hook to fire + :type command: str + :param args: the list of arguments, if any, the command was passed + :type args: list + :param prefix: prefix of the sender of this command + :type prefix: str + :param trailing: data payload of the command + :type trailing: str""" + + for hook in self.hookcalls[command]: + try: + hook(args, prefix, trailing) + except: + self.log.warning("Error processing hook: \n%s"% self.trace()) + + def addHook(self, command, method): + """**Internal.** Enable (connect) a single hook of a module + + :param command: command this hook will trigger on + :type command: str + :param method: callable method object to hook in + :type method: object""" + " add a single hook " + if command in self.hooks: + self.hookcalls[command].append(method) + else: + self.log.warning("Invalid hook - %s" % command) + return False + + def removeHook(self, command, method): + """**Internal.** Disable (disconnect) a single hook of a module + + :param command: command this hook triggers on + :type command: str + :param method: callable method that should be removed + :type method: object""" + " remove a single hook " + if command in self.hooks: + for hookedMethod in self.hookcalls[command]: + if hookedMethod == method: + self.hookcalls[command].remove(hookedMethod) + else: + self.log.warning("Invalid hook - %s" % command) + return False + + " Utility methods " + @staticmethod + def decodePrefix(prefix): + """Given a prefix like nick!username@hostname, return an object with these properties + + :param prefix: the prefix to disassemble + :type prefix: str + :returns: object -- an UserPrefix object with the properties `nick`, `username`, `hostname` or a ServerPrefix object with the property `hostname`""" + if "!" in prefix: + ob = type('UserPrefix', (object,), {}) + ob.nick, prefix = prefix.split("!") + ob.username, ob.hostname = prefix.split("@") + return ob + else: + ob = type('ServerPrefix', (object,), {}) + ob.hostname = prefix + return ob + + @staticmethod + def trace(): + """Return the stack trace of the bot as a string""" + return traceback.format_exc() + + " Data Methods " + def get_nick(self): + """Get the bot's current nick + + :returns: str - the bot's current nickname""" + return self.nick + + " Action Methods " + def act_PONG(self, data): + """Use the `/pong` command - respond to server pings + + :param data: the string or number the server sent with it's ping + :type data: str""" + self.sendRaw("PONG :%s" % data) + + def act_USER(self, username, hostname, realname): + """Use the USER protocol command. Used during connection + + :param username: the bot's username + :type username: str + :param hostname: the bot's hostname + :type hostname: str + :param realname: the bot's realname + :type realname: str""" + self.sendRaw("USER %s %s %s :%s" % (username, hostname, self.server, realname)) + + def act_NICK(self, newNick): + """Use the `/nick` command + + :param newNick: new nick for the bot + :type newNick: str""" + self.nick = newNick + self.sendRaw("NICK %s" % newNick) + + def act_JOIN(self, channel): + """Use the `/join` command + + :param channel: the channel to attempt to join + :type channel: str""" + self.sendRaw("JOIN %s"%channel) + + def act_PRIVMSG(self, towho, message): + """Use the `/msg` command + + :param towho: the target #channel or user's name + :type towho: str + :param message: the message to send + :type message: str""" + self.sendRaw("PRIVMSG %s :%s"%(towho,message)) + + def act_MODE(self, channel, mode, extra=None): + """Use the `/mode` command + + :param channel: the channel this mode is for + :type channel: str + :param mode: the mode string. Example: +b + :type mode: str + :param extra: additional argument if the mode needs it. Example: user@*!* + :type extra: str""" + if extra != None: + self.sendRaw("MODE %s %s %s" % (channel,mode,extra)) + else: + self.sendRaw("MODE %s %s" % (channel,mode)) + + def act_ACTION(self, channel, action): + """Use the `/me ` command + + :param channel: the channel name or target's name the message is sent to + :type channel: str + :param action: the text to send + :type action: str""" + self.sendRaw("PRIVMSG %s :\x01ACTION %s"%(channel,action)) + + def act_KICK(self, channel, who, comment=""): + """Use the `/kick ` command + + :param channel: the channel from which the user will be kicked + :type channel: str + :param who: the nickname of the user to kick + :type action: str + :param comment: the kick message + :type comment: str""" + self.sendRaw("KICK %s %s :%s" % (channel, who, comment)) + + def act_QUIT(self, message): + """Use the `/quit` command + + :param message: quit message + :type message: str""" + self.sendRaw("QUIT :%s" % message) + diff --git a/pyircbot/core/pyircbot.py b/pyircbot/core/pyircbot.py index 2f4f1ce..c94862d 100644 --- a/pyircbot/core/pyircbot.py +++ b/pyircbot/core/pyircbot.py @@ -6,22 +6,14 @@ """ -import socket -import asynchat import logging -import traceback import time import sys -from socket import SHUT_RDWR from core.rpc import BotRPC +from core.irccore import IRCCore import os.path -try: - from cStringIO import StringIO -except: - from io import BytesIO as StringIO - -class PyIRCBot(asynchat.async_chat): +class PyIRCBot: """:param coreconfig: The core configuration of the bot. Passed by main.py. :type coreconfig: dict :param botconfig: The configuration of this instance of the bot. Passed by main.py. @@ -32,11 +24,6 @@ class PyIRCBot(asynchat.async_chat): """ PyIRCBot version """ def __init__(self, coreconfig, botconfig): - asynchat.async_chat.__init__(self) - - self.connected=False - """If we're connected or not""" - self.log = logging.getLogger('PyIRCBot') """Reference to logger object""" @@ -49,215 +36,58 @@ class PyIRCBot(asynchat.async_chat): self.rpc = BotRPC(self) """Reference to BotRPC thread""" - self.buffer = StringIO() - """cSTringIO used as a buffer""" + self.irc = IRCCore() + """IRC protocol class""" + self.irc.server = self.botconfig["connection"]["server"] + self.irc.port = self.botconfig["connection"]["port"] + self.irc.ipv6 = True if self.botconfig["connection"]["ipv6"]=="on" else False - self.alive = True - """ True if we should try to stay connected""" + self.irc.addHook("_DISCONNECT", self.handle_close) - # IRC Messages are terminated with \r\n - self.set_terminator(b"\r\n") - - # Set up hooks for modules - self.initHooks() + # legacy support + self.act_PONG = self.irc.act_PONG + self.act_USER = self.irc.act_USER + self.act_NICK = self.irc.act_NICK + self.act_JOIN = self.irc.act_JOIN + self.act_PRIVMSG = self.irc.act_PRIVMSG + self.act_MODE = self.irc.act_MODE + self.act_ACTION = self.irc.act_ACTION + self.act_KICK = self.irc.act_KICK + self.act_QUIT = self.irc.act_QUIT + self.get_nick = self.irc.get_nick # Load modules self.initModules() # Connect to IRC - self._connect() + self.connect() + + def connect(self): + self.irc._connect() + + def loop(self): + self.irc.loop() def kill(self): """Shut down the bot violently""" - #TODO: have rpc thread be daemonized so it just dies - #try: - # self.rpc.server._Server__transport.shutdown(SHUT_RDWR) - #except Exception as e: - # self.log.error(str(e)) - #try: - # self.rpc.server._Server__transport.close() - #except Exception as e: - # self.log.error(str(e)) - - #Kill RPC thread - #self.rpc._stop() - #Close all modules self.closeAllModules() - # Mark for shutdown - self.alive = False - # Exit + + self.irc.kill() + sys.exit(0) + " Net related code here on down " - def getBuf(self): - """Return the network buffer and clear it""" - self.buffer.seek(0) - data = self.buffer.read() - self.buffer = StringIO() - return data - - def collect_incoming_data(self, data): - """Recieve data from the IRC server, append it to the buffer - - :param data: the data that was recieved - :type data: str""" - #self.log.debug("<< %(message)s", {"message":repr(data)}) - self.buffer.write(data) - - def found_terminator(self): - """A complete command was pushed through, so clear the buffer and process it.""" - line = None - buf = self.getBuf() - try: - line = buf.decode("UTF-8") - except UnicodeDecodeError as ude: - self.log.error("found_terminator(): could not decode input as UTF-8") - self.log.error("found_terminator(): data: %s" % line) - self.log.error("found_terminator(): repr(data): %s" % repr(line)) - self.log.error("found_terminator(): error: %s" % str(ude)) - return - self.process_data(line) - + # TODO move handle_close to an event hook def handle_close(self): """Called when the socket is disconnected. We will want to reconnect. """ - self.log.debug("handle_close") - self.connected=False - self.close() if self.alive: self.log.warning("Connection was lost. Reconnecting in 5 seconds.") time.sleep(5) self._connect() - def handle_error(self, *args, **kwargs): - """Called on fatal network errors.""" - self.log.warning("Connection failed.") - - def _connect(self): - """Connect to IRC""" - self.log.debug("Connecting to %(server)s:%(port)i", {"server":self.botconfig["connection"]["server"], "port":self.botconfig["connection"]["port"]}) - socket_type = socket.AF_INET - if self.botconfig["connection"]["ipv6"]: - self.log.info("IPv6 is enabled.") - socket_type = socket.AF_INET6 - socketInfo = socket.getaddrinfo(self.botconfig["connection"]["server"], self.botconfig["connection"]["port"], socket_type) - self.create_socket(socket_type, socket.SOCK_STREAM) - if "bindaddr" in self.botconfig["connection"]: - self.bind((self.botconfig["connection"]["bindaddr"], 0)) - self.connect(socketInfo[0][4]) - - def handle_connect(self): - """When asynchat indicates our socket is connected, fire the connect hook""" - self.connected=True - self.log.debug("handle_connect: setting USER and NICK") - self.fire_hook("_CONNECT") - self.log.debug("handle_connect: complete") - - def sendRaw(self, text): - """Send a raw string to the IRC server - - :param text: the string to send - :type text: str""" - if self.connected: - #self.log.debug(">> "+text) - self.send( (text+"\r\n").encode("UTF-8").decode().encode("UTF-8")) - else: - self.log.warning("Send attempted while disconnected. >> "+text) - - def process_data(self, data): - """Process one line of tet irc sent us - - :param data: the data to process - :type data: str""" - if data.strip() == "": - return - - prefix = None - command = None - args=[] - trailing=None - - if data[0]==":": - prefix=data.split(" ")[0][1:] - data=data[data.find(" ")+1:] - command = data.split(" ")[0] - data=data[data.find(" ")+1:] - if(data[0]==":"): - # no args - trailing = data[1:].strip() - else: - trailing = data[data.find(" :")+2:].strip() - data = data[:data.find(" :")] - args = data.split(" ") - for index,arg in enumerate(args): - args[index]=arg.strip() - if not command in self.hookcalls: - self.log.warning("Unknown command: cmd='%s' prefix='%s' args='%s' trailing='%s'" % (command, prefix, args, trailing)) - else: - self.fire_hook(command, args=args, prefix=prefix, trailing=trailing) - - - " Module related code " - def initHooks(self): - """Defines hooks that modules can listen for events of""" - self.hooks = [ - '_CONNECT', # Called when the bot connects to IRC on the socket level - 'NOTICE', # :irc.129irc.com NOTICE AUTH :*** Looking up your hostname... - 'MODE', # :CloneABCD MODE CloneABCD :+iwx - 'PING', # PING :irc.129irc.com - 'JOIN', # :CloneA!dave@hidden-B4F6B1AA.rit.edu JOIN :#clonea - 'QUIT', # :HCSMPBot!~HCSMPBot@108.170.48.18 QUIT :Quit: Disconnecting! - 'NICK', # :foxiAway!foxi@irc.hcsmp.com NICK :foxi - 'PART', # :CloneA!dave@hidden-B4F6B1AA.rit.edu PART #clonea - 'PRIVMSG', # :CloneA!dave@hidden-B4F6B1AA.rit.edu PRIVMSG #clonea :aaa - 'KICK', # :xMopxShell!~rduser@host KICK #xMopx2 xBotxShellTest :xBotxShellTest - 'INVITE', # :gmx!~gmxgeek@irc.hcsmp.com INVITE Tyrone :#hcsmp' - '001', # :irc.129irc.com 001 CloneABCD :Welcome to the 129irc IRC Network CloneABCD!CloneABCD@djptwc-laptop1.rit.edu - '002', # :irc.129irc.com 002 CloneABCD :Your host is irc.129irc.com, running version Unreal3.2.8.1 - '003', # :irc.129irc.com 003 CloneABCD :This server was created Mon Jul 19 2010 at 03:12:01 EDT - '004', # :irc.129irc.com 004 CloneABCD irc.129irc.com Unreal3.2.8.1 iowghraAsORTVSxNCWqBzvdHtGp lvhopsmntikrRcaqOALQbSeIKVfMCuzNTGj - '005', # :irc.129irc.com 005 CloneABCD CMDS=KNOCK,MAP,DCCALLOW,USERIP UHNAMES NAMESX SAFELIST HCN MAXCHANNELS=10 CHANLIMIT=#:10 MAXLIST=b:60,e:60,I:60 NICKLEN=30 CHANNELLEN=32 TOPICLEN=307 KICKLEN=307 AWAYLEN=307 :are supported by this server - '250', # :chaos.esper.net 250 xBotxShellTest :Highest connection count: 1633 (1632 clients) (186588 connections received) - '251', # :irc.129irc.com 251 CloneABCD :There are 1 users and 48 invisible on 2 servers - '252', # :irc.129irc.com 252 CloneABCD 9 :operator(s) online - '254', # :irc.129irc.com 254 CloneABCD 6 :channels formed - '255', # :irc.129irc.com 255 CloneABCD :I have 42 clients and 1 servers - '265', # :irc.129irc.com 265 CloneABCD :Current Local Users: 42 Max: 47 - '266', # :irc.129irc.com 266 CloneABCD :Current Global Users: 49 Max: 53 - '332', # :chaos.esper.net 332 xBotxShellTest #xMopx2 :/ #XMOPX2 / https://code.google.com/p/pyircbot/ (Channel Topic) - '333', # :chaos.esper.net 333 xBotxShellTest #xMopx2 xMopxShell!~rduser@108.170.60.242 1344370109 - '353', # :irc.129irc.com 353 CloneABCD = #clonea :CloneABCD CloneABC - '366', # :irc.129irc.com 366 CloneABCD #clonea :End of /NAMES list. - '372', # :chaos.esper.net 372 xBotxShell :motd text here - '375', # :chaos.esper.net 375 xBotxShellTest :- chaos.esper.net Message of the Day - - '376', # :chaos.esper.net 376 xBotxShell :End of /MOTD command. - '422', # :irc.129irc.com 422 CloneABCD :MOTD File is missing - '433', # :nova.esper.net 433 * pyircbot3 :Nickname is already in use. - ] - " mapping of hooks to methods " - self.hookcalls = {} - for command in self.hooks: - self.hookcalls[command]=[] - - def fire_hook(self, command, args=None, prefix=None, trailing=None): - """Run any listeners for a specific hook - - :param command: the hook to fire - :type command: str - :param args: the list of arguments, if any, the command was passed - :type args: list - :param prefix: prefix of the sender of this command - :type prefix: str - :param trailing: data payload of the command - :type trailing: str""" - - for hook in self.hookcalls[command]: - try: - hook(args, prefix, trailing) - except: - self.log.warning("Error processing hook: \n%s"% self.trace()) - def initModules(self): """load modules specified in instance config""" " storage of imported modules " @@ -394,9 +224,9 @@ class PyIRCBot(asynchat.async_chat): for hook in module.hooks: if type(hook.hook) == list: for hookcmd in hook.hook: - self.addHook(hookcmd, hook.method) + self.irc.addHook(hookcmd, hook.method) else: - self.addHook(hook.hook, hook.method) + self.irc.addHook(hook.hook, hook.method) def unloadModuleHooks(self, module): """**Internal.** Disable (disconnect) hooks of a module @@ -407,39 +237,9 @@ class PyIRCBot(asynchat.async_chat): for hook in module.hooks: if type(hook.hook) == list: for hookcmd in hook.hook: - self.removeHook(hookcmd, hook.method) + self.irc.removeHook(hookcmd, hook.method) else: - self.removeHook(hook.hook, hook.method) - - def addHook(self, command, method): - """**Internal.** Enable (connect) a single hook of a module - - :param command: command this hook will trigger on - :type command: str - :param method: callable method object to hook in - :type method: object""" - " add a single hook " - if command in self.hooks: - self.hookcalls[command].append(method) - else: - self.log.warning("Invalid hook - %s" % command) - return False - - def removeHook(self, command, method): - """**Internal.** Disable (disconnect) a single hook of a module - - :param command: command this hook triggers on - :type command: str - :param method: callable method that should be removed - :type method: object""" - " remove a single hook " - if command in self.hooks: - for hookedMethod in self.hookcalls[command]: - if hookedMethod == method: - self.hookcalls[command].remove(hookedMethod) - else: - self.log.warning("Invalid hook - %s" % command) - return False + self.irc.removeHook(hook.hook, hook.method) def getmodulebyname(self, name): """Get a module object by name @@ -504,28 +304,6 @@ class PyIRCBot(asynchat.async_chat): return "%s/config/%s.yml" % (self.botconfig["bot"]["datadir"], moduleName) " Utility methods " - @staticmethod - def decodePrefix(prefix): - """Given a prefix like nick!username@hostname, return an object with these properties - - :param prefix: the prefix to disassemble - :type prefix: str - :returns: object -- an UserPrefix object with the properties `nick`, `username`, `hostname` or a ServerPrefix object with the property `hostname`""" - if "!" in prefix: - ob = type('UserPrefix', (object,), {}) - ob.nick, prefix = prefix.split("!") - ob.username, ob.hostname = prefix.split("@") - return ob - else: - ob = type('ServerPrefix', (object,), {}) - ob.hostname = prefix - return ob - - @staticmethod - def trace(): - """Return the stack trace of the bot as a string""" - return traceback.format_exc() - @staticmethod def messageHasCommand(command, message, requireArgs=False): """Check if a message has a command with or without args in it @@ -573,96 +351,3 @@ class PyIRCBot(asynchat.async_chat): ob.message = message return ob # return (True, command, args, message) - - - " Data Methods " - def get_nick(self): - """Get the bot's current nick - - :returns: str - the bot's current nickname""" - return self.config["nick"] - - - " Action Methods " - def act_PONG(self, data): - """Use the `/pong` command - respond to server pings - - :param data: the string or number the server sent with it's ping - :type data: str""" - self.sendRaw("PONG :%s" % data) - - def act_USER(self, username, hostname, realname): - """Use the USER protocol command. Used during connection - - :param username: the bot's username - :type username: str - :param hostname: the bot's hostname - :type hostname: str - :param realname: the bot's realname - :type realname: str""" - self.sendRaw("USER %s %s %s :%s" % (username, hostname, self.botconfig["connection"]["server"], realname)) - - def act_NICK(self, newNick): - """Use the `/nick` command - - :param newNick: new nick for the bot - :type newNick: str""" - self.sendRaw("NICK %s" % newNick) - - def act_JOIN(self, channel): - """Use the `/join` command - - :param channel: the channel to attempt to join - :type channel: str""" - self.sendRaw("JOIN %s"%channel) - - def act_PRIVMSG(self, towho, message): - """Use the `/msg` command - - :param towho: the target #channel or user's name - :type towho: str - :param message: the message to send - :type message: str""" - self.sendRaw("PRIVMSG %s :%s"%(towho,message)) - - def act_MODE(self, channel, mode, extra=None): - """Use the `/mode` command - - :param channel: the channel this mode is for - :type channel: str - :param mode: the mode string. Example: +b - :type mode: str - :param extra: additional argument if the mode needs it. Example: user@*!* - :type extra: str""" - if extra != None: - self.sendRaw("MODE %s %s %s" % (channel,mode,extra)) - else: - self.sendRaw("MODE %s %s" % (channel,mode)) - - def act_ACTION(self, channel, action): - """Use the `/me ` command - - :param channel: the channel name or target's name the message is sent to - :type channel: str - :param action: the text to send - :type action: str""" - self.sendRaw("PRIVMSG %s :\x01ACTION %s"%(channel,action)) - - def act_KICK(self, channel, who, comment=""): - """Use the `/kick ` command - - :param channel: the channel from which the user will be kicked - :type channel: str - :param who: the nickname of the user to kick - :type action: str - :param comment: the kick message - :type comment: str""" - self.sendRaw("KICK %s %s :%s" % (channel, who, comment)) - - def act_QUIT(self, message): - """Use the `/quit` command - - :param message: quit message - :type message: str""" - self.sendRaw("QUIT :%s" % message) - diff --git a/pyircbot/main.py b/pyircbot/main.py index 13b4c21..42692c7 100755 --- a/pyircbot/main.py +++ b/pyircbot/main.py @@ -3,7 +3,6 @@ import os import sys import logging import yaml -import asyncore from optparse import OptionParser from core.pyircbot import PyIRCBot @@ -36,7 +35,7 @@ if __name__ == "__main__": bot = PyIRCBot(coreconfig, botconfig) try: - asyncore.loop() + bot.loop() except KeyboardInterrupt: bot.kill() diff --git a/pyircbot/modules/PingResponder.py b/pyircbot/modules/PingResponder.py old mode 100644 new mode 100755 index 9344d51..25e1840 --- a/pyircbot/modules/PingResponder.py +++ b/pyircbot/modules/PingResponder.py @@ -16,4 +16,4 @@ class PingResponder(ModuleBase): def pingrespond(self, args, prefix, trailing): # got a ping? send it right back self.bot.act_PONG(trailing) - self.log.info("Responded to a ping: %s" % trailing) + self.log.info("%s Responded to a ping: %s" % (self.bot.get_nick(), trailing))