example: Group Chat Moderator

- Related to https://github.com/status-im/status-python-sdk/issues/25
This commit is contained in:
Nick Ninov
2026-08-04 18:17:38 +03:00
parent a0f7201a81
commit e12c29fa1e
5 changed files with 179 additions and 1 deletions
-1
View File
@@ -26,7 +26,6 @@ graph TB
subgraph bot[status-im/status-python-sdk]
REQUIREMENTS[requirements.txt]
GROUP_CHAT[class GroupChat]
SDK[class Account]
SIGNAL[class Signal]
+104
View File
@@ -0,0 +1,104 @@
# Group Chat Moderator
An **automatic moderator** for a [Group Chat](https://status.app/help/messaging/create-a-group-chat). The script logs into a Status account, listens for new messages **in real time**, and scores each one for toxicity with [Detoxify](https://github.com/unitaryai/detoxify) (local model). Authors of toxic messages are **warned**, and after a specified number of warnings they are **removed** from the chat.
## How it works
Every message that lands in the chat is handled in its **own thread** by [`check_message`](./main.py). The thread:
1. Skips messages sent by the bot itself.
2. Scores the message text with [Detoxify](https://github.com/unitaryai/detoxify) and takes the highest label (`toxicity`, `insult`, `threat`, ...).
3. Ignores anything below the `threshold` (default `0.6`).
4. Otherwise increments the author's warning count under a lock - the `warnings` dict is shared across threads, so the read-modify-write must be atomic.
5. Sends a warning reply, or [`remove`](../../docs/group-chat.md#removepublic_keys)s the author once they hit the `warning_limit` (default `3`).
```mermaid
sequenceDiagram
actor Member as Group Chat Member
participant Listen as listen_messages
participant Check as check_message thread
participant Model as Detoxify model
participant Warnings as warning counts
Member->>Listen: sends message
Listen->>Check: spawns per-message thread
Check->>Model: score message text
Model-->>Check: highest label + score
alt Below Threshold
Check-->>Check: ignore
else At / Above Threshold
Check->>Warnings: increment author's count
Warnings-->>Check: current count
alt count < warning_limit
Check->>Member: send warning reply
else count >= warning_limit
Check->>Member: remove from chat
end
end
```
This is just one moderation policy. [`check_message`](./main.py) is self-contained, so you can rewrite it for your own use case - swap in a different model or keyword filter, adjust `threshold` and `warning_limit` or escalate through different labels. The listener loop stays the same and only the per-message logic changes.
**Note**: Removing members requires the account to be the [administrator](../../docs/group-chat.md#administrator) of the chat. See [Moderation power](./README.md#moderation-power).
## Setup
### 1. Install
From the **repository root**, install the SDK with the `group-chat-moderator` dependencies:
```
pip install ".[group-chat-moderator]"
```
This pulls in [Detoxify](https://github.com/unitaryai/detoxify) and its [PyTorch](https://pytorch.org/) backend. The first run downloads the model weights.
**Note**: `detoxify` installs the CPU build of PyTorch by default. For faster inference on a CUDA GPU, uninstall `torch` and `torchvision`, then reinstall the GPU builds by following the instructions on [PyTorch's website](https://pytorch.org/).
### 2. Configure
Copy [`env.example`](./env.example) to `.env` in this folder and fill it in:
```
cp env.example .env
```
| Variable | What it is |
|-----|-------------|
| `PASSWORD` | The password of the moderating Status account. |
| `NAME` | The [display name](../../docs/account.md#display-name) or ENS name of the account. If you have previously logged in with the SDK you can provide an ENS. For first time log ins, it is best to provide a [display name](../../docs/account.md#display-name). |
| `MNEMONIC` | The [recovery phrase](https://status.app/help/profile/understand-your-status-keys-and-recovery-phrase) of the account. Used to recover it into the container. |
| `GROUP_CHAT_ID` | The `id` of the group chat to moderate. Group chat IDs come from the [`chats`](../../docs/account.md#chats) property, where `type` is `group_chat`. |
### 3. Run
The script loads its `.env` from the current directory, so run it from inside this folder:
```
cd examples/group-chat-moderator
python main.py
```
On the first run, [`launch_docker_container`](../../docs/utils.md#launch_docker_container) builds the Status Backend image, which takes a few minutes. Tthe bot starts listening:
```
[INFO] Successfully logged in!
[INFO] Loading Detoxify [cpu]
[INFO] Listening Group Chat Status Bots
```
Detoxify runs on the **GPU** automatically when CUDA is available (`[cuda]` above), and falls back to the CPU otherwise. The bot runs until you stop it with `Ctrl+C`.
## Moderation power
**This account acts as the moderator of the group chat.** To warn members it only needs to be in the chat, but to **remove** them it must be the [administrator](../../docs/group-chat.md#administrator) - only the admin can remove members. Point the moderator at a chat it created (or was made admin of), otherwise removals are rejected and members can only be warned.
The moderation logic in this example is deliberately simple:
- **One model, one threshold.** Every message is scored by [Detoxify](https://github.com/unitaryai/detoxify); anything scoring `0.6` or higher on any label counts as toxic. Tune `threshold` and `warning_limit` in [`check_message`](./main.py) to make moderation stricter or more lenient.
- **Warnings are per public key.** The count lives only in memory, so restarting the bot resets everyone's warnings to zero.
Detoxify is a machine-learning model and will make mistakes - both false positives and false negatives. Treat it as a first line of moderation, not a final judge.
@@ -0,0 +1,7 @@
# Status Account setup
PASSWORD = "your-password-here"
NAME = "status-display-name"
MNEMONIC = "phrase_1 phrase_2 phrase_3 phrase_4 phrase_5 phrase_6 phrase_7 phrase_8 phrase_9 phrase_10 phrase_11 phrase_12"
# Monitor messages from public chat
GROUP_CHAT_ID = "group-chat-id"
+64
View File
@@ -0,0 +1,64 @@
from dotenv import load_dotenv
from status_sdk import Account, GroupChat, launch_docker_container
from detoxify import Detoxify
import os, threading, torch
# `warnings` is shared by every check_message thread, so the
# read-modify-write of a member's warning count must be atomic
warnings_lock = threading.Lock()
def check_message(account: Account, message: dict, warnings: dict, group_chat: GroupChat, model: Detoxify, threshold: float = 0.6, warning_limit: int = 3):
"""
Score a single message and warn (or remove) its author.
"""
public_key = message["from"]
if public_key == account.info["public_key"]:
return
label, score = max(model.predict(message["text"]).items(), key=lambda item: item[1])
account.logger.info(f"Message: '{message['text']}'\t\t{label} - {(score * 100):.2f}%")
if score < threshold:
return
with warnings_lock:
warnings[public_key] = warnings.get(public_key, 0) + 1
count = warnings[public_key]
if count < warning_limit:
group_chat.send_message(f"Warning {count} /{warning_limit} - @{public_key} please keep it civil.", message["id"])
account.logger.info(f"Sent warning to {public_key}")
elif count >= warning_limit:
group_chat.send_message(f"Removing @{public_key} member after {warning_limit} warnings.")
account.logger.info(f"Removed {public_key} from {group_chat.name}")
group_chat.remove(public_key)
def main():
launch_docker_container()
load_dotenv()
account = Account(backup_folder=os.path.dirname(__file__))
account.login(
password=os.environ["PASSWORD"],
name=os.environ["NAME"],
mnemonic=os.environ["MNEMONIC"]
)
group_chat = GroupChat(account, os.environ["GROUP_CHAT_ID"])
warnings = {}
device = "cuda" if torch.cuda.is_available() else "cpu"
account.logger.info(f"Loading Detoxify [{device}]")
model = Detoxify("original", device=device)
account.logger.info(f"Listening Group Chat {group_chat.name}")
for message in account.listen_messages():
for chat in message["event"]["chats"]:
if chat["id"] != group_chat.id:
continue
threading.Thread(
target=check_message,
args=(account, chat["lastMessage"], warnings, group_chat, model),
daemon=True
).start()
if __name__ == "__main__":
main()
+4
View File
@@ -36,6 +36,10 @@ agents = [
"langchain-groq",
"python-dotenv",
]
group-chat-moderator = [
"detoxify",
"python-dotenv"
]
[project.urls]
Source = "https://github.com/status-im/status-python-sdk"