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; +}