Compare commits

...

29 Commits

Author SHA1 Message Date
Badiboy bd94d8d91c
Merge pull request #1873 from coder2020official/circular_import_fix
Little code style improvement in service_utils
2023-01-08 09:55:33 +03:00
_run 6b399ab8cd
Being specific with except block 2023-01-08 10:49:27 +04:00
_run 8744402efc
Removed built-in io module from try/except block 2023-01-08 10:48:45 +04:00
Badiboy d5bbaa900e
Merge pull request #1870 from Cub11k/master
Make create_dir() method of StatePickleStorage cross-platform instead of POSIX only.
2023-01-07 13:02:26 +03:00
Konstantin Ostashenko 02ae255701
Revert changes in util.py 2023-01-06 22:39:27 +02:00
Cub11k c27f60b94b Make create_dir() method cross-platform instead of POSIX only.
Fix for issue #1869
2023-01-06 22:36:08 +02:00
Badiboy a781929a2d
Merge pull request #1868 from Cub11k/master
Fix circular import
2023-01-06 22:51:52 +03:00
Cub11k e6f8acadf4 rename _util.py to service_utils.py 2023-01-06 21:41:30 +02:00
Cub11k c298d95d0f Move functions, required in types.py to _util.py
Add __all__ to util.py for sphinx to generate docs properly
2023-01-06 19:27:25 +02:00
Cub11k 8aee5372ee Update README.md - add link to ru docs 2023-01-05 16:25:16 +02:00
_run df105ab1d8
Merge pull request #1867 from Cub11k/master
Translated into russian files: index.po, calldata.po
2023-01-05 18:20:30 +04:00
Cub11k b93ec5d0e0 Translated calldata.po to russian 2023-01-05 16:13:00 +02:00
Cub11k f201df3275 Translated index.po to russian 2023-01-05 16:03:14 +02:00
_run 206e4e024b
Merge pull request #1865 from coder2020official/master
Fix docs issues
2023-01-04 18:08:35 +04:00
coder2020official bd1290592b Fix docs issues 2023-01-04 18:07:29 +04:00
_run 9b9eb775f7
Merge pull request #1863 from Cub11k/master
Finished translations into russian of files: sync_version.po, async_version.po
2023-01-04 17:39:34 +04:00
Konstantin Ostashenko 3cfa24f9c0
Fix msgid for forward_message:5 in async_version.po 2023-01-04 15:35:09 +02:00
Konstantin Ostashenko b540a6c4d4
Fix msgid for forward_message:5 in sync_version.po 2023-01-04 15:33:18 +02:00
Cub11k a0ba5ae9af Finished translations on sync and async versions.
Spelling fixes
2023-01-04 00:43:42 +02:00
Cub11k 651db29cb2 Fix typehints for stop_poll reply markup 2023-01-03 23:45:59 +02:00
Cub11k 490168f3f6 Some translations
Up to lines sync_version.po:4527 and async_version.po:4448
2023-01-03 19:43:36 +02:00
Cub11k bf38071e8f Some translations
Up to lines sync_version.po:3691 and async_version.po:3609
2023-01-03 17:32:31 +02:00
Konstantin Ostashenko e8aaa524fe
Merge branch 'sync_and_async_upd' into master 2023-01-02 17:41:48 +02:00
Konstantin Ostashenko e2e754fdff
Merge pull request #3 from eternnoir/master
Update fork
2023-01-02 17:35:04 +02:00
Konstantin Ostashenko fe0dc6930c
Merge pull request #2 from Cub11k/master
Update branch from master
2023-01-02 17:14:33 +02:00
Konstantin Ostashenko 6d4d3f8005
Merge pull request #1 from eternnoir/master
Update fork
2023-01-02 17:12:49 +02:00
Cub11k 0f7464e8c4 Some translations
Up to lines sync_version.po:3008 and async_version.po:3122
2023-01-02 17:09:48 +02:00
Cub11k dd50273c95 Some translations
Up to lines sync_version.po:2709 and async_version.po:2853
2022-12-31 14:16:13 +02:00
Cub11k 8e9d566d5c Minor fixes and some translations
Up to lines sync_version.po:2382 and async_version.po:2528
2022-12-31 00:56:38 +02:00
12 changed files with 2070 additions and 789 deletions

View File

@ -14,6 +14,7 @@
## <p align="center">Supported Bot API version: <a href="https://core.telegram.org/bots/api#december-30-2022">6.4</a>! ## <p align="center">Supported Bot API version: <a href="https://core.telegram.org/bots/api#december-30-2022">6.4</a>!
<h2><a href='https://pytba.readthedocs.io/en/latest/index.html'>Official documentation</a></h2> <h2><a href='https://pytba.readthedocs.io/en/latest/index.html'>Official documentation</a></h2>
<h2><a href='https://pytba.readthedocs.io/ru/latest/index.html'>Official ru documentation</a></h2>
## Contents ## Contents

