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 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)}>"