diff --git a/CHANGELOG.md b/CHANGELOG.md
index a56c7e1..28ef1e8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,16 @@
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.2.2] - 2026-10-08
+
+### Added
+
+- [status-go] logs can be turned on and off when initializing `class Account`.
+
+### Changed
+
+- `def send_images()` accepts a list of `file_path` instead of a single `file_path`.
+
## [1.2.1] - 2026-09-25
### Changed
@@ -209,6 +219,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.2.2]: https://github.com/status-im/status-python-sdk/releases/tag/1.2.2
[1.2.1]: https://github.com/status-im/status-python-sdk/releases/tag/1.2.1
[1.2.0]: https://github.com/status-im/status-python-sdk/releases/tag/1.2.0
[1.1.6]: https://github.com/status-im/status-python-sdk/releases/tag/1.1.6
diff --git a/docs/account.md b/docs/account.md
index 74e66fa..8f2ad89 100644
--- a/docs/account.md
+++ b/docs/account.md
@@ -55,7 +55,7 @@ Backup files (`.bkp`) can be both created in [Status App](https://our.status.im/
**Note**: Status App will not automatically backup messages. This has to be manually overridden on the app (above screenshot). When using the Python SDK, the messages are automatically stored in the `.bkp` files.
-You can point [`backup_folder`](./account.md#accountdomainlocalhost-port8080-is_securefalse-backup_foldernone) at the folder Status App uses for its own local backups. Because both Status App and the SDK name `.bkp` files deterministically from the account's compressed key (see [`backup()`](./account.md#backup)), a backup created by the SDK lands with the exact filename Status App expects, and Status App will pick it up and load it directly - and likewise, a backup created in Status App can be auto-loaded by the SDK during recovery. This lets you move backups between the app and the SDK without renaming anything.
+You can point [`backup_folder`](./account.md#accountdomainlocalhost-backend_port8080-media_port9000-is_securefalse-backup_foldernone-volume_foldernone-show_logstrue) at the folder Status App uses for its own local backups. Because both Status App and the SDK name `.bkp` files deterministically from the account's compressed key (see [`backup()`](./account.md#backup)), a backup created by the SDK lands with the exact filename Status App expects, and Status App will pick it up and load it directly - and likewise, a backup created in Status App can be auto-loaded by the SDK during recovery. This lets you move backups between the app and the SDK without renaming anything.
```mermaid
flowchart LR
@@ -114,7 +114,7 @@ The **Installation ID** should be used when calling [`sync`](./account.md#syncin

-## `Account(domain="localhost", backend_port=8080, media_port=9000, is_secure=False, backup_folder=None, volume_folder=None)`
+## `Account(domain="localhost", backend_port=8080, media_port=9000, is_secure=False, backup_folder=None, volume_folder=None, show_logs=True)`
Create a new `Account` instance ready to be logged in. The constructor wires the SDK to a running [Status Backend](https://github.com/status-im/status-go) at the given `domain` and `backend_port`, prepares the local `assets/` folder (used for image uploads, such as the [profile picture](./account.md#profile_picture)) and `backups/` folder (used for [backup uploads](./account.md#backups) and recovery).
@@ -126,6 +126,7 @@ Create a new `Account` instance ready to be logged in. The constructor wires the
| `is_secure` | `bool` | No | When `True`, the SDK communicates over `https`; otherwise `http` is used. Defaults to `False`. |
| `backup_folder` | `str` | No | Absolute path on the host machine where `.bkp` files will be created and loaded from. If not provided, the SDK's own `backups/` folder is used. See [Backups](./account.md#backups). |
| `volume_folder` | `str` | No | Directory containing the `docker-compose.yaml` whose `backups/` and `assets/` folders are mounted into the Status Backend container. Defaults to this package's own installation folder (e.g. the `status_sdk` folder under `site-packages` when installed via `pip`). Set this when Status Backend is launched from a different `docker-compose.yaml`, such as a local clone of the repository. |
+| `show_logs` | `bool` | No | Whether `status-go` logs are shown in Docker / Terminal. Defaults to `True`. |
The constructor does not log into any account on its own - call [`login`](./account.md#loginpassword-key_uidnone-display_namenone-mnemonicnone-infura_tokennone-alchemy_tokennone-coingecko_api_keynone) afterwards. To discover what accounts already exist in the configured data directory, use the [`available_accounts`](./account.md#available_accounts) property, which is also populated automatically during initialization.
@@ -439,18 +440,18 @@ account.send_message(
)
```
-#### `send_image(chat_id, file_path, message=None, reply_to_message_id=None)`
+#### `send_image(chat_id, file_paths, message=None, reply_to_message_id=None)`
-Send an image to a specific chat, with an optional text message. The image renders inline in Status App, the same as attaching an image in the app. Like [`send_message`](./account.md#send_messagechat_id-message-reply_to_message_idnone), it can be sent as a **reply** to an existing message.
+Send one or more images to a specific chat, with an optional text message. Images render inline in Status App, the same as attaching an image in the app. When more than one path is passed, the images are sent together as an **album** - a single group of images sharing one caption - rather than as separate messages. Like [`send_message`](./account.md#send_messagechat_id-message-reply_to_message_idnone), it can be sent as a **reply** to an existing message.
| Name | Type | Required | Description |
|-----|-----|-----|-------------|
| `chat_id` | `str` | Yes | Identifier of the chat where the image will be sent. All available chat IDs can be obtained from the [`chats`](./account.md#chats) property. |
-| `file_path` | `str` | Yes | Local full path to the image file. |
-| `message` | `str` | No | Caption sent together with the image. Cannot be longer than **2000 characters**. When omitted (default), the image is sent without any text. |
+| `file_paths` | `list[str]`
`str` | Yes | Local full path(s) to the image file(s). A single value can be passed as a `str`. Passing more than one path sends all of them together as a single **album**. |
+| `message` | `str` | No | Caption sent together with the image(s). Cannot be longer than **2000 characters**. When omitted (default), the image is sent without any text. |
| `reply_to_message_id` | `str` | No | The `id` of the message being replied to. Message IDs can be obtained from the `id` key of [`get_messages`](./account.md#get_messageschat_id-start_timestampnone-end_timestampnone) or from a [`listen_messages`](./account.md#listen_messages) event. When omitted (default), the image is sent as a standalone message. |
-Returns `str` - the `id` of the message that was just sent, exactly as [`send_message`](./account.md#send_messagechat_id-message-reply_to_message_idnone) does, so it can be passed straight into [`delete_message`](./account.md#delete_messageid) or used as the `reply_to_message_id` of a follow-up message.
+Returns `str` - the `id` of the message that was just sent, exactly as [`send_message`](./account.md#send_messagechat_id-message-reply_to_message_idnone) does, so it can be passed straight into [`delete_message`](./account.md#delete_messageid) or used as the `reply_to_message_id` of a follow-up message. For an album, this is the id of its last image - [`listen_messages`](./account.md#listen_messages) yields the whole album under this same id once every image in it has arrived, but [`get_messages`](./account.md#get_messageschat_id-start_timestampnone-end_timestampnone) returns each image in the album as its own entry rather than merging them.
```python
from status_sdk import Account
@@ -484,11 +485,32 @@ chat = account.chats[0]
account.send_image(
chat_id=chat["id"],
- file_path="/full/file-path/meme-67.png",
+ file_paths="/full/file-path/meme-67.png",
message="Du bist gut genug"
)
```
+Send multiple images together as an album:
+
+```python
+from status_sdk import Account
+
+account = Account()
+params = {
+ "name": "status-app-bot",
+ "password": "SNTPUMP"
+}
+account.login(**params)
+
+chat = account.chats[0]
+
+account.send_image(
+ chat_id=chat["id"],
+ file_paths=["/full/file-path/meme-67.png", "/full/file-path/meme-68.png"],
+ message="Daily random memes"
+)
+```
+
Reply to a message with an image:
```python
@@ -509,7 +531,7 @@ latest = messages[0]
account.send_image(
chat_id=chat["id"],
- file_path="/full/file-path/meme-67.png",
+ file_paths="/full/file-path/meme-67.png",
message="Du bist gut genug",
reply_to_message_id=latest["id"]
)
@@ -527,7 +549,7 @@ Relay a message that came from **another messaging platform** - Discord, Telegra
| `username` | `str` | No | The author's username **on the other platform**, shown as the sender. Defaults to `Anon`. |
| `user_id` | `str` | No | The author's id on the other platform. Status uses it to tell one bridged author from another, so **pass the real id** - see the note below. |
| `message_id` | `str` | No | The original message's id on the other platform. Pass it if you want later messages to be able to reply to this one. |
-| `reply_to_message_id` | `str` | No | The **other platform's** id of the message being replied to - *not* a Status message id. Unlike in [`send_message`](./account.md#send_messagechat_id-message-reply_to_message_idnone) and [`send_image`](./account.md#send_imagechat_id-file_path-messagenone-reply_to_message_idnone). It threads correctly only when the message it points at was itself relayed with that same value as its `message_id`. Passing a Status id will not thread. |
+| `reply_to_message_id` | `str` | No | The **other platform's** id of the message being replied to - *not* a Status message id. Unlike in [`send_message`](./account.md#send_messagechat_id-message-reply_to_message_idnone) and [`send_image`](./account.md#send_imagechat_id-file_paths-messagenone-reply_to_message_idnone). It threads correctly only when the message it points at was itself relayed with that same value as its `message_id`. Passing a Status id will not thread. |
| `image_url` | `str` | No | URL of the author's avatar on the other platform. Defaults to no avatar. |
Returns `str` - the `id` of the message **in Status App**, which is a different value from the `message_id` you passed in. It can be used with [`delete_message`](./account.md#delete_messageid) like any other sent message.
@@ -588,7 +610,7 @@ Passing `chat_id` is purely an **optimisation**. Without it the chat has to be r
| Name | Type | Required | Description |
|-----|-----|-----|-------------|
-| `message_id` | `str` | Yes | The `id` of the message to react to. Message IDs can be obtained from the `id` key of [`get_messages`](./account.md#get_messageschat_id-start_timestampnone-end_timestampnone), from the `lastMessage` of a [`listen_messages`](./account.md#listen_messages) event, or directly from the return value of [`send_message`](./account.md#send_messagechat_id-message-reply_to_message_idnone) / [`send_image`](./account.md#send_imagechat_id-file_path-messagenone-reply_to_message_idnone). |
+| `message_id` | `str` | Yes | The `id` of the message to react to. Message IDs can be obtained from the `id` key of [`get_messages`](./account.md#get_messageschat_id-start_timestampnone-end_timestampnone), from the `lastMessage` of a [`listen_messages`](./account.md#listen_messages) event, or directly from the return value of [`send_message`](./account.md#send_messagechat_id-message-reply_to_message_idnone) / [`send_image`](./account.md#send_imagechat_id-file_paths-messagenone-reply_to_message_idnone). |
| `emoji_shortname` | `str` | Yes | The emoji shortname as in Status App, with or without the surrounding colons. See [Emojis](./utils.md#emojis) for all supported values. |
| `chat_id` | `str` | No | Identifier of the chat the message belongs to, as found in the [`chats`](./account.md#chats) property. When omitted (default), it is resolved from `message_id` with an extra call to the Status Backend. |
diff --git a/docs/community.md b/docs/community.md
index 39858d8..effa855 100644
--- a/docs/community.md
+++ b/docs/community.md
@@ -1246,17 +1246,17 @@ message_id = channel.send_message("Hello from my Status bot!")
print(f"Sent message: {message_id}")
```
-### `send_image(file_path, message=None, reply_to_message_id=None)`
+### `send_image(file_paths, message=None, reply_to_message_id=None)`
-Send an image to the channel, with an optional text **caption**. The image renders inline in Status App, the same as attaching an image in the app. Like [`send_message`](./community.md#send_messagemessage-reply_to_message_idnone), it can be sent as a **reply** to an existing message in the channel.
+Send one or more images to the channel, with an optional text **caption**. Images render inline in Status App, the same as attaching an image in the app. When more than one path is passed, the images are sent together as an **album** - a single group of images sharing one caption - rather than as separate messages. Like [`send_message`](./community.md#send_messagemessage-reply_to_message_idnone), it can be sent as a **reply** to an existing message in the channel.
| Name | Type | Required | Description |
|-----|-----|-----|-------------|
-| `file_path` | `str` | Yes | Local full path to the image file. |
-| `message` | `str` | No | Caption sent together with the image. |
+| `file_paths` | `list[str]`
`str` | Yes | Local full path(s) to the image file(s). A single value can be passed as a `str`. Passing more than one path sends all of them together as a single **album**. |
+| `message` | `str` | No | Caption sent together with the image(s). |
| `reply_to_message_id` | `str` | No | The `id` of the message being replied to. Message IDs can be obtained from the `id` key of [`get_messages`](./group-chat.md#get_messagesstart_timestampnone-end_timestampnone). When omitted (default), the image is sent as a standalone message. |
-Returns `str` - the `id` of the message that was just sent, delegated from [`send_image`](./account.md#send_imagechat_id-file_path-messagenone-reply_to_message_idnone) on `Account`. It is the same identifier that appears under the `id` key in [`get_messages`](./community.md#get_messagesstart_timestampnone-end_timestampnone), so it can be passed straight into [`delete_message`](./community.md#delete_messageid) or used as the `reply_to_message_id` of a follow-up message.
+Returns `str` - the `id` of the message that was just sent, delegated from [`send_image`](./account.md#send_imagechat_id-file_paths-messagenone-reply_to_message_idnone) on `Account`. It is the same identifier that appears under the `id` key in [`get_messages`](./community.md#get_messagesstart_timestampnone-end_timestampnone), so it can be passed straight into [`delete_message`](./community.md#delete_messageid) or used as the `reply_to_message_id` of a follow-up message. For an album, this is the id of its last image - `get_messages` returns each image in the album as its own entry rather than merging them.
```python
from status_sdk import Account, Community
@@ -1276,6 +1276,25 @@ message_id = channel.send_image("./meme-67.png", "Daily random meme")
print(f"Sent image: {message_id}")
```
+Send multiple images together as an album:
+
+```python
+from status_sdk import Account, Community
+
+account = Account()
+params = {
+ "name": "status-app-bot",
+ "password": "SNTPUMP"
+}
+account.login(**params)
+
+url = "https://status.app/c/G3QAAMQn9ueHRsR3W5Ouuy25fkCxziknAIEkCbYAoC04HjyGeQ6X8k45q3GVeyZiksbd38tQ4S_EfhrJKhRV3sDvjhmrCuSoDBIf2QJiEKwAOZipxis8ntNRVyPhC5IoWaEsj9X4P5zw093pcLofZzTV2gM=#zQ3shZeEJqTC1xhGUjxuS4rtHSrhJ8vUYp64v6qWkLpvdy9L9"
+community = Community(account, url=url)
+
+channel = community["general"]
+channel.send_image(["./meme-67.png", "./meme-68.png"], "Daily random memes")
+```
+
### `send_bridged_message(message, name=None, username=None, user_id=None, message_id=None, reply_to_message_id=None, image_url=None)`
Relay a message that came from **another messaging platform** - Discord, Telegram, Slack, IRC - into the channel. Status App renders it as a **bridged message**: the original author's name and avatar are shown, along with the platform it came from, instead of the message appearing to come from the bot account.
@@ -1287,7 +1306,7 @@ Relay a message that came from **another messaging platform** - Discord, Telegra
| `username` | `str` | No | The author's username **on the other platform**, shown as the sender. Defaults to `Anon`. |
| `user_id` | `str` | No | The author's id on the other platform. Status uses it to tell one bridged author from another, so **pass the real id** - see the note below. |
| `message_id` | `str` | No | The original message's id on the other platform. Pass it if you want later messages to be able to reply to this one. |
-| `reply_to_message_id` | `str` | No | The **other platform's** id of the message being replied to - *not* a Status message id, unlike in [`send_message`](./community.md#send_messagemessage-reply_to_message_idnone) and [`send_image`](./community.md#send_imagefile_path-messagenone-reply_to_message_idnone). It threads correctly only when the message it points at was itself relayed with that same value as its `message_id`. |
+| `reply_to_message_id` | `str` | No | The **other platform's** id of the message being replied to - *not* a Status message id, unlike in [`send_message`](./community.md#send_messagemessage-reply_to_message_idnone) and [`send_image`](./community.md#send_imagefile_paths-messagenone-reply_to_message_idnone). It threads correctly only when the message it points at was itself relayed with that same value as its `message_id`. |
| `image_url` | `str` | No | URL of the author's avatar on the other platform. Defaults to no avatar. |
Returns `str` - the `id` of the message **in Status App**, delegated from [`send_bridged_message`](./account.md#send_bridged_messagechat_id-message-namenone-usernamenone-user_idnone-message_idnone-reply_to_message_idnone-image_urlnone) on `Account`. This is a different value from the `message_id` you passed in, and it can be used with [`delete_message`](./community.md#delete_messageid) like any other sent message. Returns `None` when [`can_post`](./community.md#can_post) is `False` - nothing is relayed and no error is raised.
@@ -1326,7 +1345,7 @@ Emojis are identified by their **shortname**, exactly as Status App names them (
| Name | Type | Required | Description |
|-----|-----|-----|-------------|
-| `message_id` | `str` | Yes | The `id` of the message to react to. Message IDs can be obtained from the `id` key of [`get_messages`](./community.md#get_messagesstart_timestampnone-end_timestampnone), or directly from the return value of [`send_message`](./community.md#send_messagemessage-reply_to_message_idnone) / [`send_image`](./community.md#send_imagefile_path-messagenone-reply_to_message_idnone). |
+| `message_id` | `str` | Yes | The `id` of the message to react to. Message IDs can be obtained from the `id` key of [`get_messages`](./community.md#get_messagesstart_timestampnone-end_timestampnone), or directly from the return value of [`send_message`](./community.md#send_messagemessage-reply_to_message_idnone) / [`send_image`](./community.md#send_imagefile_paths-messagenone-reply_to_message_idnone). |
| `emoji_shortname` | `str` | Yes | The emoji shortname as in Status App, with or without the surrounding colons. See [Emojis](./utils.md#emojis) for all supported values. |
```python
diff --git a/docs/group-chat.md b/docs/group-chat.md
index ec70f4e..2e49e48 100644
--- a/docs/group-chat.md
+++ b/docs/group-chat.md
@@ -223,17 +223,17 @@ latest = messages[0]
group_chat.send_message("Thanks for the update!", latest["id"])
```
-### `send_image(file_path, message=None, reply_to_message_id=None)`
+### `send_image(file_paths, message=None, reply_to_message_id=None)`
-Send an image to the group chat, with an optional text **caption**. The image renders inline in Status App, the same as attaching an image in the app. Like [`send_message`](./group-chat.md#send_messagemessage-reply_to_message_idnone), it can be sent as a **reply** to an existing message in the chat.
+Send one or more images to the group chat, with an optional text **caption**. Images render inline in Status App, the same as attaching an image in the app. When more than one path is passed, the images are sent together as an **album** - a single group of images sharing one caption - rather than as separate messages. Like [`send_message`](./group-chat.md#send_messagemessage-reply_to_message_idnone), it can be sent as a **reply** to an existing message in the chat.
| Name | Type | Required | Description |
|-----|-----|-----|-------------|
-| `file_path` | `str` | Yes | Local full path to the image file. |
-| `message` | `str` | No | Caption sent together with the image. |
+| `file_paths` | `list[str]`
`str` | Yes | Local full path(s) to the image file(s). A single value can be passed as a `str`. Passing more than one path sends all of them together as a single **album**. |
+| `message` | `str` | No | Caption sent together with the image(s). |
| `reply_to_message_id` | `str` | No | The `id` of the message being replied to. Message IDs can be obtained from the `id` key of [`get_messages`](./group-chat.md#get_messagesstart_timestampnone-end_timestampnone). When omitted (default), the image is sent as a standalone message. |
-Returns `str` - the `id` of the message that was just sent, delegated from [`send_image`](./account.md#send_imagechat_id-file_path-messagenone-reply_to_message_idnone) on `Account`. It is the same identifier that appears under the `id` key in [`get_messages`](./group-chat.md#get_messagesstart_timestampnone-end_timestampnone), so it can be passed straight into [`delete_message`](./group-chat.md#delete_messageid) or used as the `reply_to_message_id` of a follow-up message.
+Returns `str` - the `id` of the message that was just sent, delegated from [`send_image`](./account.md#send_imagechat_id-file_paths-messagenone-reply_to_message_idnone) on `Account`. It is the same identifier that appears under the `id` key in [`get_messages`](./group-chat.md#get_messagesstart_timestampnone-end_timestampnone), so it can be passed straight into [`delete_message`](./group-chat.md#delete_messageid) or used as the `reply_to_message_id` of a follow-up message. For an album, this is the id of its last image - `get_messages` returns each image in the album as its own entry rather than merging them.
```python
from status_sdk import Account, GroupChat
@@ -252,6 +252,24 @@ message_id = group_chat.send_image("./meme-67.png", "Daily random meme")
print(f"Sent image: {message_id}")
```
+Send multiple images together as an album:
+
+```python
+from status_sdk import Account, GroupChat
+
+account = Account()
+params = {
+ "name": "status-app-bot",
+ "password": "SNTPUMP"
+}
+account.login(**params)
+
+chat = [chat for chat in account.chats if chat["type"] == "group_chat"][0]
+group_chat = GroupChat(account, chat["id"])
+
+group_chat.send_image(["./meme-67.png", "./meme-68.png"], "Daily random memes")
+```
+
### `send_bridged_message(message, name=None, username=None, user_id=None, message_id=None, reply_to_message_id=None, image_url=None)`
Relay a message that came from **another messaging platform** - Discord, Telegram, Slack, IRC - into the group chat. Status App renders it as a **bridged message**: the original author's name and avatar are shown, along with the platform it came from, instead of the message appearing to come from the bot account. Use [`send_message`](./group-chat.md#send_messagemessage-reply_to_message_idnone) when the bot is speaking as itself.
@@ -263,7 +281,7 @@ Relay a message that came from **another messaging platform** - Discord, Telegra
| `username` | `str` | No | The author's username **on the other platform**, shown as the sender. Defaults to `Anon`. |
| `user_id` | `str` | No | The author's id on the other platform. Status uses it to tell one bridged author from another, so **pass the real id** - see the note below. |
| `message_id` | `str` | No | The original message's id on the other platform. Pass it if you want later messages to be able to reply to this one. |
-| `reply_to_message_id` | `str` | No | The **other platform's** id of the message being replied to - *not* a Status message id, unlike in [`send_message`](./group-chat.md#send_messagemessage-reply_to_message_idnone) and [`send_image`](./group-chat.md#send_imagefile_path-messagenone-reply_to_message_idnone). It threads correctly only when the message it points at was itself relayed with that same value as its `message_id`. |
+| `reply_to_message_id` | `str` | No | The **other platform's** id of the message being replied to - *not* a Status message id, unlike in [`send_message`](./group-chat.md#send_messagemessage-reply_to_message_idnone) and [`send_image`](./group-chat.md#send_imagefile_paths-messagenone-reply_to_message_idnone). It threads correctly only when the message it points at was itself relayed with that same value as its `message_id`. |
| `image_url` | `str` | No | URL of the author's avatar on the other platform. Defaults to no avatar. |
Returns `str` - the `id` of the message **in Status App**, delegated from [`send_bridged_message`](./account.md#send_bridged_messagechat_id-message-namenone-usernamenone-user_idnone-message_idnone-reply_to_message_idnone-image_urlnone) on `Account`. This is a different value from the `message_id` you passed in, and it can be used with [`delete_message`](./group-chat.md#delete_messageid) like any other sent message.
@@ -300,7 +318,7 @@ Emojis are identified by their **shortname**, exactly as Status App names them (
| Name | Type | Required | Description |
|-----|-----|-----|-------------|
-| `message_id` | `str` | Yes | The `id` of the message to react to. Message IDs can be obtained from the `id` key of [`get_messages`](./group-chat.md#get_messagesstart_timestampnone-end_timestampnone), or directly from the return value of [`send_message`](./group-chat.md#send_messagemessage-reply_to_message_idnone) / [`send_image`](./group-chat.md#send_imagefile_path-messagenone-reply_to_message_idnone). |
+| `message_id` | `str` | Yes | The `id` of the message to react to. Message IDs can be obtained from the `id` key of [`get_messages`](./group-chat.md#get_messagesstart_timestampnone-end_timestampnone), or directly from the return value of [`send_message`](./group-chat.md#send_messagemessage-reply_to_message_idnone) / [`send_image`](./group-chat.md#send_imagefile_paths-messagenone-reply_to_message_idnone). |
| `emoji_shortname` | `str` | Yes | The emoji shortname as in Status App, with or without the surrounding colons. See [Emojis](./utils.md#emojis) for all supported values. |
```python
diff --git a/docs/utils.md b/docs/utils.md
index 30caaf0..0ff3d35 100644
--- a/docs/utils.md
+++ b/docs/utils.md
@@ -21,7 +21,7 @@ The image is always rebuilt (`docker compose up --build`) so a newly chosen `com
| Name | Type | Required | Description |
|-----|-----|-----|-------------|
| `commit` | `str` | No | The `status-im/status-go` git ref to build from - a commit SHA, branch, or tag. When omitted, the latest `develop` branch is built. |
-| `wait_seconds` | `int` | No | Number of seconds to pause after the `docker compose up` command returns, giving Status Backend enough time to finish booting before subsequent code runs. Defaults to `5`. This matters mainly when the container already exists and is being restarted, because `docker compose up` returns immediately while the backend is still warming up - instantiating [`Account`](./account.md#accountdomainlocalhost-port8080-is_securefalse-backup_foldernone) too quickly will fail to connect. On [Windows](./utils.md#windows) the same value is used to wait between retries after WSL has been restarted. |
+| `wait_seconds` | `int` | No | Number of seconds to pause after the `docker compose up` command returns, giving Status Backend enough time to finish booting before subsequent code runs. Defaults to `5`. This matters mainly when the container already exists and is being restarted, because `docker compose up` returns immediately while the backend is still warming up - instantiating [`Account`](./account.md#accountdomainlocalhost-backend_port8080-media_port9000-is_securefalse-backup_foldernone-volume_foldernone-show_logstrue) too quickly will fail to connect. On [Windows](./utils.md#windows) the same value is used to wait between retries after WSL has been restarted. |
| `platform` | `str` | No | The platform the image is built for. Defaults to `linux/amd64`. Run `docker buildx ls` to see the platforms your Docker installation supports, and pass the matching value if the default does not build on your machine. |
| `data_folder` | `str` | No | The folder on **your machine** where Status Backend keeps the accounts it creates. If you are a **[Community Control Node](./community.md#control-node)** you will need to create a Docker container with a volume folder, and pass that **same** folder to [`Community`](./community.md#communityaccount-community_idnone-urlnone-data_foldernone) so [`upload_control_node`](./community.md#upload_control_nodefolder) can reach it. |
@@ -113,7 +113,7 @@ The build runs inside the repository's **Nix dev shell**, so the Go toolchain an
|-----|-----|-----|-------------|
| `commit` | `str` | No | The `status-im/status-go` git commit SHA,. When omitted, the latest `develop` branch is built. |
| `repo_dir` | `str` | No | Local folder to clone `status-go` into, and reuse on later calls. Defaults to a `status-go` folder next to this package's installation. When the folder does not already hold a clone, the repository is fetched from GitHub. |
-| `address` | `str` | No | The `host:port` to run `status-backend` on. Defaults to `localhost:8080`, which matches the defaults of [`Account`](./account.md#accountdomainlocalhost-backend_port8080-media_port9000-is_securefalse-backup_foldernone-volume_foldernone). If you change it, pass the matching `domain` and `backend_port` when creating the `Account`. |
+| `address` | `str` | No | The `host:port` to run `status-backend` on. Defaults to `localhost:8080`, which matches the defaults of [`Account`](./account.md#accountdomainlocalhost-backend_port8080-media_port9000-is_securefalse-backup_foldernone-volume_foldernone-show_logstrue). If you change it, pass the matching `domain` and `backend_port` when creating the `Account`. |
| `wait_seconds` | `int` | No | How long to poll the backend's `/health` endpoint before giving up, in seconds. Defaults to `30`. **The build itself is not subject to this timeout** - only the startup that follows it. |
| `install_deps` | `bool` | No | Whether to run `make status-go-deps` before building. This also runs `go clean -cache` and `go clean -modcache`, which throws away every cached Go build on your machine and makes the next build much slower. Only needed on a first build or after a Go toolchain upgrade. |
@@ -166,7 +166,7 @@ Download a prebuilt `status-backend` bundle from a GitHub release, extract it an
| `repo_name` | `str` | No | The `owner/name` of the repository to pull the release from. Defaults to `status-im/status-go`. |
| `tag` | `str` | No | A release tag. When omitted, the latest release is used. |
| `token` | `str` | No | A GitHub token. Required for private repositories. |
-| `address` | `str` | No | The `host:port` to run `status-backend` on. Defaults to `localhost:8080`, which matches the defaults of [`Account`](./account.md#accountdomainlocalhost-backend_port8080-media_port9000-is_securefalse-backup_foldernone-volume_foldernone). If you change it, pass the matching `domain` and `backend_port` when creating the `Account`. |
+| `address` | `str` | No | The `host:port` to run `status-backend` on. Defaults to `localhost:8080`, which matches the defaults of [`Account`](./account.md#accountdomainlocalhost-backend_port8080-media_port9000-is_securefalse-backup_foldernone-volume_foldernone-show_logstrue). If you change it, pass the matching `domain` and `backend_port` when creating the `Account`. |
| `wait_seconds` | `int` | No | How long to poll the backend's `/health` endpoint before giving up, in seconds. Defaults to `30`. **Downloading and extracting are not subject to this timeout** - only the startup that follows them. |
Returns `str` - the path of the file that was launched. A custom exception is raised when the release has no asset for this machine, when the extracted bundle contains nothing runnable, or when the backend does not answer on `/health` in time.
diff --git a/pyproject.toml b/pyproject.toml
index c9b5045..4bcf2e2 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "status-sdk"
-version = "1.2.1"
+version = "1.2.2"
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 f14a05b..b4eb8e6 100644
--- a/status_sdk/account.py
+++ b/status_sdk/account.py
@@ -3,6 +3,7 @@
import requests, datetime, re, logging, os, json, ast, shutil, eth_abi, shutil
import pandas as pd
from . import exceptions
+from pathlib import Path
from Crypto.Hash import keccak
from io import BytesIO
from PIL import Image
@@ -42,7 +43,7 @@ class Account:
__ETH_ADDRESS = "0x0000000000000000000000000000000000000000"
__KECCAK256_ERROR = "failed to open database: failed to set `journal_mode` pragma: file is not a database"
__INSTALLATION_NAME = "python-sdk"
- def __init__(self, domain: str = "localhost", backend_port: int = 8080, media_port: int = 9000, is_secure: bool = False, backup_folder: Optional[str] = None, volume_folder: Optional[str] = None):
+ def __init__(self, domain: str = "localhost", backend_port: int = 8080, media_port: int = 9000, is_secure: bool = False, backup_folder: Optional[str] = None, volume_folder: Optional[str] = None, show_logs: bool = True):
"""
Work with your own Status App account
@@ -53,6 +54,7 @@ def __init__(self, domain: str = "localhost", backend_port: int = 8080, media_po
- `is_secure` - if `http` or `https` should be used
- `backup_folder` - where backup files will be created and loaded
- `volume_folder` - directory containing the `backups` and `assets` folders mounted into the Status Backend Docker container (folder holding `docker-compose.yaml`). Defaults to this package's own installation folder. Set this when Status Backend is launched from a different `docker-compose.yaml` location, such as a local clone of the repo.
+ - `show_logs` - if `status-go` logs should be shown in Docker / Terminal
"""
# Wallet transactions
self.__alchemy_token = None
@@ -106,7 +108,7 @@ def __init__(self, domain: str = "localhost", backend_port: int = 8080, media_po
version = health_info.get("version", "")
self.__is_docker = not bool(version_regex.match(version))
self.__status_go_commit_sha = version if self.__is_docker else version_regex.match(version)["sha"]
-
+ self.__show_logs = show_logs
base_folder = os.path.dirname(__file__)
if not self.__is_docker:
os.makedirs(base_folder, exist_ok=True)
@@ -236,8 +238,8 @@ def login(self, password: str, key_uid: Optional[str] = None, name: Optional[str
url = self.__urls["http"][url_key]
params.update({
- "logEnabled": True,
- "logToStderr": True,
+ "logEnabled": self.__show_logs,
+ "logToStderr": self.__show_logs,
"logLevel": "INFO",
})
response = requests.post(url, json=params)
@@ -672,20 +674,20 @@ def __getitem__(self, key: str) -> pd.DataFrame:
return balance.copy()
- def send_image(self, chat_id: str, file_path: str, message: Optional[str] = None, reply_to_message_id: Optional[str] = None) -> str:
+ def send_image(self, chat_id: str, file_paths: Union[str, list[str]], message: Optional[str] = None, reply_to_message_id: Optional[str] = None) -> str:
"""
Send an image to the given chat.
Parameters:
- `chat_id` - the chat ID can be found in `self.chats`
- - `file_path` - the file path of the image
+ - `file_paths` - the file paths of the images
- `message` - the message that will be sent
- `reply_to_message_id` - the `id` of the message to reply to, as it appears in `self.get_messages()`. If not provided, the message is sent as a standalone message.
Output:
- The message ID
"""
- return self.__send_content(chat_id, message, reply_to_message_id, image_path = file_path)
+ return self.__send_content(chat_id, message, reply_to_message_id, image_paths = file_paths)
def send_message(self, chat_id: str, message: str, reply_to_message_id: Optional[str] = None) -> str:
"""
@@ -762,7 +764,7 @@ def send_emoji_reaction(self, message_id: str, emoji_shortname: str, chat_id: Op
if error:
raise exceptions.ChatNotFoundError(error.get("message"))
- def __send_content(self, chat_id: str, message: Optional[str] = None, reply_to_message_id: Optional[str] = None, image_path: Optional[str] = None, bridged_content: Optional[models.BridgedContent] = None) -> str:
+ def __send_content(self, chat_id: str, message: Optional[str] = None, reply_to_message_id: Optional[str] = None, image_paths: Optional[Union[str, list[str]]] = None, bridged_content: Optional[models.BridgedContent] = None) -> str:
"""
Send a message with optional media attached to the given chat.
@@ -770,7 +772,7 @@ def __send_content(self, chat_id: str, message: Optional[str] = None, reply_to_m
- `chat_id` - the chat ID can be found in `self.chats`
- `message` - the text that will be sent. Optional when media is attached, so an image can be sent on its own
- `reply_to_message_id` - the `id` of the message to reply to, as it appears in `self.get_messages()`. If not provided, the message is sent as a standalone message.
- - `image_path` - local path to the image to attach.
+ - `image_paths` - local paths of the images to attach
Output:
- The message ID
@@ -788,6 +790,18 @@ def validate_path(path: str):
return path
+ def to_list(paths: Optional[Union[str, list[str]]] = None) -> list[str]:
+ """
+ Make sure the path is always a list
+ """
+ if isinstance(paths, str):
+ paths = [paths]
+
+ if isinstance(paths, type(None)):
+ paths = []
+
+ return paths
+
self.info
if not message:
message = ""
@@ -795,6 +809,7 @@ def validate_path(path: str):
if len(message) > self.message_length:
raise exceptions.MessageTooLongError(f"Message cannot be longer than 2000 characters (got {len(message)})...")
+ image_paths = to_list(image_paths)
msg_params = {
"chatId": chat_id,
"text": message,
@@ -804,15 +819,15 @@ def validate_path(path: str):
# Content status-go key name for RPC request
content_key = None
# Non `status-im/status-go` path
- file_path = None
+ file_paths = []
# File path in `status-im/status-go`
docker_file_path = [self.__docker_asset_folder]
# subfolder name in ./assets/ (if necessary)
asset_subfolder = None
- if image_path:
- image_path = validate_path(image_path)
- file_path = image_path
+ if image_paths:
+ image_paths = [validate_path(path) for path in image_paths]
+ file_paths = [*image_paths]
msg_params["contentType"] = 7
content_key = "imagePath"
asset_subfolder = "images"
@@ -825,28 +840,37 @@ def validate_path(path: str):
if asset_subfolder:
docker_file_path.append(asset_subfolder)
- asset_file_path = None
- if file_path:
- docker_file_path.append(os.path.basename(file_path))
+ params = []
+ asset_file_paths = []
+ for file_path in file_paths:
+ current_docker_file_path = [*docker_file_path]
+ current_docker_file_path.append(os.path.basename(file_path))
asset_file_path = os.path.join(self.__assets_local_folder, asset_subfolder, os.path.basename(file_path))
os.makedirs(os.path.dirname(asset_file_path), exist_ok=True)
if os.path.exists(asset_file_path):
os.remove(asset_file_path)
shutil.copy(file_path, asset_file_path)
-
- if content_key:
- msg_params.update({
- content_key: "/".join(docker_file_path)
+ asset_file_paths.append(asset_file_path)
+ params.append({
+ **msg_params,
+ content_key: str(Path("/".join(current_docker_file_path)))
})
if msg_params["contentType"] == 1 and len(msg_params["text"]) == 0:
raise exceptions.SendContentError("Cannot send empty text messages")
- params = [msg_params]
- response = self._call_rpc("messaging", "sendChatMessage", params)
- if asset_file_path and os.path.exists(asset_file_path):
- os.remove(asset_file_path)
+ rpc_method = "sendChatMessages"
+ if len(params) == 0:
+ params = [msg_params]
+ rpc_method = rpc_method[:-1]
+ else:
+ params = [params]
+
+ response = self._call_rpc("messaging", rpc_method, params)
+ for asset_file_path in asset_file_paths:
+ if asset_file_path and os.path.exists(asset_file_path):
+ os.remove(asset_file_path)
error = response.get("error", {}) or {}
if error:
diff --git a/status_sdk/community/channel.py b/status_sdk/community/channel.py
index 87fa5cf..5f70723 100644
--- a/status_sdk/community/channel.py
+++ b/status_sdk/community/channel.py
@@ -269,19 +269,19 @@ def send_message(self, message: str, reply_to_message_id: Optional[str] = None)
"""
return self.__account.send_message(self.id, message, reply_to_message_id)
- def send_image(self, file_path: str, message: Optional[str] = None, reply_to_message_id: Optional[str] = None) -> Optional[str]:
+ def send_image(self, file_paths: Union[str, list[str]], message: Optional[str] = None, reply_to_message_id: Optional[str] = None) -> Optional[str]:
"""
Send a image to the group chat.
Parameters:
- - `file_path` - the file path of the image
+ - `file_paths` - the file paths of the images
- `message` - the message that will be sent. Currently only text messages are supported
- `reply_to_message_id` - the `id` of the message to reply to, as it appears in `self.get_messages()`. If not provided, the message is sent as a standalone message.
Output:
- The message ID
"""
- return self.__account.send_image(self.id, file_path, message, reply_to_message_id)
+ return self.__account.send_image(self.id, file_paths, message, reply_to_message_id)
def send_bridged_message(self, message: str, name: Optional[str] = None, username: Optional[str] = None, user_id: Optional[str] = None, message_id: Optional[str] = None, reply_to_message_id: Optional[str] = None, image_url: Optional[str] = None) -> Optional[str]:
"""
diff --git a/status_sdk/group_chat.py b/status_sdk/group_chat.py
index 2fd1267..6ec5165 100644
--- a/status_sdk/group_chat.py
+++ b/status_sdk/group_chat.py
@@ -70,19 +70,19 @@ def send_message(self, message: str, reply_to_message_id: Optional[str] = None)
"""
return self.__account.send_message(self.id, message, reply_to_message_id)
- def send_image(self, file_path: str, message: Optional[str] = None, reply_to_message_id: Optional[str] = None) -> str:
+ def send_image(self, file_paths: Union[str, list[str]], message: Optional[str] = None, reply_to_message_id: Optional[str] = None) -> str:
"""
Send a image to the group chat.
Parameters:
- - `file_path` - the file path of the image
+ - `file_paths` - the file paths of the images
- `message` - the message that will be sent. Currently only text messages are supported
- `reply_to_message_id` - the `id` of the message to reply to, as it appears in `self.get_messages()`. If not provided, the message is sent as a standalone message.
Output:
- The message ID
"""
- return self.__account.send_image(self.id, file_path, message, reply_to_message_id)
+ return self.__account.send_image(self.id, file_paths, message, reply_to_message_id)
def send_bridged_message(self, message: str, name: Optional[str] = None, username: Optional[str] = None, user_id: Optional[str] = None, message_id: Optional[str] = None, reply_to_message_id: Optional[str] = None, image_url: Optional[str] = None) -> str:
"""