kernelbot 0.0.2__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.
kernelbot/__init__.py ADDED
@@ -0,0 +1,13 @@
1
+ """kernelbot — KernelGram Bot API library, telebot-compatible."""
2
+
3
+ import logging
4
+
5
+ from .version import __version__
6
+ from .bot import TeleBot
7
+ from . import apihelper
8
+ from . import types
9
+ from . import util
10
+
11
+ logger = logging.getLogger("kernelbot")
12
+
13
+ __all__ = ["TeleBot", "apihelper", "types", "util", "__version__"]
kernelbot/apihelper.py ADDED
@@ -0,0 +1,131 @@
1
+ """Low-level HTTP layer for the KernelGram Bot API.
2
+
3
+ Mirrors the public surface of `telebot.apihelper`.
4
+ """
5
+
6
+ import json
7
+ import socket
8
+ import requests
9
+ import logging
10
+
11
+ from .version import __version__
12
+
13
+ logger = logging.getLogger("kernelbot")
14
+
15
+ # --- KernelGram endpoints (hardcoded) ---
16
+ API_HOST = "https://api.kernelgram.club"
17
+ API_URL = API_HOST + "/bot{0}/{1}"
18
+ FILE_URL = API_HOST + "/file/bot{0}/{1}"
19
+
20
+ # --- Behaviour flags ---
21
+ CONNECT_TIMEOUT = 3.5
22
+ READ_TIMEOUT = 999
23
+ SESSION_TIME_TO_LIVE = None
24
+ RETRY_ON_ERROR = False
25
+ MAX_RETRIES = 3
26
+
27
+ FORCE_IPV4 = True
28
+ CUSTOM_REQUEST_SENDER = None
29
+ proxy = None
30
+
31
+ # Поля, которые при multipart-запросе KernelGram ожидает как JSON-строку.
32
+ _JSON_FIELDS = ("reply_markup",)
33
+
34
+
35
+ def _build_session():
36
+ session = requests.Session()
37
+ if FORCE_IPV4:
38
+ from requests.adapters import HTTPAdapter
39
+ from urllib3.util import connection
40
+
41
+ def _ipv4_only(address, *args, **kwargs):
42
+ host, port = address
43
+ infos = socket.getaddrinfo(host, port, socket.AF_INET, socket.SOCK_STREAM)
44
+ family, socktype, proto, _canon, sockaddr = infos[0]
45
+ sock = socket.socket(family, socktype, proto)
46
+ try:
47
+ sock.connect(sockaddr)
48
+ except Exception:
49
+ sock.close()
50
+ raise
51
+ return sock
52
+
53
+ connection.create_connection = _ipv4_only
54
+
55
+ adapter = HTTPAdapter(max_retries=MAX_RETRIES)
56
+ session.mount("https://", adapter)
57
+ session.mount("http://", adapter)
58
+ return session
59
+
60
+
61
+ _session = None
62
+
63
+
64
+ def _get_session():
65
+ global _session
66
+ if _session is None:
67
+ _session = _build_session()
68
+ return _session
69
+
70
+
71
+ def _serialize_for_multipart(params):
72
+ """Привести dict-параметры к JSON-строкам для multipart-запроса."""
73
+ out = dict(params)
74
+ for key in _JSON_FIELDS:
75
+ if key in out and not isinstance(out[key], str):
76
+ out[key] = json.dumps(out[key], ensure_ascii=False)
77
+ return out
78
+
79
+
80
+ def _make_request(token, method_name, method="get", params=None, files=None, **kwargs):
81
+ url = API_URL.format(token, method_name)
82
+ params = params or {}
83
+ timeout = kwargs.pop("timeout", READ_TIMEOUT + CONNECT_TIMEOUT)
84
+
85
+ if CUSTOM_REQUEST_SENDER is not None:
86
+ return CUSTOM_REQUEST_SENDER(method=method, url=url, params=params, files=files, timeout=timeout)
87
+
88
+ session = _get_session()
89
+ proxies = proxy
90
+
91
+ try:
92
+ if method == "get":
93
+ resp = session.get(url, params=params, timeout=timeout, proxies=proxies, **kwargs)
94
+ elif method == "post":
95
+ if files:
96
+ data = _serialize_for_multipart(params)
97
+ resp = session.post(url, data=data, files=files, timeout=timeout, proxies=proxies, **kwargs)
98
+ else:
99
+ resp = session.post(url, json=params, timeout=timeout, proxies=proxies, **kwargs)
100
+ else:
101
+ raise ValueError("Unsupported HTTP method: %r" % method)
102
+ except requests.exceptions.RequestException as e:
103
+ logger.error("Request to %s failed: %s", url, e)
104
+ raise
105
+
106
+ try:
107
+ result = resp.json()
108
+ except ValueError:
109
+ resp.raise_for_status()
110
+ raise
111
+
112
+ if not result.get("ok"):
113
+ description = result.get("description", "Unknown error")
114
+ logger.error("KernelGram API error in %s: %s", method_name, description)
115
+ raise ApiTelegramException(method_name, result, result)
116
+
117
+ return result
118
+
119
+
120
+ class ApiTelegramException(Exception):
121
+ """Raised when the KernelGram Bot API returns ok=false."""
122
+
123
+ def __init__(self, function_name, result, payload):
124
+ super().__init__(
125
+ "KernelGram API error in %s: %s" % (function_name, result.get("description"))
126
+ )
127
+ self.function_name = function_name
128
+ self.result = result
129
+ self.error_code = result.get("error_code")
130
+ self.description = result.get("description")
131
+ self.payload = payload
kernelbot/bot.py ADDED
@@ -0,0 +1,237 @@
1
+ """TeleBot class for KernelGram. Mirrors telebot.TeleBot."""
2
+
3
+ import re
4
+ import time
5
+ import logging
6
+
7
+ from . import apihelper
8
+ from . import types
9
+
10
+ logger = logging.getLogger("kernelbot")
11
+
12
+
13
+ class TeleBot:
14
+ """Synchronous KernelGram bot, API-compatible with pyTelegramBotAPI."""
15
+
16
+ def __init__(self, token, parse_mode=None, threaded=True, num_threads=2):
17
+ if not token or not isinstance(token, str):
18
+ raise ValueError("Token must be a non-empty string")
19
+ self.token = token
20
+ self.parse_mode = parse_mode
21
+ self.threaded = threaded
22
+ self.num_threads = num_threads
23
+ self._message_handlers = []
24
+ self._callback_handlers = []
25
+ self._update_listener = None
26
+
27
+ # ---------------- API methods ----------------
28
+
29
+ def get_me(self):
30
+ return types.User(**apihelper._make_request(self.token, "getMe"))
31
+
32
+ def get_updates(self, offset=None, limit=None, timeout=20, allowed_updates=None):
33
+ params = {"timeout": timeout}
34
+ if offset is not None:
35
+ params["offset"] = offset
36
+ if limit is not None:
37
+ params["limit"] = limit
38
+ if allowed_updates is not None:
39
+ params["allowed_updates"] = allowed_updates
40
+ return apihelper._make_request(self.token, "getUpdates", method="get", params=params)
41
+
42
+ def send_message(self, chat_id, text, parse_mode=None, reply_markup=None,
43
+ disable_web_page_preview=None, **kwargs):
44
+ params = {"chat_id": chat_id, "text": text}
45
+ pm = parse_mode if parse_mode is not None else self.parse_mode
46
+ if pm:
47
+ params["parse_mode"] = pm
48
+ if reply_markup is not None:
49
+ params["reply_markup"] = reply_markup
50
+ if disable_web_page_preview is not None:
51
+ params["disable_web_page_preview"] = disable_web_page_preview
52
+ params.update(kwargs)
53
+ result = apihelper._make_request(self.token, "sendMessage", method="post", params=params)
54
+ return types.Message(**result)
55
+
56
+ def send_photo(self, chat_id, photo, caption=None, parse_mode=None,
57
+ reply_markup=None, **kwargs):
58
+ params = {"chat_id": chat_id}
59
+ pm = parse_mode if parse_mode is not None else self.parse_mode
60
+ if caption is not None:
61
+ params["caption"] = caption
62
+ if pm:
63
+ params["parse_mode"] = pm
64
+ if reply_markup is not None:
65
+ params["reply_markup"] = reply_markup
66
+ params.update(kwargs)
67
+
68
+ files = None
69
+ if hasattr(photo, "read"):
70
+ files = {"photo": photo}
71
+ result = apihelper._make_request(self.token, "sendPhoto", method="post",
72
+ params=params, files=files)
73
+ else:
74
+ params["photo"] = photo
75
+ result = apihelper._make_request(self.token, "sendPhoto", method="post",
76
+ params=params)
77
+ return types.Message(**result)
78
+
79
+ def delete_message(self, chat_id, message_id):
80
+ params = {"chat_id": chat_id, "message_id": message_id}
81
+ return apihelper._make_request(self.token, "deleteMessage", method="post", params=params)
82
+
83
+ def edit_message_text(self, text, chat_id=None, message_id=None, inline_message_id=None,
84
+ parse_mode=None, reply_markup=None, **kwargs):
85
+ params = {"text": text}
86
+ if chat_id is not None:
87
+ params["chat_id"] = chat_id
88
+ if message_id is not None:
89
+ params["message_id"] = message_id
90
+ if inline_message_id is not None:
91
+ params["inline_message_id"] = inline_message_id
92
+ pm = parse_mode if parse_mode is not None else self.parse_mode
93
+ if pm:
94
+ params["parse_mode"] = pm
95
+ if reply_markup is not None:
96
+ params["reply_markup"] = reply_markup
97
+ params.update(kwargs)
98
+ result = apihelper._make_request(self.token, "editMessageText", method="post", params=params)
99
+ if isinstance(result, dict) and "message_id" in result:
100
+ return types.Message(**result)
101
+ return result
102
+
103
+ def edit_message_caption(self, caption, chat_id=None, message_id=None,
104
+ parse_mode=None, reply_markup=None, **kwargs):
105
+ params = {"caption": caption}
106
+ if chat_id is not None:
107
+ params["chat_id"] = chat_id
108
+ if message_id is not None:
109
+ params["message_id"] = message_id
110
+ pm = parse_mode if parse_mode is not None else self.parse_mode
111
+ if pm:
112
+ params["parse_mode"] = pm
113
+ if reply_markup is not None:
114
+ params["reply_markup"] = reply_markup
115
+ params.update(kwargs)
116
+ result = apihelper._make_request(self.token, "editMessageCaption", method="post", params=params)
117
+ if isinstance(result, dict) and "message_id" in result:
118
+ return types.Message(**result)
119
+ return result
120
+
121
+ def answer_callback_query(self, callback_query_id, text=None, show_alert=False,
122
+ url=None, cache_time=None, **kwargs):
123
+ params = {"callback_query_id": callback_query_id}
124
+ if text is not None:
125
+ params["text"] = text
126
+ if show_alert:
127
+ params["show_alert"] = True
128
+ if url is not None:
129
+ params["url"] = url
130
+ if cache_time is not None:
131
+ params["cache_time"] = cache_time
132
+ params.update(kwargs)
133
+ return apihelper._make_request(self.token, "answerCallbackQuery", method="post", params=params)
134
+
135
+ def reply_to(self, message, text, **kwargs):
136
+ return self.send_message(message.chat.id, text, **kwargs)
137
+
138
+ def get_file(self, file_id):
139
+ result = apihelper._make_request(self.token, "getFile", method="get", params={"file_id": file_id})
140
+ return types.File(**result)
141
+
142
+ # ---------------- Handlers ----------------
143
+
144
+ def message_handler(self, commands=None, regexp=None, func=None,
145
+ content_types=None, chat_types=None):
146
+ def decorator(handler):
147
+ self._message_handlers.append({
148
+ "function": handler,
149
+ "commands": commands,
150
+ "regexp": re.compile(regexp) if regexp else None,
151
+ "func": func,
152
+ "content_types": content_types or ["text"],
153
+ "chat_types": chat_types,
154
+ })
155
+ return handler
156
+ return decorator
157
+
158
+ def callback_query_handler(self, func=None):
159
+ def decorator(handler):
160
+ self._callback_handlers.append({"function": handler, "func": func})
161
+ return handler
162
+ return decorator
163
+
164
+ def set_update_listener(self, listener):
165
+ self._update_listener = listener
166
+
167
+ def _test_message_handler(self, handler, message):
168
+ if handler["content_types"] and message.content_type not in handler["content_types"]:
169
+ return False
170
+ if handler["chat_types"] and getattr(message.chat, "type", None) not in handler["chat_types"]:
171
+ return False
172
+ if handler["commands"]:
173
+ text = getattr(message, "text", "") or ""
174
+ if not any(text.startswith("/" + c) for c in handler["commands"]):
175
+ return False
176
+ if handler["regexp"] and not handler["regexp"].search(getattr(message, "text", "") or ""):
177
+ return False
178
+ if handler["func"] and not handler["func"](message):
179
+ return False
180
+ return True
181
+
182
+ def process_new_updates(self, updates):
183
+ for update in updates:
184
+ if self._update_listener:
185
+ self._update_listener([update])
186
+
187
+ msg = update.get("message")
188
+ if msg:
189
+ message = types.Message(**msg)
190
+ for handler in self._message_handlers:
191
+ if self._test_message_handler(handler, message):
192
+ handler["function"](message)
193
+ break
194
+ continue
195
+
196
+ cb = update.get("callback_query")
197
+ if cb:
198
+ callback = types.CallbackQuery(**cb)
199
+ for handler in self._callback_handlers:
200
+ if handler["func"] is None or handler["func"](callback):
201
+ handler["function"](callback)
202
+ break
203
+
204
+ # ---------------- Polling ----------------
205
+
206
+ def polling(self, interval=0, timeout=20, allowed_updates=None, offset=None):
207
+ offset = offset or self._last_update_id()
208
+ logger.info("kernelbot: polling started")
209
+ while True:
210
+ try:
211
+ result = self.get_updates(offset=offset, timeout=timeout,
212
+ allowed_updates=allowed_updates)
213
+ updates = result.get("result", [])
214
+ if updates:
215
+ offset = updates[-1]["update_id"] + 1
216
+ self.process_new_updates(updates)
217
+ if interval:
218
+ time.sleep(interval)
219
+ except KeyboardInterrupt:
220
+ logger.info("kernelbot: polling stopped")
221
+ return
222
+ except Exception as e:
223
+ logger.error("kernelbot: polling error: %s", e)
224
+ time.sleep(3)
225
+
226
+ def infinity_polling(self, timeout=20, **kwargs):
227
+ self.polling(timeout=timeout, **kwargs)
228
+
229
+ def _last_update_id(self):
230
+ try:
231
+ result = self.get_updates(offset=-1, timeout=0)
232
+ updates = result.get("result", [])
233
+ if updates:
234
+ return updates[-1]["update_id"] + 1
235
+ except Exception:
236
+ pass
237
+ return None
kernelbot/types.py ADDED
@@ -0,0 +1,85 @@
1
+ """KernelGram Bot API types, mirroring telebot.types."""
2
+
3
+
4
+ class TeleBotBase:
5
+ """Base class: turns dicts into attribute-accessible objects."""
6
+
7
+ def __init__(self, **kwargs):
8
+ for key, value in kwargs.items():
9
+ if isinstance(value, dict):
10
+ value = self._wrap(value)
11
+ elif isinstance(value, list):
12
+ value = [self._wrap(v) for v in value]
13
+ setattr(self, key, value)
14
+
15
+ @staticmethod
16
+ def _wrap(value):
17
+ if isinstance(value, dict):
18
+ return TeleBotBase(**value)
19
+ return value
20
+
21
+ def __repr__(self):
22
+ return "<%s %s>" % (self.__class__.__name__, self.__dict__)
23
+
24
+
25
+ class User(TeleBotBase):
26
+ @property
27
+ def full_name(self):
28
+ name = getattr(self, "first_name", "") or ""
29
+ last = getattr(self, "last_name", "") or ""
30
+ return (name + " " + last).strip()
31
+
32
+
33
+ class Chat(TeleBotBase):
34
+ pass
35
+
36
+
37
+ class Message(TeleBotBase):
38
+ def __init__(self, **kwargs):
39
+ if "from" in kwargs:
40
+ kwargs["from_user"] = kwargs.pop("from")
41
+ super().__init__(**kwargs)
42
+
43
+ @property
44
+ def content_type(self):
45
+ for key in (
46
+ "text", "audio", "document", "animation", "photo", "sticker",
47
+ "video", "video_note", "voice", "location", "contact", "venue",
48
+ "dice", "new_chat_members", "left_chat_member", "new_chat_title",
49
+ "new_chat_photo", "delete_chat_photo", "group_chat_created",
50
+ "supergroup_chat_created", "channel_chat_created",
51
+ "migrate_to_chat_id", "migrate_from_chat_id", "pinned_message",
52
+ "invoice", "successful_payment", "connected_website", "poll",
53
+ "passport_data", "proximity_alert_triggered", "video_chat_scheduled",
54
+ "video_chat_started", "video_chat_ended",
55
+ "video_chat_participants_invited", "web_app_data",
56
+ "message_auto_delete_timer_changed", "forum_topic_created",
57
+ "forum_topic_closed", "forum_topic_reopened", "forum_topic_edited",
58
+ "general_forum_topic_hidden", "general_forum_topic_unhidden",
59
+ "write_access_allowed", "user_shared", "chat_shared", "story",
60
+ ):
61
+ if hasattr(self, key):
62
+ return key
63
+ return "text"
64
+
65
+
66
+ class Update(TeleBotBase):
67
+ pass
68
+
69
+
70
+ class CallbackQuery(TeleBotBase):
71
+ def __init__(self, **kwargs):
72
+ if "from" in kwargs:
73
+ kwargs["from_user"] = kwargs.pop("from")
74
+ super().__init__(**kwargs)
75
+
76
+
77
+ class InlineQuery(TeleBotBase):
78
+ def __init__(self, **kwargs):
79
+ if "from" in kwargs:
80
+ kwargs["from_user"] = kwargs.pop("from")
81
+ super().__init__(**kwargs)
82
+
83
+
84
+ class File(TeleBotBase):
85
+ pass
kernelbot/util.py ADDED
@@ -0,0 +1,27 @@
1
+ """Utility helpers for kernelbot."""
2
+
3
+
4
+ def split_string(text, chars_per_string):
5
+ """Split text into chunks of at most `chars_per_string` characters."""
6
+ return [text[i:i + chars_per_string] for i in range(0, len(text), chars_per_string)]
7
+
8
+
9
+ def smart_split(text, chars_per_string=3000):
10
+ """Split text preferring '\\n', '. ' and ' ' boundaries."""
11
+ if len(text) <= chars_per_string:
12
+ return [text]
13
+
14
+ parts = []
15
+ remaining = text
16
+ while len(remaining) > chars_per_string:
17
+ chunk = remaining[:chars_per_string]
18
+ for sep in ("\n", ". ", " "):
19
+ idx = chunk.rfind(sep)
20
+ if idx != -1:
21
+ chunk = remaining[:idx + len(sep)]
22
+ break
23
+ parts.append(chunk)
24
+ remaining = remaining[len(chunk):]
25
+ if remaining:
26
+ parts.append(remaining)
27
+ return parts
kernelbot/version.py ADDED
@@ -0,0 +1 @@
1
+ __version__ = "0.0.2"
@@ -0,0 +1,293 @@
1
+ Metadata-Version: 2.4
2
+ Name: kernelbot
3
+ Version: 0.0.2
4
+ Summary: A simple, but extensible Python implementation for the KernelGram Bot API. Telebot-compatible.
5
+ Author-email: ReaalRick <rickReaal@proton.me>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/ReaalRick/kernelbot
8
+ Project-URL: Repository, https://github.com/ReaalRick/kernelbot
9
+ Project-URL: Issues, https://github.com/ReaalRick/kernelbot/issues
10
+ Keywords: kernelgram,bot,api,telebot
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.8
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Requires-Python: >=3.8
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: requests>=2.28.0
24
+ Dynamic: license-file
25
+
26
+ # kernelbot
27
+
28
+ Простая, но расширяемая реализация **KernelGram Bot API** на Python.
29
+
30
+ [![PyPI version](https://img.shields.io/badge/pypi-coming%20soon-lightgrey)](https://pypi.org/project/kernelbot/)
31
+ [![Python](https://img.shields.io/badge/python-3.8%2B-blue)](https://www.python.org/)
32
+ [![License](https://img.shields.io/badge/license-MIT-green)](LICENSE)
33
+ [![KernelGram](https://img.shields.io/badge/platform-KernelGram-2ea44f)](https://kernelgram.club)
34
+
35
+ `kernelbot` — это замена [`pyTelegramBotAPI`](https://github.com/eternnoir/pyTelegramBotAPI) (telebot), адаптированная для [KernelGram](https://kernelgram.club) — мессенджера по типу телеграма но со своим сервером. Если у вас есть код под `telebot`, достаточно поменять импорт — и он заработает.
36
+
37
+ ---
38
+
39
+ ## Содержание
40
+
41
+ - [Официальная документация](#официальная-документация)
42
+ - [Установка](#установка)
43
+ - [Первый бот](#первый-бот)
44
+ - [Что нужно](#что-нужно)
45
+ - [Простой эхо-бот](#простой-эхо-бот)
46
+ - [Общая документация API](#общая-документация-api)
47
+ - [Типы](#типы)
48
+ - [Методы](#методы)
49
+ - [Обработчики сообщений](#обработчики-сообщений)
50
+ - [Обработчики callback-запросов](#обработчики-callback-запросов)
51
+ - [Отправка фото](#отправка-фото)
52
+ - [Отправка больших сообщений](#отправка-больших-сообщений)
53
+ - [Продвинутое использование](#продвинутое-использование)
54
+ - [Только IPv4](#только-ipv4)
55
+ - [Скачивание файлов](#скачивание-файлов)
56
+ - [Прокси](#прокси)
57
+ - [Ограничения KernelGram](#ограничения-kernelgram)
58
+ - [Переход с telebot](#переход-с-telebot)
59
+ - [Лицензия](#лицензия)
60
+
61
+ ---
62
+
63
+ ## Официальная документация
64
+
65
+ - FAQ по ботам KernelGram: https://kernelgram.club/bots
66
+
67
+ ---
68
+
69
+ ## Установка
70
+
71
+ Библиотека протестирована на Python 3.8+.
72
+
73
+ Установка через pip:
74
+
75
+ ```
76
+
77
+ pip install kernelbot
78
+
79
+ ```
80
+
81
+ Или из исходников:
82
+
83
+ ```
84
+
85
+ pip install git+https://github.com/ReaalRick/kernelbot.git
86
+
87
+ ```
88
+
89
+ ---
90
+
91
+ ## Первый бот
92
+
93
+ ### Что нужно
94
+
95
+ Токен, полученный у `@BotFather` внутри приложения KernelGram. Назовём его `TOKEN`.
96
+
97
+ ### Простой эхо-бот
98
+
99
+ Класс `TeleBot` инкапсулирует все вызовы API в одном классе. Он предоставляет функции вида `send_xyz` (`send_message`, `send_photo` и другие) и несколько способов слушать входящие сообщения.
100
+
101
+ Создайте файл `echo_bot.py`:
102
+
103
+ ```python
104
+ import kernelbot
105
+
106
+ bot = kernelbot.TeleBot("TOKEN", parse_mode=None)
107
+
108
+ @bot.message_handler(commands=['start', 'help'])
109
+ def send_welcome(message):
110
+ bot.reply_to(message, "Howdy, how are you doing?")
111
+
112
+ @bot.message_handler(func=lambda m: True)
113
+ def echo_all(message):
114
+ bot.reply_to(message, message.text)
115
+
116
+ bot.infinity_polling()
117
+ ```
118
+
119
+ Запуск:
120
+
121
+ ```
122
+ python echo_bot.py
123
+ ```
124
+
125
+ Проверьте: отправьте /start и любое текстовое сообщение.
126
+
127
+ ---
128
+
129
+ ### Общая документация API
130
+
131
+ Все типы описаны в kernelbot.types. Они соответствуют определениям KernelGram Bot API
132
+
133
+ У объекта Message есть атрибут content_type, одно из значений:
134
+
135
+ ```
136
+ text, audio, document, animation, photo, sticker, video,
137
+ video_note, voice, location, contact, venue, dice,
138
+ new_chat_members, left_chat_member, new_chat_title, new_chat_photo,
139
+ delete_chat_photo, group_chat_created, supergroup_chat_created,
140
+ channel_chat_created, migrate_to_chat_id, migrate_from_chat_id,
141
+ pinned_message, invoice, successful_payment, connected_website,
142
+ poll, passport_data, proximity_alert_triggered, video_chat_scheduled,
143
+ video_chat_started, video_chat_ended, video_chat_participants_invited,
144
+ web_app_data, message_auto_delete_timer_changed, forum_topic_created,
145
+ forum_topic_closed, forum_topic_reopened, forum_topic_edited,
146
+ general_forum_topic_hidden, general_forum_topic_unhidden,
147
+ write_access_allowed, user_shared, chat_shared, story.
148
+ ```
149
+
150
+ У типа Message также есть from_user — KernelGram Bot API возвращает отправителя в поле from, но from — зарезервированное слово в Python, поэтому оно переименовано.
151
+
152
+ Методы
153
+
154
+ Все методы API находятся в классе TeleBot. Имена следуют обычным соглашениям Python:
155
+
156
+ KernelGram API метод kernelbot
157
+ getMe get_me()
158
+ sendMessage send_message()
159
+ sendPhoto send_photo()
160
+ editMessageText edit_message_text()
161
+ editMessageCaption edit_message_caption()
162
+ deleteMessage delete_message()
163
+ answerCallbackQuery answer_callback_query()
164
+ getUpdates get_updates()
165
+ getFile get_file()
166
+
167
+ Обработчики сообщений
168
+
169
+ Обработчик сообщений — это функция, декорированная message_handler экземпляра TeleBot:
170
+
171
+ ```python
172
+ @bot.message_handler(commands=['start'])
173
+ def handle_start(message):
174
+ pass
175
+ ```
176
+
177
+ Поддерживаемые фильтры:
178
+
179
+ Имя Аргументы Условие
180
+ content_types список строк (по умолчанию ['text']) True, если message.content_type в списке
181
+ regexp строка регулярного выражения True, если re.search совпадает с message.text
182
+ commands список строк True, если message.text начинается с одной из команд
183
+ chat_types список типов чатов True, если message.chat.type в списке
184
+ func lambda или ссылка на функцию True, если вызываемый объект возвращает True
185
+
186
+ Все обработчики проверяются в порядке объявления.
187
+
188
+ Обработчики callback-запросов
189
+
190
+ Инлайн-кнопки передаются через reply_markup с полем inline_keyboard:
191
+
192
+ ```python
193
+ @bot.message_handler(commands=["start"])
194
+ def start(message):
195
+ markup = {
196
+ "inline_keyboard": [
197
+ [{"text": "Say hi", "callback_data": "hi"}],
198
+ ],
199
+ }
200
+ bot.send_message(message.chat.id, "Choose:", reply_markup=markup)
201
+
202
+
203
+ @bot.callback_query_handler(func=lambda call: call.data == "hi")
204
+ def on_hi(call):
205
+ bot.answer_callback_query(call.id, text="Hello!")
206
+ bot.send_message(call.message.chat.id, "Hi there!")
207
+ ```
208
+
209
+ Отправка фото
210
+
211
+ ```python
212
+ with open("photo.jpg", "rb") as f:
213
+ bot.send_photo(chat_id, f, caption="<b>Look</b>", reply_markup=markup)
214
+ ```
215
+
216
+ Отправка больших сообщений
217
+
218
+ ```python
219
+ from kernelbot import util
220
+
221
+ large_text = open("large_text.txt", "rb").read()
222
+ for text in util.smart_split(large_text, chars_per_string=3000):
223
+ bot.send_message(chat_id, text)
224
+ ```
225
+
226
+ ---
227
+
228
+ Продвинутое использование
229
+
230
+ Только IPv4
231
+
232
+ API KernelGram обслуживается через Cloudflare, который отдаёт и IPv4-, и IPv6-адреса. В некоторых мобильных сетях IPv6 до Cloudflare не доходит, что вызывает ClientConnectorDNSError или No route to host.
233
+
234
+ kernelbot по умолчанию форсит IPv4. Чтобы отключить:
235
+
236
+ ```python
237
+ from kernelbot import apihelper
238
+ apihelper.FORCE_IPV4 = False
239
+ ```
240
+
241
+ Скачивание файлов
242
+
243
+ ```python
244
+ import requests
245
+ from kernelbot import apihelper
246
+
247
+ file_info = bot.get_file(file_id)
248
+ url = apihelper.FILE_URL.format(bot.token, file_info.file_path)
249
+ content = requests.get(url).content
250
+ ```
251
+
252
+ Прокси
253
+
254
+ ```python
255
+ from kernelbot import apihelper
256
+ apihelper.proxy = {"https": "socks5://user:pass@host:port"}
257
+ ```
258
+
259
+ Требует pip install requests[socks] для SOCKS-прокси.
260
+
261
+ ---
262
+
263
+ Ограничения KernelGram
264
+
265
+ Некоторые методы и возможности Telegram Bot API в KernelGram не реализованы или работают иначе:
266
+
267
+ · setMyProfilePhoto — отсутствует (METHOD_NOT_FOUND). Аватар бота устанавливается через @BotFather.
268
+ · Reply-клавиатуры (reply_markup.keyboard) принимаются API, но не отображаются в клиенте. Используйте инлайн-клавиатуры (reply_markup.inline_keyboard).
269
+ · Все методы set* для профиля бота (имя, описание, команды) доступны только через @BotFather.
270
+
271
+ ---
272
+
273
+ Переход с telebot
274
+
275
+ kernelbot повторяет публичный API pyTelegramBotAPI. Для перехода достаточно двух строк:
276
+
277
+ ```python
278
+ # Было
279
+ import telebot
280
+ bot = telebot.TeleBot(TOKEN)
281
+
282
+ # Стало
283
+ import kernelbot
284
+ bot = kernelbot.TeleBot(TOKEN)
285
+ ```
286
+
287
+ Всё остальное — обработчики, фильтры, reply_to, send_message, infinity_polling — работает так же.
288
+
289
+ ---
290
+
291
+ Лицензия
292
+
293
+ MIT. См. LICENSE.
@@ -0,0 +1,11 @@
1
+ kernelbot/__init__.py,sha256=14_q0BvQUHiZBTzeDmzJRZgWudtJCmp2o4ZCO-8yPKw,315
2
+ kernelbot/apihelper.py,sha256=4ycGi6NR5sfWWzGgyVX7C3o6GKVvr1JtD_O7wRbtlJg,4007
3
+ kernelbot/bot.py,sha256=4z3mQrHgv4jLX5sOjU6lSbEmoOx_7reolCoWL6KFKnA,9622
4
+ kernelbot/types.py,sha256=wBHCWWcL-tcOj6SkkL0RdMITdVYVzsztFDZsMVm8ITg,2722
5
+ kernelbot/util.py,sha256=XDEvIVTgm3yRgYN7wXdiMoOSLxeQ2rq7N2OlANaU3Ws,854
6
+ kernelbot/version.py,sha256=QvlVh4JTl3JL7jQAja76yKtT-IvF4631ASjWY1wS6AQ,22
7
+ kernelbot-0.0.2.dist-info/licenses/LICENSE,sha256=xGZxQ1A4VzAX7SoOPIteaWD3qhQPSexXlCIEpL1VtE4,1066
8
+ kernelbot-0.0.2.dist-info/METADATA,sha256=BACWDPn7CF-4YCglm8GTzLl9nuqncZL4wEElnSQ_YH8,10622
9
+ kernelbot-0.0.2.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
10
+ kernelbot-0.0.2.dist-info/top_level.txt,sha256=9xoFJVdmfKyFREyqgNFOBERj4BgFFfP16riRn_2265E,10
11
+ kernelbot-0.0.2.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ReaalRick
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
+ kernelbot