1
0
mirror of https://github.com/eternnoir/pyTelegramBotAPI.git synced 2023-08-10 21:12:57 +03:00

Compare commits

..

68 Commits
3.7.9 ... 3.8.1

Author SHA1 Message Date
72ed7c1dde Merge pull request #1207 from Badiboy/master
Post-release fix for infinity_polling
2021-06-27 20:43:34 +03:00
a29c4af2ee Post-release fix for infinity_polling 2021-06-27 20:40:16 +03:00
8d8f234138 Merge pull request #1206 from MAIKS1900/master
2 of 3 Bot API 5.3 changes
2021-06-27 17:33:04 +03:00
491cc05a95 - Set BotCommandScope as abstract class.
- Docstrings from telegram API Scope types
2021-06-27 17:28:11 +03:00
b2c6077f4d Merge branch 'master' of https://github.com/MAIKS1900/pyTelegramBotAPI into master 2021-06-27 15:08:37 +03:00
fb290dc12d Merge pull request #1205 from Badiboy/master
Release version 3.8.0
2021-06-27 13:24:55 +03:00
c088fabe6c Release version 3.8.0 2021-06-27 13:09:08 +03:00
3e33b7f1cb Bot API 5.3 changes
- Personalized Commands for different chats
- Custom Placeholders of input field for ReplyKeyboardMarkup and ForceReply.
2021-06-26 14:36:14 +03:00
e381671645 Merge pull request #1201 from SwissCorePy/master
Added handlers for `my_chat_member` and `chat_member`.
Added aalowed_updates to polling functions.
2021-06-24 09:06:33 +03:00
ce991e9ac3 Update types.py
added the missing attributes `can_manage_chat` and `can_manage_voice_chats` to ChatMember class
2021-06-23 22:52:24 +02:00
3d5415433e Update __init__.py
Updated TeleBot doc string and added the missing functions to AsyncTeleBot
2021-06-23 22:51:17 +02:00
0bfefdf15d changed allowed_updates in util to update_types
i think its more clear name
2021-06-23 19:57:44 +02:00
506464e637 Update __init__.py
Added the parameter `allowed_updates` to polling and infinity_polling functions
2021-06-23 19:29:36 +02:00
4554cb969f Update __init__.py
added handlers for `my_chat_member` and `chat_member`
2021-06-23 16:10:48 +02:00
65cf841015 Update util.py
added `allowed_updates` list (used by `_init_._retrieve_all_updates` because `chat_member` is not requested by default)
2021-06-23 16:09:40 +02:00
0f0ce934dc Merge pull request #1199 from SwissCorePy/master
added InputInvoiceMessageContent and tgs_sticker support
2021-06-22 17:34:05 +03:00
bffbe764e5 Update tgs_sticker support
* Updated `create_new_sticker_set` and `add_sticker_to_set` functions
* Removed `create_new_animated_sticker_set` and `add_sticker_to_animated_sticker_set` functions
2021-06-22 15:57:34 +02:00
c00595e212 Update types.py
* Added Parameter `caption_entities` to `InputMedia` class
* Added Parameter `disable_content_type_detection` to `InputMediaDocument` class
2021-06-22 15:55:14 +02:00
b20f5b359b Merge pull request #1200 from pablodz/patch-1
Fix long string blocking version of python on github actions setup
2021-06-22 08:29:50 +03:00
558eef78b4 Fix long string blocking version of python on github actions setup 2021-06-21 17:27:35 -05:00
3f46ce3b7b added InputInvoiceMessageContent and tgs_sticker support
and some small changes
2021-06-21 19:59:39 +02:00
69e8edef19 Merge pull request #1198 from SwissCorePy/master
Added some missing features
2021-06-21 20:48:04 +03:00
d3369245c4 fixed wrong type hint 2021-06-21 17:49:03 +02:00
55e9f2095e Merge branch 'master' of https://github.com/eternnoir/pyTelegramBotAPI 2021-06-21 17:39:57 +02:00
7118613ef7 Added missing features
* added some missing features of TelegramBotAPI 4.6-5.2 to pyTelegramBotAPI
* added type hints to (almost) all public TeleBot functions
2021-06-21 17:39:13 +02:00
105d65d5ce Merge pull request #1197 from vishal2376/master
Add developer bot
2021-06-21 14:16:37 +03:00
f11bf08ba1 Update README.md 2021-06-21 16:30:17 +05:30
66598e39fe Change description of developer bot 2021-06-21 16:27:32 +05:30
4146b50384 Add developer bot 2021-06-21 13:16:22 +05:30
f62d642572 Merge pull request #1196 from leonheess/patch-1
Add Anti-Tracking Bot
2021-06-20 19:33:50 +03:00
18f1fd42b0 Add Anti-Tracking Bot 2021-06-20 13:14:55 +02:00
07d198aebe Merge pull request #1195 from SwissCorePy/master
Added class ChatMemberUpdated
2021-06-19 22:18:50 +03:00
0370a9f277 Added class ChatMemberUpdated
* Added class `ChatMemberUpdated` to types
* Simplified `de_json` functions in `WebhookInfo` and `Update` classes (for overall more consistent code)
* changed `options_ids` to ´option_id` in class `PollAnswer`
* Added test for `ChatMemberUpdated` class in `test_types.py` and added the fields `my_chat_member` and `chat_member` to the `Update` class and its tests
2021-06-19 20:13:53 +02:00
22d3ac027a Merge pull request #1194 from SwissCorePy/master
Minor updates to the https://github.com/eternnoir/pyTelegramBotAPI/pull/1191
2021-06-19 19:25:04 +03:00
795f7fff7f Some small changes
* Fixed type warnings in some editors by changing `var: Type = None` to `var: Union[Type, None] = None`
* changed some args from `obj['arg']` to `obj.get('arg')` if arg is optional
* better PEP-8 compliance for less weak warnings
* added tests for the new type `ChatInviteLink`
2021-06-19 17:59:55 +02:00
ab6d40a072 Merge pull request #1193 from Badiboy/master
Raise exception if no token passed
2021-06-19 15:10:32 +03:00
d26923e167 Raise exception if no token passed 2021-06-19 15:09:52 +03:00
05aff236c1 Merge pull request #1191 from SwissCorePy/master
Huge update with type checking and some new properties.

Note: should be presisely tested before publishing.
2021-06-19 15:00:12 +03:00
a9ae070256 Update types.py 2021-06-18 22:37:31 +02:00
63fe6e01d1 Fixed the errors from my last PRs
I testet all using pytest and python versions 3.6-3.9 on macOS
2021-06-18 22:35:49 +02:00
bbafdd1c1d Some Updates
> Added lot of type hints to types.py
> Added some new fields from TelegramBotAPI to pyTelegramBotAPI
> fixed `circular import error in util.py
> Added functions `log_out` and `close` to __init__.py and apihelper.py
> And some more small changes
2021-06-17 20:28:53 +02:00
fe9df2df8c Merge pull request #1186 from AREEG94FAHAD/master
Add to translator bot
2021-06-12 21:13:41 +03:00
b0b8623dce Merge pull request #1 from AREEG94FAHAD/Cryptocurrency
Update README.md
2021-06-12 19:02:06 +03:00
a01e59951a Update README.md 2021-06-12 19:01:42 +03:00
d5c202abbd Update README.md 2021-06-12 18:19:18 +03:00
81299ff613 Update README.md 2021-06-12 18:18:51 +03:00
25bac68309 Update README.md 2021-06-12 18:18:16 +03:00
a05324bdad Merge pull request #1181 from pablodz/master
Github Actions: setup for 3.6+ pypy3.6+
2021-06-09 17:29:57 +03:00
74c4ab2f04 Merge pull request #1183 from SwissCorePy/master
Fixed a bug in `user_link`
2021-06-09 17:28:13 +03:00
ab05cb0045 Fixed a bug in user_link
`user_link` returned an empty string if `include_id` was set to False
2021-06-09 16:20:42 +02:00
709eb8cf45 Github Actions: setup for 3.6+ pypy3.6+ 2021-06-06 14:30:55 -05:00
643cdeceee Merge pull request #1179 from Badiboy/master
Fix special case when content_type is None
2021-06-04 12:29:05 +03:00
2add34c702 Fix special case when content_type is None 2021-06-04 12:28:33 +03:00
877397a46b Merge pull request #1178 from Badiboy/master
Partial rollback for previous update
2021-06-04 12:12:26 +03:00
afbc67795a Partial rollback for previous update 2021-06-04 12:11:37 +03:00
da5dc20b3a Merge pull request #1176 from SwissCorePy/master
Added some functions and type hints
2021-06-03 23:45:21 +03:00
ed5e5e5077 Update util.py
- Removed function `unix_time`
- Added function `escape`
- Added function `user_link`
- Added function `quick_markup`
- Added some type hints
2021-06-03 19:51:33 +02:00
9a6ddce8df Added the function unix_time 2021-06-03 19:06:53 +02:00
db8478d0a4 Merge pull request #1174 from SwissCorePy/master
Add `smart_split` function to utils.py
2021-06-03 19:55:44 +03:00
20030f47af Update util.py
Added the function `smart_split` to split text into meaningful parts.
2021-06-03 18:51:32 +02:00
f7cf1965cb Merge pull request #1168 from anvarjamgirov/patch-1
Bug fixed on set_game_score
2021-06-01 08:30:42 +03:00
aea067f789 Bug fixed on set_game_score
fixed wrong ordered argument error on calling apihelper.set_game_score method in set_game_score
2021-06-01 08:39:09 +05:00
e2c20c1e55 Merge pull request #1167 from zora89/master
typo corrected.
2021-05-25 23:56:01 +03:00
1209281787 type corrected. 2021-05-26 02:17:49 +05:30
742f67c85b Merge pull request #1163 from yehwankim23/patch-1
Fix typo
2021-05-20 12:07:34 +03:00
e22fcbe3c0 Fix typo 2021-05-20 18:01:10 +09:00
d3998dfadb Merge pull request #1161 from yarreg/feature/invite-link-api
Create_chat_invite_link, edit_chat_invite_link, revoke_chat_invite_link methods
2021-05-19 11:30:06 +03:00
ff54f194ad Added: create_chat_invite_link, edit_chat_invite_link, revoke_chat_invite_link methods 2021-05-19 11:22:40 +03:00
11 changed files with 2394 additions and 1087 deletions

