diff --git a/CHANGELOG.md b/CHANGELOG.md index d3483f8..c32e245 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,21 @@ All notable changes to `status-python-sdk` will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.4.0] - 2026-10-14 + +### Added + +- Completed Enum for Content Type +- Function `listen_events` that listen to all event by default + +### Changed + +- `listen_messages` don't take any arguement and return all message of type `TEXT`, `STICKER`, `EMOJI`, `IMAGE`, `BRIDGED` + +### Fixed + +- Remove `image_path` from `content` of `Message` object + ## [1.3.0] - 2026-10-11 ### Added @@ -242,6 +257,7 @@ will try to log in without a hashed password - Launch [status-go] Docker container with Python instead of manual `docker compose up -d` setup. - Custom library errors +[1.4.0]: https://github.com/status-im/status-python-sdk/releases/tag/1.4.0 [1.3.0]: https://github.com/status-im/status-python-sdk/releases/tag/1.3.0 [1.2.3]: https://github.com/status-im/status-python-sdk/releases/tag/1.2.3 [1.2.2]: https://github.com/status-im/status-python-sdk/releases/tag/1.2.2 diff --git a/docs/account.md b/docs/account.md index 7aa2cc9..322d767 100644 --- a/docs/account.md +++ b/docs/account.md @@ -724,13 +724,13 @@ deleted = account.delete_message(message_id) print(f"Deleted: {deleted}") ``` -#### `listen_messages(content=None)` +#### `listen_events(content=[])` -Listen for new incoming messages **in real time**. This method is ideal for developing real time chat applications. +Listen for new events **in real time**, not only the messages. To listen to specifc events only, specify then in the `content` parameters | Name | Type | Required | Description | |-----|-----|-----|-------------| -| `content` | `list[types.Content]`
`types.Content` | No | Only yield messages of the given [content type(s)](./types.md#content). | +| `content` | `list[types.Content]`
`types.Content` | No | Only yield event of the given [content type(s)](./types.md#content). | ```python from status_sdk import Account @@ -746,10 +746,13 @@ for msg in account.listen_messages(): print(msg.content) ``` -Only listen for text and emoji messages: +#### `listen_messages()` + +Listen for new incoming messages **in real time**. This method is ideal for developing real time chat applications. + ```python -from status_sdk import Account, types +from status_sdk import Account account = Account() params = { @@ -758,12 +761,10 @@ params = { } account.login(**params) -for msg in account.listen_messages(content=[types.Content.TEXT, types.Content.EMOJI]): +for msg in account.listen_messages(): print(msg.content) ``` -**Note**: If you receive multiple messages at once, `contacts` and `chats` will grow. - #### `listen_contact_requests()` Listen for contact requests **in real time**. This method yields [`ContactRequest`](./models.md#contactrequest) objects, whose `incoming`, `accepted` and `removed` booleans tell the events apart: diff --git a/docs/types.md b/docs/types.md index 257e6e2..d691aa7 100644 --- a/docs/types.md +++ b/docs/types.md @@ -23,7 +23,7 @@ params = { account.login(**params) # Only yield text and emoji messages -for msg in account.listen_messages(content=[types.Content.TEXT, types.Content.EMOJI]): +for msg in account.listen_events(content=[types.Content.TEXT, types.Content.EMOJI]): print(msg.content) ``` @@ -31,7 +31,7 @@ A single value can be passed instead of a list: ```python # Only yield images (and albums) -for msg in account.listen_messages(content=types.Content.IMAGE): +for msg in account.listen_events(content=types.Content.IMAGE): print(msg.image_urls) ``` diff --git a/pyproject.toml b/pyproject.toml index 73163a3..864285e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "status-sdk" -version = "1.3.0" +version = "1.4.0" description = "Private chat. Communities. Multi-chain wallet. Browser. dApps all in one app, powered by SNT." readme = "README.md" requires-python = ">=3.11" diff --git a/status_sdk/account.py b/status_sdk/account.py index 21526f1..d0db96f 100644 --- a/status_sdk/account.py +++ b/status_sdk/account.py @@ -967,16 +967,17 @@ def listen_message_mentions(self) -> Generator[models.Message, None, None]: if mention_everyone in current_text or account_mention in current_text: yield models.Message.from_raw(event["body"]["message"]) - def listen_messages(self, content: Optional[Union[list[types.Content], types.Content]] = None) -> Generator[models.Message, None, None]: + def listen_messages(self) -> Generator[models.Message, None, None]: """ Listen for new messages continuously. Can be used for real time processing. """ - if isinstance(content, type(None)): - content = [] - elif isinstance(content, types.Content): - content = [content] - - allowed_content_types = [content_type.value for content_type in (content if content else types.Content)] + allowed_content_types = [ + types.Content.TEXT, + types.Content.STICKER, + types.Content.EMOJI, + types.Content.IMAGE, + types.Content.BRIDGED + ] albums: dict[str, list[dict]] = {} processed: list[str] = [] SIZE = 100 @@ -1011,6 +1012,50 @@ def listen_messages(self, content: Optional[Union[list[types.Content], types.Con if len(processed) == SIZE: processed = [] + + + def listen_events(self, content: Optional[Union[list[types.Content], types.Content]] = None) -> Generator[models.Message, None, None]: + """ + Listen for all events continuously. Can be used for real time processing. + """ + if isinstance(content, type(None)): + content = [] + elif isinstance(content, types.Content): + content = [content] + + allowed_content_types = [content_type.value for content_type in (content if content else types.Content)] + albums: dict[str, list[dict]] = {} + for message in self.signal.listen("messages.new"): + event: dict = message.get("event", {}) + raw_messages: list[dict] = event.get("messages", []) + self.logger.debug(f"Received Message {json.dumps(raw_messages)}") + for raw in raw_messages: + if raw["contentType"] not in allowed_content_types: + continue + # Edge case with when receiving an Edit of a message with a reply + if "outgoingStatus" in raw and raw["outgoingStatus"] == "delivered": + continue + + if not raw.get("albumId"): + yield models.Message.from_raw(raw) + continue + + album_id: str = raw["albumId"] + if album_id not in albums: + albums[album_id] = [] + + albums[album_id].append(raw) + if len(albums[album_id]) != raw["albumImagesCount"]: + continue + + raw["image"] = [ + point["image"] + for point in albums[album_id] + ] + yield models.Message.from_raw(raw) + albums.pop(album_id) + + def get_messages(self, chat_id: str, start_timestamp: Optional[Union[str, datetime.datetime, datetime.date, pd.Timestamp]] = None, end_timestamp: Optional[Union[str, datetime.datetime, datetime.date, pd.Timestamp]] = None) -> list[dict]: """ Get all of the messages in the given start and end timestamps. diff --git a/status_sdk/models/message.py b/status_sdk/models/message.py index 5f3dccc..8ee8343 100644 --- a/status_sdk/models/message.py +++ b/status_sdk/models/message.py @@ -80,6 +80,7 @@ class Message: image_urls: list[str] = field(default_factory=list) reply_id: Optional[str] = None bridge_id: Optional[str] = None + edited_at: Optional[str] = None payment_requests: list[PaymentRequest] = field(default_factory=list) @classmethod @@ -92,9 +93,13 @@ def from_raw(cls, raw: dict) -> Self: "from_public_key": raw["from"], "timestamp": datetime.datetime.fromtimestamp(raw["whisperTimestamp"] / 1_000), "source": raw.get("bridgeMessage", {}).get("bridgeName", "status"), - "image_urls": [] + "image_urls": [], + "content_type": raw["contentType"], + "content": raw["text"] } + print(f"Transforming raw message {raw}") + if len(raw["responseTo"]) > 0: params["reply_id"] = raw["responseTo"] @@ -123,9 +128,6 @@ def from_raw(cls, raw: dict) -> Self: img_paths: list[str] = raw["image"] params["image_urls"] += img_paths - text = raw["text"] - caption = f"{text}\n\n" if len(text) > 0 else "" - params["content"] = caption + "\n".join(img_paths) params["content_type"] = types.Content.IMAGE # Bridged Message elif content_type == types.Content.BRIDGED.value: diff --git a/status_sdk/types.py b/status_sdk/types.py index 209e42d..8ea7fbd 100644 --- a/status_sdk/types.py +++ b/status_sdk/types.py @@ -3,10 +3,15 @@ class Content(Enum): TEXT = 1 STICKER = 2 + STATUS = 3 EMOJI = 4 IMAGE = 7 + CONTACT_REQUEST = 11 + ACCEPTED_REQUEST = 16 + REMOVED_CONTACT = 17 BRIDGED = 18 + class Chat(Enum): PRIVATE = 1 COMMUNITY = 2