baleio 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- baleio-0.1.0/.github/workflows/ci.yml +32 -0
- baleio-0.1.0/.github/workflows/docs.yml +51 -0
- baleio-0.1.0/.github/workflows/publish.yml +54 -0
- baleio-0.1.0/.gitignore +15 -0
- baleio-0.1.0/CHANGELOG.md +29 -0
- baleio-0.1.0/CONTRIBUTING.md +47 -0
- baleio-0.1.0/LICENSE +21 -0
- baleio-0.1.0/PKG-INFO +389 -0
- baleio-0.1.0/README.fa.md +281 -0
- baleio-0.1.0/README.md +357 -0
- baleio-0.1.0/baleio/__init__.py +93 -0
- baleio-0.1.0/baleio/__meta__.py +2 -0
- baleio-0.1.0/baleio/client/__init__.py +5 -0
- baleio-0.1.0/baleio/client/bot.py +655 -0
- baleio-0.1.0/baleio/client/default.py +18 -0
- baleio-0.1.0/baleio/client/session/__init__.py +4 -0
- baleio-0.1.0/baleio/client/session/aiohttp.py +115 -0
- baleio-0.1.0/baleio/client/session/base.py +178 -0
- baleio-0.1.0/baleio/dispatcher/__init__.py +4 -0
- baleio-0.1.0/baleio/dispatcher/dispatcher.py +223 -0
- baleio-0.1.0/baleio/dispatcher/event/__init__.py +4 -0
- baleio-0.1.0/baleio/dispatcher/event/handler.py +23 -0
- baleio-0.1.0/baleio/dispatcher/event/observer.py +74 -0
- baleio-0.1.0/baleio/dispatcher/router.py +72 -0
- baleio-0.1.0/baleio/enums/__init__.py +128 -0
- baleio-0.1.0/baleio/exceptions.py +121 -0
- baleio-0.1.0/baleio/filters/__init__.py +30 -0
- baleio-0.1.0/baleio/filters/base.py +81 -0
- baleio-0.1.0/baleio/filters/callback_data.py +149 -0
- baleio-0.1.0/baleio/filters/command.py +96 -0
- baleio-0.1.0/baleio/filters/exception.py +40 -0
- baleio-0.1.0/baleio/filters/logic.py +41 -0
- baleio-0.1.0/baleio/filters/state.py +32 -0
- baleio-0.1.0/baleio/fsm/__init__.py +14 -0
- baleio-0.1.0/baleio/fsm/context.py +33 -0
- baleio-0.1.0/baleio/fsm/state.py +81 -0
- baleio-0.1.0/baleio/fsm/storage/__init__.py +4 -0
- baleio-0.1.0/baleio/fsm/storage/base.py +53 -0
- baleio-0.1.0/baleio/fsm/storage/memory.py +39 -0
- baleio-0.1.0/baleio/types/__init__.py +143 -0
- baleio-0.1.0/baleio/types/attachments.py +53 -0
- baleio-0.1.0/baleio/types/base.py +84 -0
- baleio-0.1.0/baleio/types/callback.py +41 -0
- baleio-0.1.0/baleio/types/chat.py +135 -0
- baleio-0.1.0/baleio/types/error.py +20 -0
- baleio-0.1.0/baleio/types/input_file.py +115 -0
- baleio-0.1.0/baleio/types/input_media.py +58 -0
- baleio-0.1.0/baleio/types/keyboards.py +59 -0
- baleio-0.1.0/baleio/types/media.py +98 -0
- baleio-0.1.0/baleio/types/message.py +272 -0
- baleio-0.1.0/baleio/types/misc.py +18 -0
- baleio-0.1.0/baleio/types/payments.py +50 -0
- baleio-0.1.0/baleio/types/update.py +33 -0
- baleio-0.1.0/baleio/types/user.py +27 -0
- baleio-0.1.0/baleio/utils/__init__.py +3 -0
- baleio-0.1.0/baleio/utils/call.py +77 -0
- baleio-0.1.0/baleio/utils/keyboard.py +84 -0
- baleio-0.1.0/baleio/utils/markdown.py +35 -0
- baleio-0.1.0/docs/Makefile +20 -0
- baleio-0.1.0/docs/_static/custom.css +27 -0
- baleio-0.1.0/docs/api.md +96 -0
- baleio-0.1.0/docs/callback_data.md +76 -0
- baleio-0.1.0/docs/changelog.md +2 -0
- baleio-0.1.0/docs/conf.py +101 -0
- baleio-0.1.0/docs/dispatcher.md +115 -0
- baleio-0.1.0/docs/errors.md +57 -0
- baleio-0.1.0/docs/filters.md +106 -0
- baleio-0.1.0/docs/fsm.md +77 -0
- baleio-0.1.0/docs/index.md +116 -0
- baleio-0.1.0/docs/installation.md +43 -0
- baleio-0.1.0/docs/keyboards.md +78 -0
- baleio-0.1.0/docs/quickstart.md +80 -0
- baleio-0.1.0/docs/requirements.txt +5 -0
- baleio-0.1.0/examples/echo_bot.py +59 -0
- baleio-0.1.0/examples/fsm_form_bot.py +58 -0
- baleio-0.1.0/examples/quickstart.py +56 -0
- baleio-0.1.0/examples/webhook_bot.py +43 -0
- baleio-0.1.0/pyproject.toml +46 -0
- baleio-0.1.0/tests/conftest.py +74 -0
- baleio-0.1.0/tests/test_bot_and_keyboard.py +118 -0
- baleio-0.1.0/tests/test_callback_data.py +87 -0
- baleio-0.1.0/tests/test_dispatch.py +146 -0
- baleio-0.1.0/tests/test_errors.py +96 -0
- baleio-0.1.0/tests/test_serialization.py +74 -0
- baleio-0.1.0/tests/test_types.py +63 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
strategy:
|
|
13
|
+
fail-fast: false
|
|
14
|
+
matrix:
|
|
15
|
+
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
|
|
16
|
+
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v4
|
|
19
|
+
|
|
20
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
21
|
+
uses: actions/setup-python@v5
|
|
22
|
+
with:
|
|
23
|
+
python-version: ${{ matrix.python-version }}
|
|
24
|
+
cache: pip
|
|
25
|
+
|
|
26
|
+
- name: Install
|
|
27
|
+
run: |
|
|
28
|
+
python -m pip install --upgrade pip
|
|
29
|
+
pip install -e ".[dev]"
|
|
30
|
+
|
|
31
|
+
- name: Run tests
|
|
32
|
+
run: pytest -q
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
name: Docs
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
pages: write
|
|
11
|
+
id-token: write
|
|
12
|
+
|
|
13
|
+
concurrency:
|
|
14
|
+
group: pages
|
|
15
|
+
cancel-in-progress: true
|
|
16
|
+
|
|
17
|
+
jobs:
|
|
18
|
+
build:
|
|
19
|
+
runs-on: ubuntu-latest
|
|
20
|
+
steps:
|
|
21
|
+
- uses: actions/checkout@v4
|
|
22
|
+
|
|
23
|
+
- uses: actions/setup-python@v5
|
|
24
|
+
with:
|
|
25
|
+
python-version: "3.12"
|
|
26
|
+
cache: pip
|
|
27
|
+
|
|
28
|
+
- name: Install package + docs deps
|
|
29
|
+
run: |
|
|
30
|
+
python -m pip install --upgrade pip
|
|
31
|
+
pip install -e .
|
|
32
|
+
pip install -r docs/requirements.txt
|
|
33
|
+
|
|
34
|
+
- name: Build HTML
|
|
35
|
+
run: sphinx-build -b html docs docs/_build/html
|
|
36
|
+
|
|
37
|
+
- name: Upload artifact
|
|
38
|
+
uses: actions/upload-pages-artifact@v3
|
|
39
|
+
with:
|
|
40
|
+
path: docs/_build/html
|
|
41
|
+
|
|
42
|
+
deploy:
|
|
43
|
+
needs: build
|
|
44
|
+
runs-on: ubuntu-latest
|
|
45
|
+
environment:
|
|
46
|
+
name: github-pages
|
|
47
|
+
url: ${{ steps.deployment.outputs.page_url }}
|
|
48
|
+
steps:
|
|
49
|
+
- name: Deploy to GitHub Pages
|
|
50
|
+
id: deployment
|
|
51
|
+
uses: actions/deploy-pages@v4
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
# Publishes to PyPI when you publish a GitHub Release.
|
|
4
|
+
# Uses PyPI Trusted Publishing (OIDC) — no API tokens or secrets to store.
|
|
5
|
+
|
|
6
|
+
on:
|
|
7
|
+
release:
|
|
8
|
+
types: [published]
|
|
9
|
+
workflow_dispatch:
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
build:
|
|
13
|
+
name: Build distributions
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
|
|
18
|
+
- uses: actions/setup-python@v5
|
|
19
|
+
with:
|
|
20
|
+
python-version: "3.12"
|
|
21
|
+
|
|
22
|
+
- name: Build sdist and wheel
|
|
23
|
+
run: |
|
|
24
|
+
python -m pip install --upgrade build
|
|
25
|
+
python -m build
|
|
26
|
+
|
|
27
|
+
- name: Check metadata
|
|
28
|
+
run: |
|
|
29
|
+
python -m pip install --upgrade twine
|
|
30
|
+
twine check dist/*
|
|
31
|
+
|
|
32
|
+
- uses: actions/upload-artifact@v4
|
|
33
|
+
with:
|
|
34
|
+
name: dist
|
|
35
|
+
path: dist/
|
|
36
|
+
|
|
37
|
+
publish:
|
|
38
|
+
name: Publish to PyPI
|
|
39
|
+
needs: build
|
|
40
|
+
runs-on: ubuntu-latest
|
|
41
|
+
# A GitHub "environment" you can protect with required reviewers if you like.
|
|
42
|
+
environment:
|
|
43
|
+
name: pypi
|
|
44
|
+
url: https://pypi.org/p/baleio
|
|
45
|
+
permissions:
|
|
46
|
+
id-token: write # REQUIRED for trusted publishing
|
|
47
|
+
steps:
|
|
48
|
+
- uses: actions/download-artifact@v4
|
|
49
|
+
with:
|
|
50
|
+
name: dist
|
|
51
|
+
path: dist/
|
|
52
|
+
|
|
53
|
+
- name: Publish
|
|
54
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
baleio-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here. The format is based on
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/), and this project adheres to
|
|
5
|
+
[Semantic Versioning](https://semver.org/).
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
## [0.1.0] - 2026-07-06
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- Initial release of **baleio**, an aiogram-style async framework for the Bale bot API.
|
|
13
|
+
- `Bot` client covering every documented Bale method, with `aiohttp` session and
|
|
14
|
+
automatic JSON / `multipart/form-data` request serialization.
|
|
15
|
+
- Pydantic v2 models for all Bale API types.
|
|
16
|
+
- `Dispatcher` / `Router` with per-event observers, nested routers, and dependency
|
|
17
|
+
injection.
|
|
18
|
+
- Filters: `Command`, `CommandStart`, `StateFilter`, magic filter `F`, `&` / `|` / `~`
|
|
19
|
+
combinators, and the `CallbackData` factory.
|
|
20
|
+
- Finite State Machine: `StatesGroup`, `State`, `FSMContext`, `MemoryStorage`.
|
|
21
|
+
- Inline and reply keyboard builders.
|
|
22
|
+
- Centralized error handling via `@dp.errors()` and `ErrorEvent`
|
|
23
|
+
(`ExceptionTypeFilter`, `ExceptionMessageFilter`).
|
|
24
|
+
- Message shortcuts (`answer`, `reply`, `send_copy`, `edit_text`, …) and Markdown
|
|
25
|
+
helpers (`md`).
|
|
26
|
+
- Long polling and webhook support.
|
|
27
|
+
|
|
28
|
+
[Unreleased]: https://github.com/ehsndvr/baleio/compare/v0.1.0...HEAD
|
|
29
|
+
[0.1.0]: https://github.com/ehsndvr/baleio/releases/tag/v0.1.0
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Contributing to baleio
|
|
2
|
+
|
|
3
|
+
Thanks for your interest in improving **baleio**! 🎉
|
|
4
|
+
|
|
5
|
+
## Getting started
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
git clone https://github.com/ehsndvr/baleio && cd baleio
|
|
9
|
+
python -m venv .venv && source .venv/bin/activate
|
|
10
|
+
pip install -e ".[dev]"
|
|
11
|
+
pytest
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Guidelines
|
|
15
|
+
|
|
16
|
+
- **Match the surrounding style.** baleio mirrors aiogram's public API where it makes
|
|
17
|
+
sense; keep naming and patterns consistent with existing code.
|
|
18
|
+
- **Type everything.** All public functions are type-annotated (Pydantic v2 models for
|
|
19
|
+
API objects).
|
|
20
|
+
- **Add tests.** New behaviour needs coverage in `tests/`. Tests run fully offline via
|
|
21
|
+
the `FakeSession` fixture — no network or real bot token required.
|
|
22
|
+
- **Keep it async-first.** The whole framework runs on `asyncio` / `aiohttp`.
|
|
23
|
+
- **Document public API.** Update the relevant page under `docs/` and, if user-facing,
|
|
24
|
+
both `README.md` and `README.fa.md`.
|
|
25
|
+
|
|
26
|
+
## Running the docs locally
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pip install -r docs/requirements.txt
|
|
30
|
+
sphinx-build -b html docs docs/_build/html
|
|
31
|
+
# open docs/_build/html/index.html
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Pull requests
|
|
35
|
+
|
|
36
|
+
1. Fork and create a feature branch.
|
|
37
|
+
2. Make your change with tests and docs.
|
|
38
|
+
3. Ensure `pytest` passes.
|
|
39
|
+
4. Open a PR describing the change and linking any related issue.
|
|
40
|
+
|
|
41
|
+
## Reporting bugs
|
|
42
|
+
|
|
43
|
+
Open an issue with a minimal reproduction, the traceback, and your Python / baleio
|
|
44
|
+
versions. For API mismatches with Bale, include the method name and the raw request /
|
|
45
|
+
response if possible (redact your token!).
|
|
46
|
+
|
|
47
|
+
By contributing, you agree that your contributions are licensed under the MIT License.
|
baleio-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 baleio contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
baleio-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: baleio
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Modern, fully async framework for Bale (بله) messenger bots — inspired by aiogram.
|
|
5
|
+
Project-URL: Homepage, https://github.com/ehsndvr/baleio
|
|
6
|
+
Project-URL: Documentation, https://ehsndvr.github.io/baleio/
|
|
7
|
+
Project-URL: Repository, https://github.com/ehsndvr/baleio
|
|
8
|
+
Project-URL: Issues, https://github.com/ehsndvr/baleio/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/ehsndvr/baleio/blob/main/CHANGELOG.md
|
|
10
|
+
Author: baleio contributors
|
|
11
|
+
License: MIT
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: aiogram,api,async,bale,bot,بازو,بله
|
|
14
|
+
Classifier: Development Status :: 3 - Alpha
|
|
15
|
+
Classifier: Framework :: AsyncIO
|
|
16
|
+
Classifier: Intended Audience :: Developers
|
|
17
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
20
|
+
Classifier: Topic :: Communications :: Chat
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
22
|
+
Requires-Python: >=3.9
|
|
23
|
+
Requires-Dist: aiohttp>=3.9.0
|
|
24
|
+
Requires-Dist: certifi>=2023.7.22
|
|
25
|
+
Requires-Dist: magic-filter>=1.0.12
|
|
26
|
+
Requires-Dist: pydantic<3.0,>=2.4.1
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: mypy>=1.5; extra == 'dev'
|
|
29
|
+
Requires-Dist: pytest-asyncio>=0.21; extra == 'dev'
|
|
30
|
+
Requires-Dist: pytest>=7.4; extra == 'dev'
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
<div align="center">
|
|
34
|
+
|
|
35
|
+
# baleio
|
|
36
|
+
|
|
37
|
+
**A modern, fully-async framework for [Bale](https://bale.ai) (بله) bots — inspired by [aiogram](https://github.com/aiogram/aiogram).**
|
|
38
|
+
|
|
39
|
+
[](https://github.com/ehsndvr/baleio/actions/workflows/ci.yml)
|
|
40
|
+
[](https://ehsndvr.github.io/baleio/)
|
|
41
|
+
[](https://www.python.org/)
|
|
42
|
+
[](https://docs.pydantic.dev/)
|
|
43
|
+
[](LICENSE)
|
|
44
|
+
|
|
45
|
+
[**Documentation**](https://ehsndvr.github.io/baleio/) · [**Quick start**](#quick-start) · [**Examples**](examples/) · [**فارسی 🇮🇷**](README.fa.md)
|
|
46
|
+
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
If you have ever worked with `aiogram`, you already know `baleio`. Same architecture,
|
|
52
|
+
same patterns — this time for the **Bale bot API** (`https://tapi.bale.ai`). Routers,
|
|
53
|
+
magic filters, FSM, dependency injection, keyboard builders, callback-data factories
|
|
54
|
+
and centralized error handling all work the way you expect.
|
|
55
|
+
|
|
56
|
+
## Features
|
|
57
|
+
|
|
58
|
+
- ⚡️ **Fully asynchronous** on top of `aiohttp`.
|
|
59
|
+
- 🧩 **`Dispatcher` / `Router`** with nested routing and per-event observers.
|
|
60
|
+
- 🎯 **Filters**: `Command`, `CommandStart`, `StateFilter`, the magic filter `F`, plain
|
|
61
|
+
callables, and `&` / `|` / `~` combinators.
|
|
62
|
+
- 🏷️ **`CallbackData` factory** — type-safe, packed/unpacked structured callback payloads.
|
|
63
|
+
- 💾 **Finite State Machine** with `StatesGroup` / `State` and pluggable storage (`MemoryStorage`).
|
|
64
|
+
- ⌨️ **Keyboard builders** for inline and reply keyboards.
|
|
65
|
+
- 🧱 **Pydantic v2 models** for every Bale API type.
|
|
66
|
+
- 🔌 **Middlewares** at the event level (inner & outer).
|
|
67
|
+
- 🧯 **Centralized error handling** via `@dp.errors()` and `ErrorEvent`.
|
|
68
|
+
- 📎 File uploads (`multipart/form-data`), sending by `file_id` or URL, and media groups.
|
|
69
|
+
- 💳 Wallet payments (`sendInvoice`, `PreCheckoutQuery`, `answerPreCheckoutQuery`, …).
|
|
70
|
+
- 🪝 Long polling **and** webhook support.
|
|
71
|
+
|
|
72
|
+
## Installation
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
pip install baleio # once published to PyPI
|
|
76
|
+
# or, from source:
|
|
77
|
+
git clone https://github.com/ehsndvr/baleio && cd baleio
|
|
78
|
+
pip install -e .
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**Requirements:** Python 3.9+, `aiohttp`, `pydantic>=2`, `magic-filter`.
|
|
82
|
+
|
|
83
|
+
## Quick start
|
|
84
|
+
|
|
85
|
+
The snippet below is the **official aiogram quickstart**, ported to `baleio` almost
|
|
86
|
+
line-for-line. Only the import roots and the text formatter change (Bale renders
|
|
87
|
+
Markdown, so `md` replaces aiogram's `html`).
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
import asyncio
|
|
91
|
+
import logging
|
|
92
|
+
import sys
|
|
93
|
+
from os import getenv
|
|
94
|
+
|
|
95
|
+
from baleio import Bot, Dispatcher, md
|
|
96
|
+
from baleio.client.default import DefaultBotProperties
|
|
97
|
+
from baleio.enums import ParseMode
|
|
98
|
+
from baleio.filters import CommandStart
|
|
99
|
+
from baleio.types import Message
|
|
100
|
+
|
|
101
|
+
TOKEN = getenv("BOT_TOKEN") # get one from @botfather in Bale
|
|
102
|
+
|
|
103
|
+
dp = Dispatcher()
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@dp.message(CommandStart())
|
|
107
|
+
async def command_start_handler(message: Message) -> None:
|
|
108
|
+
await message.answer(f"Hello, {md.bold(message.from_user.full_name)}!")
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@dp.message()
|
|
112
|
+
async def echo_handler(message: Message) -> None:
|
|
113
|
+
try:
|
|
114
|
+
await message.send_copy(chat_id=message.chat.id)
|
|
115
|
+
except TypeError:
|
|
116
|
+
await message.answer("Nice try!")
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
async def main() -> None:
|
|
120
|
+
bot = Bot(token=TOKEN, default=DefaultBotProperties(parse_mode=ParseMode.MARKDOWN))
|
|
121
|
+
await dp.start_polling(bot)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
if __name__ == "__main__":
|
|
125
|
+
logging.basicConfig(level=logging.INFO, stream=sys.stdout)
|
|
126
|
+
asyncio.run(main())
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
> **Compatibility note:** Bale does not accept a `parse_mode` request parameter (it
|
|
130
|
+
> always renders Markdown), but `DefaultBotProperties(parse_mode=...)` is accepted for
|
|
131
|
+
> full aiogram parity and safely ignored. Use `md` for bold/italic/link formatting.
|
|
132
|
+
|
|
133
|
+
## Core concepts
|
|
134
|
+
|
|
135
|
+
### The Bot client
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
bot = Bot("123456:TOKEN")
|
|
139
|
+
|
|
140
|
+
me = await bot.get_me()
|
|
141
|
+
await bot.send_message(chat_id, "Hello")
|
|
142
|
+
await bot.send_photo(chat_id, "https://example.com/pic.jpg", caption="A photo")
|
|
143
|
+
await bot.send_photo(chat_id, FSInputFile("local.jpg")) # upload from disk
|
|
144
|
+
await bot.edit_message_text(chat_id, message_id, "New text")
|
|
145
|
+
await bot.delete_message(chat_id, message_id)
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Every documented Bale method is implemented: `sendMessage`, `forwardMessage`,
|
|
149
|
+
`copyMessage`, `sendPhoto/Audio/Document/Video/Animation/Voice`, `sendMediaGroup`,
|
|
150
|
+
`sendLocation`, `sendContact`, `sendChatAction`, `getFile`, `answerCallbackQuery`,
|
|
151
|
+
`askReview`, chat administration (`banChatMember`, `promoteChatMember`, `getChat`,
|
|
152
|
+
`getChatAdministrators`, `pinChatMessage`, …), message editing/deletion, stickers,
|
|
153
|
+
and payments (`sendInvoice`, `createInvoiceLink`, `answerPreCheckoutQuery`,
|
|
154
|
+
`inquireTransaction`).
|
|
155
|
+
|
|
156
|
+
### Dispatcher & Router
|
|
157
|
+
|
|
158
|
+
```python
|
|
159
|
+
from baleio import Dispatcher, Router
|
|
160
|
+
from baleio.filters import Command
|
|
161
|
+
|
|
162
|
+
dp = Dispatcher()
|
|
163
|
+
admin = Router(name="admin")
|
|
164
|
+
dp.include_router(admin)
|
|
165
|
+
|
|
166
|
+
@dp.message(Command("start"))
|
|
167
|
+
async def start(message): ...
|
|
168
|
+
|
|
169
|
+
@admin.message(Command("ban"))
|
|
170
|
+
async def ban(message): ...
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Handlers are checked in registration order; the first whose filters all pass runs.
|
|
174
|
+
|
|
175
|
+
### Dependency injection
|
|
176
|
+
|
|
177
|
+
Each handler receives the event as its first argument. Remaining parameters are
|
|
178
|
+
filled **by name** from the propagated context:
|
|
179
|
+
|
|
180
|
+
```python
|
|
181
|
+
@dp.message(Command("me"))
|
|
182
|
+
async def me(message: Message, bot: Bot, state: FSMContext):
|
|
183
|
+
... # bot and state are injected automatically
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Available keys: `bot`, `state`, `raw_state`, `event_update` (the `Update`),
|
|
187
|
+
`event_router`, and anything a filter returned (e.g. `command`).
|
|
188
|
+
|
|
189
|
+
### Filters
|
|
190
|
+
|
|
191
|
+
```python
|
|
192
|
+
from baleio.filters import Command, CommandStart, StateFilter, F
|
|
193
|
+
|
|
194
|
+
@dp.message(Command("help")) # /help
|
|
195
|
+
@dp.message(CommandStart()) # /start
|
|
196
|
+
@dp.message(F.text == "hello") # magic filter
|
|
197
|
+
@dp.message(F.text.startswith("/"))
|
|
198
|
+
@dp.message(F.from_user.id == 12345)
|
|
199
|
+
@dp.message(lambda m: m.text and len(m.text) > 100) # plain callable
|
|
200
|
+
@dp.message(Command("go") & (F.from_user.id == 42)) # combinators
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
`Command` also injects a `CommandObject`:
|
|
204
|
+
|
|
205
|
+
```python
|
|
206
|
+
@dp.message(Command("say"))
|
|
207
|
+
async def say(message: Message, command: CommandObject):
|
|
208
|
+
await message.answer(command.args or "Say what?")
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### CallbackData factory
|
|
212
|
+
|
|
213
|
+
Type-safe, structured callback payloads — exactly like aiogram:
|
|
214
|
+
|
|
215
|
+
```python
|
|
216
|
+
from baleio import F
|
|
217
|
+
from baleio.filters import CallbackData
|
|
218
|
+
from baleio.types import CallbackQuery
|
|
219
|
+
from baleio.utils import InlineKeyboardBuilder
|
|
220
|
+
|
|
221
|
+
class Vote(CallbackData, prefix="vote"): # optional: prefix="v", sep="|"
|
|
222
|
+
action: str
|
|
223
|
+
post_id: int
|
|
224
|
+
|
|
225
|
+
kb = (
|
|
226
|
+
InlineKeyboardBuilder()
|
|
227
|
+
.button("👍", callback_data=Vote(action="up", post_id=7).pack()) # -> "vote:up:7"
|
|
228
|
+
.button("👎", callback_data=Vote(action="down", post_id=7).pack())
|
|
229
|
+
.as_markup()
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
@dp.callback_query(Vote.filter(F.action == "up"))
|
|
233
|
+
async def upvote(query: CallbackQuery, callback_data: Vote):
|
|
234
|
+
await query.answer(f"Post {callback_data.post_id} liked")
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
Fields are coerced back to their declared types on `unpack` (`post_id` is an `int`),
|
|
238
|
+
and Bale's 64-byte `callback_data` limit is enforced automatically.
|
|
239
|
+
|
|
240
|
+
### Finite State Machine
|
|
241
|
+
|
|
242
|
+
```python
|
|
243
|
+
from baleio.fsm import State, StatesGroup, FSMContext
|
|
244
|
+
|
|
245
|
+
class Form(StatesGroup):
|
|
246
|
+
name = State()
|
|
247
|
+
age = State()
|
|
248
|
+
|
|
249
|
+
@dp.message(Command("start"))
|
|
250
|
+
async def start(message: Message, state: FSMContext):
|
|
251
|
+
await state.set_state(Form.name)
|
|
252
|
+
await message.answer("What's your name?")
|
|
253
|
+
|
|
254
|
+
@dp.message(Form.name) # a State used directly as a filter
|
|
255
|
+
async def got_name(message: Message, state: FSMContext):
|
|
256
|
+
await state.update_data(name=message.text)
|
|
257
|
+
await state.set_state(Form.age)
|
|
258
|
+
await message.answer("How old are you?")
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### Keyboards
|
|
262
|
+
|
|
263
|
+
```python
|
|
264
|
+
from baleio.utils import InlineKeyboardBuilder
|
|
265
|
+
|
|
266
|
+
kb = (
|
|
267
|
+
InlineKeyboardBuilder()
|
|
268
|
+
.button("Yes", callback_data="yes")
|
|
269
|
+
.button("No", callback_data="no")
|
|
270
|
+
.adjust(2)
|
|
271
|
+
.as_markup()
|
|
272
|
+
)
|
|
273
|
+
await message.answer("Agree?", reply_markup=kb)
|
|
274
|
+
|
|
275
|
+
@dp.callback_query(F.data == "yes")
|
|
276
|
+
async def yes(cb):
|
|
277
|
+
await cb.answer("Great!", show_alert=True)
|
|
278
|
+
await cb.message.edit_text("You chose: Yes")
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
> ⚠️ You must call `answerCallbackQuery` (or `cb.answer()`) to release the button.
|
|
282
|
+
|
|
283
|
+
### Centralized error handling
|
|
284
|
+
|
|
285
|
+
If a handler raises, an `ErrorEvent` is routed to `@dp.errors()` handlers:
|
|
286
|
+
|
|
287
|
+
```python
|
|
288
|
+
from baleio.filters import ExceptionTypeFilter
|
|
289
|
+
from baleio.types import ErrorEvent
|
|
290
|
+
|
|
291
|
+
@dp.errors(ExceptionTypeFilter(ValueError))
|
|
292
|
+
async def on_value_error(event: ErrorEvent):
|
|
293
|
+
await event.update.message.answer("Something went wrong 😔")
|
|
294
|
+
|
|
295
|
+
@dp.errors() # any other unhandled error
|
|
296
|
+
async def on_any_error(event: ErrorEvent):
|
|
297
|
+
logging.exception("Unhandled", exc_info=event.exception)
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
If no error handler matches, the exception is re-raised (logged in polling, so the bot
|
|
301
|
+
stays alive; handled by you in webhook mode).
|
|
302
|
+
|
|
303
|
+
### Shortcut methods
|
|
304
|
+
|
|
305
|
+
Every received object is bound to its `Bot`, so you never pass `bot` around:
|
|
306
|
+
|
|
307
|
+
```python
|
|
308
|
+
await message.answer("...") # sendMessage to the same chat
|
|
309
|
+
await message.reply("...") # reply (reply_to_message_id)
|
|
310
|
+
await message.answer_photo(photo)
|
|
311
|
+
await message.send_copy(chat_id) # content-aware copy
|
|
312
|
+
await message.edit_text("...")
|
|
313
|
+
await message.delete()
|
|
314
|
+
await callback.answer("...")
|
|
315
|
+
await callback.message.edit_text("...")
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
## Receiving updates
|
|
319
|
+
|
|
320
|
+
### Long polling
|
|
321
|
+
|
|
322
|
+
```python
|
|
323
|
+
await dp.start_polling(bot) # async
|
|
324
|
+
dp.run_polling(bot) # blocking (wraps asyncio.run)
|
|
325
|
+
await dp.start_polling(bot, drop_pending_updates=True)
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
### Webhook
|
|
329
|
+
|
|
330
|
+
```python
|
|
331
|
+
from aiohttp import web
|
|
332
|
+
from baleio.types import Update
|
|
333
|
+
|
|
334
|
+
async def handler(request):
|
|
335
|
+
await dp.feed_update(bot, Update.model_validate(await request.json()))
|
|
336
|
+
return web.Response()
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
See [`examples/webhook_bot.py`](examples/webhook_bot.py).
|
|
340
|
+
|
|
341
|
+
## Examples
|
|
342
|
+
|
|
343
|
+
- [`examples/quickstart.py`](examples/quickstart.py) — the aiogram quickstart, ported.
|
|
344
|
+
- [`examples/echo_bot.py`](examples/echo_bot.py) — echo + inline keyboard + callbacks.
|
|
345
|
+
- [`examples/fsm_form_bot.py`](examples/fsm_form_bot.py) — a registration form with FSM.
|
|
346
|
+
- [`examples/webhook_bot.py`](examples/webhook_bot.py) — receiving updates via webhook.
|
|
347
|
+
|
|
348
|
+
```bash
|
|
349
|
+
export BOT_TOKEN=123456:xxxxxxxx
|
|
350
|
+
python examples/echo_bot.py
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
## Development
|
|
354
|
+
|
|
355
|
+
```bash
|
|
356
|
+
python -m venv .venv && source .venv/bin/activate
|
|
357
|
+
pip install -e ".[dev]"
|
|
358
|
+
pytest
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
## aiogram → baleio cheat sheet
|
|
362
|
+
|
|
363
|
+
| aiogram | baleio |
|
|
364
|
+
|---|---|
|
|
365
|
+
| `from aiogram import Bot, Dispatcher, F` | `from baleio import Bot, Dispatcher, F` |
|
|
366
|
+
| `aiogram.types.Message` | `baleio.types.Message` |
|
|
367
|
+
| `aiogram.filters.Command` | `baleio.filters.Command` |
|
|
368
|
+
| `aiogram.filters.callback_data.CallbackData` | `baleio.filters.CallbackData` |
|
|
369
|
+
| `aiogram.fsm.state.StatesGroup` | `baleio.fsm.StatesGroup` |
|
|
370
|
+
| `@dp.errors()` + `ErrorEvent` | `@dp.errors()` + `baleio.types.ErrorEvent` |
|
|
371
|
+
| `dp.start_polling(bot)` | `dp.start_polling(bot)` |
|
|
372
|
+
| `InlineKeyboardBuilder` | `baleio.utils.InlineKeyboardBuilder` |
|
|
373
|
+
| `aiogram.html.bold` | `baleio.md.bold` (Bale is Markdown-only) |
|
|
374
|
+
|
|
375
|
+
**Key differences from Telegram/aiogram:** Bale has no `parse_mode` parameter (always
|
|
376
|
+
Markdown), its updates are limited to `message`, `edited_message`, `callback_query`
|
|
377
|
+
and `pre_checkout_query`, and payments are wallet-only.
|
|
378
|
+
|
|
379
|
+
## Contributing
|
|
380
|
+
|
|
381
|
+
Contributions are welcome! Please read [CONTRIBUTING.md](CONTRIBUTING.md) and open an
|
|
382
|
+
issue or pull request.
|
|
383
|
+
|
|
384
|
+
## License
|
|
385
|
+
|
|
386
|
+
[MIT](LICENSE) © baleio contributors.
|
|
387
|
+
|
|
388
|
+
> **Disclaimer:** `baleio` is an independent, community project and is not affiliated
|
|
389
|
+
> with or endorsed by Bale Messenger or the aiogram project.
|