darkob
Fast, stable async Python for the Rubika & Shad platforms.
Bots and userbots that stay up — with an API verified against the real server.
Docs · AI context · PyPI · Roadmap
pip install darkob
from darkob import Bot, Keyboard
bot = Bot(token="YOUR_BOT_TOKEN")
@bot.on_command("start")
async def start(bot, event):
await event.reply(
"Welcome 👋",
inline_keypad=Keyboard().row("Docs", "Support"),
)
bot.run()
Why darkob
A fork of maxrubika, rebuilt so failures are visible, the fast paths are actually fast, and the API surface matches the server.
| Inherited problem | What darkob does |
|---|---|
| Contacted PyPI at import time | Importing performs no network I/O |
| TLS verification disabled everywhere | Verification on by default, opt-out explicit |
Bare except: swallowed KeyboardInterrupt |
Narrowed to Exception, and logged |
| Background handler crashes vanished at GC | Logged the moment they happen |
| Every event scanned all handlers | Indexed dispatch, built once |
Retries were endpoints × max_retries |
Bounded budget with jitter and 429 handling |
| A new TLS handshake per download | Pooled sessions everywhere, including files |
| Saved sessions opened but never read | Credentials restored; sessions survive restarts |
| A corrupt session file was deleted | Quarantined, never destroyed |
| Keypad editing called a non-existent endpoint | Fixed by live testing, pinned by a regression test |
What's inside
|
Two surfaces, one library
|
Async first, sync friendly Every method works with |
|
Handlers, filters, routers Decorators, 42 filters composing with |
Conversations
|
|
Keyboards for every button type
|
Message helpers
|
|
Iterators, not pagination loops
|
Testing toolkit
|
Reliability
Six primitives that most bot libraries leave to the user:
| Primitive | What it buys you |
|---|---|
Retry(attempts=, backoff=, jitter=) |
A policy you can reason about, honoured by both surfaces |
RateLimiter(rate=, per_chat_rate=) |
Token buckets globally and per chat, applied to every attempt |
CircuitBreaker |
A dead data centre stops burning your retry budget |
SendQueue(path=) |
Messages survive a dropped connection and flush on reconnect |
ProtocolTracker |
Reports when Rubika reshuffles its endpoints |
| Resumable downloads | Byte-range resume that survives an interrupted file |
from darkob import Bot, RateLimiter, Retry, SendQueue
bot = Bot(
token="...",
retry=Retry(attempts=4, backoff=0.5),
rate_limiter=RateLimiter(rate=20, per_chat_rate=3),
)
outbox = SendQueue(bot, path="outbox.json")
outbox.start()
Performance
Measured with benchmarks/benchmark_suite.py against maxrubika 1.17.0 on the
same machine, each case in a fresh process so neither library inherits the
other's warm state:
| Benchmark | darkob | maxrubika | Result |
|---|---|---|---|
Read event.text ×20,000 |
13.5 ms | 20.9 ms | 1.5× faster |
| Nested key lookup ×20,000 | 3.0 ms | 16.8 ms | 5.7× faster |
| Filter evaluation ×5,000 | 5.1 ms | 10.5 ms | 1.4× faster |
| Route 5,000 updates, 50 handlers | 160 ms | 344 ms | 2.2× faster |
| Route 5,000 updates, 500 handlers | 1.50 s | 3.33 s | 2.2× faster |
Under realistic load (benchmarks/scenarios.py, same machine): 17,500
updates/s routing 1,000 updates against 100 registered handlers, and a 4 MiB
download resumed from 50% completes in 1.9 ms of library overhead.
Three things carry the difference:
- Lookups are indexed. Every payload builds a key index on first
access, so
event.text— which searchestextthencaption— no longer walks the structure on every read. - Dispatch is an index, not a scan. Handlers are grouped by update name once, so routing cost scales with the handlers that match, not with the handlers that exist.
- The update name and waiter list are cheap.
capitalize()is memoised and a pendingask()costs one integer check per update.
Read this before quoting those numbers
The benchmark used to be wrong, and in our favour, which is the worst kind of
wrong. Its dispatch cases registered handlers as
{handler: MessageUpdates() for _ in range(count)} — a dict keyed by the
function object, so 500 identical entries collapsed into one key. Both
"50 handlers" and "500 handlers" were measuring a single handler, which is why
the two rows previously reported nearly identical times.
With distinct handlers the gap widened rather than narrowed: 1.5× became 2.2×,
because an index only helps once there is something to index. README numbers
before 0.18.0 should be discarded.
Two more caveats, stated plainly:
- Nothing here measures the network. In a running bot, network latency dominates every row. These numbers describe CPU work per update.
- The dispatch rows measure the client dispatch path — the one both
libraries expose identically, which is what makes the comparison fair.
darkob.Botroutes through its own handler registry, which is a linear scan rather than an index; it is a different code path and is not what these rows time. - The micro-benchmarks isolate one code path each. The 5.7× nested lookup is real but applies to a payload shape those properties actually walk; the end-to-end gain is smaller. The dispatch rows are the ones you will notice, because routing runs on every single update.
CI enforces this: benchmarks/check_regression.py fails the build when a
head-to-head factor drops by more than 25%, or a calibration-normalised
scenario ratio rises by more than 75% (wider, because the committed baseline
was recorded on a different platform). See
benchmarks/README.md for the methodology, the
recording hardware and the exact commands.
Verified against the real API
The Bot API documents 19 endpoints. All are implemented, and 18 were
exercised against a live server — including the full file round-trip
(requestSendFile → uploadFile → getFile → sendFile → download) and
byte-range resume.
That testing caught three things static reading could not:
editInlineKeypad— the docs' own cURL — answersINVALID_INPUT: Invalid Method. The working endpoint iseditMessageKeypad.- A bare
Paymentbutton is rejected;Button.payment()now demands its fields instead of failing at send time. Bot.download_filewas opening its own HTTP session, paying a fresh TLS handshake per file.
The remaining two gaps are documented rather than guessed: getChatMember
needs a group chat to verify, and setCommands has no read-back, so
testing it would destroy your command list.
Documentation
Full site: manitheferfri.github.io/darkob
| Quickstart | Install to running bot in five minutes |
| Handlers · Filters | Every decorator and all 42 constraints |
| Routers | Split a bot into modules, with scoped middleware |
| Conversations | State, storage backends, StateFilter |
| Keyboards | All button types and formatting |
| Composing messages | MessageBuilder and formatting together |
| Errors | Retries, breaker, rate limiting, queue |
| Bot API · Messenger API | Generated from the package |
Built for AI-assisted development
Most darkob bots are written by an assistant, so the docs are built to be read by a model.
https://manitheferfri.github.io/darkob/llms-full.txt
One file, one fetch: hard rules, every guide, and the complete API with full docstrings for all 688 methods.
llms.txt— curated mapapi.json— every method as JSON- Every page is also served as raw Markdown: append
.mdto any URL - The API reference is generated from the installed package, so it cannot drift
Development
git clone https://github.com/ManiTheFerfri/darkob.git
cd darkob
pip install -e ".[dev]"
pytest --cov=darkob # 463 tests, no network required
ruff check . # lint
mypy darkob/router.py darkob/retry.py darkob/builders.py \
darkob/keyboards.py darkob/formatting.py darkob/testing.py
The suite never opens a socket: HTTP goes through fake transports, and a
subprocess test asserts that importing darkob cannot reach the network.
Two extra tools live in scripts/:
benchmark.py— micro-benchmarks for the hot paths, no network.conformance.py— exercises every Bot API method against a real token, cleans up after itself, and exits non-zero on failure.
Roadmap
See docs/roadmap.md — milestones 1–3 and P0–P2 are closed. It records what is proven, what is impossible, and why.
License
MIT. Derived from maxrubika, copyright MEHRAB Farahmand; the original MIT notice is preserved in LICENSE.
Maintained by Mani Keikhosravi.
Not affiliated with Rubika or Shad. Use responsibly and in line with the platforms' terms of service.
Metadata
Release files for darkob 0.18.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 | Provenance | Actions |
|---|---|---|---|---|
| darkob-0.18.0.tar.gz | 294.2 kB | With provenance | Details for darkob-0.18.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| darkob-0.18.0-py3-none-any.whl | Python 3 | none | any | Details for darkob-0.18.0-py3-none-any.whl |
Total release size: 718.5 kB
Release files / darkob-0.18.0.tar.gz
| Download URL | darkob-0.18.0.tar.gz |
|---|---|
| Size | 294.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
0305cb47d5708723abd8484a61e9eccbb53df3dc36ceeca7075f183d600d7a61
|
|
BLAKE2b-256 checksum How to use checksums |
471f08afb29043d0d37a34a2192447fb1af120572c5106d5108c5a03ee7285ea
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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 Nov 6, 2026.
Transparency logRelease files / darkob-0.18.0-py3-none-any.whl
| Download URL | darkob-0.18.0-py3-none-any.whl |
|---|---|
| Size | 424.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
fc4453e7d4bf489366238323f8601667f0291122562da8f8205ee4d04a42d649
|
|
BLAKE2b-256 checksum How to use checksums |
5f3f76706efafa07fe12fca69290fbb002b67062c5b9c0d1ab155e0e6577567c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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 Nov 6, 2026.
Transparency logSecurity for darkob 0.18.0
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.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for darkob-0.18.0.tar.gz
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 Nov 6, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.