Skip to main content

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 beautifulsoup4import 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

bale_sdk-0.1.0.tar.gz (171.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

bale_sdk-0.1.0-py3-none-any.whl (174.7 kB view details)

Uploaded Python 3

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

Hashes for bale_sdk-0.1.0.tar.gz
Algorithm Hash digest
SHA256 dc16346b09674ba6625853e6711aad72f35e7467245dd2b2004d59a2b2b7f5b7
MD5 f1f8991c0c2bea9b6583bdd3d0d700f1
BLAKE2b-256 782d4adb12e76627425b32d8b5d73e76786d889d0459c19affc960f7aac0e63b

See more details on using hashes here.

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

Hashes for bale_sdk-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 e5839b8d6e6807ccabe0acdb09313e8f02aebefc353128927ba6fd8ac3fa8f87
MD5 0a794767e5c71b5853fa5f61ff479b0d
BLAKE2b-256 53346809f9e07735c08609274adb059029b28b536a926bed2565e350b2f2f9c6

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page