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