diff --git a/docs/API spec.md b/docs/API spec.md index 4e331bb..0ce6278 100644 --- a/docs/API spec.md +++ b/docs/API spec.md @@ -112,9 +112,10 @@ Context can consist of the following fields: - `client` - [Client](#Client) object - `webhook_url` - string - webhook url for given formatter - `message_type` - string - can be either `embed` or `compact` +- `feed_type` - string - type of the feed, can be either `recentchanges`, `abuselog` or `discussion` +- `event` - string - action called, should be the same as formatter event action - `categories` - {"new": set(), "removed": set()} - each set containing strings of added or removed categories for given page - `parsedcomment` - string - contains escaped and Markdown parsed summary (parsed_comment) of a log/edit action -- `event` - string - action called, should be the same as formatter event action - `comment_page` - dict - containing `fullUrl` and `article` with strings both to full article url and its name ### Util @@ -148,4 +149,4 @@ RcGcDw implements i18n with gettext and already exposes Translations instance wi **Path**: `src.api.hook` There are two decorator functions available in the module: `pre_hook` and `post_hook`. They don't take arguments and simply register the function as a hook. Pre-hook functions take the following arguments: `context` ([Context object](#Context)) and `change` (dict object with change). -Post-hook functions take the following arguments: `message` ([Discord message object](#DiscordMessage)), `metadata` ([Discord message metadata](#DiscordMessageMetadata)), `context` ([Context object](#Context)) and `change` (dictionary of main change body) \ No newline at end of file +Post-hook functions take the following arguments: `message` ([Discord message object](#DiscordMessage)), `metadata` ([Discord message metadata](#DiscordMessageMetadata)), `context` ([Context object](#Context)) and `change` (dictionary of main change body) diff --git a/extensions/base/discussions.py b/extensions/base/discussions.py index db609e5..f9aaaa8 100644 --- a/extensions/base/discussions.py +++ b/extensions/base/discussions.py @@ -331,7 +331,7 @@ def embed_discussion_article_comment(ctx: Context, post: dict): embed_author_discussions(post, embed) article_paths = ctx.comment_page if article_paths is None: - article_page = {"title": _("unknown"), "fullUrl": settings["fandom_discussions"]["wiki_url"]} # No page known + article_paths = {"title": _("unknown"), "fullUrl": settings["fandom_discussions"]["wiki_url"]} # No page known if not post["isReply"]: embed.event_type = "discussion/comment/post" embed["url"] = "{url}?commentId={commentId}".format(url=article_paths["fullUrl"], commentId=post["threadId"]) @@ -365,4 +365,4 @@ def compact_discussion_article_comment(ctx: Context, post: dict): "[{author}]({author_url}) created a [reply](<{url}?commentId={commentId}&replyId={replyId}>) to a [comment](<{url}?commentId={commentId}>) on [{article}](<{url}>)").format( author=author, author_url=author_url, url=article_paths["fullUrl"], article=article_paths["title"], commentId=post["threadId"], replyId=post["id"]) - return DiscordMessage("compact", event_type, ctx.webhook_url, content=message) \ No newline at end of file + return DiscordMessage("compact", event_type, ctx.webhook_url, content=message) diff --git a/extensions/hooks/__init__.py b/extensions/hooks/__init__.py index 669ca03..727931b 100644 --- a/extensions/hooks/__init__.py +++ b/extensions/hooks/__init__.py @@ -13,4 +13,6 @@ # You should have received a copy of the GNU General Public License # along with RcGcDw. If not, see . -import extensions.hooks.example_hook \ No newline at end of file +#import extensions.hooks.example_hook +#import extensions.hooks.usertalk +#import extensions.hooks.edit_alerts diff --git a/extensions/hooks/edit_alerts.py b/extensions/hooks/edit_alerts.py index 4339aff..e3cb8c4 100644 --- a/extensions/hooks/edit_alerts.py +++ b/extensions/hooks/edit_alerts.py @@ -13,6 +13,7 @@ # You should have received a copy of the GNU General Public License # along with RcGcDw. If not, see . +import ipaddress from src.api.hook import post_hook from src.configloader import settings @@ -27,6 +28,11 @@ from src.configloader import settings # }, # "requirements": [ # { +# "feed": [ +# "recentchanges", +# "abuselog", +# "discussion" +# ], # "action": [ # "edit", # "delete/delete", @@ -40,6 +46,14 @@ from src.configloader import settings # "title": [ # "PAGETITLE" # ], +# "forum": [ +# "FORUMNAME", +# null +# ], +# "is_reply": null, +# "namespace": [ +# 0 +# ], # "tags": [ # ["EDIT TAG", "AND EDIT TAG"], # ["OR EDIT TAG"] @@ -55,6 +69,16 @@ from src.configloader import settings # ["OR CATEGORY"] # ] # } +# ], +# "filter": [ +# "Section removal", +# "1" +# ], +# "af_action": [ +# "edit" +# ], +# "result": [ +# "disallow" # ] # } # ] @@ -92,28 +116,79 @@ def edit_alerts_hook(message, metadata, context, change): # For every requirement, if one of the requirements passes the alert gets executed for requirement in alert.get("requirements", []): try: + req_feed = requirement.get("feed", []) + if req_feed and context.feed_type not in req_feed: + raise RequirementNotMet req_action = requirement.get("action", []) # If current action isn't in config for this requirement AND current event type is not in the requirements in settings skip this requirement if req_action and context.event not in req_action and context.event.split('/', 1)[0] not in req_action: raise RequirementNotMet req_user = requirement.get("user", []) + change_user = None + change_anon = False + if context.feed_type == "discussion": + if change["creatorIp"]: + change_user = change["creatorIp"][1:] + change_anon = True + elif change["createdBy"]["name"]: + change_user = change["createdBy"]["name"] + change_anon = False + else: + change_user = change["user"] + if context.feed_type == "recentchanges": + change_anon = "anon" in change + else: + try: + ipaddress.ip_address(change_user) + except ValueError: + change_anon = False + else: + change_anon = True # If current user is not in config AND checkings for anon and user fail - if req_user and change["user"] not in req_user and ("@__anon__" if "anon" in change else "@__user__") not in req_user: + if req_user and change_user and change_user not in req_user and ("@__anon__" if change_anon else "@__user__") not in req_user: raise RequirementNotMet req_title = requirement.get("title", []) - if req_title and change["title"] not in req_title: + change_title = change["title"] + if context.feed_type == "discussion" and change_title is None: + change_title = change["_embedded"]["thread"][0]["title"] + if change_title is None and context.comment_page is not None: + change_title = context.comment_page["title"] + if req_title and change_title not in req_title: raise RequirementNotMet - check_group_requirements(change.get("tags", []), requirement.get("tags", [])) - if requirement.get("categories", []): - for req_cats in requirement.get("categories", []): - try: - check_group_requirements(context.categories.new, req_cats.get("added", [])) - check_group_requirements(context.categories.removed, req_cats.get("removed", [])) - except RequirementNotMet: - continue + if context.feed_type == "discussion": + req_forum = requirement.get("forum", []) + if req_forum and change["forumName"] not in req_forum: + raise RequirementNotMet + req_reply = requirement.get("is_reply", None) + if req_reply is not None and change["isReply"] == req_reply: + raise RequirementNotMet + else: + req_namespace = requirement.get("namespace", []) + if req_namespace and change["ns"] not in req_namespace: + raise RequirementNotMet + if context.feed_type == "recentchanges": + check_group_requirements(change.get("tags", []), requirement.get("tags", [])) + if requirement.get("categories", []): + for req_cats in requirement.get("categories", []): + try: + check_group_requirements(context.categories.new, req_cats.get("added", [])) + check_group_requirements(context.categories.removed, req_cats.get("removed", [])) + except RequirementNotMet: + continue + else: + break else: - break - else: + raise RequirementNotMet + elif context.feed_type == "abuselog": + req_filter = requirement.get("filter", []) + # Allow both filter id and name as id might be hidden when logged out + if req_filter and change["filter"] not in req_filter and change["filter_id"] not in req_filter: + raise RequirementNotMet + af_action = requirement.get("af_action", []) + if af_action and change["action"] not in af_action: + raise RequirementNotMet + req_result = requirement.get("result", []) + if req_result and change["result"] not in req_result: raise RequirementNotMet except RequirementNotMet: continue diff --git a/extensions/hooks/usertalk.py b/extensions/hooks/usertalk.py index 75911aa..1576676 100644 --- a/extensions/hooks/usertalk.py +++ b/extensions/hooks/usertalk.py @@ -25,14 +25,29 @@ from src.configloader import settings # } discord_users = settings.get("hooks", {}).get("usertalk", {}) +def add_mention(message, userid): + """This function adds a mention for the userid""" + message.webhook_object["content"] = (message.webhook_object.get("content", "") or "") + " <@{}>".format(userid) + if message.webhook_object["allowed_mentions"].get("users", []): + if userid not in message.webhook_object["allowed_mentions"]["users"]: + message.webhook_object["allowed_mentions"]["users"].append(userid) + else: + message.webhook_object["allowed_mentions"]["users"] = [userid] + @post_hook def usertalk_hook(message, metadata, context, change): - if discord_users and change["ns"] in [2, 3, 202] and not "/" in change["title"]: + if not discord_users: + return + if context.feed_type in ["recentchanges", "abuselog"] and change["ns"] in [2, 3, 202, 1200] and "/" not in change["title"]: username = change["title"].split(':', 1)[1] if discord_users.get(username, "") and username != change["user"]: - message.webhook_object["content"] = (message.webhook_object.get("content", "") or "") + " <@{}>".format(discord_users[username]) - if message.webhook_object["allowed_mentions"].get("users", []): - if discord_users[username] not in message.webhook_object["allowed_mentions"]["users"]: - message.webhook_object["allowed_mentions"]["users"].append(discord_users[username]) - else: - message.webhook_object["allowed_mentions"]["users"] = [discord_users[username]] + add_mention(message, discord_users[username]) + elif context.feed_type == "discussion" and context.event == "discussion/wall" and change["forumName"].endswith(' Message Wall'): + username = change["forumName"][:-13] + author = None + if change["creatorIp"]: + author = change["creatorIp"][1:] + elif change["createdBy"]["name"]: + author = change["createdBy"]["name"] + if discord_users.get(username, "") and username != author: + add_mention(message, discord_users[username]) diff --git a/src/api/context.py b/src/api/context.py index e469eae..8e38cc9 100644 --- a/src/api/context.py +++ b/src/api/context.py @@ -22,10 +22,11 @@ if TYPE_CHECKING: class Context: """Context object containing client and some metadata regarding specific formatter call, they are mainly used as a bridge between part that fetches the changes and API's formatters""" - def __init__(self, message_type: str, webhook_url: str, client: Client): + def __init__(self, message_type: str, feed_type: str, webhook_url: str, client: Client): self.client = client self.webhook_url = webhook_url self.message_type = message_type + self.feed_type = feed_type self.categories = None self.parsedcomment = None self.event = None @@ -41,4 +42,4 @@ class Context: self.comment_page = page def __str__(self): - return f"