35
.github/workflows/setup_python.yml vendored Normal file
View File

@ -0,0 +1,35 @@
# This is a basic workflow to help you get started with Actions
name: Setup
# Controls when the action will run.
on:
# Triggers the workflow on push or pull request events but only for the master branch
push:
branches: [ master ]
pull_request:
branches: [ master ]
# Allows you to run this workflow manually from the Actions tab
#workflow_dispatch:
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
# This workflow contains a single job called "build"
all-setups:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [ '3.6','3.7','3.8','3.9', 'pypy-3.6', 'pypy-3.7' ] #'pypy-3.8', 'pypy-3.9' NOT SUPPORTED NOW
name: ${{ matrix.python-version }} and tests
steps:
- uses: actions/checkout@v2
- name: Setup python
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
architecture: x64
- run: |
pip3 install -r requirements.txt
python setup.py install
cd tests && py.test

1
.gitignore vendored
View File

@ -62,3 +62,4 @@ testMain.py
#VS Code
.vscode/
.DS_Store

View File

@ -146,7 +146,7 @@ Outlined below are some general use cases of the API.
#### Message handlers
A message handler is a function that is decorated with the `message_handler` decorator of a TeleBot instance. Message handlers consist of one or multiple filters.
Each filter much return True for a certain message in order for a message handler to become eligible to handle that message. A message handler is declared in the following way (provided `bot` is an instance of TeleBot):
Each filter must return True for a certain message in order for a message handler to become eligible to handle that message. A message handler is declared in the following way (provided `bot` is an instance of TeleBot):
```python
@bot.message_handler(filters)
def function_name(message):
@ -455,7 +455,7 @@ Refer [Bot Api](https://core.telegram.org/bots/api#messageentity) for extra deta
## Advanced use of the API
### Asynchronous delivery of messages
There exists an implementation of TeleBot which executes all `send_xyz` and the `get_me` functions asynchronously. This can speed up you bot __significantly__, but it has unwanted side effects if used without caution.
There exists an implementation of TeleBot which executes all `send_xyz` and the `get_me` functions asynchronously. This can speed up your bot __significantly__, but it has unwanted side effects if used without caution.
To enable this behaviour, create an instance of AsyncTeleBot instead of TeleBot.
```python
tb = telebot.AsyncTeleBot("TOKEN")
@ -685,5 +685,9 @@ Get help. Discuss. Chat.
* [BarnameKon](https://t.me/BarnameKonBot) by [Anvaari](https://github.com/anvaari). This Bot make "Add to google calendar" link for your events. It give information about event and return link. It work for Jalali calendar and in Tehran Time. [Source code](https://github.com/anvaari/BarnameKon)
* [Price Tracker](https://t.me/trackokbot) by [@barbax7](https://github.com/barbax7). This bot tracks amazon.it product's prices the user is interested to and notify him when one price go down.
* [Torrent Hunt](https://t.me/torrenthuntbot) by [@Hemantapkh](https://github.com/hemantapkh/torrenthunt). Torrent Hunt bot is a multilingual bot with inline mode support to search and explore torrents from 1337x.to.
* Translator bot by [Areeg Fahad (source)](https://github.com/AREEG94FAHAD/translate_text_bot). This bot can be use to translate texts.
* Digital Cryptocurrency bot by [Areeg Fahad (source)](https://github.com/AREEG94FAHAD/currencies_bot). With this bot, you can now monitor the prices of more than 12 digital Cryptocurrency.
* [Anti-Tracking Bot](https://t.me/AntiTrackingBot) by [Leon Heess (source)](https://github.com/leonheess/AntiTrackingBot). Send any link, and the bot tries its best to remove all tracking from the link you sent.
* [Developer Bot](https://t.me/IndDeveloper_bot) by [Vishal Singh](https://github.com/vishal2376) [(source code)](https://github.com/vishal2376/telegram-bot) This telegram bot can do tasks like GitHub search & clone,provide c++ learning resources ,Stackoverflow search, Codeforces(profile visualizer,random problems)
**Want to have your bot listed here? Just make a pull request.**

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
import time
from datetime import datetime
from typing import Dict
try:
import ujson as json
@ -68,6 +69,8 @@ def _make_request(token, method_name, method='get', params=None, files=None):
: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:
request_url = API_URL.format(token, method_name)
else:
@ -166,6 +169,16 @@ def get_me(token):
return _make_request(token, method_url)
def log_out(token):
method_url = r'logOut'
return _make_request(token, method_url)
def close(token):
method_url = r'close'
return _make_request(token, method_url)
def get_file(token, file_id):
method_url = r'getFile'
return _make_request(token, method_url, params={'file_id': file_id})
@ -194,7 +207,8 @@ def download_file(token, file_path):
def send_message(
token, chat_id, text,
disable_web_page_preview=None, reply_to_message_id=None, reply_markup=None,
parse_mode=None, disable_notification=None, timeout=None):
parse_mode=None, disable_notification=None, timeout=None,
entities=None, allow_sending_without_reply=None):
"""
Use this method to send text messages. On success, the sent Message is returned.
:param token:
@ -206,6 +220,8 @@ def send_message(
:param parse_mode:
:param disable_notification:
:param timeout:
:param entities:
:param allow_sending_without_reply:
:return:
"""
method_url = r'sendMessage'
@ -222,6 +238,10 @@ def send_message(
payload['disable_notification'] = disable_notification
if timeout:
payload['connect-timeout'] = timeout
if entities:
payload['entities'] = json.dumps(types.MessageEntity.to_list_of_dicts(entities))
if allow_sending_without_reply is not None:
payload['allow_sending_without_reply'] = allow_sending_without_reply
return _make_request(token, method_url, params=payload, method='post')
@ -315,6 +335,18 @@ def get_chat_members_count(token, chat_id):
return _make_request(token, method_url, params=payload)
def set_sticker_set_thumb(token, name, user_id, thumb):
method_url = r'setStickerSetThumb'
payload = {'name': name, 'user_id': user_id}
files = {}
if thumb:
if not isinstance(thumb, str):
files['thumb'] = thumb
else:
payload['thumb'] = thumb
return _make_request(token, method_url, params=payload, files=files or None)
def set_chat_sticker_set(token, chat_id, sticker_set_name):
method_url = r'setChatStickerSet'
payload = {'chat_id': chat_id, 'sticker_set_name': sticker_set_name}
@ -372,7 +404,7 @@ def copy_message(token, chat_id, from_chat_id, message_id, caption=None, parse_m
def send_dice(
token, chat_id,
emoji=None, disable_notification=None, reply_to_message_id=None,
reply_markup=None, timeout=None):
reply_markup=None, timeout=None, allow_sending_without_reply=None):
method_url = r'sendDice'
payload = {'chat_id': chat_id}
if emoji:
@ -385,13 +417,16 @@ def send_dice(
payload['reply_markup'] = _convert_markup(reply_markup)
if timeout:
payload['connect-timeout'] = timeout
if allow_sending_without_reply is not None:
payload['allow_sending_without_reply'] = allow_sending_without_reply
return _make_request(token, method_url, params=payload)
def send_photo(
token, chat_id, photo,
caption=None, reply_to_message_id=None, reply_markup=None,
parse_mode=None, disable_notification=None, timeout=None):
parse_mode=None, disable_notification=None, timeout=None,
caption_entities=None, allow_sending_without_reply=None):
method_url = r'sendPhoto'
payload = {'chat_id': chat_id}
files = None
@ -413,13 +448,17 @@ def send_photo(
payload['disable_notification'] = disable_notification
if timeout:
payload['connect-timeout'] = timeout
if caption_entities:
payload['caption_entities'] = json.dumps(types.MessageEntity.to_list_of_dicts(caption_entities))
if allow_sending_without_reply is not None:
payload['allow_sending_without_reply'] = allow_sending_without_reply
return _make_request(token, method_url, params=payload, files=files, method='post')
def send_media_group(
token, chat_id, media,
disable_notification=None, reply_to_message_id=None,
timeout=None):
timeout=None, allow_sending_without_reply=None):
method_url = r'sendMediaGroup'
media_json, files = convert_input_media_array(media)
payload = {'chat_id': chat_id, 'media': media_json}
@ -429,6 +468,8 @@ def send_media_group(
payload['reply_to_message_id'] = reply_to_message_id
if timeout:
payload['connect-timeout'] = timeout
if allow_sending_without_reply is not None:
payload['allow_sending_without_reply'] = allow_sending_without_reply
return _make_request(
token, method_url, params=payload,
method='post' if files else 'get',
@ -437,14 +478,24 @@ def send_media_group(
def send_location(
token, chat_id, latitude, longitude,
live_period=None, reply_to_message_id=None, reply_markup=None,
disable_notification=None, timeout=None):
live_period=None, reply_to_message_id=None,
reply_markup=None, disable_notification=None,
timeout=None, horizontal_accuracy=None, heading=None,
proximity_alert_radius=None, allow_sending_without_reply=None):
method_url = r'sendLocation'
payload = {'chat_id': chat_id, 'latitude': latitude, 'longitude': longitude}
if live_period:
payload['live_period'] = live_period
if horizontal_accuracy:
payload['horizontal_accuracy'] = horizontal_accuracy
if heading:
payload['heading'] = heading
if proximity_alert_radius:
payload['proximity_alert_radius'] = proximity_alert_radius
if reply_to_message_id:
payload['reply_to_message_id'] = reply_to_message_id
if allow_sending_without_reply is not None:
payload['allow_sending_without_reply'] = allow_sending_without_reply
if reply_markup:
payload['reply_markup'] = _convert_markup(reply_markup)
if disable_notification is not None:
@ -454,14 +505,22 @@ def send_location(
return _make_request(token, method_url, params=payload)
def edit_message_live_location(token, latitude, longitude, chat_id=None, message_id=None,
inline_message_id=None, reply_markup=None, timeout=None):
def edit_message_live_location(
token, latitude, longitude, chat_id=None, message_id=None,
inline_message_id=None, reply_markup=None, timeout=None,
horizontal_accuracy=None, heading=None, proximity_alert_radius=None):
method_url = r'editMessageLiveLocation'
payload = {'latitude': latitude, 'longitude': longitude}
if chat_id:
payload['chat_id'] = chat_id
if message_id:
payload['message_id'] = message_id
if horizontal_accuracy:
payload['horizontal_accuracy'] = horizontal_accuracy
if heading:
payload['heading'] = heading
if proximity_alert_radius:
payload['proximity_alert_radius'] = proximity_alert_radius
if inline_message_id:
payload['inline_message_id'] = inline_message_id
if reply_markup:
@ -492,7 +551,9 @@ def stop_message_live_location(
def send_venue(
token, chat_id, latitude, longitude, title, address,
foursquare_id=None, foursquare_type=None, disable_notification=None,
reply_to_message_id=None, reply_markup=None, timeout=None):
reply_to_message_id=None, reply_markup=None, timeout=None,
allow_sending_without_reply=None, google_place_id=None,
google_place_type=None):
method_url = r'sendVenue'
payload = {'chat_id': chat_id, 'latitude': latitude, 'longitude': longitude, 'title': title, 'address': address}
if foursquare_id:
@ -507,12 +568,19 @@ def send_venue(
payload['reply_markup'] = _convert_markup(reply_markup)
if timeout:
payload['connect-timeout'] = timeout
if allow_sending_without_reply is not None:
payload['allow_sending_without_reply'] = allow_sending_without_reply
if google_place_id:
payload['google_place_id'] = google_place_id
if google_place_type:
payload['google_place_type'] = google_place_type
return _make_request(token, method_url, params=payload)
def send_contact(
token, chat_id, phone_number, first_name, last_name=None, vcard=None,
disable_notification=None, reply_to_message_id=None, reply_markup=None, timeout=None):
disable_notification=None, reply_to_message_id=None, reply_markup=None, timeout=None,
allow_sending_without_reply=None):
method_url = r'sendContact'
payload = {'chat_id': chat_id, 'phone_number': phone_number, 'first_name': first_name}
if last_name:
@ -527,6 +595,8 @@ def send_contact(
payload['reply_markup'] = _convert_markup(reply_markup)
if timeout:
payload['connect-timeout'] = timeout
if allow_sending_without_reply is not None:
payload['allow_sending_without_reply'] = allow_sending_without_reply
return _make_request(token, method_url, params=payload)
@ -539,7 +609,8 @@ def send_chat_action(token, chat_id, action, timeout=None):
def send_video(token, chat_id, data, duration=None, caption=None, reply_to_message_id=None, reply_markup=None,
parse_mode=None, supports_streaming=None, disable_notification=None, timeout=None, thumb=None, width=None, height=None):
parse_mode=None, supports_streaming=None, disable_notification=None, timeout=None,
thumb=None, width=None, height=None, caption_entities=None, allow_sending_without_reply=None):
method_url = r'sendVideo'
payload = {'chat_id': chat_id}
files = None
@ -575,11 +646,17 @@ def send_video(token, chat_id, data, duration=None, caption=None, reply_to_messa
payload['width'] = width
if height:
payload['height'] = height
if caption_entities:
payload['caption_entities'] = json.dumps(types.MessageEntity.to_list_of_dicts(caption_entities))
if allow_sending_without_reply is not None:
payload['allow_sending_without_reply'] = allow_sending_without_reply
return _make_request(token, method_url, params=payload, files=files, method='post')
def send_animation(token, chat_id, data, duration=None, caption=None, reply_to_message_id=None, reply_markup=None,
parse_mode=None, disable_notification=None, timeout=None, thumb=None):
def send_animation(
token, chat_id, data, duration=None, caption=None, reply_to_message_id=None, reply_markup=None,
parse_mode=None, disable_notification=None, timeout=None, thumb=None, caption_entities=None,
allow_sending_without_reply=None):
method_url = r'sendAnimation'
payload = {'chat_id': chat_id}
files = None
@ -609,11 +686,16 @@ def send_animation(token, chat_id, data, duration=None, caption=None, reply_to_m
files = {'thumb': thumb}
else:
payload['thumb'] = thumb
if caption_entities:
payload['caption_entities'] = json.dumps(types.MessageEntity.to_list_of_dicts(caption_entities))
if allow_sending_without_reply is not None:
payload['allow_sending_without_reply'] = allow_sending_without_reply
return _make_request(token, method_url, params=payload, files=files, method='post')
def send_voice(token, chat_id, voice, caption=None, duration=None, reply_to_message_id=None, reply_markup=None,
parse_mode=None, disable_notification=None, timeout=None):
parse_mode=None, disable_notification=None, timeout=None, caption_entities=None,
allow_sending_without_reply=None):
method_url = r'sendVoice'
payload = {'chat_id': chat_id}
files = None
@ -635,11 +717,15 @@ def send_voice(token, chat_id, voice, caption=None, duration=None, reply_to_mess
payload['disable_notification'] = disable_notification
if timeout:
payload['connect-timeout'] = timeout
if caption_entities:
payload['caption_entities'] = json.dumps(types.MessageEntity.to_list_of_dicts(caption_entities))
if allow_sending_without_reply is not None:
payload['allow_sending_without_reply'] = allow_sending_without_reply
return _make_request(token, method_url, params=payload, files=files, method='post')
def send_video_note(token, chat_id, data, duration=None, length=None, reply_to_message_id=None, reply_markup=None,
disable_notification=None, timeout=None, thumb=None):
disable_notification=None, timeout=None, thumb=None, allow_sending_without_reply=None):
method_url = r'sendVideoNote'
payload = {'chat_id': chat_id}
files = None
@ -669,11 +755,14 @@ def send_video_note(token, chat_id, data, duration=None, length=None, reply_to_m
files = {'thumb': thumb}
else:
payload['thumb'] = thumb
if allow_sending_without_reply is not None:
payload['allow_sending_without_reply'] = allow_sending_without_reply
return _make_request(token, method_url, params=payload, files=files, method='post')
def send_audio(token, chat_id, audio, caption=None, duration=None, performer=None, title=None, reply_to_message_id=None,
reply_markup=None, parse_mode=None, disable_notification=None, timeout=None, thumb=None):
reply_markup=None, parse_mode=None, disable_notification=None, timeout=None, thumb=None,
caption_entities=None, allow_sending_without_reply=None):
method_url = r'sendAudio'
payload = {'chat_id': chat_id}
files = None
@ -707,11 +796,16 @@ def send_audio(token, chat_id, audio, caption=None, duration=None, performer=Non
files = {'thumb': thumb}
else:
payload['thumb'] = thumb
if caption_entities:
payload['caption_entities'] = json.dumps(types.MessageEntity.to_list_of_dicts(caption_entities))
if allow_sending_without_reply is not None:
payload['allow_sending_without_reply'] = allow_sending_without_reply
return _make_request(token, method_url, params=payload, files=files, method='post')
def send_data(token, chat_id, data, data_type, reply_to_message_id=None, reply_markup=None, parse_mode=None,
disable_notification=None, timeout=None, caption=None, thumb=None):
disable_notification=None, timeout=None, caption=None, thumb=None, caption_entities=None,
allow_sending_without_reply=None, disable_content_type_detection=None):
method_url = get_method_by_type(data_type)
payload = {'chat_id': chat_id}
files = None
@ -739,6 +833,12 @@ def send_data(token, chat_id, data, data_type, reply_to_message_id=None, reply_m
files = {'thumb': thumb}
else:
payload['thumb'] = thumb
if caption_entities:
payload['caption_entities'] = json.dumps(types.MessageEntity.to_list_of_dicts(caption_entities))
if allow_sending_without_reply is not None:
payload['allow_sending_without_reply'] = allow_sending_without_reply
if method_url == 'sendDocument' and disable_content_type_detection is not None:
payload['disable_content_type_detection'] = disable_content_type_detection
return _make_request(token, method_url, params=payload, files=files, method='post')
@ -749,13 +849,15 @@ def get_method_by_type(data_type):
return r'sendSticker'
def kick_chat_member(token, chat_id, user_id, until_date=None):
def kick_chat_member(token, chat_id, user_id, until_date=None, revoke_messages=None):
method_url = 'kickChatMember'
payload = {'chat_id': chat_id, 'user_id': user_id}
if isinstance(until_date, datetime):
payload['until_date'] = until_date.timestamp()
else:
payload['until_date'] = until_date
if revoke_messages is not None:
payload['revoke_messages'] = revoke_messages
return _make_request(token, method_url, params=payload, method='post')
@ -804,7 +906,8 @@ def restrict_chat_member(
def promote_chat_member(
token, chat_id, user_id, can_change_info=None, can_post_messages=None,
can_edit_messages=None, can_delete_messages=None, can_invite_users=None,
can_restrict_members=None, can_pin_messages=None, can_promote_members=None):
can_restrict_members=None, can_pin_messages=None, can_promote_members=None,
is_anonymous=None, can_manage_chat=None, can_manage_voice_chats=None):
method_url = 'promoteChatMember'
payload = {'chat_id': chat_id, 'user_id': user_id}
if can_change_info is not None:
@ -823,6 +926,12 @@ def promote_chat_member(
payload['can_pin_messages'] = can_pin_messages
if can_promote_members is not None:
payload['can_promote_members'] = can_promote_members
if is_anonymous is not None:
payload['is_anonymous'] = is_anonymous
if can_manage_chat is not None:
payload['can_manage_chat'] = can_manage_chat
if can_manage_voice_chats is not None:
payload['can_manage_voice_chats'] = can_manage_voice_chats
return _make_request(token, method_url, params=payload, method='post')
@ -843,6 +952,50 @@ def set_chat_permissions(token, chat_id, permissions):
return _make_request(token, method_url, params=payload, method='post')
def create_chat_invite_link(token, chat_id, expire_date, member_limit):
method_url = 'createChatInviteLink'
payload = {
'chat_id': chat_id
}
if expire_date is not None:
payload['expire_date'] = expire_date
if isinstance(payload['expire_date'], datetime):
payload['expire_date'] = payload['expire_date'].timestamp()
if member_limit is not None:
payload['member_limit'] = member_limit
return _make_request(token, method_url, params=payload, method='post')
def edit_chat_invite_link(token, chat_id, invite_link, expire_date, member_limit):
method_url = 'editChatInviteLink'
payload = {
'chat_id': chat_id,
'invite_link': invite_link
}
if expire_date is not None:
payload['expire_date'] = expire_date
if isinstance(payload['expire_date'], datetime):
payload['expire_date'] = payload['expire_date'].timestamp()
if member_limit is not None:
payload['member_limit'] = member_limit
return _make_request(token, method_url, params=payload, method='post')
def revoke_chat_invite_link(token, chat_id, invite_link):
method_url = 'revokeChatInviteLink'
payload = {
'chat_id': chat_id,
'invite_link': invite_link
}
return _make_request(token, method_url, params=payload, method='post')
def export_chat_invite_link(token, chat_id):
method_url = 'exportChatInviteLink'
payload = {'chat_id': chat_id}
@ -874,9 +1027,33 @@ def set_chat_title(token, chat_id, title):
return _make_request(token, method_url, params=payload, method='post')
def set_my_commands(token, commands):
def get_my_commands(token, scope, language_code):
method_url = r'getMyCommands'
payload = {}
if scope is not None:
payload['scope'] = scope.to_json()
if language_code is not None:
payload['language_code'] = language_code
return _make_request(token, method_url, params=payload, method='post')
def set_my_commands(token, commands, scope, language_code):
method_url = r'setMyCommands'
payload = {'commands': _convert_list_json_serializable(commands)}
if scope is not None:
payload['scope'] = scope.to_json()
if language_code is not None:
payload['language_code'] = language_code
return _make_request(token, method_url, params=payload, method='post')
def delete_my_commands(token, scope, language_code):
method_url = r'deleteMyCommands'
payload = {}
if scope is not None:
payload['scope'] = scope.to_json()
if language_code is not None:
payload['language_code'] = language_code
return _make_request(token, method_url, params=payload, method='post')
@ -989,7 +1166,8 @@ def delete_message(token, chat_id, message_id, timeout=None):
def send_game(
token, chat_id, game_short_name,
disable_notification=None, reply_to_message_id=None, reply_markup=None, timeout=None):
disable_notification=None, reply_to_message_id=None, reply_markup=None, timeout=None,
allow_sending_without_reply=None):
method_url = r'sendGame'
payload = {'chat_id': chat_id, 'game_short_name': game_short_name}
if disable_notification is not None:
@ -1000,6 +1178,8 @@ def send_game(
payload['reply_markup'] = _convert_markup(reply_markup)
if timeout:
payload['connect-timeout'] = timeout
if allow_sending_without_reply is not None:
payload['allow_sending_without_reply'] = allow_sending_without_reply
return _make_request(token, method_url, params=payload)
@ -1064,7 +1244,7 @@ def send_invoice(
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,
disable_notification=None, reply_to_message_id=None, reply_markup=None, provider_data=None,
timeout=None):
timeout=None, allow_sending_without_reply=None):
"""
Use this method to send invoices. On success, the sent Message is returned.
:param token: Bot's token (you don't need to fill this)
@ -1092,6 +1272,7 @@ def send_invoice(
: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:
:return:
"""
method_url = r'sendInvoice'
@ -1130,6 +1311,8 @@ def send_invoice(
payload['provider_data'] = provider_data
if timeout:
payload['connect-timeout'] = timeout
if allow_sending_without_reply is not None:
payload['allow_sending_without_reply'] = allow_sending_without_reply
return _make_request(token, method_url, params=payload)
@ -1226,15 +1409,17 @@ def upload_sticker_file(token, user_id, png_sticker):
def create_new_sticker_set(
token, user_id, name, title, png_sticker, emojis,
token, user_id, name, title, emojis, png_sticker, tgs_sticker,
contains_masks=None, mask_position=None):
method_url = 'createNewStickerSet'
payload = {'user_id': user_id, 'name': name, 'title': title, 'emojis': emojis}
stype = 'png_sticker' if png_sticker else 'tgs_sticker'
sticker = png_sticker or tgs_sticker
files = None
if not util.is_string(png_sticker):
files = {'png_sticker': png_sticker}
if not util.is_string(sticker):
files = {stype: sticker}
else:
payload['png_sticker'] = png_sticker
payload[stype] = sticker
if contains_masks is not None:
payload['contains_masks'] = contains_masks
if mask_position:
@ -1242,14 +1427,16 @@ def create_new_sticker_set(
return _make_request(token, method_url, params=payload, files=files, method='post')
def add_sticker_to_set(token, user_id, name, png_sticker, emojis, mask_position):
def add_sticker_to_set(token, user_id, name, emojis, png_sticker, tgs_sticker, mask_position):
method_url = 'addStickerToSet'
payload = {'user_id': user_id, 'name': name, 'emojis': emojis}
stype = 'png_sticker' if png_sticker else 'tgs_sticker'
sticker = png_sticker or tgs_sticker
files = None
if not util.is_string(png_sticker):
files = {'png_sticker': png_sticker}
if not util.is_string(sticker):
files = {stype: sticker}
else:
payload['png_sticker'] = png_sticker
payload[stype] = sticker
if mask_position:
payload['mask_position'] = mask_position.to_json()
return _make_request(token, method_url, params=payload, files=files, method='post')
@ -1272,7 +1459,8 @@ def send_poll(
question, options,
is_anonymous = None, type = None, allows_multiple_answers = None, correct_option_id = None,
explanation = None, explanation_parse_mode=None, open_period = None, close_date = None, is_closed = None,
disable_notifications=False, reply_to_message_id=None, reply_markup=None, timeout=None):
disable_notifications=False, reply_to_message_id=None, allow_sending_without_reply=None,
reply_markup=None, timeout=None, explanation_entities=None):
method_url = r'sendPoll'
payload = {
'chat_id': str(chat_id),
@ -1305,10 +1493,15 @@ def send_poll(
payload['disable_notification'] = disable_notifications
if reply_to_message_id is not None:
payload['reply_to_message_id'] = reply_to_message_id
if allow_sending_without_reply is not None:
payload['allow_sending_without_reply'] = allow_sending_without_reply
if reply_markup is not None:
payload['reply_markup'] = _convert_markup(reply_markup)
if timeout:
payload['connect-timeout'] = timeout
if explanation_entities:
payload['explanation_entities'] = json.dumps(
types.MessageEntity.to_list_of_dicts(explanation_entities))
return _make_request(token, method_url, params=payload)

File diff suppressed because it is too large Load Diff

View File

@ -6,10 +6,13 @@ import threading
import traceback
import warnings
import functools
from typing import Any, List, Dict, Union
import queue as Queue
import logging
from telebot import types
try:
from PIL import Image
from io import BytesIO
@ -17,6 +20,8 @@ try:
except:
pil_imported = False
MAX_MESSAGE_LENGTH = 4096
logger = logging.getLogger('TeleBot')
thread_local = threading.local()
@ -28,7 +33,15 @@ content_type_media = [
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'
'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"
]
class WorkerThread(threading.Thread):
@ -165,15 +178,19 @@ def async_dec():
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()
@ -184,17 +201,18 @@ def pil_image_to_file(image, extension='JPEG', quality='web_low'):
else:
raise RuntimeError('PIL module is not imported')
def is_command(text):
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 None
if text is None: return False
return text.startswith('/')
def extract_command(text):
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.
@ -208,11 +226,28 @@ def extract_command(text):
: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
if text is None: return None
return text.split()[0].split('@')[0][1:] if is_command(text) else None
def split_string(text, chars_per_string):
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.
@ -223,6 +258,106 @@ def split_string(text, chars_per_string):
"""
return [text[i:i + chars_per_string] for i in range(0, len(text), chars_per_string)]
def smart_split(text: str, chars_per_string: int=MAX_MESSAGE_LENGTH) -> List[str]:
"""
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 '&amp;', '<' with '&lt;' and '>' with '&gt;').
:param text: the text to escape
:return: the escaped text
"""
chars = {"&": "&amp;", "<": "&lt;", ">": "&gt"}
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"<a href='tg://user?id={user.id}'>{name}</a>"
+ (f" (<pre>{user.id}</pre>)" 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:
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 = []
for text, kwargs in values.items():
buttons.append(types.InlineKeyboardButton(text=text, **kwargs))
markup.add(*buttons)
return markup
# CREDITS TO http://stackoverflow.com/questions/12317940#answer-12320352
def or_set(self):
self._set()
@ -243,8 +378,10 @@ def orify(e, 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):
@ -263,22 +400,6 @@ def OrEvent(*events):
changed()
return or_event
def extract_arguments(text):
"""
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 per_thread(key, construct_value, reset=False):
if reset or not hasattr(thread_local, key):
@ -287,15 +408,18 @@ def per_thread(key, construct_value, reset=False):
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(func):
"""This is a decorator which can be used to mark functions
as deprecated. It will result in a warning being emitted

View File

@ -1,3 +1,3 @@
# Versions should comply with PEP440.
# This line is parsed in setup.py:
__version__ = '3.7.9'
__version__ = '3.8.0'

View File

@ -62,8 +62,11 @@ def update_type(message):
pre_checkout_query = None
poll = None
poll_answer = None
my_chat_member = None
chat_member = None
return types.Update(1001234038283, message, edited_message, channel_post, edited_channel_post, inline_query,
chosen_inline_result, callback_query, shipping_query, pre_checkout_query, poll, poll_answer)
chosen_inline_result, callback_query, shipping_query, pre_checkout_query, poll, poll_answer,
my_chat_member, chat_member)
@pytest.fixture()
@ -78,9 +81,11 @@ def reply_to_message_update_type(reply_to_message):
pre_checkout_query = None
poll = None
poll_answer = None
my_chat_member = None
chat_member = None
return types.Update(1001234038284, reply_to_message, edited_message, channel_post, edited_channel_post,
inline_query,
chosen_inline_result, callback_query, shipping_query, pre_checkout_query, poll, poll_answer)
inline_query, chosen_inline_result, callback_query, shipping_query, pre_checkout_query,
poll, poll_answer, my_chat_member, chat_member)
def next_handler(message):

View File

@ -6,6 +6,7 @@ sys.path.append('../')
import time
import pytest
import os
from datetime import datetime, timedelta
import telebot
from telebot import types
@ -407,6 +408,23 @@ class TestTeleBot:
cn = tb.get_chat_members_count(GROUP_ID)
assert cn > 1
def test_export_chat_invite_link(self):
tb = telebot.TeleBot(TOKEN)
il = tb.export_chat_invite_link(GROUP_ID)
assert isinstance(il, str)
def test_create_revoke_detailed_chat_invite_link(self):
tb = telebot.TeleBot(TOKEN)
cil = tb.create_chat_invite_link(GROUP_ID,
(datetime.now() + timedelta(minutes=1)).timestamp(), member_limit=5)
assert isinstance(cil.invite_link, str)
assert cil.creator.id == tb.get_me().id
assert isinstance(cil.expire_date, (float, int))
assert cil.member_limit == 5
assert not cil.is_revoked
rcil = tb.revoke_chat_invite_link(GROUP_ID, cil.invite_link)
assert rcil.is_revoked
def test_edit_markup(self):
text = 'CI Test Message'
tb = telebot.TeleBot(TOKEN)
@ -440,8 +458,11 @@ class TestTeleBot:
pre_checkout_query = None
poll = None
poll_answer = None
my_chat_member = None
chat_member = None
return types.Update(-1001234038283, message, edited_message, channel_post, edited_channel_post, inline_query,
chosen_inline_result, callback_query, shipping_query, pre_checkout_query, poll, poll_answer)
chosen_inline_result, callback_query, shipping_query, pre_checkout_query, poll, poll_answer,
my_chat_member, chat_member)
def test_is_string_unicode(self):
s1 = u'string'
@ -525,6 +546,24 @@ class TestTeleBot:
ret_msg = tb.send_document(CHAT_ID, file_data, caption='_italic_', parse_mode='Markdown')
assert ret_msg.caption_entities[0].type == 'italic'
def test_chat_commands(self):
tb = telebot.TeleBot(TOKEN)
command, description, lang = 'command_1', 'description of command 1', 'en'
scope = telebot.types.BotCommandScopeChat(CHAT_ID)
ret_msg = tb.set_my_commands([telebot.types.BotCommand(command, description)], scope, lang)
assert ret_msg is True
ret_msg = tb.get_my_commands(scope, lang)
assert ret_msg[0].command == command
assert ret_msg[0].description == description
ret_msg = tb.delete_my_commands(scope, lang)
assert ret_msg is True
ret_msg = tb.get_my_commands(scope, lang)
assert ret_msg == []
def test_typed_middleware_handler(self):
from telebot import apihelper

View File

@ -210,7 +210,7 @@ def test_json_poll_answer():
poll_answer = types.PollAnswer.de_json(jsonstring)
assert poll_answer.poll_id == '5895675970559410186'
assert isinstance(poll_answer.user, types.User)
assert poll_answer.options_ids == [1]
assert poll_answer.option_ids == [1]
def test_KeyboardButtonPollType():
@ -219,3 +219,24 @@ def test_KeyboardButtonPollType():
json_str = markup.to_json()
assert 'request_poll' in json_str
assert 'quiz' in json_str
def test_json_chat_invite_link():
json_string = r'{"invite_link": "https://t.me/joinchat/z-abCdEFghijKlMn", "creator": {"id": 329343347, "is_bot": false, "first_name": "Test", "username": "test_user", "last_name": "User", "language_code": "en"}, "is_primary": false, "is_revoked": false, "expire_date": 1624119999, "member_limit": 10}'
invite_link = types.ChatInviteLink.de_json(json_string)
assert invite_link.invite_link == 'https://t.me/joinchat/z-abCdEFghijKlMn'
assert isinstance(invite_link.creator, types.User)
assert not invite_link.is_primary
assert not invite_link.is_revoked
assert invite_link.expire_date == 1624119999
assert invite_link.member_limit == 10
def test_chat_member_updated():
json_string = r'{"chat": {"id": -1234567890123, "type": "supergroup", "title": "No Real Group", "username": "NoRealGroup"}, "from": {"id": 133869498, "is_bot": false, "first_name": "Vincent"}, "date": 1624119999, "old_chat_member": {"user": {"id": 77777777, "is_bot": false, "first_name": "Pepe"}, "status": "member"}, "new_chat_member": {"user": {"id": 77777777, "is_bot": false, "first_name": "Pepe"}, "status": "administrator"}}'
cm_updated = types.ChatMemberUpdated.de_json(json_string)
assert cm_updated.chat.id == -1234567890123
assert cm_updated.from_user.id == 133869498
assert cm_updated.date == 1624119999
assert cm_updated.old_chat_member.status == "member"
assert cm_updated.new_chat_member.status == "administrator"