File diff suppressed because it is too large Load Diff

View File

@ -20,38 +20,40 @@ msgstr ""
#: ../../calldata.rst:4 #: ../../calldata.rst:4
msgid "Callback data factory" msgid "Callback data factory"
msgstr "" msgstr "Фабрика callback data"
#: ../../calldata.rst:6 #: ../../calldata.rst:6
msgid "Callback data factory in pyTelegramBotAPI" msgid "Callback data factory in pyTelegramBotAPI"
msgstr "" msgstr "Фабрика callback data в pyTelegramBotAPI"
#: ../../calldata.rst:6 #: ../../calldata.rst:6
msgid "" msgid ""
"ptba, pytba, pyTelegramBotAPI, callbackdatafactory, guide, callbackdata, " "ptba, pytba, pyTelegramBotAPI, callbackdatafactory, guide, callbackdata, "
"factory" "factory"
msgstr "" msgstr ""
"ptba, pytba, pyTelegramBotAPI, callbackdatafactory, гайд, callbackdata, "
"фабрика"
#: ../../calldata.rst:12 #: ../../calldata.rst:12
msgid "callback\\_data file" msgid "callback\\_data file"
msgstr "" msgstr "Файл callback\\_data"
#: of telebot.callback_data:1 #: of telebot.callback_data:1
msgid "Callback data factory's file." msgid "Callback data factory's file."
msgstr "" msgstr "Файл фабрики callback data."
#: of telebot.callback_data.CallbackData:1 #: of telebot.callback_data.CallbackData:1
#: telebot.callback_data.CallbackDataFilter:1 #: telebot.callback_data.CallbackDataFilter:1
msgid "Bases: :py:class:`object`" msgid "Bases: :py:class:`object`"
msgstr "" msgstr "Базовые классы: :py:class:`object`"
#: of telebot.callback_data.CallbackData:1 #: of telebot.callback_data.CallbackData:1
msgid "Callback data factory This class will help you to work with CallbackQuery" msgid "Callback data factory This class will help you to work with CallbackQuery"
msgstr "" msgstr "Фабрика Callback data. Этот класс поможет вам в работе с CallbackQuery"
#: of telebot.callback_data.CallbackData.filter:1 #: of telebot.callback_data.CallbackData.filter:1
msgid "Generate filter" msgid "Generate filter"
msgstr "" msgstr "Сгенерировать фильтр"
#: of telebot.callback_data.CallbackData.filter #: of telebot.callback_data.CallbackData.filter
#: telebot.callback_data.CallbackData.new #: telebot.callback_data.CallbackData.new
@ -62,7 +64,7 @@ msgstr ""
#: of telebot.callback_data.CallbackData.filter:3 #: of telebot.callback_data.CallbackData.filter:3
msgid "specified named parameters will be checked with CallbackQuery.data" msgid "specified named parameters will be checked with CallbackQuery.data"
msgstr "" msgstr "заданные именованные параметры будут проверены в CallbackQuery.data"
#: of telebot.callback_data.CallbackData.filter #: of telebot.callback_data.CallbackData.filter
#: telebot.callback_data.CallbackData.new #: telebot.callback_data.CallbackData.new
@ -73,19 +75,19 @@ msgstr ""
#: of telebot.callback_data.CallbackData.filter:4 #: of telebot.callback_data.CallbackData.filter:4
msgid "CallbackDataFilter class" msgid "CallbackDataFilter class"
msgstr "" msgstr "Класс CallbackDataFilter"
#: of telebot.callback_data.CallbackData.new:1 #: of telebot.callback_data.CallbackData.new:1
msgid "Generate callback data" msgid "Generate callback data"
msgstr "" msgstr "Сгенерировать callback data"
#: of telebot.callback_data.CallbackData.new:3 #: of telebot.callback_data.CallbackData.new:3
msgid "positional parameters of CallbackData instance parts" msgid "positional parameters of CallbackData instance parts"
msgstr "" msgstr "позиционные параметры экземпляра CallbackData"
#: of telebot.callback_data.CallbackData.new:4 #: of telebot.callback_data.CallbackData.new:4
msgid "named parameters" msgid "named parameters"
msgstr "" msgstr "именованные параметры"
#: of telebot.callback_data.CallbackData.new:5 #: of telebot.callback_data.CallbackData.new:5
msgid "str" msgid "str"
@ -93,25 +95,27 @@ msgstr ""
#: of telebot.callback_data.CallbackData.parse:1 #: of telebot.callback_data.CallbackData.parse:1
msgid "Parse data from the callback data" msgid "Parse data from the callback data"
msgstr "" msgstr "Получить данные из callback data"
#: of telebot.callback_data.CallbackData.parse:3 #: of telebot.callback_data.CallbackData.parse:3
msgid "" msgid ""
"string, use to telebot.types.CallbackQuery to parse it from string to a " "string, use to telebot.types.CallbackQuery to parse it from string to a "
"dict" "dict"
msgstr "" msgstr ""
"string, примените к telebot.types.CallbackQuery, чтобы преобразовать "
"callback_data из строки (str) в словарь (dict)"
#: of telebot.callback_data.CallbackData.parse:4 #: of telebot.callback_data.CallbackData.parse:4
msgid "dict parsed from callback data" msgid "dict parsed from callback data"
msgstr "" msgstr "словарь (dict), полученный из callback data"
#: of telebot.callback_data.CallbackDataFilter:1 #: of telebot.callback_data.CallbackDataFilter:1
msgid "Filter for CallbackData." msgid "Filter for CallbackData."
msgstr "" msgstr "Фильтр для CallbackData."
#: of telebot.callback_data.CallbackDataFilter.check:1 #: of telebot.callback_data.CallbackDataFilter.check:1
msgid "Checks if query.data appropriates to specified config" msgid "Checks if query.data appropriates to specified config"
msgstr "" msgstr "Проверяет, соответствует ли query.data заданной конфигурации"
#: of telebot.callback_data.CallbackDataFilter.check:3 #: of telebot.callback_data.CallbackDataFilter.check:3
msgid "telebot.types.CallbackQuery" msgid "telebot.types.CallbackQuery"
@ -119,9 +123,8 @@ msgstr ""
#: of telebot.callback_data.CallbackDataFilter.check:6 #: of telebot.callback_data.CallbackDataFilter.check:6
msgid "True if query.data appropriates to specified config" msgid "True if query.data appropriates to specified config"
msgstr "" msgstr "True, если query.data соответствует заданной конфигурации"
#: of telebot.callback_data.CallbackDataFilter.check #: of telebot.callback_data.CallbackDataFilter.check
msgid "Return type" msgid "Return type"
msgstr "" msgstr ""

