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..70b8eb2c --- /dev/null +++ b/notifiers/providers/dingtalk.py @@ -0,0 +1,90 @@ +from ..core import Provider +from ..core import Response +from ..utils import requests +from ..utils.helpers import snake_to_camel_case + + +class DingTalk(Provider): + """Send DingTalk notifications""" + + base_url = "https://oapi.dingtalk.com/robot/send" + site_url = "https://oapi.dingtalk.com/" + path_to_errors = ("message",) + name = "dingtalk" + + _required = {"required": ["access_token", "message"]} + _schema = { + "type": "object", + "properties": { + "access_token": { + "type": "string", + "title": "access token to pair a channel to receive notification", + }, + "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_key = snake_to_camel_case(new_data["msgtype"]) + new_data["msgtype"] = camel_case_key[0].lower() + camel_case_key[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: + params = {"access_token": data.pop("access_token")} + response, errors = requests.post( + self.base_url, params=params, json=data, path_to_errors=self.path_to_errors + ) + return self.create_response(data, response, errors) 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. diff --git a/source/providers/dingtalk.rst b/source/providers/dingtalk.rst new file mode 100644 index 00000000..a0ba88d4 --- /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', message='Hi there!') + +Full schema: + +.. code-block:: yaml + + additionalProperties: false + properties: + access_token: + title: your access token + type: string + message: + title: message content + type: string + required: + - access_token + - message + type: object + diff --git a/tests/providers/test_dingtalk.py b/tests/providers/test_dingtalk.py new file mode 100644 index 00000000..410bf7fd --- /dev/null +++ b/tests/providers/test_dingtalk.py @@ -0,0 +1,17 @@ +import pytest + +provider = "dingtalk" + + +class TestDingTalk: + def test_dingtalk_metadata(self, provider): + assert provider.metadata == { + "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): + data = {"access_token": "token", "message": test_message} + provider.notify(**data, raise_on_errors=True)