diff -r 2266d6d73de3 -r d67cc4fbc3f1 cobalt.py --- a/cobalt.py Sun Nov 09 19:59:10 2014 +0200 +++ b/cobalt.py Mon Nov 10 02:06:06 2014 +0200 @@ -28,28 +28,52 @@ ''' import asyncore -import socket -import time import sys import traceback -import re -import urllib -import urllib2 -import hgapi import os -import math -import json -from datetime import datetime import modulecore as ModuleCore -import configfile +import configfile as ConfigFile from configfile import Config import hgpoll as HgPoll import bt as Bt +import irc as Irc + +if __name__ != '__main__': + raise ImportError ('cobalt may not be imported as a module') g_BotActive = False +# +# Exception handling +# +def handle_exception(excType, excValue, trace): + excepterm (traceback.format_exception(excType, excValue, trace)) + +def excepterm (data): + for segment in data: + for line in segment.splitlines(): + print line + Irc.broadcast (line) + + for client in Irc.all_clients: + if len(data) > 0: + client.exceptdie() + else: + client.quit_irc() + + if g_BotActive: + restart_self() + else: + quit() + +def restart_self(): + os.execl (sys.executable, sys.executable, * sys.argv) + def main(): - ModuleCore.init_data() + global g_BotActive + sys.excepthook = handle_exception + all_clients = [] + g_BotActive = False try: uid = os.geteuid() @@ -59,9 +83,10 @@ if uid == 0 and raw_input ('Do you seriously want to run cobalt as root? [y/N] ') != 'y': quit() - configfile.init() - g_admins = Config.get_value ('admins', default=[]) - g_mynick = Config.get_value ('nickname', default='cobalt') + ConfigFile.init() + ModuleCore.init_data() + Bt.init() + HgPoll.init() try: autoconnects = Config.get_value ('autoconnect', []) @@ -73,7 +98,7 @@ for aconn in autoconnects: for conndata in Config.get_nodelist ('connections'): if conndata.get_value ('name') == aconn: - irc_client (conndata, 0) + Irc.irc_client (conndata, 0) break else: raise ValueError ("unknown autoconnect entry %s" % (aconn)) @@ -81,335 +106,11 @@ g_BotActive = True asyncore.loop() except KeyboardInterrupt: - for client in all_clients: + for client in Irc.all_clients: client.keyboardinterrupt() quit() - -# -# irc_client flags -# -CLIF_CONNECTED = (1 << 1) - -# -# List of all clients -# -all_clients = [] - -class channel (object): - name = "" - password = "" - - def __init__ (self, name): - self.name = name - -# -# Prints a line to log channel(s) -# -def chanlog (line): - for client in all_clients: - if not client.flags & CLIF_CONNECTED: - continue - - for channel in client.channels: - if channel.get_value ('logchannel', default=False): - client.write ("PRIVMSG %s :%s" % (channel['name'], line)) - -# -# Exception handling -# -def handle_exception(excType, excValue, trace): - excepterm (traceback.format_exception(excType, excValue, trace)) - -def excepterm(data): - for segment in data: - for line in segment.splitlines(): - print line - chanlog (line) - - for client in all_clients: - if len(data) > 0: - client.exceptdie() - else: - client.quit_irc() - - if g_BotActive: - restart_self() - else: - quit() - -sys.excepthook = handle_exception - -def check_admin (sender, ident, host, command): - if not "%s@%s" % (ident, host) in g_admins: - raise logical_exception (".%s requires admin access" % command) - -class logical_exception (Exception): - def __init__ (self, value): - self.value = value - def __str__ (self): - return self.value - -# from http://www.daniweb.com/software-development/python/code/260268/restart-your-python-program -def restart_self(): - python = sys.executable - os.execl (python, python, * sys.argv) - -def plural (a): - return '' if a == 1 else 's' - -# -# Main IRC client class -# -class irc_client (asyncore.dispatcher): - def __init__ (self, cfg, flags): - self.name = cfg.get_value ('name') - self.host = cfg.get_value ('address') - self.port = cfg.get_value ('port', default=6667) - self.password = cfg.get_value ('password', default='') - self.channels = cfg.get_nodelist ('channels') - self.flags = flags - self.send_buffer = [] - self.umode = cfg.get_value ('umode', default='') - self.cfg = cfg - self.desired_name = Config.get_value ('nickname', default='cobalt') - self.mynick = self.desired_name - self.verbose = Config.get_value ('verbose', default=False) - self.commandprefix = Config.get_value ('commandprefix', default='.') - - all_clients.append (self) - asyncore.dispatcher.__init__ (self) - self.create_socket (socket.AF_INET, socket.SOCK_STREAM) - self.connect ((self.host, self.port)) - - def register_to_irc (self): - ident = Config.get_value ('ident', default='cobalt') - gecos = Config.get_value ('gecos', default='cobalt') - self.write ("PASS %s" % self.password) - self.write ("USER %s * * :%s" % (ident, gecos)) - self.write ("NICK %s" % self.mynick) - - def handle_connect (self): - print "Connected to [%s] %s:%d" % (self.name, self.host, self.port) - self.register_to_irc() - - def write (self, utfdata): - try: - self.send_buffer.append ("%s" % utfdata.decode("utf-8","ignore").encode("ascii","ignore")) - except UnicodeEncodeError: - pass - - def handle_close (self): - print "Connection to [%s] %s:%d terminated." % (self.name, self.host, self.port) - self.close() - - def handle_write (self): - self.send_all_now() - - def readable (self): - return True - - def writable (self): - return len (self.send_buffer) > 0 - - def send_all_now (self): - for line in self.send_buffer: - if self.verbose: - print "[%s] <- %s" % (self.name, line) - self.send ("%s\n" % line) - self.send_buffer = [] - - def handle_read (self): - lines = self.recv (4096).splitlines() - for utfline in lines: - try: - line = utfline.decode("utf-8","ignore").encode("ascii","ignore") - except UnicodeDecodeError: - continue - - if self.verbose: - print "[%s] -> %s" % (self.name, line) - - if line.startswith ("PING :"): - self.write ("PONG :%s" % line[6:]) - else: - words = line.split(" ") - if len(words) >= 2: - if words[1] == "001": - self.flags |= CLIF_CONNECTED - - for channel in self.channels: - self.write ("JOIN %s %s" % (channel.get_value ('name'), channel.get_value ('password', default=''))) - - umode = self.cfg.get_value ('umode', '') - - if umode != '': - self.write ('MODE %s %s' % (self.mynick, self.cfg.get_value ('umode', ''))) - elif words[1] == "PRIVMSG": - self.handle_privmsg (line) - elif words[1] == 'QUIT': - rex = re.compile (r'^:([^!]+)!([^@]+)@([^ ]+) QUIT') - match = rex.match (line) - - # Try reclaim our nickname if possible - if match and match.group(1) == self.desired_name: - self.mynick = self.desired_name - self.write ("NICK %s" % self.mynick) - elif words[1] == "433": - #:irc.localhost 433 * cobalt :Nickname is already in use. - self.mynick += self.cfg.get_value ('conflictsuffix', default='`') - self.write ("NICK " + self.mynick) - - # Check for new issues on the bugtracker - bt_checklatest() - - # Check for new commits in the repositories - HgPoll.poll() - - def channel_by_name (self, name): - for channel in self.channels: - if channel.get_value ('name').upper() == args[0].upper(): - return channel - else: - raise logical_exception ('unknown channel ' + args[0]) - - # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # - # - # Handle a PRIVMSG line from the IRC server - # - def handle_privmsg (self, line): - rex = re.compile (r'^:([^!]+)!([^@]+)@([^ ]+) PRIVMSG ([^ ]+) :(.+)$') - match = rex.match (line) - if match: - sender = match.group (1) - user = match.group (2) - host = match.group (3) - channel = match.group (4) - message = match.group (5) - replyto = channel if channel != g_mynick else sender - - # Check for tracker url in the message - url = Config.get_node ('bt').get_value ('url') - http_regex = re.compile (r'.*http(s?)://%s/view\.php\?id=([0-9]+).*' % url) - http_match = http_regex.match (line) - - # Check for command. - if len(message) >= 2 and message[0] == self.commandprefix and message[1] != self.commandprefix: - stuff = message[1:].split(' ') - command = stuff[0] - args = stuff[1:] - try: - self.handle_command (sender, user, host, replyto, command, args, message) - except logical_exception as e: - for line in e.value.split ('\n'): - if len(line) > 0: - self.privmsg (replyto, "error: %s" % line) - elif http_match: - self.get_ticket_data (replyto, http_match.group (2), False) - else: - chanlog ("Recieved bad PRIVMSG: %s" % line) - - def is_admin (self, ident, host): - return ("%s@%s" % (ident, host)) in g_admins - - # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # - # - # Process an IRC command - # - def handle_command (self, sender, ident, host, replyto, command, args, message): - kvargs = {'sender': sender, 'ident': ident, 'host': host, 'replyto': replyto, 'cmdname': command, 'message': message} - - try: - result = ModuleCore.call_command (self, **kvargs) - - if result: - return - except ModuleCore.CommandError as e: - lines = str (e).split ('\n') - self.privmsg (replyto, 'error: %s' % lines[0]) - - for line in lines[1:]: - self.privmsg (replyto, ' ' + line) - return - #tried - - if command == 'die': - check_admin (sender, ident, host, command) - quit() - elif command == 'convert': - if len(args) != 3 or args[1] != 'as': - raise logical_exception ("usage: .%s as " % command) - - value = float (args[0]) - valuetype = args[2] - - if valuetype in ['radians', 'degrees']: - if valuetype == 'radians': - radvalue = value - degvalue = (value * 180.) / math.pi - else: - radvalue = (value * math.pi) / 180. - degvalue = value - - self.privmsg (replyto, '%s radians, %s degrees (%s)' %(radvalue, degvalue, degvalue % 360.)) - elif valuetype in ['celsius', 'fahrenheit']: - if valuetype == 'celsius': - celvalue = value - fahrvalue = value * 1.8 + 32 - else: - celvalue = (value - 32) / 1.8 - fahrvalue = value - - self.privmsg (replyto, '%s degrees celsius, %s degrees fahrenheit' %(celvalue, fahrvalue)) - else: - raise logical_exception ('unknown valuetype, expected one of: degrees, radians (angle conversion), ' + - 'celsius, fahrenheit (temperature conversion)') - elif command == 'urban' or command == 'ud': - try: - if len(args) < 1: - raise logical_exception ('usage: %s ' % command) - - url = 'http://api.urbandictionary.com/v0/define?term=%s' % ('%20'.join (args)) - response = urllib2.urlopen (url).read() - data = json.loads (response) - - if 'list' in data and len(data['list']) > 0 and 'word' in data['list'][0] and 'definition' in data['list'][0]: - word = data['list'][0]['word'] - definition = data['list'][0]['definition'].replace ('\r', ' ').replace ('\n', ' ').replace (' ', ' ') - up = data['list'][0]['thumbs_up'] - down = data['list'][0]['thumbs_down'] - self.privmsg (replyto, "\002%s\002: %s\0033 %d\003 up,\0035 %d\003 down" % (word, definition, up, down)) - else: - self.privmsg (replyto, "couldn't find a definition of \002%s\002" % args[0]) - except logical_exception as e: - raise e - except Exception as e: - raise logical_exception ('Urban dictionary lookup failed: %s' % `e`) -# else: -# raise logical_exception ("unknown command `.%s`" % command) - - def handle_error(self): - excepterm (traceback.format_exception(sys.exc_type, sys.exc_value, sys.exc_traceback)) - - def restart(self): - excepterm('') - - def privmsg (self, channel, msg): - self.write ("PRIVMSG %s :%s" % (channel, msg)) - - def close_connection (self, message): - if self.flags & CLIF_CONNECTED: - self.write ("QUIT :" + message) - self.send_all_now() - self.close() - - def quit_irc (self): - self.close_connection ('Leaving') - - def exceptdie (self): - self.close_connection ('Caught exception') - - def keyboardinterrupt (self): - self.close_connection ('KeyboardInterrupt') + except Irc.RestartError as e: + excepterm (e.message) if __name__ == '__main__': main() \ No newline at end of file