View File

@ -20,15 +20,15 @@ msgstr ""
#: ../../index.rst:8 #: ../../index.rst:8
msgid "Welcome to pyTelegramBotAPI's documentation!" msgid "Welcome to pyTelegramBotAPI's documentation!"
msgstr "" msgstr "Добро пожаловать в документацию pyTelegramBotAPI!"
#: ../../index.rst:10 #: ../../index.rst:10
msgid "Official documentation of pyTelegramBotAPI" msgid "Official documentation of pyTelegramBotAPI"
msgstr "" msgstr "Официальная документация pyTelegramBotAPI"
#: ../../index.rst:10 #: ../../index.rst:10
msgid "ptba, pytba, pyTelegramBotAPI, documentation, guide" msgid "ptba, pytba, pyTelegramBotAPI, documentation, guide"
msgstr "" msgstr "ptba, pytba, pyTelegramBotAPI, документация, гайд"
#: ../../index.rst:17 #: ../../index.rst:17
msgid "TeleBot" msgid "TeleBot"
@ -39,26 +39,32 @@ msgid ""
"TeleBot is synchronous and asynchronous implementation of `Telegram Bot " "TeleBot is synchronous and asynchronous implementation of `Telegram Bot "
"API <https://core.telegram.org/bots/api>`_." "API <https://core.telegram.org/bots/api>`_."
msgstr "" msgstr ""
"TeleBot это синхронная и асинхронная реализация `Telegram Bot "
"API <https://core.telegram.org/bots/api>`_."
#: ../../index.rst:21 #: ../../index.rst:21
msgid "Chats" msgid "Chats"
msgstr "" msgstr "Чаты"
#: ../../index.rst:22 #: ../../index.rst:22
msgid "" msgid ""
"English chat: `Private chat " "English chat: `Private chat "
"<https://telegram.me/joinchat/Bn4ixj84FIZVkwhk2jag6A>`__" "<https://telegram.me/joinchat/Bn4ixj84FIZVkwhk2jag6A>`__"
msgstr "" msgstr ""
"Англоязычный чат: `Private chat "
"<https://telegram.me/joinchat/Bn4ixj84FIZVkwhk2jag6A>`__"
#: ../../index.rst:24 #: ../../index.rst:24
msgid "" msgid ""
"Russian chat: `@pytelegrambotapi_talks_ru " "Russian chat: `@pytelegrambotapi_talks_ru "
"<https://t.me/pytelegrambotapi_talks_ru>`__" "<https://t.me/pytelegrambotapi_talks_ru>`__"
msgstr "" msgstr ""
"Русскоязычный чат: `@pytelegrambotapi_talks_ru "
"<https://t.me/pytelegrambotapi_talks_ru>`__"
#: ../../index.rst:26 #: ../../index.rst:26
msgid "News: `@pyTelegramBotAPI <https://t.me/pytelegrambotapi>`__" msgid "News: `@pyTelegramBotAPI <https://t.me/pytelegrambotapi>`__"
msgstr "" msgstr "Новости: `@pyTelegramBotAPI <https://t.me/pytelegrambotapi>`__"
#: ../../index.rst:28 #: ../../index.rst:28
msgid "Pypi: `Pypi <https://pypi.org/project/pyTelegramBotAPI/>`__" msgid "Pypi: `Pypi <https://pypi.org/project/pyTelegramBotAPI/>`__"
@ -69,42 +75,44 @@ msgid ""
"Source: `Github repository " "Source: `Github repository "
"<https://github.com/eternnoir/pyTelegramBotAPI>`__" "<https://github.com/eternnoir/pyTelegramBotAPI>`__"
msgstr "" msgstr ""
"Исходники: `Github repository "
"<https://github.com/eternnoir/pyTelegramBotAPI>`__"
#: ../../index.rst:33 #: ../../index.rst:33
msgid "Some features:" msgid "Some features:"
msgstr "" msgstr "Некоторые особенности:"
#: ../../index.rst:34 #: ../../index.rst:34
msgid "Easy to learn and use." msgid "Easy to learn and use."
msgstr "" msgstr "Простой в изучении и использовании."
#: ../../index.rst:36 #: ../../index.rst:36
msgid "Easy to understand." msgid "Easy to understand."
msgstr "" msgstr "Простой в понимании."
#: ../../index.rst:38 #: ../../index.rst:38
msgid "Both sync and async." msgid "Both sync and async."
msgstr "" msgstr "И синхронный, и асинхронный."
#: ../../index.rst:40 #: ../../index.rst:40
msgid "Examples on features." msgid "Examples on features."
msgstr "" msgstr "Примеры возможностей."
#: ../../index.rst:42 #: ../../index.rst:42
msgid "States" msgid "States"
msgstr "" msgstr "Состояния (стейты, FSM)"
#: ../../index.rst:44 #: ../../index.rst:44
msgid "And more..." msgid "And more..."
msgstr "" msgstr "И другое..."
#: ../../index.rst:47 #: ../../index.rst:47
msgid "Content" msgid "Content"
msgstr "" msgstr "Содержимое"
#: ../../index.rst:63 #: ../../index.rst:63
msgid "Indices and tables" msgid "Indices and tables"
msgstr "" msgstr "Ссылки"
#: ../../index.rst:65 #: ../../index.rst:65
msgid ":ref:`genindex`" msgid ":ref:`genindex`"

