telekit 0.0.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.
telekit/__init__.py ADDED
@@ -0,0 +1,14 @@
1
+ # Copyright (c) 2025 Ving Studio, Romashka
2
+ # Licensed under the MIT License. See LICENSE file for full terms.
3
+
4
+ # engine/__init__.py
5
+ from .handler import Handler
6
+ from .chain import Chain
7
+ from .callback_query_handler import CallbackQueryHandler
8
+ from .server import Server, example
9
+ from .snapvault.snapvault import Vault # type: ignore
10
+ from .chapters import chapters
11
+ from .user import User
12
+ from . import senders
13
+
14
+ __all__ = ["senders", "Chain", "Handler", "CallbackQueryHandler", "Server", "Vault", "User", "chapters", "example"]
@@ -0,0 +1,58 @@
1
+ from typing import Any
2
+
3
+ import telebot # type: ignore
4
+ from telebot.types import ( # type: ignore
5
+ Message,
6
+ InaccessibleMessage,
7
+ CallbackQuery
8
+ )
9
+
10
+
11
+ class CallbackQueryHandler:
12
+
13
+ bot: telebot.TeleBot
14
+
15
+ @classmethod
16
+ def init(cls, bot: telebot.TeleBot):
17
+ """
18
+ Initializes the bot instance for the class.
19
+
20
+ Args:
21
+ bot (TeleBot): The Telegram bot instance to be used for sending messages.
22
+ """
23
+ cls.bot = bot
24
+
25
+ @bot.callback_query_handler(func=lambda call: True) # type: ignore
26
+ def handle_callback(call: telebot.types.CallbackQuery) -> None: # type: ignore
27
+ CallbackQueryHandler().handle(call)
28
+
29
+ def handle(self, call: CallbackQuery):
30
+ self._simulate(call.message, str(call.data), from_user=call.from_user)
31
+
32
+ def _simulate(self, message: Message | InaccessibleMessage, text: str, from_user: Any=None) -> None:
33
+ args = {}
34
+
35
+ is_bot: bool = getattr(getattr(message, "from_user", from_user), "is_bot", False)
36
+
37
+ args["message_id"] = getattr(message, "message_id", None)
38
+ args["from_user"] = from_user if from_user else getattr(message, "from_user", None)
39
+ args["date"] = getattr(message, "date", None)
40
+ args["chat"] = getattr(message, "chat", None)
41
+ args["json_string"] = getattr(message, "json", None)
42
+
43
+ args["content_type"] = "text"
44
+ args["options"] = {}
45
+
46
+ if any(value is None for value in args.values()): # type: ignore
47
+ return print("Error: Missing required fields in message simulation.")
48
+
49
+ original_message = message
50
+
51
+ message = Message(**args) # type: ignore
52
+ message.message_thread_id = getattr(original_message, "message_thread_id", None)
53
+ message.text = text
54
+
55
+ if message.from_user:
56
+ message.from_user.is_bot = is_bot
57
+
58
+ self.bot.process_new_messages([message]) # type: ignore
telekit/chain.py ADDED
@@ -0,0 +1,248 @@
1
+ import random
2
+ from typing import Callable, Any
3
+
4
+ import telebot # type: ignore
5
+ from telebot.types import ( # type: ignore
6
+ Message,
7
+ InlineKeyboardButton,
8
+ InlineKeyboardMarkup
9
+ )
10
+
11
+ from . import senders
12
+ from . import input_handler
13
+
14
+
15
+ class Chain:
16
+
17
+ bot: telebot.TeleBot
18
+
19
+ @classmethod
20
+ def init(cls, bot: telebot.TeleBot):
21
+ """
22
+ Initializes the bot instance for the class.
23
+
24
+ Args:
25
+ bot (TeleBot): The Telegram bot instance to be used for sending messages.
26
+ """
27
+ cls.bot = bot
28
+
29
+ def __init__(self, chat_id: int):
30
+ self.chat_id = chat_id
31
+ self.sender = senders.AlertSender(chat_id)
32
+ self.handler = input_handler.InputHandler(chat_id)
33
+ self.parent: Chain | None = None
34
+ self._previous_message: Message | None = None
35
+ self.always_edit_previous_message: bool = False
36
+
37
+ def set_inline_keyboard(self, keyboard: dict[str, 'Chain' | Callable[[Message], Any]], row_width: int = 1) -> None:
38
+ """
39
+ Sets an inline keyboard for the chain with buttons that call the corresponding functions.
40
+ Each button will call the function with the message as an argument.
41
+
42
+ Args:
43
+ keyboard (dict[str, Callable[[Message], Any]]): A dictionary where keys are button captions
44
+ and values are functions to be called when the button is clicked.
45
+ """
46
+ callback_functions: dict[str, Callable[[Message], Any]] = {}
47
+ buttons: list[InlineKeyboardButton] = []
48
+
49
+ for i, (caption, callback) in enumerate(keyboard.items()):
50
+ callback_data = f"button_{i}_{random.randint(1000, 9999)}"
51
+ callback_functions[callback_data] = callback
52
+ buttons.append(
53
+ InlineKeyboardButton(
54
+ text=caption,
55
+ callback_data=callback_data
56
+ )
57
+ )
58
+
59
+ rows = [buttons[i:i + row_width] for i in range(0, len(buttons), row_width)]
60
+ markup = InlineKeyboardMarkup()
61
+ markup.keyboard = rows
62
+
63
+ self.sender.set_reply_markup(markup) # type: ignore
64
+ self.handler.set_callback_functions(callback_functions)
65
+
66
+ def inline_keyboard[Caption: str, Value](self, keyboard: dict[Caption, Value], row_width: int = 1) -> Callable[[Callable[[Message, Value], None]], None]:
67
+ """
68
+ Decorator to set an inline keyboard for the chain
69
+ with buttons that call the decorated function with the button value.
70
+ """
71
+ def wrapper(func: Callable[[Message, Value], None]) -> None:
72
+ callback_functions: dict[str, Callable[[Message], Any]] = {}
73
+ buttons: list[InlineKeyboardButton] = []
74
+
75
+ def get_callback(value: Value) -> Callable[[Message], None]:
76
+ def callback(message: Message) -> None:
77
+ func(message, value)
78
+ return callback
79
+
80
+ for i, (caption, value) in enumerate(keyboard.items()):
81
+ callback_data = f"button_{i}_{random.randint(1000, 9999)}"
82
+ callback_functions[callback_data] = get_callback(value)
83
+ buttons.append(
84
+ InlineKeyboardButton(
85
+ text=caption,
86
+ callback_data=callback_data
87
+ )
88
+ )
89
+
90
+ rows = [buttons[i:i + row_width] for i in range(0, len(buttons), row_width)]
91
+ markup = InlineKeyboardMarkup()
92
+ markup.keyboard = rows
93
+
94
+ self.sender.set_reply_markup(markup) # type: ignore
95
+ self.handler.set_callback_functions(callback_functions)
96
+
97
+ return wrapper
98
+
99
+ def set_entry_suggestions(self, keyboard: dict[str, str] | list[str], row_width: int = 1) -> None:
100
+ """
101
+ Sets reply suggestions as inline buttons below the message input field.
102
+ These buttons act as quick replies, and send the corresponding `callback_data` when clicked.
103
+
104
+ Args:
105
+ keyboard (dict[Caption, Value]): A dictionary where each key is the button's visible text (caption),
106
+ and each value is the string to send as callback_data.
107
+ row_width (int, optional): Number of buttons per row. Defaults to 1.
108
+ """
109
+
110
+ buttons: list[InlineKeyboardButton] = []
111
+
112
+ if isinstance(keyboard, list):
113
+ keyboard = {c: c for c in keyboard}
114
+
115
+ for caption, value in keyboard.items():
116
+ buttons.append(
117
+ InlineKeyboardButton(
118
+ text=caption,
119
+ callback_data=value
120
+ )
121
+ )
122
+
123
+ rows = [buttons[i:i + row_width] for i in range(0, len(buttons), row_width)]
124
+ markup = InlineKeyboardMarkup()
125
+ markup.keyboard = rows
126
+
127
+ self.sender.set_reply_markup(markup) # type: ignore
128
+
129
+ def entry(self,
130
+ filter_message: Callable[[Message], bool] | None=None,
131
+ delete_user_response: bool=False) -> Callable[[Callable[[Message], Any]], None]:
132
+ def wrapper(func: Callable[[Message], Any]) -> None:
133
+ def callback(message: Message) -> bool:
134
+ if delete_user_response:
135
+ self.sender.delete_message(message)
136
+
137
+ if filter_message and not filter_message(message):
138
+ return False
139
+
140
+ func(message)
141
+ return True
142
+
143
+ self.handler.set_entry_callback(callback)
144
+
145
+ return wrapper
146
+
147
+ def entry_text(self,
148
+ filter_message: Callable[[Message, str], bool] | None=None,
149
+ delete_user_response: bool=False) -> Callable[[Callable[[Message, str], Any]], None]:
150
+ def wrapper(func: Callable[[Message, str], Any]) -> None:
151
+ def callback(message: Message) -> bool:
152
+ if delete_user_response:
153
+ self.sender.delete_message(message)
154
+
155
+ if not message.text:
156
+ return False # Only text messages
157
+
158
+ if filter_message and not filter_message(message, message.text):
159
+ return False
160
+
161
+ func(message, message.text)
162
+ return True
163
+
164
+ self.handler.set_entry_callback(callback)
165
+
166
+ return wrapper
167
+
168
+ def set_always_edit_previous_message(self, value: bool=True) -> None:
169
+ self.always_edit_previous_message = value
170
+
171
+ def send(self) -> Message | None:
172
+ self.handler.handle_next_message()
173
+
174
+ if self.always_edit_previous_message:
175
+ self.edit_previous_message()
176
+
177
+ message = self.sender.send_or_handle_error()
178
+ self._set_previous_message(message)
179
+ return message
180
+
181
+ def _set_previous_message(self, message: Message | None) -> None:
182
+ self._previous_message = message
183
+
184
+ if self.parent:
185
+ self.parent._set_previous_message(message)
186
+
187
+ def get_previous_message(self) -> Message | None:
188
+ """
189
+ Returns the previous message sent by the chain.
190
+
191
+ Returns:
192
+ Message | None: The previous message or None if no message was sent
193
+ """
194
+ if self._previous_message:
195
+ return self._previous_message
196
+ elif self.parent:
197
+ return self.parent.get_previous_message()
198
+ else:
199
+ return None
200
+
201
+ def edit_previous_message(self) -> None:
202
+ """
203
+ Edits the previous message sent by the chain with the current sender's message
204
+ """
205
+ self.sender.set_edit_message(self.get_previous_message())
206
+
207
+ def set_parent(self, parent: 'Chain') -> None:
208
+ """
209
+ Sets the parent chain for this chain.
210
+
211
+ Args:
212
+ parent (Chain): The parent chain to be set.
213
+ """
214
+ self.parent = parent
215
+ self.always_edit_previous_message = parent.always_edit_previous_message
216
+
217
+ def __call__(self, message: Message | None = None):
218
+ self.send()
219
+
220
+ def get_alert_sender(self) -> senders.AlertSender:
221
+ """
222
+ Returns the sender for sending alert messages.
223
+ """
224
+ return senders.AlertSender(self.chat_id)
225
+
226
+ def get_bot(self) -> telebot.TeleBot:
227
+ """
228
+ Returns the bot instance associated with this chain.
229
+
230
+ Returns:
231
+ telebot.TeleBot: The bot instance.
232
+ """
233
+ return self.bot
234
+
235
+ @classmethod
236
+ def get_chain_factory(cls, chat_id: int) -> Callable[[], 'Chain']:
237
+ def message_factory() -> Chain:
238
+ return cls(chat_id)
239
+ return message_factory
240
+
241
+ @classmethod
242
+ def get_children_factory(cls, chat_id: int) -> Callable[[Any], 'Chain']:
243
+ def children_factory(parent: Chain | None=None) -> Chain:
244
+ chain = cls(chat_id)
245
+ if parent:
246
+ chain.set_parent(parent)
247
+ return chain
248
+ return children_factory
@@ -0,0 +1 @@
1
+ from . import example_server
@@ -0,0 +1,4 @@
1
+ from . import start # type: ignore
2
+ from . import help # type: ignore
3
+ from . import entry # type: ignore
4
+
@@ -0,0 +1,125 @@
1
+ import telebot.types # type: ignore
2
+ import telekit
3
+ import telekit.snapvault
4
+ import telekit.snapvault.snapvault
5
+
6
+ class User:
7
+ names: telekit.Vault = telekit.Vault(
8
+ path = "data_base",
9
+ table_name = "names",
10
+ key_field_name = "user_id",
11
+ value_field_name = "name"
12
+ )
13
+
14
+ ages: telekit.Vault = telekit.Vault(
15
+ path = "data_base",
16
+ table_name = "ages",
17
+ key_field_name = "user_id",
18
+ value_field_name = "age"
19
+ )
20
+
21
+ def __init__(self, chat_id: int):
22
+ self.chat_id = chat_id
23
+
24
+ def get_name(self, default: str | None=None) -> str | None:
25
+ return self.names.get(self.chat_id, default)
26
+
27
+ def set_name(self, value: str):
28
+ self.names[self.chat_id] = value
29
+
30
+ def get_age(self, default: int | None=None) -> int | None:
31
+ return self.ages.get(self.chat_id, default)
32
+
33
+ def set_age(self, value: int):
34
+ self.ages[self.chat_id] = value
35
+
36
+
37
+ class EntryHandler(telekit.Handler):
38
+
39
+ @classmethod
40
+ def init_handler(cls, bot: telebot.TeleBot) -> None:
41
+ """
42
+ Initializes the command handler.
43
+ """
44
+ @bot.message_handler(commands=['entry']) # type: ignore
45
+ def handler(message: telebot.types.Message) -> None: # type: ignore
46
+ cls(message).handle()
47
+
48
+ # ------------------------------------------
49
+ # Handling Logic
50
+ # ------------------------------------------
51
+
52
+ def handle(self) -> None:
53
+ self._user = User(self.message.chat.id)
54
+ self.entry_name()
55
+
56
+ def entry_name(self, message: telebot.types.Message | None=None) -> None:
57
+ prompt: telekit.Chain = self.get_child()
58
+ prompt.set_always_edit_previous_message(True)
59
+
60
+ prompt.sender.set_title("⌨️ What`s your name?")
61
+ prompt.sender.set_message("Please, send a text message")
62
+
63
+ name = self._user.get_name(
64
+ self.user.get_username()
65
+ )
66
+
67
+ if name:
68
+ prompt.set_entry_suggestions([name])
69
+
70
+ @prompt.entry_text(delete_user_response=True) # Text message only (User will not be abled to send photos, stickers, ...)
71
+ def _(message: telebot.types.Message, name: str) -> None:
72
+ received: telekit.Chain = self.get_child()
73
+
74
+ received.sender.set_title(f"👋 Bonjour, {name}!")
75
+ received.sender.set_message(f"Is that your name?")
76
+
77
+ self._user.set_name(name)
78
+
79
+ received.set_inline_keyboard(
80
+ {
81
+ "« Change": prompt,
82
+ "Yes »": self.entry_age,
83
+ }, row_width=2
84
+ )
85
+
86
+ received.send()
87
+
88
+ prompt.send()
89
+
90
+ def entry_age(self, message: telebot.types.Message) -> None:
91
+ prompt: telekit.Chain = self.get_child() # Child of `entry_name.<locals>.received` (previous chain)
92
+
93
+ prompt.sender.set_title("⏳ How old are you?")
94
+ prompt.sender.set_message("Please, send a numeric message")
95
+
96
+ @prompt.entry_text( # Text message only (User will not be abled to send photos, stickers, ...)
97
+ filter_message=lambda message, text: text.isdigit() and 0 < int(text) < 130, # Numeric message only
98
+ delete_user_response=True)
99
+ def _(message: telebot.types.Message, text: str) -> None:
100
+ received: telekit.Chain = self.get_child()
101
+
102
+ received.sender.set_title(f"😏 {text} years old?")
103
+ received.sender.set_message(f"Noted. Now I know which memes are safe to show you")
104
+ self._user.set_age(int(text))
105
+
106
+ received.set_inline_keyboard(
107
+ {
108
+ "« Change": prompt,
109
+ "Ok »": self.result,
110
+ }, row_width=2
111
+ )
112
+
113
+ received.send()
114
+
115
+ prompt.send()
116
+
117
+ def result(self, message: telebot.types.Message) -> None:
118
+ result: telekit.Chain = self.get_child() # Child of `entry_age.<locals>.received` (previous chain)
119
+
120
+ result.sender.set_title("😏 Well well well")
121
+ result.sender.set_message(f"So your name is {self._user.get_name()} and you're {self._user.get_age()}? Fancy!")
122
+
123
+ result.set_inline_keyboard({"« Change": self.entry_name})
124
+
125
+ result.send()
@@ -0,0 +1,42 @@
1
+ import telebot.types # type: ignore
2
+ import telekit
3
+
4
+ pages: dict[str, tuple[str, str]] = {}
5
+
6
+ for title, text in telekit.chapters.read("help.txt").items():
7
+ pages[title] = (title, text)
8
+
9
+ class HelpHandler(telekit.Handler):
10
+
11
+ @classmethod
12
+ def init_handler(cls, bot: telebot.TeleBot) -> None:
13
+ """
14
+ Initializes the command handler.
15
+ """
16
+ @bot.message_handler(commands=['help']) # type: ignore
17
+ def handler(message: telebot.types.Message) -> None: # type: ignore
18
+ cls(message).handle()
19
+
20
+ # ------------------------------------------
21
+ # Handling Logic
22
+ # ------------------------------------------
23
+
24
+ def handle(self) -> None:
25
+ main: telekit.Chain = self.get_chain()
26
+ main.set_always_edit_previous_message(True)
27
+
28
+ main.sender.set_title("FAQ - Frequently Asked Questions")
29
+ main.sender.set_message("Here are some common questions and answers to help you get started:")
30
+
31
+ @main.inline_keyboard(pages)
32
+ def _(message: telebot.types.Message, value: tuple[str, str]) -> None:
33
+ page: telekit.Chain = self.get_child()
34
+
35
+ page.sender.set_title(value[0])
36
+ page.sender.set_message(value[1])
37
+
38
+ page.set_inline_keyboard({"« Back": main})
39
+
40
+ page.send()
41
+
42
+ main.send()
@@ -0,0 +1,49 @@
1
+ import telebot.types # type: ignore
2
+ import telekit
3
+ import typing
4
+
5
+
6
+ class StartHandler(telekit.Handler):
7
+
8
+ # ------------------------------------------
9
+ # Initialization
10
+ # ------------------------------------------
11
+
12
+ @classmethod
13
+ def init_handler(cls, bot: telebot.TeleBot) -> None:
14
+ """
15
+ Initializes the message handler for the '/start' command.
16
+ """
17
+ @bot.message_handler(commands=['start']) # type: ignore
18
+ def handler(message: telebot.types.Message) -> None: # type: ignore
19
+ cls(message).handle()
20
+
21
+ # ------------------------------------------
22
+ # Handling Logic
23
+ # ------------------------------------------
24
+
25
+ def handle(self) -> None:
26
+ chain: telekit.Chain = self.get_chain()
27
+
28
+ chain.sender.set_title("Hello")
29
+ chain.sender.set_message("Welcome to the bot! Click the button below to start interacting.")
30
+ chain.sender.set_photo("https://static.wikia.nocookie.net/ssb-tourney/images/d/db/Bot_CG_Art.jpg/revision/latest?cb=20151224123450")
31
+ chain.sender.set_effect(chain.sender.Effect.PARTY)
32
+
33
+ def counter_factory() -> typing.Callable[[int], int]:
34
+ count = 0
35
+ def counter(value: int=1) -> int:
36
+ nonlocal count
37
+ count += value
38
+ return count
39
+ return counter
40
+
41
+ click_counter = counter_factory()
42
+
43
+ @chain.inline_keyboard({"⊕": 1, "⊖": -1}, row_width=2)
44
+ def _(message: telebot.types.Message, value: int) -> None:
45
+ chain.sender.set_message(f"You clicked {click_counter(value)} times")
46
+ chain.edit_previous_message()
47
+ chain.send()
48
+
49
+ chain.send()
@@ -0,0 +1,9 @@
1
+ # -*- encoding:utf-8 -*-
2
+ import telebot # type: ignore
3
+ import telekit
4
+
5
+ from . import example_handlers # type: ignore
6
+
7
+ def run_example(token: str):
8
+ bot = telebot.TeleBot(token)
9
+ telekit.Server(bot).polling()
telekit/handler.py ADDED
@@ -0,0 +1,59 @@
1
+ from typing import Callable
2
+
3
+ from telebot.types import Message # type: ignore
4
+ import telebot # type: ignore
5
+
6
+ from .chain import Chain
7
+ from .user import User
8
+
9
+
10
+ class Handler:
11
+
12
+ bot: telebot.TeleBot
13
+
14
+ # def __init__(self, message: telebot.types.Message):
15
+ # super().__init__(message)
16
+
17
+ @classmethod
18
+ def init(cls, bot: telebot.TeleBot):
19
+ """
20
+ Initializes the bot instance for the class.
21
+
22
+ Args:
23
+ bot (TeleBot): The Telegram bot instance to be used for sending messages.
24
+ """
25
+ cls.bot = bot
26
+
27
+ for handler in cls.handlers:
28
+ handler.init_handler(bot)
29
+
30
+ @classmethod
31
+ def init_handler(cls, bot: telebot.TeleBot) -> None:
32
+ """
33
+ Initializes the message handler
34
+ """
35
+ pass
36
+
37
+ def __init__(self, message: Message):
38
+ self.message: Message = message
39
+ self.chain: Chain | None = None
40
+ self.user = User(self.message.chat.id, self.message.from_user)
41
+ self._chain_factory: Callable[[], Chain] = Chain.get_chain_factory(self.message.chat.id)
42
+ self._children_factory: Callable[[Chain | None], Chain] = Chain.get_children_factory(self.message.chat.id)
43
+
44
+ def get_chain(self) -> Chain:
45
+ self.chain = self._chain_factory()
46
+ return self.chain
47
+
48
+ def get_child(self, parent: Chain | None = None) -> Chain:
49
+ if parent is None:
50
+ parent = self.chain
51
+
52
+ self.chain = self._children_factory(self.chain)
53
+ return self.chain
54
+
55
+ handlers: list[type['Handler']] = []
56
+
57
+ def __init_subclass__(cls, **kwargs): # type: ignore
58
+ super().__init_subclass__(**kwargs)
59
+ Handler.handlers.append(cls)
telekit/init.py ADDED
@@ -0,0 +1,18 @@
1
+ from .handler import Handler
2
+ from .chain import Chain
3
+ from .input_handler import InputHandler
4
+ from .callback_query_handler import CallbackQueryHandler
5
+ from .user import User
6
+ from . import senders
7
+
8
+ import telebot # type: ignore
9
+
10
+ __all__ = ["init"]
11
+
12
+ def init(bot: telebot.TeleBot) -> None:
13
+ senders.BaseSender.init(bot)
14
+ Handler.init(bot)
15
+ Chain.init(bot)
16
+ InputHandler.init(bot)
17
+ CallbackQueryHandler.init(bot)
18
+ User.init(bot)