zafather 0.4.1__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.
- zafather/__init__.py +145 -0
- zafather/__main__.py +76 -0
- zafather/app.py +284 -0
- zafather/bot.py +231 -0
- zafather/enums.py +165 -0
- zafather/filters.py +292 -0
- zafather/fsm.py +193 -0
- zafather/keyboards.py +364 -0
- zafather/magic.py +146 -0
- zafather/managed.py +198 -0
- zafather/py.typed +0 -0
- zafather/rich.py +344 -0
- zafather/router.py +248 -0
- zafather/text.py +223 -0
- zafather/types.py +415 -0
- zafather/webapp.py +410 -0
- zafather/webserver.py +225 -0
- zafather-0.4.1.dist-info/METADATA +591 -0
- zafather-0.4.1.dist-info/RECORD +23 -0
- zafather-0.4.1.dist-info/WHEEL +5 -0
- zafather-0.4.1.dist-info/entry_points.txt +2 -0
- zafather-0.4.1.dist-info/licenses/LICENSE +21 -0
- zafather-0.4.1.dist-info/top_level.txt +1 -0
zafather/__init__.py
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Zafather — Telegram botlar uchun yengil async framework.
|
|
3
|
+
|
|
4
|
+
Bot API **10.2** (14-iyul, 2026) imkoniyatlarini qo'llab-quvvatlaydi:
|
|
5
|
+
rangli tugmalar, premium emoji, bot yaratadigan botlar, ephemeral xabarlar,
|
|
6
|
+
guest mode, reaksiyalar, obunalar va boshqalar.
|
|
7
|
+
|
|
8
|
+
from zafather import Zafather, Message, InlineKeyboard, emoji, F
|
|
9
|
+
|
|
10
|
+
bot = Zafather("TOKEN")
|
|
11
|
+
|
|
12
|
+
@bot.command("start")
|
|
13
|
+
async def start(m: Message):
|
|
14
|
+
kb = InlineKeyboard().success("✅ Ha", "yes").danger("❌ Yo'q", "no")
|
|
15
|
+
await m.answer(f"{emoji('5368324170671202286', '🔥')} Salom!", reply_markup=kb)
|
|
16
|
+
|
|
17
|
+
bot.run()
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from .app import Zafather
|
|
21
|
+
from .bot import Bot, InputFile, NetworkError, TelegramError
|
|
22
|
+
from .enums import (
|
|
23
|
+
BOT_API_VERSION,
|
|
24
|
+
ButtonStyle,
|
|
25
|
+
ChatAction,
|
|
26
|
+
ChatTypeEnum,
|
|
27
|
+
ContentType as ContentTypes,
|
|
28
|
+
Currency,
|
|
29
|
+
DiceEmoji,
|
|
30
|
+
ParseMode,
|
|
31
|
+
PollType,
|
|
32
|
+
UpdateType,
|
|
33
|
+
)
|
|
34
|
+
from .filters import (
|
|
35
|
+
ChatType,
|
|
36
|
+
Command,
|
|
37
|
+
ContentType,
|
|
38
|
+
Ephemeral,
|
|
39
|
+
Filter,
|
|
40
|
+
HasCustomEmoji,
|
|
41
|
+
IsGroup,
|
|
42
|
+
IsPrivate,
|
|
43
|
+
Premium,
|
|
44
|
+
Regex,
|
|
45
|
+
Service,
|
|
46
|
+
StateFilter,
|
|
47
|
+
Text,
|
|
48
|
+
UserFilter,
|
|
49
|
+
)
|
|
50
|
+
from .fsm import BaseStorage, FSMContext, JSONStorage, MemoryStorage, State, StatesGroup
|
|
51
|
+
from .keyboards import (
|
|
52
|
+
ForceReply,
|
|
53
|
+
InlineKeyboard,
|
|
54
|
+
RemoveKeyboard,
|
|
55
|
+
ReplyKeyboard,
|
|
56
|
+
confirm_keyboard,
|
|
57
|
+
)
|
|
58
|
+
from .magic import F
|
|
59
|
+
from .managed import BotFarm, ManagedBots
|
|
60
|
+
from .rich import RichMessage, RichStream, markdown_rich
|
|
61
|
+
from .router import Router, SkipHandler
|
|
62
|
+
from .text import (
|
|
63
|
+
SafeHTML,
|
|
64
|
+
TextBuilder,
|
|
65
|
+
bold,
|
|
66
|
+
code,
|
|
67
|
+
emoji,
|
|
68
|
+
escape,
|
|
69
|
+
italic,
|
|
70
|
+
link,
|
|
71
|
+
mention,
|
|
72
|
+
pre,
|
|
73
|
+
quote,
|
|
74
|
+
spoiler,
|
|
75
|
+
strike,
|
|
76
|
+
strip_custom_emoji,
|
|
77
|
+
underline,
|
|
78
|
+
)
|
|
79
|
+
from .webapp import (
|
|
80
|
+
MiniApp,
|
|
81
|
+
WebAppAuthError,
|
|
82
|
+
WebAppData,
|
|
83
|
+
WebAppInitData,
|
|
84
|
+
attach_link,
|
|
85
|
+
direct_link,
|
|
86
|
+
is_valid,
|
|
87
|
+
main_app_link,
|
|
88
|
+
parse_init_data,
|
|
89
|
+
validate,
|
|
90
|
+
validate_third_party,
|
|
91
|
+
)
|
|
92
|
+
from .webserver import MiniAppServer
|
|
93
|
+
from .types import (
|
|
94
|
+
BotSubscriptionUpdated,
|
|
95
|
+
BusinessConnection,
|
|
96
|
+
CallbackQuery,
|
|
97
|
+
Chat,
|
|
98
|
+
ChatBoostUpdated,
|
|
99
|
+
InlineQuery,
|
|
100
|
+
ManagedBotUpdated,
|
|
101
|
+
Message,
|
|
102
|
+
MessageReactionUpdated,
|
|
103
|
+
TelegramObject,
|
|
104
|
+
Update,
|
|
105
|
+
User,
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
__version__ = "0.4.1"
|
|
109
|
+
__bot_api__ = BOT_API_VERSION
|
|
110
|
+
__author__ = "ismoilov299"
|
|
111
|
+
__license__ = "MIT"
|
|
112
|
+
|
|
113
|
+
__all__ = [
|
|
114
|
+
# asosiy
|
|
115
|
+
"Zafather", "Bot", "Router", "SkipHandler", "F",
|
|
116
|
+
# tiplar
|
|
117
|
+
"Message", "CallbackQuery", "InlineQuery", "User", "Chat", "Update",
|
|
118
|
+
"TelegramObject", "ManagedBotUpdated", "BusinessConnection",
|
|
119
|
+
"MessageReactionUpdated", "ChatBoostUpdated", "BotSubscriptionUpdated",
|
|
120
|
+
# filtrlar
|
|
121
|
+
"Filter", "Command", "Text", "Regex", "ChatType", "ContentType", "UserFilter",
|
|
122
|
+
"StateFilter", "Service", "Ephemeral", "Premium", "HasCustomEmoji",
|
|
123
|
+
"IsPrivate", "IsGroup",
|
|
124
|
+
# FSM
|
|
125
|
+
"State", "StatesGroup", "FSMContext", "MemoryStorage", "JSONStorage", "BaseStorage",
|
|
126
|
+
# klaviaturalar
|
|
127
|
+
"InlineKeyboard", "ReplyKeyboard", "RemoveKeyboard", "ForceReply", "confirm_keyboard",
|
|
128
|
+
# matn va premium emoji
|
|
129
|
+
"emoji", "bold", "italic", "underline", "strike", "spoiler", "code", "pre",
|
|
130
|
+
"link", "mention", "quote", "escape", "TextBuilder", "strip_custom_emoji", "SafeHTML",
|
|
131
|
+
# managed bots
|
|
132
|
+
"ManagedBots", "BotFarm",
|
|
133
|
+
# rich messages
|
|
134
|
+
"RichMessage", "RichStream", "markdown_rich",
|
|
135
|
+
# Mini App
|
|
136
|
+
"validate", "validate_third_party", "is_valid", "parse_init_data",
|
|
137
|
+
"WebAppInitData", "WebAppAuthError", "WebAppData", "MiniApp", "MiniAppServer",
|
|
138
|
+
"direct_link", "main_app_link", "attach_link",
|
|
139
|
+
# konstantalar
|
|
140
|
+
"ButtonStyle", "UpdateType", "ContentTypes", "ChatAction", "ParseMode",
|
|
141
|
+
"ChatTypeEnum", "Currency", "DiceEmoji", "PollType", "BOT_API_VERSION",
|
|
142
|
+
# boshqa
|
|
143
|
+
"InputFile", "TelegramError", "NetworkError",
|
|
144
|
+
"__version__", "__bot_api__",
|
|
145
|
+
]
|
zafather/__main__.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""Zafather CLI.
|
|
2
|
+
|
|
3
|
+
python -m zafather new mybot # yangi loyiha yaratadi
|
|
4
|
+
python -m zafather version
|
|
5
|
+
"""
|
|
6
|
+
import os
|
|
7
|
+
import sys
|
|
8
|
+
|
|
9
|
+
from . import __version__
|
|
10
|
+
|
|
11
|
+
TEMPLATE = '''"""{name} — Zafather bilan yozilgan bot."""
|
|
12
|
+
import os
|
|
13
|
+
|
|
14
|
+
from zafather import F, Message, Zafather
|
|
15
|
+
|
|
16
|
+
TOKEN = os.getenv("BOT_TOKEN", "TOKENNI_SHU_YERGA")
|
|
17
|
+
|
|
18
|
+
bot = Zafather(TOKEN, parse_mode="HTML")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@bot.command("start")
|
|
22
|
+
async def start(m: Message):
|
|
23
|
+
await m.answer(f"Salom, <b>{{m.from_user.first_name}}</b>!")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@bot.message(F.text)
|
|
27
|
+
async def echo(m: Message):
|
|
28
|
+
await m.answer(m.text)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
if __name__ == "__main__":
|
|
32
|
+
bot.run()
|
|
33
|
+
'''
|
|
34
|
+
|
|
35
|
+
ENV = "BOT_TOKEN=bu_yerga_tokenni_yozing\n"
|
|
36
|
+
|
|
37
|
+
REQS = "zafather\n"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def create(name: str) -> None:
|
|
41
|
+
if os.path.exists(name):
|
|
42
|
+
print(f"Xato: '{name}' papkasi allaqachon mavjud.")
|
|
43
|
+
sys.exit(1)
|
|
44
|
+
os.makedirs(name)
|
|
45
|
+
with open(os.path.join(name, "bot.py"), "w", encoding="utf-8") as f:
|
|
46
|
+
f.write(TEMPLATE.format(name=name))
|
|
47
|
+
with open(os.path.join(name, ".env"), "w", encoding="utf-8") as f:
|
|
48
|
+
f.write(ENV)
|
|
49
|
+
with open(os.path.join(name, "requirements.txt"), "w", encoding="utf-8") as f:
|
|
50
|
+
f.write(REQS)
|
|
51
|
+
print(f"✅ '{name}' loyihasi yaratildi.\n")
|
|
52
|
+
print(f" cd {name}")
|
|
53
|
+
print(" # .env faylga tokenni yozing")
|
|
54
|
+
print(" python bot.py")
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def main(argv=None) -> None:
|
|
58
|
+
argv = argv if argv is not None else sys.argv[1:]
|
|
59
|
+
if not argv or argv[0] in ("-h", "--help", "help"):
|
|
60
|
+
print(__doc__)
|
|
61
|
+
return
|
|
62
|
+
cmd = argv[0]
|
|
63
|
+
if cmd in ("version", "-v", "--version"):
|
|
64
|
+
print(f"Zafather {__version__}")
|
|
65
|
+
elif cmd == "new":
|
|
66
|
+
if len(argv) < 2:
|
|
67
|
+
print("Foydalanish: python -m zafather new <loyiha_nomi>")
|
|
68
|
+
sys.exit(1)
|
|
69
|
+
create(argv[1])
|
|
70
|
+
else:
|
|
71
|
+
print(f"Noma'lum buyruq: {cmd}")
|
|
72
|
+
sys.exit(1)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
if __name__ == "__main__":
|
|
76
|
+
main()
|
zafather/app.py
ADDED
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
"""Zafather — asosiy ilova sinfi (dispatcher + polling)."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import asyncio
|
|
5
|
+
import logging
|
|
6
|
+
import signal
|
|
7
|
+
from typing import Any, Callable, List, Optional, Set
|
|
8
|
+
|
|
9
|
+
from .bot import Bot, NetworkError, TelegramError
|
|
10
|
+
from .enums import UpdateType
|
|
11
|
+
from .fsm import BaseStorage, FSMContext, MemoryStorage
|
|
12
|
+
from .router import Router
|
|
13
|
+
from .types import Update
|
|
14
|
+
|
|
15
|
+
log = logging.getLogger("zafather")
|
|
16
|
+
|
|
17
|
+
BANNER = r"""
|
|
18
|
+
______ __ _ _
|
|
19
|
+
|__ / __ _ / _| __ _ | |_| |__ ___ _ __
|
|
20
|
+
/ / / _` || |_ / _` || __| '_ \ / _ \ '__|
|
|
21
|
+
/ /_| (_| || _| (_| || |_| | | | __/ |
|
|
22
|
+
/____|\__,_||_| \__,_| \__|_| |_|\___|_|
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Zafather(Router):
|
|
27
|
+
"""Botni yaratish va ishga tushirish uchun asosiy sinf.
|
|
28
|
+
|
|
29
|
+
bot = Zafather("TOKEN")
|
|
30
|
+
|
|
31
|
+
@bot.command("start")
|
|
32
|
+
async def start(m: Message):
|
|
33
|
+
await m.answer("Salom!")
|
|
34
|
+
|
|
35
|
+
bot.run()
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def __init__(
|
|
39
|
+
self,
|
|
40
|
+
token: str,
|
|
41
|
+
parse_mode: Optional[str] = "HTML",
|
|
42
|
+
storage: Optional[BaseStorage] = None,
|
|
43
|
+
api_url: str = "https://api.telegram.org",
|
|
44
|
+
polling_timeout: int = 30,
|
|
45
|
+
concurrent: bool = True,
|
|
46
|
+
name: str = "zafather",
|
|
47
|
+
) -> None:
|
|
48
|
+
super().__init__(name=name)
|
|
49
|
+
self.bot = Bot(token, parse_mode=parse_mode, api_url=api_url, timeout=polling_timeout)
|
|
50
|
+
self.storage: BaseStorage = storage or MemoryStorage()
|
|
51
|
+
self.polling_timeout = polling_timeout
|
|
52
|
+
self.concurrent = concurrent
|
|
53
|
+
self.data: dict = {} # global kontekst (DB, config va h.k.)
|
|
54
|
+
self._offset: Optional[int] = None
|
|
55
|
+
self._running = False
|
|
56
|
+
self._tasks: Set[asyncio.Task] = set()
|
|
57
|
+
self._mini_app = None
|
|
58
|
+
self._startup: List[Callable] = []
|
|
59
|
+
self._shutdown: List[Callable] = []
|
|
60
|
+
|
|
61
|
+
# --- hooks ----------------------------------------------------------------
|
|
62
|
+
def on_startup(self, func: Callable) -> Callable:
|
|
63
|
+
self._startup.append(func)
|
|
64
|
+
return func
|
|
65
|
+
|
|
66
|
+
def on_shutdown(self, func: Callable) -> Callable:
|
|
67
|
+
self._shutdown.append(func)
|
|
68
|
+
return func
|
|
69
|
+
|
|
70
|
+
# --- update'ni qayta ishlash ---------------------------------------------
|
|
71
|
+
def _make_context(self, event, event_type: str) -> dict:
|
|
72
|
+
chat = getattr(event, "chat", None)
|
|
73
|
+
if chat is None:
|
|
74
|
+
message = getattr(event, "message", None)
|
|
75
|
+
chat = getattr(message, "chat", None) if message else None
|
|
76
|
+
user = getattr(event, "from_user", None)
|
|
77
|
+
chat_id = chat.id if chat else (user.id if user else 0)
|
|
78
|
+
user_id = user.id if user else chat_id
|
|
79
|
+
return {"key": (chat_id, user_id), "chat_id": chat_id, "user_id": user_id}
|
|
80
|
+
|
|
81
|
+
async def feed_update(self, update: Update) -> bool:
|
|
82
|
+
"""Bitta update'ni handlerlarga uzatadi."""
|
|
83
|
+
event_type = update.event_type
|
|
84
|
+
event = update.event
|
|
85
|
+
if event_type is None or event is None:
|
|
86
|
+
log.debug("Qo'llab-quvvatlanmaydigan update: %s", update.raw.keys())
|
|
87
|
+
return False
|
|
88
|
+
|
|
89
|
+
ctx = self._make_context(event, event_type)
|
|
90
|
+
state = FSMContext(self.storage, ctx["key"])
|
|
91
|
+
data = {
|
|
92
|
+
**self.data,
|
|
93
|
+
"bot": self.bot,
|
|
94
|
+
"app": self,
|
|
95
|
+
"update": update,
|
|
96
|
+
"event_type": event_type,
|
|
97
|
+
"state": state,
|
|
98
|
+
"raw_state": await state.get_state(),
|
|
99
|
+
"chat_id": ctx["chat_id"],
|
|
100
|
+
"user_id": ctx["user_id"],
|
|
101
|
+
}
|
|
102
|
+
try:
|
|
103
|
+
return await self.trigger(event_type, event, data)
|
|
104
|
+
except asyncio.CancelledError:
|
|
105
|
+
raise
|
|
106
|
+
except Exception as exc: # noqa: BLE001
|
|
107
|
+
handled = await self.handle_error(event, exc, data)
|
|
108
|
+
if not handled:
|
|
109
|
+
log.exception("Handler xatosi (%s): %s", event_type, exc)
|
|
110
|
+
return False
|
|
111
|
+
|
|
112
|
+
async def _spawn(self, update: Update) -> None:
|
|
113
|
+
task = asyncio.create_task(self.feed_update(update))
|
|
114
|
+
self._tasks.add(task)
|
|
115
|
+
task.add_done_callback(self._tasks.discard)
|
|
116
|
+
|
|
117
|
+
# --- polling --------------------------------------------------------------
|
|
118
|
+
async def start_polling(
|
|
119
|
+
self,
|
|
120
|
+
skip_updates: bool = True,
|
|
121
|
+
allowed_updates: Optional[List[str]] = None,
|
|
122
|
+
banner: bool = False,
|
|
123
|
+
) -> None:
|
|
124
|
+
"""Long polling. `allowed_updates=None` — barcha update turlari so'raladi.
|
|
125
|
+
|
|
126
|
+
Bu muhim: `managed_bot`, `guest_message`, `message_reaction`,
|
|
127
|
+
`subscription` kabi yangi turlar aniq so'ralmasa Telegram ularni
|
|
128
|
+
yubormaydi.
|
|
129
|
+
"""
|
|
130
|
+
self._running = True
|
|
131
|
+
if allowed_updates is None:
|
|
132
|
+
allowed_updates = list(UpdateType.ALL)
|
|
133
|
+
me = await self.bot.me()
|
|
134
|
+
log.info("Ishga tushdi: @%s (id=%s)", me.username, me.id)
|
|
135
|
+
|
|
136
|
+
for hook in self._startup:
|
|
137
|
+
await self._maybe_await(hook)
|
|
138
|
+
|
|
139
|
+
if skip_updates:
|
|
140
|
+
pending = await self.bot.call("getUpdates", offset=-1, timeout=0)
|
|
141
|
+
if pending:
|
|
142
|
+
self._offset = pending[-1]["update_id"] + 1
|
|
143
|
+
|
|
144
|
+
try:
|
|
145
|
+
while self._running:
|
|
146
|
+
try:
|
|
147
|
+
raw_updates = await self.bot.call(
|
|
148
|
+
"getUpdates",
|
|
149
|
+
offset=self._offset,
|
|
150
|
+
timeout=self.polling_timeout,
|
|
151
|
+
allowed_updates=allowed_updates,
|
|
152
|
+
)
|
|
153
|
+
except asyncio.CancelledError:
|
|
154
|
+
raise
|
|
155
|
+
except (NetworkError, TelegramError) as exc:
|
|
156
|
+
log.warning("getUpdates xatosi: %s — 3s dan keyin qayta urinish", exc)
|
|
157
|
+
await asyncio.sleep(3)
|
|
158
|
+
continue
|
|
159
|
+
|
|
160
|
+
for raw in raw_updates or []:
|
|
161
|
+
self._offset = raw["update_id"] + 1
|
|
162
|
+
update = Update(raw, self.bot)
|
|
163
|
+
if self.concurrent:
|
|
164
|
+
await self._spawn(update)
|
|
165
|
+
else:
|
|
166
|
+
await self.feed_update(update)
|
|
167
|
+
finally:
|
|
168
|
+
await self._cleanup()
|
|
169
|
+
|
|
170
|
+
async def _cleanup(self) -> None:
|
|
171
|
+
self._running = False
|
|
172
|
+
if self._tasks:
|
|
173
|
+
await asyncio.gather(*list(self._tasks), return_exceptions=True)
|
|
174
|
+
for hook in self._shutdown:
|
|
175
|
+
try:
|
|
176
|
+
await self._maybe_await(hook)
|
|
177
|
+
except Exception: # noqa: BLE001
|
|
178
|
+
log.exception("on_shutdown xatosi")
|
|
179
|
+
await self.storage.close()
|
|
180
|
+
await self.bot.close()
|
|
181
|
+
log.info("To'xtatildi.")
|
|
182
|
+
|
|
183
|
+
async def stop(self) -> None:
|
|
184
|
+
self._running = False
|
|
185
|
+
|
|
186
|
+
@staticmethod
|
|
187
|
+
async def _maybe_await(func: Callable) -> Any:
|
|
188
|
+
result = func() if callable(func) else func
|
|
189
|
+
if asyncio.iscoroutine(result) or asyncio.isfuture(result):
|
|
190
|
+
return await result
|
|
191
|
+
return result
|
|
192
|
+
|
|
193
|
+
# --- managed bots ---------------------------------------------------------
|
|
194
|
+
def spawn(self, token: str, **kwargs) -> "Zafather":
|
|
195
|
+
"""Yangi (yaratilgan) bot uchun nusxa qaytaradi — handlerlar meros bo'ladi.
|
|
196
|
+
|
|
197
|
+
token = await ManagedBots(bot.bot).token(bot_id)
|
|
198
|
+
child = bot.spawn(token)
|
|
199
|
+
await child.start_polling()
|
|
200
|
+
"""
|
|
201
|
+
child = Zafather(
|
|
202
|
+
token,
|
|
203
|
+
parse_mode=kwargs.pop("parse_mode", self.bot.parse_mode),
|
|
204
|
+
storage=kwargs.pop("storage", self.storage),
|
|
205
|
+
api_url=kwargs.pop("api_url", self.bot.api_url),
|
|
206
|
+
name=kwargs.pop("name", f"child:{token.split(':')[0]}"),
|
|
207
|
+
**kwargs,
|
|
208
|
+
)
|
|
209
|
+
child.sub_routers.append(self) # ota-bot handlerlaridan foydalanadi
|
|
210
|
+
child.data.update(self.data)
|
|
211
|
+
child.data["parent"] = self
|
|
212
|
+
return child
|
|
213
|
+
|
|
214
|
+
def farm(self, router=None, **kwargs):
|
|
215
|
+
"""Ko'p bolalar botni yuritish uchun `BotFarm` yaratadi."""
|
|
216
|
+
from .managed import BotFarm
|
|
217
|
+
|
|
218
|
+
return BotFarm(router or self, storage=self.storage, **kwargs)
|
|
219
|
+
|
|
220
|
+
# --- Mini App -------------------------------------------------------------
|
|
221
|
+
@property
|
|
222
|
+
def mini_app(self):
|
|
223
|
+
"""Mini App metodlari: menyu tugmasi, answerWebAppQuery va h.k."""
|
|
224
|
+
from .webapp import MiniApp
|
|
225
|
+
|
|
226
|
+
if self._mini_app is None:
|
|
227
|
+
self._mini_app = MiniApp(self.bot)
|
|
228
|
+
return self._mini_app
|
|
229
|
+
|
|
230
|
+
def serve_mini_app(self, static_dir: str = None, **kwargs):
|
|
231
|
+
"""Mini App uchun backend server yaratadi."""
|
|
232
|
+
from .webserver import MiniAppServer
|
|
233
|
+
|
|
234
|
+
return MiniAppServer(self, static_dir=static_dir, **kwargs)
|
|
235
|
+
|
|
236
|
+
def validate_init_data(self, init_data: str, max_age: int = 3600):
|
|
237
|
+
"""Mini App'dan kelgan `initData` ni shu botning tokeni bilan tekshiradi."""
|
|
238
|
+
from .webapp import validate
|
|
239
|
+
|
|
240
|
+
return validate(init_data, self.bot.token, max_age)
|
|
241
|
+
|
|
242
|
+
# --- webhook uchun --------------------------------------------------------
|
|
243
|
+
async def handle_webhook(self, payload: dict) -> bool:
|
|
244
|
+
"""Webhook'dan kelgan JSON'ni qayta ishlash (FastAPI/aiohttp bilan)."""
|
|
245
|
+
return await self.feed_update(Update(payload, self.bot))
|
|
246
|
+
|
|
247
|
+
# --- sinxron ishga tushirish ---------------------------------------------
|
|
248
|
+
def run(
|
|
249
|
+
self,
|
|
250
|
+
skip_updates: bool = True,
|
|
251
|
+
allowed_updates: Optional[List[str]] = None,
|
|
252
|
+
log_level: int = logging.INFO,
|
|
253
|
+
banner: bool = True,
|
|
254
|
+
) -> None:
|
|
255
|
+
"""Botni ishga tushiradi (Ctrl+C bilan to'xtatiladi)."""
|
|
256
|
+
logging.basicConfig(
|
|
257
|
+
level=log_level,
|
|
258
|
+
format="%(asctime)s | %(levelname)-7s | %(name)s | %(message)s",
|
|
259
|
+
datefmt="%H:%M:%S",
|
|
260
|
+
)
|
|
261
|
+
if banner:
|
|
262
|
+
print(BANNER)
|
|
263
|
+
|
|
264
|
+
async def main() -> None:
|
|
265
|
+
loop = asyncio.get_running_loop()
|
|
266
|
+
for sig in (signal.SIGINT, signal.SIGTERM):
|
|
267
|
+
try:
|
|
268
|
+
loop.add_signal_handler(sig, lambda: asyncio.create_task(self.stop()))
|
|
269
|
+
except (NotImplementedError, AttributeError):
|
|
270
|
+
pass # Windows
|
|
271
|
+
await self.start_polling(
|
|
272
|
+
skip_updates=skip_updates,
|
|
273
|
+
allowed_updates=allowed_updates,
|
|
274
|
+
banner=False,
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
try:
|
|
278
|
+
asyncio.run(main())
|
|
279
|
+
except KeyboardInterrupt:
|
|
280
|
+
log.info("Ctrl+C — to'xtatilmoqda…")
|
|
281
|
+
|
|
282
|
+
def __repr__(self) -> str:
|
|
283
|
+
total = sum(len(v) for v in self.handlers.values())
|
|
284
|
+
return f"<Zafather handlers={total} routers={len(self.sub_routers)}>"
|