mirror of
https://github.com/status-im/status-python-sdk.git
synced 2026-08-31 06:01:19 +00:00
account properties: display_name and bio
- Change `username` to `display_name` - Add `key_uid` login support - Add bio functionality - Update documentation
This commit is contained in:
+115
-11
@@ -1,4 +1,4 @@
|
||||
from typing import Optional, Union, Generator
|
||||
from typing import Optional, Union, Generator, Any
|
||||
import requests, datetime, re
|
||||
from .signal import Signal
|
||||
|
||||
@@ -57,26 +57,45 @@ class Account:
|
||||
if not isinstance(accounts, list):
|
||||
accounts = []
|
||||
|
||||
self.__available_accounts = {
|
||||
account["name"]: {
|
||||
self.__available_accounts = [
|
||||
{
|
||||
"display_name": account["name"],
|
||||
"key_uid": account["key-uid"],
|
||||
"created_at": datetime.datetime.fromtimestamp(account["timestamp"])
|
||||
}
|
||||
for account in accounts
|
||||
}
|
||||
]
|
||||
# In case if there is a hanging logged in session
|
||||
self.logout()
|
||||
|
||||
def login(self, username: str, password: str):
|
||||
def login(self, password: str, key_uid: Optional[str] = None, display_name: Optional[str] = None):
|
||||
"""
|
||||
Login to the given account. If it does not exist,
|
||||
it will be created and automatically logged in.
|
||||
|
||||
Parameters:
|
||||
- `username` - your Status display name
|
||||
- `password` - your Status password
|
||||
- `key_uid` - your key unique identifier. If not provided `display_name` will be used to fetch it. This means that each `display_name` can be linked to one `key_uid`
|
||||
- `display_name` - your Status display name. Use `display_name` and `password` parameter combination if you have a 1 to 1 mapping (each display name has a unique `key_uid`)
|
||||
"""
|
||||
key_uid = self.__available_accounts.get(username, {}).get("key_uid")
|
||||
if not key_uid and not display_name:
|
||||
raise ValueError("Please provide either a Key Unique Identifier (key_uid) or a Display Name (display_name)...")
|
||||
|
||||
# Login combination: display_name + password
|
||||
if not key_uid:
|
||||
for account in self.__available_accounts:
|
||||
if account["display_name"] != display_name:
|
||||
continue
|
||||
|
||||
key_uid = account["key_uid"]
|
||||
break
|
||||
# Login combination: key_uid + password
|
||||
else:
|
||||
available_key_uids = [current["key_uid"] for current in self.__available_accounts]
|
||||
if key_uid not in available_key_uids:
|
||||
info = "\n".join([f"{current['key_uid']} - {current['display_name']}" for current in self.__available_accounts])
|
||||
raise ValueError(f"Given Key Unique Identifier is invalid...\nAvailable Key Unique Identifiers:\n{info}")
|
||||
|
||||
is_new_account = isinstance(key_uid, type(None))
|
||||
|
||||
params = {
|
||||
@@ -85,10 +104,11 @@ class Account:
|
||||
'kdfIterations': self.__kd_iterations
|
||||
}
|
||||
if is_new_account:
|
||||
self.__validate_display_name(display_name)
|
||||
params = {
|
||||
"rootDataDir": self.__unix_folder,
|
||||
"kdfIterations": self.__kd_iterations,
|
||||
"displayName": username,
|
||||
"displayName": display_name,
|
||||
"password": password,
|
||||
"customizationColor": "primary",
|
||||
"wakuV2LightClient": False,
|
||||
@@ -106,8 +126,10 @@ class Account:
|
||||
"public_key": event["public-key"],
|
||||
"emojis": event["emojiHash"],
|
||||
"key_uid": event["key-uid"],
|
||||
"compressed_key": event["compressedKey"],
|
||||
"mnemonic": event["mnemonic"],
|
||||
"name": event["display-name"],
|
||||
"display_name": event["display-name"],
|
||||
"bio": event.get("bio", ""),
|
||||
"password": password,
|
||||
"wallet_address": event["address"],
|
||||
"logged_in_timestamp": datetime.datetime.now()
|
||||
@@ -125,6 +147,13 @@ class Account:
|
||||
self.__is_messenger_launched = False
|
||||
return self
|
||||
|
||||
@property
|
||||
def available_accounts(self) -> list[dict]:
|
||||
"""
|
||||
All locally available accounts
|
||||
"""
|
||||
return self.__available_accounts
|
||||
|
||||
@property
|
||||
def info(self) -> dict:
|
||||
"""
|
||||
@@ -135,6 +164,52 @@ class Account:
|
||||
raise Exception("Make sure you are logged in to your Status account with login() first...")
|
||||
return self.__info
|
||||
|
||||
@property
|
||||
def display_name(self) -> str:
|
||||
"""
|
||||
Get the current display name
|
||||
"""
|
||||
return self.info["display_name"]
|
||||
|
||||
@display_name.setter
|
||||
def display_name(self, name: str):
|
||||
self.__validate_display_name(name)
|
||||
output = self.__call_rpc("messaging", "setDisplayName", [name])
|
||||
# It seems that if a valid name is given, it will be instantly updated
|
||||
# However after tracing the signals, an `envelope.sent` is sent a bit
|
||||
# after the name has been changed.
|
||||
self.signal.get("envelope.sent")
|
||||
self.__info["display_name"] = name
|
||||
|
||||
@property
|
||||
def bio(self) -> str:
|
||||
"""
|
||||
Get the current bio
|
||||
"""
|
||||
return self.info["bio"]
|
||||
|
||||
@bio.setter
|
||||
def bio(self, bio: Any):
|
||||
if isinstance(bio, type(None)):
|
||||
bio = ""
|
||||
|
||||
bio = str(bio).strip()
|
||||
# Limit based from Status App
|
||||
CHARACTERS = 240
|
||||
if len(bio) > CHARACTERS:
|
||||
raise ValueError(f"Bio cannot be longer than {CHARACTERS} characters...")
|
||||
|
||||
self.__call_rpc("messaging", "setBio", [bio])
|
||||
# It seems that if a valid bio is given, it will be instantly updated
|
||||
# However after tracing the signals, an `envelope.sent` is sent a bit
|
||||
# after the bio has been updated.
|
||||
self.signal.get("envelope.sent")
|
||||
self.__info["bio"] = bio
|
||||
|
||||
@bio.deleter
|
||||
def bio(self):
|
||||
self.bio = ""
|
||||
|
||||
@property
|
||||
def contacts(self) -> dict[str, dict]:
|
||||
"""
|
||||
@@ -347,7 +422,7 @@ class Account:
|
||||
break
|
||||
|
||||
if not display_name:
|
||||
raise Exception(f"Cannot add contact {public_key}...\nPlease make sure you add display_name for contacts that you are sending friend requests to and have never interacted with before!")
|
||||
raise ValueError(f"Cannot add contact {public_key}...\nPlease make sure you add display_name for contacts that you are sending friend requests to and have never interacted with before!")
|
||||
|
||||
params = [{"id": public_key, "nickname": "", "displayName": display_name, "ensName": ""}]
|
||||
self.__call_rpc("messaging", "addContact", params)
|
||||
@@ -442,7 +517,7 @@ class Account:
|
||||
self.info
|
||||
name = self.__prefix_mapping.get(prefix)
|
||||
if not name:
|
||||
raise Exception(f"Name {name} does not exist... Available options: {list(self.__prefix_mapping.keys())}")
|
||||
raise ValueError(f"Name {name} does not exist... Available options: {list(self.__prefix_mapping.keys())}")
|
||||
|
||||
data = {
|
||||
'jsonrpc': '2.0',
|
||||
@@ -471,3 +546,32 @@ class Account:
|
||||
s1 = re.sub(r'(.)([A-Z][a-z]+)', r'\1_\2', name)
|
||||
s2 = re.sub(r'([a-z0-9])([A-Z])', r'\1_\2', s1)
|
||||
return s2.lower()
|
||||
|
||||
def __validate_display_name(self, name: str) -> bool:
|
||||
"""
|
||||
Validate the display name based on Status App rules.
|
||||
Validation most probably is dealt with on the GUI side
|
||||
of the application instead of the backend.
|
||||
|
||||
Status App validation rules:
|
||||
- Use A-Z and 0-9, hyphens and underscores only
|
||||
- Display name must be at least 5 characters long
|
||||
- Display name can't start or end with a space
|
||||
|
||||
Parameters:
|
||||
- `name` - the name that the user wants to use to login / create account / change
|
||||
|
||||
Output:
|
||||
- `True` if the name was successfully changed. A
|
||||
"""
|
||||
if name != name.strip():
|
||||
raise ValueError("Display name cannot start or end with a space.")
|
||||
|
||||
if len(name) < 5:
|
||||
raise ValueError("Display name must be at least 5 characters long.")
|
||||
|
||||
if not re.fullmatch(r"[A-Za-z0-9_-]+", name):
|
||||
raise ValueError("Display name can contain only A-Z, 0-9, hyphens (-), and underscores (_).")
|
||||
|
||||
return True
|
||||
|
||||
|
||||
+227
-14
@@ -2,16 +2,55 @@
|
||||
|
||||
The account class allows you to easily work with a Status account.
|
||||
|
||||
## Display name
|
||||
|
||||
The **display name** is the human‑readable identifier for a Status account. It is used when creating an account, resolving an existing account during [`login`](./account.md#loginpassword-key_uidnone-display_namenone), and when updating the account name through the [`display_name`](./account.md#display_name) property.
|
||||
|
||||
Display names must follow strict validation rules enforced by the library and expected by the Status application. A valid display name must satisfy all of the following conditions:
|
||||
|
||||
- It may contain **uppercase letters (`A–Z`)**
|
||||
- It may contain **numbers (`0–9`)**
|
||||
- It may contain **hyphens (`-`)**
|
||||
- It may contain **underscores (`_`)**
|
||||
- It must be **at least 5 characters long**
|
||||
- It **cannot start or end with a space**
|
||||
|
||||
Characters such as spaces, punctuation, emojis, or other symbols are **not allowed**.
|
||||
|
||||
### Valid examples
|
||||
|
||||
```
|
||||
alpha_01
|
||||
STATUS-01
|
||||
bot_user_5
|
||||
HELLO123
|
||||
node-42
|
||||
```
|
||||
|
||||
### Invalid examples
|
||||
|
||||
| Example | Reason |
|
||||
|-------|--------|
|
||||
| `bot` | Too short (minimum length is 5) |
|
||||
| ` mybot` | Leading space |
|
||||
| `mybot ` | Trailing space |
|
||||
| `bot!123` | Contains invalid character `!` |
|
||||
| `bot user` | Spaces are not allowed |
|
||||
|
||||
If a display name does not follow these rules, a **`ValueError`** will be raised by the account validation logic.
|
||||
|
||||
|
||||
## Methods
|
||||
|
||||
### `login(username, password)`
|
||||
### `login(password, key_uid=None, display_name=None)`
|
||||
|
||||
Login to an existing Status account. If the account does not exist in the initialized data directory, a new account will be created and automatically logged in. After a successful login, the decentralized messenger service is automatically started so the account can send and receive messages.
|
||||
|
||||
| Name | Type | Required | Description |
|
||||
|-----|-----|-----|-------------|
|
||||
| `username` | `str` | Yes | Display name of the Status account |
|
||||
| `password` | `str` | Yes | Password used to encrypt the account |
|
||||
| `key_uid` | `str` | Yes* | Unique key identifier of the account. If provided, the account will be logged in directly using this identifier. If not provided, then you must use `display_name` and `password` to login. |
|
||||
| `display_name` | `str` | Yes* | Display name of the account. Used to resolve the `key_uid` if it is not provided, or to create a new account if one does not already exist. |
|
||||
|
||||
Returns the current `Account` instance, allowing method chaining.
|
||||
|
||||
@@ -19,7 +58,22 @@ Returns the current `Account` instance, allowing method chaining.
|
||||
from bot import Account
|
||||
|
||||
account = Account()
|
||||
account.login("status-app-bot", "SNTPUMP")
|
||||
params = {
|
||||
"display_name": "status-app-bot",
|
||||
"password": "SNTPUMP"
|
||||
}
|
||||
account.login(**params)
|
||||
```
|
||||
|
||||
```python
|
||||
from bot import Account
|
||||
|
||||
account = Account()
|
||||
params = {
|
||||
"key_uid": "0xff2c3...",
|
||||
"password": "SNTPUMP"
|
||||
}
|
||||
account.login(**params)
|
||||
```
|
||||
|
||||
### `logout()`
|
||||
@@ -30,7 +84,11 @@ Logout from the currently logged-in Status account. This method also clears the
|
||||
from bot import Account
|
||||
|
||||
account = Account()
|
||||
account.login("status-app-bot", "SNTPUMP")
|
||||
params = {
|
||||
"display_name": "status-app-bot",
|
||||
"password": "SNTPUMP"
|
||||
}
|
||||
account.login(**params)
|
||||
|
||||
# Optional - even if not specified __del__ will log you out
|
||||
account.logout()
|
||||
@@ -54,7 +112,11 @@ Send a text message to a specific chat. This method currently supports **text me
|
||||
from bot import Account
|
||||
|
||||
account = Account()
|
||||
account.login("status-app-bot", "SNTPUMP")
|
||||
params = {
|
||||
"display_name": "status-app-bot",
|
||||
"password": "SNTPUMP"
|
||||
}
|
||||
account.login(**params)
|
||||
|
||||
# This is under the assumption you already have a contact / joined a community
|
||||
chat = account.chats[0]
|
||||
@@ -82,7 +144,11 @@ from bot import Account
|
||||
import datetime
|
||||
|
||||
account = Account()
|
||||
account.login("status-app-bot", "SNTPUMP")
|
||||
params = {
|
||||
"display_name": "status-app-bot",
|
||||
"password": "SNTPUMP"
|
||||
}
|
||||
account.login(**params)
|
||||
|
||||
chat = account.chats[0]
|
||||
|
||||
@@ -109,7 +175,11 @@ from rich import print as rprint
|
||||
from rich.pretty import Pretty
|
||||
|
||||
account = Account()
|
||||
account.login("status-app-bot", "SNTPUMP")
|
||||
params = {
|
||||
"display_name": "status-app-bot",
|
||||
"password": "SNTPUMP"
|
||||
}
|
||||
account.login(**params)
|
||||
|
||||
for msg in account.listen_messages():
|
||||
rprint(Pretty(msg))
|
||||
@@ -143,7 +213,11 @@ Returns the current `Account` instance, allowing method chaining.
|
||||
from bot import Account
|
||||
|
||||
account = Account()
|
||||
account.login("status-app-bot", "SNTPUMP")
|
||||
params = {
|
||||
"display_name": "status-app-bot",
|
||||
"password": "SNTPUMP"
|
||||
}
|
||||
account.login(**params)
|
||||
|
||||
# Send a contact request
|
||||
account.add_contact(
|
||||
@@ -182,7 +256,11 @@ Returns `bool`.
|
||||
from bot import Account
|
||||
|
||||
account = Account()
|
||||
account.login("status-app-bot", "SNTPUMP")
|
||||
params = {
|
||||
"display_name": "status-app-bot",
|
||||
"password": "SNTPUMP"
|
||||
}
|
||||
account.login(**params)
|
||||
|
||||
# NOTE: contacts are returned as a dict for
|
||||
# internal class checks and scalability
|
||||
@@ -208,7 +286,11 @@ Returns `datetime.datetime` representing when the join request was submitted.
|
||||
from bot import Account
|
||||
|
||||
account = Account()
|
||||
account.login("status-app-bot", "SNTPUMP")
|
||||
params = {
|
||||
"display_name": "status-app-bot",
|
||||
"password": "SNTPUMP"
|
||||
}
|
||||
account.login(**params)
|
||||
|
||||
account.send_request_community(
|
||||
"https://status.app/c/community-invite-link"
|
||||
@@ -218,6 +300,30 @@ account.send_request_community(
|
||||
|
||||
## Properties
|
||||
|
||||
### `available_accounts`
|
||||
|
||||
Returns all Status accounts that are **locally available** in the initialized data directory. These accounts are detected when the `Account` class is initialized.
|
||||
|
||||
This property is useful when you want to:
|
||||
- inspect which accounts exist locally
|
||||
- retrieve a `key_uid` for login
|
||||
- display metadata about stored accounts
|
||||
|
||||
**You will have to know the passwords for the given `key_uid`.**
|
||||
|
||||
Returns `list[dict]`.
|
||||
|
||||
```python
|
||||
from bot import Account
|
||||
# For terminal readability only
|
||||
from rich import print as rprint
|
||||
from rich.pretty import Pretty
|
||||
|
||||
account = Account()
|
||||
|
||||
rprint(Pretty(account.available_accounts))
|
||||
```
|
||||
|
||||
### `info`
|
||||
|
||||
Provides information about the currently logged-in account. If `login()` has not been called, accessing this property will raise an exception. Returns `dict` containing account metadata.
|
||||
@@ -227,8 +333,9 @@ Provides information about the currently logged-in account. If `login()` has not
|
||||
| `public_key` | `str` | Public key that uniquely identifies the account. |
|
||||
| `emojis` | `str` | Emoji hash associated with the account identity. |
|
||||
| `key_uid` | `str` | Internal Status key identifier for the account. |
|
||||
| `compressed_key` | `str` | The chat key as it is in Status App. |
|
||||
| `mnemonic` | `str` | Mnemonic phrase used to generate the account keys. |
|
||||
| `name` | `str` | Display name of the account. |
|
||||
| `display_name` | `str` | Display name of the account. |
|
||||
| `password` | `str` | Password used to encrypt the account locally. |
|
||||
| `wallet_address` | `str` | Ethereum wallet address associated with the account. |
|
||||
| `logged_in_timestamp` | `datetime.datetime` | Timestamp when the account successfully logged in. |
|
||||
@@ -237,7 +344,11 @@ Provides information about the currently logged-in account. If `login()` has not
|
||||
from bot import Account
|
||||
|
||||
account = Account()
|
||||
account.login("status-app-bot", "SNTPUMP")
|
||||
params = {
|
||||
"display_name": "status-app-bot",
|
||||
"password": "SNTPUMP"
|
||||
}
|
||||
account.login(**params)
|
||||
|
||||
print(account.info)
|
||||
```
|
||||
@@ -279,7 +390,11 @@ Returns `dict[str, dict]` where the key is the contact's **public key**. This ma
|
||||
from bot import Account
|
||||
|
||||
account = Account()
|
||||
account.login("status-app-bot", "SNTPUMP")
|
||||
params = {
|
||||
"display_name": "status-app-bot",
|
||||
"password": "SNTPUMP"
|
||||
}
|
||||
account.login(**params)
|
||||
|
||||
contacts = account.contacts
|
||||
|
||||
@@ -368,7 +483,11 @@ Returns `list[dict]` where each `dict` represents a chat that can be used with [
|
||||
from bot import Account
|
||||
|
||||
account = Account()
|
||||
account.login("status-app-bot", "SNTPUMP")
|
||||
params = {
|
||||
"display_name": "status-app-bot",
|
||||
"password": "SNTPUMP"
|
||||
}
|
||||
account.login(**params)
|
||||
|
||||
# This is under the assumption you already have a contact / joined a community
|
||||
for chat in account.chats:
|
||||
@@ -390,3 +509,97 @@ The property exposes two primary methods:
|
||||
|
||||
- `signal.get()` — fetch a single event. If the event is not found, you may end up in an infinite loop.
|
||||
- `signal.listen()` — stream events continuously. Example usage of this is found in [`listen_messages()`](./account.md#listen_messages)
|
||||
|
||||
### `display_name`
|
||||
|
||||
Get or update the current display name of the logged‑in account.
|
||||
|
||||
Returns `str` when reading the property.
|
||||
|
||||
```python
|
||||
from bot import Account
|
||||
|
||||
account = Account()
|
||||
params = {
|
||||
"display_name": "status-app-bot",
|
||||
"password": "SNTPUMP"
|
||||
}
|
||||
account.login(**params)
|
||||
|
||||
# Get the current display name
|
||||
print(account.display_name)
|
||||
```
|
||||
|
||||
You can update the display name by assigning a new value:
|
||||
|
||||
```python
|
||||
from bot import Account
|
||||
|
||||
account = Account()
|
||||
params = {
|
||||
"display_name": "status-app-bot",
|
||||
"password": "SNTPUMP"
|
||||
}
|
||||
account.login(**params)
|
||||
|
||||
# Change the display name
|
||||
account.name = "status_bot_42"
|
||||
print(account.display_name)
|
||||
```
|
||||
|
||||
**Note**: Next time you login with the changed display name, you will have to put in the new display name, instead of the initial one.
|
||||
|
||||
### `bio`
|
||||
|
||||
Get or update the **bio** of the currently logged‑in account. The length of the bio (as in Status App) is 240 characters.
|
||||
|
||||
Returns `str` when reading the property.
|
||||
|
||||
```python
|
||||
from bot import Account
|
||||
|
||||
account = Account()
|
||||
params = {
|
||||
"display_name": "status-app-bot",
|
||||
"password": "SNTPUMP"
|
||||
}
|
||||
account.login(**params)
|
||||
|
||||
# Read the current bio
|
||||
print(account.bio)
|
||||
```
|
||||
|
||||
The value assigned to `bio` will automatically be converted to a string before being sent to the backend. You can update the bio by assigning a new value:
|
||||
|
||||
```python
|
||||
from bot import Account
|
||||
|
||||
account = Account()
|
||||
params = {
|
||||
"display_name": "status-app-bot",
|
||||
"password": "SNTPUMP"
|
||||
}
|
||||
account.login(**params)
|
||||
|
||||
# Update the bio
|
||||
account.bio = "Monitoring Status communities and chats"
|
||||
print(account.bio)
|
||||
```
|
||||
|
||||
You can also **clear the bio** by deleting the property:
|
||||
|
||||
```python
|
||||
from bot import Account
|
||||
|
||||
account = Account()
|
||||
params = {
|
||||
"display_name": "status-app-bot",
|
||||
"password": "SNTPUMP"
|
||||
}
|
||||
account.login(**params)
|
||||
|
||||
# Clears the bio - same as:
|
||||
# account.bio = ""
|
||||
# account.bio = None
|
||||
del account.bio
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user