setting up threading and two IRC sockets setting up watcher to report recent changes to a given list of channels on the main srver code cleanuptags/v0.1
@@ -1,69 +0,0 @@ | |||||
# -*- coding: utf-8 -*- | |||||
## Imports | |||||
import socket, re, time | |||||
from config.irc_config import * | |||||
from config.secure_config import * | |||||
from irc import triggers | |||||
from irc.actions import * | |||||
from irc.data import * | |||||
def main(): | |||||
read_buffer = str() | |||||
while 1: | |||||
try: | |||||
read_buffer = read_buffer + actions.get() | |||||
except RuntimeError: # socket broke | |||||
print "socket has broken, sleeping for a minute and restarting..." | |||||
time.sleep(60) # sleep for sixty seconds | |||||
return # then exit our loop and restart the bot | |||||
lines = read_buffer.split("\n") | |||||
read_buffer = lines.pop() | |||||
for line in lines: | |||||
line = line.strip().split() | |||||
data = Data() | |||||
if line[1] == "JOIN": | |||||
data.nick, data.ident, data.host = re.findall(":(.*?)!(.*?)@(.*?)\Z", line[0])[0] | |||||
data.chan = line[2][1:] | |||||
triggers.check(actions, data, "join") # check if there's anything we can respond to, and if so, respond | |||||
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 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: | |||||
triggers.check(actions, data, "msg_public") # only respond if it's a public (channel) message | |||||
triggers.check(actions, data, "msg") # check for general messages | |||||
if data.msg == "!restart": # hardcode the !restart command (we can't return from within actions.py) | |||||
if data.host in ADMINS: | |||||
return True | |||||
if line[0] == "PING": # If we are pinged, pong back to the server | |||||
actions.send("PONG %s" % line[1]) | |||||
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__": | |||||
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() |
@@ -3,23 +3,24 @@ | |||||
# EarwigBot Configuration File | # EarwigBot Configuration File | ||||
# This file contains information that the bot uses to connect to IRC. | # This file contains information that the bot uses to connect to IRC. | ||||
# our server's hostname | |||||
# our main (front-end) server's hostname and port | |||||
HOST = "irc.freenode.net" | HOST = "irc.freenode.net" | ||||
# our server's port | |||||
PORT = 6667 | PORT = 6667 | ||||
# our nick | |||||
NICK = "EarwigBot" | |||||
# our watcher server's hostname, port, and RC channel | |||||
WATCHER_HOST = "irc.wikimedia.org" | |||||
WATCHER_PORT = 6667 | |||||
WATCHER_CHAN = "#en.wikipedia" | |||||
# our ident | |||||
# our nick, ident, and real name, used on both servers | |||||
NICK = "EarwigBot" | |||||
IDENT = "earwigbot" | IDENT = "earwigbot" | ||||
# our real name | |||||
REALNAME = "[[w:en:User:EarwigBot]]" | REALNAME = "[[w:en:User:EarwigBot]]" | ||||
# channel to join on startup | |||||
# channels to join on main server's startup | |||||
CHANS = ["##earwigbot", "##earwig", "#wikipedia-en-afc"] | CHANS = ["##earwigbot", "##earwig", "#wikipedia-en-afc"] | ||||
AFC_CHANS = ["#wikipedia-en-afc"] # report recent AFC changes | |||||
BOT_CHANS = ["##earwigbot", "#wikipedia-en-afc"] # report edits containing "!earwigbot" | |||||
# hostnames of users who can update/restart the bot with !update | |||||
# hardcoded hostnames of users who can use !restart and !git | |||||
ADMINS = ["wikipedia/The-Earwig"] | ADMINS = ["wikipedia/The-Earwig"] |
@@ -0,0 +1,50 @@ | |||||
# -*- coding: utf-8 -*- | |||||
## EarwigBot's Core | |||||
## Basically, this creates threads for our IRC watcher component and Wikipedia component, and then runs the main IRC bot on the main thread. | |||||
## The IRC bot component of EarwigBot has two parts: a front-end and a watcher. | |||||
## The front-end runs on a normal IRC server and expects users to interact with it/give it commands. | |||||
## The watcher runs on a wiki recent-changes server and listens for edits. Users cannot interact with this part of the bot. | |||||
import threading | |||||
import time | |||||
import traceback | |||||
import sys | |||||
import os | |||||
parent_dir = os.path.split(sys.path[0])[0] | |||||
sys.path.append(parent_dir) # make sure we look in the parent directory for modules | |||||
from irc import frontend, watcher | |||||
f_conn = None | |||||
w_conn = None | |||||
def irc_watcher(f_conn): | |||||
global w_conn | |||||
while 1: # restart the watcher component if (just) it breaks | |||||
w_conn = watcher.get_connection() | |||||
try: | |||||
watcher.main(w_conn, f_conn) | |||||
except: | |||||
traceback.print_exc() | |||||
time.sleep(5) # sleep a bit before restarting watcher | |||||
print "restarting watcher component..." | |||||
def run(): | |||||
global f_conn | |||||
f_conn = frontend.get_connection() | |||||
t_watcher = threading.Thread(target=irc_watcher, args=(f_conn,)) | |||||
t_watcher.daemon = True | |||||
t_watcher.start() | |||||
frontend.main(f_conn) | |||||
if __name__ == "__main__": | |||||
try: | |||||
run() | |||||
finally: | |||||
f_conn.close() | |||||
w_conn.close() |
@@ -0,0 +1,15 @@ | |||||
# -*- coding: utf-8 -*- | |||||
import time | |||||
from subprocess import * | |||||
try: | |||||
from config import irc_config, secure_config | |||||
except ImportError: | |||||
print """Missing a config file! Make sure you have configured the bot. All *.py.default files in config/ | |||||
should have their .default extension removed, and the info inside should be corrected.""" | |||||
exit() | |||||
while 1: | |||||
call(['python', 'core/main.py']) | |||||
time.sleep(5) # sleep for five seconds between bot runs |
@@ -1,40 +0,0 @@ | |||||
# -*- 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 get(self, size = 4096): | |||||
"""receive (get) data from the server""" | |||||
data = self.sock.recv(4096) | |||||
if not data: | |||||
raise RuntimeError("socket is dead") | |||||
return data | |||||
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 reply(self, target, nick, msg): | |||||
"""send a message as a reply""" | |||||
self.say(target, "%s%s%s: %s" % (chr(2), nick, chr(0x0f), 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) |
@@ -5,18 +5,18 @@ | |||||
import shlex, subprocess, re | import shlex, subprocess, re | ||||
from config.irc_config import * | from config.irc_config import * | ||||
actions, data = None, None | |||||
connection, data = None, None | |||||
def call(a, d): | |||||
global actions, data | |||||
actions, data = a, d | |||||
def call(c, d): | |||||
global connection, data | |||||
connection, data = c, d | |||||
if data.host not in ADMINS: | if data.host not in ADMINS: | ||||
actions.reply(data.chan, data.nick, "you must be a bot admin to use this command.") | |||||
connection.reply(data.chan, data.nick, "you must be a bot admin to use this command.") | |||||
return | return | ||||
if not data.args: | if not data.args: | ||||
actions.reply(data.chan, data.nick, "no arguments provided.") | |||||
connection.reply(data.chan, data.nick, "no arguments provided.") | |||||
return | return | ||||
if data.args[0] == "help": | if data.args[0] == "help": | ||||
@@ -41,7 +41,7 @@ def call(a, d): | |||||
do_status() | do_status() | ||||
else: # they asked us to do something we don't know | else: # they asked us to do something we don't know | ||||
actions.reply(data.chan, data.nick, "unknown argument: \x0303%s\x0301." % data.args[0]) | |||||
connection.reply(data.chan, data.nick, "unknown argument: \x0303%s\x0301." % data.args[0]) | |||||
def exec_shell(command): | def exec_shell(command): | ||||
"""execute a shell command and get the output""" | """execute a shell command and get the output""" | ||||
@@ -68,14 +68,14 @@ def do_help(): | |||||
help += "\x0303%s\x0301 (%s), " % (key, help_dict[key]) | help += "\x0303%s\x0301 (%s), " % (key, help_dict[key]) | ||||
help = help[:-2] # trim last comma | help = help[:-2] # trim last comma | ||||
actions.reply(data.chan, data.nick, "sub-commands are: %s." % help) | |||||
connection.reply(data.chan, data.nick, "sub-commands are: %s." % help) | |||||
def do_branch(): | def do_branch(): | ||||
"""get our current branch""" | """get our current branch""" | ||||
branch = exec_shell("git name-rev --name-only HEAD") | branch = exec_shell("git name-rev --name-only HEAD") | ||||
branch = branch[:-1] # strip newline | branch = branch[:-1] # strip newline | ||||
actions.reply(data.chan, data.nick, "currently on branch \x0302%s\x0301." % branch) | |||||
connection.reply(data.chan, data.nick, "currently on branch \x0302%s\x0301." % branch) | |||||
def do_branches(): | def do_branches(): | ||||
"""get list of branches""" | """get list of branches""" | ||||
@@ -87,66 +87,66 @@ def do_branches(): | |||||
branches = branches.replace('\n ', ', ') | branches = branches.replace('\n ', ', ') | ||||
branches = branches.strip() | branches = branches.strip() | ||||
actions.reply(data.chan, data.nick, "branches: \x0302%s\x0301." % branches) | |||||
connection.reply(data.chan, data.nick, "branches: \x0302%s\x0301." % branches) | |||||
def do_checkout(): | def do_checkout(): | ||||
"""switch branches""" | """switch branches""" | ||||
try: | try: | ||||
branch = data.args[1] | branch = data.args[1] | ||||
except IndexError: # no branch name provided | except IndexError: # no branch name provided | ||||
actions.reply(data.chan, data.nick, "switch to which branch?") | |||||
connection.reply(data.chan, data.nick, "switch to which branch?") | |||||
return | return | ||||
try: | try: | ||||
result = exec_shell("git checkout %s" % branch) | result = exec_shell("git checkout %s" % branch) | ||||
if "Already on" in result: | if "Already on" in result: | ||||
actions.reply(data.chan, data.nick, "already on \x0302%s\x0301!" % branch) | |||||
connection.reply(data.chan, data.nick, "already on \x0302%s\x0301!" % branch) | |||||
else: | else: | ||||
actions.reply(data.chan, data.nick, "switched to branch \x0302%s\x0301." % branch) | |||||
connection.reply(data.chan, data.nick, "switched to branch \x0302%s\x0301." % branch) | |||||
except subprocess.CalledProcessError: # git couldn't switch branches | except subprocess.CalledProcessError: # git couldn't switch branches | ||||
actions.reply(data.chan, data.nick, "branch \x0302%s\x0301 doesn't exist!" % branch) | |||||
connection.reply(data.chan, data.nick, "branch \x0302%s\x0301 doesn't exist!" % branch) | |||||
def do_delete(): | def do_delete(): | ||||
"""delete a branch, while making sure that we are not on it""" | """delete a branch, while making sure that we are not on it""" | ||||
try: | try: | ||||
delete_branch = data.args[1] | delete_branch = data.args[1] | ||||
except IndexError: # no branch name provided | except IndexError: # no branch name provided | ||||
actions.reply(data.chan, data.nick, "delete which branch?") | |||||
connection.reply(data.chan, data.nick, "delete which branch?") | |||||
return | return | ||||
current_branch = exec_shell("git name-rev --name-only HEAD") | current_branch = exec_shell("git name-rev --name-only HEAD") | ||||
current_branch = current_branch[:-1] # strip newline | current_branch = current_branch[:-1] # strip newline | ||||
if current_branch == delete_branch: | if current_branch == delete_branch: | ||||
actions.reply(data.chan, data.nick, "you're currently on this branch; please checkout to a different branch before deleting.") | |||||
connection.reply(data.chan, data.nick, "you're currently on this branch; please checkout to a different branch before deleting.") | |||||
return | return | ||||
try: | try: | ||||
exec_shell("git branch -d %s" % delete_branch) | exec_shell("git branch -d %s" % delete_branch) | ||||
actions.reply(data.chan, data.nick, "branch \x0302%s\x0301 has been deleted locally." % delete_branch) | |||||
connection.reply(data.chan, data.nick, "branch \x0302%s\x0301 has been deleted locally." % delete_branch) | |||||
except subprocess.CalledProcessError: # git couldn't delete | except subprocess.CalledProcessError: # git couldn't delete | ||||
actions.reply(data.chan, data.nick, "branch \x0302%s\x0301 doesn't exist!" % delete_branch) | |||||
connection.reply(data.chan, data.nick, "branch \x0302%s\x0301 doesn't exist!" % delete_branch) | |||||
def do_pull(): | def do_pull(): | ||||
"""pull from remote repository""" | """pull from remote repository""" | ||||
branch = exec_shell("git name-rev --name-only HEAD") | branch = exec_shell("git name-rev --name-only HEAD") | ||||
branch = branch[:-1] # strip newline | branch = branch[:-1] # strip newline | ||||
actions.reply(data.chan, data.nick, "pulling from remote (currently on \x0302%s\x0301)..." % branch) | |||||
connection.reply(data.chan, data.nick, "pulling from remote (currently on \x0302%s\x0301)..." % branch) | |||||
result = exec_shell("git pull") | result = exec_shell("git pull") | ||||
if "Already up-to-date." in result: | if "Already up-to-date." in result: | ||||
actions.reply(data.chan, data.nick, "done; no new changes.") | |||||
connection.reply(data.chan, data.nick, "done; no new changes.") | |||||
else: | else: | ||||
changes = re.findall("\s*((.*?)\sfile(.*?)tions?\(-\))", result)[0][0] # find the changes | changes = re.findall("\s*((.*?)\sfile(.*?)tions?\(-\))", result)[0][0] # find the changes | ||||
actions.reply(data.chan, data.nick, "done; %s." % changes) | |||||
connection.reply(data.chan, data.nick, "done; %s." % changes) | |||||
def do_status(): | def do_status(): | ||||
"""check whether we have anything to pull""" | """check whether we have anything to pull""" | ||||
actions.reply(data.chan, data.nick, "checking remote for updates...") | |||||
connection.reply(data.chan, data.nick, "checking remote for updates...") | |||||
result = exec_shell("git fetch --dry-run") | result = exec_shell("git fetch --dry-run") | ||||
if not result: | if not result: | ||||
actions.reply(data.chan, data.nick, "local copy is up-to-date with remote.") | |||||
connection.reply(data.chan, data.nick, "local copy is up-to-date with remote.") | |||||
else: | else: | ||||
actions.reply(data.chan, data.nick, "remote is ahead of local copy.") | |||||
connection.reply(data.chan, data.nick, "remote is ahead of local copy.") |
@@ -2,11 +2,11 @@ | |||||
"""Generates help information.""" | """Generates help information.""" | ||||
actions, data = None, None | |||||
connection, data = None, None | |||||
def call(a, d): | |||||
global actions, data | |||||
actions, data = a, d | |||||
def call(c, d): | |||||
global connection, data | |||||
connection, data = c, d | |||||
if not data.args: | if not data.args: | ||||
do_general_help() | do_general_help() | ||||
@@ -15,7 +15,7 @@ def call(a, d): | |||||
do_command_help() | do_command_help() | ||||
def do_general_help(): | def do_general_help(): | ||||
actions.reply(data.chan, data.nick, "I am a bot! You can get help for any command by typing '!help <command>'.") | |||||
connection.reply(data.chan, data.nick, "I am a bot! You can get help for any command by typing '!help <command>'.") | |||||
def do_command_help(): | def do_command_help(): | ||||
command = data.args[0] | command = data.args[0] | ||||
@@ -23,12 +23,12 @@ def do_command_help(): | |||||
try: | try: | ||||
exec "from irc.commands import %s as this_command" % command | exec "from irc.commands import %s as this_command" % command | ||||
except ImportError: | except ImportError: | ||||
actions.reply(data.chan, data.nick, "command \x0303%s\x0301 not found!" % command) | |||||
connection.reply(data.chan, data.nick, "command \x0303%s\x0301 not found!" % command) | |||||
return | return | ||||
info = this_command.__doc__ | info = this_command.__doc__ | ||||
if info: | if info: | ||||
actions.reply(data.chan, data.nick, "info for command \x0303%s\x0301: \"%s\"" % (command, info)) | |||||
connection.reply(data.chan, data.nick, "info for command \x0303%s\x0301: \"%s\"" % (command, info)) | |||||
else: | else: | ||||
actions.reply(data.chan, data.nick, "sorry, no information for \x0303%s\x0301." % command) | |||||
connection.reply(data.chan, data.nick, "sorry, no information for \x0303%s\x0301." % command) |
@@ -4,17 +4,17 @@ | |||||
import random | import random | ||||
actions, data = None, None | |||||
connection, data = None, None | |||||
def call(a, d): | |||||
global actions, data | |||||
actions, data = a, d | |||||
def call(c, d): | |||||
global connection, data | |||||
connection, data = c, d | |||||
choices = ("say_hi()", "say_sup()") | choices = ("say_hi()", "say_sup()") | ||||
exec random.choice(choices) | exec random.choice(choices) | ||||
def say_hi(): | def say_hi(): | ||||
actions.say(data.chan, "Hey \x02%s\x0F!" % data.nick) | |||||
connection.say(data.chan, "Hey \x02%s\x0F!" % data.nick) | |||||
def say_sup(): | def say_sup(): | ||||
actions.say(data.chan, "'sup \x02%s\x0F?" % data.nick) | |||||
connection.say(data.chan, "'sup \x02%s\x0F?" % data.nick) |
@@ -0,0 +1,67 @@ | |||||
# -*- coding: utf-8 -*- | |||||
# A class to interface with IRC. | |||||
import socket | |||||
import threading | |||||
class Connection: | |||||
def __init__(self, host, port, nick, ident, realname): | |||||
"""a class to interface with IRC""" | |||||
self.host = host | |||||
self.port = port | |||||
self.nick = nick | |||||
self.ident = ident | |||||
self.realname = realname | |||||
def connect(self): | |||||
"""connect to IRC""" | |||||
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | |||||
self.sock.connect((self.host, self.port)) | |||||
self.send("NICK %s" % self.nick) | |||||
self.send("USER %s %s * :%s" % (self.ident, self.host, self.realname)) | |||||
def close(self): | |||||
"""close our connection with IRC""" | |||||
try: | |||||
self.sock.shutdown(socket.SHUT_RDWR) # shut down connection first | |||||
except socket.error: | |||||
pass # ignore if the socket is already down | |||||
self.sock.close() | |||||
def get(self, size=4096): | |||||
"""receive (get) data from the server""" | |||||
data = self.sock.recv(4096) | |||||
if not data: # socket giving us no data, so it is dead/broken | |||||
raise RuntimeError("socket is dead") | |||||
return data | |||||
def send(self, msg): | |||||
"""send data to the server""" | |||||
lock = threading.Lock() | |||||
lock.acquire() # ensure that we only send one message at a time (blocking) | |||||
try: | |||||
self.sock.sendall(msg + "\r\n") | |||||
print " %s" % msg | |||||
finally: | |||||
lock.release() | |||||
def say(self, target, msg): | |||||
"""send a message""" | |||||
self.send("PRIVMSG %s :%s" % (target, msg)) | |||||
def reply(self, target, nick, msg): | |||||
"""send a message as a reply""" | |||||
self.say(target, "%s%s%s: %s" % (chr(2), nick, chr(0x0f), 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) |
@@ -22,7 +22,4 @@ class Data: | |||||
except IndexError: | except IndexError: | ||||
self.command = None | self.command = None | ||||
try: | |||||
self.args = args[1:] # the command arguments | |||||
except IndexError: | |||||
self.args = None | |||||
self.args = args[1:] # the command arguments |
@@ -0,0 +1,66 @@ | |||||
# -*- coding: utf-8 -*- | |||||
## Imports | |||||
import re | |||||
from config.irc_config import * | |||||
from config.secure_config import * | |||||
from irc import triggers | |||||
from irc.connection import Connection | |||||
from irc.data import Data | |||||
def get_connection(): | |||||
connection = Connection(HOST, PORT, NICK, IDENT, REALNAME) | |||||
return connection | |||||
def main(connection): | |||||
connection.connect() | |||||
read_buffer = str() | |||||
while 1: | |||||
try: | |||||
read_buffer = read_buffer + connection.get() | |||||
except RuntimeError: # socket broke | |||||
print "socket has broken on front-end; restarting bot..." | |||||
return | |||||
lines = read_buffer.split("\n") | |||||
read_buffer = lines.pop() | |||||
for line in lines: | |||||
line = line.strip().split() | |||||
data = Data() | |||||
if line[1] == "JOIN": | |||||
data.nick, data.ident, data.host = re.findall(":(.*?)!(.*?)@(.*?)\Z", line[0])[0] | |||||
data.chan = line[2][1:] | |||||
triggers.check(connection, data, "join") # check if there's anything we can respond to, and if so, respond | |||||
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 data.chan == NICK: # this is a privmsg to us, so set 'chan' as the nick of the sender | |||||
data.chan = data.nick | |||||
triggers.check(connection, data, "msg_private") # only respond if it's a private message | |||||
else: | |||||
triggers.check(connection, data, "msg_public") # only respond if it's a public (channel) message | |||||
triggers.check(connection, data, "msg") # check for general messages | |||||
if data.msg.startswith("!restart"): # hardcode the !restart command (we can't restart from within an ordinary command) | |||||
if data.host in ADMINS: | |||||
print "restarting bot per admin request..." | |||||
return | |||||
if line[0] == "PING": # If we are pinged, pong back to the server | |||||
connection.send("PONG %s" % line[1]) | |||||
if line[1] == "376": | |||||
if NS_AUTH: # if we're supposed to auth to nickserv, do that | |||||
connection.say("NickServ", "IDENTIFY %s %s" % (NS_USER, NS_PASS)) | |||||
for chan in CHANS: # join all of our startup channels | |||||
connection.join(chan) |
@@ -0,0 +1,33 @@ | |||||
# -*- coding: utf-8 -*- | |||||
# A class to store data on an individual event received from our IRC watcher. | |||||
import re | |||||
class RC: | |||||
def __init__(self, msg): | |||||
"""store data on an individual event received from our IRC watcher""" | |||||
self.msg = msg | |||||
def parse(self): | |||||
"""parse recent changes log into some variables""" | |||||
msg = self.msg | |||||
msg = re.sub("\x03([0-9]{1,2}(,[0-9]{1,2})?)?", "", msg) # strip IRC color codes; we don't want/need 'em | |||||
msg = msg.strip() | |||||
self.msg = msg | |||||
# page name of the modified page | |||||
# 'M' for minor edit, 'B' for bot edit, 'create' for a user creation log entry... | |||||
try: | |||||
page, flags, url, user, comment = re.findall("\A\[\[(.*?)\]\]\s(.*?)\s(http://.*?)\s\*\s(.*?)\s\*\s(.*?)\Z", msg)[0] | |||||
except IndexError: # we're probably missing the http:// part, because it's a log entry, which lacks a url | |||||
page, flags, user, comment = re.findall("\A\[\[(.*?)\]\]\s(.*?)\s\*\s(.*?)\s\*\s(.*?)\Z", msg)[0] | |||||
url = "http://en.wikipedia.org/wiki/%s" % page | |||||
flags = flags.strip() # flag tends to have a extraneous whitespace character at the end when it's a log entry | |||||
self.page, self.flags, self.url, self.user, self.comment = page, flags, url, user, comment | |||||
def pretty(self): | |||||
"""make a nice, colorful message from self.msg to send to the front-end""" | |||||
pretty = self.msg | |||||
return pretty |
@@ -4,7 +4,7 @@ | |||||
from irc.commands import test, help, git | from irc.commands import test, help, git | ||||
def check(actions, data, hook): | |||||
def check(connection, data, hook): | |||||
data.parse_args() # parse command arguments into data.command and data.args | data.parse_args() # parse command arguments into data.command and data.args | ||||
if hook == "join": | if hook == "join": | ||||
@@ -18,10 +18,10 @@ def check(actions, data, hook): | |||||
if hook == "msg": | if hook == "msg": | ||||
if data.command == "!test": | if data.command == "!test": | ||||
test.call(actions, data) | |||||
test.call(connection, data) | |||||
elif data.command == "!help": | elif data.command == "!help": | ||||
help.call(actions, data) | |||||
help.call(connection, data) | |||||
elif data.command == "!git": | elif data.command == "!git": | ||||
git.call(actions, data) | |||||
git.call(connection, data) |
@@ -0,0 +1,71 @@ | |||||
# -*- coding: utf-8 -*- | |||||
## Imports | |||||
import re | |||||
from config.irc_config import * | |||||
from irc.connection import Connection | |||||
from irc.rc import RC | |||||
global frontend_conn | |||||
def get_connection(): | |||||
connection = Connection(WATCHER_HOST, WATCHER_PORT, NICK, IDENT, REALNAME) | |||||
return connection | |||||
def main(connection, f_conn): | |||||
global frontend_conn | |||||
frontend_conn = f_conn | |||||
connection.connect() | |||||
read_buffer = str() | |||||
while 1: | |||||
try: | |||||
read_buffer = read_buffer + connection.get() | |||||
except RuntimeError: # socket broke | |||||
print "socket has broken on watcher, restarting component..." | |||||
return | |||||
lines = read_buffer.split("\n") | |||||
read_buffer = lines.pop() | |||||
for line in lines: | |||||
line = line.strip().split() | |||||
if line[1] == "PRIVMSG": | |||||
chan = line[2] | |||||
if chan != WATCHER_CHAN: # if we're getting a msg from another channel, ignore it | |||||
continue | |||||
msg = ' '.join(line[3:])[1:] | |||||
rc = RC(msg) # create a new RC object to store this change's data | |||||
rc.parse() | |||||
check(rc) | |||||
if line[0] == "PING": # If we are pinged, pong back to the server | |||||
connection.send("PONG %s" % line[1]) | |||||
if line[1] == "376": # Join the recent changes channel when we've finished starting up | |||||
connection.join(WATCHER_CHAN) | |||||
def report(msg, chans): | |||||
"""send a message to a list of report channels on our front-end server""" | |||||
for chan in chans: | |||||
frontend_conn.say(chan, msg) | |||||
def check(rc): | |||||
"""check to see if """ | |||||
page_name = rc.page.lower() | |||||
pretty_msg = rc.pretty() | |||||
if "!earwigbot" in rc.msg.lower(): | |||||
report(pretty_msg, chans=BOT_CHANS) | |||||
if re.match("wikipedia( talk)?:(wikiproject )?articles for creation", page_name): | |||||
report(pretty_msg, chans=AFC_CHANS) | |||||
elif re.match("wikipedia( talk)?:files for upload", page_name): | |||||
report(pretty_msg, chans=AFC_CHANS) | |||||
elif page_name.startswith("template:afc submission"): | |||||
report(pretty_msg, chans=AFC_CHANS) | |||||
if rc.flags == "delete" and re.match("deleted \"\[\[wikipedia( talk)?:(wikiproject )?articles for creation", rc.comment.lower()): | |||||
report(pretty_msg, chans=AFC_CHANS) |
@@ -1,14 +0,0 @@ | |||||
# -*- coding: utf-8 -*- | |||||
from subprocess import * | |||||
try: | |||||
from config.secure_config import * | |||||
except ImportError: | |||||
print "Can't find a secure_config file!" | |||||
print "Make sure you have configured the bot by moving 'config/secure_config.py.default' to 'config/secure_config.py' and by filling out the information inside." | |||||
exit() | |||||
while 1: | |||||
cmd = ['python', 'bot.py'] | |||||
call(cmd) |