| @@ -6,17 +6,9 @@ import urllib | |||||
| from classes import BaseCommand | from classes import BaseCommand | ||||
| class AFCReport(BaseCommand): | |||||
| def get_hooks(self): | |||||
| return ["msg"] | |||||
| def get_help(self, command): | |||||
| return "Get information about an AFC submission by name." | |||||
| def check(self, data): | |||||
| if data.is_command and data.command in ["report", "afc_report"]: | |||||
| return True | |||||
| return False | |||||
| class Command(BaseCommand): | |||||
| """Get information about an AFC submission by name.""" | |||||
| name = "report" | |||||
| def process(self, data): | def process(self, data): | ||||
| self.data = data | self.data = data | ||||
| @@ -1,24 +1,22 @@ | |||||
| # -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||
| """Report the status of AFC submissions, either as an automatic message on join | |||||
| or a request via !status.""" | |||||
| import re | import re | ||||
| from classes import BaseCommand | from classes import BaseCommand | ||||
| import config | import config | ||||
| import wiki | import wiki | ||||
| class AFCStatus(BaseCommand): | |||||
| def get_hooks(self): | |||||
| return ["join", "msg"] | |||||
| def get_help(self, command): | |||||
| return "Get the number of pending AfC submissions, open redirect requests, and open file upload requests." | |||||
| class Command(BaseCommand): | |||||
| """Get the number of pending AfC submissions, open redirect requests, and | |||||
| open file upload requests.""" | |||||
| name = "status" | |||||
| hooks = ["join", "msg"] | |||||
| def check(self, data): | def check(self, data): | ||||
| if data.is_command and data.command in ["status", "count", "num", "number", "afc_status"]: | |||||
| commands = ["status", "count", "num", "number"] | |||||
| if data.is_command and data.command in commands: | |||||
| return True | return True | ||||
| try: | try: | ||||
| if data.line[1] == "JOIN" and data.chan == "#wikipedia-en-afc": | if data.line[1] == "JOIN" and data.chan == "#wikipedia-en-afc": | ||||
| if data.nick != config.irc["frontend"]["nick"]: | if data.nick != config.irc["frontend"]["nick"]: | ||||
| @@ -39,41 +37,48 @@ class AFCStatus(BaseCommand): | |||||
| action = data.args[0].lower() | action = data.args[0].lower() | ||||
| if action.startswith("sub") or action == "s": | if action.startswith("sub") or action == "s": | ||||
| subs = self.count_submissions() | subs = self.count_submissions() | ||||
| self.connection.reply(data, "there are currently %s pending AfC submissions." % subs) | |||||
| msg = "there are currently {0} pending AfC submissions." | |||||
| self.connection.reply(data, msg.format(subs)) | |||||
| elif action.startswith("redir") or action == "r": | elif action.startswith("redir") or action == "r": | ||||
| redirs = self.count_redirects() | redirs = self.count_redirects() | ||||
| self.connection.reply(data, "there are currently %s open redirect requests." % redirs) | |||||
| msg = "there are currently {0} open redirect requests." | |||||
| self.connection.reply(data, msg.format(redirs)) | |||||
| elif action.startswith("file") or action == "f": | elif action.startswith("file") or action == "f": | ||||
| files = self.count_redirects() | files = self.count_redirects() | ||||
| self.connection.reply(data, "there are currently %s open file upload requests." % files) | |||||
| msg = "there are currently {0} open file upload requests." | |||||
| self.connection.reply(data, msg.format(files)) | |||||
| elif action.startswith("agg") or action == "a": | elif action.startswith("agg") or action == "a": | ||||
| try: | try: | ||||
| agg_num = int(data.args[1]) | agg_num = int(data.args[1]) | ||||
| except IndexError: | except IndexError: | ||||
| agg_data = (self.count_submissions(), self.count_redirects(), self.count_files()) | |||||
| agg_data = (self.count_submissions(), | |||||
| self.count_redirects(), self.count_files()) | |||||
| agg_num = self.get_aggregate_number(agg_data) | agg_num = self.get_aggregate_number(agg_data) | ||||
| except ValueError: | except ValueError: | ||||
| self.connection.reply(data, "\x0303%s\x0301 isn't a number!" % data.args[1]) | |||||
| msg = "\x0303{0}\x0301 isn't a number!" | |||||
| self.connection.reply(data, msg.format(data.args[1])) | |||||
| return | return | ||||
| aggregate = self.get_aggregate(agg_num) | aggregate = self.get_aggregate(agg_num) | ||||
| self.connection.reply(data, "aggregate is currently %s (AfC %s)." % (agg_num, aggregate)) | |||||
| msg = "aggregate is currently {0} (AfC {1})." | |||||
| self.connection.reply(data, msg.format(agg_num, aggregate)) | |||||
| elif action.startswith("join") or action == "j": | elif action.startswith("join") or action == "j": | ||||
| notice = self.get_join_notice() | notice = self.get_join_notice() | ||||
| self.connection.reply(data, notice) | self.connection.reply(data, notice) | ||||
| else: | else: | ||||
| self.connection.reply(data, "unknown argument: \x0303%s\x0301. Valid args are 'subs', 'redirs', 'files', 'agg', and 'join'." % data.args[0]) | |||||
| msg = "unknown argument: \x0303{0}\x0301. Valid args are 'subs', 'redirs', 'files', 'agg', and 'join'." | |||||
| self.connection.reply(data, msg.format(data.args[0])) | |||||
| else: | else: | ||||
| subs = self.count_submissions() | subs = self.count_submissions() | ||||
| redirs = self.count_redirects() | redirs = self.count_redirects() | ||||
| files = self.count_files() | files = self.count_files() | ||||
| self.connection.reply(data, "there are currently %s pending submissions, %s open redirect requests, and %s open file upload requests." | |||||
| % (subs, redirs, files)) | |||||
| msg = "there are currently {0} pending submissions, {1} open redirect requests, and {2} open file upload requests." | |||||
| self.connection.reply(data, msg.format(subs, redirs, files)) | |||||
| def get_join_notice(self): | def get_join_notice(self): | ||||
| subs = self.count_submissions() | subs = self.count_submissions() | ||||
| @@ -81,20 +86,25 @@ class AFCStatus(BaseCommand): | |||||
| files = self.count_files() | files = self.count_files() | ||||
| agg_num = self.get_aggregate_number((subs, redirs, files)) | agg_num = self.get_aggregate_number((subs, redirs, files)) | ||||
| aggregate = self.get_aggregate(agg_num) | aggregate = self.get_aggregate(agg_num) | ||||
| return ("\x02Current status:\x0F Articles for Creation %s (\x0302AFC\x0301: \x0305%s\x0301; \x0302AFC/R\x0301: \x0305%s\x0301; \x0302FFU\x0301: \x0305%s\x0301)" | |||||
| % (aggregate, subs, redirs, files)) | |||||
| msg = "\x02Current status:\x0F Articles for Creation {0} (\x0302AFC\x0301: \x0305{1}\x0301; \x0302AFC/R\x0301: \x0305{2}\x0301; \x0302FFU\x0301: \x0305{3}\x0301)" | |||||
| return msg.format(aggregate, subs, redirs, files) | |||||
| def count_submissions(self): | def count_submissions(self): | ||||
| """Returns the number of open AFC submissions (count of CAT:PEND).""" | """Returns the number of open AFC submissions (count of CAT:PEND).""" | ||||
| cat = self.site.get_category("Pending AfC submissions") | cat = self.site.get_category("Pending AfC submissions") | ||||
| subs = cat.members(limit=500) | |||||
| subs -= 2 # remove [[Wikipedia:Articles for creation/Redirects]] and [[Wikipedia:Files for upload]], which aren't real submissions | |||||
| subs = len(cat.members(limit=500)) | |||||
| # Remove [[Wikipedia:Articles for creation/Redirects]] and | |||||
| # [[Wikipedia:Files for upload]], which aren't real submissions: | |||||
| subs -= 2 | |||||
| return subs | return subs | ||||
| def count_redirects(self): | def count_redirects(self): | ||||
| """Returns the number of open redirect submissions. Calculated as the | """Returns the number of open redirect submissions. Calculated as the | ||||
| total number of submissions minus the closed ones.""" | total number of submissions minus the closed ones.""" | ||||
| content = self.site.get_page("Wikipedia:Articles for creation/Redirects").get() | |||||
| title = "Wikipedia:Articles for creation/Redirects" | |||||
| content = self.site.get_page(title).get() | |||||
| total = len(re.findall("^\s*==(.*?)==\s*$", content, re.MULTILINE)) | total = len(re.findall("^\s*==(.*?)==\s*$", content, re.MULTILINE)) | ||||
| closed = content.lower().count("{{afc-c|b}}") | closed = content.lower().count("{{afc-c|b}}") | ||||
| redirs = total - closed | redirs = total - closed | ||||
| @@ -1,23 +1,14 @@ | |||||
| # -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||
| # A somewhat advanced calculator: http://futureboy.us/fsp/frink.fsp. | |||||
| import re | import re | ||||
| import urllib | import urllib | ||||
| from classes import BaseCommand | from classes import BaseCommand | ||||
| class Calc(BaseCommand): | |||||
| def get_hooks(self): | |||||
| return ["msg"] | |||||
| def get_help(self, command): | |||||
| return "A somewhat advanced calculator: see http://futureboy.us/fsp/frink.fsp for details." | |||||
| def check(self, data): | |||||
| if data.is_command and data.command == "calc": | |||||
| return True | |||||
| return False | |||||
| class Command(BaseCommand): | |||||
| """A somewhat advanced calculator: see http://futureboy.us/fsp/frink.fsp | |||||
| for details.""" | |||||
| name = "calc" | |||||
| def process(self, data): | def process(self, data): | ||||
| if not data.args: | if not data.args: | ||||
| @@ -27,7 +18,8 @@ class Calc(BaseCommand): | |||||
| query = ' '.join(data.args) | query = ' '.join(data.args) | ||||
| query = self.cleanup(query) | query = self.cleanup(query) | ||||
| url = "http://futureboy.us/fsp/frink.fsp?fromVal=%s" % urllib.quote(query) | |||||
| url = "http://futureboy.us/fsp/frink.fsp?fromVal={0}" | |||||
| url = url.format(urllib.quote(query)) | |||||
| result = urllib.urlopen(url).read() | result = urllib.urlopen(url).read() | ||||
| r_result = re.compile(r'(?i)<A NAME=results>(.*?)</A>') | r_result = re.compile(r'(?i)<A NAME=results>(.*?)</A>') | ||||
| @@ -1,31 +1,30 @@ | |||||
| # -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||
| # Voice/devoice/op/deop users in the channel. | |||||
| from classes import BaseCommand | from classes import BaseCommand | ||||
| import config | import config | ||||
| class ChanOps(BaseCommand): | |||||
| def get_hooks(self): | |||||
| return ["msg"] | |||||
| def get_help(self, command): | |||||
| action = command.capitalize() | |||||
| return "%s users in the channel." % action | |||||
| class Command(BaseCommand): | |||||
| """Voice, devoice, op, or deop users in the channel.""" | |||||
| name = "chanops" | |||||
| def check(self, data): | def check(self, data): | ||||
| if data.is_command and data.command in ["voice", "devoice", "op", "deop"]: | |||||
| commands = ["voice", "devoice", "op", "deop"] | |||||
| if data.is_command and data.command in commands: | |||||
| return True | return True | ||||
| return False | return False | ||||
| def process(self, data): | def process(self, data): | ||||
| if data.host not in config.irc["permissions"]["admins"]: | if data.host not in config.irc["permissions"]["admins"]: | ||||
| self.connection.reply(data, "you must be a bot admin to use this command.") | |||||
| msg = "you must be a bot admin to use this command." | |||||
| self.connection.reply(data, msg) | |||||
| return | return | ||||
| if not data.args: # if it is just !op/!devoice/whatever without arguments, assume they want to do this to themselves | |||||
| # If it is just !op/!devoice/whatever without arguments, assume they | |||||
| # want to do this to themselves: | |||||
| if not data.args: | |||||
| target = data.nick | target = data.nick | ||||
| else: | else: | ||||
| target = data.args[0] | target = data.args[0] | ||||
| self.connection.say("ChanServ", "%s %s %s" % (data.command, data.chan, target)) | |||||
| msg = " ".join((data.command, data.chan, target)) | |||||
| self.connection.say("ChanServ", msg) | |||||
| @@ -1,31 +1,14 @@ | |||||
| # -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||
| """ | |||||
| Cryptography functions (hashing and cyphers) for EarwigBot IRC. | |||||
| """ | |||||
| import hashlib | import hashlib | ||||
| from classes import BaseCommand | from classes import BaseCommand | ||||
| import blowfish | import blowfish | ||||
| class Cryptography(BaseCommand): | |||||
| def get_hooks(self): | |||||
| return ["msg"] | |||||
| def get_help(self, command): | |||||
| if command == "hash": | |||||
| return ("Return the hash of a string using a given algorithm, " + | |||||
| "e.g. '!hash sha512 Hello world!'. Use '!hash list' for " + | |||||
| "a list of supported algorithms.") | |||||
| elif command == "encrypt": | |||||
| return ("Encrypt any string with a given key using an " + | |||||
| "implementation of Blowfish, e.g. '!encrypt some_key " + | |||||
| "Hello!'.") | |||||
| else: | |||||
| return ("Decrypt any string with a given key using an " + | |||||
| "implementation of Blowfish, e.g. '!decrypt some_key " + | |||||
| "762cee8a5239548af18275d6c1184f16'.") | |||||
| class Command(BaseCommand): | |||||
| """Provides hash functions with !hash (!hash list for supported algorithms) | |||||
| and blowfish encryption with !encrypt and !decrypt.""" | |||||
| name = "cryptography" | |||||
| def check(self, data): | def check(self, data): | ||||
| if data.is_command and data.command in ["hash", "encrypt", "decrypt"]: | if data.is_command and data.command in ["hash", "encrypt", "decrypt"]: | ||||
| @@ -34,31 +17,31 @@ class Cryptography(BaseCommand): | |||||
| def process(self, data): | def process(self, data): | ||||
| if not data.args: | if not data.args: | ||||
| self.connection.reply(data, "what do you want me to {0}?".format( | |||||
| data.command)) | |||||
| msg = "what do you want me to {0}?".format(data.command) | |||||
| self.connection.reply(data, msg) | |||||
| return | return | ||||
| if data.command == "hash": | if data.command == "hash": | ||||
| algo = data.args[0] | algo = data.args[0] | ||||
| if algo == "list": | if algo == "list": | ||||
| algos = ', '.join(hashlib.algorithms) | algos = ', '.join(hashlib.algorithms) | ||||
| self.connection.reply(data, "supported algorithms: " + algos + | |||||
| ".") | |||||
| msg = algos.join(("supported algorithms: ", ".")) | |||||
| self.connection.reply(data, msg) | |||||
| elif algo in hashlib.algorithms: | elif algo in hashlib.algorithms: | ||||
| string = ' '.join(data.args[1:]) | string = ' '.join(data.args[1:]) | ||||
| result = eval("hashlib.{0}(string)".format(algo)).hexdigest() | |||||
| result = getattr(hashlib, algo)(string).hexdigest() | |||||
| self.connection.reply(data, result) | self.connection.reply(data, result) | ||||
| else: | else: | ||||
| self.connection.reply(data, "unknown algorithm: '{0}'.".format( | |||||
| algo)) | |||||
| msg = "unknown algorithm: '{0}'.".format(algo) | |||||
| self.connection.reply(data, msg) | |||||
| else: | else: | ||||
| key = data.args[0] | key = data.args[0] | ||||
| text = ' '.join(data.args[1:]) | text = ' '.join(data.args[1:]) | ||||
| if not text: | if not text: | ||||
| self.connection.reply(data, ("a key was provided, but text " + | |||||
| "to {0} was not.").format(data.command)) | |||||
| msg = "a key was provided, but text to {0} was not." | |||||
| self.connection.reply(data, msg.format(data.command)) | |||||
| return | return | ||||
| try: | try: | ||||
| @@ -67,5 +50,5 @@ class Cryptography(BaseCommand): | |||||
| else: | else: | ||||
| self.connection.reply(data, blowfish.decrypt(key, text)) | self.connection.reply(data, blowfish.decrypt(key, text)) | ||||
| except blowfish.BlowfishError as error: | except blowfish.BlowfishError as error: | ||||
| self.connection.reply(data, "{0}: {1}.".format( | |||||
| error.__class__.__name__, error)) | |||||
| msg = "{0}: {1}.".format(error.__class__.__name__, error) | |||||
| self.connection.reply(data, msg) | |||||
| @@ -1,7 +1,5 @@ | |||||
| # -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||
| # Commands to interface with the bot's git repository; use '!git help' for sub-command list. | |||||
| import shlex | import shlex | ||||
| import subprocess | import subprocess | ||||
| import re | import re | ||||
| @@ -9,26 +7,21 @@ import re | |||||
| from classes import BaseCommand | from classes import BaseCommand | ||||
| import config | import config | ||||
| class Git(BaseCommand): | |||||
| def get_hooks(self): | |||||
| return ["msg"] | |||||
| def get_help(self, command): | |||||
| return "Commands to interface with the bot's git repository; use '!git help' for sub-command list." | |||||
| def check(self, data): | |||||
| if data.is_command and data.command == "git": | |||||
| return True | |||||
| return False | |||||
| class Command(BaseCommand): | |||||
| """Commands to interface with the bot's git repository; use '!git help' for | |||||
| a sub-command list.""" | |||||
| name = "git" | |||||
| def process(self, data): | def process(self, data): | ||||
| self.data = data | self.data = data | ||||
| if data.host not in config.irc["permissions"]["owners"]: | if data.host not in config.irc["permissions"]["owners"]: | ||||
| self.connection.reply(data, "you must be a bot owner to use this command.") | |||||
| msg = "you must be a bot owner to use this command." | |||||
| self.connection.reply(data, msg) | |||||
| return | return | ||||
| if not data.args: | if not data.args: | ||||
| self.connection.reply(data, "no arguments provided. Maybe you wanted '!git help'?") | |||||
| msg = "no arguments provided. Maybe you wanted '!git help'?" | |||||
| self.connection.reply(data, msg) | |||||
| return | return | ||||
| if data.args[0] == "help": | if data.args[0] == "help": | ||||
| @@ -52,19 +45,20 @@ class Git(BaseCommand): | |||||
| elif data.args[0] == "status": | elif data.args[0] == "status": | ||||
| self.do_status() | self.do_status() | ||||
| else: # they asked us to do something we don't know | |||||
| self.connection.reply(data, "unknown argument: \x0303%s\x0301." % data.args[0]) | |||||
| else: # They asked us to do something we don't know | |||||
| msg = "unknown argument: \x0303{0}\x0301.".format(data.args[0]) | |||||
| self.connection.reply(data, msg) | |||||
| def exec_shell(self, command): | def exec_shell(self, command): | ||||
| """execute a shell command and get the output""" | |||||
| """Execute a shell command and get the output.""" | |||||
| command = shlex.split(command) | command = shlex.split(command) | ||||
| result = subprocess.check_output(command, stderr=subprocess.STDOUT) | result = subprocess.check_output(command, stderr=subprocess.STDOUT) | ||||
| if result: | if result: | ||||
| result = result[:-1] # strip newline | |||||
| result = result[:-1] # Strip newline | |||||
| return result | return result | ||||
| def do_help(self): | def do_help(self): | ||||
| """display all commands""" | |||||
| """Display all commands.""" | |||||
| help_dict = { | help_dict = { | ||||
| "branch": "get current branch", | "branch": "get current branch", | ||||
| "branches": "get all branches", | "branches": "get all branches", | ||||
| @@ -82,21 +76,24 @@ class Git(BaseCommand): | |||||
| self.connection.reply(self.data, "sub-commands are: %s." % help) | self.connection.reply(self.data, "sub-commands are: %s." % help) | ||||
| def do_branch(self): | def do_branch(self): | ||||
| """get our current branch""" | |||||
| """Get our current branch.""" | |||||
| branch = self.exec_shell("git name-rev --name-only HEAD") | branch = self.exec_shell("git name-rev --name-only HEAD") | ||||
| self.connection.reply(self.data, "currently on branch \x0302%s\x0301." % branch) | |||||
| msg = "currently on branch \x0302{0}\x0301.".format(branch) | |||||
| self.connection.reply(self.data, msg) | |||||
| def do_branches(self): | def do_branches(self): | ||||
| """get list of branches""" | |||||
| """Get a list of branches.""" | |||||
| branches = self.exec_shell("git branch") | branches = self.exec_shell("git branch") | ||||
| branches = branches.replace('\n* ', ', ') # cleanup extraneous characters | |||||
| # Remove extraneous characters: | |||||
| branches = branches.replace('\n* ', ', ') | |||||
| branches = branches.replace('* ', ' ') | branches = branches.replace('* ', ' ') | ||||
| branches = branches.replace('\n ', ', ') | branches = branches.replace('\n ', ', ') | ||||
| branches = branches.strip() | branches = branches.strip() | ||||
| self.connection.reply(self.data, "branches: \x0302%s\x0301." % branches) | |||||
| msg = "branches: \x0302{0}\x0301.".format(branches) | |||||
| self.connection.reply(self.data, msg) | |||||
| def do_checkout(self): | def do_checkout(self): | ||||
| """switch branches""" | |||||
| """Switch branches.""" | |||||
| try: | try: | ||||
| branch = self.data.args[1] | branch = self.data.args[1] | ||||
| except IndexError: # no branch name provided | except IndexError: # no branch name provided | ||||
| @@ -108,15 +105,20 @@ class Git(BaseCommand): | |||||
| try: | try: | ||||
| result = self.exec_shell("git checkout %s" % branch) | result = self.exec_shell("git checkout %s" % branch) | ||||
| if "Already on" in result: | if "Already on" in result: | ||||
| self.connection.reply(self.data, "already on \x0302%s\x0301!" % branch) | |||||
| msg = "already on \x0302{0}\x0301!".format(branch) | |||||
| self.connection.reply(self.data, msg) | |||||
| else: | else: | ||||
| self.connection.reply(self.data, "switched from branch \x0302%s\x0301 to \x0302%s\x0301." % (current_branch, branch)) | |||||
| ms = "switched from branch \x0302{1}\x0301 to \x0302{1}\x0301." | |||||
| msg = ms.format(current_branch, branch) | |||||
| self.connection.reply(self.data, msg) | |||||
| except subprocess.CalledProcessError: # git couldn't switch branches | |||||
| self.connection.reply(self.data, "branch \x0302%s\x0301 doesn't exist!" % branch) | |||||
| except subprocess.CalledProcessError: | |||||
| # Git couldn't switch branches; assume the branch doesn't exist: | |||||
| msg = "branch \x0302{0}\x0301 doesn't exist!".format(branch) | |||||
| self.connection.reply(self.data, msg) | |||||
| def do_delete(self): | def do_delete(self): | ||||
| """delete a branch, while making sure that we are not on it""" | |||||
| """Delete a branch, while making sure that we are not already on it.""" | |||||
| try: | try: | ||||
| delete_branch = self.data.args[1] | delete_branch = self.data.args[1] | ||||
| except IndexError: # no branch name provided | except IndexError: # no branch name provided | ||||
| @@ -126,38 +128,51 @@ class Git(BaseCommand): | |||||
| current_branch = self.exec_shell("git name-rev --name-only HEAD") | current_branch = self.exec_shell("git name-rev --name-only HEAD") | ||||
| if current_branch == delete_branch: | if current_branch == delete_branch: | ||||
| self.connection.reply(self.data, "you're currently on this branch; please checkout to a different branch before deleting.") | |||||
| msg = "you're currently on this branch; please checkout to a different branch before deleting." | |||||
| self.connection.reply(self.data, msg) | |||||
| return | return | ||||
| try: | try: | ||||
| self.exec_shell("git branch -d %s" % delete_branch) | self.exec_shell("git branch -d %s" % delete_branch) | ||||
| self.connection.reply(self.data, "branch \x0302%s\x0301 has been deleted locally." % delete_branch) | |||||
| except subprocess.CalledProcessError: # git couldn't delete | |||||
| self.connection.reply(self.data, "branch \x0302%s\x0301 doesn't exist!" % delete_branch) | |||||
| msg = "branch \x0302{0}\x0301 has been deleted locally." | |||||
| self.connection.reply(self.data, msg.format(delete_branch)) | |||||
| except subprocess.CalledProcessError: | |||||
| # Git couldn't switch branches; assume the branch doesn't exist: | |||||
| msg = "branch \x0302{0}\x0301 doesn't exist!".format(delete_branch) | |||||
| self.connection.reply(self.data, msg) | |||||
| def do_pull(self): | def do_pull(self): | ||||
| """pull from remote repository""" | |||||
| """Pull from our remote repository.""" | |||||
| branch = self.exec_shell("git name-rev --name-only HEAD") | branch = self.exec_shell("git name-rev --name-only HEAD") | ||||
| self.connection.reply(self.data, "pulling from remote (currently on \x0302%s\x0301)..." % branch) | |||||
| msg = "pulling from remote (currently on \x0302{0}\x0301)..." | |||||
| self.connection.reply(self.data, msg.format(branch)) | |||||
| result = self.exec_shell("git pull") | result = self.exec_shell("git pull") | ||||
| if "Already up-to-date." in result: | if "Already up-to-date." in result: | ||||
| self.connection.reply(self.data, "done; no new changes.") | self.connection.reply(self.data, "done; no new changes.") | ||||
| else: | else: | ||||
| changes = re.findall("\s*((.*?)\sfile(.*?)tions?\(-\))", result)[0][0] # find the changes | |||||
| regex = "\s*((.*?)\sfile(.*?)tions?\(-\))" | |||||
| changes = re.findall(regex, result)[0][0] | |||||
| try: | try: | ||||
| remote = self.exec_shell("git config --get branch.%s.remote" % branch) | |||||
| url = self.exec_shell("git config --get remote.%s.url" % remote) | |||||
| self.connection.reply(self.data, "done; %s [from %s]." % (changes, url)) | |||||
| except subprocess.CalledProcessError: # something in .git/config is not specified correctly, so we cannot get the remote's url | |||||
| cmnd_remt = "git config --get branch.{0}.remote".format(branch) | |||||
| remote = self.exec_shell(cmnd_rmt) | |||||
| cmnd_url = "git config --get remote.{0}.url".format(remote) | |||||
| url = self.exec_shell(cmnd_url) | |||||
| msg = "done; {0} [from {1}].".format(changes, url) | |||||
| self.connection.reply(self.data, msg) | |||||
| except subprocess.CalledProcessError: | |||||
| # Something in .git/config is not specified correctly, so we | |||||
| # cannot get the remote's URL. However, pull was a success: | |||||
| self.connection.reply(self.data, "done; %s." % changes) | self.connection.reply(self.data, "done; %s." % changes) | ||||
| def do_status(self): | def do_status(self): | ||||
| """check whether we have anything to pull""" | |||||
| last = self.exec_shell("git log -n 1 --pretty=\"%ar\"") | |||||
| """Check whether we have anything to pull.""" | |||||
| last = self.exec_shell('git log -n 1 --pretty="%ar"') | |||||
| result = self.exec_shell("git fetch --dry-run") | result = self.exec_shell("git fetch --dry-run") | ||||
| if not result: # nothing was fetched, so remote and local are equal | |||||
| self.connection.reply(self.data, "last commit was %s. Local copy is \x02up-to-date\x0F with remote." % last) | |||||
| if not result: # Nothing was fetched, so remote and local are equal | |||||
| msg = "last commit was {0}. Local copy is \x02up-to-date\x0F with remote." | |||||
| self.connection.reply(self.data, msg.format(last)) | |||||
| else: | else: | ||||
| self.connection.reply(self.data, "last local commit was %s. Remote is \x02ahead\x0F of local copy." % last) | |||||
| msg = "last local commit was {0}. Remote is \x02ahead\x0F of local copy." | |||||
| self.connection.reply(self.data, msg.format(last)) | |||||
| @@ -1,5 +1,7 @@ | |||||
| # -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||
| import re | |||||
| from classes import BaseCommand, Data | from classes import BaseCommand, Data | ||||
| import commands | import commands | ||||
| @@ -17,7 +19,9 @@ class Command(BaseCommand): | |||||
| def do_main_help(self, data): | def do_main_help(self, data): | ||||
| """Give the user a general help message with a list of all commands.""" | """Give the user a general help message with a list of all commands.""" | ||||
| msg = "I am a bot! I have {0} commands loaded: {1}. You can get help for any command with '!help <command>'." | msg = "I am a bot! I have {0} commands loaded: {1}. You can get help for any command with '!help <command>'." | ||||
| msg = msg.format(len(self.cmnds.keys()), ', '.join(self.cmnds)) | |||||
| cmnds = self.cmnds.keys() | |||||
| cmnds.sort() | |||||
| msg = msg.format(len(cmnds), ', '.join(cmnds)) | |||||
| self.connection.reply(data, msg) | self.connection.reply(data, msg) | ||||
| def do_command_help(self, data): | def do_command_help(self, data): | ||||
| @@ -26,18 +30,20 @@ class Command(BaseCommand): | |||||
| # Create a dummy message to test which commands pick up the user's | # Create a dummy message to test which commands pick up the user's | ||||
| # input: | # input: | ||||
| dummy = Data(1) | |||||
| dummy = Data("PRIVMSG #fake-channel :Fake messsage!") | |||||
| dummy.command = command.lower() | dummy.command = command.lower() | ||||
| dummy.is_command = True | dummy.is_command = True | ||||
| for cmnd in self.cmnds.values(): | for cmnd in self.cmnds.values(): | ||||
| if cmnd.check(dummy): | |||||
| doc = cmnd.__doc__ | |||||
| if doc: | |||||
| msg = "info for command \x0303{0}\x0301: \"{1}\"" | |||||
| self.connection.reply(data, msg.format(command, doc)) | |||||
| return | |||||
| break | |||||
| if not cmnd.check(dummy): | |||||
| continue | |||||
| if cmnd.__doc__: | |||||
| doc = cmnd.__doc__.replace("\n", "") | |||||
| doc = re.sub("\s\s+", " ", doc) | |||||
| msg = "info for command \x0303{0}\x0301: \"{1}\"" | |||||
| self.connection.reply(data, msg.format(command, doc)) | |||||
| return | |||||
| break | |||||
| msg = "sorry, no help for \x0303{0}\x0301.".format(command) | msg = "sorry, no help for \x0303{0}\x0301.".format(command) | ||||
| self.connection.reply(data, msg) | self.connection.reply(data, msg) | ||||
| @@ -1,17 +1,13 @@ | |||||
| # -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||
| # Convert a Wikipedia page name into a URL. | |||||
| import re | import re | ||||
| from urllib import quote | |||||
| from classes import BaseCommand | from classes import BaseCommand | ||||
| class Link(BaseCommand): | |||||
| def get_hooks(self): | |||||
| return ["msg"] | |||||
| def get_help(self, command): | |||||
| return "Convert a Wikipedia page name into a URL." | |||||
| class Command(BaseCommand): | |||||
| """Convert a Wikipedia page name into a URL.""" | |||||
| name = "link" | |||||
| def check(self, data): | def check(self, data): | ||||
| if ((data.is_command and data.command == "link") or | if ((data.is_command and data.command == "link") or | ||||
| @@ -37,29 +33,31 @@ class Link(BaseCommand): | |||||
| self.connection.reply(data, link) | self.connection.reply(data, link) | ||||
| def parse_line(self, line): | def parse_line(self, line): | ||||
| results = list() | |||||
| results = [] | |||||
| line = re.sub("\{\{\{(.*?)\}\}\}", "", line) # destroy {{{template parameters}}} | |||||
| # Destroy {{{template parameters}}}: | |||||
| line = re.sub("\{\{\{(.*?)\}\}\}", "", line) | |||||
| links = re.findall("(\[\[(.*?)(\||\]\]))", line) # find all [[links]] | |||||
| # Find all [[links]]: | |||||
| links = re.findall("(\[\[(.*?)(\||\]\]))", line) | |||||
| if links: | if links: | ||||
| links = map(lambda x: x[1], links) # re.findall() returns a list of tuples, but we only want the 2nd item in each tuple | |||||
| results.extend(map(self.parse_link, links)) | |||||
| # re.findall() returns a list of tuples, but we only want the 2nd | |||||
| # item in each tuple: | |||||
| links = [i[1] for i in links] | |||||
| results = map(self.parse_link, links) | |||||
| templates = re.findall("(\{\{(.*?)(\||\}\}))", line) # find all {{templates}} | |||||
| # Find all {{templates}} | |||||
| templates = re.findall("(\{\{(.*?)(\||\}\}))", line) | |||||
| if templates: | if templates: | ||||
| templates = map(lambda x: x[1], templates) | |||||
| templates = [i[1] for i in templates] | |||||
| results.extend(map(self.parse_template, templates)) | results.extend(map(self.parse_template, templates)) | ||||
| return results | return results | ||||
| def parse_link(self, pagename): | def parse_link(self, pagename): | ||||
| pagename = pagename.strip() | |||||
| link = "http://enwp.org/" + pagename | |||||
| link = link.replace(" ", "_") | |||||
| return link | |||||
| link = quote(pagename.replace(" ", "_"), safe="/:") | |||||
| return "".join(("http://enwp.org/", link)) | |||||
| def parse_template(self, pagename): | def parse_template(self, pagename): | ||||
| pagename = "Template:%s" % pagename # TODO: implement an actual namespace check | |||||
| link = self.parse_link(pagename) | |||||
| return link | |||||
| pagename = "".join(("Template:", pagename)) | |||||
| return self.parse_link(pagename) | |||||
| @@ -1,20 +1,13 @@ | |||||
| # -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||
| """ | |||||
| Set a message to be repeated to you in a certain amount of time. | |||||
| """ | |||||
| import threading | import threading | ||||
| import time | import time | ||||
| from classes import BaseCommand | from classes import BaseCommand | ||||
| class Remind(BaseCommand): | |||||
| def get_hooks(self): | |||||
| return ["msg"] | |||||
| def get_help(self, command): | |||||
| return "Set a message to be repeated to you in a certain amount of time." | |||||
| class Command(BaseCommand): | |||||
| """Set a message to be repeated to you in a certain amount of time.""" | |||||
| name = "remind" | |||||
| def check(self, data): | def check(self, data): | ||||
| if data.is_command and data.command in ["remind", "reminder"]: | if data.is_command and data.command in ["remind", "reminder"]: | ||||
| @@ -23,24 +16,32 @@ class Remind(BaseCommand): | |||||
| def process(self, data): | def process(self, data): | ||||
| if not data.args: | if not data.args: | ||||
| self.connection.reply(data, "please specify a time (in seconds) and a message in the following format: !remind <time> <msg>.") | |||||
| msg = "please specify a time (in seconds) and a message in the following format: !remind <time> <msg>." | |||||
| self.connection.reply(data, msg) | |||||
| return | return | ||||
| try: | try: | ||||
| wait = int(data.args[0]) | wait = int(data.args[0]) | ||||
| except ValueError: | except ValueError: | ||||
| self.connection.reply(data, "the time must be given as an integer, in seconds.") | |||||
| msg = "the time must be given as an integer, in seconds." | |||||
| self.connection.reply(data, msg) | |||||
| return | return | ||||
| message = ' '.join(data.args[1:]) | message = ' '.join(data.args[1:]) | ||||
| if not message: | if not message: | ||||
| self.connection.reply(data, "what message do you want me to give you when time is up?") | |||||
| msg = "what message do you want me to give you when time is up?" | |||||
| self.connection.reply(data, msg) | |||||
| return | return | ||||
| end_time = time.strftime("%b %d %H:%M:%S", time.localtime(time.time() + wait)) | |||||
| end_time_with_timezone = time.strftime("%b %d %H:%M:%S %Z", time.localtime(time.time() + wait)) | |||||
| self.connection.reply(data, 'Set reminder for "{0}" in {1} seconds (ends {2}).'.format(message, wait, end_time_with_timezone)) | |||||
| end = time.localtime(time.time() + wait) | |||||
| end_time = time.strftime("%b %d %H:%M:%S", end) | |||||
| end_time_with_timezone = time.strftime("%b %d %H:%M:%S %Z", end) | |||||
| msg = 'Set reminder for "{0}" in {1} seconds (ends {2}).' | |||||
| msg = msg.format(message, wait, end_time_with_timezone) | |||||
| self.connection.reply(data, msg) | |||||
| t_reminder = threading.Thread(target=self.reminder, args=(data, message, wait)) | |||||
| t_reminder = threading.Thread(target=self.reminder, | |||||
| args=(data, message, wait)) | |||||
| t_reminder.name = "reminder " + end_time | t_reminder.name = "reminder " + end_time | ||||
| t_reminder.daemon = True | t_reminder.daemon = True | ||||
| t_reminder.start() | t_reminder.start() | ||||
| @@ -1,38 +1,37 @@ | |||||
| # -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||
| """ | |||||
| Retrieve a list of user rights for a given username via the API. | |||||
| """ | |||||
| from classes import BaseCommand | from classes import BaseCommand | ||||
| import wiki | import wiki | ||||
| class Rights(BaseCommand): | |||||
| def get_hooks(self): | |||||
| return ["msg"] | |||||
| def get_help(self, command): | |||||
| return "Retrieve a list of rights for a given username." | |||||
| class Command(BaseCommand): | |||||
| """Retrieve a list of rights for a given username.""" | |||||
| name = "rights" | |||||
| def check(self, data): | def check(self, data): | ||||
| if data.is_command and data.command in ["rights", "groups", "permissions", "privileges"]: | |||||
| commands = ["rights", "groups", "permissions", "privileges"] | |||||
| if data.is_command and data.command in commands: | |||||
| return True | return True | ||||
| return False | return False | ||||
| def process(self, data): | def process(self, data): | ||||
| if not data.args: | if not data.args: | ||||
| self.connection.reply(data, "what user do you want me to look up?") | |||||
| self.connection.reply(data, "who do you want me to look up?") | |||||
| return | return | ||||
| username = ' '.join(data.args) | username = ' '.join(data.args) | ||||
| site = wiki.get_site() | site = wiki.get_site() | ||||
| user = site.get_user(username) | user = site.get_user(username) | ||||
| rights = user.groups() | |||||
| if rights: | |||||
| try: | |||||
| rights.remove("*") # remove the implicit '*' group given to everyone | |||||
| except ValueError: | |||||
| pass | |||||
| self.connection.reply(data, "the rights for \x0302{0}\x0301 are {1}.".format(username, ', '.join(rights))) | |||||
| else: | |||||
| self.connection.reply(data, "the user \x0302{0}\x0301 has no rights, or does not exist.".format(username)) | |||||
| try: | |||||
| rights = user.groups() | |||||
| except wiki.UserNotFoundError: | |||||
| msg = "the user \x0302{0}\x0301 does not exist." | |||||
| self.connection.reply(data, msg.format(username)) | |||||
| return | |||||
| try: | |||||
| rights.remove("*") # Remove the '*' group given to everyone | |||||
| except ValueError: | |||||
| pass | |||||
| msg = "the rights for \x0302{0}\x0301 are {1}." | |||||
| self.connection.reply(data, msg.format(username, ', '.join(rights))) | |||||
| @@ -1,7 +1,5 @@ | |||||
| # -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||
| # Manage wiki tasks from IRC, and check on thread status. | |||||
| import threading | import threading | ||||
| import re | import re | ||||
| @@ -9,29 +7,29 @@ from classes import BaseCommand, Data, KwargParseException | |||||
| import tasks | import tasks | ||||
| import config | import config | ||||
| class Tasks(BaseCommand): | |||||
| def get_hooks(self): | |||||
| return ["msg"] | |||||
| def get_help(self, command): | |||||
| return "Manage wiki tasks from IRC, and check on thread status." | |||||
| class Command(BaseCommand): | |||||
| """Manage wiki tasks from IRC, and check on thread status.""" | |||||
| name = "threads" | |||||
| def check(self, data): | def check(self, data): | ||||
| if data.is_command and data.command in ["tasks", "task", "threads", "tasklist"]: | |||||
| commands = ["tasks", "task", "threads", "tasklist"] | |||||
| if data.is_command and data.command in commands: | |||||
| return True | return True | ||||
| return False | return False | ||||
| def process(self, data): | def process(self, data): | ||||
| self.data = data | self.data = data | ||||
| if data.host not in config.irc["permissions"]["owners"]: | if data.host not in config.irc["permissions"]["owners"]: | ||||
| self.connection.reply(data, "at this time, you must be a bot owner to use this command.") | |||||
| msg = "at this time, you must be a bot owner to use this command." | |||||
| self.connection.reply(data, msg) | |||||
| return | return | ||||
| if not data.args: | if not data.args: | ||||
| if data.command == "tasklist": | if data.command == "tasklist": | ||||
| self.do_list() | self.do_list() | ||||
| else: | else: | ||||
| self.connection.reply(data, "no arguments provided. Maybe you wanted '!{cmnd} list', '!{cmnd} start', or '!{cmnd} listall'?".format(cmnd=data.command)) | |||||
| msg = "no arguments provided. Maybe you wanted '!{0} list', '!{0} start', or '!{0} listall'?" | |||||
| self.connection.reply(data, msg.format(data.command)) | |||||
| return | return | ||||
| if data.args[0] == "list": | if data.args[0] == "list": | ||||
| @@ -43,8 +41,9 @@ class Tasks(BaseCommand): | |||||
| elif data.args[0] in ["listall", "all"]: | elif data.args[0] in ["listall", "all"]: | ||||
| self.do_listall() | self.do_listall() | ||||
| else: # they asked us to do something we don't know | |||||
| self.connection.reply(data, "unknown argument: \x0303{0}\x0301.".format(data.args[0])) | |||||
| else: # They asked us to do something we don't know | |||||
| msg = "unknown argument: \x0303{0}\x0301.".format(data.args[0]) | |||||
| self.connection.reply(data, msg) | |||||
| def do_list(self): | def do_list(self): | ||||
| """With !tasks list (or abbreviation !tasklist), list all running | """With !tasks list (or abbreviation !tasklist), list all running | ||||
| @@ -59,43 +58,54 @@ class Tasks(BaseCommand): | |||||
| tname = thread.name | tname = thread.name | ||||
| if tname == "MainThread": | if tname == "MainThread": | ||||
| tname = self.get_main_thread_name() | tname = self.get_main_thread_name() | ||||
| normal_threads.append("\x0302{0}\x0301 (as main thread, id {1})".format(tname, thread.ident)) | |||||
| t = "\x0302{0}\x0301 (as main thread, id {1})" | |||||
| normal_threads.append(t.format(tname, thread.ident)) | |||||
| elif tname in ["irc-frontend", "irc-watcher", "wiki-scheduler"]: | elif tname in ["irc-frontend", "irc-watcher", "wiki-scheduler"]: | ||||
| normal_threads.append("\x0302{0}\x0301 (id {1})".format(tname, thread.ident)) | |||||
| t = "\x0302{0}\x0301 (id {1})" | |||||
| normal_threads.append(t.format(tname, thread.ident)) | |||||
| elif tname.startswith("reminder"): | elif tname.startswith("reminder"): | ||||
| normal_threads.append("\x0302reminder\x0301 (until {0})".format(tname.replace("reminder ", ""))) | |||||
| tname = tname.replace("reminder ", "") | |||||
| t = "\x0302reminder\x0301 (until {0})" | |||||
| normal_threads.append(t.format(tname)) | |||||
| else: | else: | ||||
| tname, start_time = re.findall("^(.*?) \((.*?)\)$", tname)[0] | tname, start_time = re.findall("^(.*?) \((.*?)\)$", tname)[0] | ||||
| task_threads.append("\x0302{0}\x0301 (id {1}, since {2})".format(tname, thread.ident, start_time)) | |||||
| t = "\x0302{0}\x0301 (id {1}, since {2})" | |||||
| task_threads.append(t.format(tname, thread.ident, start_time)) | |||||
| if task_threads: | if task_threads: | ||||
| msg = "\x02{0}\x0F threads active: {1}, and \x02{2}\x0F task threads: {3}.".format(len(threads), ', '.join(normal_threads), len(task_threads), ', '.join(task_threads)) | |||||
| msg = "\x02{0}\x0F threads active: {1}, and \x02{2}\x0F task threads: {3}." | |||||
| msg = msg.format(len(threads), ', '.join(normal_threads), | |||||
| len(task_threads), ', '.join(task_threads)) | |||||
| else: | else: | ||||
| msg = "\x02{0}\x0F threads active: {1}, and \x020\x0F task threads.".format(len(threads), ', '.join(normal_threads)) | |||||
| msg = "\x02{0}\x0F threads active: {1}, and \x020\x0F task threads." | |||||
| msg = msg.format(len(threads), ', '.join(normal_threads)) | |||||
| self.connection.reply(self.data, msg) | self.connection.reply(self.data, msg) | ||||
| def do_listall(self): | def do_listall(self): | ||||
| """With !tasks listall or !tasks all, list all loaded tasks, and report | """With !tasks listall or !tasks all, list all loaded tasks, and report | ||||
| whether they are currently running or idle.""" | whether they are currently running or idle.""" | ||||
| tasks = tasks._tasks.keys() | |||||
| all_tasks = tasks.get_all().keys() | |||||
| threads = threading.enumerate() | threads = threading.enumerate() | ||||
| tasklist = [] | tasklist = [] | ||||
| tasks.sort() | |||||
| all_tasks.sort() | |||||
| for task in tasks: | |||||
| threads_running_task = [t for t in threads if t.name.startswith(task)] | |||||
| ids = map(lambda t: str(t.ident), threads_running_task) | |||||
| for task in all_tasks: | |||||
| threadlist = [t for t in threads if t.name.startswith(task)] | |||||
| ids = [str(t.ident) for t in threadlist] | |||||
| if not ids: | if not ids: | ||||
| tasklist.append("\x0302{0}\x0301 (idle)".format(task)) | tasklist.append("\x0302{0}\x0301 (idle)".format(task)) | ||||
| elif len(ids) == 1: | elif len(ids) == 1: | ||||
| tasklist.append("\x0302{0}\x0301 (\x02active\x0F as id {1})".format(task, ids[0])) | |||||
| t = "\x0302{0}\x0301 (\x02active\x0F as id {1})" | |||||
| tasklist.append(t.format(task, ids[0])) | |||||
| else: | else: | ||||
| tasklist.append("\x0302{0}\x0301 (\x02active\x0F as ids {1})".format(task, ', '.join(ids))) | |||||
| t = "\x0302{0}\x0301 (\x02active\x0F as ids {1})" | |||||
| tasklist.append(t.format(task, ', '.join(ids))) | |||||
| tasklist = ", ".join(tasklist) | tasklist = ", ".join(tasklist) | ||||
| msg = "{0} tasks loaded: {1}.".format(len(tasks), tasklist) | |||||
| msg = "{0} tasks loaded: {1}.".format(len(all_tasks), tasklist) | |||||
| self.connection.reply(self.data, msg) | self.connection.reply(self.data, msg) | ||||
| def do_start(self): | def do_start(self): | ||||
| @@ -105,26 +115,29 @@ class Tasks(BaseCommand): | |||||
| try: | try: | ||||
| task_name = data.args[1] | task_name = data.args[1] | ||||
| except IndexError: # no task name given | |||||
| except IndexError: # No task name given | |||||
| self.connection.reply(data, "what task do you want me to start?") | self.connection.reply(data, "what task do you want me to start?") | ||||
| return | return | ||||
| try: | try: | ||||
| data.parse_kwargs() | data.parse_kwargs() | ||||
| except KwargParseException, arg: | except KwargParseException, arg: | ||||
| self.connection.reply(data, "error parsing argument: \x0303{0}\x0301.".format(arg)) | |||||
| msg = "error parsing argument: \x0303{0}\x0301.".format(arg) | |||||
| self.connection.reply(data, msg) | |||||
| return | return | ||||
| if task_name not in tasks._tasks.keys(): # this task does not exist or hasn't been loaded | |||||
| self.connection.reply(data, "task could not be found; either wiki/tasks/{0}.py doesn't exist, or it wasn't loaded correctly.".format(task_name)) | |||||
| # This task does not exist or hasn't been loaded: | |||||
| if task_name not in tasks._tasks.keys(): | |||||
| msg = "task could not be found; either bot/tasks/{0}.py doesn't exist, or it wasn't loaded correctly." | |||||
| self.connection.reply(data, msg.format(task_name)) | |||||
| return | return | ||||
| tasks.start(task_name, **data.kwargs) | tasks.start(task_name, **data.kwargs) | ||||
| self.connection.reply(data, "task \x0302{0}\x0301 started.".format(task_name)) | |||||
| msg = "task \x0302{0}\x0301 started.".format(task_name) | |||||
| self.connection.reply(data, msg) | |||||
| def get_main_thread_name(self): | def get_main_thread_name(self): | ||||
| """Return the "proper" name of the MainThread; e.g. "irc-frontend" or | |||||
| "irc-watcher".""" | |||||
| """Return the "proper" name of the MainThread.""" | |||||
| if "irc_frontend" in config.components: | if "irc_frontend" in config.components: | ||||
| return "irc-frontend" | return "irc-frontend" | ||||
| elif "wiki_schedule" in config.components: | elif "wiki_schedule" in config.components: | ||||
| @@ -14,7 +14,7 @@ import os | |||||
| import config | import config | ||||
| __all__ = ["load", "schedule", "start"] | |||||
| __all__ = ["load", "schedule", "start", "get_all"] | |||||
| # Store loaded tasks as a dict where the key is the task name and the value is | # Store loaded tasks as a dict where the key is the task name and the value is | ||||
| # an instance of the task class: | # an instance of the task class: | ||||
| @@ -46,14 +46,15 @@ def _wrapper(task, **kwargs): | |||||
| try: | try: | ||||
| task.run(**kwargs) | task.run(**kwargs) | ||||
| except: | except: | ||||
| print "Task '{0}' raised an exception and had to stop:".format(task.task_name) | |||||
| error = "Task '{0}' raised an exception and had to stop:" | |||||
| print error.format(task.task_name) | |||||
| traceback.print_exc() | traceback.print_exc() | ||||
| else: | else: | ||||
| print "Task '{0}' finished without error.".format(task.task_name) | print "Task '{0}' finished without error.".format(task.task_name) | ||||
| def load(): | def load(): | ||||
| """Load all valid task classes from bot/tasks/, and add them to the | |||||
| _tasks variable.""" | |||||
| """Load all valid task classes from bot/tasks/, and add them to the _tasks | |||||
| variable.""" | |||||
| files = os.listdir(os.path.join("bot", "tasks")) | files = os.listdir(os.path.join("bot", "tasks")) | ||||
| files.sort() # alphabetically sort all files in wiki/tasks/ | files.sort() # alphabetically sort all files in wiki/tasks/ | ||||
| for f in files: | for f in files: | ||||
| @@ -83,13 +84,19 @@ def start(task_name, **kwargs): | |||||
| try: | try: | ||||
| task = _tasks[task_name] | task = _tasks[task_name] | ||||
| except KeyError: | except KeyError: | ||||
| print ("Couldn't find task '{0}': bot/tasks/{0}.py does not exist.").format(task_name) | |||||
| error = "Couldn't find task '{0}': bot/tasks/{0}.py does not exist." | |||||
| print error.format(task_name) | |||||
| return | return | ||||
| task_thread = threading.Thread(target=lambda: _wrapper(task, **kwargs)) | task_thread = threading.Thread(target=lambda: _wrapper(task, **kwargs)) | ||||
| task_thread.name = "{0} ({1})".format(task_name, time.strftime("%b %d %H:%M:%S")) | |||||
| start_time = time.strftime("%b %d %H:%M:%S") | |||||
| task_thread.name = "{0} ({1})".format(task_name, start_time) | |||||
| # stop bot task threads automagically if the main bot stops | |||||
| # Stop bot task threads automagically if the main bot stops: | |||||
| task_thread.daemon = True | task_thread.daemon = True | ||||
| task_thread.start() | task_thread.start() | ||||
| def get_all(): | |||||
| """Return our dict of all loaded tasks.""" | |||||
| return _tasks | |||||
| @@ -7,14 +7,14 @@ This is a collection of classes and functions to read from and write to | |||||
| Wikipedia and other wiki sites. No connection whatsoever to python-wikitools | Wikipedia and other wiki sites. No connection whatsoever to python-wikitools | ||||
| written by Mr.Z-man, other than a similar purpose. We share no code. | written by Mr.Z-man, other than a similar purpose. We share no code. | ||||
| Import the toolset with `from wiki import tools`. | |||||
| Import the toolset with `import wiki`. | |||||
| """ | """ | ||||
| from wiki.tools.constants import * | |||||
| from wiki.tools.exceptions import * | |||||
| from wiki.tools.functions import * | |||||
| from wiki.constants import * | |||||
| from wiki.exceptions import * | |||||
| from wiki.functions import * | |||||
| from wiki.tools.category import Category | |||||
| from wiki.tools.page import Page | |||||
| from wiki.tools.site import Site | |||||
| from wiki.tools.user import User | |||||
| from wiki.category import Category | |||||
| from wiki.page import Page | |||||
| from wiki.site import Site | |||||
| from wiki.user import User | |||||
| @@ -1,6 +1,6 @@ | |||||
| # -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||
| from wiki.tools.page import Page | |||||
| from wiki.page import Page | |||||
| class Category(Page): | class Category(Page): | ||||
| """ | """ | ||||
| @@ -24,7 +24,7 @@ class Category(Page): | |||||
| up to 500, and bots can go up to 5,000 on a single API query. | up to 500, and bots can go up to 5,000 on a single API query. | ||||
| """ | """ | ||||
| params = {"action": "query", "list": "categorymembers", | params = {"action": "query", "list": "categorymembers", | ||||
| "cmlimit": limit, "cmtitle": self.title} | |||||
| "cmlimit": limit, "cmtitle": self._title} | |||||
| result = self._site._api_query(params) | result = self._site._api_query(params) | ||||
| members = result['query']['categorymembers'] | members = result['query']['categorymembers'] | ||||
| return [member["title"] for member in members] | return [member["title"] for member in members] | ||||
| @@ -6,7 +6,7 @@ EarwigBot's Wiki Toolset: Constants | |||||
| This module defines some useful constants, such as default namespace IDs for | This module defines some useful constants, such as default namespace IDs for | ||||
| easy lookup and our user agent. | easy lookup and our user agent. | ||||
| Import with `from wiki.tools.constants import *`. | |||||
| Import with `from wiki.constants import *`. | |||||
| """ | """ | ||||
| import platform | import platform | ||||
| @@ -3,7 +3,7 @@ | |||||
| """ | """ | ||||
| EarwigBot's Wiki Toolset: Exceptions | EarwigBot's Wiki Toolset: Exceptions | ||||
| This module contains all exceptions used by the wiki.tools package. | |||||
| This module contains all exceptions used by the wiki package. | |||||
| """ | """ | ||||
| class WikiToolsetError(Exception): | class WikiToolsetError(Exception): | ||||
| @@ -3,11 +3,11 @@ | |||||
| """ | """ | ||||
| EarwigBot's Wiki Toolset: Misc Functions | EarwigBot's Wiki Toolset: Misc Functions | ||||
| This module, a component of the wiki.tools package, contains miscellaneous | |||||
| functions that are not methods of any class, like get_site(). | |||||
| This module, a component of the wiki package, contains miscellaneous functions | |||||
| that are not methods of any class, like get_site(). | |||||
| There's no need to import this module explicitly. All functions here are | There's no need to import this module explicitly. All functions here are | ||||
| automatically available from wiki.tools. | |||||
| automatically available from wiki. | |||||
| """ | """ | ||||
| from cookielib import LWPCookieJar, LoadError | from cookielib import LWPCookieJar, LoadError | ||||
| @@ -16,9 +16,9 @@ from getpass import getpass | |||||
| from os import chmod, path | from os import chmod, path | ||||
| import stat | import stat | ||||
| from core import config | |||||
| from wiki.tools.exceptions import SiteNotFoundError | |||||
| from wiki.tools.site import Site | |||||
| import config | |||||
| from wiki.exceptions import SiteNotFoundError | |||||
| from wiki.site import Site | |||||
| __all__ = ["get_site"] | __all__ = ["get_site"] | ||||
| @@ -84,10 +84,14 @@ def _get_site_object_from_dict(name, d): | |||||
| article_path = d.get("articlePath") | article_path = d.get("articlePath") | ||||
| script_path = d.get("scriptPath") | script_path = d.get("scriptPath") | ||||
| sql = (d.get("sqlServer"), d.get("sqlDB")) | sql = (d.get("sqlServer"), d.get("sqlDB")) | ||||
| namespaces = d.get("namespaces") | |||||
| namespaces = d.get("namespaces", {}) | |||||
| login = (config.wiki.get("username"), config.wiki.get("password")) | login = (config.wiki.get("username"), config.wiki.get("password")) | ||||
| cookiejar = _get_cookiejar() | cookiejar = _get_cookiejar() | ||||
| for key, value in namespaces.items(): # Convert string keys to integers | |||||
| del namespaces[key] | |||||
| namespaces[int(key)] = value | |||||
| return Site(name=name, project=project, lang=lang, base_url=base_url, | return Site(name=name, project=project, lang=lang, base_url=base_url, | ||||
| article_path=article_path, script_path=script_path, sql=sql, | article_path=article_path, script_path=script_path, sql=sql, | ||||
| namespaces=namespaces, login=login, cookiejar=cookiejar) | namespaces=namespaces, login=login, cookiejar=cookiejar) | ||||
| @@ -3,7 +3,7 @@ | |||||
| import re | import re | ||||
| from urllib import quote | from urllib import quote | ||||
| from wiki.tools.exceptions import * | |||||
| from wiki.exceptions import * | |||||
| class Page(object): | class Page(object): | ||||
| """ | """ | ||||
| @@ -9,11 +9,11 @@ from urllib import unquote_plus, urlencode | |||||
| from urllib2 import build_opener, HTTPCookieProcessor, URLError | from urllib2 import build_opener, HTTPCookieProcessor, URLError | ||||
| from urlparse import urlparse | from urlparse import urlparse | ||||
| from wiki.tools.category import Category | |||||
| from wiki.tools.constants import * | |||||
| from wiki.tools.exceptions import * | |||||
| from wiki.tools.page import Page | |||||
| from wiki.tools.user import User | |||||
| from wiki.category import Category | |||||
| from wiki.constants import * | |||||
| from wiki.exceptions import * | |||||
| from wiki.page import Page | |||||
| from wiki.user import User | |||||
| class Site(object): | class Site(object): | ||||
| """ | """ | ||||
| @@ -96,8 +96,9 @@ class Site(object): | |||||
| We'll encode the given params, adding format=json along the way, and | We'll encode the given params, adding format=json along the way, and | ||||
| make the request through self._opener, which has built-in cookie | make the request through self._opener, which has built-in cookie | ||||
| support via self._cookiejar, a User-Agent | |||||
| (wiki.tools.constants.USER_AGENT), and Accept-Encoding set to "gzip". | |||||
| support via self._cookiejar, a User-Agent (wiki.constants.USER_AGENT), | |||||
| and Accept-Encoding set to "gzip". | |||||
| Assuming everything went well, we'll gunzip the data (if compressed), | Assuming everything went well, we'll gunzip the data (if compressed), | ||||
| load it as a JSON object, and return it. | load it as a JSON object, and return it. | ||||
| @@ -153,7 +154,7 @@ class Site(object): | |||||
| params = {"action": "query", "meta": "siteinfo"} | params = {"action": "query", "meta": "siteinfo"} | ||||
| if self._namespaces is None or force: | |||||
| if not self._namespaces or force: | |||||
| params["siprop"] = "general|namespaces|namespacealiases" | params["siprop"] = "general|namespaces|namespacealiases" | ||||
| result = self._api_query(params) | result = self._api_query(params) | ||||
| self._load_namespaces(result) | self._load_namespaces(result) | ||||
| @@ -2,9 +2,9 @@ | |||||
| from time import strptime | from time import strptime | ||||
| from wiki.tools.constants import * | |||||
| from wiki.tools.exceptions import UserNotFoundError | |||||
| from wiki.tools.page import Page | |||||
| from wiki.constants import * | |||||
| from wiki.exceptions import UserNotFoundError | |||||
| from wiki.page import Page | |||||
| class User(object): | class User(object): | ||||
| """ | """ | ||||
| @@ -94,7 +94,10 @@ class User(object): | |||||
| self._blockinfo = False | self._blockinfo = False | ||||
| self._groups = res["groups"] | self._groups = res["groups"] | ||||
| self._rights = res["rights"].values() | |||||
| try: | |||||
| self._rights = res["rights"].values() | |||||
| except AttributeError: | |||||
| self._rights = res["rights"] | |||||
| self._editcount = res["editcount"] | self._editcount = res["editcount"] | ||||
| reg = res["registration"] | reg = res["registration"] | ||||