diff --git a/earwigbot/tasks/drn_clerkbot.py b/earwigbot/tasks/drn_clerkbot.py index 10cc7cc..a885fa4 100644 --- a/earwigbot/tasks/drn_clerkbot.py +++ b/earwigbot/tasks/drn_clerkbot.py @@ -24,7 +24,7 @@ from datetime import datetime from os import expanduser import re from threading import RLock -from time import sleep, time +from time import mktime, sleep, time import oursql @@ -55,6 +55,7 @@ class DRNClerkBot(Task): STATUS_REVIEW: ("review",), STATUS_RESOLVED: ("resolved", "resolve"), STATUS_CLOSED: ("closed", "close"), + STATUS_ARCHIVE: ("archive",) } def setup(self): @@ -62,9 +63,11 @@ class DRNClerkBot(Task): cfg = self.config.tasks.get(self.name, {}) # Set some wiki-related attributes: - self.title = cfg.get("title", "Wikipedia:Dispute resolution noticeboard") - self.talk = cfg.get("talk", "Wikipedia talk:Dispute resolution noticeboard") - self.volunteer_title = cfg.get("volunteers", "Wikipedia:Dispute resolution noticeboard/Volunteering") + self.title = cfg.get("title", + "Wikipedia:Dispute resolution noticeboard") + self.chart_title = cfg.get("chartTitle", "Template:DRN case status") + self.volunteer_title = cfg.get("volunteers", + "Wikipedia:Dispute resolution noticeboard/Volunteering") self.very_old_title = cfg.get("veryOldTitle", "User talk:Szhang (WMF)") default_summary = "Updating $3 cases for the [[WP:DRN|dispute resolution noticeboard]]." self.summary = self.make_summary(cfg.get("summary", default_summary)) @@ -77,6 +80,11 @@ class DRNClerkBot(Task): self.tl_archive_top = templates.get("archiveTop", "DRN archive top") self.tl_archive_bottom = templates.get("archiveBottom", "DRN archive bottom") + self.tl_chart_header = templates.get("chartHeader", + "DRN case status/header") + self.tl_chart_row = templates.get("chartRow", "DRN case status/row") + self.tl_chart_footer = templates.get("chartFooter", + "DRN case status/footer") # Connection data for our SQL database: kwargs = cfg.get("sql", {}) @@ -106,6 +114,8 @@ class DRNClerkBot(Task): noticies = self.clerk(conn, cases) self.save(page, cases, kwargs) self.send_notices(site, notices) + if action in ["all", "update_chart"]: + self.update_chart(conn) finally: self.db_access_lock.release() @@ -176,11 +186,20 @@ class DRNClerkBot(Task): except (AttributeError, IndexError): id_ = self.select_next_id(conn, "case_id", "case") re_id2 = "(\{\{" + tl_status_esc - re_id2 += "(.*?)\}\})()?" + re_id2 += r"(.*?)\}\})()?" repl = ur"\1 " body = re.sub(re_id2, repl.format(id_), body) - case = _Case(id_, title, status, self.STATUS_UNKNOWN, time(), - 0, False, False, 0, new=True) + re_f = r"\{\{drn filing editor\|(.*?)\|(\d{2}:\d{2},\s\d{2}\s\w+\s\d{4}\s\(UTC\))\}\}" + match = re.search(re_f, body, re.U) + if match: + f_user = match.group(1).split("/", 1)[0].replace("_", " ") + strp = "%H:%M, %d %B %Y (UTC)" + f_time = datetime.strptime(strp, match.group(2)) + else: + f_user, f_time = None, datetime.utcnow() + case = _Case(id_, title, status, self.STATUS_UNKNOWN, f_user, + f_time, "", 0, "", 0, 0, False, False, 0, + new=True) cases.append(case) else: case.status = status @@ -252,7 +271,7 @@ class DRNClerkBot(Task): log = u"Unsure of how to deal with case {0} (title: {1})" self.logger.error(log.format(case.id, case.title)) return notices - self.save_case_updates(conn, case, signatures, storedsigs) + self.save_case_updates(conn, case, volunteers, signatures, storedsigs) return notices def clerk_new_case(self, case, volunteers, signatures): @@ -351,7 +370,7 @@ class DRNClerkBot(Task): case.parties_notified = True return notices - def save_case_updates(self, conn, case, signatures, storedsigs): + def save_case_updates(self, conn, case, volunteers, sigs, storedsigs): if case.status != case.original_status: case.last_action = case.status new = self.ALIASES[case.status][0] @@ -360,6 +379,16 @@ class DRNClerkBot(Task): repl = "{{" + self.tl_status + "|" + new + "}}" case.body = re.sub(search, repl, case.body) + newest_ts = max([stamp for (user, stamp) in sigs]) + newest_user = [usr for (usr, stamp) in sigs if stamp == newest_ts][0] + case.modify_user = newest_ts + case.modify_time = newest_user + + newest_vts = max([stamp for (usr, stamp) in sigs if usr in volunteers]) + newest_vuser = [usr for (usr, stamp) in sigs if stamp == newest_vts][0] + case.volunteer_user = newest_vol + case.volunteer_time = volsigs[1] + if case.new: self.save_new_case(conn, case) else: @@ -368,8 +397,8 @@ class DRNClerkBot(Task): with conn.cursor() as cursor: query1 = "DELETE FROM signature WHERE signature_case = ? AND signature_username = ? AND signature_timestamp = ?" query2 = "INSERT INTO signature VALUES (?, ?, ?, ?)" - removals = set(storedsigs) - set(signatures) - additions = set(signatures) - set(storedsigs) + removals = set(storedsigs) - set(sigs) + additions = set(sigs) - set(storedsigs) if removals: args = [(case.id, name, stamp) for (name, stamp) in removals] cursor.execute(query1, args) @@ -466,17 +495,87 @@ class DRNClerkBot(Task): self.logger.info("Done sending notices") + def update_chart(self, conn, site): + page = site.get_page(self.chart_title) + self.logger.info(u"Updating case status at [[{0}]]".format(page.title)) + statuses = self.compile_chart(conn) + text = page.get() + newtext = re.sub(u"(.*?)", + "\n" + statuses + "\n", + text, flags=re.DOTALL) + if newtext == text: + self.logger.info("Chart unchanged; not saving") + return + + newtext = re.sub("(.*?)", + "~~~ at ~~~~~", + newtext) + page.edit(newtext, summary, minor=True, bot=True) + self.logger.info(u"Chart saved to [[{0}]]".format(page.title)) + + def compile_chart(self, conn): + chart = "{{" + self.tl_chart_header + "}}\n" + query = "SELECT case_title, case_status, case_file_time FROM case" + with conn.cursor(oursql.DictCursor) as cursor: + cursor.execute(query) + for case in query: + chart += self.compile_row(case) + chart += "{{" + self.tl_chart_footer + "}}" + return chart + + def compile_row(self, case): + data = "|t={case_title}|s={status}" + data += "|cu={case_file_user}|cs={file_sortkey}|ct={file_time}" + if case["volunteer_user"]: + data += "|vu={case_volunteer_user}|vs={volunteer_sortkey}|vt={volunteer_time}" + case["volunteer_time"] = self.format_time(case["case_volunteer_time"]) + case["volunteer_sortkey"] = int(mktime(case["case_volunteer_time"].timetuple())) + data += "|mu={case_modify_user}|ms={modify_sortkey}|mt={modify_time}" + + case["case_title"] = case["case_title"].replace("|", "|") + case["status"] = self.translate_status(case["case_status"]) + case["file_time"] = self.format_time(case["case_file_time"]) + case["file_sortkey"] = int(mktime(case["case_file_time"].timetuple())) + case["modify_time"] = self.format_time(case["case_modify_time"]) + case["modify_sortkey"] = int(mktime(case["case_modify_time"].timetuple())) + row = "{{" + self.tl_chart_row + data.format(**case) + "}}\n" + return row + + def translate_status(self, num): + translations = { + STATUS_UNKNOWN: "u", + STATUS_NEW: "n", + STATUS_OPEN: "o", + STATUS_STALE: "s", + STATUS_NEEDASSIST: "e", + STATUS_REVIEW: "r", + STATUS_RESOLVED: "d", + STATUS_CLOSED: "c", + STATUS_ARCHIVE: "a" + } + return translations[num] + + def format_time(self, dt): + """Format a datetime into the standard MediaWiki timestamp format.""" + return dt.strftime("%H:%M, %d %b %Y") + class _Case(object): """A object representing a dispute resolution case.""" - def __init__(self, id_, title, status, last_action, file_time, close_time, - parties_notified, very_old_notified, last_volunteer_size, - new=False): + def __init__(self, id_, title, status, last_action, file_user, file_time, + modify_user, modify_time, volunteer_user, volunteer_time, + close_time, parties_notified, very_old_notified, + last_volunteer_size, new=False): self.id = id_ self.title = title self.status = status self.last_action = last_action + self.file_user = file_user self.file_time = file_time + self.modify_user = modify_user + self.modify_time = modify_time + self.volunteer_user = volunteer_user + self.volunteer_time = volunteer_time self.close_time = close_time self.parties_notified = parties_notified self.very_old_notified = very_old_notified diff --git a/earwigbot/tasks/schema/drn_clerkbot.sql b/earwigbot/tasks/schema/drn_clerkbot.sql index 23ce572..0bbca97 100644 --- a/earwigbot/tasks/schema/drn_clerkbot.sql +++ b/earwigbot/tasks/schema/drn_clerkbot.sql @@ -18,7 +18,12 @@ CREATE TABLE `case` ( `case_title` varchar(512) COLLATE utf8_unicode_ci DEFAULT NULL, `case_status` int(2) unsigned DEFAULT NULL, `case_last_action` int(2) unsigned DEFAULT NULL, + `case_file_user` varchar(512) COLLATE utf8_unicode_ci DEFAULT NULL, `case_file_time` int(10) unsigned DEFAULT NULL, + `case_modify_user` varchar(512) COLLATE utf8_unicode_ci DEFAULT NULL, + `case_modify_time` int(10) unsigned DEFAULT NULL, + `case_volunteer_user` varchar(512) COLLATE utf8_unicode_ci DEFAULT NULL, + `case_volunteer_time` int(10) unsigned DEFAULT NULL, `case_close_time` int(10) unsigned DEFAULT NULL, `case_parties_notified` tinyint(1) unsigned DEFAULT NULL, `case_very_old_notified` tinyint(1) unsigned DEFAULT NULL,