diff --git a/services/chat-router/hive/chat_router/handlers/ping.py b/services/chat-router/hive/chat_router/handlers/ping.py
index 6902a5d..c4fc316 100644
--- a/services/chat-router/hive/chat_router/handlers/ping.py
+++ b/services/chat-router/hive/chat_router/handlers/ping.py
@@ -3,8 +3,8 @@
from typing import Optional
-from hive.messaging import Channel
from hive.chat import ChatMessage, tell_user
+from hive.messaging import Channel
from ..handler import Handler
diff --git a/services/chat-router/hive/chat_router/handlers/reading_list.py b/services/chat-router/hive/chat_router/handlers/reading_list.py
new file mode 100644
index 0000000..1b686a3
--- /dev/null
+++ b/services/chat-router/hive/chat_router/handlers/reading_list.py
@@ -0,0 +1,77 @@
+import logging
+
+from email.utils import format_datetime
+from html import escape
+from urllib.parse import urlparse
+
+from hive.chat import ChatMessage, tell_user
+from hive.messaging import Channel
+
+from ..handler import Handler
+
+logger = logging.getLogger(__name__)
+d = logger.info
+
+
+class ReadingListUpdateHandler(Handler):
+ def handle(self, channel: Channel, message: ChatMessage) -> bool:
+ try:
+ url = urlparse(message.text.split(maxsplit=1)[0])
+ except Exception:
+ return False
+ if url.scheme not in {"http", "https"}:
+ return False
+
+ d("Reading list update: %r", message.text)
+ self.request_reading_list_update(channel, message)
+
+ try:
+ self.update_message_in_webui(channel, message)
+ except Exception:
+ logger.warning(
+ "EXCEPTION processing %s",
+ message,
+ exc_info=True,
+ )
+
+ return True
+
+ def request_reading_list_update(
+ self,
+ channel: Channel,
+ message: ChatMessage
+ ):
+ channel.publish_request(
+ message={
+ "meta": {
+ "origin": {
+ "channel": "chat",
+ "message": message.json(),
+ },
+ },
+ "date": format_datetime(message.timestamp),
+ "body": message.text,
+ },
+ routing_key="readinglist.update.requests",
+ )
+
+ def update_message_in_webui(
+ self,
+ channel: Channel,
+ message: ChatMessage
+ ):
+ if message.html:
+ split_html = message.html.split(maxsplit=1)
+ else:
+ split_text = message.text.split(maxsplit=1)
+ split_html = list(map(escape, split_text))
+
+ link = split_html[0]
+ if not link.startswith("http"):
+ raise ValueError(link)
+ if '"' in link:
+ raise ValueError(link)
+ split_html[0] = f'{link}'
+
+ message.html = " ".join(split_html)
+ tell_user(message, channel=channel)
diff --git a/services/chat-router/tests/conftest.py b/services/chat-router/tests/conftest.py
new file mode 100644
index 0000000..3607169
--- /dev/null
+++ b/services/chat-router/tests/conftest.py
@@ -0,0 +1,23 @@
+import pytest
+
+
+class MockChannel:
+ def __init__(self):
+ self.call_log = []
+
+ def __getattr__(self, attr):
+ return MockMethod(attr, self.call_log)
+
+
+class MockMethod:
+ def __init__(self, name, call_log):
+ self.name = name
+ self.call_log = call_log
+
+ def __call__(self, *args, **kwargs):
+ self.call_log.append((self.name, args, kwargs))
+
+
+@pytest.fixture
+def mock_channel():
+ return MockChannel()
diff --git a/services/chat-router/tests/test_reading_list.py b/services/chat-router/tests/test_reading_list.py
new file mode 100644
index 0000000..2df2f88
--- /dev/null
+++ b/services/chat-router/tests/test_reading_list.py
@@ -0,0 +1,56 @@
+from datetime import datetime, timezone
+
+import pytest
+
+from hive.chat import ChatMessage
+
+from hive.chat_router.handlers.reading_list import ReadingListUpdateHandler
+
+
+@pytest.mark.parametrize(
+ "body,expect_html",
+ (("http://www.example.com",
+ 'http://www.example.com'),
+ ("https://example.com/foo?whatever=4#bar some quote",
+ ''
+ "https://example.com/foo?whatever=4#bar some quote"),
+ ))
+def test_reading_list_update(mock_channel, body, expect_html):
+ handler = ReadingListUpdateHandler()
+ assert handler.handle(mock_channel, ChatMessage(
+ text=body,
+ sender="user",
+ timestamp=datetime.fromtimestamp(1730071727.043, tz=timezone.utc),
+ uuid="1c0a44e5-48ac-4464-b9ef-0117b11c2140",
+ ))
+ assert mock_channel.call_log == [(
+ "publish_request", (), {
+ "message": {
+ "meta": {
+ "origin": {
+ "channel": "chat",
+ "message": {
+ "text": body,
+ "sender": "user",
+ "timestamp": "2024-10-27 23:28:47.043000+00:00",
+ "uuid": "1c0a44e5-48ac-4464-b9ef-0117b11c2140",
+ },
+ },
+ },
+ "date": "Sun, 27 Oct 2024 23:28:47 +0000",
+ "body": body,
+ },
+ "routing_key": "readinglist.update.requests",
+ },
+ ), (
+ "publish_event", (), {
+ "message": {
+ "text": body,
+ "html": expect_html,
+ "sender": "user",
+ "timestamp": "2024-10-27 23:28:47.043000+00:00",
+ "uuid": "1c0a44e5-48ac-4464-b9ef-0117b11c2140",
+ },
+ "routing_key": "chat.messages",
+ },
+ )]
diff --git a/services/matrix-router/hive/matrix_router/reading_list.py b/services/matrix-router/hive/matrix_router/reading_list.py
deleted file mode 100644
index 7eaedc3..0000000
--- a/services/matrix-router/hive/matrix_router/reading_list.py
+++ /dev/null
@@ -1,39 +0,0 @@
-from email.utils import format_datetime
-from urllib.parse import urlparse
-
-from hive.messaging import Channel
-
-from .event import MatrixEvent
-from .reaction_manager import reaction_manager
-
-
-def is_reading_list_update(event: MatrixEvent) -> bool:
- if event.event_type != "m.room.message":
- return False
- if event.content.msgtype != "m.text":
- return False
- try:
- url = urlparse(event.body.split(maxsplit=1)[0])
- except Exception:
- return False
- return url.scheme in {"http", "https"}
-
-
-def route_reading_list_update(channel: Channel, event: MatrixEvent):
- assert is_reading_list_update(event)
-
- reaction_manager.start_story(channel, event.event_id)
-
- channel.publish_request(
- message={
- "meta": {
- "origin": {
- "channel": "matrix",
- "event_id": event.event_id,
- },
- },
- "date": format_datetime(event.timestamp),
- "body": event.body,
- },
- routing_key="readinglist.update.requests",
- )
diff --git a/services/matrix-router/hive/matrix_router/router.py b/services/matrix-router/hive/matrix_router/router.py
index 6090ea7..04a756b 100644
--- a/services/matrix-router/hive/matrix_router/router.py
+++ b/services/matrix-router/hive/matrix_router/router.py
@@ -3,7 +3,6 @@
from hive.messaging import Channel
from .event import MatrixEvent
-from .reading_list import is_reading_list_update, route_reading_list_update
logger = logging.getLogger(__name__)
d = logger.info # logger.debug
@@ -27,7 +26,4 @@ def _on_room_message(self, channel: Channel, event: MatrixEvent):
raise NotImplementedError(unhandled_type)
def _on_text_message(self, channel: Channel, event: MatrixEvent):
- if is_reading_list_update(event):
- route_reading_list_update(channel, event)
- return
raise NotImplementedError(f"event.body={event.body!r}")
diff --git a/services/matrix-router/tests/test_router.py b/services/matrix-router/tests/test_router.py
deleted file mode 100644
index d5d269c..0000000
--- a/services/matrix-router/tests/test_router.py
+++ /dev/null
@@ -1,61 +0,0 @@
-import pytest
-
-from hive.matrix_router.event import MatrixEvent
-from hive.matrix_router.router import Router
-
-
-class MockChannel:
- def __init__(self):
- self.published_requests = []
-
- def publish_request(self, **kwargs):
- self.published_requests.append(kwargs)
-
- def publish_event(self, *, routing_key, message):
- assert routing_key == "chat.messages"
-
-
-class MockReactionManager:
- def start_story(self, *args, **kwargs):
- pass
-
-
-@pytest.fixture
-def channel():
- channel = MockChannel()
- channel.reaction_manager = MockReactionManager()
- yield channel
-
-
-@pytest.mark.parametrize(
- "body",
- ("http://www.example.com",
- "https://example.com/foo?whatever=4#bar some quote",
- ))
-def test_reading_list_update(channel, body):
- router = Router()
- router.on_matrix_event(channel, MatrixEvent({
- "source": {
- "type": "m.room.message",
- "content": {
- "msgtype": "m.text",
- "body": body,
- },
- "event_id": "$26RqwJMLw-yds1GAH_QxjHRC1Da9oasK0e5VLnck_45",
- "origin_server_ts": 1730071727043,
- "sender": "@neo:matrix.org",
- },
- }))
- assert channel.published_requests == [{
- "message": {
- "meta": {
- "origin": {
- "channel": "matrix",
- "event_id": "$26RqwJMLw-yds1GAH_QxjHRC1Da9oasK0e5VLnck_45",
- },
- },
- "date": "Sun, 27 Oct 2024 23:28:47 +0000",
- "body": body,
- },
- "routing_key": "readinglist.update.requests",
- }]
diff --git a/services/nginx-ingress/vane_webui/style.css b/services/nginx-ingress/vane_webui/style.css
index 86c9ccf..42945d2 100644
--- a/services/nginx-ingress/vane_webui/style.css
+++ b/services/nginx-ingress/vane_webui/style.css
@@ -150,3 +150,15 @@ input, button, .from-user {
font-weight: bold;
text-transform: uppercase;
}
+a:link {
+ color: #9dc6f2;
+}
+a:visited {
+ color: #6da1da;
+}
+a {
+ text-decoration: none;
+}
+a:hover, a:active {
+ text-decoration: underline;
+}