Sunnygram
An async, MTProto-native Telegram client library for user accounts.
The user-side sibling of Moonlygram.
Documentation · Quickstart · Bring your session with you
Sunnygram speaks Telegram's native binary protocol instead of the HTTP Bot API, so it signs in as a person, or as a bot over MTProto, and reaches the whole client API surface. The stack is written here from scratch: crypto, transport, session, update state machine. It is not a wrapper over another library.
Where Moonlygram covers the Bot API, Sunnygram covers everything a real account can do.
Install
pip install sunnygram
No required dependencies. sunnygram[speedups] adds a native AES backend and uvloop, both
picked up automatically if present. See
performance.
You need an api_id and an api_hash from my.telegram.org.
Example
from sunnygram import Client, filters
app = Client("my.session", api_id=API_ID, api_hash=API_HASH)
@app.on_message(filters.private & filters.text & filters.incoming)
async def echo(client, message):
await message.reply(f"you said: **{message.text}**")
app.run()
The first run asks for a phone number and a code. Every run after that finds the key in the session file and asks nothing.
async with Client("my.session", api_id=API_ID, api_hash=API_HASH) as app:
me = await app.get_me()
await app.send_message("me", f"hello from {me.full_name}")
async for message in app.get_history("@durov", limit=20):
if message.has_media:
await message.download(into=f"{message.id}.bin")
# Anything unwrapped is one invoke away, typed as what the call answers with.
from sunnygram.raw import functions
config = await app.invoke(functions.help.GetConfig()) # -> types.Config
Bring an existing session
imported = sunnygram.read_session("my_account.session")
await sunnygram.adopt_session(imported, "sunny.session")
Session files and session strings both read. The peer cache and any stored file_id come
across with the key, and the source file is opened read-only.
Bringing a project over.
What it does
Around 260 client methods:
| Send | text, photos, video, music, voice notes, files, albums, polls, quizzes, stickers, dice, locations, venues, contacts |
| Read | history, search, dialogs, members, contacts |
| Act | edit, delete, forward, pin, react, vote, mark read, typing |
| Administer | promote, restrict, ban, titles, photos, permissions, slow mode, invite links, join requests, admin log |
| Talk to bots | press a button by its label, inline queries, start with a parameter |
| Pay | invoices in money or Stars, subscriptions, revenue, withdrawals, affiliate programs |
| Gifts | the catalogue, sending, upgrading, transferring, resale, collections, auctions, crafting |
| Be a bot | keyboards, callback queries, inline mode both ways, the command menu |
| Account | sessions listed and terminated, the second factor, privacy, usernames |
| Forums | topics listed, searched, opened, renamed, closed, pinned |
| Payments | invoices, Telegram Stars, balance, ledger, refunds |
| Stories | posted, edited, pinned, taken down, read |
| Statistics | channel, supergroup, post and story figures, and the graphs behind them |
| Boosts | a chat's level, who is boosting it, and lending it one of your slots |
| Shared folders | a folder given a link, joined, kept up to date, left |
| Sticker sets | made, added to, reordered, retitled, deleted |
| Later | any send queued by datetime or WHEN_ONLINE |
Twenty-one kinds of event, from messages and edits to inline queries, join requests, reactions and presence. Which of them a user account sees and which a bot does.
The stack under it, layer by layer:
sunnygram.tl |
the TL binary codec, bounds-checked against hostile input |
sunnygram.raw |
2495 constructors and functions at layer 228, generated from a pinned schema |
sunnygram.transport |
TCP, four framings including abridged and padded |
sunnygram.crypto |
the authorization handshake, AES-IGE and AES-CTR, RSA_PAD, SRP |
sunnygram.session |
message ids, envelopes, sequence numbers, containers, replay checks |
sunnygram.network |
the connection loop, the invoker, per-datacenter keys, the rate limiter |
sunnygram.updates |
pts / qts / seq and gap recovery through getDifference |
sunnygram.storage |
sqlite, in memory, or a session string |
sunnygram.peers |
access hashes learned from every answer, in a bounded LRU |
sunnygram.files |
multi-part transfers, cross-datacenter, CDN, stale reference refresh |
sunnygram.auth |
phone and code, 2FA over SRP, bot token, QR |
sunnygram.errors |
all 780 documented errors, generated, hung off their status codes |
Not here: calls above the raw layer, and TLS-disguised MTProxy. Account registration is deliberate and permanent: Sunnygram signs in to accounts that already exist.
Design
- Generated where it is mechanical. TL constructors and the error tree come from a pinned schema, drift-guarded in CI. Friendly types and client methods are written by hand.
- Safe by default. Calls are paced,
FLOOD_WAITis honored automatically, secrets never reach a log or arepr, malformed server data fails closed. - Correct updates. One state machine owns the counters. Every update is delivered once and in order, or not at all.
- Never silent. Everything the library survives on your behalf goes to the
sunnygramlogger, including a handler of yours that raised. - Never blocked. Cipher calls, the handshake's 2048-bit arithmetic and SRP's PBKDF2 all leave the event loop.
- Conversations natively.
answer = await app.ask(chat, "What is your name?"), with no state machine in between. - Plugins.
app.load_plugins("plugins")registers every decorated handler in a package.
Rights are said the readable way round, which Telegram's own flags are not. Text is markdown unless you say otherwise, HTML if you ask, and offsets are counted in UTF-16 the way Telegram counts them.
Status
Stable. The protocol stack, the client surface and the documentation are in place.
ARCHITECTURE.md has the layer stack and the numbered rules the code is
held to.
Over 2200 offline tests, mypy --strict across the package, and a scripted MTProto server that
completes a real handshake, plus a live tour against a real account covering sign-in, the read
surface and a file round trip.
Runnable examples in examples/, each against a real account:
login.py signs in and keeps the session,
tour.py walks the client surface,
echo.py answers anybody who writes to you,
files.py round-trips a file,
adopt.py takes over a session from elsewhere,
ask.py holds a conversation,
buttons.py is a bot with a menu,
events.py answers inline queries and greets joiners, and
moderate.py, topics.py,
shop.py, listen.py,
plugin_bot.py and get_config.py.
Development
pip install -e ".[dev]"
ruff check src tests codegen examples
mypy
pytest -q
Tests are offline. No account, no credentials, no network.
Two trees are generated and neither is edited by hand:
python codegen/refresh.py --check # has Telegram shipped a newer layer?
python codegen/gen_tl.py # rebuild raw/ from the pinned schema
python codegen/refresh.py --errors # take a fresh error table
python codegen/gen_errors.py # rebuild the error tree
See CONTRIBUTING.md and SECURITY.md.
License
Mozilla Public License 2.0. See LICENSE. Copyright © 2026 AtarixiaFamine.
File-level copyleft: use Sunnygram in a program of any licence, closed included, and publish changes to Sunnygram's own files under the same one. Your program's code is unaffected.
Release files for sunnygram 1.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| sunnygram-1.2.0.tar.gz | 1.2 MB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| sunnygram-1.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 2.0 MB
Release files / sunnygram-1.2.0.tar.gz
| Download URL | sunnygram-1.2.0.tar.gz |
|---|---|
| Size | 1.2 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
99ebfdaf4959a1833ed7291224b4997150fd565d3592f0ed6b36ea213f8fa45a
|
|
BLAKE2b-256 checksum How to use checksums |
a198cd5651f6973eb6289fb94084086fc9925e352172c30c1947941d7832663e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 29, 2026.
Transparency logRelease files / sunnygram-1.2.0-py3-none-any.whl
| Download URL | sunnygram-1.2.0-py3-none-any.whl |
|---|---|
| Size | 839.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
fc7f549f60d383ff813eeea1d6510b7332ccfc278288203a514adbac9bb093b8
|
|
BLAKE2b-256 checksum How to use checksums |
8b8d5f549bcdb1914dbaaa9730ae3987a3c23cd14effc4147459209414fdee90
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 29, 2026.
Transparency log