@@ -1,8 +1,12 @@ | |||||
# -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||
import config | |||||
import wiki | |||||
class BaseTask(object): | class BaseTask(object): | ||||
"""A base class for bot tasks that edit Wikipedia.""" | """A base class for bot tasks that edit Wikipedia.""" | ||||
name = None | name = None | ||||
number = 0 | |||||
def __init__(self): | def __init__(self): | ||||
"""Constructor for new tasks. | """Constructor for new tasks. | ||||
@@ -25,3 +29,57 @@ class BaseTask(object): | |||||
(e.g. start('mytask', action='save')). | (e.g. start('mytask', action='save')). | ||||
""" | """ | ||||
pass | 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", 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", self.number) | |||||
page = site.get_page(title) | |||||
try: | |||||
content = page.get() | |||||
except wiki.PageNotFoundError: | |||||
return False | |||||
if content == cfg.get("disabled", "run"): | |||||
return False | |||||
return True |
@@ -8,10 +8,11 @@ including encrypting and decrypting passwords and making a new config file from | |||||
scratch at the inital bot run. | scratch at the inital bot run. | ||||
Usually you'll just want to do "from core import config" and access config data | Usually you'll just want to do "from core import config" and access config data | ||||
from within config's four global variables and one function: | |||||
from within config's five global variables and one function: | |||||
* config.components - a list of enabled components | * config.components - a list of enabled components | ||||
* config.wiki - a dict of information about wiki-editing | * config.wiki - a dict of information about wiki-editing | ||||
* config.tasks - a dict of information for bot tasks | |||||
* config.irc - a dict of information about IRC | * config.irc - a dict of information about IRC | ||||
* config.metadata - a dict of miscellaneous information | * config.metadata - a dict of miscellaneous information | ||||
* config.schedule() - returns a list of tasks scheduled to run at a given time | * config.schedule() - returns a list of tasks scheduled to run at a given time | ||||
@@ -34,8 +35,8 @@ config_path = path.join(root_dir, "config.json") | |||||
_config = None # Holds data loaded from our config file | _config = None # Holds data loaded from our config file | ||||
# Set our four easy-config-access global variables to None | |||||
components, wiki, irc, metadata = None, None, None, None | |||||
# Set our five easy-config-access global variables to None | |||||
components, wiki, tasks, irc, metadata = None, None, None, None, None | |||||
def _load(): | def _load(): | ||||
"""Load data from our JSON config file (config.json) into _config.""" | """Load data from our JSON config file (config.json) into _config.""" | ||||
@@ -68,8 +69,9 @@ def load(): | |||||
First, check if we have a valid config file, and if not, notify the user. | First, check if we have a valid config file, and if not, notify the user. | ||||
If there is no config file at all, offer to make one, otherwise exit. | If there is no config file at all, offer to make one, otherwise exit. | ||||
Store data from our config file in four global variables (components, wiki, | |||||
irc, metadata) for easy access (as well as the internal _config variable). | |||||
Store data from our config file in five global variables (components, wiki, | |||||
tasks, irc, metadata) for easy access (as well as the internal _config | |||||
variable). | |||||
If everything goes well, return True if stored passwords are | If everything goes well, return True if stored passwords are | ||||
encrypted in the file, or False if they are not. | encrypted in the file, or False if they are not. | ||||
@@ -88,6 +90,7 @@ def load(): | |||||
components = _config.get("components", []) | components = _config.get("components", []) | ||||
wiki = _config.get("wiki", {}) | wiki = _config.get("wiki", {}) | ||||
tasks = _config.get("tasks", {}) | |||||
irc = _config.get("irc", {}) | irc = _config.get("irc", {}) | ||||
metadata = _config.get("metadata", {}) | metadata = _config.get("metadata", {}) | ||||
@@ -1,9 +1,11 @@ | |||||
# -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||
import re | |||||
from os import path | from os import path | ||||
from classes import BaseTask | from classes import BaseTask | ||||
import config | import config | ||||
import wiki | |||||
class Task(BaseTask): | class Task(BaseTask): | ||||
"""A task to generate statistics for WikiProject Articles for Creation. | """A task to generate statistics for WikiProject Articles for Creation. | ||||
@@ -16,35 +18,56 @@ class Task(BaseTask): | |||||
In the live bot, this is "Template:AFC statistics". | In the live bot, this is "Template:AFC statistics". | ||||
""" | """ | ||||
name = "afc_statistics" | name = "afc_statistics" | ||||
number = 2 | |||||
def __init__(self): | def __init__(self): | ||||
self.filename = path.join(config.root_dir, "statistics.txt") | self.filename = path.join(config.root_dir, "statistics.txt") | ||||
self.pagename = "User:EarwigBot/Sandbox/Statistics" | |||||
self.cfg = config.tasks.get(self.name, {}) | |||||
self.pagename = cfg.get("page", "Template:AFC statistics") | |||||
default = "Updating statistics for [[WP:WPAFC|WikiProject Articles for creation]]." | |||||
self.summary = self.make_summary(cfg.get("summary", default)) | |||||
def run(self, **kwargs): | def run(self, **kwargs): | ||||
try: | |||||
action = kwargs["action"] | |||||
except KeyError: | |||||
self.site = wiki.get_site() | |||||
action = kwargs.get("action") | |||||
if not action: | |||||
return | return | ||||
if action == "save": | if action == "save": | ||||
self.save() | self.save() | ||||
else: | |||||
try: | |||||
page = kwargs["page"] | |||||
except KeyError: | |||||
return | |||||
if action == "edit": | |||||
self.process_edit(page) | |||||
elif action == "move": | |||||
self.process_move(page) | |||||
elif action == "delete": | |||||
self.process_delete(page) | |||||
elif action == "restore": | |||||
self.process_restore(page) | |||||
return | |||||
page = kwargs.get("page") | |||||
if page: | |||||
methods = { | |||||
"edit": self.process_edit, | |||||
"move": self.process_move, | |||||
"delete": self.process_delete, | |||||
"restore": self.process_restore, | |||||
} | |||||
method = methods.get(action) | |||||
if method: | |||||
method(page) | |||||
def save(self): | def save(self): | ||||
pass | |||||
if self.shutoff_enabled(): | |||||
return | |||||
try: | |||||
with open(self.filename) as fp: | |||||
statistics = fp.read() | |||||
except IOError: | |||||
pass | |||||
page = self.site.get_page(self.pagename) | |||||
text = page.get() | |||||
newtext = re.sub("(<!-- stat begin -->)(.*?)(<!-- stat end -->)", | |||||
"\\1~~~~\\3", text, flags=re.DOTALL) | |||||
if newtext == text: | |||||
return # Don't edit the page if we're not adding anything | |||||
newtext = re.sub("(<!-- sig begin -->)(.*?)(<!-- sig end -->)", | |||||
"\\1~~~~\\3", newtext) | |||||
page.edit(newtext, self.summary, minor=True) | |||||
def process_edit(self, page): | def process_edit(self, page): | ||||
pass | pass | ||||
@@ -18,8 +18,7 @@ class Category(Page): | |||||
def __repr__(self): | def __repr__(self): | ||||
"""Returns the canonical string representation of the Category.""" | """Returns the canonical string representation of the Category.""" | ||||
res = ", ".join(("Category(title={0!r}", "follow_redirects={1!r}", | |||||
"site={2!r})")) | |||||
res = "Category(title={0!r}, follow_redirects={1!r}, site={2!r})" | |||||
return res.format(self._title, self._follow_redirects, self._site) | return res.format(self._title, self._follow_redirects, self._site) | ||||
def __str__(self): | def __str__(self): | ||||
@@ -83,8 +83,7 @@ class Page(object): | |||||
def __repr__(self): | def __repr__(self): | ||||
"""Returns the canonical string representation of the Page.""" | """Returns the canonical string representation of the Page.""" | ||||
res = ", ".join(("Page(title={0!r}", "follow_redirects={1!r}", | |||||
"site={2!r})")) | |||||
res = "Page(title={0!r}, follow_redirects={1!r}, site={2!r})" | |||||
return res.format(self._title, self._follow_redirects, self._site) | return res.format(self._title, self._follow_redirects, self._site) | ||||
def __str__(self): | def __str__(self): | ||||