| @@ -32,6 +32,5 @@ __version__ = "0.1.dev" | |||
| __email__ = "ben.kurtovic@verizon.net" | |||
| from earwigbot import ( | |||
| blowfish, config, classes, commands, config, irc, main, rules, runner, | |||
| tasks, tests, wiki | |||
| blowfish, commands, config, irc, main, rules, runner, tasks, tests, wiki | |||
| ) | |||
| @@ -1,23 +0,0 @@ | |||
| # -*- coding: utf-8 -*- | |||
| # | |||
| # Copyright (C) 2009-2012 by Ben Kurtovic <ben.kurtovic@verizon.net> | |||
| # | |||
| # Permission is hereby granted, free of charge, to any person obtaining a copy | |||
| # of this software and associated documentation files (the "Software"), to deal | |||
| # in the Software without restriction, including without limitation the rights | |||
| # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
| # copies of the Software, and to permit persons to whom the Software is | |||
| # furnished to do so, subject to the following conditions: | |||
| # | |||
| # The above copyright notice and this permission notice shall be included in | |||
| # all copies or substantial portions of the Software. | |||
| # | |||
| # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
| # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
| # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
| # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
| # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
| # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||
| # SOFTWARE. | |||
| from earwigbot.classes.base_task import * | |||
| @@ -1,117 +0,0 @@ | |||
| # -*- coding: utf-8 -*- | |||
| # | |||
| # Copyright (C) 2009-2012 by Ben Kurtovic <ben.kurtovic@verizon.net> | |||
| # | |||
| # Permission is hereby granted, free of charge, to any person obtaining a copy | |||
| # of this software and associated documentation files (the "Software"), to deal | |||
| # in the Software without restriction, including without limitation the rights | |||
| # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
| # copies of the Software, and to permit persons to whom the Software is | |||
| # furnished to do so, subject to the following conditions: | |||
| # | |||
| # The above copyright notice and this permission notice shall be included in | |||
| # all copies or substantial portions of the Software. | |||
| # | |||
| # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
| # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
| # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
| # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
| # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
| # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||
| # SOFTWARE. | |||
| import logging | |||
| from earwigbot.config import config | |||
| from earwigbot import wiki | |||
| __all__ = ["BaseTask"] | |||
| class BaseTask(object): | |||
| """A base class for bot tasks that edit Wikipedia.""" | |||
| name = None | |||
| number = 0 | |||
| def __init__(self): | |||
| """Constructor for new tasks. | |||
| This is called once immediately after the task class is loaded by | |||
| the task manager (in tasks._load_task()). | |||
| """ | |||
| pass | |||
| def _setup_logger(self): | |||
| """Set up a basic module-level logger.""" | |||
| logger_name = ".".join(("earwigbot", "tasks", self.name)) | |||
| self.logger = logging.getLogger(logger_name) | |||
| self.logger.setLevel(logging.DEBUG) | |||
| def run(self, **kwargs): | |||
| """Main entry point to run a given task. | |||
| This is called directly by tasks.start() and is the main way to make a | |||
| task do stuff. kwargs will be any keyword arguments passed to start() | |||
| which are entirely optional. | |||
| The same task instance is preserved between runs, so you can | |||
| theoretically store data in self (e.g. | |||
| start('mytask', action='store', data='foo')) and then use it later | |||
| (e.g. start('mytask', action='save')). | |||
| """ | |||
| pass | |||
| def make_summary(self, comment): | |||
| """Makes an edit summary by filling in variables in a config value. | |||
| config.wiki["summary"] is used, where $2 is replaced by the main | |||
| summary body, given as a method arg, and $1 is replaced by the task | |||
| number. | |||
| If the config value is not found, we just return the arg as-is. | |||
| """ | |||
| try: | |||
| summary = config.wiki["summary"] | |||
| except KeyError: | |||
| return comment | |||
| return summary.replace("$1", str(self.number)).replace("$2", comment) | |||
| def shutoff_enabled(self, site=None): | |||
| """Returns whether on-wiki shutoff is enabled for this task. | |||
| We check a certain page for certain content. This is determined by | |||
| our config file: config.wiki["shutoff"]["page"] is used as the title, | |||
| with $1 replaced by our username and $2 replaced by the task number, | |||
| and config.wiki["shutoff"]["disabled"] is used as the content. | |||
| If the page has that content or the page does not exist, then shutoff | |||
| is "disabled", meaning the bot is supposed to run normally, and we | |||
| return False. If the page's content is something other than what we | |||
| expect, shutoff is enabled, and we return True. | |||
| If a site is not provided, we'll try to use self.site if it's set. | |||
| Otherwise, we'll use our default site. | |||
| """ | |||
| if not site: | |||
| try: | |||
| site = self.site | |||
| except AttributeError: | |||
| site = wiki.get_site() | |||
| try: | |||
| cfg = config.wiki["shutoff"] | |||
| except KeyError: | |||
| return False | |||
| title = cfg.get("page", "User:$1/Shutoff/Task $2") | |||
| username = site.get_user().name() | |||
| title = title.replace("$1", username).replace("$2", str(self.number)) | |||
| page = site.get_page(title) | |||
| try: | |||
| content = page.get() | |||
| except wiki.PageNotFoundError: | |||
| return False | |||
| if content == cfg.get("disabled", "run"): | |||
| return False | |||
| self.logger.warn("Emergency task shutoff has been enabled!") | |||
| return True | |||
| @@ -26,7 +26,7 @@ EarwigBot's IRC Command Manager | |||
| This package provides the IRC "commands" used by the bot's front-end component. | |||
| This module contains the BaseCommand class (import with | |||
| `from earwigbot.commands import BaseCommand`) and an internal _CommandManager | |||
| class. This can be accessed through the singleton `command_manager`. | |||
| class. This can be accessed through the `command_manager` singleton. | |||
| """ | |||
| import logging | |||
| @@ -22,9 +22,9 @@ | |||
| import re | |||
| from earwigbot import tasks | |||
| from earwigbot import wiki | |||
| from earwigbot.commands import BaseCommand | |||
| from earwigbot.tasks import task_manager | |||
| class Command(BaseCommand): | |||
| """Get information about an AFC submission by name.""" | |||
| @@ -36,7 +36,7 @@ class Command(BaseCommand): | |||
| self.data = data | |||
| try: | |||
| self.statistics = tasks.get("afc_statistics") | |||
| self.statistics = task_manager.get("afc_statistics") | |||
| except KeyError: | |||
| e = "Cannot run command: requires afc_statistics task." | |||
| self.logger.error(e) | |||
| @@ -23,10 +23,10 @@ | |||
| import threading | |||
| import re | |||
| from earwigbot import tasks | |||
| from earwigbot.commands import BaseCommand | |||
| from earwigbot.config import config | |||
| from earwigbot.irc import KwargParseException | |||
| from earwigbot.tasks import task_manager | |||
| class Command(BaseCommand): | |||
| """Manage wiki tasks from IRC, and check on thread status.""" | |||
| @@ -106,7 +106,7 @@ class Command(BaseCommand): | |||
| def do_listall(self): | |||
| """With !tasks listall or !tasks all, list all loaded tasks, and report | |||
| whether they are currently running or idle.""" | |||
| all_tasks = tasks.get_all().keys() | |||
| all_tasks = task_manager.get_all().keys() | |||
| threads = threading.enumerate() | |||
| tasklist = [] | |||
| @@ -147,14 +147,14 @@ class Command(BaseCommand): | |||
| self.connection.reply(data, msg) | |||
| return | |||
| if task_name not in tasks.get_all().keys(): | |||
| if task_name not in task_manager.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 tasks/{0}.py doesn't exist, or it wasn't loaded correctly." | |||
| self.connection.reply(data, msg.format(task_name)) | |||
| return | |||
| data.kwargs["fromIRC"] = True | |||
| tasks.start(task_name, **data.kwargs) | |||
| task_manager.start(task_name, **data.kwargs) | |||
| msg = "task \x0302{0}\x0301 started.".format(task_name) | |||
| self.connection.reply(data, msg) | |||
| @@ -49,9 +49,9 @@ import logging | |||
| import threading | |||
| import time | |||
| from earwigbot import tasks | |||
| from earwigbot.irc import Frontend, Watcher | |||
| from earwigbot.config import config | |||
| from earwigbot.irc import Frontend, Watcher | |||
| from earwigbot.tasks import task_manager | |||
| logger = logging.getLogger("earwigbot") | |||
| @@ -72,10 +72,7 @@ def wiki_scheduler(): | |||
| primary thread if the IRC frontend is not enabled.""" | |||
| while 1: | |||
| time_start = time.time() | |||
| now = time.gmtime(time_start) | |||
| tasks.schedule(now) | |||
| task_manager.schedule() | |||
| time_end = time.time() | |||
| time_diff = time_start - time_end | |||
| if time_diff < 60: # Sleep until the next minute | |||
| @@ -90,7 +87,7 @@ def irc_frontend(): | |||
| if config.components.get("wiki_schedule"): | |||
| logger.info("Starting wiki scheduler") | |||
| tasks.load() | |||
| task_manager.load() | |||
| t_scheduler = threading.Thread(target=wiki_scheduler) | |||
| t_scheduler.name = "wiki-scheduler" | |||
| t_scheduler.daemon = True | |||
| @@ -119,7 +116,7 @@ def main(): | |||
| # Run the scheduler on the main thread, but also run the IRC watcher on | |||
| # another thread iff it is enabled: | |||
| logger.info("Starting wiki scheduler") | |||
| tasks.load() | |||
| task_manager.load() | |||
| if "irc_watcher" in enabled: | |||
| logger.info("Starting IRC watcher") | |||
| t_watcher = threading.Thread(target=irc_watcher) | |||
| @@ -29,7 +29,7 @@ recieves an event from IRC. | |||
| import re | |||
| from earwigbot import tasks | |||
| from earwigbot.tasks import task_manager | |||
| afc_prefix = "wikipedia( talk)?:(wikiproject )?articles for creation" | |||
| @@ -56,7 +56,7 @@ def process(rc): | |||
| chans.update(("##earwigbot", "#wikipedia-en-afc-feed")) | |||
| if r_page.search(page_name): | |||
| #tasks.start("afc_copyvios", page=rc.page) | |||
| #task_manager.start("afc_copyvios", page=rc.page) | |||
| chans.add("#wikipedia-en-afc-feed") | |||
| elif r_ffu.match(page_name): | |||
| @@ -76,7 +76,7 @@ def process(rc): | |||
| elif rc.flags == "restore" and r_restore.match(comment): | |||
| p = r_restored_page.findall(rc.comment)[0] | |||
| #tasks.start("afc_copyvios", page=p) | |||
| #task_manager.start("afc_copyvios", page=p) | |||
| chans.add("#wikipedia-en-afc-feed") | |||
| elif rc.flags == "protect" and r_protect.match(comment): | |||
| @@ -23,8 +23,10 @@ | |||
| """ | |||
| EarwigBot's Wiki Task Manager | |||
| This package provides the wiki bot "tasks" EarwigBot runs. Here in __init__, | |||
| you can find some functions used to load and run these tasks. | |||
| This package provides the wiki bot "tasks" EarwigBot runs. This module contains | |||
| the BaseTask class (import with `from earwigbot.tasks import BaseTask`) and an | |||
| internal _TaskManager class. This can be accessed through the `task_manager` | |||
| singleton. | |||
| """ | |||
| import logging | |||
| @@ -33,106 +35,199 @@ import sys | |||
| import threading | |||
| import time | |||
| from earwigbot.classes import BaseTask | |||
| from earwigbot import wiki | |||
| from earwigbot.config import config | |||
| __all__ = ["load", "schedule", "start", "get", "get_all"] | |||
| # Base directory when searching for tasks: | |||
| base_dir = os.path.dirname(os.path.abspath(__file__)) | |||
| # Store loaded tasks as a dict where the key is the task name and the value is | |||
| # an instance of the task class: | |||
| _tasks = {} | |||
| # Logger for this module: | |||
| logger = logging.getLogger("earwigbot.commands") | |||
| def _load_task(filename): | |||
| """Try to load a specific task from a module, identified by file name.""" | |||
| global _tasks | |||
| # Strip .py from the end of the filename and join with our package name: | |||
| name = ".".join(("tasks", filename[:-3])) | |||
| try: | |||
| __import__(name) | |||
| except: | |||
| logger.exception("Couldn't load file {0}:".format(filename)) | |||
| return | |||
| task = sys.modules[name].Task() | |||
| task._setup_logger() | |||
| if not isinstance(task, BaseTask): | |||
| return | |||
| _tasks[task.name] = task | |||
| logger.debug("Added task {0}".format(task.name)) | |||
| def _wrapper(task, **kwargs): | |||
| """Wrapper for task classes: run the task and catch any errors.""" | |||
| try: | |||
| task.run(**kwargs) | |||
| except: | |||
| error = "Task '{0}' raised an exception and had to stop" | |||
| logger.exception(error.format(task.name)) | |||
| else: | |||
| logger.info("Task '{0}' finished without error".format(task.name)) | |||
| def load(): | |||
| """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 | |||
| __all__ = ["BaseTask", "task_manager"] | |||
| class BaseTask(object): | |||
| """A base class for bot tasks that edit Wikipedia.""" | |||
| name = None | |||
| number = 0 | |||
| def __init__(self): | |||
| """Constructor for new tasks. | |||
| This is called once immediately after the task class is loaded by | |||
| the task manager (in tasks._load_task()). | |||
| """ | |||
| pass | |||
| def _setup_logger(self): | |||
| """Set up a basic module-level logger.""" | |||
| logger_name = ".".join(("earwigbot", "tasks", self.name)) | |||
| self.logger = logging.getLogger(logger_name) | |||
| self.logger.setLevel(logging.DEBUG) | |||
| def run(self, **kwargs): | |||
| """Main entry point to run a given task. | |||
| This is called directly by tasks.start() and is the main way to make a | |||
| task do stuff. kwargs will be any keyword arguments passed to start() | |||
| which are entirely optional. | |||
| The same task instance is preserved between runs, so you can | |||
| theoretically store data in self (e.g. | |||
| start('mytask', action='store', data='foo')) and then use it later | |||
| (e.g. start('mytask', action='save')). | |||
| """ | |||
| pass | |||
| def make_summary(self, comment): | |||
| """Makes an edit summary by filling in variables in a config value. | |||
| config.wiki["summary"] is used, where $2 is replaced by the main | |||
| summary body, given as a method arg, and $1 is replaced by the task | |||
| number. | |||
| If the config value is not found, we just return the arg as-is. | |||
| """ | |||
| try: | |||
| _load_task(filename) | |||
| except AttributeError: | |||
| pass # The file is doesn't contain a task, so just move on | |||
| summary = config.wiki["summary"] | |||
| except KeyError: | |||
| return comment | |||
| return summary.replace("$1", str(self.number)).replace("$2", comment) | |||
| def shutoff_enabled(self, site=None): | |||
| """Returns whether on-wiki shutoff is enabled for this task. | |||
| We check a certain page for certain content. This is determined by | |||
| our config file: config.wiki["shutoff"]["page"] is used as the title, | |||
| with $1 replaced by our username and $2 replaced by the task number, | |||
| and config.wiki["shutoff"]["disabled"] is used as the content. | |||
| If the page has that content or the page does not exist, then shutoff | |||
| is "disabled", meaning the bot is supposed to run normally, and we | |||
| return False. If the page's content is something other than what we | |||
| expect, shutoff is enabled, and we return True. | |||
| If a site is not provided, we'll try to use self.site if it's set. | |||
| Otherwise, we'll use our default site. | |||
| """ | |||
| if not site: | |||
| try: | |||
| site = self.site | |||
| except AttributeError: | |||
| site = wiki.get_site() | |||
| try: | |||
| cfg = config.wiki["shutoff"] | |||
| except KeyError: | |||
| return False | |||
| title = cfg.get("page", "User:$1/Shutoff/Task $2") | |||
| username = site.get_user().name() | |||
| title = title.replace("$1", username).replace("$2", str(self.number)) | |||
| page = site.get_page(title) | |||
| try: | |||
| content = page.get() | |||
| except wiki.PageNotFoundError: | |||
| return False | |||
| if content == cfg.get("disabled", "run"): | |||
| return False | |||
| self.logger.warn("Emergency task shutoff has been enabled!") | |||
| return True | |||
| class _TaskManager(object): | |||
| def __init__(self): | |||
| self.logger = logging.getLogger("earwigbot.commands") | |||
| self._base_dir = os.path.dirname(os.path.abspath(__file__)) | |||
| self._tasks = {} | |||
| def _load_task(self, filename): | |||
| """Load a specific task from a module, identified by file name.""" | |||
| # Strip .py from the filename's end and join with our package name: | |||
| name = ".".join(("tasks", filename[:-3])) | |||
| try: | |||
| __import__(name) | |||
| except: | |||
| self.logger.exception("Couldn't load file {0}:".format(filename)) | |||
| return | |||
| logger.info("Found {0} tasks: {1}".format(len(_tasks), ', '.join(_tasks.keys()))) | |||
| try: | |||
| task = sys.modules[name].Task() | |||
| except AttributeError: | |||
| return # No task in this module | |||
| if not isinstance(task, BaseTask): | |||
| return | |||
| task._setup_logger() | |||
| def schedule(now=time.gmtime()): | |||
| """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, | |||
| now.tm_wday) | |||
| self._tasks[task.name] = task | |||
| self.logger.debug("Added task {0}".format(task.name)) | |||
| for task in tasks: | |||
| if isinstance(task, list): # they've specified kwargs | |||
| start(task[0], **task[1]) # so pass those to start_task | |||
| else: # otherwise, just pass task_name | |||
| start(task) | |||
| def _wrapper(self, task, **kwargs): | |||
| """Wrapper for task classes: run the task and catch any errors.""" | |||
| try: | |||
| task.run(**kwargs) | |||
| except: | |||
| msg = "Task '{0}' raised an exception and had to stop" | |||
| self.logger.exception(msg.format(task.name)) | |||
| else: | |||
| msg = "Task '{0}' finished without error" | |||
| self.logger.info(msg.format(task.name)) | |||
| def load(self): | |||
| """Load all valid tasks from tasks/ into self._tasks.""" | |||
| files = os.listdir(self._base_dir) | |||
| files.sort() | |||
| for filename in files: | |||
| if filename.startswith("_") or not filename.endswith(".py"): | |||
| continue | |||
| self._load_task(filename) | |||
| msg = "Found {0} tasks: {1}" | |||
| tasks = ', '.join(self._tasks.keys()) | |||
| self.logger.info(msg.format(len(self._tasks), tasks)) | |||
| def schedule(self, now=None): | |||
| """Start all tasks that are supposed to be run at a given time.""" | |||
| if not now: | |||
| now = time.gmtime() | |||
| # Get list of tasks to run this turn: | |||
| tasks = config.schedule(now.tm_min, now.tm_hour, now.tm_mday, | |||
| now.tm_mon, now.tm_wday) | |||
| for task in tasks: | |||
| if isinstance(task, list): # They've specified kwargs, | |||
| self.start(task[0], **task[1]) # so pass those to start_task | |||
| else: # Otherwise, just pass task_name | |||
| self.start(task) | |||
| def start(self, task_name, **kwargs): | |||
| """Start a given task in a new thread. Pass args to the task's run() | |||
| function.""" | |||
| msg = "Starting task '{0}' in a new thread" | |||
| self.logger.info(msg.format(task_name)) | |||
| def start(task_name, **kwargs): | |||
| """Start a given task in a new thread. Pass args to the task's run() | |||
| function.""" | |||
| logger.info("Starting task '{0}' in a new thread".format(task_name)) | |||
| try: | |||
| task = self._tasks[task_name] | |||
| except KeyError: | |||
| e = "Couldn't find task '{0}': bot/tasks/{0}.py does not exist" | |||
| self.logger.error(e.format(task_name)) | |||
| return | |||
| try: | |||
| task = _tasks[task_name] | |||
| except KeyError: | |||
| error = "Couldn't find task '{0}': bot/tasks/{0}.py does not exist" | |||
| logger.error(error.format(task_name)) | |||
| return | |||
| func = lambda: self._wrapper(task, **kwargs) | |||
| task_thread = threading.Thread(target=func) | |||
| start_time = time.strftime("%b %d %H:%M:%S") | |||
| task_thread.name = "{0} ({1})".format(task_name, start_time) | |||
| task_thread = threading.Thread(target=lambda: _wrapper(task, **kwargs)) | |||
| 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: | |||
| task_thread.daemon = True | |||
| # Stop bot task threads automagically if the main bot stops: | |||
| task_thread.daemon = True | |||
| task_thread.start() | |||
| task_thread.start() | |||
| def get(self, task_name): | |||
| """Return the class instance associated with a certain task name. | |||
| def get(task_name): | |||
| """Return the class instance associated with a certain task name. | |||
| Will raise KeyError if the task is not found. | |||
| """ | |||
| return self._tasks[task_name] | |||
| Will raise KeyError if the task is not found. | |||
| """ | |||
| return _tasks[task_name] | |||
| def get_all(self): | |||
| """Return our dict of all loaded tasks.""" | |||
| return self._tasks | |||
| def get_all(): | |||
| """Return our dict of all loaded tasks.""" | |||
| return _tasks | |||
| task_manager = _TaskManager() | |||
| @@ -20,7 +20,7 @@ | |||
| # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||
| # SOFTWARE. | |||
| from earwigbot.classes import BaseTask | |||
| from earwigbot.tasks import BaseTask | |||
| class Task(BaseTask): | |||
| """A task to delink mainspace categories in declined [[WP:AFC]] | |||
| @@ -27,8 +27,8 @@ from threading import Lock | |||
| import oursql | |||
| from earwigbot import wiki | |||
| from earwigbot.classes import BaseTask | |||
| from earwigbot.config import config | |||
| from earwigbot.tasks import BaseTask | |||
| class Task(BaseTask): | |||
| """A task to check newly-edited [[WP:AFC]] submissions for copyright | |||
| @@ -20,7 +20,7 @@ | |||
| # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||
| # SOFTWARE. | |||
| from earwigbot.classes import BaseTask | |||
| from earwigbot.tasks import BaseTask | |||
| class Task(BaseTask): | |||
| """ A task to create daily categories for [[WP:AFC]].""" | |||
| @@ -32,8 +32,8 @@ from numpy import arange | |||
| import oursql | |||
| from earwigbot import wiki | |||
| from earwigbot.classes import BaseTask | |||
| from earwigbot.config import config | |||
| from earwigbot.tasks import BaseTask | |||
| # Valid submission statuses: | |||
| STATUS_NONE = 0 | |||
| @@ -30,8 +30,8 @@ from time import sleep | |||
| import oursql | |||
| from earwigbot import wiki | |||
| from earwigbot.classes import BaseTask | |||
| from earwigbot.config import config | |||
| from earwigbot.tasks import BaseTask | |||
| # Chart status number constants: | |||
| CHART_NONE = 0 | |||
| @@ -20,7 +20,7 @@ | |||
| # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||
| # SOFTWARE. | |||
| from earwigbot.classes import BaseTask | |||
| from earwigbot.tasks import BaseTask | |||
| class Task(BaseTask): | |||
| """A task to clear [[Category:Undated AfC submissions]].""" | |||
| @@ -20,7 +20,7 @@ | |||
| # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||
| # SOFTWARE. | |||
| from earwigbot.classes import BaseTask | |||
| from earwigbot.tasks import BaseTask | |||
| class Task(BaseTask): | |||
| """A task to add |blp=yes to {{WPB}} or {{WPBS}} when it is used along with | |||
| @@ -20,7 +20,7 @@ | |||
| # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||
| # SOFTWARE. | |||
| from earwigbot.classes import BaseTask | |||
| from earwigbot.tasks import BaseTask | |||
| class Task(BaseTask): | |||
| """A task to create daily categories for [[WP:FEED]].""" | |||
| @@ -20,7 +20,7 @@ | |||
| # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||
| # SOFTWARE. | |||
| from earwigbot.classes import BaseTask | |||
| from earwigbot.tasks import BaseTask | |||
| class Task(BaseTask): | |||
| """A task to tag files whose extensions do not agree with their MIME | |||