diff --git a/menuflow/config.py b/menuflow/config.py index 9d8dd9c..0e431ec 100644 --- a/menuflow/config.py +++ b/menuflow/config.py @@ -20,6 +20,7 @@ def do_update(self, helper: ConfigUpdateHelper) -> None: copy("menuflow.timeouts.http_requests") copy("menuflow.timeouts.middlewares") copy("menuflow.email_servers") + copy("menuflow.typing_notification") copy("server.hostname") copy("server.port") copy("server.public_url") diff --git a/menuflow/example-config.yaml b/menuflow/example-config.yaml index 8796c94..8d46800 100644 --- a/menuflow/example-config.yaml +++ b/menuflow/example-config.yaml @@ -35,6 +35,13 @@ menuflow: username: user2 password: pass2 + # Do you want the menu to generate a typing notification event before sending messages to rooms? + # The range is related to the time duration of the write notification event. + typing_notification: + enable: true + start: 1 + end: 3 + server: # The IP and port to listen to. hostname: 0.0.0.0 diff --git a/menuflow/nodes/base.py b/menuflow/nodes/base.py index edf5817..a7afce1 100644 --- a/menuflow/nodes/base.py +++ b/menuflow/nodes/base.py @@ -1,12 +1,15 @@ from __future__ import annotations from abc import abstractmethod +from asyncio import create_task, sleep from json import JSONDecodeError, dumps, loads from logging import getLogger +from random import randrange from typing import Dict, List from aiohttp import ClientSession from mautrix.client import Client as MatrixClient +from mautrix.types import MessageEventContent, RoomID from mautrix.util.logging import TraceLogger from ..config import Config @@ -61,6 +64,41 @@ def init_cls(cls, config: Config, matrix_client: MatrixClient, default_variables async def run(self): pass + async def set_typing(self, room_id: RoomID): + """It sets the typing notification for a random amount of time between 1 and 3 seconds + + Parameters + ---------- + room_id : RoomID + The room ID of the room you want to send the typing notification to. + + """ + start = self.config["menuflow.typing_notification.start"] or 1 + end = self.config["menuflow.typing_notification.end"] or 3 + typing_time = randrange(start, end) + await self.matrix_client.set_typing(room_id=room_id, timeout=typing_time) + await sleep(typing_time) + + async def send_message(self, room_id: RoomID, content: MessageEventContent): + """It sends a message to the room. + + Parameters + ---------- + room_id : RoomID + The room ID of the room you want to send the message to. + content : MessageEventContent + The content of the message. + + """ + + async def send(): + if self.config["menuflow.typing_notification.enable"]: + await self.set_typing(room_id=room_id) + + await self.matrix_client.send_message(room_id=room_id, content=content) + + create_task(send()) + def render_data(self, data: Dict | List | str) -> Dict | List | str: """It takes a dictionary or list, converts it to a string, and then uses Jinja to render the string diff --git a/menuflow/nodes/location.py b/menuflow/nodes/location.py index 44c250f..56f82d0 100644 --- a/menuflow/nodes/location.py +++ b/menuflow/nodes/location.py @@ -30,7 +30,7 @@ async def run(self): body=f"User Location geo:{self.longitude},{self.latitude} at {datetime.utcnow()}", geo_uri=f"geo:{self.longitude},{self.latitude}", ) - await self.matrix_client.send_message(room_id=self.room.room_id, content=location_message) + await self.send_message(room_id=self.room.room_id, content=location_message) await self.room.update_menu( node_id=self.o_connection, state=RoomState.END if not self.o_connection else None, diff --git a/menuflow/nodes/media.py b/menuflow/nodes/media.py index 3061ec3..52de642 100644 --- a/menuflow/nodes/media.py +++ b/menuflow/nodes/media.py @@ -116,7 +116,7 @@ async def run(self): media_message = await self.load_media() self.media_cache[self.url] = media_message - await self.matrix_client.send_message(room_id=self.room.room_id, content=media_message) + await self.send_message(room_id=self.room.room_id, content=media_message) await self.room.update_menu( node_id=self.o_connection, diff --git a/menuflow/nodes/message.py b/menuflow/nodes/message.py index 6d42101..bfde1bf 100644 --- a/menuflow/nodes/message.py +++ b/menuflow/nodes/message.py @@ -60,6 +60,6 @@ async def run(self): formatted_body=markdown(self.text), ) - await self.matrix_client.send_message(room_id=self.room.room_id, content=msg_content) + await self.send_message(room_id=self.room.room_id, content=msg_content) await self._update_node() diff --git a/test/conftest.py b/test/conftest.py index f18c447..abf701f 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -3,13 +3,28 @@ from pytest_mock import MockerFixture from menuflow import Flow, Room, Util +from menuflow.config import Config from menuflow.nodes import Base, Input, Location, Message, Switch @pytest_asyncio.fixture -async def flow() -> Flow: +async def config() -> Config: + _config = Config( + path="menuflow/example-config.yaml", + base_path="menuflow", + ) + _config.load() + return _config + + +@pytest_asyncio.fixture +async def flow(config: Config) -> Flow: flow = Flow(Util.flow_example().get("menu")) flow.load() + + for node in [Input, Location, Message, Switch]: + node.config = config + return flow diff --git a/test/test_nodes/test_location.py b/test/test_nodes/test_location.py index 9b359f7..d0d52c1 100644 --- a/test/test_nodes/test_location.py +++ b/test/test_nodes/test_location.py @@ -17,6 +17,6 @@ def test_longitude_latitude(self, location: Location): @pytest.mark.asyncio async def test_run(self, location: Location, mocker: MockerFixture): async_mock = AsyncMock() - mock_func = mocker.patch.object(Client, "send_message", side_effect=async_mock) + mock_func = mocker.patch.object(Location, "send_message", side_effect=async_mock) await location.run() assert mock_func.called == True diff --git a/test/test_nodes/test_message.py b/test/test_nodes/test_message.py index e61f051..0450621 100644 --- a/test/test_nodes/test_message.py +++ b/test/test_nodes/test_message.py @@ -1,7 +1,6 @@ import nest_asyncio import pytest from asyncmock import AsyncMock -from mautrix.client import Client from mautrix.types import MessageType from pytest_mock import MockerFixture @@ -39,9 +38,9 @@ async def test_update_node_to_end(self, message: Message): assert message.room.node_id == "" assert message.room.state == RoomState.END - @pytest.mark.asyncio + @pytest.mark.asyncio() async def test_run(self, message: Message, mocker: MockerFixture): async_mock = AsyncMock() - mock_func = mocker.patch.object(Client, "send_message", side_effect=async_mock) + mock_func = mocker.patch.object(Message, "send_message", side_effect=async_mock) await message.run() assert mock_func.called == True