From 873068071e12a8026e61c5d92d32b1d3259c36f7 Mon Sep 17 00:00:00 2001 From: Bence Skorka <skorka.bence@gmail.com> Date: Sun, 26 Feb 2023 00:34:04 +0100 Subject: [PATCH 1/2] Generate markdown messages --- main.py | 3 +- notify/channels/email.py | 7 +++- notify/channels/matrix.py | 39 ++++++++----------- notify/channels/vonage_sms.py | 5 ++- notify/messages/__tests__/test_generator.py | 15 +++---- notify/messages/generator.py | 3 +- notify/messages/notification.py | 2 +- .../__tests__/test_markdown_converter.py | 26 +++++++++++++ notify/text_converters/markdown_converter.py | 26 +++++++++++++ requirements.txt | 1 + templates/daily.j2 | 10 ++--- 11 files changed, 97 insertions(+), 40 deletions(-) create mode 100644 notify/text_converters/__tests__/test_markdown_converter.py create mode 100644 notify/text_converters/markdown_converter.py diff --git a/main.py b/main.py index c1188dc..2490476 100644 --- a/main.py +++ b/main.py @@ -7,6 +7,7 @@ from notify.config import load_config, build_channels from notify.timeline.basic import BasicTimelineIterator, FixedDay from notify.messages.generator import build_notification from notify.calendar.calendar import Calendar +from notify.text_converters.markdown_converter import markdown_to_html def parse_args() -> Any: @@ -59,7 +60,7 @@ def main() -> None: channel.send_notification(notification) else: print("--- This is a dry-run, messages are not sent ---") - print(notification.message) + print(markdown_to_html(notification.markdown_message)) if __name__ == "__main__": diff --git a/notify/channels/email.py b/notify/channels/email.py index e88d076..8ca55fb 100644 --- a/notify/channels/email.py +++ b/notify/channels/email.py @@ -1,6 +1,7 @@ from typing import Optional from .channel import NotificationChannel from ..messages.notification import Notification +from ..text_converters.markdown_converter import markdown_to_html, markdown_to_plaintext from redmail.email.sender import EmailSender @@ -23,6 +24,9 @@ class EmailChannel(NotificationChannel): ) def send_notification(self, message: Notification) -> None: + html_message = markdown_to_html(message.markdown_message) + text_message = markdown_to_plaintext(message.markdown_message) + try: self._client.connect() # type: ignore for to_email in self._to_emails: @@ -32,7 +36,8 @@ class EmailChannel(NotificationChannel): subject=message.title, sender=self._from_email, receivers=to_email, - text=message.message, + html=html_message, + text=text_message ) except Exception as e: print(f"Failed to send email: {e}") diff --git a/notify/channels/matrix.py b/notify/channels/matrix.py index 97ac7b6..c522864 100644 --- a/notify/channels/matrix.py +++ b/notify/channels/matrix.py @@ -1,34 +1,27 @@ import requests -import urllib.parse from .channel import NotificationChannel from ..messages.notification import Notification +from ..text_converters.markdown_converter import markdown_to_plaintext class MatrixMessageChannel(NotificationChannel): - def __init__(self, bot_server: str, instance_id: str, secret: str, room_ids: list[str]) -> None: + def __init__(self, webhook_url: str, webhook_key: str) -> None: super().__init__() - self._bot_server = bot_server - self._instance_id = instance_id - self._secret = secret - self.room_ids = room_ids + self._webhook_url = webhook_url + self._webhook_key = webhook_key def send_notification(self, message: Notification) -> None: - for room_id in self.room_ids: - print("Sending message to room", room_id) + plaintext_message = markdown_to_plaintext(message.markdown_message) - e_instance_id = urllib.parse.quote(self._instance_id) - e_room_id = urllib.parse.quote(room_id) - url = f"https://{self._bot_server}/_matrix/maubot/plugin/{e_instance_id}/webhook/r0?room={e_room_id}" - - try: - response = requests.post( - url, - json={ - "secret": self._secret, - "message": message.message - } - ) - response.raise_for_status() - except Exception as e: - print(f"Failed to send matrix message: {e}") + try: + response = requests.post( + self._webhook_url, + json={ + "key": self._webhook_key, + "text": plaintext_message + } + ) + response.raise_for_status() + except Exception as e: + print(f"Failed to send matrix message: {e}") diff --git a/notify/channels/vonage_sms.py b/notify/channels/vonage_sms.py index 80b8fd6..bd88d95 100644 --- a/notify/channels/vonage_sms.py +++ b/notify/channels/vonage_sms.py @@ -2,6 +2,7 @@ import nexmo from .channel import NotificationChannel from ..messages.notification import Notification +from ..text_converters.markdown_converter import markdown_to_plaintext class VonageSmsChannel(NotificationChannel): @@ -12,6 +13,8 @@ class VonageSmsChannel(NotificationChannel): self._sender = sender def send_notification(self, message: Notification) -> None: + plaintext_message = markdown_to_plaintext(message.markdown_message) + for number in self._numbers: print("Sending SMS to", number) try: @@ -19,7 +22,7 @@ class VonageSmsChannel(NotificationChannel): 'from': self._sender, 'to': number, 'text': message.short_message - if message.short_message else message.message + if message.short_message else plaintext_message }) except Exception as e: print(f"Failed to send SMS: {e}") diff --git a/notify/messages/__tests__/test_generator.py b/notify/messages/__tests__/test_generator.py index 2e6a43a..9e49796 100644 --- a/notify/messages/__tests__/test_generator.py +++ b/notify/messages/__tests__/test_generator.py @@ -51,16 +51,17 @@ def test_message_generation(basic_timeline_iterator: TimelineIterator, template_ msg = build_notification(basic_timeline_iterator, template_config, date(2022, 9, 21)) assert msg is not None assert msg.title == "Daily standup" - assert msg.message == """Dear Team Tirith! + assert msg.markdown_message == """Dear Team Tirith, -Today John will hold the daily meeting! +Today **John** will hold the daily meeting! The upcoming schedule for the next 5 workdays: - 2022-09-22 (Thursday): John - 2022-09-23 (Friday): John - 2022-09-26 (Monday): Jane - 2022-09-27 (Tuesday): Jane - 2022-09-28 (Wednesday): Jane + +* 2022-09-22 (Thursday): John +* 2022-09-23 (Friday): John +* 2022-09-26 (Monday): Jane +* 2022-09-27 (Tuesday): Jane +* 2022-09-28 (Wednesday): Jane Have a nice day!""" diff --git a/notify/messages/generator.py b/notify/messages/generator.py index 0b3cdf9..b9e733b 100644 --- a/notify/messages/generator.py +++ b/notify/messages/generator.py @@ -1,5 +1,6 @@ from typing import Any, Optional import jinja2 +from markdown import Markdown from datetime import date from ..timeline.iterator import TimelineIterator, CalendarDay @@ -43,7 +44,7 @@ def build_notification(iter: TimelineIterator, config: Any, starting_day: date) return Notification( title=title, - message=render(normal_msg_template_file, template_data), + markdown_message=render(normal_msg_template_file, template_data), short_message=render(short_msg_template_file, template_data) if short_msg_template_file else None ) diff --git a/notify/messages/notification.py b/notify/messages/notification.py index 802578b..4e92626 100644 --- a/notify/messages/notification.py +++ b/notify/messages/notification.py @@ -5,5 +5,5 @@ from typing import Optional @dataclass(frozen=True) class Notification: title: str - message: str + markdown_message: str short_message: Optional[str] diff --git a/notify/text_converters/__tests__/test_markdown_converter.py b/notify/text_converters/__tests__/test_markdown_converter.py new file mode 100644 index 0000000..4568979 --- /dev/null +++ b/notify/text_converters/__tests__/test_markdown_converter.py @@ -0,0 +1,26 @@ +from notify.text_converters.markdown_converter import markdown_to_html, markdown_to_plaintext + + +def test_basic_html_conversion() -> None: + markdown_text = "Hi, this is **markdown** _text_." + + assert markdown_to_html( + markdown_text) == "<p>Hi, this is <strong>markdown</strong> <em>text</em>.</p>" + + +def test_basic_plaintext_conversion() -> None: + markdown_text = "Hi, this is **markdown** _text_" + + assert markdown_to_plaintext( + markdown_text) == "Hi, this is markdown text" + + +def test_multiline_plaintext_conversion() -> None: + markdown_text = """Hi, this is **markdown** _text_. + +And this is the second line. +""" + + assert markdown_to_plaintext( + markdown_text) == """Hi, this is markdown text. +And this is the second line.""" diff --git a/notify/text_converters/markdown_converter.py b/notify/text_converters/markdown_converter.py new file mode 100644 index 0000000..2881a87 --- /dev/null +++ b/notify/text_converters/markdown_converter.py @@ -0,0 +1,26 @@ +from typing import Any +import markdown +from io import StringIO + + +def _unmark_element(element: Any, stream: Any = None) -> Any: + if stream is None: + stream = StringIO() + if element.text: + stream.write(element.text) + for sub in element: + _unmark_element(sub, stream) + if element.tail: + stream.write(element.tail) + return stream.getvalue() + + +def markdown_to_html(markdown_text: str) -> str: + return markdown.markdown(markdown_text, output_format="html") + + +def markdown_to_plaintext(markdown_text: str) -> str: + markdown.Markdown.output_formats["plain"] = _unmark_element # type: ignore + __md = markdown.Markdown(output_format="plain") # type: ignore + __md.stripTopLevelTags = False # type: ignore + return __md.convert(markdown_text) diff --git a/requirements.txt b/requirements.txt index dbf7d62..bdb13a0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -16,3 +16,4 @@ mypy # types types-requests types-PyYAML +types-Markdown diff --git a/templates/daily.j2 b/templates/daily.j2 index b6d2ecf..c73b6ea 100644 --- a/templates/daily.j2 +++ b/templates/daily.j2 @@ -1,10 +1,10 @@ -Dear Team Tirith! +Dear Team Tirith, -Today {{ today.moderator }} will hold the daily meeting! +Today **{{ today.moderator }}** will hold the daily meeting! The upcoming schedule for the next {{ days | length }} workdays: -{%- for item in days %} - {{ item.day }} ({{ item.dow }}): {{ item.moderator }} -{%- endfor %} +{% for item in days -%} +* {{ item.day }} ({{ item.dow }}): {{ item.moderator }} +{% endfor %} Have a nice day! -- GitLab From 56acf113ad776d8562292eadf250dbdbf677a137 Mon Sep 17 00:00:00 2001 From: Bence Skorka <skorka.bence@gmail.com> Date: Sun, 26 Feb 2023 00:35:45 +0100 Subject: [PATCH 2/2] Remove unused import --- notify/messages/generator.py | 1 - 1 file changed, 1 deletion(-) diff --git a/notify/messages/generator.py b/notify/messages/generator.py index b9e733b..7e55bb8 100644 --- a/notify/messages/generator.py +++ b/notify/messages/generator.py @@ -1,6 +1,5 @@ from typing import Any, Optional import jinja2 -from markdown import Markdown from datetime import date from ..timeline.iterator import TimelineIterator, CalendarDay -- GitLab