]> jfr.im git - z_archive/Ophion.git/commitdiff
Initialize repo
authorJohn Runyon <redacted>
Tue, 16 Oct 2012 08:54:09 +0000 (03:54 -0500)
committerJohn Runyon <redacted>
Tue, 16 Oct 2012 08:54:09 +0000 (03:54 -0500)
20 files changed:
.gitignore [new file with mode: 0644]
README [new file with mode: 0644]
bot.py [new file with mode: 0755]
classes.py [new file with mode: 0644]
modules/autoload/adminmsg.py [new file with mode: 0644]
modules/autoload/auth.py [new file with mode: 0644]
modules/autoload/chkick.py [new file with mode: 0644]
modules/autoload/chlevel.py [new file with mode: 0644]
modules/autoload/chmode.py [new file with mode: 0644]
modules/autoload/chmsg.py [new file with mode: 0644]
modules/autoload/debug.py [new file with mode: 0644]
modules/autoload/help.py [new file with mode: 0644]
modules/autoload/modctrl.py [new file with mode: 0644]
modules/autoload/quit.py [new file with mode: 0644]
modules/autoload/weather.py [new file with mode: 0644]
modules/sample.py [new file with mode: 0644]
modules/trivia.py [new file with mode: 0644]
modules/trivia/points.json [new file with mode: 0644]
modules/trivia/questions.txt [new file with mode: 0644]
util.py [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..5aca241
--- /dev/null
@@ -0,0 +1,3 @@
+*.pyc
+*.log
+.*
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..6a13f54
--- /dev/null
+++ b/README
@@ -0,0 +1,5 @@
+Ophion multibot.
+
+Config:
+       bot.py (top)
+       classes.py (class Cache)
diff --git a/bot.py b/bot.py
new file mode 100755 (executable)
index 0000000..be850a8
--- /dev/null
+++ b/bot.py
@@ -0,0 +1,281 @@
+#!/usr/bin/python
+
+###
+### Ophion Bot
+### v1.00
+### Copyright 2012 John Runyon
+### <http://inaccurate.us/ophion/>
+###
+
+## CONFIG
+dbhost = 'localhost'
+dbuser = 'bot'
+dbpass = 'roboticism'
+dbname = 'bot'
+rootdir = '/home/ophion'
+logfile = rootdir+'/output.log'
+excfile = rootdir+'/exception.log'
+
+oidfile = '/home/ophion/.oidentd.conf'
+identprefix = 'jobbig' # ident will be <identprefix><bot ID#>
+
+import socket, select, sys, os, signal, time
+from threading import *
+from traceback import print_exc
+
+import MySQLdb, MySQLdb.cursors
+
+from classes import *
+from util import *
+
+cache = Cache()
+
+def quit(reason, restart=False):
+       cache.quitting = True
+       cache.mainBot.cmsg('fatal', "Shutting down because: %s" % (reason))
+       time.sleep(1)
+       for bot in cache.bots.values():
+               bot.disconnect()
+       for tkey in cache.timers.keys():
+               cache.timers[tkey].cancel()
+               del cache.timers[tkey]
+       for modf in cache.modules:
+               unloadmod(modf, cache, True)
+
+       if restart:
+               os.execv(sys.argv[0], sys.argv)
+       sys.exit(0)
+def sigHandle(signum, stack):
+       quit("Signal: %d" % (signum))
+def online(bid):
+       return (bid in cache.bots and cache.bots[bid].online)
+def toofew(bot, nick): # like: if len(params) < NEEDED: return toofew(bot, nick)
+       bot.msg(nick, "Too few parameters for that command.")
+       return False
+
+def sendRaws():
+       for bot in cache.bots.values():
+               bot.sendRaws()
+       if not cache.quitting:
+               t = Timer(1, sendRaws)
+               t.daemon = True
+               if 'raws' in cache.timers: del cache.timers['raws']
+               cache.timers['raws'] = t
+               t.start()
+
+def connectBots():
+       curs = cache.dbc.cursor()
+       curs.execute("SELECT 1")
+       curs.close()
+
+       connected = 0
+       for bot in cache.bots.values():
+               if not bot.online:
+                       ident = "%s%d" % (identprefix, bot.id)
+                       oid = open(oidfile, 'w')
+                       oid.write("global {\n\treply \"%s\"\n}" % ident)
+                       oid.close()
+                       if bot.connect(ident):
+                               bot.cmsg('info', "Connected")
+                               connected += 1
+                               if connected == 2:
+                                       break
+
+       if not cache.quitting:
+               if connected < 2: interval = 300
+               else: interval = 60
+               t = Timer(interval, connectBots)
+               t.daemon = True
+               if 'conn' in cache.timers: del cache.timers['conn']
+               cache.timers['conn'] = t
+               t.start()
+
+       return connected
+def makeBots():
+       curs = cache.dbc.cursor()
+       curs.execute("SELECT id FROM bots")
+       row = curs.fetchone()
+       while row is not None:
+               bot = Bot(row['id'])
+               if row['id'] == 0:
+                       cache.mainBot = bot
+                       bot.mainbot = True
+               cache.bots[row['id']] = bot
+               row = curs.fetchone()
+
+def fillCache():
+       curs = cache.dbc.cursor()
+       curs.execute("SELECT username, level FROM admins")
+       row = curs.fetchone()
+       while row is not None:
+               cache.admins[row['username']] = row['level']
+               row = curs.fetchone()
+       curs.execute("SELECT chname FROM chans ORDER BY id LIMIT 2")
+       rows = curs.fetchall()
+       cache.ctrl = rows[0]['chname']
+       cache.home = rows[1]['chname']
+       curs.close()
+       makeBots()
+
+def parseCmd(bot, line, linepieces, msg, iscm):
+       hostmask = linepieces[0][1:]
+       fromnick = hostmask.split('!')[0]
+       target = linepieces[2]
+       pieces = msg.split()
+       cmd = pieces[0].upper()
+       if len(pieces) == 1: params = ''
+       else: params = ' '.join(pieces[1:])
+
+       ret = ""
+
+       if cmd not in cache.cmds:
+               noaccess(bot, fromnick)
+               return ret
+       else: ci = cache.cmds[cmd]
+
+       if not ci['isadmin']:
+               if fromnick not in cache.users:
+                       cache.users[fromnick] = User(fromnick)
+               auth = cache.users[fromnick].auth
+               if not iscm:
+                       if ci['reqchan']:
+                               target = pieces.pop(1).lower()
+                               params = ' '.join(pieces[1:])
+                       else:
+                               target = fromnick
+               if target not in cache.chans:
+                       bot.msg(fromnick, "No such channel.")
+                       return ret 
+               chan = cache.chans[target]
+               if auth not in chan.alist or chan.alist[auth] < ci['level']:
+                       noaccess(bot, fromnick, True)
+                       return ret
+               elif len(pieces)-1 < cache.cmds[cmd]['params']:
+                       toofew(bot, fromnick)
+                       return ret
+               else:
+                       try: ret = cache.cmds[cmd]['func'](fromnick, target, params, chan.bot, cache)
+                       except Exception as e:
+                               print_exc(None, cache.excfile)
+                               bot.msg(fromnick, "An error occurred, sorry! Try again later.")
+                               cache.mainBot.cmsg('warn', "EXCEPTION! %r Caused by <%s> %s" % (e, fromnick, line))
+
+       else: # admin/global command
+               if fromnick in cache.users: glblevel = cache.users[fromnick].access
+               else: glblevel = 0 
+               if glblevel < ci['level']:
+                       noaccess(bot, fromnick)
+               elif len(pieces)-1 < cache.cmds[cmd]['params']:
+                       toofew(bot, fromnick)
+               else:
+                       try: ret = cache.cmds[cmd]['func'](fromnick, target, params, bot, cache)
+                       except Exception as e:
+                               print_exc(None, cache.excfile)
+                               bot.msg(fromnick, "An error occurred, sorry! Try again later.")
+                               cache.mainBot.cmsg('warn', "EXCEPTION! %r Caused by <%s> %s" % (e, fromnick, line))
+       return ret
+
+def _parse(bot, line):
+       pieces = line.split()
+       if pieces[1] == "PRIVMSG":
+               target = pieces[2]
+               msg = ' '.join(pieces[3:]).lstrip(':')
+               if msg[0] == cache.trigger:
+                       return parseCmd(bot, line, pieces, msg[1:], True)
+               elif target == bot.nick:
+                       return parseCmd(bot, line, pieces, msg, False)
+       elif pieces[1] == "JOIN":
+               nick = pieces[0][1:].split('!')[0]
+               host = pieces[0][1:].split('@')[1]
+               chname = pieces[2].lower()
+               if chname in cache.chans:
+                       bot.joined(chname, nick)
+       elif pieces[1] == "PART":
+               nick = pieces[0][1:].split('!')[0]
+               chname = pieces[2].lower()
+               if chname in cache.chans:
+                       bot.parted(chname, nick)
+       elif pieces[1] == "QUIT":
+               nick = pieces[0][1:].split('!')[0]
+               bot.quit(nick)
+       elif pieces[1] == "NICK":
+               fromnick = pieces[0][1:].split('!')[0]
+               tonick = pieces[2][1:]
+               cache.users[tonick] = cache.users[fromnick]
+               del cache.users[fromnick]
+               cache.users[tonick].nick = tonick
+       elif pieces[0] == "PING":
+               bot.rawnow("PONG %s" % (pieces[1]))
+       elif pieces[0] == "ERROR":
+               try: bot.disconnect()
+               except: pass
+       elif pieces[1] in cache.nums:
+               for fn in cache.nums[pieces[1]]:
+                       try: fn(line, bot)
+                       except Exception as e:
+                               print_exc(None, cache.excfile)
+                               bot.msg(fromnick, "An error occurred, sorry! Try again later.")
+                               cache.mainBot.cmsg('warn', "EXCEPTION! %r Caused by <%s> %s" % (e, fromnick, line))
+
+def parse(bot):
+       line = bot.get().strip()
+       return _parse(bot, line)
+
+
+def loop():
+       while True:
+               ready = cache.poll.poll()
+               for fde in ready:
+                       ret = ""
+                       if fde[0] == sys.stdin.fileno():
+                               line = sys.stdin.readline().strip()
+                               pieces = line.split(None, 2)
+                               if len(pieces) == 3 and pieces[1].isdigit():
+                                       mode = pieces[0]
+                                       bid = int(pieces[1])
+                                       line = pieces[2]
+                                       if mode.upper() == "IN": ret = _parse(cache.bots[bid], line)
+                                       elif mode.upper() == "OUT": cache.bots[bid].rawnow(line)
+                                       else: print "ERROR! <'IN'|'OUT'>:<bid>:<line>"
+                               else: print "ERROR! <'IN'|'OUT'>:<bid>:<line>"
+                       elif fde[0] in cache.botsByFD: # it's a bot
+                               bot = cache.botsByFD[fde[0]]
+                               if fde[1] & select.POLLERR or fde[1] & select.POLLHUP or fde[1] & select.POLLNVAL:
+                                       bot.disconnect()
+                               else:
+                                       ret = parse(bot)
+                       if ret == "QUIT": break
+               else: continue
+               break # if the "for" was broken, break the while as well.
+
+
+signal.signal(signal.SIGHUP, signal.SIG_IGN)
+signal.signal(signal.SIGINT, sigHandle)
+
+cache.excfile = open(excfile, 'a')
+
+sys.path.append(rootdir+'/modules')
+sys.path.append(rootdir+'/modules/autoload')
+for modf in os.listdir(rootdir+'/modules/autoload'):
+       if modf[-3:] == ".py":
+               loadmod(modf[:-3], cache)
+
+cache.dbc = MySQLdb.connect(host=dbhost, user=dbuser, passwd=dbpass, db=dbname, cursorclass=MySQLdb.cursors.DictCursor)
+fillCache()
+
+cache.poll = select.poll()
+
+cache.poll.register(sys.stdin.fileno(), select.POLLIN)
+
+t = Timer(1, sendRaws)
+t.daemon = True
+cache.timers['raws'] = t
+t.start()
+
+connectBots()
+t = Timer(60, connectBots)
+t.daemon = True
+cache.timers['conn'] = t
+t.start()
+
+loop()
diff --git a/classes.py b/classes.py
new file mode 100644 (file)
index 0000000..3c0838a
--- /dev/null
@@ -0,0 +1,263 @@
+import socket, random, select
+from collections import deque
+
+from util import *
+
+class User:
+       def __init__(self, nick):
+               self.nick = nick
+               self.auth = ''
+               self.access = 0
+               self.alist = []
+               self.commonchans = []
+               self.host = None
+       def __str__(self): return self.nick
+       def __repr__(self): return "<User: %s (%d)>" % (self.nick, self.access)
+       def authed(self, username):
+               self.auth = username
+               if username in cache.admins:
+                       self.access = cache.admins[username]
+       def joined(self, chan):
+               self.commonchans.append(chan)
+       def parted(self, chan):
+               if chan in self.commonchans: self.commonchans.remove(chan)
+               if self.commonchans == []:
+                       self.auth = ''
+                       self.access = 0
+
+class Channel:
+       def __init__(self, name, bot, id):
+               self.id = id
+               self.name = name
+               self.bot = bot
+
+               self.triggers = {}
+
+               self.alist = {}
+               curs = cache.dbc.cursor()
+               curs.execute("SELECT authname, level FROM chusers WHERE chid = %s", (self.id,))
+               row = curs.fetchone()
+               while row is not None:
+                       self.alist[row['authname']] = row['level']
+                       row = curs.fetchone()
+
+               self.ops = []
+               self.voices = []
+               self.users = []
+       def joined(self, user):
+               self.users.append(user)
+               user.joined(self.name)
+       def parted(self, user):
+               if user in self.users: self.users.remove(user)
+               if user in self.ops: self.ops.remove(user)
+               if user in self.voices: self.voices.remove(user)
+               user.parted(self.name)
+       def opped(self, user):
+               self.ops.append(user)
+       def voiced(self, user):
+               self.voices.append(user)
+       def __str__(self): return self.name
+       def __repr__(self): return "<Chan: %s>" % (self.name)
+
+class Bot:
+       def __init__(self, id):
+               self.id = id
+               self.online = False
+               self.chans = {}
+               self.mainbot = False
+               self.rawqueue = deque()
+
+       def get(self):
+               buf = ""
+               chin = self.s.recv(1)
+               while chin != "\n":
+                       buf += chin
+                       chin = self.s.recv(1)
+               buf = buf.strip()
+               print "<%d<%s" % (self.id, buf)
+               return buf
+
+       def sendRaws(self, count=2):
+               if self.online:
+                       for i in range(count):
+                               try: line = self.rawqueue.popleft()
+                               except IndexError: return
+                               self.rawnow(line)
+       def raw(self, line):
+               self.rawqueue.append(line)
+       def rawnow(self, line):
+               self.s.sendall(line+"\r\n")
+               print ">%d>%s" % (self.id, line)
+
+       def msg(self, target, msg):
+               msgs = msg.split("\n")
+               for msg in msgs:
+                       if target[0] == '#':
+                               self.raw("PRIVMSG %s :%s" % (target, msg))
+                       else:
+                               self.raw("NOTICE %s :%s" % (target, msg))
+       def cmsg(self, cmtype, msg, id=None):
+               if id is None: id = self.id
+               self.msg(cache.ctrl, cache.cmsgs[cmtype] % {'id': id, 'msg': msg})
+
+       def joined(self, chname, nick, chlevel=None):
+               chname = chname.lower()
+               if chname not in self.chans and chname not in cache.chans:
+                       return
+
+               self.raw("WHO %s n%%nah" % (nick))
+               if nick not in cache.users: cache.users[nick] = User(nick)
+               self.chans[chname].joined(cache.users[nick])
+               if chlevel == "@":
+                       self.chans[chname].opped(cache.users[nick])
+               if chlevel == "+":
+                       self.chans[chname].voiced(cache.users[nick])
+       def parted(self, chname, nick):
+               chname = chname.lower()
+               if nick == self.nick:
+                       del self.chans[chname]
+                       del cache.chans[chname]
+               elif chname in self.chans:
+                       self.chans[chname].parted(cache.users[nick])
+       def quit(self, nick):
+               if nick in cache.users:
+                       user = cache.users[nick]
+                       for ch in user.commonchans:
+                               cache.chans[ch].parted(user)
+                       del cache.users[nick]
+               
+       def disconnect(self):
+               try: cache.mainBot.cmsg('warn', 'Disconnected!', self.id)
+               except: pass
+               try: self.rawnow("QUIT :Disconnected")
+               except: pass
+               try: self.s.shutdown(socket.SHUT_RDWR)
+               except: pass
+               try: self.s.close()
+               except: pass
+               for ch in self.chans.values():
+                       for user in ch.users:
+                               ch.parted(user)
+               cache.poll.unregister(self.s.fileno())
+               self.online = False
+
+       def joinChans(self):
+               curs = cache.dbc.cursor()
+               curs.execute("SELECT id, chname FROM chans WHERE botid = %s", (self.id,))
+               row = curs.fetchone()
+               while row is not None:
+                       self.raw("JOIN %s" % (row['chname']))
+                       chan = Channel(row['chname'].lower(), self, row['id'])
+                       self.chans[row['chname'].lower()] = chan
+                       cache.chans[row['chname']] = chan
+                       row = curs.fetchone()
+               curs.close()
+       def connect(self, ident):
+               curs = cache.dbc.cursor()
+               curs.execute("SELECT irchost, ircport, ircpass, nick, vhost, realname, authname, authpass FROM bots WHERE id = %s", (self.id,))
+               row = curs.fetchone()
+               self.nick = row['nick']
+               self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+               self.s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+               self.s.bind((row['vhost'], 0))
+               self.s.connect((row['irchost'], row['ircport']))
+               self.rawnow("NICK %s" % (self.nick))
+               self.rawnow("USER %s * * :%s" % (ident, row['realname']))
+               curs.close()
+               while True:
+                       line = self.get()
+                       pieces = line.split()
+                       if pieces[0] == "PING":
+                               self.rawnow("PONG %s" % (pieces[1]))
+                       elif pieces[1] == "433":
+                               self.nick = self.nick+str(random.randrange(0,9))
+                               self.rawnow("NICK %s" % (self.nick))
+                       elif pieces[0] == "ERROR":
+                               self.online = False
+                               return False
+                       elif pieces[1] == "376" or pieces[1] == "422":
+                               break
+               if row['authname'] is not None and row['authpass'] is not None:
+                       self.rawnow("AUTH %s %s" % (row['authname'], row['authpass']))
+               self.rawnow("MODE %s +ix" % (self.nick))
+               cache.poll.register(self.s.fileno(), select.POLLIN)
+               cache.botsByFD[self.s.fileno()] = self
+               cache.botsByNick[self.nick] = self
+               self.joinChans()
+               self.online = True
+               return True
+       def __str__(self): return self.nick
+       def __repr__(self): return "<Bot%d: %s>" % (self.id, self.nick)
+
+class Cache:
+       dbc = None
+       ls = None
+       admins = {}
+       bots = {}
+       botsByFD = {}
+       botsByNick = {}
+       mainBot = None
+       home = None # homechan
+       ctrl = None # ctrlchan
+       modules = {}
+       unmodules = {} # unloaded
+       timers = {}
+       quitting = False
+
+       currmod = None
+       cmds = {}
+       nums = {}
+
+       users = {}
+       chans = {}
+
+       ## CONFIG
+       lshost = '0.0.0.0'
+       lsport = 13245
+       triviapath = '/home/ophion/modules/trivia/'
+       trigger = '!'
+       cmsgs = {
+               'debug':        "\00303[\037DEBUG\037][%(id)d]: %(msg)s",
+               'info':         "\00312[\037INFO\037][%(id)d]: %(msg)s",
+               'warn':         "\00306[\037WARN\037][%(id)d]: %(msg)s",
+               'fatal':        "\00304[\037FATAL\037][%(id)d]: %(msg)s",
+       }
+
+       def __init__(self):
+               global cache
+               cache = self
+
+       def hooknum(self, num, func):
+               try: self.nums[num].append(func)
+               except: self.nums[num] = [func]
+       def unhooknum(self, num, func):
+               try: self.nums[num].remove(func)
+               except: return False
+               else: return True
+
+       def hookcmd(self, cmd, level, func, params, helpfunc, isadmin=False, reqchan=True):
+               self.cmds[cmd.upper()] = {'module': cache.currmod, 'func': func, 'level': level, 'params': params, 'helpfunc': helpfunc, 'isadmin': isadmin, 'reqchan': reqchan}
+       def unhookcmd(self, cmd):
+               try: del self.cmds[cmd]
+               except: return False
+               else: return True
+
+       def gethelp(self, cmd, nick=None, user=None, access=None):
+               if cmd in self.cmds:
+                       if self.cmds[cmd]['level'] == 0:
+                               return self.cmds[cmd]['helpfunc']()
+                       if nick is None and user is None:
+                               return self.cmds[cmd]['helpfunc']()
+                       elif nick is not None and nick in self.users and self.users[nick].access > self.cmds[cmd]['level']:
+                               return self.cmds[cmd]['helpfunc']()
+                       elif user is not None and user.access > self.cmds[cmd]['level']:
+                               return self.cmds[cmd]['helpfunc']()
+                       elif access is not None and access > self.cmds[cmd]['level']:
+                               return self.cmds[cmd]['helpfunc']()
+               return None
+
+       def isbot(self, bid):
+               return (toint(bid) in self.bots)
+       def isonline(self, bid):
+               bid = toint(bid)
+               return (bid in self.bots and self.bots[bid].online)
diff --git a/modules/autoload/adminmsg.py b/modules/autoload/adminmsg.py
new file mode 100644 (file)
index 0000000..109585e
--- /dev/null
@@ -0,0 +1,43 @@
+from classes import *
+from util import *
+
+name = 'admin messaging'
+def init(cache):
+       cache.currmod = __name__
+       cache.hookcmd('ADMINSEND', 2, send, 3, helpsend, isadmin=True)
+       cache.hookcmd('ADMINACT', 2, act, 3, helpact, isadmin=True)
+def deinit(cache):
+       cache.currmod = __name__
+       cache.unhookcmd('ADMINSEND')
+       cache.unhookcmd('ADMINACT')
+
+def send(nick, target, params, bot, cache):
+       pieces = params.split()
+       sbid = pieces[0]
+       bid = toint(sbid)
+       target = pieces[1]
+       msg = ' '.join(pieces[2:])
+       if sbid == "*":
+               for bot in cache.bots.values():
+                       if bot.online: bot.msg(target, msg)
+       elif cache.isonline(bid):
+               cache.bots[bid].msg(target, msg)
+       else:
+               bot.msg(nick, "No such bot, or offline.")
+def act(nick, target, params, bot, cache):
+       pieces = params.split()
+       sbid = pieces[0]
+       bid = toint(sbid)
+       target = pieces[1]
+       msg = ' '.join(pieces[2:])
+       if sbid == "*":
+               for bot in cache.bots.values():
+                       if bot.online: bot.raw("PRIVMSG %s :\1ACTION %s\1" % (target, msg))
+       elif cache.isonline(bid):
+               cache.bots[bid].raw("PRIVMSG %s :\1ACTION %s\1" % (target, msg))
+       else:
+               bot.msg(nick, "No such bot, or offline.")
+
+
+def helpsend(): return ['ADMINSEND <bid> <nick|#channel> <msg>', 'Sends a PRIVMSG (#channel) or NOTICE (nick).']
+def helpact(): return ['ADMINACT <bid> <nick|#channel> <msg>', 'Sends an ACTION (/me).']
diff --git a/modules/autoload/auth.py b/modules/autoload/auth.py
new file mode 100644 (file)
index 0000000..76c10aa
--- /dev/null
@@ -0,0 +1,119 @@
+from classes import *
+from util import *
+
+name = 'auth'
+def init(cache):
+       cache.currmod = __name__
+       cache.hookcmd('AUTH', 0, auth, 0, helpauth, isadmin=True)
+       cache.hookcmd('WHOIS', 1, whois, 1, helpwhois, isadmin=True)
+       cache.hookcmd('WHOAMI', 0, whoami, 0, helpwhoami, isadmin=True)
+       cache.hooknum('354', rep_who)
+       cache.hooknum('353', rep_names)
+def deinit(cache, reloading=False):
+       cache.currmod = __name__
+       cache.unhookcmd('AUTH')
+       cache.unhookcmd('WHOIS')
+       cache.unhookcmd('WHOAMI')
+       cache.unhooknum('354', rep_who)
+       cache.unhooknum('353', rep_names)
+
+def auth(nick, target, params, bot, cache):
+       bot.raw("WHO %s n%%hna" % (nick))
+       bot.msg(nick, "Looking up your auth...")
+
+def whois(nick, target, params, bot, cache):
+       lookup = params.split()[0]
+       if lookup[0] == '*':
+               if cache.users[nick].access >= 1:
+                       lookup = lookup[1:]
+                       bid = toint(lookup)
+                       bot2 = None
+                       if bid is None:
+                               if lookup in cache.botsByNick:
+                                       bot2 = cache.botsByNick[lookup]
+                       else:
+                               if bid in bot2s:
+                                       bot2 = bot2s[bid]
+                       if bot2 is not None:
+                               bot.msg(nick, "Bot #%d ..." % (bot2.id))
+
+                               if bot2.nick: bot.msg(nick, "- nick: %s" % (bot2.nick))
+                               else: bot.msg(nick, "- doesn't know it's nick")
+
+                               if bot2.online: bot.msg(nick, "- is online")
+                               else: bot.msg(nick, "- is offline")
+
+                               if bot2.mainbot: bot.msg(nick, "- is the mainbot")
+                               else: bot.msg(nick, "- is not the mainbot")
+
+                               if len(bot2.rawqueue) != 0: bot.msg(nick, "- has %d lines in queue" % (len(bot2.rawqueue)))
+                               else: bot.msg(nick, "- has an empty queue")
+
+                               if len(bot2.chans) != 0: bot.msg(nick, "- in %s" % (' '.join([chan.name for chan in bot2.chans.values()])))
+                               else: bot.msg(nick, "- is in no channels.")
+
+                               bot.msg(nick, "End info for bot #%d" % (bot2.id))
+                       else:
+                               bot.msg(nick, "No such bot %s" % (lookup))
+       elif lookup[0] != '#':
+               if lookup in cache.users:
+                       auth = cache.users[lookup].auth
+                       access = cache.users[lookup].access
+                       bot.msg(nick, "%s is #%s (access: %d)" % (lookup, auth, access))
+               else:
+                       bot.msg(nick, "%s is not a known user." % (lookup))
+                       return
+       else:
+               auth = lookup[1:]
+               curs = cache.dbc.cursor()
+               curs.execute("SELECT level FROM admins WHERE username = %s", (auth,))
+               row = curs.fetchone()
+               if row is not None:
+                       bot.msg(nick, "%s (access: %d)" % (lookup, row['level']))
+               else:
+                       bot.msg(nick, "%s is unknown." % (lookup))
+
+def whoami(nick, target, params, bot, cache):
+       if nick in cache.users and cache.users[nick].access != -1:
+               bot.msg(nick, "You are %s (#%s access: %d)" % (nick, cache.users[nick].auth, cache.users[nick].access))
+
+               curs = cache.dbc.cursor()
+               curs.execute("SELECT chans.chname AS ch, chusers.level AS level FROM chans, chusers WHERE chans.id = chusers.chid AND chusers.authname = %s", (cache.users[nick].auth,))
+               rows = curs.fetchall()
+               if len(rows) != 0:
+                       bot.msg(nick, "-- CHANNELS:")
+                       for row in rows:
+                               bot.msg(nick, "%s - level %d" % (row['ch'], row['level']))
+                       bot.msg(nick, "-- END OF CHANNELS")
+       else:
+               bot.msg(nick, "You are %s (unknown auth)" % (nick))
+
+def rep_who(line, bot): # :<server.tld> 354 <self> <host> <nick> <auth>
+       pieces = line.split()
+       host = pieces[3]
+       nick = pieces[4]
+       auth = pieces[5]
+       if nick not in cache.users:
+               cache.users[nick] = User(nick)
+       user = cache.users[nick]
+       user.host = host
+       cache.users[nick].authed(auth)
+
+def rep_names(line, bot): # :<server.tld> 353 <self> = <#chan> :jrunyon @Ophion +BiohZn @Q +DimeCadmium
+       pieces = line.split()
+       chan = pieces[4]
+       nicksidx = line.find(':', 1)
+       nicks = line[nicksidx+1:].split()
+       for nick in nicks:
+               mode = nick[0]
+               if mode == '@' or mode == '+':
+                       chlevel = mode
+                       nick = nick[1:]
+               else:
+                       chlevel = None
+               if nick != bot.nick: bot.joined(chan, nick, chlevel)
+               
+
+def helpauth(): return ['AUTH', 'Requests the bot to look up your account.']
+def helpwhois(): return ['WHOIS <nick|#auth>', 'Shows info about an account with the bot.']
+def helpwhoami(): return ['WHOAMI', 'Shows info about your account with the bot.']
diff --git a/modules/autoload/chkick.py b/modules/autoload/chkick.py
new file mode 100644 (file)
index 0000000..a443bb6
--- /dev/null
@@ -0,0 +1,26 @@
+from classes import *
+from util import *
+
+name = 'channel kick'
+def init(cache):
+       cache.currmod = __name__
+       cache.hookcmd('KICK', 3, kick, 1, helpkick)
+       cache.hookcmd('BAN', 3, ban, 1, helpban)
+def deinit(cache, reloading=False):
+       cache.currmod = __name__
+       cache.unhookcmd('KICK')
+       cache.unhookcmd('BAN')
+
+def kick(nick, target, params, bot, cache):
+       pieces = params.split(None, 1)
+       reason = "Requested by %s" % (nick)
+       if len(pieces) == 2:
+               reason = "%s (%s)" % (pieces[1], reason)
+       bot.raw("KICK %s %s :%s" % (target, pieces[0], reason))
+
+def ban(nick, target, params, bot, cache):
+       bot.raw("MODE %s +b %s" % (target, pieces[0]))
+       bot.msg(nick, "Done.")
+
+def helpkick(): return ['KICK <#channel> [<reason>]', 'Kicks <user>']
+def helpban(): return ['BAN <hostmask>', 'Bans <hostmask>. (Doesn\'t kick)']
diff --git a/modules/autoload/chlevel.py b/modules/autoload/chlevel.py
new file mode 100644 (file)
index 0000000..57155f0
--- /dev/null
@@ -0,0 +1,70 @@
+from classes import *
+from util import *
+
+name = 'channel modes'
+def init(cache):
+       cache.currmod = __name__
+       cache.hookcmd('CHANLEV', 1, chanlev, 0, helpchanlev)
+def deinit(cache, reloading=False):
+       cache.currmod = __name__
+       cache.unhookcmd('CHANLEV')
+
+def chanlev(nick, target, params, bot, cache):
+       pieces = params.split()
+       user = cache.users[nick]
+       chan = cache.chans[target]
+
+       if len(pieces) == 0:
+               bot.msg(nick, "-- ACCESS LIST FOR %s" % (target))
+               for authname, access in chan.alist.items():
+                       bot.msg(nick, "%-15s %s" % (authname, access))
+               bot.msg(nick, "-- END OF ACCESS LIST")
+       elif len(pieces) == 1:
+               if pieces[0][0] == '#':
+                       auth = pieces[0][1:]
+                       bot.msg(nick, "Auth %s has level %s on %s" % (auth, chan.alist[auth], target))
+               elif pieces[0] in cache.users:
+                       auth = cache.users[pieces[0]].auth
+                       bot.msg(nick, "Nick %s has level %s on %s" % (pieces[0], chan.alist[auth], target))
+               else:
+                       bot.msg(nick, "%s is unknown." % (pieces[0]))
+       elif len(pieces) == 2:
+               auth = user.auth
+               if auth in chan.alist and chan.alist[auth] >= 4:
+                       level = chan.alist[auth]
+                       if pieces[0][0] == '#':
+                               targauth = pieces[0][1:]
+                       elif pieces[0] in cache.users:
+                               targauth = cache.users[pieces[0]].auth
+                       else:
+                               bot.msg(nick, "%s is unknown." % (pieces[0]))
+                               return
+                       targlev = toint(pieces[1])
+                       if targlev is None or targlev > 5:
+                               bot.msg(nick, "Invalid level %d." % (targlev))
+                               return
+                       if level != 5:
+                               if targauth in chan.alist and chan.alist[targauth] >= level:
+                                       noaccess(bot, nick, True)
+                                       return
+                               if targlev >= level:
+                                       noaccess(bot, nick, True)
+                                       return
+                       if targlev != 0:
+                               chan.alist[targauth] = targlev
+                               curs = cache.dbc.cursor()
+                               curs.execute("REPLACE INTO chusers(chid, authname, level) VALUES (%s, %s, %s)", (chan.id, targauth, targlev))
+                               curs.close()
+                       elif targauth in chan.alist:
+                               del chan.alist[targauth]
+                               curs = cache.dbc.cursor()
+                               curs.execute("DELETE FROM chusers WHERE chid = %s AND authname = '%s'" % (chan.id, targauth))
+                               curs.close()
+                       bot.msg(nick, "Done.")
+               else:
+                       noaccess(bot, nick, True)
+       else:
+               bot.msg(nick, "Invalid syntax. Usage: CHANLEV <#channel> [<nick|#auth> [<level>]]")
+
+def helpchanlev():
+       return ['CHANLEV <#channel> [<nick|#auth> [<level>]]', 'Change or view access.']
diff --git a/modules/autoload/chmode.py b/modules/autoload/chmode.py
new file mode 100644 (file)
index 0000000..ca137eb
--- /dev/null
@@ -0,0 +1,125 @@
+from classes import *
+from util import *
+
+name = 'channel modes'
+def init(cache):
+       cache.currmod = __name__
+       cache.hookcmd('OP', 3, op, 0, helpop)
+       cache.hookcmd('VOICE', 2, voice, 0, helpvoice)
+       cache.hookcmd('DEOP', 0, deop, 0, helpdeop)
+       cache.hookcmd('DEVOICE', 0, devoice, 0, helpdevoice)
+       cache.hookcmd('DOWN', 0, down, 0, helpdown)
+       cache.hooknum('MODE', modehook)
+def deinit(cache, reloading=False):
+       cache.currmod = __name__
+       cache.unhookcmd('OP')
+       cache.unhookcmd('VOICE')
+       cache.unhookcmd('DEOP')
+       cache.unhookcmd('DEVOICE')
+       cache.unhookcmd('DOWN')
+
+def op(nick, target, params, bot, cache):
+       if params == '': opnick = nick
+       elif cache.chans[target].alist[cache.users[nick].auth] >= 4: opnick = params
+       else:
+               noaccess(bot, nick, True)
+               return
+
+       user = cache.users[nick]
+       if user in cache.chans[target].ops:
+               bot.msg(nick, "%s is already opped on %s" % (opnick, target))
+       elif user in cache.chans[target].users:
+               bot.raw("MODE %s +o %s" % (target, opnick))
+               bot.msg(nick, "Done.")
+       else:
+               bot.msg(nick, "%s isn't on %s" % (opnick, target))
+def voice(nick, target, params, bot, cache):
+       if params == '': vnick = nick
+       elif cache.chans[target].alist[cache.users[nick].auth] >= 3: vnick = params
+       else:
+               noaccess(bot, nick, True)
+               return
+
+       user = cache.users[nick]
+       if user in cache.chans[target].voices:
+               bot.msg(nick, "%s is already voiced on %s" % (vnick, target))
+       elif user in cache.chans[target].users:
+               bot.raw("MODE %s +v %s" % (target, vnick))
+               bot.msg(nick, "Done.")
+       else:
+               bot.msg(nick, "%s isn't on %s" % (vnick, target))
+def deop(nick, target, params, bot, cache):
+       if params == '': opnick = nick
+       elif cache.chans[target].alist[cache.users[nick].auth] >= 4: opnick = params
+       else:
+               noaccess(bot, nick, True)
+               return
+
+       user = cache.users[nick]
+       if user in cache.chans[target].ops:
+               bot.raw("MODE %s -o %s" % (target, opnick))
+               bot.msg(nick, "Done.")
+       elif user in cache.chans[target].users:
+               bot.msg(nick, "%s isn't opped on %s" % (opnick, target))
+       else:
+               bot.msg(nick, "%s isn't on %s" % (opnick, target))
+def devoice(nick, target, params, bot, cache):
+       if params == '': vnick = nick
+       elif cache.chans[target].alist[cache.users[nick].auth] >= 3: vnick = params
+       else:
+               noaccess(bot, nick, True)
+               return
+
+       user = cache.users[nick]
+       if user in cache.chans[target].voices:
+               bot.raw("MODE %s -v %s" % (target, vnick))
+               bot.msg(nick, "Done.")
+       elif user in cache.chans[target].users:
+               bot.msg(nick, "%s isn't voiced on %s" % (vnick, target))
+       else:
+               bot.msg(nick, "%s isn't on %s" % (vnick, target))
+
+def down(nick, target, params, bot, cache):
+       user = cache.users[nick]
+       if user in cache.chans[target].users:
+               bot.raw("MODE %s -ov %s %s" % (target, nick, nick))
+       else:
+               bot.msg(nick, "You aren't on %s" % (target))
+
+
+def modehook(line, bot): # :U!I@H MODE #chan +o-v DimeCadmium DimeCadmium
+       pieces = deque(line.split()[2:]) # #chan +o-v DimeCadmium DimeCadmium
+       chname = pieces.popleft().lower()
+       if chname[0] != '#': return # usermode change
+       if chname not in cache.chans:
+               return
+       chan = cache.chans[chname] # +o-v DimeCadmium DimeCadmium
+       modes = pieces.popleft() # DimeCadmium DimeCadmium
+       for i in modes:
+               if i == '+':
+                       adding = True
+               elif i == '-':
+                       adding = False
+               elif i == 'o':
+                       nick = pieces.popleft()
+                       if nick not in cache.users: cache.users[nick] = User(nick)
+                       user = cache.users[nick]
+                       if adding: chan.ops.append(user)
+                       elif user in chan.ops: chan.ops.remove(user)
+               elif i == 'v':
+                       nick = pieces.popleft()
+                       if nick not in cache.users: cache.users[nick] = User(nick)
+                       user = cache.users[nick]
+                       if adding: chan.voices.append(user)
+                       elif user in chan.voices: chan.voices.remove(user)
+
+def helpop():
+       return ['OP <#channel> [<nick>]', 'Ops you, or <nick>.']
+def helpvoice():
+       return ['VOICE <#channel> [<nick>]', 'Voices you, or <nick>.']
+def helpdeop():
+       return ['DEOP <#channel> [<nick>]', 'Deops you, or <nick>.']
+def helpdevoice():
+       return ['DEVOICE <#channel> [<nick>]', 'Devoices you, or <nick>.']
+def helpdown():
+       return ['DOWN <#channel>', 'Removes your op/voice.']
diff --git a/modules/autoload/chmsg.py b/modules/autoload/chmsg.py
new file mode 100644 (file)
index 0000000..cf2a062
--- /dev/null
@@ -0,0 +1,21 @@
+from classes import *
+from util import *
+
+name = 'channel messaging'
+def init(cache):
+       cache.currmod = __name__
+       cache.hookcmd('SAY', 3, say, 1, helpsay)
+       cache.hookcmd('ACT', 3, act, 1, helpact)
+def deinit(cache, reloading=False):
+       cache.currmod = __name__
+       cache.unhookcmd('SAY')
+       cache.unhookcmd('ACT')
+
+def say(nick, target, params, bot, cache):
+       bot.msg(target, params)
+
+def act(nick, target, params, bot, cache):
+       bot.msg(target, "\001ACTION %s\001" % (params))
+
+def helpsay(): return ['SAY <#channel> <line>', 'Does a /ME.']
+def helpact(): return ['ACT <#channel> <line>', 'Does a /ME <line>.']
diff --git a/modules/autoload/debug.py b/modules/autoload/debug.py
new file mode 100644 (file)
index 0000000..d9765b0
--- /dev/null
@@ -0,0 +1,40 @@
+from classes import *
+from util import *
+
+name = 'debug'
+def init(cache):
+       cache.currmod = __name__
+       cache.hookcmd('EXEC', 8, doexec, 1, helpexec, isadmin=True)
+       cache.hookcmd('EVAL', 8, doeval, 1, helpeval, isadmin=True)
+       cache.hookcmd('RAW', 8, raw, 2, helpraw, isadmin=True)
+def deinit(cache, reloading=False):
+       cache.currmod = __name__
+       cache.unhookcmd('EXEC')
+       cache.unhookcmd('EVAL')
+       cache.unhookcmd('RAW')
+
+def doexec(nick, target, params, bot, cache):
+       try: exec(params)
+       except Exception as e: bot.msg(nick, "Exception! "+str(e))
+       else: bot.msg(nick, "Done.")
+def doeval(nick, target, params, bot, cache):
+       ret = None
+       try: ret = eval(params)
+       except Exception as e: bot.msg(nick, "Exception! "+str(e))
+       else: bot.msg(nick, "Return: %s" % (ret))
+def raw(nick, target, params, bot, cache):
+       pieces = params.split()
+       if pieces[0] == "*":
+               for bot in cache.bots.values():
+                       if bot.online: bot.raw(' '.join(pieces[1:]))
+       else:
+               bid = toint(pieces[0])
+               if cache.isonline(bid):
+                       cache.bots[bid].raw(' '.join(pieces[1:]))
+               else:
+                       bot.msg(nick, "No such bot, or offline.")
+
+
+def helpexec(): return ['EXEC <code>', 'Runs Python <code> in exec, and prints the exception (if any).']
+def helpeval(): return ['EVAL <code>', 'Runs Python <code> in eval, and prints the return or exception.']
+def helpraw(): return ['RAW <bid> <line>', 'Sends <line> to the server from bot BID.']
diff --git a/modules/autoload/help.py b/modules/autoload/help.py
new file mode 100644 (file)
index 0000000..7b5d544
--- /dev/null
@@ -0,0 +1,45 @@
+from classes import *
+from util import *
+
+name = 'help'
+def init(cache):
+       cache.currmod = __name__
+       cache.hookcmd('HELP', 0, dohelp, 0, helphelp, isadmin=True)
+       cache.hookcmd('SHOWCOMMANDS', 0, showcommands, 0, helpshowcommands, isadmin=True)
+def deinit(cache, reloading=False):
+       cache.currmod = __name__
+       cache.unhookcmd('HELP')
+       cache.unhookcmd('SHOWCOMMANDS')
+
+def dohelp(nick, target, params, bot, cache):
+       if len(params) == 0: showcommands(nick, target, params, bot, cache)
+       else:
+               thehelp = cache.gethelp(params.upper(), nick)
+               if thehelp is not None:
+                       bot.msg(nick, '-- HELP for %s' % (params))
+                       bot.msg(nick, 'Syntax: %s' % (thehelp[0]))
+                       for theline in thehelp[1:]:
+                               bot.msg(nick, theline)
+                       bot.msg(nick, '-- End of HELP for %s' % (params))
+               else:
+                       bot.msg(nick, "HELP for %s not available: no such command or not enough access." % (params))
+
+def showcommands(nick, target, params, bot, cache):
+       helps = []
+       if nick in cache.users:
+               access = cache.users[nick].access
+       else:
+               access = 0
+       bot.msg(nick, '-- COMMAND LIST')
+       for key in sorted(cache.cmds):
+               cmd = cache.cmds[key]
+               if access >= cmd['level']:
+                       cmdhelp = cmd['helpfunc']()
+                       if len(cmdhelp) == 3 and access >= 1:
+                               bot.msg(nick, "%-20s %s [module: %s]" % (cmdhelp[0], cmdhelp[1], cmdhelp[2]))
+                       else:
+                               bot.msg(nick, "%-20s %s" % (cmdhelp[0], cmdhelp[1]))
+       bot.msg(nick, '-- End of COMMAND LIST')
+
+def helphelp(): return ['HELP [<command>]', 'Requests help for a command, or shows a command list.']
+def helpshowcommands(): return ['SHOWCOMMANDS', 'Shows a command list.']
diff --git a/modules/autoload/modctrl.py b/modules/autoload/modctrl.py
new file mode 100644 (file)
index 0000000..cba4f8c
--- /dev/null
@@ -0,0 +1,49 @@
+from classes import *
+from util import *
+
+name = 'module control'
+def init(cache):
+       cache.currmod = __name__
+       cache.hookcmd('LOADMOD', 9, doloadmod, 1, helploadmod, isadmin=True)
+       cache.hookcmd('UNLOADMOD', 9, dounloadmod, 1, helpunloadmod, isadmin=True)
+       cache.hookcmd('RELOADMOD', 9, doreloadmod, 1, helpreloadmod, isadmin=True)
+       cache.hookcmd('LISTMODS', 8, dolistmods, 0, helplistmods, isadmin=True)
+def deinit(cache, reloading):
+       if reloading:
+               cache.currmod = __name__
+               cache.unhookcmd('LOADMOD')
+               cache.unhookcmd('UNLOADMOD')
+               cache.unhookcmd('RELOADMOD')
+               cache.unhookcmd('LISTMODS')
+       else:
+               return True # block unload
+
+def doloadmod(nick, target, params, bot, cache):
+       modf = params.split()[0]
+       retcode = loadmod(modf, cache)
+       if retcode == 0: bot.msg(nick, "Loaded %s successfully." % (modf))
+       elif retcode == 1: bot.msg(nick, "Error loading %s: import error." % (modf))
+       elif retcode == 2: bot.msg(nick, "Error loading %s: already loaded." % (modf))
+def dounloadmod(nick, target, params, bot, cache):
+       modf = params.split()[0]
+       retcode = unloadmod(modf, cache)
+       if retcode == 0: bot.msg(nick, "Unloaded %s successfully." % (modf))
+       elif retcode == 1: bot.msg(nick, "Error unloading %s: module refused unload." % (modf))
+       elif retcode == 2: bot.msg(nick, "Error unloading %s: not loaded." % (modf))
+def doreloadmod(nick, target, params, bot, cache):
+       modf = params.split()[0]
+       retcode = reloadmod(modf, cache)
+       if retcode == 0: bot.msg(nick, "Reloaded %s successfully." % (modf))
+       else: bot.msg(nick, "Error reloading %s: %d" % (modf, retcode))
+
+def dolistmods(nick, target, params, bot, cache):
+       bot.msg(nick, "-- LISTMODS")
+       for modf in cache.modules.keys():
+               mod = cache.modules[modf]
+               bot.msg(nick, "%-20s - %s" % (modf, mod.name))
+       bot.msg(nick, "-- END LISTMODS")
+
+def helploadmod(): return ['LOADMOD <module>', 'Loads "modules/<module>.py".', 'modctrl']
+def helpunloadmod(): return ['UNLOADMOD <module>', 'Unloads "modules/<module>.py".']
+def helpreloadmod(): return ['RELOADMOD <module>', 'Reloads "modules/<module>.py".']
+def helplistmods(): return ['LISTMODS', 'List loaded modules.']
diff --git a/modules/autoload/quit.py b/modules/autoload/quit.py
new file mode 100644 (file)
index 0000000..ab4abf2
--- /dev/null
@@ -0,0 +1,15 @@
+from classes import *
+from util import *
+
+name = 'quit'
+def init(cache):
+       cache.currmod = __name__
+       cache.hookcmd('DIE', 8, die, 0, helpdie, isadmin=True)
+def deinit(cache, reloading=False):
+       cache.currmod = __name__
+       cache.unhookcmd('DIE')
+
+def die(nick, target, params, bot, cache):
+       return "QUIT"
+
+def helpdie(): return ['DIE', 'Kills the bots.']
diff --git a/modules/autoload/weather.py b/modules/autoload/weather.py
new file mode 100644 (file)
index 0000000..452ba5b
--- /dev/null
@@ -0,0 +1,36 @@
+from classes import *
+from util import *
+
+import httplib, json
+
+apikey = '6117a56225f311c3'
+
+name = 'weather underground'
+def init(cache):
+       cache.currmod = __name__
+       cache.hookcmd('WEATHER', 0, weather, 1, helpweather, reqchan=False)
+       cache.hookcmd('W', 0, weather, 1, helpw, reqchan=False)
+def deinit(cache, reloading=False):
+       cache.currmod = __name__
+       cache.unhookcmd('WEATHER')
+       cache.unhookcmd('W')
+
+def weather(nick, target, params, bot, cache):
+       location = params.strip().replace(' ', '_')
+       conn = httplib.HTTPConnection("api.wunderground.com")
+       conn.request("GET", "/api/%s/conditions/q/%s.json" % (apikey, location))
+       res = conn.getresponse()
+       if res.status == 200:
+               data = res.read()
+               wz = json.loads(data)
+               if 'current_observation' in wz:
+                       wz = wz['current_observation']
+                       buf = "Weather for %s, %s: %s - Feels like: %s - Conditions: %s - Humidity: %s. %s" % (wz['display_location']['full'], wz['display_location']['country_iso3166'], wz['temperature_string'], wz['feelslike_string'], wz['weather'], wz['relative_humidity'], wz['observation_time'])
+                       bot.msg(target, buf)
+                       return
+       bot.msg(target, "Error retrieving weather.")
+
+def helpweather():
+       return ['WEATHER <location>', 'Gets weather data from Weather Underground']
+def helpw():
+       return ['W <location>', 'Alias for WEATHER.']
diff --git a/modules/sample.py b/modules/sample.py
new file mode 100644 (file)
index 0000000..dd43f2b
--- /dev/null
@@ -0,0 +1,38 @@
+from classes import *
+from util import *
+
+name = 'foobar'
+author = 'John Runyon'
+version = '1'
+def init(cache):
+       cache.currmod = __name__
+#cache.hookcmd('COMMAND',level,cmdfn,params,helpfn,isadmin=False,reqchan=True)
+       cache.hookcmd('FOO', 0, foo, 0, helpfoo)
+       cache.hookcmd('BAR', 1, bar, 0, helpbar, isadmin=True)
+       cache.hookcmd('BAZ', 0, baz, 0, helpbaz, reqchan=False)
+       cache.hookcmd('HELLO', 0, hello, 1, helphello)
+def deinit(cache, reloading=False):
+       cache.currmod = __name__
+       cache.unhookcmd('FOO')
+       cache.unhookcmd('BAR')
+       cache.unhookcmd('BAZ')
+       cache.unhookcmd('HELLO')
+
+def foo(nick, target, params, bot, cache):
+       bot.msg(nick, "Foo to you too! (nick)")
+       bot.msg(target, "Foo to you too! (target)")
+def bar(nick, target, params, bot, cache):
+       bot.msg(nick, "Bar to you too! (nick)")
+       bot.msg(target, "Bar to you too! (target)")
+def baz(nick, target, params, bot, cache):
+       bot.msg(nick, "Baz to you too! (nick)")
+       bot.msg(target, "Baz to you too! (target)")
+def hello(nick, target, params, bot, cache):
+       bot.msg(nick, "Hello, %s! (nick)" % (params))
+       bot.msg(target, "Hello, %s! (target" % (params))
+
+# ['COMMAND <params>', 'Help description']
+def helpfoo(): return ['FOO <#channel>', "'FOO', 0, foo, 0, helpfoo"]
+def helpbar(): return ['BAR', "'BAR', 1, bar, 0, helpbar, isadmin=True"]
+def helpbaz(): return ['BAZ', "'BAZ', 0, baz, 0, helpbaz, reqchan=False"]
+def helphello(): return ['HELLO', "'HELLO', 0, hello, 1, helphello"]
diff --git a/modules/trivia.py b/modules/trivia.py
new file mode 100644 (file)
index 0000000..0cb89f5
--- /dev/null
@@ -0,0 +1,119 @@
+from classes import *
+from util import *
+
+import time, os, random, json
+from threading import Timer
+from traceback import print_exc
+
+name = 'trivia'
+author = 'John Runyon'
+version = '1'
+
+questions = []
+points = {}
+
+aqinterval = 10 # seconds from question end -> next question
+qhinterval = 30 # seconds from question prompt -> hint
+hainterval = 30 # seconds from hint -> question end
+
+def init(cache):
+       global questions, points
+       cache.currmod = __name__
+       cache.trivia = {}
+
+       try:
+               qfile = open(cache.triviapath+"/questions.txt", 'r')
+       except IOError as e:
+               print_exc(None, cache.excfile)
+               return True
+       questions = qfile.readlines()
+       qfile.close()
+
+       try:
+               ptsfile = open(cache.triviapath+"/points.json", 'r')
+       except IOError as e:
+               print_exc(None, cache.excfile)
+               return True
+       points = json.load(ptsfile)
+       ptsfile.close()
+
+       cache.hookcmd('TRIVIA', 1, trivia, 1, helptrivia)
+       cache.hookcmd('T', 0, t, 1, helpt)
+def deinit(cache, reloading=False):
+       global questions, points
+       cache.currmod = __name__
+
+       ptsfile = open(cache.triviapath+"/points.json", 'w')
+       json.dump(points, ptsfile, indent=4)
+       ptsfile.close()
+
+       del questions
+       del points
+       del cache.trivia
+       cache.unhookcmd('TRIVIA')
+
+def _loadQuestion():
+       tdict = {}
+       line = random.choice(questions)
+       parts = line.split('~', 2)
+       tdict['question'] = parts[0]
+       tdict['answer'] = parts[1].lower()
+       if len(parts) == 3: tdict['flags'] = parts[2].split('~')
+       else: tdict['flags'] = []
+       return tdict
+def _sendQuestion(cache, chan):
+       tdict = cache.trivia[chan]['tdict']
+       cache.chans[chan].bot.msg(chan, "Next question: %s" % (tdict['question']))
+       timer = Timer(qhinterval, _sendHint, kwargs={'cache': cache, 'chan': chan})
+       cache.trivia[chan]['timer'] = timer
+       cache.trivia[chan]['timertype'] = '_sendHint'
+def _sendHint(cache, chan):
+       tdict = cache.trivia[chan]['tdict']
+       pieces = tdict['answer'].split(' ')
+       hintpieces = []
+       for piece in pieces:
+               if piece == '': continue
+               plen = len(piece)
+               reveal = int(round(plen*0.40))
+               if reveal < 2: reveal = 2
+               if reveal > plen: reveal = plen
+
+               revealpos = []
+               for i in range(reveal):
+                       pos = random.randint(0, plen-1)
+                       while pos in revealpos:
+                               pos = random.randint(0, plen-1)
+                       revealpos.append(pos)
+               hiddenpieces = []
+               for i in range(plen):
+                       if i in revealpos:
+                               hiddenpieces.append(piece[i])
+                       elif not piece[i].isalnum():
+                               hiddenpieces.append(piece[i])
+                       else:
+                               hiddenpieces.append('*')
+               hintpieces.append(''.join(hiddenpieces))
+       hint = ' '.join(hintpieces)
+       cache.chans[chan].bot.msg(chan, "Hint: %s" % (hint))
+
+def trivia(nick, target, params, bot, cache):
+       chan = target.lower()
+       if params == 'start':
+               tdict = _loadQuestion()
+               timer = Timer(aqinterval, _sendQuestion, kwargs={'cache': cache, 'chan': chan})
+               cache.trivia[chan] = {'tdict': tdict, 'timer': timer, 'timertype': '_sendQuestion'}
+       elif params == 'stop':
+               cache.trivia[chan]['timer'].cancel()
+               del cache.trivia[chan]
+def t(nick, target, params, bot, cache):
+       chan = target.lower()
+       guess = params.lower()
+       if chan not in cache.trivia:
+               bot.msg(nick, "Trivia isn't running in %s!" % (chan))
+       elif nick not in cache.users or cache.users[nick] == '':
+               bot.msg(nick, "You must be AUTH'ed and recognized by me! Try !AUTH")
+       else:
+               pass #TODO
+
+def helptrivia(): return ['TRIVIA <#channel> start|stop', 'Starts or stops trivia.']
+def helpt(): return ['T <#channel> <answer>', 'Attempt to answer a trivia question. You must be authed with the bot. If no reply, guess was incorrect.']
diff --git a/modules/trivia/points.json b/modules/trivia/points.json
new file mode 100644 (file)
index 0000000..9344ab1
--- /dev/null
@@ -0,0 +1,6 @@
+{
+    "#ophion": {
+        "BiohZn": 4, 
+        "DimeCadmium": 5
+    }
+}
\ No newline at end of file
diff --git a/modules/trivia/questions.txt b/modules/trivia/questions.txt
new file mode 100644 (file)
index 0000000..3d364b8
--- /dev/null
@@ -0,0 +1,2 @@
+Where was DimeCadmium born? (State postal abbreviation)~TX
+Where does BiohZn live? (City, Country)~Vaasa, Finland
diff --git a/util.py b/util.py
new file mode 100644 (file)
index 0000000..84a5bc0
--- /dev/null
+++ b/util.py
@@ -0,0 +1,79 @@
+from traceback import print_exc
+
+def toint(s):
+       i = None
+       try: i = int(s)
+       except: pass
+       return i
+
+def noaccess(bot, nick, chcmd=False):
+       if chcmd:
+               bot.msg(nick, "You don't have enough access to do that.")
+       else:
+               bot.msg(nick, "No such command or not enough access.")
+
+#            except Exception as e:
+#               print_exc(None, cache.excfile)
+#               bot.msg(fromnick, "An error occurred, sorry! Try again later.")
+#               cache.mainBot.cmsg('warn', "EXCEPTION! %r Caused by <%s> %s" % (e, fromnick, line))
+def loadmod(modf, cache, reloading=False):
+       # return: 0=success, 1=import error, 2=already loaded, 3=reload() failed
+       if modf in cache.modules:
+               return 2
+       elif modf in cache.unmodules:
+               mod = cache.unmodules[modf]
+               try:
+                       reload(mod)
+               except Exception as e:
+                       print "! reload"
+                       print_exc(None, cache.excfile)
+                       return 1
+               try:
+                       if mod.init(cache):
+                               print "not mod.init(cache)"
+                               cache.excfile.write("%s refused init - returned True\n" % (modf))
+                               return 1
+               except Exception as e:
+                       print "! init"
+                       print_exc(None, cache.excfile)
+                       return 1
+               del cache.unmodules[modf]
+       else:
+               try:
+                       mod = __import__(modf)
+               except Exception as e:
+                       print "! import"
+                       print_exc(None, cache.excfile)
+                       return 1
+               try:
+                       if mod.init(cache):
+                               cache.excfile.write("%s refused init - returned True\n" % (modf))
+               except Exception as e:
+                       print "! init"
+                       print_exc(None, cache.excfile)
+                       return 1
+       cache.modules[modf] = mod
+       return 0
+def unloadmod(modf, cache, reloading=False):
+       # return: 0=success, 1=refused unload, 2=not loaded
+       if modf not in cache.modules:
+               return 2
+       else:
+               try:
+                       ret = cache.modules[modf].deinit(cache, reloading)
+               except Exception as e:
+                       print_exc(None, cache.excfile)
+                       return 1
+               if ret:
+                       return 1
+               else:
+                       cache.unmodules[modf] = cache.modules[modf]
+                       del cache.modules[modf]
+                       return 0
+def reloadmod(modf, cache):
+       # return: 0 = success; 1,2=unload error; 3,4,5=load error
+       unloadret = unloadmod(modf, cache, True)
+       if unloadret != 0: return unloadret
+       loadret = loadmod(modf, cache, True)
+       if loadret != 0: return loadret+2
+       return 0