From a6b192fbd7b957002d995317f3229275d10b605e Mon Sep 17 00:00:00 2001 From: Guilhem Saurel Date: Sun, 18 Jul 2021 22:56:27 +0200 Subject: [PATCH 001/141] v3.1.0 --- CHANGELOG.md | 2 ++ pyproject.toml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b8dd191..e5f6e7b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [3.1.0] - 2021-07-18 + - Publish on PyPI & Docker Hub with Github Actions in [#10](https://github.com/nim65s/matrix-webhook/pull/10) by [@nim65s](https://github.com/) diff --git a/pyproject.toml b/pyproject.toml index b85b4ca..4e38783 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "matrix-webhook" -version = "3.0.0" +version = "3.1.0" description = "Post a message to a matrix room with a simple HTTP POST" authors = ["Guilhem Saurel "] license = "BSD-2-Clause" From c86145f794c8cd45777b06a3fdfba6743579debb Mon Sep 17 00:00:00 2001 From: Guilhem Saurel Date: Sun, 18 Jul 2021 22:59:49 +0200 Subject: [PATCH 002/141] v3.1.1 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 4e38783..ab1b583 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "matrix-webhook" -version = "3.1.0" +version = "3.1.1" description = "Post a message to a matrix room with a simple HTTP POST" authors = ["Guilhem Saurel "] license = "BSD-2-Clause" From 8b32c972b8e97e37b6a948e7478f80fa96b5334b Mon Sep 17 00:00:00 2001 From: Guilhem Saurel Date: Sun, 18 Jul 2021 23:08:40 +0200 Subject: [PATCH 003/141] typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a0fe1fe..29a96ea 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,7 @@ curl -d '{"text":"new contrib from toto: [44](http://radio.localhost/map/#44)", ## Test room -#matrix-webhook:tetaneutral.net](https://matrix.to/#/!DPrUlnwOhBEfYwsDLh:matrix.org?via=laas.fr&via=tetaneutral.net&via=aen.im) +[#matrix-webhook:tetaneutral.net](https://matrix.to/#/!DPrUlnwOhBEfYwsDLh:matrix.org?via=laas.fr&via=tetaneutral.net&via=aen.im) ## Unit tests From 292d77274d84893c05d48cf101fa2d190944a919 Mon Sep 17 00:00:00 2001 From: Guilhem Saurel Date: Sat, 31 Jul 2021 11:21:29 +0200 Subject: [PATCH 004/141] update "text" key to "body" --- CHANGELOG.md | 2 ++ README.md | 2 +- matrix_webhook/__main__.py | 12 ++++++++---- tests/start.py | 2 +- tests/tests.py | 32 +++++++++++++++++++++++++++----- 5 files changed, 39 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5f6e7b..5c55982 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +- update "text" key to "body". + ## [3.1.0] - 2021-07-18 - Publish on PyPI & Docker Hub with Github Actions diff --git a/README.md b/README.md index 29a96ea..9f2442a 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ docker-compose up -d ## Test / Usage ``` -curl -d '{"text":"new contrib from toto: [44](http://radio.localhost/map/#44)", "key": "secret"}' \ +curl -d '{"body":"new contrib from toto: [44](http://radio.localhost/map/#44)", "key": "secret"}' \ 'http://matrixwebhook.localhost/!DPrUlnwOhBEfYwsDLh:matrix.org' ``` (or localhost:4785 without docker) diff --git a/matrix_webhook/__main__.py b/matrix_webhook/__main__.py index 2b4a7d7..2977f72 100644 --- a/matrix_webhook/__main__.py +++ b/matrix_webhook/__main__.py @@ -38,9 +38,13 @@ async def handler(request): except json.decoder.JSONDecodeError: return create_json_response(HTTPStatus.BAD_REQUEST, "Invalid JSON") - if not all(key in data for key in ["text", "key"]): + # legacy naming: + if "text" in data and "body" not in data: + data["body"] = data["text"] + + if not all(key in data for key in ["body", "key"]): return create_json_response( - HTTPStatus.BAD_REQUEST, "Missing text and/or API key property" + HTTPStatus.BAD_REQUEST, "Missing body and/or API key property" ) if data["key"] != conf.API_KEY: @@ -49,9 +53,9 @@ async def handler(request): room_id = request.path[1:] content = { "msgtype": "m.text", - "body": data["text"], + "body": data["body"], "format": "org.matrix.custom.html", - "formatted_body": markdown(str(data["text"]), extensions=["extra"]), + "formatted_body": markdown(str(data["body"]), extensions=["extra"]), } for _ in range(10): try: diff --git a/tests/start.py b/tests/start.py index 3df3418..3acb3b5 100755 --- a/tests/start.py +++ b/tests/start.py @@ -88,7 +88,7 @@ def run_and_test(): srv.terminate() # TODO Check what the bot says when the server is offline - # print(bot_req({'text': 'bye'}, KEY), {'status': 200, 'ret': 'OK'}) + # print(bot_req({'data': 'bye'}, KEY), {'status': 200, 'ret': 'OK'}) LOGGER.info("Stopping the bot") bot.terminate() diff --git a/tests/tests.py b/tests/tests.py index c92fb95..096dace 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -15,19 +15,19 @@ class BotTest(unittest.IsolatedAsyncioTestCase): self.assertEqual(bot_req(), {"status": 400, "ret": "Invalid JSON"}) self.assertEqual( bot_req({"toto": 3}), - {"status": 400, "ret": "Missing text and/or API key property"}, + {"status": 400, "ret": "Missing body and/or API key property"}, ) self.assertEqual( - bot_req({"text": 3, "key": None}), {"status": 401, "ret": "Invalid API key"} + bot_req({"body": 3, "key": None}), {"status": 401, "ret": "Invalid API key"} ) # TODO: if the client from matrix_webhook has olm support, this won't be a 403 from synapse, # but a LocalProtocolError from matrix_webhook self.assertEqual( - bot_req({"text": 3}, KEY), {"status": 403, "ret": "Unknown room"} + bot_req({"body": 3}, KEY), {"status": 403, "ret": "Unknown room"} ) async def test_message(self): - """Send a markdown message, and check the result.""" + """Send a markdown message with the old format, and check the result.""" text = "# Hello" messages = [] client = nio.AsyncClient(MATRIX_URL, MATRIX_ID) @@ -48,6 +48,28 @@ class BotTest(unittest.IsolatedAsyncioTestCase): self.assertEqual(message.body, text) self.assertEqual(message.formatted_body, "

Hello

") + async def test_markdown_body(self): + """Send a markdown message, and check the result.""" + body = "# Hello" + messages = [] + client = nio.AsyncClient(MATRIX_URL, MATRIX_ID) + + await client.login(MATRIX_PW) + room = await client.room_create() + + self.assertEqual( + bot_req({"body": body}, KEY, room.room_id), {"status": 200, "ret": "OK"} + ) + + sync = await client.sync() + messages = await client.room_messages(room.room_id, sync.next_batch) + await client.close() + + message = messages.chunk[0] + self.assertEqual(message.sender, FULL_ID) + self.assertEqual(message.body, body) + self.assertEqual(message.formatted_body, "

Hello

") + async def test_reconnect(self): """Check the reconnecting path.""" client = nio.AsyncClient(MATRIX_URL, MATRIX_ID) @@ -56,6 +78,6 @@ class BotTest(unittest.IsolatedAsyncioTestCase): await client.logout(all_devices=True) await client.close() self.assertEqual( - bot_req({"text": "Re"}, KEY, room.room_id), + bot_req({"body": "Re"}, KEY, room.room_id), {"status": 200, "ret": "OK"}, ) From fa8f9b4a51eed409a892deeba10b13369d717e51 Mon Sep 17 00:00:00 2001 From: Guilhem Saurel Date: Sat, 31 Jul 2021 11:31:31 +0200 Subject: [PATCH 005/141] allow "key" to be passed as a parameter This was initially designed and implemented in #4 Co-authored-by: Sven Seeberg --- CHANGELOG.md | 3 ++- matrix_webhook/__main__.py | 6 +++++- tests/start.py | 11 ++++++++--- tests/tests.py | 10 +++++++++- 4 files changed, 24 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c55982..35d0863 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -- update "text" key to "body". +- allow "key" to be passed as a parameter +- rename "text" to "body". ## [3.1.0] - 2021-07-18 diff --git a/matrix_webhook/__main__.py b/matrix_webhook/__main__.py index 2977f72..17305cc 100644 --- a/matrix_webhook/__main__.py +++ b/matrix_webhook/__main__.py @@ -38,10 +38,14 @@ async def handler(request): except json.decoder.JSONDecodeError: return create_json_response(HTTPStatus.BAD_REQUEST, "Invalid JSON") - # legacy naming: + # legacy naming if "text" in data and "body" not in data: data["body"] = data["text"] + # allow key to be passed as a parameter + if "key" in request.rel_url.query and "key" not in data: + data["key"] = request.rel_url.query["key"] + if not all(key in data for key in ["body", "key"]): return create_json_response( HTTPStatus.BAD_REQUEST, "Missing body and/or API key property" diff --git a/tests/start.py b/tests/start.py index 3acb3b5..f235edd 100755 --- a/tests/start.py +++ b/tests/start.py @@ -25,12 +25,17 @@ parser.add_argument( ) -def bot_req(req=None, key=None, room_id=None): +def bot_req(req=None, key=None, room_id=None, key_as_param=False): """Bot requests boilerplate.""" + params = {} + if key is not None: - req["key"] = key + if key_as_param: + params["key"] = key + else: + req["key"] = key url = BOT_URL if room_id is None else f"{BOT_URL}/{room_id}" - return httpx.post(url, json=req).json() + return httpx.post(url, params=params, json=req).json() def wait_available(url: str, key: str, timeout: int = 10) -> bool: diff --git a/tests/tests.py b/tests/tests.py index 096dace..2bc97e3 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -18,13 +18,21 @@ class BotTest(unittest.IsolatedAsyncioTestCase): {"status": 400, "ret": "Missing body and/or API key property"}, ) self.assertEqual( - bot_req({"body": 3, "key": None}), {"status": 401, "ret": "Invalid API key"} + bot_req({"body": 3}, "wrong_key"), {"status": 401, "ret": "Invalid API key"} + ) + self.assertEqual( + bot_req({"body": 3}, "wrong_key", key_as_param=True), + {"status": 401, "ret": "Invalid API key"}, ) # TODO: if the client from matrix_webhook has olm support, this won't be a 403 from synapse, # but a LocalProtocolError from matrix_webhook self.assertEqual( bot_req({"body": 3}, KEY), {"status": 403, "ret": "Unknown room"} ) + self.assertEqual( + bot_req({"body": 3}, KEY, key_as_param=True), + {"status": 403, "ret": "Unknown room"}, + ) async def test_message(self): """Send a markdown message with the old format, and check the result.""" From 3bebc88ee21438838d43828c4b6c47c2bed96dfc Mon Sep 17 00:00:00 2001 From: Guilhem Saurel Date: Sat, 31 Jul 2021 12:03:26 +0200 Subject: [PATCH 006/141] allow direct formatted_body MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This was initially designed and implemented in #6 Co-authored-by: Gerhard Bräunlich --- CHANGELOG.md | 2 ++ matrix_webhook/__main__.py | 7 ++++++- tests/tests.py | 26 ++++++++++++++++++++++++++ 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 35d0863..baff78f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +- add formatted_body to bypass markdown with direct + [matrix-custom-HTML](https://matrix.org/docs/spec/client_server/r0.6.1#m-room-message-msgtypes) - allow "key" to be passed as a parameter - rename "text" to "body". diff --git a/matrix_webhook/__main__.py b/matrix_webhook/__main__.py index 17305cc..005a3eb 100644 --- a/matrix_webhook/__main__.py +++ b/matrix_webhook/__main__.py @@ -54,12 +54,17 @@ async def handler(request): if data["key"] != conf.API_KEY: return create_json_response(HTTPStatus.UNAUTHORIZED, "Invalid API key") + if "formatted_body" in data: + formatted_body = data["formatted_body"] + else: + formatted_body = markdown(str(data["body"]), extensions=["extra"]) + room_id = request.path[1:] content = { "msgtype": "m.text", "body": data["body"], "format": "org.matrix.custom.html", - "formatted_body": markdown(str(data["body"]), extensions=["extra"]), + "formatted_body": formatted_body, } for _ in range(10): try: diff --git a/tests/tests.py b/tests/tests.py index 2bc97e3..9a82cc9 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -78,6 +78,32 @@ class BotTest(unittest.IsolatedAsyncioTestCase): self.assertEqual(message.body, body) self.assertEqual(message.formatted_body, "

Hello

") + async def test_formatted_body(self): + """Send a formatted message, and check the result.""" + body = "Formatted message" + formatted_body = "markdownFormatted message" + messages = [] + client = nio.AsyncClient(MATRIX_URL, MATRIX_ID) + + await client.login(MATRIX_PW) + room = await client.room_create() + + self.assertEqual( + bot_req( + {"body": body, "formatted_body": formatted_body}, KEY, room.room_id + ), + {"status": 200, "ret": "OK"}, + ) + + sync = await client.sync() + messages = await client.room_messages(room.room_id, sync.next_batch) + await client.close() + + message = messages.chunk[0] + self.assertEqual(message.sender, FULL_ID) + self.assertEqual(message.body, body) + self.assertEqual(message.formatted_body, formatted_body) + async def test_reconnect(self): """Check the reconnecting path.""" client = nio.AsyncClient(MATRIX_URL, MATRIX_ID) From 2d232fe1f7b8f0839e9219e3eb4fff7823d61fe7 Mon Sep 17 00:00:00 2001 From: Guilhem Saurel Date: Sat, 31 Jul 2021 13:06:36 +0200 Subject: [PATCH 007/141] add grafana formatter This was initially designed and implemented in #4 Co-authored-by: Sven Seeberg --- CHANGELOG.md | 1 + matrix_webhook/__main__.py | 8 +++++++- matrix_webhook/formatters.py | 15 +++++++++++++++ 3 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 matrix_webhook/formatters.py diff --git a/CHANGELOG.md b/CHANGELOG.md index baff78f..d7778fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +- add grafana formatter - add formatted_body to bypass markdown with direct [matrix-custom-HTML](https://matrix.org/docs/spec/client_server/r0.6.1#m-room-message-msgtypes) - allow "key" to be passed as a parameter diff --git a/matrix_webhook/__main__.py b/matrix_webhook/__main__.py index 005a3eb..c1ac638 100644 --- a/matrix_webhook/__main__.py +++ b/matrix_webhook/__main__.py @@ -16,7 +16,7 @@ from nio import AsyncClient from nio.exceptions import LocalProtocolError from nio.responses import RoomSendError -from . import conf +from . import conf, formatters ERROR_MAP = {"M_FORBIDDEN": HTTPStatus.FORBIDDEN} @@ -46,6 +46,12 @@ async def handler(request): if "key" in request.rel_url.query and "key" not in data: data["key"] = request.rel_url.query["key"] + if "formatter" in request.rel_url.query: + try: + data = getattr(formatters, request.rel_url.query["formatter"])(data) + except AttributeError: + return create_json_response(HTTPStatus.BAD_REQUEST, "Unknown formatter") + if not all(key in data for key in ["body", "key"]): return create_json_response( HTTPStatus.BAD_REQUEST, "Missing body and/or API key property" diff --git a/matrix_webhook/formatters.py b/matrix_webhook/formatters.py new file mode 100644 index 0000000..1e4600e --- /dev/null +++ b/matrix_webhook/formatters.py @@ -0,0 +1,15 @@ +"""Formatters for matrix webhook.""" + + +def grafana(data): + """Pretty-print a grafana notification.""" + text = "" + if "title" in data: + text = "### " + data["title"] + "\n" + if "message" in data: + text = text + data["message"] + "\n\n" + if "evalMatches" in data: + for match in data["evalMatches"]: + text = text + "* " + match["metric"] + ": " + str(match["value"]) + "\n" + data["body"] = text + return data From c07d4bfa8d506bce5fd9f5d1a85d18f35dc4aade Mon Sep 17 00:00:00 2001 From: Guilhem Saurel Date: Sat, 31 Jul 2021 13:06:52 +0200 Subject: [PATCH 008/141] add tests for grafana formatter --- tests/start.py | 6 ++-- tests/test_grafana.py | 66 +++++++++++++++++++++++++++++++++++++++++++ tests/tests.py | 4 +++ 3 files changed, 73 insertions(+), 3 deletions(-) create mode 100644 tests/test_grafana.py diff --git a/tests/start.py b/tests/start.py index f235edd..ea76b56 100755 --- a/tests/start.py +++ b/tests/start.py @@ -25,10 +25,10 @@ parser.add_argument( ) -def bot_req(req=None, key=None, room_id=None, key_as_param=False): +def bot_req(req=None, key=None, room_id=None, params=None, key_as_param=False): """Bot requests boilerplate.""" - params = {} - + if params is None: + params = {} if key is not None: if key_as_param: params["key"] = key diff --git a/tests/test_grafana.py b/tests/test_grafana.py new file mode 100644 index 0000000..33c080c --- /dev/null +++ b/tests/test_grafana.py @@ -0,0 +1,66 @@ +"""Test module for grafana formatter.""" + +import unittest + +import httpx +import nio + +from .start import BOT_URL, FULL_ID, KEY, MATRIX_ID, MATRIX_PW, MATRIX_URL + +# ref https://grafana.com/docs/grafana/latest/alerting/old-alerting/notifications/#webhook +EXAMPLE_GRAFANA_REQUEST = """ +{ + "dashboardId":1, + "evalMatches":[ + { + "value":1, + "metric":"Count", + "tags":{} + } + ], + "imageUrl":"https://grafana.com/assets/img/blog/mixed_styles.png", + "message":"Notification Message", + "orgId":1, + "panelId":2, + "ruleId":1, + "ruleName":"Panel Title alert", + "ruleUrl":"http://localhost:3000/d/hZ7BuVbWz/test-dashboard?fullscreen\u0026edit\u0026tab=alert\u0026panelId=2\u0026orgId=1", + "state":"alerting", + "tags":{ + "tag name":"tag value" + }, + "title":"[Alerting] Panel Title alert" +} +""" + + +class GrafanaFormatterTest(unittest.IsolatedAsyncioTestCase): + """Grafana formatter test class.""" + + async def test_grafana_body(self): + """Send a markdown message, and check the result.""" + messages = [] + client = nio.AsyncClient(MATRIX_URL, MATRIX_ID) + + await client.login(MATRIX_PW) + room = await client.room_create() + + self.assertEqual( + httpx.post( + f"{BOT_URL}/{room.room_id}", + params={"formatter": "grafana", "key": KEY}, + content=EXAMPLE_GRAFANA_REQUEST, + ).json(), + {"status": 200, "ret": "OK"}, + ) + + sync = await client.sync() + messages = await client.room_messages(room.room_id, sync.next_batch) + await client.close() + + message = messages.chunk[0] + self.assertEqual(message.sender, FULL_ID) + self.assertEqual( + message.body, + "### [Alerting] Panel Title alert\nNotification Message\n\n* Count: 1\n", + ) diff --git a/tests/tests.py b/tests/tests.py index 9a82cc9..1b17f9e 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -24,6 +24,10 @@ class BotTest(unittest.IsolatedAsyncioTestCase): bot_req({"body": 3}, "wrong_key", key_as_param=True), {"status": 401, "ret": "Invalid API key"}, ) + self.assertEqual( + bot_req({"body": 3}, KEY, params={"formatter": "wrong_formatter"}), + {"status": 400, "ret": "Unknown formatter"}, + ) # TODO: if the client from matrix_webhook has olm support, this won't be a 403 from synapse, # but a LocalProtocolError from matrix_webhook self.assertEqual( From c8f6c9ec287408a7b1581b2b1bd79a928afaf5e2 Mon Sep 17 00:00:00 2001 From: Guilhem Saurel Date: Sat, 31 Jul 2021 13:11:54 +0200 Subject: [PATCH 009/141] README: document grafana usage --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 9f2442a..404f051 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,11 @@ curl -d '{"body":"new contrib from toto: [44](http://radio.localhost/map/#44)", ``` (or localhost:4785 without docker) +### Grafana + +Add a webhook with an URL like: +`http://matrixwebhook.localhost/!DPrUlnwOhBEfYwsDLh:matrix.org?key=secret&formatter=grafana' + ## Test room [#matrix-webhook:tetaneutral.net](https://matrix.to/#/!DPrUlnwOhBEfYwsDLh:matrix.org?via=laas.fr&via=tetaneutral.net&via=aen.im) From 0ccec84eef55103ae614229884b9a0e3efc9574f Mon Sep 17 00:00:00 2001 From: Guilhem Saurel Date: Sat, 31 Jul 2021 15:16:07 +0200 Subject: [PATCH 010/141] room_id can come from url, content, or parameters --- CHANGELOG.md | 1 + matrix_webhook/__main__.py | 16 ++++++++--- tests/start.py | 13 +++++++-- tests/tests.py | 58 ++++++++++++++++++++++++++++++++++---- 4 files changed, 77 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d7778fc..2ec7b0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - add formatted_body to bypass markdown with direct [matrix-custom-HTML](https://matrix.org/docs/spec/client_server/r0.6.1#m-room-message-msgtypes) - allow "key" to be passed as a parameter +- allow "room_id" to be passed as a parameter or with the data - rename "text" to "body". ## [3.1.0] - 2021-07-18 diff --git a/matrix_webhook/__main__.py b/matrix_webhook/__main__.py index c1ac638..4416ccb 100644 --- a/matrix_webhook/__main__.py +++ b/matrix_webhook/__main__.py @@ -52,9 +52,18 @@ async def handler(request): except AttributeError: return create_json_response(HTTPStatus.BAD_REQUEST, "Unknown formatter") - if not all(key in data for key in ["body", "key"]): + if "room_id" in request.rel_url.query and "room_id" not in data: + data["room_id"] = request.rel_url.query["room_id"] + if "room_id" not in data: + data["room_id"] = request.path.lstrip("/") + + missing = [] + for key in ["body", "key", "room_id"]: + if key not in data or not data[key]: + missing.append(key) + if missing: return create_json_response( - HTTPStatus.BAD_REQUEST, "Missing body and/or API key property" + HTTPStatus.BAD_REQUEST, f"Missing {', '.join(missing)}" ) if data["key"] != conf.API_KEY: @@ -65,7 +74,6 @@ async def handler(request): else: formatted_body = markdown(str(data["body"]), extensions=["extra"]) - room_id = request.path[1:] content = { "msgtype": "m.text", "body": data["body"], @@ -74,7 +82,7 @@ async def handler(request): } for _ in range(10): try: - resp = await send_room_message(room_id, content) + resp = await send_room_message(data["room_id"], content) if isinstance(resp, RoomSendError): if resp.status_code == "M_UNKNOWN_TOKEN": LOGGER.warning("Reconnecting") diff --git a/tests/start.py b/tests/start.py index ea76b56..c4160bd 100755 --- a/tests/start.py +++ b/tests/start.py @@ -25,7 +25,14 @@ parser.add_argument( ) -def bot_req(req=None, key=None, room_id=None, params=None, key_as_param=False): +def bot_req( + req=None, + key=None, + room_id=None, + params=None, + key_as_param=False, + room_as_parameter=False, +): """Bot requests boilerplate.""" if params is None: params = {} @@ -34,7 +41,9 @@ def bot_req(req=None, key=None, room_id=None, params=None, key_as_param=False): params["key"] = key else: req["key"] = key - url = BOT_URL if room_id is None else f"{BOT_URL}/{room_id}" + if room_as_parameter: + params["room_id"] = room_id + url = BOT_URL if room_id is None or room_as_parameter else f"{BOT_URL}/{room_id}" return httpx.post(url, params=params, json=req).json() diff --git a/tests/tests.py b/tests/tests.py index 1b17f9e..a997aea 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -15,13 +15,14 @@ class BotTest(unittest.IsolatedAsyncioTestCase): self.assertEqual(bot_req(), {"status": 400, "ret": "Invalid JSON"}) self.assertEqual( bot_req({"toto": 3}), - {"status": 400, "ret": "Missing body and/or API key property"}, + {"status": 400, "ret": "Missing body, key, room_id"}, ) self.assertEqual( - bot_req({"body": 3}, "wrong_key"), {"status": 401, "ret": "Invalid API key"} + bot_req({"body": 3}, "wrong_key", "wrong_room"), + {"status": 401, "ret": "Invalid API key"}, ) self.assertEqual( - bot_req({"body": 3}, "wrong_key", key_as_param=True), + bot_req({"body": 3}, "wrong_key", "wrong_room", key_as_param=True), {"status": 401, "ret": "Invalid API key"}, ) self.assertEqual( @@ -31,10 +32,11 @@ class BotTest(unittest.IsolatedAsyncioTestCase): # TODO: if the client from matrix_webhook has olm support, this won't be a 403 from synapse, # but a LocalProtocolError from matrix_webhook self.assertEqual( - bot_req({"body": 3}, KEY), {"status": 403, "ret": "Unknown room"} + bot_req({"body": 3}, KEY, "wrong_room"), + {"status": 403, "ret": "Unknown room"}, ) self.assertEqual( - bot_req({"body": 3}, KEY, key_as_param=True), + bot_req({"body": 3}, KEY, "wrong_room", key_as_param=True), {"status": 403, "ret": "Unknown room"}, ) @@ -60,6 +62,52 @@ class BotTest(unittest.IsolatedAsyncioTestCase): self.assertEqual(message.body, text) self.assertEqual(message.formatted_body, "

Hello

") + async def test_room_id_req(self): + """Send a markdown message in a room given as data, and check the result.""" + body = "# Hello" + messages = [] + client = nio.AsyncClient(MATRIX_URL, MATRIX_ID) + + await client.login(MATRIX_PW) + room = await client.room_create() + + self.assertEqual( + bot_req({"body": body, "room_id": room.room_id}, KEY, room.room_id), + {"status": 200, "ret": "OK"}, + ) + + sync = await client.sync() + messages = await client.room_messages(room.room_id, sync.next_batch) + await client.close() + + message = messages.chunk[0] + self.assertEqual(message.sender, FULL_ID) + self.assertEqual(message.body, body) + self.assertEqual(message.formatted_body, "

Hello

") + + async def test_room_id_parameter(self): + """Send a markdown message in a room given as parameter, and check the result.""" + body = "# Hello" + messages = [] + client = nio.AsyncClient(MATRIX_URL, MATRIX_ID) + + await client.login(MATRIX_PW) + room = await client.room_create() + + self.assertEqual( + bot_req({"body": body}, KEY, room.room_id, room_as_parameter=True), + {"status": 200, "ret": "OK"}, + ) + + sync = await client.sync() + messages = await client.room_messages(room.room_id, sync.next_batch) + await client.close() + + message = messages.chunk[0] + self.assertEqual(message.sender, FULL_ID) + self.assertEqual(message.body, body) + self.assertEqual(message.formatted_body, "

Hello

") + async def test_markdown_body(self): """Send a markdown message, and check the result.""" body = "# Hello" From 528940abccc3cd00164fdd0c67ec215c2a8fc7cd Mon Sep 17 00:00:00 2001 From: Guilhem Saurel Date: Sat, 7 Aug 2021 17:50:11 +0200 Subject: [PATCH 011/141] fix changelog --- CHANGELOG.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ec7b0f..168d746 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,11 +13,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - allow "room_id" to be passed as a parameter or with the data - rename "text" to "body". +## [3.1.1] - 2021-07-18 + ## [3.1.0] - 2021-07-18 - Publish on PyPI & Docker Hub with Github Actions in [#10](https://github.com/nim65s/matrix-webhook/pull/10) - by [@nim65s](https://github.com/) + by [@nim65s](https://github.com/nim65s) ## [3.0.0] - 2021-07-18 @@ -43,6 +45,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [1.0.0] - 2020-02-14 - First release with matrix-client & http.server -[Unreleased]: https://github.com/nim65s/matrix-webhook/compare/v2.0.0...devel +[Unreleased]: https://github.com/nim65s/matrix-webhook/compare/v3.1.1...devel +[3.1.1]: https://github.com/nim65s/matrix-webhook/compare/v3.1.0...v3.1.1 +[3.1.0]: https://github.com/nim65s/matrix-webhook/compare/v3.0.0...v3.1.0 +[3.0.0]: https://github.com/nim65s/matrix-webhook/compare/v2.0.0...v3.0.0 [2.0.0]: https://github.com/nim65s/matrix-webhook/compare/v1.0.0...v2.0.0 [1.0.0]: https://github.com/nim65s/matrix-webhook/releases/tag/v1.0.0 From 530f40a1295f22c3ed430808c7d4dfd57ce2b95f Mon Sep 17 00:00:00 2001 From: Guilhem Saurel Date: Sat, 7 Aug 2021 17:50:49 +0200 Subject: [PATCH 012/141] setup action to publish releases on github --- .github/workflows/pypi.yml | 15 --------------- .github/workflows/release.yml | 19 +++++++++++++++++++ CHANGELOG.md | 1 + 3 files changed, 20 insertions(+), 15 deletions(-) delete mode 100644 .github/workflows/pypi.yml create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml deleted file mode 100644 index ee77ec4..0000000 --- a/.github/workflows/pypi.yml +++ /dev/null @@ -1,15 +0,0 @@ -name: PyPI - -on: - push: - tags: - - 'v*' - -jobs: - pypi: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - run: pip install -U poetry twine wheel - - run: poetry build - - run: twine upload --non-interactive -u __token__ -p ${{ secrets.PYPI_TOKEN }} dist/* diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..9d52615 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,19 @@ +name: Release on GitHub & PyPI + +on: + push: + tags: + - 'v*' + +jobs: + release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - run: pip install -U poetry twine wheel + - run: poetry build + - run: twine upload --non-interactive -u __token__ -p ${{ secrets.PYPI_TOKEN }} dist/* + - run: echo "TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV + - run: gh release create -t "Release ${{ env.TAG}}" -n "$(awk '/## \[${{ env.TAG }}] - /{flag=1;next}/## \[/{flag=0}flag' CHANGELOG.md)" ${{ env.TAG }} dist/* + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 168d746..66a1d03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - allow "key" to be passed as a parameter - allow "room_id" to be passed as a parameter or with the data - rename "text" to "body". +- Publish releases also on github from github actions ## [3.1.1] - 2021-07-18 From 6f7d38dbd70b8034fe477786bd17db0b391753e0 Mon Sep 17 00:00:00 2001 From: Guilhem Saurel Date: Fri, 27 Aug 2021 17:44:40 +0200 Subject: [PATCH 013/141] update test for latest synapse docker image --- CHANGELOG.md | 1 + tests/Dockerfile | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 66a1d03..cf6fbe8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - allow "room_id" to be passed as a parameter or with the data - rename "text" to "body". - Publish releases also on github from github actions +- fixed tests for recent synapse docker image ## [3.1.1] - 2021-07-18 diff --git a/tests/Dockerfile b/tests/Dockerfile index 42b60b9..09c8b05 100644 --- a/tests/Dockerfile +++ b/tests/Dockerfile @@ -4,7 +4,7 @@ FROM matrixdotorg/synapse # The config dir defaults to /data which is a volume made to keep data. # Here, we want to trash those (and avoid the permission issues) by using something else -ENV SYNAPSE_CONFIG_DIR=/srv SYNAPSE_SERVER_NAME=tests SYNAPSE_REPORT_STATS=no +ENV SYNAPSE_CONFIG_DIR=/srv SYNAPSE_DATA_DIR=/srv SYNAPSE_SERVER_NAME=tests SYNAPSE_REPORT_STATS=no # Generate configuration and keys for synapse WORKDIR $SYNAPSE_CONFIG_DIR From eabb446d05d119df345c45489e1238b943a09675 Mon Sep 17 00:00:00 2001 From: Guilhem Saurel Date: Fri, 27 Aug 2021 17:49:11 +0200 Subject: [PATCH 014/141] ci: release also on github --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 404f051..55d3ace 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![Tests](https://github.com/nim65s/matrix-webhook/actions/workflows/test.yml/badge.svg)](https://github.com/nim65s/matrix-webhook/actions/workflows/test.yml) [![Lints](https://github.com/nim65s/matrix-webhook/actions/workflows/lint.yml/badge.svg)](https://github.com/nim65s/matrix-webhook/actions/workflows/lint.yml) [![Docker-Hub](https://github.com/nim65s/matrix-webhook/actions/workflows/docker-hub.yml/badge.svg)](https://hub.docker.com/r/nim65s/matrix-webhook) -[![PyPI](https://github.com/nim65s/matrix-webhook/actions/workflows/pypi.yml/badge.svg)](https://pypi.org/project/matrix-webhook/) +[![Release](https://github.com/nim65s/matrix-webhook/actions/workflows/release.yml/badge.svg)](https://pypi.org/project/matrix-webhook/) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) [![codecov](https://codecov.io/gh/nim65s/matrix-webhook/branch/master/graph/badge.svg?token=BLGISGCYKG)](https://codecov.io/gh/nim65s/matrix-webhook) From c03ae0a571287f4f6a129993c6589efc472c731a Mon Sep 17 00:00:00 2001 From: Guilhem Saurel Date: Fri, 27 Aug 2021 17:58:34 +0200 Subject: [PATCH 015/141] lint: add flake8 configuration Black allows up to 88 characters per line. Put this configuration into a separated file, as pyproject.toml won't do ref. https://github.com/PyCQA/flake8/issues/234 --- .flake8 | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .flake8 diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..2bcd70e --- /dev/null +++ b/.flake8 @@ -0,0 +1,2 @@ +[flake8] +max-line-length = 88 From eb3c795368da825ee74254b936c2dace81287276 Mon Sep 17 00:00:00 2001 From: Guilhem Saurel Date: Fri, 27 Aug 2021 18:04:34 +0200 Subject: [PATCH 016/141] lint: fix line length --- matrix_webhook/conf.py | 3 ++- tests/start.py | 3 ++- tests/tests.py | 4 ++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/matrix_webhook/conf.py b/matrix_webhook/conf.py index fd39e02..bb93335 100644 --- a/matrix_webhook/conf.py +++ b/matrix_webhook/conf.py @@ -20,7 +20,8 @@ parser.add_argument( "-u", "--matrix-url", default=os.environ.get("MATRIX_URL", "https://matrix.org"), - help="matrix homeserver url. Default: `https://matrix.org`. Environment variable: `MATRIX_URL`", + help="matrix homeserver url. Default: `https://matrix.org`. " + "Environment variable: `MATRIX_URL`", ) parser.add_argument( "-i", diff --git a/tests/start.py b/tests/start.py index c4160bd..89aa8ec 100755 --- a/tests/start.py +++ b/tests/start.py @@ -51,7 +51,8 @@ def wait_available(url: str, key: str, timeout: int = 10) -> bool: """Wait until a service answer correctly or timeout.""" def check_json(url: str, key: str) -> bool: - """Ensure a service at a given url answers with valid json containing a certain key.""" + """Ensure a service at a given url answers + with valid json containing a certain key.""" try: data = httpx.get(url).json() return key in data diff --git a/tests/tests.py b/tests/tests.py index a997aea..01993c3 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -29,8 +29,8 @@ class BotTest(unittest.IsolatedAsyncioTestCase): bot_req({"body": 3}, KEY, params={"formatter": "wrong_formatter"}), {"status": 400, "ret": "Unknown formatter"}, ) - # TODO: if the client from matrix_webhook has olm support, this won't be a 403 from synapse, - # but a LocalProtocolError from matrix_webhook + # TODO: if the client from matrix_webhook has olm support, + # this won't be a 403 from synapse, but a LocalProtocolError from matrix_webhook self.assertEqual( bot_req({"body": 3}, KEY, "wrong_room"), {"status": 403, "ret": "Unknown room"}, From 6aaac9149d510d1d7bec7a040631f12dcdfea5bf Mon Sep 17 00:00:00 2001 From: Guilhem Saurel Date: Fri, 27 Aug 2021 18:06:35 +0200 Subject: [PATCH 017/141] lint: pre-commit autoupdate --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6fc3f8b..c0214fd 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -14,7 +14,7 @@ repos: - id: mixed-line-ending - id: trailing-whitespace - repo: https://github.com/psf/black - rev: 21.6b0 + rev: 21.7b0 hooks: - id: black language_version: python3 From 2b7b79971dbbd81e9a070f7b284b305c2a141f38 Mon Sep 17 00:00:00 2001 From: Guilhem Saurel Date: Fri, 27 Aug 2021 18:12:11 +0200 Subject: [PATCH 018/141] lint: fix pydocstyle --- .pre-commit-config.yaml | 2 ++ pyproject.toml | 2 +- tests/start.py | 5 +++-- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c0214fd..6e8ad61 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,6 +22,8 @@ repos: rev: 6.1.1 hooks: - id: pydocstyle + args: + - --ignore=D200,D212 - repo: https://github.com/PyCQA/flake8 rev: 3.9.2 hooks: diff --git a/pyproject.toml b/pyproject.toml index ab1b583..01252ba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,7 @@ pydocstyle = "^6.1.1" flake8 = "^3.9.2" [tool.pydocstyle] -ignore = ["D203", "D204", "D212"] +ignore = ["D200", "D203", "D204", "D212"] [build-system] requires = ["poetry-core>=1.0.0"] diff --git a/tests/start.py b/tests/start.py index 89aa8ec..3cc66eb 100755 --- a/tests/start.py +++ b/tests/start.py @@ -51,8 +51,9 @@ def wait_available(url: str, key: str, timeout: int = 10) -> bool: """Wait until a service answer correctly or timeout.""" def check_json(url: str, key: str) -> bool: - """Ensure a service at a given url answers - with valid json containing a certain key.""" + """ + Ensure a service at a given url answers with valid json including a certain key. + """ try: data = httpx.get(url).json() return key in data From 7f20fb7ff984f795217e910d87ac6cad98bb3ade Mon Sep 17 00:00:00 2001 From: Guilhem Saurel Date: Sun, 8 Aug 2021 08:41:37 +0200 Subject: [PATCH 019/141] split code in utils / handler / app --- matrix_webhook/__main__.py | 167 +------------------------------------ matrix_webhook/app.py | 56 +++++++++++++ matrix_webhook/handler.py | 72 ++++++++++++++++ matrix_webhook/utils.py | 47 +++++++++++ 4 files changed, 178 insertions(+), 164 deletions(-) create mode 100644 matrix_webhook/app.py create mode 100644 matrix_webhook/handler.py create mode 100644 matrix_webhook/utils.py diff --git a/matrix_webhook/__main__.py b/matrix_webhook/__main__.py index 4416ccb..da99dcc 100644 --- a/matrix_webhook/__main__.py +++ b/matrix_webhook/__main__.py @@ -1,170 +1,9 @@ -""" -Matrix Webhook. - -Post a message to a matrix room with a simple HTTP POST -""" - -import asyncio -import json +"""Matrix Webhook module entrypoint.""" import logging -from http import HTTPStatus -from signal import SIGINT, SIGTERM - -from aiohttp import web -from markdown import markdown -from nio import AsyncClient -from nio.exceptions import LocalProtocolError -from nio.responses import RoomSendError - -from . import conf, formatters - -ERROR_MAP = {"M_FORBIDDEN": HTTPStatus.FORBIDDEN} - -CLIENT = AsyncClient(conf.MATRIX_URL, conf.MATRIX_ID) -LOGGER = logging.getLogger("matrix-webhook") - - -async def handler(request): - """ - Coroutine given to the server, st. it knows what to do with an HTTP request. - - This one handles a POST, checks its content, and forwards it to the matrix room. - """ - LOGGER.debug(f"Handling {request=}") - data = await request.read() - - try: - data = json.loads(data.decode()) - except json.decoder.JSONDecodeError: - return create_json_response(HTTPStatus.BAD_REQUEST, "Invalid JSON") - - # legacy naming - if "text" in data and "body" not in data: - data["body"] = data["text"] - - # allow key to be passed as a parameter - if "key" in request.rel_url.query and "key" not in data: - data["key"] = request.rel_url.query["key"] - - if "formatter" in request.rel_url.query: - try: - data = getattr(formatters, request.rel_url.query["formatter"])(data) - except AttributeError: - return create_json_response(HTTPStatus.BAD_REQUEST, "Unknown formatter") - - if "room_id" in request.rel_url.query and "room_id" not in data: - data["room_id"] = request.rel_url.query["room_id"] - if "room_id" not in data: - data["room_id"] = request.path.lstrip("/") - - missing = [] - for key in ["body", "key", "room_id"]: - if key not in data or not data[key]: - missing.append(key) - if missing: - return create_json_response( - HTTPStatus.BAD_REQUEST, f"Missing {', '.join(missing)}" - ) - - if data["key"] != conf.API_KEY: - return create_json_response(HTTPStatus.UNAUTHORIZED, "Invalid API key") - - if "formatted_body" in data: - formatted_body = data["formatted_body"] - else: - formatted_body = markdown(str(data["body"]), extensions=["extra"]) - - content = { - "msgtype": "m.text", - "body": data["body"], - "format": "org.matrix.custom.html", - "formatted_body": formatted_body, - } - for _ in range(10): - try: - resp = await send_room_message(data["room_id"], content) - if isinstance(resp, RoomSendError): - if resp.status_code == "M_UNKNOWN_TOKEN": - LOGGER.warning("Reconnecting") - await CLIENT.login(conf.MATRIX_PW) - else: - return create_json_response( - ERROR_MAP[resp.status_code], resp.message - ) - else: - break - except LocalProtocolError as e: - LOGGER.error(f"Send error: {e}") - LOGGER.warning("Trying again") - else: - return create_json_response( - HTTPStatus.GATEWAY_TIMEOUT, "Homeserver not responding" - ) - - return create_json_response(HTTPStatus.OK, "OK") - - -def create_json_response(status, ret): - """Create a JSON response.""" - LOGGER.debug(f"Creating json response: {status=}, {ret=}") - response_data = {"status": status, "ret": ret} - return web.json_response(response_data, status=status) - - -async def send_room_message(room_id, content): - """Send a message to a room.""" - LOGGER.debug(f"Sending room message in {room_id=}: {content=}") - return await CLIENT.room_send( - room_id=room_id, message_type="m.room.message", content=content - ) - - -async def main(event): - """ - Launch main coroutine. - - matrix client login & start web server - """ - LOGGER.info(f"Log in {conf.MATRIX_ID=} on {conf.MATRIX_URL=}") - await CLIENT.login(conf.MATRIX_PW) - - server = web.Server(handler) - runner = web.ServerRunner(server) - await runner.setup() - LOGGER.info(f"Binding on {conf.SERVER_ADDRESS=}") - site = web.TCPSite(runner, *conf.SERVER_ADDRESS) - await site.start() - - # Run until we get a shutdown request - await event.wait() - - # Cleanup - await runner.cleanup() - await CLIENT.close() - - -def terminate(event, signal): - """Close handling stuff.""" - event.set() - asyncio.get_event_loop().remove_signal_handler(signal) - - -def run(): - """Launch everything.""" - LOGGER.info("Starting...") - loop = asyncio.get_event_loop() - event = asyncio.Event() - - for sig in (SIGINT, SIGTERM): - loop.add_signal_handler(sig, terminate, event, sig) - - loop.run_until_complete(main(event)) - - LOGGER.info("Closing...") - loop.close() +from . import app, conf if __name__ == "__main__": log_format = "%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(message)s" logging.basicConfig(level=50 - 10 * conf.VERBOSE, format=log_format) - run() + app.run() diff --git a/matrix_webhook/app.py b/matrix_webhook/app.py new file mode 100644 index 0000000..dec9ea1 --- /dev/null +++ b/matrix_webhook/app.py @@ -0,0 +1,56 @@ +"""Matrix Webhook app.""" + +import asyncio +import logging +from signal import SIGINT, SIGTERM + +from aiohttp import web + +from . import conf, handler, utils + +LOGGER = logging.getLogger("matrix_webhook.app") + + +async def main(event): + """ + Launch main coroutine. + + matrix client login & start web server + """ + LOGGER.info(f"Log in {conf.MATRIX_ID=} on {conf.MATRIX_URL=}") + await utils.CLIENT.login(conf.MATRIX_PW) + + server = web.Server(handler.matrix_webhook) + runner = web.ServerRunner(server) + await runner.setup() + LOGGER.info(f"Binding on {conf.SERVER_ADDRESS=}") + site = web.TCPSite(runner, *conf.SERVER_ADDRESS) + await site.start() + + # Run until we get a shutdown request + await event.wait() + + # Cleanup + await runner.cleanup() + await utils.CLIENT.close() + + +def terminate(event, signal): + """Close handling stuff.""" + event.set() + asyncio.get_event_loop().remove_signal_handler(signal) + + +def run(): + """Launch everything.""" + LOGGER.info("Starting...") + loop = asyncio.get_event_loop() + event = asyncio.Event() + + for sig in (SIGINT, SIGTERM): + loop.add_signal_handler(sig, terminate, event, sig) + + loop.run_until_complete(main(event)) + + LOGGER.info("Closing...") + loop.close() diff --git a/matrix_webhook/handler.py b/matrix_webhook/handler.py new file mode 100644 index 0000000..6ba9c59 --- /dev/null +++ b/matrix_webhook/handler.py @@ -0,0 +1,72 @@ +"""Matrix Webhook main request handler.""" + +import json +import logging +from http import HTTPStatus + +from markdown import markdown + +from . import conf, formatters, utils + +LOGGER = logging.getLogger("matrix_webhook.handler") + + +async def matrix_webhook(request): + """ + Coroutine given to the server, st. it knows what to do with an HTTP request. + + This one handles a POST, checks its content, and forwards it to the matrix room. + """ + LOGGER.debug(f"Handling {request=}") + data = await request.read() + + try: + data = json.loads(data.decode()) + except json.decoder.JSONDecodeError: + return utils.create_json_response(HTTPStatus.BAD_REQUEST, "Invalid JSON") + + # legacy naming + if "text" in data and "body" not in data: + data["body"] = data["text"] + + # allow key to be passed as a parameter + if "key" in request.rel_url.query and "key" not in data: + data["key"] = request.rel_url.query["key"] + + if "formatter" in request.rel_url.query: + try: + data = getattr(formatters, request.rel_url.query["formatter"])(data) + except AttributeError: + return utils.create_json_response( + HTTPStatus.BAD_REQUEST, "Unknown formatter" + ) + + if "room_id" in request.rel_url.query and "room_id" not in data: + data["room_id"] = request.rel_url.query["room_id"] + if "room_id" not in data: + data["room_id"] = request.path.lstrip("/") + + missing = [] + for key in ["body", "key", "room_id"]: + if key not in data or not data[key]: + missing.append(key) + if missing: + return utils.create_json_response( + HTTPStatus.BAD_REQUEST, f"Missing {', '.join(missing)}" + ) + + if data["key"] != conf.API_KEY: + return utils.create_json_response(HTTPStatus.UNAUTHORIZED, "Invalid API key") + + if "formatted_body" in data: + formatted_body = data["formatted_body"] + else: + formatted_body = markdown(str(data["body"]), extensions=["extra"]) + + content = { + "msgtype": "m.text", + "body": data["body"], + "format": "org.matrix.custom.html", + "formatted_body": formatted_body, + } + return await utils.send_room_message(data["room_id"], content) diff --git a/matrix_webhook/utils.py b/matrix_webhook/utils.py new file mode 100644 index 0000000..2b4d74d --- /dev/null +++ b/matrix_webhook/utils.py @@ -0,0 +1,47 @@ +"""Matrix Webhook utils.""" + +import logging +from http import HTTPStatus + +from aiohttp import web +from nio import AsyncClient +from nio.exceptions import LocalProtocolError +from nio.responses import RoomSendError + +from . import conf + +ERROR_MAP = {"M_FORBIDDEN": HTTPStatus.FORBIDDEN} +LOGGER = logging.getLogger("matrix_webhook.utils") +CLIENT = AsyncClient(conf.MATRIX_URL, conf.MATRIX_ID) + + +def create_json_response(status, ret): + """Create a JSON response.""" + LOGGER.debug(f"Creating json response: {status=}, {ret=}") + response_data = {"status": status, "ret": ret} + return web.json_response(response_data, status=status) + + +async def send_room_message(room_id, content): + """Send a message to a room.""" + LOGGER.debug(f"Sending room message in {room_id=}: {content=}") + + for _ in range(10): + try: + resp = await CLIENT.room_send( + room_id=room_id, message_type="m.room.message", content=content + ) + if isinstance(resp, RoomSendError): + if resp.status_code == "M_UNKNOWN_TOKEN": + LOGGER.warning("Reconnecting") + await CLIENT.login(conf.MATRIX_PW) + else: + return create_json_response( + ERROR_MAP[resp.status_code], resp.message + ) + else: + return create_json_response(HTTPStatus.OK, "OK") + except LocalProtocolError as e: + LOGGER.error(f"Send error: {e}") + LOGGER.warning("Trying again") + return create_json_response(HTTPStatus.GATEWAY_TIMEOUT, "Homeserver not responding") From 8f215c04fdb18aa9b386156e3e0938ab216323bb Mon Sep 17 00:00:00 2001 From: Guilhem Saurel Date: Sun, 8 Aug 2021 09:50:11 +0200 Subject: [PATCH 020/141] docs: release --- CHANGELOG.md | 2 +- docs/release.md | 18 ++++++++++++++++++ docs/release.sh | 20 ++++++++++++++++++++ 3 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 docs/release.md create mode 100755 docs/release.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index cf6fbe8..6a24cfc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,7 +47,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [1.0.0] - 2020-02-14 - First release with matrix-client & http.server -[Unreleased]: https://github.com/nim65s/matrix-webhook/compare/v3.1.1...devel +[Unreleased]: https://github.com/nim65s/matrix-webhook/compare/v3.1.1...master [3.1.1]: https://github.com/nim65s/matrix-webhook/compare/v3.1.0...v3.1.1 [3.1.0]: https://github.com/nim65s/matrix-webhook/compare/v3.0.0...v3.1.0 [3.0.0]: https://github.com/nim65s/matrix-webhook/compare/v2.0.0...v3.0.0 diff --git a/docs/release.md b/docs/release.md new file mode 100644 index 0000000..ba59891 --- /dev/null +++ b/docs/release.md @@ -0,0 +1,18 @@ +# Publish a new release + +A github actions handle the build of the release archives, and push them to PyPI and Github Releases. +To trigger it, we just need to: + +1. use poetry to update the version number +2. update the changelog +3. git commit +4. git tag +5. git push +6. git push --tags + + +For this, an helper script is provided: + +```bash +./docs/release.sh [patch|minor|major|x.y.z] +``` diff --git a/docs/release.sh b/docs/release.sh new file mode 100755 index 0000000..c1cb632 --- /dev/null +++ b/docs/release.sh @@ -0,0 +1,20 @@ +#!/bin/bash -eux +# ./docs/release.sh [patch|minor|major|x.y.z] + +[[ $(basename $PWD) == docs ]] && cd .. + + +OLD=$(poetry version -s) + +poetry version $1 + +NEW=$(poetry version -s) +DATE=$(date +%Y-%m-%d) + +sed -i "/^## \[Unreleased\]/a \\\n## [$NEW] - $DATE" CHANGELOG.md +sed -i "/^\[Unreleased\]/s/$OLD/$NEW/" CHANGELOG.md +sed -i "/^\[Unreleased\]/a [$NEW] https://github.com/nim65s/matrix-webhook/compare/v$OLD...v$NEW" CHANGELOG.md + +echo git add pyproject.toml CHANGELOG.md +echo git commit -m "Release v$NEW" +echo git tag -s "v$NEW" -m "Release v$NEW" From 9a544b8f2bc8200844d359643a1bf125bc341eb5 Mon Sep 17 00:00:00 2001 From: Guilhem Saurel Date: Sun, 8 Aug 2021 10:01:59 +0200 Subject: [PATCH 021/141] detail --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 55d3ace..c4c31d1 100644 --- a/README.md +++ b/README.md @@ -81,8 +81,7 @@ curl -d '{"body":"new contrib from toto: [44](http://radio.localhost/map/#44)", ### Grafana -Add a webhook with an URL like: -`http://matrixwebhook.localhost/!DPrUlnwOhBEfYwsDLh:matrix.org?key=secret&formatter=grafana' +Add a webhook with an URL like ending with `?formatter=grafana&key=API_KEY' ## Test room From ac7d1d9647008145e9d0cf65d24744d0db4862b8 Mon Sep 17 00:00:00 2001 From: Guilhem Saurel Date: Fri, 27 Aug 2021 18:39:28 +0200 Subject: [PATCH 022/141] typo --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a24cfc..f75b3db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - allow "room_id" to be passed as a parameter or with the data - rename "text" to "body". - Publish releases also on github from github actions -- fixed tests for recent synapse docker image +- fix tests for recent synapse docker image ## [3.1.1] - 2021-07-18 From 4bcdb25c809391baaabc264d9309059f9f48ead2 Mon Sep 17 00:00:00 2001 From: Guilhem Saurel Date: Fri, 27 Aug 2021 20:05:08 +0200 Subject: [PATCH 023/141] formatters: also get headers --- README.md | 8 ++++++-- matrix_webhook/formatters.py | 2 +- matrix_webhook/handler.py | 4 +++- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index c4c31d1..62f7dbd 100644 --- a/README.md +++ b/README.md @@ -79,9 +79,13 @@ curl -d '{"body":"new contrib from toto: [44](http://radio.localhost/map/#44)", ``` (or localhost:4785 without docker) -### Grafana +### For Github -Add a webhook with an URL like ending with `?formatter=grafana&key=API_KEY' +Add a JSON webhook with `?formatter=github`, and put the `API_KEY` as secret + +### For Grafana + +Add a webhook with an URL ending with `?formatter=grafana&key=API_KEY' ## Test room diff --git a/matrix_webhook/formatters.py b/matrix_webhook/formatters.py index 1e4600e..bec0979 100644 --- a/matrix_webhook/formatters.py +++ b/matrix_webhook/formatters.py @@ -1,7 +1,7 @@ """Formatters for matrix webhook.""" -def grafana(data): +def grafana(data, headers): """Pretty-print a grafana notification.""" text = "" if "title" in data: diff --git a/matrix_webhook/handler.py b/matrix_webhook/handler.py index 6ba9c59..ee48710 100644 --- a/matrix_webhook/handler.py +++ b/matrix_webhook/handler.py @@ -35,7 +35,9 @@ async def matrix_webhook(request): if "formatter" in request.rel_url.query: try: - data = getattr(formatters, request.rel_url.query["formatter"])(data) + data = getattr(formatters, request.rel_url.query["formatter"])( + data, request.headers + ) except AttributeError: return utils.create_json_response( HTTPStatus.BAD_REQUEST, "Unknown formatter" From 6b5d6e6e8745e1e17fbbbd2e93039fedbf874ba2 Mon Sep 17 00:00:00 2001 From: Guilhem Saurel Date: Fri, 27 Aug 2021 23:47:07 +0200 Subject: [PATCH 024/141] formatters: add github --- .pre-commit-config.yaml | 2 +- CHANGELOG.md | 3 +- matrix_webhook/formatters.py | 17 ++++++++ matrix_webhook/handler.py | 15 ++++++- tests/example_github_push.json | 1 + tests/example_grafana.json | 22 +++++++++++ tests/test_github.py | 71 ++++++++++++++++++++++++++++++++++ tests/test_grafana.py | 36 ++++------------- tests/tests.py | 2 +- 9 files changed, 136 insertions(+), 33 deletions(-) create mode 100644 tests/example_github_push.json create mode 100644 tests/example_grafana.json create mode 100644 tests/test_github.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6e8ad61..e09667c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -23,7 +23,7 @@ repos: hooks: - id: pydocstyle args: - - --ignore=D200,D212 + - --ignore=D200,D203,D212 - repo: https://github.com/PyCQA/flake8 rev: 3.9.2 hooks: diff --git a/CHANGELOG.md b/CHANGELOG.md index f75b3db..ede170d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,10 +6,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -- add grafana formatter +- add github & grafana formatters - add formatted_body to bypass markdown with direct [matrix-custom-HTML](https://matrix.org/docs/spec/client_server/r0.6.1#m-room-message-msgtypes) - allow "key" to be passed as a parameter +- allow to use a sha256 HMAC hex digest with the key instead of the raw key - allow "room_id" to be passed as a parameter or with the data - rename "text" to "body". - Publish releases also on github from github actions diff --git a/matrix_webhook/formatters.py b/matrix_webhook/formatters.py index bec0979..55ba43d 100644 --- a/matrix_webhook/formatters.py +++ b/matrix_webhook/formatters.py @@ -13,3 +13,20 @@ def grafana(data, headers): text = text + "* " + match["metric"] + ": " + str(match["value"]) + "\n" data["body"] = text return data + + +def github(data, headers): + """Pretty-print a github notification.""" + # TODO: Write nice useful formatters. This is only an example. + if headers["X-GitHub-Event"] == "push": + pusher, ref, a, b, c = [ + data[k] for k in ["pusher", "ref", "after", "before", "compare"] + ] + pusher = f"[{pusher['name']}](https://github.com/{pusher['name']})" + data["body"] = f"@{pusher} pushed on {ref}: [{b} → {a}]({c}):\n\n" + for commit in data["commits"]: + data["body"] += f"- [{commit['message']}]({commit['url']})\n" + else: + data["body"] = "notification from github" + data["digest"] = headers["X-Hub-Signature-256"].replace("sha256=", "") + return data diff --git a/matrix_webhook/handler.py b/matrix_webhook/handler.py index ee48710..1f1ac5f 100644 --- a/matrix_webhook/handler.py +++ b/matrix_webhook/handler.py @@ -3,6 +3,7 @@ import json import logging from http import HTTPStatus +from hmac import HMAC from markdown import markdown @@ -18,10 +19,10 @@ async def matrix_webhook(request): This one handles a POST, checks its content, and forwards it to the matrix room. """ LOGGER.debug(f"Handling {request=}") - data = await request.read() + data_b = await request.read() try: - data = json.loads(data.decode()) + data = json.loads(data_b.decode()) except json.decoder.JSONDecodeError: return utils.create_json_response(HTTPStatus.BAD_REQUEST, "Invalid JSON") @@ -48,6 +49,16 @@ async def matrix_webhook(request): if "room_id" not in data: data["room_id"] = request.path.lstrip("/") + # If we get a good SHA-256 HMAC digest, + # we can consider that the sender has the right API key + if "digest" in data: + if data["digest"] == HMAC(conf.API_KEY.encode(), data_b, "sha256").hexdigest(): + data["key"] = conf.API_KEY + else: # but if there is a wrong digest, an informative error should be provided + return utils.create_json_response( + HTTPStatus.UNAUTHORIZED, "Invalid SHA-256 HMAC digest" + ) + missing = [] for key in ["body", "key", "room_id"]: if key not in data or not data[key]: diff --git a/tests/example_github_push.json b/tests/example_github_push.json new file mode 100644 index 0000000..6be68fa --- /dev/null +++ b/tests/example_github_push.json @@ -0,0 +1 @@ +{"ref":"refs/heads/devel","before":"ac7d1d9647008145e9d0cf65d24744d0db4862b8","after":"4bcdb25c809391baaabc264d9309059f9f48ead2","repository":{"id":171114171,"node_id":"MDEwOlJlcG9zaXRvcnkxNzExMTQxNzE=","name":"matrix-webhook","full_name":"nim65s/matrix-webhook","private":false,"owner":{"name":"nim65s","email":"guilhem.saurel@laas.fr","login":"nim65s","id":131929,"node_id":"MDQ6VXNlcjEzMTkyOQ==","avatar_url":"https://avatars.githubusercontent.com/u/131929?v=4","gravatar_id":"","url":"https://api.github.com/users/nim65s","html_url":"https://github.com/nim65s","followers_url":"https://api.github.com/users/nim65s/followers","following_url":"https://api.github.com/users/nim65s/following{/other_user}","gists_url":"https://api.github.com/users/nim65s/gists{/gist_id}","starred_url":"https://api.github.com/users/nim65s/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/nim65s/subscriptions","organizations_url":"https://api.github.com/users/nim65s/orgs","repos_url":"https://api.github.com/users/nim65s/repos","events_url":"https://api.github.com/users/nim65s/events{/privacy}","received_events_url":"https://api.github.com/users/nim65s/received_events","type":"User","site_admin":false},"html_url":"https://github.com/nim65s/matrix-webhook","description":"Post a message to a matrix room with a simple HTTP POST","fork":false,"url":"https://github.com/nim65s/matrix-webhook","forks_url":"https://api.github.com/repos/nim65s/matrix-webhook/forks","keys_url":"https://api.github.com/repos/nim65s/matrix-webhook/keys{/key_id}","collaborators_url":"https://api.github.com/repos/nim65s/matrix-webhook/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/nim65s/matrix-webhook/teams","hooks_url":"https://api.github.com/repos/nim65s/matrix-webhook/hooks","issue_events_url":"https://api.github.com/repos/nim65s/matrix-webhook/issues/events{/number}","events_url":"https://api.github.com/repos/nim65s/matrix-webhook/events","assignees_url":"https://api.github.com/repos/nim65s/matrix-webhook/assignees{/user}","branches_url":"https://api.github.com/repos/nim65s/matrix-webhook/branches{/branch}","tags_url":"https://api.github.com/repos/nim65s/matrix-webhook/tags","blobs_url":"https://api.github.com/repos/nim65s/matrix-webhook/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/nim65s/matrix-webhook/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/nim65s/matrix-webhook/git/refs{/sha}","trees_url":"https://api.github.com/repos/nim65s/matrix-webhook/git/trees{/sha}","statuses_url":"https://api.github.com/repos/nim65s/matrix-webhook/statuses/{sha}","languages_url":"https://api.github.com/repos/nim65s/matrix-webhook/languages","stargazers_url":"https://api.github.com/repos/nim65s/matrix-webhook/stargazers","contributors_url":"https://api.github.com/repos/nim65s/matrix-webhook/contributors","subscribers_url":"https://api.github.com/repos/nim65s/matrix-webhook/subscribers","subscription_url":"https://api.github.com/repos/nim65s/matrix-webhook/subscription","commits_url":"https://api.github.com/repos/nim65s/matrix-webhook/commits{/sha}","git_commits_url":"https://api.github.com/repos/nim65s/matrix-webhook/git/commits{/sha}","comments_url":"https://api.github.com/repos/nim65s/matrix-webhook/comments{/number}","issue_comment_url":"https://api.github.com/repos/nim65s/matrix-webhook/issues/comments{/number}","contents_url":"https://api.github.com/repos/nim65s/matrix-webhook/contents/{+path}","compare_url":"https://api.github.com/repos/nim65s/matrix-webhook/compare/{base}...{head}","merges_url":"https://api.github.com/repos/nim65s/matrix-webhook/merges","archive_url":"https://api.github.com/repos/nim65s/matrix-webhook/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/nim65s/matrix-webhook/downloads","issues_url":"https://api.github.com/repos/nim65s/matrix-webhook/issues{/number}","pulls_url":"https://api.github.com/repos/nim65s/matrix-webhook/pulls{/number}","milestones_url":"https://api.github.com/repos/nim65s/matrix-webhook/milestones{/number}","notifications_url":"https://api.github.com/repos/nim65s/matrix-webhook/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/nim65s/matrix-webhook/labels{/name}","releases_url":"https://api.github.com/repos/nim65s/matrix-webhook/releases{/id}","deployments_url":"https://api.github.com/repos/nim65s/matrix-webhook/deployments","created_at":1550402971,"updated_at":"2021-07-20T22:30:52Z","pushed_at":1630087539,"git_url":"git://github.com/nim65s/matrix-webhook.git","ssh_url":"git@github.com:nim65s/matrix-webhook.git","clone_url":"https://github.com/nim65s/matrix-webhook.git","svn_url":"https://github.com/nim65s/matrix-webhook","homepage":"https://code.ffdn.org/tetaneutral.net/matrix-webhook","size":158,"stargazers_count":17,"watchers_count":17,"language":"Python","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":7,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":2,"license":{"key":"other","name":"Other","spdx_id":"NOASSERTION","url":null,"node_id":"MDc6TGljZW5zZTA="},"forks":7,"open_issues":2,"watchers":17,"default_branch":"master","stargazers":17,"master_branch":"master"},"pusher":{"name":"nim65s","email":"guilhem.saurel@laas.fr"},"sender":{"login":"nim65s","id":131929,"node_id":"MDQ6VXNlcjEzMTkyOQ==","avatar_url":"https://avatars.githubusercontent.com/u/131929?v=4","gravatar_id":"","url":"https://api.github.com/users/nim65s","html_url":"https://github.com/nim65s","followers_url":"https://api.github.com/users/nim65s/followers","following_url":"https://api.github.com/users/nim65s/following{/other_user}","gists_url":"https://api.github.com/users/nim65s/gists{/gist_id}","starred_url":"https://api.github.com/users/nim65s/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/nim65s/subscriptions","organizations_url":"https://api.github.com/users/nim65s/orgs","repos_url":"https://api.github.com/users/nim65s/repos","events_url":"https://api.github.com/users/nim65s/events{/privacy}","received_events_url":"https://api.github.com/users/nim65s/received_events","type":"User","site_admin":false},"created":false,"deleted":false,"forced":false,"base_ref":null,"compare":"https://github.com/nim65s/matrix-webhook/compare/ac7d1d964700...4bcdb25c8093","commits":[{"id":"4bcdb25c809391baaabc264d9309059f9f48ead2","tree_id":"e423e7482b0231d04dca2caafcdc48a4b064f17b","distinct":true,"message":"formatters: also get headers","timestamp":"2021-08-27T20:05:08+02:00","url":"https://github.com/nim65s/matrix-webhook/commit/4bcdb25c809391baaabc264d9309059f9f48ead2","author":{"name":"Guilhem Saurel","email":"guilhem.saurel@laas.fr","username":"nim65s"},"committer":{"name":"Guilhem Saurel","email":"guilhem.saurel@laas.fr","username":"nim65s"},"added":[],"removed":[],"modified":["README.md","matrix_webhook/formatters.py","matrix_webhook/handler.py"]}],"head_commit":{"id":"4bcdb25c809391baaabc264d9309059f9f48ead2","tree_id":"e423e7482b0231d04dca2caafcdc48a4b064f17b","distinct":true,"message":"formatters: also get headers","timestamp":"2021-08-27T20:05:08+02:00","url":"https://github.com/nim65s/matrix-webhook/commit/4bcdb25c809391baaabc264d9309059f9f48ead2","author":{"name":"Guilhem Saurel","email":"guilhem.saurel@laas.fr","username":"nim65s"},"committer":{"name":"Guilhem Saurel","email":"guilhem.saurel@laas.fr","username":"nim65s"},"added":[],"removed":[],"modified":["README.md","matrix_webhook/formatters.py","matrix_webhook/handler.py"]}} diff --git a/tests/example_grafana.json b/tests/example_grafana.json new file mode 100644 index 0000000..a768bc0 --- /dev/null +++ b/tests/example_grafana.json @@ -0,0 +1,22 @@ +{ + "dashboardId":1, + "evalMatches":[ + { + "value":1, + "metric":"Count", + "tags":{} + } + ], + "imageUrl":"https://grafana.com/assets/img/blog/mixed_styles.png", + "message":"Notification Message", + "orgId":1, + "panelId":2, + "ruleId":1, + "ruleName":"Panel Title alert", + "ruleUrl":"http://localhost:3000/d/hZ7BuVbWz/test-dashboard?fullscreen\u0026edit\u0026tab=alert\u0026panelId=2\u0026orgId=1", + "state":"alerting", + "tags":{ + "tag name":"tag value" + }, + "title":"[Alerting] Panel Title alert" +} diff --git a/tests/test_github.py b/tests/test_github.py new file mode 100644 index 0000000..30158e5 --- /dev/null +++ b/tests/test_github.py @@ -0,0 +1,71 @@ +"""Test module for grafana formatter.""" + +import unittest + +import httpx +import nio + +from .start import BOT_URL, FULL_ID, MATRIX_ID, MATRIX_PW, MATRIX_URL + +SHA256 = "fd7522672889385736be8ffc86d1f8de2e15668864f49af729b5c63e5e0698c4" +EXAMPLE_GITHUB_REQUEST_HEADERS = { + # 'Request URL': 'https://bot.saurel.me/room?formatter=github', + # 'Request method': 'POST', + "Accept": "*/*", + "content-type": "application/json", + "User-Agent": "GitHub-Hookshot/8d33975", + "X-GitHub-Delivery": "636b9b1c-0761-11ec-8a8a-5e435c5ac4f4", + "X-GitHub-Event": "push", + "X-GitHub-Hook-ID": "311845633", + "X-GitHub-Hook-Installation-Target-ID": "171114171", + "X-GitHub-Hook-Installation-Target-Type": "repository", + "X-Hub-Signature": "sha1=ea68fdfcb2f328aaa8f50d176f355e5d4fc95d94", + "X-Hub-Signature-256": f"sha256={SHA256}", +} + + +class GithubFormatterTest(unittest.IsolatedAsyncioTestCase): + """Github formatter test class.""" + + async def test_github_body(self): + """Send a markdown message, and check the result.""" + messages = [] + client = nio.AsyncClient(MATRIX_URL, MATRIX_ID) + + await client.login(MATRIX_PW) + room = await client.room_create() + + with open("tests/example_github_push.json", "rb") as f: + example_github_push = f.read().strip() + + self.assertEqual( + httpx.post( + f"{BOT_URL}/{room.room_id}", + params={ + "formatter": "github", + }, + content=example_github_push, + headers=EXAMPLE_GITHUB_REQUEST_HEADERS, + ).json(), + {"status": 200, "ret": "OK"}, + ) + + sync = await client.sync() + messages = await client.room_messages(room.room_id, sync.next_batch) + await client.close() + + before = "ac7d1d9647008145e9d0cf65d24744d0db4862b8" + after = "4bcdb25c809391baaabc264d9309059f9f48ead2" + GH = "https://github.com" + expected = f'

@nim65s pushed on refs/heads/devel: ' + expected += f'{before} → {after}:

\n" + + message = messages.chunk[0] + self.assertEqual(message.sender, FULL_ID) + self.assertEqual( + message.formatted_body, + expected, + ) diff --git a/tests/test_grafana.py b/tests/test_grafana.py index 33c080c..6cd04cf 100644 --- a/tests/test_grafana.py +++ b/tests/test_grafana.py @@ -1,4 +1,8 @@ -"""Test module for grafana formatter.""" +""" +Test module for grafana formatter. + +ref https://grafana.com/docs/grafana/latest/alerting/old-alerting/notifications/#webhook +""" import unittest @@ -7,32 +11,6 @@ import nio from .start import BOT_URL, FULL_ID, KEY, MATRIX_ID, MATRIX_PW, MATRIX_URL -# ref https://grafana.com/docs/grafana/latest/alerting/old-alerting/notifications/#webhook -EXAMPLE_GRAFANA_REQUEST = """ -{ - "dashboardId":1, - "evalMatches":[ - { - "value":1, - "metric":"Count", - "tags":{} - } - ], - "imageUrl":"https://grafana.com/assets/img/blog/mixed_styles.png", - "message":"Notification Message", - "orgId":1, - "panelId":2, - "ruleId":1, - "ruleName":"Panel Title alert", - "ruleUrl":"http://localhost:3000/d/hZ7BuVbWz/test-dashboard?fullscreen\u0026edit\u0026tab=alert\u0026panelId=2\u0026orgId=1", - "state":"alerting", - "tags":{ - "tag name":"tag value" - }, - "title":"[Alerting] Panel Title alert" -} -""" - class GrafanaFormatterTest(unittest.IsolatedAsyncioTestCase): """Grafana formatter test class.""" @@ -45,11 +23,13 @@ class GrafanaFormatterTest(unittest.IsolatedAsyncioTestCase): await client.login(MATRIX_PW) room = await client.room_create() + with open("tests/example_grafana.json") as f: + example_grafana_request = f.read() self.assertEqual( httpx.post( f"{BOT_URL}/{room.room_id}", params={"formatter": "grafana", "key": KEY}, - content=EXAMPLE_GRAFANA_REQUEST, + content=example_grafana_request, ).json(), {"status": 200, "ret": "OK"}, ) diff --git a/tests/tests.py b/tests/tests.py index 01993c3..aa5991c 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -86,7 +86,7 @@ class BotTest(unittest.IsolatedAsyncioTestCase): self.assertEqual(message.formatted_body, "

Hello

") async def test_room_id_parameter(self): - """Send a markdown message in a room given as parameter, and check the result.""" + """Send a markdown message in a room given as parameter.""" body = "# Hello" messages = [] client = nio.AsyncClient(MATRIX_URL, MATRIX_ID) From cda88980195fd8b1ad49c5a852d379f00f836e19 Mon Sep 17 00:00:00 2001 From: Guilhem Saurel Date: Fri, 27 Aug 2021 23:56:56 +0200 Subject: [PATCH 025/141] tests: improve coverage --- tests/test_github.py | 94 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 76 insertions(+), 18 deletions(-) diff --git a/tests/test_github.py b/tests/test_github.py index 30158e5..fed01d7 100644 --- a/tests/test_github.py +++ b/tests/test_github.py @@ -8,27 +8,31 @@ import nio from .start import BOT_URL, FULL_ID, MATRIX_ID, MATRIX_PW, MATRIX_URL SHA256 = "fd7522672889385736be8ffc86d1f8de2e15668864f49af729b5c63e5e0698c4" -EXAMPLE_GITHUB_REQUEST_HEADERS = { - # 'Request URL': 'https://bot.saurel.me/room?formatter=github', - # 'Request method': 'POST', - "Accept": "*/*", - "content-type": "application/json", - "User-Agent": "GitHub-Hookshot/8d33975", - "X-GitHub-Delivery": "636b9b1c-0761-11ec-8a8a-5e435c5ac4f4", - "X-GitHub-Event": "push", - "X-GitHub-Hook-ID": "311845633", - "X-GitHub-Hook-Installation-Target-ID": "171114171", - "X-GitHub-Hook-Installation-Target-Type": "repository", - "X-Hub-Signature": "sha1=ea68fdfcb2f328aaa8f50d176f355e5d4fc95d94", - "X-Hub-Signature-256": f"sha256={SHA256}", -} + + +def headers(sha256=SHA256, event="push"): + """Mock headers from github webhooks.""" + return { + # 'Request URL': 'https://bot.saurel.me/room?formatter=github', + # 'Request method': 'POST', + "Accept": "*/*", + "content-type": "application/json", + "User-Agent": "GitHub-Hookshot/8d33975", + "X-GitHub-Delivery": "636b9b1c-0761-11ec-8a8a-5e435c5ac4f4", + "X-GitHub-Event": event, + "X-GitHub-Hook-ID": "311845633", + "X-GitHub-Hook-Installation-Target-ID": "171114171", + "X-GitHub-Hook-Installation-Target-Type": "repository", + "X-Hub-Signature": "sha1=ea68fdfcb2f328aaa8f50d176f355e5d4fc95d94", + "X-Hub-Signature-256": f"sha256={sha256}", + } class GithubFormatterTest(unittest.IsolatedAsyncioTestCase): """Github formatter test class.""" - async def test_github_body(self): - """Send a markdown message, and check the result.""" + async def test_github_notification(self): + """Send a mock github webhook, and check the result.""" messages = [] client = nio.AsyncClient(MATRIX_URL, MATRIX_ID) @@ -37,7 +41,6 @@ class GithubFormatterTest(unittest.IsolatedAsyncioTestCase): with open("tests/example_github_push.json", "rb") as f: example_github_push = f.read().strip() - self.assertEqual( httpx.post( f"{BOT_URL}/{room.room_id}", @@ -45,7 +48,40 @@ class GithubFormatterTest(unittest.IsolatedAsyncioTestCase): "formatter": "github", }, content=example_github_push, - headers=EXAMPLE_GITHUB_REQUEST_HEADERS, + headers=headers(event="something else"), + ).json(), + {"status": 200, "ret": "OK"}, + ) + + sync = await client.sync() + messages = await client.room_messages(room.room_id, sync.next_batch) + await client.close() + + message = messages.chunk[0] + self.assertEqual(message.sender, FULL_ID) + self.assertEqual( + message.formatted_body, + "

notification from github

", + ) + + async def test_github_push(self): + """Send a mock github push webhook, and check the result.""" + messages = [] + client = nio.AsyncClient(MATRIX_URL, MATRIX_ID) + + await client.login(MATRIX_PW) + room = await client.room_create() + + with open("tests/example_github_push.json", "rb") as f: + example_github_push = f.read().strip() + self.assertEqual( + httpx.post( + f"{BOT_URL}/{room.room_id}", + params={ + "formatter": "github", + }, + content=example_github_push, + headers=headers(), ).json(), {"status": 200, "ret": "OK"}, ) @@ -69,3 +105,25 @@ class GithubFormatterTest(unittest.IsolatedAsyncioTestCase): message.formatted_body, expected, ) + + async def test_github_wrong_digest(self): + """Send a mock github push webhook with a wrong digest.""" + client = nio.AsyncClient(MATRIX_URL, MATRIX_ID) + + await client.login(MATRIX_PW) + room = await client.room_create() + + with open("tests/example_github_push.json", "rb") as f: + example_github_push = f.read().strip() + + self.assertEqual( + httpx.post( + f"{BOT_URL}/{room.room_id}", + params={ + "formatter": "github", + }, + content=example_github_push, + headers=headers("wrong digest"), + ).json(), + {"status": 401, "ret": "Invalid SHA-256 HMAC digest"}, + ) From 4928ceb91a8b6d0a70338a6fa583a8fbdb173c31 Mon Sep 17 00:00:00 2001 From: Guilhem Saurel Date: Fri, 27 Aug 2021 23:59:49 +0200 Subject: [PATCH 026/141] release: details --- docs/release.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/release.sh b/docs/release.sh index c1cb632..40f05e2 100755 --- a/docs/release.sh +++ b/docs/release.sh @@ -1,12 +1,12 @@ #!/bin/bash -eux # ./docs/release.sh [patch|minor|major|x.y.z] -[[ $(basename $PWD) == docs ]] && cd .. +[[ $(basename "$PWD") == docs ]] && cd .. OLD=$(poetry version -s) -poetry version $1 +poetry version "$1" NEW=$(poetry version -s) DATE=$(date +%Y-%m-%d) @@ -15,6 +15,6 @@ sed -i "/^## \[Unreleased\]/a \\\n## [$NEW] - $DATE" CHANGELOG.md sed -i "/^\[Unreleased\]/s/$OLD/$NEW/" CHANGELOG.md sed -i "/^\[Unreleased\]/a [$NEW] https://github.com/nim65s/matrix-webhook/compare/v$OLD...v$NEW" CHANGELOG.md -echo git add pyproject.toml CHANGELOG.md -echo git commit -m "Release v$NEW" -echo git tag -s "v$NEW" -m "Release v$NEW" +git add pyproject.toml CHANGELOG.md +git commit -m "Release v$NEW" +git tag -s "v$NEW" -m "Release v$NEW" From 7f5c8583a110d55cb0e69f2cbebc023dba9fb21c Mon Sep 17 00:00:00 2001 From: Guilhem Saurel Date: Fri, 27 Aug 2021 23:59:56 +0200 Subject: [PATCH 027/141] Release v3.2.0 --- CHANGELOG.md | 5 ++++- pyproject.toml | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ede170d..64dc15f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [3.2.0] - 2021-08-27 + - add github & grafana formatters - add formatted_body to bypass markdown with direct [matrix-custom-HTML](https://matrix.org/docs/spec/client_server/r0.6.1#m-room-message-msgtypes) @@ -48,7 +50,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [1.0.0] - 2020-02-14 - First release with matrix-client & http.server -[Unreleased]: https://github.com/nim65s/matrix-webhook/compare/v3.1.1...master +[Unreleased]: https://github.com/nim65s/matrix-webhook/compare/v3.2.0...master +[3.2.0] https://github.com/nim65s/matrix-webhook/compare/v3.1.1...v3.2.0 [3.1.1]: https://github.com/nim65s/matrix-webhook/compare/v3.1.0...v3.1.1 [3.1.0]: https://github.com/nim65s/matrix-webhook/compare/v3.0.0...v3.1.0 [3.0.0]: https://github.com/nim65s/matrix-webhook/compare/v2.0.0...v3.0.0 diff --git a/pyproject.toml b/pyproject.toml index 01252ba..4d815bb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "matrix-webhook" -version = "3.1.1" +version = "3.2.0" description = "Post a message to a matrix room with a simple HTTP POST" authors = ["Guilhem Saurel "] license = "BSD-2-Clause" From 7e1be831df74a85d60f271ae8697237cc8f49759 Mon Sep 17 00:00:00 2001 From: Guilhem Saurel Date: Sat, 28 Aug 2021 00:05:20 +0200 Subject: [PATCH 028/141] ci: detail --- .github/workflows/docker-hub.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker-hub.yml b/.github/workflows/docker-hub.yml index 8fa86ac..9be581b 100644 --- a/.github/workflows/docker-hub.yml +++ b/.github/workflows/docker-hub.yml @@ -1,4 +1,4 @@ -name: Publish +name: Publish on Docker Hub on: push: From 1c00ff22f3276024c58bb42ed3cbacb32116c8b8 Mon Sep 17 00:00:00 2001 From: Guilhem Saurel Date: Sat, 28 Aug 2021 00:06:38 +0200 Subject: [PATCH 029/141] fix changelog --- CHANGELOG.md | 26 ++++++++++++++------------ docs/release.sh | 4 ++-- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 64dc15f..3b4554b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -## [3.2.0] - 2021-08-27 +- fix changelog + +## [v3.2.0] - 2021-08-27 - add github & grafana formatters - add formatted_body to bypass markdown with direct @@ -18,15 +20,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Publish releases also on github from github actions - fix tests for recent synapse docker image -## [3.1.1] - 2021-07-18 +## [v3.1.1] - 2021-07-18 -## [3.1.0] - 2021-07-18 +## [v3.1.0] - 2021-07-18 - Publish on PyPI & Docker Hub with Github Actions in [#10](https://github.com/nim65s/matrix-webhook/pull/10) by [@nim65s](https://github.com/nim65s) -## [3.0.0] - 2021-07-18 +## [v3.0.0] - 2021-07-18 - Simplify code in [#1](https://github.com/nim65s/matrix-webhook/pull/1) @@ -44,16 +46,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 in [#9](https://github.com/nim65s/matrix-webhook/pull/9) by [@nim65s](https://github.com/nim65s) -## [2.0.0] - 2020-03-14 +## [v2.0.0] - 2020-03-14 - Update to matrix-nio & aiohttp & markdown -## [1.0.0] - 2020-02-14 +## [v1.0.0] - 2020-02-14 - First release with matrix-client & http.server [Unreleased]: https://github.com/nim65s/matrix-webhook/compare/v3.2.0...master -[3.2.0] https://github.com/nim65s/matrix-webhook/compare/v3.1.1...v3.2.0 -[3.1.1]: https://github.com/nim65s/matrix-webhook/compare/v3.1.0...v3.1.1 -[3.1.0]: https://github.com/nim65s/matrix-webhook/compare/v3.0.0...v3.1.0 -[3.0.0]: https://github.com/nim65s/matrix-webhook/compare/v2.0.0...v3.0.0 -[2.0.0]: https://github.com/nim65s/matrix-webhook/compare/v1.0.0...v2.0.0 -[1.0.0]: https://github.com/nim65s/matrix-webhook/releases/tag/v1.0.0 +[v3.2.0] https://github.com/nim65s/matrix-webhook/compare/v3.1.1...v3.2.0 +[v3.1.1]: https://github.com/nim65s/matrix-webhook/compare/v3.1.0...v3.1.1 +[v3.1.0]: https://github.com/nim65s/matrix-webhook/compare/v3.0.0...v3.1.0 +[v3.0.0]: https://github.com/nim65s/matrix-webhook/compare/v2.0.0...v3.0.0 +[v2.0.0]: https://github.com/nim65s/matrix-webhook/compare/v1.0.0...v2.0.0 +[v1.0.0]: https://github.com/nim65s/matrix-webhook/releases/tag/v1.0.0 diff --git a/docs/release.sh b/docs/release.sh index 40f05e2..26b7b83 100755 --- a/docs/release.sh +++ b/docs/release.sh @@ -11,9 +11,9 @@ poetry version "$1" NEW=$(poetry version -s) DATE=$(date +%Y-%m-%d) -sed -i "/^## \[Unreleased\]/a \\\n## [$NEW] - $DATE" CHANGELOG.md +sed -i "/^## \[Unreleased\]/a \\\n## [v$NEW] - $DATE" CHANGELOG.md sed -i "/^\[Unreleased\]/s/$OLD/$NEW/" CHANGELOG.md -sed -i "/^\[Unreleased\]/a [$NEW] https://github.com/nim65s/matrix-webhook/compare/v$OLD...v$NEW" CHANGELOG.md +sed -i "/^\[Unreleased\]/a [v$NEW] https://github.com/nim65s/matrix-webhook/compare/v$OLD...v$NEW" CHANGELOG.md git add pyproject.toml CHANGELOG.md git commit -m "Release v$NEW" From d726db6ed222c0ba543ecd6fe5fef7587c8bcab4 Mon Sep 17 00:00:00 2001 From: Guilhem Saurel Date: Sat, 28 Aug 2021 00:07:30 +0200 Subject: [PATCH 030/141] release: push --- docs/release.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/release.sh b/docs/release.sh index 26b7b83..933e083 100755 --- a/docs/release.sh +++ b/docs/release.sh @@ -18,3 +18,5 @@ sed -i "/^\[Unreleased\]/a [v$NEW] https://github.com/nim65s/matrix-webhook/comp git add pyproject.toml CHANGELOG.md git commit -m "Release v$NEW" git tag -s "v$NEW" -m "Release v$NEW" +git push +git push --tags From 58d0e83f8fe0208fdcd202ddcef35548f2f51af2 Mon Sep 17 00:00:00 2001 From: Guilhem Saurel Date: Sat, 28 Aug 2021 00:07:36 +0200 Subject: [PATCH 031/141] Release v3.2.1 --- CHANGELOG.md | 5 ++++- pyproject.toml | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b4554b..d55ec76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [v3.2.1] - 2021-08-28 + - fix changelog ## [v3.2.0] - 2021-08-27 @@ -52,7 +54,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [v1.0.0] - 2020-02-14 - First release with matrix-client & http.server -[Unreleased]: https://github.com/nim65s/matrix-webhook/compare/v3.2.0...master +[Unreleased]: https://github.com/nim65s/matrix-webhook/compare/v3.2.1...master +[v3.2.1] https://github.com/nim65s/matrix-webhook/compare/v3.2.0...v3.2.1 [v3.2.0] https://github.com/nim65s/matrix-webhook/compare/v3.1.1...v3.2.0 [v3.1.1]: https://github.com/nim65s/matrix-webhook/compare/v3.1.0...v3.1.1 [v3.1.0]: https://github.com/nim65s/matrix-webhook/compare/v3.0.0...v3.1.0 diff --git a/pyproject.toml b/pyproject.toml index 4d815bb..2994446 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "matrix-webhook" -version = "3.2.0" +version = "3.2.1" description = "Post a message to a matrix room with a simple HTTP POST" authors = ["Guilhem Saurel "] license = "BSD-2-Clause" From df8ce523cb9ad6d9cfbda213744c3da9f444438b Mon Sep 17 00:00:00 2001 From: Guilhem Saurel Date: Sat, 28 Aug 2021 00:08:56 +0200 Subject: [PATCH 032/141] fix changelog --- CHANGELOG.md | 4 ++-- docs/release.sh | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d55ec76..646d4fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -55,8 +55,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - First release with matrix-client & http.server [Unreleased]: https://github.com/nim65s/matrix-webhook/compare/v3.2.1...master -[v3.2.1] https://github.com/nim65s/matrix-webhook/compare/v3.2.0...v3.2.1 -[v3.2.0] https://github.com/nim65s/matrix-webhook/compare/v3.1.1...v3.2.0 +[v3.2.1]: https://github.com/nim65s/matrix-webhook/compare/v3.2.0...v3.2.1 +[v3.2.0]: https://github.com/nim65s/matrix-webhook/compare/v3.1.1...v3.2.0 [v3.1.1]: https://github.com/nim65s/matrix-webhook/compare/v3.1.0...v3.1.1 [v3.1.0]: https://github.com/nim65s/matrix-webhook/compare/v3.0.0...v3.1.0 [v3.0.0]: https://github.com/nim65s/matrix-webhook/compare/v2.0.0...v3.0.0 diff --git a/docs/release.sh b/docs/release.sh index 933e083..c2ef8e3 100755 --- a/docs/release.sh +++ b/docs/release.sh @@ -13,7 +13,7 @@ DATE=$(date +%Y-%m-%d) sed -i "/^## \[Unreleased\]/a \\\n## [v$NEW] - $DATE" CHANGELOG.md sed -i "/^\[Unreleased\]/s/$OLD/$NEW/" CHANGELOG.md -sed -i "/^\[Unreleased\]/a [v$NEW] https://github.com/nim65s/matrix-webhook/compare/v$OLD...v$NEW" CHANGELOG.md +sed -i "/^\[Unreleased\]/a [v$NEW]: https://github.com/nim65s/matrix-webhook/compare/v$OLD...v$NEW" CHANGELOG.md git add pyproject.toml CHANGELOG.md git commit -m "Release v$NEW" From 0aea63903dc31fedb3f53200d3af4c7e8cd01f59 Mon Sep 17 00:00:00 2001 From: Guilhem Saurel Date: Sat, 28 Aug 2021 00:11:30 +0200 Subject: [PATCH 033/141] badges: add PyPI --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 62f7dbd..12fc780 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ [![Release](https://github.com/nim65s/matrix-webhook/actions/workflows/release.yml/badge.svg)](https://pypi.org/project/matrix-webhook/) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) [![codecov](https://codecov.io/gh/nim65s/matrix-webhook/branch/master/graph/badge.svg?token=BLGISGCYKG)](https://codecov.io/gh/nim65s/matrix-webhook) +[![PyPI version](https://badge.fury.io/py/matrix-webhook.svg)](https://badge.fury.io/py/matrix-webhook) Post a message to a matrix room with a simple HTTP POST From 8592be257fdd5e6d0af26e3ac3a1b5d94dcc219f Mon Sep 17 00:00:00 2001 From: Guilhem Saurel Date: Sat, 28 Aug 2021 00:15:41 +0200 Subject: [PATCH 034/141] badges: new line --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 12fc780..1971405 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ [![Lints](https://github.com/nim65s/matrix-webhook/actions/workflows/lint.yml/badge.svg)](https://github.com/nim65s/matrix-webhook/actions/workflows/lint.yml) [![Docker-Hub](https://github.com/nim65s/matrix-webhook/actions/workflows/docker-hub.yml/badge.svg)](https://hub.docker.com/r/nim65s/matrix-webhook) [![Release](https://github.com/nim65s/matrix-webhook/actions/workflows/release.yml/badge.svg)](https://pypi.org/project/matrix-webhook/) + [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) [![codecov](https://codecov.io/gh/nim65s/matrix-webhook/branch/master/graph/badge.svg?token=BLGISGCYKG)](https://codecov.io/gh/nim65s/matrix-webhook) [![PyPI version](https://badge.fury.io/py/matrix-webhook.svg)](https://badge.fury.io/py/matrix-webhook) From 179ef11ae70b6f7fcfccff641769497a16f292a8 Mon Sep 17 00:00:00 2001 From: Guilhem Saurel Date: Sat, 28 Aug 2021 00:18:42 +0200 Subject: [PATCH 035/141] readme: typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1971405..6c66d3b 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,7 @@ Add a JSON webhook with `?formatter=github`, and put the `API_KEY` as secret ### For Grafana -Add a webhook with an URL ending with `?formatter=grafana&key=API_KEY' +Add a webhook with an URL ending with `?formatter=grafana&key=API_KEY` ## Test room From ef79d39a9f6cecaaf54c70f1dca5ed46896ad499 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 30 Aug 2021 17:11:56 +0000 Subject: [PATCH 036/141] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/psf/black: 21.7b0 → 21.8b0](https://github.com/psf/black/compare/21.7b0...21.8b0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e09667c..7340a47 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -14,7 +14,7 @@ repos: - id: mixed-line-ending - id: trailing-whitespace - repo: https://github.com/psf/black - rev: 21.7b0 + rev: 21.8b0 hooks: - id: black language_version: python3 From 7aa5df3871da80b3630049c3fc6685ea9485c7f2 Mon Sep 17 00:00:00 2001 From: Guilhem Saurel Date: Sat, 28 Aug 2021 00:38:39 +0200 Subject: [PATCH 037/141] readme: detail --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6c66d3b..6e3db88 100644 --- a/README.md +++ b/README.md @@ -91,7 +91,7 @@ Add a webhook with an URL ending with `?formatter=grafana&key=API_KEY` ## Test room -[#matrix-webhook:tetaneutral.net](https://matrix.to/#/!DPrUlnwOhBEfYwsDLh:matrix.org?via=laas.fr&via=tetaneutral.net&via=aen.im) +[#matrix-webhook:tetaneutral.net](https://matrix.to/#/!DPrUlnwOhBEfYwsDLh:matrix.org) ## Unit tests From d0c4cd42274cc2261aab755190e92c3daca08b08 Mon Sep 17 00:00:00 2001 From: Guilhem Saurel Date: Sat, 28 Aug 2021 00:40:56 +0200 Subject: [PATCH 038/141] formatters: fix github format --- matrix_webhook/formatters.py | 4 ++-- tests/test_github.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/matrix_webhook/formatters.py b/matrix_webhook/formatters.py index 55ba43d..2d52788 100644 --- a/matrix_webhook/formatters.py +++ b/matrix_webhook/formatters.py @@ -22,8 +22,8 @@ def github(data, headers): pusher, ref, a, b, c = [ data[k] for k in ["pusher", "ref", "after", "before", "compare"] ] - pusher = f"[{pusher['name']}](https://github.com/{pusher['name']})" - data["body"] = f"@{pusher} pushed on {ref}: [{b} → {a}]({c}):\n\n" + pusher = f"[@{pusher['name']}](https://github.com/{pusher['name']})" + data["body"] = f"{pusher} pushed on {ref}: [{b} → {a}]({c}):\n\n" for commit in data["commits"]: data["body"] += f"- [{commit['message']}]({commit['url']})\n" else: diff --git a/tests/test_github.py b/tests/test_github.py index fed01d7..3943202 100644 --- a/tests/test_github.py +++ b/tests/test_github.py @@ -93,7 +93,7 @@ class GithubFormatterTest(unittest.IsolatedAsyncioTestCase): before = "ac7d1d9647008145e9d0cf65d24744d0db4862b8" after = "4bcdb25c809391baaabc264d9309059f9f48ead2" GH = "https://github.com" - expected = f'

@nim65s pushed on refs/heads/devel: ' + expected = f'

@nim65s pushed on refs/heads/devel: ' expected += f'{before} → {after}:

\n