diff --git a/README.md b/README.md index b6bd223f..312ac164 100644 --- a/README.md +++ b/README.md @@ -120,6 +120,18 @@ By default the `bot.storage` is in-memory. Any changes are lost when the bot is stopped or reseted. For persistent storage to disk, check the SQLite or Redis storage in `storage.py`. +To use Redis with password authentication, add `redis_password` to the storage config: + +```python +config = { + "storage": { + "redis_host": "redis", + "redis_port": 6379, + "redis_password": "your_password", + }, +} +``` + ### Command To implement your own commands, you need to inherent `Command` and overwrite following methods: diff --git a/signalbot/bot.py b/signalbot/bot.py index 28929f88..5b184934 100644 --- a/signalbot/bot.py +++ b/signalbot/bot.py @@ -115,7 +115,10 @@ def __init__(self, config: dict): # noqa: ANN204 else: self._redis_host = config_storage["redis_host"] self._redis_port = config_storage["redis_port"] - self.storage = RedisStorage(self._redis_host, self._redis_port) + self._redis_password = config_storage.get("redis_password") + self.storage = RedisStorage( + self._redis_host, self._redis_port, self._redis_password + ) self._logger.info("redis storage initilized") except Exception: # noqa: BLE001 self.storage = SQLiteStorage() diff --git a/signalbot/message.py b/signalbot/message.py index 17435e93..cf121019 100644 --- a/signalbot/message.py +++ b/signalbot/message.py @@ -13,10 +13,11 @@ class MessageType(Enum): - SYNC_MESSAGE = 1 # Message recieved in a linked device - DATA_MESSAGE = 2 # Message recieved in a primary device + SYNC_MESSAGE = 1 # Message received in a linked device + DATA_MESSAGE = 2 # Message received in a primary device EDIT_MESSAGE = 3 # Message received is an edit of a previous message DELETE_MESSAGE = 4 # Message received is a remote delete of a previous message + READ_MESSAGE = 5 # User read some messages @dataclass @@ -37,6 +38,7 @@ class Message: reaction: str | None = None mentions: list[str] = field(default_factory=list) quote: Quote | None = None + read_messages: list[dict] | None = None target_sent_timestamp: int | None = None remote_delete_timestamp: int | None = None raw_message: str | None = None @@ -67,8 +69,15 @@ def _extract_message_data( if sync_message == {}: raise UnknownMessageFormatError - message_type = MessageType.SYNC_MESSAGE - data_message = sync_message["sentMessage"] + if "readMessages" in sync_message: + message_type = MessageType.READ_MESSAGE + data_message = { + "message": "", + "readMessages": sync_message["readMessages"], + } + else: + message_type = MessageType.SYNC_MESSAGE + data_message = sync_message["sentMessage"] if "editMessage" in data_message: message_type = MessageType.EDIT_MESSAGE @@ -128,6 +137,7 @@ async def parse(cls, signal: SignalAPI, raw_message_str: str) -> Message: reaction = cls._parse_reaction(data_message) mentions = cls._parse_mentions(data_message) quote = cls._parse_quote(data_message) + read_messages = data_message.get("readMessages") base64_attachments, attachments_local_filenames, link_previews = [], [], [] view_once = False @@ -154,6 +164,7 @@ async def parse(cls, signal: SignalAPI, raw_message_str: str) -> Message: reaction=reaction, mentions=mentions, quote=quote, + read_messages=read_messages, target_sent_timestamp=target_sent_timestamp, remote_delete_timestamp=remote_delete_timestamp, raw_message=raw_message_str, diff --git a/signalbot/storage.py b/signalbot/storage.py index 27ba11cc..52060f5a 100644 --- a/signalbot/storage.py +++ b/signalbot/storage.py @@ -74,8 +74,8 @@ def delete(self, key: str) -> None: class RedisStorage(Storage): - def __init__(self, host: str, port: int): # noqa: ANN204 - self._redis = redis.Redis(host=host, port=port, db=0) + def __init__(self, host: str, port: int, password: str | None = None): # noqa: ANN204 + self._redis = redis.Redis(host=host, port=port, db=0, password=password) def exists(self, key: str) -> bool: return self._redis.exists(key) diff --git a/tests/test_message.py b/tests/test_message.py index 3d3afd2d..1965c95c 100644 --- a/tests/test_message.py +++ b/tests/test_message.py @@ -14,6 +14,7 @@ class TestMessage(unittest.IsolatedAsyncioTestCase): raw_reaction_message = '{"envelope":{"source":"","sourceNumber":"","sourceUuid":"","sourceName":"","sourceDevice":1,"timestamp":1632576001632,"syncMessage":{"sentMessage":{"timestamp":1632576001632,"message":null,"expiresInSeconds":0,"viewOnce":false,"reaction":{"emoji":"👍","targetAuthor":"","targetAuthorNumber":"","targetAuthorUuid":"","targetSentTimestamp":1632576001632,"isRemove":false},"mentions":[],"attachments":[],"contacts":[],"groupInfo":{"groupId":"","type":"DELIVER"},"destination":null,"destinationNumber":null,"destinationUuid":null}}}}' # noqa: E501 raw_user_chat_message = '{"envelope":{"source":"+490123456789","sourceNumber":"+490123456789","sourceUuid":"","sourceName":"","sourceDevice":1,"timestamp":1632576001632,"dataMessage":{"timestamp":1632576001632,"message":"Uhrzeit","expiresInSeconds":0,"viewOnce":false}},"account":"+49987654321","subscription":0}' # noqa: E501 raw_attachment_message = '{"envelope":{"source":"+490123456789","sourceNumber":"+490123456789","sourceUuid":"","sourceName":"","sourceDevice":1,"timestamp":1632576001632,"dataMessage":{"timestamp":1632576001632,"message":"Uhrzeit","expiresInSeconds":0,"viewOnce":false, "attachments": [{"contentType": "image/png", "filename": "image.png", "id": "1qeCjjWOOo9Gxv8pfdCw.png","size": 12005}]}},"account":"+49987654321","subscription":0}' # noqa: E501 + raw_user_read_message = '{"envelope":{"source":"+490123456789","sourceNumber":"+490123456789","sourceUuid":"","sourceName":"","sourceDevice":1,"timestamp":1632576001632,"serverReceivedTimestamp":1632576001632,"serverDeliveredTimestamp":1632576001632,"syncMessage":{"readMessages":[{"sender":"+49987654321","senderNumber":"+49987654321","senderUuid":"","timestamp":1632576001632}]}},"account":"+49987654321"}' # noqa: E501 expected_source = "+490123456789" expected_timestamp = 1632576001632 @@ -116,6 +117,22 @@ async def test_parse_user_chat_message(self): self.assertEqual(message.timestamp, TestMessage.expected_timestamp) # noqa: PT009 self.assertIsNone(message.group) # noqa: PT009 + async def test_message_read(self): + message = await Message.parse( + self.signal_api, TestMessage.raw_user_read_message + ) + + self.assertEqual(message.type, MessageType.READ_MESSAGE) # noqa: PT009 + self.assertEqual(message.text, "") # noqa: PT009 + self.assertIsInstance(message.read_messages, list) # noqa: PT009 + self.assertEqual(len(message.read_messages), 1) # noqa: PT009 + + rm = message.read_messages[0] + self.assertEqual(rm.get("sender"), "+49987654321") # noqa: PT009 + self.assertEqual(rm.get("senderNumber"), "+49987654321") # noqa: PT009 + self.assertEqual(rm.get("timestamp"), TestMessage.expected_timestamp) # noqa: PT009 + self.assertIn("senderUuid", rm) # noqa: PT009 + if __name__ == "__main__": unittest.main()