Make some stuff nicer
This commit is contained in:
parent
0d3fbfc91c
commit
19b7a95d76
35
bin/pyircbot
35
bin/pyircbot
|
@ -1,32 +1,33 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
import os
|
|
||||||
import sys
|
import sys
|
||||||
import logging
|
import logging
|
||||||
from optparse import OptionParser
|
from argparse import ArgumentParser
|
||||||
from pyircbot import PyIRCBot
|
from pyircbot import PyIRCBot
|
||||||
from time import sleep
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
" logging level and facility "
|
" logging level and facility "
|
||||||
logging.basicConfig(level=logging.DEBUG, format="%(asctime)-15s %(levelname)-8s %(filename)s:%(lineno)d %(message)s")
|
logging.basicConfig(level=logging.INFO, format="%(asctime)-15s %(levelname)-8s %(filename)s:%(lineno)d %(message)s")
|
||||||
log = logging.getLogger('main')
|
log = logging.getLogger('main')
|
||||||
|
|
||||||
" parse command line args "
|
" parse command line args "
|
||||||
parser = OptionParser()
|
parser = ArgumentParser(description="Run pyircbot")
|
||||||
parser.add_option("-c", "--config", action="store", type="string", dest="config", help="Path to config file")
|
parser.add_argument("-c", "--config", help="Path to config file", required=True)
|
||||||
|
parser.add_argument("--debug", action="store_true", help="Dump raw irc network")
|
||||||
(options, args) = parser.parse_args()
|
|
||||||
|
args = parser.parse_args()
|
||||||
log.debug(options)
|
|
||||||
|
if args.debug:
|
||||||
if not options.config:
|
logging.getLogger().setLevel(logging.DEBUG)
|
||||||
|
log.debug(args)
|
||||||
|
|
||||||
|
if not args.config:
|
||||||
log.critical("No bot config file specified (-c). Exiting.")
|
log.critical("No bot config file specified (-c). Exiting.")
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
botconfig = PyIRCBot.load(options.config)
|
botconfig = PyIRCBot.load(args.config)
|
||||||
|
|
||||||
log.debug(botconfig)
|
log.debug(botconfig)
|
||||||
|
|
||||||
bot = PyIRCBot(botconfig)
|
bot = PyIRCBot(botconfig)
|
||||||
try:
|
try:
|
||||||
bot.loop()
|
bot.loop()
|
||||||
|
|
|
@ -16,30 +16,36 @@ import sys
|
||||||
from inspect import getargspec
|
from inspect import getargspec
|
||||||
from socket import SHUT_RDWR
|
from socket import SHUT_RDWR
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
from time import sleep,time
|
from time import sleep, time
|
||||||
|
from collections import namedtuple
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from cStringIO import StringIO
|
from cStringIO import StringIO
|
||||||
except:
|
except:
|
||||||
from io import BytesIO as StringIO
|
from io import BytesIO as StringIO
|
||||||
|
|
||||||
|
IRCEvent = namedtuple("IRCEvent", "args prefix trailing")
|
||||||
|
UserPrefix = namedtuple("UserPrefix", "nick username hostname")
|
||||||
|
ServerPrefix = namedtuple("ServerPrefix", "hostname")
|
||||||
|
|
||||||
|
|
||||||
class IRCCore(asynchat.async_chat):
|
class IRCCore(asynchat.async_chat):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
asynchat.async_chat.__init__(self)
|
asynchat.async_chat.__init__(self)
|
||||||
|
|
||||||
self.connected=False
|
self.connected=False
|
||||||
"""If we're connected or not"""
|
"""If we're connected or not"""
|
||||||
|
|
||||||
self.log = logging.getLogger('IRCCore')
|
self.log = logging.getLogger('IRCCore')
|
||||||
"""Reference to logger object"""
|
"""Reference to logger object"""
|
||||||
|
|
||||||
self.buffer = StringIO()
|
self.buffer = StringIO()
|
||||||
"""cStringIO used as a buffer"""
|
"""cStringIO used as a buffer"""
|
||||||
|
|
||||||
self.alive = True
|
self.alive = True
|
||||||
"""True if we should try to stay connected"""
|
"""True if we should try to stay connected"""
|
||||||
|
|
||||||
self.server = 0
|
self.server = 0
|
||||||
"""Current server index"""
|
"""Current server index"""
|
||||||
self.servers = []
|
self.servers = []
|
||||||
|
@ -48,22 +54,22 @@ class IRCCore(asynchat.async_chat):
|
||||||
"""Server port"""
|
"""Server port"""
|
||||||
self.ipv6 = False
|
self.ipv6 = False
|
||||||
"""Use IPv6?"""
|
"""Use IPv6?"""
|
||||||
|
|
||||||
self.OUTPUT_BUFFER_SIZE = 1000
|
self.OUTPUT_BUFFER_SIZE = 1000
|
||||||
self.SEND_WAIT = 0.800
|
self.SEND_WAIT = 0.800
|
||||||
self.outputQueue = queue.PriorityQueue(self.OUTPUT_BUFFER_SIZE)
|
self.outputQueue = queue.PriorityQueue(self.OUTPUT_BUFFER_SIZE)
|
||||||
self.outputQueueRunner = OutputQueueRunner(self)
|
self.outputQueueRunner = OutputQueueRunner(self)
|
||||||
self.outputQueueRunner.start()
|
self.outputQueueRunner.start()
|
||||||
|
|
||||||
# IRC Messages are terminated with \r\n
|
# IRC Messages are terminated with \r\n
|
||||||
self.set_terminator(b"\r\n")
|
self.set_terminator(b"\r\n")
|
||||||
|
|
||||||
# Set up hooks for modules
|
# Set up hooks for modules
|
||||||
self.initHooks()
|
self.initHooks()
|
||||||
|
|
||||||
# Map for asynchat
|
# Map for asynchat
|
||||||
self.asynmap = {}
|
self.asynmap = {}
|
||||||
|
|
||||||
def loop(self):
|
def loop(self):
|
||||||
while self.alive:
|
while self.alive:
|
||||||
try:
|
try:
|
||||||
|
@ -83,10 +89,10 @@ class IRCCore(asynchat.async_chat):
|
||||||
except Exception as e2:
|
except Exception as e2:
|
||||||
self.log.error("Error reconnecting: ")
|
self.log.error("Error reconnecting: ")
|
||||||
self.log.error(IRCCore.trace())
|
self.log.error(IRCCore.trace())
|
||||||
|
|
||||||
def kill(self, message="Help! Another thread is killing me :(", alive=False):
|
def kill(self, message="Help! Another thread is killing me :(", alive=False):
|
||||||
"""Send quit message, flush queue, and close the socket
|
"""Send quit message, flush queue, and close the socket
|
||||||
|
|
||||||
:param message: Quit message
|
:param message: Quit message
|
||||||
:type message: str
|
:type message: str
|
||||||
:param alive: True causes a reconnect after disconnecting
|
:param alive: True causes a reconnect after disconnecting
|
||||||
|
@ -105,24 +111,24 @@ class IRCCore(asynchat.async_chat):
|
||||||
self.socket.shutdown(SHUT_RDWR)
|
self.socket.shutdown(SHUT_RDWR)
|
||||||
self.close()
|
self.close()
|
||||||
self.log.info("Kill complete")
|
self.log.info("Kill complete")
|
||||||
|
|
||||||
" Net related code here on down "
|
" Net related code here on down "
|
||||||
|
|
||||||
def getBuf(self):
|
def getBuf(self):
|
||||||
"""Return the network buffer and clear it"""
|
"""Return the network buffer and clear it"""
|
||||||
self.buffer.seek(0)
|
self.buffer.seek(0)
|
||||||
data = self.buffer.read()
|
data = self.buffer.read()
|
||||||
self.buffer = StringIO()
|
self.buffer = StringIO()
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def collect_incoming_data(self, data):
|
def collect_incoming_data(self, data):
|
||||||
"""Recieve data from the IRC server, append it to the buffer
|
"""Recieve data from the IRC server, append it to the buffer
|
||||||
|
|
||||||
:param data: the data that was recieved
|
:param data: the data that was recieved
|
||||||
:type data: str"""
|
:type data: str"""
|
||||||
#self.log.debug("<< %(message)s", {"message":repr(data)})
|
#self.log.info("<< %(message)s", {"message":repr(data)})
|
||||||
self.buffer.write(data)
|
self.buffer.write(data)
|
||||||
|
|
||||||
def found_terminator(self):
|
def found_terminator(self):
|
||||||
"""A complete command was pushed through, so clear the buffer and process it."""
|
"""A complete command was pushed through, so clear the buffer and process it."""
|
||||||
line = None
|
line = None
|
||||||
|
@ -135,70 +141,71 @@ class IRCCore(asynchat.async_chat):
|
||||||
self.log.error("found_terminator(): repr(data): %s" % repr(line))
|
self.log.error("found_terminator(): repr(data): %s" % repr(line))
|
||||||
self.log.error("found_terminator(): error: %s" % str(ude))
|
self.log.error("found_terminator(): error: %s" % str(ude))
|
||||||
return
|
return
|
||||||
|
self.log.debug("< {}".format(line))
|
||||||
self.process_data(line)
|
self.process_data(line)
|
||||||
|
|
||||||
def handle_close(self):
|
def handle_close(self):
|
||||||
"""Called when the socket is disconnected. Triggers the _DISCONNECT hook"""
|
"""Called when the socket is disconnected. Triggers the _DISCONNECT hook"""
|
||||||
self.log.debug("handle_close")
|
self.log.info("handle_close")
|
||||||
self.connected=False
|
self.connected=False
|
||||||
self.close()
|
self.close()
|
||||||
self.fire_hook("_DISCONNECT")
|
self.fire_hook("_DISCONNECT")
|
||||||
|
|
||||||
def handle_error(self, *args, **kwargs):
|
def handle_error(self, *args, **kwargs):
|
||||||
"""Called on fatal network errors."""
|
"""Called on fatal network errors."""
|
||||||
self.log.error("Connection failed (handle_error)")
|
self.log.error("Connection failed (handle_error)")
|
||||||
self.log.error(str(args))
|
self.log.error(str(args))
|
||||||
self.log.error(str(kwargs))
|
self.log.error(str(kwargs))
|
||||||
self.log.error(IRCCore.trace());
|
self.log.error(IRCCore.trace());
|
||||||
|
|
||||||
def _connect(self):
|
def _connect(self):
|
||||||
"""Connect to IRC"""
|
"""Connect to IRC"""
|
||||||
self.server+=1
|
self.server+=1
|
||||||
if self.server >= len(self.servers):
|
if self.server >= len(self.servers):
|
||||||
self.server=0
|
self.server=0
|
||||||
serverHostname = self.servers[self.server]
|
serverHostname = self.servers[self.server]
|
||||||
self.log.debug("Connecting to %(server)s:%(port)i", {"server":serverHostname, "port":self.port})
|
self.log.info("Connecting to %(server)s:%(port)i", {"server":serverHostname, "port":self.port})
|
||||||
socket_type = socket.AF_INET
|
socket_type = socket.AF_INET
|
||||||
if self.ipv6:
|
if self.ipv6:
|
||||||
self.log.info("IPv6 is enabled.")
|
self.log.info("IPv6 is enabled.")
|
||||||
socket_type = socket.AF_INET6
|
socket_type = socket.AF_INET6
|
||||||
socketInfo = socket.getaddrinfo(serverHostname, self.port, socket_type)
|
socketInfo = socket.getaddrinfo(serverHostname, self.port, socket_type)
|
||||||
self.create_socket(socket_type, socket.SOCK_STREAM)
|
self.create_socket(socket_type, socket.SOCK_STREAM)
|
||||||
self.log.debug("Socket created: %s" % self.socket.fileno())
|
self.log.info("Socket created: %s" % self.socket.fileno())
|
||||||
self.connect(socketInfo[0][4])
|
self.connect(socketInfo[0][4])
|
||||||
self.log.debug("Connection established")
|
self.log.info("Connection established")
|
||||||
self._fileno = self.socket.fileno()
|
self._fileno = self.socket.fileno()
|
||||||
self.asynmap[self._fileno] = self # http://willpython.blogspot.com/2010/08/multiple-event-loops-with-asyncore-and.html
|
self.asynmap[self._fileno] = self # http://willpython.blogspot.com/2010/08/multiple-event-loops-with-asyncore-and.html
|
||||||
self.log.info("_connect: Socket map: %s" % str(self.asynmap))
|
self.log.info("_connect: Socket map: %s" % str(self.asynmap))
|
||||||
|
|
||||||
def handle_connect(self):
|
def handle_connect(self):
|
||||||
"""When asynchat indicates our socket is connected, fire the _CONNECT hook"""
|
"""When asynchat indicates our socket is connected, fire the _CONNECT hook"""
|
||||||
self.connected=True
|
self.connected=True
|
||||||
self.log.debug("handle_connect: connected")
|
self.log.info("handle_connect: connected")
|
||||||
self.fire_hook("_CONNECT")
|
self.fire_hook("_CONNECT")
|
||||||
self.log.debug("handle_connect: complete")
|
self.log.info("handle_connect: complete")
|
||||||
|
|
||||||
def sendRaw(self, text, prio=2):
|
def sendRaw(self, text, prio=2):
|
||||||
"""Queue messages (raw string) to be sent to the IRC server
|
"""Queue messages (raw string) to be sent to the IRC server
|
||||||
|
|
||||||
:param text: the string to send
|
:param text: the string to send
|
||||||
:type text: str"""
|
:type text: str"""
|
||||||
text = (text+"\r\n").encode("UTF-8").decode().encode("UTF-8")
|
text = (text+"\r\n").encode("UTF-8").decode().encode("UTF-8")
|
||||||
self.outputQueue.put((prio, text), block=False)
|
self.outputQueue.put((prio, text), block=False)
|
||||||
|
|
||||||
def process_data(self, data):
|
def process_data(self, data):
|
||||||
"""Process one line of tet irc sent us
|
"""Process one line of tet irc sent us
|
||||||
|
|
||||||
:param data: the data to process
|
:param data: the data to process
|
||||||
:type data: str"""
|
:type data: str"""
|
||||||
if data.strip() == "":
|
if data.strip() == "":
|
||||||
return
|
return
|
||||||
|
|
||||||
prefix = None
|
prefix = None
|
||||||
command = None
|
command = None
|
||||||
args=[]
|
args=[]
|
||||||
trailing=None
|
trailing=None
|
||||||
|
|
||||||
if data[0]==":":
|
if data[0]==":":
|
||||||
prefix=data.split(" ")[0][1:]
|
prefix=data.split(" ")[0][1:]
|
||||||
data=data[data.find(" ")+1:]
|
data=data[data.find(" ")+1:]
|
||||||
|
@ -218,8 +225,8 @@ class IRCCore(asynchat.async_chat):
|
||||||
self.log.warning("Unknown command: cmd='%s' prefix='%s' args='%s' trailing='%s'" % (command, prefix, args, trailing))
|
self.log.warning("Unknown command: cmd='%s' prefix='%s' args='%s' trailing='%s'" % (command, prefix, args, trailing))
|
||||||
else:
|
else:
|
||||||
self.fire_hook(command, args=args, prefix=prefix, trailing=trailing)
|
self.fire_hook(command, args=args, prefix=prefix, trailing=trailing)
|
||||||
|
|
||||||
|
|
||||||
" Module related code "
|
" Module related code "
|
||||||
def initHooks(self):
|
def initHooks(self):
|
||||||
"""Defines hooks that modules can listen for events of"""
|
"""Defines hooks that modules can listen for events of"""
|
||||||
|
@ -251,7 +258,7 @@ class IRCCore(asynchat.async_chat):
|
||||||
'266', # :irc.129irc.com 266 CloneABCD :Current Global Users: 49 Max: 53
|
'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)
|
'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
|
'333', # :chaos.esper.net 333 xBotxShellTest #xMopx2 xMopxShell!~rduser@108.170.60.242 1344370109
|
||||||
'353', # :irc.129irc.com 353 CloneABCD = #clonea :CloneABCD CloneABC
|
'353', # :irc.129irc.com 353 CloneABCD = #clonea :CloneABCD CloneABC
|
||||||
'366', # :irc.129irc.com 366 CloneABCD #clonea :End of /NAMES list.
|
'366', # :irc.129irc.com 366 CloneABCD #clonea :End of /NAMES list.
|
||||||
'372', # :chaos.esper.net 372 xBotxShell :motd text here
|
'372', # :chaos.esper.net 372 xBotxShell :motd text here
|
||||||
'375', # :chaos.esper.net 375 xBotxShellTest :- chaos.esper.net Message of the Day -
|
'375', # :chaos.esper.net 375 xBotxShellTest :- chaos.esper.net Message of the Day -
|
||||||
|
@ -261,10 +268,10 @@ class IRCCore(asynchat.async_chat):
|
||||||
]
|
]
|
||||||
" mapping of hooks to methods "
|
" mapping of hooks to methods "
|
||||||
self.hookcalls = {command:[] for command in self.hooks}
|
self.hookcalls = {command:[] for command in self.hooks}
|
||||||
|
|
||||||
def fire_hook(self, command, args=None, prefix=None, trailing=None):
|
def fire_hook(self, command, args=None, prefix=None, trailing=None):
|
||||||
"""Run any listeners for a specific hook
|
"""Run any listeners for a specific hook
|
||||||
|
|
||||||
:param command: the hook to fire
|
:param command: the hook to fire
|
||||||
:type command: str
|
:type command: str
|
||||||
:param args: the list of arguments, if any, the command was passed
|
:param args: the list of arguments, if any, the command was passed
|
||||||
|
@ -273,20 +280,20 @@ class IRCCore(asynchat.async_chat):
|
||||||
:type prefix: str
|
:type prefix: str
|
||||||
:param trailing: data payload of the command
|
:param trailing: data payload of the command
|
||||||
:type trailing: str"""
|
:type trailing: str"""
|
||||||
|
|
||||||
for hook in self.hookcalls[command]:
|
for hook in self.hookcalls[command]:
|
||||||
try:
|
try:
|
||||||
if len(getargspec(hook).args) == 2:
|
if len(getargspec(hook).args) == 2:
|
||||||
hook(IRCCore.packetAsObject(args, prefix, trailing))
|
hook(IRCCore.packetAsObject(args, prefix, trailing))
|
||||||
else:
|
else:
|
||||||
hook(args, prefix, trailing)
|
hook(args, prefix, trailing)
|
||||||
|
|
||||||
except:
|
except:
|
||||||
self.log.warning("Error processing hook: \n%s"% self.trace())
|
self.log.warning("Error processing hook: \n%s"% self.trace())
|
||||||
|
|
||||||
def addHook(self, command, method):
|
def addHook(self, command, method):
|
||||||
"""**Internal.** Enable (connect) a single hook of a module
|
"""**Internal.** Enable (connect) a single hook of a module
|
||||||
|
|
||||||
:param command: command this hook will trigger on
|
:param command: command this hook will trigger on
|
||||||
:type command: str
|
:type command: str
|
||||||
:param method: callable method object to hook in
|
:param method: callable method object to hook in
|
||||||
|
@ -297,10 +304,10 @@ class IRCCore(asynchat.async_chat):
|
||||||
else:
|
else:
|
||||||
self.log.warning("Invalid hook - %s" % command)
|
self.log.warning("Invalid hook - %s" % command)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def removeHook(self, command, method):
|
def removeHook(self, command, method):
|
||||||
"""**Internal.** Disable (disconnect) a single hook of a module
|
"""**Internal.** Disable (disconnect) a single hook of a module
|
||||||
|
|
||||||
:param command: command this hook triggers on
|
:param command: command this hook triggers on
|
||||||
:type command: str
|
:type command: str
|
||||||
:param method: callable method that should be removed
|
:param method: callable method that should be removed
|
||||||
|
@ -313,10 +320,10 @@ class IRCCore(asynchat.async_chat):
|
||||||
else:
|
else:
|
||||||
self.log.warning("Invalid hook - %s" % command)
|
self.log.warning("Invalid hook - %s" % command)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def packetAsObject(args, prefix, trailing):
|
def packetAsObject(args, prefix, trailing):
|
||||||
"""Given an irc message's args, prefix, and trailing data return an object with these properties
|
"""Given an irc message's args, prefix, and trailing data return an object with these properties
|
||||||
|
|
||||||
:param args: list of args from the IRC packet
|
:param args: list of args from the IRC packet
|
||||||
:type args: list
|
:type args: list
|
||||||
:param prefix: prefix object parsed from the IRC packet
|
:param prefix: prefix object parsed from the IRC packet
|
||||||
|
@ -324,38 +331,31 @@ class IRCCore(asynchat.async_chat):
|
||||||
:param trailing: trailing data from the IRC packet
|
:param trailing: trailing data from the IRC packet
|
||||||
:type trailing: str
|
:type trailing: str
|
||||||
:returns: object -- a IRCEvent object with the ``args``, ``prefix``, ``trailing``"""
|
:returns: object -- a IRCEvent object with the ``args``, ``prefix``, ``trailing``"""
|
||||||
|
|
||||||
return type('IRCEvent', (object,), {
|
return IRCEvent(args,
|
||||||
"args": args,
|
IRCCore.decodePrefix(prefix) if prefix else None,
|
||||||
"prefix": IRCCore.decodePrefix(prefix) if prefix else None,
|
trailing)
|
||||||
"trailing": trailing
|
|
||||||
})
|
|
||||||
|
|
||||||
" Utility methods "
|
" Utility methods "
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def decodePrefix(prefix):
|
def decodePrefix(prefix):
|
||||||
"""Given a prefix like nick!username@hostname, return an object with these properties
|
"""Given a prefix like nick!username@hostname, return an object with these properties
|
||||||
|
|
||||||
:param prefix: the prefix to disassemble
|
:param prefix: the prefix to disassemble
|
||||||
:type prefix: str
|
:type prefix: str
|
||||||
:returns: object -- an UserPrefix object with the properties `nick`, `username`, `hostname` or a ServerPrefix object with the property `hostname`"""
|
:returns: object -- an UserPrefix object with the properties `nick`, `username`, `hostname` or a ServerPrefix object with the property `hostname`"""
|
||||||
if "!" in prefix:
|
if "!" in prefix:
|
||||||
ob = type('UserPrefix', (object,), {})
|
nick, prefix = prefix.split("!")
|
||||||
ob.str = prefix
|
username, hostname = prefix.split("@")
|
||||||
ob.nick, prefix = prefix.split("!")
|
return UserPrefix(nick, username, hostname)
|
||||||
ob.username, ob.hostname = prefix.split("@")
|
|
||||||
return ob
|
|
||||||
else:
|
else:
|
||||||
ob = type('ServerPrefix', (object,), {})
|
return ServerPrefix(prefix)
|
||||||
ob.str = prefix
|
|
||||||
ob.hostname = prefix
|
|
||||||
return ob
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def trace():
|
def trace():
|
||||||
"""Return the stack trace of the bot as a string"""
|
"""Return the stack trace of the bot as a string"""
|
||||||
return traceback.format_exc()
|
return traceback.format_exc()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def fulltrace():
|
def fulltrace():
|
||||||
"""Return the stack trace of the bot as a string"""
|
"""Return the stack trace of the bot as a string"""
|
||||||
|
@ -372,25 +372,25 @@ class IRCCore(asynchat.async_chat):
|
||||||
result += line + "\n"
|
result += line + "\n"
|
||||||
result += "\n*** STACKTRACE - END ***\n"
|
result += "\n*** STACKTRACE - END ***\n"
|
||||||
return result
|
return result
|
||||||
|
|
||||||
" Data Methods "
|
" Data Methods "
|
||||||
def get_nick(self):
|
def get_nick(self):
|
||||||
"""Get the bot's current nick
|
"""Get the bot's current nick
|
||||||
|
|
||||||
:returns: str - the bot's current nickname"""
|
:returns: str - the bot's current nickname"""
|
||||||
return self.nick
|
return self.nick
|
||||||
|
|
||||||
" Action Methods "
|
" Action Methods "
|
||||||
def act_PONG(self, data):
|
def act_PONG(self, data):
|
||||||
"""Use the `/pong` command - respond to server pings
|
"""Use the `/pong` command - respond to server pings
|
||||||
|
|
||||||
:param data: the string or number the server sent with it's ping
|
:param data: the string or number the server sent with it's ping
|
||||||
:type data: str"""
|
:type data: str"""
|
||||||
self.sendRaw("PONG :%s" % data)
|
self.sendRaw("PONG :%s" % data)
|
||||||
|
|
||||||
def act_USER(self, username, hostname, realname):
|
def act_USER(self, username, hostname, realname):
|
||||||
"""Use the USER protocol command. Used during connection
|
"""Use the USER protocol command. Used during connection
|
||||||
|
|
||||||
:param username: the bot's username
|
:param username: the bot's username
|
||||||
:type username: str
|
:type username: str
|
||||||
:param hostname: the bot's hostname
|
:param hostname: the bot's hostname
|
||||||
|
@ -398,34 +398,34 @@ class IRCCore(asynchat.async_chat):
|
||||||
:param realname: the bot's realname
|
:param realname: the bot's realname
|
||||||
:type realname: str"""
|
:type realname: str"""
|
||||||
self.sendRaw("USER %s %s %s :%s" % (username, hostname, self.servers[self.server], realname))
|
self.sendRaw("USER %s %s %s :%s" % (username, hostname, self.servers[self.server], realname))
|
||||||
|
|
||||||
def act_NICK(self, newNick):
|
def act_NICK(self, newNick):
|
||||||
"""Use the `/nick` command
|
"""Use the `/nick` command
|
||||||
|
|
||||||
:param newNick: new nick for the bot
|
:param newNick: new nick for the bot
|
||||||
:type newNick: str"""
|
:type newNick: str"""
|
||||||
self.nick = newNick
|
self.nick = newNick
|
||||||
self.sendRaw("NICK %s" % newNick)
|
self.sendRaw("NICK %s" % newNick)
|
||||||
|
|
||||||
def act_JOIN(self, channel):
|
def act_JOIN(self, channel):
|
||||||
"""Use the `/join` command
|
"""Use the `/join` command
|
||||||
|
|
||||||
:param channel: the channel to attempt to join
|
:param channel: the channel to attempt to join
|
||||||
:type channel: str"""
|
:type channel: str"""
|
||||||
self.sendRaw("JOIN %s"%channel)
|
self.sendRaw("JOIN %s"%channel)
|
||||||
|
|
||||||
def act_PRIVMSG(self, towho, message):
|
def act_PRIVMSG(self, towho, message):
|
||||||
"""Use the `/msg` command
|
"""Use the `/msg` command
|
||||||
|
|
||||||
:param towho: the target #channel or user's name
|
:param towho: the target #channel or user's name
|
||||||
:type towho: str
|
:type towho: str
|
||||||
:param message: the message to send
|
:param message: the message to send
|
||||||
:type message: str"""
|
:type message: str"""
|
||||||
self.sendRaw("PRIVMSG %s :%s"%(towho,message))
|
self.sendRaw("PRIVMSG %s :%s"%(towho,message))
|
||||||
|
|
||||||
def act_MODE(self, channel, mode, extra=None):
|
def act_MODE(self, channel, mode, extra=None):
|
||||||
"""Use the `/mode` command
|
"""Use the `/mode` command
|
||||||
|
|
||||||
:param channel: the channel this mode is for
|
:param channel: the channel this mode is for
|
||||||
:type channel: str
|
:type channel: str
|
||||||
:param mode: the mode string. Example: +b
|
:param mode: the mode string. Example: +b
|
||||||
|
@ -436,19 +436,19 @@ class IRCCore(asynchat.async_chat):
|
||||||
self.sendRaw("MODE %s %s %s" % (channel,mode,extra))
|
self.sendRaw("MODE %s %s %s" % (channel,mode,extra))
|
||||||
else:
|
else:
|
||||||
self.sendRaw("MODE %s %s" % (channel,mode))
|
self.sendRaw("MODE %s %s" % (channel,mode))
|
||||||
|
|
||||||
def act_ACTION(self, channel, action):
|
def act_ACTION(self, channel, action):
|
||||||
"""Use the `/me <action>` command
|
"""Use the `/me <action>` command
|
||||||
|
|
||||||
:param channel: the channel name or target's name the message is sent to
|
:param channel: the channel name or target's name the message is sent to
|
||||||
:type channel: str
|
:type channel: str
|
||||||
:param action: the text to send
|
:param action: the text to send
|
||||||
:type action: str"""
|
:type action: str"""
|
||||||
self.sendRaw("PRIVMSG %s :\x01ACTION %s"%(channel,action))
|
self.sendRaw("PRIVMSG %s :\x01ACTION %s"%(channel,action))
|
||||||
|
|
||||||
def act_KICK(self, channel, who, comment=""):
|
def act_KICK(self, channel, who, comment=""):
|
||||||
"""Use the `/kick <user> <message>` command
|
"""Use the `/kick <user> <message>` command
|
||||||
|
|
||||||
:param channel: the channel from which the user will be kicked
|
:param channel: the channel from which the user will be kicked
|
||||||
:type channel: str
|
:type channel: str
|
||||||
:param who: the nickname of the user to kick
|
:param who: the nickname of the user to kick
|
||||||
|
@ -456,14 +456,14 @@ class IRCCore(asynchat.async_chat):
|
||||||
:param comment: the kick message
|
:param comment: the kick message
|
||||||
:type comment: str"""
|
:type comment: str"""
|
||||||
self.sendRaw("KICK %s %s :%s" % (channel, who, comment))
|
self.sendRaw("KICK %s %s :%s" % (channel, who, comment))
|
||||||
|
|
||||||
def act_QUIT(self, message):
|
def act_QUIT(self, message):
|
||||||
"""Use the `/quit` command
|
"""Use the `/quit` command
|
||||||
|
|
||||||
:param message: quit message
|
:param message: quit message
|
||||||
:type message: str"""
|
:type message: str"""
|
||||||
self.sendRaw("QUIT :%s" % message, prio=0)
|
self.sendRaw("QUIT :%s" % message, prio=0)
|
||||||
|
|
||||||
class OutputQueueRunner(Thread):
|
class OutputQueueRunner(Thread):
|
||||||
"""Rate-limited output queue"""
|
"""Rate-limited output queue"""
|
||||||
def __init__(self, bot):
|
def __init__(self, bot):
|
||||||
|
@ -471,7 +471,7 @@ class OutputQueueRunner(Thread):
|
||||||
self.bot = bot #reference to main bot thread
|
self.bot = bot #reference to main bot thread
|
||||||
self.log = logging.getLogger('OutputQueueRunner')
|
self.log = logging.getLogger('OutputQueueRunner')
|
||||||
self.paused = False
|
self.paused = False
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
"""Constantly sends messages unless bot is disconnecting/ed"""
|
"""Constantly sends messages unless bot is disconnecting/ed"""
|
||||||
lastSend = time()
|
lastSend = time()
|
||||||
|
@ -481,24 +481,25 @@ class OutputQueueRunner(Thread):
|
||||||
if sinceLast < self.bot.SEND_WAIT:
|
if sinceLast < self.bot.SEND_WAIT:
|
||||||
toSleep = self.bot.SEND_WAIT - sinceLast
|
toSleep = self.bot.SEND_WAIT - sinceLast
|
||||||
sleep(toSleep)
|
sleep(toSleep)
|
||||||
|
|
||||||
# Pop item and execute
|
# Pop item and execute
|
||||||
if self.bot.connected and not self.paused:
|
if self.bot.connected and not self.paused:
|
||||||
try:
|
try:
|
||||||
self.process_queue_item()
|
self.process_queue_item()
|
||||||
lastSend = time()
|
lastSend = time()
|
||||||
except queue.Empty:
|
except queue.Empty:
|
||||||
#self.log.debug("Queue is empty")
|
#self.log.info("Queue is empty")
|
||||||
pass
|
pass
|
||||||
sleep(0.01)
|
sleep(0.01)
|
||||||
|
|
||||||
def process_queue_item(self):
|
def process_queue_item(self):
|
||||||
"""Remove 1 item from queue and process it"""
|
"""Remove 1 item from queue and process it"""
|
||||||
prio,text = self.bot.outputQueue.get(block=True, timeout=10)
|
prio,text = self.bot.outputQueue.get(block=True, timeout=10)
|
||||||
#self.log.debug("%s>> %s" % (prio,text))
|
#self.log.info("%s>> %s" % (prio,text))
|
||||||
self.bot.outputQueue.task_done()
|
self.bot.outputQueue.task_done()
|
||||||
|
self.log.debug("> {}".format(text.decode('UTF-8')))
|
||||||
self.bot.send(text)
|
self.bot.send(text)
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
"""Discard all items from queue"""
|
"""Discard all items from queue"""
|
||||||
length = self.bot.outputQueue.qsize()
|
length = self.bot.outputQueue.qsize()
|
||||||
|
@ -507,9 +508,9 @@ class OutputQueueRunner(Thread):
|
||||||
self.bot.outputQueue.get(block=False)
|
self.bot.outputQueue.get(block=False)
|
||||||
except queue.Empty:
|
except queue.Empty:
|
||||||
pass
|
pass
|
||||||
#self.log.debug("output queue cleared")
|
#self.log.info("output queue cleared")
|
||||||
return length
|
return length
|
||||||
|
|
||||||
def flush(self):
|
def flush(self):
|
||||||
"""Process all items in queue"""
|
"""Process all items in queue"""
|
||||||
for i in range(0, self.bot.outputQueue.qsize()):
|
for i in range(0, self.bot.outputQueue.qsize()):
|
||||||
|
@ -517,4 +518,4 @@ class OutputQueueRunner(Thread):
|
||||||
self.process_queue_item()
|
self.process_queue_item()
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
#self.log.debug("output queue flushed")
|
#self.log.info("output queue flushed")
|
||||||
|
|
|
@ -166,9 +166,9 @@ class AttributeStorage(ModuleBase):
|
||||||
if value == None:
|
if value == None:
|
||||||
# delete it
|
# delete it
|
||||||
c = self.db.connection.query("DELETE FROM `values` WHERE `itemid`=%s AND `attributeid`=%s ;", (itemId, attributeId))
|
c = self.db.connection.query("DELETE FROM `values` WHERE `itemid`=%s AND `attributeid`=%s ;", (itemId, attributeId))
|
||||||
self.log.debug("AttributeStorage: Stored item %s attribute %s value: %s (Deleted)" % (itemId, attributeId, value))
|
self.log.info("AttributeStorage: Stored item %s attribute %s value: %s (Deleted)" % (itemId, attributeId, value))
|
||||||
else:
|
else:
|
||||||
# add attribute
|
# add attribute
|
||||||
c = self.db.connection.query("REPLACE INTO `values` (`itemid`, `attributeid`, `value`) VALUES (%s, %s, %s);", (itemId, attributeId, value))
|
c = self.db.connection.query("REPLACE INTO `values` (`itemid`, `attributeid`, `value`) VALUES (%s, %s, %s);", (itemId, attributeId, value))
|
||||||
self.log.debug("AttributeStorage: Stored item %s attribute %s value: %s" % (itemId, attributeId, value))
|
self.log.info("AttributeStorage: Stored item %s attribute %s value: %s" % (itemId, attributeId, value))
|
||||||
c.close()
|
c.close()
|
||||||
|
|
|
@ -161,9 +161,9 @@ class AttributeStorageLite(ModuleBase):
|
||||||
if value == None:
|
if value == None:
|
||||||
# delete it
|
# delete it
|
||||||
c = self.db.query("DELETE FROM `values` WHERE `itemid`=? AND `attributeid`=? ;", (itemId, attributeId))
|
c = self.db.query("DELETE FROM `values` WHERE `itemid`=? AND `attributeid`=? ;", (itemId, attributeId))
|
||||||
self.log.debug("Stored item %s attribute %s value: %s (Deleted)" % (itemId, attributeId, value))
|
self.log.info("Stored item %s attribute %s value: %s (Deleted)" % (itemId, attributeId, value))
|
||||||
else:
|
else:
|
||||||
# add attribute
|
# add attribute
|
||||||
c = self.db.query("REPLACE INTO `values` (`itemid`, `attributeid`, `value`) VALUES (?, ?, ?);", (itemId, attributeId, value))
|
c = self.db.query("REPLACE INTO `values` (`itemid`, `attributeid`, `value`) VALUES (?, ?, ?);", (itemId, attributeId, value))
|
||||||
self.log.debug("Stored item %s attribute %s value: %s" % (itemId, attributeId, value))
|
self.log.info("Stored item %s attribute %s value: %s" % (itemId, attributeId, value))
|
||||||
c.close()
|
c.close()
|
||||||
|
|
|
@ -121,13 +121,13 @@ class BitcoinRPC:
|
||||||
|
|
||||||
def connect(self):
|
def connect(self):
|
||||||
# internal. connect to the service
|
# internal. connect to the service
|
||||||
self.log.debug("CryptoWalletRPC: %s: Connecting to %s:%s" % (self.name, self.host,self.port))
|
self.log.info("CryptoWalletRPC: %s: Connecting to %s:%s" % (self.name, self.host,self.port))
|
||||||
try:
|
try:
|
||||||
self.con = AuthServiceProxy("http://%s:%s@%s:%s" % (self.username, self.password, self.host, self.port))
|
self.con = AuthServiceProxy("http://%s:%s@%s:%s" % (self.username, self.password, self.host, self.port))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.log.debug("CryptoWalletRPC: %s: Could not connect to %s:%s: %s" % (self.name, self.host, self.port, str(e)))
|
self.log.info("CryptoWalletRPC: %s: Could not connect to %s:%s: %s" % (self.name, self.host, self.port, str(e)))
|
||||||
return
|
return
|
||||||
|
|
||||||
self.log.debug("CryptoWalletRPC: %s: Connected to %s:%s" % (self.name, self.host, self.port))
|
self.log.info("CryptoWalletRPC: %s: Connected to %s:%s" % (self.name, self.host, self.port))
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -73,7 +73,7 @@ class DogeController:
|
||||||
|
|
||||||
def connect(self):
|
def connect(self):
|
||||||
"Connect to RPC endpoint"
|
"Connect to RPC endpoint"
|
||||||
self.log.debug("DogeRPC: Connecting to dogecoind")
|
self.log.info("DogeRPC: Connecting to dogecoind")
|
||||||
self.con = AuthServiceProxy("http://%s:%s@%s:%s" % (self.config["username"], self.config["password"], self.config["host"], self.config["port"]))
|
self.con = AuthServiceProxy("http://%s:%s@%s:%s" % (self.config["username"], self.config["password"], self.config["host"], self.config["port"]))
|
||||||
self.con.getinfo()
|
self.con.getinfo()
|
||||||
self.log.debug("DogeRPC: Connected to %s:%s" % (self.config["host"], self.config["port"]))
|
self.log.info("DogeRPC: Connected to %s:%s" % (self.config["host"], self.config["port"]))
|
||||||
|
|
|
@ -145,7 +145,7 @@ class scrambleGame:
|
||||||
self.nextTimer.start()
|
self.nextTimer.start()
|
||||||
self.guesses=0
|
self.guesses=0
|
||||||
self.category_count+=1
|
self.category_count+=1
|
||||||
self.master.log.debug("DogeScramble: category_count is: %s" % (self.category_count))
|
self.master.log.info("DogeScramble: category_count is: %s" % (self.category_count))
|
||||||
if self.category_count >= self.change_category_after_words:
|
if self.category_count >= self.change_category_after_words:
|
||||||
self.should_change_category = True
|
self.should_change_category = True
|
||||||
else:
|
else:
|
||||||
|
@ -247,7 +247,7 @@ class scrambleGame:
|
||||||
picked = f.readline().strip().lower()
|
picked = f.readline().strip().lower()
|
||||||
f.close()
|
f.close()
|
||||||
|
|
||||||
self.master.log.debug("DogeScramble: picked %s for %s" % (picked, self.channel))
|
self.master.log.info("DogeScramble: picked %s for %s" % (picked, self.channel))
|
||||||
self.lastwords.append(picked)
|
self.lastwords.append(picked)
|
||||||
if len(self.lastwords) > 5:
|
if len(self.lastwords) > 5:
|
||||||
self.lastwords.pop(0)
|
self.lastwords.pop(0)
|
||||||
|
|
|
@ -111,13 +111,13 @@ class LinkTitler(ModuleBase):
|
||||||
|
|
||||||
def url_headers(self, url):
|
def url_headers(self, url):
|
||||||
"HEAD requests a url to check content type & length, returns something like: {'type': 'image/jpeg', 'size': '90583'}"
|
"HEAD requests a url to check content type & length, returns something like: {'type': 'image/jpeg', 'size': '90583'}"
|
||||||
self.log.debug("url_headers(%s)" % (url,))
|
self.log.info("url_headers(%s)" % (url,))
|
||||||
resp = head(url=url, allow_redirects=True)
|
resp = head(url=url, allow_redirects=True)
|
||||||
return resp.headers
|
return resp.headers
|
||||||
|
|
||||||
def url_htmltitle(self, url):
|
def url_htmltitle(self, url):
|
||||||
"Requests page html and returns title in a safe way"
|
"Requests page html and returns title in a safe way"
|
||||||
self.log.debug("url_htmltitle(%s)" % (url,))
|
self.log.info("url_htmltitle(%s)" % (url,))
|
||||||
resp = get(url=url, stream=True)
|
resp = get(url=url, stream=True)
|
||||||
# Fetch no more than first 10kb
|
# Fetch no more than first 10kb
|
||||||
# if the title isn't seen by then, you're doing it wrong
|
# if the title isn't seen by then, you're doing it wrong
|
||||||
|
|
|
@ -41,6 +41,6 @@ class Triggered(ModuleBase):
|
||||||
|
|
||||||
def scream(self, channel):
|
def scream(self, channel):
|
||||||
delay = randrange(self.config["mindelay"], self.config["maxdelay"])
|
delay = randrange(self.config["mindelay"], self.config["maxdelay"])
|
||||||
self.log.debug("Sleeping for %s seconds" % delay)
|
self.log.info("Sleeping for %s seconds" % delay)
|
||||||
sleep(delay)
|
sleep(delay)
|
||||||
self.bot.act_PRIVMSG(channel, choice(self.config["responses"]))
|
self.bot.act_PRIVMSG(channel, choice(self.config["responses"]))
|
||||||
|
|
|
@ -12,38 +12,42 @@ import sys
|
||||||
import traceback
|
import traceback
|
||||||
from pyircbot.rpc import BotRPC
|
from pyircbot.rpc import BotRPC
|
||||||
from pyircbot.irccore import IRCCore
|
from pyircbot.irccore import IRCCore
|
||||||
|
from collections import namedtuple
|
||||||
import os.path
|
import os.path
|
||||||
|
|
||||||
|
ParsedCommand = namedtuple("ParsedCommand", "command args args_str message")
|
||||||
|
|
||||||
|
|
||||||
class PyIRCBot:
|
class PyIRCBot:
|
||||||
""":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.
|
||||||
:type botconfig: dict
|
:type botconfig: dict
|
||||||
"""
|
"""
|
||||||
version = "4.0.0-r03"
|
version = "4.0.0-r03"
|
||||||
|
|
||||||
def __init__(self, botconfig):
|
def __init__(self, botconfig):
|
||||||
self.log = logging.getLogger('PyIRCBot')
|
self.log = logging.getLogger('PyIRCBot')
|
||||||
"""Reference to logger object"""
|
"""Reference to logger object"""
|
||||||
|
|
||||||
self.botconfig = botconfig
|
self.botconfig = botconfig
|
||||||
"""saved copy of the instance config"""
|
"""saved copy of the instance config"""
|
||||||
|
|
||||||
"""storage of imported modules"""
|
"""storage of imported modules"""
|
||||||
self.modules = {}
|
self.modules = {}
|
||||||
|
|
||||||
"""instances of modules"""
|
"""instances of modules"""
|
||||||
self.moduleInstances = {}
|
self.moduleInstances = {}
|
||||||
|
|
||||||
self.rpc = BotRPC(self)
|
self.rpc = BotRPC(self)
|
||||||
"""Reference to BotRPC thread"""
|
"""Reference to BotRPC thread"""
|
||||||
|
|
||||||
self.irc = IRCCore()
|
self.irc = IRCCore()
|
||||||
"""IRC protocol class"""
|
"""IRC protocol class"""
|
||||||
self.irc.servers = self.botconfig["connection"]["servers"]
|
self.irc.servers = self.botconfig["connection"]["servers"]
|
||||||
self.irc.port = self.botconfig["connection"]["port"]
|
self.irc.port = self.botconfig["connection"]["port"]
|
||||||
self.irc.ipv6 = True if self.botconfig["connection"]["ipv6"]=="on" else False
|
self.irc.ipv6 = True if self.botconfig["connection"]["ipv6"]=="on" else False
|
||||||
|
|
||||||
self.irc.addHook("_DISCONNECT", self.connection_closed)
|
self.irc.addHook("_DISCONNECT", self.connection_closed)
|
||||||
|
|
||||||
# legacy support
|
# legacy support
|
||||||
self.act_PONG = self.irc.act_PONG
|
self.act_PONG = self.irc.act_PONG
|
||||||
self.act_USER = self.irc.act_USER
|
self.act_USER = self.irc.act_USER
|
||||||
|
@ -56,26 +60,26 @@ class PyIRCBot:
|
||||||
self.act_QUIT = self.irc.act_QUIT
|
self.act_QUIT = self.irc.act_QUIT
|
||||||
self.get_nick = self.irc.get_nick
|
self.get_nick = self.irc.get_nick
|
||||||
self.decodePrefix = IRCCore.decodePrefix
|
self.decodePrefix = IRCCore.decodePrefix
|
||||||
|
|
||||||
# Load modules
|
# Load modules
|
||||||
self.initModules()
|
self.initModules()
|
||||||
|
|
||||||
# Connect to IRC
|
# Connect to IRC
|
||||||
self.connect()
|
self.connect()
|
||||||
|
|
||||||
def connect(self):
|
def connect(self):
|
||||||
try:
|
try:
|
||||||
self.irc._connect()
|
self.irc._connect()
|
||||||
except:
|
except:
|
||||||
self.log.error("Pyircbot attempted to connect and failed!")
|
self.log.error("Pyircbot attempted to connect and failed!")
|
||||||
self.log.error(traceback.format_exc())
|
self.log.error(traceback.format_exc())
|
||||||
|
|
||||||
def loop(self):
|
def loop(self):
|
||||||
self.irc.loop()
|
self.irc.loop()
|
||||||
|
|
||||||
def disconnect(self, message, reconnect=True):
|
def disconnect(self, message, reconnect=True):
|
||||||
"""Send quit message and disconnect from IRC.
|
"""Send quit message and disconnect from IRC.
|
||||||
|
|
||||||
:param message: Quit message
|
:param message: Quit message
|
||||||
:type message: str
|
:type message: str
|
||||||
:param reconnect: True causes a reconnection attempt to be made after the disconnect
|
:param reconnect: True causes a reconnection attempt to be made after the disconnect
|
||||||
|
@ -83,10 +87,10 @@ class PyIRCBot:
|
||||||
"""
|
"""
|
||||||
self.log.info("disconnect")
|
self.log.info("disconnect")
|
||||||
self.irc.kill(message=message, alive=reconnect)
|
self.irc.kill(message=message, alive=reconnect)
|
||||||
|
|
||||||
def kill(self, sys_exit=True, message="Help! Another thread is killing me :("):
|
def kill(self, sys_exit=True, message="Help! Another thread is killing me :("):
|
||||||
"""Shut down the bot violently
|
"""Shut down the bot violently
|
||||||
|
|
||||||
:param sys_exit: True causes sys.exit(0) to be called
|
:param sys_exit: True causes sys.exit(0) to be called
|
||||||
:type sys_exit: bool
|
:type sys_exit: bool
|
||||||
:param message: Quit message
|
:param message: Quit message
|
||||||
|
@ -94,39 +98,39 @@ class PyIRCBot:
|
||||||
"""
|
"""
|
||||||
#Close all modules
|
#Close all modules
|
||||||
self.closeAllModules()
|
self.closeAllModules()
|
||||||
|
|
||||||
self.irc.kill(message=message, alive=not sys_exit)
|
self.irc.kill(message=message, alive=not sys_exit)
|
||||||
|
|
||||||
if sys_exit:
|
if sys_exit:
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
def connection_closed(self, args, prefix, trailing):
|
def connection_closed(self, args, prefix, trailing):
|
||||||
"""Called when the socket is disconnected. We will want to reconnect. """
|
"""Called when the socket is disconnected. We will want to reconnect. """
|
||||||
if self.irc.alive:
|
if self.irc.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 initModules(self):
|
def initModules(self):
|
||||||
"""load modules specified in instance config"""
|
"""load modules specified in instance config"""
|
||||||
" append module location to path "
|
" append module location to path "
|
||||||
sys.path.append(os.path.dirname(__file__)+"/modules/")
|
sys.path.append(os.path.dirname(__file__)+"/modules/")
|
||||||
|
|
||||||
" append usermodule dir to beginning of path"
|
" append usermodule dir to beginning of path"
|
||||||
for path in self.botconfig["bot"]["usermodules"]:
|
for path in self.botconfig["bot"]["usermodules"]:
|
||||||
sys.path.insert(0, path+"/")
|
sys.path.insert(0, path+"/")
|
||||||
|
|
||||||
for modulename in self.botconfig["modules"]:
|
for modulename in self.botconfig["modules"]:
|
||||||
self.loadmodule(modulename)
|
self.loadmodule(modulename)
|
||||||
|
|
||||||
def importmodule(self, name):
|
def importmodule(self, name):
|
||||||
"""Import a module
|
"""Import a module
|
||||||
|
|
||||||
:param moduleName: Name of the module to import
|
:param moduleName: Name of the module to import
|
||||||
:type moduleName: str"""
|
:type moduleName: str"""
|
||||||
" check if already exists "
|
" check if already exists "
|
||||||
if not name in self.modules:
|
if not name in self.modules:
|
||||||
self.log.debug("Importing %s" % name)
|
self.log.info("Importing %s" % name)
|
||||||
" attempt to load "
|
" attempt to load "
|
||||||
try:
|
try:
|
||||||
moduleref = __import__(name)
|
moduleref = __import__(name)
|
||||||
|
@ -140,13 +144,13 @@ class PyIRCBot:
|
||||||
else:
|
else:
|
||||||
self.log.warning("Module %s already imported" % name)
|
self.log.warning("Module %s already imported" % name)
|
||||||
return (False, "Module already imported")
|
return (False, "Module already imported")
|
||||||
|
|
||||||
def deportmodule(self, name):
|
def deportmodule(self, name):
|
||||||
"""Remove a module's code from memory. If the module is loaded it will be unloaded silently.
|
"""Remove a module's code from memory. If the module is loaded it will be unloaded silently.
|
||||||
|
|
||||||
:param moduleName: Name of the module to import
|
:param moduleName: Name of the module to import
|
||||||
:type moduleName: str"""
|
:type moduleName: str"""
|
||||||
self.log.debug("Deporting %s" % name)
|
self.log.info("Deporting %s" % name)
|
||||||
" unload if necessary "
|
" unload if necessary "
|
||||||
if name in self.moduleInstances:
|
if name in self.moduleInstances:
|
||||||
self.unloadmodule(name)
|
self.unloadmodule(name)
|
||||||
|
@ -158,10 +162,10 @@ class PyIRCBot:
|
||||||
" delete copy that python stores in sys.modules "
|
" delete copy that python stores in sys.modules "
|
||||||
if name in sys.modules:
|
if name in sys.modules:
|
||||||
del sys.modules[name]
|
del sys.modules[name]
|
||||||
|
|
||||||
def loadmodule(self, name):
|
def loadmodule(self, name):
|
||||||
"""Activate a module.
|
"""Activate a module.
|
||||||
|
|
||||||
:param moduleName: Name of the module to activate
|
:param moduleName: Name of the module to activate
|
||||||
:type moduleName: str"""
|
:type moduleName: str"""
|
||||||
" check if already loaded "
|
" check if already loaded "
|
||||||
|
@ -177,10 +181,10 @@ class PyIRCBot:
|
||||||
self.moduleInstances[name] = getattr(self.modules[name], name)(self, name)
|
self.moduleInstances[name] = getattr(self.modules[name], name)(self, name)
|
||||||
" load hooks "
|
" load hooks "
|
||||||
self.loadModuleHooks(self.moduleInstances[name])
|
self.loadModuleHooks(self.moduleInstances[name])
|
||||||
|
|
||||||
def unloadmodule(self, name):
|
def unloadmodule(self, name):
|
||||||
"""Deactivate a module.
|
"""Deactivate a module.
|
||||||
|
|
||||||
:param moduleName: Name of the module to deactivate
|
:param moduleName: Name of the module to deactivate
|
||||||
:type moduleName: str"""
|
:type moduleName: str"""
|
||||||
if name in self.moduleInstances:
|
if name in self.moduleInstances:
|
||||||
|
@ -197,10 +201,10 @@ class PyIRCBot:
|
||||||
else:
|
else:
|
||||||
self.log.info("Module %s not loaded" % name)
|
self.log.info("Module %s not loaded" % name)
|
||||||
return (False, "Module not loaded")
|
return (False, "Module not loaded")
|
||||||
|
|
||||||
def reloadmodule(self, name):
|
def reloadmodule(self, name):
|
||||||
"""Deactivate and activate a module.
|
"""Deactivate and activate a module.
|
||||||
|
|
||||||
:param moduleName: Name of the target module
|
:param moduleName: Name of the target module
|
||||||
:type moduleName: str"""
|
:type moduleName: str"""
|
||||||
" make sure it's imporeted"
|
" make sure it's imporeted"
|
||||||
|
@ -215,10 +219,10 @@ class PyIRCBot:
|
||||||
self.loadmodule(name)
|
self.loadmodule(name)
|
||||||
return (True, None)
|
return (True, None)
|
||||||
return (False, "Module is not loaded")
|
return (False, "Module is not loaded")
|
||||||
|
|
||||||
def redomodule(self, name):
|
def redomodule(self, name):
|
||||||
"""Reload a running module from disk
|
"""Reload a running module from disk
|
||||||
|
|
||||||
:param moduleName: Name of the target module
|
:param moduleName: Name of the target module
|
||||||
:type moduleName: str"""
|
:type moduleName: str"""
|
||||||
" remember if it was loaded before "
|
" remember if it was loaded before "
|
||||||
|
@ -233,10 +237,10 @@ class PyIRCBot:
|
||||||
if loadedbefore:
|
if loadedbefore:
|
||||||
self.loadmodule(name)
|
self.loadmodule(name)
|
||||||
return (True, None)
|
return (True, None)
|
||||||
|
|
||||||
def loadModuleHooks(self, module):
|
def loadModuleHooks(self, module):
|
||||||
"""**Internal.** Enable (connect) hooks of a module
|
"""**Internal.** Enable (connect) hooks of a module
|
||||||
|
|
||||||
:param module: module object to hook in
|
:param module: module object to hook in
|
||||||
:type module: object"""
|
:type module: object"""
|
||||||
" activate a module's hooks "
|
" activate a module's hooks "
|
||||||
|
@ -246,10 +250,10 @@ class PyIRCBot:
|
||||||
self.irc.addHook(hookcmd, hook.method)
|
self.irc.addHook(hookcmd, hook.method)
|
||||||
else:
|
else:
|
||||||
self.irc.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
|
||||||
|
|
||||||
:param module: module object to unhook
|
:param module: module object to unhook
|
||||||
:type module: object"""
|
:type module: object"""
|
||||||
" remove a modules hooks "
|
" remove a modules hooks "
|
||||||
|
@ -259,20 +263,20 @@ class PyIRCBot:
|
||||||
self.irc.removeHook(hookcmd, hook.method)
|
self.irc.removeHook(hookcmd, hook.method)
|
||||||
else:
|
else:
|
||||||
self.irc.removeHook(hook.hook, hook.method)
|
self.irc.removeHook(hook.hook, hook.method)
|
||||||
|
|
||||||
def getmodulebyname(self, name):
|
def getmodulebyname(self, name):
|
||||||
"""Get a module object by name
|
"""Get a module object by name
|
||||||
|
|
||||||
:param name: name of the module to return
|
:param name: name of the module to return
|
||||||
:type name: str
|
:type name: str
|
||||||
:returns: object -- the module object"""
|
:returns: object -- the module object"""
|
||||||
if not name in self.moduleInstances:
|
if not name in self.moduleInstances:
|
||||||
return None
|
return None
|
||||||
return self.moduleInstances[name]
|
return self.moduleInstances[name]
|
||||||
|
|
||||||
def getmodulesbyservice(self, service):
|
def getmodulesbyservice(self, service):
|
||||||
"""Get a list of modules that provide the specified service
|
"""Get a list of modules that provide the specified service
|
||||||
|
|
||||||
:param service: name of the service searched for
|
:param service: name of the service searched for
|
||||||
:type service: str
|
:type service: str
|
||||||
:returns: list -- a list of module objects"""
|
:returns: list -- a list of module objects"""
|
||||||
|
@ -281,10 +285,10 @@ class PyIRCBot:
|
||||||
if service in self.moduleInstances[module].services:
|
if service in self.moduleInstances[module].services:
|
||||||
validModules.append(self.moduleInstances[module])
|
validModules.append(self.moduleInstances[module])
|
||||||
return validModules
|
return validModules
|
||||||
|
|
||||||
def getBestModuleForService(self, service):
|
def getBestModuleForService(self, service):
|
||||||
"""Get the first module that provides the specified service
|
"""Get the first module that provides the specified service
|
||||||
|
|
||||||
:param service: name of the service searched for
|
:param service: name of the service searched for
|
||||||
:type service: str
|
:type service: str
|
||||||
:returns: object -- the module object, if found. None if not found."""
|
:returns: object -- the module object, if found. None if not found."""
|
||||||
|
@ -292,7 +296,7 @@ class PyIRCBot:
|
||||||
if len(m)>0:
|
if len(m)>0:
|
||||||
return m[0]
|
return m[0]
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def closeAllModules(self):
|
def closeAllModules(self):
|
||||||
""" Deport all modules (for shutdown). Modules are unloaded in the opposite order listed in the config. """
|
""" Deport all modules (for shutdown). Modules are unloaded in the opposite order listed in the config. """
|
||||||
loaded = list(self.moduleInstances.keys())
|
loaded = list(self.moduleInstances.keys())
|
||||||
|
@ -304,41 +308,41 @@ class PyIRCBot:
|
||||||
self.deportmodule(key)
|
self.deportmodule(key)
|
||||||
for key in loaded:
|
for key in loaded:
|
||||||
self.deportmodule(key)
|
self.deportmodule(key)
|
||||||
|
|
||||||
" Filesystem Methods "
|
" Filesystem Methods "
|
||||||
def getDataPath(self, moduleName):
|
def getDataPath(self, moduleName):
|
||||||
"""Return the absolute path for a module's data dir
|
"""Return the absolute path for a module's data dir
|
||||||
|
|
||||||
:param moduleName: the module who's data dir we want
|
:param moduleName: the module who's data dir we want
|
||||||
:type moduleName: str"""
|
:type moduleName: str"""
|
||||||
if not os.path.exists("%s/data/%s" % (self.botconfig["bot"]["datadir"], moduleName)):
|
if not os.path.exists("%s/data/%s" % (self.botconfig["bot"]["datadir"], moduleName)):
|
||||||
os.mkdir("%s/data/%s/" % (self.botconfig["bot"]["datadir"], moduleName))
|
os.mkdir("%s/data/%s/" % (self.botconfig["bot"]["datadir"], moduleName))
|
||||||
return "%s/data/%s/" % (self.botconfig["bot"]["datadir"], moduleName)
|
return "%s/data/%s/" % (self.botconfig["bot"]["datadir"], moduleName)
|
||||||
|
|
||||||
def getConfigPath(self, moduleName):
|
def getConfigPath(self, moduleName):
|
||||||
"""Return the absolute path for a module's config file
|
"""Return the absolute path for a module's config file
|
||||||
|
|
||||||
:param moduleName: the module who's config file we want
|
:param moduleName: the module who's config file we want
|
||||||
:type moduleName: str"""
|
:type moduleName: str"""
|
||||||
|
|
||||||
basepath = "%s/config/%s" % (self.botconfig["bot"]["datadir"], moduleName)
|
basepath = "%s/config/%s" % (self.botconfig["bot"]["datadir"], moduleName)
|
||||||
|
|
||||||
if os.path.exists("%s.json"%basepath):
|
if os.path.exists("%s.json"%basepath):
|
||||||
return "%s.json"%basepath
|
return "%s.json"%basepath
|
||||||
return None
|
return None
|
||||||
|
|
||||||
" Utility methods "
|
" Utility methods "
|
||||||
@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
|
||||||
|
|
||||||
:param command: the command string to look for, like !ban. If a list is passed, the first match is returned.
|
:param command: the command string to look for, like !ban. If a list is passed, the first match is returned.
|
||||||
:type command: str or list
|
:type command: str or list
|
||||||
:param message: the message string to look in, like "!ban Lil_Mac"
|
:param message: the message string to look in, like "!ban Lil_Mac"
|
||||||
:type message: str
|
:type message: str
|
||||||
:param requireArgs: if true, only validate if the command use has any amount of trailing text
|
:param requireArgs: if true, only validate if the command use has any amount of trailing text
|
||||||
:type requireArgs: bool"""
|
:type requireArgs: bool"""
|
||||||
|
|
||||||
if not type(command)==list:
|
if not type(command)==list:
|
||||||
command = [command]
|
command = [command]
|
||||||
for item in command:
|
for item in command:
|
||||||
|
@ -346,7 +350,7 @@ class PyIRCBot:
|
||||||
if cmd:
|
if cmd:
|
||||||
return cmd
|
return cmd
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def messageHasCommandSingle(command, message, requireArgs=False):
|
def messageHasCommandSingle(command, message, requireArgs=False):
|
||||||
# Check if the message at least starts with the command
|
# Check if the message at least starts with the command
|
||||||
|
@ -357,34 +361,31 @@ class PyIRCBot:
|
||||||
subsetCheck = message[len(command):len(command)+1]
|
subsetCheck = message[len(command):len(command)+1]
|
||||||
if subsetCheck!=" " and subsetCheck!="":
|
if subsetCheck!=" " and subsetCheck!="":
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# We've got the command! Do we need args?
|
# We've got the command! Do we need args?
|
||||||
argsStart = len(command)
|
argsStart = len(command)
|
||||||
args = ""
|
args = ""
|
||||||
if argsStart > 0:
|
if argsStart > 0:
|
||||||
args = message[argsStart+1:]
|
args = message[argsStart+1:]
|
||||||
|
|
||||||
if requireArgs and args.strip() == '':
|
if requireArgs and args.strip() == '':
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Verified! Return the set.
|
# Verified! Return the set.
|
||||||
ob = type('ParsedCommand', (object,), {})
|
return ParsedCommand(command,
|
||||||
ob.command = command
|
args.split(" "),
|
||||||
ob.args = [] if args=="" else args.split(" ")
|
args,
|
||||||
ob.args_str = args
|
message)
|
||||||
ob.message = message
|
|
||||||
return ob
|
|
||||||
# return (True, command, args, message)
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def load(filepath):
|
def load(filepath):
|
||||||
"""Return an object from the passed filepath
|
"""Return an object from the passed filepath
|
||||||
|
|
||||||
:param filepath: path to a json file. filename must end with .json
|
:param filepath: path to a json file. filename must end with .json
|
||||||
:type filepath: str
|
:type filepath: str
|
||||||
:Returns: | dict
|
:Returns: | dict
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if filepath.endswith(".json"):
|
if filepath.endswith(".json"):
|
||||||
from json import load
|
from json import load
|
||||||
return load(open(filepath, 'r'))
|
return load(open(filepath, 'r'))
|
||||||
|
|
Loading…
Reference in New Issue