diff --git a/settings.json.example b/settings.json.example index b8e645b..256b2fa 100644 --- a/settings.json.example +++ b/settings.json.example @@ -30,6 +30,7 @@ "wiki_bot_password": "", "show_added_categories": true, "show_bots": false, + "show_abuselog": false, "logging": { "version": 1, "disable_existing_loggers": false, diff --git a/src/misc.py b/src/misc.py index 5b88130..093bb1d 100644 --- a/src/misc.py +++ b/src/misc.py @@ -30,7 +30,7 @@ _ = misc.gettext misc_logger = logging.getLogger("rcgcdw.misc") -data_template = {"rcid": 99999999999, "discussion_id": 0, +data_template = {"rcid": 99999999999, "discussion_id": 0, "abuse_log_id": 0, "daily_overview": {"edits": None, "new_files": None, "admin_actions": None, "bytes_changed": None, "new_articles": None, "unique_editors": None, "day_score": None, "days_tracked": 0}} diff --git a/src/rc.py b/src/rc.py index 6cd39b7..ed647d5 100644 --- a/src/rc.py +++ b/src/rc.py @@ -9,8 +9,9 @@ from src.configloader import settings from src.misc import WIKI_SCRIPT_PATH, WIKI_API_PATH, messagequeue, datafile, send_simple, safe_read, LinkParser from src.exceptions import MWError from src.session import session -from src.rc_formatters import compact_formatter, embed_formatter +from src.rc_formatters import compact_formatter, embed_formatter, compact_abuselog_formatter, embed_abuselog_formatter from src.i18n import rc +from collections import OrderedDict _ = rc.gettext @@ -23,8 +24,10 @@ supported_logs = ["protect/protect", "protect/modify", "protect/unprotect", "upl # Set the proper formatter if settings["appearance"]["mode"] == "embed": appearance_mode = embed_formatter + abuselog_appearance_mode = embed_abuselog_formatter elif settings["appearance"]["mode"] == "compact": appearance_mode = compact_formatter + abuselog_appearance_mode = compact_abuselog_formatter else: logger.critical("Unknown formatter!") sys.exit(1) @@ -38,6 +41,7 @@ class Recent_Changes_Class(object): self.ids = [] self.map_ips = {} self.recent_id = 0 + self.recent_abuse_id = 0 self.downtimecredibility = 0 self.last_downtime = 0 self.tags = {} @@ -118,6 +122,18 @@ class Recent_Changes_Class(object): logger.debug("Most recent rcid is: {}".format(self.recent_id)) return self.recent_id + def construct_params(self, amount): + params = OrderedDict(action="query", format="json") + params["list"] = "recentchanges|abuselog" if settings.get("show_abuselog", False) else "recentchanges" + params["rcshow"] = "" if settings.get("show_bots", False) else "!bot" + params["rcprop"] = "title|redirect|timestamp|ids|loginfo|parsedcomment|sizes|flags|tags|user" + params["rclimit"] = amount + params["rctype"] = "edit|new|log|external|categorize" if settings.get("show_added_categories", True) else "edit|new|log|external" + if settings.get("show_abuselog", False): + params["afllimit"] = amount + params["aflprop"] = "ids|user|title|action|result|timestamp|hidden|revid|filter" + return params + def fetch_changes(self, amount, clean=False): """Fetches the :amount: of changes from the wiki. Returns None on error and int of rcid of latest change if succeeded""" @@ -125,20 +141,26 @@ class Recent_Changes_Class(object): if len(self.ids) == 0: logger.debug("ids is empty, triggering clean fetch") clean = True - changes = self.safe_request( - "{wiki}?action=query&format=json&list=recentchanges{show_bots}&rcprop=title%7Credirect%7Ctimestamp%7Cids%7Cloginfo%7Cparsedcomment%7Csizes%7Cflags%7Ctags%7Cuser&rclimit={amount}&rctype=edit%7Cnew%7Clog%7Cexternal{categorize}".format( - wiki=WIKI_API_PATH, amount=amount, categorize="%7Ccategorize" if settings["show_added_categories"] else "", show_bots="&rcshow=!bot" if settings["show_bots"] is False else "")) - if changes: + raw_changes = self.safe_request(WIKI_API_PATH, params=self.construct_params(amount)) + # action=query&format=json&list=recentchanges%7Cabuselog&rcprop=title%7Credirect%7Ctimestamp%7Cids%7Cloginfo%7Cparsedcomment%7Csizes%7Cflags%7Ctags%7Cuser&rcshow=!bot&rclimit=20&rctype=edit%7Cnew%7Clog%7Cexternal&afllimit=10&aflprop=ids%7Cuser%7Ctitle%7Caction%7Cresult%7Ctimestamp%7Chidden%7Crevid%7Cfilter + if raw_changes: try: - changes = changes.json()['query']['recentchanges'] + raw_changes = raw_changes.json() + changes = raw_changes['query']['recentchanges'] + # {"batchcomplete":"","warnings":{"query":{"*":"Unrecognized value for parameter \"list\": abuselog."}}} changes.reverse() + if "warnings" in raw_changes: + warnings = raw_changes.get("warnings", {"query": {"*": ""}}) + if warnings["query"]["*"] == "Unrecognized value for parameter \"list\": abuselog.": + settings["show_abuselog"] = False + logger.warning("AbuseLog extension is not enabled on the wiki. Disabling the function...") except ValueError: logger.warning("ValueError in fetching changes") - logger.warning("Changes URL:" + changes.url) + logger.warning("Changes URL:" + raw_changes.url) self.downtime_controller() return None except KeyError: - logger.warning("Wiki returned %s" % (changes.json())) + logger.warning("Wiki returned %s" % (raw_changes)) return None else: if self.downtimecredibility > 0: @@ -203,11 +225,19 @@ class Recent_Changes_Class(object): logger.debug("Rejected {val}".format(val=change["rcid"])) continue essential_info(change, categorize_events.get(change.get("revid"), None)) + if "abuselog" in raw_changes["query"]: + abuse_log = raw_changes['query']['recentchanges'] + abuse_log.reverse() + for entry in abuse_log: + abuselog_processing(entry, self) return change["rcid"] - def safe_request(self, url): + def safe_request(self, url, params=None): try: - request = self.session.get(url, timeout=10, allow_redirects=False) + if params: + request = self.session.get(url, params=params, timeout=10, allow_redirects=False) + else: + request = self.session.get(url, timeout=10, allow_redirects=False) except requests.exceptions.Timeout: logger.warning("Reached timeout error for request on link {url}".format(url=url)) self.downtime_controller() @@ -354,3 +384,6 @@ def essential_info(change, changed_categories): if identification_string in settings["ignored"]: return appearance_mode(identification_string, change, parsed_comment, changed_categories, recent_changes) + +def abuselog_processing(entry, recent_changes): + abuselog_appearance_mode(entry, recent_changes) \ No newline at end of file diff --git a/src/rc_formatters.py b/src/rc_formatters.py index cf4eba2..3f95822 100644 --- a/src/rc_formatters.py +++ b/src/rc_formatters.py @@ -19,9 +19,43 @@ ngettext = rc_formatters.ngettext logger = logging.getLogger("rcgcdw.rc_formatters") #from src.rcgcdw import recent_changes, ngettext, logger, profile_field_name, LinkParser, pull_comment +abusefilter_results = {"": _("None"), "warn": _("Warning issued"), "block": _("**Blocked user**"), "tag": _("Tagged the edit"), "disallow": _("Disallowed the action"), "rangeblock": _("IP range blocked"), "throttle": _("Throttled actions"), "blockautopromote": _("Blocked role autopromotion"), "degroup": _("**Removed from privilaged groups**")} +abusefilter_actions = {"edit": _("Edit"), "upload": _("Upload"), "move": _("Move"), "stashupload": _("Stash upload"), "delete": _("Deletion"), "createaccount": _("Account creation"), "autocreateaccount": _("Auto account creation")} LinkParser = LinkParser() +def format_user(change, recent_changes, action): + if "anon" in change: + author_url = create_article_path("Special:Contributions/{user}".format( + user=change["user"].replace(" ", "_"))) # Replace here needed in case of #75 + logger.debug("current user: {} with cache of IPs: {}".format(change["user"], recent_changes.map_ips.keys())) + if change["user"] not in list(recent_changes.map_ips.keys()): + contibs = safe_read(recent_changes.safe_request( + "{wiki}?action=query&format=json&list=usercontribs&uclimit=max&ucuser={user}&ucstart={timestamp}&ucprop=".format( + wiki=WIKI_API_PATH, user=change["user"], timestamp=change["timestamp"])), "query", "usercontribs") + if contibs is None: + logger.warning( + "WARNING: Something went wrong when checking amount of contributions for given IP address") + change["user"] = change["user"] + "(?)" + else: + recent_changes.map_ips[change["user"]] = len(contibs) + logger.debug( + "Current params user {} and state of map_ips {}".format(change["user"], recent_changes.map_ips)) + change["user"] = "{author} ({contribs})".format(author=change["user"], contribs=len(contibs)) + else: + logger.debug( + "Current params user {} and state of map_ips {}".format(change["user"], recent_changes.map_ips)) + if action in ("edit", "new"): + recent_changes.map_ips[change["user"]] += 1 + change["user"] = "{author} ({amount})".format(author=change["user"], + amount=recent_changes.map_ips[change["user"]]) + else: + author_url = create_article_path("User:{}".format(change["user"].replace(" ", "_"))) + return change["user"], author_url + +def compact_abuselog_formatter(change, recent_changes): + pass + def compact_formatter(action, change, parsed_comment, categories, recent_changes): if action != "suppressed": author_url = link_formatter(create_article_path("User:{user}".format(user=change["user"]))) @@ -330,36 +364,25 @@ def compact_formatter(action, change, parsed_comment, categories, recent_changes event=action, author=author, author_url=author_url, support=settings["support"]) send_to_discord(DiscordMessage("compact", action, settings["webhookURL"], content=content)) +def embed_abuselog_formatter(change, recent_changes): + action = "abuselog/{}".format(change["result"]) + embed = DiscordMessage("embed", action, settings["webhookURL"]) + raw_username = change["user"] + change["user"], author_url = format_user(change, recent_changes, action) + embed["title"] = _("{user} triggered \"{abuse_filter}\"").format(user=raw_username, abuse_filter=change["filter"]) + embed.add_field(_("Performed"), abusefilter_actions.get(change["action"], _("Unknown"))) + embed.add_field(_("Action taken"), abusefilter_results.get(change["result"], _("Unknown"))) + embed.add_field(_("Title"), change.get("title", _("Unknown"))) + embed.finish_embed() + send_to_discord(embed) + def embed_formatter(action, change, parsed_comment, categories, recent_changes): embed = DiscordMessage("embed", action, settings["webhookURL"]) if parsed_comment is None: parsed_comment = _("No description provided") if action != "suppressed": - if "anon" in change: - author_url = create_article_path("Special:Contributions/{user}".format(user=change["user"].replace(" ", "_"))) # Replace here needed in case of #75 - logger.debug("current user: {} with cache of IPs: {}".format(change["user"], recent_changes.map_ips.keys())) - if change["user"] not in list(recent_changes.map_ips.keys()): - contibs = safe_read(recent_changes.safe_request( - "{wiki}?action=query&format=json&list=usercontribs&uclimit=max&ucuser={user}&ucstart={timestamp}&ucprop=".format( - wiki=WIKI_API_PATH, user=change["user"], timestamp=change["timestamp"])), "query", "usercontribs") - if contibs is None: - logger.warning( - "WARNING: Something went wrong when checking amount of contributions for given IP address") - change["user"] = change["user"] + "(?)" - else: - recent_changes.map_ips[change["user"]] = len(contibs) - logger.debug("Current params user {} and state of map_ips {}".format(change["user"], recent_changes.map_ips)) - change["user"] = "{author} ({contribs})".format(author=change["user"], contribs=len(contibs)) - else: - logger.debug( - "Current params user {} and state of map_ips {}".format(change["user"], recent_changes.map_ips)) - if action in ("edit", "new"): - recent_changes.map_ips[change["user"]] += 1 - change["user"] = "{author} ({amount})".format(author=change["user"], - amount=recent_changes.map_ips[change["user"]]) - else: - author_url = create_article_path("User:{}".format(change["user"].replace(" ", "_"))) + change["user"], author_url = format_user(change, recent_changes, action) embed.set_author(change["user"], author_url) if action in ("edit", "new"): # edit or new page editsize = change["newlen"] - change["oldlen"] @@ -790,4 +813,4 @@ def embed_formatter(action, change, parsed_comment, categories, recent_changes): del_cat = (_("**Removed**: ") + ", ".join(list(categories["removed"])[0:16]) + ("" if len(categories["removed"])<=15 else _(" and {} more").format(len(categories["removed"])-15))) if categories["removed"] else "" embed.add_field(_("Changed categories"), new_cat + del_cat) embed.finish_embed() - send_to_discord(embed) \ No newline at end of file + send_to_discord(embed)