@@ -2,7 +2,7 @@ | |||||
class BaseTask(object): | class BaseTask(object): | ||||
"""A base class for bot tasks that edit Wikipedia.""" | """A base class for bot tasks that edit Wikipedia.""" | ||||
task_name = None | |||||
name = None | |||||
def __init__(self): | def __init__(self): | ||||
"""Constructor for new tasks. | """Constructor for new tasks. | ||||
@@ -67,7 +67,7 @@ def load(connection): | |||||
except AttributeError: | except AttributeError: | ||||
pass # The file is doesn't contain a command, so just move on | pass # The file is doesn't contain a command, so just move on | ||||
msg = "Found {0} command classes: {1}." | |||||
msg = "Found {0} commands: {1}." | |||||
print msg.format(len(_commands), ", ".join(_commands.keys())) | print msg.format(len(_commands), ", ".join(_commands.keys())) | ||||
def get_all(): | def get_all(): | ||||
@@ -126,8 +126,8 @@ class Command(BaseCommand): | |||||
self.connection.reply(data, msg) | self.connection.reply(data, msg) | ||||
return | return | ||||
# This task does not exist or hasn't been loaded: | |||||
if task_name not in tasks._tasks.keys(): | |||||
if task_name not in tasks.get_all().keys(): | |||||
# This task does not exist or hasn't been loaded: | |||||
msg = "task could not be found; either bot/tasks/{0}.py doesn't exist, or it wasn't loaded correctly." | 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)) | self.connection.reply(data, msg.format(task_name)) | ||||
return | return | ||||
@@ -7,39 +7,43 @@ This package provides the wiki bot "tasks" EarwigBot runs. Here in __init__, | |||||
you can find some functions used to load and run these tasks. | you can find some functions used to load and run these tasks. | ||||
""" | """ | ||||
import os | |||||
import sys | |||||
import threading | |||||
import time | import time | ||||
import traceback | import traceback | ||||
import threading | |||||
import os | |||||
from classes import BaseTask | |||||
import config | import config | ||||
__all__ = ["load", "schedule", "start", "get_all"] | __all__ = ["load", "schedule", "start", "get_all"] | ||||
# Base directory when searching for tasks: | |||||
base_dir = os.path.join(config.root_dir, "bot", "tasks") | |||||
# 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: | ||||
_tasks = {} | _tasks = {} | ||||
def _load_task(f): | |||||
"""Look in a given file for the task class.""" | |||||
def _load_task(filename): | |||||
"""Try to load a specific task from a module, identified by file name.""" | |||||
global _tasks | global _tasks | ||||
module = f[:-3] # strip .py from end | |||||
# Strip .py from the end of the filename and join with our package name: | |||||
name = ".".join(("tasks", filename[:-3])) | |||||
try: | try: | ||||
exec "from wiki.tasks import %s as m" % module | |||||
except: # importing the file failed for some reason... | |||||
print "Couldn't load task file %s:" % f | |||||
traceback.print_exc() | |||||
return | |||||
try: | |||||
task_class = m.Task | |||||
__import__(name) | |||||
except: | except: | ||||
print "Couldn't find or get task class in file %s:" % f | |||||
print "Couldn't load file {0}:".format(filename) | |||||
traceback.print_exc() | traceback.print_exc() | ||||
return | return | ||||
task_name = task_class.task_name | |||||
_tasks[task_name] = task_class() | |||||
print "Added task %s from bot/tasks/%s..." % (task_name, f) | |||||
task = sys.modules[name].Task() | |||||
if not isinstance(task, BaseTask): | |||||
return | |||||
_tasks[task.name] = task | |||||
print "Added task {0}...".format(task.name) | |||||
def _wrapper(task, **kwargs): | def _wrapper(task, **kwargs): | ||||
"""Wrapper for task classes: run the task and catch any errors.""" | """Wrapper for task classes: run the task and catch any errors.""" | ||||
@@ -47,28 +51,31 @@ def _wrapper(task, **kwargs): | |||||
task.run(**kwargs) | task.run(**kwargs) | ||||
except: | except: | ||||
error = "Task '{0}' raised an exception and had to stop:" | error = "Task '{0}' raised an exception and had to stop:" | ||||
print error.format(task.task_name) | |||||
print error.format(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.name) | |||||
def load(): | def load(): | ||||
"""Load all valid task classes from bot/tasks/, and add them to the _tasks | |||||
variable.""" | |||||
files = os.listdir(os.path.join("bot", "tasks")) | |||||
files.sort() # alphabetically sort all files in wiki/tasks/ | |||||
for f in files: | |||||
if not os.path.isfile(os.path.join("bot", "tasks", f)): | |||||
continue # ignore non-files | |||||
if f.startswith("_") or not f.endswith(".py"): | |||||
continue # ignore non-python files or files beginning with an _ | |||||
load_class_from_file(f) | |||||
print "Found %s tasks: %s." % (len(_tasks), ', '.join(_tasks.keys())) | |||||
"""Load all valid tasks from bot/tasks/, into the _tasks variable.""" | |||||
files = os.listdir(base_dir) | |||||
files.sort() | |||||
for filename in files: | |||||
if filename.startswith("_") or not filename.endswith(".py"): | |||||
continue | |||||
try: | |||||
_load_task(filename) | |||||
except AttributeError: | |||||
pass # The file is doesn't contain a task, so just move on | |||||
print "Found {0} tasks: {1}.".format(len(_tasks), ', '.join(_tasks.keys())) | |||||
def schedule(now=time.gmtime()): | def schedule(now=time.gmtime()): | ||||
"""Start all tasks that are supposed to be run at a given time.""" | """Start all tasks that are supposed to be run at a given time.""" | ||||
# Get list of tasks to run this turn: | |||||
tasks = config.schedule(now.tm_min, now.tm_hour, now.tm_mday, now.tm_mon, | tasks = config.schedule(now.tm_min, now.tm_hour, now.tm_mday, now.tm_mon, | ||||
now.tm_wday) # get list of tasks to run this turn | |||||
now.tm_wday) | |||||
for task in tasks: | for task in tasks: | ||||
if isinstance(task, list): # they've specified kwargs | if isinstance(task, list): # they've specified kwargs | ||||
@@ -1,11 +1,11 @@ | |||||
# -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||
from wiki.base_task import BaseTask | |||||
from classes import BaseTask | |||||
class Task(BaseTask): | class Task(BaseTask): | ||||
"""A task to delink mainspace categories in declined [[WP:AFC]] | """A task to delink mainspace categories in declined [[WP:AFC]] | ||||
submissions.""" | submissions.""" | ||||
task_name = "afc_catdelink" | |||||
name = "afc_catdelink" | |||||
def __init__(self): | def __init__(self): | ||||
pass | pass | ||||
@@ -1,11 +1,11 @@ | |||||
# -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||
from wiki.base_task import BaseTask | |||||
from classes import BaseTask | |||||
class Task(BaseTask): | class Task(BaseTask): | ||||
"""A task to check newly-edited [[WP:AFC]] submissions for copyright | """A task to check newly-edited [[WP:AFC]] submissions for copyright | ||||
violations.""" | violations.""" | ||||
task_name = "afc_copyvios" | |||||
name = "afc_copyvios" | |||||
def __init__(self): | def __init__(self): | ||||
pass | pass | ||||
@@ -1,10 +1,10 @@ | |||||
# -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||
from wiki.base_task import BaseTask | |||||
from classes import BaseTask | |||||
class Task(BaseTask): | class Task(BaseTask): | ||||
""" A task to create daily categories for [[WP:AFC]].""" | """ A task to create daily categories for [[WP:AFC]].""" | ||||
task_name = "afc_dailycats" | |||||
name = "afc_dailycats" | |||||
def __init__(self): | def __init__(self): | ||||
pass | pass | ||||
@@ -1,14 +1,17 @@ | |||||
# -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||
from wiki.base_task import BaseTask | |||||
import time | |||||
from classes import BaseTask | |||||
class Task(BaseTask): | class Task(BaseTask): | ||||
"""A task to generate statistics for [[WP:AFC]] and save them to | """A task to generate statistics for [[WP:AFC]] and save them to | ||||
[[Template:AFC_statistics]].""" | [[Template:AFC_statistics]].""" | ||||
task_name = "afc_statistics" | |||||
name = "afc_statistics" | |||||
def __init__(self): | def __init__(self): | ||||
pass | pass | ||||
def run(self, **kwargs): | def run(self, **kwargs): | ||||
time.sleep(5) | |||||
print kwargs | print kwargs |
@@ -1,10 +1,10 @@ | |||||
# -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||
from wiki.base_task import BaseTask | |||||
from classes import BaseTask | |||||
class Task(BaseTask): | class Task(BaseTask): | ||||
"""A task to clear [[Category:Undated AfC submissions]].""" | """A task to clear [[Category:Undated AfC submissions]].""" | ||||
task_name = "afc_undated" | |||||
name = "afc_undated" | |||||
def __init__(self): | def __init__(self): | ||||
pass | pass | ||||
@@ -1,11 +1,11 @@ | |||||
# -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||
from wiki.base_task import BaseTask | |||||
from classes import BaseTask | |||||
class Task(BaseTask): | class Task(BaseTask): | ||||
"""A task to add |blp=yes to {{WPB}} or {{WPBS}} when it is used along with | """A task to add |blp=yes to {{WPB}} or {{WPBS}} when it is used along with | ||||
{{WP Biography}}.""" | {{WP Biography}}.""" | ||||
task_name = "blptag" | |||||
name = "blptag" | |||||
def __init__(self): | def __init__(self): | ||||
pass | pass | ||||
@@ -1,10 +1,10 @@ | |||||
# -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||
from wiki.base_task import BaseTask | |||||
from classes import BaseTask | |||||
class Task(BaseTask): | class Task(BaseTask): | ||||
"""A task to create daily categories for [[WP:FEED]].""" | """A task to create daily categories for [[WP:FEED]].""" | ||||
task_name = "feed_dailycats" | |||||
name = "feed_dailycats" | |||||
def __init__(self): | def __init__(self): | ||||
pass | pass | ||||
@@ -1,11 +1,11 @@ | |||||
# -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||
from wiki.base_task import BaseTask | |||||
from classes import BaseTask | |||||
class Task(BaseTask): | class Task(BaseTask): | ||||
"""A task to tag files whose extensions do not agree with their MIME | """A task to tag files whose extensions do not agree with their MIME | ||||
type.""" | type.""" | ||||
task_name = "wrongmime" | |||||
name = "wrongmime" | |||||
def __init__(self): | def __init__(self): | ||||
pass | pass | ||||
@@ -48,9 +48,9 @@ def main(connection, f_conn=None): | |||||
read_buffer = lines.pop() | read_buffer = lines.pop() | ||||
for line in lines: | for line in lines: | ||||
_process_message(line) | |||||
_process_message(connection, line) | |||||
def _process_message(line): | |||||
def _process_message(connection, line): | |||||
"""Process a single message from IRC.""" | """Process a single message from IRC.""" | ||||
line = line.strip().split() | line = line.strip().split() | ||||