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

Compare commits

...

155 Commits
3.7.8 ... 3.8.3

Author SHA1 Message Date
6e871b8eb1 Merge pull request #1270 from Badiboy/master
Check and update for full compatibility to Bot API up to 5.3
2021-08-18 23:31:15 +03:00
f6359bc32c Readme fix 2021-08-18 23:29:40 +03:00
2bc052ad5a Check and update for full compatibility to Bot API up to 5.3
Pre-release of 4.0.0
2021-08-18 23:27:28 +03:00
022ef6a64c Dependecies clearing 2021-08-18 22:16:30 +03:00
3232811543 Merge pull request #1269 from Badiboy/master
Check and update for full compatibility to Bot API up to 5.0
2021-08-18 22:01:12 +03:00
fabcd93dd7 API update fix 03 2021-08-18 21:57:56 +03:00
8053183cb5 API update fix 02 2021-08-18 19:36:48 +03:00
b2b7d90888 API update fix 01 2021-08-18 19:32:43 +03:00
3e9d73c25d Merge remote-tracking branch 'upstream/master' 2021-08-18 18:52:09 +03:00
d6501ddc0e Check and update for full compatibility to Bot API up to 5.0 2021-08-18 18:47:38 +03:00
e818e3875d Merge pull request #1266 from coder2020official/master
New set of register_xxx_handler functions for dynamic handlers registering.
2021-08-17 16:50:00 +03:00
56cd3971dc Update __init__.py 2021-08-16 22:41:27 +04:00
958ca34e9c Merge pull request #1264 from coder2020official/master
Added skip_pending parameter to polling and infinity_polling in addition to skip_pending in Telebot.Init. Allows skip pending updates for already created Bot instances.
2021-08-16 21:07:41 +03:00
f4ef2366b6 Update skip_updates_example.py 2021-08-16 22:03:17 +04:00
f553960096 Update __init__.py 2021-08-16 22:00:08 +04:00
24ef64456b Update __init__.py 2021-08-16 14:53:00 +04:00
3e7da0fd18 Update skip_updates_example.py 2021-08-16 14:49:45 +04:00
2c0f42b363 Update __init__.py 2021-08-16 14:48:21 +04:00
1e4a6e2125 Update __init__.py 2021-08-15 13:32:11 +04:00
beeb60aab8 skip_updates 2021-08-15 11:40:13 +04:00
5b942a5b31 Merge pull request #1263 from coder2020official/master
Create chat_member_example.py
2021-08-14 17:55:15 +03:00
3e4a6cd702 Create chat_member_example.py 2021-08-14 18:46:45 +04:00
0e369953cb Merge pull request #1261 from Badiboy/master
BotCommandScopeChatMember fix
2021-08-12 15:17:11 +03:00
911e356930 BotCommandScopeChatMember fix 2021-08-12 15:16:04 +03:00
554b39a49a Merge pull request #1257 from AmolDerickSoans/master
Add IPO bot
2021-08-06 14:48:51 +03:00
ea16f35432 Add IPO bot
Listed oneIPO bot created using pyTelegramBotAPI in  section : Bpts using this API
2021-08-06 12:29:00 +05:30
81d94687be Merge pull request #1254 from snikidev/bug/InputInvoiceMessageContent-return-statement
🐛 Bugfix: Add return statement to to_dict() method inside InputInvoiceMessageContent type
2021-08-03 21:03:38 +03:00
4ba4bc18cf add extra space 2021-08-03 17:35:59 +01:00
c117ff2d50 Add return statement to to_dict() method inside InputInvoiceMessageContent 2021-08-03 17:34:29 +01:00
735c224444 Merge pull request #1248 from coder2020official/master
caption_entities in edit_message_caption
2021-07-30 19:11:05 +03:00
81adfd335e UPD 2021-07-30 19:15:37 +05:00
7ebe589b46 Update __init__.py 2021-07-28 23:10:15 +05:00
9c1b19a9e4 upd 2021-07-28 23:06:31 +05:00
02b886465e new filters 2021-07-25 15:46:53 +05:00
2d89ceb745 Merge pull request #1241 from Badiboy/master
Release version 3.8.2
2021-07-21 21:54:29 +03:00
ae8c3252df Release version 3.8.2 2021-07-21 21:53:56 +03:00
7914f71938 Merge pull request #1237 from monosans/comprehension
Replace for loops with comprehensions
2021-07-19 23:40:55 +03:00
097ba9fec2 Replace for loops with comprehensions 2021-07-19 20:03:03 +03:00
d09d9f0c09 Merge pull request #1232 from Badiboy/master
Invoice tips typo fix
2021-07-15 09:27:49 +03:00
29c98b0230 Invoice tips typo fix 2021-07-15 09:27:07 +03:00
2b1db1f1b3 Merge pull request #1231 from vnagornyy/master
Added tip for invoice
2021-07-15 09:23:10 +03:00
fa80b1dba0 Added tip for invoice 2021-07-15 08:56:04 +03:00
b45db584df Merge pull request #1230 from Badiboy/master
Fix worker_pool issue
2021-07-13 22:16:48 +03:00
f52ea635e5 Fix worker_pool issue 2021-07-13 22:09:56 +03:00
9b56afd569 Merge pull request #1229 from Badiboy/master
Fix CallbackQuery issue for games
2021-07-13 20:13:36 +03:00
6fb10e92e4 Fix CallbackQuery issue for games 2021-07-13 20:11:47 +03:00
fcf4d91564 Merge remote-tracking branch 'upstream/master' 2021-07-13 20:00:17 +03:00
38319871e6 Merge pull request #1225 from dannkunt/patch-1
Fix wrong type hint
2021-07-13 11:32:06 +03:00
2d0b092ea4 Fix wrong type hint
call.id gives int
2021-07-10 22:03:31 +03:00
060b8c61bb Merge remote-tracking branch 'upstream/master' 2021-07-09 10:50:53 +03:00
db2accc2f8 Merge pull request #1223 from Badiboy/master
Timeouts in making requests are rethought
2021-07-09 10:50:46 +03:00
798fda4c8a Merge remote-tracking branch 'upstream/master' 2021-07-09 10:50:03 +03:00
2578e48134 Timeouts in making requests are rethought 2021-07-09 10:42:56 +03:00
ac20216a7a Merge pull request #1222 from Badiboy/master
Preserve dict change in de_json routines
2021-07-08 13:43:09 +03:00
beb5a456eb Preserve dict change in Update 2021-07-08 09:35:48 +03:00
41faadd572 Merge pull request #1221 from AndydeCleyre/feature/mentioncolorcodebot
mention colorcodebot as a project using this library
2021-07-08 08:36:22 +03:00
a15016d7d9 mention colorcodebot as a project using this library 2021-07-07 13:00:32 -04:00
47dd84c441 Merge pull request #1216 from SwissCorePy/master
fixed bug
2021-07-01 20:04:37 +03:00
c7b360e982 fixed bug 2021-07-01 18:54:39 +02:00
09041b018f Merge pull request #1215 from SwissCorePy/master
Added the property `difference` to the class ChatMemberUpdated
2021-06-30 17:33:13 +03:00
3a4cf47def Merge branch 'master' of https://github.com/SwissCorePy/pyTelegramBotAPI 2021-06-30 14:16:54 +02:00
56e4f68a83 added the property difference to ChatMemberUpdated 2021-06-30 14:16:38 +02:00
484e7fccbd Merge pull request #1214 from SwissCorePy/master
new deprecated decorator
2021-06-30 15:01:46 +03:00
791d65e95a replaced old deprecated decorator 2021-06-30 13:47:39 +02:00
073d7fb6a7 Update util.py
whoops warn is not optional
2021-06-30 13:11:48 +02:00
a6668397e1 new deprecated decorator
added a new deprecated decorator to util
2021-06-30 13:08:05 +02:00
983d626d87 Merge pull request #1212 from Badiboy/master
Update file_name to visible_file_name in send_document
2021-06-29 13:31:25 +03:00
a4e73a05c6 Update file_name to visible_file_name in send_document 2021-06-29 13:30:01 +03:00
30e304ffb5 Merge pull request #1204 from floydya/file-name-patch
Allows to set visible document file_name on send.
2021-06-29 13:27:44 +03:00
430b34c7a2 Merge pull request #1210 from SwissCorePy/master
README update
2021-06-28 17:15:45 +03:00
b222416fd8 Update README.md 2021-06-28 15:44:49 +02:00
f8110cd046 Update README.md
* Added the new message_handlers
* Added some information about local Bot API Server
* Replaced the split_string with the smart_split function
2021-06-28 15:17:53 +02:00
6bc60f4aa9 Merge pull request #1208 from SwissCorePy/master
get_chat_member_count and ban_chat_member added.
get_chat_members_count and kick_chat_member are marked as deprecated.
2021-06-28 13:09:19 +03:00
b48a445e9f Update __init__.py
updated docstrings
2021-06-28 12:02:40 +02:00
0b383498eb addded logger info for deprecated funcs 2021-06-28 11:59:21 +02:00
2e3b4223a5 Merge pull request #1209 from Badiboy/master
Release 3.8.1 - bugfix
2021-06-28 12:41:38 +03:00
60bb63ab2b Release 3.8.1 - bugfix 2021-06-28 12:41:15 +03:00
0aa7a8a8f6 new 5.3 function names
added the new function names (the previous names are still working) from 5.3 and some other small changes
2021-06-28 09:31:06 +02:00
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
a791ff4e46 Add tests for file sending with name 2021-06-27 11:58:33 +03:00
e56f134a7c Add file_name support to send_document method 2021-06-27 11:38:45 +03:00
38c4c21030 Add file_name argument to send_data method 2021-06-27 11:37:27 +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
f6b967421e Merge pull request #1160 from Badiboy/master
Update version.py
2021-05-15 20:30:27 +03:00
59559199d5 Update version.py 2021-05-15 20:29:58 +03:00
98784c811e Merge pull request #1159 from Badiboy/master
Fix release 3.7.8u1 (update was inconsistent, sorry)
2021-05-15 20:28:51 +03:00
26e5f3d3a8 Fix release 3.7.8u1 2021-05-15 20:27:52 +03:00
fe1f99abdf Merge pull request #1158 from Badiboy/master
send_poll fix of fix
2021-05-15 20:09:49 +03:00
7540a26fb9 send_poll fix of fix
Previous update was inconsistent, sorry.
2021-05-15 20:08:51 +03:00
15 changed files with 3231 additions and 1520 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

