From 4c0f6ab766d74ff3b24d288f040c211c9ee960f4 Mon Sep 17 00:00:00 2001 From: GeoJulien Date: Thu, 29 Apr 2021 23:33:11 +0200 Subject: [PATCH 01/15] Add webhook --- isso/__init__.py | 8 +-- isso/ext/notifications.py | 111 +++++++++++++++++++++++++++++++++++++- 2 files changed, 115 insertions(+), 4 deletions(-) diff --git a/isso/__init__.py b/isso/__init__.py index 6c7476b3..e6e86a70 100644 --- a/isso/__init__.py +++ b/isso/__init__.py @@ -69,7 +69,7 @@ from isso.utils import http, JSONRequest, JSONResponse, hash from isso.views import comments -from isso.ext.notifications import Stdout, SMTP +from isso.ext.notifications import Stdout, SMTP, WebHook LOG_FORMAT = "%(asctime)s:%(levelname)s: %(message)s" logging.getLogger("werkzeug").setLevel(logging.WARN) @@ -107,10 +107,12 @@ def __init__(self, conf): subscribers = [] smtp_backend = False for backend in conf.getlist("general", "notify"): - if backend == "stdout": + if backend.lower() == "stdout": subscribers.append(Stdout(self)) - elif backend in ("smtp", "SMTP"): + elif backend.lower() == "smtp": smtp_backend = True + elif backend.lower() == "webhook": + subscribers.append(WebHook(self)) else: logger.warning("unknown notification backend '%s'", backend) if smtp_backend or conf.getboolean("general", "reply-notifications"): diff --git a/isso/ext/notifications.py b/isso/ext/notifications.py index 89953521..9593f73c 100644 --- a/isso/ext/notifications.py +++ b/isso/ext/notifications.py @@ -9,11 +9,12 @@ from _thread import start_new_thread from email.message import EmailMessage from email.utils import formatdate +from pathlib import Path +from string import Template from urllib.parse import quote import logging -logger = logging.getLogger("isso") try: import uwsgi @@ -21,12 +22,18 @@ uwsgi = None from isso import local +from isso.utils import http +from isso.views.comments import isurl def create_comment_action_url(uri, action, key): return uri + "/" + action + "/" + key +# Globals +logger = logging.getLogger("isso") + + class SMTPConnection(object): def __init__(self, conf): self.conf = conf @@ -250,3 +257,105 @@ def _delete_comment(self, id): def _activate_comment(self, thread, comment): logger.info("comment %(id)s activated" % thread) + + +class WebHook(object): + def __init__(self, isso_instance: object): + # store isso instance + self.isso_instance = isso_instance + # retrieve relevant configuration + self.public_endpoint = isso_instance.conf.get( + section="server", option="public-endpoint" + ) or local("host") + webhook_conf_section = isso_instance.conf.section("webhook") + self.wh_url = webhook_conf_section.get("url") + self.wh_template = webhook_conf_section.get("template") + + # check required settings + if not isurl(self.wh_url): + raise ValueError( + f"Web hook requires a valid URL. " + "The provided one is not correct: {self.wh_url}" + ) + + # check optional template + if not len(self.wh_template): + self.wh_template = None + logger.debug("No template provided.") + elif not Path(self.wh_template).is_file(): + raise FileExistsError(f"Invalid web hook template path: {self.wh_template}") + else: + self.wh_template = Path(self.wh_template) + + def __iter__(self): + + yield "comments.new:after-save", self._new_comment + + def _new_comment(self, thread: dict, comment: dict): + + if self.wh_template: + post_data = self.format(thread, comment) + else: + post_data = { + "author": comment.get("author", "Anonymous"), + "author_email": comment.get("email"), + "text": comment.get("text"), + } + print(post_data) + + self.send(post_data) + + def comment_urls(self, thread: dict, comment: dict) -> tuple: + + uri = "{}/id/{}".format(self.public_endpoint, comment.get("id")) + key = self.isso_instance.sign(comment.get("id")) + + url_activate = "{}/activate/{}".format(uri, key) + url_delete = "{}/delete/{}".format(uri, key) + url_view = "{}#isso-{}".format( + local("origin") + thread.get("uri"), comment.get("id") + ) + + return url_activate, url_delete, url_view + + def format( + self, + thread: dict, + comment: dict, + ) -> str: + # load template + with self.wh_template.open("r") as in_file: + tpl_json_data = json.load(in_file) + tpl_str = Template(json.dumps(tpl_json_data)) + print(type(tpl_str)) + + # build URLs + comment_urls = self.comment_urls(thread, comment) + + # substitute + out_msg = tpl_str.substitute( + AUTHOR_NAME=comment.get("author", "Anonymous"), + AUTHOR_EMAIL="<>".format(comment.get("email")), + AUTHOR_WEBSITE=comment.get("website"), + COMMENT_IP_ADDRESS=comment.get("remote_addr"), + COMMENT_TEXT=comment.get("text"), + COMMENT_URL_ACTIVATE=comment_urls[0], + COMMENT_URL_DELETE=comment_urls[1], + COMMENT_URL_VIEW=comment_urls[2], + ) + + return out_msg + + def send(self, structured_msg: dict) -> bool: + """Send the structured message as a notification to the class webhook URL. + + :param dict structured_msg: structured message to send + + :rtype: bool + """ + with http.curl("POST", self.wh_url, "/") as resp: + if resp: # may be None if request failed + return resp.status + + # if no error occurred + return True From d3aac38b6d76c15ad6907cd57c4eb86d9ed96bdd Mon Sep 17 00:00:00 2001 From: GeoJulien Date: Fri, 30 Apr 2021 13:17:39 +0200 Subject: [PATCH 02/15] Fix string formatting --- isso/ext/notifications.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/isso/ext/notifications.py b/isso/ext/notifications.py index 9593f73c..483a1f20 100644 --- a/isso/ext/notifications.py +++ b/isso/ext/notifications.py @@ -274,8 +274,8 @@ def __init__(self, isso_instance: object): # check required settings if not isurl(self.wh_url): raise ValueError( - f"Web hook requires a valid URL. " - "The provided one is not correct: {self.wh_url}" + "Web hook requires a valid URL. " + f"The provided one is not correct: {self.wh_url}" ) # check optional template From f5ed062e812edc88c6c82b6c120370cb76f981b1 Mon Sep 17 00:00:00 2001 From: GeoJulien Date: Fri, 30 Apr 2021 13:18:14 +0200 Subject: [PATCH 03/15] Fix string formatting --- isso/ext/notifications.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/isso/ext/notifications.py b/isso/ext/notifications.py index 483a1f20..ee7716c0 100644 --- a/isso/ext/notifications.py +++ b/isso/ext/notifications.py @@ -335,7 +335,7 @@ def format( # substitute out_msg = tpl_str.substitute( AUTHOR_NAME=comment.get("author", "Anonymous"), - AUTHOR_EMAIL="<>".format(comment.get("email")), + AUTHOR_EMAIL="<{}>".format(comment.get("email")), AUTHOR_WEBSITE=comment.get("website"), COMMENT_IP_ADDRESS=comment.get("remote_addr"), COMMENT_TEXT=comment.get("text"), From ed1a93bd62b3b3664e6e7f99a8ee4f925b72ad80 Mon Sep 17 00:00:00 2001 From: GeoJulien Date: Fri, 30 Apr 2021 13:21:25 +0200 Subject: [PATCH 04/15] Fix flake8 --- isso/tests/test_vote.py | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/isso/tests/test_vote.py b/isso/tests/test_vote.py index 72cb54e5..a01d31a7 100644 --- a/isso/tests/test_vote.py +++ b/isso/tests/test_vote.py @@ -1,4 +1,4 @@ -# -*- encoding: utf-8 -*- +from __future__ import unicode_literals import json import tempfile @@ -11,6 +11,7 @@ from fixtures import curl, loads, FakeIP, JSONClient + http.curl = curl @@ -19,7 +20,8 @@ def setUp(self): self.path = tempfile.NamedTemporaryFile().name def makeClient(self, ip): - conf = config.load(config.default_file()) + + conf = config.load(pkg_resources.resource_filename("isso", "defaults.ini")) conf.set("general", "dbpath", self.path) conf.set("guard", "enabled", "off") conf.set("hash", "algorithm", "none") @@ -33,12 +35,18 @@ class App(Isso, core.Mixin): return JSONClient(app, Response) def testZeroLikes(self): - rv = self.makeClient("127.0.0.1").post("/new?uri=test", data=json.dumps({"text": "..."})) + + rv = self.makeClient("127.0.0.1").post( + "/new?uri=test", data=json.dumps({"text": "..."}) + ) self.assertEqual(loads(rv.data)["likes"], 0) self.assertEqual(loads(rv.data)["dislikes"], 0) def testSingleLike(self): - self.makeClient("127.0.0.1").post("/new?uri=test", data=json.dumps({"text": "..."})) + + self.makeClient("127.0.0.1").post( + "/new?uri=test", data=json.dumps({"text": "..."}) + ) rv = self.makeClient("0.0.0.0").post("/id/1/like") self.assertEqual(rv.status_code, 200) @@ -48,12 +56,16 @@ def testSelfLike(self): bob = self.makeClient("127.0.0.1") bob.post("/new?uri=test", data=json.dumps({"text": "..."})) rv = bob.post("/id/1/like") + rv = bob.post("/id/1/like") self.assertEqual(rv.status_code, 200) self.assertEqual(loads(rv.data)["likes"], 0) def testMultipleLikes(self): - self.makeClient("127.0.0.1").post("/new?uri=test", data=json.dumps({"text": "..."})) + + self.makeClient("127.0.0.1").post( + "/new?uri=test", data=json.dumps({"text": "..."}) + ) for num in range(15): rv = self.makeClient("1.2.%i.0" % num).post("/id/1/like") self.assertEqual(rv.status_code, 200) @@ -65,7 +77,10 @@ def testVoteOnNonexistentComment(self): self.assertEqual(loads(rv.data), None) def testTooManyLikes(self): - self.makeClient("127.0.0.1").post("/new?uri=test", data=json.dumps({"text": "..."})) + + self.makeClient("127.0.0.1").post( + "/new?uri=test", data=json.dumps({"text": "..."}) + ) for num in range(256): rv = self.makeClient("1.2.%i.0" % num).post("/id/1/like") self.assertEqual(rv.status_code, 200) @@ -76,7 +91,9 @@ def testTooManyLikes(self): self.assertEqual(loads(rv.data)["likes"], num + 1) def testDislike(self): - self.makeClient("127.0.0.1").post("/new?uri=test", data=json.dumps({"text": "..."})) + self.makeClient("127.0.0.1").post( + "/new?uri=test", data=json.dumps({"text": "..."}) + ) rv = self.makeClient("1.2.3.4").post("/id/1/dislike") self.assertEqual(rv.status_code, 200) From 28cce88ca29518dbf5da31f16e443ea56cc3159a Mon Sep 17 00:00:00 2001 From: GeoJulien Date: Fri, 30 Apr 2021 13:24:48 +0200 Subject: [PATCH 05/15] Make it compatible with Python 3.5 ?! --- isso/ext/notifications.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/isso/ext/notifications.py b/isso/ext/notifications.py index ee7716c0..ea8d8b45 100644 --- a/isso/ext/notifications.py +++ b/isso/ext/notifications.py @@ -275,7 +275,7 @@ def __init__(self, isso_instance: object): if not isurl(self.wh_url): raise ValueError( "Web hook requires a valid URL. " - f"The provided one is not correct: {self.wh_url}" + "The provided one is not correct: {}".format(self.wh_url) ) # check optional template @@ -283,7 +283,7 @@ def __init__(self, isso_instance: object): self.wh_template = None logger.debug("No template provided.") elif not Path(self.wh_template).is_file(): - raise FileExistsError(f"Invalid web hook template path: {self.wh_template}") + raise FileExistsError("Invalid web hook template path: {}".format(self.wh_template)) else: self.wh_template = Path(self.wh_template) From 226d6f49fe5e364409b2587496d107a3487f907d Mon Sep 17 00:00:00 2001 From: GeoJulien Date: Fri, 30 Apr 2021 13:35:00 +0200 Subject: [PATCH 06/15] Add myself --- CONTRIBUTORS.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index b2db2c67..4ab8dc8d 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -117,6 +117,9 @@ In chronological order: * Update Polish translation * Redirect to comment after moderation +* Julien Moura @Guts + * Notify through web hooks + * fliiiix * Import disqus posts without Email * Import disqus post without IP From 432c02b2c2e992212bf0798b94178e981b02eb19 Mon Sep 17 00:00:00 2001 From: GeoJulien Date: Fri, 30 Apr 2021 14:21:23 +0200 Subject: [PATCH 07/15] Add default webhook section --- isso/isso.cfg | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/isso/isso.cfg b/isso/isso.cfg index 9c927ce4..edcb9b70 100644 --- a/isso/isso.cfg +++ b/isso/isso.cfg @@ -271,3 +271,15 @@ base = # Limit the number of elements to return for each thread. limit = 100 + +[webhook] +# Isso can notify you on new comments via web hook. +# By default, it sends a POST data with new comment metadata: author, author email, author website, text, moderation URLs (activation, deletion, view). +# It's also possible to add a JSON template (using Python string.Template) to customize the POST data. Useful to fit some tools abilities ike Slack, Matrix, Teams, etc. +# An example for Slack with block builder is prodived in the contrib folder. + +# webhook URL +url = + +# path to the JSON template. Optional. +template = From 55c5f0dc906dbab51c9a81572b1a2a4c4c7ecd21 Mon Sep 17 00:00:00 2001 From: GeoJulien Date: Fri, 30 Apr 2021 14:48:35 +0200 Subject: [PATCH 08/15] Clean up and docstrings --- isso/ext/notifications.py | 118 ++++++++++++++++++++++++++++---------- 1 file changed, 88 insertions(+), 30 deletions(-) diff --git a/isso/ext/notifications.py b/isso/ext/notifications.py index ea8d8b45..ab843346 100644 --- a/isso/ext/notifications.py +++ b/isso/ext/notifications.py @@ -21,14 +21,14 @@ except ImportError: uwsgi = None -from isso import local -from isso.utils import http +from isso import dist, local from isso.views.comments import isurl def create_comment_action_url(uri, action, key): return uri + "/" + action + "/" + key +from requests import Session # Globals logger = logging.getLogger("isso") @@ -260,7 +260,18 @@ def _activate_comment(self, thread, comment): class WebHook(object): + """Notification handler for web hook. + + :param isso_instance: Isso application instance. Used to get moderation key. + :type isso_instance: object + + :raises ValueError: if the provided URL is not valid + :raises FileExistsError: if the provided JSON template doesn't exist + :raises TypeError: if the provided template file is not a JSON + """ + def __init__(self, isso_instance: object): + """Instanciate class.""" # store isso instance self.isso_instance = isso_instance # retrieve relevant configuration @@ -283,30 +294,57 @@ def __init__(self, isso_instance: object): self.wh_template = None logger.debug("No template provided.") elif not Path(self.wh_template).is_file(): - raise FileExistsError("Invalid web hook template path: {}".format(self.wh_template)) + raise FileExistsError( + "Invalid web hook template path: {}".format(self.wh_template) + ) + elif not Path(self.wh_template).suffix == ".json": + raise TypeError()( + "Template must be a JSON file: {}".format(self.wh_template) + ) else: self.wh_template = Path(self.wh_template) def __iter__(self): - yield "comments.new:after-save", self._new_comment + yield "comments.new:after-save", self.new_comment - def _new_comment(self, thread: dict, comment: dict): + def new_comment(self, thread: dict, comment: dict): + """Triggered when a new comment is saved. - if self.wh_template: - post_data = self.format(thread, comment) - else: - post_data = { - "author": comment.get("author", "Anonymous"), - "author_email": comment.get("email"), - "text": comment.get("text"), - } - print(post_data) + :param thread: comment thread + :type thread: dict + :param comment: comment object + :type comment: dict + """ + + try: + # get moderation URLs + moderation_urls = self.moderation_urls(thread, comment) - self.send(post_data) + if self.wh_template: + post_data = self.render_template(thread, comment, moderation_urls) + else: + post_data = { + "author": comment.get("author", "Anonymous"), + "author_email": comment.get("email"), + "text": comment.get("text"), + } + + self.send(post_data) + except Exception as err: + logger.error(err) - def comment_urls(self, thread: dict, comment: dict) -> tuple: + def moderation_urls(self, thread: dict, comment: dict) -> tuple: + """Helper to build comment related URLs (deletion, activation, etc.). + :param thread: comment thread + :type thread: dict + :param comment: comment object + :type comment: dict + + :return: tuple of URS in alpha order (activate, admin, delete, view) + :rtype: tuple + """ uri = "{}/id/{}".format(self.public_endpoint, comment.get("id")) key = self.isso_instance.sign(comment.get("id")) @@ -318,11 +356,21 @@ def comment_urls(self, thread: dict, comment: dict) -> tuple: return url_activate, url_delete, url_view - def format( - self, - thread: dict, - comment: dict, + def render_template( + self, thread: dict, comment: dict, moderation_urls: tuple ) -> str: + """Format comment information as webhook payload filling the specified template. + + :param thread: isso thread + :type thread: dict + :param comment: isso comment + :type comment: dict + :param moderation_urls: comment moderation URLs + :type comment: tuple + + :return: formatted message from template + :rtype: str + """ # load template with self.wh_template.open("r") as in_file: tpl_json_data = json.load(in_file) @@ -335,27 +383,37 @@ def format( # substitute out_msg = tpl_str.substitute( AUTHOR_NAME=comment.get("author", "Anonymous"), - AUTHOR_EMAIL="<{}>".format(comment.get("email")), - AUTHOR_WEBSITE=comment.get("website"), + AUTHOR_EMAIL="<{}>".format(comment.get("email", "")), + AUTHOR_WEBSITE=comment.get("website", ""), COMMENT_IP_ADDRESS=comment.get("remote_addr"), COMMENT_TEXT=comment.get("text"), - COMMENT_URL_ACTIVATE=comment_urls[0], - COMMENT_URL_DELETE=comment_urls[1], - COMMENT_URL_VIEW=comment_urls[2], + COMMENT_URL_ACTIVATE=moderation_urls[0], + COMMENT_URL_DELETE=moderation_urls[1], + COMMENT_URL_VIEW=moderation_urls[2], ) return out_msg - def send(self, structured_msg: dict) -> bool: + def send(self, structured_msg: str) -> bool: """Send the structured message as a notification to the class webhook URL. - :param dict structured_msg: structured message to send + :param str structured_msg: structured message to send :rtype: bool """ - with http.curl("POST", self.wh_url, "/") as resp: - if resp: # may be None if request failed - return resp.status + with Session() as requests_session: + + # send requests + response = requests_session.post( + url=self.wh_url, + data=structured_msg, + headers={ + "Content-Type": "application/json", + "User-Agent": "Isso/{0} (+https://posativ.org/isso)".format( + dist.version + ), + }, + ) # if no error occurred return True From 2c963498799858c14bd114103383e8789791e59f Mon Sep 17 00:00:00 2001 From: GeoJulien Date: Sat, 1 May 2021 09:13:56 +0200 Subject: [PATCH 09/15] Handle HTTP errors --- isso/ext/notifications.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/isso/ext/notifications.py b/isso/ext/notifications.py index ab843346..61e0d902 100644 --- a/isso/ext/notifications.py +++ b/isso/ext/notifications.py @@ -28,7 +28,7 @@ def create_comment_action_url(uri, action, key): return uri + "/" + action + "/" + key -from requests import Session +from requests import HTTPError, Session # Globals logger = logging.getLogger("isso") @@ -415,5 +415,15 @@ def send(self, structured_msg: str) -> bool: }, ) + try: + response.raise_for_status() + logger.info("Web hook sent to %s" % self.wh_url) + except HTTPError as err: + logger.error( + "Something went wrong during POST request to the web hook. Trace: %s" + % err + ) + return False + # if no error occurred return True From 2562027a08223e0ae798755b5ac949f0f6fca00d Mon Sep 17 00:00:00 2001 From: GeoJulien Date: Sat, 1 May 2021 12:25:32 +0200 Subject: [PATCH 10/15] Add webhook template for Slack --- contrib/webhook_template_slack.json | 70 +++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 contrib/webhook_template_slack.json diff --git a/contrib/webhook_template_slack.json b/contrib/webhook_template_slack.json new file mode 100644 index 00000000..57339e7e --- /dev/null +++ b/contrib/webhook_template_slack.json @@ -0,0 +1,70 @@ +{ + "blocks": [ + { + "type": "header", + "text": { + "type": "plain_text", + "text": ":speech_balloon: New comment posted", + "emoji": true + } + }, + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "*Author:* $AUTHOR_NAME $AUTHOR_EMAIL $AUTHOR_WEBSITE" + } + }, + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "*IP:* $COMMENT_IP_ADDRESS" + } + }, + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "*Comment:*\n$COMMENT_TEXT" + } + }, + { + "type": "divider" + }, + { + "type": "actions", + "elements": [ + { + "type": "button", + "text": { + "type": "plain_text", + "emoji": true, + "text": ":eye-in-speech-bubble: View comment" + }, + "url": "$COMMENT_URL_VIEW" + }, + { + "type": "button", + "text": { + "type": "plain_text", + "emoji": true, + "text": ":white_check_mark: Approve" + }, + "style": "primary", + "url": "$COMMENT_URL_ACTIVATE" + }, + { + "type": "button", + "text": { + "type": "plain_text", + "emoji": true, + "text": ":wastebasket: Deny" + }, + "style": "danger", + "url": "$COMMENT_URL_DELETE" + } + ] + } + ] +} From db0f069b64bb664955e30ada8dd43be881a80894 Mon Sep 17 00:00:00 2001 From: GeoJulien Date: Sat, 1 May 2021 12:26:00 +0200 Subject: [PATCH 11/15] Align tox dependencies with package --- tox.ini | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tox.ini b/tox.ini index 2382932f..e583bc72 100755 --- a/tox.ini +++ b/tox.ini @@ -11,11 +11,12 @@ commands = [testenv:debian] deps= + bleach + flask-caching>=1.9,<1.11 + html5lib + ipaddr==2.1.10 itsdangerous Jinja2 misaka>=2.0,<3.0 - mistune>=3.1,<4.0 - html5lib + passlib==1.5.3 werkzeug>=1.0 - bleach - flask-caching>=1.9 From 7efc7118950c5fb546da0c13b3a8054b8177baf8 Mon Sep 17 00:00:00 2001 From: GeoJulien Date: Sat, 1 May 2021 12:33:59 +0200 Subject: [PATCH 12/15] Add requests as dependency --- tox.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/tox.ini b/tox.ini index e583bc72..9d499ab4 100755 --- a/tox.ini +++ b/tox.ini @@ -19,4 +19,5 @@ deps= Jinja2 misaka>=2.0,<3.0 passlib==1.5.3 + requests>=2.25,<2.26 werkzeug>=1.0 From 5403ce635764bf398f780c7906f42bc388e8b5b1 Mon Sep 17 00:00:00 2001 From: GeoJulien Date: Sat, 1 May 2021 12:34:38 +0200 Subject: [PATCH 13/15] Remove unused var --- isso/ext/notifications.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/isso/ext/notifications.py b/isso/ext/notifications.py index 61e0d902..d681afbf 100644 --- a/isso/ext/notifications.py +++ b/isso/ext/notifications.py @@ -375,10 +375,6 @@ def render_template( with self.wh_template.open("r") as in_file: tpl_json_data = json.load(in_file) tpl_str = Template(json.dumps(tpl_json_data)) - print(type(tpl_str)) - - # build URLs - comment_urls = self.comment_urls(thread, comment) # substitute out_msg = tpl_str.substitute( From d0f2bda0d1859de34525bd1b585d11d1406af543 Mon Sep 17 00:00:00 2001 From: GeoJulien Date: Sat, 1 May 2021 12:38:56 +0200 Subject: [PATCH 14/15] Complete post data without template --- isso/ext/notifications.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/isso/ext/notifications.py b/isso/ext/notifications.py index d681afbf..b7751af2 100644 --- a/isso/ext/notifications.py +++ b/isso/ext/notifications.py @@ -308,13 +308,16 @@ def __iter__(self): yield "comments.new:after-save", self.new_comment - def new_comment(self, thread: dict, comment: dict): + def new_comment(self, thread: dict, comment: dict) -> bool: """Triggered when a new comment is saved. :param thread: comment thread :type thread: dict :param comment: comment object :type comment: dict + + :return: True if eveythring went fine. False if not. + :rtype: bool """ try: @@ -325,14 +328,22 @@ def new_comment(self, thread: dict, comment: dict): post_data = self.render_template(thread, comment, moderation_urls) else: post_data = { - "author": comment.get("author", "Anonymous"), + "author_name": comment.get("author", "Anonymous"), "author_email": comment.get("email"), - "text": comment.get("text"), + "author_website": comment.get("website"), + "comment_ip_address": comment.get("remote_addr"), + "comment_text": comment.get("text"), + "comment_url_activate": moderation_urls[0], + "comment_url_delete": moderation_urls[1], + "comment_url_view": moderation_urls[2], } self.send(post_data) except Exception as err: logger.error(err) + return False + + return True def moderation_urls(self, thread: dict, comment: dict) -> tuple: """Helper to build comment related URLs (deletion, activation, etc.). From 83e0dae005a8e6e2d5f260ecfd4d0f8de5af3afe Mon Sep 17 00:00:00 2001 From: GeoJulien Date: Thu, 17 Jun 2021 07:01:02 +0200 Subject: [PATCH 15/15] Use json loads/dumps to prevent encoding aleas --- isso/ext/notifications.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/isso/ext/notifications.py b/isso/ext/notifications.py index b7751af2..9566bc8f 100644 --- a/isso/ext/notifications.py +++ b/isso/ext/notifications.py @@ -408,12 +408,15 @@ def send(self, structured_msg: str) -> bool: :rtype: bool """ + # load the message to ensure encoding + msg_json = json.loads(structured_msg) + with Session() as requests_session: # send requests response = requests_session.post( url=self.wh_url, - data=structured_msg, + json=json.dumps(msg_json), headers={ "Content-Type": "application/json", "User-Agent": "Isso/{0} (+https://posativ.org/isso)".format(