Abstract protocol handling code from bot logic. This will likely break a few modules.

This commit is contained in:
dpedu 2015-06-16 23:36:23 -07:00
parent 0e28e96321
commit 595a38c741
4 changed files with 409 additions and 353 deletions

372
pyircbot/core/irccore.py Normal file
View File

@ -0,0 +1,372 @@
"""
.. module:: IRCCore
:synopsis: IRC protocol class
.. moduleauthor:: Dave Pedu <dave@davepedu.com>
"""
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 <action>` 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 <user> <message>` 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)

View File

@ -6,22 +6,14 @@
""" """
import socket
import asynchat
import logging import logging
import traceback
import time import time
import sys import sys
from socket import SHUT_RDWR
from core.rpc import BotRPC from core.rpc import BotRPC
from core.irccore import IRCCore
import os.path import os.path
try: class PyIRCBot:
from cStringIO import StringIO
except:
from io import BytesIO as StringIO
class PyIRCBot(asynchat.async_chat):
""":param coreconfig: The core configuration of the bot. Passed by main.py. """:param coreconfig: The core configuration of the bot. Passed by main.py.
:type coreconfig: dict :type coreconfig: dict
:param botconfig: The configuration of this instance of the bot. Passed by main.py. :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 """ """ PyIRCBot version """
def __init__(self, coreconfig, botconfig): def __init__(self, coreconfig, botconfig):
asynchat.async_chat.__init__(self)
self.connected=False
"""If we're connected or not"""
self.log = logging.getLogger('PyIRCBot') self.log = logging.getLogger('PyIRCBot')
"""Reference to logger object""" """Reference to logger object"""
@ -49,215 +36,58 @@ class PyIRCBot(asynchat.async_chat):
self.rpc = BotRPC(self) self.rpc = BotRPC(self)
"""Reference to BotRPC thread""" """Reference to BotRPC thread"""
self.buffer = StringIO() self.irc = IRCCore()
"""cSTringIO used as a buffer""" """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 self.irc.addHook("_DISCONNECT", self.handle_close)
""" True if we should try to stay connected"""
# IRC Messages are terminated with \r\n # legacy support
self.set_terminator(b"\r\n") self.act_PONG = self.irc.act_PONG
self.act_USER = self.irc.act_USER
# Set up hooks for modules self.act_NICK = self.irc.act_NICK
self.initHooks() 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 # Load modules
self.initModules() self.initModules()
# Connect to IRC # Connect to IRC
self._connect() self.connect()
def connect(self):
self.irc._connect()
def loop(self):
self.irc.loop()
def kill(self): def kill(self):
"""Shut down the bot violently""" """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 #Close all modules
self.closeAllModules() self.closeAllModules()
# Mark for shutdown
self.alive = False self.irc.kill()
# Exit
sys.exit(0) sys.exit(0)
" Net related code here on down " " Net related code here on down "
def getBuf(self): # TODO move handle_close to an event hook
"""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): def handle_close(self):
"""Called when the socket is disconnected. We will want to reconnect. """ """Called when the socket is disconnected. We will want to reconnect. """
self.log.debug("handle_close")
self.connected=False
self.close()
if self.alive: if self.alive:
self.log.warning("Connection was lost. Reconnecting in 5 seconds.") self.log.warning("Connection was lost. Reconnecting in 5 seconds.")
time.sleep(5) time.sleep(5)
self._connect() 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): def initModules(self):
"""load modules specified in instance config""" """load modules specified in instance config"""
" storage of imported modules " " storage of imported modules "
@ -394,9 +224,9 @@ class PyIRCBot(asynchat.async_chat):
for hook in module.hooks: for hook in module.hooks:
if type(hook.hook) == list: if type(hook.hook) == list:
for hookcmd in hook.hook: for hookcmd in hook.hook:
self.addHook(hookcmd, hook.method) self.irc.addHook(hookcmd, hook.method)
else: else:
self.addHook(hook.hook, hook.method) self.irc.addHook(hook.hook, hook.method)
def unloadModuleHooks(self, module): def unloadModuleHooks(self, module):
"""**Internal.** Disable (disconnect) hooks of a module """**Internal.** Disable (disconnect) hooks of a module
@ -407,39 +237,9 @@ class PyIRCBot(asynchat.async_chat):
for hook in module.hooks: for hook in module.hooks:
if type(hook.hook) == list: if type(hook.hook) == list:
for hookcmd in hook.hook: for hookcmd in hook.hook:
self.removeHook(hookcmd, hook.method) self.irc.removeHook(hookcmd, hook.method)
else: else:
self.removeHook(hook.hook, hook.method) self.irc.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
def getmodulebyname(self, name): def getmodulebyname(self, name):
"""Get a module object by 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) return "%s/config/%s.yml" % (self.botconfig["bot"]["datadir"], moduleName)
" Utility methods " " 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 @staticmethod
def messageHasCommand(command, message, requireArgs=False): def messageHasCommand(command, message, requireArgs=False):
"""Check if a message has a command with or without args in it """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 ob.message = message
return ob return ob
# return (True, command, args, message) # 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 <action>` 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 <user> <message>` 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)

View File

@ -3,7 +3,6 @@ import os
import sys import sys
import logging import logging
import yaml import yaml
import asyncore
from optparse import OptionParser from optparse import OptionParser
from core.pyircbot import PyIRCBot from core.pyircbot import PyIRCBot
@ -36,7 +35,7 @@ if __name__ == "__main__":
bot = PyIRCBot(coreconfig, botconfig) bot = PyIRCBot(coreconfig, botconfig)
try: try:
asyncore.loop() bot.loop()
except KeyboardInterrupt: except KeyboardInterrupt:
bot.kill() bot.kill()

2
pyircbot/modules/PingResponder.py Normal file → Executable file
View File

@ -16,4 +16,4 @@ class PingResponder(ModuleBase):
def pingrespond(self, args, prefix, trailing): def pingrespond(self, args, prefix, trailing):
# got a ping? send it right back # got a ping? send it right back
self.bot.act_PONG(trailing) 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))