121
README.md
View File

@ -1,12 +1,17 @@
# <p align="center">pyTelegramBotAPI
<p align="center">A simple, but extensible Python implementation for the <a href="https://core.telegram.org/bots/api">Telegram Bot API</a>.
[![PyPi Package Version](https://img.shields.io/pypi/v/pyTelegramBotAPI.svg)](https://pypi.python.org/pypi/pyTelegramBotAPI)
[![Supported Python versions](https://img.shields.io/pypi/pyversions/pyTelegramBotAPI.svg)](https://pypi.python.org/pypi/pyTelegramBotAPI)
[![Build Status](https://travis-ci.org/eternnoir/pyTelegramBotAPI.svg?branch=master)](https://travis-ci.org/eternnoir/pyTelegramBotAPI)
[![PyPi downloads](https://img.shields.io/pypi/dm/pyTelegramBotAPI.svg)](https://pypi.org/project/pyTelegramBotAPI/)
# <p align="center">pyTelegramBotAPI
<p align="center">A simple, but extensible Python implementation for the <a href="https://core.telegram.org/bots/api">Telegram Bot API</a>.
## <p align="center">Supported Bot API version: <a href="https://core.telegram.org/bots/api#june-25-2021">5.3</a>!
##Contents
* [Getting started.](#getting-started)
* [Writing your first bot](#writing-your-first-bot)
* [Prerequisites](#prerequisites)
@ -146,7 +151,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):
@ -208,26 +213,59 @@ def send_something(message):
**Important: all handlers are tested in the order in which they were declared**
#### Edited Message handlers
@bot.edited_message_handler(filters)
Handle edited messages
`@bot.edited_message_handler(filters) # <- passes a Message type object to your function`
#### channel_post_handler
@bot.channel_post_handler(filters)
Handle channel post messages
`@bot.channel_post_handler(filters) # <- passes a Message type object to your function`
#### edited_channel_post_handler
@bot.edited_channel_post_handler(filters)
Handle edited channel post messages
`@bot.edited_channel_post_handler(filters) # <- passes a Message type object to your function`
#### Callback Query Handler
In bot2.0 update. You can get `callback_query` in update object. In telebot use `callback_query_handler` to process callback queries.
Handle callback queries
```python
@bot.callback_query_handler(func=lambda call: True)
def test_callback(call):
def test_callback(call): # <- passes a CallbackQuery type object to your function
logger.info(call)
```
#### Inline Handler
Handle inline queries
`@bot.inline_handler() # <- passes a InlineQuery type object to your function`
#### Chosen Inline Handler
Handle chosen inline results
`@bot.chosen_inline_handler() # <- passes a ChosenInlineResult type object to your function`
#### Shipping Query Handler
Handle shipping queries
`@bot.shipping_query_handeler() # <- passes a ShippingQuery type object to your function`
#### Pre Checkout Query Handler
Handle pre checkoupt queries
`@bot.pre_checkout_query_handler() # <- passes a PreCheckoutQuery type object to your function`
#### Poll Handler
Handle poll updates
`@bot.poll_handler() # <- passes a Poll type object to your function`
#### Poll Answer Handler
Handle poll answers
`@bot.poll_answer_handler() # <- passes a PollAnswer type object to your function`
#### My Chat Member Handler
Handle updates of a the bot's member status in a chat
`@bot.my_chat_member_handler() # <- passes a ChatMemberUpdated type object to your function`
#### Chat Member Handler
Handle updates of a chat member's status in a chat
`@bot.chat_member_handler() # <- passes a ChatMemberUpdated type object to your function`
*Note: "chat_member" updates are not requested by default. If you want to allow all update types, set `allowed_updates` in `bot.polling()` / `bot.infinity_polling()` to `util.update_types`*
#### Middleware Handler
A middleware handler is a function that allows you to modify requests or the bot context as they pass through the
@ -261,6 +299,7 @@ tb = telebot.TeleBot(TOKEN) #create a new Telegram Bot object
# - interval: True/False (default False) - The interval between polling requests
# Note: Editing this parameter harms the bot's response time
# - timeout: integer (default 20) - Timeout in seconds for long polling.
# - allowed_updates: List of Strings (default None) - List of update types to request
tb.polling(none_stop=False, interval=0, timeout=20)
# getMe
@ -454,8 +493,20 @@ Refer [Bot Api](https://core.telegram.org/bots/api#messageentity) for extra deta
## Advanced use of the API
### Using local Bot API Sever
Since version 5.0 of the Bot API, you have the possibility to run your own [Local Bot API Server](https://core.telegram.org/bots/api#using-a-local-bot-api-server).
pyTelegramBotAPI also supports this feature.
```python
from telebot import apihelper
apihelper.API_URL = "http://localhost:4200/bot{0}/{1}"
```
**Important: Like described [here](https://core.telegram.org/bots/api#logout), you have to log out your bot from the Telegram server before switching to your local API server. in pyTelegramBotAPI use `bot.log_out()`**
*Note: 4200 is an example port*
### 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")
@ -484,6 +535,19 @@ large_text = open("large_text.txt", "rb").read()
# Split the text each 3000 characters.
# split_string returns a list with the splitted text.
splitted_text = util.split_string(large_text, 3000)
for text in splitted_text:
tb.send_message(chat_id, text)
```
Or you can use the new `smart_split` function to get more meaningful substrings:
```python
from telebot import util
large_text = open("large_text.txt", "rb").read()
# Splits one string into multiple strings, with a maximum amount of `chars_per_string` (max. 4096)
# Splits by last '\n', '. ' or ' ' in exactly this priority.
# smart_split returns a list with the splitted text.
splitted_text = util.smart_split(large_text, chars_per_string=3000)
for text in splitted_text:
tb.send_message(chat_id, text)
```
@ -532,7 +596,7 @@ You can use proxy for request. `apihelper.proxy` object will use by call `reques
```python
from telebot import apihelper
apihelper.proxy = {'http':'http://10.10.1.10:3128'}
apihelper.proxy = {'http':'http://127.0.0.1:3128'}
```
If you want to use socket5 proxy you need install dependency `pip install requests[socks]` and make sure, that you have the latest version of `gunicorn`, `PySocks`, `pyTelegramBotAPI`, `requests` and `urllib3`.
@ -544,15 +608,20 @@ apihelper.proxy = {'https':'socks5://userproxy:password@proxy_address:port'}
## API conformance
_Checking is in progress..._
[Bot API 4.5](https://core.telegram.org/bots/api-changelog#december-31-2019) _- To be checked..._
* [Bot API 5.3](https://core.telegram.org/bots/api#june-25-2021) - ChatMemberXXX classes are full copies of ChatMember
* ✔ [Bot API 5.2](https://core.telegram.org/bots/api#april-26-2021)
* ✔ [Bot API 5.1](https://core.telegram.org/bots/api#march-9-2021)
* ✔ [Bot API 5.0](https://core.telegram.org/bots/api-changelog#november-4-2020)
* ✔ [Bot API 4.9](https://core.telegram.org/bots/api-changelog#june-4-2020)
* ✔ [Bot API 4.8](https://core.telegram.org/bots/api-changelog#april-24-2020)
* ✔ [Bot API 4.7](https://core.telegram.org/bots/api-changelog#march-30-2020)
* ✔ [Bot API 4.6](https://core.telegram.org/bots/api-changelog#january-23-2020)
* [Bot API 4.5](https://core.telegram.org/bots/api-changelog#december-31-2019) - No nested MessageEntities and Markdown2 support
* ✔ [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.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 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)
@ -632,6 +701,7 @@ Get help. Discuss. Chat.
* [League of Legends bot](https://telegram.me/League_of_Legends_bot) ([source](https://github.com/i32ropie/lol)) by *i32ropie*
* [NeoBot](https://github.com/neoranger/NeoBot) by [@NeoRanger](https://github.com/neoranger)
* [TagAlertBot](https://github.com/pitasi/TagAlertBot) by *pitasi*
* [ColorCodeBot](https://t.me/colorcodebot) ([source](https://github.com/andydecleyre/colorcodebot)) - Share code snippets as beautifully syntax-highlighted HTML and/or images.
* [ComedoresUGRbot](http://telegram.me/ComedoresUGRbot) ([source](https://github.com/alejandrocq/ComedoresUGRbot)) by [*alejandrocq*](https://github.com/alejandrocq) - Telegram bot to check the menu of Universidad de Granada dining hall.
* [picpingbot](https://web.telegram.org/#/im?p=%40picpingbot) - Fun anonymous photo exchange by Boogie Muffin.
* [TheZigZagProject](https://github.com/WebShark025/TheZigZagProject) - The 'All In One' bot for Telegram! by WebShark025
@ -685,5 +755,10 @@ 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)
* [oneIPO bot](https://github.com/aaditya2200/IPO-proj) by [Aadithya](https://github.com/aaditya2200) & [Amol Soans](https://github.com/AmolDerickSoans) This Telegram bot provides live updates , data and documents on current and upcoming IPOs(Initial Public Offerings)
**Want to have your bot listed here? Just make a pull request.**

View File

@ -0,0 +1,33 @@
import telebot
from telebot import types,util
bot = telebot.TeleBot("token")
#chat_member_handler. When status changes, telegram gives update. check status from old_chat_member and new_chat_member.
@bot.chat_member_handler()
def chat_m(message: types.ChatMemberUpdated):
old = message.old_chat_member
new = message.new_chat_member
if new.status == "member":
bot.send_message(message.chat.id,"Hello {name}!".format(name=new.user.first_name)) # Welcome message
#if bot is added to group, this handler will work
@bot.my_chat_member_handler()
def my_chat_m(message: types.ChatMemberUpdated):
old = message.old_chat_member
new = message.new_chat_member
if new.status == "member":
bot.send_message(message.chat.id,"Somebody added me to group") # Welcome message, if bot was added to group
bot.leave_chat(message.chat.id)
#content_Type_service is:
#'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'
# this handler deletes service messages
@bot.message_handler(content_types=util.content_type_service)
def delall(message: types.Message):
bot.delete_message(message.chat.id,message.message_id)
bot.polling(allowed_updates=util.update_types)

View File

@ -0,0 +1,13 @@
import telebot
bot = telebot.TeleBot("TOKEN")
@bot.message_handler(commands=['start', 'help'])
def send_welcome(message):
bot.reply_to(message, "Howdy, how are you doing?")
@bot.message_handler(func=lambda message: True)
def echo_all(message):
bot.reply_to(message, message.text)
bot.polling(skip_pending=True)# Skip pending skips old updates

View File

@ -1,4 +1,3 @@
py==1.10.0
pytest==3.0.2
requests==2.20.0
wheel==0.24.0

View File

@ -25,6 +25,7 @@ setup(name='pyTelegramBotAPI',
install_requires=['requests'],
extras_require={
'json': 'ujson',
'PIL': 'Pillow',
'redis': 'redis>=3.4.1'
},
classifiers=[

File diff suppressed because it is too large Load Diff

View File

@ -11,6 +11,7 @@ import requests
from requests.exceptions import HTTPError, ConnectionError, Timeout
try:
# noinspection PyUnresolvedReferences
from requests.packages.urllib3 import fields
format_header_param = fields.format_header_param
except ImportError:
@ -27,8 +28,11 @@ session = None
API_URL = None
FILE_URL = None
CONNECT_TIMEOUT = 3.5
READ_TIMEOUT = 9999
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 = None # In seconds. None - live forever, 0 - one-time
RETRY_ON_ERROR = False
@ -44,6 +48,7 @@ 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
@ -68,7 +73,10 @@ 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:
# noinspection PyUnresolvedReferences
request_url = API_URL.format(token, method_name)
else:
request_url = "https://api.telegram.org/bot{0}/{1}".format(token, method_name)
@ -80,16 +88,21 @@ def _make_request(token, method_name, method='get', params=None, files=None):
fields.format_header_param = _no_encode(format_header_param)
if params:
if 'timeout' in params:
read_timeout = params.pop('timeout') + 10
if 'connect-timeout' in params:
connect_timeout = params.pop('connect-timeout') + 10
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
# The only function with timeout on the BOT API side
params['timeout'] = params.pop('long_polling_timeout')
# Long polling hangs for given time. Read timeout should be greater that long_polling_timeout
read_timeout = max(params['timeout'] + 10, read_timeout)
# 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:
@ -166,6 +179,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})
@ -175,13 +198,15 @@ def get_file_url(token, file_id):
if FILE_URL is None:
return "https://api.telegram.org/file/bot{0}/{1}".format(token, get_file(token, file_id)['file_path'])
else:
# noinspection PyUnresolvedReferences
return FILE_URL.format(token, get_file(token, file_id)['file_path'])
def download_file(token, file_path):
if FILE_URL is None:
url = "https://api.telegram.org/file/bot{0}/{1}".format(token, file_path)
else:
# noinspection PyUnresolvedReferences
url = FILE_URL.format(token, file_path)
result = _get_req_session().get(url, proxies=proxy)
@ -194,7 +219,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 +232,8 @@ def send_message(
:param parse_mode:
:param disable_notification:
:param timeout:
:param entities:
:param allow_sending_without_reply:
:return:
"""
method_url = r'sendMessage'
@ -221,7 +249,11 @@ def send_message(
if disable_notification is not None:
payload['disable_notification'] = disable_notification
if timeout:
payload['connect-timeout'] = timeout
payload['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')
@ -243,7 +275,7 @@ def set_webhook(token, url=None, certificate=None, max_connections=None, allowed
if drop_pending_updates is not None: # Any bool value should pass
payload['drop_pending_updates'] = drop_pending_updates
if timeout:
payload['connect-timeout'] = timeout
payload['timeout'] = timeout
return _make_request(token, method_url, params=payload, files=files)
@ -253,7 +285,7 @@ def delete_webhook(token, drop_pending_updates=None, timeout=None):
if drop_pending_updates is not None: # Any bool value should pass
payload['drop_pending_updates'] = drop_pending_updates
if timeout:
payload['connect-timeout'] = timeout
payload['timeout'] = timeout
return _make_request(token, method_url, params=payload)
@ -261,7 +293,7 @@ def get_webhook_info(token, timeout=None):
method_url = r'getWebhookInfo'
payload = {}
if timeout:
payload['connect-timeout'] = timeout
payload['timeout'] = timeout
return _make_request(token, method_url, params=payload)
@ -273,9 +305,8 @@ def get_updates(token, offset=None, limit=None, timeout=None, allowed_updates=No
if limit:
payload['limit'] = limit
if timeout:
payload['connect-timeout'] = timeout
if long_polling_timeout:
payload['long_polling_timeout'] = long_polling_timeout
payload['timeout'] = timeout
payload['long_polling_timeout'] = long_polling_timeout if long_polling_timeout else LONG_POLLING_TIMEOUT
if allowed_updates is not None: # Empty lists should pass
payload['allowed_updates'] = json.dumps(allowed_updates)
return _make_request(token, method_url, params=payload)
@ -309,12 +340,24 @@ def get_chat_administrators(token, chat_id):
return _make_request(token, method_url, params=payload)
def get_chat_members_count(token, chat_id):
method_url = r'getChatMembersCount'
def get_chat_member_count(token, chat_id):
method_url = r'getChatMemberCount'
payload = {'chat_id': 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}
@ -341,7 +384,7 @@ def forward_message(
if disable_notification is not None:
payload['disable_notification'] = disable_notification
if timeout:
payload['connect-timeout'] = timeout
payload['timeout'] = timeout
return _make_request(token, method_url, params=payload)
@ -365,14 +408,14 @@ def copy_message(token, chat_id, from_chat_id, message_id, caption=None, parse_m
if allow_sending_without_reply is not None:
payload['allow_sending_without_reply'] = allow_sending_without_reply
if timeout:
payload['connect-timeout'] = timeout
payload['timeout'] = timeout
return _make_request(token, method_url, params=payload)
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:
@ -384,14 +427,17 @@ def send_dice(
if reply_markup:
payload['reply_markup'] = _convert_markup(reply_markup)
if timeout:
payload['connect-timeout'] = timeout
payload['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
@ -412,14 +458,18 @@ def send_photo(
if disable_notification is not None:
payload['disable_notification'] = disable_notification
if timeout:
payload['connect-timeout'] = timeout
payload['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}
@ -428,7 +478,9 @@ def send_media_group(
if reply_to_message_id:
payload['reply_to_message_id'] = reply_to_message_id
if timeout:
payload['connect-timeout'] = timeout
payload['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,37 +489,55 @@ 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:
payload['disable_notification'] = disable_notification
if timeout:
payload['connect-timeout'] = timeout
payload['timeout'] = timeout
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:
payload['reply_markup'] = _convert_markup(reply_markup)
if timeout:
payload['connect-timeout'] = timeout
payload['timeout'] = timeout
return _make_request(token, method_url, params=payload)
@ -485,14 +555,16 @@ def stop_message_live_location(
if reply_markup:
payload['reply_markup'] = _convert_markup(reply_markup)
if timeout:
payload['connect-timeout'] = timeout
payload['timeout'] = timeout
return _make_request(token, method_url, params=payload)
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:
@ -506,13 +578,20 @@ def send_venue(
if reply_markup:
payload['reply_markup'] = _convert_markup(reply_markup)
if timeout:
payload['connect-timeout'] = timeout
payload['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:
@ -526,7 +605,9 @@ def send_contact(
if reply_markup:
payload['reply_markup'] = _convert_markup(reply_markup)
if timeout:
payload['connect-timeout'] = timeout
payload['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)
@ -534,12 +615,13 @@ def send_chat_action(token, chat_id, action, timeout=None):
method_url = r'sendChatAction'
payload = {'chat_id': chat_id, 'action': action}
if timeout:
payload['connect-timeout'] = timeout
payload['timeout'] = timeout
return _make_request(token, method_url, params=payload)
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
@ -562,7 +644,7 @@ def send_video(token, chat_id, data, duration=None, caption=None, reply_to_messa
if disable_notification is not None:
payload['disable_notification'] = disable_notification
if timeout:
payload['connect-timeout'] = timeout
payload['timeout'] = timeout
if thumb:
if not util.is_string(thumb):
if files:
@ -575,11 +657,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
@ -600,7 +688,7 @@ def send_animation(token, chat_id, data, duration=None, caption=None, reply_to_m
if disable_notification is not None:
payload['disable_notification'] = disable_notification
if timeout:
payload['connect-timeout'] = timeout
payload['timeout'] = timeout
if thumb:
if not util.is_string(thumb):
if files:
@ -609,11 +697,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
@ -634,12 +727,16 @@ def send_voice(token, chat_id, voice, caption=None, duration=None, reply_to_mess
if disable_notification is not None:
payload['disable_notification'] = disable_notification
if timeout:
payload['connect-timeout'] = timeout
payload['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
@ -660,7 +757,7 @@ def send_video_note(token, chat_id, data, duration=None, length=None, reply_to_m
if disable_notification is not None:
payload['disable_notification'] = disable_notification
if timeout:
payload['connect-timeout'] = timeout
payload['timeout'] = timeout
if thumb:
if not util.is_string(thumb):
if files:
@ -669,11 +766,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
@ -698,7 +798,7 @@ def send_audio(token, chat_id, audio, caption=None, duration=None, performer=Non
if disable_notification is not None:
payload['disable_notification'] = disable_notification
if timeout:
payload['connect-timeout'] = timeout
payload['timeout'] = timeout
if thumb:
if not util.is_string(thumb):
if files:
@ -707,16 +807,24 @@ 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, visible_file_name=None):
method_url = get_method_by_type(data_type)
payload = {'chat_id': chat_id}
files = None
if not util.is_string(data):
files = {data_type: data}
file_data = data
if visible_file_name:
file_data = (visible_file_name, data)
files = {data_type: file_data}
else:
payload[data_type] = data
if reply_to_message_id:
@ -728,7 +836,7 @@ def send_data(token, chat_id, data, data_type, reply_to_message_id=None, reply_m
if disable_notification is not None:
payload['disable_notification'] = disable_notification
if timeout:
payload['connect-timeout'] = timeout
payload['timeout'] = timeout
if caption:
payload['caption'] = caption
if thumb:
@ -739,6 +847,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 +863,15 @@ def get_method_by_type(data_type):
return r'sendSticker'
def kick_chat_member(token, chat_id, user_id, until_date=None):
method_url = 'kickChatMember'
def ban_chat_member(token, chat_id, user_id, until_date=None, revoke_messages=None):
method_url = 'banChatMember'
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 +920,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 +940,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 +966,51 @@ 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:
if isinstance(payload['expire_date'], datetime):
payload['expire_date'] = payload['expire_date'].timestamp()
else:
payload['expire_date'] = expire_date
if member_limit:
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:
if isinstance(payload['expire_date'], datetime):
payload['expire_date'] = payload['expire_date'].timestamp()
else:
payload['expire_date'] = expire_date
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 +1042,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=None, language_code=None):
method_url = r'getMyCommands'
payload = {}
if scope:
payload['scope'] = scope.to_json()
if language_code:
payload['language_code'] = language_code
return _make_request(token, method_url, params=payload)
def set_my_commands(token, commands, scope=None, language_code=None):
method_url = r'setMyCommands'
payload = {'commands': _convert_list_json_serializable(commands)}
if scope:
payload['scope'] = scope.to_json()
if language_code:
payload['language_code'] = language_code
return _make_request(token, method_url, params=payload, method='post')
def delete_my_commands(token, scope=None, language_code=None):
method_url = r'deleteMyCommands'
payload = {}
if scope:
payload['scope'] = scope.to_json()
if language_code:
payload['language_code'] = language_code
return _make_request(token, method_url, params=payload, method='post')
@ -913,7 +1105,7 @@ def unpin_all_chat_messages(token, chat_id):
# Updating messages
def edit_message_text(token, text, chat_id=None, message_id=None, inline_message_id=None, parse_mode=None,
disable_web_page_preview=None, reply_markup=None):
entities = None, disable_web_page_preview=None, reply_markup=None):
method_url = r'editMessageText'
payload = {'text': text}
if chat_id:
@ -924,6 +1116,8 @@ def edit_message_text(token, text, chat_id=None, message_id=None, inline_message
payload['inline_message_id'] = inline_message_id
if parse_mode:
payload['parse_mode'] = parse_mode
if entities:
payload['entities'] = json.dumps(types.MessageEntity.to_list_of_dicts(entities))
if disable_web_page_preview is not None:
payload['disable_web_page_preview'] = disable_web_page_preview
if reply_markup:
@ -932,7 +1126,7 @@ def edit_message_text(token, text, chat_id=None, message_id=None, inline_message
def edit_message_caption(token, caption, chat_id=None, message_id=None, inline_message_id=None,
parse_mode=None, reply_markup=None):
parse_mode=None, caption_entities=None,reply_markup=None):
method_url = r'editMessageCaption'
payload = {'caption': caption}
if chat_id:
@ -943,6 +1137,8 @@ def edit_message_caption(token, caption, chat_id=None, message_id=None, inline_m
payload['inline_message_id'] = inline_message_id
if parse_mode:
payload['parse_mode'] = parse_mode
if caption_entities:
payload['caption_entities'] = json.dumps(types.MessageEntity.to_list_of_dicts(caption_entities))
if reply_markup:
payload['reply_markup'] = _convert_markup(reply_markup)
return _make_request(token, method_url, params=payload, method='post')
@ -981,7 +1177,7 @@ def delete_message(token, chat_id, message_id, timeout=None):
method_url = r'deleteMessage'
payload = {'chat_id': chat_id, 'message_id': message_id}
if timeout:
payload['connect-timeout'] = timeout
payload['timeout'] = timeout
return _make_request(token, method_url, params=payload, method='post')
@ -989,7 +1185,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:
@ -999,7 +1196,9 @@ def send_game(
if reply_markup:
payload['reply_markup'] = _convert_markup(reply_markup)
if timeout:
payload['connect-timeout'] = timeout
payload['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)
@ -1060,11 +1259,11 @@ def get_game_high_scores(token, user_id, chat_id=None, message_id=None, inline_m
def send_invoice(
token, chat_id, title, description, invoice_payload, provider_token, currency, prices,
start_parameter, photo_url=None, photo_size=None, photo_width=None, photo_height=None,
start_parameter = 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,
disable_notification=None, reply_to_message_id=None, reply_markup=None, provider_data=None,
timeout=None):
timeout=None, allow_sending_without_reply=None, max_tip_amount=None, suggested_tip_amounts=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,12 +1291,18 @@ 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:
: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.
:return:
"""
method_url = r'sendInvoice'
payload = {'chat_id': chat_id, 'title': title, 'description': description, 'payload': invoice_payload,
'provider_token': provider_token, 'start_parameter': start_parameter, 'currency': currency,
'provider_token': provider_token, 'currency': currency,
'prices': _convert_list_json_serializable(prices)}
if start_parameter:
payload['start_parameter'] = start_parameter
if photo_url:
payload['photo_url'] = photo_url
if photo_size:
@ -1129,7 +1334,13 @@ def send_invoice(
if provider_data:
payload['provider_data'] = provider_data
if timeout:
payload['connect-timeout'] = timeout
payload['timeout'] = timeout
if allow_sending_without_reply is not None:
payload['allow_sending_without_reply'] = allow_sending_without_reply
if max_tip_amount is not None:
payload['max_tip_amount'] = max_tip_amount
if suggested_tip_amounts is not None:
payload['suggested_tip_amounts'] = json.dumps(suggested_tip_amounts)
return _make_request(token, method_url, params=payload)
@ -1226,15 +1437,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 +1455,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')
@ -1267,12 +1482,14 @@ def delete_sticker_from_set(token, sticker):
return _make_request(token, method_url, params=payload, method='post')
# noinspection PyShadowingBuiltins
def send_poll(
token, chat_id,
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_notification=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),
@ -1301,14 +1518,19 @@ def send_poll(
if is_closed is not None:
payload['is_closed'] = is_closed
if disable_notifications:
payload['disable_notification'] = disable_notifications
if disable_notification:
payload['disable_notification'] = disable_notification
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
payload['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)
@ -1355,7 +1577,7 @@ def _convert_poll_options(poll_options):
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.JsonSerializable):
elif isinstance(poll_options[0], types.PollOption):
return [option.text for option in poll_options]
else:
return poll_options

File diff suppressed because it is too large Load Diff

View File

@ -4,19 +4,24 @@ import re
import string
import threading
import traceback
import warnings
import functools
from typing import Any, Callable, List, Dict, Optional, Union
# noinspection PyPep8Naming
import queue as Queue
import logging
from telebot import types
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()
@ -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,107 @@ 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 = [
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()
@ -243,8 +379,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 +401,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,26 +409,34 @@ 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
when the function is used."""
# https://stackoverflow.com/a/30253848/441814
@functools.wraps(func)
def new_func(*args, **kwargs):
warnings.simplefilter('always', DeprecationWarning) # turn off filter
warnings.warn("Call to deprecated function {}.".format(func.__name__),
category=DeprecationWarning,
stacklevel=2)
warnings.simplefilter('default', DeprecationWarning) # reset filter
return func(*args, **kwargs)
return new_func
def deprecated(warn: bool=False, 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):
if not warn:
logger.info(f"`{function.__name__}` is deprecated."
+ (f" Use `{alternative.__name__}` instead" if alternative else ""))
else:
logger.warn(f"`{function.__name__}` is deprecated."
+ (f" Use `{alternative.__name__}` instead" if alternative else ""))
return function(*args, **kwargs)
return wrapper
return decorator

View File

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

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
@ -18,6 +19,14 @@ if not should_skip:
CHAT_ID = os.environ['CHAT_ID']
GROUP_ID = os.environ['GROUP_ID']
def _new_test():
pass
@util.deprecated(alternative=_new_test)
def _test():
pass
@pytest.mark.skipif(should_skip, reason="No environment variables configured")
class TestTeleBot:
@ -125,6 +134,16 @@ class TestTeleBot:
ret_msg = tb.send_document(CHAT_ID, ret_msg.document.file_id)
assert ret_msg.message_id
def test_send_file_with_filename(self):
file_data = open('../examples/detailed_example/kitten.jpg', 'rb')
tb = telebot.TeleBot(TOKEN)
ret_msg = tb.send_document(CHAT_ID, file_data)
assert ret_msg.message_id
ret_msg = tb.send_document(CHAT_ID, file_data, visible_file_name="test.jpg")
assert ret_msg.message_id
def test_send_file_dis_noti(self):
file_data = open('../examples/detailed_example/kitten.jpg', 'rb')
tb = telebot.TeleBot(TOKEN)
@ -407,6 +426,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 +476,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 +564,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
@ -566,6 +623,9 @@ class TestTeleBot:
tb.process_new_updates([update])
time.sleep(1)
assert update.message.text == 'got' * 2
def test_deprecated_dec(self):
_test()
def test_chat_permissions(self):
return # CHAT_ID is private chat, no permissions can be set

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"