File diff suppressed because it is too large Load Diff

View File

@ -4197,7 +4197,7 @@ class TeleBot:
def stop_poll( def stop_poll(
self, chat_id: Union[int, str], message_id: int, self, chat_id: Union[int, str], message_id: int,
reply_markup: Optional[REPLY_MARKUP_TYPES]=None) -> types.Poll: reply_markup: Optional[types.InlineKeyboardMarkup]=None) -> types.Poll:
""" """
Use this method to stop a poll which was sent by the bot. On success, the stopped Poll is returned. Use this method to stop a poll which was sent by the bot. On success, the stopped Poll is returned.
@ -4210,7 +4210,7 @@ class TeleBot:
:type message_id: :obj:`int` :type message_id: :obj:`int`
:param reply_markup: A JSON-serialized object for a new message markup. :param reply_markup: A JSON-serialized object for a new message markup.
:type reply_markup: :obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply` :type reply_markup: :obj:`InlineKeyboardMarkup`
:return: On success, the stopped Poll is returned. :return: On success, the stopped Poll is returned.
:rtype: :obj:`types.Poll` :rtype: :obj:`types.Poll`

View File

@ -5061,7 +5061,7 @@ class AsyncTeleBot:
async def stop_poll( async def stop_poll(
self, chat_id: Union[int, str], message_id: int, self, chat_id: Union[int, str], message_id: int,
reply_markup: Optional[REPLY_MARKUP_TYPES]=None) -> types.Poll: reply_markup: Optional[types.InlineKeyboardMarkup]=None) -> types.Poll:
""" """
Use this method to stop a poll which was sent by the bot. On success, the stopped Poll is returned. Use this method to stop a poll which was sent by the bot. On success, the stopped Poll is returned.
@ -5074,7 +5074,7 @@ class AsyncTeleBot:
:type message_id: :obj:`int` :type message_id: :obj:`int`
:param reply_markup: A JSON-serialized object for a new message markup. :param reply_markup: A JSON-serialized object for a new message markup.
:type reply_markup: :obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply` :type reply_markup: :obj:`InlineKeyboardMarkup`
:return: On success, the stopped Poll is returned. :return: On success, the stopped Poll is returned.
:rtype: :obj:`types.Poll` :rtype: :obj:`types.Poll`

View File

