styledbots 1.0.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.
styledbots/__init__.py ADDED
@@ -0,0 +1,38 @@
1
+ """
2
+ StyledBots - Modern, clean & powerful Telegram Bot library
3
+ Better than / equal to Telebot • Perfect for Pydroid 3
4
+ """
5
+
6
+ from .core.bot import Bot
7
+ from .core.exceptions import (
8
+ StyledBotsError,
9
+ TelegramAPIError,
10
+ NetworkError,
11
+ InvalidTokenError,
12
+ )
13
+ from .core.types import Message, User, Chat, CallbackQuery, Update
14
+ from .styles.style import Style
15
+ from .keyboards.inline import InlineKeyboard
16
+ from .keyboards.reply import ReplyKeyboard
17
+ from .keyboards.builder import KeyboardBuilder
18
+ from .filters.filters import filters
19
+ from .version import __version__
20
+
21
+ __all__ = [
22
+ "Bot",
23
+ "Style",
24
+ "InlineKeyboard",
25
+ "ReplyKeyboard",
26
+ "KeyboardBuilder",
27
+ "filters",
28
+ "Message",
29
+ "User",
30
+ "Chat",
31
+ "CallbackQuery",
32
+ "Update",
33
+ "StyledBotsError",
34
+ "TelegramAPIError",
35
+ "NetworkError",
36
+ "InvalidTokenError",
37
+ "__version__",
38
+ ]
@@ -0,0 +1,4 @@
1
+ from .bot import Bot
2
+ from .api import TelegramAPI
3
+ from .exceptions import *
4
+ from .types import *
styledbots/core/api.py ADDED
@@ -0,0 +1,184 @@
1
+ import requests
2
+ from typing import Optional, Dict, Any, List, Union
3
+ from .exceptions import TelegramAPIError, NetworkError, InvalidTokenError
4
+
5
+
6
+ class TelegramAPI:
7
+ """Low-level Telegram Bot API client"""
8
+
9
+ def __init__(self, token: str, timeout: int = 30):
10
+ self.token = token
11
+ self.base_url = f"https://api.telegram.org/bot{token}/"
12
+ self.timeout = timeout
13
+ self.session = requests.Session()
14
+
15
+ def _request(self, method: str, params: dict = None, files: dict = None) -> dict:
16
+ url = self.base_url + method
17
+ try:
18
+ if files:
19
+ response = self.session.post(url, data=params, files=files, timeout=self.timeout)
20
+ else:
21
+ response = self.session.post(url, json=params or {}, timeout=self.timeout)
22
+
23
+ data = response.json()
24
+
25
+ if not data.get("ok"):
26
+ description = data.get("description", "Unknown error")
27
+ error_code = data.get("error_code")
28
+ if error_code == 401:
29
+ raise InvalidTokenError(description)
30
+ raise TelegramAPIError(description, error_code)
31
+
32
+ return data.get("result")
33
+
34
+ except requests.exceptions.RequestException as e:
35
+ raise NetworkError(f"Network error: {e}")
36
+
37
+ # ===== Core methods =====
38
+
39
+ def get_me(self) -> dict:
40
+ return self._request("getMe")
41
+
42
+ def get_updates(self, offset: int = None, timeout: int = 25, limit_types: list = None) -> list:
43
+ params = {"timeout": timeout}
44
+ if offset is not None:
45
+ params["offset"] = offset
46
+ if allowed_updates:
47
+ params["allowed_updates"] = allowed_updates
48
+ return self._request("getUpdates", params) or []
49
+
50
+ def send_message(
51
+ self,
52
+ chat_id: Union[int, str],
53
+ text: str,
54
+ parse_mode: str = "HTML",
55
+ reply_markup: dict = None,
56
+ reply_to_message_id: int = None,
57
+ disable_web_page_preview: bool = False,
58
+ disable_notification: bool = False,
59
+ **kwargs
60
+ ) -> dict:
61
+ params = {
62
+ "chat_id": chat_id,
63
+ "text": text,
64
+ "parse_mode": parse_mode,
65
+ "disable_web_page_preview": disable_web_page_preview,
66
+ "disable_notification": disable_notification,
67
+ **kwargs
68
+ }
69
+ if reply_markup:
70
+ params["reply_markup"] = reply_markup
71
+ if reply_to_message_id:
72
+ params["reply_to_message_id"] = reply_to_message_id
73
+ return self._request("sendMessage", params)
74
+
75
+ def edit_message_text(
76
+ self,
77
+ text: str,
78
+ chat_id: Union[int, str] = None,
79
+ message_id: int = None,
80
+ inline_message_id: str = None,
81
+ parse_mode: str = "HTML",
82
+ reply_markup: dict = None,
83
+ **kwargs
84
+ ) -> dict:
85
+ params = {
86
+ "text": text,
87
+ "parse_mode": parse_mode,
88
+ **kwargs
89
+ }
90
+ if chat_id:
91
+ params["chat_id"] = chat_id
92
+ if message_id:
93
+ params["message_id"] = message_id
94
+ if inline_message_id:
95
+ params["inline_message_id"] = inline_message_id
96
+ if reply_markup:
97
+ params["reply_markup"] = reply_markup
98
+ return self._request("editMessageText", params)
99
+
100
+ def delete_message(self, chat_id: Union[int, str], message_id: int) -> bool:
101
+ return self._request("deleteMessage", {
102
+ "chat_id": chat_id,
103
+ "message_id": message_id
104
+ })
105
+
106
+ def answer_callback_query(
107
+ self,
108
+ callback_query_id: str,
109
+ text: str = None,
110
+ show_alert: bool = False,
111
+ url: str = None,
112
+ cache_time: int = 0
113
+ ) -> bool:
114
+ params = {
115
+ "callback_query_id": callback_query_id,
116
+ "show_alert": show_alert,
117
+ "cache_time": cache_time
118
+ }
119
+ if text:
120
+ params["text"] = text
121
+ if url:
122
+ params["url"] = url
123
+ return self._request("answerCallbackQuery", params)
124
+
125
+ def send_photo(
126
+ self,
127
+ chat_id: Union[int, str],
128
+ photo: Union[str, bytes],
129
+ caption: str = None,
130
+ parse_mode: str = "HTML",
131
+ reply_markup: dict = None,
132
+ **kwargs
133
+ ) -> dict:
134
+ params = {
135
+ "chat_id": chat_id,
136
+ "parse_mode": parse_mode,
137
+ **kwargs
138
+ }
139
+ if caption:
140
+ params["caption"] = caption
141
+ if reply_markup:
142
+ params["reply_markup"] = reply_markup
143
+
144
+ if isinstance(photo, str) and (photo.startswith("http") or photo.startswith("AgAC")):
145
+ params["photo"] = photo
146
+ return self._request("sendPhoto", params)
147
+ else:
148
+ files = {"photo": photo}
149
+ return self._request("sendPhoto", params, files=files)
150
+
151
+ def send_document(
152
+ self,
153
+ chat_id: Union[int, str],
154
+ document: Union[str, bytes],
155
+ caption: str = None,
156
+ parse_mode: str = "HTML",
157
+ reply_markup: dict = None,
158
+ **kwargs
159
+ ) -> dict:
160
+ params = {
161
+ "chat_id": chat_id,
162
+ "parse_mode": parse_mode,
163
+ **kwargs
164
+ }
165
+ if caption:
166
+ params["caption"] = caption
167
+ if reply_markup:
168
+ params["reply_markup"] = reply_markup
169
+
170
+ if isinstance(document, str) and not document.startswith("/"):
171
+ params["document"] = document
172
+ return self._request("sendDocument", params)
173
+ else:
174
+ files = {"document": document}
175
+ return self._request("sendDocument", params, files=files)
176
+
177
+ def get_chat(self, chat_id: Union[int, str]) -> dict:
178
+ return self._request("getChat", {"chat_id": chat_id})
179
+
180
+ def get_chat_member(self, chat_id: Union[int, str], user_id: int) -> dict:
181
+ return self._request("getChatMember", {
182
+ "chat_id": chat_id,
183
+ "user_id": user_id
184
+ })
styledbots/core/bot.py ADDED
@@ -0,0 +1,270 @@
1
+ import time
2
+ import threading
3
+ from typing import Callable, Optional, List, Dict, Any, Union
4
+ from .api import TelegramAPI
5
+ from .types import Update, Message, CallbackQuery
6
+ from .exceptions import StyledBotsError, HandlerError
7
+ from ..filters.filters import filters as Filters
8
+
9
+
10
+ class Bot:
11
+ """
12
+ Main Bot class - Clean, powerful, Telebot-compatible API
13
+ """
14
+
15
+ def __init__(self, token: str, parse_mode: str = "HTML", threaded: bool = True):
16
+ if not token or ":" not in token:
17
+ raise ValueError("Invalid bot token")
18
+
19
+ self.token = token
20
+ self.parse_mode = parse_mode
21
+ self.api = TelegramAPI(token)
22
+ self.threaded = threaded
23
+
24
+ # Handlers storage
25
+ self.message_handlers: List[dict] = []
26
+ self.callback_handlers: List[dict] = []
27
+ self.command_handlers: Dict[str, Callable] = {}
28
+ self.middlewares: List[Callable] = []
29
+
30
+ # State
31
+ self._offset = 0
32
+ self._running = False
33
+ self._stop_event = threading.Event()
34
+
35
+ # Bot info
36
+ self.me = None
37
+ try:
38
+ self.me = self.api.get_me()
39
+ print(f"✅ StyledBots started as @{self.me.get('username')}")
40
+ except Exception as e:
41
+ print(f"⚠️ Warning: Could not get bot info: {e}")
42
+
43
+ # ==================== Decorators ====================
44
+
45
+ def message_handler(self, commands: List[str] = None, regexp: str = None,
46
+ func: Callable = None, content_types: List[str] = None,
47
+ chat_types: List[str] = None, **kwargs):
48
+ """
49
+ Decorator for message handlers (Telebot compatible + better)
50
+ """
51
+ def decorator(handler: Callable):
52
+ filters_list = []
53
+
54
+ if commands:
55
+ filters_list.append(Filters.command(commands))
56
+ if regexp:
57
+ filters_list.append(Filters.regexp(regexp))
58
+ if content_types:
59
+ filters_list.append(Filters.content_type(content_types))
60
+ if chat_types:
61
+ filters_list.append(Filters.chat_type(chat_types))
62
+ if func:
63
+ filters_list.append(Filters.func(func))
64
+
65
+ self.message_handlers.append({
66
+ "handler": handler,
67
+ "filters": filters_list,
68
+ "kwargs": kwargs
69
+ })
70
+ return handler
71
+ return decorator
72
+
73
+ def callback_query_handler(self, func: Callable = None, **kwargs):
74
+ """Decorator for callback query handlers"""
75
+ def decorator(handler: Callable):
76
+ filters_list = []
77
+ if func:
78
+ filters_list.append(Filters.func(func))
79
+
80
+ self.callback_handlers.append({
81
+ "handler": handler,
82
+ "filters": filters_list,
83
+ "kwargs": kwargs
84
+ })
85
+ return handler
86
+ return decorator
87
+
88
+ def command(self, *commands: str):
89
+ """Simple command decorator: @bot.command("start", "help")"""
90
+ def decorator(handler: Callable):
91
+ for cmd in commands:
92
+ self.command_handlers[cmd.lower()] = handler
93
+ return handler
94
+ return decorator
95
+
96
+ def middleware_handler(self, update_types: List[str] = None):
97
+ """Register middleware"""
98
+ def decorator(handler: Callable):
99
+ self.middlewares.append(handler)
100
+ return handler
101
+ return decorator
102
+
103
+ # ==================== Sending methods ====================
104
+
105
+ def send_message(self, chat_id: Union[int, str], text: str,
106
+ parse_mode: str = None, reply_markup=None,
107
+ reply_to_message_id: int = None, **kwargs):
108
+ if hasattr(reply_markup, "to_dict"):
109
+ reply_markup = reply_markup.to_dict()
110
+ return self.api.send_message(
111
+ chat_id=chat_id,
112
+ text=text,
113
+ parse_mode=parse_mode or self.parse_mode,
114
+ reply_markup=reply_markup,
115
+ reply_to_message_id=reply_to_message_id,
116
+ **kwargs
117
+ )
118
+
119
+ def reply_to(self, message: Message, text: str, **kwargs):
120
+ return self.send_message(
121
+ chat_id=message.chat.id,
122
+ text=text,
123
+ reply_to_message_id=message.message_id,
124
+ **kwargs
125
+ )
126
+
127
+ def edit_message_text(self, text: str, chat_id=None, message_id=None,
128
+ reply_markup=None, **kwargs):
129
+ if hasattr(reply_markup, "to_dict"):
130
+ reply_markup = reply_markup.to_dict()
131
+ return self.api.edit_message_text(
132
+ text=text,
133
+ chat_id=chat_id,
134
+ message_id=message_id,
135
+ reply_markup=reply_markup,
136
+ parse_mode=kwargs.pop("parse_mode", self.parse_mode),
137
+ **kwargs
138
+ )
139
+
140
+ def delete_message(self, chat_id, message_id):
141
+ return self.api.delete_message(chat_id, message_id)
142
+
143
+ def answer_callback_query(self, callback_query_id: str, text: str = None,
144
+ show_alert: bool = False, **kwargs):
145
+ return self.api.answer_callback_query(
146
+ callback_query_id, text=text, show_alert=show_alert, **kwargs
147
+ )
148
+
149
+ def send_photo(self, chat_id, photo, caption=None, reply_markup=None, **kwargs):
150
+ if hasattr(reply_markup, "to_dict"):
151
+ reply_markup = reply_markup.to_dict()
152
+ return self.api.send_photo(
153
+ chat_id, photo, caption=caption,
154
+ reply_markup=reply_markup,
155
+ parse_mode=kwargs.pop("parse_mode", self.parse_mode),
156
+ **kwargs
157
+ )
158
+
159
+ def send_document(self, chat_id, document, caption=None, reply_markup=None, **kwargs):
160
+ if hasattr(reply_markup, "to_dict"):
161
+ reply_markup = reply_markup.to_dict()
162
+ return self.api.send_document(
163
+ chat_id, document, caption=caption,
164
+ reply_markup=reply_markup,
165
+ parse_mode=kwargs.pop("parse_mode", self.parse_mode),
166
+ **kwargs
167
+ )
168
+
169
+ # ==================== Processing ====================
170
+
171
+ def _check_filters(self, filters_list: list, obj) -> bool:
172
+ if not filters_list:
173
+ return True
174
+ return all(f(obj) for f in filters_list)
175
+
176
+ def process_new_updates(self, updates: list):
177
+ for update_data in updates:
178
+ try:
179
+ update = Update(update_data, bot=self)
180
+
181
+ # Middlewares
182
+ for mw in self.middlewares:
183
+ try:
184
+ mw(update)
185
+ except Exception as e:
186
+ print(f"Middleware error: {e}")
187
+
188
+ # Commands (fast path)
189
+ if update.message and update.message.text and update.message.text.startswith("/"):
190
+ cmd = update.message.text.split()[0][1:].split("@")[0].lower()
191
+ if cmd in self.command_handlers:
192
+ try:
193
+ self.command_handlers[cmd](update.message)
194
+ except Exception as e:
195
+ print(f"Command handler error: {e}")
196
+ continue
197
+
198
+ # Message handlers
199
+ if update.message:
200
+ for h in self.message_handlers:
201
+ if self._check_filters(h["filters"], update.message):
202
+ try:
203
+ h["handler"](update.message)
204
+ except Exception as e:
205
+ print(f"Message handler error: {e}")
206
+ break
207
+
208
+ # Callback handlers
209
+ if update.callback_query:
210
+ for h in self.callback_handlers:
211
+ if self._check_filters(h["filters"], update.callback_query):
212
+ try:
213
+ h["handler"](update.callback_query)
214
+ except Exception as e:
215
+ print(f"Callback handler error: {e}")
216
+ break
217
+
218
+ except Exception as e:
219
+ print(f"Update processing error: {e}")
220
+
221
+ # ==================== Polling ====================
222
+
223
+ def infinity_polling(self, timeout: int = 25, skip_pending: bool = True,
224
+ allowed_updates: list = None, **kwargs):
225
+ """Start infinite polling (Telebot style)"""
226
+ print("🚀 StyledBots is polling...")
227
+ self._running = True
228
+ self._stop_event.clear()
229
+
230
+ if skip_pending:
231
+ # Skip old updates
232
+ try:
233
+ updates = self.api.get_updates(timeout=1)
234
+ if updates:
235
+ self._offset = updates[-1]["update_id"] + 1
236
+ except Exception:
237
+ pass
238
+
239
+ while self._running and not self._stop_event.is_set():
240
+ try:
241
+ updates = self.api.get_updates(
242
+ offset=self._offset,
243
+ timeout=timeout,
244
+ allowed_updates=allowed_updates
245
+ )
246
+ if updates:
247
+ self._offset = updates[-1]["update_id"] + 1
248
+ self.process_new_updates(updates)
249
+ except Exception as e:
250
+ print(f"Polling error: {e}")
251
+ time.sleep(3)
252
+
253
+ print("🛑 Polling stopped")
254
+
255
+ def stop_polling(self):
256
+ self._running = False
257
+ self._stop_event.set()
258
+
259
+ def polling(self, non_stop: bool = True, **kwargs):
260
+ """Alias for infinity_polling"""
261
+ self.infinity_polling(**kwargs)
262
+
263
+ # ==================== Helpers ====================
264
+
265
+ def get_me(self):
266
+ return self.api.get_me()
267
+
268
+ def __repr__(self):
269
+ username = self.me.get("username") if self.me else "Unknown"
270
+ return f"<StyledBots Bot @{username}>"
@@ -0,0 +1,26 @@
1
+ class StyledBotsError(Exception):
2
+ """Base exception for StyledBots"""
3
+ pass
4
+
5
+
6
+ class TelegramAPIError(StyledBotsError):
7
+ """Raised when Telegram API returns an error"""
8
+ def __init__(self, description: str, error_code: int = None):
9
+ self.description = description
10
+ self.error_code = error_code
11
+ super().__init__(f"Telegram API Error [{error_code}]: {description}")
12
+
13
+
14
+ class NetworkError(StyledBotsError):
15
+ """Raised on network / connection problems"""
16
+ pass
17
+
18
+
19
+ class InvalidTokenError(StyledBotsError):
20
+ """Raised when bot token is invalid"""
21
+ pass
22
+
23
+
24
+ class HandlerError(StyledBotsError):
25
+ """Raised when a handler fails"""
26
+ pass
@@ -0,0 +1,107 @@
1
+ from typing import Optional, Dict, Any, List
2
+
3
+
4
+ class User:
5
+ def __init__(self, data: dict):
6
+ self.id: int = data.get("id")
7
+ self.is_bot: bool = data.get("is_bot", False)
8
+ self.first_name: str = data.get("first_name", "")
9
+ self.last_name: Optional[str] = data.get("last_name")
10
+ self.username: Optional[str] = data.get("username")
11
+ self.language_code: Optional[str] = data.get("language_code")
12
+ self.is_premium: bool = data.get("is_premium", False)
13
+ self._raw = data
14
+
15
+ @property
16
+ def full_name(self) -> str:
17
+ if self.last_name:
18
+ return f"{self.first_name} {self.last_name}"
19
+ return self.first_name
20
+
21
+ def __repr__(self):
22
+ return f"<User {self.id} @{self.username or self.first_name}>"
23
+
24
+
25
+ class Chat:
26
+ def __init__(self, data: dict):
27
+ self.id: int = data.get("id")
28
+ self.type: str = data.get("type", "private")
29
+ self.title: Optional[str] = data.get("title")
30
+ self.username: Optional[str] = data.get("username")
31
+ self.first_name: Optional[str] = data.get("first_name")
32
+ self.last_name: Optional[str] = data.get("last_name")
33
+ self._raw = data
34
+
35
+ def __repr__(self):
36
+ return f"<Chat {self.id} ({self.type})>"
37
+
38
+
39
+ class Message:
40
+ def __init__(self, data: dict, bot=None):
41
+ self.message_id: int = data.get("message_id")
42
+ self.date: int = data.get("date")
43
+ self.chat = Chat(data.get("chat", {}))
44
+ self.from_user = User(data["from"]) if data.get("from") else None
45
+ self.text: Optional[str] = data.get("text")
46
+ self.caption: Optional[str] = data.get("caption")
47
+ self.content_type: str = self._detect_content_type(data)
48
+ self.reply_to_message = Message(data["reply_to_message"], bot) if data.get("reply_to_message") else None
49
+ self._raw = data
50
+ self._bot = bot
51
+
52
+ def _detect_content_type(self, data: dict) -> str:
53
+ for key in ("text", "photo", "video", "document", "audio", "voice",
54
+ "sticker", "animation", "video_note", "contact", "location"):
55
+ if key in data:
56
+ return key
57
+ return "unknown"
58
+
59
+ def reply(self, text: str, **kwargs):
60
+ """Shortcut: reply to this message"""
61
+ if self._bot:
62
+ return self._bot.send_message(
63
+ self.chat.id,
64
+ text,
65
+ reply_to_message_id=self.message_id,
66
+ **kwargs
67
+ )
68
+ raise RuntimeError("Bot instance not attached")
69
+
70
+ def __repr__(self):
71
+ return f"<Message {self.message_id} from {self.from_user}>"
72
+
73
+
74
+ class CallbackQuery:
75
+ def __init__(self, data: dict, bot=None):
76
+ self.id: str = data.get("id")
77
+ self.from_user = User(data.get("from", {}))
78
+ self.data: Optional[str] = data.get("data")
79
+ self.message = Message(data["message"], bot) if data.get("message") else None
80
+ self.chat_instance: Optional[str] = data.get("chat_instance")
81
+ self._raw = data
82
+ self._bot = bot
83
+
84
+ def answer(self, text: str = None, show_alert: bool = False, **kwargs):
85
+ if self._bot:
86
+ return self._bot.answer_callback_query(
87
+ self.id,
88
+ text=text,
89
+ show_alert=show_alert,
90
+ **kwargs
91
+ )
92
+ raise RuntimeError("Bot instance not attached")
93
+
94
+ def __repr__(self):
95
+ return f"<CallbackQuery {self.id} data={self.data}>"
96
+
97
+
98
+ class Update:
99
+ def __init__(self, data: dict, bot=None):
100
+ self.update_id: int = data.get("update_id")
101
+ self.message = Message(data["message"], bot) if data.get("message") else None
102
+ self.edited_message = Message(data["edited_message"], bot) if data.get("edited_message") else None
103
+ self.callback_query = CallbackQuery(data["callback_query"], bot) if data.get("callback_query") else None
104
+ self._raw = data
105
+
106
+ def __repr__(self):
107
+ return f"<Update {self.update_id}>"
@@ -0,0 +1 @@
1
+ from .filters import filters