From 7b00d5e2e9a0f39ba0c36da34638e7f36c68a7ef Mon Sep 17 00:00:00 2001 From: Henry Guo Date: Wed, 9 Mar 2022 16:05:53 +0800 Subject: [PATCH 01/10] feat: dingTalk Robot message support --- notifiers/providers/__init__.py | 2 ++ notifiers/providers/dingtalk.py | 39 ++++++++++++++++++++++++++++++++ source/providers/dingtalk.rst | 29 ++++++++++++++++++++++++ tests/providers/test_dingtalk.py | 23 +++++++++++++++++++ 4 files changed, 93 insertions(+) create mode 100644 notifiers/providers/dingtalk.py create mode 100644 source/providers/dingtalk.rst create mode 100644 tests/providers/test_dingtalk.py diff --git a/notifiers/providers/__init__.py b/notifiers/providers/__init__.py index 2ae02e27..9b9419c3 100644 --- a/notifiers/providers/__init__.py +++ b/notifiers/providers/__init__.py @@ -1,3 +1,4 @@ +from . import dingtalk from . import email from . import gitter from . import gmail @@ -21,6 +22,7 @@ "simplepush": simplepush.SimplePush, "slack": slack.Slack, "email": email.SMTP, + "dingtalk": dingtalk.DingTalk, "gmail": gmail.Gmail, "icloud": icloud.iCloud, "telegram": telegram.Telegram, diff --git a/notifiers/providers/dingtalk.py b/notifiers/providers/dingtalk.py new file mode 100644 index 00000000..167fcee7 --- /dev/null +++ b/notifiers/providers/dingtalk.py @@ -0,0 +1,39 @@ +from ..core import Provider +from ..core import Response +from ..utils import requests + + +class DingTalk(Provider): + """Send DingTalk notifications""" + + base_url = "https://oapi.dingtalk.com/robot/send?access_token={}" + site_url = "https://oapi.dingtalk.com/" + path_to_errors = ("message",) + name = "dingtalk" + + _required = {"required": ["access_token", "msg_data"]} + _schema = { + "type": "object", + "properties": { + "access_token": { + "type": "string", + "title": "access token to pair a channel to receive notification", + }, + "msg_data": { + "type": "object", + "title": "dingtalk message body definition", + }, + }, + "additionalProperties": False, + } + + def _prepare_data(self, data: dict) -> dict: + return data + + def _send_notification(self, data: dict) -> Response: + access_token = data.pop("access_token") + url = self.base_url.format(access_token) + response, errors = requests.post( + url, json=data.pop('msg_data'), path_to_errors=self.path_to_errors + ) + return self.create_response(data, response, errors) diff --git a/source/providers/dingtalk.rst b/source/providers/dingtalk.rst new file mode 100644 index 00000000..6bafc256 --- /dev/null +++ b/source/providers/dingtalk.rst @@ -0,0 +1,29 @@ +DingTalk +---------- +Send `DingTalk Robot `_ notifications + +Minimal example: + +.. code-block:: python + + >>> from notifiers import get_notifier + >>> dingtalk = get_notifier('dingtalk') + >>> dingtalk.notify(access_token='token', msg_data={'msgtype': 'text', 'text':{'content': 'Hi there!'}}) + +Full schema: + +.. code-block:: yaml + + additionalProperties: false + properties: + access_token: + title: your access token + type: string + msg_data: + title: your message definition + type: object + required: + - access_token + - msg_data + type: object + diff --git a/tests/providers/test_dingtalk.py b/tests/providers/test_dingtalk.py new file mode 100644 index 00000000..4cbaa677 --- /dev/null +++ b/tests/providers/test_dingtalk.py @@ -0,0 +1,23 @@ +import pytest + +provider = "dingtalk" + + +class TestDingTalk: + def test_dingtalk_metadata(self, provider): + assert provider.metadata == { + "base_url": "https://oapi.dingtalk.com/robot/send?access_token={}", + "name": "dingtalk", + "site_url": "https://oapi.dingtalk.com/", + } + + @pytest.mark.online + def test_sanity(self, provider, test_message): + msg_data = { + "msgtype": "text", + "text": { + "content": test_message + } + } + data = {"access_token": "token", "msg_data": msg_data} + provider.notify(**data, raise_on_errors=True) From 685262110aa677821d63658d40e7141398521ee8 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 9 Mar 2022 08:08:31 +0000 Subject: [PATCH 02/10] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- notifiers/providers/dingtalk.py | 2 +- tests/providers/test_dingtalk.py | 7 +------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/notifiers/providers/dingtalk.py b/notifiers/providers/dingtalk.py index 167fcee7..92c04777 100644 --- a/notifiers/providers/dingtalk.py +++ b/notifiers/providers/dingtalk.py @@ -34,6 +34,6 @@ def _send_notification(self, data: dict) -> Response: access_token = data.pop("access_token") url = self.base_url.format(access_token) response, errors = requests.post( - url, json=data.pop('msg_data'), path_to_errors=self.path_to_errors + url, json=data.pop("msg_data"), path_to_errors=self.path_to_errors ) return self.create_response(data, response, errors) diff --git a/tests/providers/test_dingtalk.py b/tests/providers/test_dingtalk.py index 4cbaa677..6ad191a9 100644 --- a/tests/providers/test_dingtalk.py +++ b/tests/providers/test_dingtalk.py @@ -13,11 +13,6 @@ def test_dingtalk_metadata(self, provider): @pytest.mark.online def test_sanity(self, provider, test_message): - msg_data = { - "msgtype": "text", - "text": { - "content": test_message - } - } + msg_data = {"msgtype": "text", "text": {"content": test_message}} data = {"access_token": "token", "msg_data": msg_data} provider.notify(**data, raise_on_errors=True) From 2ae5ca06cb6b6df726ce1505cc15d2718ce2d08b Mon Sep 17 00:00:00 2001 From: Henry Guo Date: Thu, 10 Mar 2022 11:18:38 +0800 Subject: [PATCH 03/10] style: improve dingtalk `msg_data` schema --- notifiers/providers/dingtalk.py | 78 +++++++++++++++++++++++++++++++-- 1 file changed, 75 insertions(+), 3 deletions(-) diff --git a/notifiers/providers/dingtalk.py b/notifiers/providers/dingtalk.py index 92c04777..5cd68fb4 100644 --- a/notifiers/providers/dingtalk.py +++ b/notifiers/providers/dingtalk.py @@ -22,14 +22,86 @@ class DingTalk(Provider): "msg_data": { "type": "object", "title": "dingtalk message body definition", + "properties": { + "msgtype": { + "type": "string", + "title": "choose a message type, these type supported: text, markdown, link, actionCard" + }, + "text": { + "type": "object", + "title": "text message", + "properties": { + "content": { + "type": "string", + "title": "text message content" + } + }, + "required": ["content"] + }, + "markdown": { + "type": "object", + "title": "markdown message", + "properties": { + "title": { + "type": "string", + "title": "message title" + }, + "text": { + "type": "string", + "title": "markdown message content" + } + }, + "required": ["title", "text"] + }, + "link": { + "type": "object", + "title": "link message", + "properties": { + "title": { + "type": "string", + "title": "message title" + }, + "text": { + "type": "string", + "title": "message content" + }, + "messageUrl": { + "type": "string", + "title": "link url" + } + }, + "required": ["title", "text", "messageUrl"] + }, + "actionCard": { + "type": "object", + "title": "card message", + "properties": { + "title": { + "type": "string", + "title": "message title" + }, + "text": { + "type": "string", + "title": "message content" + }, + "singleTitle": { + "type": "string", + "title": "title for card footage button, like 'Read more.' button" + }, + "singleURL": { + "type": "string", + "title": "link url when user click card button" + } + }, + "required": ["title", "text", "singleTitle", "singleURL"] + }, + }, + "required": ["msgtype"] }, }, "additionalProperties": False, } - def _prepare_data(self, data: dict) -> dict: - return data - def _send_notification(self, data: dict) -> Response: access_token = data.pop("access_token") url = self.base_url.format(access_token) From f42f45e32210715f245b7b8b680d01b9bd6edff9 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 10 Mar 2022 03:19:00 +0000 Subject: [PATCH 04/10] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- notifiers/providers/dingtalk.py | 54 +++++++++++---------------------- 1 file changed, 18 insertions(+), 36 deletions(-) diff --git a/notifiers/providers/dingtalk.py b/notifiers/providers/dingtalk.py index 5cd68fb4..9d0364cb 100644 --- a/notifiers/providers/dingtalk.py +++ b/notifiers/providers/dingtalk.py @@ -25,7 +25,7 @@ class DingTalk(Provider): "properties": { "msgtype": { "type": "string", - "title": "choose a message type, these type supported: text, markdown, link, actionCard" + "title": "choose a message type, these type supported: text, markdown, link, actionCard", }, "text": { "type": "object", @@ -33,70 +33,52 @@ class DingTalk(Provider): "properties": { "content": { "type": "string", - "title": "text message content" + "title": "text message content", } }, - "required": ["content"] + "required": ["content"], }, "markdown": { "type": "object", "title": "markdown message", "properties": { - "title": { - "type": "string", - "title": "message title" - }, + "title": {"type": "string", "title": "message title"}, "text": { "type": "string", - "title": "markdown message content" - } + "title": "markdown message content", + }, }, - "required": ["title", "text"] + "required": ["title", "text"], }, "link": { "type": "object", "title": "link message", "properties": { - "title": { - "type": "string", - "title": "message title" - }, - "text": { - "type": "string", - "title": "message content" - }, - "messageUrl": { - "type": "string", - "title": "link url" - } + "title": {"type": "string", "title": "message title"}, + "text": {"type": "string", "title": "message content"}, + "messageUrl": {"type": "string", "title": "link url"}, }, - "required": ["title", "text", "messageUrl"] + "required": ["title", "text", "messageUrl"], }, "actionCard": { "type": "object", "title": "card message", "properties": { - "title": { - "type": "string", - "title": "message title" - }, - "text": { - "type": "string", - "title": "message content" - }, + "title": {"type": "string", "title": "message title"}, + "text": {"type": "string", "title": "message content"}, "singleTitle": { "type": "string", - "title": "title for card footage button, like 'Read more.' button" + "title": "title for card footage button, like 'Read more.' button", }, "singleURL": { "type": "string", - "title": "link url when user click card button" - } + "title": "link url when user click card button", + }, }, - "required": ["title", "text", "singleTitle", "singleURL"] + "required": ["title", "text", "singleTitle", "singleURL"], }, }, - "required": ["msgtype"] + "required": ["msgtype"], }, }, "additionalProperties": False, From 601a896fd714f62477ec920660a5a27b0ce2c958 Mon Sep 17 00:00:00 2001 From: Henry Guo Date: Fri, 11 Mar 2022 14:53:41 +0800 Subject: [PATCH 05/10] style: update `dingtalk` provider params --- notifiers/providers/dingtalk.py | 139 ++++++++++++++------------------ 1 file changed, 59 insertions(+), 80 deletions(-) diff --git a/notifiers/providers/dingtalk.py b/notifiers/providers/dingtalk.py index 5cd68fb4..042e1c56 100644 --- a/notifiers/providers/dingtalk.py +++ b/notifiers/providers/dingtalk.py @@ -11,7 +11,7 @@ class DingTalk(Provider): path_to_errors = ("message",) name = "dingtalk" - _required = {"required": ["access_token", "msg_data"]} + _required = {"required": ["access_token", "message"]} _schema = { "type": "object", "properties": { @@ -19,93 +19,72 @@ class DingTalk(Provider): "type": "string", "title": "access token to pair a channel to receive notification", }, - "msg_data": { - "type": "object", - "title": "dingtalk message body definition", - "properties": { - "msgtype": { - "type": "string", - "title": "choose a message type, these type supported: text, markdown, link, actionCard" - }, - "text": { - "type": "object", - "title": "text message", - "properties": { - "content": { - "type": "string", - "title": "text message content" - } - }, - "required": ["content"] - }, - "markdown": { - "type": "object", - "title": "markdown message", - "properties": { - "title": { - "type": "string", - "title": "message title" - }, - "text": { - "type": "string", - "title": "markdown message content" - } - }, - "required": ["title", "text"] - }, - "link": { - "type": "object", - "title": "link message", - "properties": { - "title": { - "type": "string", - "title": "message title" - }, - "text": { - "type": "string", - "title": "message content" - }, - "messageUrl": { - "type": "string", - "title": "link url" - } - }, - "required": ["title", "text", "messageUrl"] - }, - "actionCard": { - "type": "object", - "title": "card message", - "properties": { - "title": { - "type": "string", - "title": "message title" - }, - "text": { - "type": "string", - "title": "message content" - }, - "singleTitle": { - "type": "string", - "title": "title for card footage button, like 'Read more.' button" - }, - "singleURL": { - "type": "string", - "title": "link url when user click card button" - } - }, - "required": ["title", "text", "singleTitle", "singleURL"] - }, - }, - "required": ["msgtype"] + "msg_type": { + "type": "string", + "title": "choose a message type, these type are supported: text, markdown, link, action_card", + "enum": ["text", "markdown", "link", "action_card"] + }, + "message": { + "type": "string", + "title": "This is the text that will be posted to the dingtalk", + "maxLength": 4096, + }, + "msg_title": { + "type": "string", + "title": "title for markdown message and card message" + }, + "msg_url": { + "type": "string", + "format": "uri", + "title": "url for markdown message" + }, + "msg_btn_title": { + "type": "string", + "title": "title for card message button, like 'Read more.'" }, + "msg_btn_url": { + "type": "string", + "format": "uri", + "title": "url for card message button" + } }, "additionalProperties": False, } + @property + def defaults(self) -> dict: + return {"msg_type": "text"} + + def _prepare_data(self, data: dict) -> dict: + text = data.pop("message") + mapping_key = { + "msg_title": "title", + "msg_url": "messageUrl", + "msg_btn_title": "singleTitle", + "msg_btn_url": "singleURL" + } + + new_data = { + "access_token": data.pop("access_token"), + "msgtype": data.pop("msg_type") + } + + if new_data["msgtype"] != "text": + camel_case_str = "".join(word.capitalize() for word in new_data["msgtype"].split("_")) + new_data["msgtype"] = camel_case_str[0].lower() + camel_case_str[1:] + + new_data[new_data["msgtype"]] = {"text": text} + for key in data: + new_data[new_data["msgtype"]][mapping_key[key]] = data[key] + else: + new_data["text"] = {"content": text} + + return new_data + def _send_notification(self, data: dict) -> Response: access_token = data.pop("access_token") url = self.base_url.format(access_token) response, errors = requests.post( - url, json=data.pop("msg_data"), path_to_errors=self.path_to_errors + url, json=data, path_to_errors=self.path_to_errors ) return self.create_response(data, response, errors) From a36f245fd91729b53db6c01944b460dbf157a580 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 11 Mar 2022 07:16:59 +0000 Subject: [PATCH 06/10] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- notifiers/providers/dingtalk.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/notifiers/providers/dingtalk.py b/notifiers/providers/dingtalk.py index 271aa05d..d5882b36 100644 --- a/notifiers/providers/dingtalk.py +++ b/notifiers/providers/dingtalk.py @@ -23,7 +23,7 @@ class DingTalk(Provider): "msg_type": { "type": "string", "title": "choose a message type, these type are supported: text, markdown, link, action_card", - "enum": ["text", "markdown", "link", "action_card"] + "enum": ["text", "markdown", "link", "action_card"], }, "message": { "type": "string", @@ -32,22 +32,22 @@ class DingTalk(Provider): }, "msg_title": { "type": "string", - "title": "title for markdown message and card message" + "title": "title for markdown message and card message", }, "msg_url": { "type": "string", "format": "uri", - "title": "url for markdown message" + "title": "url for markdown message", }, "msg_btn_title": { "type": "string", - "title": "title for card message button, like 'Read more.'" + "title": "title for card message button, like 'Read more.'", }, "msg_btn_url": { "type": "string", "format": "uri", - "title": "url for card message button" - } + "title": "url for card message button", + }, }, "additionalProperties": False, } @@ -62,12 +62,12 @@ def _prepare_data(self, data: dict) -> dict: "msg_title": "title", "msg_url": "messageUrl", "msg_btn_title": "singleTitle", - "msg_btn_url": "singleURL" + "msg_btn_url": "singleURL", } new_data = { "access_token": data.pop("access_token"), - "msgtype": data.pop("msg_type") + "msgtype": data.pop("msg_type"), } if new_data["msgtype"] != "text": From 4dd4cbf1e102f5b22cfac432124c364d00446168 Mon Sep 17 00:00:00 2001 From: Henry Guo Date: Fri, 11 Mar 2022 16:09:49 +0800 Subject: [PATCH 07/10] style: make the `access_token` as a params for requests --- notifiers/providers/dingtalk.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/notifiers/providers/dingtalk.py b/notifiers/providers/dingtalk.py index d5882b36..70b8eb2c 100644 --- a/notifiers/providers/dingtalk.py +++ b/notifiers/providers/dingtalk.py @@ -7,7 +7,7 @@ class DingTalk(Provider): """Send DingTalk notifications""" - base_url = "https://oapi.dingtalk.com/robot/send?access_token={}" + base_url = "https://oapi.dingtalk.com/robot/send" site_url = "https://oapi.dingtalk.com/" path_to_errors = ("message",) name = "dingtalk" @@ -83,9 +83,8 @@ def _prepare_data(self, data: dict) -> dict: return new_data def _send_notification(self, data: dict) -> Response: - access_token = data.pop("access_token") - url = self.base_url.format(access_token) + params = {"access_token": data.pop("access_token")} response, errors = requests.post( - url, json=data, path_to_errors=self.path_to_errors + self.base_url, params=params, json=data, path_to_errors=self.path_to_errors ) return self.create_response(data, response, errors) From 73871a232890b3a50ce69a3f43b68a350cdad1a8 Mon Sep 17 00:00:00 2001 From: Henry Guo Date: Sun, 13 Mar 2022 23:03:34 +0800 Subject: [PATCH 08/10] test: fix the test case and doc --- source/providers/dingtalk.rst | 10 +++++----- tests/providers/test_dingtalk.py | 5 ++--- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/source/providers/dingtalk.rst b/source/providers/dingtalk.rst index 6bafc256..a0ba88d4 100644 --- a/source/providers/dingtalk.rst +++ b/source/providers/dingtalk.rst @@ -8,7 +8,7 @@ Minimal example: >>> from notifiers import get_notifier >>> dingtalk = get_notifier('dingtalk') - >>> dingtalk.notify(access_token='token', msg_data={'msgtype': 'text', 'text':{'content': 'Hi there!'}}) + >>> dingtalk.notify(access_token='token', message='Hi there!') Full schema: @@ -19,11 +19,11 @@ Full schema: access_token: title: your access token type: string - msg_data: - title: your message definition - type: object + message: + title: message content + type: string required: - access_token - - msg_data + - message type: object diff --git a/tests/providers/test_dingtalk.py b/tests/providers/test_dingtalk.py index 6ad191a9..7c06de2a 100644 --- a/tests/providers/test_dingtalk.py +++ b/tests/providers/test_dingtalk.py @@ -6,13 +6,12 @@ class TestDingTalk: def test_dingtalk_metadata(self, provider): assert provider.metadata == { - "base_url": "https://oapi.dingtalk.com/robot/send?access_token={}", + "base_url": "https://oapi.dingtalk.com/robot/send", "name": "dingtalk", "site_url": "https://oapi.dingtalk.com/", } @pytest.mark.online def test_sanity(self, provider, test_message): - msg_data = {"msgtype": "text", "text": {"content": test_message}} - data = {"access_token": "token", "msg_data": msg_data} + data = {"access_token": "token", "message": test_message} provider.notify(**data, raise_on_errors=True) From f4222a02f13105c10aceccebb93ff2ea1d884a21 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 13 Mar 2022 15:03:56 +0000 Subject: [PATCH 09/10] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/providers/test_dingtalk.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/providers/test_dingtalk.py b/tests/providers/test_dingtalk.py index 7c06de2a..410bf7fd 100644 --- a/tests/providers/test_dingtalk.py +++ b/tests/providers/test_dingtalk.py @@ -13,5 +13,5 @@ def test_dingtalk_metadata(self, provider): @pytest.mark.online def test_sanity(self, provider, test_message): - data = {"access_token": "token", "message": test_message} + data = {"access_token": "token", "message": test_message} provider.notify(**data, raise_on_errors=True) From b47eba4cb9f89a604e0891565ba3f5a2b7c96a32 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 19 Jul 2022 16:31:29 +0000 Subject: [PATCH 10/10] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- source/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/conf.py b/source/conf.py index 6f39d52c..ec3e4771 100644 --- a/source/conf.py +++ b/source/conf.py @@ -76,7 +76,7 @@ # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. -language = 'en' +language = "en" # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files.