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"