@ -28,7 +28,7 @@ class StatePickleStorage(StateStorageBase):
""" """
Create directory .save-handlers. Create directory .save-handlers.
""" """
dirs = self.file_path.rsplit('/', maxsplit=1)[0] dirs, filename = os.path.split(self.file_path)
os.makedirs(dirs, exist_ok=True) os.makedirs(dirs, exist_ok=True)
if not os.path.isfile(self.file_path): if not os.path.isfile(self.file_path):
with open(self.file_path,'wb') as file: with open(self.file_path,'wb') as file:

84
telebot/service_utils.py Normal file
View File

@ -0,0 +1,84 @@
import random
import string
from io import BytesIO
try:
# noinspection PyPackageRequirements
from PIL import Image
pil_imported = True
except ImportError:
pil_imported = False
def is_string(var) -> bool:
"""
Returns True if the given object is a string.
"""
return isinstance(var, str)
def is_dict(var) -> bool:
"""
Returns True if the given object is a dictionary.
:param var: object to be checked
:type var: :obj:`object`
:return: True if the given object is a dictionary.
:rtype: :obj:`bool`
"""
return isinstance(var, dict)
def is_bytes(var) -> bool:
"""
Returns True if the given object is a bytes object.
:param var: object to be checked
:type var: :obj:`object`
:return: True if the given object is a bytes object.
:rtype: :obj:`bool`
"""
return isinstance(var, bytes)
def is_pil_image(var) -> bool:
"""
Returns True if the given object is a PIL.Image.Image object.
:param var: object to be checked
:type var: :obj:`object`
:return: True if the given object is a PIL.Image.Image object.
:rtype: :obj:`bool`
"""
return pil_imported and isinstance(var, Image.Image)
def pil_image_to_file(image, extension='JPEG', quality='web_low'):
if pil_imported:
photoBuffer = BytesIO()
image.convert('RGB').save(photoBuffer, extension, quality=quality)
photoBuffer.seek(0)
return photoBuffer
else:
raise RuntimeError('PIL module is not imported')
def chunks(lst, n):
"""Yield successive n-sized chunks from lst."""
# https://stackoverflow.com/a/312464/9935473
for i in range(0, len(lst), n):
yield lst[i:i + n]
def generate_random_token() -> str:
"""
Generates a random token consisting of letters and digits, 16 characters long.
:return: a random token
:rtype: :obj:`str`
"""
return ''.join(random.sample(string.ascii_letters, 16))

View File

@ -34,7 +34,7 @@ class StatePickleStorage(StateStorageBase):
""" """
Create directory .save-handlers. Create directory .save-handlers.
""" """
dirs = self.file_path.rsplit('/', maxsplit=1)[0] dirs, filename = os.path.split(self.file_path)
os.makedirs(dirs, exist_ok=True) os.makedirs(dirs, exist_ok=True)
if not os.path.isfile(self.file_path): if not os.path.isfile(self.file_path):
with open(self.file_path,'wb') as file: with open(self.file_path,'wb') as file:

View File

