Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
b937e66
feat: add process stats to raito commands
fxnxtic Sep 4, 2025
1bf61f2
feat: add commandline execution to raito commands
fxnxtic Sep 4, 2025
602dc41
feat: add python execution to raito commands
fxnxtic Sep 4, 2025
457107a
Merge pull request #25 from fxnxtic/main
Aidenable Sep 5, 2025
42b0600
refactor: ruff auto-formatter
Aidenable Sep 4, 2025
8022b16
refactor: fix ruff typing errors
Aidenable Sep 4, 2025
58e932c
feat: expand eval locals
Aidenable Sep 4, 2025
39367e6
feat: add inline evaluation
Aidenable Sep 4, 2025
2374134
feat: add async evaluation
Aidenable Sep 4, 2025
2fa919c
feat: add advanced evaluation with stdout capture and auto-return
Aidenable Sep 5, 2025
7ab0086
docs: describe code evaluator
Aidenable Sep 5, 2025
d7383f3
feat: rename `exec` to `bash`, add inline bash execution
Aidenable Sep 5, 2025
4fa9d62
fix: ensure proper module loading via importlib
Aidenable Sep 5, 2025
1fc38cf
refactor: add dataclasses, simplify text builder
Aidenable Sep 5, 2025
2137413
refactor: fix mypy linter errors
Aidenable Sep 5, 2025
d205f15
examples: add webhook bot
Aidenable Sep 5, 2025
7074e07
refactor: simplify imports, remove unnecessary code
Aidenable Sep 5, 2025
d8b0f07
examples: change prefix
Aidenable Sep 5, 2025
18ad0cb
docs: rewrite keyboard factory examples
Aidenable Sep 13, 2025
b705e31
docs: expand example of manual pagination keyboard
Aidenable Sep 13, 2025
e9f6d6c
docs: add example for role-manager api
Aidenable Sep 13, 2025
d903600
docs: add list of available roles
Aidenable Sep 13, 2025
32c729e
docs: add hint about developer role
Aidenable Sep 13, 2025
5069d4d
feat: add list pagination
Aidenable Sep 13, 2025
62e8650
feat: add truncate helper
Aidenable Sep 13, 2025
798fd58
fix: change RaitoCommand flag to `raito__command`
Aidenable Sep 13, 2025
a2f72d1
fix: apply `pagination.answer` parameters to the edit method
Aidenable Sep 14, 2025
b594cd8
feat: add `.rt help` command
Aidenable Sep 14, 2025
c2e90b0
feat: add footer
Aidenable Sep 14, 2025
d97df3a
docs: add truncate helper
Aidenable Sep 14, 2025
be0efc6
docs: add tip about raito commands to the quick start
Aidenable Sep 14, 2025
5636d1c
chore: bump version
Aidenable Sep 22, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added docs/source/_static/help-command.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
94 changes: 72 additions & 22 deletions docs/source/plugins/keyboards.rst
Original file line number Diff line number Diff line change
Expand Up @@ -77,53 +77,103 @@ Both decorators accept optional arguments:
Examples
~~~~~~~~~~~~

Static layout:
Static inline keyboard:
^^^^^^^^^^^^^^^^

.. code-block:: python

from raito import rt

@rt.keyboard.static(inline=True)
def faq_buttons():
@rt.keyboard.static()
def info_markup():
return [
("Terms of Service", "tos"),
("Privacy", "privacy"),
[("💬 Support", "support")],
[("🔒 Privacy", "privacy"), ("📄 TOS", "terms_of_use")]
]

Dynamic layout:
@router.message(...)
async def handler(message: Message):
await message.answer("Buttons:", reply_markup=info_markup())

Static reply keyboard:
^^^^^^^^^^^^^^^^

.. code-block:: python

from raito import rt

@rt.keyboard.static(inline=False)
def info_markup():
return [
["💬 Support"],
[["🔒 Privacy"], ["📄 TOS"]]
]

@router.message(...)
async def handler(message: Message):
await message.answer("Buttons:", reply_markup=info_markup())

Dynamic inline keyboard:
^^^^^^^^^^^^^^^^

.. code-block:: python

from aiogram.utils.keyboard import InlineKeyboardBuilder
from raito import rt

from ... import Player
@rt.keyboard.dynamic(1, 2)
def info_markup(builder: InlineKeyboardBuilder, privacy_url: str, tos_url: str):
builder.button(text="💬 Support", callback_data="support")
builder.button(text="🔒 Privacy", url=privacy_url)
builder.button(text="📄 TOS", url=tos_url)

@router.message(...)
async def handler(message: Message):
await message.answer("Buttons:", reply_markup=info_markup(
privacy_url="https://example.com/privacy",
tos_url="https://example.com/tos",
))

@rt.keyboard.dynamic()
def leaderboard(builder: InlineKeyboardBuilder, players: list[Player]):
for player in players:
builder.button(text=f"{player.name}", callback_data=f"player:{player.id}")
Dynamic reply keyboard:
^^^^^^^^^^^^^^^^

