diff --git a/bot.py b/bot.py index 237e37f..094c8fd 100644 --- a/bot.py +++ b/bot.py @@ -3,17 +3,20 @@ # text bot at t.me/projektaktienbot # API Documentation https://core.telegram.org/bots/api # Code examples https://github.com/eternnoir/pyTelegramBotAPI#getting-started - +import os import telebot -bot = telebot.TeleBot("5228016873:AAGFrh0P6brag7oD3gxXjCh5gnLLE8JMvMs") +bot = telebot.TeleBot(os.getenv('BOT_API_KEY')) + @bot.message_handler(commands=['start', 'help']) def send_welcome(message): - bot.reply_to(message, "Thank you for using this bot") + bot.reply_to(message, "Thank you for using this bot") + @bot.message_handler(func=lambda message: True) def echo_all(message): - bot.reply_to(message, message.text) + bot.reply_to(message, message.text) -bot.infinity_polling() \ No newline at end of file + +bot.infinity_polling() diff --git a/requirements.txt b/requirements.txt index d4e3341..d7858ae 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ -Flask==2.0.2 +Flask~=2.0.3 python-dotenv==0.19.2 requests==2.27.1 uwsgi==2.0.20 +pyTelegramBotAPI~=4.4.0 \ No newline at end of file diff --git a/telebot/__init__.py b/telebot/__init__.py deleted file mode 100644 index 9d4fde3..0000000 --- a/telebot/__init__.py +++ /dev/null @@ -1,3887 +0,0 @@ -# -*- coding: utf-8 -*- -from datetime import datetime - -import logging -import re -import sys -import threading -import time -import traceback -from typing import Any, Callable, List, Optional, Union - -# these imports are used to avoid circular import error -import telebot.util -import telebot.types - -# storage -from telebot.storage import StatePickleStorage, StateMemoryStorage - - - -logger = logging.getLogger('TeleBot') - -formatter = logging.Formatter( - '%(asctime)s (%(filename)s:%(lineno)d %(threadName)s) %(levelname)s - %(name)s: "%(message)s"' -) - -import inspect - -console_output_handler = logging.StreamHandler(sys.stderr) -console_output_handler.setFormatter(formatter) -logger.addHandler(console_output_handler) - -logger.setLevel(logging.ERROR) - -from telebot import apihelper, util, types -from telebot.handler_backends import MemoryHandlerBackend, FileHandlerBackend, BaseMiddleware, CancelUpdate, SkipHandler -from telebot.custom_filters import SimpleCustomFilter, AdvancedCustomFilter - - -REPLY_MARKUP_TYPES = Union[ - types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, - types.ReplyKeyboardRemove, types.ForceReply] - - -""" -Module : telebot -""" - - -class Handler: - """ - Class for (next step|reply) handlers - """ - - def __init__(self, callback, *args, **kwargs): - self.callback = callback - self.args = args - self.kwargs = kwargs - - def __getitem__(self, item): - return getattr(self, item) - - -class ExceptionHandler: - """ - Class for handling exceptions while Polling - """ - - # noinspection PyMethodMayBeStatic,PyUnusedLocal - def handle(self, exception): - return False - - -class TeleBot: - """ - This is the main synchronous class for Bot. - - It allows you to add handlers for different kind of updates. - - Usage: - - .. code-block:: python - - from telebot import TeleBot - bot = TeleBot('token') # get token from @BotFather - - See more examples in examples/ directory: - https://github.com/eternnoir/pyTelegramBotAPI/tree/master/examples - - """ - - def __init__( - self, token, parse_mode=None, threaded=True, skip_pending=False, num_threads=2, - next_step_backend=None, reply_backend=None, exception_handler=None, last_update_id=0, - suppress_middleware_excepions=False, state_storage=StateMemoryStorage(), use_class_middlewares=False - ): - """ - :param token: bot API token - :param parse_mode: default parse_mode - :return: Telebot object. - """ - self.token = token - self.parse_mode = parse_mode - self.update_listener = [] - self.skip_pending = skip_pending - self.suppress_middleware_excepions = suppress_middleware_excepions - - self.__stop_polling = threading.Event() - self.last_update_id = last_update_id - self.exc_info = None - - self.next_step_backend = next_step_backend - if not self.next_step_backend: - self.next_step_backend = MemoryHandlerBackend() - - self.reply_backend = reply_backend - if not self.reply_backend: - self.reply_backend = MemoryHandlerBackend() - - self.exception_handler = exception_handler - - self.message_handlers = [] - self.edited_message_handlers = [] - self.channel_post_handlers = [] - self.edited_channel_post_handlers = [] - self.inline_handlers = [] - self.chosen_inline_handlers = [] - self.callback_query_handlers = [] - self.shipping_query_handlers = [] - self.pre_checkout_query_handlers = [] - self.poll_handlers = [] - self.poll_answer_handlers = [] - self.my_chat_member_handlers = [] - self.chat_member_handlers = [] - self.chat_join_request_handlers = [] - self.custom_filters = {} - self.state_handlers = [] - - self.current_states = state_storage - - self.use_class_middlewares = use_class_middlewares - if apihelper.ENABLE_MIDDLEWARE and not use_class_middlewares: - self.typed_middleware_handlers = { - 'message': [], - 'edited_message': [], - 'channel_post': [], - 'edited_channel_post': [], - 'inline_query': [], - 'chosen_inline_result': [], - 'callback_query': [], - 'shipping_query': [], - 'pre_checkout_query': [], - 'poll': [], - 'poll_answer': [], - 'my_chat_member': [], - 'chat_member': [], - 'chat_join_request': [] - } - self.default_middleware_handlers = [] - if apihelper.ENABLE_MIDDLEWARE and use_class_middlewares: - logger.warning( - 'You are using class based middlewares, but you have ' - 'ENABLE_MIDDLEWARE set to True. This is not recommended.' - ) - self.middlewares = [] if use_class_middlewares else None - - - self.threaded = threaded - if self.threaded: - self.worker_pool = util.ThreadPool(self, num_threads=num_threads) - - @property - def user(self) -> types.User: - """ - The User object representing this bot. - Equivalent to bot.get_me() but the result is cached so only one API call is needed - """ - if not hasattr(self, "_user"): - self._user = self.get_me() - return self._user - - def enable_save_next_step_handlers(self, delay=120, filename="./.handler-saves/step.save"): - """ - Enable saving next step handlers (by default saving disabled) - - This function explicitly assigns FileHandlerBackend (instead of Saver) just to keep backward - compatibility whose purpose was to enable file saving capability for handlers. And the same - implementation is now available with FileHandlerBackend - - Most probably this function should be deprecated in future major releases - - :param delay: Delay between changes in handlers and saving - :param filename: Filename of save file - """ - self.next_step_backend = FileHandlerBackend(self.next_step_backend.handlers, filename, delay) - - def enable_saving_states(self, filename="./.state-save/states.pkl"): - """ - Enable saving states (by default saving disabled) - - :param filename: Filename of saving file - """ - self.current_states = StatePickleStorage(file_path=filename) - self.current_states.create_dir() - - def enable_save_reply_handlers(self, delay=120, filename="./.handler-saves/reply.save"): - """ - Enable saving reply handlers (by default saving disable) - - This function explicitly assigns FileHandlerBackend (instead of Saver) just to keep backward - compatibility whose purpose was to enable file saving capability for handlers. And the same - implementation is now available with FileHandlerBackend - - Most probably this function should be deprecated in future major releases - - :param delay: Delay between changes in handlers and saving - :param filename: Filename of save file - """ - self.reply_backend = FileHandlerBackend(self.reply_backend.handlers, filename, delay) - - def disable_save_next_step_handlers(self): - """ - Disable saving next step handlers (by default saving disable) - - This function is left to keep backward compatibility whose purpose was to disable file saving capability - for handlers. For the same purpose, MemoryHandlerBackend is reassigned as a new next_step_backend backend - instead of FileHandlerBackend. - - Most probably this function should be deprecated in future major releases - """ - self.next_step_backend = MemoryHandlerBackend(self.next_step_backend.handlers) - - def disable_save_reply_handlers(self): - """ - Disable saving next step handlers (by default saving disable) - - This function is left to keep backward compatibility whose purpose was to disable file saving capability - for handlers. For the same purpose, MemoryHandlerBackend is reassigned as a new reply_backend backend - instead of FileHandlerBackend. - - Most probably this function should be deprecated in future major releases - """ - self.reply_backend = MemoryHandlerBackend(self.reply_backend.handlers) - - def load_next_step_handlers(self, filename="./.handler-saves/step.save", del_file_after_loading=True): - """ - Load next step handlers from save file - - This function is left to keep backward compatibility whose purpose was to load handlers from file with the - help of FileHandlerBackend and is only recommended to use if next_step_backend was assigned as - FileHandlerBackend before entering this function - - Most probably this function should be deprecated in future major releases - - :param filename: Filename of the file where handlers was saved - :param del_file_after_loading: Is passed True, after loading save file will be deleted - """ - self.next_step_backend.load_handlers(filename, del_file_after_loading) - - def load_reply_handlers(self, filename="./.handler-saves/reply.save", del_file_after_loading=True): - """ - Load reply handlers from save file - - This function is left to keep backward compatibility whose purpose was to load handlers from file with the - help of FileHandlerBackend and is only recommended to use if reply_backend was assigned as - FileHandlerBackend before entering this function - - Most probably this function should be deprecated in future major releases - - :param filename: Filename of the file where handlers was saved - :param del_file_after_loading: Is passed True, after loading save file will be deleted - """ - self.reply_backend.load_handlers(filename, del_file_after_loading) - - def set_webhook(self, url=None, certificate=None, max_connections=None, allowed_updates=None, ip_address=None, - drop_pending_updates = None, timeout=None): - """ - Use this method to specify a url and receive incoming updates via an outgoing webhook. Whenever there is an - update for the bot, we will send an HTTPS POST request to the specified url, - containing a JSON-serialized Update. - In case of an unsuccessful request, we will give up after a reasonable amount of attempts. - Returns True on success. - - Telegram documentation: https://core.telegram.org/bots/api#setwebhook - - :param url: HTTPS url to send updates to. Use an empty string to remove webhook integration - :param certificate: Upload your public key certificate so that the root certificate in use can be checked. - See our self-signed guide for details. - :param max_connections: Maximum allowed number of simultaneous HTTPS connections to the webhook - for update delivery, 1-100. Defaults to 40. Use lower values to limit the load on your bot's server, - and higher values to increase your bot's throughput. - :param allowed_updates: A JSON-serialized list of the update types you want your bot to receive. - For example, specify [“message”, “edited_channel_post”, “callback_query”] to only receive updates - of these types. See Update for a complete list of available update types. - Specify an empty list to receive all updates regardless of type (default). - If not specified, the previous setting will be used. - :param ip_address: The fixed IP address which will be used to send webhook requests instead of the IP address - resolved through DNS - :param drop_pending_updates: Pass True to drop all pending updates - :param timeout: Integer. Request connection timeout - :return: API reply. - """ - return apihelper.set_webhook(self.token, url, certificate, max_connections, allowed_updates, ip_address, - drop_pending_updates, timeout) - - def delete_webhook(self, drop_pending_updates=None, timeout=None): - """ - Use this method to remove webhook integration if you decide to switch back to getUpdates. - - Telegram documentation: https://core.telegram.org/bots/api#deletewebhook - - :param drop_pending_updates: Pass True to drop all pending updates - :param timeout: Integer. Request connection timeout - :return: bool - """ - return apihelper.delete_webhook(self.token, drop_pending_updates, timeout) - - def get_webhook_info(self, timeout: Optional[int]=None): - """ - Use this method to get current webhook status. Requires no parameters. - If the bot is using getUpdates, will return an object with the url field empty. - - Telegram documentation: https://core.telegram.org/bots/api#getwebhookinfo - - :param timeout: Integer. Request connection timeout - :return: On success, returns a WebhookInfo object. - """ - result = apihelper.get_webhook_info(self.token, timeout) - return types.WebhookInfo.de_json(result) - - def remove_webhook(self): - return self.set_webhook() # No params resets webhook - - def get_updates(self, offset: Optional[int]=None, limit: Optional[int]=None, - timeout: Optional[int]=20, allowed_updates: Optional[List[str]]=None, - long_polling_timeout: int=20) -> List[types.Update]: - """ - Use this method to receive incoming updates using long polling (wiki). An Array of Update objects is returned. - - Telegram documentation: https://core.telegram.org/bots/api#getupdates - - :param allowed_updates: Array of string. List the types of updates you want your bot to receive. - :param offset: Integer. Identifier of the first update to be returned. - :param limit: Integer. Limits the number of updates to be retrieved. - :param timeout: Integer. Request connection timeout - :param long_polling_timeout: Timeout in seconds for long polling. - :return: array of Updates - """ - json_updates = apihelper.get_updates(self.token, offset, limit, timeout, allowed_updates, long_polling_timeout) - return [types.Update.de_json(ju) for ju in json_updates] - - def __skip_updates(self): - """ - Get and discard all pending updates before first poll of the bot - - :return: - """ - self.get_updates(offset=-1) - - def __retrieve_updates(self, timeout=20, long_polling_timeout=20, allowed_updates=None): - """ - Retrieves any updates from the Telegram API. - Registered listeners and applicable message handlers will be notified when a new message arrives. - - :raises ApiException when a call has failed. - """ - if self.skip_pending: - self.__skip_updates() - logger.debug('Skipped all pending messages') - self.skip_pending = False - updates = self.get_updates(offset=(self.last_update_id + 1), - allowed_updates=allowed_updates, - timeout=timeout, long_polling_timeout=long_polling_timeout) - self.process_new_updates(updates) - - def process_new_updates(self, updates): - """ - Processes new updates. Just pass list of subclasses of Update to this method. - - :param updates: List of Update objects - """ - upd_count = len(updates) - logger.debug('Received {0} new updates'.format(upd_count)) - if upd_count == 0: return - - new_messages = None - new_edited_messages = None - new_channel_posts = None - new_edited_channel_posts = None - new_inline_queries = None - new_chosen_inline_results = None - new_callback_queries = None - new_shipping_queries = None - new_pre_checkout_queries = None - new_polls = None - new_poll_answers = None - new_my_chat_members = None - new_chat_members = None - chat_join_request = None - - for update in updates: - if apihelper.ENABLE_MIDDLEWARE: - try: - self.process_middlewares(update) - except Exception as e: - logger.error(str(e)) - if not self.suppress_middleware_excepions: - raise - else: - if update.update_id > self.last_update_id: self.last_update_id = update.update_id - continue - - - if update.update_id > self.last_update_id: - self.last_update_id = update.update_id - if update.message: - if new_messages is None: new_messages = [] - new_messages.append(update.message) - if update.edited_message: - if new_edited_messages is None: new_edited_messages = [] - new_edited_messages.append(update.edited_message) - if update.channel_post: - if new_channel_posts is None: new_channel_posts = [] - new_channel_posts.append(update.channel_post) - if update.edited_channel_post: - if new_edited_channel_posts is None: new_edited_channel_posts = [] - new_edited_channel_posts.append(update.edited_channel_post) - if update.inline_query: - if new_inline_queries is None: new_inline_queries = [] - new_inline_queries.append(update.inline_query) - if update.chosen_inline_result: - if new_chosen_inline_results is None: new_chosen_inline_results = [] - new_chosen_inline_results.append(update.chosen_inline_result) - if update.callback_query: - if new_callback_queries is None: new_callback_queries = [] - new_callback_queries.append(update.callback_query) - if update.shipping_query: - if new_shipping_queries is None: new_shipping_queries = [] - new_shipping_queries.append(update.shipping_query) - if update.pre_checkout_query: - if new_pre_checkout_queries is None: new_pre_checkout_queries = [] - new_pre_checkout_queries.append(update.pre_checkout_query) - if update.poll: - if new_polls is None: new_polls = [] - new_polls.append(update.poll) - if update.poll_answer: - if new_poll_answers is None: new_poll_answers = [] - new_poll_answers.append(update.poll_answer) - if update.my_chat_member: - if new_my_chat_members is None: new_my_chat_members = [] - new_my_chat_members.append(update.my_chat_member) - if update.chat_member: - if new_chat_members is None: new_chat_members = [] - new_chat_members.append(update.chat_member) - if update.chat_join_request: - if chat_join_request is None: chat_join_request = [] - chat_join_request.append(update.chat_join_request) - - if new_messages: - self.process_new_messages(new_messages) - if new_edited_messages: - self.process_new_edited_messages(new_edited_messages) - if new_channel_posts: - self.process_new_channel_posts(new_channel_posts) - if new_edited_channel_posts: - self.process_new_edited_channel_posts(new_edited_channel_posts) - if new_inline_queries: - self.process_new_inline_query(new_inline_queries) - if new_chosen_inline_results: - self.process_new_chosen_inline_query(new_chosen_inline_results) - if new_callback_queries: - self.process_new_callback_query(new_callback_queries) - if new_shipping_queries: - self.process_new_shipping_query(new_shipping_queries) - if new_pre_checkout_queries: - self.process_new_pre_checkout_query(new_pre_checkout_queries) - if new_polls: - self.process_new_poll(new_polls) - if new_poll_answers: - self.process_new_poll_answer(new_poll_answers) - if new_my_chat_members: - self.process_new_my_chat_member(new_my_chat_members) - if new_chat_members: - self.process_new_chat_member(new_chat_members) - if chat_join_request: - self.process_new_chat_join_request(chat_join_request) - - def process_new_messages(self, new_messages): - self._notify_next_handlers(new_messages) - self._notify_reply_handlers(new_messages) - self.__notify_update(new_messages) - self._notify_command_handlers(self.message_handlers, new_messages, 'message') - - def process_new_edited_messages(self, edited_message): - self._notify_command_handlers(self.edited_message_handlers, edited_message, 'edited_message') - - def process_new_channel_posts(self, channel_post): - self._notify_command_handlers(self.channel_post_handlers, channel_post, 'channel_post') - - def process_new_edited_channel_posts(self, edited_channel_post): - self._notify_command_handlers(self.edited_channel_post_handlers, edited_channel_post, 'edited_channel_post') - - def process_new_inline_query(self, new_inline_querys): - self._notify_command_handlers(self.inline_handlers, new_inline_querys, 'inline_query') - - def process_new_chosen_inline_query(self, new_chosen_inline_querys): - self._notify_command_handlers(self.chosen_inline_handlers, new_chosen_inline_querys, 'chosen_inline_query') - - def process_new_callback_query(self, new_callback_querys): - self._notify_command_handlers(self.callback_query_handlers, new_callback_querys, 'callback_query') - - def process_new_shipping_query(self, new_shipping_querys): - self._notify_command_handlers(self.shipping_query_handlers, new_shipping_querys, 'shipping_query') - - def process_new_pre_checkout_query(self, pre_checkout_querys): - self._notify_command_handlers(self.pre_checkout_query_handlers, pre_checkout_querys, 'pre_checkout_query') - - def process_new_poll(self, polls): - self._notify_command_handlers(self.poll_handlers, polls, 'poll') - - def process_new_poll_answer(self, poll_answers): - self._notify_command_handlers(self.poll_answer_handlers, poll_answers, 'poll_answer') - - def process_new_my_chat_member(self, my_chat_members): - self._notify_command_handlers(self.my_chat_member_handlers, my_chat_members, 'my_chat_member') - - def process_new_chat_member(self, chat_members): - self._notify_command_handlers(self.chat_member_handlers, chat_members, 'chat_member') - - def process_new_chat_join_request(self, chat_join_request): - self._notify_command_handlers(self.chat_join_request_handlers, chat_join_request, 'chat_join_request') - - def process_middlewares(self, update): - for update_type, middlewares in self.typed_middleware_handlers.items(): - if getattr(update, update_type) is not None: - for typed_middleware_handler in middlewares: - try: - typed_middleware_handler(self, getattr(update, update_type)) - except Exception as e: - e.args = e.args + (f'Typed middleware handler "{typed_middleware_handler.__qualname__}"',) - raise - - if len(self.default_middleware_handlers) > 0: - for default_middleware_handler in self.default_middleware_handlers: - try: - default_middleware_handler(self, update) - except Exception as e: - e.args = e.args + (f'Default middleware handler "{default_middleware_handler.__qualname__}"',) - raise - - def __notify_update(self, new_messages): - if len(self.update_listener) == 0: - return - for listener in self.update_listener: - self._exec_task(listener, new_messages) - - def infinity_polling(self, timeout: int=20, skip_pending: bool=False, long_polling_timeout: int=20, logger_level=logging.ERROR, - allowed_updates: Optional[List[str]]=None, *args, **kwargs): - """ - Wrap polling with infinite loop and exception handling to avoid bot stops polling. - - :param timeout: Request connection timeout - :param long_polling_timeout: Timeout in seconds for long polling (see API docs) - :param skip_pending: skip old updates - :param logger_level: Custom logging level for infinity_polling logging. - Use logger levels from logging as a value. None/NOTSET = no error logging - :param allowed_updates: A list of the update types you want your bot to receive. - For example, specify [“message”, “edited_channel_post”, “callback_query”] to only receive updates of these types. - See util.update_types for a complete list of available update types. - Specify an empty list to receive all update types except chat_member (default). - If not specified, the previous setting will be used. - - Please note that this parameter doesn't affect updates created before the call to the get_updates, - so unwanted updates may be received for a short period of time. - """ - if skip_pending: - self.__skip_updates() - - while not self.__stop_polling.is_set(): - try: - self.polling(none_stop=True, timeout=timeout, long_polling_timeout=long_polling_timeout, - allowed_updates=allowed_updates, *args, **kwargs) - except Exception as e: - if logger_level and logger_level >= logging.ERROR: - logger.error("Infinity polling exception: %s", str(e)) - if logger_level and logger_level >= logging.DEBUG: - logger.error("Exception traceback:\n%s", traceback.format_exc()) - time.sleep(3) - continue - if logger_level and logger_level >= logging.INFO: - logger.error("Infinity polling: polling exited") - if logger_level and logger_level >= logging.INFO: - logger.error("Break infinity polling") - - def polling(self, non_stop: bool=False, skip_pending=False, interval: int=0, timeout: int=20, - long_polling_timeout: int=20, allowed_updates: Optional[List[str]]=None, - none_stop: Optional[bool]=None): - """ - This function creates a new Thread that calls an internal __retrieve_updates function. - This allows the bot to retrieve Updates automagically and notify listeners and message handlers accordingly. - - Warning: Do not call this function more than once! - - Always get updates. - - :param interval: Delay between two update retrivals - :param non_stop: Do not stop polling when an ApiException occurs. - :param timeout: Request connection timeout - :param skip_pending: skip old updates - :param long_polling_timeout: Timeout in seconds for long polling (see API docs) - :param allowed_updates: A list of the update types you want your bot to receive. - For example, specify [“message”, “edited_channel_post”, “callback_query”] to only receive updates of these types. - See util.update_types for a complete list of available update types. - Specify an empty list to receive all update types except chat_member (default). - If not specified, the previous setting will be used. - - Please note that this parameter doesn't affect updates created before the call to the get_updates, - so unwanted updates may be received for a short period of time. - :param none_stop: Deprecated, use non_stop. Old typo f***up compatibility - :return: - """ - if none_stop is not None: - non_stop = none_stop - - if skip_pending: - self.__skip_updates() - - if self.threaded: - self.__threaded_polling(non_stop, interval, timeout, long_polling_timeout, allowed_updates) - else: - self.__non_threaded_polling(non_stop, interval, timeout, long_polling_timeout, allowed_updates) - - def __threaded_polling(self, non_stop=False, interval=0, timeout = None, long_polling_timeout = None, allowed_updates=None): - logger.info('Started polling.') - self.__stop_polling.clear() - error_interval = 0.25 - - polling_thread = util.WorkerThread(name="PollingThread") - or_event = util.OrEvent( - polling_thread.done_event, - polling_thread.exception_event, - self.worker_pool.exception_event - ) - - while not self.__stop_polling.wait(interval): - or_event.clear() - try: - polling_thread.put(self.__retrieve_updates, timeout, long_polling_timeout, allowed_updates=allowed_updates) - or_event.wait() # wait for polling thread finish, polling thread error or thread pool error - polling_thread.raise_exceptions() - self.worker_pool.raise_exceptions() - error_interval = 0.25 - except apihelper.ApiException as e: - if self.exception_handler is not None: - handled = self.exception_handler.handle(e) - else: - handled = False - if not handled: - logger.error(e) - if not non_stop: - self.__stop_polling.set() - logger.info("Exception occurred. Stopping.") - else: - # polling_thread.clear_exceptions() - # self.worker_pool.clear_exceptions() - logger.info("Waiting for {0} seconds until retry".format(error_interval)) - time.sleep(error_interval) - if error_interval * 2 < 60: - error_interval *= 2 - else: - error_interval = 60 - else: - # polling_thread.clear_exceptions() - # self.worker_pool.clear_exceptions() - time.sleep(error_interval) - polling_thread.clear_exceptions() #* - self.worker_pool.clear_exceptions() #* - except KeyboardInterrupt: - logger.info("KeyboardInterrupt received.") - self.__stop_polling.set() - break - except Exception as e: - if self.exception_handler is not None: - handled = self.exception_handler.handle(e) - else: - handled = False - if not handled: - polling_thread.stop() - polling_thread.clear_exceptions() #* - self.worker_pool.clear_exceptions() #* - raise e - else: - polling_thread.clear_exceptions() - self.worker_pool.clear_exceptions() - time.sleep(error_interval) - - polling_thread.stop() - polling_thread.clear_exceptions() #* - self.worker_pool.clear_exceptions() #* - logger.info('Stopped polling.') - - def __non_threaded_polling(self, non_stop=False, interval=0, timeout=None, long_polling_timeout=None, allowed_updates=None): - logger.info('Started polling.') - self.__stop_polling.clear() - error_interval = 0.25 - - while not self.__stop_polling.wait(interval): - try: - self.__retrieve_updates(timeout, long_polling_timeout, allowed_updates=allowed_updates) - error_interval = 0.25 - except apihelper.ApiException as e: - if self.exception_handler is not None: - handled = self.exception_handler.handle(e) - else: - handled = False - - if not handled: - logger.error(e) - if not non_stop: - self.__stop_polling.set() - logger.info("Exception occurred. Stopping.") - else: - logger.info("Waiting for {0} seconds until retry".format(error_interval)) - time.sleep(error_interval) - error_interval *= 2 - else: - time.sleep(error_interval) - except KeyboardInterrupt: - logger.info("KeyboardInterrupt received.") - self.__stop_polling.set() - break - except Exception as e: - if self.exception_handler is not None: - handled = self.exception_handler.handle(e) - else: - handled = False - if not handled: - raise e - else: - time.sleep(error_interval) - - logger.info('Stopped polling.') - - def _exec_task(self, task, *args, **kwargs): - if kwargs and kwargs.get('task_type') == 'handler': - pass_bot = kwargs.get('pass_bot') - kwargs.pop('pass_bot') - kwargs.pop('task_type') - if pass_bot: - kwargs['bot'] = self - - if self.threaded: - self.worker_pool.put(task, *args, **kwargs) - else: - try: - task(*args, **kwargs) - except Exception as e: - if self.exception_handler is not None: - handled = self.exception_handler.handle(e) - else: - handled = False - if not handled: - raise e - - def stop_polling(self): - self.__stop_polling.set() - - def stop_bot(self): - self.stop_polling() - if self.threaded and self.worker_pool: - self.worker_pool.close() - - def set_update_listener(self, listener): - self.update_listener.append(listener) - - def get_me(self) -> types.User: - """ - Returns basic information about the bot in form of a User object. - - Telegram documentation: https://core.telegram.org/bots/api#getme - """ - result = apihelper.get_me(self.token) - return types.User.de_json(result) - - def get_file(self, file_id: str) -> types.File: - """ - Use this method to get basic info about a file and prepare it for downloading. - For the moment, bots can download files of up to 20MB in size. - On success, a File object is returned. - It is guaranteed that the link will be valid for at least 1 hour. - When the link expires, a new one can be requested by calling get_file again. - - Telegram documentation: https://core.telegram.org/bots/api#getfile - - :param file_id: File identifier - """ - return types.File.de_json(apihelper.get_file(self.token, file_id)) - - def get_file_url(self, file_id: str) -> str: - """ - Get a valid URL for downloading a file. - - :param file_id: File identifier to get download URL for. - """ - return apihelper.get_file_url(self.token, file_id) - - def download_file(self, file_path: str) -> bytes: - return apihelper.download_file(self.token, file_path) - - def log_out(self) -> bool: - """ - Use this method to log out from the cloud Bot API server before launching the bot locally. - You MUST log out the bot before running it locally, otherwise there is no guarantee - that the bot will receive updates. - After a successful call, you can immediately log in on a local server, - but will not be able to log in back to the cloud Bot API server for 10 minutes. - Returns True on success. - - Telegram documentation: https://core.telegram.org/bots/api#logout - """ - return apihelper.log_out(self.token) - - def close(self) -> bool: - """ - Use this method to close the bot instance before moving it from one local server to another. - You need to delete the webhook before calling this method to ensure that the bot isn't launched again - after server restart. - The method will return error 429 in the first 10 minutes after the bot is launched. - Returns True on success. - - Telegram documentation: https://core.telegram.org/bots/api#close - """ - return apihelper.close(self.token) - - def get_user_profile_photos(self, user_id: int, offset: Optional[int]=None, - limit: Optional[int]=None) -> types.UserProfilePhotos: - """ - Retrieves the user profile photos of the person with 'user_id' - - Telegram documentation: https://core.telegram.org/bots/api#getuserprofilephotos - - :param user_id: Integer - Unique identifier of the target user - :param offset: - :param limit: - :return: API reply. - """ - result = apihelper.get_user_profile_photos(self.token, user_id, offset, limit) - return types.UserProfilePhotos.de_json(result) - - def get_chat(self, chat_id: Union[int, str]) -> types.Chat: - """ - Use this method to get up to date information about the chat (current name of the user for one-on-one - conversations, current username of a user, group or channel, etc.). Returns a Chat object on success. - - Telegram documentation: https://core.telegram.org/bots/api#getchat - - :param chat_id: - :return: API reply. - """ - result = apihelper.get_chat(self.token, chat_id) - return types.Chat.de_json(result) - - def leave_chat(self, chat_id: Union[int, str]) -> bool: - """ - Use this method for your bot to leave a group, supergroup or channel. Returns True on success. - - Telegram documentation: https://core.telegram.org/bots/api#leavechat - - :param chat_id: - :return: API reply. - """ - result = apihelper.leave_chat(self.token, chat_id) - return result - - def get_chat_administrators(self, chat_id: Union[int, str]) -> List[types.ChatMember]: - """ - Use this method to get a list of administrators in a chat. - On success, returns an Array of ChatMember objects that contains - information about all chat administrators except other bots. - - Telegram documentation: https://core.telegram.org/bots/api#getchatadministrators - - :param chat_id: Unique identifier for the target chat or username - of the target supergroup or channel (in the format @channelusername) - :return: API reply. - """ - result = apihelper.get_chat_administrators(self.token, chat_id) - return [types.ChatMember.de_json(r) for r in result] - - def get_chat_members_count(self, chat_id: Union[int, str]) -> int: - """ - This function is deprecated. Use `get_chat_member_count` instead - """ - logger.info('get_chat_members_count is deprecated. Use get_chat_member_count instead.') - result = apihelper.get_chat_member_count(self.token, chat_id) - return result - - def get_chat_member_count(self, chat_id: Union[int, str]) -> int: - """ - Use this method to get the number of members in a chat. Returns Int on success. - - Telegram documentation: https://core.telegram.org/bots/api#getchatmembercount - - :param chat_id: - :return: API reply. - """ - result = apihelper.get_chat_member_count(self.token, chat_id) - return result - - def set_chat_sticker_set(self, chat_id: Union[int, str], sticker_set_name: str) -> types.StickerSet: - """ - Use this method to set a new group sticker set for a supergroup. The bot must be an administrator - in the chat for this to work and must have the appropriate admin rights. - Use the field can_set_sticker_set optionally returned in getChat requests to check - if the bot can use this method. Returns True on success. - - Telegram documentation: https://core.telegram.org/bots/api#setchatstickerset - - :param chat_id: Unique identifier for the target chat or username of the target supergroup (in the format @supergroupusername) - :param sticker_set_name: Name of the sticker set to be set as the group sticker set - :return: API reply. - """ - result = apihelper.set_chat_sticker_set(self.token, chat_id, sticker_set_name) - return result - - def delete_chat_sticker_set(self, chat_id: Union[int, str]) -> bool: - """ - Use this method to delete a group sticker set from a supergroup. The bot must be an administrator in the chat - for this to work and must have the appropriate admin rights. Use the field can_set_sticker_set - optionally returned in getChat requests to check if the bot can use this method. Returns True on success. - - Telegram documentation: https://core.telegram.org/bots/api#deletechatstickerset - - :param chat_id: Unique identifier for the target chat or username of the target supergroup (in the format @supergroupusername) - :return: API reply. - """ - result = apihelper.delete_chat_sticker_set(self.token, chat_id) - return result - - def get_chat_member(self, chat_id: Union[int, str], user_id: int) -> types.ChatMember: - """ - Use this method to get information about a member of a chat. Returns a ChatMember object on success. - - Telegram documentation: https://core.telegram.org/bots/api#getchatmember - - :param chat_id: - :param user_id: - :return: API reply. - """ - result = apihelper.get_chat_member(self.token, chat_id, user_id) - return types.ChatMember.de_json(result) - - def send_message( - self, chat_id: Union[int, str], text: str, - parse_mode: Optional[str]=None, - entities: Optional[List[types.MessageEntity]]=None, - disable_web_page_preview: Optional[bool]=None, - disable_notification: Optional[bool]=None, - protect_content: Optional[bool]=None, - reply_to_message_id: Optional[int]=None, - allow_sending_without_reply: Optional[bool]=None, - reply_markup: Optional[REPLY_MARKUP_TYPES]=None, - timeout: Optional[int]=None) -> types.Message: - """ - Use this method to send text messages. - - Warning: Do not send more than about 4000 characters each message, otherwise you'll risk an HTTP 414 error. - If you must send more than 4000 characters, - use the `split_string` or `smart_split` function in util.py. - - Telegram documentation: https://core.telegram.org/bots/api#sendmessage - - :param chat_id: Unique identifier for the target chat or username of the target channel (in the format @channelusername) - :param text: Text of the message to be sent - :param parse_mode: Send Markdown or HTML, if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in your bot's message. - :param entities: List of special entities that appear in message text, which can be specified instead of parse_mode - :param disable_web_page_preview: Disables link previews for links in this message - :param disable_notification: Sends the message silently. Users will receive a notification with no sound. - :param protect_content: If True, the message content will be hidden for all users except for the target user - :param reply_to_message_id: If the message is a reply, ID of the original message - :param allow_sending_without_reply: Pass True, if the message should be sent even if the specified replied-to message is not found - :param reply_markup: Additional interface options. A JSON-serialized object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. - :param timeout: - :return: API reply. - """ - parse_mode = self.parse_mode if (parse_mode is None) else parse_mode - - return types.Message.de_json( - apihelper.send_message( - self.token, chat_id, text, disable_web_page_preview, reply_to_message_id, - reply_markup, parse_mode, disable_notification, timeout, - entities, allow_sending_without_reply, protect_content=protect_content)) - - def forward_message( - self, chat_id: Union[int, str], from_chat_id: Union[int, str], - message_id: int, disable_notification: Optional[bool]=None, - protect_content: Optional[bool]=None, - timeout: Optional[int]=None) -> types.Message: - """ - Use this method to forward messages of any kind. - - Telegram documentation: https://core.telegram.org/bots/api#forwardmessage - - :param disable_notification: - :param chat_id: which chat to forward - :param from_chat_id: which chat message from - :param message_id: message id - :param protect_content: Protects the contents of the forwarded message from forwarding and saving - :param timeout: - :return: API reply. - """ - return types.Message.de_json( - apihelper.forward_message(self.token, chat_id, from_chat_id, message_id, disable_notification, timeout, protect_content)) - - def copy_message( - self, chat_id: Union[int, str], - from_chat_id: Union[int, str], - message_id: int, - caption: Optional[str]=None, - parse_mode: Optional[str]=None, - caption_entities: Optional[List[types.MessageEntity]]=None, - disable_notification: Optional[bool]=None, - protect_content: Optional[bool]=None, - reply_to_message_id: Optional[int]=None, - allow_sending_without_reply: Optional[bool]=None, - reply_markup: Optional[REPLY_MARKUP_TYPES]=None, - timeout: Optional[int]=None) -> int: - """ - Use this method to copy messages of any kind. - - Telegram documentation: https://core.telegram.org/bots/api#copymessage - - :param chat_id: which chat to forward - :param from_chat_id: which chat message from - :param message_id: message id - :param caption: - :param parse_mode: - :param caption_entities: - :param disable_notification: - :param protect_content: - :param reply_to_message_id: - :param allow_sending_without_reply: - :param reply_markup: - :param timeout: - :return: API reply. - """ - return types.MessageID.de_json( - apihelper.copy_message(self.token, chat_id, from_chat_id, message_id, caption, parse_mode, caption_entities, - disable_notification, reply_to_message_id, allow_sending_without_reply, reply_markup, - timeout, protect_content)) - - def delete_message(self, chat_id: Union[int, str], message_id: int, - timeout: Optional[int]=None) -> bool: - """ - Use this method to delete message. Returns True on success. - - Telegram documentation: https://core.telegram.org/bots/api#deletemessage - - :param chat_id: in which chat to delete - :param message_id: which message to delete - :param timeout: - :return: API reply. - """ - return apihelper.delete_message(self.token, chat_id, message_id, timeout) - - def send_dice( - self, chat_id: Union[int, str], - emoji: Optional[str]=None, disable_notification: Optional[bool]=None, - reply_to_message_id: Optional[int]=None, - reply_markup: Optional[REPLY_MARKUP_TYPES]=None, - timeout: Optional[int]=None, - allow_sending_without_reply: Optional[bool]=None, - protect_content: Optional[bool]=None) -> types.Message: - """ - Use this method to send dices. - - Telegram documentation: https://core.telegram.org/bots/api#senddice - - :param chat_id: - :param emoji: - :param disable_notification: - :param reply_to_message_id: - :param reply_markup: - :param timeout: - :param allow_sending_without_reply: - :param protect_content: - :return: Message - """ - return types.Message.de_json( - apihelper.send_dice( - self.token, chat_id, emoji, disable_notification, reply_to_message_id, - reply_markup, timeout, allow_sending_without_reply, protect_content) - ) - - def send_photo( - self, chat_id: Union[int, str], photo: Union[Any, str], - caption: Optional[str]=None, parse_mode: Optional[str]=None, - caption_entities: Optional[List[types.MessageEntity]]=None, - disable_notification: Optional[bool]=None, - protect_content: Optional[bool]=None, - reply_to_message_id: Optional[int]=None, - allow_sending_without_reply: Optional[bool]=None, - reply_markup: Optional[REPLY_MARKUP_TYPES]=None, - timeout: Optional[int]=None,) -> types.Message: - """ - Use this method to send photos. On success, the sent Message is returned. - - Telegram documentation: https://core.telegram.org/bots/api#sendphoto - - :param chat_id: - :param photo: - :param caption: - :param parse_mode: - :param caption_entities: - :param disable_notification: - :param protect_content: - :param reply_to_message_id: - :param allow_sending_without_reply: - :param reply_markup: - :param timeout: - :return: Message - """ - parse_mode = self.parse_mode if (parse_mode is None) else parse_mode - - return types.Message.de_json( - apihelper.send_photo( - self.token, chat_id, photo, caption, reply_to_message_id, reply_markup, - parse_mode, disable_notification, timeout, caption_entities, - allow_sending_without_reply, protect_content)) - - # TODO: Rewrite this method like in API. - def send_audio( - self, chat_id: Union[int, str], audio: Union[Any, str], - caption: Optional[str]=None, duration: Optional[int]=None, - performer: Optional[str]=None, title: Optional[str]=None, - reply_to_message_id: Optional[int]=None, - reply_markup: Optional[REPLY_MARKUP_TYPES]=None, - parse_mode: Optional[str]=None, - disable_notification: Optional[bool]=None, - timeout: Optional[int]=None, - thumb: Optional[Union[Any, str]]=None, - caption_entities: Optional[List[types.MessageEntity]]=None, - allow_sending_without_reply: Optional[bool]=None, - protect_content: Optional[bool]=None) -> types.Message: - """ - Use this method to send audio files, if you want Telegram clients to display them in the music player. - Your audio must be in the .mp3 format. - - Telegram documentation: https://core.telegram.org/bots/api#sendaudio - - :param chat_id:Unique identifier for the message recipient - :param audio:Audio file to send. - :param caption: - :param duration:Duration of the audio in seconds - :param performer:Performer - :param title:Track name - :param reply_to_message_id:If the message is a reply, ID of the original message - :param reply_markup: - :param parse_mode - :param disable_notification: - :param timeout: - :param thumb: - :param caption_entities: - :param allow_sending_without_reply: - :param protect_content: - :return: Message - """ - parse_mode = self.parse_mode if (parse_mode is None) else parse_mode - - return types.Message.de_json( - apihelper.send_audio( - self.token, chat_id, audio, caption, duration, performer, title, reply_to_message_id, - reply_markup, parse_mode, disable_notification, timeout, thumb, - caption_entities, allow_sending_without_reply, protect_content)) - - # TODO: Rewrite this method like in API. - def send_voice( - self, chat_id: Union[int, str], voice: Union[Any, str], - caption: Optional[str]=None, duration: Optional[int]=None, - reply_to_message_id: Optional[int]=None, - reply_markup: Optional[REPLY_MARKUP_TYPES]=None, - parse_mode: Optional[str]=None, - disable_notification: Optional[bool]=None, - timeout: Optional[int]=None, - caption_entities: Optional[List[types.MessageEntity]]=None, - allow_sending_without_reply: Optional[bool]=None, - protect_content: Optional[bool]=None) -> types.Message: - """ - Use this method to send audio files, if you want Telegram clients to display the file - as a playable voice message. - - Telegram documentation: https://core.telegram.org/bots/api#sendvoice - - :param chat_id: Unique identifier for the message recipient. - :param voice: - :param caption: - :param duration: Duration of sent audio in seconds - :param reply_to_message_id: - :param reply_markup: - :param parse_mode: - :param disable_notification: - :param timeout: - :param caption_entities: - :param allow_sending_without_reply: - :param protect_content: - :return: Message - """ - parse_mode = self.parse_mode if (parse_mode is None) else parse_mode - - return types.Message.de_json( - apihelper.send_voice( - self.token, chat_id, voice, caption, duration, reply_to_message_id, reply_markup, - parse_mode, disable_notification, timeout, caption_entities, - allow_sending_without_reply, protect_content)) - - # TODO: Rewrite this method like in API. - def send_document( - self, chat_id: Union[int, str], document: Union[Any, str], - reply_to_message_id: Optional[int]=None, - caption: Optional[str]=None, - reply_markup: Optional[REPLY_MARKUP_TYPES]=None, - parse_mode: Optional[str]=None, - disable_notification: Optional[bool]=None, - timeout: Optional[int]=None, - thumb: Optional[Union[Any, str]]=None, - caption_entities: Optional[List[types.MessageEntity]]=None, - allow_sending_without_reply: Optional[bool]=None, - visible_file_name: Optional[str]=None, - disable_content_type_detection: Optional[bool]=None, - data: Optional[Union[Any, str]]=None, - protect_content: Optional[bool]=None) -> types.Message: - """ - Use this method to send general files. - - Telegram documentation: https://core.telegram.org/bots/api#senddocument - - :param chat_id: Unique identifier for the target chat or username of the target channel (in the format @channelusername) - :param document: (document) File to send. Pass a file_id as String to send a file that exists on the Telegram servers (recommended), pass an HTTP URL as a String for Telegram to get a file from the Internet, or upload a new one using multipart/form-data - :param reply_to_message_id: If the message is a reply, ID of the original message - :param caption: Document caption (may also be used when resending documents by file_id), 0-1024 characters after entities parsing - :param reply_markup: - :param parse_mode: Mode for parsing entities in the document caption - :param disable_notification: Sends the message silently. Users will receive a notification with no sound. - :param timeout: - :param thumb: InputFile or String : Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side. The thumbnail should be in JPEG format and less than 200 kB in size. A thumbnail's width and height should not exceed 320. Ignored if the file is not uploaded using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new file, so you can pass “attach://” if the thumbnail was uploaded using multipart/form-data under - :param caption_entities: - :param allow_sending_without_reply: - :param visible_file_name: allows to define file name that will be visible in the Telegram instead of original file name - :param disable_content_type_detection: Disables automatic server-side content type detection for files uploaded using multipart/form-data - :param data: function typo miss compatibility: do not use it - :param protect_content: - :return: API reply. - """ - parse_mode = self.parse_mode if (parse_mode is None) else parse_mode - if data and not(document): - # function typo miss compatibility - document = data - - return types.Message.de_json( - apihelper.send_data( - self.token, chat_id, document, 'document', - reply_to_message_id = reply_to_message_id, reply_markup = reply_markup, parse_mode = parse_mode, - disable_notification = disable_notification, timeout = timeout, caption = caption, thumb = thumb, - caption_entities = caption_entities, allow_sending_without_reply = allow_sending_without_reply, - disable_content_type_detection = disable_content_type_detection, visible_file_name = visible_file_name, - protect_content = protect_content)) - - # TODO: Rewrite this method like in API. - def send_sticker( - self, chat_id: Union[int, str], - sticker: Union[Any, str], - reply_to_message_id: Optional[int]=None, - reply_markup: Optional[REPLY_MARKUP_TYPES]=None, - disable_notification: Optional[bool]=None, - timeout: Optional[int]=None, - allow_sending_without_reply: Optional[bool]=None, - protect_content:Optional[bool]=None, - data: Union[Any, str]=None) -> types.Message: - """ - Use this method to send .webp stickers. - - Telegram documentation: https://core.telegram.org/bots/api#sendsticker - - :param chat_id: - :param sticker: - :param data: - :param reply_to_message_id: - :param reply_markup: - :param disable_notification: to disable the notification - :param timeout: timeout - :param allow_sending_without_reply: - :param protect_content: - :param data: function typo miss compatibility: do not use it - :return: API reply. - """ - if data and not(sticker): - # function typo miss compatibility - sticker = data - return types.Message.de_json( - apihelper.send_data( - self.token, chat_id, sticker, 'sticker', - reply_to_message_id=reply_to_message_id, reply_markup=reply_markup, - disable_notification=disable_notification, timeout=timeout, - allow_sending_without_reply=allow_sending_without_reply, - protect_content=protect_content)) - - def send_video( - self, chat_id: Union[int, str], video: Union[Any, str], - duration: Optional[int]=None, - width: Optional[int]=None, - height: Optional[int]=None, - thumb: Optional[Union[Any, str]]=None, - caption: Optional[str]=None, - parse_mode: Optional[str]=None, - caption_entities: Optional[List[types.MessageEntity]]=None, - supports_streaming: Optional[bool]=None, - disable_notification: Optional[bool]=None, - protect_content: Optional[bool]=None, - reply_to_message_id: Optional[int]=None, - allow_sending_without_reply: Optional[bool]=None, - reply_markup: Optional[REPLY_MARKUP_TYPES]=None, - timeout: Optional[int]=None, - data: Optional[Union[Any, str]]=None) -> types.Message: - """ - Use this method to send video files, Telegram clients support mp4 videos (other formats may be sent as Document). - - Telegram documentation: https://core.telegram.org/bots/api#sendvideo - - :param chat_id: Unique identifier for the target chat or username of the target channel (in the format @channelusername) - :param video: Video to send. You can either pass a file_id as String to resend a video that is already on the Telegram servers, or upload a new video file using multipart/form-data. - :param duration: Duration of sent video in seconds - :param width: Video width - :param height: Video height - :param thumb: Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side. The thumbnail should be in JPEG format and less than 200 kB in size. A thumbnail's width and height should not exceed 320. Ignored if the file is not uploaded using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new file, so you can pass “attach://” if the thumbnail was uploaded using multipart/form-data under . - :param caption: Video caption (may also be used when resending videos by file_id), 0-1024 characters after entities parsing - :param parse_mode: Mode for parsing entities in the video caption - :param caption_entities: - :param supports_streaming: Pass True, if the uploaded video is suitable for streaming - :param disable_notification: Sends the message silently. Users will receive a notification with no sound. - :param protect_content: - :param reply_to_message_id: If the message is a reply, ID of the original message - :param allow_sending_without_reply: - :param reply_markup: - :param timeout: - :param data: function typo miss compatibility: do not use it - """ - parse_mode = self.parse_mode if (parse_mode is None) else parse_mode - if data and not(video): - # function typo miss compatibility - video = data - - return types.Message.de_json( - apihelper.send_video( - self.token, chat_id, video, duration, caption, reply_to_message_id, reply_markup, - parse_mode, supports_streaming, disable_notification, timeout, thumb, width, height, - caption_entities, allow_sending_without_reply, protect_content)) - - def send_animation( - self, chat_id: Union[int, str], animation: Union[Any, str], - duration: Optional[int]=None, - width: Optional[int]=None, - height: Optional[int]=None, - thumb: Optional[Union[Any, str]]=None, - caption: Optional[str]=None, - parse_mode: Optional[str]=None, - caption_entities: Optional[List[types.MessageEntity]]=None, - disable_notification: Optional[bool]=None, - protect_content: Optional[bool]=None, - reply_to_message_id: Optional[int]=None, - allow_sending_without_reply: Optional[bool]=None, - reply_markup: Optional[REPLY_MARKUP_TYPES]=None, - timeout: Optional[int]=None, ) -> types.Message: - """ - Use this method to send animation files (GIF or H.264/MPEG-4 AVC video without sound). - - Telegram documentation: https://core.telegram.org/bots/api#sendanimation - - :param chat_id: Integer : Unique identifier for the message recipient — User or GroupChat id - :param animation: InputFile or String : Animation to send. You can either pass a file_id as String to resend an - animation that is already on the Telegram server - :param duration: Integer : Duration of sent video in seconds - :param width: Integer : Video width - :param height: Integer : Video height - :param thumb: InputFile or String : Thumbnail of the file sent - :param caption: String : Animation caption (may also be used when resending animation by file_id). - :param parse_mode: - :param protect_content: - :param reply_to_message_id: - :param reply_markup: - :param disable_notification: - :param timeout: - :param caption_entities: - :param allow_sending_without_reply: - :return: - """ - parse_mode = self.parse_mode if (parse_mode is None) else parse_mode - - return types.Message.de_json( - apihelper.send_animation( - self.token, chat_id, animation, duration, caption, reply_to_message_id, - reply_markup, parse_mode, disable_notification, timeout, thumb, - caption_entities, allow_sending_without_reply, protect_content, width, height)) - - # TODO: Rewrite this method like in API. - def send_video_note( - self, chat_id: Union[int, str], data: Union[Any, str], - duration: Optional[int]=None, - length: Optional[int]=None, - reply_to_message_id: Optional[int]=None, - reply_markup: Optional[REPLY_MARKUP_TYPES]=None, - disable_notification: Optional[bool]=None, - timeout: Optional[int]=None, - thumb: Optional[Union[Any, str]]=None, - allow_sending_without_reply: Optional[bool]=None, - protect_content: Optional[bool]=None) -> types.Message: - """ - As of v.4.0, Telegram clients support rounded square mp4 videos of up to 1 minute long. Use this method to send - video messages. - - Telegram documentation: https://core.telegram.org/bots/api#sendvideonote - - :param chat_id: Integer : Unique identifier for the message recipient — User or GroupChat id - :param data: InputFile or String : Video note to send. You can either pass a file_id as String to resend - a video that is already on the Telegram server - :param duration: Integer : Duration of sent video in seconds - :param length: Integer : Video width and height, Can't be None and should be in range of (0, 640) - :param reply_to_message_id: - :param reply_markup: - :param disable_notification: - :param timeout: - :param thumb: InputFile or String : Thumbnail of the file sent - :param allow_sending_without_reply: - :param protect_content: - :return: - """ - return types.Message.de_json( - apihelper.send_video_note( - self.token, chat_id, data, duration, length, reply_to_message_id, reply_markup, - disable_notification, timeout, thumb, allow_sending_without_reply, protect_content)) - - def send_media_group( - self, chat_id: Union[int, str], - media: List[Union[ - types.InputMediaAudio, types.InputMediaDocument, - types.InputMediaPhoto, types.InputMediaVideo]], - disable_notification: Optional[bool]=None, - protect_content: Optional[bool]=None, - reply_to_message_id: Optional[int]=None, - timeout: Optional[int]=None, - allow_sending_without_reply: Optional[bool]=None) -> List[types.Message]: - """ - Send a group of photos or videos as an album. On success, an array of the sent Messages is returned. - - Telegram documentation: https://core.telegram.org/bots/api#sendmediagroup - - :param chat_id: - :param media: - :param disable_notification: - :param protect_content: - :param reply_to_message_id: - :param timeout: - :param allow_sending_without_reply: - :return: - """ - result = apihelper.send_media_group( - self.token, chat_id, media, disable_notification, reply_to_message_id, timeout, - allow_sending_without_reply, protect_content) - return [types.Message.de_json(msg) for msg in result] - - # TODO: Rewrite this method like in API. - def send_location( - self, chat_id: Union[int, str], - latitude: float, longitude: float, - live_period: Optional[int]=None, - reply_to_message_id: Optional[int]=None, - reply_markup: Optional[REPLY_MARKUP_TYPES]=None, - disable_notification: Optional[bool]=None, - timeout: Optional[int]=None, - horizontal_accuracy: Optional[float]=None, - heading: Optional[int]=None, - proximity_alert_radius: Optional[int]=None, - allow_sending_without_reply: Optional[bool]=None, - protect_content: Optional[bool]=None) -> types.Message: - """ - Use this method to send point on the map. - - Telegram documentation: https://core.telegram.org/bots/api#sendlocation - - :param chat_id: - :param latitude: - :param longitude: - :param live_period: - :param reply_to_message_id: - :param reply_markup: - :param disable_notification: - :param timeout: - :param horizontal_accuracy: - :param heading: - :param proximity_alert_radius: - :param allow_sending_without_reply: - :param protect_content: - :return: API reply. - """ - return types.Message.de_json( - apihelper.send_location( - self.token, chat_id, latitude, longitude, live_period, - reply_to_message_id, reply_markup, disable_notification, timeout, - horizontal_accuracy, heading, proximity_alert_radius, - allow_sending_without_reply, protect_content)) - - def edit_message_live_location( - self, latitude: float, longitude: float, - chat_id: Optional[Union[int, str]]=None, - message_id: Optional[int]=None, - inline_message_id: Optional[str]=None, - reply_markup: Optional[REPLY_MARKUP_TYPES]=None, - timeout: Optional[int]=None, - horizontal_accuracy: Optional[float]=None, - heading: Optional[int]=None, - proximity_alert_radius: Optional[int]=None) -> types.Message: - """ - Use this method to edit live location. - - Telegram documentation: https://core.telegram.org/bots/api#editmessagelivelocation - - :param latitude: - :param longitude: - :param chat_id: - :param message_id: - :param reply_markup: - :param timeout: - :param inline_message_id: - :param horizontal_accuracy: - :param heading: - :param proximity_alert_radius: - :return: - """ - return types.Message.de_json( - apihelper.edit_message_live_location( - self.token, latitude, longitude, chat_id, message_id, - inline_message_id, reply_markup, timeout, - horizontal_accuracy, heading, proximity_alert_radius)) - - def stop_message_live_location( - self, chat_id: Optional[Union[int, str]]=None, - message_id: Optional[int]=None, - inline_message_id: Optional[str]=None, - reply_markup: Optional[REPLY_MARKUP_TYPES]=None, - timeout: Optional[int]=None) -> types.Message: - """ - Use this method to stop updating a live location message sent by the bot - or via the bot (for inline bots) before live_period expires - - Telegram documentation: https://core.telegram.org/bots/api#stopmessagelivelocation - - :param chat_id: - :param message_id: - :param inline_message_id: - :param reply_markup: - :param timeout: - :return: - """ - return types.Message.de_json( - apihelper.stop_message_live_location( - self.token, chat_id, message_id, inline_message_id, reply_markup, timeout)) - - # TODO: Rewrite this method like in API. - def send_venue( - self, chat_id: Union[int, str], - latitude: float, longitude: float, - title: str, address: str, - foursquare_id: Optional[str]=None, - foursquare_type: Optional[str]=None, - disable_notification: Optional[bool]=None, - reply_to_message_id: Optional[int]=None, - reply_markup: Optional[REPLY_MARKUP_TYPES]=None, - timeout: Optional[int]=None, - allow_sending_without_reply: Optional[bool]=None, - google_place_id: Optional[str]=None, - google_place_type: Optional[str]=None, - protect_content: Optional[bool]=None) -> types.Message: - """ - Use this method to send information about a venue. - - Telegram documentation: https://core.telegram.org/bots/api#sendvenue - - :param chat_id: Integer or String : Unique identifier for the target chat or username of the target channel - :param latitude: Float : Latitude of the venue - :param longitude: Float : Longitude of the venue - :param title: String : Name of the venue - :param address: String : Address of the venue - :param foursquare_id: String : Foursquare identifier of the venue - :param foursquare_type: Foursquare type of the venue, if known. (For example, “arts_entertainment/default”, - “arts_entertainment/aquarium” or “food/icecream”.) - :param disable_notification: - :param reply_to_message_id: - :param reply_markup: - :param timeout: - :param allow_sending_without_reply: - :param google_place_id: - :param google_place_type: - :param protect_content: - :return: - """ - return types.Message.de_json( - apihelper.send_venue( - self.token, chat_id, latitude, longitude, title, address, foursquare_id, foursquare_type, - disable_notification, reply_to_message_id, reply_markup, timeout, - allow_sending_without_reply, google_place_id, google_place_type, protect_content)) - - # TODO: Rewrite this method like in API. - def send_contact( - self, chat_id: Union[int, str], phone_number: str, - first_name: str, last_name: Optional[str]=None, - vcard: Optional[str]=None, - disable_notification: Optional[bool]=None, - reply_to_message_id: Optional[int]=None, - reply_markup: Optional[REPLY_MARKUP_TYPES]=None, - timeout: Optional[int]=None, - allow_sending_without_reply: Optional[bool]=None, - protect_content: Optional[bool]=None) -> types.Message: - """ - Use this method to send phone contacts. - - Telegram documentation: https://core.telegram.org/bots/api#sendcontact - - :param chat_id: Integer or String : Unique identifier for the target chat or username of the target channel - :param phone_number: String : Contact's phone number - :param first_name: String : Contact's first name - :param last_name: String : Contact's last name - :param vcard: String : Additional data about the contact in the form of a vCard, 0-2048 bytes - :param disable_notification: - :param reply_to_message_id: - :param reply_markup: - :param timeout: - :param allow_sending_without_reply: - :param protect_content: - :return: - """ - - return types.Message.de_json( - apihelper.send_contact( - self.token, chat_id, phone_number, first_name, last_name, vcard, - disable_notification, reply_to_message_id, reply_markup, timeout, - allow_sending_without_reply, protect_content)) - - def send_chat_action( - self, chat_id: Union[int, str], action: str, timeout: Optional[int]=None) -> bool: - """ - Use this method when you need to tell the user that something is happening on the bot's side. - The status is set for 5 seconds or less (when a message arrives from your bot, Telegram clients clear - its typing status). - - Telegram documentation: https://core.telegram.org/bots/api#sendchataction - - :param chat_id: - :param action: One of the following strings: 'typing', 'upload_photo', 'record_video', 'upload_video', - 'record_audio', 'upload_audio', 'upload_document', 'find_location', 'record_video_note', - 'upload_video_note'. - :param timeout: - :return: API reply. :type: boolean - """ - return apihelper.send_chat_action(self.token, chat_id, action, timeout) - - def kick_chat_member( - self, chat_id: Union[int, str], user_id: int, - until_date:Optional[Union[int, datetime]]=None, - revoke_messages: Optional[bool]=None) -> bool: - """ - This function is deprecated. Use `ban_chat_member` instead - """ - logger.info('kick_chat_member is deprecated. Use ban_chat_member instead.') - return apihelper.ban_chat_member(self.token, chat_id, user_id, until_date, revoke_messages) - - def ban_chat_member( - self, chat_id: Union[int, str], user_id: int, - until_date:Optional[Union[int, datetime]]=None, - revoke_messages: Optional[bool]=None) -> bool: - """ - Use this method to ban a user in a group, a supergroup or a channel. - In the case of supergroups and channels, the user will not be able to return to the chat on their - own using invite links, etc., unless unbanned first. - Returns True on success. - - Telegram documentation: https://core.telegram.org/bots/api#banchatmember - - :param chat_id: Int or string : Unique identifier for the target group or username of the target supergroup - :param user_id: Int : Unique identifier of the target user - :param until_date: Date when the user will be unbanned, unix time. If user is banned for more than 366 days or - less than 30 seconds from the current time they are considered to be banned forever - :param revoke_messages: Bool: Pass True to delete all messages from the chat for the user that is being removed. - If False, the user will be able to see messages in the group that were sent before the user was removed. - Always True for supergroups and channels. - :return: boolean - """ - return apihelper.ban_chat_member(self.token, chat_id, user_id, until_date, revoke_messages) - - def unban_chat_member( - self, chat_id: Union[int, str], user_id: int, - only_if_banned: Optional[bool]=False) -> bool: - """ - Use this method to unban a previously kicked user in a supergroup or channel. - The user will not return to the group or channel automatically, but will be able to join via link, etc. - The bot must be an administrator for this to work. By default, this method guarantees that after the call - the user is not a member of the chat, but will be able to join it. So if the user is a member of the chat - they will also be removed from the chat. If you don't want this, use the parameter only_if_banned. - - Telegram documentation: https://core.telegram.org/bots/api#unbanchatmember - - :param chat_id: Unique identifier for the target group or username of the target supergroup or channel - (in the format @username) - :param user_id: Unique identifier of the target user - :param only_if_banned: Do nothing if the user is not banned - :return: True on success - """ - return apihelper.unban_chat_member(self.token, chat_id, user_id, only_if_banned) - - def restrict_chat_member( - self, chat_id: Union[int, str], user_id: int, - until_date: Optional[Union[int, datetime]]=None, - can_send_messages: Optional[bool]=None, - can_send_media_messages: Optional[bool]=None, - can_send_polls: Optional[bool]=None, - can_send_other_messages: Optional[bool]=None, - can_add_web_page_previews: Optional[bool]=None, - can_change_info: Optional[bool]=None, - can_invite_users: Optional[bool]=None, - can_pin_messages: Optional[bool]=None) -> bool: - """ - Use this method to restrict a user in a supergroup. - The bot must be an administrator in the supergroup for this to work and must have - the appropriate admin rights. Pass True for all boolean parameters to lift restrictions from a user. - - Telegram documentation: https://core.telegram.org/bots/api#restrictchatmember - - :param chat_id: Int or String : Unique identifier for the target group or username of the target supergroup - or channel (in the format @channelusername) - :param user_id: Int : Unique identifier of the target user - :param until_date: Date when restrictions will be lifted for the user, unix time. - If user is restricted for more than 366 days or less than 30 seconds from the current time, - they are considered to be restricted forever - :param can_send_messages: Pass True, if the user can send text messages, contacts, locations and venues - :param can_send_media_messages: Pass True, if the user can send audios, documents, photos, videos, video notes - and voice notes, implies can_send_messages - :param can_send_polls: Pass True, if the user is allowed to send polls, implies can_send_messages - :param can_send_other_messages: Pass True, if the user can send animations, games, stickers and use inline bots, implies can_send_media_messages - :param can_add_web_page_previews: Pass True, if the user may add web page previews to their messages, - implies can_send_media_messages - :param can_change_info: Pass True, if the user is allowed to change the chat title, photo and other settings. - Ignored in public supergroups - :param can_invite_users: Pass True, if the user is allowed to invite new users to the chat, - implies can_invite_users - :param can_pin_messages: Pass True, if the user is allowed to pin messages. Ignored in public supergroups - :return: True on success - """ - return apihelper.restrict_chat_member( - self.token, chat_id, user_id, until_date, - can_send_messages, can_send_media_messages, - can_send_polls, can_send_other_messages, - can_add_web_page_previews, can_change_info, - can_invite_users, can_pin_messages) - - def promote_chat_member( - self, chat_id: Union[int, str], user_id: int, - can_change_info: Optional[bool]=None, - can_post_messages: Optional[bool]=None, - can_edit_messages: Optional[bool]=None, - can_delete_messages: Optional[bool]=None, - can_invite_users: Optional[bool]=None, - can_restrict_members: Optional[bool]=None, - can_pin_messages: Optional[bool]=None, - can_promote_members: Optional[bool]=None, - is_anonymous: Optional[bool]=None, - can_manage_chat: Optional[bool]=None, - can_manage_voice_chats: Optional[bool]=None) -> bool: - """ - Use this method to promote or demote a user in a supergroup or a channel. The bot must be an administrator - in the chat for this to work and must have the appropriate admin rights. - Pass False for all boolean parameters to demote a user. - - Telegram documentation: https://core.telegram.org/bots/api#promotechatmember - - :param chat_id: Unique identifier for the target chat or username of the target channel ( - in the format @channelusername) - :param user_id: Int : Unique identifier of the target user - :param can_change_info: Bool: Pass True, if the administrator can change chat title, photo and other settings - :param can_post_messages: Bool : Pass True, if the administrator can create channel posts, channels only - :param can_edit_messages: Bool : Pass True, if the administrator can edit messages of other users, channels only - :param can_delete_messages: Bool : Pass True, if the administrator can delete messages of other users - :param can_invite_users: Bool : Pass True, if the administrator can invite new users to the chat - :param can_restrict_members: Bool: Pass True, if the administrator can restrict, ban or unban chat members - :param can_pin_messages: Bool: Pass True, if the administrator can pin messages, supergroups only - :param can_promote_members: Bool: Pass True, if the administrator can add new administrators with a subset - of his own privileges or demote administrators that he has promoted, directly or indirectly - (promoted by administrators that were appointed by him) - :param is_anonymous: Bool: Pass True, if the administrator's presence in the chat is hidden - :param can_manage_chat: Bool: Pass True, if the administrator can access the chat event log, chat statistics, - message statistics in channels, see channel members, - see anonymous administrators in supergroups and ignore slow mode. - Implied by any other administrator privilege - :param can_manage_voice_chats: Bool: Pass True, if the administrator can manage voice chats - For now, bots can use this privilege only for passing to other administrators. - :return: True on success. - """ - return apihelper.promote_chat_member( - self.token, chat_id, user_id, can_change_info, can_post_messages, - can_edit_messages, can_delete_messages, can_invite_users, - can_restrict_members, can_pin_messages, can_promote_members, - is_anonymous, can_manage_chat, can_manage_voice_chats) - - def set_chat_administrator_custom_title( - self, chat_id: Union[int, str], user_id: int, custom_title: str) -> bool: - """ - Use this method to set a custom title for an administrator - in a supergroup promoted by the bot. - - Telegram documentation: https://core.telegram.org/bots/api#setchatadministratorcustomtitle - - :param chat_id: Unique identifier for the target chat or username of the target supergroup - (in the format @supergroupusername) - :param user_id: Unique identifier of the target user - :param custom_title: New custom title for the administrator; - 0-16 characters, emoji are not allowed - :return: True on success. - """ - return apihelper.set_chat_administrator_custom_title(self.token, chat_id, user_id, custom_title) - - def ban_chat_sender_chat(self, chat_id: Union[int, str], sender_chat_id: Union[int, str]) -> bool: - """ - Use this method to ban a channel chat in a supergroup or a channel. - The owner of the chat will not be able to send messages and join live - streams on behalf of the chat, unless it is unbanned first. - The bot must be an administrator in the supergroup or channel - for this to work and must have the appropriate administrator rights. - Returns True on success. - - Telegram documentation: https://core.telegram.org/bots/api#banchatsenderchat - - :param chat_id: Unique identifier for the target chat or username of the target channel (in the format @channelusername) - :param sender_chat_id: Unique identifier of the target sender chat - :return: True on success. - """ - return apihelper.ban_chat_sender_chat(self.token, chat_id, sender_chat_id) - - def unban_chat_sender_chat(self, chat_id: Union[int, str], sender_chat_id: Union[int, str]) -> bool: - """ - Use this method to unban a previously banned channel chat in a supergroup or channel. - The bot must be an administrator for this to work and must have the appropriate - administrator rights. - Returns True on success. - - Telegram documentation: https://core.telegram.org/bots/api#unbanchatsenderchat - - :params: - :param chat_id: Unique identifier for the target chat or username of the target channel (in the format @channelusername) - :param sender_chat_id: Unique identifier of the target sender chat - :return: True on success. - """ - return apihelper.unban_chat_sender_chat(self.token, chat_id, sender_chat_id) - - def set_chat_permissions( - self, chat_id: Union[int, str], permissions: types.ChatPermissions) -> bool: - """ - Use this method to set default chat permissions for all members. - The bot must be an administrator in the group or a supergroup for this to work - and must have the can_restrict_members admin rights. - - Telegram documentation: https://core.telegram.org/bots/api#setchatpermissions - - :param chat_id: Unique identifier for the target chat or username of the target supergroup - (in the format @supergroupusername) - :param permissions: New default chat permissions - :return: True on success - """ - return apihelper.set_chat_permissions(self.token, chat_id, permissions) - - def create_chat_invite_link( - self, chat_id: Union[int, str], - name: Optional[str]=None, - expire_date: Optional[Union[int, datetime]]=None, - member_limit: Optional[int]=None, - creates_join_request: Optional[bool]=None) -> types.ChatInviteLink: - """ - Use this method to create an additional invite link for a chat. - The bot must be an administrator in the chat for this to work and must have the appropriate admin rights. - - Telegram documentation: https://core.telegram.org/bots/api#createchatinvitelink - - :param chat_id: Id: Unique identifier for the target chat or username of the target channel - (in the format @channelusername) - :param name: Invite link name; 0-32 characters - :param expire_date: Point in time (Unix timestamp) when the link will expire - :param member_limit: Maximum number of users that can be members of the chat simultaneously - :param creates_join_request: True, if users joining the chat via the link need to be approved by chat administrators. If True, member_limit can't be specified - :return: - """ - return types.ChatInviteLink.de_json( - apihelper.create_chat_invite_link(self.token, chat_id, name, expire_date, member_limit, creates_join_request) - ) - - def edit_chat_invite_link( - self, chat_id: Union[int, str], - invite_link: Optional[str] = None, - name: Optional[str]=None, - expire_date: Optional[Union[int, datetime]]=None, - member_limit: Optional[int]=None, - creates_join_request: Optional[bool]=None) -> types.ChatInviteLink: - """ - Use this method to edit a non-primary invite link created by the bot. - The bot must be an administrator in the chat for this to work and must have the appropriate admin rights. - - Telegram documentation: https://core.telegram.org/bots/api#editchatinvitelink - - :param chat_id: Id: Unique identifier for the target chat or username of the target channel - (in the format @channelusername) - :param name: Invite link name; 0-32 characters - :param invite_link: The invite link to edit - :param expire_date: Point in time (Unix timestamp) when the link will expire - :param member_limit: Maximum number of users that can be members of the chat simultaneously - :param creates_join_request: True, if users joining the chat via the link need to be approved by chat administrators. If True, member_limit can't be specified - :return: - """ - return types.ChatInviteLink.de_json( - apihelper.edit_chat_invite_link(self.token, chat_id, name, invite_link, expire_date, member_limit, creates_join_request) - ) - - def revoke_chat_invite_link( - self, chat_id: Union[int, str], invite_link: str) -> types.ChatInviteLink: - """ - Use this method to revoke an invite link created by the bot. - Note: If the primary link is revoked, a new link is automatically generated The bot must be an administrator - in the chat for this to work and must have the appropriate admin rights. - - Telegram documentation: https://core.telegram.org/bots/api#revokechatinvitelink - - :param chat_id: Id: Unique identifier for the target chat or username of the target channel - (in the format @channelusername) - :param invite_link: The invite link to revoke - :return: - """ - return types.ChatInviteLink.de_json( - apihelper.revoke_chat_invite_link(self.token, chat_id, invite_link) - ) - - def export_chat_invite_link(self, chat_id: Union[int, str]) -> str: - """ - Use this method to export an invite link to a supergroup or a channel. The bot must be an administrator - in the chat for this to work and must have the appropriate admin rights. - - Telegram documentation: https://core.telegram.org/bots/api#exportchatinvitelink - - :param chat_id: Id: Unique identifier for the target chat or username of the target channel - (in the format @channelusername) - :return: exported invite link as String on success. - """ - return apihelper.export_chat_invite_link(self.token, chat_id) - - def approve_chat_join_request(self, chat_id: Union[str, int], user_id: Union[int, str]) -> bool: - """ - Use this method to approve a chat join request. - The bot must be an administrator in the chat for this to work and must have - the can_invite_users administrator right. Returns True on success. - - Telegram documentation: https://core.telegram.org/bots/api#approvechatjoinrequest - - :param chat_id: Unique identifier for the target chat or username of the target supergroup - (in the format @supergroupusername) - :param user_id: Unique identifier of the target user - :return: True on success. - """ - return apihelper.approve_chat_join_request(self.token, chat_id, user_id) - - def decline_chat_join_request(self, chat_id: Union[str, int], user_id: Union[int, str]) -> bool: - """ - Use this method to decline a chat join request. - The bot must be an administrator in the chat for this to work and must have - the can_invite_users administrator right. Returns True on success. - - Telegram documentation: https://core.telegram.org/bots/api#declinechatjoinrequest - - :param chat_id: Unique identifier for the target chat or username of the target supergroup - (in the format @supergroupusername) - :param user_id: Unique identifier of the target user - :return: True on success. - """ - return apihelper.decline_chat_join_request(self.token, chat_id, user_id) - - def set_chat_photo(self, chat_id: Union[int, str], photo: Any) -> bool: - """ - Use this method to set a new profile photo for the chat. Photos can't be changed for private chats. - The bot must be an administrator in the chat for this to work and must have the appropriate admin rights. - Returns True on success. - Note: In regular groups (non-supergroups), this method will only work if the ‘All Members Are Admins’ - setting is off in the target group. - - Telegram documentation: https://core.telegram.org/bots/api#setchatphoto - - :param chat_id: Int or Str: Unique identifier for the target chat or username of the target channel - (in the format @channelusername) - :param photo: InputFile: New chat photo, uploaded using multipart/form-data - :return: - """ - return apihelper.set_chat_photo(self.token, chat_id, photo) - - def delete_chat_photo(self, chat_id: Union[int, str]) -> bool: - """ - Use this method to delete a chat photo. Photos can't be changed for private chats. - The bot must be an administrator in the chat for this to work and must have the appropriate admin rights. - Returns True on success. - Note: In regular groups (non-supergroups), this method will only work if the ‘All Members Are Admins’ setting is off in the target group. - - Telegram documentation: https://core.telegram.org/bots/api#deletechatphoto - - :param chat_id: Int or Str: Unique identifier for the target chat or username of the target channel - (in the format @channelusername) - """ - return apihelper.delete_chat_photo(self.token, chat_id) - - def get_my_commands(self, scope: Optional[types.BotCommandScope]=None, - language_code: Optional[str]=None) -> List[types.BotCommand]: - """ - Use this method to get the current list of the bot's commands. - Returns List of BotCommand on success. - - Telegram documentation: https://core.telegram.org/bots/api#getmycommands - - :param scope: The scope of users for which the commands are relevant. - Defaults to BotCommandScopeDefault. - :param language_code: A two-letter ISO 639-1 language code. If empty, - commands will be applied to all users from the given scope, - for whose language there are no dedicated commands - """ - result = apihelper.get_my_commands(self.token, scope, language_code) - return [types.BotCommand.de_json(cmd) for cmd in result] - - def set_my_commands(self, commands: List[types.BotCommand], - scope: Optional[types.BotCommandScope]=None, - language_code: Optional[str]=None) -> bool: - """ - Use this method to change the list of the bot's commands. - - Telegram documentation: https://core.telegram.org/bots/api#setmycommands - - :param commands: List of BotCommand. At most 100 commands can be specified. - :param scope: The scope of users for which the commands are relevant. - Defaults to BotCommandScopeDefault. - :param language_code: A two-letter ISO 639-1 language code. If empty, - commands will be applied to all users from the given scope, - for whose language there are no dedicated commands - :return: - """ - return apihelper.set_my_commands(self.token, commands, scope, language_code) - - def delete_my_commands(self, scope: Optional[types.BotCommandScope]=None, - language_code: Optional[str]=None) -> bool: - """ - Use this method to delete the list of the bot's commands for the given scope and user language. - After deletion, higher level commands will be shown to affected users. - Returns True on success. - - Telegram documentation: https://core.telegram.org/bots/api#deletemycommands - - :param scope: The scope of users for which the commands are relevant. - Defaults to BotCommandScopeDefault. - :param language_code: A two-letter ISO 639-1 language code. If empty, - commands will be applied to all users from the given scope, - for whose language there are no dedicated commands - """ - return apihelper.delete_my_commands(self.token, scope, language_code) - - def set_chat_title(self, chat_id: Union[int, str], title: str) -> bool: - """ - Use this method to change the title of a chat. Titles can't be changed for private chats. - The bot must be an administrator in the chat for this to work and must have the appropriate admin rights. - Returns True on success. - Note: In regular groups (non-supergroups), this method will only work if the ‘All Members Are Admins’ - setting is off in the target group. - - Telegram documentation: https://core.telegram.org/bots/api#setchattitle - - :param chat_id: Int or Str: Unique identifier for the target chat or username of the target channel - (in the format @channelusername) - :param title: New chat title, 1-255 characters - :return: - """ - return apihelper.set_chat_title(self.token, chat_id, title) - - def set_chat_description(self, chat_id: Union[int, str], description: Optional[str]=None) -> bool: - """ - Use this method to change the description of a supergroup or a channel. - The bot must be an administrator in the chat for this to work and must have the appropriate admin rights. - - Telegram documentation: https://core.telegram.org/bots/api#setchatdescription - - :param chat_id: Int or Str: Unique identifier for the target chat or username of the target channel - (in the format @channelusername) - :param description: Str: New chat description, 0-255 characters - :return: True on success. - """ - return apihelper.set_chat_description(self.token, chat_id, description) - - def pin_chat_message( - self, chat_id: Union[int, str], message_id: int, - disable_notification: Optional[bool]=False) -> bool: - """ - Use this method to pin a message in a supergroup. - The bot must be an administrator in the chat for this to work and must have the appropriate admin rights. - Returns True on success. - - Telegram documentation: https://core.telegram.org/bots/api#pinchatmessage - - :param chat_id: Int or Str: Unique identifier for the target chat or username of the target channel - (in the format @channelusername) - :param message_id: Int: Identifier of a message to pin - :param disable_notification: Bool: Pass True, if it is not necessary to send a notification - to all group members about the new pinned message - :return: - """ - return apihelper.pin_chat_message(self.token, chat_id, message_id, disable_notification) - - def unpin_chat_message(self, chat_id: Union[int, str], message_id: Optional[int]=None) -> bool: - """ - Use this method to unpin specific pinned message in a supergroup chat. - The bot must be an administrator in the chat for this to work and must have the appropriate admin rights. - Returns True on success. - - Telegram documentation: https://core.telegram.org/bots/api#unpinchatmessage - - :param chat_id: Int or Str: Unique identifier for the target chat or username of the target channel - (in the format @channelusername) - :param message_id: Int: Identifier of a message to unpin - :return: - """ - return apihelper.unpin_chat_message(self.token, chat_id, message_id) - - def unpin_all_chat_messages(self, chat_id: Union[int, str]) -> bool: - """ - Use this method to unpin a all pinned messages in a supergroup chat. - The bot must be an administrator in the chat for this to work and must have the appropriate admin rights. - Returns True on success. - - Telegram documentation: https://core.telegram.org/bots/api#unpinallchatmessages - - :param chat_id: Int or Str: Unique identifier for the target chat or username of the target channel - (in the format @channelusername) - :return: - """ - return apihelper.unpin_all_chat_messages(self.token, chat_id) - - def edit_message_text( - self, text: str, - chat_id: Optional[Union[int, str]]=None, - message_id: Optional[int]=None, - inline_message_id: Optional[str]=None, - parse_mode: Optional[str]=None, - entities: Optional[List[types.MessageEntity]]=None, - disable_web_page_preview: Optional[bool]=None, - reply_markup: Optional[REPLY_MARKUP_TYPES]=None) -> Union[types.Message, bool]: - """ - Use this method to edit text and game messages. - - Telegram documentation: https://core.telegram.org/bots/api#editmessagetext - - :param text: - :param chat_id: - :param message_id: - :param inline_message_id: - :param parse_mode: - :param entities: - :param disable_web_page_preview: - :param reply_markup: - :return: - """ - parse_mode = self.parse_mode if (parse_mode is None) else parse_mode - - result = apihelper.edit_message_text(self.token, text, chat_id, message_id, inline_message_id, parse_mode, - entities, disable_web_page_preview, reply_markup) - if type(result) == bool: # if edit inline message return is bool not Message. - return result - return types.Message.de_json(result) - - def edit_message_media( - self, media: Any, chat_id: Optional[Union[int, str]]=None, - message_id: Optional[int]=None, - inline_message_id: Optional[str]=None, - reply_markup: Optional[REPLY_MARKUP_TYPES]=None) -> Union[types.Message, bool]: - """ - Use this method to edit animation, audio, document, photo, or video messages. - If a message is a part of a message album, then it can be edited only to a photo or a video. - Otherwise, message type can be changed arbitrarily. When inline message is edited, new file can't be uploaded. - Use previously uploaded file via its file_id or specify a URL. - - Telegram documentation: https://core.telegram.org/bots/api#editmessagemedia - - :param media: - :param chat_id: - :param message_id: - :param inline_message_id: - :param reply_markup: - :return: - """ - result = apihelper.edit_message_media(self.token, media, chat_id, message_id, inline_message_id, reply_markup) - if type(result) == bool: # if edit inline message return is bool not Message. - return result - return types.Message.de_json(result) - - def edit_message_reply_markup( - self, chat_id: Optional[Union[int, str]]=None, - message_id: Optional[int]=None, - inline_message_id: Optional[str]=None, - reply_markup: Optional[REPLY_MARKUP_TYPES]=None) -> Union[types.Message, bool]: - """ - Use this method to edit only the reply markup of messages. - - Telegram documentation: https://core.telegram.org/bots/api#editmessagereplymarkup - - :param chat_id: - :param message_id: - :param inline_message_id: - :param reply_markup: - :return: - """ - result = apihelper.edit_message_reply_markup(self.token, chat_id, message_id, inline_message_id, reply_markup) - if type(result) == bool: - return result - return types.Message.de_json(result) - - def send_game( - self, chat_id: Union[int, str], game_short_name: str, - disable_notification: Optional[bool]=None, - reply_to_message_id: Optional[int]=None, - reply_markup: Optional[REPLY_MARKUP_TYPES]=None, - timeout: Optional[int]=None, - allow_sending_without_reply: Optional[bool]=None, - protect_content: Optional[bool]=None) -> types.Message: - """ - Used to send the game. - - Telegram documentation: https://core.telegram.org/bots/api#sendgame - - :param chat_id: - :param game_short_name: - :param disable_notification: - :param reply_to_message_id: - :param reply_markup: - :param timeout: - :param allow_sending_without_reply: - :param protect_content: - :return: - """ - result = apihelper.send_game( - self.token, chat_id, game_short_name, disable_notification, - reply_to_message_id, reply_markup, timeout, - allow_sending_without_reply, protect_content) - return types.Message.de_json(result) - - def set_game_score( - self, user_id: Union[int, str], score: int, - force: Optional[bool]=None, - chat_id: Optional[Union[int, str]]=None, - message_id: Optional[int]=None, - inline_message_id: Optional[str]=None, - disable_edit_message: Optional[bool]=None) -> Union[types.Message, bool]: - """ - Sets the value of points in the game to a specific user. - - Telegram documentation: https://core.telegram.org/bots/api#setgamecore - - :param user_id: - :param score: - :param force: - :param chat_id: - :param message_id: - :param inline_message_id: - :param disable_edit_message: - :return: - """ - result = apihelper.set_game_score(self.token, user_id, score, force, disable_edit_message, chat_id, - message_id, inline_message_id) - if type(result) == bool: - return result - return types.Message.de_json(result) - - def get_game_high_scores( - self, user_id: int, chat_id: Optional[Union[int, str]]=None, - message_id: Optional[int]=None, - inline_message_id: Optional[str]=None) -> List[types.GameHighScore]: - """ - Gets top points and game play. - - Telegram documentation: https://core.telegram.org/bots/api#getgamehighscores - - :param user_id: - :param chat_id: - :param message_id: - :param inline_message_id: - :return: - """ - result = apihelper.get_game_high_scores(self.token, user_id, chat_id, message_id, inline_message_id) - return [types.GameHighScore.de_json(r) for r in result] - - # TODO: rewrite this method like in API - def send_invoice( - self, chat_id: Union[int, str], title: str, description: str, - invoice_payload: str, provider_token: str, currency: str, - prices: List[types.LabeledPrice], start_parameter: Optional[str]=None, - photo_url: Optional[str]=None, photo_size: Optional[int]=None, - photo_width: Optional[int]=None, photo_height: Optional[int]=None, - need_name: Optional[bool]=None, need_phone_number: Optional[bool]=None, - need_email: Optional[bool]=None, need_shipping_address: Optional[bool]=None, - send_phone_number_to_provider: Optional[bool]=None, - send_email_to_provider: Optional[bool]=None, - is_flexible: Optional[bool]=None, - disable_notification: Optional[bool]=None, - reply_to_message_id: Optional[int]=None, - reply_markup: Optional[REPLY_MARKUP_TYPES]=None, - provider_data: Optional[str]=None, - timeout: Optional[int]=None, - allow_sending_without_reply: Optional[bool]=None, - max_tip_amount: Optional[int] = None, - suggested_tip_amounts: Optional[List[int]]=None, - protect_content: Optional[bool]=None) -> types.Message: - """ - Sends invoice. - - Telegram documentation: https://core.telegram.org/bots/api#sendinvoice - - :param chat_id: Unique identifier for the target private chat - :param title: Product name - :param description: Product description - :param invoice_payload: Bot-defined invoice payload, 1-128 bytes. This will not be displayed to the user, - use for your internal processes. - :param provider_token: Payments provider token, obtained via @Botfather - :param currency: Three-letter ISO 4217 currency code, - see https://core.telegram.org/bots/payments#supported-currencies - :param prices: Price breakdown, a list of components - (e.g. product price, tax, discount, delivery cost, delivery tax, bonus, etc.) - :param start_parameter: Unique deep-linking parameter that can be used to generate this invoice - when used as a start parameter - :param photo_url: URL of the product photo for the invoice. Can be a photo of the goods - or a marketing image for a service. People like it better when they see what they are paying for. - :param photo_size: Photo size - :param photo_width: Photo width - :param photo_height: Photo height - :param need_name: Pass True, if you require the user's full name to complete the order - :param need_phone_number: Pass True, if you require the user's phone number to complete the order - :param need_email: Pass True, if you require the user's email to complete the order - :param need_shipping_address: Pass True, if you require the user's shipping address to complete the order - :param is_flexible: Pass True, if the final price depends on the shipping method - :param send_phone_number_to_provider: Pass True, if user's phone number should be sent to provider - :param send_email_to_provider: Pass True, if user's email address should be sent to provider - :param disable_notification: Sends the message silently. Users will receive a notification with no sound. - :param reply_to_message_id: If the message is a reply, ID of the original message - :param reply_markup: A JSON-serialized object for an inline keyboard. If empty, - one 'Pay total price' button will be shown. If not empty, the first button must be a Pay button - :param provider_data: A JSON-serialized data about the invoice, which will be shared with the payment provider. - A detailed description of required fields should be provided by the payment provider. - :param timeout: - :param allow_sending_without_reply: - :param max_tip_amount: The maximum accepted amount for tips in the smallest units of the currency - :param suggested_tip_amounts: A JSON-serialized array of suggested amounts of tips in the smallest - units of the currency. At most 4 suggested tip amounts can be specified. The suggested tip - amounts must be positive, passed in a strictly increased order and must not exceed max_tip_amount. - :param protect_content: - :return: - """ - result = apihelper.send_invoice( - self.token, chat_id, title, description, invoice_payload, provider_token, - currency, prices, start_parameter, photo_url, photo_size, photo_width, - photo_height, need_name, need_phone_number, need_email, need_shipping_address, - send_phone_number_to_provider, send_email_to_provider, is_flexible, disable_notification, - reply_to_message_id, reply_markup, provider_data, timeout, allow_sending_without_reply, - max_tip_amount, suggested_tip_amounts, protect_content) - return types.Message.de_json(result) - - # noinspection PyShadowingBuiltins - # TODO: rewrite this method like in API - def send_poll( - self, chat_id: Union[int, str], question: str, options: List[str], - is_anonymous: Optional[bool]=None, type: Optional[str]=None, - allows_multiple_answers: Optional[bool]=None, - correct_option_id: Optional[int]=None, - explanation: Optional[str]=None, - explanation_parse_mode: Optional[str]=None, - open_period: Optional[int]=None, - close_date: Optional[Union[int, datetime]]=None, - is_closed: Optional[bool]=None, - disable_notification: Optional[bool]=False, - reply_to_message_id: Optional[int]=None, - reply_markup: Optional[REPLY_MARKUP_TYPES]=None, - allow_sending_without_reply: Optional[bool]=None, - timeout: Optional[int]=None, - explanation_entities: Optional[List[types.MessageEntity]]=None, - protect_content: Optional[bool]=None) -> types.Message: - """ - Sends a poll. - - Telegram documentation: https://core.telegram.org/bots/api#sendpoll - - :param chat_id: - :param question: - :param options: array of str with answers - :param is_anonymous: - :param type: - :param allows_multiple_answers: - :param correct_option_id: - :param explanation: - :param explanation_parse_mode: - :param open_period: - :param close_date: - :param is_closed: - :param disable_notification: - :param reply_to_message_id: - :param allow_sending_without_reply: - :param reply_markup: - :param timeout: - :param explanation_entities: - :param protect_content: - :return: - """ - if isinstance(question, types.Poll): - raise RuntimeError("The send_poll signature was changed, please see send_poll function details.") - - return types.Message.de_json( - apihelper.send_poll( - self.token, chat_id, - question, options, - is_anonymous, type, allows_multiple_answers, correct_option_id, - explanation, explanation_parse_mode, open_period, close_date, is_closed, - disable_notification, reply_to_message_id, allow_sending_without_reply, - reply_markup, timeout, explanation_entities, protect_content)) - - def stop_poll( - self, chat_id: Union[int, str], message_id: int, - reply_markup: Optional[REPLY_MARKUP_TYPES]=None) -> types.Poll: - """ - Stops a poll. - - Telegram documentation: https://core.telegram.org/bots/api#stoppoll - - :param chat_id: - :param message_id: - :param reply_markup: - :return: - """ - return types.Poll.de_json(apihelper.stop_poll(self.token, chat_id, message_id, reply_markup)) - - def answer_shipping_query( - self, shipping_query_id: str, ok: bool, - shipping_options: Optional[List[types.ShippingOption]]=None, - error_message: Optional[str]=None) -> bool: - """ - Asks for an answer to a shipping question. - - Telegram documentation: https://core.telegram.org/bots/api#answershippingquery - - :param shipping_query_id: - :param ok: - :param shipping_options: - :param error_message: - :return: - """ - return apihelper.answer_shipping_query(self.token, shipping_query_id, ok, shipping_options, error_message) - - def answer_pre_checkout_query( - self, pre_checkout_query_id: int, ok: bool, - error_message: Optional[str]=None) -> bool: - """ - Response to a request for pre-inspection. - - Telegram documentation: https://core.telegram.org/bots/api#answerprecheckoutquery - - :param pre_checkout_query_id: - :param ok: - :param error_message: - :return: - """ - return apihelper.answer_pre_checkout_query(self.token, pre_checkout_query_id, ok, error_message) - - def edit_message_caption( - self, caption: str, chat_id: Optional[Union[int, str]]=None, - message_id: Optional[int]=None, - inline_message_id: Optional[str]=None, - parse_mode: Optional[str]=None, - caption_entities: Optional[List[types.MessageEntity]]=None, - reply_markup: Optional[REPLY_MARKUP_TYPES]=None) -> Union[types.Message, bool]: - """ - Use this method to edit captions of messages. - - Telegram documentation: https://core.telegram.org/bots/api#editmessagecaption - - :param caption: - :param chat_id: - :param message_id: - :param inline_message_id: - :param parse_mode: - :param caption_entities: - :param reply_markup: - :return: - """ - parse_mode = self.parse_mode if (parse_mode is None) else parse_mode - - result = apihelper.edit_message_caption(self.token, caption, chat_id, message_id, inline_message_id, - parse_mode, caption_entities, reply_markup) - if type(result) == bool: - return result - return types.Message.de_json(result) - - def reply_to(self, message: types.Message, text: str, **kwargs) -> types.Message: - """ - Convenience function for `send_message(message.chat.id, text, reply_to_message_id=message.message_id, **kwargs)` - - :param message: - :param text: - :param kwargs: - :return: - """ - return self.send_message(message.chat.id, text, reply_to_message_id=message.message_id, **kwargs) - - def answer_inline_query( - self, inline_query_id: str, - results: List[Any], - cache_time: Optional[int]=None, - is_personal: Optional[bool]=None, - next_offset: Optional[str]=None, - switch_pm_text: Optional[str]=None, - switch_pm_parameter: Optional[str]=None) -> bool: - """ - Use this method to send answers to an inline query. On success, True is returned. - No more than 50 results per query are allowed. - - Telegram documentation: https://core.telegram.org/bots/api#answerinlinequery - - :param inline_query_id: Unique identifier for the answered query - :param results: Array of results for the inline query - :param cache_time: The maximum amount of time in seconds that the result of the inline query - may be cached on the server. - :param is_personal: Pass True, if results may be cached on the server side only for - the user that sent the query. - :param next_offset: Pass the offset that a client should send in the next query with the same text - to receive more results. - :param switch_pm_parameter: If passed, clients will display a button with specified text that switches the user - to a private chat with the bot and sends the bot a start message with the parameter switch_pm_parameter - :param switch_pm_text: Parameter for the start message sent to the bot when user presses the switch button - :return: True means success. - """ - return apihelper.answer_inline_query(self.token, inline_query_id, results, cache_time, is_personal, next_offset, - switch_pm_text, switch_pm_parameter) - - def answer_callback_query( - self, callback_query_id: int, - text: Optional[str]=None, show_alert: Optional[bool]=None, - url: Optional[str]=None, cache_time: Optional[int]=None) -> bool: - """ - Use this method to send answers to callback queries sent from inline keyboards. The answer will be displayed to - the user as a notification at the top of the chat screen or as an alert. - - Telegram documentation: https://core.telegram.org/bots/api#answercallbackquery - - :param callback_query_id: - :param text: - :param show_alert: - :param url: - :param cache_time: - :return: - """ - return apihelper.answer_callback_query(self.token, callback_query_id, text, show_alert, url, cache_time) - - def set_sticker_set_thumb( - self, name: str, user_id: int, thumb: Union[Any, str]=None): - """ - Use this method to set the thumbnail of a sticker set. - Animated thumbnails can be set for animated sticker sets only. Returns True on success. - - Telegram documentation: https://core.telegram.org/bots/api#setstickersetthumb - - :param name: Sticker set name - :param user_id: User identifier - :param thumb: - """ - return apihelper.set_sticker_set_thumb(self.token, name, user_id, thumb) - - def get_sticker_set(self, name: str) -> types.StickerSet: - """ - Use this method to get a sticker set. On success, a StickerSet object is returned. - - Telegram documentation: https://core.telegram.org/bots/api#getstickerset - - :param name: - :return: - """ - result = apihelper.get_sticker_set(self.token, name) - return types.StickerSet.de_json(result) - - def upload_sticker_file(self, user_id: int, png_sticker: Union[Any, str]) -> types.File: - """ - Use this method to upload a .png file with a sticker for later use in createNewStickerSet and addStickerToSet - methods (can be used multiple times). Returns the uploaded File on success. - - Telegram documentation: https://core.telegram.org/bots/api#uploadstickerfile - - :param user_id: - :param png_sticker: - :return: - """ - result = apihelper.upload_sticker_file(self.token, user_id, png_sticker) - return types.File.de_json(result) - - def create_new_sticker_set( - self, user_id: int, name: str, title: str, - emojis: str, - png_sticker: Union[Any, str]=None, - tgs_sticker: Union[Any, str]=None, - webm_sticker: Union[Any, str]=None, - contains_masks: Optional[bool]=None, - mask_position: Optional[types.MaskPosition]=None) -> bool: - """ - Use this method to create new sticker set owned by a user. - The bot will be able to edit the created sticker set. - Returns True on success. - - Telegram documentation: https://core.telegram.org/bots/api#createnewstickerset - - :param user_id: - :param name: - :param title: - :param emojis: - :param png_sticker: - :param tgs_sticker: - :param webm_sticker: - :param contains_masks: - :param mask_position: - :return: - """ - return apihelper.create_new_sticker_set( - self.token, user_id, name, title, emojis, png_sticker, tgs_sticker, - contains_masks, mask_position, webm_sticker) - - def add_sticker_to_set( - self, user_id: int, name: str, emojis: str, - png_sticker: Optional[Union[Any, str]]=None, - tgs_sticker: Optional[Union[Any, str]]=None, - webm_sticker: Optional[Union[Any, str]]=None, - mask_position: Optional[types.MaskPosition]=None) -> bool: - """ - Use this method to add a new sticker to a set created by the bot. - It's required to pass `png_sticker` or `tgs_sticker`. - Returns True on success. - - Telegram documentation: https://core.telegram.org/bots/api#addstickertoset - - :param user_id: - :param name: - :param emojis: - :param png_sticker: Required if `tgs_sticker` is None - :param tgs_sticker: Required if `png_sticker` is None - :param webm_sticker: - :param mask_position: - :return: - """ - return apihelper.add_sticker_to_set( - self.token, user_id, name, emojis, png_sticker, tgs_sticker, mask_position, webm_sticker) - - def set_sticker_position_in_set(self, sticker: str, position: int) -> bool: - """ - Use this method to move a sticker in a set created by the bot to a specific position . Returns True on success. - - Telegram documentation: https://core.telegram.org/bots/api#setstickerpositioninset - - :param sticker: - :param position: - :return: - """ - return apihelper.set_sticker_position_in_set(self.token, sticker, position) - - def delete_sticker_from_set(self, sticker: str) -> bool: - """ - Use this method to delete a sticker from a set created by the bot. Returns True on success. - - Telegram documentation: https://core.telegram.org/bots/api#deletestickerfromset - - :param sticker: - :return: - """ - return apihelper.delete_sticker_from_set(self.token, sticker) - - def register_for_reply( - self, message: types.Message, callback: Callable, *args, **kwargs) -> None: - """ - Registers a callback function to be notified when a reply to `message` arrives. - - Warning: In case `callback` as lambda function, saving reply handlers will not work. - - :param message: The message for which we are awaiting a reply. - :param callback: The callback function to be called when a reply arrives. Must accept one `message` - parameter, which will contain the replied message. - """ - message_id = message.message_id - self.register_for_reply_by_message_id(message_id, callback, *args, **kwargs) - - def register_for_reply_by_message_id( - self, message_id: int, callback: Callable, *args, **kwargs) -> None: - """ - Registers a callback function to be notified when a reply to `message` arrives. - - Warning: In case `callback` as lambda function, saving reply handlers will not work. - - :param message_id: The id of the message for which we are awaiting a reply. - :param callback: The callback function to be called when a reply arrives. Must accept one `message` - parameter, which will contain the replied message. - """ - self.reply_backend.register_handler(message_id, Handler(callback, *args, **kwargs)) - - def _notify_reply_handlers(self, new_messages) -> None: - """ - Notify handlers of the answers - - :param new_messages: - :return: - """ - for message in new_messages: - if hasattr(message, "reply_to_message") and message.reply_to_message is not None: - handlers = self.reply_backend.get_handlers(message.reply_to_message.message_id) - if handlers: - for handler in handlers: - self._exec_task(handler["callback"], message, *handler["args"], **handler["kwargs"]) - - def register_next_step_handler( - self, message: types.Message, callback: Callable, *args, **kwargs) -> None: - """ - Registers a callback function to be notified when new message arrives after `message`. - - Warning: In case `callback` as lambda function, saving next step handlers will not work. - - :param message: The message for which we want to handle new message in the same chat. - :param callback: The callback function which next new message arrives. - :param args: Args to pass in callback func - :param kwargs: Args to pass in callback func - """ - chat_id = message.chat.id - self.register_next_step_handler_by_chat_id(chat_id, callback, *args, **kwargs) - - - def setup_middleware(self, middleware: BaseMiddleware): - """ - Register middleware - - :param middleware: Subclass of `telebot.handler_backends.BaseMiddleware` - :return: None - """ - if not self.use_class_middlewares: - logger.warning('Middleware is not enabled. Pass use_class_middlewares=True to enable it.') - return - self.middlewares.append(middleware) - - - - def set_state(self, user_id: int, state: Union[int, str], chat_id: int=None) -> None: - """ - Sets a new state of a user. - - :param user_id: - :param state: new state. can be string or integer. - :param chat_id: - """ - if chat_id is None: - chat_id = user_id - self.current_states.set_state(chat_id, user_id, state) - - def reset_data(self, user_id: int, chat_id: int=None): - """ - Reset data for a user in chat. - - :param user_id: - :param chat_id: - """ - if chat_id is None: - chat_id = user_id - - self.current_states.reset_data(chat_id, user_id) - def delete_state(self, user_id: int, chat_id: int=None) -> None: - """ - Delete the current state of a user. - - :param user_id: - :param chat_id: - :return: - """ - if chat_id is None: - chat_id = user_id - self.current_states.delete_state(chat_id, user_id) - - def retrieve_data(self, user_id: int, chat_id: int=None) -> Optional[Union[int, str]]: - if chat_id is None: - chat_id = user_id - return self.current_states.get_interactive_data(chat_id, user_id) - - def get_state(self, user_id: int, chat_id: int=None) -> Optional[Union[int, str]]: - """ - Get current state of a user. - - :param user_id: - :param chat_id: - :return: state of a user - """ - if chat_id is None: - chat_id = user_id - return self.current_states.get_state(chat_id, user_id) - - def add_data(self, user_id: int, chat_id:int=None, **kwargs): - """ - Add data to states. - - :param user_id: - :param chat_id: - """ - if chat_id is None: - chat_id = user_id - for key, value in kwargs.items(): - self.current_states.set_data(chat_id, user_id, key, value) - - def register_next_step_handler_by_chat_id( - self, chat_id: Union[int, str], callback: Callable, *args, **kwargs) -> None: - """ - Registers a callback function to be notified when new message arrives after `message`. - - Warning: In case `callback` as lambda function, saving next step handlers will not work. - - :param chat_id: The chat for which we want to handle new message. - :param callback: The callback function which next new message arrives. - :param args: Args to pass in callback func - :param kwargs: Args to pass in callback func - """ - self.next_step_backend.register_handler(chat_id, Handler(callback, *args, **kwargs)) - - def clear_step_handler(self, message: types.Message) -> None: - """ - Clears all callback functions registered by register_next_step_handler(). - - :param message: The message for which we want to handle new message after that in same chat. - """ - chat_id = message.chat.id - self.clear_step_handler_by_chat_id(chat_id) - - def clear_step_handler_by_chat_id(self, chat_id: Union[int, str]) -> None: - """ - Clears all callback functions registered by register_next_step_handler(). - - :param chat_id: The chat for which we want to clear next step handlers - """ - self.next_step_backend.clear_handlers(chat_id) - - def clear_reply_handlers(self, message: types.Message) -> None: - """ - Clears all callback functions registered by register_for_reply() and register_for_reply_by_message_id(). - - :param message: The message for which we want to clear reply handlers - """ - message_id = message.message_id - self.clear_reply_handlers_by_message_id(message_id) - - def clear_reply_handlers_by_message_id(self, message_id: int) -> None: - """ - Clears all callback functions registered by register_for_reply() and register_for_reply_by_message_id(). - - :param message_id: The message id for which we want to clear reply handlers - """ - self.reply_backend.clear_handlers(message_id) - - def _notify_next_handlers(self, new_messages): - """ - Description: TBD - - :param new_messages: - :return: - """ - for i, message in enumerate(new_messages): - need_pop = False - handlers = self.next_step_backend.get_handlers(message.chat.id) - if handlers: - for handler in handlers: - need_pop = True - self._exec_task(handler["callback"], message, *handler["args"], **handler["kwargs"]) - if need_pop: - # removing message that was detected with next_step_handler - new_messages.pop(i) - - @staticmethod - def _build_handler_dict(handler, pass_bot=False, **filters): - """ - Builds a dictionary for a handler - - :param handler: - :param filters: - :return: - """ - return { - 'function': handler, - 'pass_bot': pass_bot, - 'filters': {ftype: fvalue for ftype, fvalue in filters.items() if fvalue is not None} - # Remove None values, they are skipped in _test_filter anyway - #'filters': filters - } - - def middleware_handler(self, update_types=None): - """ - Middleware handler decorator. - - This decorator can be used to decorate functions that must be handled as middlewares before entering any other - message handlers - But, be careful and check type of the update inside the handler if more than one update_type is given - - Example: - - .. code-block:: python3 - - bot = TeleBot('TOKEN') - - # Print post message text before entering to any post_channel handlers - @bot.middleware_handler(update_types=['channel_post', 'edited_channel_post']) - def print_channel_post_text(bot_instance, channel_post): - print(channel_post.text) - - # Print update id before entering to any handlers - @bot.middleware_handler() - def print_channel_post_text(bot_instance, update): - print(update.update_id) - - :param update_types: Optional list of update types that can be passed into the middleware handler. - """ - def decorator(handler): - self.add_middleware_handler(handler, update_types) - return handler - - return decorator - - def add_middleware_handler(self, handler, update_types=None): - """ - Add middleware handler - :param handler: - :param update_types: - :return: - """ - if not apihelper.ENABLE_MIDDLEWARE: - raise RuntimeError("Middleware is not enabled. Use apihelper.ENABLE_MIDDLEWARE before initialising TeleBot.") - - if update_types: - for update_type in update_types: - self.typed_middleware_handlers[update_type].append(handler) - else: - self.default_middleware_handlers.append(handler) - - # function register_middleware_handler - def register_middleware_handler(self, callback, update_types=None): - """ - Middleware handler decorator. - - This function will create a decorator that can be used to decorate functions that must be handled as middlewares before entering any other - message handlers - But, be careful and check type of the update inside the handler if more than one update_type is given - - Example: - - bot = TeleBot('TOKEN') - - bot.register_middleware_handler(print_channel_post_text, update_types=['channel_post', 'edited_channel_post']) - - :param callback: - :param update_types: Optional list of update types that can be passed into the middleware handler. - """ - self.add_middleware_handler(callback, update_types) - - @staticmethod - def check_commands_input(commands, method_name): - if not isinstance(commands, list) or not all(isinstance(item, str) for item in commands): - logger.error(f"{method_name}: Commands filter should be list of strings (commands), unknown type supplied to the 'commands' filter list. Not able to use the supplied type.") - - @staticmethod - def check_regexp_input(regexp, method_name): - if not isinstance(regexp, str): - logger.error(f"{method_name}: Regexp filter should be string. Not able to use the supplied type.") - - def message_handler(self, commands=None, regexp=None, func=None, content_types=None, chat_types=None, **kwargs): - """ - Message handler decorator. - This decorator can be used to decorate functions that must handle certain types of messages. - All message handlers are tested in the order they were added. - - Example: - - .. code-block:: python - - bot = TeleBot('TOKEN') - - # Handles all messages which text matches regexp. - @bot.message_handler(regexp='someregexp') - def command_help(message): - bot.send_message(message.chat.id, 'Did someone call for help?') - - # Handles messages in private chat - @bot.message_handler(chat_types=['private']) # You can add more chat types - def command_help(message): - bot.send_message(message.chat.id, 'Private chat detected, sir!') - - # Handle all sent documents of type 'text/plain'. - @bot.message_handler(func=lambda message: message.document.mime_type == 'text/plain', - content_types=['document']) - def command_handle_document(message): - bot.send_message(message.chat.id, 'Document received, sir!') - - # Handle all other messages. - @bot.message_handler(func=lambda message: True, content_types=['audio', 'photo', 'voice', 'video', 'document', - 'text', 'location', 'contact', 'sticker']) - def default_command(message): - bot.send_message(message.chat.id, "This is the default command handler.") - - :param commands: Optional list of strings (commands to handle). - :param regexp: Optional regular expression. - :param func: Optional lambda function. The lambda receives the message to test as the first parameter. - It must return True if the command should handle the message. - :param content_types: Supported message content types. Must be a list. Defaults to ['text']. - :param chat_types: list of chat types - """ - if content_types is None: - content_types = ["text"] - - method_name = "message_handler" - - if commands is not None: - self.check_commands_input(commands, method_name) - if isinstance(commands, str): - commands = [commands] - - if regexp is not None: - self.check_regexp_input(regexp, method_name) - - if isinstance(content_types, str): - logger.warning("message_handler: 'content_types' filter should be List of strings (content types), not string.") - content_types = [content_types] - - def decorator(handler): - handler_dict = self._build_handler_dict(handler, - chat_types=chat_types, - content_types=content_types, - commands=commands, - regexp=regexp, - func=func, - **kwargs) - self.add_message_handler(handler_dict) - return handler - - return decorator - - def add_message_handler(self, handler_dict): - """ - Adds a message handler - Note that you should use register_message_handler to add message_handler to the bot. - - :param handler_dict: - :return: - """ - self.message_handlers.append(handler_dict) - - def register_message_handler(self, callback, content_types=None, commands=None, regexp=None, func=None, chat_types=None, pass_bot=False, **kwargs): - """ - Registers message handler. - - :param callback: function to be called - :param content_types: list of content_types - :param commands: list of commands - :param regexp: - :param func: - :param chat_types: True for private chat - :param pass_bot: Pass TeleBot to handler. - :return: decorated function - """ - method_name = "register_message_handler" - - if commands is not None: - self.check_commands_input(commands, method_name) - if isinstance(commands, str): - commands = [commands] - - if regexp is not None: - self.check_regexp_input(regexp, method_name) - - if isinstance(content_types, str): - logger.warning("register_message_handler: 'content_types' filter should be List of strings (content types), not string.") - content_types = [content_types] - - - - handler_dict = self._build_handler_dict(callback, - chat_types=chat_types, - content_types=content_types, - commands=commands, - regexp=regexp, - func=func, - pass_bot=pass_bot, - **kwargs) - self.add_message_handler(handler_dict) - - def edited_message_handler(self, commands=None, regexp=None, func=None, content_types=None, chat_types=None, **kwargs): - """ - Edit message handler decorator - - :param commands: - :param regexp: - :param func: - :param content_types: - :param chat_types: list of chat types - :param kwargs: - :return: - """ - if content_types is None: - content_types = ["text"] - - method_name = "edited_message_handler" - - if commands is not None: - self.check_commands_input(commands, method_name) - if isinstance(commands, str): - commands = [commands] - - if regexp is not None: - self.check_regexp_input(regexp, method_name) - - if isinstance(content_types, str): - logger.warning("edited_message_handler: 'content_types' filter should be List of strings (content types), not string.") - content_types = [content_types] - - def decorator(handler): - handler_dict = self._build_handler_dict(handler, - chat_types=chat_types, - content_types=content_types, - commands=commands, - regexp=regexp, - func=func, - **kwargs) - self.add_edited_message_handler(handler_dict) - return handler - - return decorator - - def add_edited_message_handler(self, handler_dict): - """ - Adds the edit message handler - Note that you should use register_edited_message_handler to add edited_message_handler to the bot. - :param handler_dict: - :return: - """ - self.edited_message_handlers.append(handler_dict) - - def register_edited_message_handler(self, callback, content_types=None, commands=None, regexp=None, func=None, chat_types=None, pass_bot=False, **kwargs): - """ - Registers edited message handler. - - :param callback: function to be called - :param content_types: list of content_types - :param commands: list of commands - :param regexp: - :param func: - :param chat_types: True for private chat - :param pass_bot: Pass TeleBot to handler. - :return: decorated function - """ - method_name = "register_edited_message_handler" - - if commands is not None: - self.check_commands_input(commands, method_name) - if isinstance(commands, str): - commands = [commands] - - if regexp is not None: - self.check_regexp_input(regexp, method_name) - - if isinstance(content_types, str): - logger.warning("register_edited_message_handler: 'content_types' filter should be List of strings (content types), not string.") - content_types = [content_types] - - handler_dict = self._build_handler_dict(callback, - chat_types=chat_types, - content_types=content_types, - commands=commands, - regexp=regexp, - func=func, - pass_bot=pass_bot, - **kwargs) - self.add_edited_message_handler(handler_dict) - - - def channel_post_handler(self, commands=None, regexp=None, func=None, content_types=None, **kwargs): - """ - Channel post handler decorator - - :param commands: - :param regexp: - :param func: - :param content_types: - :param kwargs: - :return: - """ - if content_types is None: - content_types = ["text"] - - method_name = "channel_post_handler" - - if commands is not None: - self.check_commands_input(commands, method_name) - if isinstance(commands, str): - commands = [commands] - - if regexp is not None: - self.check_regexp_input(regexp, method_name) - - if isinstance(content_types, str): - logger.warning("channel_post_handler: 'content_types' filter should be List of strings (content types), not string.") - content_types = [content_types] - - def decorator(handler): - handler_dict = self._build_handler_dict(handler, - content_types=content_types, - commands=commands, - regexp=regexp, - func=func, - **kwargs) - self.add_channel_post_handler(handler_dict) - return handler - - return decorator - - def add_channel_post_handler(self, handler_dict): - """ - Adds channel post handler - Note that you should use register_channel_post_handler to add channel_post_handler to the bot. - - :param handler_dict: - :return: - """ - self.channel_post_handlers.append(handler_dict) - - def register_channel_post_handler(self, callback, content_types=None, commands=None, regexp=None, func=None, pass_bot=False, **kwargs): - """ - Registers channel post message handler. - - :param callback: function to be called - :param content_types: list of content_types - :param commands: list of commands - :param regexp: - :param func: - :param pass_bot: Pass TeleBot to handler. - :return: decorated function - """ - method_name = "register_channel_post_handler" - - if commands is not None: - self.check_commands_input(commands, method_name) - if isinstance(commands, str): - commands = [commands] - - if regexp is not None: - self.check_regexp_input(regexp, method_name) - - if isinstance(content_types, str): - logger.warning("register_channel_post_handler: 'content_types' filter should be List of strings (content types), not string.") - content_types = [content_types] - - handler_dict = self._build_handler_dict(callback, - content_types=content_types, - commands=commands, - regexp=regexp, - func=func, - pass_bot=pass_bot, - **kwargs) - self.add_channel_post_handler(handler_dict) - - def edited_channel_post_handler(self, commands=None, regexp=None, func=None, content_types=None, **kwargs): - """ - Edit channel post handler decorator - - :param commands: - :param regexp: - :param func: - :param content_types: - :param kwargs: - :return: - """ - if content_types is None: - content_types = ["text"] - - method_name = "edited_channel_post_handler" - - if commands is not None: - self.check_commands_input(commands, method_name) - if isinstance(commands, str): - commands = [commands] - - if regexp is not None: - self.check_regexp_input(regexp, method_name) - - if isinstance(content_types, str): - logger.warning("edited_channel_post_handler: 'content_types' filter should be List of strings (content types), not string.") - content_types = [content_types] - - def decorator(handler): - handler_dict = self._build_handler_dict(handler, - content_types=content_types, - commands=commands, - regexp=regexp, - func=func, - **kwargs) - self.add_edited_channel_post_handler(handler_dict) - return handler - - return decorator - - def add_edited_channel_post_handler(self, handler_dict): - """ - Adds the edit channel post handler - Note that you should use register_edited_channel_post_handler to add edited_channel_post_handler to the bot. - - :param handler_dict: - :return: - """ - self.edited_channel_post_handlers.append(handler_dict) - - def register_edited_channel_post_handler(self, callback, content_types=None, commands=None, regexp=None, func=None, pass_bot=False, **kwargs): - """ - Registers edited channel post message handler. - - :param callback: function to be called - :param content_types: list of content_types - :param commands: list of commands - :param regexp: - :param func: - :param pass_bot: Pass TeleBot to handler. - :return: decorated function - """ - method_name = "register_edited_channel_post_handler" - - if commands is not None: - self.check_commands_input(commands, method_name) - if isinstance(commands, str): - commands = [commands] - - if regexp is not None: - self.check_regexp_input(regexp, method_name) - - if isinstance(content_types, str): - logger.warning("register_edited_channel_post_handler: 'content_types' filter should be List of strings (content types), not string.") - content_types = [content_types] - - handler_dict = self._build_handler_dict(callback, - content_types=content_types, - commands=commands, - regexp=regexp, - func=func, - pass_bot=pass_bot, - **kwargs) - self.add_edited_channel_post_handler(handler_dict) - - def inline_handler(self, func, **kwargs): - """ - Inline call handler decorator - - :param func: - :param kwargs: - :return: - """ - def decorator(handler): - handler_dict = self._build_handler_dict(handler, func=func, **kwargs) - self.add_inline_handler(handler_dict) - return handler - - return decorator - - def add_inline_handler(self, handler_dict): - """ - Adds inline call handler - Note that you should use register_inline_handler to add inline_handler to the bot. - - :param handler_dict: - :return: - """ - self.inline_handlers.append(handler_dict) - - def register_inline_handler(self, callback, func, pass_bot=False, **kwargs): - """ - Registers inline handler. - - :param callback: function to be called - :param func: - :param pass_bot: Pass TeleBot to handler. - :return: decorated function - """ - handler_dict = self._build_handler_dict(callback, func=func, pass_bot=pass_bot, **kwargs) - self.add_inline_handler(handler_dict) - - def chosen_inline_handler(self, func, **kwargs): - """ - Description: TBD - - :param func: - :param kwargs: - :return: - """ - def decorator(handler): - handler_dict = self._build_handler_dict(handler, func=func, **kwargs) - self.add_chosen_inline_handler(handler_dict) - return handler - - return decorator - - def add_chosen_inline_handler(self, handler_dict): - """ - Description: TBD - Note that you should use register_chosen_inline_handler to add chosen_inline_handler to the bot. - - :param handler_dict: - :return: - """ - self.chosen_inline_handlers.append(handler_dict) - - def register_chosen_inline_handler(self, callback, func, pass_bot=False, **kwargs): - """ - Registers chosen inline handler. - :param callback: function to be called - :param func: - :param pass_bot: Pass TeleBot to handler. - :return: decorated function - """ - handler_dict = self._build_handler_dict(callback, func=func, pass_bot=pass_bot, **kwargs) - self.add_chosen_inline_handler(handler_dict) - - def callback_query_handler(self, func, **kwargs): - """ - Callback request handler decorator - - :param func: - :param kwargs: - :return: - """ - def decorator(handler): - handler_dict = self._build_handler_dict(handler, func=func, **kwargs) - self.add_callback_query_handler(handler_dict) - return handler - - return decorator - - def add_callback_query_handler(self, handler_dict): - """ - Adds a callback request handler - Note that you should use register_callback_query_handler to add callback_query_handler to the bot. - - :param handler_dict: - :return: - """ - self.callback_query_handlers.append(handler_dict) - - def register_callback_query_handler(self, callback, func, pass_bot=False, **kwargs): - """ - Registers callback query handler. - - :param callback: function to be called - :param func: - :param pass_bot: Pass TeleBot to handler. - :return: decorated function - """ - handler_dict = self._build_handler_dict(callback, func=func, pass_bot=pass_bot, **kwargs) - self.add_callback_query_handler(handler_dict) - - def shipping_query_handler(self, func, **kwargs): - """ - Shipping request handler - - :param func: - :param kwargs: - :return: - """ - def decorator(handler): - handler_dict = self._build_handler_dict(handler, func=func, **kwargs) - self.add_shipping_query_handler(handler_dict) - return handler - - return decorator - - def add_shipping_query_handler(self, handler_dict): - """ - Adds a shipping request handler. - Note that you should use register_shipping_query_handler to add shipping_query_handler to the bot. - - :param handler_dict: - :return: - """ - self.shipping_query_handlers.append(handler_dict) - - def register_shipping_query_handler(self, callback, func, pass_bot=False, **kwargs): - """ - Registers shipping query handler. - - :param callback: function to be called - :param func: - :param pass_bot: Pass TeleBot to handler. - :return: decorated function - """ - handler_dict = self._build_handler_dict(callback, func=func, pass_bot=pass_bot, **kwargs) - self.add_shipping_query_handler(handler_dict) - - def pre_checkout_query_handler(self, func, **kwargs): - """ - Pre-checkout request handler - - :param func: - :param kwargs: - :return: - """ - def decorator(handler): - handler_dict = self._build_handler_dict(handler, func=func, **kwargs) - self.add_pre_checkout_query_handler(handler_dict) - return handler - - return decorator - - def add_pre_checkout_query_handler(self, handler_dict): - """ - Adds a pre-checkout request handler - Note that you should use register_pre_checkout_query_handler to add pre_checkout_query_handler to the bot. - - :param handler_dict: - :return: - """ - self.pre_checkout_query_handlers.append(handler_dict) - - def register_pre_checkout_query_handler(self, callback, func, pass_bot=False, **kwargs): - """ - Registers pre-checkout request handler. - - :param callback: function to be called - :param func: - :param pass_bot: Pass TeleBot to handler. - :return: decorated function - """ - handler_dict = self._build_handler_dict(callback, func=func, pass_bot=pass_bot, **kwargs) - self.add_pre_checkout_query_handler(handler_dict) - - def poll_handler(self, func, **kwargs): - """ - Poll request handler - - :param func: - :param kwargs: - :return: - """ - def decorator(handler): - handler_dict = self._build_handler_dict(handler, func=func, **kwargs) - self.add_poll_handler(handler_dict) - return handler - - return decorator - - def add_poll_handler(self, handler_dict): - """ - Adds a poll request handler - Note that you should use register_poll_handler to add poll_handler to the bot. - - :param handler_dict: - :return: - """ - self.poll_handlers.append(handler_dict) - - def register_poll_handler(self, callback, func, pass_bot=False, **kwargs): - """ - Registers poll handler. - - :param callback: function to be called - :param func: - :param pass_bot: Pass TeleBot to handler. - :return: decorated function - """ - handler_dict = self._build_handler_dict(callback, func=func, pass_bot=pass_bot, **kwargs) - self.add_poll_handler(handler_dict) - - def poll_answer_handler(self, func=None, **kwargs): - """ - Poll_answer request handler - - :param func: - :param kwargs: - :return: - """ - def decorator(handler): - handler_dict = self._build_handler_dict(handler, func=func, **kwargs) - self.add_poll_answer_handler(handler_dict) - return handler - - return decorator - - def add_poll_answer_handler(self, handler_dict): - """ - Adds a poll_answer request handler. - Note that you should use register_poll_answer_handler to add poll_answer_handler to the bot. - - :param handler_dict: - :return: - """ - self.poll_answer_handlers.append(handler_dict) - - def register_poll_answer_handler(self, callback, func, pass_bot=False, **kwargs): - """ - Registers poll answer handler. - - :param callback: function to be called - :param func: - :param pass_bot: Pass TeleBot to handler. - :return: decorated function - """ - handler_dict = self._build_handler_dict(callback, func=func, pass_bot=pass_bot, **kwargs) - self.add_poll_answer_handler(handler_dict) - - def my_chat_member_handler(self, func=None, **kwargs): - """ - my_chat_member handler. - - :param func: - :param kwargs: - :return: - """ - def decorator(handler): - handler_dict = self._build_handler_dict(handler, func=func, **kwargs) - self.add_my_chat_member_handler(handler_dict) - return handler - - return decorator - - def add_my_chat_member_handler(self, handler_dict): - """ - Adds a my_chat_member handler. - Note that you should use register_my_chat_member_handler to add my_chat_member_handler to the bot. - - :param handler_dict: - :return: - """ - self.my_chat_member_handlers.append(handler_dict) - - def register_my_chat_member_handler(self, callback, func=None, pass_bot=False, **kwargs): - """ - Registers my chat member handler. - - :param callback: function to be called - :param func: - :param pass_bot: Pass TeleBot to handler. - :return: decorated function - """ - handler_dict = self._build_handler_dict(callback, func=func, pass_bot=pass_bot, **kwargs) - self.add_my_chat_member_handler(handler_dict) - - def chat_member_handler(self, func=None, **kwargs): - """ - chat_member handler. - - :param func: - :param kwargs: - :return: - """ - def decorator(handler): - handler_dict = self._build_handler_dict(handler, func=func, **kwargs) - self.add_chat_member_handler(handler_dict) - return handler - - return decorator - - def add_chat_member_handler(self, handler_dict): - """ - Adds a chat_member handler. - Note that you should use register_chat_member_handler to add chat_member_handler to the bot. - - :param handler_dict: - :return: - """ - self.chat_member_handlers.append(handler_dict) - - def register_chat_member_handler(self, callback, func=None, pass_bot=False, **kwargs): - """ - Registers chat member handler. - - :param callback: function to be called - :param func: - :param pass_bot: Pass TeleBot to handler. - :return: decorated function - """ - handler_dict = self._build_handler_dict(callback, func=func, pass_bot=pass_bot, **kwargs) - self.add_chat_member_handler(handler_dict) - - def chat_join_request_handler(self, func=None, **kwargs): - """ - chat_join_request handler - - :param func: - :param kwargs: - :return: - """ - def decorator(handler): - handler_dict = self._build_handler_dict(handler, func=func, **kwargs) - self.add_chat_join_request_handler(handler_dict) - return handler - - return decorator - - def add_chat_join_request_handler(self, handler_dict): - """ - Adds a chat_join_request handler. - Note that you should use register_chat_join_request_handler to add chat_join_request_handler to the bot. - - :param handler_dict: - :return: - """ - self.chat_join_request_handlers.append(handler_dict) - - def register_chat_join_request_handler(self, callback, func=None, pass_bot=False, **kwargs): - """ - Registers chat join request handler. - :param callback: function to be called - :param func: - :param pass_bot: Pass TeleBot to handler. - :return: decorated function - """ - handler_dict = self._build_handler_dict(callback, func=func, pass_bot=pass_bot, **kwargs) - self.add_chat_join_request_handler(handler_dict) - - def _test_message_handler(self, message_handler, message): - """ - Test message handler - - :param message_handler: - :param message: - :return: - """ - for message_filter, filter_value in message_handler['filters'].items(): - if filter_value is None: - continue - - if not self._test_filter(message_filter, filter_value, message): - return False - - return True - - def add_custom_filter(self, custom_filter: Union[SimpleCustomFilter, AdvancedCustomFilter]): - """ - Create custom filter. - - custom_filter: Class with check(message) method. - :param custom_filter: Custom filter class with key. - """ - self.custom_filters[custom_filter.key] = custom_filter - - def _test_filter(self, message_filter, filter_value, message): - """ - Test filters - - :param message_filter: Filter type passed in handler - :param filter_value: Filter value passed in handler - :param message: Message to test - :return: True if filter conforms - """ - if message_filter == 'content_types': - return message.content_type in filter_value - elif message_filter == 'regexp': - return message.content_type == 'text' and re.search(filter_value, message.text, re.IGNORECASE) - elif message_filter == 'commands': - return message.content_type == 'text' and util.extract_command(message.text) in filter_value - elif message_filter == 'chat_types': - return message.chat.type in filter_value - elif message_filter == 'func': - return filter_value(message) - elif self.custom_filters and message_filter in self.custom_filters: - return self._check_filter(message_filter,filter_value,message) - else: - return False - - def _check_filter(self, message_filter, filter_value, message): - filter_check = self.custom_filters.get(message_filter) - if not filter_check: - return False - elif isinstance(filter_check, SimpleCustomFilter): - return filter_value == filter_check.check(message) - elif isinstance(filter_check, AdvancedCustomFilter): - return filter_check.check(message, filter_value) - else: - logger.error("Custom filter: wrong type. Should be SimpleCustomFilter or AdvancedCustomFilter.") - return False - - # middleware check-up method - def _check_middleware(self, update_type): - """ - Check middleware - - :param message: - :return: - """ - middlewares = None - if self.middlewares: - middlewares = [i for i in self.middlewares if update_type in i.update_types] - return middlewares - - def _run_middlewares_and_handler(self, message, handlers, middlewares, *args, **kwargs): - """ - This class is made to run handler and middleware in queue. - - :param handler: handler that should be executed. - :param middleware: middleware that should be executed. - :return: - """ - data = {} - params =[] - handler_error = None - skip_handler = False - if middlewares: - for middleware in middlewares: - result = middleware.pre_process(message, data) - # We will break this loop if CancelUpdate is returned - # Also, we will not run other middlewares - if isinstance(result, CancelUpdate): - return - elif isinstance(result, SkipHandler) and skip_handler is False: - skip_handler = True - - try: - if handlers and not skip_handler: - for handler in handlers: - process_handler = self._test_message_handler(handler, message) - if not process_handler: continue - else: - for i in inspect.signature(handler['function']).parameters: - params.append(i) - if len(params) == 1: - handler['function'](message) - - elif len(params) == 2: - if handler.get('pass_bot') is True: - handler['function'](message, self) - - elif handler.get('pass_bot') is False: - handler['function'](message, data) - - elif len(params) == 3: - if params[2] == 'bot' and handler.get('pass_bot') is True: - handler['function'](message, data, self) - - else: - handler['function'](message, self, data) - - except Exception as e: - handler_error = e - - if not middlewares: - if self.exception_handler: - return self.exception_handler.handle(e) - logging.error(str(e)) - return - if middlewares: - for middleware in middlewares: - middleware.post_process(message, data, handler_error) - - - - - def _notify_command_handlers(self, handlers, new_messages, update_type): - """ - Notifies command handlers. - - :param handlers: - :param new_messages: - :return: - """ - if len(handlers) == 0 and not self.use_class_middlewares: - return - - for message in new_messages: - if self.use_class_middlewares: - middleware = self._check_middleware(update_type) - self._exec_task(self._run_middlewares_and_handler, message, handlers=handlers, middlewares=middleware) - return - else: - for message_handler in handlers: - if self._test_message_handler(message_handler, message): - self._exec_task(message_handler['function'], message, pass_bot=message_handler['pass_bot'], task_type='handler') - break diff --git a/telebot/apihelper.py b/telebot/apihelper.py deleted file mode 100644 index 67a8e7c..0000000 --- a/telebot/apihelper.py +++ /dev/null @@ -1,1776 +0,0 @@ -# -*- coding: utf-8 -*- -import time -from datetime import datetime - -try: - import ujson as json -except ImportError: - import json - -import requests -from requests.exceptions import HTTPError, ConnectionError, Timeout -from requests.adapters import HTTPAdapter - -try: - # noinspection PyUnresolvedReferences - from requests.packages.urllib3 import fields - format_header_param = fields.format_header_param -except ImportError: - format_header_param = None -import telebot -from telebot import types -from telebot import util - -logger = telebot.logger - -proxy = None -session = None - -API_URL = None -FILE_URL = None - -CONNECT_TIMEOUT = 15 -READ_TIMEOUT = 30 - -LONG_POLLING_TIMEOUT = 10 # Should be positive, short polling should be used for testing purposes only (https://core.telegram.org/bots/api#getupdates) - -SESSION_TIME_TO_LIVE = 600 # In seconds. None - live forever, 0 - one-time - -RETRY_ON_ERROR = False -RETRY_TIMEOUT = 2 -MAX_RETRIES = 15 -RETRY_ENGINE = 1 - -CUSTOM_SERIALIZER = None -CUSTOM_REQUEST_SENDER = None - -ENABLE_MIDDLEWARE = False - - - -def _get_req_session(reset=False): - if SESSION_TIME_TO_LIVE: - # If session TTL is set - check time passed - creation_date = util.per_thread('req_session_time', lambda: datetime.now(), reset) - # noinspection PyTypeChecker - if (datetime.now() - creation_date).total_seconds() > SESSION_TIME_TO_LIVE: - # Force session reset - reset = True - # Save reset time - util.per_thread('req_session_time', lambda: datetime.now(), True) - - if SESSION_TIME_TO_LIVE == 0: - # Session is one-time use - return requests.sessions.Session() - else: - # Session lives some time or forever once created. Default - return util.per_thread('req_session', lambda: session if session else requests.sessions.Session(), reset) - - -def _make_request(token, method_name, method='get', params=None, files=None): - """ - Makes a request to the Telegram API. - :param token: The bot's API token. (Created with @BotFather) - :param method_name: Name of the API method to be called. (E.g. 'getUpdates') - :param method: HTTP method to be used. Defaults to 'get'. - :param params: Optional parameters. Should be a dictionary with key-value pairs. - :param files: Optional files. - :return: The result parsed to a JSON dictionary. - """ - if not token: - raise Exception('Bot token is not defined') - if API_URL: - # noinspection PyUnresolvedReferences - request_url = API_URL.format(token, method_name) - else: - request_url = "https://api.telegram.org/bot{0}/{1}".format(token, method_name) - - logger.debug("Request: method={0} url={1} params={2} files={3}".format(method, request_url, params, files).replace(token, token.split(':')[0] + ":{TOKEN}")) - read_timeout = READ_TIMEOUT - connect_timeout = CONNECT_TIMEOUT - if files and format_header_param: - fields.format_header_param = _no_encode(format_header_param) - if params: - if 'timeout' in params: - read_timeout = params.pop('timeout') - connect_timeout = read_timeout -# if 'connect-timeout' in params: -# connect_timeout = params.pop('connect-timeout') + 10 - if 'long_polling_timeout' in params: - # For getUpdates: it's the only function with timeout parameter on the BOT API side - long_polling_timeout = params.pop('long_polling_timeout') - params['timeout'] = long_polling_timeout - # Long polling hangs for a given time. Read timeout should be greater that long_polling_timeout - read_timeout = max(long_polling_timeout + 5, read_timeout) - # Lets stop suppose that user is stupid and assume that he knows what he do... - # read_timeout = read_timeout + 10 - # connect_timeout = connect_timeout + 10 - - params = params or None # Set params to None if empty - - result = None - if RETRY_ON_ERROR and RETRY_ENGINE == 1: - got_result = False - current_try = 0 - while not got_result and current_try 0: - ret = ret[:-1] - return '[' + ret + ']' - - -def _convert_markup(markup): - if isinstance(markup, types.JsonSerializable): - return markup.to_json() - return markup - - -def _convert_entites(entites): - if entites is None: - return None - elif len(entites) == 0: - return [] - elif isinstance(entites[0], types.JsonSerializable): - return [entity.to_json() for entity in entites] - else: - return entites - - -def _convert_poll_options(poll_options): - if poll_options is None: - return None - elif len(poll_options) == 0: - return [] - elif isinstance(poll_options[0], str): - # Compatibility mode with previous bug when only list of string was accepted as poll_options - return poll_options - elif isinstance(poll_options[0], types.PollOption): - return [option.text for option in poll_options] - else: - return poll_options - - -def convert_input_media(media): - if isinstance(media, types.InputMedia): - return media.convert_input_media() - return None, None - - -def convert_input_media_array(array): - media = [] - files = {} - for input_media in array: - if isinstance(input_media, types.InputMedia): - media_dict = input_media.to_dict() - if media_dict['media'].startswith('attach://'): - key = media_dict['media'].replace('attach://', '') - files[key] = input_media.media - media.append(media_dict) - return json.dumps(media), files - - -def _no_encode(func): - def wrapper(key, val): - if key == 'filename': - return u'{0}={1}'.format(key, val) - else: - return func(key, val) - - return wrapper - - -class ApiException(Exception): - """ - This class represents a base Exception thrown when a call to the Telegram API fails. - In addition to an informative message, it has a `function_name` and a `result` attribute, which respectively - contain the name of the failed function and the returned result that made the function to be considered as - failed. - """ - - def __init__(self, msg, function_name, result): - super(ApiException, self).__init__("A request to the Telegram API was unsuccessful. {0}".format(msg)) - self.function_name = function_name - self.result = result - -class ApiHTTPException(ApiException): - """ - This class represents an Exception thrown when a call to the - Telegram API server returns HTTP code that is not 200. - """ - def __init__(self, function_name, result): - super(ApiHTTPException, self).__init__( - "The server returned HTTP {0} {1}. Response body:\n[{2}]" \ - .format(result.status_code, result.reason, result.text.encode('utf8')), - function_name, - result) - -class ApiInvalidJSONException(ApiException): - """ - This class represents an Exception thrown when a call to the - Telegram API server returns invalid json. - """ - def __init__(self, function_name, result): - super(ApiInvalidJSONException, self).__init__( - "The server returned an invalid JSON response. Response body:\n[{0}]" \ - .format(result.text.encode('utf8')), - function_name, - result) - -class ApiTelegramException(ApiException): - """ - This class represents an Exception thrown when a Telegram API returns error code. - """ - def __init__(self, function_name, result, result_json): - super(ApiTelegramException, self).__init__( - "Error code: {0}. Description: {1}" \ - .format(result_json['error_code'], result_json['description']), - function_name, - result) - self.result_json = result_json - self.error_code = result_json['error_code'] - self.description = result_json['description'] - diff --git a/telebot/async_telebot.py b/telebot/async_telebot.py deleted file mode 100644 index 7b45ed9..0000000 --- a/telebot/async_telebot.py +++ /dev/null @@ -1,3357 +0,0 @@ -# -*- coding: utf-8 -*- -from datetime import datetime - -import logging -import re -import time -import traceback -from typing import Any, List, Optional, Union - -# this imports are used to avoid circular import error -import telebot.util -import telebot.types - - -# storages -from telebot.asyncio_storage import StateMemoryStorage, StatePickleStorage -from telebot.asyncio_handler_backends import CancelUpdate, SkipHandler - -from inspect import signature - -from telebot import logger - -from telebot import util, types, asyncio_helper -import asyncio -from telebot import asyncio_filters - - -REPLY_MARKUP_TYPES = Union[ - types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, - types.ReplyKeyboardRemove, types.ForceReply] - - -""" -Module : telebot -""" - - -class Handler: - """ - Class for (next step|reply) handlers - """ - def __init__(self, callback, *args, **kwargs): - self.callback = callback - self.args = args - self.kwargs = kwargs - - def __getitem__(self, item): - return getattr(self, item) - - -class ExceptionHandler: - """ - Class for handling exceptions while Polling - """ - - # noinspection PyMethodMayBeStatic,PyUnusedLocal - def handle(self, exception): - return False - - -class AsyncTeleBot: - """ - This is the main asynchronous class for Bot. - - It allows you to add handlers for different kind of updates. - - Usage: - - .. code-block:: python - - from telebot.async_telebot import AsyncTeleBot - bot = AsyncTeleBot('token') # get token from @BotFather - - See more examples in examples/ directory: - https://github.com/eternnoir/pyTelegramBotAPI/tree/master/examples - - """ - - def __init__(self, token: str, parse_mode: Optional[str]=None, offset=None, - exception_handler=None, state_storage=StateMemoryStorage()) -> None: # TODO: ADD TYPEHINTS - self.token = token - - self.offset = offset - self.token = token - self.parse_mode = parse_mode - self.update_listener = [] - - - - self.exception_handler = exception_handler - - self.message_handlers = [] - self.edited_message_handlers = [] - self.channel_post_handlers = [] - self.edited_channel_post_handlers = [] - self.inline_handlers = [] - self.chosen_inline_handlers = [] - self.callback_query_handlers = [] - self.shipping_query_handlers = [] - self.pre_checkout_query_handlers = [] - self.poll_handlers = [] - self.poll_answer_handlers = [] - self.my_chat_member_handlers = [] - self.chat_member_handlers = [] - self.chat_join_request_handlers = [] - self.custom_filters = {} - self.state_handlers = [] - - self.current_states = state_storage - - - self.middlewares = [] - - async def close_session(self): - """ - Closes existing session of aiohttp. - Use this function if you stop polling. - """ - await asyncio_helper.session_manager.session.close() - async def get_updates(self, offset: Optional[int]=None, limit: Optional[int]=None, - timeout: Optional[int]=None, allowed_updates: Optional[List]=None, request_timeout: Optional[int]=None) -> List[types.Update]: - json_updates = await asyncio_helper.get_updates(self.token, offset, limit, timeout, allowed_updates, request_timeout) - return [types.Update.de_json(ju) for ju in json_updates] - - async def polling(self, non_stop: bool=False, skip_pending=False, interval: int=0, timeout: int=20, - request_timeout: int=20, allowed_updates: Optional[List[str]]=None, - none_stop: Optional[bool]=None): - """ - This allows the bot to retrieve Updates automatically and notify listeners and message handlers accordingly. - - Warning: Do not call this function more than once! - - Always get updates. - - :param interval: Delay between two update retrivals - :param non_stop: Do not stop polling when an ApiException occurs. - :param timeout: Request connection timeout - :param skip_pending: skip old updates - :param request_timeout: Timeout in seconds for a request. - :param allowed_updates: A list of the update types you want your bot to receive. - For example, specify [“message”, “edited_channel_post”, “callback_query”] to only receive updates of these types. - See util.update_types for a complete list of available update types. - Specify an empty list to receive all update types except chat_member (default). - If not specified, the previous setting will be used. - - Please note that this parameter doesn't affect updates created before the call to the get_updates, - so unwanted updates may be received for a short period of time. - :param none_stop: Deprecated, use non_stop. Old typo f***up compatibility - :return: - """ - if none_stop is not None: - non_stop = none_stop - - if skip_pending: - await self.skip_updates() - await self._process_polling(non_stop, interval, timeout, request_timeout, allowed_updates) - - async def infinity_polling(self, timeout: int=20, skip_pending: bool=False, request_timeout: int=20, logger_level=logging.ERROR, - allowed_updates: Optional[List[str]]=None, *args, **kwargs): - """ - Wrap polling with infinite loop and exception handling to avoid bot stops polling. - - :param timeout: Request connection timeout - :param request_timeout: Timeout in seconds for long polling (see API docs) - :param skip_pending: skip old updates - :param logger_level: Custom logging level for infinity_polling logging. - Use logger levels from logging as a value. None/NOTSET = no error logging - :param allowed_updates: A list of the update types you want your bot to receive. - For example, specify [“message”, “edited_channel_post”, “callback_query”] to only receive updates of these types. - See util.update_types for a complete list of available update types. - Specify an empty list to receive all update types except chat_member (default). - If not specified, the previous setting will be used. - - Please note that this parameter doesn't affect updates created before the call to the get_updates, - so unwanted updates may be received for a short period of time. - """ - if skip_pending: - await self.skip_updates() - self._polling = True - while self._polling: - try: - await self._process_polling(non_stop=True, timeout=timeout, request_timeout=request_timeout, - allowed_updates=allowed_updates, *args, **kwargs) - except Exception as e: - if logger_level and logger_level >= logging.ERROR: - logger.error("Infinity polling exception: %s", str(e)) - if logger_level and logger_level >= logging.DEBUG: - logger.error("Exception traceback:\n%s", traceback.format_exc()) - time.sleep(3) - continue - if logger_level and logger_level >= logging.INFO: - logger.error("Infinity polling: polling exited") - if logger_level and logger_level >= logging.INFO: - logger.error("Break infinity polling") - - async def _process_polling(self, non_stop: bool=False, interval: int=0, timeout: int=20, - request_timeout: int=20, allowed_updates: Optional[List[str]]=None): - """ - Function to process polling. - - :param non_stop: Do not stop polling when an ApiException occurs. - :param interval: Delay between two update retrivals - :param timeout: Request connection timeout - :param request_timeout: Timeout in seconds for long polling (see API docs) - :param allowed_updates: A list of the update types you want your bot to receive. - For example, specify [“message”, “edited_channel_post”, “callback_query”] to only receive updates of these types. - See util.update_types for a complete list of available update types. - Specify an empty list to receive all update types except chat_member (default). - If not specified, the previous setting will be used. - - Please note that this parameter doesn't affect updates created before the call to the get_updates, - so unwanted updates may be received for a short period of time. - :return: - - """ - self._polling = True - - try: - while self._polling: - try: - - updates = await self.get_updates(offset=self.offset, allowed_updates=allowed_updates, timeout=timeout, request_timeout=request_timeout) - if updates: - self.offset = updates[-1].update_id + 1 - asyncio.create_task(self.process_new_updates(updates)) # Seperate task for processing updates - if interval: await asyncio.sleep(interval) - - except KeyboardInterrupt: - return - except asyncio.CancelledError: - return - except asyncio_helper.RequestTimeout as e: - logger.error(str(e)) - if non_stop: - await asyncio.sleep(2) - continue - else: - return - except asyncio_helper.ApiTelegramException as e: - logger.error(str(e)) - if non_stop: - continue - else: - break - except Exception as e: - logger.error('Cause exception while getting updates.') - if non_stop: - logger.error(str(e)) - await asyncio.sleep(3) - continue - else: - raise e - finally: - self._polling = False - await self.close_session() - logger.warning('Polling is stopped.') - - def _loop_create_task(self, coro): - return asyncio.create_task(coro) - - async def _process_updates(self, handlers, messages, update_type): - """ - Process updates. - - :param handlers: - :param messages: - :return: - """ - tasks = [] - for message in messages: - middleware = await self.process_middlewares(update_type) - tasks.append(self._run_middlewares_and_handlers(handlers, message, middleware)) - await asyncio.gather(*tasks) - - async def _run_middlewares_and_handlers(self, handlers, message, middlewares): - handler_error = None - data = {} - process_handler = True - - if middlewares: - for middleware in middlewares: - middleware_result = await middleware.pre_process(message, data) - if isinstance(middleware_result, SkipHandler): - await middleware.post_process(message, data, handler_error) - process_handler = False - if isinstance(middleware_result, CancelUpdate): - return - for handler in handlers: - if not process_handler: - break - - process_update = await self._test_message_handler(handler, message) - if not process_update: - continue - elif process_update: - try: - params = [] - - for i in signature(handler['function']).parameters: - params.append(i) - if len(params) == 1: - await handler['function'](message) - break - elif len(params) == 2: - if handler['pass_bot']: - await handler['function'](message, self) - break - else: - await handler['function'](message, data) - break - elif len(params) == 3: - if handler['pass_bot'] and params[1] == 'bot': - await handler['function'](message, self, data) - break - else: - await handler['function'](message, data) - break - except Exception as e: - handler_error = e - - if not middlewares: - if self.exception_handler: - return self.exception_handler.handle(e) - logging.error(str(e)) - return - - if middlewares: - for middleware in middlewares: - await middleware.post_process(message, data, handler_error) - # update handling - async def process_new_updates(self, updates): - """ - Process new updates. - Just pass list of updates - each update should be - instance of Update object. - - :param updates: list of updates - """ - upd_count = len(updates) - logger.info('Received {0} new updates'.format(upd_count)) - if upd_count == 0: return - - new_messages = None - new_edited_messages = None - new_channel_posts = None - new_edited_channel_posts = None - new_inline_queries = None - new_chosen_inline_results = None - new_callback_queries = None - new_shipping_queries = None - new_pre_checkout_queries = None - new_polls = None - new_poll_answers = None - new_my_chat_members = None - new_chat_members = None - chat_join_request = None - for update in updates: - logger.debug('Processing updates: {0}'.format(update)) - if update.message: - if new_messages is None: new_messages = [] - new_messages.append(update.message) - if update.edited_message: - if new_edited_messages is None: new_edited_messages = [] - new_edited_messages.append(update.edited_message) - if update.channel_post: - if new_channel_posts is None: new_channel_posts = [] - new_channel_posts.append(update.channel_post) - if update.edited_channel_post: - if new_edited_channel_posts is None: new_edited_channel_posts = [] - new_edited_channel_posts.append(update.edited_channel_post) - if update.inline_query: - if new_inline_queries is None: new_inline_queries = [] - new_inline_queries.append(update.inline_query) - if update.chosen_inline_result: - if new_chosen_inline_results is None: new_chosen_inline_results = [] - new_chosen_inline_results.append(update.chosen_inline_result) - if update.callback_query: - if new_callback_queries is None: new_callback_queries = [] - new_callback_queries.append(update.callback_query) - if update.shipping_query: - if new_shipping_queries is None: new_shipping_queries = [] - new_shipping_queries.append(update.shipping_query) - if update.pre_checkout_query: - if new_pre_checkout_queries is None: new_pre_checkout_queries = [] - new_pre_checkout_queries.append(update.pre_checkout_query) - if update.poll: - if new_polls is None: new_polls = [] - new_polls.append(update.poll) - if update.poll_answer: - if new_poll_answers is None: new_poll_answers = [] - new_poll_answers.append(update.poll_answer) - if update.my_chat_member: - if new_my_chat_members is None: new_my_chat_members = [] - new_my_chat_members.append(update.my_chat_member) - if update.chat_member: - if new_chat_members is None: new_chat_members = [] - new_chat_members.append(update.chat_member) - if update.chat_join_request: - if chat_join_request is None: chat_join_request = [] - chat_join_request.append(update.chat_join_request) - - if new_messages: - await self.process_new_messages(new_messages) - if new_edited_messages: - await self.process_new_edited_messages(new_edited_messages) - if new_channel_posts: - await self.process_new_channel_posts(new_channel_posts) - if new_edited_channel_posts: - await self.process_new_edited_channel_posts(new_edited_channel_posts) - if new_inline_queries: - await self.process_new_inline_query(new_inline_queries) - if new_chosen_inline_results: - await self.process_new_chosen_inline_query(new_chosen_inline_results) - if new_callback_queries: - await self.process_new_callback_query(new_callback_queries) - if new_shipping_queries: - await self.process_new_shipping_query(new_shipping_queries) - if new_pre_checkout_queries: - await self.process_new_pre_checkout_query(new_pre_checkout_queries) - if new_polls: - await self.process_new_poll(new_polls) - if new_poll_answers: - await self.process_new_poll_answer(new_poll_answers) - if new_my_chat_members: - await self.process_new_my_chat_member(new_my_chat_members) - if new_chat_members: - await self.process_new_chat_member(new_chat_members) - if chat_join_request: - await self.process_chat_join_request(chat_join_request) - - async def process_new_messages(self, new_messages): - await self.__notify_update(new_messages) - await self._process_updates(self.message_handlers, new_messages, 'message') - - async def process_new_edited_messages(self, edited_message): - await self._process_updates(self.edited_message_handlers, edited_message, 'edited_message') - - async def process_new_channel_posts(self, channel_post): - await self._process_updates(self.channel_post_handlers, channel_post , 'channel_post') - - async def process_new_edited_channel_posts(self, edited_channel_post): - await self._process_updates(self.edited_channel_post_handlers, edited_channel_post, 'edited_channel_post') - - async def process_new_inline_query(self, new_inline_querys): - await self._process_updates(self.inline_handlers, new_inline_querys, 'inline_query') - - async def process_new_chosen_inline_query(self, new_chosen_inline_querys): - await self._process_updates(self.chosen_inline_handlers, new_chosen_inline_querys, 'chosen_inline_query') - - async def process_new_callback_query(self, new_callback_querys): - await self._process_updates(self.callback_query_handlers, new_callback_querys, 'callback_query') - - async def process_new_shipping_query(self, new_shipping_querys): - await self._process_updates(self.shipping_query_handlers, new_shipping_querys, 'shipping_query') - - async def process_new_pre_checkout_query(self, pre_checkout_querys): - await self._process_updates(self.pre_checkout_query_handlers, pre_checkout_querys, 'pre_checkout_query') - - async def process_new_poll(self, polls): - await self._process_updates(self.poll_handlers, polls, 'poll') - - async def process_new_poll_answer(self, poll_answers): - await self._process_updates(self.poll_answer_handlers, poll_answers, 'poll_answer') - - async def process_new_my_chat_member(self, my_chat_members): - await self._process_updates(self.my_chat_member_handlers, my_chat_members, 'my_chat_member') - - async def process_new_chat_member(self, chat_members): - await self._process_updates(self.chat_member_handlers, chat_members, 'chat_member') - - async def process_chat_join_request(self, chat_join_request): - await self._process_updates(self.chat_join_request_handlers, chat_join_request, 'chat_join_request') - - async def process_middlewares(self, update_type): - if self.middlewares: - middlewares = [middleware for middleware in self.middlewares if update_type in middleware.update_types] - return middlewares - return None - - async def __notify_update(self, new_messages): - if len(self.update_listener) == 0: - return - for listener in self.update_listener: - self._loop_create_task(listener(new_messages)) - - async def _test_message_handler(self, message_handler, message): - """ - Test message handler. - - :param message_handler: - :param message: - :return: - """ - for message_filter, filter_value in message_handler['filters'].items(): - if filter_value is None: - continue - - if not await self._test_filter(message_filter, filter_value, message): - return False - - return True - - def set_update_listener(self, func): - """ - Update listener is a function that gets any update. - - :param func: function that should get update. - """ - self.update_listener.append(func) - - def add_custom_filter(self, custom_filter): - """ - Create custom filter. - - custom_filter: Class with check(message) method. - """ - self.custom_filters[custom_filter.key] = custom_filter - - async def _test_filter(self, message_filter, filter_value, message): - """ - Test filters. - - :param message_filter: Filter type passed in handler - :param filter_value: Filter value passed in handler - :param message: Message to test - :return: True if filter conforms - """ - # test_cases = { - # 'content_types': lambda msg: msg.content_type in filter_value, - # 'regexp': lambda msg: msg.content_type == 'text' and re.search(filter_value, msg.text, re.IGNORECASE), - # 'commands': lambda msg: msg.content_type == 'text' and util.extract_command(msg.text) in filter_value, - # 'func': lambda msg: filter_value(msg) - # } - # return test_cases.get(message_filter, lambda msg: False)(message) - if message_filter == 'content_types': - return message.content_type in filter_value - elif message_filter == 'regexp': - return message.content_type == 'text' and re.search(filter_value, message.text, re.IGNORECASE) - elif message_filter == 'commands': - return message.content_type == 'text' and util.extract_command(message.text) in filter_value - elif message_filter == 'chat_types': - return message.chat.type in filter_value - elif message_filter == 'func': - return filter_value(message) - elif self.custom_filters and message_filter in self.custom_filters: - return await self._check_filter(message_filter,filter_value,message) - else: - return False - - async def _check_filter(self, message_filter, filter_value, message): - """ - Check up the filter. - - :param message_filter: - :param filter_value: - :param message: - :return: - """ - filter_check = self.custom_filters.get(message_filter) - if not filter_check: - return False - elif isinstance(filter_check, asyncio_filters.SimpleCustomFilter): - return filter_value == await filter_check.check(message) - elif isinstance(filter_check, asyncio_filters.AdvancedCustomFilter): - return await filter_check.check(message, filter_value) - else: - logger.error("Custom filter: wrong type. Should be SimpleCustomFilter or AdvancedCustomFilter.") - return False - - def setup_middleware(self, middleware): - """ - Setup middleware. - - :param middleware: Middleware-class. - :return: - """ - self.middlewares.append(middleware) - - def message_handler(self, commands=None, regexp=None, func=None, content_types=None, chat_types=None, **kwargs): - """ - Message handler decorator. - This decorator can be used to decorate functions that must handle certain types of messages. - All message handlers are tested in the order they were added. - - Example: - - .. code-block:: python - - bot = TeleBot('TOKEN') - - # Handles all messages which text matches regexp. - @bot.message_handler(regexp='someregexp') - async def command_help(message): - bot.send_message(message.chat.id, 'Did someone call for help?') - - # Handles messages in private chat - @bot.message_handler(chat_types=['private']) # You can add more chat types - async def command_help(message): - bot.send_message(message.chat.id, 'Private chat detected, sir!') - - # Handle all sent documents of type 'text/plain'. - @bot.message_handler(func=lambda message: message.document.mime_type == 'text/plain', - content_types=['document']) - async def command_handle_document(message): - bot.send_message(message.chat.id, 'Document received, sir!') - - # Handle all other messages. - @bot.message_handler(func=lambda message: True, content_types=['audio', 'photo', 'voice', 'video', 'document', - 'text', 'location', 'contact', 'sticker']) - async def async default_command(message): - bot.send_message(message.chat.id, "This is the async default command handler.") - - :param commands: Optional list of strings (commands to handle). - :param regexp: Optional regular expression. - :param func: Optional lambda function. The lambda receives the message to test as the first parameter. - It must return True if the command should handle the message. - :param content_types: Supported message content types. Must be a list. async defaults to ['text']. - :param chat_types: list of chat types - """ - - if content_types is None: - content_types = ["text"] - - if isinstance(commands, str): - logger.warning("message_handler: 'commands' filter should be List of strings (commands), not string.") - commands = [commands] - - if isinstance(content_types, str): - logger.warning("message_handler: 'content_types' filter should be List of strings (content types), not string.") - content_types = [content_types] - - def decorator(handler): - handler_dict = self._build_handler_dict(handler, - chat_types=chat_types, - content_types=content_types, - commands=commands, - regexp=regexp, - func=func, - **kwargs) - self.add_message_handler(handler_dict) - return handler - - return decorator - - def add_message_handler(self, handler_dict): - """ - Adds a message handler. - Note that you should use register_message_handler to add message_handler. - - :param handler_dict: - :return: - """ - self.message_handlers.append(handler_dict) - - def register_message_handler(self, callback, content_types=None, commands=None, regexp=None, func=None, chat_types=None, pass_bot=False, **kwargs): - """ - Registers message handler. - - :param callback: function to be called - :param content_types: list of content_types - :param commands: list of commands - :param regexp: - :param func: - :param chat_types: True for private chat - :param pass_bot: True if you want to get TeleBot instance in your handler - :return: decorated function - """ - if content_types is None: - content_types = ["text"] - if isinstance(commands, str): - logger.warning("register_message_handler: 'commands' filter should be List of strings (commands), not string.") - commands = [commands] - - if isinstance(content_types, str): - logger.warning("register_message_handler: 'content_types' filter should be List of strings (content types), not string.") - content_types = [content_types] - - handler_dict = self._build_handler_dict(callback, - chat_types=chat_types, - content_types=content_types, - commands=commands, - regexp=regexp, - func=func, - pass_bot=pass_bot, - **kwargs) - self.add_message_handler(handler_dict) - - def edited_message_handler(self, commands=None, regexp=None, func=None, content_types=None, chat_types=None, **kwargs): - """ - Edit message handler decorator. - - :param commands: - :param regexp: - :param func: - :param content_types: - :param chat_types: list of chat types - :param kwargs: - :return: - """ - - if content_types is None: - content_types = ["text"] - - if isinstance(commands, str): - logger.warning("edited_message_handler: 'commands' filter should be List of strings (commands), not string.") - commands = [commands] - - if isinstance(content_types, str): - logger.warning("edited_message_handler: 'content_types' filter should be List of strings (content types), not string.") - content_types = [content_types] - - def decorator(handler): - handler_dict = self._build_handler_dict(handler, - chat_types=chat_types, - content_types=content_types, - commands=commands, - regexp=regexp, - func=func, - **kwargs) - self.add_edited_message_handler(handler_dict) - return handler - - return decorator - - def add_edited_message_handler(self, handler_dict): - """ - Adds the edit message handler. - Note that you should use register_edited_message_handler to add edited_message_handler. - - :param handler_dict: - :return: - """ - self.edited_message_handlers.append(handler_dict) - - def register_edited_message_handler(self, callback, content_types=None, commands=None, regexp=None, func=None, chat_types=None, pass_bot=False, **kwargs): - """ - Registers edited message handler. - - :param pass_bot: - :param callback: function to be called - :param content_types: list of content_types - :param commands: list of commands - :param regexp: - :param func: - :param chat_types: True for private chat - :return: decorated function - """ - if isinstance(commands, str): - logger.warning("register_edited_message_handler: 'commands' filter should be List of strings (commands), not string.") - commands = [commands] - - if isinstance(content_types, str): - logger.warning("register_edited_message_handler: 'content_types' filter should be List of strings (content types), not string.") - content_types = [content_types] - - handler_dict = self._build_handler_dict(callback, - chat_types=chat_types, - content_types=content_types, - commands=commands, - regexp=regexp, - func=func, - pass_bot=pass_bot, - **kwargs) - self.add_edited_message_handler(handler_dict) - - - def channel_post_handler(self, commands=None, regexp=None, func=None, content_types=None, **kwargs): - """ - Channel post handler decorator. - - :param commands: - :param regexp: - :param func: - :param content_types: - :param kwargs: - :return: - """ - if content_types is None: - content_types = ["text"] - - if isinstance(commands, str): - logger.warning("channel_post_handler: 'commands' filter should be List of strings (commands), not string.") - commands = [commands] - - if isinstance(content_types, str): - logger.warning("channel_post_handler: 'content_types' filter should be List of strings (content types), not string.") - content_types = [content_types] - - def decorator(handler): - handler_dict = self._build_handler_dict(handler, - content_types=content_types, - commands=commands, - regexp=regexp, - func=func, - **kwargs) - self.add_channel_post_handler(handler_dict) - return handler - - return decorator - - def add_channel_post_handler(self, handler_dict): - """ - Adds channel post handler. - Note that you should use register_channel_post_handler to add channel_post_handler. - - :param handler_dict: - :return: - """ - self.channel_post_handlers.append(handler_dict) - - def register_channel_post_handler(self, callback, content_types=None, commands=None, regexp=None, func=None, pass_bot=False, **kwargs): - """ - Registers channel post message handler. - - :param pass_bot: - :param callback: function to be called - :param content_types: list of content_types - :param commands: list of commands - :param regexp: - :param func: - :return: decorated function - """ - if isinstance(commands, str): - logger.warning("register_channel_post_handler: 'commands' filter should be List of strings (commands), not string.") - commands = [commands] - - if isinstance(content_types, str): - logger.warning("register_channel_post_handler: 'content_types' filter should be List of strings (content types), not string.") - content_types = [content_types] - - handler_dict = self._build_handler_dict(callback, - content_types=content_types, - commands=commands, - regexp=regexp, - func=func, - pass_bot=pass_bot, - **kwargs) - self.add_channel_post_handler(handler_dict) - - def edited_channel_post_handler(self, commands=None, regexp=None, func=None, content_types=None, **kwargs): - """ - Edit channel post handler decorator. - - :param commands: - :param regexp: - :param func: - :param content_types: - :param kwargs: - :return: - """ - if content_types is None: - content_types = ["text"] - - if isinstance(commands, str): - logger.warning("edited_channel_post_handler: 'commands' filter should be List of strings (commands), not string.") - commands = [commands] - - if isinstance(content_types, str): - logger.warning("edited_channel_post_handler: 'content_types' filter should be List of strings (content types), not string.") - content_types = [content_types] - - def decorator(handler): - handler_dict = self._build_handler_dict(handler, - content_types=content_types, - commands=commands, - regexp=regexp, - func=func, - **kwargs) - self.add_edited_channel_post_handler(handler_dict) - return handler - - return decorator - - def add_edited_channel_post_handler(self, handler_dict): - """ - Adds the edit channel post handler. - Note that you should use register_edited_channel_post_handler to add edited_channel_post_handler. - - :param handler_dict: - :return: - """ - self.edited_channel_post_handlers.append(handler_dict) - - def register_edited_channel_post_handler(self, callback, content_types=None, commands=None, regexp=None, func=None, pass_bot=False, **kwargs): - """ - Registers edited channel post message handler. - - :param pass_bot: - :param callback: function to be called - :param content_types: list of content_types - :param commands: list of commands - :param regexp: - :param func: - :return: decorated function - """ - if isinstance(commands, str): - logger.warning("register_edited_channel_post_handler: 'commands' filter should be List of strings (commands), not string.") - commands = [commands] - - if isinstance(content_types, str): - logger.warning("register_edited_channel_post_handler: 'content_types' filter should be List of strings (content types), not string.") - content_types = [content_types] - - handler_dict = self._build_handler_dict(callback, - content_types=content_types, - commands=commands, - regexp=regexp, - func=func, - pass_bot=pass_bot, - **kwargs) - self.add_edited_channel_post_handler(handler_dict) - - def inline_handler(self, func, **kwargs): - """ - Inline call handler decorator. - - :param func: - :param kwargs: - :return: - """ - - def decorator(handler): - handler_dict = self._build_handler_dict(handler, func=func, **kwargs) - self.add_inline_handler(handler_dict) - return handler - - return decorator - - def add_inline_handler(self, handler_dict): - """ - Adds inline call handler. - Note that you should use register_inline_handler to add inline_handler. - - :param handler_dict: - :return: - """ - self.inline_handlers.append(handler_dict) - - def register_inline_handler(self, callback, func, pass_bot=False, **kwargs): - """ - Registers inline handler. - - :param pass_bot: - :param callback: function to be called - :param func: - :return: decorated function - """ - handler_dict = self._build_handler_dict(callback, func=func, pass_bot=pass_bot, **kwargs) - self.add_inline_handler(handler_dict) - - def chosen_inline_handler(self, func, **kwargs): - """ - - Description: TBD - - :param func: - :param kwargs: - :return: - """ - - def decorator(handler): - handler_dict = self._build_handler_dict(handler, func=func, **kwargs) - self.add_chosen_inline_handler(handler_dict) - return handler - - return decorator - - def add_chosen_inline_handler(self, handler_dict): - """ - Description: TBD - Note that you should use register_chosen_inline_handler to add chosen_inline_handler. - - :param handler_dict: - :return: - """ - self.chosen_inline_handlers.append(handler_dict) - - def register_chosen_inline_handler(self, callback, func, pass_bot=False, **kwargs): - """ - Registers chosen inline handler. - - :param pass_bot: - :param callback: function to be called - :param func: - :return: decorated function - """ - handler_dict = self._build_handler_dict(callback, func=func, pass_bot=pass_bot, **kwargs) - self.add_chosen_inline_handler(handler_dict) - - def callback_query_handler(self, func, **kwargs): - """ - Callback request handler decorator. - - :param func: - :param kwargs: - :return: - """ - - def decorator(handler): - handler_dict = self._build_handler_dict(handler, func=func, **kwargs) - self.add_callback_query_handler(handler_dict) - return handler - - return decorator - - def add_callback_query_handler(self, handler_dict): - """ - Adds a callback request handler. - Note that you should use register_callback_query_handler to add callback_query_handler. - - :param handler_dict: - :return: - """ - self.callback_query_handlers.append(handler_dict) - - def register_callback_query_handler(self, callback, func, pass_bot=False, **kwargs): - """ - Registers callback query handler. - - :param pass_bot: - :param callback: function to be called - :param func: - :return: decorated function - """ - handler_dict = self._build_handler_dict(callback, func=func, pass_bot=pass_bot, **kwargs) - self.add_callback_query_handler(handler_dict) - - def shipping_query_handler(self, func, **kwargs): - """ - Shipping request handler. - - :param func: - :param kwargs: - :return: - """ - - def decorator(handler): - handler_dict = self._build_handler_dict(handler, func=func, **kwargs) - self.add_shipping_query_handler(handler_dict) - return handler - - return decorator - - def add_shipping_query_handler(self, handler_dict): - """ - Adds a shipping request handler. - Note that you should use register_shipping_query_handler to add shipping_query_handler. - - :param handler_dict: - :return: - """ - self.shipping_query_handlers.append(handler_dict) - - def register_shipping_query_handler(self, callback, func, pass_bot=False, **kwargs): - """ - Registers shipping query handler. - - :param pass_bot: - :param callback: function to be called - :param func: - :return: decorated function - """ - handler_dict = self._build_handler_dict(callback, func=func, pass_bot=pass_bot, **kwargs) - self.add_shipping_query_handler(handler_dict) - - def pre_checkout_query_handler(self, func, **kwargs): - """ - Pre-checkout request handler. - - :param func: - :param kwargs: - :return: - """ - - def decorator(handler): - handler_dict = self._build_handler_dict(handler, func=func, **kwargs) - self.add_pre_checkout_query_handler(handler_dict) - return handler - - return decorator - - def add_pre_checkout_query_handler(self, handler_dict): - """ - Adds a pre-checkout request handler. - Note that you should use register_pre_checkout_query_handler to add pre_checkout_query_handler. - - :param handler_dict: - :return: - """ - self.pre_checkout_query_handlers.append(handler_dict) - - def register_pre_checkout_query_handler(self, callback, func, pass_bot=False, **kwargs): - """ - Registers pre-checkout request handler. - - :param pass_bot: - :param callback: function to be called - :param func: - :return: decorated function - """ - handler_dict = self._build_handler_dict(callback, func=func, pass_bot=pass_bot, **kwargs) - self.add_pre_checkout_query_handler(handler_dict) - - def poll_handler(self, func, **kwargs): - """ - Poll request handler. - - :param func: - :param kwargs: - :return: - """ - - def decorator(handler): - handler_dict = self._build_handler_dict(handler, func=func, **kwargs) - self.add_poll_handler(handler_dict) - return handler - - return decorator - - def add_poll_handler(self, handler_dict): - """ - Adds a poll request handler. - Note that you should use register_poll_handler to add poll_handler. - - :param handler_dict: - :return: - """ - self.poll_handlers.append(handler_dict) - - def register_poll_handler(self, callback, func, pass_bot=False, **kwargs): - """ - Registers poll handler. - - :param pass_bot: - :param callback: function to be called - :param func: - :return: decorated function - """ - handler_dict = self._build_handler_dict(callback, func=func, pass_bot=pass_bot, **kwargs) - self.add_poll_handler(handler_dict) - - def poll_answer_handler(self, func=None, **kwargs): - """ - Poll_answer request handler. - - :param func: - :param kwargs: - :return: - """ - - def decorator(handler): - handler_dict = self._build_handler_dict(handler, func=func, **kwargs) - self.add_poll_answer_handler(handler_dict) - return handler - - return decorator - - def add_poll_answer_handler(self, handler_dict): - """ - Adds a poll_answer request handler. - Note that you should use register_poll_answer_handler to add poll_answer_handler. - - :param handler_dict: - :return: - """ - self.poll_answer_handlers.append(handler_dict) - - def register_poll_answer_handler(self, callback, func, pass_bot=False, **kwargs): - """ - Registers poll answer handler. - - :param pass_bot: - :param callback: function to be called - :param func: - :return: decorated function - """ - handler_dict = self._build_handler_dict(callback, func=func, pass_bot=pass_bot, **kwargs) - self.add_poll_answer_handler(handler_dict) - - def my_chat_member_handler(self, func=None, **kwargs): - """ - my_chat_member handler. - - :param func: - :param kwargs: - :return: - """ - - def decorator(handler): - handler_dict = self._build_handler_dict(handler, func=func, **kwargs) - self.add_my_chat_member_handler(handler_dict) - return handler - - return decorator - - def add_my_chat_member_handler(self, handler_dict): - """ - Adds a my_chat_member handler. - Note that you should use register_my_chat_member_handler to add my_chat_member_handler. - - :param handler_dict: - :return: - """ - self.my_chat_member_handlers.append(handler_dict) - - def register_my_chat_member_handler(self, callback, func=None, pass_bot=False, **kwargs): - """ - Registers my chat member handler. - - :param pass_bot: - :param callback: function to be called - :param func: - :return: decorated function - """ - handler_dict = self._build_handler_dict(callback, func=func, pass_bot=pass_bot, **kwargs) - self.add_my_chat_member_handler(handler_dict) - - def chat_member_handler(self, func=None, **kwargs): - """ - chat_member handler. - - :param func: - :param kwargs: - :return: - """ - - def decorator(handler): - handler_dict = self._build_handler_dict(handler, func=func, **kwargs) - self.add_chat_member_handler(handler_dict) - return handler - - return decorator - - def add_chat_member_handler(self, handler_dict): - """ - Adds a chat_member handler. - Note that you should use register_chat_member_handler to add chat_member_handler. - - :param handler_dict: - :return: - """ - self.chat_member_handlers.append(handler_dict) - - def register_chat_member_handler(self, callback, func=None, pass_bot=False, **kwargs): - """ - Registers chat member handler. - - :param pass_bot: - :param callback: function to be called - :param func: - :return: decorated function - """ - handler_dict = self._build_handler_dict(callback, func=func, pass_bot=pass_bot, **kwargs) - self.add_chat_member_handler(handler_dict) - - def chat_join_request_handler(self, func=None, **kwargs): - """ - chat_join_request handler. - - :param func: - :param kwargs: - :return: - """ - - def decorator(handler): - handler_dict = self._build_handler_dict(handler, func=func, **kwargs) - self.add_chat_join_request_handler(handler_dict) - return handler - - return decorator - - def add_chat_join_request_handler(self, handler_dict): - """ - Adds a chat_join_request handler. - Note that you should use register_chat_join_request_handler to add chat_join_request_handler. - - :param handler_dict: - :return: - """ - self.chat_join_request_handlers.append(handler_dict) - - def register_chat_join_request_handler(self, callback, func=None, pass_bot=False, **kwargs): - """ - Registers chat join request handler. - - :param pass_bot: - :param callback: function to be called - :param func: - :return: decorated function - """ - handler_dict = self._build_handler_dict(callback, func=func, pass_bot=pass_bot, **kwargs) - self.add_chat_join_request_handler(handler_dict) - - @staticmethod - def _build_handler_dict(handler, pass_bot=False, **filters): - """ - Builds a dictionary for a handler. - - :param handler: - :param filters: - :return: - """ - return { - 'function': handler, - 'pass_bot': pass_bot, - 'filters': {ftype: fvalue for ftype, fvalue in filters.items() if fvalue is not None} - # Remove None values, they are skipped in _test_filter anyway - #'filters': filters - } - - async def skip_updates(self): - """ - Skip existing updates. - Only last update will remain on server. - """ - await self.get_updates(-1) - return True - - # all methods begin here - - async def get_me(self) -> types.User: - """ - Returns basic information about the bot in form of a User object. - - Telegram documentation: https://core.telegram.org/bots/api#getme - """ - result = await asyncio_helper.get_me(self.token) - return types.User.de_json(result) - - async def get_file(self, file_id: str) -> types.File: - """ - Use this method to get basic info about a file and prepare it for downloading. - For the moment, bots can download files of up to 20MB in size. - On success, a File object is returned. - It is guaranteed that the link will be valid for at least 1 hour. - When the link expires, a new one can be requested by calling get_file again. - - Telegram documentation: https://core.telegram.org/bots/api#getfile - - :param file_id: - """ - return types.File.de_json(await asyncio_helper.get_file(self.token, file_id)) - - async def get_file_url(self, file_id: str) -> str: - - return await asyncio_helper.get_file_url(self.token, file_id) - - async def download_file(self, file_path: str) -> bytes: - return await asyncio_helper.download_file(self.token, file_path) - - async def log_out(self) -> bool: - """ - Use this method to log out from the cloud Bot API server before launching the bot locally. - You MUST log out the bot before running it locally, otherwise there is no guarantee - that the bot will receive updates. - After a successful call, you can immediately log in on a local server, - but will not be able to log in back to the cloud Bot API server for 10 minutes. - Returns True on success. - - Telegram documentation: https://core.telegram.org/bots/api#logout - """ - return await asyncio_helper.log_out(self.token) - - async def close(self) -> bool: - """ - Use this method to close the bot instance before moving it from one local server to another. - You need to delete the webhook before calling this method to ensure that the bot isn't launched again - after server restart. - The method will return error 429 in the first 10 minutes after the bot is launched. - Returns True on success. - - Telegram documentation: https://core.telegram.org/bots/api#close - """ - return await asyncio_helper.close(self.token) - - def enable_saving_states(self, filename="./.state-save/states.pkl"): - """ - Enable saving states (by default saving disabled) - - :param filename: Filename of saving file - """ - - self.current_states = StatePickleStorage(file_path=filename) - - async def set_webhook(self, url=None, certificate=None, max_connections=None, allowed_updates=None, ip_address=None, - drop_pending_updates = None, timeout=None): - """ - Use this method to specify a url and receive incoming updates via an outgoing webhook. Whenever there is an - update for the bot, we will send an HTTPS POST request to the specified url, - containing a JSON-serialized Update. - In case of an unsuccessful request, we will give up after a reasonable amount of attempts. - Returns True on success. - - Telegram documentation: https://core.telegram.org/bots/api#setwebhook - - :param url: HTTPS url to send updates to. Use an empty string to remove webhook integration - :param certificate: Upload your public key certificate so that the root certificate in use can be checked. - See our self-signed guide for details. - :param max_connections: Maximum allowed number of simultaneous HTTPS connections to the webhook - for update delivery, 1-100. Defaults to 40. Use lower values to limit the load on your bot's server, - and higher values to increase your bot's throughput. - :param allowed_updates: A JSON-serialized list of the update types you want your bot to receive. - For example, specify [“message”, “edited_channel_post”, “callback_query”] to only receive updates - of these types. See Update for a complete list of available update types. - Specify an empty list to receive all updates regardless of type (default). - If not specified, the previous setting will be used. - :param ip_address: The fixed IP address which will be used to send webhook requests instead of the IP address - resolved through DNS - :param drop_pending_updates: Pass True to drop all pending updates - :param timeout: Integer. Request connection timeout - :return: - """ - return await asyncio_helper.set_webhook(self.token, url, certificate, max_connections, allowed_updates, ip_address, - drop_pending_updates, timeout) - - - - async def delete_webhook(self, drop_pending_updates=None, timeout=None): - """ - Use this method to remove webhook integration if you decide to switch back to getUpdates. - - Telegram documentation: https://core.telegram.org/bots/api#deletewebhook - - :param drop_pending_updates: Pass True to drop all pending updates - :param timeout: Integer. Request connection timeout - :return: bool - """ - return await asyncio_helper.delete_webhook(self.token, drop_pending_updates, timeout) - - async def remove_webhook(self): - """ - Alternative for delete_webhook but uses set_webhook - """ - await self.set_webhook() - - async def get_webhook_info(self, timeout=None): - """ - Use this method to get current webhook status. Requires no parameters. - If the bot is using getUpdates, will return an object with the url field empty. - - Telegram documentation: https://core.telegram.org/bots/api#getwebhookinfo - - :param timeout: Integer. Request connection timeout - :return: On success, returns a WebhookInfo object. - """ - result = await asyncio_helper.get_webhook_info(self.token, timeout) - return types.WebhookInfo.de_json(result) - - async def get_user_profile_photos(self, user_id: int, offset: Optional[int]=None, - limit: Optional[int]=None) -> types.UserProfilePhotos: - """ - Retrieves the user profile photos of the person with 'user_id' - - Telegram documentation: https://core.telegram.org/bots/api#getuserprofilephotos - - :param user_id: - :param offset: - :param limit: - :return: API reply. - """ - result = await asyncio_helper.get_user_profile_photos(self.token, user_id, offset, limit) - return types.UserProfilePhotos.de_json(result) - - async def get_chat(self, chat_id: Union[int, str]) -> types.Chat: - """ - Use this method to get up to date information about the chat (current name of the user for one-on-one - conversations, current username of a user, group or channel, etc.). Returns a Chat object on success. - - Telegram documentation: https://core.telegram.org/bots/api#getchat - - :param chat_id: - :return: - """ - result = await asyncio_helper.get_chat(self.token, chat_id) - return types.Chat.de_json(result) - - async def leave_chat(self, chat_id: Union[int, str]) -> bool: - """ - Use this method for your bot to leave a group, supergroup or channel. Returns True on success. - - Telegram documentation: https://core.telegram.org/bots/api#leavechat - - :param chat_id: - :return: - """ - result = await asyncio_helper.leave_chat(self.token, chat_id) - return result - - async def get_chat_administrators(self, chat_id: Union[int, str]) -> List[types.ChatMember]: - """ - Use this method to get a list of administrators in a chat. - On success, returns an Array of ChatMember objects that contains - information about all chat administrators except other bots. - - Telegram documentation: https://core.telegram.org/bots/api#getchatadministrators - - :param chat_id: Unique identifier for the target chat or username of the target supergroup or channel (in the format @channelusername) - :return: API reply. - """ - result = await asyncio_helper.get_chat_administrators(self.token, chat_id) - return [types.ChatMember.de_json(r) for r in result] - - async def get_chat_members_count(self, chat_id: Union[int, str]) -> int: - """ - This function is deprecated. Use `get_chat_member_count` instead - """ - logger.info('get_chat_members_count is deprecated. Use get_chat_member_count instead.') - result = await asyncio_helper.get_chat_member_count(self.token, chat_id) - return result - - async def get_chat_member_count(self, chat_id: Union[int, str]) -> int: - """ - Use this method to get the number of members in a chat. Returns Int on success. - - Telegram documentation: https://core.telegram.org/bots/api#getchatmemberscount - - :param chat_id: - :return: - """ - result = await asyncio_helper.get_chat_member_count(self.token, chat_id) - return result - - async def set_chat_sticker_set(self, chat_id: Union[int, str], sticker_set_name: str) -> types.StickerSet: - """ - Use this method to set a new group sticker set for a supergroup. The bot must be an administrator - in the chat for this to work and must have the appropriate admin rights. - Use the field can_set_sticker_set optionally returned in getChat requests to check - if the bot can use this method. Returns True on success. - - Telegram documentation: https://core.telegram.org/bots/api#setchatstickerset - - :param chat_id: Unique identifier for the target chat or username of the target supergroup (in the format @supergroupusername) - :param sticker_set_name: Name of the sticker set to be set as the group sticker set - :return: API reply. - """ - result = await asyncio_helper.set_chat_sticker_set(self.token, chat_id, sticker_set_name) - return result - - async def delete_chat_sticker_set(self, chat_id: Union[int, str]) -> bool: - """ - Use this method to delete a group sticker set from a supergroup. The bot must be an administrator in the chat - for this to work and must have the appropriate admin rights. Use the field can_set_sticker_set - optionally returned in getChat requests to check if the bot can use this method. Returns True on success. - - Telegram documentation: https://core.telegram.org/bots/api#deletechatstickerset - - :param chat_id: Unique identifier for the target chat or username of the target supergroup (in the format @supergroupusername) - :return: API reply. - """ - result = await asyncio_helper.delete_chat_sticker_set(self.token, chat_id) - return result - - async def get_chat_member(self, chat_id: Union[int, str], user_id: int) -> types.ChatMember: - """ - Use this method to get information about a member of a chat. Returns a ChatMember object on success. - - Telegram documentation: https://core.telegram.org/bots/api#getchatmember - - :param chat_id: - :param user_id: - :return: API reply. - """ - result = await asyncio_helper.get_chat_member(self.token, chat_id, user_id) - return types.ChatMember.de_json(result) - - - - async def send_message( - self, chat_id: Union[int, str], text: str, - parse_mode: Optional[str]=None, - entities: Optional[List[types.MessageEntity]]=None, - disable_web_page_preview: Optional[bool]=None, - disable_notification: Optional[bool]=None, - protect_content: Optional[bool]=None, - reply_to_message_id: Optional[int]=None, - allow_sending_without_reply: Optional[bool]=None, - reply_markup: Optional[REPLY_MARKUP_TYPES]=None, - timeout: Optional[int]=None) -> types.Message: - """ - Use this method to send text messages. - - Warning: Do not send more than about 4000 characters each message, otherwise you'll risk an HTTP 414 error. - If you must send more than 4000 characters, - use the `split_string` or `smart_split` function in util.py. - - Telegram documentation: https://core.telegram.org/bots/api#sendmessage - - :param chat_id: - :param text: - :param disable_web_page_preview: - :param reply_to_message_id: - :param reply_markup: - :param parse_mode: - :param disable_notification: Boolean, Optional. Sends the message silently. - :param timeout: - :param entities: - :param allow_sending_without_reply: - :param protect_content: - :return: API reply. - """ - parse_mode = self.parse_mode if (parse_mode is None) else parse_mode - - return types.Message.de_json( - await asyncio_helper.send_message( - self.token, chat_id, text, disable_web_page_preview, reply_to_message_id, - reply_markup, parse_mode, disable_notification, timeout, - entities, allow_sending_without_reply, protect_content)) - - async def forward_message( - self, chat_id: Union[int, str], from_chat_id: Union[int, str], - message_id: int, disable_notification: Optional[bool]=None, - protect_content: Optional[bool]=None, - timeout: Optional[int]=None) -> types.Message: - """ - Use this method to forward messages of any kind. - - Telegram documentation: https://core.telegram.org/bots/api#forwardmessage - - :param disable_notification: - :param chat_id: which chat to forward - :param from_chat_id: which chat message from - :param message_id: message id - :param protect_content: - :param timeout: - :return: API reply. - """ - return types.Message.de_json( - await asyncio_helper.forward_message(self.token, chat_id, from_chat_id, message_id, disable_notification, timeout, protect_content)) - - async def copy_message( - self, chat_id: Union[int, str], - from_chat_id: Union[int, str], - message_id: int, - caption: Optional[str]=None, - parse_mode: Optional[str]=None, - caption_entities: Optional[List[types.MessageEntity]]=None, - disable_notification: Optional[bool]=None, - protect_content: Optional[bool]=None, - reply_to_message_id: Optional[int]=None, - allow_sending_without_reply: Optional[bool]=None, - reply_markup: Optional[REPLY_MARKUP_TYPES]=None, - timeout: Optional[int]=None) -> int: - """ - Use this method to copy messages of any kind. - - Telegram documentation: https://core.telegram.org/bots/api#copymessage - - :param chat_id: which chat to forward - :param from_chat_id: which chat message from - :param message_id: message id - :param caption: - :param parse_mode: - :param caption_entities: - :param disable_notification: - :param reply_to_message_id: - :param allow_sending_without_reply: - :param reply_markup: - :param timeout: - :param protect_content: - :return: API reply. - """ - return types.MessageID.de_json( - await asyncio_helper.copy_message(self.token, chat_id, from_chat_id, message_id, caption, parse_mode, caption_entities, - disable_notification, reply_to_message_id, allow_sending_without_reply, reply_markup, - timeout, protect_content)) - - async def delete_message(self, chat_id: Union[int, str], message_id: int, - timeout: Optional[int]=None) -> bool: - """ - Use this method to delete message. Returns True on success. - - Telegram documentation: https://core.telegram.org/bots/api#deletemessage - - :param chat_id: in which chat to delete - :param message_id: which message to delete - :param timeout: - :return: API reply. - """ - return await asyncio_helper.delete_message(self.token, chat_id, message_id, timeout) - - async def send_dice( - self, chat_id: Union[int, str], - emoji: Optional[str]=None, disable_notification: Optional[bool]=None, - reply_to_message_id: Optional[int]=None, - reply_markup: Optional[REPLY_MARKUP_TYPES]=None, - timeout: Optional[int]=None, - allow_sending_without_reply: Optional[bool]=None, - protect_content: Optional[bool]=None) -> types.Message: - """ - Use this method to send dices. - - Telegram documentation: https://core.telegram.org/bots/api#senddice - - :param chat_id: - :param emoji: - :param disable_notification: - :param reply_to_message_id: - :param reply_markup: - :param timeout: - :param allow_sending_without_reply: - :param protect_content: - :return: Message - """ - return types.Message.de_json( - await asyncio_helper.send_dice( - self.token, chat_id, emoji, disable_notification, reply_to_message_id, - reply_markup, timeout, allow_sending_without_reply, protect_content) - ) - - async def send_photo( - self, chat_id: Union[int, str], photo: Union[Any, str], - caption: Optional[str]=None, parse_mode: Optional[str]=None, - caption_entities: Optional[List[types.MessageEntity]]=None, - disable_notification: Optional[bool]=None, - protect_content: Optional[bool]=None, - reply_to_message_id: Optional[int]=None, - allow_sending_without_reply: Optional[bool]=None, - reply_markup: Optional[REPLY_MARKUP_TYPES]=None, - timeout: Optional[int]=None,) -> types.Message: - """ - Use this method to send photos. - - Telegram documentation: https://core.telegram.org/bots/api#sendphoto - - :param chat_id: - :param photo: - :param caption: - :param parse_mode: - :param disable_notification: - :param reply_to_message_id: - :param reply_markup: - :param timeout: - :param caption_entities: - :param allow_sending_without_reply: - :param protect_content: - :return: API reply. - """ - parse_mode = self.parse_mode if (parse_mode is None) else parse_mode - - return types.Message.de_json( - await asyncio_helper.send_photo( - self.token, chat_id, photo, caption, reply_to_message_id, reply_markup, - parse_mode, disable_notification, timeout, caption_entities, - allow_sending_without_reply, protect_content)) - - async def send_audio( - self, chat_id: Union[int, str], audio: Union[Any, str], - caption: Optional[str]=None, duration: Optional[int]=None, - performer: Optional[str]=None, title: Optional[str]=None, - reply_to_message_id: Optional[int]=None, - reply_markup: Optional[REPLY_MARKUP_TYPES]=None, - parse_mode: Optional[str]=None, - disable_notification: Optional[bool]=None, - timeout: Optional[int]=None, - thumb: Optional[Union[Any, str]]=None, - caption_entities: Optional[List[types.MessageEntity]]=None, - allow_sending_without_reply: Optional[bool]=None, - protect_content: Optional[bool]=None) -> types.Message: - """ - Use this method to send audio files, if you want Telegram clients to display them in the music player. - Your audio must be in the .mp3 format. - - Telegram documentation: https://core.telegram.org/bots/api#sendaudio - - :param chat_id: Unique identifier for the message recipient - :param audio: Audio file to send. - :param caption: - :param duration: Duration of the audio in seconds - :param performer: Performer - :param title: Track name - :param reply_to_message_id: If the message is a reply, ID of the original message - :param reply_markup: - :param parse_mode: - :param disable_notification: - :param timeout: - :param thumb: - :param caption_entities: - :param allow_sending_without_reply: - :param protect_content: - :return: Message - """ - parse_mode = self.parse_mode if (parse_mode is None) else parse_mode - - return types.Message.de_json( - await asyncio_helper.send_audio( - self.token, chat_id, audio, caption, duration, performer, title, reply_to_message_id, - reply_markup, parse_mode, disable_notification, timeout, thumb, - caption_entities, allow_sending_without_reply, protect_content)) - - async def send_voice( - self, chat_id: Union[int, str], voice: Union[Any, str], - caption: Optional[str]=None, duration: Optional[int]=None, - reply_to_message_id: Optional[int]=None, - reply_markup: Optional[REPLY_MARKUP_TYPES]=None, - parse_mode: Optional[str]=None, - disable_notification: Optional[bool]=None, - timeout: Optional[int]=None, - caption_entities: Optional[List[types.MessageEntity]]=None, - allow_sending_without_reply: Optional[bool]=None, - protect_content: Optional[bool]=None) -> types.Message: - """ - Use this method to send audio files, if you want Telegram clients to display the file - as a playable voice message. - - Telegram documentation: https://core.telegram.org/bots/api#sendvoice - - :param chat_id: Unique identifier for the message recipient. - :param voice: - :param caption: - :param duration: Duration of sent audio in seconds - :param reply_to_message_id: - :param reply_markup: - :param parse_mode: - :param disable_notification: - :param timeout: - :param caption_entities: - :param allow_sending_without_reply: - :param protect_content: - :return: Message - """ - parse_mode = self.parse_mode if (parse_mode is None) else parse_mode - - return types.Message.de_json( - await asyncio_helper.send_voice( - self.token, chat_id, voice, caption, duration, reply_to_message_id, reply_markup, - parse_mode, disable_notification, timeout, caption_entities, - allow_sending_without_reply, protect_content)) - - async def send_document( - self, chat_id: Union[int, str], document: Union[Any, str], - reply_to_message_id: Optional[int]=None, - caption: Optional[str]=None, - reply_markup: Optional[REPLY_MARKUP_TYPES]=None, - parse_mode: Optional[str]=None, - disable_notification: Optional[bool]=None, - timeout: Optional[int]=None, - thumb: Optional[Union[Any, str]]=None, - caption_entities: Optional[List[types.MessageEntity]]=None, - allow_sending_without_reply: Optional[bool]=None, - visible_file_name: Optional[str]=None, - disable_content_type_detection: Optional[bool]=None, - data: Optional[Union[Any, str]]=None, - protect_content: Optional[bool]=None) -> types.Message: - """ - Use this method to send general files. - - Telegram documentation: https://core.telegram.org/bots/api#senddocument - - :param chat_id: Unique identifier for the target chat or username of the target channel (in the format @channelusername) - :param document: (document) File to send. Pass a file_id as String to send a file that exists on the Telegram servers (recommended), pass an HTTP URL as a String for Telegram to get a file from the Internet, or upload a new one using multipart/form-data - :param reply_to_message_id: If the message is a reply, ID of the original message - :param caption: Document caption (may also be used when resending documents by file_id), 0-1024 characters after entities parsing - :param reply_markup: - :param parse_mode: Mode for parsing entities in the document caption - :param disable_notification: Sends the message silently. Users will receive a notification with no sound. - :param timeout: - :param thumb: InputFile or String : Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side. The thumbnail should be in JPEG format and less than 200 kB in size. A thumbnail's width and height should not exceed 320. Ignored if the file is not uploaded using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new file, so you can pass “attach://” if the thumbnail was uploaded using multipart/form-data under - :param caption_entities: - :param allow_sending_without_reply: - :param visible_file_name: allows to async define file name that will be visible in the Telegram instead of original file name - :param disable_content_type_detection: Disables automatic server-side content type detection for files uploaded using multipart/form-data - :param data: function typo compatibility: do not use it - :param protect_content: - :return: API reply. - """ - parse_mode = self.parse_mode if (parse_mode is None) else parse_mode - if data and not(document): - # function typo miss compatibility - document = data - - return types.Message.de_json( - await asyncio_helper.send_data( - self.token, chat_id, document, 'document', - reply_to_message_id = reply_to_message_id, reply_markup = reply_markup, parse_mode = parse_mode, - disable_notification = disable_notification, timeout = timeout, caption = caption, thumb = thumb, - caption_entities = caption_entities, allow_sending_without_reply = allow_sending_without_reply, - disable_content_type_detection = disable_content_type_detection, visible_file_name = visible_file_name, protect_content = protect_content)) - - async def send_sticker( - self, chat_id: Union[int, str], sticker: Union[Any, str], - reply_to_message_id: Optional[int]=None, - reply_markup: Optional[REPLY_MARKUP_TYPES]=None, - disable_notification: Optional[bool]=None, - timeout: Optional[int]=None, - allow_sending_without_reply: Optional[bool]=None, - protect_content: Optional[bool]=None, - data: Union[Any, str]=None) -> types.Message: - """ - Use this method to send .webp stickers. - - Telegram documentation: https://core.telegram.org/bots/api#sendsticker - - :param chat_id: - :param sticker: - :param reply_to_message_id: - :param reply_markup: - :param disable_notification: to disable the notification - :param timeout: timeout - :param allow_sending_without_reply: - :param protect_content: - :param data: deprecated, for backward compatibility - :return: API reply. - """ - if data and not(sticker): - # function typo miss compatibility - sticker = data - return types.Message.de_json( - await asyncio_helper.send_data( - self.token, chat_id, sticker, 'sticker', - reply_to_message_id=reply_to_message_id, reply_markup=reply_markup, - disable_notification=disable_notification, timeout=timeout, - allow_sending_without_reply=allow_sending_without_reply, protect_content=protect_content)) - - async def send_video( - self, chat_id: Union[int, str], video: Union[Any, str], - duration: Optional[int]=None, - width: Optional[int]=None, - height: Optional[int]=None, - thumb: Optional[Union[Any, str]]=None, - caption: Optional[str]=None, - parse_mode: Optional[str]=None, - caption_entities: Optional[List[types.MessageEntity]]=None, - supports_streaming: Optional[bool]=None, - disable_notification: Optional[bool]=None, - protect_content: Optional[bool]=None, - reply_to_message_id: Optional[int]=None, - allow_sending_without_reply: Optional[bool]=None, - reply_markup: Optional[REPLY_MARKUP_TYPES]=None, - timeout: Optional[int]=None, - data: Optional[Union[Any, str]]=None) -> types.Message: - """ - Use this method to send video files, Telegram clients support mp4 videos (other formats may be sent as Document). - - Telegram documentation: https://core.telegram.org/bots/api#sendvideo - - :param chat_id: Unique identifier for the target chat or username of the target channel (in the format @channelusername) - :param video: Video to send. You can either pass a file_id as String to resend a video that is already on the Telegram servers, or upload a new video file using multipart/form-data. - :param duration: Duration of sent video in seconds - :param width: Video width - :param height: Video height - :param thumb: Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side. The thumbnail should be in JPEG format and less than 200 kB in size. A thumbnail's width and height should not exceed 320. Ignored if the file is not uploaded using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new file, so you can pass “attach://” if the thumbnail was uploaded using multipart/form-data under . - :param caption: Video caption (may also be used when resending videos by file_id), 0-1024 characters after entities parsing - :param parse_mode: Mode for parsing entities in the video caption - :param caption_entities: - :param supports_streaming: Pass True, if the uploaded video is suitable for streaming - :param disable_notification: Sends the message silently. Users will receive a notification with no sound. - :param protect_content: - :param reply_to_message_id: If the message is a reply, ID of the original message - :param allow_sending_without_reply: - :param reply_markup: - :param timeout: - :param data: deprecated, for backward compatibility - """ - parse_mode = self.parse_mode if (parse_mode is None) else parse_mode - if data and not(video): - # function typo miss compatibility - video = data - - return types.Message.de_json( - await asyncio_helper.send_video( - self.token, chat_id, video, duration, caption, reply_to_message_id, reply_markup, - parse_mode, supports_streaming, disable_notification, timeout, thumb, width, height, - caption_entities, allow_sending_without_reply, protect_content)) - - async def send_animation( - self, chat_id: Union[int, str], animation: Union[Any, str], - duration: Optional[int]=None, - width: Optional[int]=None, - height: Optional[int]=None, - thumb: Optional[Union[Any, str]]=None, - caption: Optional[str]=None, - parse_mode: Optional[str]=None, - caption_entities: Optional[List[types.MessageEntity]]=None, - disable_notification: Optional[bool]=None, - protect_content: Optional[bool]=None, - reply_to_message_id: Optional[int]=None, - allow_sending_without_reply: Optional[bool]=None, - reply_markup: Optional[REPLY_MARKUP_TYPES]=None, - timeout: Optional[int]=None, ) -> types.Message: - """ - Use this method to send animation files (GIF or H.264/MPEG-4 AVC video without sound). - - Telegram documentation: https://core.telegram.org/bots/api#sendanimation - - :param chat_id: Integer : Unique identifier for the message recipient — User or GroupChat id - :param animation: InputFile or String : Animation to send. You can either pass a file_id as String to resend an - animation that is already on the Telegram server - :param duration: Integer : Duration of sent video in seconds - :param width: Integer : Video width - :param height: Integer : Video height - :param thumb: InputFile or String : Thumbnail of the file sent - :param caption: String : Animation caption (may also be used when resending animation by file_id). - :param parse_mode: - :param protect_content: - :param reply_to_message_id: - :param reply_markup: - :param disable_notification: - :param timeout: - :param caption_entities: - :param allow_sending_without_reply: - :return: - """ - parse_mode = self.parse_mode if (parse_mode is None) else parse_mode - - return types.Message.de_json( - await asyncio_helper.send_animation( - self.token, chat_id, animation, duration, caption, reply_to_message_id, - reply_markup, parse_mode, disable_notification, timeout, thumb, - caption_entities, allow_sending_without_reply, width, height, protect_content)) - - async def send_video_note( - self, chat_id: Union[int, str], data: Union[Any, str], - duration: Optional[int]=None, - length: Optional[int]=None, - reply_to_message_id: Optional[int]=None, - reply_markup: Optional[REPLY_MARKUP_TYPES]=None, - disable_notification: Optional[bool]=None, - timeout: Optional[int]=None, - thumb: Optional[Union[Any, str]]=None, - allow_sending_without_reply: Optional[bool]=None, - protect_content: Optional[bool]=None) -> types.Message: - """ - As of v.4.0, Telegram clients support rounded square mp4 videos of up to 1 minute long. Use this method to send - video messages. - - Telegram documentation: https://core.telegram.org/bots/api#sendvideonote - - :param chat_id: Integer : Unique identifier for the message recipient — User or GroupChat id - :param data: InputFile or String : Video note to send. You can either pass a file_id as String to resend - a video that is already on the Telegram server - :param duration: Integer : Duration of sent video in seconds - :param length: Integer : Video width and height, Can't be None and should be in range of (0, 640) - :param reply_to_message_id: - :param reply_markup: - :param disable_notification: - :param timeout: - :param thumb: InputFile or String : Thumbnail of the file sent - :param allow_sending_without_reply: - :param protect_content: - :return: - """ - return types.Message.de_json( - await asyncio_helper.send_video_note( - self.token, chat_id, data, duration, length, reply_to_message_id, reply_markup, - disable_notification, timeout, thumb, allow_sending_without_reply, protect_content)) - - async def send_media_group( - self, chat_id: Union[int, str], - media: List[Union[ - types.InputMediaAudio, types.InputMediaDocument, - types.InputMediaPhoto, types.InputMediaVideo]], - disable_notification: Optional[bool]=None, - protect_content: Optional[bool]=None, - reply_to_message_id: Optional[int]=None, - timeout: Optional[int]=None, - allow_sending_without_reply: Optional[bool]=None) -> List[types.Message]: - """ - send a group of photos or videos as an album. On success, an array of the sent Messages is returned. - - Telegram documentation: https://core.telegram.org/bots/api#sendmediagroup - - :param chat_id: - :param media: - :param disable_notification: - :param reply_to_message_id: - :param timeout: - :param allow_sending_without_reply: - :param protect_content: - :return: - """ - result = await asyncio_helper.send_media_group( - self.token, chat_id, media, disable_notification, reply_to_message_id, timeout, - allow_sending_without_reply, protect_content) - return [types.Message.de_json(msg) for msg in result] - - async def send_location( - self, chat_id: Union[int, str], - latitude: float, longitude: float, - live_period: Optional[int]=None, - reply_to_message_id: Optional[int]=None, - reply_markup: Optional[REPLY_MARKUP_TYPES]=None, - disable_notification: Optional[bool]=None, - timeout: Optional[int]=None, - horizontal_accuracy: Optional[float]=None, - heading: Optional[int]=None, - proximity_alert_radius: Optional[int]=None, - allow_sending_without_reply: Optional[bool]=None, - protect_content: Optional[bool]=None) -> types.Message: - - - """ - Use this method to send point on the map. - - Telegram documentation: https://core.telegram.org/bots/api#sendlocation - - :param chat_id: - :param latitude: - :param longitude: - :param live_period: - :param reply_to_message_id: - :param reply_markup: - :param disable_notification: - :param timeout: - :param horizontal_accuracy: - :param heading: - :param proximity_alert_radius: - :param allow_sending_without_reply: - :param protect_content: - :return: API reply. - """ - return types.Message.de_json( - await asyncio_helper.send_location( - self.token, chat_id, latitude, longitude, live_period, - reply_to_message_id, reply_markup, disable_notification, timeout, - horizontal_accuracy, heading, proximity_alert_radius, - allow_sending_without_reply, protect_content)) - - async def edit_message_live_location( - self, latitude: float, longitude: float, - chat_id: Optional[Union[int, str]]=None, - message_id: Optional[int]=None, - inline_message_id: Optional[str]=None, - reply_markup: Optional[REPLY_MARKUP_TYPES]=None, - timeout: Optional[int]=None, - horizontal_accuracy: Optional[float]=None, - heading: Optional[int]=None, - proximity_alert_radius: Optional[int]=None) -> types.Message: - """ - Use this method to edit live location. - - Telegram documentation: https://core.telegram.org/bots/api#editmessagelivelocation - - :param latitude: - :param longitude: - :param chat_id: - :param message_id: - :param reply_markup: - :param timeout: - :param inline_message_id: - :param horizontal_accuracy: - :param heading: - :param proximity_alert_radius: - :return: - """ - return types.Message.de_json( - await asyncio_helper.edit_message_live_location( - self.token, latitude, longitude, chat_id, message_id, - inline_message_id, reply_markup, timeout, - horizontal_accuracy, heading, proximity_alert_radius)) - - async def stop_message_live_location( - self, chat_id: Optional[Union[int, str]]=None, - message_id: Optional[int]=None, - inline_message_id: Optional[str]=None, - reply_markup: Optional[REPLY_MARKUP_TYPES]=None, - timeout: Optional[int]=None) -> types.Message: - """ - Use this method to stop updating a live location message sent by the bot - or via the bot (for inline bots) before live_period expires. - - Telegram documentation: https://core.telegram.org/bots/api#stopmessagelivelocation - - :param chat_id: - :param message_id: - :param inline_message_id: - :param reply_markup: - :param timeout: - :return: - """ - return types.Message.de_json( - await asyncio_helper.stop_message_live_location( - self.token, chat_id, message_id, inline_message_id, reply_markup, timeout)) - - async def send_venue( - self, chat_id: Union[int, str], - latitude: float, longitude: float, - title: str, address: str, - foursquare_id: Optional[str]=None, - foursquare_type: Optional[str]=None, - disable_notification: Optional[bool]=None, - reply_to_message_id: Optional[int]=None, - reply_markup: Optional[REPLY_MARKUP_TYPES]=None, - timeout: Optional[int]=None, - allow_sending_without_reply: Optional[bool]=None, - google_place_id: Optional[str]=None, - google_place_type: Optional[str]=None, - protect_content: Optional[bool]=None) -> types.Message: - """ - Use this method to send information about a venue. - - Telegram documentation: https://core.telegram.org/bots/api#sendvenue - - :param chat_id: Integer or String : Unique identifier for the target chat or username of the target channel - :param latitude: Float : Latitude of the venue - :param longitude: Float : Longitude of the venue - :param title: String : Name of the venue - :param address: String : Address of the venue - :param foursquare_id: String : Foursquare identifier of the venue - :param foursquare_type: Foursquare type of the venue, if known. (For example, “arts_entertainment/async default”, - “arts_entertainment/aquarium” or “food/icecream”.) - :param disable_notification: - :param reply_to_message_id: - :param reply_markup: - :param timeout: - :param allow_sending_without_reply: - :param google_place_id: - :param google_place_type: - :param protect_content: - :return: - """ - return types.Message.de_json( - await asyncio_helper.send_venue( - self.token, chat_id, latitude, longitude, title, address, foursquare_id, foursquare_type, - disable_notification, reply_to_message_id, reply_markup, timeout, - allow_sending_without_reply, google_place_id, google_place_type, protect_content) - ) - - async def send_contact( - self, chat_id: Union[int, str], phone_number: str, - first_name: str, last_name: Optional[str]=None, - vcard: Optional[str]=None, - disable_notification: Optional[bool]=None, - reply_to_message_id: Optional[int]=None, - reply_markup: Optional[REPLY_MARKUP_TYPES]=None, - timeout: Optional[int]=None, - allow_sending_without_reply: Optional[bool]=None, - protect_content: Optional[bool]=None) -> types.Message: - """ - Use this method to send phone contacts. - - Telegram documentation: https://core.telegram.org/bots/api#sendcontact - - :param chat_id: Integer or String : Unique identifier for the target chat or username of the target channel - :param phone_number: String : Contact's phone number - :param first_name: String : Contact's first name - :param last_name: String : Contact's last name - :param vcard: String : Additional data about the contact in the form of a vCard, 0-2048 bytes - :param disable_notification: - :param reply_to_message_id: - :param reply_markup: - :param timeout: - :param allow_sending_without_reply: - :param protect_content: - """ - return types.Message.de_json( - await asyncio_helper.send_contact( - self.token, chat_id, phone_number, first_name, last_name, vcard, - disable_notification, reply_to_message_id, reply_markup, timeout, - allow_sending_without_reply, protect_content) - ) - - async def send_chat_action( - self, chat_id: Union[int, str], action: str, timeout: Optional[int]=None) -> bool: - """ - Use this method when you need to tell the user that something is happening on the bot's side. - The status is set for 5 seconds or less (when a message arrives from your bot, Telegram clients clear - its typing status). - - Telegram documentation: https://core.telegram.org/bots/api#sendchataction - - :param chat_id: - :param action: One of the following strings: 'typing', 'upload_photo', 'record_video', 'upload_video', - 'record_audio', 'upload_audio', 'upload_document', 'find_location', 'record_video_note', - 'upload_video_note'. - :param timeout: - :return: API reply. :type: boolean - """ - return await asyncio_helper.send_chat_action(self.token, chat_id, action, timeout) - - async def kick_chat_member( - self, chat_id: Union[int, str], user_id: int, - until_date:Optional[Union[int, datetime]]=None, - revoke_messages: Optional[bool]=None) -> bool: - """ - This function is deprecated. Use `ban_chat_member` instead - """ - logger.info('kick_chat_member is deprecated. Use ban_chat_member instead.') - return await asyncio_helper.ban_chat_member(self.token, chat_id, user_id, until_date, revoke_messages) - - async def ban_chat_member( - self, chat_id: Union[int, str], user_id: int, - until_date:Optional[Union[int, datetime]]=None, - revoke_messages: Optional[bool]=None) -> bool: - """ - Use this method to ban a user in a group, a supergroup or a channel. - In the case of supergroups and channels, the user will not be able to return to the chat on their - own using invite links, etc., unless unbanned first. - Returns True on success. - - Telegram documentation: https://core.telegram.org/bots/api#banchatmember - - :param chat_id: Int or string : Unique identifier for the target group or username of the target supergroup - :param user_id: Int : Unique identifier of the target user - :param until_date: Date when the user will be unbanned, unix time. If user is banned for more than 366 days or - less than 30 seconds from the current time they are considered to be banned forever - :param revoke_messages: Bool: Pass True to delete all messages from the chat for the user that is being removed. - If False, the user will be able to see messages in the group that were sent before the user was removed. - Always True for supergroups and channels. - :return: boolean - """ - return await asyncio_helper.ban_chat_member(self.token, chat_id, user_id, until_date, revoke_messages) - - async def unban_chat_member( - self, chat_id: Union[int, str], user_id: int, - only_if_banned: Optional[bool]=False) -> bool: - """ - Use this method to unban a previously kicked user in a supergroup or channel. - The user will not return to the group or channel automatically, but will be able to join via link, etc. - The bot must be an administrator for this to work. By async default, this method guarantees that after the call - the user is not a member of the chat, but will be able to join it. So if the user is a member of the chat - they will also be removed from the chat. If you don't want this, use the parameter only_if_banned. - - Telegram documentation: https://core.telegram.org/bots/api#unbanchatmember - - :param chat_id: Unique identifier for the target group or username of the target supergroup or channel - (in the format @username) - :param user_id: Unique identifier of the target user - :param only_if_banned: Do nothing if the user is not banned - :return: True on success - """ - return await asyncio_helper.unban_chat_member(self.token, chat_id, user_id, only_if_banned) - - async def restrict_chat_member( - self, chat_id: Union[int, str], user_id: int, - until_date: Optional[Union[int, datetime]]=None, - can_send_messages: Optional[bool]=None, - can_send_media_messages: Optional[bool]=None, - can_send_polls: Optional[bool]=None, - can_send_other_messages: Optional[bool]=None, - can_add_web_page_previews: Optional[bool]=None, - can_change_info: Optional[bool]=None, - can_invite_users: Optional[bool]=None, - can_pin_messages: Optional[bool]=None) -> bool: - """ - Use this method to restrict a user in a supergroup. - The bot must be an administrator in the supergroup for this to work and must have - the appropriate admin rights. Pass True for all boolean parameters to lift restrictions from a user. - - Telegram documentation: https://core.telegram.org/bots/api#restrictchatmember - - :param chat_id: Int or String : Unique identifier for the target group or username of the target supergroup or channel (in the format @channelusername) - :param user_id: Int : Unique identifier of the target user - :param until_date: Date when restrictions will be lifted for the user, unix time. - If user is restricted for more than 366 days or less than 30 seconds from the current time, - they are considered to be restricted forever - :param can_send_messages: Pass True, if the user can send text messages, contacts, locations and venues - :param can_send_media_messages: Pass True, if the user can send audios, documents, photos, videos, video notes - and voice notes, implies can_send_messages - :param can_send_polls: Pass True, if the user is allowed to send polls, implies can_send_messages - :param can_send_other_messages: Pass True, if the user can send animations, games, stickers and - use inline bots, implies can_send_media_messages - :param can_add_web_page_previews: Pass True, if the user may add web page previews to their messages, implies can_send_media_messages - :param can_change_info: Pass True, if the user is allowed to change the chat title, photo and other settings. Ignored in public supergroups - :param can_invite_users: Pass True, if the user is allowed to invite new users to the chat, implies can_invite_users - :param can_pin_messages: Pass True, if the user is allowed to pin messages. Ignored in public supergroups - :return: True on success - """ - return await asyncio_helper.restrict_chat_member( - self.token, chat_id, user_id, until_date, - can_send_messages, can_send_media_messages, - can_send_polls, can_send_other_messages, - can_add_web_page_previews, can_change_info, - can_invite_users, can_pin_messages) - - async def promote_chat_member( - self, chat_id: Union[int, str], user_id: int, - can_change_info: Optional[bool]=None, - can_post_messages: Optional[bool]=None, - can_edit_messages: Optional[bool]=None, - can_delete_messages: Optional[bool]=None, - can_invite_users: Optional[bool]=None, - can_restrict_members: Optional[bool]=None, - can_pin_messages: Optional[bool]=None, - can_promote_members: Optional[bool]=None, - is_anonymous: Optional[bool]=None, - can_manage_chat: Optional[bool]=None, - can_manage_voice_chats: Optional[bool]=None) -> bool: - """ - Use this method to promote or demote a user in a supergroup or a channel. The bot must be an administrator - in the chat for this to work and must have the appropriate admin rights. - Pass False for all boolean parameters to demote a user. - - Telegram documentation: https://core.telegram.org/bots/api#promotechatmember - - :param chat_id: Unique identifier for the target chat or username of the target channel ( - in the format @channelusername) - :param user_id: Int : Unique identifier of the target user - :param can_change_info: Bool: Pass True, if the administrator can change chat title, photo and other settings - :param can_post_messages: Bool : Pass True, if the administrator can create channel posts, channels only - :param can_edit_messages: Bool : Pass True, if the administrator can edit messages of other users, channels only - :param can_delete_messages: Bool : Pass True, if the administrator can delete messages of other users - :param can_invite_users: Bool : Pass True, if the administrator can invite new users to the chat - :param can_restrict_members: Bool: Pass True, if the administrator can restrict, ban or unban chat members - :param can_pin_messages: Bool: Pass True, if the administrator can pin messages, supergroups only - :param can_promote_members: Bool: Pass True, if the administrator can add new administrators with a subset - of his own privileges or demote administrators that he has promoted, directly or indirectly - (promoted by administrators that were appointed by him) - :param is_anonymous: Bool: Pass True, if the administrator's presence in the chat is hidden - :param can_manage_chat: Bool: Pass True, if the administrator can access the chat event log, chat statistics, - message statistics in channels, see channel members, - see anonymous administrators in supergroups and ignore slow mode. - Implied by any other administrator privilege - :param can_manage_voice_chats: Bool: Pass True, if the administrator can manage voice chats - For now, bots can use this privilege only for passing to other administrators. - :return: True on success. - """ - return await asyncio_helper.promote_chat_member( - self.token, chat_id, user_id, can_change_info, can_post_messages, - can_edit_messages, can_delete_messages, can_invite_users, - can_restrict_members, can_pin_messages, can_promote_members, - is_anonymous, can_manage_chat, can_manage_voice_chats) - - async def set_chat_administrator_custom_title( - self, chat_id: Union[int, str], user_id: int, custom_title: str) -> bool: - """ - Use this method to set a custom title for an administrator - in a supergroup promoted by the bot. - - Telegram documentation: https://core.telegram.org/bots/api#setchatadministratorcustomtitle - - :param chat_id: Unique identifier for the target chat or username of the target supergroup - (in the format @supergroupusername) - :param user_id: Unique identifier of the target user - :param custom_title: New custom title for the administrator; - 0-16 characters, emoji are not allowed - :return: True on success. - """ - return await asyncio_helper.set_chat_administrator_custom_title(self.token, chat_id, user_id, custom_title) - - - async def ban_chat_sender_chat(self, chat_id: Union[int, str], sender_chat_id: Union[int, str]) -> bool: - """ - Use this method to ban a channel chat in a supergroup or a channel. - The owner of the chat will not be able to send messages and join live - streams on behalf of the chat, unless it is unbanned first. - The bot must be an administrator in the supergroup or channel - for this to work and must have the appropriate administrator rights. - Returns True on success. - - Telegram documentation: https://core.telegram.org/bots/api#banchatsenderchat - - :param chat_id: Unique identifier for the target chat or username of the target channel (in the format @channelusername) - :param sender_chat_id: Unique identifier of the target sender chat - :return: True on success. - """ - return await asyncio_helper.ban_chat_sender_chat(self.token, chat_id, sender_chat_id) - - async def unban_chat_sender_chat(self, chat_id: Union[int, str], sender_chat_id: Union[int, str]) -> bool: - """ - Use this method to unban a previously banned channel chat in a supergroup or channel. - The bot must be an administrator for this to work and must have the appropriate - administrator rights. - Returns True on success. - - Telegram documentation: https://core.telegram.org/bots/api#unbanchatsenderchat - - :params: - :param chat_id: Unique identifier for the target chat or username of the target channel (in the format @channelusername) - :param sender_chat_id: Unique identifier of the target sender chat - :return: True on success. - """ - return await asyncio_helper.unban_chat_sender_chat(self.token, chat_id, sender_chat_id) - - async def set_chat_permissions( - self, chat_id: Union[int, str], permissions: types.ChatPermissions) -> bool: - """ - Use this method to set async default chat permissions for all members. - The bot must be an administrator in the group or a supergroup for this to work - and must have the can_restrict_members admin rights. - - Telegram documentation: https://core.telegram.org/bots/api#setchatpermissions - - :param chat_id: Unique identifier for the target chat or username of the target supergroup - (in the format @supergroupusername) - :param permissions: New async default chat permissions - :return: True on success - """ - return await asyncio_helper.set_chat_permissions(self.token, chat_id, permissions) - - async def create_chat_invite_link( - self, chat_id: Union[int, str], - name: Optional[str]=None, - expire_date: Optional[Union[int, datetime]]=None, - member_limit: Optional[int]=None, - creates_join_request: Optional[bool]=None) -> types.ChatInviteLink: - """ - Use this method to create an additional invite link for a chat. - The bot must be an administrator in the chat for this to work and must have the appropriate admin rights. - - Telegram documentation: https://core.telegram.org/bots/api#createchatinvitelink - - :param chat_id: Id: Unique identifier for the target chat or username of the target channel - (in the format @channelusername) - :param name: Invite link name; 0-32 characters - :param expire_date: Point in time (Unix timestamp) when the link will expire - :param member_limit: Maximum number of users that can be members of the chat simultaneously - :param creates_join_request: True, if users joining the chat via the link need to be approved by chat administrators. If True, member_limit can't be specified - :return: - """ - return types.ChatInviteLink.de_json( - await asyncio_helper.create_chat_invite_link(self.token, chat_id, name, expire_date, member_limit, creates_join_request) - ) - - async def edit_chat_invite_link( - self, chat_id: Union[int, str], - invite_link: Optional[str] = None, - name: Optional[str]=None, - expire_date: Optional[Union[int, datetime]]=None, - member_limit: Optional[int]=None, - creates_join_request: Optional[bool]=None) -> types.ChatInviteLink: - """ - Use this method to edit a non-primary invite link created by the bot. - The bot must be an administrator in the chat for this to work and must have the appropriate admin rights. - - Telegram documentation: https://core.telegram.org/bots/api#editchatinvitelink - - :param chat_id: Id: Unique identifier for the target chat or username of the target channel - (in the format @channelusername) - :param name: Invite link name; 0-32 characters - :param invite_link: The invite link to edit - :param expire_date: Point in time (Unix timestamp) when the link will expire - :param member_limit: Maximum number of users that can be members of the chat simultaneously - :param creates_join_request: True, if users joining the chat via the link need to be approved by chat administrators. If True, member_limit can't be specified - :return: - """ - return types.ChatInviteLink.de_json( - await asyncio_helper.edit_chat_invite_link(self.token, chat_id, name, invite_link, expire_date, member_limit, creates_join_request) - ) - - async def revoke_chat_invite_link( - self, chat_id: Union[int, str], invite_link: str) -> types.ChatInviteLink: - """ - Use this method to revoke an invite link created by the bot. - Note: If the primary link is revoked, a new link is automatically generated The bot must be an administrator - in the chat for this to work and must have the appropriate admin rights. - - Telegram documentation: https://core.telegram.org/bots/api#revokechatinvitelink - - :param chat_id: Id: Unique identifier for the target chat or username of the target channel(in the format @channelusername) - :param invite_link: The invite link to revoke - :return: API reply. - """ - return types.ChatInviteLink.de_json( - await asyncio_helper.revoke_chat_invite_link(self.token, chat_id, invite_link) - ) - - async def export_chat_invite_link(self, chat_id: Union[int, str]) -> str: - """ - Use this method to export an invite link to a supergroup or a channel. The bot must be an administrator - in the chat for this to work and must have the appropriate admin rights. - - Telegram documentation: https://core.telegram.org/bots/api#exportchatinvitelink - - :param chat_id: Id: Unique identifier for the target chat or username of the target channel - (in the format @channelusername) - :return: exported invite link as String on success. - """ - return await asyncio_helper.export_chat_invite_link(self.token, chat_id) - - - async def approve_chat_join_request(self, chat_id: Union[str, int], user_id: Union[int, str]) -> bool: - """ - Use this method to approve a chat join request. - The bot must be an administrator in the chat for this to work and must have - the can_invite_users administrator right. Returns True on success. - - Telegram documentation: https://core.telegram.org/bots/api#approvechatjoinrequest - - :param chat_id: Unique identifier for the target chat or username of the target supergroup - (in the format @supergroupusername) - :param user_id: Unique identifier of the target user - :return: True on success. - """ - return await asyncio_helper.approve_chat_join_request(self.token, chat_id, user_id) - - async def decline_chat_join_request(self, chat_id: Union[str, int], user_id: Union[int, str]) -> bool: - """ - Use this method to decline a chat join request. - The bot must be an administrator in the chat for this to work and must have - the can_invite_users administrator right. Returns True on success. - - Telegram documentation: https://core.telegram.org/bots/api#declinechatjoinrequest - - :param chat_id: Unique identifier for the target chat or username of the target supergroup - (in the format @supergroupusername) - :param user_id: Unique identifier of the target user - :return: True on success. - """ - return await asyncio_helper.decline_chat_join_request(self.token, chat_id, user_id) - - async def set_chat_photo(self, chat_id: Union[int, str], photo: Any) -> bool: - """ - Use this method to set a new profile photo for the chat. Photos can't be changed for private chats. - The bot must be an administrator in the chat for this to work and must have the appropriate admin rights. - Returns True on success. - Note: In regular groups (non-supergroups), this method will only work if the ‘All Members Are Admins’ setting is off in the target group. - - Telegram documentation: https://core.telegram.org/bots/api#setchatphoto - - :param chat_id: Int or Str: Unique identifier for the target chat or username of the target channel (in the format @channelusername) - :param photo: InputFile: New chat photo, uploaded using multipart/form-data - :return: - """ - return await asyncio_helper.set_chat_photo(self.token, chat_id, photo) - - async def delete_chat_photo(self, chat_id: Union[int, str]) -> bool: - """ - Use this method to delete a chat photo. Photos can't be changed for private chats. - The bot must be an administrator in the chat for this to work and must have the appropriate admin rights. - Returns True on success. - Note: In regular groups (non-supergroups), this method will only work if the ‘All Members Are Admins’ - setting is off in the target group. - - Telegram documentation: https://core.telegram.org/bots/api#deletechatphoto - - :param chat_id: Int or Str: Unique identifier for the target chat or username of the target channel - (in the format @channelusername) - """ - return await asyncio_helper.delete_chat_photo(self.token, chat_id) - - async def get_my_commands(self, scope: Optional[types.BotCommandScope], - language_code: Optional[str]) -> List[types.BotCommand]: - """ - Use this method to get the current list of the bot's commands. - Returns List of BotCommand on success. - - Telegram documentation: https://core.telegram.org/bots/api#getmycommands - - :param scope: The scope of users for which the commands are relevant. - async defaults to BotCommandScopeasync default. - :param language_code: A two-letter ISO 639-1 language code. If empty, - commands will be applied to all users from the given scope, - for whose language there are no dedicated commands - """ - result = await asyncio_helper.get_my_commands(self.token, scope, language_code) - return [types.BotCommand.de_json(cmd) for cmd in result] - - async def set_my_commands(self, commands: List[types.BotCommand], - scope: Optional[types.BotCommandScope]=None, - language_code: Optional[str]=None) -> bool: - """ - Use this method to change the list of the bot's commands. - - Telegram documentation: https://core.telegram.org/bots/api#setmycommands - - :param commands: List of BotCommand. At most 100 commands can be specified. - :param scope: The scope of users for which the commands are relevant. - async defaults to BotCommandScopeasync default. - :param language_code: A two-letter ISO 639-1 language code. If empty, - commands will be applied to all users from the given scope, - for whose language there are no dedicated commands - :return: - """ - return await asyncio_helper.set_my_commands(self.token, commands, scope, language_code) - - async def delete_my_commands(self, scope: Optional[types.BotCommandScope]=None, - language_code: Optional[int]=None) -> bool: - """ - Use this method to delete the list of the bot's commands for the given scope and user language. - After deletion, higher level commands will be shown to affected users. - Returns True on success. - - Telegram documentation: https://core.telegram.org/bots/api#deletemycommands - - :param scope: The scope of users for which the commands are relevant. - async defaults to BotCommandScopeasync default. - :param language_code: A two-letter ISO 639-1 language code. If empty, - commands will be applied to all users from the given scope, - for whose language there are no dedicated commands - """ - return await asyncio_helper.delete_my_commands(self.token, scope, language_code) - - async def set_chat_title(self, chat_id: Union[int, str], title: str) -> bool: - """ - Use this method to change the title of a chat. Titles can't be changed for private chats. - The bot must be an administrator in the chat for this to work and must have the appropriate admin rights. - Returns True on success. - Note: In regular groups (non-supergroups), this method will only work if the ‘All Members Are Admins’ - setting is off in the target group. - - Telegram documentation: https://core.telegram.org/bots/api#setchattitle - - :param chat_id: Int or Str: Unique identifier for the target chat or username of the target channel - (in the format @channelusername) - :param title: New chat title, 1-255 characters - :return: - """ - return await asyncio_helper.set_chat_title(self.token, chat_id, title) - - async def set_chat_description(self, chat_id: Union[int, str], description: Optional[str]=None) -> bool: - """ - Use this method to change the description of a supergroup or a channel. - The bot must be an administrator in the chat for this to work and must have the appropriate admin rights. - - Telegram documentation: https://core.telegram.org/bots/api#setchatdescription - - :param chat_id: Int or Str: Unique identifier for the target chat or username of the target channel - (in the format @channelusername) - :param description: Str: New chat description, 0-255 characters - :return: True on success. - """ - return await asyncio_helper.set_chat_description(self.token, chat_id, description) - - async def pin_chat_message( - self, chat_id: Union[int, str], message_id: int, - disable_notification: Optional[bool]=False) -> bool: - """ - Use this method to pin a message in a supergroup. - The bot must be an administrator in the chat for this to work and must have the appropriate admin rights. - Returns True on success. - - Telegram documentation: https://core.telegram.org/bots/api#pinchatmessage - - :param chat_id: Int or Str: Unique identifier for the target chat or username of the target channel - (in the format @channelusername) - :param message_id: Int: Identifier of a message to pin - :param disable_notification: Bool: Pass True, if it is not necessary to send a notification - to all group members about the new pinned message - :return: - """ - return await asyncio_helper.pin_chat_message(self.token, chat_id, message_id, disable_notification) - - async def unpin_chat_message(self, chat_id: Union[int, str], message_id: Optional[int]=None) -> bool: - """ - Use this method to unpin specific pinned message in a supergroup chat. - The bot must be an administrator in the chat for this to work and must have the appropriate admin rights. - Returns True on success. - - Telegram documentation: https://core.telegram.org/bots/api#unpinchatmessage - - :param chat_id: Int or Str: Unique identifier for the target chat or username of the target channel - (in the format @channelusername) - :param message_id: Int: Identifier of a message to unpin - :return: - """ - return await asyncio_helper.unpin_chat_message(self.token, chat_id, message_id) - - async def unpin_all_chat_messages(self, chat_id: Union[int, str]) -> bool: - """ - Use this method to unpin a all pinned messages in a supergroup chat. - The bot must be an administrator in the chat for this to work and must have the appropriate admin rights. - Returns True on success. - - Telegram documentation: https://core.telegram.org/bots/api#unpinallchatmessages - - :param chat_id: Int or Str: Unique identifier for the target chat or username of the target channel - (in the format @channelusername) - :return: - """ - return await asyncio_helper.unpin_all_chat_messages(self.token, chat_id) - - async def edit_message_text( - self, text: str, - chat_id: Optional[Union[int, str]]=None, - message_id: Optional[int]=None, - inline_message_id: Optional[str]=None, - parse_mode: Optional[str]=None, - entities: Optional[List[types.MessageEntity]]=None, - disable_web_page_preview: Optional[bool]=None, - reply_markup: Optional[REPLY_MARKUP_TYPES]=None) -> Union[types.Message, bool]: - """ - Use this method to edit text and game messages. - - Telegram documentation: https://core.telegram.org/bots/api#editmessagetext - - :param text: - :param chat_id: - :param message_id: - :param inline_message_id: - :param parse_mode: - :param entities: - :param disable_web_page_preview: - :param reply_markup: - :return: - """ - parse_mode = self.parse_mode if (parse_mode is None) else parse_mode - - result = await asyncio_helper.edit_message_text(self.token, text, chat_id, message_id, inline_message_id, parse_mode, - entities, disable_web_page_preview, reply_markup) - if type(result) == bool: # if edit inline message return is bool not Message. - return result - return types.Message.de_json(result) - - async def edit_message_media( - self, media: Any, chat_id: Optional[Union[int, str]]=None, - message_id: Optional[int]=None, - inline_message_id: Optional[str]=None, - reply_markup: Optional[REPLY_MARKUP_TYPES]=None) -> Union[types.Message, bool]: - """ - Use this method to edit animation, audio, document, photo, or video messages. - If a message is a part of a message album, then it can be edited only to a photo or a video. - Otherwise, message type can be changed arbitrarily. When inline message is edited, new file can't be uploaded. - Use previously uploaded file via its file_id or specify a URL. - - Telegram documentation: https://core.telegram.org/bots/api#editmessagemedia - - :param media: - :param chat_id: - :param message_id: - :param inline_message_id: - :param reply_markup: - :return: - """ - result = await asyncio_helper.edit_message_media(self.token, media, chat_id, message_id, inline_message_id, reply_markup) - if type(result) == bool: # if edit inline message return is bool not Message. - return result - return types.Message.de_json(result) - - async def edit_message_reply_markup( - self, chat_id: Optional[Union[int, str]]=None, - message_id: Optional[int]=None, - inline_message_id: Optional[str]=None, - reply_markup: Optional[REPLY_MARKUP_TYPES]=None) -> Union[types.Message, bool]: - """ - Use this method to edit only the reply markup of messages. - - Telegram documentation: https://core.telegram.org/bots/api#editmessagereplymarkup - - :param chat_id: - :param message_id: - :param inline_message_id: - :param reply_markup: - :return: - """ - result = await asyncio_helper.edit_message_reply_markup(self.token, chat_id, message_id, inline_message_id, reply_markup) - if type(result) == bool: - return result - return types.Message.de_json(result) - - async def send_game( - self, chat_id: Union[int, str], game_short_name: str, - disable_notification: Optional[bool]=None, - reply_to_message_id: Optional[int]=None, - reply_markup: Optional[REPLY_MARKUP_TYPES]=None, - timeout: Optional[int]=None, - allow_sending_without_reply: Optional[bool]=None, - protect_content: Optional[bool]=None) -> types.Message: - """ - Used to send the game. - - Telegram documentation: https://core.telegram.org/bots/api#sendgame - - :param chat_id: - :param game_short_name: - :param disable_notification: - :param reply_to_message_id: - :param reply_markup: - :param timeout: - :param allow_sending_without_reply: - :param protect_content: - :return: - """ - result = await asyncio_helper.send_game( - self.token, chat_id, game_short_name, disable_notification, - reply_to_message_id, reply_markup, timeout, - allow_sending_without_reply, protect_content) - return types.Message.de_json(result) - - async def set_game_score( - self, user_id: Union[int, str], score: int, - force: Optional[bool]=None, - chat_id: Optional[Union[int, str]]=None, - message_id: Optional[int]=None, - inline_message_id: Optional[str]=None, - disable_edit_message: Optional[bool]=None) -> Union[types.Message, bool]: - """ - Sets the value of points in the game to a specific user. - - Telegram documentation: https://core.telegram.org/bots/api#setgamescore - - :param user_id: - :param score: - :param force: - :param chat_id: - :param message_id: - :param inline_message_id: - :param disable_edit_message: - :return: - """ - result = await asyncio_helper.set_game_score(self.token, user_id, score, force, disable_edit_message, chat_id, - message_id, inline_message_id) - if type(result) == bool: - return result - return types.Message.de_json(result) - - async def get_game_high_scores( - self, user_id: int, chat_id: Optional[Union[int, str]]=None, - message_id: Optional[int]=None, - inline_message_id: Optional[str]=None) -> List[types.GameHighScore]: - """ - Gets top points and game play. - - Telegram documentation: https://core.telegram.org/bots/api#getgamehighscores - - :param user_id: - :param chat_id: - :param message_id: - :param inline_message_id: - :return: - """ - result = await asyncio_helper.get_game_high_scores(self.token, user_id, chat_id, message_id, inline_message_id) - return [types.GameHighScore.de_json(r) for r in result] - - async def send_invoice( - self, chat_id: Union[int, str], title: str, description: str, - invoice_payload: str, provider_token: str, currency: str, - prices: List[types.LabeledPrice], start_parameter: Optional[str]=None, - photo_url: Optional[str]=None, photo_size: Optional[int]=None, - photo_width: Optional[int]=None, photo_height: Optional[int]=None, - need_name: Optional[bool]=None, need_phone_number: Optional[bool]=None, - need_email: Optional[bool]=None, need_shipping_address: Optional[bool]=None, - send_phone_number_to_provider: Optional[bool]=None, - send_email_to_provider: Optional[bool]=None, - is_flexible: Optional[bool]=None, - disable_notification: Optional[bool]=None, - reply_to_message_id: Optional[int]=None, - reply_markup: Optional[REPLY_MARKUP_TYPES]=None, - provider_data: Optional[str]=None, - timeout: Optional[int]=None, - allow_sending_without_reply: Optional[bool]=None, - max_tip_amount: Optional[int] = None, - suggested_tip_amounts: Optional[List[int]]=None, - protect_content: Optional[bool]=None) -> types.Message: - """ - Sends invoice. - - Telegram documentation: https://core.telegram.org/bots/api#sendinvoice - - :param chat_id: Unique identifier for the target private chat - :param title: Product name - :param description: Product description - :param invoice_payload: Bot-async defined invoice payload, 1-128 bytes. This will not be displayed to the user, - use for your internal processes. - :param provider_token: Payments provider token, obtained via @Botfather - :param currency: Three-letter ISO 4217 currency code, - see https://core.telegram.org/bots/payments#supported-currencies - :param prices: Price breakdown, a list of components - (e.g. product price, tax, discount, delivery cost, delivery tax, bonus, etc.) - :param start_parameter: Unique deep-linking parameter that can be used to generate this invoice - when used as a start parameter - :param photo_url: URL of the product photo for the invoice. Can be a photo of the goods - or a marketing image for a service. People like it better when they see what they are paying for. - :param photo_size: Photo size - :param photo_width: Photo width - :param photo_height: Photo height - :param need_name: Pass True, if you require the user's full name to complete the order - :param need_phone_number: Pass True, if you require the user's phone number to complete the order - :param need_email: Pass True, if you require the user's email to complete the order - :param need_shipping_address: Pass True, if you require the user's shipping address to complete the order - :param is_flexible: Pass True, if the final price depends on the shipping method - :param send_phone_number_to_provider: Pass True, if user's phone number should be sent to provider - :param send_email_to_provider: Pass True, if user's email address should be sent to provider - :param disable_notification: Sends the message silently. Users will receive a notification with no sound. - :param reply_to_message_id: If the message is a reply, ID of the original message - :param reply_markup: A JSON-serialized object for an inline keyboard. If empty, - one 'Pay total price' button will be shown. If not empty, the first button must be a Pay button - :param provider_data: A JSON-serialized data about the invoice, which will be shared with the payment provider. - A detailed description of required fields should be provided by the payment provider. - :param timeout: - :param allow_sending_without_reply: - :param max_tip_amount: The maximum accepted amount for tips in the smallest units of the currency - :param suggested_tip_amounts: A JSON-serialized array of suggested amounts of tips in the smallest - units of the currency. At most 4 suggested tip amounts can be specified. The suggested tip - amounts must be positive, passed in a strictly increased order and must not exceed max_tip_amount. - :param protect_content: - :return: - """ - result = await asyncio_helper.send_invoice( - self.token, chat_id, title, description, invoice_payload, provider_token, - currency, prices, start_parameter, photo_url, photo_size, photo_width, - photo_height, need_name, need_phone_number, need_email, need_shipping_address, - send_phone_number_to_provider, send_email_to_provider, is_flexible, disable_notification, - reply_to_message_id, reply_markup, provider_data, timeout, allow_sending_without_reply, - max_tip_amount, suggested_tip_amounts, protect_content) - return types.Message.de_json(result) - - # noinspection PyShadowingBuiltins - async def send_poll( - self, chat_id: Union[int, str], question: str, options: List[str], - is_anonymous: Optional[bool]=None, type: Optional[str]=None, - allows_multiple_answers: Optional[bool]=None, - correct_option_id: Optional[int]=None, - explanation: Optional[str]=None, - explanation_parse_mode: Optional[str]=None, - open_period: Optional[int]=None, - close_date: Optional[Union[int, datetime]]=None, - is_closed: Optional[bool]=None, - disable_notification: Optional[bool]=False, - reply_to_message_id: Optional[int]=None, - reply_markup: Optional[REPLY_MARKUP_TYPES]=None, - allow_sending_without_reply: Optional[bool]=None, - timeout: Optional[int]=None, - explanation_entities: Optional[List[types.MessageEntity]]=None, - protect_content: Optional[bool]=None) -> types.Message: - """ - Send polls. - - Telegram documentation: https://core.telegram.org/bots/api#sendpoll - - :param chat_id: - :param question: - :param options: array of str with answers - :param is_anonymous: - :param type: - :param allows_multiple_answers: - :param correct_option_id: - :param explanation: - :param explanation_parse_mode: - :param open_period: - :param close_date: - :param is_closed: - :param disable_notification: - :param reply_to_message_id: - :param allow_sending_without_reply: - :param reply_markup: - :param timeout: - :param explanation_entities: - :param protect_content: - :return: - """ - - if isinstance(question, types.Poll): - raise RuntimeError("The send_poll signature was changed, please see send_poll function details.") - - return types.Message.de_json( - await asyncio_helper.send_poll( - self.token, chat_id, - question, options, - is_anonymous, type, allows_multiple_answers, correct_option_id, - explanation, explanation_parse_mode, open_period, close_date, is_closed, - disable_notification, reply_to_message_id, allow_sending_without_reply, - reply_markup, timeout, explanation_entities, protect_content)) - - async def stop_poll( - self, chat_id: Union[int, str], message_id: int, - reply_markup: Optional[REPLY_MARKUP_TYPES]=None) -> types.Poll: - """ - Stops poll. - - Telegram documentation: https://core.telegram.org/bots/api#stoppoll - - :param chat_id: - :param message_id: - :param reply_markup: - :return: - """ - return types.Poll.de_json(await asyncio_helper.stop_poll(self.token, chat_id, message_id, reply_markup)) - - async def answer_shipping_query( - self, shipping_query_id: str, ok: bool, - shipping_options: Optional[List[types.ShippingOption]]=None, - error_message: Optional[str]=None) -> bool: - """ - Asks for an answer to a shipping question. - - Telegram documentation: https://core.telegram.org/bots/api#answershippingquery - - :param shipping_query_id: - :param ok: - :param shipping_options: - :param error_message: - :return: - """ - return await asyncio_helper.answer_shipping_query(self.token, shipping_query_id, ok, shipping_options, error_message) - - async def answer_pre_checkout_query( - self, pre_checkout_query_id: int, ok: bool, - error_message: Optional[str]=None) -> bool: - """ - Response to a request for pre-inspection. - - Telegram documentation: https://core.telegram.org/bots/api#answerprecheckoutquery - - :param pre_checkout_query_id: - :param ok: - :param error_message: - :return: - """ - return await asyncio_helper.answer_pre_checkout_query(self.token, pre_checkout_query_id, ok, error_message) - - async def edit_message_caption( - self, caption: str, chat_id: Optional[Union[int, str]]=None, - message_id: Optional[int]=None, - inline_message_id: Optional[str]=None, - parse_mode: Optional[str]=None, - caption_entities: Optional[List[types.MessageEntity]]=None, - reply_markup: Optional[REPLY_MARKUP_TYPES]=None) -> Union[types.Message, bool]: - """ - Use this method to edit captions of messages. - - Telegram documentation: https://core.telegram.org/bots/api#editmessagecaption - - :param caption: - :param chat_id: - :param message_id: - :param inline_message_id: - :param parse_mode: - :param caption_entities: - :param reply_markup: - :return: - """ - parse_mode = self.parse_mode if (parse_mode is None) else parse_mode - - result = await asyncio_helper.edit_message_caption(self.token, caption, chat_id, message_id, inline_message_id, - parse_mode, caption_entities, reply_markup) - if type(result) == bool: - return result - return types.Message.de_json(result) - - async def reply_to(self, message: types.Message, text: str, **kwargs) -> types.Message: - """ - Convenience function for `send_message(message.chat.id, text, reply_to_message_id=message.message_id, **kwargs)` - - :param message: - :param text: - :param kwargs: - :return: - """ - return await self.send_message(message.chat.id, text, reply_to_message_id=message.message_id, **kwargs) - - async def answer_inline_query( - self, inline_query_id: str, - results: List[Any], - cache_time: Optional[int]=None, - is_personal: Optional[bool]=None, - next_offset: Optional[str]=None, - switch_pm_text: Optional[str]=None, - switch_pm_parameter: Optional[str]=None) -> bool: - """ - Use this method to send answers to an inline query. On success, True is returned. - No more than 50 results per query are allowed. - - Telegram documentation: https://core.telegram.org/bots/api#answerinlinequery - - :param inline_query_id: Unique identifier for the answered query - :param results: Array of results for the inline query - :param cache_time: The maximum amount of time in seconds that the result of the inline query - may be cached on the server. - :param is_personal: Pass True, if results may be cached on the server side only for - the user that sent the query. - :param next_offset: Pass the offset that a client should send in the next query with the same text - to receive more results. - :param switch_pm_parameter: If passed, clients will display a button with specified text that switches the user - to a private chat with the bot and sends the bot a start message with the parameter switch_pm_parameter - :param switch_pm_text: Parameter for the start message sent to the bot when user presses the switch button - :return: True means success. - """ - return await asyncio_helper.answer_inline_query(self.token, inline_query_id, results, cache_time, is_personal, next_offset, - switch_pm_text, switch_pm_parameter) - - async def answer_callback_query( - self, callback_query_id: int, - text: Optional[str]=None, show_alert: Optional[bool]=None, - url: Optional[str]=None, cache_time: Optional[int]=None) -> bool: - """ - Use this method to send answers to callback queries sent from inline keyboards. The answer will be displayed to - the user as a notification at the top of the chat screen or as an alert. - - Telegram documentation: https://core.telegram.org/bots/api#answercallbackquery - - :param callback_query_id: - :param text: - :param show_alert: - :param url: - :param cache_time: - :return: - """ - return await asyncio_helper.answer_callback_query(self.token, callback_query_id, text, show_alert, url, cache_time) - - async def set_sticker_set_thumb( - self, name: str, user_id: int, thumb: Union[Any, str]=None): - """ - Use this method to set the thumbnail of a sticker set. - Animated thumbnails can be set for animated sticker sets only. Returns True on success. - - Telegram documentation: https://core.telegram.org/bots/api#setstickersetthumb - - :param name: Sticker set name - :param user_id: User identifier - :param thumb: A PNG image with the thumbnail, must be up to 128 kilobytes in size and have width and height - exactly 100px, or a TGS animation with the thumbnail up to 32 kilobytes in size; - see https://core.telegram.org/animated_stickers#technical-requirements - - """ - return await asyncio_helper.set_sticker_set_thumb(self.token, name, user_id, thumb) - - async def get_sticker_set(self, name: str) -> types.StickerSet: - """ - Use this method to get a sticker set. On success, a StickerSet object is returned. - - Telegram documentation: https://core.telegram.org/bots/api#getstickerset - - :param name: - :return: - """ - result = await asyncio_helper.get_sticker_set(self.token, name) - return types.StickerSet.de_json(result) - - async def upload_sticker_file(self, user_id: int, png_sticker: Union[Any, str]) -> types.File: - """ - Use this method to upload a .png file with a sticker for later use in createNewStickerSet and addStickerToSet - methods (can be used multiple times). Returns the uploaded File on success. - - - Telegram documentation: https://core.telegram.org/bots/api#uploadstickerfile - - :param user_id: - :param png_sticker: - :return: - """ - result = await asyncio_helper.upload_sticker_file(self.token, user_id, png_sticker) - return types.File.de_json(result) - - async def create_new_sticker_set( - self, user_id: int, name: str, title: str, - emojis: str, - png_sticker: Union[Any, str]=None, - tgs_sticker: Union[Any, str]=None, - webm_sticker: Union[Any, str]=None, - contains_masks: Optional[bool]=None, - mask_position: Optional[types.MaskPosition]=None) -> bool: - """ - Use this method to create new sticker set owned by a user. - The bot will be able to edit the created sticker set. - Returns True on success. - - Telegram documentation: https://core.telegram.org/bots/api#createnewstickerset - - :param user_id: - :param name: - :param title: - :param emojis: - :param png_sticker: - :param tgs_sticker: - :webm_sticker: - :param contains_masks: - :param mask_position: - :return: - """ - return await asyncio_helper.create_new_sticker_set( - self.token, user_id, name, title, emojis, png_sticker, tgs_sticker, - contains_masks, mask_position, webm_sticker) - - - async def add_sticker_to_set( - self, user_id: int, name: str, emojis: str, - png_sticker: Optional[Union[Any, str]]=None, - tgs_sticker: Optional[Union[Any, str]]=None, - webm_sticker: Optional[Union[Any, str]]=None, - mask_position: Optional[types.MaskPosition]=None) -> bool: - """ - Use this method to add a new sticker to a set created by the bot. - It's required to pass `png_sticker` or `tgs_sticker`. - Returns True on success. - - Telegram documentation: https://core.telegram.org/bots/api#addstickertoset - - :param user_id: - :param name: - :param emojis: - :param png_sticker: Required if `tgs_sticker` is None - :param tgs_sticker: Required if `png_sticker` is None - :webm_sticker: - :param mask_position: - :return: - """ - return await asyncio_helper.add_sticker_to_set( - self.token, user_id, name, emojis, png_sticker, tgs_sticker, mask_position, webm_sticker) - - - async def set_sticker_position_in_set(self, sticker: str, position: int) -> bool: - """ - Use this method to move a sticker in a set created by the bot to a specific position . Returns True on success. - - Telegram documentation: https://core.telegram.org/bots/api#setstickerpositioninset - - :param sticker: - :param position: - :return: - """ - return await asyncio_helper.set_sticker_position_in_set(self.token, sticker, position) - - async def delete_sticker_from_set(self, sticker: str) -> bool: - """ - Use this method to delete a sticker from a set created by the bot. Returns True on success. - - Telegram documentation: https://core.telegram.org/bots/api#deletestickerfromset - - :param sticker: - :return: - """ - return await asyncio_helper.delete_sticker_from_set(self.token, sticker) - - - async def set_state(self, user_id: int, state: str, chat_id: int=None): - """ - Sets a new state of a user. - - :param user_id: - :param chat_id: - :param state: new state. can be string or integer. - """ - if not chat_id: - chat_id = user_id - await self.current_states.set_state(chat_id, user_id, state) - - async def reset_data(self, user_id: int, chat_id: int=None): - """ - Reset data for a user in chat. - - :param user_id: - :param chat_id: - """ - if chat_id is None: - chat_id = user_id - await self.current_states.reset_data(chat_id, user_id) - - async def delete_state(self, user_id: int, chat_id:int=None): - """ - Delete the current state of a user. - - :param user_id: - :param chat_id: - :return: - """ - if not chat_id: - chat_id = user_id - await self.current_states.delete_state(chat_id, user_id) - - def retrieve_data(self, user_id: int, chat_id: int=None): - if not chat_id: - chat_id = user_id - return self.current_states.get_interactive_data(chat_id, user_id) - - async def get_state(self, user_id, chat_id: int=None): - """ - Get current state of a user. - - :param user_id: - :param chat_id: - :return: state of a user - """ - if not chat_id: - chat_id = user_id - return await self.current_states.get_state(chat_id, user_id) - - async def add_data(self, user_id: int, chat_id: int=None, **kwargs): - """ - Add data to states. - - :param user_id: - :param chat_id: - """ - if not chat_id: - chat_id = user_id - for key, value in kwargs.items(): - await self.current_states.set_data(chat_id, user_id, key, value) diff --git a/telebot/asyncio_filters.py b/telebot/asyncio_filters.py deleted file mode 100644 index 1b39761..0000000 --- a/telebot/asyncio_filters.py +++ /dev/null @@ -1,328 +0,0 @@ -from abc import ABC -from typing import Optional, Union -from telebot.asyncio_handler_backends import State - -from telebot import types - - -class SimpleCustomFilter(ABC): - """ - Simple Custom Filter base class. - Create child class with check() method. - Accepts only message, returns bool value, that is compared with given in handler. - """ - - async def check(self, message): - """ - Perform a check. - """ - pass - - -class AdvancedCustomFilter(ABC): - """ - Simple Custom Filter base class. - Create child class with check() method. - Accepts two parameters, returns bool: True - filter passed, False - filter failed. - message: Message class - text: Filter value given in handler - """ - - async def check(self, message, text): - """ - Perform a check. - """ - pass - - -class TextFilter: - """ - Advanced text filter to check (types.Message, types.CallbackQuery, types.InlineQuery, types.Poll) - - example of usage is in examples/custom_filters/advanced_text_filter.py - """ - - def __init__(self, - equals: Optional[str] = None, - contains: Optional[Union[list, tuple]] = None, - starts_with: Optional[Union[str, list, tuple]] = None, - ends_with: Optional[Union[str, list, tuple]] = None, - ignore_case: bool = False): - - """ - :param equals: string, True if object's text is equal to passed string - :param contains: list[str] or tuple[str], True if any string element of iterable is in text - :param starts_with: string, True if object's text starts with passed string - :param ends_with: string, True if object's text starts with passed string - :param ignore_case: bool (default False), case insensitive - """ - - to_check = sum((pattern is not None for pattern in (equals, contains, starts_with, ends_with))) - if to_check == 0: - raise ValueError('None of the check modes was specified') - - self.equals = equals - self.contains = self._check_iterable(contains, filter_name='contains') - self.starts_with = self._check_iterable(starts_with, filter_name='starts_with') - self.ends_with = self._check_iterable(ends_with, filter_name='ends_with') - self.ignore_case = ignore_case - - def _check_iterable(self, iterable, filter_name): - if not iterable: - pass - elif not isinstance(iterable, str) and not isinstance(iterable, list) and not isinstance(iterable, tuple): - raise ValueError(f"Incorrect value of {filter_name!r}") - elif isinstance(iterable, str): - iterable = [iterable] - elif isinstance(iterable, list) or isinstance(iterable, tuple): - iterable = [i for i in iterable if isinstance(i, str)] - return iterable - - async def check(self, obj: Union[types.Message, types.CallbackQuery, types.InlineQuery, types.Poll]): - - if isinstance(obj, types.Poll): - text = obj.question - elif isinstance(obj, types.Message): - text = obj.text or obj.caption - elif isinstance(obj, types.CallbackQuery): - text = obj.data - elif isinstance(obj, types.InlineQuery): - text = obj.query - else: - return False - - if self.ignore_case: - text = text.lower() - prepare_func = lambda string: str(string).lower() - else: - prepare_func = str - - if self.equals: - result = prepare_func(self.equals) == text - if result: - return True - elif not result and not any((self.contains, self.starts_with, self.ends_with)): - return False - - if self.contains: - result = any([prepare_func(i) in text for i in self.contains]) - if result: - return True - elif not result and not any((self.starts_with, self.ends_with)): - return False - - if self.starts_with: - result = any([text.startswith(prepare_func(i)) for i in self.starts_with]) - if result: - return True - elif not result and not self.ends_with: - return False - - if self.ends_with: - return any([text.endswith(prepare_func(i)) for i in self.ends_with]) - - return False - - -class TextMatchFilter(AdvancedCustomFilter): - """ - Filter to check Text message. - key: text - - Example: - @bot.message_handler(text=['account']) - """ - - key = 'text' - - async def check(self, message, text): - if isinstance(text, TextFilter): - return await text.check(message) - elif type(text) is list: - return message.text in text - else: - return text == message.text - - -class TextContainsFilter(AdvancedCustomFilter): - """ - Filter to check Text message. - key: text - - Example: - # Will respond if any message.text contains word 'account' - @bot.message_handler(text_contains=['account']) - """ - - key = 'text_contains' - - async def check(self, message, text): - if not isinstance(text, str) and not isinstance(text, list) and not isinstance(text, tuple): - raise ValueError("Incorrect text_contains value") - elif isinstance(text, str): - text = [text] - elif isinstance(text, list) or isinstance(text, tuple): - text = [i for i in text if isinstance(i, str)] - - return any([i in message.text for i in text]) - - -class TextStartsFilter(AdvancedCustomFilter): - """ - Filter to check whether message starts with some text. - - Example: - # Will work if message.text starts with 'Sir'. - @bot.message_handler(text_startswith='Sir') - """ - - key = 'text_startswith' - - async def check(self, message, text): - return message.text.startswith(text) - - -class ChatFilter(AdvancedCustomFilter): - """ - Check whether chat_id corresponds to given chat_id. - - Example: - @bot.message_handler(chat_id=[99999]) - """ - - key = 'chat_id' - - async def check(self, message, text): - return message.chat.id in text - - -class ForwardFilter(SimpleCustomFilter): - """ - Check whether message was forwarded from channel or group. - - Example: - - @bot.message_handler(is_forwarded=True) - """ - - key = 'is_forwarded' - - async def check(self, message): - return message.forward_from_chat is not None - - -class IsReplyFilter(SimpleCustomFilter): - """ - Check whether message is a reply. - - Example: - - @bot.message_handler(is_reply=True) - """ - - key = 'is_reply' - - async def check(self, message): - return message.reply_to_message is not None - - -class LanguageFilter(AdvancedCustomFilter): - """ - Check users language_code. - - Example: - - @bot.message_handler(language_code=['ru']) - """ - - key = 'language_code' - - async def check(self, message, text): - if type(text) is list: - return message.from_user.language_code in text - else: - return message.from_user.language_code == text - - -class IsAdminFilter(SimpleCustomFilter): - """ - Check whether the user is administrator / owner of the chat. - - Example: - @bot.message_handler(chat_types=['supergroup'], is_chat_admin=True) - """ - - key = 'is_chat_admin' - - def __init__(self, bot): - self._bot = bot - - async def check(self, message): - result = await self._bot.get_chat_member(message.chat.id, message.from_user.id) - return result.status in ['creator', 'administrator'] - - -class StateFilter(AdvancedCustomFilter): - """ - Filter to check state. - - Example: - @bot.message_handler(state=1) - """ - - def __init__(self, bot): - self.bot = bot - - key = 'state' - - async def check(self, message, text): - if text == '*': return True - - # needs to work with callbackquery - if isinstance(message, types.Message): - chat_id = message.chat.id - user_id = message.from_user.id - - if isinstance(message, types.CallbackQuery): - - chat_id = message.message.chat.id - user_id = message.from_user.id - message = message.message - - - if isinstance(text, list): - new_text = [] - for i in text: - if isinstance(i, State): i = i.name - new_text.append(i) - text = new_text - elif isinstance(text, State): - text = text.name - - if message.chat.type == 'group': - group_state = await self.bot.current_states.get_state(user_id, chat_id) - if group_state == text: - return True - elif group_state in text and type(text) is list: - return True - - - else: - user_state = await self.bot.current_states.get_state(user_id, chat_id) - if user_state == text: - return True - elif type(text) is list and user_state in text: - return True - - -class IsDigitFilter(SimpleCustomFilter): - """ - Filter to check whether the string is made up of only digits. - - Example: - @bot.message_handler(is_digit=True) - """ - key = 'is_digit' - - async def check(self, message): - return message.text.isdigit() diff --git a/telebot/asyncio_handler_backends.py b/telebot/asyncio_handler_backends.py deleted file mode 100644 index e5e2e4f..0000000 --- a/telebot/asyncio_handler_backends.py +++ /dev/null @@ -1,56 +0,0 @@ -class BaseMiddleware: - """ - Base class for middleware. - Your middlewares should be inherited from this class. - """ - - def __init__(self): - pass - - async def pre_process(self, message, data): - raise NotImplementedError - - async def post_process(self, message, data, exception): - raise NotImplementedError - - -class State: - def __init__(self) -> None: - self.name = None - - def __str__(self) -> str: - return self.name - - -class StatesGroup: - def __init_subclass__(cls) -> None: - - for name, value in cls.__dict__.items(): - if not name.startswith('__') and not callable(value) and isinstance(value, State): - # change value of that variable - value.name = ':'.join((cls.__name__, name)) - - -class SkipHandler: - """ - Class for skipping handlers. - Just return instance of this class - in middleware to skip handler. - Update will go to post_process, - but will skip execution of handler. - """ - - def __init__(self) -> None: - pass - -class CancelUpdate: - """ - Class for canceling updates. - Just return instance of this class - in middleware to skip update. - Update will skip handler and execution - of post_process in middlewares. - """ - - def __init__(self) -> None: - pass \ No newline at end of file diff --git a/telebot/asyncio_helper.py b/telebot/asyncio_helper.py deleted file mode 100644 index 591410c..0000000 --- a/telebot/asyncio_helper.py +++ /dev/null @@ -1,1741 +0,0 @@ -import asyncio # for future uses -import aiohttp -from telebot import types - -try: - import ujson as json -except ImportError: - import json -import os -API_URL = 'https://api.telegram.org/bot{0}/{1}' - -from datetime import datetime - -import telebot -from telebot import util, logger - - -proxy = None -session = None - -FILE_URL = None - -CONNECT_TIMEOUT = 15 -READ_TIMEOUT = 30 - -LONG_POLLING_TIMEOUT = 10 # Should be positive, short polling should be used for testing purposes only (https://core.telegram.org/bots/api#getupdates) -REQUEST_TIMEOUT = 10 -MAX_RETRIES = 3 -logger = telebot.logger - - -REQUEST_LIMIT = 50 - -class SessionManager: - def __init__(self) -> None: - self.session = aiohttp.ClientSession(connector=aiohttp.TCPConnector(limit=REQUEST_LIMIT)) - - async def create_session(self): - self.session = aiohttp.ClientSession(connector=aiohttp.TCPConnector(limit=REQUEST_LIMIT)) - return self.session - - async def get_session(self): - if self.session.closed: - self.session = await self.create_session() - - # noinspection PyProtectedMember - if not self.session._loop.is_running(): - await self.session.close() - self.session = await self.create_session() - return self.session - - -session_manager = SessionManager() - -async def _process_request(token, url, method='get', params=None, files=None, request_timeout=None): - params = prepare_data(params, files) - if request_timeout is None: - request_timeout = REQUEST_TIMEOUT - timeout = aiohttp.ClientTimeout(total=request_timeout) - got_result = False - current_try=0 - session = await session_manager.get_session() - while not got_result and current_try 0: - ret = ret[:-1] - return '[' + ret + ']' - - - -async def _convert_entites(entites): - if entites is None: - return None - elif len(entites) == 0: - return [] - elif isinstance(entites[0], types.JsonSerializable): - return [entity.to_json() for entity in entites] - else: - return entites - - -async def _convert_poll_options(poll_options): - if poll_options is None: - return None - elif len(poll_options) == 0: - return [] - elif isinstance(poll_options[0], str): - # Compatibility mode with previous bug when only list of string was accepted as poll_options - return poll_options - elif isinstance(poll_options[0], types.PollOption): - return [option.text for option in poll_options] - else: - return poll_options - - -async def convert_input_media(media): - if isinstance(media, types.InputMedia): - return media.convert_input_media() - return None, None - - -async def convert_input_media_array(array): - media = [] - files = {} - for input_media in array: - if isinstance(input_media, types.InputMedia): - media_dict = input_media.to_dict() - if media_dict['media'].startswith('attach://'): - key = media_dict['media'].replace('attach://', '') - files[key] = input_media.media - media.append(media_dict) - return json.dumps(media), files - - -async def _no_encode(func): - def wrapper(key, val): - if key == 'filename': - return u'{0}={1}'.format(key, val) - else: - return func(key, val) - - return wrapper - -async def stop_poll(token, chat_id, message_id, reply_markup=None): - method_url = r'stopPoll' - payload = {'chat_id': str(chat_id), 'message_id': message_id} - if reply_markup: - payload['reply_markup'] = await _convert_markup(reply_markup) - return await _process_request(token, method_url, params=payload) - -# exceptions -class ApiException(Exception): - """ - This class represents a base Exception thrown when a call to the Telegram API fails. - In addition to an informative message, it has a `function_name` and a `result` attribute, which respectively - contain the name of the failed function and the returned result that made the function to be considered as - failed. - """ - - def __init__(self, msg, function_name, result): - super(ApiException, self).__init__("A request to the Telegram API was unsuccessful. {0}".format(msg)) - self.function_name = function_name - self.result = result - -class ApiHTTPException(ApiException): - """ - This class represents an Exception thrown when a call to the - Telegram API server returns HTTP code that is not 200. - """ - def __init__(self, function_name, result): - super(ApiHTTPException, self).__init__( - "The server returned HTTP {0} {1}. Response body:\n[{2}]" \ - .format(result.status_code, result.reason, result), - function_name, - result) - -class ApiInvalidJSONException(ApiException): - """ - This class represents an Exception thrown when a call to the - Telegram API server returns invalid json. - """ - def __init__(self, function_name, result): - super(ApiInvalidJSONException, self).__init__( - "The server returned an invalid JSON response. Response body:\n[{0}]" \ - .format(result), - function_name, - result) - -class ApiTelegramException(ApiException): - """ - This class represents an Exception thrown when a Telegram API returns error code. - """ - def __init__(self, function_name, result, result_json): - super(ApiTelegramException, self).__init__( - "Error code: {0}. Description: {1}" \ - .format(result_json['error_code'], result_json['description']), - function_name, - result) - self.result_json = result_json - self.error_code = result_json['error_code'] - -class RequestTimeout(Exception): - """ - This class represents a request timeout. - """ - pass \ No newline at end of file diff --git a/telebot/asyncio_storage/__init__.py b/telebot/asyncio_storage/__init__.py deleted file mode 100644 index 892f0af..0000000 --- a/telebot/asyncio_storage/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -from telebot.asyncio_storage.memory_storage import StateMemoryStorage -from telebot.asyncio_storage.redis_storage import StateRedisStorage -from telebot.asyncio_storage.pickle_storage import StatePickleStorage -from telebot.asyncio_storage.base_storage import StateContext,StateStorageBase - - - - - -__all__ = [ - 'StateStorageBase', 'StateContext', - 'StateMemoryStorage', 'StateRedisStorage', 'StatePickleStorage' -] \ No newline at end of file diff --git a/telebot/asyncio_storage/base_storage.py b/telebot/asyncio_storage/base_storage.py deleted file mode 100644 index 38615c4..0000000 --- a/telebot/asyncio_storage/base_storage.py +++ /dev/null @@ -1,68 +0,0 @@ -import copy - - -class StateStorageBase: - def __init__(self) -> None: - pass - - async def set_data(self, chat_id, user_id, key, value): - """ - Set data for a user in a particular chat. - """ - raise NotImplementedError - - async def get_data(self, chat_id, user_id): - """ - Get data for a user in a particular chat. - """ - raise NotImplementedError - - async def set_state(self, chat_id, user_id, state): - """ - Set state for a particular user. - - ! Note that you should create a - record if it does not exist, and - if a record with state already exists, - you need to update a record. - """ - raise NotImplementedError - - async def delete_state(self, chat_id, user_id): - """ - Delete state for a particular user. - """ - raise NotImplementedError - - async def reset_data(self, chat_id, user_id): - """ - Reset data for a particular user in a chat. - """ - raise NotImplementedError - - async def get_state(self, chat_id, user_id): - raise NotImplementedError - - async def save(self, chat_id, user_id, data): - raise NotImplementedError - - -class StateContext: - """ - Class for data. - """ - - def __init__(self, obj, chat_id, user_id): - self.obj = obj - self.data = None - self.chat_id = chat_id - self.user_id = user_id - - - - async def __aenter__(self): - self.data = copy.deepcopy(await self.obj.get_data(self.chat_id, self.user_id)) - return self.data - - async def __aexit__(self, exc_type, exc_val, exc_tb): - return await self.obj.save(self.chat_id, self.user_id, self.data) \ No newline at end of file diff --git a/telebot/asyncio_storage/memory_storage.py b/telebot/asyncio_storage/memory_storage.py deleted file mode 100644 index 58a6e35..0000000 --- a/telebot/asyncio_storage/memory_storage.py +++ /dev/null @@ -1,66 +0,0 @@ -from telebot.asyncio_storage.base_storage import StateStorageBase, StateContext - -class StateMemoryStorage(StateStorageBase): - def __init__(self) -> None: - self.data = {} - # - # {chat_id: {user_id: {'state': None, 'data': {}}, ...}, ...} - - - async def set_state(self, chat_id, user_id, state): - if isinstance(state, object): - state = state.name - if chat_id in self.data: - if user_id in self.data[chat_id]: - self.data[chat_id][user_id]['state'] = state - return True - else: - self.data[chat_id][user_id] = {'state': state, 'data': {}} - return True - self.data[chat_id] = {user_id: {'state': state, 'data': {}}} - return True - - async def delete_state(self, chat_id, user_id): - if self.data.get(chat_id): - if self.data[chat_id].get(user_id): - del self.data[chat_id][user_id] - if chat_id == user_id: - del self.data[chat_id] - - return True - - return False - - - async def get_state(self, chat_id, user_id): - if self.data.get(chat_id): - if self.data[chat_id].get(user_id): - return self.data[chat_id][user_id]['state'] - - return None - async def get_data(self, chat_id, user_id): - if self.data.get(chat_id): - if self.data[chat_id].get(user_id): - return self.data[chat_id][user_id]['data'] - - return None - - async def reset_data(self, chat_id, user_id): - if self.data.get(chat_id): - if self.data[chat_id].get(user_id): - self.data[chat_id][user_id]['data'] = {} - return True - return False - - async def set_data(self, chat_id, user_id, key, value): - if self.data.get(chat_id): - if self.data[chat_id].get(user_id): - self.data[chat_id][user_id]['data'][key] = value - return True - raise RuntimeError('chat_id {} and user_id {} does not exist'.format(chat_id, user_id)) - - def get_interactive_data(self, chat_id, user_id): - return StateContext(self, chat_id, user_id) - - async def save(self, chat_id, user_id, data): - self.data[chat_id][user_id]['data'] = data \ No newline at end of file diff --git a/telebot/asyncio_storage/pickle_storage.py b/telebot/asyncio_storage/pickle_storage.py deleted file mode 100644 index dd6419e..0000000 --- a/telebot/asyncio_storage/pickle_storage.py +++ /dev/null @@ -1,109 +0,0 @@ -from telebot.asyncio_storage.base_storage import StateStorageBase, StateContext -import os - - -import pickle - - -class StatePickleStorage(StateStorageBase): - def __init__(self, file_path="./.state-save/states.pkl") -> None: - self.file_path = file_path - self.create_dir() - self.data = self.read() - - async def convert_old_to_new(self): - # old looks like: - # {1: {'state': 'start', 'data': {'name': 'John'}} - # we should update old version pickle to new. - # new looks like: - # {1: {2: {'state': 'start', 'data': {'name': 'John'}}}} - new_data = {} - for key, value in self.data.items(): - # this returns us id and dict with data and state - new_data[key] = {key: value} # convert this to new - # pass it to global data - self.data = new_data - self.update_data() # update data in file - - def create_dir(self): - """ - Create directory .save-handlers. - """ - dirs = self.file_path.rsplit('/', maxsplit=1)[0] - os.makedirs(dirs, exist_ok=True) - if not os.path.isfile(self.file_path): - with open(self.file_path,'wb') as file: - pickle.dump({}, file) - - def read(self): - file = open(self.file_path, 'rb') - data = pickle.load(file) - file.close() - return data - - def update_data(self): - file = open(self.file_path, 'wb+') - pickle.dump(self.data, file, protocol=pickle.HIGHEST_PROTOCOL) - file.close() - - async def set_state(self, chat_id, user_id, state): - if isinstance(state, object): - state = state.name - if chat_id in self.data: - if user_id in self.data[chat_id]: - self.data[chat_id][user_id]['state'] = state - return True - else: - self.data[chat_id][user_id] = {'state': state, 'data': {}} - return True - self.data[chat_id] = {user_id: {'state': state, 'data': {}}} - self.update_data() - return True - - async def delete_state(self, chat_id, user_id): - if self.data.get(chat_id): - if self.data[chat_id].get(user_id): - del self.data[chat_id][user_id] - if chat_id == user_id: - del self.data[chat_id] - self.update_data() - return True - - return False - - - async def get_state(self, chat_id, user_id): - if self.data.get(chat_id): - if self.data[chat_id].get(user_id): - return self.data[chat_id][user_id]['state'] - - return None - async def get_data(self, chat_id, user_id): - if self.data.get(chat_id): - if self.data[chat_id].get(user_id): - return self.data[chat_id][user_id]['data'] - - return None - - async def reset_data(self, chat_id, user_id): - if self.data.get(chat_id): - if self.data[chat_id].get(user_id): - self.data[chat_id][user_id]['data'] = {} - self.update_data() - return True - return False - - async def set_data(self, chat_id, user_id, key, value): - if self.data.get(chat_id): - if self.data[chat_id].get(user_id): - self.data[chat_id][user_id]['data'][key] = value - self.update_data() - return True - raise RuntimeError('chat_id {} and user_id {} does not exist'.format(chat_id, user_id)) - - def get_interactive_data(self, chat_id, user_id): - return StateContext(self, chat_id, user_id) - - async def save(self, chat_id, user_id, data): - self.data[chat_id][user_id]['data'] = data - self.update_data() \ No newline at end of file diff --git a/telebot/asyncio_storage/redis_storage.py b/telebot/asyncio_storage/redis_storage.py deleted file mode 100644 index f2a2606..0000000 --- a/telebot/asyncio_storage/redis_storage.py +++ /dev/null @@ -1,171 +0,0 @@ -from telebot.asyncio_storage.base_storage import StateStorageBase, StateContext -import json - -redis_installed = True -try: - import aioredis -except: - redis_installed = False - - -class StateRedisStorage(StateStorageBase): - """ - This class is for Redis storage. - This will work only for states. - To use it, just pass this class to: - TeleBot(storage=StateRedisStorage()) - """ - def __init__(self, host='localhost', port=6379, db=0, password=None, prefix='telebot_'): - if not redis_installed: - raise ImportError('AioRedis is not installed. Install it via "pip install aioredis"') - - - aioredis_version = tuple(map(int, aioredis.__version__.split(".")[0])) - if aioredis_version < (2,): - raise ImportError('Invalid aioredis version. Aioredis version should be >= 2.0.0') - self.redis = aioredis.Redis(host=host, port=port, db=db, password=password) - - self.prefix = prefix - #self.con = Redis(connection_pool=self.redis) -> use this when necessary - # - # {chat_id: {user_id: {'state': None, 'data': {}}, ...}, ...} - - async def get_record(self, key): - """ - Function to get record from database. - It has nothing to do with states. - Made for backend compatibility - """ - result = await self.redis.get(self.prefix+str(key)) - if result: return json.loads(result) - return - - async def set_record(self, key, value): - """ - Function to set record to database. - It has nothing to do with states. - Made for backend compatibility - """ - - await self.redis.set(self.prefix+str(key), json.dumps(value)) - return True - - async def delete_record(self, key): - """ - Function to delete record from database. - It has nothing to do with states. - Made for backend compatibility - """ - await self.redis.delete(self.prefix+str(key)) - return True - - async def set_state(self, chat_id, user_id, state): - """ - Set state for a particular user in a chat. - """ - response = await self.get_record(chat_id) - user_id = str(user_id) - if isinstance(state, object): - state = state.name - if response: - if user_id in response: - response[user_id]['state'] = state - else: - response[user_id] = {'state': state, 'data': {}} - else: - response = {user_id: {'state': state, 'data': {}}} - await self.set_record(chat_id, response) - - return True - - async def delete_state(self, chat_id, user_id): - """ - Delete state for a particular user in a chat. - """ - response = await self.get_record(chat_id) - user_id = str(user_id) - if response: - if user_id in response: - del response[user_id] - if user_id == str(chat_id): - await self.delete_record(chat_id) - return True - else: await self.set_record(chat_id, response) - return True - return False - - async def get_value(self, chat_id, user_id, key): - """ - Get value for a data of a user in a chat. - """ - response = await self.get_record(chat_id) - user_id = str(user_id) - if response: - if user_id in response: - if key in response[user_id]['data']: - return response[user_id]['data'][key] - return None - - async def get_state(self, chat_id, user_id): - """ - Get state of a user in a chat. - """ - response = await self.get_record(chat_id) - user_id = str(user_id) - if response: - if user_id in response: - return response[user_id]['state'] - - return None - - async def get_data(self, chat_id, user_id): - """ - Get data of particular user in a particular chat. - """ - response = await self.get_record(chat_id) - user_id = str(user_id) - if response: - if user_id in response: - return response[user_id]['data'] - return None - - async def reset_data(self, chat_id, user_id): - """ - Reset data of a user in a chat. - """ - response = await self.get_record(chat_id) - user_id = str(user_id) - if response: - if user_id in response: - response[user_id]['data'] = {} - await self.set_record(chat_id, response) - return True - - async def set_data(self, chat_id, user_id, key, value): - """ - Set data without interactive data. - """ - response = await self.get_record(chat_id) - user_id = str(user_id) - if response: - if user_id in response: - response[user_id]['data'][key] = value - await self.set_record(chat_id, response) - return True - return False - - def get_interactive_data(self, chat_id, user_id): - """ - Get Data in interactive way. - You can use with() with this function. - """ - return StateContext(self, chat_id, user_id) - - async def save(self, chat_id, user_id, data): - response = await self.get_record(chat_id) - user_id = str(user_id) - if response: - if user_id in response: - response[user_id]['data'] = dict(data, **response[user_id]['data']) - await self.set_record(chat_id, response) - return True diff --git a/telebot/callback_data.py b/telebot/callback_data.py deleted file mode 100644 index ecbe81e..0000000 --- a/telebot/callback_data.py +++ /dev/null @@ -1,115 +0,0 @@ -import typing - - -class CallbackDataFilter: - - def __init__(self, factory, config: typing.Dict[str, str]): - self.config = config - self.factory = factory - - def check(self, query): - """ - Checks if query.data appropriates to specified config - :param query: telebot.types.CallbackQuery - :return: bool - """ - - try: - data = self.factory.parse(query.data) - except ValueError: - return False - - for key, value in self.config.items(): - if isinstance(value, (list, tuple, set, frozenset)): - if data.get(key) not in value: - return False - elif data.get(key) != value: - return False - return True - - -class CallbackData: - """ - Callback data factory - This class will help you to work with CallbackQuery - """ - - def __init__(self, *parts, prefix: str, sep=':'): - if not isinstance(prefix, str): - raise TypeError(f'Prefix must be instance of str not {type(prefix).__name__}') - if not prefix: - raise ValueError("Prefix can't be empty") - if sep in prefix: - raise ValueError(f"Separator {sep!r} can't be used in prefix") - - self.prefix = prefix - self.sep = sep - - self._part_names = parts - - def new(self, *args, **kwargs) -> str: - """ - Generate callback data - :param args: positional parameters of CallbackData instance parts - :param kwargs: named parameters - :return: str - """ - args = list(args) - - data = [self.prefix] - - for part in self._part_names: - value = kwargs.pop(part, None) - if value is None: - if args: - value = args.pop(0) - else: - raise ValueError(f'Value for {part!r} was not passed!') - - if value is not None and not isinstance(value, str): - value = str(value) - - if self.sep in value: - raise ValueError(f"Symbol {self.sep!r} is defined as the separator and can't be used in parts' values") - - data.append(value) - - if args or kwargs: - raise TypeError('Too many arguments were passed!') - - callback_data = self.sep.join(data) - - if len(callback_data.encode()) > 64: - raise ValueError('Resulted callback data is too long!') - - return callback_data - - def parse(self, callback_data: str) -> typing.Dict[str, str]: - """ - Parse data from the callback data - :param callback_data: string, use to telebot.types.CallbackQuery to parse it from string to a dict - :return: dict parsed from callback data - """ - - prefix, *parts = callback_data.split(self.sep) - if prefix != self.prefix: - raise ValueError("Passed callback data can't be parsed with that prefix.") - elif len(parts) != len(self._part_names): - raise ValueError('Invalid parts count!') - - result = {'@': prefix} - result.update(zip(self._part_names, parts)) - return result - - def filter(self, **config) -> CallbackDataFilter: - """ - Generate filter - - :param config: specified named parameters will be checked with CallbackQury.data - :return: CallbackDataFilter class - """ - - for key in config.keys(): - if key not in self._part_names: - raise ValueError(f'Invalid field name {key!r}') - return CallbackDataFilter(self, config) diff --git a/telebot/custom_filters.py b/telebot/custom_filters.py deleted file mode 100644 index 8442be4..0000000 --- a/telebot/custom_filters.py +++ /dev/null @@ -1,336 +0,0 @@ -from abc import ABC -from typing import Optional, Union -from telebot.handler_backends import State - -from telebot import types - - - - -class SimpleCustomFilter(ABC): - """ - Simple Custom Filter base class. - Create child class with check() method. - Accepts only message, returns bool value, that is compared with given in handler. - """ - - def check(self, message): - """ - Perform a check. - """ - pass - - -class AdvancedCustomFilter(ABC): - """ - Simple Custom Filter base class. - Create child class with check() method. - Accepts two parameters, returns bool: True - filter passed, False - filter failed. - message: Message class - text: Filter value given in handler - """ - - def check(self, message, text): - """ - Perform a check. - """ - pass - - -class TextFilter: - """ - Advanced text filter to check (types.Message, types.CallbackQuery, types.InlineQuery, types.Poll) - - example of usage is in examples/custom_filters/advanced_text_filter.py - """ - - def __init__(self, - equals: Optional[str] = None, - contains: Optional[Union[list, tuple]] = None, - starts_with: Optional[Union[str, list, tuple]] = None, - ends_with: Optional[Union[str, list, tuple]] = None, - ignore_case: bool = False): - - """ - :param equals: string, True if object's text is equal to passed string - :param contains: list[str] or tuple[str], True if any string element of iterable is in text - :param starts_with: string, True if object's text starts with passed string - :param ends_with: string, True if object's text starts with passed string - :param ignore_case: bool (default False), case insensitive - """ - - to_check = sum((pattern is not None for pattern in (equals, contains, starts_with, ends_with))) - if to_check == 0: - raise ValueError('None of the check modes was specified') - - self.equals = equals - self.contains = self._check_iterable(contains, filter_name='contains') - self.starts_with = self._check_iterable(starts_with, filter_name='starts_with') - self.ends_with = self._check_iterable(ends_with, filter_name='ends_with') - self.ignore_case = ignore_case - - def _check_iterable(self, iterable, filter_name: str): - if not iterable: - pass - elif not isinstance(iterable, str) and not isinstance(iterable, list) and not isinstance(iterable, tuple): - raise ValueError(f"Incorrect value of {filter_name!r}") - elif isinstance(iterable, str): - iterable = [iterable] - elif isinstance(iterable, list) or isinstance(iterable, tuple): - iterable = [i for i in iterable if isinstance(i, str)] - return iterable - - def check(self, obj: Union[types.Message, types.CallbackQuery, types.InlineQuery, types.Poll]): - - if isinstance(obj, types.Poll): - text = obj.question - elif isinstance(obj, types.Message): - text = obj.text or obj.caption - elif isinstance(obj, types.CallbackQuery): - text = obj.data - elif isinstance(obj, types.InlineQuery): - text = obj.query - else: - return False - - if self.ignore_case: - text = text.lower() - - if self.equals: - self.equals = self.equals.lower() - elif self.contains: - self.contains = tuple(map(str.lower, self.contains)) - elif self.starts_with: - self.starts_with = tuple(map(str.lower, self.starts_with)) - elif self.ends_with: - self.ends_with = tuple(map(str.lower, self.ends_with)) - - if self.equals: - result = self.equals == text - if result: - return True - elif not result and not any((self.contains, self.starts_with, self.ends_with)): - return False - - if self.contains: - result = any([i in text for i in self.contains]) - if result: - return True - elif not result and not any((self.starts_with, self.ends_with)): - return False - - if self.starts_with: - result = any([text.startswith(i) for i in self.starts_with]) - if result: - return True - elif not result and not self.ends_with: - return False - - if self.ends_with: - return any([text.endswith(i) for i in self.ends_with]) - - return False - -class TextMatchFilter(AdvancedCustomFilter): - """ - Filter to check Text message. - key: text - - Example: - @bot.message_handler(text=['account']) - """ - - key = 'text' - - def check(self, message, text): - if isinstance(text, TextFilter): - return text.check(message) - elif type(text) is list: - return message.text in text - else: - return text == message.text - - -class TextContainsFilter(AdvancedCustomFilter): - """ - Filter to check Text message. - key: text - - Example: - # Will respond if any message.text contains word 'account' - @bot.message_handler(text_contains=['account']) - """ - - key = 'text_contains' - - def check(self, message, text): - if not isinstance(text, str) and not isinstance(text, list) and not isinstance(text, tuple): - raise ValueError("Incorrect text_contains value") - elif isinstance(text, str): - text = [text] - elif isinstance(text, list) or isinstance(text, tuple): - text = [i for i in text if isinstance(i, str)] - - return any([i in message.text for i in text]) - - -class TextStartsFilter(AdvancedCustomFilter): - """ - Filter to check whether message starts with some text. - - Example: - # Will work if message.text starts with 'Sir'. - @bot.message_handler(text_startswith='Sir') - """ - - key = 'text_startswith' - - def check(self, message, text): - return message.text.startswith(text) - - -class ChatFilter(AdvancedCustomFilter): - """ - Check whether chat_id corresponds to given chat_id. - - Example: - @bot.message_handler(chat_id=[99999]) - """ - - key = 'chat_id' - - def check(self, message, text): - return message.chat.id in text - - -class ForwardFilter(SimpleCustomFilter): - """ - Check whether message was forwarded from channel or group. - - Example: - - @bot.message_handler(is_forwarded=True) - """ - - key = 'is_forwarded' - - def check(self, message): - return message.forward_from_chat is not None - - -class IsReplyFilter(SimpleCustomFilter): - """ - Check whether message is a reply. - - Example: - - @bot.message_handler(is_reply=True) - """ - - key = 'is_reply' - - def check(self, message): - return message.reply_to_message is not None - - -class LanguageFilter(AdvancedCustomFilter): - """ - Check users language_code. - - Example: - - @bot.message_handler(language_code=['ru']) - """ - - key = 'language_code' - - def check(self, message, text): - if type(text) is list: - return message.from_user.language_code in text - else: - return message.from_user.language_code == text - - -class IsAdminFilter(SimpleCustomFilter): - """ - Check whether the user is administrator / owner of the chat. - - Example: - @bot.message_handler(chat_types=['supergroup'], is_chat_admin=True) - """ - - key = 'is_chat_admin' - - def __init__(self, bot): - self._bot = bot - - def check(self, message): - return self._bot.get_chat_member(message.chat.id, message.from_user.id).status in ['creator', 'administrator'] - - -class StateFilter(AdvancedCustomFilter): - """ - Filter to check state. - - Example: - @bot.message_handler(state=1) - """ - - def __init__(self, bot): - self.bot = bot - - key = 'state' - - def check(self, message, text): - if text == '*': return True - - # needs to work with callbackquery - if isinstance(message, types.Message): - chat_id = message.chat.id - user_id = message.from_user.id - - if isinstance(message, types.CallbackQuery): - - chat_id = message.message.chat.id - user_id = message.from_user.id - message = message.message - - - - - if isinstance(text, list): - new_text = [] - for i in text: - if isinstance(i, State): i = i.name - new_text.append(i) - text = new_text - elif isinstance(text, State): - text = text.name - - if message.chat.type == 'group': - group_state = self.bot.current_states.get_state(user_id, chat_id) - if group_state == text: - return True - elif group_state in text and type(text) is list: - return True - - - else: - user_state = self.bot.current_states.get_state(user_id, chat_id) - if user_state == text: - return True - elif type(text) is list and user_state in text: - return True - - -class IsDigitFilter(SimpleCustomFilter): - """ - Filter to check whether the string is made up of only digits. - - Example: - @bot.message_handler(is_digit=True) - """ - key = 'is_digit' - - def check(self, message): - return message.text.isdigit() diff --git a/telebot/handler_backends.py b/telebot/handler_backends.py deleted file mode 100644 index b838231..0000000 --- a/telebot/handler_backends.py +++ /dev/null @@ -1,206 +0,0 @@ -import os -import pickle -import threading - -from telebot import apihelper -try: - from redis import Redis - redis_installed = True -except: - redis_installed = False - - -class HandlerBackend(object): - """ - Class for saving (next step|reply) handlers - """ - def __init__(self, handlers=None): - if handlers is None: - handlers = {} - self.handlers = handlers - - def register_handler(self, handler_group_id, handler): - raise NotImplementedError() - - def clear_handlers(self, handler_group_id): - raise NotImplementedError() - - def get_handlers(self, handler_group_id): - raise NotImplementedError() - - -class MemoryHandlerBackend(HandlerBackend): - def register_handler(self, handler_group_id, handler): - if handler_group_id in self.handlers: - self.handlers[handler_group_id].append(handler) - else: - self.handlers[handler_group_id] = [handler] - - def clear_handlers(self, handler_group_id): - self.handlers.pop(handler_group_id, None) - - def get_handlers(self, handler_group_id): - return self.handlers.pop(handler_group_id, None) - - def load_handlers(self, filename, del_file_after_loading): - raise NotImplementedError() - - -class FileHandlerBackend(HandlerBackend): - def __init__(self, handlers=None, filename='./.handler-saves/handlers.save', delay=120): - super(FileHandlerBackend, self).__init__(handlers) - self.filename = filename - self.delay = delay - self.timer = threading.Timer(delay, self.save_handlers) - - def register_handler(self, handler_group_id, handler): - if handler_group_id in self.handlers: - self.handlers[handler_group_id].append(handler) - else: - self.handlers[handler_group_id] = [handler] - self.start_save_timer() - - def clear_handlers(self, handler_group_id): - self.handlers.pop(handler_group_id, None) - self.start_save_timer() - - def get_handlers(self, handler_group_id): - handlers = self.handlers.pop(handler_group_id, None) - self.start_save_timer() - return handlers - - def start_save_timer(self): - if not self.timer.is_alive(): - if self.delay <= 0: - self.save_handlers() - else: - self.timer = threading.Timer(self.delay, self.save_handlers) - self.timer.start() - - def save_handlers(self): - self.dump_handlers(self.handlers, self.filename) - - def load_handlers(self, filename=None, del_file_after_loading=True): - if not filename: - filename = self.filename - tmp = self.return_load_handlers(filename, del_file_after_loading=del_file_after_loading) - if tmp is not None: - self.handlers.update(tmp) - - @staticmethod - def dump_handlers(handlers, filename, file_mode="wb"): - dirs = filename.rsplit('/', maxsplit=1)[0] - os.makedirs(dirs, exist_ok=True) - - with open(filename + ".tmp", file_mode) as file: - if (apihelper.CUSTOM_SERIALIZER is None): - pickle.dump(handlers, file) - else: - apihelper.CUSTOM_SERIALIZER.dump(handlers, file) - - if os.path.isfile(filename): - os.remove(filename) - - os.rename(filename + ".tmp", filename) - - @staticmethod - def return_load_handlers(filename, del_file_after_loading=True): - if os.path.isfile(filename) and os.path.getsize(filename) > 0: - with open(filename, "rb") as file: - if (apihelper.CUSTOM_SERIALIZER is None): - handlers = pickle.load(file) - else: - handlers = apihelper.CUSTOM_SERIALIZER.load(file) - - if del_file_after_loading: - os.remove(filename) - - return handlers - - -class RedisHandlerBackend(HandlerBackend): - def __init__(self, handlers=None, host='localhost', port=6379, db=0, prefix='telebot', password=None): - super(RedisHandlerBackend, self).__init__(handlers) - if not redis_installed: - raise Exception("Redis is not installed. Install it via 'pip install redis'") - self.prefix = prefix - self.redis = Redis(host, port, db, password) - - def _key(self, handle_group_id): - return ':'.join((self.prefix, str(handle_group_id))) - - def register_handler(self, handler_group_id, handler): - handlers = [] - value = self.redis.get(self._key(handler_group_id)) - if value: - handlers = pickle.loads(value) - handlers.append(handler) - self.redis.set(self._key(handler_group_id), pickle.dumps(handlers)) - - def clear_handlers(self, handler_group_id): - self.redis.delete(self._key(handler_group_id)) - - def get_handlers(self, handler_group_id): - handlers = None - value = self.redis.get(self._key(handler_group_id)) - if value: - handlers = pickle.loads(value) - self.clear_handlers(handler_group_id) - return handlers - - -class State: - def __init__(self) -> None: - self.name = None - def __str__(self) -> str: - return self.name - - - -class StatesGroup: - def __init_subclass__(cls) -> None: - for name, value in cls.__dict__.items(): - if not name.startswith('__') and not callable(value) and isinstance(value, State): - # change value of that variable - value.name = ':'.join((cls.__name__, name)) - - -class BaseMiddleware: - """ - Base class for middleware. - Your middlewares should be inherited from this class. - """ - - def __init__(self): - pass - - def pre_process(self, message, data): - raise NotImplementedError - - def post_process(self, message, data, exception): - raise NotImplementedError - - -class SkipHandler: - """ - Class for skipping handlers. - Just return instance of this class - in middleware to skip handler. - Update will go to post_process, - but will skip execution of handler. - """ - - def __init__(self) -> None: - pass - -class CancelUpdate: - """ - Class for canceling updates. - Just return instance of this class - in middleware to skip update. - Update will skip handler and execution - of post_process in middlewares. - """ - - def __init__(self) -> None: - pass \ No newline at end of file diff --git a/telebot/storage/__init__.py b/telebot/storage/__init__.py deleted file mode 100644 index 59e2b05..0000000 --- a/telebot/storage/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -from telebot.storage.memory_storage import StateMemoryStorage -from telebot.storage.redis_storage import StateRedisStorage -from telebot.storage.pickle_storage import StatePickleStorage -from telebot.storage.base_storage import StateContext,StateStorageBase - - - - - -__all__ = [ - 'StateStorageBase', 'StateContext', - 'StateMemoryStorage', 'StateRedisStorage', 'StatePickleStorage' -] \ No newline at end of file diff --git a/telebot/storage/base_storage.py b/telebot/storage/base_storage.py deleted file mode 100644 index bafd9a1..0000000 --- a/telebot/storage/base_storage.py +++ /dev/null @@ -1,65 +0,0 @@ -import copy - -class StateStorageBase: - def __init__(self) -> None: - pass - - def set_data(self, chat_id, user_id, key, value): - """ - Set data for a user in a particular chat. - """ - raise NotImplementedError - - def get_data(self, chat_id, user_id): - """ - Get data for a user in a particular chat. - """ - raise NotImplementedError - - def set_state(self, chat_id, user_id, state): - """ - Set state for a particular user. - - ! Note that you should create a - record if it does not exist, and - if a record with state already exists, - you need to update a record. - """ - raise NotImplementedError - - def delete_state(self, chat_id, user_id): - """ - Delete state for a particular user. - """ - raise NotImplementedError - - def reset_data(self, chat_id, user_id): - """ - Reset data for a particular user in a chat. - """ - raise NotImplementedError - - def get_state(self, chat_id, user_id): - raise NotImplementedError - - def save(self, chat_id, user_id, data): - raise NotImplementedError - - - -class StateContext: - """ - Class for data. - """ - def __init__(self , obj, chat_id, user_id) -> None: - self.obj = obj - self.data = copy.deepcopy(obj.get_data(chat_id, user_id)) - self.chat_id = chat_id - self.user_id = user_id - - - def __enter__(self): - return self.data - - def __exit__(self, exc_type, exc_val, exc_tb): - return self.obj.save(self.chat_id, self.user_id, self.data) \ No newline at end of file diff --git a/telebot/storage/memory_storage.py b/telebot/storage/memory_storage.py deleted file mode 100644 index 45d4da3..0000000 --- a/telebot/storage/memory_storage.py +++ /dev/null @@ -1,67 +0,0 @@ -from telebot.storage.base_storage import StateStorageBase, StateContext - -class StateMemoryStorage(StateStorageBase): - def __init__(self) -> None: - self.data = {} - # - # {chat_id: {user_id: {'state': None, 'data': {}}, ...}, ...} - - - def set_state(self, chat_id, user_id, state): - if isinstance(state, object): - state = state.name - if chat_id in self.data: - if user_id in self.data[chat_id]: - self.data[chat_id][user_id]['state'] = state - return True - else: - self.data[chat_id][user_id] = {'state': state, 'data': {}} - return True - self.data[chat_id] = {user_id: {'state': state, 'data': {}}} - return True - - def delete_state(self, chat_id, user_id): - if self.data.get(chat_id): - if self.data[chat_id].get(user_id): - del self.data[chat_id][user_id] - if chat_id == user_id: - del self.data[chat_id] - - return True - - return False - - - def get_state(self, chat_id, user_id): - - if self.data.get(chat_id): - if self.data[chat_id].get(user_id): - return self.data[chat_id][user_id]['state'] - - return None - def get_data(self, chat_id, user_id): - if self.data.get(chat_id): - if self.data[chat_id].get(user_id): - return self.data[chat_id][user_id]['data'] - - return None - - def reset_data(self, chat_id, user_id): - if self.data.get(chat_id): - if self.data[chat_id].get(user_id): - self.data[chat_id][user_id]['data'] = {} - return True - return False - - def set_data(self, chat_id, user_id, key, value): - if self.data.get(chat_id): - if self.data[chat_id].get(user_id): - self.data[chat_id][user_id]['data'][key] = value - return True - raise RuntimeError('chat_id {} and user_id {} does not exist'.format(chat_id, user_id)) - - def get_interactive_data(self, chat_id, user_id): - return StateContext(self, chat_id, user_id) - - def save(self, chat_id, user_id, data): - self.data[chat_id][user_id]['data'] = data \ No newline at end of file diff --git a/telebot/storage/pickle_storage.py b/telebot/storage/pickle_storage.py deleted file mode 100644 index a273690..0000000 --- a/telebot/storage/pickle_storage.py +++ /dev/null @@ -1,115 +0,0 @@ -from telebot.storage.base_storage import StateStorageBase, StateContext -import os - - -import pickle - - -class StatePickleStorage(StateStorageBase): - # noinspection PyMissingConstructor - def __init__(self, file_path="./.state-save/states.pkl") -> None: - self.file_path = file_path - self.create_dir() - self.data = self.read() - - def convert_old_to_new(self): - """ - Use this function to convert old storage to new storage. - This function is for people who was using pickle storage - that was in version <=4.3.1. - """ - # old looks like: - # {1: {'state': 'start', 'data': {'name': 'John'}} - # we should update old version pickle to new. - # new looks like: - # {1: {2: {'state': 'start', 'data': {'name': 'John'}}}} - new_data = {} - for key, value in self.data.items(): - # this returns us id and dict with data and state - new_data[key] = {key: value} # convert this to new - # pass it to global data - self.data = new_data - self.update_data() # update data in file - - def create_dir(self): - """ - Create directory .save-handlers. - """ - dirs = self.file_path.rsplit('/', maxsplit=1)[0] - os.makedirs(dirs, exist_ok=True) - if not os.path.isfile(self.file_path): - with open(self.file_path,'wb') as file: - pickle.dump({}, file) - - def read(self): - file = open(self.file_path, 'rb') - data = pickle.load(file) - file.close() - return data - - def update_data(self): - file = open(self.file_path, 'wb+') - pickle.dump(self.data, file, protocol=pickle.HIGHEST_PROTOCOL) - file.close() - - def set_state(self, chat_id, user_id, state): - if isinstance(state, object): - state = state.name - if chat_id in self.data: - if user_id in self.data[chat_id]: - self.data[chat_id][user_id]['state'] = state - return True - else: - self.data[chat_id][user_id] = {'state': state, 'data': {}} - return True - self.data[chat_id] = {user_id: {'state': state, 'data': {}}} - self.update_data() - return True - - def delete_state(self, chat_id, user_id): - if self.data.get(chat_id): - if self.data[chat_id].get(user_id): - del self.data[chat_id][user_id] - if chat_id == user_id: - del self.data[chat_id] - self.update_data() - return True - - return False - - - def get_state(self, chat_id, user_id): - if self.data.get(chat_id): - if self.data[chat_id].get(user_id): - return self.data[chat_id][user_id]['state'] - - return None - def get_data(self, chat_id, user_id): - if self.data.get(chat_id): - if self.data[chat_id].get(user_id): - return self.data[chat_id][user_id]['data'] - - return None - - def reset_data(self, chat_id, user_id): - if self.data.get(chat_id): - if self.data[chat_id].get(user_id): - self.data[chat_id][user_id]['data'] = {} - self.update_data() - return True - return False - - def set_data(self, chat_id, user_id, key, value): - if self.data.get(chat_id): - if self.data[chat_id].get(user_id): - self.data[chat_id][user_id]['data'][key] = value - self.update_data() - return True - raise RuntimeError('chat_id {} and user_id {} does not exist'.format(chat_id, user_id)) - - def get_interactive_data(self, chat_id, user_id): - return StateContext(self, chat_id, user_id) - - def save(self, chat_id, user_id, data): - self.data[chat_id][user_id]['data'] = data - self.update_data() diff --git a/telebot/storage/redis_storage.py b/telebot/storage/redis_storage.py deleted file mode 100644 index ff21b6e..0000000 --- a/telebot/storage/redis_storage.py +++ /dev/null @@ -1,180 +0,0 @@ -from pyclbr import Class -from telebot.storage.base_storage import StateStorageBase, StateContext -import json - -redis_installed = True -try: - from redis import Redis, ConnectionPool - -except: - redis_installed = False - -class StateRedisStorage(StateStorageBase): - """ - This class is for Redis storage. - This will work only for states. - To use it, just pass this class to: - TeleBot(storage=StateRedisStorage()) - """ - def __init__(self, host='localhost', port=6379, db=0, password=None, prefix='telebot_'): - self.redis = ConnectionPool(host=host, port=port, db=db, password=password) - #self.con = Redis(connection_pool=self.redis) -> use this when necessary - # - # {chat_id: {user_id: {'state': None, 'data': {}}, ...}, ...} - self.prefix = prefix - if not redis_installed: - raise Exception("Redis is not installed. Install it via 'pip install redis'") - - def get_record(self, key): - """ - Function to get record from database. - It has nothing to do with states. - Made for backend compatibility - """ - connection = Redis(connection_pool=self.redis) - result = connection.get(self.prefix+str(key)) - connection.close() - if result: return json.loads(result) - return - - def set_record(self, key, value): - """ - Function to set record to database. - It has nothing to do with states. - Made for backend compatibility - """ - connection = Redis(connection_pool=self.redis) - connection.set(self.prefix+str(key), json.dumps(value)) - connection.close() - return True - - def delete_record(self, key): - """ - Function to delete record from database. - It has nothing to do with states. - Made for backend compatibility - """ - connection = Redis(connection_pool=self.redis) - connection.delete(self.prefix+str(key)) - connection.close() - return True - - def set_state(self, chat_id, user_id, state): - """ - Set state for a particular user in a chat. - """ - response = self.get_record(chat_id) - user_id = str(user_id) - if isinstance(state, object): - state = state.name - - if response: - if user_id in response: - response[user_id]['state'] = state - else: - response[user_id] = {'state': state, 'data': {}} - else: - response = {user_id: {'state': state, 'data': {}}} - self.set_record(chat_id, response) - - return True - - def delete_state(self, chat_id, user_id): - """ - Delete state for a particular user in a chat. - """ - response = self.get_record(chat_id) - user_id = str(user_id) - if response: - if user_id in response: - del response[user_id] - if user_id == str(chat_id): - self.delete_record(chat_id) - return True - else: self.set_record(chat_id, response) - return True - return False - - - def get_value(self, chat_id, user_id, key): - """ - Get value for a data of a user in a chat. - """ - response = self.get_record(chat_id) - user_id = str(user_id) - if response: - if user_id in response: - if key in response[user_id]['data']: - return response[user_id]['data'][key] - return None - - - def get_state(self, chat_id, user_id): - """ - Get state of a user in a chat. - """ - response = self.get_record(chat_id) - user_id = str(user_id) - if response: - if user_id in response: - return response[user_id]['state'] - - return None - - - def get_data(self, chat_id, user_id): - """ - Get data of particular user in a particular chat. - """ - response = self.get_record(chat_id) - user_id = str(user_id) - if response: - if user_id in response: - return response[user_id]['data'] - return None - - - def reset_data(self, chat_id, user_id): - """ - Reset data of a user in a chat. - """ - response = self.get_record(chat_id) - user_id = str(user_id) - if response: - if user_id in response: - response[user_id]['data'] = {} - self.set_record(chat_id, response) - return True - - - - - def set_data(self, chat_id, user_id, key, value): - """ - Set data without interactive data. - """ - response = self.get_record(chat_id) - user_id = str(user_id) - if response: - if user_id in response: - response[user_id]['data'][key] = value - self.set_record(chat_id, response) - return True - return False - - def get_interactive_data(self, chat_id, user_id): - """ - Get Data in interactive way. - You can use with() with this function. - """ - return StateContext(self, chat_id, user_id) - - def save(self, chat_id, user_id, data): - response = self.get_record(chat_id) - user_id = str(user_id) - if response: - if user_id in response: - response[user_id]['data'] = dict(data, **response[user_id]['data']) - self.set_record(chat_id, response) - return True - \ No newline at end of file diff --git a/telebot/types.py b/telebot/types.py deleted file mode 100644 index 081c0ed..0000000 --- a/telebot/types.py +++ /dev/null @@ -1,2888 +0,0 @@ -# -*- coding: utf-8 -*- - -import logging -from typing import Dict, List, Optional, Union -from abc import ABC - -try: - import ujson as json -except ImportError: - import json - -from telebot import util - -DISABLE_KEYLEN_ERROR = False - -logger = logging.getLogger('TeleBot') - - -class JsonSerializable(object): - """ - Subclasses of this class are guaranteed to be able to be converted to JSON format. - All subclasses of this class must override to_json. - """ - - def to_json(self): - """ - Returns a JSON string representation of this class. - - This function must be overridden by subclasses. - :return: a JSON formatted string. - """ - raise NotImplementedError - - -class Dictionaryable(object): - """ - Subclasses of this class are guaranteed to be able to be converted to dictionary. - All subclasses of this class must override to_dict. - """ - - def to_dict(self): - """ - Returns a DICT with class field values - - This function must be overridden by subclasses. - :return: a DICT - """ - raise NotImplementedError - - -class JsonDeserializable(object): - """ - Subclasses of this class are guaranteed to be able to be created from a json-style dict or json formatted string. - All subclasses of this class must override de_json. - """ - - @classmethod - def de_json(cls, json_string): - """ - Returns an instance of this class from the given json dict or string. - - This function must be overridden by subclasses. - :return: an instance of this class created from the given json dict or string. - """ - raise NotImplementedError - - @staticmethod - def check_json(json_type, dict_copy = True): - """ - Checks whether json_type is a dict or a string. If it is already a dict, it is returned as-is. - If it is not, it is converted to a dict by means of json.loads(json_type) - :param json_type: input json or parsed dict - :param dict_copy: if dict is passed and it is changed outside - should be True! - :return: Dictionary parsed from json or original dict - """ - if util.is_dict(json_type): - return json_type.copy() if dict_copy else json_type - elif util.is_string(json_type): - return json.loads(json_type) - else: - raise ValueError("json_type should be a json dict or string.") - - def __str__(self): - d = { - x: y.__dict__ if hasattr(y, '__dict__') else y - for x, y in self.__dict__.items() - } - return str(d) - - -class Update(JsonDeserializable): - @classmethod - def de_json(cls, json_string): - if json_string is None: return None - obj = cls.check_json(json_string, dict_copy=False) - update_id = obj['update_id'] - message = Message.de_json(obj.get('message')) - edited_message = Message.de_json(obj.get('edited_message')) - channel_post = Message.de_json(obj.get('channel_post')) - edited_channel_post = Message.de_json(obj.get('edited_channel_post')) - inline_query = InlineQuery.de_json(obj.get('inline_query')) - chosen_inline_result = ChosenInlineResult.de_json(obj.get('chosen_inline_result')) - callback_query = CallbackQuery.de_json(obj.get('callback_query')) - shipping_query = ShippingQuery.de_json(obj.get('shipping_query')) - pre_checkout_query = PreCheckoutQuery.de_json(obj.get('pre_checkout_query')) - poll = Poll.de_json(obj.get('poll')) - poll_answer = PollAnswer.de_json(obj.get('poll_answer')) - my_chat_member = ChatMemberUpdated.de_json(obj.get('my_chat_member')) - chat_member = ChatMemberUpdated.de_json(obj.get('chat_member')) - chat_join_request = ChatJoinRequest.de_json(obj.get('chat_join_request')) - return cls(update_id, message, edited_message, channel_post, edited_channel_post, inline_query, - chosen_inline_result, callback_query, shipping_query, pre_checkout_query, poll, poll_answer, - my_chat_member, chat_member, chat_join_request) - - def __init__(self, update_id, message, edited_message, channel_post, edited_channel_post, inline_query, - chosen_inline_result, callback_query, shipping_query, pre_checkout_query, poll, poll_answer, - my_chat_member, chat_member, chat_join_request): - self.update_id = update_id - self.message = message - self.edited_message = edited_message - self.channel_post = channel_post - self.edited_channel_post = edited_channel_post - self.inline_query = inline_query - self.chosen_inline_result = chosen_inline_result - self.callback_query = callback_query - self.shipping_query = shipping_query - self.pre_checkout_query = pre_checkout_query - self.poll = poll - self.poll_answer = poll_answer - self.my_chat_member = my_chat_member - self.chat_member = chat_member - self.chat_join_request = chat_join_request - - -class ChatMemberUpdated(JsonDeserializable): - @classmethod - def de_json(cls, json_string): - if json_string is None: return None - obj = cls.check_json(json_string) - obj['chat'] = Chat.de_json(obj['chat']) - obj['from_user'] = User.de_json(obj.pop('from')) - obj['old_chat_member'] = ChatMember.de_json(obj['old_chat_member']) - obj['new_chat_member'] = ChatMember.de_json(obj['new_chat_member']) - obj['invite_link'] = ChatInviteLink.de_json(obj.get('invite_link')) - return cls(**obj) - - def __init__(self, chat, from_user, date, old_chat_member, new_chat_member, invite_link=None, **kwargs): - self.chat: Chat = chat - self.from_user: User = from_user - self.date: int = date - self.old_chat_member: ChatMember = old_chat_member - self.new_chat_member: ChatMember = new_chat_member - self.invite_link: Optional[ChatInviteLink] = invite_link - - @property - def difference(self) -> Dict[str, List]: - """ - Get the difference between `old_chat_member` and `new_chat_member` - as a dict in the following format {'parameter': [old_value, new_value]} - E.g {'status': ['member', 'kicked'], 'until_date': [None, 1625055092]} - """ - old: Dict = self.old_chat_member.__dict__ - new: Dict = self.new_chat_member.__dict__ - dif = {} - for key in new: - if key == 'user': continue - if new[key] != old[key]: - dif[key] = [old[key], new[key]] - return dif - -class ChatJoinRequest(JsonDeserializable): - @classmethod - def de_json(cls, json_string): - if json_string is None: return None - obj = cls.check_json(json_string) - obj['chat'] = Chat.de_json(obj['chat']) - obj['from_user'] = User.de_json(obj['from']) - obj['invite_link'] = ChatInviteLink.de_json(obj.get('invite_link')) - return cls(**obj) - - def __init__(self, chat, from_user, date, bio=None, invite_link=None, **kwargs): - self.chat = chat - self.from_user = from_user - self.date = date - self.bio = bio - self.invite_link = invite_link - -class WebhookInfo(JsonDeserializable): - @classmethod - def de_json(cls, json_string): - if json_string is None: return None - obj = cls.check_json(json_string, dict_copy=False) - return cls(**obj) - - def __init__(self, url, has_custom_certificate, pending_update_count, ip_address=None, - last_error_date=None, last_error_message=None, max_connections=None, - allowed_updates=None, **kwargs): - self.url = url - self.has_custom_certificate = has_custom_certificate - self.pending_update_count = pending_update_count - self.ip_address = ip_address - self.last_error_date = last_error_date - self.last_error_message = last_error_message - self.max_connections = max_connections - self.allowed_updates = allowed_updates - - -class User(JsonDeserializable, Dictionaryable, JsonSerializable): - @classmethod - def de_json(cls, json_string): - if json_string is None: return None - obj = cls.check_json(json_string, dict_copy=False) - return cls(**obj) - - def __init__(self, id, is_bot, first_name, last_name=None, username=None, language_code=None, - can_join_groups=None, can_read_all_group_messages=None, supports_inline_queries=None, **kwargs): - self.id: int = id - self.is_bot: bool = is_bot - self.first_name: str = first_name - self.username: str = username - self.last_name: str = last_name - self.language_code: str = language_code - self.can_join_groups: bool = can_join_groups - self.can_read_all_group_messages: bool = can_read_all_group_messages - self.supports_inline_queries: bool = supports_inline_queries - - @property - def full_name(self): - full_name = self.first_name - if self.last_name: - full_name += ' {0}'.format(self.last_name) - return full_name - - def to_json(self): - return json.dumps(self.to_dict()) - - def to_dict(self): - return {'id': self.id, - 'is_bot': self.is_bot, - 'first_name': self.first_name, - 'last_name': self.last_name, - 'username': self.username, - 'language_code': self.language_code, - 'can_join_groups': self.can_join_groups, - 'can_read_all_group_messages': self.can_read_all_group_messages, - 'supports_inline_queries': self.supports_inline_queries} - - -class GroupChat(JsonDeserializable): - @classmethod - def de_json(cls, json_string): - if json_string is None: return None - obj = cls.check_json(json_string, dict_copy=False) - return cls(**obj) - - def __init__(self, id, title, **kwargs): - self.id: int = id - self.title: str = title - - -class Chat(JsonDeserializable): - @classmethod - def de_json(cls, json_string): - if json_string is None: return None - obj = cls.check_json(json_string) - if 'photo' in obj: - obj['photo'] = ChatPhoto.de_json(obj['photo']) - if 'pinned_message' in obj: - obj['pinned_message'] = Message.de_json(obj['pinned_message']) - if 'permissions' in obj: - obj['permissions'] = ChatPermissions.de_json(obj['permissions']) - if 'location' in obj: - obj['location'] = ChatLocation.de_json(obj['location']) - return cls(**obj) - - def __init__(self, id, type, title=None, username=None, first_name=None, - last_name=None, photo=None, bio=None, has_private_forwards=None, - description=None, invite_link=None, pinned_message=None, - permissions=None, slow_mode_delay=None, - message_auto_delete_time=None, has_protected_content=None, sticker_set_name=None, - can_set_sticker_set=None, linked_chat_id=None, location=None, **kwargs): - self.id: int = id - self.type: str = type - self.title: str = title - self.username: str = username - self.first_name: str = first_name - self.last_name: str = last_name - self.photo: ChatPhoto = photo - self.bio: str = bio - self.has_private_forwards: bool = has_private_forwards - self.description: str = description - self.invite_link: str = invite_link - self.pinned_message: Message = pinned_message - self.permissions: ChatPermissions = permissions - self.slow_mode_delay: int = slow_mode_delay - self.message_auto_delete_time: int = message_auto_delete_time - self.has_protected_content: bool = has_protected_content - self.sticker_set_name: str = sticker_set_name - self.can_set_sticker_set: bool = can_set_sticker_set - self.linked_chat_id: int = linked_chat_id - self.location: ChatLocation = location - - -class MessageID(JsonDeserializable): - @classmethod - def de_json(cls, json_string): - if json_string is None: return None - obj = cls.check_json(json_string, dict_copy=False) - return cls(**obj) - - def __init__(self, message_id, **kwargs): - self.message_id = message_id - - -class Message(JsonDeserializable): - @classmethod - def de_json(cls, json_string): - if json_string is None: return None - obj = cls.check_json(json_string, dict_copy=False) - message_id = obj['message_id'] - from_user = User.de_json(obj.get('from')) - date = obj['date'] - chat = Chat.de_json(obj['chat']) - content_type = None - opts = {} - if 'sender_chat' in obj: - opts['sender_chat'] = Chat.de_json(obj['sender_chat']) - if 'forward_from' in obj: - opts['forward_from'] = User.de_json(obj['forward_from']) - if 'forward_from_chat' in obj: - opts['forward_from_chat'] = Chat.de_json(obj['forward_from_chat']) - if 'forward_from_message_id' in obj: - opts['forward_from_message_id'] = obj.get('forward_from_message_id') - if 'forward_signature' in obj: - opts['forward_signature'] = obj.get('forward_signature') - if 'forward_sender_name' in obj: - opts['forward_sender_name'] = obj.get('forward_sender_name') - if 'forward_date' in obj: - opts['forward_date'] = obj.get('forward_date') - if 'is_automatic_forward' in obj: - opts['is_automatic_forward'] = obj.get('is_automatic_forward') - if 'reply_to_message' in obj: - opts['reply_to_message'] = Message.de_json(obj['reply_to_message']) - if 'via_bot' in obj: - opts['via_bot'] = User.de_json(obj['via_bot']) - if 'edit_date' in obj: - opts['edit_date'] = obj.get('edit_date') - if 'has_protected_content' in obj: - opts['has_protected_content'] = obj.get('has_protected_content') - if 'media_group_id' in obj: - opts['media_group_id'] = obj.get('media_group_id') - if 'author_signature' in obj: - opts['author_signature'] = obj.get('author_signature') - if 'text' in obj: - opts['text'] = obj['text'] - content_type = 'text' - if 'entities' in obj: - opts['entities'] = Message.parse_entities(obj['entities']) - if 'caption_entities' in obj: - opts['caption_entities'] = Message.parse_entities(obj['caption_entities']) - if 'audio' in obj: - opts['audio'] = Audio.de_json(obj['audio']) - content_type = 'audio' - if 'document' in obj: - opts['document'] = Document.de_json(obj['document']) - content_type = 'document' - if 'animation' in obj: - # Document content type accompanies "animation", - # so "animation" should be checked below "document" to override it - opts['animation'] = Animation.de_json(obj['animation']) - content_type = 'animation' - if 'game' in obj: - opts['game'] = Game.de_json(obj['game']) - content_type = 'game' - if 'photo' in obj: - opts['photo'] = Message.parse_photo(obj['photo']) - content_type = 'photo' - if 'sticker' in obj: - opts['sticker'] = Sticker.de_json(obj['sticker']) - content_type = 'sticker' - if 'video' in obj: - opts['video'] = Video.de_json(obj['video']) - content_type = 'video' - if 'video_note' in obj: - opts['video_note'] = VideoNote.de_json(obj['video_note']) - content_type = 'video_note' - if 'voice' in obj: - opts['voice'] = Audio.de_json(obj['voice']) - content_type = 'voice' - if 'caption' in obj: - opts['caption'] = obj['caption'] - if 'contact' in obj: - opts['contact'] = Contact.de_json(json.dumps(obj['contact'])) - content_type = 'contact' - if 'location' in obj: - opts['location'] = Location.de_json(obj['location']) - content_type = 'location' - if 'venue' in obj: - opts['venue'] = Venue.de_json(obj['venue']) - content_type = 'venue' - if 'dice' in obj: - opts['dice'] = Dice.de_json(obj['dice']) - content_type = 'dice' - if 'new_chat_members' in obj: - new_chat_members = [] - for member in obj['new_chat_members']: - new_chat_members.append(User.de_json(member)) - opts['new_chat_members'] = new_chat_members - content_type = 'new_chat_members' - if 'left_chat_member' in obj: - opts['left_chat_member'] = User.de_json(obj['left_chat_member']) - content_type = 'left_chat_member' - if 'new_chat_title' in obj: - opts['new_chat_title'] = obj['new_chat_title'] - content_type = 'new_chat_title' - if 'new_chat_photo' in obj: - opts['new_chat_photo'] = Message.parse_photo(obj['new_chat_photo']) - content_type = 'new_chat_photo' - if 'delete_chat_photo' in obj: - opts['delete_chat_photo'] = obj['delete_chat_photo'] - content_type = 'delete_chat_photo' - if 'group_chat_created' in obj: - opts['group_chat_created'] = obj['group_chat_created'] - content_type = 'group_chat_created' - if 'supergroup_chat_created' in obj: - opts['supergroup_chat_created'] = obj['supergroup_chat_created'] - content_type = 'supergroup_chat_created' - if 'channel_chat_created' in obj: - opts['channel_chat_created'] = obj['channel_chat_created'] - content_type = 'channel_chat_created' - if 'migrate_to_chat_id' in obj: - opts['migrate_to_chat_id'] = obj['migrate_to_chat_id'] - content_type = 'migrate_to_chat_id' - if 'migrate_from_chat_id' in obj: - opts['migrate_from_chat_id'] = obj['migrate_from_chat_id'] - content_type = 'migrate_from_chat_id' - if 'pinned_message' in obj: - opts['pinned_message'] = Message.de_json(obj['pinned_message']) - content_type = 'pinned_message' - if 'invoice' in obj: - opts['invoice'] = Invoice.de_json(obj['invoice']) - content_type = 'invoice' - if 'successful_payment' in obj: - opts['successful_payment'] = SuccessfulPayment.de_json(obj['successful_payment']) - content_type = 'successful_payment' - if 'connected_website' in obj: - opts['connected_website'] = obj['connected_website'] - content_type = 'connected_website' - if 'poll' in obj: - opts['poll'] = Poll.de_json(obj['poll']) - content_type = 'poll' - if 'passport_data' in obj: - opts['passport_data'] = obj['passport_data'] - content_type = 'passport_data' - if 'proximity_alert_triggered' in obj: - opts['proximity_alert_triggered'] = ProximityAlertTriggered.de_json(obj[ - 'proximity_alert_triggered']) - content_type = 'proximity_alert_triggered' - if 'voice_chat_scheduled' in obj: - opts['voice_chat_scheduled'] = VoiceChatScheduled.de_json(obj['voice_chat_scheduled']) - content_type = 'voice_chat_scheduled' - if 'voice_chat_started' in obj: - opts['voice_chat_started'] = VoiceChatStarted.de_json(obj['voice_chat_started']) - content_type = 'voice_chat_started' - if 'voice_chat_ended' in obj: - opts['voice_chat_ended'] = VoiceChatEnded.de_json(obj['voice_chat_ended']) - content_type = 'voice_chat_ended' - if 'voice_chat_participants_invited' in obj: - opts['voice_chat_participants_invited'] = VoiceChatParticipantsInvited.de_json(obj['voice_chat_participants_invited']) - content_type = 'voice_chat_participants_invited' - if 'message_auto_delete_timer_changed' in obj: - opts['message_auto_delete_timer_changed'] = MessageAutoDeleteTimerChanged.de_json(obj['message_auto_delete_timer_changed']) - content_type = 'message_auto_delete_timer_changed' - if 'reply_markup' in obj: - opts['reply_markup'] = InlineKeyboardMarkup.de_json(obj['reply_markup']) - return cls(message_id, from_user, date, chat, content_type, opts, json_string) - - @classmethod - def parse_chat(cls, chat): - if 'first_name' not in chat: - return GroupChat.de_json(chat) - else: - return User.de_json(chat) - - @classmethod - def parse_photo(cls, photo_size_array): - ret = [] - for ps in photo_size_array: - ret.append(PhotoSize.de_json(ps)) - return ret - - @classmethod - def parse_entities(cls, message_entity_array): - ret = [] - for me in message_entity_array: - ret.append(MessageEntity.de_json(me)) - return ret - - def __init__(self, message_id, from_user, date, chat, content_type, options, json_string): - self.content_type: str = content_type - self.id: int = message_id # Lets fix the telegram usability ####up with ID in Message :) - self.message_id: int = message_id - self.from_user: User = from_user - self.date: int = date - self.chat: Chat = chat - self.sender_chat: Optional[Chat] = None - self.forward_from: Optional[User] = None - self.forward_from_chat: Optional[Chat] = None - self.forward_from_message_id: Optional[int] = None - self.forward_signature: Optional[str] = None - self.forward_sender_name: Optional[str] = None - self.forward_date: Optional[int] = None - self.is_automatic_forward: Optional[bool] = None - self.reply_to_message: Optional[Message] = None - self.via_bot: Optional[User] = None - self.edit_date: Optional[int] = None - self.has_protected_content: Optional[bool] = None - self.media_group_id: Optional[str] = None - self.author_signature: Optional[str] = None - self.text: Optional[str] = None - self.entities: Optional[List[MessageEntity]] = None - self.caption_entities: Optional[List[MessageEntity]] = None - self.audio: Optional[Audio] = None - self.document: Optional[Document] = None - self.photo: Optional[List[PhotoSize]] = None - self.sticker: Optional[Sticker] = None - self.video: Optional[Video] = None - self.video_note: Optional[VideoNote] = None - self.voice: Optional[Voice] = None - self.caption: Optional[str] = None - self.contact: Optional[Contact] = None - self.location: Optional[Location] = None - self.venue: Optional[Venue] = None - self.animation: Optional[Animation] = None - self.dice: Optional[Dice] = None - self.new_chat_member: Optional[User] = None # Deprecated since Bot API 3.0. Not processed anymore - self.new_chat_members: Optional[List[User]] = None - self.left_chat_member: Optional[User] = None - self.new_chat_title: Optional[str] = None - self.new_chat_photo: Optional[List[PhotoSize]] = None - self.delete_chat_photo: Optional[bool] = None - self.group_chat_created: Optional[bool] = None - self.supergroup_chat_created: Optional[bool] = None - self.channel_chat_created: Optional[bool] = None - self.migrate_to_chat_id: Optional[int] = None - self.migrate_from_chat_id: Optional[int] = None - self.pinned_message: Optional[Message] = None - self.invoice: Optional[Invoice] = None - self.successful_payment: Optional[SuccessfulPayment] = None - self.connected_website: Optional[str] = None - self.reply_markup: Optional[InlineKeyboardMarkup] = None - for key in options: - setattr(self, key, options[key]) - self.json = json_string - - def __html_text(self, text, entities): - """ - Author: @sviat9440 - Updaters: @badiboy - Message: "*Test* parse _formatting_, [url](https://example.com), [text_mention](tg://user?id=123456) and mention @username" - - Example: - message.html_text - >> "Test parse formatting, url, text_mention and mention @username" - - Custom subs: - You can customize the substitutes. By default, there is no substitute for the entities: hashtag, bot_command, email. You can add or modify substitute an existing entity. - Example: - message.custom_subs = {"bold": "{text}", "italic": "{text}", "mention": "{text}"} - message.html_text - >> "Test parse formatting, url and text_mention and mention @username" - """ - - if not entities: - return text - - _subs = { - "bold": "{text}", - "italic": "{text}", - "pre": "
{text}
", - "code": "{text}", - # "url": "{text}", # @badiboy plain URLs have no text and do not need tags - "text_link": "{text}", - "strikethrough": "{text}", - "underline": "{text}", - "spoiler": "{text}", - } - - if hasattr(self, "custom_subs"): - for key, value in self.custom_subs.items(): - _subs[key] = value - utf16_text = text.encode("utf-16-le") - html_text = "" - - def func(upd_text, subst_type=None, url=None, user=None): - upd_text = upd_text.decode("utf-16-le") - if subst_type == "text_mention": - subst_type = "text_link" - url = "tg://user?id={0}".format(user.id) - elif subst_type == "mention": - url = "https://t.me/{0}".format(upd_text[1:]) - upd_text = upd_text.replace("&", "&").replace("<", "<").replace(">", ">") - if not subst_type or not _subs.get(subst_type): - return upd_text - subs = _subs.get(subst_type) - return subs.format(text=upd_text, url=url) - - offset = 0 - for entity in entities: - if entity.offset > offset: - html_text += func(utf16_text[offset * 2 : entity.offset * 2]) - offset = entity.offset - html_text += func(utf16_text[offset * 2 : (offset + entity.length) * 2], entity.type, entity.url, entity.user) - offset += entity.length - elif entity.offset == offset: - html_text += func(utf16_text[offset * 2 : (offset + entity.length) * 2], entity.type, entity.url, entity.user) - offset += entity.length - else: - # TODO: process nested entities from Bot API 4.5 - # Now ignoring them - pass - if offset * 2 < len(utf16_text): - html_text += func(utf16_text[offset * 2:]) - return html_text - - @property - def html_text(self): - return self.__html_text(self.text, self.entities) - - @property - def html_caption(self): - return self.__html_text(self.caption, self.caption_entities) - - -class MessageEntity(Dictionaryable, JsonSerializable, JsonDeserializable): - @staticmethod - def to_list_of_dicts(entity_list) -> Union[List[Dict], None]: - res = [] - for e in entity_list: - res.append(MessageEntity.to_dict(e)) - return res or None - - @classmethod - def de_json(cls, json_string): - if json_string is None: return None - obj = cls.check_json(json_string) - if 'user' in obj: - obj['user'] = User.de_json(obj['user']) - return cls(**obj) - - def __init__(self, type, offset, length, url=None, user=None, language=None, **kwargs): - self.type: str = type - self.offset: int = offset - self.length: int = length - self.url: str = url - self.user: User = user - self.language: str = language - - def to_json(self): - return json.dumps(self.to_dict()) - - def to_dict(self): - return {"type": self.type, - "offset": self.offset, - "length": self.length, - "url": self.url, - "user": self.user, - "language": self.language} - - -class Dice(JsonSerializable, Dictionaryable, JsonDeserializable): - @classmethod - def de_json(cls, json_string): - if json_string is None: return None - obj = cls.check_json(json_string, dict_copy=False) - return cls(**obj) - - def __init__(self, value, emoji, **kwargs): - self.value: int = value - self.emoji: str = emoji - - def to_json(self): - return json.dumps(self.to_dict()) - - def to_dict(self): - return {'value': self.value, - 'emoji': self.emoji} - - -class PhotoSize(JsonDeserializable): - @classmethod - def de_json(cls, json_string): - if json_string is None: return None - obj = cls.check_json(json_string, dict_copy=False) - return cls(**obj) - - def __init__(self, file_id, file_unique_id, width, height, file_size=None, **kwargs): - self.file_id: str = file_id - self.file_unique_id: str = file_unique_id - self.width: int = width - self.height: int = height - self.file_size: int = file_size - - -class Audio(JsonDeserializable): - @classmethod - def de_json(cls, json_string): - if json_string is None: return None - obj = cls.check_json(json_string) - if 'thumb' in obj and 'file_id' in obj['thumb']: - obj['thumb'] = PhotoSize.de_json(obj['thumb']) - else: - obj['thumb'] = None - return cls(**obj) - - def __init__(self, file_id, file_unique_id, duration, performer=None, title=None, file_name=None, mime_type=None, - file_size=None, thumb=None, **kwargs): - self.file_id: str = file_id - self.file_unique_id: str = file_unique_id - self.duration: int = duration - self.performer: str = performer - self.title: str = title - self.file_name: str = file_name - self.mime_type: str = mime_type - self.file_size: int = file_size - self.thumb: PhotoSize = thumb - - -class Voice(JsonDeserializable): - @classmethod - def de_json(cls, json_string): - if json_string is None: return None - obj = cls.check_json(json_string, dict_copy=False) - return cls(**obj) - - def __init__(self, file_id, file_unique_id, duration, mime_type=None, file_size=None, **kwargs): - self.file_id: str = file_id - self.file_unique_id: str = file_unique_id - self.duration: int = duration - self.mime_type: str = mime_type - self.file_size: int = file_size - - -class Document(JsonDeserializable): - @classmethod - def de_json(cls, json_string): - if json_string is None: return None - obj = cls.check_json(json_string) - if 'thumb' in obj and 'file_id' in obj['thumb']: - obj['thumb'] = PhotoSize.de_json(obj['thumb']) - else: - obj['thumb'] = None - return cls(**obj) - - def __init__(self, file_id, file_unique_id, thumb=None, file_name=None, mime_type=None, file_size=None, **kwargs): - self.file_id: str = file_id - self.file_unique_id: str = file_unique_id - self.thumb: PhotoSize = thumb - self.file_name: str = file_name - self.mime_type: str = mime_type - self.file_size: int = file_size - - -class Video(JsonDeserializable): - @classmethod - def de_json(cls, json_string): - if json_string is None: return None - obj = cls.check_json(json_string) - if 'thumb' in obj and 'file_id' in obj['thumb']: - obj['thumb'] = PhotoSize.de_json(obj['thumb']) - return cls(**obj) - - def __init__(self, file_id, file_unique_id, width, height, duration, thumb=None, file_name=None, mime_type=None, file_size=None, **kwargs): - self.file_id: str = file_id - self.file_unique_id: str = file_unique_id - self.width: int = width - self.height: int = height - self.duration: int = duration - self.thumb: PhotoSize = thumb - self.file_name: str = file_name - self.mime_type: str = mime_type - self.file_size: int = file_size - - -class VideoNote(JsonDeserializable): - @classmethod - def de_json(cls, json_string): - if json_string is None: return None - obj = cls.check_json(json_string) - if 'thumb' in obj and 'file_id' in obj['thumb']: - obj['thumb'] = PhotoSize.de_json(obj['thumb']) - return cls(**obj) - - def __init__(self, file_id, file_unique_id, length, duration, thumb=None, file_size=None, **kwargs): - self.file_id: str = file_id - self.file_unique_id: str = file_unique_id - self.length: int = length - self.duration: int = duration - self.thumb: PhotoSize = thumb - self.file_size: int = file_size - - -class Contact(JsonDeserializable): - @classmethod - def de_json(cls, json_string): - if json_string is None: return None - obj = cls.check_json(json_string, dict_copy=False) - return cls(**obj) - - def __init__(self, phone_number, first_name, last_name=None, user_id=None, vcard=None, **kwargs): - self.phone_number: str = phone_number - self.first_name: str = first_name - self.last_name: str = last_name - self.user_id: int = user_id - self.vcard: str = vcard - - -class Location(JsonDeserializable, JsonSerializable, Dictionaryable): - @classmethod - def de_json(cls, json_string): - if json_string is None: return None - obj = cls.check_json(json_string, dict_copy=False) - return cls(**obj) - - def __init__(self, longitude, latitude, horizontal_accuracy=None, - live_period=None, heading=None, proximity_alert_radius=None, **kwargs): - self.longitude: float = longitude - self.latitude: float = latitude - self.horizontal_accuracy: float = horizontal_accuracy - self.live_period: int = live_period - self.heading: int = heading - self.proximity_alert_radius: int = proximity_alert_radius - - def to_json(self): - return json.dumps(self.to_dict()) - - def to_dict(self): - return { - "longitude": self.longitude, - "latitude": self.latitude, - "horizontal_accuracy": self.horizontal_accuracy, - "live_period": self.live_period, - "heading": self.heading, - "proximity_alert_radius": self.proximity_alert_radius, - } - - -class Venue(JsonDeserializable): - @classmethod - def de_json(cls, json_string): - if json_string is None: return None - obj = cls.check_json(json_string) - obj['location'] = Location.de_json(obj['location']) - return cls(**obj) - - def __init__(self, location, title, address, foursquare_id=None, foursquare_type=None, - google_place_id=None, google_place_type=None, **kwargs): - self.location: Location = location - self.title: str = title - self.address: str = address - self.foursquare_id: str = foursquare_id - self.foursquare_type: str = foursquare_type - self.google_place_id: str = google_place_id - self.google_place_type: str = google_place_type - - -class UserProfilePhotos(JsonDeserializable): - @classmethod - def de_json(cls, json_string): - if json_string is None: return None - obj = cls.check_json(json_string) - if 'photos' in obj: - photos = [[PhotoSize.de_json(y) for y in x] for x in obj['photos']] - obj['photos'] = photos - return cls(**obj) - - def __init__(self, total_count, photos=None, **kwargs): - self.total_count: int = total_count - self.photos: List[PhotoSize] = photos - - -class File(JsonDeserializable): - @classmethod - def de_json(cls, json_string): - if json_string is None: return None - obj = cls.check_json(json_string, dict_copy=False) - return cls(**obj) - - def __init__(self, file_id, file_unique_id, file_size, file_path, **kwargs): - self.file_id: str = file_id - self.file_unique_id: str = file_unique_id - self.file_size: int = file_size - self.file_path: str = file_path - - -class ForceReply(JsonSerializable): - def __init__(self, selective: Optional[bool]=None, input_field_placeholder: Optional[str]=None): - self.selective: bool = selective - self.input_field_placeholder: str = input_field_placeholder - - def to_json(self): - json_dict = {'force_reply': True} - if self.selective is not None: - json_dict['selective'] = self.selective - if self.input_field_placeholder: - json_dict['input_field_placeholder'] = self.input_field_placeholder - return json.dumps(json_dict) - - -class ReplyKeyboardRemove(JsonSerializable): - def __init__(self, selective=None): - self.selective: bool = selective - - def to_json(self): - json_dict = {'remove_keyboard': True} - if self.selective: - json_dict['selective'] = self.selective - return json.dumps(json_dict) - - -class ReplyKeyboardMarkup(JsonSerializable): - max_row_keys = 12 - - def __init__(self, resize_keyboard: Optional[bool]=None, one_time_keyboard: Optional[bool]=None, - selective: Optional[bool]=None, row_width: int=3, input_field_placeholder: Optional[str]=None): - if row_width > self.max_row_keys: - # Todo: Will be replaced with Exception in future releases - if not DISABLE_KEYLEN_ERROR: - logger.error('Telegram does not support reply keyboard row width over %d.' % self.max_row_keys) - row_width = self.max_row_keys - - self.resize_keyboard: bool = resize_keyboard - self.one_time_keyboard: bool = one_time_keyboard - self.selective: bool = selective - self.row_width: int = row_width - self.input_field_placeholder: str = input_field_placeholder - self.keyboard: List[List[KeyboardButton]] = [] - - def add(self, *args, row_width=None): - """ - This function adds strings to the keyboard, while not exceeding row_width. - E.g. ReplyKeyboardMarkup#add("A", "B", "C") yields the json result {keyboard: [["A"], ["B"], ["C"]]} - when row_width is set to 1. - When row_width is set to 2, the following is the result of this function: {keyboard: [["A", "B"], ["C"]]} - See https://core.telegram.org/bots/api#replykeyboardmarkup - :param args: KeyboardButton to append to the keyboard - :param row_width: width of row - :return: self, to allow function chaining. - """ - if row_width is None: - row_width = self.row_width - - if row_width > self.max_row_keys: - # Todo: Will be replaced with Exception in future releases - if not DISABLE_KEYLEN_ERROR: - logger.error('Telegram does not support reply keyboard row width over %d.' % self.max_row_keys) - row_width = self.max_row_keys - - for row in util.chunks(args, row_width): - button_array = [] - for button in row: - if util.is_string(button): - button_array.append({'text': button}) - elif util.is_bytes(button): - button_array.append({'text': button.decode('utf-8')}) - else: - button_array.append(button.to_dict()) - self.keyboard.append(button_array) - - return self - - def row(self, *args): - """ - Adds a list of KeyboardButton to the keyboard. This function does not consider row_width. - ReplyKeyboardMarkup#row("A")#row("B", "C")#to_json() outputs '{keyboard: [["A"], ["B", "C"]]}' - See https://core.telegram.org/bots/api#replykeyboardmarkup - :param args: strings - :return: self, to allow function chaining. - """ - - return self.add(*args, row_width=self.max_row_keys) - - def to_json(self): - """ - Converts this object to its json representation following the Telegram API guidelines described here: - https://core.telegram.org/bots/api#replykeyboardmarkup - :return: - """ - json_dict = {'keyboard': self.keyboard} - if self.one_time_keyboard is not None: - json_dict['one_time_keyboard'] = self.one_time_keyboard - if self.resize_keyboard is not None: - json_dict['resize_keyboard'] = self.resize_keyboard - if self.selective is not None: - json_dict['selective'] = self.selective - if self.input_field_placeholder: - json_dict['input_field_placeholder'] = self.input_field_placeholder - return json.dumps(json_dict) - - -class KeyboardButtonPollType(Dictionaryable): - def __init__(self, type=''): - self.type: str = type - - def to_dict(self): - return {'type': self.type} - - -class KeyboardButton(Dictionaryable, JsonSerializable): - def __init__(self, text: str, request_contact: Optional[bool]=None, - request_location: Optional[bool]=None, request_poll: Optional[KeyboardButtonPollType]=None): - self.text: str = text - self.request_contact: bool = request_contact - self.request_location: bool = request_location - self.request_poll: KeyboardButtonPollType = request_poll - - def to_json(self): - return json.dumps(self.to_dict()) - - def to_dict(self): - json_dict = {'text': self.text} - if self.request_contact is not None: - json_dict['request_contact'] = self.request_contact - if self.request_location is not None: - json_dict['request_location'] = self.request_location - if self.request_poll is not None: - json_dict['request_poll'] = self.request_poll.to_dict() - return json_dict - - -class InlineKeyboardMarkup(Dictionaryable, JsonSerializable, JsonDeserializable): - max_row_keys = 8 - - @classmethod - def de_json(cls, json_string): - if json_string is None: return None - obj = cls.check_json(json_string, dict_copy=False) - keyboard = [[InlineKeyboardButton.de_json(button) for button in row] for row in obj['inline_keyboard']] - return cls(keyboard = keyboard) - - def __init__(self, keyboard=None, row_width=3): - """ - This object represents an inline keyboard that appears - right next to the message it belongs to. - - :return: None - """ - if row_width > self.max_row_keys: - # Todo: Will be replaced with Exception in future releases - logger.error('Telegram does not support inline keyboard row width over %d.' % self.max_row_keys) - row_width = self.max_row_keys - - self.row_width: int = row_width - self.keyboard: List[List[InlineKeyboardButton]] = keyboard or [] - - def add(self, *args, row_width=None): - """ - This method adds buttons to the keyboard without exceeding row_width. - - E.g. InlineKeyboardMarkup.add("A", "B", "C") yields the json result: - {keyboard: [["A"], ["B"], ["C"]]} - when row_width is set to 1. - When row_width is set to 2, the result: - {keyboard: [["A", "B"], ["C"]]} - See https://core.telegram.org/bots/api#inlinekeyboardmarkup - - :param args: Array of InlineKeyboardButton to append to the keyboard - :param row_width: width of row - :return: self, to allow function chaining. - """ - if row_width is None: - row_width = self.row_width - - if row_width > self.max_row_keys: - # Todo: Will be replaced with Exception in future releases - logger.error('Telegram does not support inline keyboard row width over %d.' % self.max_row_keys) - row_width = self.max_row_keys - - for row in util.chunks(args, row_width): - button_array = [button for button in row] - self.keyboard.append(button_array) - - return self - - def row(self, *args): - """ - Adds a list of InlineKeyboardButton to the keyboard. - This method does not consider row_width. - - InlineKeyboardMarkup.row("A").row("B", "C").to_json() outputs: - '{keyboard: [["A"], ["B", "C"]]}' - See https://core.telegram.org/bots/api#inlinekeyboardmarkup - - :param args: Array of InlineKeyboardButton to append to the keyboard - :return: self, to allow function chaining. - """ - - return self.add(*args, row_width=self.max_row_keys) - - def to_json(self): - """ - Converts this object to its json representation - following the Telegram API guidelines described here: - https://core.telegram.org/bots/api#inlinekeyboardmarkup - :return: - """ - return json.dumps(self.to_dict()) - - def to_dict(self): - json_dict = dict() - json_dict['inline_keyboard'] = [[button.to_dict() for button in row] for row in self.keyboard] - return json_dict - - -class InlineKeyboardButton(Dictionaryable, JsonSerializable, JsonDeserializable): - @classmethod - def de_json(cls, json_string): - if json_string is None: return None - obj = cls.check_json(json_string) - if 'login_url' in obj: - obj['login_url'] = LoginUrl.de_json(obj.get('login_url')) - return cls(**obj) - - def __init__(self, text, url=None, callback_data=None, switch_inline_query=None, - switch_inline_query_current_chat=None, callback_game=None, pay=None, login_url=None, **kwargs): - self.text: str = text - self.url: str = url - self.callback_data: str = callback_data - self.switch_inline_query: str = switch_inline_query - self.switch_inline_query_current_chat: str = switch_inline_query_current_chat - self.callback_game = callback_game # Not Implemented - self.pay: bool = pay - self.login_url: LoginUrl = login_url - - def to_json(self): - return json.dumps(self.to_dict()) - - def to_dict(self): - json_dict = {'text': self.text} - if self.url: - json_dict['url'] = self.url - if self.callback_data: - json_dict['callback_data'] = self.callback_data - if self.switch_inline_query is not None: - json_dict['switch_inline_query'] = self.switch_inline_query - if self.switch_inline_query_current_chat is not None: - json_dict['switch_inline_query_current_chat'] = self.switch_inline_query_current_chat - if self.callback_game is not None: - json_dict['callback_game'] = self.callback_game - if self.pay is not None: - json_dict['pay'] = self.pay - if self.login_url is not None: - json_dict['login_url'] = self.login_url.to_dict() - return json_dict - - -class LoginUrl(Dictionaryable, JsonSerializable, JsonDeserializable): - @classmethod - def de_json(cls, json_string): - if json_string is None: return None - obj = cls.check_json(json_string, dict_copy=False) - return cls(**obj) - - def __init__(self, url, forward_text=None, bot_username=None, request_write_access=None, **kwargs): - self.url: str = url - self.forward_text: str = forward_text - self.bot_username: str = bot_username - self.request_write_access: bool = request_write_access - - def to_json(self): - return json.dumps(self.to_dict()) - - def to_dict(self): - json_dict = {'url': self.url} - if self.forward_text: - json_dict['forward_text'] = self.forward_text - if self.bot_username: - json_dict['bot_username'] = self.bot_username - if self.request_write_access is not None: - json_dict['request_write_access'] = self.request_write_access - return json_dict - - -class CallbackQuery(JsonDeserializable): - @classmethod - def de_json(cls, json_string): - if json_string is None: return None - obj = cls.check_json(json_string) - if not "data" in obj: - # "data" field is Optional in the API, but historically is mandatory in the class constructor - obj['data'] = None - obj['from_user'] = User.de_json(obj.pop('from')) - if 'message' in obj: - obj['message'] = Message.de_json(obj.get('message')) - return cls(**obj) - - def __init__(self, id, from_user, data, chat_instance, message=None, inline_message_id=None, game_short_name=None, **kwargs): - self.id: int = id - self.from_user: User = from_user - self.message: Message = message - self.inline_message_id: str = inline_message_id - self.chat_instance: str = chat_instance - self.data: str = data - self.game_short_name: str = game_short_name - - -class ChatPhoto(JsonDeserializable): - @classmethod - def de_json(cls, json_string): - if json_string is None: return None - obj = cls.check_json(json_string, dict_copy=False) - return cls(**obj) - - def __init__(self, small_file_id, small_file_unique_id, big_file_id, big_file_unique_id, **kwargs): - self.small_file_id: str = small_file_id - self.small_file_unique_id: str = small_file_unique_id - self.big_file_id: str = big_file_id - self.big_file_unique_id: str = big_file_unique_id - - -class ChatMember(JsonDeserializable): - @classmethod - def de_json(cls, json_string): - if json_string is None: return None - obj = cls.check_json(json_string) - obj['user'] = User.de_json(obj['user']) - return cls(**obj) - - def __init__(self, user, status, custom_title=None, is_anonymous=None, can_be_edited=None, - can_post_messages=None, can_edit_messages=None, can_delete_messages=None, - can_restrict_members=None, can_promote_members=None, can_change_info=None, - can_invite_users=None, can_pin_messages=None, is_member=None, - can_send_messages=None, can_send_media_messages=None, can_send_polls=None, - can_send_other_messages=None, can_add_web_page_previews=None, - can_manage_chat=None, can_manage_voice_chats=None, - until_date=None, **kwargs): - self.user: User = user - self.status: str = status - self.custom_title: str = custom_title - self.is_anonymous: bool = is_anonymous - self.can_be_edited: bool = can_be_edited - self.can_post_messages: bool = can_post_messages - self.can_edit_messages: bool = can_edit_messages - self.can_delete_messages: bool = can_delete_messages - self.can_restrict_members: bool = can_restrict_members - self.can_promote_members: bool = can_promote_members - self.can_change_info: bool = can_change_info - self.can_invite_users: bool = can_invite_users - self.can_pin_messages: bool = can_pin_messages - self.is_member: bool = is_member - self.can_send_messages: bool = can_send_messages - self.can_send_media_messages: bool = can_send_media_messages - self.can_send_polls: bool = can_send_polls - self.can_send_other_messages: bool = can_send_other_messages - self.can_add_web_page_previews: bool = can_add_web_page_previews - self.can_manage_chat: bool = can_manage_chat - self.can_manage_voice_chats: bool = can_manage_voice_chats - self.until_date: int = until_date - - -class ChatMemberOwner(ChatMember): - pass - -class ChatMemberAdministrator(ChatMember): - pass - - -class ChatMemberMember(ChatMember): - pass - - -class ChatMemberRestricted(ChatMember): - pass - - -class ChatMemberLeft(ChatMember): - pass - - -class ChatMemberBanned(ChatMember): - pass - - -class ChatPermissions(JsonDeserializable, JsonSerializable, Dictionaryable): - @classmethod - def de_json(cls, json_string): - if json_string is None: return json_string - obj = cls.check_json(json_string, dict_copy=False) - return cls(**obj) - - def __init__(self, can_send_messages=None, can_send_media_messages=None, - can_send_polls=None, can_send_other_messages=None, - can_add_web_page_previews=None, can_change_info=None, - can_invite_users=None, can_pin_messages=None, **kwargs): - self.can_send_messages: bool = can_send_messages - self.can_send_media_messages: bool = can_send_media_messages - self.can_send_polls: bool = can_send_polls - self.can_send_other_messages: bool = can_send_other_messages - self.can_add_web_page_previews: bool = can_add_web_page_previews - self.can_change_info: bool = can_change_info - self.can_invite_users: bool = can_invite_users - self.can_pin_messages: bool = can_pin_messages - - def to_json(self): - return json.dumps(self.to_dict()) - - def to_dict(self): - json_dict = dict() - if self.can_send_messages is not None: - json_dict['can_send_messages'] = self.can_send_messages - if self.can_send_media_messages is not None: - json_dict['can_send_media_messages'] = self.can_send_media_messages - if self.can_send_polls is not None: - json_dict['can_send_polls'] = self.can_send_polls - if self.can_send_other_messages is not None: - json_dict['can_send_other_messages'] = self.can_send_other_messages - if self.can_add_web_page_previews is not None: - json_dict['can_add_web_page_previews'] = self.can_add_web_page_previews - if self.can_change_info is not None: - json_dict['can_change_info'] = self.can_change_info - if self.can_invite_users is not None: - json_dict['can_invite_users'] = self.can_invite_users - if self.can_pin_messages is not None: - json_dict['can_pin_messages'] = self.can_pin_messages - return json_dict - - -class BotCommand(JsonSerializable, JsonDeserializable): - @classmethod - def de_json(cls, json_string): - if json_string is None: return None - obj = cls.check_json(json_string, dict_copy=False) - return cls(**obj) - - def __init__(self, command, description): - """ - This object represents a bot command. - :param command: Text of the command, 1-32 characters. - Can contain only lowercase English letters, digits and underscores. - :param description: Description of the command, 3-256 characters. - :return: - """ - self.command: str = command - self.description: str = description - - def to_json(self): - return json.dumps(self.to_dict()) - - def to_dict(self): - return {'command': self.command, 'description': self.description} - - -# BotCommandScopes - -class BotCommandScope(ABC, JsonSerializable): - def __init__(self, type='default', chat_id=None, user_id=None): - """ - Abstract class. - Use BotCommandScopeX classes to set a specific scope type: - BotCommandScopeDefault - BotCommandScopeAllPrivateChats - BotCommandScopeAllGroupChats - BotCommandScopeAllChatAdministrators - BotCommandScopeChat - BotCommandScopeChatAdministrators - BotCommandScopeChatMember - """ - self.type: str = type - self.chat_id: Optional[Union[int, str]] = chat_id - self.user_id: Optional[Union[int, str]] = user_id - - def to_json(self): - json_dict = {'type': self.type} - if self.chat_id: - json_dict['chat_id'] = self.chat_id - if self.user_id: - json_dict['user_id'] = self.user_id - return json.dumps(json_dict) - - -class BotCommandScopeDefault(BotCommandScope): - def __init__(self): - """ - Represents the default scope of bot commands. - Default commands are used if no commands with a narrower scope are specified for the user. - """ - super(BotCommandScopeDefault, self).__init__(type='default') - - -class BotCommandScopeAllPrivateChats(BotCommandScope): - def __init__(self): - """ - Represents the scope of bot commands, covering all private chats. - """ - super(BotCommandScopeAllPrivateChats, self).__init__(type='all_private_chats') - - -class BotCommandScopeAllGroupChats(BotCommandScope): - def __init__(self): - """ - Represents the scope of bot commands, covering all group and supergroup chats. - """ - super(BotCommandScopeAllGroupChats, self).__init__(type='all_group_chats') - - -class BotCommandScopeAllChatAdministrators(BotCommandScope): - def __init__(self): - """ - Represents the scope of bot commands, covering all group and supergroup chat administrators. - """ - super(BotCommandScopeAllChatAdministrators, self).__init__(type='all_chat_administrators') - - -class BotCommandScopeChat(BotCommandScope): - def __init__(self, chat_id=None): - super(BotCommandScopeChat, self).__init__(type='chat', chat_id=chat_id) - - -class BotCommandScopeChatAdministrators(BotCommandScope): - def __init__(self, chat_id=None): - """ - Represents the scope of bot commands, covering a specific chat. - @param chat_id: Unique identifier for the target chat - """ - super(BotCommandScopeChatAdministrators, self).__init__(type='chat_administrators', chat_id=chat_id) - - -class BotCommandScopeChatMember(BotCommandScope): - def __init__(self, chat_id=None, user_id=None): - """ - Represents the scope of bot commands, covering all administrators of a specific group or supergroup chat - @param chat_id: Unique identifier for the target chat - @param user_id: Unique identifier of the target user - """ - super(BotCommandScopeChatMember, self).__init__(type='chat_member', chat_id=chat_id, user_id=user_id) - - -# InlineQuery - -class InlineQuery(JsonDeserializable): - @classmethod - def de_json(cls, json_string): - if json_string is None: return None - obj = cls.check_json(json_string) - obj['from_user'] = User.de_json(obj.pop('from')) - if 'location' in obj: - obj['location'] = Location.de_json(obj['location']) - return cls(**obj) - - def __init__(self, id, from_user, query, offset, chat_type=None, location=None, **kwargs): - """ - This object represents an incoming inline query. - When the user sends an empty query, your bot could - return some default or trending results. - :param id: string Unique identifier for this query - :param from_user: User Sender - :param query: String Text of the query - :param chat_type: String Type of the chat, from which the inline query was sent. - Can be either “sender” for a private chat with the inline query sender, - “private”, “group”, “supergroup”, or “channel”. - :param offset: String Offset of the results to be returned, can be controlled by the bot - :param location: Sender location, only for bots that request user location - :return: InlineQuery Object - """ - self.id: int = id - self.from_user: User = from_user - self.query: str = query - self.offset: str = offset - self.chat_type: str = chat_type - self.location: Location = location - - -class InputTextMessageContent(Dictionaryable): - def __init__(self, message_text, parse_mode=None, entities=None, disable_web_page_preview=None): - self.message_text: str = message_text - self.parse_mode: str = parse_mode - self.entities: List[MessageEntity] = entities - self.disable_web_page_preview: bool = disable_web_page_preview - - def to_dict(self): - json_dict = {'message_text': self.message_text} - if self.parse_mode: - json_dict['parse_mode'] = self.parse_mode - if self.entities: - json_dict['entities'] = MessageEntity.to_list_of_dicts(self.entities) - if self.disable_web_page_preview is not None: - json_dict['disable_web_page_preview'] = self.disable_web_page_preview - return json_dict - - -class InputLocationMessageContent(Dictionaryable): - def __init__(self, latitude, longitude, horizontal_accuracy=None, live_period=None, heading=None, proximity_alert_radius=None): - self.latitude: float = latitude - self.longitude: float = longitude - self.horizontal_accuracy: float = horizontal_accuracy - self.live_period: int = live_period - self.heading: int = heading - self.proximity_alert_radius: int = proximity_alert_radius - - def to_dict(self): - json_dict = {'latitude': self.latitude, 'longitude': self.longitude} - if self.horizontal_accuracy: - json_dict['horizontal_accuracy'] = self.horizontal_accuracy - if self.live_period: - json_dict['live_period'] = self.live_period - if self.heading: - json_dict['heading'] = self.heading - if self.proximity_alert_radius: - json_dict['proximity_alert_radius'] = self.proximity_alert_radius - return json_dict - - -class InputVenueMessageContent(Dictionaryable): - def __init__(self, latitude, longitude, title, address, foursquare_id=None, foursquare_type=None, - google_place_id=None, google_place_type=None): - self.latitude: float = latitude - self.longitude: float = longitude - self.title: str = title - self.address: str = address - self.foursquare_id: str = foursquare_id - self.foursquare_type: str = foursquare_type - self.google_place_id: str = google_place_id - self.google_place_type: str = google_place_type - - def to_dict(self): - json_dict = { - 'latitude': self.latitude, - 'longitude': self.longitude, - 'title': self.title, - 'address' : self.address - } - if self.foursquare_id: - json_dict['foursquare_id'] = self.foursquare_id - if self.foursquare_type: - json_dict['foursquare_type'] = self.foursquare_type - if self.google_place_id: - json_dict['google_place_id'] = self.google_place_id - if self.google_place_type: - json_dict['google_place_type'] = self.google_place_type - return json_dict - - -class InputContactMessageContent(Dictionaryable): - def __init__(self, phone_number, first_name, last_name=None, vcard=None): - self.phone_number: str = phone_number - self.first_name: str = first_name - self.last_name: str = last_name - self.vcard: str = vcard - - def to_dict(self): - json_dict = {'phone_number': self.phone_number, 'first_name': self.first_name} - if self.last_name: - json_dict['last_name'] = self.last_name - if self.vcard: - json_dict['vcard'] = self.vcard - return json_dict - - -class InputInvoiceMessageContent(Dictionaryable): - def __init__(self, title, description, payload, provider_token, currency, prices, - max_tip_amount=None, suggested_tip_amounts=None, provider_data=None, - photo_url=None, photo_size=None, photo_width=None, photo_height=None, - need_name=None, need_phone_number=None, need_email=None, need_shipping_address=None, - send_phone_number_to_provider=None, send_email_to_provider=None, - is_flexible=None): - self.title: str = title - self.description: str = description - self.payload: str = payload - self.provider_token: str = provider_token - self.currency: str = currency - self.prices: List[LabeledPrice] = prices - self.max_tip_amount: Optional[int] = max_tip_amount - self.suggested_tip_amounts: Optional[List[int]] = suggested_tip_amounts - self.provider_data: Optional[str] = provider_data - self.photo_url: Optional[str] = photo_url - self.photo_size: Optional[int] = photo_size - self.photo_width: Optional[int] = photo_width - self.photo_height: Optional[int] = photo_height - self.need_name: Optional[bool] = need_name - self.need_phone_number: Optional[bool] = need_phone_number - self.need_email: Optional[bool] = need_email - self.need_shipping_address: Optional[bool] = need_shipping_address - self.send_phone_number_to_provider: Optional[bool] = send_phone_number_to_provider - self.send_email_to_provider: Optional[bool] = send_email_to_provider - self.is_flexible: Optional[bool] = is_flexible - - def to_dict(self): - json_dict = { - 'title': self.title, - 'description': self.description, - 'payload': self.payload, - 'provider_token': self.provider_token, - 'currency': self.currency, - 'prices': [LabeledPrice.to_dict(lp) for lp in self.prices] - } - if self.max_tip_amount: - json_dict['max_tip_amount'] = self.max_tip_amount - if self.suggested_tip_amounts: - json_dict['suggested_tip_amounts'] = self.suggested_tip_amounts - if self.provider_data: - json_dict['provider_data'] = self.provider_data - if self.photo_url: - json_dict['photo_url'] = self.photo_url - if self.photo_size: - json_dict['photo_size'] = self.photo_size - if self.photo_width: - json_dict['photo_width'] = self.photo_width - if self.photo_height: - json_dict['photo_height'] = self.photo_height - if self.need_name is not None: - json_dict['need_name'] = self.need_name - if self.need_phone_number is not None: - json_dict['need_phone_number'] = self.need_phone_number - if self.need_email is not None: - json_dict['need_email'] = self.need_email - if self.need_shipping_address is not None: - json_dict['need_shipping_address'] = self.need_shipping_address - if self.send_phone_number_to_provider is not None: - json_dict['send_phone_number_to_provider'] = self.send_phone_number_to_provider - if self.send_email_to_provider is not None: - json_dict['send_email_to_provider'] = self.send_email_to_provider - if self.is_flexible is not None: - json_dict['is_flexible'] = self.is_flexible - return json_dict - - -class ChosenInlineResult(JsonDeserializable): - @classmethod - def de_json(cls, json_string): - if json_string is None: return None - obj = cls.check_json(json_string) - obj['from_user'] = User.de_json(obj.pop('from')) - if 'location' in obj: - obj['location'] = Location.de_json(obj['location']) - return cls(**obj) - - def __init__(self, result_id, from_user, query, location=None, inline_message_id=None, **kwargs): - """ - This object represents a result of an inline query - that was chosen by the user and sent to their chat partner. - :param result_id: string The unique identifier for the result that was chosen. - :param from_user: User The user that chose the result. - :param query: String The query that was used to obtain the result. - :return: ChosenInlineResult Object. - """ - self.result_id: str = result_id - self.from_user: User = from_user - self.location: Location = location - self.inline_message_id: str = inline_message_id - self.query: str = query - - -class InlineQueryResultBase(ABC, Dictionaryable, JsonSerializable): - # noinspection PyShadowingBuiltins - def __init__(self, type, id, title = None, caption = None, input_message_content = None, - reply_markup = None, caption_entities = None, parse_mode = None): - self.type = type - self.id = id - self.title = title - self.caption = caption - self.input_message_content = input_message_content - self.reply_markup = reply_markup - self.caption_entities = caption_entities - self.parse_mode = parse_mode - - def to_json(self): - return json.dumps(self.to_dict()) - - def to_dict(self): - json_dict = { - 'type': self.type, - 'id': self.id - } - if self.title: - json_dict['title'] = self.title - if self.caption: - json_dict['caption'] = self.caption - if self.input_message_content: - json_dict['input_message_content'] = self.input_message_content.to_dict() - if self.reply_markup: - json_dict['reply_markup'] = self.reply_markup.to_dict() - if self.caption_entities: - json_dict['caption_entities'] = MessageEntity.to_list_of_dicts(self.caption_entities) - if self.parse_mode: - json_dict['parse_mode'] = self.parse_mode - return json_dict - - -class InlineQueryResultArticle(InlineQueryResultBase): - def __init__(self, id, title, input_message_content, reply_markup=None, - url=None, hide_url=None, description=None, thumb_url=None, thumb_width=None, thumb_height=None): - """ - Represents a link to an article or web page. - :param id: Unique identifier for this result, 1-64 Bytes. - :param title: Title of the result. - :param input_message_content: InputMessageContent : Content of the message to be sent - :param reply_markup: InlineKeyboardMarkup : Inline keyboard attached to the message - :param url: URL of the result. - :param hide_url: Pass True, if you don't want the URL to be shown in the message. - :param description: Short description of the result. - :param thumb_url: Url of the thumbnail for the result. - :param thumb_width: Thumbnail width. - :param thumb_height: Thumbnail height - :return: - """ - super().__init__('article', id, title = title, input_message_content = input_message_content, reply_markup = reply_markup) - self.url = url - self.hide_url = hide_url - self.description = description - self.thumb_url = thumb_url - self.thumb_width = thumb_width - self.thumb_height = thumb_height - - def to_dict(self): - json_dict = super().to_dict() - if self.url: - json_dict['url'] = self.url - if self.hide_url: - json_dict['hide_url'] = self.hide_url - if self.description: - json_dict['description'] = self.description - if self.thumb_url: - json_dict['thumb_url'] = self.thumb_url - if self.thumb_width: - json_dict['thumb_width'] = self.thumb_width - if self.thumb_height: - json_dict['thumb_height'] = self.thumb_height - return json_dict - - -class InlineQueryResultPhoto(InlineQueryResultBase): - def __init__(self, id, photo_url, thumb_url, photo_width=None, photo_height=None, title=None, - description=None, caption=None, caption_entities=None, parse_mode=None, reply_markup=None, input_message_content=None): - """ - Represents a link to a photo. - :param id: Unique identifier for this result, 1-64 bytes - :param photo_url: A valid URL of the photo. Photo must be in jpeg format. Photo size must not exceed 5MB - :param thumb_url: URL of the thumbnail for the photo - :param photo_width: Width of the photo. - :param photo_height: Height of the photo. - :param title: Title for the result. - :param description: Short description of the result. - :param caption: Caption of the photo to be sent, 0-200 characters. - :param parse_mode: Send Markdown or HTML, if you want Telegram apps to show bold, italic, fixed-width text or - inline URLs in the media caption. - :param reply_markup: InlineKeyboardMarkup : Inline keyboard attached to the message - :param input_message_content: InputMessageContent : Content of the message to be sent instead of the photo - :return: - """ - super().__init__('photo', id, title = title, caption = caption, - input_message_content = input_message_content, reply_markup = reply_markup, - parse_mode = parse_mode, caption_entities = caption_entities) - self.photo_url = photo_url - self.thumb_url = thumb_url - self.photo_width = photo_width - self.photo_height = photo_height - self.description = description - - def to_dict(self): - json_dict = super().to_dict() - json_dict['photo_url'] = self.photo_url - json_dict['thumb_url'] = self.thumb_url - if self.photo_width: - json_dict['photo_width'] = self.photo_width - if self.photo_height: - json_dict['photo_height'] = self.photo_height - if self.description: - json_dict['description'] = self.description - return json_dict - - -class InlineQueryResultGif(InlineQueryResultBase): - def __init__(self, id, gif_url, thumb_url, gif_width=None, gif_height=None, - title=None, caption=None, caption_entities=None, - reply_markup=None, input_message_content=None, gif_duration=None, parse_mode=None, - thumb_mime_type=None): - """ - Represents a link to an animated GIF file. - :param id: Unique identifier for this result, 1-64 bytes. - :param gif_url: A valid URL for the GIF file. File size must not exceed 1MB - :param thumb_url: URL of the static thumbnail (jpeg or gif) for the result. - :param gif_width: Width of the GIF. - :param gif_height: Height of the GIF. - :param title: Title for the result. - :param caption: Caption of the GIF file to be sent, 0-200 characters - :param reply_markup: InlineKeyboardMarkup : Inline keyboard attached to the message - :param input_message_content: InputMessageContent : Content of the message to be sent instead of the photo - :return: - """ - super().__init__('gif', id, title = title, caption = caption, - input_message_content = input_message_content, reply_markup = reply_markup, - parse_mode = parse_mode, caption_entities = caption_entities) - self.gif_url = gif_url - self.gif_width = gif_width - self.gif_height = gif_height - self.thumb_url = thumb_url - self.gif_duration = gif_duration - self.thumb_mime_type = thumb_mime_type - - def to_dict(self): - json_dict = super().to_dict() - json_dict['gif_url'] = self.gif_url - if self.gif_width: - json_dict['gif_width'] = self.gif_width - if self.gif_height: - json_dict['gif_height'] = self.gif_height - json_dict['thumb_url'] = self.thumb_url - if self.gif_duration: - json_dict['gif_duration'] = self.gif_duration - if self.thumb_mime_type: - json_dict['thumb_mime_type'] = self.thumb_mime_type - return json_dict - - -class InlineQueryResultMpeg4Gif(InlineQueryResultBase): - def __init__(self, id, mpeg4_url, thumb_url, mpeg4_width=None, mpeg4_height=None, - title=None, caption=None, caption_entities=None, - parse_mode=None, reply_markup=None, input_message_content=None, mpeg4_duration=None, - thumb_mime_type=None): - """ - Represents a link to a video animation (H.264/MPEG-4 AVC video without sound). - :param id: Unique identifier for this result, 1-64 bytes - :param mpeg4_url: A valid URL for the MP4 file. File size must not exceed 1MB - :param thumb_url: URL of the static thumbnail (jpeg or gif) for the result - :param mpeg4_width: Video width - :param mpeg4_height: Video height - :param title: Title for the result - :param caption: Caption of the MPEG-4 file to be sent, 0-200 characters - :param parse_mode: Send Markdown or HTML, if you want Telegram apps to show bold, italic, fixed-width text - or inline URLs in the media caption. - :param reply_markup: InlineKeyboardMarkup : Inline keyboard attached to the message - :param input_message_content: InputMessageContent : Content of the message to be sent instead of the photo - :return: - """ - super().__init__('mpeg4_gif', id, title = title, caption = caption, - input_message_content = input_message_content, reply_markup = reply_markup, - parse_mode = parse_mode, caption_entities = caption_entities) - self.mpeg4_url = mpeg4_url - self.mpeg4_width = mpeg4_width - self.mpeg4_height = mpeg4_height - self.thumb_url = thumb_url - self.mpeg4_duration = mpeg4_duration - self.thumb_mime_type = thumb_mime_type - - def to_dict(self): - json_dict = super().to_dict() - json_dict['mpeg4_url'] = self.mpeg4_url - if self.mpeg4_width: - json_dict['mpeg4_width'] = self.mpeg4_width - if self.mpeg4_height: - json_dict['mpeg4_height'] = self.mpeg4_height - json_dict['thumb_url'] = self.thumb_url - if self.mpeg4_duration: - json_dict['mpeg4_duration '] = self.mpeg4_duration - if self.thumb_mime_type: - json_dict['thumb_mime_type'] = self.thumb_mime_type - return json_dict - - -class InlineQueryResultVideo(InlineQueryResultBase): - def __init__(self, id, video_url, mime_type, thumb_url, - title, caption=None, caption_entities=None, parse_mode=None, - video_width=None, video_height=None, video_duration=None, - description=None, reply_markup=None, input_message_content=None): - """ - Represents link to a page containing an embedded video player or a video file. - :param id: Unique identifier for this result, 1-64 bytes - :param video_url: A valid URL for the embedded video player or video file - :param mime_type: Mime type of the content of video url, “text/html” or “video/mp4” - :param thumb_url: URL of the thumbnail (jpeg only) for the video - :param title: Title for the result - :param parse_mode: Send Markdown or HTML, if you want Telegram apps to show bold, italic, fixed-width text or - inline URLs in the media caption. - :param video_width: Video width - :param video_height: Video height - :param video_duration: Video duration in seconds - :param description: Short description of the result - :return: - """ - super().__init__('video', id, title = title, caption = caption, - input_message_content = input_message_content, reply_markup = reply_markup, - parse_mode = parse_mode, caption_entities = caption_entities) - self.video_url = video_url - self.mime_type = mime_type - self.thumb_url = thumb_url - self.video_width = video_width - self.video_height = video_height - self.video_duration = video_duration - self.description = description - - def to_dict(self): - json_dict = super().to_dict() - json_dict['video_url'] = self.video_url - json_dict['mime_type'] = self.mime_type - json_dict['thumb_url'] = self.thumb_url - if self.video_height: - json_dict['video_height'] = self.video_height - if self.video_duration: - json_dict['video_duration'] = self.video_duration - if self.description: - json_dict['description'] = self.description - return json_dict - - -class InlineQueryResultAudio(InlineQueryResultBase): - def __init__(self, id, audio_url, title, - caption=None, caption_entities=None, parse_mode=None, performer=None, - audio_duration=None, reply_markup=None, input_message_content=None): - super().__init__('audio', id, title = title, caption = caption, - input_message_content = input_message_content, reply_markup = reply_markup, - parse_mode = parse_mode, caption_entities = caption_entities) - self.audio_url = audio_url - self.performer = performer - self.audio_duration = audio_duration - - def to_dict(self): - json_dict = super().to_dict() - json_dict['audio_url'] = self.audio_url - if self.performer: - json_dict['performer'] = self.performer - if self.audio_duration: - json_dict['audio_duration'] = self.audio_duration - return json_dict - - -class InlineQueryResultVoice(InlineQueryResultBase): - def __init__(self, id, voice_url, title, caption=None, caption_entities=None, - parse_mode=None, voice_duration=None, reply_markup=None, input_message_content=None): - super().__init__('voice', id, title = title, caption = caption, - input_message_content = input_message_content, reply_markup = reply_markup, - parse_mode = parse_mode, caption_entities = caption_entities) - self.voice_url = voice_url - self.voice_duration = voice_duration - - def to_dict(self): - json_dict = super().to_dict() - json_dict['voice_url'] = self.voice_url - if self.voice_duration: - json_dict['voice_duration'] = self.voice_duration - return json_dict - - -class InlineQueryResultDocument(InlineQueryResultBase): - def __init__(self, id, title, document_url, mime_type, caption=None, caption_entities=None, - parse_mode=None, description=None, reply_markup=None, input_message_content=None, - thumb_url=None, thumb_width=None, thumb_height=None): - super().__init__('document', id, title = title, caption = caption, - input_message_content = input_message_content, reply_markup = reply_markup, - parse_mode = parse_mode, caption_entities = caption_entities) - self.document_url = document_url - self.mime_type = mime_type - self.description = description - self.thumb_url = thumb_url - self.thumb_width = thumb_width - self.thumb_height = thumb_height - - def to_dict(self): - json_dict = super().to_dict() - json_dict['document_url'] = self.document_url - json_dict['mime_type'] = self.mime_type - if self.description: - json_dict['description'] = self.description - if self.thumb_url: - json_dict['thumb_url'] = self.thumb_url - if self.thumb_width: - json_dict['thumb_width'] = self.thumb_width - if self.thumb_height: - json_dict['thumb_height'] = self.thumb_height - return json_dict - - -class InlineQueryResultLocation(InlineQueryResultBase): - def __init__(self, id, title, latitude, longitude, horizontal_accuracy, live_period=None, reply_markup=None, - input_message_content=None, thumb_url=None, thumb_width=None, thumb_height=None, heading=None, proximity_alert_radius = None): - super().__init__('location', id, title = title, - input_message_content = input_message_content, reply_markup = reply_markup) - self.latitude = latitude - self.longitude = longitude - self.horizontal_accuracy = horizontal_accuracy - self.live_period = live_period - self.heading: int = heading - self.proximity_alert_radius: int = proximity_alert_radius - self.thumb_url = thumb_url - self.thumb_width = thumb_width - self.thumb_height = thumb_height - - def to_dict(self): - json_dict = super().to_dict() - json_dict['latitude'] = self.latitude - json_dict['longitude'] = self.longitude - if self.horizontal_accuracy: - json_dict['horizontal_accuracy'] = self.horizontal_accuracy - if self.live_period: - json_dict['live_period'] = self.live_period - if self.heading: - json_dict['heading'] = self.heading - if self.proximity_alert_radius: - json_dict['proximity_alert_radius'] = self.proximity_alert_radius - if self.thumb_url: - json_dict['thumb_url'] = self.thumb_url - if self.thumb_width: - json_dict['thumb_width'] = self.thumb_width - if self.thumb_height: - json_dict['thumb_height'] = self.thumb_height - return json_dict - - -class InlineQueryResultVenue(InlineQueryResultBase): - def __init__(self, id, title, latitude, longitude, address, foursquare_id=None, foursquare_type=None, - reply_markup=None, input_message_content=None, thumb_url=None, - thumb_width=None, thumb_height=None, google_place_id=None, google_place_type=None): - super().__init__('venue', id, title = title, - input_message_content = input_message_content, reply_markup = reply_markup) - self.latitude = latitude - self.longitude = longitude - self.address = address - self.foursquare_id = foursquare_id - self.foursquare_type = foursquare_type - self.google_place_id = google_place_id - self.google_place_type = google_place_type - self.thumb_url = thumb_url - self.thumb_width = thumb_width - self.thumb_height = thumb_height - - def to_dict(self): - json_dict = super().to_dict() - json_dict['latitude'] = self.latitude - json_dict['longitude'] = self.longitude - json_dict['address'] = self.address - if self.foursquare_id: - json_dict['foursquare_id'] = self.foursquare_id - if self.foursquare_type: - json_dict['foursquare_type'] = self.foursquare_type - if self.google_place_id: - json_dict['google_place_id'] = self.google_place_id - if self.google_place_type: - json_dict['google_place_type'] = self.google_place_type - if self.thumb_url: - json_dict['thumb_url'] = self.thumb_url - if self.thumb_width: - json_dict['thumb_width'] = self.thumb_width - if self.thumb_height: - json_dict['thumb_height'] = self.thumb_height - return json_dict - - -class InlineQueryResultContact(InlineQueryResultBase): - def __init__(self, id, phone_number, first_name, last_name=None, vcard=None, - reply_markup=None, input_message_content=None, - thumb_url=None, thumb_width=None, thumb_height=None): - super().__init__('contact', id, - input_message_content = input_message_content, reply_markup = reply_markup) - self.phone_number = phone_number - self.first_name = first_name - self.last_name = last_name - self.vcard = vcard - self.thumb_url = thumb_url - self.thumb_width = thumb_width - self.thumb_height = thumb_height - - def to_dict(self): - json_dict = super().to_dict() - json_dict['phone_number'] = self.phone_number - json_dict['first_name'] = self.first_name - if self.last_name: - json_dict['last_name'] = self.last_name - if self.vcard: - json_dict['vcard'] = self.vcard - if self.thumb_url: - json_dict['thumb_url'] = self.thumb_url - if self.thumb_width: - json_dict['thumb_width'] = self.thumb_width - if self.thumb_height: - json_dict['thumb_height'] = self.thumb_height - return json_dict - - -class InlineQueryResultGame(InlineQueryResultBase): - def __init__(self, id, game_short_name, reply_markup=None): - super().__init__('game', id, reply_markup = reply_markup) - self.game_short_name = game_short_name - - def to_dict(self): - json_dict = super().to_dict() - json_dict['game_short_name'] = self.game_short_name - return json_dict - - -class InlineQueryResultCachedBase(ABC, JsonSerializable): - def __init__(self): - self.type = None - self.id = None - self.title = None - self.description = None - self.caption = None - self.reply_markup = None - self.input_message_content = None - self.parse_mode = None - self.caption_entities = None - self.payload_dic = {} - - def to_json(self): - json_dict = self.payload_dic - json_dict['type'] = self.type - json_dict['id'] = self.id - if self.title: - json_dict['title'] = self.title - if self.description: - json_dict['description'] = self.description - if self.caption: - json_dict['caption'] = self.caption - if self.reply_markup: - json_dict['reply_markup'] = self.reply_markup.to_dict() - if self.input_message_content: - json_dict['input_message_content'] = self.input_message_content.to_dict() - if self.parse_mode: - json_dict['parse_mode'] = self.parse_mode - if self.caption_entities: - json_dict['caption_entities'] = MessageEntity.to_list_of_dicts(self.caption_entities) - return json.dumps(json_dict) - - -class InlineQueryResultCachedPhoto(InlineQueryResultCachedBase): - def __init__(self, id, photo_file_id, title=None, description=None, - caption=None, caption_entities = None, parse_mode=None, - reply_markup=None, input_message_content=None): - InlineQueryResultCachedBase.__init__(self) - self.type = 'photo' - self.id = id - self.photo_file_id = photo_file_id - self.title = title - self.description = description - self.caption = caption - self.caption_entities = caption_entities - self.reply_markup = reply_markup - self.input_message_content = input_message_content - self.parse_mode = parse_mode - self.payload_dic['photo_file_id'] = photo_file_id - - -class InlineQueryResultCachedGif(InlineQueryResultCachedBase): - def __init__(self, id, gif_file_id, title=None, description=None, - caption=None, caption_entities = None, parse_mode=None, - reply_markup=None, input_message_content=None): - InlineQueryResultCachedBase.__init__(self) - self.type = 'gif' - self.id = id - self.gif_file_id = gif_file_id - self.title = title - self.description = description - self.caption = caption - self.caption_entities = caption_entities - self.reply_markup = reply_markup - self.input_message_content = input_message_content - self.parse_mode = parse_mode - self.payload_dic['gif_file_id'] = gif_file_id - - -class InlineQueryResultCachedMpeg4Gif(InlineQueryResultCachedBase): - def __init__(self, id, mpeg4_file_id, title=None, description=None, - caption=None, caption_entities = None, parse_mode=None, - reply_markup=None, input_message_content=None): - InlineQueryResultCachedBase.__init__(self) - self.type = 'mpeg4_gif' - self.id = id - self.mpeg4_file_id = mpeg4_file_id - self.title = title - self.description = description - self.caption = caption - self.caption_entities = caption_entities - self.reply_markup = reply_markup - self.input_message_content = input_message_content - self.parse_mode = parse_mode - self.payload_dic['mpeg4_file_id'] = mpeg4_file_id - - -class InlineQueryResultCachedSticker(InlineQueryResultCachedBase): - def __init__(self, id, sticker_file_id, reply_markup=None, input_message_content=None): - InlineQueryResultCachedBase.__init__(self) - self.type = 'sticker' - self.id = id - self.sticker_file_id = sticker_file_id - self.reply_markup = reply_markup - self.input_message_content = input_message_content - self.payload_dic['sticker_file_id'] = sticker_file_id - - -class InlineQueryResultCachedDocument(InlineQueryResultCachedBase): - def __init__(self, id, document_file_id, title, description=None, - caption=None, caption_entities = None, parse_mode=None, - reply_markup=None, input_message_content=None): - InlineQueryResultCachedBase.__init__(self) - self.type = 'document' - self.id = id - self.document_file_id = document_file_id - self.title = title - self.description = description - self.caption = caption - self.caption_entities = caption_entities - self.reply_markup = reply_markup - self.input_message_content = input_message_content - self.parse_mode = parse_mode - self.payload_dic['document_file_id'] = document_file_id - - -class InlineQueryResultCachedVideo(InlineQueryResultCachedBase): - def __init__(self, id, video_file_id, title, description=None, - caption=None, caption_entities = None, parse_mode=None, - reply_markup=None, - input_message_content=None): - InlineQueryResultCachedBase.__init__(self) - self.type = 'video' - self.id = id - self.video_file_id = video_file_id - self.title = title - self.description = description - self.caption = caption - self.caption_entities = caption_entities - self.reply_markup = reply_markup - self.input_message_content = input_message_content - self.parse_mode = parse_mode - self.payload_dic['video_file_id'] = video_file_id - - -class InlineQueryResultCachedVoice(InlineQueryResultCachedBase): - def __init__(self, id, voice_file_id, title, caption=None, caption_entities = None, - parse_mode=None, reply_markup=None, input_message_content=None): - InlineQueryResultCachedBase.__init__(self) - self.type = 'voice' - self.id = id - self.voice_file_id = voice_file_id - self.title = title - self.caption = caption - self.caption_entities = caption_entities - self.reply_markup = reply_markup - self.input_message_content = input_message_content - self.parse_mode = parse_mode - self.payload_dic['voice_file_id'] = voice_file_id - - -class InlineQueryResultCachedAudio(InlineQueryResultCachedBase): - def __init__(self, id, audio_file_id, caption=None, caption_entities = None, - parse_mode=None, reply_markup=None, input_message_content=None): - InlineQueryResultCachedBase.__init__(self) - self.type = 'audio' - self.id = id - self.audio_file_id = audio_file_id - self.caption = caption - self.caption_entities = caption_entities - self.reply_markup = reply_markup - self.input_message_content = input_message_content - self.parse_mode = parse_mode - self.payload_dic['audio_file_id'] = audio_file_id - - -# Games - -class Game(JsonDeserializable): - @classmethod - def de_json(cls, json_string): - if (json_string is None): return None - obj = cls.check_json(json_string) - obj['photo'] = Game.parse_photo(obj['photo']) - if 'text_entities' in obj: - obj['text_entities'] = Game.parse_entities(obj['text_entities']) - if 'animation' in obj: - obj['animation'] = Animation.de_json(obj['animation']) - return cls(**obj) - - @classmethod - def parse_photo(cls, photo_size_array): - ret = [] - for ps in photo_size_array: - ret.append(PhotoSize.de_json(ps)) - return ret - - @classmethod - def parse_entities(cls, message_entity_array): - ret = [] - for me in message_entity_array: - ret.append(MessageEntity.de_json(me)) - return ret - - def __init__(self, title, description, photo, text=None, text_entities=None, animation=None, **kwargs): - self.title: str = title - self.description: str = description - self.photo: List[PhotoSize] = photo - self.text: str = text - self.text_entities: List[MessageEntity] = text_entities - self.animation: Animation = animation - - -class Animation(JsonDeserializable): - @classmethod - def de_json(cls, json_string): - if (json_string is None): return None - obj = cls.check_json(json_string) - if 'thumb' in obj and 'file_id' in obj['thumb']: - obj["thumb"] = PhotoSize.de_json(obj['thumb']) - else: - obj['thumb'] = None - return cls(**obj) - - def __init__(self, file_id, file_unique_id, width=None, height=None, duration=None, - thumb=None, file_name=None, mime_type=None, file_size=None, **kwargs): - self.file_id: str = file_id - self.file_unique_id: str = file_unique_id - self.width: int = width - self.height: int = height - self.duration: int = duration - self.thumb: PhotoSize = thumb - self.file_name: str = file_name - self.mime_type: str = mime_type - self.file_size: int = file_size - - -class GameHighScore(JsonDeserializable): - @classmethod - def de_json(cls, json_string): - if (json_string is None): return None - obj = cls.check_json(json_string) - obj['user'] = User.de_json(obj['user']) - return cls(**obj) - - def __init__(self, position, user, score, **kwargs): - self.position: int = position - self.user: User = user - self.score: int = score - - -# Payments - -class LabeledPrice(JsonSerializable): - def __init__(self, label, amount): - self.label: str = label - self.amount: int = amount - - def to_dict(self): - return { - 'label': self.label, 'amount': self.amount - } - - def to_json(self): - return json.dumps(self.to_dict()) - - -class Invoice(JsonDeserializable): - @classmethod - def de_json(cls, json_string): - if (json_string is None): return None - obj = cls.check_json(json_string, dict_copy=False) - return cls(**obj) - - def __init__(self, title, description, start_parameter, currency, total_amount, **kwargs): - self.title: str = title - self.description: str = description - self.start_parameter: str = start_parameter - self.currency: str = currency - self.total_amount: int = total_amount - - -class ShippingAddress(JsonDeserializable): - @classmethod - def de_json(cls, json_string): - if (json_string is None): return None - obj = cls.check_json(json_string, dict_copy=False) - return cls(**obj) - - def __init__(self, country_code, state, city, street_line1, street_line2, post_code, **kwargs): - self.country_code: str = country_code - self.state: str = state - self.city: str = city - self.street_line1: str = street_line1 - self.street_line2: str = street_line2 - self.post_code: str = post_code - - -class OrderInfo(JsonDeserializable): - @classmethod - def de_json(cls, json_string): - if (json_string is None): return None - obj = cls.check_json(json_string) - obj['shipping_address'] = ShippingAddress.de_json(obj.get('shipping_address')) - return cls(**obj) - - def __init__(self, name=None, phone_number=None, email=None, shipping_address=None, **kwargs): - self.name: str = name - self.phone_number: str = phone_number - self.email: str = email - self.shipping_address: ShippingAddress = shipping_address - - -class ShippingOption(JsonSerializable): - def __init__(self, id, title): - self.id: str = id - self.title: str = title - self.prices: List[LabeledPrice] = [] - - def add_price(self, *args): - """ - Add LabeledPrice to ShippingOption - :param args: LabeledPrices - """ - for price in args: - self.prices.append(price) - return self - - def to_json(self): - price_list = [] - for p in self.prices: - price_list.append(p.to_dict()) - json_dict = json.dumps({'id': self.id, 'title': self.title, 'prices': price_list}) - return json_dict - - -class SuccessfulPayment(JsonDeserializable): - @classmethod - def de_json(cls, json_string): - if (json_string is None): return None - obj = cls.check_json(json_string) - obj['order_info'] = OrderInfo.de_json(obj.get('order_info')) - return cls(**obj) - - def __init__(self, currency, total_amount, invoice_payload, shipping_option_id=None, order_info=None, - telegram_payment_charge_id=None, provider_payment_charge_id=None, **kwargs): - self.currency: str = currency - self.total_amount: int = total_amount - self.invoice_payload: str = invoice_payload - self.shipping_option_id: str = shipping_option_id - self.order_info: OrderInfo = order_info - self.telegram_payment_charge_id: str = telegram_payment_charge_id - self.provider_payment_charge_id: str = provider_payment_charge_id - - -class ShippingQuery(JsonDeserializable): - @classmethod - def de_json(cls, json_string): - if (json_string is None): return None - obj = cls.check_json(json_string) - obj['from_user'] = User.de_json(obj.pop('from')) - obj['shipping_address'] = ShippingAddress.de_json(obj['shipping_address']) - return cls(**obj) - - def __init__(self, id, from_user, invoice_payload, shipping_address, **kwargs): - self.id: str = id - self.from_user: User = from_user - self.invoice_payload: str = invoice_payload - self.shipping_address: ShippingAddress = shipping_address - - -class PreCheckoutQuery(JsonDeserializable): - @classmethod - def de_json(cls, json_string): - if (json_string is None): return None - obj = cls.check_json(json_string) - obj['from_user'] = User.de_json(obj.pop('from')) - obj['order_info'] = OrderInfo.de_json(obj.get('order_info')) - return cls(**obj) - - def __init__(self, id, from_user, currency, total_amount, invoice_payload, shipping_option_id=None, order_info=None, **kwargs): - self.id: str = id - self.from_user: User = from_user - self.currency: str = currency - self.total_amount: int = total_amount - self.invoice_payload: str = invoice_payload - self.shipping_option_id: str = shipping_option_id - self.order_info: OrderInfo = order_info - - -# Stickers - -class StickerSet(JsonDeserializable): - @classmethod - def de_json(cls, json_string): - if (json_string is None): return None - obj = cls.check_json(json_string) - stickers = [] - for s in obj['stickers']: - stickers.append(Sticker.de_json(s)) - obj['stickers'] = stickers - if 'thumb' in obj and 'file_id' in obj['thumb']: - obj['thumb'] = PhotoSize.de_json(obj['thumb']) - else: - obj['thumb'] = None - return cls(**obj) - - def __init__(self, name, title, is_animated, is_video, contains_masks, stickers, thumb=None, **kwargs): - self.name: str = name - self.title: str = title - self.is_animated: bool = is_animated - self.is_video: bool = is_video - self.contains_masks: bool = contains_masks - self.stickers: List[Sticker] = stickers - self.thumb: PhotoSize = thumb - - -class Sticker(JsonDeserializable): - @classmethod - def de_json(cls, json_string): - if (json_string is None): return None - obj = cls.check_json(json_string) - if 'thumb' in obj and 'file_id' in obj['thumb']: - obj['thumb'] = PhotoSize.de_json(obj['thumb']) - else: - obj['thumb'] = None - if 'mask_position' in obj: - obj['mask_position'] = MaskPosition.de_json(obj['mask_position']) - return cls(**obj) - - def __init__(self, file_id, file_unique_id, width, height, is_animated, - is_video, thumb=None, emoji=None, set_name=None, mask_position=None, file_size=None, **kwargs): - self.file_id: str = file_id - self.file_unique_id: str = file_unique_id - self.width: int = width - self.height: int = height - self.is_animated: bool = is_animated - self.is_video: bool = is_video - self.thumb: PhotoSize = thumb - self.emoji: str = emoji - self.set_name: str = set_name - self.mask_position: MaskPosition = mask_position - self.file_size: int = file_size - - - -class MaskPosition(Dictionaryable, JsonDeserializable, JsonSerializable): - @classmethod - def de_json(cls, json_string): - if (json_string is None): return None - obj = cls.check_json(json_string, dict_copy=False) - return cls(**obj) - - def __init__(self, point, x_shift, y_shift, scale, **kwargs): - self.point: str = point - self.x_shift: float = x_shift - self.y_shift: float = y_shift - self.scale: float = scale - - def to_json(self): - return json.dumps(self.to_dict()) - - def to_dict(self): - return {'point': self.point, 'x_shift': self.x_shift, 'y_shift': self.y_shift, 'scale': self.scale} - - -# InputMedia - -class InputMedia(Dictionaryable, JsonSerializable): - def __init__(self, type, media, caption=None, parse_mode=None, caption_entities=None): - self.type: str = type - self.media: str = media - self.caption: Optional[str] = caption - self.parse_mode: Optional[str] = parse_mode - self.caption_entities: Optional[List[MessageEntity]] = caption_entities - - if util.is_string(self.media): - self._media_name = '' - self._media_dic = self.media - else: - self._media_name = util.generate_random_token() - self._media_dic = 'attach://{0}'.format(self._media_name) - - def to_json(self): - return json.dumps(self.to_dict()) - - def to_dict(self): - json_dict = {'type': self.type, 'media': self._media_dic} - if self.caption: - json_dict['caption'] = self.caption - if self.parse_mode: - json_dict['parse_mode'] = self.parse_mode - if self.caption_entities: - json_dict['caption_entities'] = MessageEntity.to_list_of_dicts(self.caption_entities) - return json_dict - - def convert_input_media(self): - if util.is_string(self.media): - return self.to_json(), None - - return self.to_json(), {self._media_name: self.media} - - -class InputMediaPhoto(InputMedia): - def __init__(self, media, caption=None, parse_mode=None): - if util.is_pil_image(media): - media = util.pil_image_to_file(media) - - super(InputMediaPhoto, self).__init__(type="photo", media=media, caption=caption, parse_mode=parse_mode) - - def to_dict(self): - return super(InputMediaPhoto, self).to_dict() - - -class InputMediaVideo(InputMedia): - def __init__(self, media, thumb=None, caption=None, parse_mode=None, width=None, height=None, duration=None, - supports_streaming=None): - super(InputMediaVideo, self).__init__(type="video", media=media, caption=caption, parse_mode=parse_mode) - self.thumb = thumb - self.width = width - self.height = height - self.duration = duration - self.supports_streaming = supports_streaming - - def to_dict(self): - ret = super(InputMediaVideo, self).to_dict() - if self.thumb: - ret['thumb'] = self.thumb - if self.width: - ret['width'] = self.width - if self.height: - ret['height'] = self.height - if self.duration: - ret['duration'] = self.duration - if self.supports_streaming: - ret['supports_streaming'] = self.supports_streaming - return ret - - -class InputMediaAnimation(InputMedia): - def __init__(self, media, thumb=None, caption=None, parse_mode=None, width=None, height=None, duration=None): - super(InputMediaAnimation, self).__init__(type="animation", media=media, caption=caption, parse_mode=parse_mode) - self.thumb = thumb - self.width = width - self.height = height - self.duration = duration - - def to_dict(self): - ret = super(InputMediaAnimation, self).to_dict() - if self.thumb: - ret['thumb'] = self.thumb - if self.width: - ret['width'] = self.width - if self.height: - ret['height'] = self.height - if self.duration: - ret['duration'] = self.duration - return ret - - -class InputMediaAudio(InputMedia): - def __init__(self, media, thumb=None, caption=None, parse_mode=None, duration=None, performer=None, title=None): - super(InputMediaAudio, self).__init__(type="audio", media=media, caption=caption, parse_mode=parse_mode) - self.thumb = thumb - self.duration = duration - self.performer = performer - self.title = title - - def to_dict(self): - ret = super(InputMediaAudio, self).to_dict() - if self.thumb: - ret['thumb'] = self.thumb - if self.duration: - ret['duration'] = self.duration - if self.performer: - ret['performer'] = self.performer - if self.title: - ret['title'] = self.title - return ret - - -class InputMediaDocument(InputMedia): - def __init__(self, media, thumb=None, caption=None, parse_mode=None, disable_content_type_detection=None): - super(InputMediaDocument, self).__init__(type="document", media=media, caption=caption, parse_mode=parse_mode) - self.thumb = thumb - self.disable_content_type_detection = disable_content_type_detection - - def to_dict(self): - ret = super(InputMediaDocument, self).to_dict() - if self.thumb: - ret['thumb'] = self.thumb - if self.disable_content_type_detection is not None: - ret['disable_content_type_detection'] = self.disable_content_type_detection - return ret - - -class PollOption(JsonDeserializable): - @classmethod - def de_json(cls, json_string): - if (json_string is None): return None - obj = cls.check_json(json_string, dict_copy=False) - return cls(**obj) - - def __init__(self, text, voter_count = 0, **kwargs): - self.text: str = text - self.voter_count: int = voter_count - # Converted in _convert_poll_options - # def to_json(self): - # # send_poll Option is a simple string: https://core.telegram.org/bots/api#sendpoll - # return json.dumps(self.text) - - -class Poll(JsonDeserializable): - @classmethod - def de_json(cls, json_string): - if (json_string is None): return None - obj = cls.check_json(json_string) - obj['poll_id'] = obj.pop('id') - options = [] - for opt in obj['options']: - options.append(PollOption.de_json(opt)) - obj['options'] = options or None - if 'explanation_entities' in obj: - obj['explanation_entities'] = Message.parse_entities(obj['explanation_entities']) - return cls(**obj) - - def __init__( - self, - question, options, - poll_id=None, total_voter_count=None, is_closed=None, is_anonymous=None, poll_type=None, - allows_multiple_answers=None, correct_option_id=None, explanation=None, explanation_entities=None, - open_period=None, close_date=None, **kwargs): - self.id: str = poll_id - self.question: str = question - self.options: List[PollOption] = options - self.total_voter_count: int = total_voter_count - self.is_closed: bool = is_closed - self.is_anonymous: bool = is_anonymous - self.type: str = poll_type - self.allows_multiple_answers: bool = allows_multiple_answers - self.correct_option_id: int = correct_option_id - self.explanation: str = explanation - self.explanation_entities: List[MessageEntity] = explanation_entities # Default state of entities is None. if (explanation_entities is not None) else [] - self.open_period: int = open_period - self.close_date: int = close_date - - def add(self, option): - if type(option) is PollOption: - self.options.append(option) - else: - self.options.append(PollOption(option)) - - -class PollAnswer(JsonSerializable, JsonDeserializable, Dictionaryable): - @classmethod - def de_json(cls, json_string): - if (json_string is None): return None - obj = cls.check_json(json_string) - obj['user'] = User.de_json(obj['user']) - return cls(**obj) - - def __init__(self, poll_id, user, option_ids, **kwargs): - self.poll_id: str = poll_id - self.user: User = user - self.option_ids: List[int] = option_ids - - def to_json(self): - return json.dumps(self.to_dict()) - - def to_dict(self): - return {'poll_id': self.poll_id, - 'user': self.user.to_dict(), - 'option_ids': self.option_ids} - - -class ChatLocation(JsonSerializable, JsonDeserializable, Dictionaryable): - @classmethod - def de_json(cls, json_string): - if json_string is None: return json_string - obj = cls.check_json(json_string) - obj['location'] = Location.de_json(obj['location']) - return cls(**obj) - - def __init__(self, location, address, **kwargs): - self.location: Location = location - self.address: str = address - - def to_json(self): - return json.dumps(self.to_dict()) - - def to_dict(self): - return { - "location": self.location.to_dict(), - "address": self.address - } - - -class ChatInviteLink(JsonSerializable, JsonDeserializable, Dictionaryable): - @classmethod - def de_json(cls, json_string): - if json_string is None: return None - obj = cls.check_json(json_string) - obj['creator'] = User.de_json(obj['creator']) - return cls(**obj) - - def __init__(self, invite_link, creator, creates_join_request , is_primary, is_revoked, - name=None, expire_date=None, member_limit=None, pending_join_request_count=None, **kwargs): - self.invite_link: str = invite_link - self.creator: User = creator - self.creates_join_request: bool = creates_join_request - self.is_primary: bool = is_primary - self.is_revoked: bool = is_revoked - self.name: str = name - self.expire_date: int = expire_date - self.member_limit: int = member_limit - self.pending_join_request_count: int = pending_join_request_count - - def to_json(self): - return json.dumps(self.to_dict()) - - def to_dict(self): - json_dict = { - "invite_link": self.invite_link, - "creator": self.creator.to_dict(), - "is_primary": self.is_primary, - "is_revoked": self.is_revoked, - "creates_join_request": self.creates_join_request - } - if self.expire_date: - json_dict["expire_date"] = self.expire_date - if self.member_limit: - json_dict["member_limit"] = self.member_limit - if self.pending_join_request_count: - json_dict["pending_join_request_count"] = self.pending_join_request_count - if self.name: - json_dict["name"] = self.name - return json_dict - - -class ProximityAlertTriggered(JsonDeserializable): - @classmethod - def de_json(cls, json_string): - if json_string is None: return None - obj = cls.check_json(json_string, dict_copy=False) - return cls(**obj) - - def __init__(self, traveler, watcher, distance, **kwargs): - self.traveler: User = traveler - self.watcher: User = watcher - self.distance: int = distance - - -class VoiceChatStarted(JsonDeserializable): - @classmethod - def de_json(cls, json_string): - return cls() - - def __init__(self): - """ - This object represents a service message about a voice chat started in the chat. - Currently holds no information. - """ - pass - - -class VoiceChatScheduled(JsonDeserializable): - @classmethod - def de_json(cls, json_string): - if json_string is None: return None - obj = cls.check_json(json_string, dict_copy=False) - return cls(**obj) - - def __init__(self, start_date, **kwargs): - self.start_date: int = start_date - - -class VoiceChatEnded(JsonDeserializable): - @classmethod - def de_json(cls, json_string): - if json_string is None: return None - obj = cls.check_json(json_string, dict_copy=False) - return cls(**obj) - - def __init__(self, duration, **kwargs): - self.duration: int = duration - - -class VoiceChatParticipantsInvited(JsonDeserializable): - @classmethod - def de_json(cls, json_string): - if json_string is None: return None - obj = cls.check_json(json_string) - if 'users' in obj: - obj['users'] = [User.de_json(u) for u in obj['users']] - return cls(**obj) - - def __init__(self, users=None, **kwargs): - self.users: List[User] = users - - -class MessageAutoDeleteTimerChanged(JsonDeserializable): - @classmethod - def de_json(cls, json_string): - if json_string is None: return None - obj = cls.check_json(json_string, dict_copy=False) - return cls(**obj) - - def __init__(self, message_auto_delete_time, **kwargs): - self.message_auto_delete_time = message_auto_delete_time diff --git a/telebot/util.py b/telebot/util.py deleted file mode 100644 index 14ed360..0000000 --- a/telebot/util.py +++ /dev/null @@ -1,512 +0,0 @@ -# -*- coding: utf-8 -*- -import random -import re -import string -import threading -import traceback -from typing import Any, Callable, List, Dict, Optional, Union - -# noinspection PyPep8Naming -import queue as Queue -import logging - -from telebot import types - -try: - import ujson as json -except ImportError: - import json - -try: - # noinspection PyPackageRequirements - from PIL import Image - from io import BytesIO - - pil_imported = True -except: - pil_imported = False - -MAX_MESSAGE_LENGTH = 4096 - -logger = logging.getLogger('TeleBot') - -thread_local = threading.local() - -content_type_media = [ - 'text', 'audio', 'animation', 'document', 'photo', 'sticker', 'video', 'video_note', 'voice', 'contact', 'dice', 'poll', - 'venue', 'location' -] - -content_type_service = [ - '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', - 'proximity_alert_triggered', 'voice_chat_scheduled', 'voice_chat_started', 'voice_chat_ended', - 'voice_chat_participants_invited', 'message_auto_delete_timer_changed' -] - -update_types = [ - "update_id", "message", "edited_message", "channel_post", "edited_channel_post", "inline_query", - "chosen_inline_result", "callback_query", "shipping_query", "pre_checkout_query", "poll", "poll_answer", - "my_chat_member", "chat_member", "chat_join_request" -] - - -class WorkerThread(threading.Thread): - count = 0 - - def __init__(self, exception_callback=None, queue=None, name=None): - if not name: - name = "WorkerThread{0}".format(self.__class__.count + 1) - self.__class__.count += 1 - if not queue: - queue = Queue.Queue() - - threading.Thread.__init__(self, name=name) - self.queue = queue - self.daemon = True - - self.received_task_event = threading.Event() - self.done_event = threading.Event() - self.exception_event = threading.Event() - self.continue_event = threading.Event() - - self.exception_callback = exception_callback - self.exception_info = None - self._running = True - self.start() - - def run(self): - while self._running: - try: - task, args, kwargs = self.queue.get(block=True, timeout=.5) - self.continue_event.clear() - self.received_task_event.clear() - self.done_event.clear() - self.exception_event.clear() - logger.debug("Received task") - self.received_task_event.set() - - task(*args, **kwargs) - logger.debug("Task complete") - self.done_event.set() - except Queue.Empty: - pass - except Exception as e: - logger.debug(type(e).__name__ + " occurred, args=" + str(e.args) + "\n" + traceback.format_exc()) - self.exception_info = e - self.exception_event.set() - if self.exception_callback: - self.exception_callback(self, self.exception_info) - self.continue_event.wait() - - def put(self, task, *args, **kwargs): - self.queue.put((task, args, kwargs)) - - def raise_exceptions(self): - if self.exception_event.is_set(): - raise self.exception_info - - def clear_exceptions(self): - self.exception_event.clear() - self.continue_event.set() - - def stop(self): - self._running = False - - -class ThreadPool: - - def __init__(self, telebot, num_threads=2): - self.telebot = telebot - self.tasks = Queue.Queue() - self.workers = [WorkerThread(self.on_exception, self.tasks) for _ in range(num_threads)] - self.num_threads = num_threads - - self.exception_event = threading.Event() - self.exception_info = None - - def put(self, func, *args, **kwargs): - self.tasks.put((func, args, kwargs)) - - def on_exception(self, worker_thread, exc_info): - if self.telebot.exception_handler is not None: - handled = self.telebot.exception_handler.handle(exc_info) - else: - handled = False - if not handled: - self.exception_info = exc_info - self.exception_event.set() - worker_thread.continue_event.set() - - def raise_exceptions(self): - if self.exception_event.is_set(): - raise self.exception_info - - def clear_exceptions(self): - self.exception_event.clear() - - def close(self): - for worker in self.workers: - worker.stop() - for worker in self.workers: - worker.join() - - -class AsyncTask: - def __init__(self, target, *args, **kwargs): - self.target = target - self.args = args - self.kwargs = kwargs - - self.done = False - self.thread = threading.Thread(target=self._run) - self.thread.start() - - def _run(self): - try: - self.result = self.target(*self.args, **self.kwargs) - except Exception as e: - self.result = e - self.done = True - - def wait(self): - if not self.done: - self.thread.join() - if isinstance(self.result, BaseException): - raise self.result - else: - return self.result - - -class CustomRequestResponse(): - def __init__(self, json_text, status_code = 200, reason = ""): - self.status_code = status_code - self.text = json_text - self.reason = reason - - def json(self): - return json.loads(self.text) - - -def async_dec(): - def decorator(fn): - def wrapper(*args, **kwargs): - return AsyncTask(fn, *args, **kwargs) - - return wrapper - - return decorator - - -def is_string(var): - return isinstance(var, str) - - -def is_dict(var): - return isinstance(var, dict) - - -def is_bytes(var): - return isinstance(var, bytes) - - -def is_pil_image(var): - 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: - """ - Checks if `text` is a command. Telegram chat commands start with the '/' character. - :param text: Text to check. - :return: True if `text` is a command, else False. - """ - if text is None: return False - return text.startswith('/') - - -def extract_command(text: str) -> Union[str, None]: - """ - Extracts the command from `text` (minus the '/') if `text` is a command (see is_command). - If `text` is not a command, this function returns None. - - Examples: - extract_command('/help'): 'help' - extract_command('/help@BotName'): 'help' - extract_command('/search black eyed peas'): 'search' - extract_command('Good day to you'): None - - :param text: String to extract the command from - :return: the command if `text` is a command (according to is_command), else None. - """ - if text is None: return None - return text.split()[0].split('@')[0][1:] if is_command(text) else None - - -def extract_arguments(text: str) -> str: - """ - Returns the argument after the command. - - Examples: - extract_arguments("/get name"): 'name' - extract_arguments("/get"): '' - extract_arguments("/get@botName name"): 'name' - - :param text: String to extract the arguments from a command - :return: the arguments if `text` is a command (according to is_command), else None. - """ - regexp = re.compile(r"/\w*(@\w*)*\s*([\s\S]*)", re.IGNORECASE) - result = regexp.match(text) - return result.group(2) if is_command(text) else None - - -def split_string(text: str, chars_per_string: int) -> List[str]: - """ - 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. - - :param text: The text to split - :param chars_per_string: The number of characters per line the text is split into. - :return: The splitted text as a list of strings. - """ - 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]: - r""" - 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. - If `chars_per_string` > 4096: `chars_per_string` = 4096. - Splits by '\n', '. ' or ' ' in exactly this priority. - - :param text: The text to split - :param chars_per_string: The number of maximum characters per part the text is split to. - :return: The splitted text as a list of strings. - """ - - def _text_before_last(substr: str) -> str: - return substr.join(part.split(substr)[:-1]) + substr - - if chars_per_string > MAX_MESSAGE_LENGTH: chars_per_string = MAX_MESSAGE_LENGTH - - parts = [] - while True: - if len(text) < chars_per_string: - parts.append(text) - return parts - - part = text[:chars_per_string] - - if "\n" in part: part = _text_before_last("\n") - elif ". " in part: part = _text_before_last(". ") - elif " " in part: part = _text_before_last(" ") - - parts.append(part) - text = text[len(part):] - - -def escape(text: str) -> str: - """ - Replaces the following chars in `text` ('&' with '&', '<' with '<' and '>' with '>'). - - :param text: the text to escape - :return: the escaped text - """ - chars = {"&": "&", "<": "<", ">": ">"} - for old, new in chars.items(): text = text.replace(old, new) - return text - - -def user_link(user: types.User, include_id: bool=False) -> str: - """ - Returns an HTML user link. This is useful for reports. - Attention: Don't forget to set parse_mode to 'HTML'! - - Example: - bot.send_message(your_user_id, user_link(message.from_user) + ' started the bot!', parse_mode='HTML') - - :param user: the user (not the user_id) - :param include_id: include the user_id - :return: HTML user link - """ - name = escape(user.first_name) - return (f"{name}" - + (f" (
{user.id}
)" if include_id else "")) - - -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} - This is useful to avoid always typing 'btn1 = InlineKeyboardButton(...)' 'btn2 = InlineKeyboardButton(...)' - - Example: - - .. code-block:: python - - quick_markup({ - 'Twitter': {'url': 'https://twitter.com'}, - 'Facebook': {'url': 'https://facebook.com'}, - 'Back': {'callback_data': 'whatever'} - }, row_width=2): - # returns an InlineKeyboardMarkup with two buttons in a row, one leading to Twitter, the other to facebook - # and a back button below - - # kwargs can be: - { - 'url': None, - 'callback_data': None, - 'switch_inline_query': None, - 'switch_inline_query_current_chat': None, - 'callback_game': None, - 'pay': None, - 'login_url': None - } - - :param values: a dict containing all buttons to create in this format: {text: kwargs} {str:} - :param row_width: int row width - :return: InlineKeyboardMarkup - """ - markup = types.InlineKeyboardMarkup(row_width=row_width) - buttons = [ - types.InlineKeyboardButton(text=text, **kwargs) - for text, kwargs in values.items() - ] - markup.add(*buttons) - return markup - - -# CREDITS TO http://stackoverflow.com/questions/12317940#answer-12320352 -def or_set(self): - self._set() - self.changed() - - -def or_clear(self): - self._clear() - self.changed() - - -def orify(e, changed_callback): - if not hasattr(e, "_set"): - e._set = e.set - if not hasattr(e, "_clear"): - e._clear = e.clear - e.changed = changed_callback - e.set = lambda: or_set(e) - e.clear = lambda: or_clear(e) - - -def OrEvent(*events): - or_event = threading.Event() - - def changed(): - bools = [ev.is_set() for ev in events] - if any(bools): - or_event.set() - else: - or_event.clear() - - def busy_wait(): - while not or_event.is_set(): - # noinspection PyProtectedMember - or_event._wait(3) - - for e in events: - orify(e, changed) - or_event._wait = or_event.wait - or_event.wait = busy_wait - changed() - return or_event - - -def per_thread(key, construct_value, reset=False): - if reset or not hasattr(thread_local, key): - value = construct_value() - setattr(thread_local, key, value) - - return getattr(thread_local, key) - - -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(): - return ''.join(random.sample(string.ascii_letters, 16)) - - -def deprecated(warn: bool=True, alternative: Optional[Callable]=None): - """ - Use this decorator to mark functions as deprecated. - When the function is used, an info (or warning if `warn` is True) is logged. - :param warn: If True a warning is logged else an info - :param alternative: The new function to use instead - """ - def decorator(function): - def wrapper(*args, **kwargs): - info = f"`{function.__name__}` is deprecated." + (f" Use `{alternative.__name__}` instead" if alternative else "") - if not warn: - logger.info(info) - else: - logger.warning(info) - return function(*args, **kwargs) - return wrapper - return decorator - - -# Cloud helpers -def webhook_google_functions(bot, request): - """A webhook endpoint for Google Cloud Functions FaaS.""" - if request.is_json: - try: - request_json = request.get_json() - update = types.Update.de_json(request_json) - bot.process_new_updates([update]) - return '' - except Exception as e: - print(e) - return 'Bot FAIL', 400 - else: - return 'Bot ON' - - -def antiflood(function, *args, **kwargs): - """ - Use this function inside loops in order to avoid getting TooManyRequests error. - Example: - - .. code-block:: python3 - - from telebot.util import antiflood - for chat_id in chat_id_list: - msg = antiflood(bot.send_message, chat_id, text) - - :param function: - :param args: - :param kwargs: - :return: None - """ - from telebot.apihelper import ApiTelegramException - from time import sleep - msg = None - try: - msg = function(*args, **kwargs) - except ApiTelegramException as ex: - if ex.error_code == 429: - sleep(ex.result_json['parameters']['retry_after']) - msg = function(*args, **kwargs) - finally: - return msg diff --git a/telebot/version.py b/telebot/version.py deleted file mode 100644 index 0a15dc1..0000000 --- a/telebot/version.py +++ /dev/null @@ -1,3 +0,0 @@ -# Versions should comply with PEP440. -# This line is parsed in setup.py: -__version__ = '4.4.0'