@ -12,7 +12,7 @@ try:
except ImportError: except ImportError:
import json import json
from telebot import util from telebot import service_utils
DISABLE_KEYLEN_ERROR = False DISABLE_KEYLEN_ERROR = False
@ -87,9 +87,9 @@ class JsonDeserializable(object):
:param dict_copy: if dict is passed and it is changed outside - should be True! :param dict_copy: if dict is passed and it is changed outside - should be True!
:return: Dictionary parsed from json or original dict :return: Dictionary parsed from json or original dict
""" """
if util.is_dict(json_type): if service_utils.is_dict(json_type):
return json_type.copy() if dict_copy else json_type return json_type.copy() if dict_copy else json_type
elif util.is_string(json_type): elif service_utils.is_string(json_type):
return json.loads(json_type) return json.loads(json_type)
else: else:
raise ValueError("json_type should be a json dict or string.") raise ValueError("json_type should be a json dict or string.")
@ -2156,12 +2156,12 @@ class ReplyKeyboardMarkup(JsonSerializable):
logger.error('Telegram does not support reply keyboard row width over %d.' % self.max_row_keys) logger.error('Telegram does not support reply keyboard row width over %d.' % self.max_row_keys)
row_width = self.max_row_keys row_width = self.max_row_keys
for row in util.chunks(args, row_width): for row in service_utils.chunks(args, row_width):
button_array = [] button_array = []
for button in row: for button in row:
if util.is_string(button): if service_utils.is_string(button):
button_array.append({'text': button}) button_array.append({'text': button})
elif util.is_bytes(button): elif service_utils.is_bytes(button):
button_array.append({'text': button.decode('utf-8')}) button_array.append({'text': button.decode('utf-8')})
else: else:
button_array.append(button.to_dict()) button_array.append(button.to_dict())
@ -2278,12 +2278,12 @@ class InlineKeyboardMarkup(Dictionaryable, JsonSerializable, JsonDeserializable)
This object represents an inline keyboard that appears right next to the message it belongs to. This object represents an inline keyboard that appears right next to the message it belongs to.
.. note:: .. note::
It is recommended to use :meth:`telebot.util.quick_markup` instead. It is recommended to use :meth:`telebot.service_utils..quick_markup` instead.
.. code-block:: python3 .. code-block:: python3
:caption: Example of a custom keyboard with buttons. :caption: Example of a custom keyboard with buttons.
from telebot.util import quick_markup from telebot.service_utils..import quick_markup
markup = quick_markup( markup = quick_markup(
{'text': 'Press me', 'callback_data': 'press'}, {'text': 'Press me', 'callback_data': 'press'},
@ -2345,7 +2345,7 @@ class InlineKeyboardMarkup(Dictionaryable, JsonSerializable, JsonDeserializable)
logger.error('Telegram does not support inline keyboard row width over %d.' % self.max_row_keys) logger.error('Telegram does not support inline keyboard row width over %d.' % self.max_row_keys)
row_width = self.max_row_keys row_width = self.max_row_keys
for row in util.chunks(args, row_width): for row in service_utils.chunks(args, row_width):
button_array = [button for button in row] button_array = [button for button in row]
self.keyboard.append(button_array) self.keyboard.append(button_array)
@ -5786,11 +5786,11 @@ class InputMedia(Dictionaryable, JsonSerializable):
self.parse_mode: Optional[str] = parse_mode self.parse_mode: Optional[str] = parse_mode
self.caption_entities: Optional[List[MessageEntity]] = caption_entities self.caption_entities: Optional[List[MessageEntity]] = caption_entities
if util.is_string(self.media): if service_utils.is_string(self.media):
self._media_name = '' self._media_name = ''
self._media_dic = self.media self._media_dic = self.media
else: else:
self._media_name = util.generate_random_token() self._media_name = service_utils.generate_random_token()
self._media_dic = 'attach://{0}'.format(self._media_name) self._media_dic = 'attach://{0}'.format(self._media_name)
def to_json(self): def to_json(self):
@ -5810,7 +5810,7 @@ class InputMedia(Dictionaryable, JsonSerializable):
""" """
:meta private: :meta private:
""" """
if util.is_string(self.media): if service_utils.is_string(self.media):
return self.to_json(), None return self.to_json(), None
return self.to_json(), {self._media_name: self.media} return self.to_json(), {self._media_name: self.media}
@ -5845,8 +5845,8 @@ class InputMediaPhoto(InputMedia):
:rtype: :class:`telebot.types.InputMediaPhoto` :rtype: :class:`telebot.types.InputMediaPhoto`
""" """
def __init__(self, media, caption=None, parse_mode=None, caption_entities=None, has_spoiler=None): def __init__(self, media, caption=None, parse_mode=None, caption_entities=None, has_spoiler=None):
if util.is_pil_image(media): if service_utils.is_pil_image(media):
media = util.pil_image_to_file(media) media = service_utils.pil_image_to_file(media)
super(InputMediaPhoto, self).__init__( super(InputMediaPhoto, self).__init__(
type="photo", media=media, caption=caption, parse_mode=parse_mode, caption_entities=caption_entities) type="photo", media=media, caption=caption, parse_mode=parse_mode, caption_entities=caption_entities)

View File

@ -1,7 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import random
import re import re
import string
import threading import threading
import traceback import traceback
from typing import Any, Callable, List, Dict, Optional, Union from typing import Any, Callable, List, Dict, Optional, Union
@ -14,21 +12,13 @@ import queue as Queue
import logging import logging
from telebot import types from telebot import types
from telebot.service_utils import is_pil_image, is_dict, is_string, is_bytes, chunks, generate_random_token, pil_image_to_file
try: try:
import ujson as json import ujson as json
except ImportError: except ImportError:
import json import json
try:
# noinspection PyPackageRequirements
from PIL import Image
from io import BytesIO
pil_imported = True
except:
pil_imported = False
MAX_MESSAGE_LENGTH = 4096 MAX_MESSAGE_LENGTH = 4096
logger = logging.getLogger('TeleBot') logger = logging.getLogger('TeleBot')
@ -44,7 +34,8 @@ content_type_media = [
#: Contains all service content types such as `User joined the group`. #: Contains all service content types such as `User joined the group`.
content_type_service = [ content_type_service = [
'new_chat_members', 'left_chat_member', 'new_chat_title', 'new_chat_photo', 'delete_chat_photo', 'group_chat_created', 'new_chat_members', 'left_chat_member', 'new_chat_title', 'new_chat_photo', 'delete_chat_photo',
'group_chat_created',
'supergroup_chat_created', 'channel_chat_created', 'migrate_to_chat_id', 'migrate_from_chat_id', 'pinned_message', 'supergroup_chat_created', 'channel_chat_created', 'migrate_to_chat_id', 'migrate_from_chat_id', 'pinned_message',
'proximity_alert_triggered', 'video_chat_scheduled', 'video_chat_started', 'video_chat_ended', 'proximity_alert_triggered', 'video_chat_scheduled', 'video_chat_started', 'video_chat_ended',
'video_chat_participants_invited', 'message_auto_delete_timer_changed', 'forum_topic_created', 'forum_topic_closed', 'video_chat_participants_invited', 'message_auto_delete_timer_changed', 'forum_topic_created', 'forum_topic_closed',
@ -129,6 +120,7 @@ class ThreadPool:
""" """
:meta private: :meta private:
""" """
def __init__(self, telebot, num_threads=2): def __init__(self, telebot, num_threads=2):
self.telebot = telebot self.telebot = telebot
self.tasks = Queue.Queue() self.tasks = Queue.Queue()
@ -169,6 +161,7 @@ class AsyncTask:
""" """
:meta private: :meta private:
""" """
def __init__(self, target, *args, **kwargs): def __init__(self, target, *args, **kwargs):
self.target = target self.target = target
self.args = args self.args = args
@ -198,7 +191,8 @@ class CustomRequestResponse():
""" """
:meta private: :meta private:
""" """
def __init__(self, json_text, status_code = 200, reason = ""):
def __init__(self, json_text, status_code=200, reason=""):
self.status_code = status_code self.status_code = status_code
self.text = json_text self.text = json_text
self.reason = reason self.reason = reason
@ -211,6 +205,7 @@ def async_dec():
""" """
:meta private: :meta private:
""" """
def decorator(fn): def decorator(fn):
def wrapper(*args, **kwargs): def wrapper(*args, **kwargs):
return AsyncTask(fn, *args, **kwargs) return AsyncTask(fn, *args, **kwargs)
@ -220,63 +215,6 @@ def async_dec():
return decorator return decorator
def is_string(var) -> bool:
"""
Returns True if the given object is a string.
"""
return isinstance(var, str)
def is_dict(var) -> bool:
"""
Returns True if the given object is a dictionary.
:param var: object to be checked
:type var: :obj:`object`
:return: True if the given object is a dictionary.
:rtype: :obj:`bool`
"""
return isinstance(var, dict)
def is_bytes(var) -> bool:
"""
Returns True if the given object is a bytes object.
:param var: object to be checked
:type var: :obj:`object`
:return: True if the given object is a bytes object.
:rtype: :obj:`bool`
"""
return isinstance(var, bytes)
def is_pil_image(var) -> bool:
"""
Returns True if the given object is a PIL.Image.Image object.
:param var: object to be checked
:type var: :obj:`object`
:return: True if the given object is a PIL.Image.Image object.
:rtype: :obj:`bool`
"""
return pil_imported and isinstance(var, Image.Image)
def pil_image_to_file(image, extension='JPEG', quality='web_low'):
if pil_imported:
photoBuffer = BytesIO()
image.convert('RGB').save(photoBuffer, extension, quality=quality)
photoBuffer.seek(0)
return photoBuffer
else:
raise RuntimeError('PIL module is not imported')
def is_command(text: str) -> bool: def is_command(text: str) -> bool:
r""" r"""
Checks if `text` is a command. Telegram chat commands start with the '/' character. Checks if `text` is a command. Telegram chat commands start with the '/' character.
@ -353,7 +291,7 @@ def split_string(text: str, chars_per_string: int) -> List[str]:
return [text[i:i + chars_per_string] for i in range(0, len(text), chars_per_string)] return [text[i:i + chars_per_string] for i in range(0, len(text), chars_per_string)]
def smart_split(text: str, chars_per_string: int=MAX_MESSAGE_LENGTH) -> List[str]: def smart_split(text: str, chars_per_string: int = MAX_MESSAGE_LENGTH) -> List[str]:
r""" r"""
Splits one string into multiple strings, with a maximum amount of `chars_per_string` characters per string. Splits one string into multiple strings, with a maximum amount of `chars_per_string` characters per string.
This is very useful for splitting one giant message into multiples. This is very useful for splitting one giant message into multiples.
@ -383,9 +321,12 @@ def smart_split(text: str, chars_per_string: int=MAX_MESSAGE_LENGTH) -> List[str
part = text[:chars_per_string] part = text[:chars_per_string]
if "\n" in part: part = _text_before_last("\n") if "\n" in part:
elif ". " in part: part = _text_before_last(". ") part = _text_before_last("\n")
elif " " in part: part = _text_before_last(" ") elif ". " in part:
part = _text_before_last(". ")
elif " " in part:
part = _text_before_last(" ")
parts.append(part) parts.append(part)
text = text[len(part):] text = text[len(part):]
@ -401,12 +342,12 @@ def escape(text: str) -> str:
chars = {"&": "&amp;", "<": "&lt;", ">": "&gt;"} chars = {"&": "&amp;", "<": "&lt;", ">": "&gt;"}
if text is None: if text is None:
return None return None
for old, new in chars.items(): for old, new in chars.items():
text = text.replace(old, new) text = text.replace(old, new)
return text return text
def user_link(user: types.User, include_id: bool=False) -> str: def user_link(user: types.User, include_id: bool = False) -> str:
""" """
Returns an HTML user link. This is useful for reports. Returns an HTML user link. This is useful for reports.
Attention: Don't forget to set parse_mode to 'HTML'! Attention: Don't forget to set parse_mode to 'HTML'!
@ -433,10 +374,10 @@ def user_link(user: types.User, include_id: bool=False) -> str:
""" """
name = escape(user.first_name) name = escape(user.first_name)
return (f"<a href='tg://user?id={user.id}'>{name}</a>" return (f"<a href='tg://user?id={user.id}'>{name}</a>"
+ (f" (<pre>{user.id}</pre>)" if include_id else "")) + (f" (<pre>{user.id}</pre>)" if include_id else ""))
def quick_markup(values: Dict[str, Dict[str, Any]], row_width: int=2) -> types.InlineKeyboardMarkup: def quick_markup(values: Dict[str, Dict[str, Any]], row_width: int = 2) -> types.InlineKeyboardMarkup:
""" """
Returns a reply markup from a dict in this format: {'text': kwargs} Returns a reply markup from a dict in this format: {'text': kwargs}
This is useful to avoid always typing 'btn1 = InlineKeyboardButton(...)' 'btn2 = InlineKeyboardButton(...)' This is useful to avoid always typing 'btn1 = InlineKeyboardButton(...)' 'btn2 = InlineKeyboardButton(...)'
@ -551,24 +492,7 @@ def per_thread(key, construct_value, reset=False):
return getattr(thread_local, key) return getattr(thread_local, key)
def chunks(lst, n): def deprecated(warn: bool = True, alternative: Optional[Callable] = None, deprecation_text=None):
"""Yield successive n-sized chunks from lst."""
# https://stackoverflow.com/a/312464/9935473
for i in range(0, len(lst), n):
yield lst[i:i + n]
def generate_random_token() -> str:
"""
Generates a random token consisting of letters and digits, 16 characters long.
:return: a random token
:rtype: :obj:`str`
"""
return ''.join(random.sample(string.ascii_letters, 16))
def deprecated(warn: bool=True, alternative: Optional[Callable]=None, deprecation_text=None):
""" """
Use this decorator to mark functions as deprecated. Use this decorator to mark functions as deprecated.
When the function is used, an info (or warning if `warn` is True) is logged. When the function is used, an info (or warning if `warn` is True) is logged.
@ -586,6 +510,7 @@ def deprecated(warn: bool=True, alternative: Optional[Callable]=None, deprecatio
:return: The decorated function :return: The decorated function
""" """
def decorator(function): def decorator(function):
def wrapper(*args, **kwargs): def wrapper(*args, **kwargs):
info = f"`{function.__name__}` is deprecated." info = f"`{function.__name__}` is deprecated."
@ -598,7 +523,9 @@ def deprecated(warn: bool=True, alternative: Optional[Callable]=None, deprecatio
else: else:
logger.warning(info) logger.warning(info)
return function(*args, **kwargs) return function(*args, **kwargs)
return wrapper return wrapper
return decorator return decorator
@ -661,8 +588,8 @@ def antiflood(function: Callable, *args, **kwargs):
return function(*args, **kwargs) return function(*args, **kwargs)
else: else:
raise raise
def parse_web_app_data(token: str, raw_init_data: str): def parse_web_app_data(token: str, raw_init_data: str):
""" """
Parses web app data. Parses web app data.
@ -715,4 +642,16 @@ def validate_web_app_data(token: str, raw_init_data: str):
return hmac.new(secret_key.digest(), data_check_string.encode(), sha256).hexdigest() == init_data_hash return hmac.new(secret_key.digest(), data_check_string.encode(), sha256).hexdigest() == init_data_hash
__all__ = (
"content_type_media", "content_type_service", "update_types",
"WorkerThread", "AsyncTask", "CustomRequestResponse",
"async_dec", "deprecated",
"is_bytes", "is_string", "is_dict", "is_pil_image",
"chunks", "generate_random_token", "pil_image_to_file",
"is_command", "extract_command", "extract_arguments",
"split_string", "smart_split", "escape", "user_link", "quick_markup",
"antiflood", "parse_web_app_data", "validate_web_app_data",
"or_set", "or_clear", "orify", "OrEvent", "per_thread",
"webhook_google_functions"
)