.. code-block:: python

builder.adjust(1, 2, 2)
from aiogram.utils.keyboard import ReplyKeyboardBuilder
from raito import rt

Dynamic pagination-like example:
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@rt.keyboard.dynamic(1, 2, inline=False)
def info_markup(builder: ReplyKeyboardBuilder):
builder.button(text="💬 Support")
builder.button(text="🔒 Privacy")
builder.button(text="📄 TOS")

@router.message(...)
async def handler(message: Message):
await message.answer("Buttons:", reply_markup=info_markup())

Custom adjust:
^^^^^^^^^^^^^^^^

.. code-block:: python

from aiogram.utils.keyboard import InlineKeyboardBuilder
from raito import rt

from ... import Player

@rt.keyboard.dynamic(adjust=False)
def leaderboard(builder: InlineKeyboardBuilder, players: list[Player]):
for i, player in enumerate(players, start=1):
builder.button(text=f"#{i} {player.name}", callback_data=f"player:{player.id}")
def admin_markup(builder: InlineKeyboardBuilder, show_balance_management: bool = False):
adjust = []

builder.button(text="👤 Users", callback_data="users")
adjust.append(1)

if show_balance_management:
builder.button(text="📤 Withdraw", callback_data="withdraw")
builder.button(text="📥 Deposit", callback_data="deposit")
adjust.append(2)

builder.button(text="◀️", callback_data="prev")
builder.button(text="▶️", callback_data="next")
builder.adjust(*adjust)

builder.adjust(3)
@router.message(...)
async def handler(message: Message):
await message.answer("Buttons:", reply_markup=admin_markup(True))
3 changes: 3 additions & 0 deletions docs/source/plugins/pagination.rst
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,11 @@ To manually attach navigation:

.. code-block:: python

builder = InlineKeyboardBuilder()
navigation = paginator.build_navigation()

builder.attach(builder.from_markup(navigation))
builder.button(text="Back", callback_data="menu")

---------

Expand Down
75 changes: 74 additions & 1 deletion docs/source/plugins/roles.rst
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ Use Telegram commands to manage roles:

To manage roles, you must already have one of the following:

