From 0ae73a4aef796dd530ce83ad29a6b66070da6c58 Mon Sep 17 00:00:00 2001 From: Ben Kurtovic Date: Sun, 10 Apr 2011 19:19:19 -0400 Subject: [PATCH] massive cleanup and moving a lot of functions to their own files; creating two new classes to store irc msg data and irc actions --- actions.py | 92 ---------------------------------- bot.py | 89 +++++++++++++++------------------ irc/Actions.py | 29 +++++++++++ irc/Data.py | 12 +++++ irc/__init__.py | 0 irc/commands/__init__.py | 0 irc/commands/git.py | 127 +++++++++++++++++++++++++++++++++++++++++++++++ irc/commands/test.py | 20 ++++++++ irc/triggers.py | 21 ++++++++ 9 files changed, 248 insertions(+), 142 deletions(-) delete mode 100644 actions.py create mode 100644 irc/Actions.py create mode 100644 irc/Data.py create mode 100644 irc/__init__.py create mode 100644 irc/commands/__init__.py create mode 100644 irc/commands/git.py create mode 100644 irc/commands/test.py create mode 100644 irc/triggers.py diff --git a/actions.py b/actions.py deleted file mode 100644 index 92d6a5f..0000000 --- a/actions.py +++ /dev/null @@ -1,92 +0,0 @@ -# -*- coding: utf-8 -*- - -import string, re, subprocess -from config.irc_config import * - -s, send, say, action, notice, join = None, None, None, None, None, None - -def check_triggers(cmds, act, nick, ident, host, chan, msg = None): - global s, send, say, action, notice, join # set commands as globals so we can use them in other functions - s, send, say, action, notice, join = cmds # unpack commands - - if act == "join": - pass - - if act == "msg_private": - pass - - if act == "msg_public": - pass - - if act == "msg": - if msg == "!test": - cmd_test(nick, chan) - if msg.startswith("!git"): - cmd_git(nick, host, chan, msg) - -def get_args(msg): # get command arguments - args = msg.strip().split(' ') # strip out extra whitespace and split the message into a list - while '' in args: # remove any empty arguments - args.remove('') - return args[1:] # remove the command itself - -def cmd_test(nick, chan): # bot test - say(chan, "Hey \x02%s\x0F!" % nick) - -def cmd_git(nick, host, chan, msg): # commands to interface with the bot's git repository - if host not in ADMINS: - say(chan, "\x02%s\x0F: you must be a bot admin to use this command." % nick) - return - - args = get_args(msg) - if not args: - say(chan, "\x02%s\x0F: no arguments provided." % nick) - return - - if args[0] == "help": # display all commands - cmds = ["\x0303branch\x0301 (show current branch)", "\x0303branches\x0301 (show all branches)", - "\x0303checkout\x0301 (switch branches)", "\x0303pull\x0301 (update current branch)"] - cmds = ', '.join(cmds) - say(chan, "\x02%s\x0F: sub-commands are: %s" % (nick, cmds)) - - elif args[0] == "branch": # get our current branch - branch = subprocess.check_output(['git', 'name-rev', '--name-only', 'HEAD'], stderr=subprocess.STDOUT) # git name-rev --name-only HEAD - branch = branch[:-1] # strip newline - say(chan, "\x02%s\x0F: currently on branch \x0302%s\x0301." % (nick, branch)) - - elif args[0] == "branches": # get list of branches - branches = subprocess.check_output(['git', 'branch'], stderr=subprocess.STDOUT) # git branch - branches = branches[:-1] # strip newline - branches = branches.replace('\n* ', ', ') # cleanup extraneous characters - branches = branches.replace('* ', ' ') - branches = branches.replace('\n ', ', ') - branches = branches.strip() - say(chan, "\x02%s\x0F: branches: \x0302%s\x0301." % (nick, branches)) - - elif args[0] == "checkout": # switch branches - try: - branch = args[1] - except IndexError: # no branch name provided - say(chan, "\x02%s\x0F: switch to which branch?" % nick) - return - try: - result = subprocess.check_output(['git', 'checkout', branch], stderr=subprocess.STDOUT) # git checkout our_branch - if "Already on" in result: - say(chan, "\x02%s\x0F: already on \x0302%s\x0301!" % (nick, branch)) - else: - say(chan, "\x02%s\x0F: switched to branch \x0302%s\x0301." % (nick, branch)) - except subprocess.CalledProcessError: # git couldn't switch branches - say(chan, "\x02%s\x0F: branch \x0302%s\x0301 does not exist!" % (nick, branch)) - - elif args[0] == "pull": # pull from remote repository - branch = subprocess.check_output(['git', 'name-rev', '--name-only', 'HEAD'], stderr=subprocess.STDOUT) # git name-rev --name-only HEAD - branch = branch[:-1] # strip newline - say(chan, "\x02%s\x0F: pulling from remote (currently on \x0302%s\x0301)..." % (nick, branch)) - result = subprocess.check_output(['git', 'pull']) # pull from remote - if "Already up-to-date." in result: - say(chan, "\x02%s\x0F: done; no new changes." % nick) - else: - say(chan, "\x02%s\x0F: done; new changes merged." % nick) - - else: - say(chan, "\x02%s\x0F: unknown argument: \x0303%s\x0301." % (nick, args[0])) diff --git a/bot.py b/bot.py index 4a4e730..12e6a13 100644 --- a/bot.py +++ b/bot.py @@ -2,73 +2,62 @@ ## Imports import socket, string, re -from actions import * + from config.irc_config import * from config.secure_config import * -def send(msg): # send a message 'msg' to the server - s.send(msg + "\r\n") - print " %s" % msg - -def say(target, msg): # send a private message 'msg' to 'target' - send("PRIVMSG %s :%s" % (target, msg)) - -def action(target, msg): # send a message as an action - say(target,"%sACTION %s%s" % (chr(1), msg, chr(1))) - -def notice(target, msg): # send a notice 'msg' to 'target' - send("NOTICE %s :%s" % (target, msg)) - -def join(chan): # join channel 'chan' - send("JOIN %s" % chan) - -cmds = (s, send, say, action, notice, join) # pack up commands +from irc import triggers +from irc.actions import * +from irc.data import * def main(): - readbuffer = str() - data = [s, send, say, notice, join] + read_buffer = str() + while 1: - readbuffer = readbuffer + s.recv(1024) - temp = string.split(readbuffer, "\n") - readbuffer = temp.pop() + read_buffer = read_buffer + actions.sock.recv(1024) + temp = string.split(read_buffer, "\n") + read_buffer = temp.pop() + for line in temp: - line2 = string.split(string.rstrip(line)) + line = string.split(string.rstrip(line)) + data = Data() - if line2[1] == "JOIN": - nick, ident, host = re.findall(":(.*?)!(.*?)@(.*?)\Z", line2[0])[0] - chan = line2[2][1:] + if line[1] == "JOIN": + data.nick, data.ident, data.host = re.findall(":(.*?)!(.*?)@(.*?)\Z", line[0])[0] + data.chan = line[2][1:] - check_triggers(cmds, "join", nick, ident, host, chan) # check if there's anything we can respond to, and if so, respond + triggers.check(actions, data, "join") # check if there's anything we can respond to, and if so, respond - if line2[1] == "PRIVMSG": - nick, ident, host = re.findall(":(.*?)!(.*?)@(.*?)\Z", line2[0])[0] - msg = ' '.join(line2[3:])[1:] - chan = line2[2] + if line[1] == "PRIVMSG": + data.nick, data.ident, data.host = re.findall(":(.*?)!(.*?)@(.*?)\Z", line[0])[0] + data.msg = ' '.join(line[3:])[1:] + data.chan = line[2] - if chan == NICK: # if line2[2] is us, this is a privmsg to us, so set 'chan' as the nick of the sender - chan = nick - check_triggers(cmds, "msg_private", nick, ident, host, chan, msg) # only respond if it's a private message + if data.chan == NICK: # this is a privmsg to us, so set 'chan' as the nick of the sender + data.chan = data.nick + triggers.check(actions, data, "msg_private") # only respond if it's a private message else: - check_triggers(cmds, "msg_public", nick, ident, host, chan, msg) # only respond if it's a public (channel) message + triggers.check(actions, data, "msg_public") # only respond if it's a public (channel) message - check_triggers(cmds, "msg", nick, ident, host, chan, msg) # check for general messages + triggers.check(actions, data, "msg") # check for general messages - if msg == "!restart": # hardcode the !restart command (we can't return from within actions.py) - if host in ADMINS: + if data.msg == "!restart": # hardcode the !restart command (we can't return from within actions.py) + if data.host in ADMINS: return True - if line2[0] == "PING": # If we are pinged, pong back to the server - send("PONG %s" % line2[1]) + if line[0] == "PING": # If we are pinged, pong back to the server + actions.send("PONG %s" % line[1]) - if line2[1] == "376": - if NS_AUTH: - say("NickServ", "IDENTIFY %s %s" % (NS_USER, NS_PASS)) - for this_chan in CHANS: # join all of our startup channels - join(this_chan) + if line[1] == "376": + if NS_AUTH: # if we're supposed to auth to nickserv, do that + actions.say("NickServ", "IDENTIFY %s %s" % (NS_USER, NS_PASS)) + for chan in CHANS: # join all of our startup channels + actions.join(chan) if __name__ == "__main__": - s = socket.socket() - s.connect((HOST, PORT)) - send("NICK %s" % NICK) - send("USER %s %s bla :%s" % (IDENT, HOST, REALNAME)) + sock = socket.socket() + sock.connect((HOST, PORT)) + actions = Actions(sock) + actions.send("NICK %s" % NICK) + actions.send("USER %s %s bla :%s" % (IDENT, HOST, REALNAME)) main() diff --git a/irc/Actions.py b/irc/Actions.py new file mode 100644 index 0000000..2b3bddf --- /dev/null +++ b/irc/Actions.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- + +# Actions/commands to interface with IRC. + +class Actions: + def __init__(self, sock): + """actions/commands to interface with IRC""" + self.sock = sock + + def send(self, msg): + """send data to the server""" + self.sock.send(msg + "\r\n") + print " %s" % msg + + def say(self, target, msg): + """send a message""" + self.send("PRIVMSG %s :%s" % (target, msg)) + + def action(self, target, msg): + """send a message as an action""" + self.say(target,"%sACTION %s%s" % (chr(1), msg, chr(1))) + + def notice(self, target, msg): + """send a notice""" + self.send("NOTICE %s :%s" % (target, msg)) + + def join(self, chan): + """join a channel""" + self.send("JOIN %s" % chan) diff --git a/irc/Data.py b/irc/Data.py new file mode 100644 index 0000000..ddf5059 --- /dev/null +++ b/irc/Data.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- + +# A class to store data from an individual line received on IRC. + +class Data: + def __init__(self): + """store data from an individual line received on IRC""" + self.chan = None + self.nick = None + self.ident = None + self.host = None + self.msg = None diff --git a/irc/__init__.py b/irc/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/irc/commands/__init__.py b/irc/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/irc/commands/git.py b/irc/commands/git.py new file mode 100644 index 0000000..d4d80ba --- /dev/null +++ b/irc/commands/git.py @@ -0,0 +1,127 @@ +# -*- coding: utf-8 -*- + +# Commands to interface with the bot's git repository. + +import shlex, subprocess +from config.irc_config import * + +actions, data = None, None +args = None + +def call(a, d): + global actions, data + actions, data = a, d + + if not check_user_is_admin(): + return + + args = get_args() + + if not check_has_args(): + return + + if args[0] == "help": + do_help() + + elif args[0] == "branch": + do_branch() + + elif args[0] == "branches": + do_branches() + + elif args[0] == "checkout": + do_checkout() + + elif args[0] == "pull": + do_pull() + + else: + unknown_arg() # they asked us to do something we don't know + +def get_args(): + """get command arguments""" + args = data.msg.strip().split(' ') # strip out extra whitespace and split the message into a list + while '' in args: # remove any empty arguments + args.remove('') + return args[1:] # remove the command itself + +def check_user_is_admin(): + """check if the user is a bot admin (and can use this command, as a result)""" + if data.host not in ADMINS: + actions.say(data.chan, "\x02%s\x0F: you must be a bot admin to use this command." % data.nick) + return False + return True + +def check_has_args(): + """check if they provided arguments along with the !git command""" + if not args: + actions.say(data.chan, "\x02%s\x0F: no arguments provided." % data.nick) + return False + return True + +def exec_shell(command): + """execute a shell command and get the output""" + command = shlex.split(command) + result = subprocess.check_output(command, stderr=subprocess.STDOUT) + return result + +def do_help(): + """display all commands""" + help = ["\x0303branch\x0301 (show current branch)", "\x0303branches\x0301 (show all branches)", + "\x0303checkout\x0301 (switch branches)", "\x0303pull\x0301 (update current branch)"] + help = ', '.join(help) + + actions.say(data.chan, "\x02%s\x0F: sub-commands are: %s" % (data.nick, help)) + +def do_branch(): + """get our current branch""" + branch = exec_shell("git name-rev --name-only HEAD") + branch = branch[:-1] # strip newline + + actions.say(data.chan, "\x02%s\x0F: currently on branch \x0302%s\x0301." % (data.nick, branch)) + +def do_branches(): + """get list of branches""" + branches = exec_shell("git branch") + + branches = branches[:-1] # strip newline + branches = branches.replace('\n* ', ', ') # cleanup extraneous characters + branches = branches.replace('* ', ' ') + branches = branches.replace('\n ', ', ') + branches = branches.strip() + + actions.say(data.chan, "\x02%s\x0F: branches: \x0302%s\x0301." % (data.nick, branches)) + +def do_checkout(): + """switch branches""" + try: + branch = args[1] + except IndexError: # no branch name provided + actions.say(chan, "\x02%s\x0F: switch to which branch?" % data.nick) + return + + try: + result = exec_shell("git checkout %s" % branch) + if "Already on" in result: + actions.say(data.chan, "\x02%s\x0F: already on \x0302%s\x0301!" % (data.nick, branch)) + else: + actions.say(data.chan, "\x02%s\x0F: switched to branch \x0302%s\x0301." % (data.nick, branch)) + + except subprocess.CalledProcessError: # git couldn't switch branches + actions.say(data.chan, "\x02%s\x0F: branch \x0302%s\x0301 does not exist!" % (data.nick, branch)) + +def do_pull(): + """pull from remote repository""" + branch = exec_shell("git name-rev --name-only HEAD") + branch = branch[:-1] # strip newline + actions.say(data.chan, "\x02%s\x0F: pulling from remote (currently on \x0302%s\x0301)..." % (data.nick, branch)) + + result = exec_shell("git pull") + + if "Already up-to-date." in result: + actions.say(data.chan, "\x02%s\x0F: done; no new changes." % data.nick) + else: + actions.say(data.chan, "\x02%s\x0F: done; new changes merged." % data.nick) + +def unknown_arg(): + actions.say(data.chan, "\x02%s\x0F: unknown argument: \x0303%s\x0301." % (data.nick, args[0])) diff --git a/irc/commands/test.py b/irc/commands/test.py new file mode 100644 index 0000000..a937791 --- /dev/null +++ b/irc/commands/test.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- + +# A simple command to test the bot. + +import random + +actions, data = None, None + +def call(a, d): + global actions, data + actions, data = a, d + + choices = ("say_hi()", "say_sup()") + exec random.choice(choices) + +def say_hi(): + actions.say(data.chan, "Hey \x02%s\x0F!" % data.nick) + +def say_sup(): + actions.say(data.chan, "'sup \x02%s\x0F?" % data.nick) diff --git a/irc/triggers.py b/irc/triggers.py new file mode 100644 index 0000000..28ec3d0 --- /dev/null +++ b/irc/triggers.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- + +# Check what events on IRC we can respond to. + +from irc.commands import test, git + +def check(actions, data, hook): + if hook == "join": + pass + + if hook == "msg_private": + pass + + if hook == "msg_public": + pass + + if hook == "msg": + if data.msg == "!test": + test.call(actions, data) + if data.msg.startswith("!git"): + git.call(actions, data)