properties: display_name

- Change `username` to `display_name`
- Add `key_uid` login support
- Update documentation
This commit is contained in:
Nick Ninov
2026-03-11 12:12:01 +00:00
parent bb471e049e
commit 70ee474cd4
2 changed files with 256 additions and 24 deletions
+84 -10
View File
@@ -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,9 @@ 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"],
"password": password,
"wallet_address": event["address"],
"logged_in_timestamp": datetime.datetime.now()
@@ -125,6 +146,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 +163,23 @@ 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 contacts(self) -> dict[str, dict]:
"""
@@ -347,7 +392,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 +487,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 +516,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
+172 -14
View File
@@ -2,16 +2,55 @@
The account class allows you to easily work with a Status account.
## Display name
The **display name** is the humanreadable 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 (`AZ`)**
- It may contain **numbers (`09`)**
- 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,42 @@ 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 loggedin 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 also 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.