| @@ -28,7 +28,7 @@ See README.md for a basic overview, or the docs/ directory for details. | |||
| """ | |||
| __author__ = "Ben Kurtovic" | |||
| __copyright__ = "Copyright (C) 2009, 2010, 2011 by Ben Kurtovic" | |||
| __copyright__ = "Copyright (C) 2009, 2010, 2011, 2012 by Ben Kurtovic" | |||
| __license__ = "MIT License" | |||
| __version__ = "0.1.dev" | |||
| __email__ = "ben.kurtovic@verizon.net" | |||
| @@ -30,7 +30,7 @@ class Command(BaseCommand): | |||
| name = "report" | |||
| def process(self, data): | |||
| self.site = wiki.get_site() | |||
| self.site = self.bot.wiki.get_site() | |||
| self.site._maxlag = None | |||
| self.data = data | |||
| @@ -22,7 +22,6 @@ | |||
| import re | |||
| from earwigbot import wiki | |||
| from earwigbot.commands import BaseCommand | |||
| class Command(BaseCommand): | |||
| @@ -45,7 +44,7 @@ class Command(BaseCommand): | |||
| return False | |||
| def process(self, data): | |||
| self.site = wiki.get_site() | |||
| self.site = self.bot.wiki.get_site() | |||
| self.site._maxlag = None | |||
| if data.line[1] == "JOIN": | |||
| @@ -41,7 +41,7 @@ class Command(BaseCommand): | |||
| else: | |||
| name = ' '.join(data.args) | |||
| site = wiki.get_site() | |||
| site = self.bot.wiki.get_site() | |||
| site._maxlag = None | |||
| user = site.get_user(name) | |||
| @@ -41,7 +41,7 @@ class Command(BaseCommand): | |||
| else: | |||
| name = ' '.join(data.args) | |||
| site = wiki.get_site() | |||
| site = self.bot.wiki.get_site() | |||
| site._maxlag = None | |||
| user = site.get_user(name) | |||
| @@ -39,7 +39,7 @@ class Command(BaseCommand): | |||
| else: | |||
| name = ' '.join(data.args) | |||
| site = wiki.get_site() | |||
| site = self.bot.wiki.get_site() | |||
| site._maxlag = None | |||
| user = site.get_user(name) | |||
| @@ -29,6 +29,8 @@ import yaml | |||
| from earwigbot import blowfish | |||
| __all__ = ["BotConfig"] | |||
| class BotConfig(object): | |||
| """ | |||
| EarwigBot's YAML Config File Manager | |||
| @@ -110,10 +110,10 @@ class BaseTask(object): | |||
| try: | |||
| site = self.site | |||
| except AttributeError: | |||
| site = wiki.get_site() | |||
| site = self.bot.wiki.get_site() | |||
| try: | |||
| cfg = self.bot.config.wiki["shutoff"] | |||
| cfg = self.config.wiki["shutoff"] | |||
| except KeyError: | |||
| return False | |||
| title = cfg.get("page", "User:$1/Shutoff/Task $2") | |||
| @@ -22,6 +22,8 @@ | |||
| from earwigbot.tasks import BaseTask | |||
| __all__ = ["Task"] | |||
| class Task(BaseTask): | |||
| """A task to delink mainspace categories in declined [[WP:AFC]] | |||
| submissions.""" | |||
| @@ -26,9 +26,10 @@ from threading import Lock | |||
| import oursql | |||
| from earwigbot import wiki | |||
| from earwigbot.tasks import BaseTask | |||
| __all__ = ["Task"] | |||
| class Task(BaseTask): | |||
| """A task to check newly-edited [[WP:AFC]] submissions for copyright | |||
| violations.""" | |||
| @@ -62,7 +63,7 @@ class Task(BaseTask): | |||
| if self.shutoff_enabled(): | |||
| return | |||
| title = kwargs["page"] | |||
| page = wiki.get_site().get_page(title) | |||
| page = self.bot.wiki.get_site().get_page(title) | |||
| with self.db_access_lock: | |||
| self.conn = oursql.connect(**self.conn_data) | |||
| self.process(page) | |||
| @@ -22,6 +22,8 @@ | |||
| from earwigbot.tasks import BaseTask | |||
| __all__ = ["Task"] | |||
| class Task(BaseTask): | |||
| """ A task to create daily categories for [[WP:AFC]].""" | |||
| name = "afc_dailycats" | |||
| @@ -34,11 +34,7 @@ import oursql | |||
| from earwigbot import wiki | |||
| from earwigbot.tasks import BaseTask | |||
| # Valid submission statuses: | |||
| STATUS_NONE = 0 | |||
| STATUS_PEND = 1 | |||
| STATUS_DECLINE = 2 | |||
| STATUS_ACCEPT = 3 | |||
| __all__ = ["Task"] | |||
| class Task(BaseTask): | |||
| """A task to generate charts about AfC submissions over time. | |||
| @@ -56,6 +52,12 @@ class Task(BaseTask): | |||
| """ | |||
| name = "afc_history" | |||
| # Valid submission statuses: | |||
| STATUS_NONE = 0 | |||
| STATUS_PEND = 1 | |||
| STATUS_DECLINE = 2 | |||
| STATUS_ACCEPT = 3 | |||
| def setup(self): | |||
| cfg = self.config.tasks.get(self.name, {}) | |||
| self.num_days = cfg.get("days", 90) | |||
| @@ -72,7 +74,7 @@ class Task(BaseTask): | |||
| self.db_access_lock = Lock() | |||
| def run(self, **kwargs): | |||
| self.site = wiki.get_site() | |||
| self.site = self.bot.wiki.get_site() | |||
| with self.db_access_lock: | |||
| self.conn = oursql.connect(**self.conn_data) | |||
| @@ -136,7 +138,7 @@ class Task(BaseTask): | |||
| stored = cursor.fetchall() | |||
| status = self.get_status(title, pageid) | |||
| if status == STATUS_NONE: | |||
| if status == self.STATUS_NONE: | |||
| if stored: | |||
| cursor.execute(q_delete, (pageid,)) | |||
| continue | |||
| @@ -154,14 +156,14 @@ class Task(BaseTask): | |||
| ns = page.namespace() | |||
| if ns == wiki.NS_FILE_TALK: # Ignore accepted FFU requests | |||
| return STATUS_NONE | |||
| return self.STATUS_NONE | |||
| if ns == wiki.NS_TALK: | |||
| new_page = page.toggle_talk() | |||
| sleep(2) | |||
| if new_page.is_redirect(): | |||
| return STATUS_NONE # Ignore accepted AFC/R requests | |||
| return STATUS_ACCEPT | |||
| return self.STATUS_NONE # Ignore accepted AFC/R requests | |||
| return self.STATUS_ACCEPT | |||
| cats = self.categories | |||
| sq = self.site.sql_query | |||
| @@ -169,16 +171,16 @@ class Task(BaseTask): | |||
| match = lambda cat: list(sq(query, (cat.replace(" ", "_"), pageid))) | |||
| if match(cats["pending"]): | |||
| return STATUS_PEND | |||
| return self.STATUS_PEND | |||
| elif match(cats["unsubmitted"]): | |||
| return STATUS_NONE | |||
| return self.STATUS_NONE | |||
| elif match(cats["declined"]): | |||
| return STATUS_DECLINE | |||
| return STATUS_NONE | |||
| return self.STATUS_DECLINE | |||
| return self.STATUS_NONE | |||
| def get_date_counts(self, date): | |||
| query = "SELECT COUNT(*) FROM page WHERE page_date = ? AND page_status = ?" | |||
| statuses = [STATUS_PEND, STATUS_DECLINE, STATUS_ACCEPT] | |||
| statuses = [self.STATUS_PEND, self.STATUS_DECLINE, self.STATUS_ACCEPT] | |||
| counts = {} | |||
| with self.conn.cursor() as cursor: | |||
| for status in statuses: | |||
| @@ -192,9 +194,9 @@ class Task(BaseTask): | |||
| plt.xlabel(self.graph.get("xaxis", "Date")) | |||
| plt.ylabel(self.graph.get("yaxis", "Submissions")) | |||
| pends = [d[STATUS_PEND] for d in data.itervalues()] | |||
| declines = [d[STATUS_DECLINE] for d in data.itervalues()] | |||
| accepts = [d[STATUS_ACCEPT] for d in data.itervalues()] | |||
| pends = [d[self.STATUS_PEND] for d in data.itervalues()] | |||
| declines = [d[self.STATUS_DECLINE] for d in data.itervalues()] | |||
| accepts = [d[self.STATUS_ACCEPT] for d in data.itervalues()] | |||
| pends_declines = [p + d for p, d in zip(pends, declines)] | |||
| ind = arange(len(data)) | |||
| xsize = self.graph.get("xsize", 1200) | |||
| @@ -32,14 +32,7 @@ import oursql | |||
| from earwigbot import wiki | |||
| from earwigbot.tasks import BaseTask | |||
| # Chart status number constants: | |||
| CHART_NONE = 0 | |||
| CHART_PEND = 1 | |||
| CHART_DRAFT = 2 | |||
| CHART_REVIEW = 3 | |||
| CHART_ACCEPT = 4 | |||
| CHART_DECLINE = 5 | |||
| CHART_MISPLACE = 6 | |||
| __all__ = ["Task"] | |||
| class Task(BaseTask): | |||
| """A task to generate statistics for WikiProject Articles for Creation. | |||
| @@ -52,6 +45,15 @@ class Task(BaseTask): | |||
| name = "afc_statistics" | |||
| number = 2 | |||
| # Chart status number constants: | |||
| CHART_NONE = 0 | |||
| CHART_PEND = 1 | |||
| CHART_DRAFT = 2 | |||
| CHART_REVIEW = 3 | |||
| CHART_ACCEPT = 4 | |||
| CHART_DECLINE = 5 | |||
| CHART_MISPLACE = 6 | |||
| def setup(self): | |||
| self.cfg = cfg = self.config.tasks.get(self.name, {}) | |||
| @@ -82,7 +84,7 @@ class Task(BaseTask): | |||
| (self.save()). We will additionally create an SQL connection with our | |||
| local database. | |||
| """ | |||
| self.site = wiki.get_site() | |||
| self.site = self.bot.wiki.get_site() | |||
| with self.db_access_lock: | |||
| self.conn = oursql.connect(**self.conn_data) | |||
| @@ -285,7 +287,7 @@ class Task(BaseTask): | |||
| query = """DELETE FROM page, row USING page JOIN row | |||
| ON page_id = row_id WHERE row_chart IN (?, ?) | |||
| AND ADDTIME(page_special_time, '36:00:00') < NOW()""" | |||
| cursor.execute(query, (CHART_ACCEPT, CHART_DECLINE)) | |||
| cursor.execute(query, (self.CHART_ACCEPT, self.CHART_DECLINE)) | |||
| def update(self, **kwargs): | |||
| """Update a page by name, regardless of whether anything has changed. | |||
| @@ -332,7 +334,7 @@ class Task(BaseTask): | |||
| namespace = self.site.get_page(title).namespace() | |||
| status, chart = self.get_status_and_chart(content, namespace) | |||
| if chart == CHART_NONE: | |||
| if chart == self.CHART_NONE: | |||
| msg = "Could not find a status for [[{0}]]".format(title) | |||
| self.logger.warn(msg) | |||
| return | |||
| @@ -366,7 +368,7 @@ class Task(BaseTask): | |||
| namespace = self.site.get_page(title).namespace() | |||
| status, chart = self.get_status_and_chart(content, namespace) | |||
| if chart == CHART_NONE: | |||
| if chart == self.CHART_NONE: | |||
| self.untrack_page(cursor, pageid) | |||
| return | |||
| @@ -498,23 +500,23 @@ class Task(BaseTask): | |||
| statuses = self.get_statuses(content) | |||
| if "R" in statuses: | |||
| status, chart = "r", CHART_REVIEW | |||
| status, chart = "r", self.CHART_REVIEW | |||
| elif "H" in statuses: | |||
| status, chart = "p", CHART_DRAFT | |||
| status, chart = "p", self.CHART_DRAFT | |||
| elif "P" in statuses: | |||
| status, chart = "p", CHART_PEND | |||
| status, chart = "p", self.CHART_PEND | |||
| elif "T" in statuses: | |||
| status, chart = None, CHART_NONE | |||
| status, chart = None, self.CHART_NONE | |||
| elif "D" in statuses: | |||
| status, chart = "d", CHART_DECLINE | |||
| status, chart = "d", self.CHART_DECLINE | |||
| else: | |||
| status, chart = None, CHART_NONE | |||
| status, chart = None, self.CHART_NONE | |||
| if namespace == wiki.NS_MAIN: | |||
| if not statuses: | |||
| status, chart = "a", CHART_ACCEPT | |||
| status, chart = "a", self.CHART_ACCEPT | |||
| else: | |||
| status, chart = None, CHART_MISPLACE | |||
| status, chart = None, self.CHART_MISPLACE | |||
| return status, chart | |||
| @@ -613,23 +615,23 @@ class Task(BaseTask): | |||
| returned if we cannot determine when the page was "special"-ed, or if | |||
| it was "special"-ed more than 250 edits ago. | |||
| """ | |||
| if chart ==CHART_NONE: | |||
| if chart ==self.CHART_NONE: | |||
| return None, None, None | |||
| elif chart == CHART_MISPLACE: | |||
| elif chart == self.CHART_MISPLACE: | |||
| return self.get_create(pageid) | |||
| elif chart == CHART_ACCEPT: | |||
| elif chart == self.CHART_ACCEPT: | |||
| search_for = None | |||
| search_not = ["R", "H", "P", "T", "D"] | |||
| elif chart == CHART_DRAFT: | |||
| elif chart == self.CHART_DRAFT: | |||
| search_for = "H" | |||
| search_not = [] | |||
| elif chart == CHART_PEND: | |||
| elif chart == self.CHART_PEND: | |||
| search_for = "P" | |||
| search_not = [] | |||
| elif chart == CHART_REVIEW: | |||
| elif chart == self.CHART_REVIEW: | |||
| search_for = "R" | |||
| search_not = [] | |||
| elif chart == CHART_DECLINE: | |||
| elif chart == self.CHART_DECLINE: | |||
| search_for = "D" | |||
| search_not = ["R", "H", "P", "T"] | |||
| @@ -683,12 +685,12 @@ class Task(BaseTask): | |||
| """ | |||
| notes = "" | |||
| ignored_charts = [CHART_NONE, CHART_ACCEPT, CHART_DECLINE] | |||
| ignored_charts = [self.CHART_NONE, self.CHART_ACCEPT, self.CHART_DECLINE] | |||
| if chart in ignored_charts: | |||
| return notes | |||
| statuses = self.get_statuses(content) | |||
| if "D" in statuses and chart != CHART_MISPLACE: | |||
| if "D" in statuses and chart != self.CHART_MISPLACE: | |||
| notes += "|nr=1" # Submission was resubmitted | |||
| if len(content) < 500: | |||
| @@ -705,7 +707,7 @@ class Task(BaseTask): | |||
| if time_since_modify > max_time: | |||
| notes += "|no=1" # Submission hasn't been touched in over 4 days | |||
| if chart in [CHART_PEND, CHART_DRAFT]: | |||
| if chart in [self.CHART_PEND, self.CHART_DRAFT]: | |||
| submitter = self.site.get_user(s_user) | |||
| try: | |||
| if submitter.blockinfo(): | |||
| @@ -22,6 +22,8 @@ | |||
| from earwigbot.tasks import BaseTask | |||
| __all__ = ["Task"] | |||
| class Task(BaseTask): | |||
| """A task to clear [[Category:Undated AfC submissions]].""" | |||
| name = "afc_undated" | |||
| @@ -22,6 +22,8 @@ | |||
| from earwigbot.tasks import BaseTask | |||
| __all__ = ["Task"] | |||
| class Task(BaseTask): | |||
| """A task to add |blp=yes to {{WPB}} or {{WPBS}} when it is used along with | |||
| {{WP Biography}}.""" | |||
| @@ -22,6 +22,8 @@ | |||
| from earwigbot.tasks import BaseTask | |||
| __all__ = ["Task"] | |||
| class Task(BaseTask): | |||
| """A task to create daily categories for [[WP:FEED]].""" | |||
| name = "feed_dailycats" | |||
| @@ -0,0 +1,35 @@ | |||
| # -*- 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.tasks import BaseTask | |||
| __all__ = ["Task"] | |||
| class Task(BaseTask): | |||
| """A task to tag talk pages with WikiProject Banners.""" | |||
| name = "wikiproject_tagger" | |||
| def setup(self): | |||
| pass | |||
| def run(self, **kwargs): | |||
| pass | |||
| @@ -22,6 +22,8 @@ | |||
| from earwigbot.tasks import BaseTask | |||
| __all__ = ["Task"] | |||
| class Task(BaseTask): | |||
| """A task to tag files whose extensions do not agree with their MIME | |||
| type.""" | |||
| @@ -27,6 +27,8 @@ from os import path | |||
| from earwigbot import __version__ | |||
| from earwigbot.bot import Bot | |||
| __all__ = ["BotUtility", "main"] | |||
| class BotUtility(object): | |||
| """ | |||
| DOCSTRING NEEDED | |||
| @@ -28,21 +28,21 @@ 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. | |||
| Import the toolset directly with `from earwigbot import wiki`. If using the | |||
| built-in integration with the rest of the bot, that's usually not necessary: | |||
| Bot() objects contain a `wiki` attribute containing a SitesDBManager object | |||
| tied to the sites.db file located in the same directory as config.yml. That | |||
| object has the principal methods get_site, add_site, and remove_site. | |||
| built-in integration with the rest of the bot, Bot() objects contain a `wiki` | |||
| attribute, which is a SitesDBManager object tied to the sites.db file located | |||
| in the same directory as config.yml. That object has the principal methods | |||
| get_site, add_site, and remove_site that should handle all of your Site (and | |||
| thus, Page, Category, and User) needs. | |||
| """ | |||
| import logging as _log | |||
| logger = _log.getLogger("earwigbot.wiki") | |||
| logger.addHandler(_log.NullHandler()) | |||
| from earwigbot.wiki.category import * | |||
| from earwigbot.wiki.constants import * | |||
| from earwigbot.wiki.exceptions import * | |||
| from earwigbot.wiki.category import Category | |||
| from earwigbot.wiki.page import Page | |||
| from earwigbot.wiki.site import Site | |||
| from earwigbot.wiki.sitesdb import SitesDBManager | |||
| from earwigbot.wiki.user import User | |||
| from earwigbot.wiki.page import * | |||
| from earwigbot.wiki.site import * | |||
| from earwigbot.wiki.sitesdb import * | |||
| from earwigbot.wiki.user import * | |||
| @@ -22,6 +22,8 @@ | |||
| from earwigbot.wiki.page import Page | |||
| __all__ = ["Category"] | |||
| class Category(Page): | |||
| """ | |||
| EarwigBot's Wiki Toolset: Category Class | |||
| @@ -27,13 +27,16 @@ This module defines some useful constants: | |||
| * USER_AGENT - our default User Agent when making API queries | |||
| * NS_* - default namespace IDs for easy lookup | |||
| Import with `from earwigbot.wiki import constants` or `from earwigbot.wiki.constants import *`. | |||
| Import directly with `from earwigbot.wiki import constants` or | |||
| `from earwigbot.wiki.constants import *`. These are also available from | |||
| earwigbot.wiki (e.g. `earwigbot.wiki.USER_AGENT`). | |||
| """ | |||
| # Default User Agent when making API queries: | |||
| from earwigbot import __version__ as _v | |||
| from platform import python_version as _p | |||
| USER_AGENT = "EarwigBot/{0} (Python/{1}; https://github.com/earwig/earwigbot)".format(_v, _p()) | |||
| del _v, _p | |||
| # Default namespace IDs: | |||
| NS_MAIN = 0 | |||
| @@ -28,6 +28,8 @@ from urllib import quote | |||
| from earwigbot.wiki.copyright import CopyrightMixin | |||
| from earwigbot.wiki.exceptions import * | |||
| __all__ = ["Page"] | |||
| class Page(CopyrightMixin): | |||
| """ | |||
| EarwigBot's Wiki Toolset: Page Class | |||
| @@ -43,6 +43,8 @@ from earwigbot.wiki.exceptions import * | |||
| from earwigbot.wiki.page import Page | |||
| from earwigbot.wiki.user import User | |||
| __all__ = ["Site"] | |||
| class Site(object): | |||
| """ | |||
| EarwigBot's Wiki Toolset: Site Class | |||
| @@ -26,6 +26,8 @@ from earwigbot.wiki.constants import * | |||
| from earwigbot.wiki.exceptions import UserNotFoundError | |||
| from earwigbot.wiki.page import Page | |||
| __all__ = ["User"] | |||
| class User(object): | |||
| """ | |||
| EarwigBot's Wiki Toolset: User Class | |||
| @@ -42,7 +42,7 @@ setup( | |||
| url = "https://github.com/earwig/earwigbot", | |||
| description = "EarwigBot is a Python robot that edits Wikipedia and interacts with people over IRC.", | |||
| long_description = long_docs, | |||
| download_url = "https://github.com/earwig/earwigbot/tarball/{0}".format(__version__), | |||
| download_url = "https://github.com/earwig/earwigbot/tarball/v{0}".format(__version__), | |||
| keywords = "earwig earwigbot irc wikipedia wiki mediawiki", | |||
| license = "MIT License", | |||
| classifiers = [ | |||