diff --git a/README.md b/README.md index 3e3d109..a4285e0 100644 --- a/README.md +++ b/README.md @@ -540,11 +540,13 @@ apihelper.proxy = {'https':'socks5://userproxy:password@proxy_address:port'} _Checking is in progress..._ -✅ [Bot API 4.3](https://core.telegram.org/bots/api-changelog#may-31-2019) _- To be checked..._ +✅ [Bot API 4.5](https://core.telegram.org/bots/api-changelog#december-31-2019) _- To be checked..._ +* ✔ [Bot API 4.4](https://core.telegram.org/bots/api-changelog#july-29-2019) +* ✔ [Bot API 4.3](https://core.telegram.org/bots/api-changelog#may-31-2019) * ✔ [Bot API 4.2](https://core.telegram.org/bots/api-changelog#april-14-2019) * ➕ [Bot API 4.1](https://core.telegram.org/bots/api-changelog#august-27-2018) - No Passport support. -* ➕ [Bot API 4.0](https://core.telegram.org/bots/api-changelog#july-26-2018) - No Passport support. +* ➕ [Bot API 4.0](https://core.telegram.org/bots/api-changelog#july-26-2018) - No Passport support. * ✔ [Bot API 3.6](https://core.telegram.org/bots/api-changelog#february-13-2018) * ✔ [Bot API 3.5](https://core.telegram.org/bots/api-changelog#november-17-2017) * ✔ [Bot API 3.4](https://core.telegram.org/bots/api-changelog#october-11-2017) diff --git a/telebot/__init__.py b/telebot/__init__.py index 5492666..180211a 100644 --- a/telebot/__init__.py +++ b/telebot/__init__.py @@ -235,17 +235,37 @@ class TeleBot: """ self.reply_backend.load_handlers(filename, del_file_after_loading) - def set_webhook(self, url=None, certificate=None, max_connections=None, allowed_updates=None): - return apihelper.set_webhook(self.token, url, certificate, max_connections, allowed_updates) + def set_webhook(self, url=None, certificate=None, max_connections=None, allowed_updates=None, ip_address=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. - def delete_webhook(self): + :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 + :return: + """ + return apihelper.set_webhook(self.token, url, certificate, max_connections, allowed_updates, ip_address) + + def delete_webhook(self, drop_pending_updates=None): """ Use this method to remove webhook integration if you decide to switch back to getUpdates. + + :param drop_pending_updates: Pass True to drop all pending updates :return: bool """ - return apihelper.delete_webhook(self.token) + return apihelper.delete_webhook(self.token, drop_pending_updates) def get_webhook_info(self): + """ + 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. + + :return: On success, returns a WebhookInfo object. + """ result = apihelper.get_webhook_info(self.token) return types.WebhookInfo.de_json(result) @@ -432,7 +452,8 @@ class TeleBot: while not self.__stop_polling.is_set(): try: self.polling(none_stop=True, timeout=timeout, long_polling_timeout=long_polling_timeout, *args, **kwargs) - except Exception: + except Exception as e: + logger.error("Infinity polling exception: {}".format(e)) time.sleep(3) pass logger.info("Break infinity polling") @@ -1059,11 +1080,16 @@ class TeleBot: def unban_chat_member(self, chat_id, user_id, only_if_banned = False): """ - Removes member from the ban - :param chat_id: - :param user_id: - :param only_if_banned: - :return: + 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. + + :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) @@ -1077,7 +1103,7 @@ class TeleBot: 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. - Returns True on success. + :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 @@ -1096,7 +1122,7 @@ class TeleBot: :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: types.Message + :return: True on success """ return apihelper.restrict_chat_member( self.token, chat_id, user_id, until_date, @@ -1111,7 +1137,8 @@ class TeleBot: """ 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. Returns True on success. + Pass False for all boolean parameters to demote a user. + :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 @@ -1125,7 +1152,7 @@ class TeleBot: :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) - :return: + :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, @@ -1134,26 +1161,27 @@ class TeleBot: def set_chat_administrator_custom_title(self, chat_id, user_id, custom_title): """ Use this method to set a custom title for an administrator - in a supergroup promoted by the bot. - Returns True on success. + in a supergroup promoted by the bot. + :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: + :return: True on success. """ return apihelper.set_chat_administrator_custom_title(self.token, chat_id, user_id, custom_title) def set_chat_permissions(self, chat_id, permissions): """ 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. + 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. + :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: + :return: True on success """ return apihelper.set_chat_permissions(self.token, chat_id, permissions) @@ -1161,10 +1189,10 @@ class TeleBot: """ 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. - Returns exported invite link as String on success. + :param chat_id: Id: Unique identifier for the target chat or username of the target channel (in the format @channelusername) - :return: + :return: exported invite link as String on success. """ return apihelper.export_chat_invite_link(self.token, chat_id) @@ -1218,15 +1246,15 @@ class TeleBot: """ return apihelper.set_chat_title(self.token, chat_id, title) - def set_chat_description(self, chat_id, description): + def set_chat_description(self, chat_id, description=None): """ 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. - Returns True on success. + :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: + :return: True on success. """ return apihelper.set_chat_description(self.token, chat_id, description) diff --git a/telebot/apihelper.py b/telebot/apihelper.py index d09b22d..207b3ae 100644 --- a/telebot/apihelper.py +++ b/telebot/apihelper.py @@ -206,7 +206,7 @@ def send_message( return _make_request(token, method_url, params=payload, method='post') -def set_webhook(token, url=None, certificate=None, max_connections=None, allowed_updates=None): +def set_webhook(token, url=None, certificate=None, max_connections=None, allowed_updates=None, ip_address=None): method_url = r'setWebhook' payload = { 'url': url if url else "", @@ -218,12 +218,17 @@ def set_webhook(token, url=None, certificate=None, max_connections=None, allowed payload['max_connections'] = max_connections if allowed_updates is not None: # Empty lists should pass payload['allowed_updates'] = json.dumps(allowed_updates) + if ip_address is not None: # Empty string should pass + payload['ip_address'] = ip_address return _make_request(token, method_url, params=payload, files=files) -def delete_webhook(token): +def delete_webhook(token, drop_pending_updates=None): method_url = r'deleteWebhook' - return _make_request(token, method_url) + payload = {} + if drop_pending_updates is not None: # None / True / False + payload['drop_pending_updates'] = drop_pending_updates + return _make_request(token, method_url, params=payload) def get_webhook_info(token): @@ -703,7 +708,7 @@ def kick_chat_member(token, chat_id, user_id, until_date=None): def unban_chat_member(token, chat_id, user_id, only_if_banned): method_url = 'unbanChatMember' payload = {'chat_id': chat_id, 'user_id': user_id} - if only_if_banned: + if only_if_banned is not None: # None / True / False payload['only_if_banned'] = only_if_banned return _make_request(token, method_url, params=payload, method='post') @@ -820,7 +825,9 @@ def set_my_commands(token, commands): def set_chat_description(token, chat_id, description): method_url = 'setChatDescription' - payload = {'chat_id': chat_id, 'description': description} + payload = {'chat_id': chat_id} + if description is not None: # Allow empty strings + payload['description'] = description return _make_request(token, method_url, params=payload, method='post') diff --git a/telebot/types.py b/telebot/types.py index 5e6b59a..73e5db5 100644 --- a/telebot/types.py +++ b/telebot/types.py @@ -132,18 +132,20 @@ class WebhookInfo(JsonDeserializable): url = obj['url'] has_custom_certificate = obj['has_custom_certificate'] pending_update_count = obj['pending_update_count'] + ip_address = obj.get('ip_address') last_error_date = obj.get('last_error_date') last_error_message = obj.get('last_error_message') max_connections = obj.get('max_connections') allowed_updates = obj.get('allowed_updates') - return cls(url, has_custom_certificate, pending_update_count, last_error_date, last_error_message, - max_connections, allowed_updates) + return cls(url, has_custom_certificate, pending_update_count, ip_address, last_error_date, + last_error_message, max_connections, allowed_updates) - def __init__(self, url, has_custom_certificate, pending_update_count, last_error_date, last_error_message, - max_connections, allowed_updates): + def __init__(self, url, has_custom_certificate, pending_update_count, ip_address, last_error_date, + last_error_message, max_connections, allowed_updates): 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 @@ -218,8 +220,8 @@ class Chat(JsonDeserializable): username = obj.get('username') first_name = obj.get('first_name') last_name = obj.get('last_name') - all_members_are_administrators = obj.get('all_members_are_administrators') photo = ChatPhoto.de_json(obj.get('photo')) + bio = obj.get('bio') description = obj.get('description') invite_link = obj.get('invite_link') pinned_message = Message.de_json(obj.get('pinned_message')) @@ -227,25 +229,27 @@ class Chat(JsonDeserializable): slow_mode_delay = obj.get('slow_mode_delay') sticker_set_name = obj.get('sticker_set_name') can_set_sticker_set = obj.get('can_set_sticker_set') + linked_chat_id = obj.get('linked_chat_id') + location = None # Not implemented return cls( id, type, title, username, first_name, last_name, - all_members_are_administrators, photo, description, invite_link, + photo, bio, description, invite_link, pinned_message, permissions, slow_mode_delay, sticker_set_name, - can_set_sticker_set) + can_set_sticker_set, linked_chat_id, location) def __init__(self, id, type, title=None, username=None, first_name=None, - last_name=None, all_members_are_administrators=None, - photo=None, description=None, invite_link=None, + last_name=None, photo=None, bio=None, description=None, invite_link=None, pinned_message=None, permissions=None, slow_mode_delay=None, - sticker_set_name=None, can_set_sticker_set=None): + sticker_set_name=None, can_set_sticker_set=None, + linked_chat_id=None, location=None): self.id = id self.type = type self.title = title self.username = username self.first_name = first_name self.last_name = last_name - self.all_members_are_administrators = all_members_are_administrators self.photo = photo + self.bio = bio self.description = description self.invite_link = invite_link self.pinned_message = pinned_message @@ -253,6 +257,8 @@ class Chat(JsonDeserializable): self.slow_mode_delay = slow_mode_delay self.sticker_set_name = sticker_set_name self.can_set_sticker_set = can_set_sticker_set + self.linked_chat_id = linked_chat_id + self.location = location class Message(JsonDeserializable): @@ -1054,7 +1060,7 @@ class LoginUrl(Dictionaryable, JsonSerializable, JsonDeserializable): json_dict['forward_text'] = self.forward_text if self.bot_username: json_dict['bot_username'] = self.bot_username - if self.request_write_access: + if self.request_write_access is not None: json_dict['request_write_access'] = self.request_write_access return json_dict @@ -1160,7 +1166,6 @@ class ChatMember(JsonDeserializable): user = User.de_json(obj['user']) status = obj['status'] custom_title = obj.get('custom_title') - until_date = obj.get('until_date') can_be_edited = obj.get('can_be_edited') can_post_messages = obj.get('can_post_messages') can_edit_messages = obj.get('can_edit_messages') @@ -1176,23 +1181,23 @@ class ChatMember(JsonDeserializable): can_send_polls = obj.get('can_send_polls') can_send_other_messages = obj.get('can_send_other_messages') can_add_web_page_previews = obj.get('can_add_web_page_previews') + until_date = obj.get('until_date') return cls( - user, status, custom_title, until_date, can_be_edited, can_post_messages, + user, status, custom_title, can_be_edited, can_post_messages, can_edit_messages, can_delete_messages, can_restrict_members, can_promote_members, can_change_info, can_invite_users, can_pin_messages, is_member, can_send_messages, can_send_media_messages, can_send_polls, - can_send_other_messages, can_add_web_page_previews) + can_send_other_messages, can_add_web_page_previews, until_date) - def __init__(self, user, status, custom_title=None, until_date=None, can_be_edited=None, + def __init__(self, user, status, custom_title=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_send_other_messages=None, can_add_web_page_previews=None, until_date=None): self.user = user self.status = status self.custom_title = custom_title - self.until_date = until_date self.can_be_edited = can_be_edited self.can_post_messages = can_post_messages self.can_edit_messages = can_edit_messages @@ -1208,6 +1213,7 @@ class ChatMember(JsonDeserializable): self.can_send_polls = can_send_polls self.can_send_other_messages = can_send_other_messages self.can_add_web_page_previews = can_add_web_page_previews + self.until_date = until_date class ChatPermissions(JsonDeserializable, JsonSerializable, Dictionaryable): @@ -2297,14 +2303,16 @@ class StickerSet(JsonDeserializable): obj = cls.check_json(json_string) name = obj['name'] title = obj['title'] + is_animated = obj['is_animated'] contains_masks = obj['contains_masks'] stickers = [] for s in obj['stickers']: stickers.append(Sticker.de_json(s)) - return cls(name, title, contains_masks, stickers) + return cls(name, title, is_animated, contains_masks, stickers) - def __init__(self, name, title, contains_masks, stickers): + def __init__(self, name, title, is_animated, contains_masks, stickers): self.stickers = stickers + self.is_animated = is_animated self.contains_masks = contains_masks self.title = title self.name = name