- `developer`
- `developer` (defined in ``Raito(developers=[...]``)
- `owner`
- `administrator`

Expand Down Expand Up @@ -113,8 +113,81 @@ Access control is enforced on ``assign`` / ``revoke``:
- Only trusted roles can assign
- You can't assign or revoke your own role

.. code-block:: python

from aiogram import Router, filters, types

from raito import Raito, rt
from raito.plugins.roles import ADMINISTRATOR, DEVELOPER, OWNER

router = Router(name="tester")


@router.message(filters.Command("give_tester"), DEVELOPER | OWNER | ADMINISTRATOR)
@rt.params(user_id=int)
async def give_tester(message: types.Message, raito: Raito, user_id: int) -> None:
if not message.from_user or not message.bot:
return

await raito.role_manager.assign_role(message.bot.id, message.from_user.id, user_id, "tester")
await message.answer(
text=f"User with ID <code>{user_id}</code> is now a tester!",
parse_mode="HTML",
)


@router.message(filters.Command("testers"), DEVELOPER | OWNER | ADMINISTRATOR)
async def testers(message: types.Message, raito: Raito) -> None:
if not message.from_user or not message.bot:
return

testers = await raito.role_manager.get_users(message.bot.id, "tester")
user_ids = [str(user_id) for user_id in testers]
await message.answer(text="🧪 Testers: " + ", ".join(user_ids))


-----

Available Roles
~~~~~~~~~~~~

.. list-table::
:header-rows: 1
:widths: 20 10 70

* - Role
- Slug
- Description
* - 🖥️ Developer
- developer
- Has full access to all internal features, including debug tools and unsafe operations.
* - 👑 Owner
- owner
- Top-level administrator with permissions to manage administrators and global settings.
* - 💼 Administrator
- administrator
- Can manage users, moderate content, and configure most system settings.
* - 🛡️ Moderator
- moderator
- Can moderate user activity, issue warnings, and enforce rules within their scope.
* - 📊 Manager
- manager
- Oversees non-technical operations like campaigns, tasks, or content planning.
* - ❤️ Sponsor
- sponsor
- Supporter of the project. Usually does not have administrative privileges.
* - 👤 Guest
- guest
- Has temporary access to specific internal features (e.g., analytics). Typically invited users.
* - 💬 Support
- support
- Handles user support requests and assists with onboarding or issues.
* - 🧪 Tester
- tester
- Helps test new features and provide feedback. May have access to experimental tools.

------

Custom Roles
~~~~~~~~~~~~

Expand Down
8 changes: 8 additions & 0 deletions docs/source/quick_start.rst
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,11 @@ What's happening here?
await message.answer("Pong! 🏓")
📦 Not installed yet? See :doc:`installation`

-----

Also, Raito has a built-in commands. Send ``.rt help`` to your bot to see the list.

.. image:: /_static/help-command.png
:alt: Raito Help Command Example
:align: left
1 change: 1 addition & 0 deletions docs/source/utils/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ Utils
suppress_not_modified
logging
retry
truncate
10 changes: 10 additions & 0 deletions docs/source/utils/truncate.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
✂️ truncate
=============================

Truncates a string to a given length.

.. code-block:: python

from raito.utils.helpers.truncate import truncate

truncate("Hello, world!", 8) # Returns "Hello..."
6 changes: 1 addition & 5 deletions examples/02-lifespan/handlers/lifespan.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,7 @@ async def lifespan(raito: Raito, bot: Bot, dispatcher: Dispatcher) -> AsyncGener

yield

rt.debug("Removing webhook...")
await bot.delete_webhook()

rt.log.debug("Closing dispatcher storages...")
rt.log.debug("Closing dispatcher storage...")
await dispatcher.storage.close()
await dispatcher.fsm.storage.close()

rt.log.info("👋 Bye!")
6 changes: 1 addition & 5 deletions examples/03-commands/handlers/events/lifespan.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,7 @@ async def lifespan(raito: Raito, bot: Bot, dispatcher: Dispatcher) -> AsyncGener

yield

rt.debug("Removing webhook...")
await bot.delete_webhook()

rt.log.debug("Closing dispatcher storages...")
rt.log.debug("Closing dispatcher storage...")
await dispatcher.storage.close()
await dispatcher.fsm.storage.close()

rt.log.info("👋 Bye!")
2 changes: 1 addition & 1 deletion examples/05-roles/handlers/dev/eval.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from aiogram.types import Message

from raito import rt
from raito.plugins.roles.roles import DEVELOPER
from raito.plugins.roles import DEVELOPER

router = Router(name="eval")

Expand Down
6 changes: 1 addition & 5 deletions examples/05-roles/handlers/events/lifespan.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,7 @@ async def lifespan(raito: Raito, bot: Bot, dispatcher: Dispatcher) -> AsyncGener

yield

rt.debug("Removing webhook...")
await bot.delete_webhook()

rt.log.debug("Closing dispatcher storages...")
rt.log.debug("Closing dispatcher storage...")
await dispatcher.storage.close()
await dispatcher.fsm.storage.close()

rt.log.info("👋 Bye!")
2 changes: 1 addition & 1 deletion examples/05-roles/handlers/moderation/ban.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from aiogram.types import Message

from raito import rt
from raito.plugins.roles.roles import ADMINISTRATOR, DEVELOPER, MODERATOR, OWNER
from raito.plugins.roles import ADMINISTRATOR, DEVELOPER, MODERATOR, OWNER

router = Router(name="ban")

Expand Down
2 changes: 1 addition & 1 deletion examples/05-roles/handlers/moderation/unban.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from aiogram.types import Message

from raito import rt
from raito.plugins.roles.roles import ADMINISTRATOR, DEVELOPER, MODERATOR, OWNER
from raito.plugins.roles import ADMINISTRATOR, DEVELOPER, MODERATOR, OWNER

router = Router(name="unban")

Expand Down
6 changes: 1 addition & 5 deletions examples/06-keyboards/handlers/events/lifespan.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,7 @@ async def lifespan(raito: Raito, bot: Bot, dispatcher: Dispatcher) -> AsyncGener

yield

rt.debug("Removing webhook...")
await bot.delete_webhook()

rt.log.debug("Closing dispatcher storages...")
rt.log.debug("Closing dispatcher storage...")
await dispatcher.storage.close()
await dispatcher.fsm.storage.close()

rt.log.info("👋 Bye!")
2 changes: 1 addition & 1 deletion examples/07-storages/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from aiogram import Bot, Dispatcher
from aiogram.client.default import DefaultBotProperties

from raito.core.raito import Raito
from raito import Raito
from raito.utils.storages.json import JSONStorage
from raito.utils.storages.sql import get_sqlite_storage

Expand Down
2 changes: 1 addition & 1 deletion examples/08-custom-roles/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from aiogram.client.default import DefaultBotProperties
from roles.manager import CustomRoleManager

from raito.core.raito import Raito
from raito import Raito
from raito.plugins.roles.providers.json import JSONRoleProvider
from raito.utils.configuration import RaitoConfiguration
from raito.utils.storages.json import JSONStorage
Expand Down
2 changes: 1 addition & 1 deletion examples/09-pagination/handlers/add.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from aiogram.fsm.context import FSMContext
from aiogram.types import CallbackQuery, Message

from raito.core.raito import Raito
from raito import Raito
from raito.plugins.pagination import InlinePaginator

router = Router(name="add")
Expand Down
2 changes: 1 addition & 1 deletion examples/10-album/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from aiogram import Bot, Dispatcher
from aiogram.client.default import DefaultBotProperties

from raito.core.raito import Raito
from raito import Raito

TOKEN = "TOKEN"
HANDLERS_DIR = Path(__file__).parent / "handlers"
Expand Down
Loading