re-aiogram 0.1.0__py3-none-any.whl
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.
- re_aiogram/__init__.py +29 -0
- re_aiogram/client/__init__.py +1 -0
- re_aiogram/core/__init__.py +2 -0
- re_aiogram/core/bot.py +97 -0
- re_aiogram/core/mediagroup.py +183 -0
- re_aiogram/dispatcher/__init__.py +1 -0
- re_aiogram/enums/__init__.py +1 -0
- re_aiogram/exceptions/__init__.py +1 -0
- re_aiogram/filters/__init__.py +4 -0
- re_aiogram/fsm/__init__.py +1 -0
- re_aiogram/handlers/__init__.py +1 -0
- re_aiogram/loggers/__init__.py +1 -0
- re_aiogram/methods/__init__.py +1 -0
- re_aiogram/types/__init__.py +2 -0
- re_aiogram/utils/__init__.py +1 -0
- re_aiogram/webhook/__init__.py +1 -0
- re_aiogram-0.1.0.dist-info/METADATA +186 -0
- re_aiogram-0.1.0.dist-info/RECORD +21 -0
- re_aiogram-0.1.0.dist-info/WHEEL +5 -0
- re_aiogram-0.1.0.dist-info/licenses/LICENSE +21 -0
- re_aiogram-0.1.0.dist-info/top_level.txt +1 -0
re_aiogram/__init__.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# features
|
|
2
|
+
from .core.bot import Bot
|
|
3
|
+
|
|
4
|
+
# aiogram
|
|
5
|
+
# from .client import *
|
|
6
|
+
# from .dispatcher import *
|
|
7
|
+
# from .enums import *
|
|
8
|
+
# from .exceptions import *
|
|
9
|
+
# from .filters import *
|
|
10
|
+
# from .fsm import *
|
|
11
|
+
# from .handlers import *
|
|
12
|
+
# from .loggers import *
|
|
13
|
+
# from .message import *
|
|
14
|
+
# from .methods import *
|
|
15
|
+
# from .types import *
|
|
16
|
+
# from .utils import *
|
|
17
|
+
# from .webhook import *
|
|
18
|
+
|
|
19
|
+
from .types import Message, CallbackQuery
|
|
20
|
+
|
|
21
|
+
from aiogram import Router
|
|
22
|
+
router = Router()
|
|
23
|
+
|
|
24
|
+
__all__=[
|
|
25
|
+
"Bot",
|
|
26
|
+
"Message",
|
|
27
|
+
"CallbackQuery",
|
|
28
|
+
"router"
|
|
29
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from aiogram.client import *
|
re_aiogram/core/bot.py
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
from aiogram import Bot as _Bot, Dispatcher as _Dispatcher
|
|
2
|
+
import asyncio
|
|
3
|
+
import logging
|
|
4
|
+
import signal
|
|
5
|
+
import sys
|
|
6
|
+
import importlib
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Bot:
|
|
10
|
+
def __init__(self, token: str = None):
|
|
11
|
+
if not token:
|
|
12
|
+
raise ValueError("Token is required")
|
|
13
|
+
|
|
14
|
+
self._bot = _Bot(token=token)
|
|
15
|
+
self._dp = _Dispatcher()
|
|
16
|
+
|
|
17
|
+
# middlewares
|
|
18
|
+
from .mediagroup import AlbumMiddleware
|
|
19
|
+
|
|
20
|
+
self._dp.message.middleware(AlbumMiddleware())
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
def message(self):
|
|
24
|
+
return self._dp.message
|
|
25
|
+
|
|
26
|
+
def load(self, *paths: str):
|
|
27
|
+
"""
|
|
28
|
+
Load routers from the specified paths.
|
|
29
|
+
|
|
30
|
+
:param paths:
|
|
31
|
+
"""
|
|
32
|
+
routers = []
|
|
33
|
+
|
|
34
|
+
for path in paths:
|
|
35
|
+
module = importlib.import_module(path)
|
|
36
|
+
|
|
37
|
+
if hasattr(module, "router"):
|
|
38
|
+
routers.append(module.router)
|
|
39
|
+
else:
|
|
40
|
+
raise ValueError(
|
|
41
|
+
f"There is no 'router' variable in the {path} module"
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
for router in routers:
|
|
45
|
+
if router.parent_router is None:
|
|
46
|
+
self._dp.include_router(router)
|
|
47
|
+
|
|
48
|
+
def run(self, logging_enabled: bool = True):
|
|
49
|
+
"""
|
|
50
|
+
Start the Bot with graceful shutdown.
|
|
51
|
+
"""
|
|
52
|
+
if logging_enabled:
|
|
53
|
+
logging.basicConfig(level=logging.INFO)
|
|
54
|
+
|
|
55
|
+
try:
|
|
56
|
+
asyncio.run(self._start())
|
|
57
|
+
except KeyboardInterrupt:
|
|
58
|
+
logging.info("Bot stopped by user")
|
|
59
|
+
except Exception as e:
|
|
60
|
+
logging.error("Polling error: %s", e)
|
|
61
|
+
sys.exit(1)
|
|
62
|
+
|
|
63
|
+
async def _start(self):
|
|
64
|
+
"""Start polling and ensure cleanup on exit."""
|
|
65
|
+
loop = asyncio.get_running_loop()
|
|
66
|
+
|
|
67
|
+
# Register signal handlers for graceful shutdown where supported
|
|
68
|
+
for sig in (signal.SIGINT, signal.SIGTERM):
|
|
69
|
+
try:
|
|
70
|
+
loop.add_signal_handler(sig, self._request_shutdown)
|
|
71
|
+
except (NotImplementedError, ValueError):
|
|
72
|
+
# Windows doesn't support add_signal_handler for all signals
|
|
73
|
+
break
|
|
74
|
+
|
|
75
|
+
try:
|
|
76
|
+
await self._dp.start_polling(self._bot)
|
|
77
|
+
finally:
|
|
78
|
+
await self._shutdown()
|
|
79
|
+
|
|
80
|
+
def _request_shutdown(self):
|
|
81
|
+
"""Signal handler: initiate graceful stop."""
|
|
82
|
+
logging.info("Shutdown signal received, stopping polling...")
|
|
83
|
+
asyncio.create_task(self._dp.stop_polling())
|
|
84
|
+
|
|
85
|
+
async def _shutdown(self):
|
|
86
|
+
"""Close sessions and clean up resources."""
|
|
87
|
+
logging.info("Closing bot session...")
|
|
88
|
+
await self._bot.session.close()
|
|
89
|
+
|
|
90
|
+
loop = asyncio.get_running_loop()
|
|
91
|
+
for sig in (signal.SIGINT, signal.SIGTERM):
|
|
92
|
+
try:
|
|
93
|
+
loop.remove_signal_handler(sig)
|
|
94
|
+
except (NotImplementedError, ValueError):
|
|
95
|
+
pass
|
|
96
|
+
|
|
97
|
+
logging.info("Shutdown complete")
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
from aiogram import BaseMiddleware as _BaseMiddleware
|
|
2
|
+
from aiogram.types import (
|
|
3
|
+
Message,
|
|
4
|
+
InputMediaPhoto,
|
|
5
|
+
InputMediaVideo,
|
|
6
|
+
InputMediaDocument,
|
|
7
|
+
InputMediaAudio,
|
|
8
|
+
InputMedia,
|
|
9
|
+
)
|
|
10
|
+
from typing import Callable, Any, Awaitable, Union, List
|
|
11
|
+
import asyncio
|
|
12
|
+
import time
|
|
13
|
+
import logging
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class MediaGroup(list):
|
|
19
|
+
"""
|
|
20
|
+
Custom list-like class for media groups.
|
|
21
|
+
Inherits from list for full aiogram compatibility (answer_media_group, etc.)
|
|
22
|
+
while providing convenient properties for introspection.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(
|
|
26
|
+
self,
|
|
27
|
+
messages: List[Message],
|
|
28
|
+
media: List[Union[InputMediaPhoto, InputMediaVideo, InputMediaDocument, InputMediaAudio, InputMedia]],
|
|
29
|
+
):
|
|
30
|
+
super().__init__(media)
|
|
31
|
+
self._messages = messages
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def messages(self) -> List[Message]:
|
|
35
|
+
"""Original aiogram Message objects."""
|
|
36
|
+
return self._messages.copy()
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def photos(self) -> List[Message]:
|
|
40
|
+
"""Messages containing photos."""
|
|
41
|
+
return [m for m in self._messages if m.photo]
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def videos(self) -> List[Message]:
|
|
45
|
+
"""Messages containing videos."""
|
|
46
|
+
return [m for m in self._messages if m.video]
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def documents(self) -> List[Message]:
|
|
50
|
+
"""Messages containing documents."""
|
|
51
|
+
return [m for m in self._messages if m.document]
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def audio(self) -> List[Message]:
|
|
55
|
+
"""Messages containing audio files."""
|
|
56
|
+
return [m for m in self._messages if m.audio]
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def caption(self) -> Union[str, None]:
|
|
60
|
+
"""First caption found in the group (Telegram sends caption only on the first item)."""
|
|
61
|
+
for msg in self._messages:
|
|
62
|
+
if msg.caption:
|
|
63
|
+
return msg.caption
|
|
64
|
+
return None
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def captions(self) -> List[str]:
|
|
68
|
+
"""All captions from the group."""
|
|
69
|
+
return [msg.caption for msg in self._messages if msg.caption]
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def is_mixed(self) -> bool:
|
|
73
|
+
"""True if album contains different media types."""
|
|
74
|
+
types = {m.content_type for m in self._messages}
|
|
75
|
+
return len(types) > 1
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def count(self) -> int:
|
|
79
|
+
"""Number of items in the group."""
|
|
80
|
+
return len(self._messages)
|
|
81
|
+
|
|
82
|
+
def __repr__(self) -> str:
|
|
83
|
+
types = [m.content_type for m in self._messages]
|
|
84
|
+
return f"<MediaGroup count={self.count} types={types}>"
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class AlbumMiddleware(_BaseMiddleware):
|
|
88
|
+
"""
|
|
89
|
+
Middleware for media groups with memory leak protection.
|
|
90
|
+
Injects a MediaGroup instance into the handler data.
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
def __init__(
|
|
94
|
+
self,
|
|
95
|
+
latency: Union[int, float] = 0.01,
|
|
96
|
+
max_age: Union[int, float] = 60.0,
|
|
97
|
+
):
|
|
98
|
+
self.latency = latency
|
|
99
|
+
self.max_age = max_age
|
|
100
|
+
self.album_data: dict[str, list[Message]] = {}
|
|
101
|
+
self._last_access: dict[str, float] = {}
|
|
102
|
+
self._lock = asyncio.Lock()
|
|
103
|
+
|
|
104
|
+
async def __call__(
|
|
105
|
+
self,
|
|
106
|
+
handler: Callable[[Message, dict[str, Any]], Awaitable[Any]],
|
|
107
|
+
message: Message,
|
|
108
|
+
data: dict[str, Any],
|
|
109
|
+
) -> Any:
|
|
110
|
+
if not message.media_group_id:
|
|
111
|
+
return await handler(message, data)
|
|
112
|
+
|
|
113
|
+
await self._cleanup_old_albums()
|
|
114
|
+
|
|
115
|
+
async with self._lock:
|
|
116
|
+
if message.media_group_id in self.album_data:
|
|
117
|
+
self.album_data[message.media_group_id].append(message)
|
|
118
|
+
self._last_access[message.media_group_id] = time.time()
|
|
119
|
+
return None
|
|
120
|
+
|
|
121
|
+
self.album_data[message.media_group_id] = [message]
|
|
122
|
+
self._last_access[message.media_group_id] = time.time()
|
|
123
|
+
|
|
124
|
+
await asyncio.sleep(self.latency)
|
|
125
|
+
|
|
126
|
+
async with self._lock:
|
|
127
|
+
album = sorted(
|
|
128
|
+
self.album_data.get(message.media_group_id, []),
|
|
129
|
+
key=lambda m: m.message_id,
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
media_items: list = []
|
|
133
|
+
for msg in album:
|
|
134
|
+
if msg.photo:
|
|
135
|
+
file_id = msg.photo[-1].file_id
|
|
136
|
+
caption = msg.caption if msg.caption else None
|
|
137
|
+
media_items.append(InputMediaPhoto(media=file_id, caption=caption))
|
|
138
|
+
elif msg.video:
|
|
139
|
+
file_id = msg.video.file_id
|
|
140
|
+
caption = msg.caption if msg.caption else None
|
|
141
|
+
media_items.append(InputMediaVideo(media=file_id, caption=caption))
|
|
142
|
+
elif msg.document:
|
|
143
|
+
file_id = msg.document.file_id
|
|
144
|
+
caption = msg.caption if msg.caption else None
|
|
145
|
+
media_items.append(InputMediaDocument(media=file_id, caption=caption))
|
|
146
|
+
elif msg.audio:
|
|
147
|
+
file_id = msg.audio.file_id
|
|
148
|
+
caption = msg.caption if msg.caption else None
|
|
149
|
+
media_items.append(InputMediaAudio(media=file_id, caption=caption))
|
|
150
|
+
else:
|
|
151
|
+
try:
|
|
152
|
+
obj_dict = msg.model_dump()
|
|
153
|
+
file_id = obj_dict[msg.content_type]["file_id"]
|
|
154
|
+
media_items.append(InputMedia(media=file_id))
|
|
155
|
+
except (KeyError, TypeError):
|
|
156
|
+
logger.warning(
|
|
157
|
+
"Unsupported media type in album: %s", msg.content_type
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
data["_is_last"] = True
|
|
161
|
+
data["album"] = album
|
|
162
|
+
data["media_group"] = MediaGroup(album, media_items)
|
|
163
|
+
|
|
164
|
+
try:
|
|
165
|
+
return await handler(message, data)
|
|
166
|
+
finally:
|
|
167
|
+
async with self._lock:
|
|
168
|
+
self.album_data.pop(message.media_group_id, None)
|
|
169
|
+
self._last_access.pop(message.media_group_id, None)
|
|
170
|
+
|
|
171
|
+
async def _cleanup_old_albums(self):
|
|
172
|
+
now = time.time()
|
|
173
|
+
stale_ids = [
|
|
174
|
+
mg_id
|
|
175
|
+
for mg_id, last_time in list(self._last_access.items())
|
|
176
|
+
if now - last_time > self.max_age
|
|
177
|
+
]
|
|
178
|
+
if stale_ids:
|
|
179
|
+
async with self._lock:
|
|
180
|
+
for mg_id in stale_ids:
|
|
181
|
+
self.album_data.pop(mg_id, None)
|
|
182
|
+
self._last_access.pop(mg_id, None)
|
|
183
|
+
logger.debug("Cleaned up %d stale album entries", len(stale_ids))
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from aiogram.dispatcher import *
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from aiogram.enums import *
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from aiogram.exceptions import *
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from aiogram.fsm import *
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from aiogram.handlers import *
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from aiogram.loggers import *
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from aiogram.methods import *
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from aiogram.utils import *
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from aiogram.webhook import *
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: re-aiogram
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Lightweight wrapper over aiogram 3.x
|
|
5
|
+
Author: carrotgoodbye
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: telegram,aiogram,bot,telebot,api,framework,wrapper,asyncio
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Requires-Python: >=3.10
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Requires-Dist: aiogram<4.0.0,>=3.0.0
|
|
14
|
+
Dynamic: license-file
|
|
15
|
+
|
|
16
|
+
# re_aiogram
|
|
17
|
+
|
|
18
|
+
**re_aiogram** is a lightweight wrapper over [aiogram 3.x](https://aiogram.dev/) that keeps the familiar aiogram API while simplifying bot development and adding extra features.
|
|
19
|
+
|
|
20
|
+
The goal of the project is to stay fully compatible with aiogram, but provide a cleaner and more convenient developer experience.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
# Install
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pip install re_aiogram
|
|
28
|
+
````
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
import re_aiogram
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
# Features
|
|
37
|
+
|
|
38
|
+
* familiar aiogram-style API
|
|
39
|
+
* simplified bot startup without explicit dispatcher
|
|
40
|
+
* router auto-loading
|
|
41
|
+
* built-in MediaGroup support
|
|
42
|
+
* aiogram-compatible imports
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
# Quick Start
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
from re_aiogram import Bot, Message
|
|
50
|
+
from re_aiogram.filters import Command
|
|
51
|
+
|
|
52
|
+
API_TOKEN = "YOUR_API_TOKEN"
|
|
53
|
+
|
|
54
|
+
bot = Bot(token=API_TOKEN)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@bot.message(Command("start"))
|
|
58
|
+
async def cmd_start(message: Message):
|
|
59
|
+
await message.answer(
|
|
60
|
+
"Hi! I am a Telegram bot powered by re_aiogram!"
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
bot.run(logging_enabled=True)
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
# MediaGroup Handler
|
|
70
|
+
|
|
71
|
+
Built-in support for Telegram media groups.
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
from re_aiogram import Bot, Message
|
|
75
|
+
from re_aiogram.filters import F
|
|
76
|
+
from re_aiogram.types import MediaGroup
|
|
77
|
+
|
|
78
|
+
bot = Bot(token="TOKEN")
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@bot.message(F.media_group_id)
|
|
82
|
+
async def handle_album(message: Message, media_group: MediaGroup):
|
|
83
|
+
await message.answer_media_group(media_group)
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
`media_group` contains the collected media group messages.
|
|
87
|
+
|
|
88
|
+
## Properties
|
|
89
|
+
|
|
90
|
+
| Property | Description |
|
|
91
|
+
| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
|
|
92
|
+
| `media_group.count` | Returns the number of media items in the group. |
|
|
93
|
+
| `media_group.caption` | Returns the first caption found in the group (Telegram sends caption only on the first item). Returns `None` if no caption is present. |
|
|
94
|
+
| `media_group.captions` | Returns a list of all captions from the group. |
|
|
95
|
+
| `media_group.messages` | Returns a copy of the original `Message` objects from the group. |
|
|
96
|
+
| `media_group.photos` | Returns a list of `Message` objects that contain photos. |
|
|
97
|
+
| `media_group.videos` | Returns a list of `Message` objects that contain videos. |
|
|
98
|
+
| `media_group.documents` | Returns a list of `Message` objects that contain documents. |
|
|
99
|
+
| `media_group.audio` | Returns a list of `Message` objects that contain audio files. |
|
|
100
|
+
| `media_group.is_mixed` | Returns `True` if the album contains different media types (e.g., photos and videos together). |
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
# Routers
|
|
105
|
+
|
|
106
|
+
Router connection is simplified with `bot.load()`
|
|
107
|
+
|
|
108
|
+
## Project structure
|
|
109
|
+
|
|
110
|
+
```text
|
|
111
|
+
project/
|
|
112
|
+
│
|
|
113
|
+
├── main.py
|
|
114
|
+
└── handlers/
|
|
115
|
+
└── start.py
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### main.py
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
from re_aiogram import Bot
|
|
122
|
+
|
|
123
|
+
API_TOKEN = "YOUR_API_TOKEN"
|
|
124
|
+
|
|
125
|
+
bot = Bot(token=API_TOKEN)
|
|
126
|
+
|
|
127
|
+
bot.load("handlers.start")
|
|
128
|
+
|
|
129
|
+
bot.run(logging_enabled=True)
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### handlers/start.py
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
from re_aiogram import router, Message
|
|
136
|
+
from re_aiogram.filters import Command
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
@router.message(Command("start"))
|
|
140
|
+
async def start(message: Message):
|
|
141
|
+
await message.answer("Router connected!")
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
# Future improvements
|
|
147
|
+
|
|
148
|
+
These features are planned for upcoming versions and are not yet part of the core API.
|
|
149
|
+
|
|
150
|
+
## Flow System (FSM alternative)
|
|
151
|
+
|
|
152
|
+
A lightweight state/flow system replacing traditional FSM:
|
|
153
|
+
|
|
154
|
+
* step-based flow control
|
|
155
|
+
* `ctx.next()` and `ctx.back()`
|
|
156
|
+
* shared `ctx.data`
|
|
157
|
+
* parallel flows via `scope()`
|
|
158
|
+
* simple chain-based conversation handling
|
|
159
|
+
|
|
160
|
+
Example concept:
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
@router.message(Command("register"))
|
|
164
|
+
async def register(message: Message, ctx: FlowContext):
|
|
165
|
+
await message.answer("What is your name?")
|
|
166
|
+
ctx.next(get_name)
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
# Philosophy
|
|
172
|
+
|
|
173
|
+
re_aiogram tries to make Telegram bot development:
|
|
174
|
+
|
|
175
|
+
* simpler
|
|
176
|
+
* cleaner
|
|
177
|
+
* less boilerplate-heavy
|
|
178
|
+
* more beginner-friendly
|
|
179
|
+
|
|
180
|
+
while still preserving compatibility with the `aiogram` ecosystem.
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
# License
|
|
185
|
+
|
|
186
|
+
`MIT License`
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
re_aiogram/__init__.py,sha256=vKCRDj0ZMtixJbQPLJsfPfenhpSkEMA9VHYfdR0XsXU,558
|
|
2
|
+
re_aiogram/client/__init__.py,sha256=Z1NYBuXC6jTn1XL9FLFVRB5ZnH3G0CLk0k-1eHUPW2M,28
|
|
3
|
+
re_aiogram/core/__init__.py,sha256=A3Xl9YpeIm5fsxP3CzOoqvZXK9pSJDofF2BXWHjTc3I,56
|
|
4
|
+
re_aiogram/core/bot.py,sha256=Iv01Fw1tVRwivJepEPykFzoAc_8SUNdJELFd5GS7050,2920
|
|
5
|
+
re_aiogram/core/mediagroup.py,sha256=Hyx2pX4tC3dFXfmShPR_vccdj19noefmMkZvlRHBMqA,6447
|
|
6
|
+
re_aiogram/dispatcher/__init__.py,sha256=Q_zgqC_C04pu53QJCVbLaGI0hMgAgU1dRitAVQ_qYJs,32
|
|
7
|
+
re_aiogram/enums/__init__.py,sha256=xfZH0fSJZCsezFzz518yJHYmiXHzCIMfBX5Lc14w-Bk,27
|
|
8
|
+
re_aiogram/exceptions/__init__.py,sha256=rzWgxPe18zVTRrAkV8_oXHxInUKF2QHCbQjULvgzaiM,32
|
|
9
|
+
re_aiogram/filters/__init__.py,sha256=uiYN75D2bG3P4YcirVZA7RhucOkNaT8Vgh0PfMXpves,102
|
|
10
|
+
re_aiogram/fsm/__init__.py,sha256=KdWz1tgVAiz_L3epQsVI83i3cw31qBi2uUhk9HiCmnQ,27
|
|
11
|
+
re_aiogram/handlers/__init__.py,sha256=Hq954qso_xVxlansetEQZVeBtlFsnK4_hSi9-LVj9TI,30
|
|
12
|
+
re_aiogram/loggers/__init__.py,sha256=nmt8fCYDzLz5Oj0Pj4WviWgu0YdK23Mc8NmzM69aM24,29
|
|
13
|
+
re_aiogram/methods/__init__.py,sha256=e8vJWIdkYg5vzgokh6h8CU5es5B4SkkKgw7LAMV0QRo,29
|
|
14
|
+
re_aiogram/types/__init__.py,sha256=1zRW-DVikVDclJsWuve9g_0-uoupYV493oKLnUVQads,69
|
|
15
|
+
re_aiogram/utils/__init__.py,sha256=2PiWpdXKqe6Biy-M96FHxOj5GLXWZpU8mnIQomsszlg,27
|
|
16
|
+
re_aiogram/webhook/__init__.py,sha256=1uX57FdMFDuICRrVsCOwCMiv9gxAu82R9m0OfPKQkfI,29
|
|
17
|
+
re_aiogram-0.1.0.dist-info/licenses/LICENSE,sha256=pA6Bs2tlRvpm1ph7Wi_3BTfLJdPwzwp1io7jViEwf3o,1091
|
|
18
|
+
re_aiogram-0.1.0.dist-info/METADATA,sha256=7inct04jIOqKFdrUj_uf8lciIKcu0ihARTxwuhVtEu0,5230
|
|
19
|
+
re_aiogram-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
20
|
+
re_aiogram-0.1.0.dist-info/top_level.txt,sha256=YLIyi2WD6p8aJOCRNe2XVAKAmo-jRzERo-mY3JDB_II,11
|
|
21
|
+
re_aiogram-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 carrotgoodbye
|
|
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.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
re_aiogram
|