Unofficial async Python client for Bale messenger's web RPC API (reverse-engineered), with a Telethon-style high-level API and event system.
Project description
bale
Unofficial async Python client for Bale messenger's web RPC API.
Reverse-engineered from the Bale web client, with a high-level API modeled on
Telethon: an async, context-managed client,
ergonomic message/dialog/entity helpers, and an @client.on(...) event system —
plus typed access to all 607 raw RPCs (52 services) when you need them.
⚠️ Unofficial & proprietary. This library is not affiliated with or endorsed by Bale. It talks to a private API recovered from the web client, so behavior may change without notice. Use it only with accounts and data you are authorized to access, and in accordance with Bale's Terms of Service. See LICENSE — all rights reserved.
Install
pip install bale-sdk
The distribution is named bale-sdk, but you import it as bale (like
beautifulsoup4 → import bs4). Requires Python 3.9+. Dependencies:
websockets and protobuf.
Authentication
The client authenticates with an access token (a JWT) — the same
access_token cookie the web client uses. Provide it however you like; the
examples read it from the BALE_TOKEN environment variable:
export BALE_TOKEN="eyJ..."
You can also obtain one programmatically via phone OTP:
from bale import auth
session = auth.start_phone_auth(989123456789) # sends an SMS code
result = auth.validate_code(session.transaction_hash, input("code: "))
print(result.access_token) # store this securely
Quickstart
import asyncio
import os
from bale import BaleClient
async def main():
async with BaleClient(os.environ["BALE_TOKEN"]) as client:
me = await client.get_me()
print("Logged in as:", me.title)
# Your most recent conversations (titles auto-resolved)
for dialog in await client.get_dialogs(limit=10):
print(f"{dialog.unread_count:>3} unread {dialog.title}")
# Send a message (accepts a username, numeric id, or Peer)
await client.send_message("@some_channel", "Hello from Python 👋")
asyncio.run(main())
High-level API
BaleClient provides Telethon-style conveniences. Entities can be referenced by
username ("@name"), numeric id, or a Peer/PeerInfo:
| Method | Description |
|---|---|
await client.get_me() |
The logged-in account (as PeerInfo). |
await client.get_entity(ref) |
Resolve a username / id / peer to PeerInfo. |
await client.send_message(ref, text, reply_to=None) |
Send a text message (optionally quoting). |
await client.edit_message(ref, rid, text) |
Edit a message. |
await client.delete_messages(ref, rids, revoke=True) |
Delete messages. |
await client.forward_messages(to, rids, from_) |
Forward messages. |
await client.react(ref, rid, "❤") |
React to a message. |
await client.mark_read(ref) |
Mark a chat read. |
await client.get_messages(ref, limit=100) |
Fetch messages (list). |
async for m in client.iter_messages(ref) |
Iterate messages (paginated). |
await client.get_dialogs(limit=100) |
Fetch the dialog list. |
async for d in client.iter_dialogs() |
Iterate dialogs (paginated). |
async with BaleClient(token) as client:
chat = await client.get_entity("@my_channel")
async for msg in client.iter_messages(chat, limit=50):
if msg.content.kind == "text":
print(msg.sender_id, msg.content.text)
Events
React to incoming updates in real time, just like Telethon:
import asyncio
from bale import BaleClient, events
client = BaleClient(token)
@client.on(events.NewMessage(pattern="/ping"))
async def on_ping(event):
await event.reply("pong")
@client.on(events.NewMessage)
async def on_message(event):
print(f"{event.sender_id} in {event.chat_id}: {event.text}")
async def main():
async with client:
await client.run_until_disconnected()
asyncio.run(main())
A NewMessage event exposes text, sender_id, chat_id, peer, id,
date, content, is_private/is_group, and the coroutines reply(),
respond(), delete(), mark_read().
Need something other than new messages? events.Raw fires for every update;
event.update is the decoded pb.Update and event.variant names which of
Bale's ~140 update types it is (e.g. messageRead, reactionsUpdate, typing):
@client.on(events.Raw)
async def on_raw(event):
if event.variant == "reactionsUpdate":
print("reaction:", event.update.reactionsUpdate)
Raw RPC access
Every Bale web RPC is available as a typed async method, grouped by service. The namespace is the snake_case of the service's final segment:
# bale.messaging.v2.Messaging -> client.messaging
resp = await client.messaging.SendMessage(
peer={"type": 1, "id": uid, "accessHash": ah},
rid=rid,
message={"textMessage": {"text": "hi"}},
)
# Pass a pre-built protobuf instead of kwargs, if you prefer:
from bale import pb
req = pb.LoadHistoryRequest(peer=peer, date=-1, loadMode=2, limit=20)
hist = await client.messaging.LoadHistory(req)
Discover the full surface:
from bale import ALL_RPCS, SERVICE_CLASSES
print(len(ALL_RPCS), "RPCs across", len(SERVICE_CLASSES), "services")
Full reference (method names, inputs, outputs, inferred usage) is in
docs/API_COVERAGE.md (English) and
docs/API_FA.md (Persian / فارسی).
Project layout
bale/ the package (high-level client, transport, events, generated RPCs)
docs/ API reference (English + Persian)
examples/ runnable scripts (read BALE_TOKEN from the environment)
proto/ the recovered .proto schemas (reference)
tests/ offline tests
Disclaimer
This is an independent, unofficial project for interoperability and research. "Bale" and related marks belong to their respective owners. You are responsible for how you use it.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file bale_sdk-0.1.0.tar.gz.
File metadata
- Download URL: bale_sdk-0.1.0.tar.gz
- Upload date:
- Size: 171.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.9.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
dc16346b09674ba6625853e6711aad72f35e7467245dd2b2004d59a2b2b7f5b7
|
|
| MD5 |
f1f8991c0c2bea9b6583bdd3d0d700f1
|
|
| BLAKE2b-256 |
782d4adb12e76627425b32d8b5d73e76786d889d0459c19affc960f7aac0e63b
|
File details
Details for the file bale_sdk-0.1.0-py3-none-any.whl.
File metadata
- Download URL: bale_sdk-0.1.0-py3-none-any.whl
- Upload date:
- Size: 174.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.9.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e5839b8d6e6807ccabe0acdb09313e8f02aebefc353128927ba6fd8ac3fa8f87
|
|
| MD5 |
0a794767e5c71b5853fa5f61ff479b0d
|
|
| BLAKE2b-256 |
53346809f9e07735c08609274adb059029b28b536a926bed2565e350b2f2f9c6
|