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

Compare commits

...

128 Commits

Author SHA1 Message Date
2c57c5c01c Update version. 2018-09-07 16:02:03 +08:00
07b82dc9b0 Merge pull request #575 from uburuntu/reset_session
Reset requests.Session feature
2018-09-07 15:56:59 +08:00
a850a0d94f Merge pull request #554 from uburuntu/patch-1
Pretty formatted docs for PyPI
2018-09-07 15:55:23 +08:00
d4f1444503 Merge pull request #574 from setazer/master
fix typo + add inline keyboard example
2018-09-07 15:54:11 +08:00
bab9f7bbb9 enh: reset requests.Session feature
Need for proxy changing and other reconnection stuff
2018-09-06 12:48:08 +03:00
d9ace2adc8 fix typo + add inline keyboard example 2018-09-05 12:34:19 +07:00
2b3c86b647 Merge pull request #562 from uburuntu/InputMedia
fix: python2 positional argument specifiers
2018-08-14 22:37:39 +08:00
e419214b49 fix: python2 positional argument specifiers 2018-08-14 17:29:35 +03:00
fe6959c38e Merge pull request #561 from uburuntu/InputMedia
fix: support python2 super()
2018-08-14 21:58:48 +08:00
7dd53b1396 fix: support python2 super() 2018-08-14 12:23:15 +03:00
421118d9d8 Merge pull request #558 from uburuntu/InputMedia
v.4.0: InputMediaAnimation, InputMediaAudio, InputMediaDocument, editMessageMedia
2018-08-13 23:47:03 +08:00
cf69a06ab8 enh: make code better and enhance test case 2018-08-10 16:48:09 +03:00
8ac6e664c5 new: InputMediaAnimation, InputMediaAudio, InputMediaDocument, editMessageMedia
Added support for editing the media content of messages: added the method editMessageMedia and new types InputMediaAnimation, InputMediaAudio, and InputMediaDocument.
2018-08-09 19:16:38 +03:00
12dbcb56d3 Update README.md 2018-08-09 08:45:07 +08:00
a46975d038 Merge pull request #556 from Andru1999/patch-2
Fix issue When you use threading mode
2018-08-08 10:06:49 +08:00
494b535a91 Fix issue When you use threading mode 2018-08-08 10:46:23 +10:00
74f75884f3 Delete now unused deprecated README.rst 2018-08-06 13:56:38 +03:00
4eae469528 Use last version of README.md for PyPI docs with pretty formatting
https://packaging.python.org/specifications/core-metadata/#description-content-type
https://pypi.org/project/pyTelegramBotAPI/
2018-08-06 13:54:46 +03:00
41f7c07959 Update version. 2018-08-03 08:35:04 +08:00
35ea2a2b7e Fix #548 2018-08-03 08:34:48 +08:00
522b2b487b Merge pull request #551 from heyyyoyy/origin/Bot_Api_3.6
Added parse mode for objects in Inline mode
2018-08-03 08:30:41 +08:00
5035e0ce80 Added parse mode for objects in Inline mode 2018-08-02 21:15:33 +03:00
7061091c1c Update version.
* Fix python 3.7 async
2018-07-31 08:58:04 +08:00
5c199bd246 Update README.md 2018-07-26 22:17:55 +08:00
44dd89881d Update README.md 2018-07-26 21:41:08 +08:00
76dbb05259 Merge pull request #537 from Andru1999/patch-1
Update __init__.py
2018-07-24 13:05:02 +08:00
578a9383b2 Merge branch 'master' into patch-1 2018-07-24 10:42:04 +08:00
85093bded5 Merge pull request #515 from WaffleWafflerov/master
Saving next step and reply handlers
2018-07-24 10:39:35 +08:00
f251def304 Merge branch 'master' into master 2018-07-22 04:43:53 +03:00
2b822f782d Update __init__.py
I find bug when I use your library without threading. If call bot.register_next_step_handler in function that register next_handler in next_step_handlers but in function _notify_next_handlers this delete and bot don`t have handler, but in threading mode function self.next_step_handlers.pop(chat_id, None) has time to eval self.next_step_handlers.pop(chat_id, None) and bug disappear. Sorry for my English
2018-07-22 00:31:02 +10:00
8bc5b74495 Remove 3.7 stable. Travis ci not support now. 2018-07-03 22:34:28 +08:00
70426ac274 Merge pull request #526 from skar404/master
rename async -> async_dec
2018-07-03 11:19:29 +08:00
a3a2bd5793 Add python 3.7 in travis ci.
#527
2018-07-03 11:17:44 +08:00
c3b6ee9dc0 bug in travis-ci 2018-07-02 23:41:37 +03:00
4079772fd3 Update .travis.yml 2018-07-02 23:29:07 +03:00
9547a8d7b1 test python 3.7 2018-07-02 23:23:48 +03:00
c8b2b14157 rename async -> async_dec 2018-07-02 18:13:11 +03:00
3d5ef5b1d8 Merge pull request #516 from Badiboy/master
_notify_next_handlers drops messages if empty handler list
2018-05-31 14:04:45 +08:00
776a699a8d _notify_next_handlers skips sequential messages
Is there are several sequential messages and next_step_handlers are set, the _notify_next_handlers will process only every even message dew to execute both .pop(i) and i+=1
2018-05-29 18:55:41 +03:00
78afd045d8 _notify_next_handlers drops messages if empty handler list
After calling
clear_step_handler(...)
code:
self.next_step_handlers[chat_id] = []
left the key in next_step_handlers.
When a next message arrives, the old handler executes nothing (no handlers), but still remove message from message queue:
new_messages.pop(i).

Updated to pop message only when there are real handlers in the list.
2018-05-27 23:24:37 +03:00
06faed887c Merge pull request #509 from Badiboy/master
html_text fix and html_caption
2018-05-27 23:14:37 +08:00
bc855f7610 Fix register_for_reply_by_message_id and register_for_reply doc strings. 2018-05-27 17:05:01 +03:00
893d5386c5 Fix register_for_reply_by_message_id doc strings. 2018-05-27 17:02:04 +03:00
909d570dca Add warning about lambda functions in callbacks 2018-05-27 17:01:07 +03:00
424c77fd2c Remove type hint for 2.x and PyPy python compatibility 2018-05-27 16:54:56 +03:00
333949683f Add doc strings to new TeleBot methods
Update telebot/__init__.py
2018-05-27 01:37:06 +03:00
fa038c2e42 Move del_file_after_loading param to right methods :face_palm:
Update: telebot/__init__.py
2018-05-27 01:30:14 +03:00
d61de35a32 Remove rudiment json things, again!
Update: telebot/__init__.py
2018-05-27 01:23:20 +03:00
13df7b5908 Add enable_save_next_step_handlers and load_next_step_handlers methods to step_example/
Update: examples/step_example.py
2018-05-26 17:21:39 +03:00
1de356dcc3 Change default save directory to "./.handler-saves/".
Add del_file_after_loading param to load methods.

Update: telebot/__init__.py
2018-05-26 17:10:00 +03:00
47e6dfd6bc Remove rudiment json things
Update: telebot/__init__.py
2018-05-26 16:52:30 +03:00
3c890a7846 Remove 2 spaces up to TeleBot class.
Update: telebot/__init__.py
2018-05-26 16:37:25 +03:00
17971ff48b Move from json to pickle.
Update: relebot/__init__.py
2018-05-26 12:19:01 +03:00
b989b7601b Add new class: Handler
Change type of (next step|reply) handlers from dict to Handler [WIP]

update: telebot/__init__.py
2018-05-25 20:57:22 +03:00
8c574a786a Merge branch 'master' of https://github.com/WaffleWafflerov/pyTelegramBotAPI-1 2018-05-25 20:37:36 +03:00
7e5f51e4ab Remove old thing.
Update telebot/__init__.py
2018-05-25 09:44:43 +03:00
018e4597a2 Add del_file_after_loading param to Saver.return_load_handlers and Saver.load_handlers methods.
Update telebot/__init__.py
2018-05-25 09:40:29 +03:00
7df6b3d4c9 Fix situation where delay <= 0.
Update telebot/__init__.py
2018-05-25 09:35:39 +03:00
4facc5f7d7 fix unenabled saving handlers.
Updated telebot/__init__.py
2018-05-25 09:30:10 +03:00
4bcfc34a50 Update _notify_next_handlers and _notify_reply_handlers methods:
Now if there wasn't any handler updates, timer willn't start.
2018-05-25 09:07:59 +03:00
b1d5cb2129 Rewrite.
Add class 'Saver' that provides methods for saving (next step|reply) handlers.
Add methods
enable_save_next_step_handlers,
enable_save_reply_handlers,
disable_save_next_step_handlers,
disable_save_reply_handlers,
load_next_step_handlers,
load_reply_handlers
to Telebot and AsyncTelebot.

update telebot/__init__.py
2018-05-25 08:57:48 +03:00
00c8dcc19b Add async methods 2018-05-25 03:10:40 +03:00
ed7e33b4c6 Fix loadings funcs 2018-05-25 02:48:46 +03:00
74a952846c Merge branch 'master' of https://github.com/CoconutWaffle/pyTelegramBotAPI-1 2018-05-24 18:32:25 +03:00
e99fb8f84f Add methods to save (reply|next step) handlers [WIP] 2018-05-20 23:40:25 +03:00
49aee14fca Make _test_filter method static and a bit clear doc strings 2018-05-19 00:42:06 +03:00
9267da205d Merge pull request #1 from eternnoir/master
Updating fork
2018-05-19 00:35:58 +03:00
9c79ba2f87 html_text fix and html_caption
html_text now works with text_link
html_caption now works for caption/caption_entities
2018-05-14 13:29:34 +03:00
3be21ae361 Update version. 2018-05-14 10:15:05 +08:00
42343c3a7f Merge pull request #490 from CoconutWaffle/master
Fixing and upgrading next step and reply handlers. + minor fixes
2018-05-04 07:20:18 +08:00
5a102ed8fa Merge pull request #492 from sviat9440/master
Bugfixes (message.html_text offset)
2018-05-04 07:19:28 +08:00
e1e109bef1 Merge pull request #502 from fojetin/patch-1
Fix #253 #231 #485
2018-05-04 07:18:57 +08:00
b5a217013a Fix #253 #231 #485 2018-04-30 15:41:12 +03:00
3ba9799b98 Renaming back pytelegrambotapi module to telebot 2018-04-28 13:50:59 +03:00
91f213ff34 Fix #501 2018-04-27 15:47:03 +08:00
8f55460924 Fix cache time is zero. 2018-04-26 09:53:55 +08:00
f6b999053d Merge pull request #497 from khabibr/master
Update apihelper.py
2018-04-24 23:52:45 +08:00
99ff104a3f Update apihelper.py
Correct files downloading when proxy used.
2018-04-24 16:48:39 +06:00
662c2c8797 Merge pull request #495 from BennyThink/master
fix issue #403: UnicodeEncodeError when sending a non-ASCII file in Python 2
2018-04-18 20:05:19 +09:00
72a0199a2f Merge pull request #496 from Jay-T/master
Update README.md
2018-04-18 20:04:27 +09:00
989cae597b Update README.md 2018-04-18 13:10:22 +05:00
5dd88f8223 fix issue #403: UnicodeEncodeError when sending a non-ASCII file in Python 2.7 2018-04-18 15:00:05 +08:00
28111bdf4e Merge pull request #494 from LeoNeeDpk1/patch-1
Update README.md SOCKS5 proxy info
2018-04-18 11:51:44 +09:00
10ec897fb5 Update README.md
Updated and 100% working info in SOCKS5 proxy block.
2018-04-18 10:05:26 +12:00
ffe3a0c3d7 Update types.py
-- Fix encoding bug (emoji shifted offset)
2018-04-15 19:19:29 +03:00
f5f48db6ba Merge pull request #1 from eternnoir/master
Update
2018-04-15 19:17:46 +03:00
183230e927 Update setup.py 2018-04-12 17:24:04 +03:00
7957bc45a8 Fixing and upgrading next step and reply handlers. + minor fixes
Rename telebot package to pytelegrambotapi becouse lib named telebot exists and it raising many errors

Add methods:
|     register_for_reply_by_message_id,
|     register_next_step_handler_by_chat_id,
|     clear_reply_handlers,
|     clear_reply_handlers_by_message_id
2018-04-12 13:45:32 +03:00
373d4d37ff Fix test case. 2018-04-10 14:48:39 +08:00
0d0e37dae5 Merge pull request #487 from sviat9440/master
Bugfixes and minor improvements
2018-04-10 15:25:36 +09:00
36d088dfbf Bugfixes and minor improvements 2018-04-04 10:47:37 +03:00
9ae20b4815 Merge pull request #482 from sviat9440/master
Minor improvements
2018-03-25 21:39:27 +08:00
e761e1e1d9 Update apihelper.py 2018-03-25 14:54:28 +03:00
cb0256b37d Update __init__.py 2018-03-25 13:22:35 +03:00
ff3cbaf45b Update apihelper.py 2018-03-25 13:21:55 +03:00
d231b1fbaa Bump version. 2018-03-23 19:59:42 +08:00
7f47f11444 Fix #481 2018-03-23 19:58:43 +08:00
0422e62f65 Update types.py
Fix
2018-03-21 10:45:34 +03:00
82e252ec46 Update types.py
Fix
2018-03-21 10:44:37 +03:00
c11a9f810c Update types.py
Added 'json' property to class 'Message', to quickly save a message to the database
2018-03-21 07:35:42 +03:00
dadcd5a577 Update "Bots using this API" entry. 2018-03-19 15:13:48 +08:00
afc9abc269 Add ci test for python 3.6 2018-03-10 14:48:30 +08:00
e01f17e3a0 Bump version. 2018-03-10 14:46:47 +08:00
48e6757686 Fix import logger problem. 2018-03-10 14:41:34 +08:00
8495229ce1 Update version 3.6.0 2018-03-02 19:28:34 +08:00
3b60f7ca67 Merge pull request #462 from heyyyoyy/Bot_Api_3.6
Bot Api 3.6
2018-02-22 22:11:46 +08:00
a1930e05c2 Merge pull request #463 from dmytrostriletskyi/master
Make the Heroku example actual
2018-02-22 20:52:53 +08:00
bc067662dc Make the Heroku example actual 2018-02-16 17:52:37 +02:00
518c49f23a fixing formatting of caption in the method send_document 2018-02-16 18:29:29 +03:00
903b1dfd50 added parse_mode in edit_message_caption 2018-02-16 14:19:35 +00:00
2e199a5684 Bot Api 3.6 2018-02-14 20:27:55 +00:00
55302cb972 Merge pull request #445 from heyyyoyy/update_send_media_group
Added support for local files in the sendMediaGroup method
2018-02-01 19:24:50 +08:00
f47653d2e4 Update README.md 2018-01-24 19:13:29 +08:00
94b4a25980 Update version. 2018-01-24 19:11:03 +08:00
afac177d7d Fix missing media_group_id in message. 2018-01-24 19:05:38 +08:00
2637e29dbe Updated sendMediaGroup method 2018-01-15 16:08:50 +03:00
6d180e30f0 Merge pull request #443 from reacheight/master
fixed example of usage sendVideoNote method
2018-01-15 10:25:06 +08:00
e2ed4cf065 Merge pull request #387 from fumycat/patch-1
Fix optional parameter
2018-01-15 10:23:26 +08:00
8b2dea1d56 fixed example of usage sendVideoNote method 2018-01-10 00:26:44 +03:00
41e31de034 Merge pull request #429 from JekaFST/master
Fix for SendLocation with live period
2017-12-06 15:50:49 +08:00
ae074fd5c9 Merge pull request #2 from JekaFST/JekaFST-live_location_fixes
Edit and stop live location fixes
2017-12-05 01:32:06 +03:00
60596a95b8 Edit and stop live location fixes
.token was missed in apihelper's methods calls
2017-12-05 01:31:47 +03:00
44531bcedf Merge pull request #1 from JekaFST/JekaFST-FixLiveLocation
Fix for SendLocation with live period
2017-12-05 00:23:32 +03:00
8aa8fa5986 Fix for SendLocation with live period
Fix for payload['live_perion'] typo -> payload['live_period']
2017-12-05 00:21:05 +03:00
f0e64b3653 Merge pull request #428 from 0xVK/patch-1
Added SmartySBot to README
2017-12-04 16:21:41 +08:00
8444ea588a Added SmartySBot to README
Added SmartySBot to README
2017-12-03 20:34:37 +02:00
b2f376a906 Remove debug message. Add content_type 2017-11-30 23:34:07 +08:00
af991ea76e Fix optional parameter 2017-08-20 01:36:08 +07:00
14 changed files with 824 additions and 1029 deletions

View File

@ -5,6 +5,7 @@ python:
- "3.3"
- "3.4"
- "3.5"
- "3.6"
- "pypy"
- "pypy3"
install: "pip install -r requirements.txt"

View File

@ -291,8 +291,8 @@ tb.send_video(chat_id, "FILEID")
# sendVideoNote
videonote = open('/tmp/videonote.mp4', 'rb')
tb.send_video(chat_id, videonote)
tb.send_video(chat_id, "FILEID")
tb.send_video_note(chat_id, videonote)
tb.send_video_note(chat_id, "FILEID")
# sendLocation
tb.send_location(chat_id, lat, lon)
@ -500,16 +500,13 @@ 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://10.10.1.10:3128'}
```
If you want to use socket5 proxy you need install dependency `pip install requests[socks]`.
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`.
```python
proxies = {
'http': 'socks5://user:pass@host:port',
'https': 'socks5://user:pass@host:port'
}
apihelper.proxy = {'https':'socks5://userproxy:password@proxy_address:port'}
```
@ -584,5 +581,11 @@ Get help. Discuss. Chat.
* [RastreioBot](http://t.me/RastreioBot) ([source](https://github.com/GabrielRF/RastreioBot)) by *GabrielRF* - Bot used to track packages on the Brazilian Mail Service.
* [filex_bot](http://t.me/filex_bot)([link](https://github.com/victor141516/FileXbot-telegram))
* [Spbu4UBot](http://t.me/Spbu4UBot)([link](https://github.com/EeOneDown/spbu4u)) by *EeOneDown* - Bot with timetables for SPbU students.
* [SmartySBot](http://t.me/ZDU_bot)([link](https://github.com/0xVK/SmartySBot)) by *0xVK* - Telegram timetable bot, for Zhytomyr Ivan Franko State University students.
* [yandex_music_bot](http://t.me/yandex_music_bot)- Downloads tracks/albums/public playlists from Yandex.Music streaming service for free.
* [LearnIt](https://t.me/LearnItbot)([link](https://github.com/tiagonapoli/LearnIt)) - A Telegram Bot created to help people to memorize other languages vocabulary.
* [MusicQuiz_bot](https://t.me/MusicQuiz_bot) by [Etoneja](https://github.com/Etoneja) - Listen to audiosamles and try to name the performer of the song.
* [Bot-Telegram-Shodan ](https://github.com/rubenleon/Bot-Telegram-Shodan) by [rubenleon](https://github.com/rubenleon)
Want to have your bot listed here? Send a Telegram message to @eternnoir or @pevdh.

View File

@ -1,807 +0,0 @@
#
.. raw:: html
<p align="center">
pyTelegramBotAPI
.. raw:: html
<p align="center">
A simple, but extensible Python implementation for the `Telegram Bot
API <https://core.telegram.org/bots/api>`__.
|Download Month| |Build Status| |Download Month|
- `Getting started. <#getting-started>`__
- `Writing your first bot <#writing-your-first-bot>`__
- `Prerequisites <#prerequisites>`__
- `A simple echo bot <#a-simple-echo-bot>`__
- `General API Documentation <#general-api-documentation>`__
- `Types <#types>`__
- `Methods <#methods>`__
- `General use of the API <#general-use-of-the-api>`__
- `Message handlers <#message-handlers>`__
- `Callback Query handlers <#callback-query-handler>`__
- `TeleBot <#telebot>`__
- `Reply markup <#reply-markup>`__
- `Inline Mode <#inline-mode>`__
- `Advanced use of the API <#advanced-use-of-the-api>`__
- `Asynchronous delivery of
messages <#asynchronous-delivery-of-messages>`__
- `Sending large text messages <#sending-large-text-messages>`__
- `Controlling the amount of Threads used by
TeleBot <#controlling-the-amount-of-threads-used-by-telebot>`__
- `The listener mechanism <#the-listener-mechanism>`__
- `Using web hooks <#using-web-hooks>`__
- `Logging <#logging>`__
- `F.A.Q. <#faq>`__
- `Bot 2.0 <#bot-20>`__
- `How can I distinguish a User and a GroupChat in
message.chat? <#how-can-i-distinguish-a-user-and-a-groupchat-in-messagechat>`__
- `The Telegram Chat Group <#the-telegram-chat-group>`__
- `More examples <#more-examples>`__
- `Bots using this API <#bots-using-this-api>`__
Getting started.
----------------
This API is tested with Python 2.6, Python 2.7, Python 3.4, Pypy and
Pypy 3. There are two ways to install the library:
- Installation using pip (a Python package manager)\*:
::
$ pip install pyTelegramBotAPI
- Installation from source (requires git):
::
$ git clone https://github.com/eternnoir/pyTelegramBotAPI.git
$ cd pyTelegramBotAPI
$ python setup.py install
It is generally recommended to use the first option.
\*\*While the API is production-ready, it is still under development and
it has regular updates, do not forget to update it regularly by calling
``pip install pytelegrambotapi --upgrade``\ \*
Writing your first bot
----------------------
Prerequisites
~~~~~~~~~~~~~
It is presumed that you [have obtained an API token with
@BotFather](https://core.telegram.org/bots#botfather). We will call this
token ``TOKEN``. Furthermore, you have basic knowledge of the Python
programming language and more importantly `the Telegram Bot
API <https://core.telegram.org/bots/api>`__.
A simple echo bot
~~~~~~~~~~~~~~~~~
The TeleBot class (defined in \_\_init\_\_.py) encapsulates all API
calls in a single class. It provides functions such as ``send_xyz``
(``send_message``, ``send_document`` etc.) and several ways to listen
for incoming messages.
Create a file called ``echo_bot.py``. Then, open the file and create an
instance of the TeleBot class.
.. code:: python
import telebot
bot = telebot.TeleBot("TOKEN")
*Note: Make sure to actually replace TOKEN with your own API token.*
After that declaration, we need to register some so-called message
handlers. Message handlers define filters which a message must pass. If
a message passes the filter, the decorated function is called and the
incoming message is passed as an argument.
Let's define a message handler which handles incoming ``/start`` and
``/help`` commands.
.. code:: python
@bot.message_handler(commands=['start', 'help'])
def send_welcome(message):
bot.reply_to(message, "Howdy, how are you doing?")
A function which is decorated by a message handler **can have an
arbitrary name, however, it must have only one parameter (the
message)**.
Let's add another handler:
.. code:: python
@bot.message_handler(func=lambda m: True)
def echo_all(message):
bot.reply_to(message, message.text)
This one echoes all incoming text messages back to the sender. It uses a
lambda function to test a message. If the lambda returns True, the
message is handled by the decorated function. Since we want all messages
to be handled by this function, we simply always return True.
*Note: all handlers are tested in the order in which they were declared*
We now have a basic bot which replies a static message to "/start" and
"/help" commands and which echoes the rest of the sent messages. To
start the bot, add the following to our source file:
.. code:: python
bot.polling()
Alright, that's it! Our source file now looks like this:
.. code:: python
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()
To start the bot, simply open up a terminal and enter
``python echo_bot.py`` to run the bot! Test it by sending commands
('/start' and '/help') and arbitrary text messages.
General API Documentation
-------------------------
Types
~~~~~
All types are defined in types.py. They are all completely in line with
the `Telegram API's definition of the
types <https://core.telegram.org/bots/api#available-types>`__, except
for the Message's ``from`` field, which is renamed to ``from_user``
(because ``from`` is a Python reserved token). Thus, attributes such as
``message_id`` can be accessed directly with ``message.message_id``.
Note that ``message.chat`` can be either an instance of ``User`` or
``GroupChat`` (see `How can I distinguish a User and a GroupChat in
message.chat? <#how-can-i-distinguish-a-user-and-a-groupchat-in-messagechat>`__).
The Message object also has a ``content_types``\ attribute, which
defines the type of the Message. ``content_types`` can be one of the
following strings: 'text', 'audio', 'document', 'photo', 'sticker',
'video', 'voice', 'location', 'contact', 'new\_chat\_participant',
'left\_chat\_participant', 'new\_chat\_title', 'new\_chat\_photo',
'delete\_chat\_photo', 'group\_chat\_created'.
Methods
~~~~~~~
All `API
methods <https://core.telegram.org/bots/api#available-methods>`__ are
located in the TeleBot class. They are renamed to follow common Python
naming conventions. E.g. ``getMe`` is renamed to ``get_me`` and
``sendMessage`` to ``send_message``.
General use of the API
~~~~~~~~~~~~~~~~~~~~~~
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):
.. code:: python
@bot.message_handler(filters)
def function_name(message):
bot.reply_to(message, "This is a message handler")
``function_name`` is not bound to any restrictions. Any function name is
permitted with message handlers. The function must accept at most one
argument, which will be the message that the function must handle.
``filters`` is a list of keyword arguments. A filter is declared in the
following manner: ``name=argument``. One handler may have multiple
filters. TeleBot supports the following filters:
+--------+------+------+
| name | argu | Cond |
| | ment | itio |
| | (s) | n |
+========+======+======+
| conten | list | ``Tr |
| t\_typ | of | ue`` |
| es | stri | if |
| | ngs | mess |
| | (def | age. |
| | ault | cont |
| | ``[' | ent\ |
| | text | _typ |
| | ']`` | e |
| | ) | is |
| | | in |
| | | the |
| | | list |
| | | of |
| | | stri |
| | | ngs. |
+--------+------+------+
| regexp | a | ``Tr |
| | regu | ue`` |
| | lar | if |
| | expr | ``re |
| | essi | .sea |
| | on | rch( |
| | as a | rege |
| | stri | xp_a |
| | ng | rg)` |
| | | ` |
| | | retu |
| | | rns |
| | | ``Tr |
| | | ue`` |
| | | and |
| | | ``me |
| | | ssag |
| | | e.co |
| | | nten |
| | | t_ty |
| | | pe = |
| | | = 't |
| | | ext' |
| | | `` |
| | | (See |
| | | `Pyt |
| | | hon |
| | | Regu |
| | | lar |
| | | Expr |
| | | essi |
| | | ons |
| | | <htt |
| | | ps:/ |
| | | /doc |
| | | s.py |
| | | thon |
| | | .org |
| | | /2/l |
| | | ibra |
| | | ry/r |
| | | e.ht |
| | | ml>` |
| | | __ |
+--------+------+------+
| comman | list | ``Tr |
| ds | of | ue`` |
| | stri | if |
| | ngs | ``me |
| | | ssag |
| | | e.co |
| | | nten |
| | | t_ty |
| | | pe = |
| | | = 't |
| | | ext' |
| | | `` |
| | | and |
| | | ``me |
| | | ssag |
| | | e.te |
| | | xt`` |
| | | star |
| | | ts |
| | | with |
| | | a |
| | | comm |
| | | and |
| | | that |
| | | is |
| | | in |
| | | the |
| | | list |
| | | of |
| | | stri |
| | | ngs. |
+--------+------+------+
| func | a | ``Tr |
| | func | ue`` |
| | tion | if |
| | (lam | the |
| | bda | lamb |
| | or | da |
| | func | or |
| | tion | func |
| | refe | tion |
| | renc | refe |
| | e) | renc |
| | | e |
| | | retu |
| | | rns |
| | | ``Tr |
| | | ue`` |
+--------+------+------+
Here are some examples of using the filters and message handlers:
.. code:: python
import telebot
bot = telebot.TeleBot("TOKEN")
# Handles all text messages that contains the commands '/start' or '/help'.
@bot.message_handler(commands=['start', 'help'])
def handle_start_help(message):
pass
# Handles all sent documents and audio files
@bot.message_handler(content_types=['document', 'audio'])
def handle_docs_audio(message):
pass
# Handles all text messages that match the regular expression
@bot.message_handler(regexp="SOME_REGEXP")
def handle_message(message):
pass
#Handles all messages for which the lambda returns True
@bot.message_handler(func=lambda message: message.document.mime_type == 'text/plain', content_types=['document'])
def handle_text_doc(message):
pass
#Which could also be defined as:
def test_message(message):
return message.document.mime_type == 'text/plan'
@bot.message_handler(func=test_message, content_types=['document'])
def handle_text_doc(message)
pass
# Handlers can be stacked to create a function which will be called if either message_handler is eligible
# This handler will be called if the message starts with '/hello' OR is some emoji
@bot.message_handler(commands=['hello'])
@bot.message_handler(func=lambda msg: msg.text.encode("utf-8") == SOME_FANCY_EMOJI)
def send_something(message):
pass
**Important: all handlers are tested in the order in which they were
declared**
Callback Query Handler
^^^^^^^^^^^^^^^^^^^^^^
In bot2.0 update. You can get ``callback_query`` in update object. In
telebot use ``callback_query_handler`` to process callback\_querys.
.. code:: python
@bot.callback_query_handler(func=lambda call: True)
def test_callback(call):
logger.info(call)
TeleBot
^^^^^^^
.. code:: python
import telebot
TOKEN = '<token_string>'
tb = telebot.TeleBot(TOKEN) #create a new Telegram Bot object
# Upon calling this function, TeleBot starts polling the Telegram servers for new messages.
# - none_stop: True/False (default False) - Don't stop polling when receiving an error from the Telegram servers
# - 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.
tb.polling(none_stop=False, interval=0, timeout=20)
# getMe
user = tb.get_me()
# setWebhook
tb.set_webhook(url="http://example.com", certificate=open('mycert.pem'))
# unset webhook
tb.remove_webhook()
# getUpdates
updates = tb.get_updates()
updates = tb.get_updates(1234,100,20) #get_Updates(offset, limit, timeout):
# sendMessage
tb.send_message(chatid, text)
# forwardMessage
tb.forward_message(to_chat_id, from_chat_id, message_id)
# All send_xyz functions which can take a file as an argument, can also take a file_id instead of a file.
# sendPhoto
photo = open('/tmp/photo.png', 'rb')
tb.send_photo(chat_id, photo)
tb.send_photo(chat_id, "FILEID")
# sendAudio
audio = open('/tmp/audio.mp3', 'rb')
tb.send_audio(chat_id, audio)
tb.send_audio(chat_id, "FILEID")
## sendAudio with duration, performer and title.
tb.send_audio(CHAT_ID, file_data, 1, 'eternnoir', 'pyTelegram')
# sendVoice
voice = open('/tmp/voice.ogg', 'rb')
tb.send_voice(chat_id, voice)
tb.send_voice(chat_id, "FILEID")
# sendDocument
doc = open('/tmp/file.txt', 'rb')
tb.send_document(chat_id, doc)
tb.send_document(chat_id, "FILEID")
# sendSticker
sti = open('/tmp/sti.webp', 'rb')
tb.send_sticker(chat_id, sti)
tb.send_sticker(chat_id, "FILEID")
# sendVideo
video = open('/tmp/video.mp4', 'rb')
tb.send_video(chat_id, video)
tb.send_video(chat_id, "FILEID")
# sendLocation
tb.send_location(chat_id, lat, lon)
# sendChatAction
# action_string can be one of the following strings: 'typing', 'upload_photo', 'record_video', 'upload_video',
# 'record_audio', 'upload_audio', 'upload_document' or 'find_location'.
tb.send_chat_action(chat_id, action_string)
# getFile
# Downloading a file is straightforward
# Returns a File object
import requests
file_info = tb.get_file(file_id)
file = requests.get('https://api.telegram.org/file/bot{0}/{1}'.format(API_TOKEN, file_info.file_path))
Reply markup
^^^^^^^^^^^^
All ``send_xyz`` functions of TeleBot take an optional ``reply_markup``
argument. This argument must be an instance of ``ReplyKeyboardMarkup``,
``ReplyKeyboardRemove`` or ``ForceReply``, which are defined in types.py.
.. code:: python
from telebot import types
# Using the ReplyKeyboardMarkup class
# It's constructor can take the following optional arguments:
# - resize_keyboard: True/False (default False)
# - one_time_keyboard: True/False (default False)
# - selective: True/False (default False)
# - row_width: integer (default 3)
# row_width is used in combination with the add() function.
# It defines how many buttons are fit on each row before continuing on the next row.
markup = types.ReplyKeyboardMarkup(row_width=2)
itembtn1 = types.KeyboardButton('a')
itembtn2 = types.KeyboardButton('v')
itembtn3 = types.KeyboardButton('d')
markup.add(itembtn1, itembtn2, itembtn3)
tb.send_message(chat_id, "Choose one letter:", reply_markup=markup)
# or add strings one row at a time:
markup = types.ReplyKeyboardMarkup()
itembtna = types.KeyboardButton('a')
itembtnv = types.KeyboardButton('v')
itembtnc = types.KeyboardButton('c')
itembtnd = types.KeyboardButton('d')
itembtne = types.KeyboardButton('e')
markup.row(itembtna, itembtnv)
markup.row(itembtnc, itembtnd, itembtne)
tb.send_message(chat_id, "Choose one letter:", reply_markup=markup)
The last example yields this result:
.. figure:: https://pp.vk.me/c624430/v624430512/473e5/_mxxW7FPe4U.jpg
:alt: ReplyKeyboardMarkup
ReplyKeyboardMarkup
.. code:: python
# ReplyKeyboardRemove: hides a previously sent ReplyKeyboardMarkup
# Takes an optional selective argument (True/False, default False)
markup = types.ReplyKeyboardRemove(selective=False)
tb.send_message(chat_id, message, reply_markup=markup)
.. code:: python
# ForceReply: forces a user to reply to a message
# Takes an optional selective argument (True/False, default False)
markup = types.ForceReply(selective=False)
tb.send_message(chat_id, "Send me another word:", reply_markup=markup)
ForceReply:
.. figure:: https://pp.vk.me/c624430/v624430512/473ec/602byyWUHcs.jpg
:alt: ForceReply
ForceReply
Inline Mode
~~~~~~~~~~~
More information about `Inline
mode <https://core.telegram.org/bots/inline>`__.
inline\_handler
^^^^^^^^^^^^^^^
Now, you can use inline\_handler to get inline\_query in telebot.
.. code:: python
@bot.inline_handler(lambda query: query.query == 'text')
def query_text(inline_query):
# Query message is text
chosen\_inline\_handler
^^^^^^^^^^^^^^^^^^^^^^^
Use chosen\_inline\_handler to get chosen\_inline\_result in telebot.
Don't forgot add the /setinlinefeedback command for @Botfather.
More information :
`collecting-feedback <https://core.telegram.org/bots/inline#collecting-feedback>`__
.. code:: python
@bot.chosen_inline_handler(func=lambda chosen_inline_result: True)
def test_chosen(chosen_inline_result):
# Process all chosen_inline_result.
answer\_inline\_query
^^^^^^^^^^^^^^^^^^^^^
.. code:: python
@bot.inline_handler(lambda query: query.query == 'text')
def query_text(inline_query):
try:
r = types.InlineQueryResultArticle('1', 'Result', 'Result message.')
r2 = types.InlineQueryResultArticle('2', 'Result2', 'Result message2.')
bot.answer_inline_query(inline_query.id, [r, r2])
except Exception as e:
print(e)
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. To enable this behaviour, create an instance of
AsyncTeleBot instead of TeleBot.
.. code:: python
tb = telebot.AsyncTeleBot("TOKEN")
Now, every function that calls the Telegram API is executed in a
separate Thread. The functions are modified to return an AsyncTask
instance (defined in util.py). Using AsyncTeleBot allows you to do the
following:
.. code:: python
import telebot
tb = telebot.AsyncTeleBot("TOKEN")
task = tb.get_me() # Execute an API call
# Do some other operations...
a = 0
for a in range(100):
a += 10
result = task.wait() # Get the result of the execution
*Note: if you execute send\_xyz functions after eachother without
calling wait(), the order in which messages are delivered might be
wrong.*
Sending large text messages
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Sometimes you must send messages that exceed 5000 characters. The
Telegram API can not handle that many characters in one request, so we
need to split the message in multiples. Here is how to do that using the
API:
.. code:: python
from telebot import util
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)
Controlling the amount of Threads used by TeleBot
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The TeleBot constructor takes the following optional arguments:
- create\_threads: True/False (default True). A flag to indicate
whether TeleBot should execute message handlers on it's polling
Thread.
- num\_threads: integer (default 4). Controls the amount of
WorkerThreads created for the internal thread pool that TeleBot uses
to execute message handlers. Is not used when create\_threads is
False.
The listener mechanism
~~~~~~~~~~~~~~~~~~~~~~
As an alternative to the message handlers, one can also register a
function as a listener to TeleBot. Example:
.. code:: python
def handle_messages(messages):
for message in messsages:
# Do something with the message
bot.reply_to(message, 'Hi')
bot.set_update_listener(handle_messages)
bot.polling()
Using webhooks
~~~~~~~~~~~~~~
When using webhooks telegram sends one Update per call, for processing
it you should call process\_new\_messages([update.message]) when you
recieve it.
There are some examples using webhooks in the
*examples/webhook\_examples* directory.
Logging
~~~~~~~
You can use the Telebot module logger to log debug info about Telebot.
Use ``telebot.logger`` to get the logger of the TeleBot module. It is
possible to add custom logging Handlers to the logger. Refer to the
`Python logging module
page <https://docs.python.org/2/library/logging.html>`__ for more info.
.. code:: python
import logging
logger = telebot.logger
telebot.logger.setLevel(logging.DEBUG) # Outputs debug messages to console.
F.A.Q.
------
Bot 2.0
~~~~~~~
April 9,2016 Telegram release new bot 2.0 API, which has a drastic
revision especially for the change of method's interface.If you want to
update to the latest version, please make sure you've switched bot's
code to bot 2.0 method interface.
`More information about pyTelegramBotAPI support
bot2.0 <https://github.com/eternnoir/pyTelegramBotAPI/issues/130>`__
How can I distinguish a User and a GroupChat in message.chat?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Telegram Bot API support new type Chat for message.chat.
- Check the ``type`` attribute in ``Chat`` object:
- \`\`\`python if message.chat.type == “private”: # private chat
message
if message.chat.type == “group”: # group chat message
if message.chat.type == “supergroup”: # supergroup chat message
if message.chat.type == “channel”: # channel message
\`\`\`
The Telegram Chat Group
-----------------------
Get help. Discuss. Chat.
- Join the pyTelegramBotAPI Telegram Chat Group
- Messge to @eternnoir by telegram for Invitation.
- We now have a Telegram Channel as well! Keep yourself up to date with
API changes, and `join it <https://telegram.me/pytelegrambotapi>`__.
More examples
-------------
- `Echo
Bot <https://github.com/eternnoir/pyTelegramBotAPI/blob/master/examples/echo_bot.py>`__
- `Deep
Linking <https://github.com/eternnoir/pyTelegramBotAPI/blob/master/examples/deep_linking.py>`__
- `next\_step\_handler
Example <https://github.com/eternnoir/pyTelegramBotAPI/blob/master/examples/step_example.py>`__
Bots using this API
-------------------
- `SiteAlert bot <https://telegram.me/SiteAlert_bot>`__
(`source <https://github.com/ilteoood/SiteAlert-Python>`__) by
*ilteoood* - Monitors websites and sends a notification on changes
- `TelegramLoggingBot <https://github.com/aRandomStranger/TelegramLoggingBot>`__
by *aRandomStranger*
- `Telegram
LMGTFY\_bot <https://github.com/GabrielRF/telegram-lmgtfy_bot>`__ by
*GabrielRF*
- `Telegram
UrlProBot <https://github.com/GabrielRF/telegram-urlprobot>`__ by
*GabrielRF*
- `Telegram Proxy
Bot <https://bitbucket.org/master_groosha/telegram-proxy-bot>`__ by
*Groosha* - A simple BITM (bot-in-the-middle) for Telegram acting as
some kind of "proxy".
- `Telegram Proxy Bot <https://github.com/mrgigabyte/proxybot>`__ by
*mrgigabyte* -
``Credits for the original version of this bot goes to`` **Groosha**
``, simply added certain features which I thought were needed``.
- `RadRetroRobot <https://github.com/Tronikart/RadRetroRobot>`__ by
*Tronikart* - Multifunctional Telegram Bot RadRetroRobot.
- `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*
- `TagAlertBot <https://github.com/pitasi/TagAlertBot>`__ by *pitasi*
Want to have your bot listed here? Send a Telegram message to @eternnoir
or @pevdh.
.. |Download Month| image:: https://img.shields.io/pypi/v/pyTelegramBotAPI.svg
:target: https://pypi.python.org/pypi/pyTelegramBotAPI
.. |Build Status| image:: https://travis-ci.org/eternnoir/pyTelegramBotAPI.svg?branch=master
:target: https://travis-ci.org/eternnoir/pyTelegramBotAPI
.. |Download Month| image:: https://img.shields.io/pypi/dm/pyTelegramBotAPI.svg
:target: https://pypi.python.org/pypi/pyTelegramBotAPI

View File

@ -1,4 +1,4 @@
# This example show how to write an inline mode telegramt bot use pyTelegramBotAPI.
# This example show how to write an inline mode telegram bot use pyTelegramBotAPI.
import telebot
import time
import sys

View File

@ -0,0 +1,27 @@
# This example show how to use inline keyboards and process button presses
import telebot
from telebot.types import InlineKeyboardMarkup, InlineKeyboardButton
TELEGRAM_TOKEN = '<TOKEN>'
bot = telebot.TeleBot(TELEGRAM_TOKEN)
def gen_markup():
markup = InlineKeyboardMarkup()
markup.row_width = 2
markup.add(InlineKeyboardButton("Yes", callback_data=f"cb_yes"),
InlineKeyboardButton("No", callback_data=f"cb_no"))
return markup
@bot.callback_query_handler(func=lambda call: True)
def callback_query(call):
if call.data == "cb_yes":
bot.answer_callback_query(call.id, "Answer is Yes")
elif call.data == "cb_no":
bot.answer_callback_query(call.id, "Answer is No")
@bot.message_handler(func=lambda message: True)
def message_handler(message):
bot.send_message(message.chat.id, "Yes/no?", reply_markup=gen_markup())
bot.polling(none_stop=True)

View File

@ -75,4 +75,14 @@ def process_sex_step(message):
bot.reply_to(message, 'oooops')
# Enable saving next step handlers to file "./.handlers-saves/step.save".
# Delay=2 means that after any change in next step handlers (e.g. calling register_next_step_handler())
# saving will hapen after delay 2 seconds.
bot.enable_save_next_step_handlers(delay=2)
# Load next_step_handlers from save file (default "./.handlers-saves/step.save")
# WARNING It will work only if enable_save_next_step_handlers was called!
bot.load_next_step_handlers()
bot.polling()

View File

@ -7,6 +7,7 @@
import flask
import telebot
import logging
import time
API_TOKEN = '<api_token>'
@ -73,6 +74,8 @@ def echo_message(message):
# Remove webhook, it fails sometimes the set if there is a previous webhook
bot.remove_webhook()
time.sleep(0.1)
# Set webhook
bot.set_webhook(url=WEBHOOK_URL_BASE+WEBHOOK_URL_PATH,
certificate=open(WEBHOOK_SSL_CERT, 'r'))

View File

@ -1,29 +1,35 @@
import telebot
import os
import telebot
from flask import Flask, request
bot = telebot.TeleBot('<api_token>')
TOKEN = '<api_token>'
bot = telebot.TeleBot(TOKEN)
server = Flask(__name__)
@bot.message_handler(commands=['start'])
def start(message):
bot.reply_to(message, 'Hello, ' + message.from_user.first_name)
@bot.message_handler(func=lambda message: True, content_types=['text'])
def echo_message(message):
bot.reply_to(message, message.text)
@server.route("/bot", methods=['POST'])
@server.route('/' + TOKEN, methods=['POST'])
def getMessage():
bot.process_new_updates([telebot.types.Update.de_json(request.stream.read().decode("utf-8"))])
return "!", 200
@server.route("/")
def webhook():
bot.remove_webhook()
bot.set_webhook(url="https://herokuProject_url/bot")
bot.set_webhook(url='https://your_heroku_project.com/' + TOKEN)
return "!", 200
server.run(host="0.0.0.0", port=os.environ.get('PORT', 5000))
server = Flask(__name__)
if __name__ == "__main__":
server.run(host="0.0.0.0", port=int(os.environ.get('PORT', 5000)))

View File

@ -2,14 +2,15 @@
from setuptools import setup
from io import open
def readme():
with open('README.rst', encoding='utf-8') as f:
return f.read()
def read(filename):
with open(filename, encoding='utf-8') as file:
return file.read()
setup(name='pyTelegramBotAPI',
version='3.5.0',
version='3.6.6',
description='Python Telegram bot api. ',
long_description=readme(),
long_description=read('README.md'),
long_description_content_type="text/markdown",
author='eternnoir',
author_email='eternnoir@gmail.com',
url='https://github.com/eternnoir/pyTelegramBotAPI',

View File

@ -7,6 +7,9 @@ import re
import sys
import six
import os
import pickle
import logging
logger = logging.getLogger('TeleBot')
@ -27,6 +30,70 @@ Module : telebot
"""
class Handler:
"""
Class for (next step|reply) handlers
"""
def __init__(self, callback, *args, **kwargs):
self.callback = callback
self.args = args
self.kwargs = kwargs
def __getitem__(self, item):
return getattr(self, item)
class Saver:
"""
Class for saving (next step|reply) handlers
"""
def __init__(self, handlers, filename, delay):
self.handlers = handlers
self.filename = filename
self.delay = delay
self.timer = threading.Timer(delay, self.save_handlers)
def start_save_timer(self):
if not self.timer.is_alive():
if self.delay <= 0:
self.save_handlers()
else:
self.timer = threading.Timer(self.delay, self.save_handlers)
self.timer.start()
def save_handlers(self):
self.dump_handlers(self.handlers, self.filename)
def load_handlers(self, filename, del_file_after_loading=True):
tmp = self.return_load_handlers(filename, del_file_after_loading=del_file_after_loading)
if tmp is not None:
self.handlers.update(tmp)
@staticmethod
def dump_handlers(handlers, filename, file_mode="wb"):
dirs = filename.rsplit('/', maxsplit=1)[0]
os.makedirs(dirs, exist_ok=True)
with open(filename + ".tmp", file_mode) as file:
pickle.dump(handlers, file)
if os.path.isfile(filename):
os.remove(filename)
os.rename(filename + ".tmp", filename)
@staticmethod
def return_load_handlers(filename, del_file_after_loading=True):
if os.path.isfile(filename) and os.path.getsize(filename) > 0:
with open(filename, "rb") as file:
handlers = pickle.load(file)
if del_file_after_loading:
os.remove(filename)
return handlers
class TeleBot:
""" This is TeleBot Class
Methods:
@ -70,6 +137,7 @@ class TeleBot:
:param token: bot API token
:return: Telebot object.
"""
self.token = token
self.update_listener = []
self.skip_pending = skip_pending
@ -78,13 +146,14 @@ class TeleBot:
self.last_update_id = 0
self.exc_info = None
self.message_subscribers_messages = []
self.message_subscribers_callbacks = []
self.message_subscribers_lock = threading.Lock()
# key: message_id, value: handler list
self.reply_handlers = {}
# key: chat_id, value: handler list
self.message_subscribers_next_step = {}
self.pre_message_subscribers_next_step = {}
self.next_step_handlers = {}
self.next_step_saver = None
self.reply_saver = None
self.message_handlers = []
self.edited_message_handlers = []
@ -100,6 +169,54 @@ class TeleBot:
if self.threaded:
self.worker_pool = util.ThreadPool(num_threads=num_threads)
def enable_save_next_step_handlers(self, delay=120, filename="./.handler-saves/step.save"):
"""
Enable saving next step handlers (by default saving disable)
:param delay: Delay between changes in handlers and saving
:param filename: Filename of save file
"""
self.next_step_saver = Saver(self.next_step_handlers, filename, delay)
def enable_save_reply_handlers(self, delay=120, filename="./.handler-saves/reply.save"):
"""
Enable saving reply handlers (by default saving disable)
:param delay: Delay between changes in handlers and saving
:param filename: Filename of save file
"""
self.reply_saver = Saver(self.reply_handlers, filename, delay)
def disable_save_next_step_handlers(self):
"""
Disable saving next step handlers (by default saving disable)
"""
self.next_step_saver = None
def disable_save_reply_handlers(self):
"""
Disable saving next step handlers (by default saving disable)
"""
self.reply_saver = None
def load_next_step_handlers(self, filename="./.handler-saves/step.save", del_file_after_loading=True):
"""
Load next step handlers from save file
:param filename: Filename of the file where handlers was saved
:param del_file_after_loading: Is passed True, after loading save file will be deleted
"""
self.next_step_saver.load_handlers(filename, del_file_after_loading)
def load_reply_handlers(self, filename="./.handler-saves/reply.save", del_file_after_loading=True):
"""
Load reply handlers from save file
:param filename: Filename of the file where handlers was saved
:param del_file_after_loading: Is passed True, after loading save file will be deleted
"""
self.reply_saver.load_handlers(filename)
def set_webhook(self, url=None, certificate=None, max_connections=None, allowed_updates=None):
return apihelper.set_webhook(self.token, url, certificate, max_connections, allowed_updates)
@ -213,11 +330,10 @@ class TeleBot:
self.process_new_shipping_query(new_shipping_querys)
def process_new_messages(self, new_messages):
self._append_pre_next_step_handler()
self._notify_next_handlers(new_messages)
self._notify_reply_handlers(new_messages)
self.__notify_update(new_messages)
self._notify_command_handlers(self.message_handlers, new_messages)
self._notify_message_subscribers(new_messages)
self._notify_message_next_handler(new_messages)
def process_new_edited_messages(self, edited_message):
self._notify_command_handlers(self.edited_message_handlers, edited_message)
@ -247,6 +363,15 @@ class TeleBot:
for listener in self.update_listener:
self._exec_task(listener, new_messages)
def infinity_polling(self, *args, **kwargs):
while not self.__stop_polling.is_set():
try:
self.polling(*args, **kwargs)
except Exception as e:
time.sleep(5)
pass
logger.info("Break infinity polling")
def polling(self, none_stop=False, interval=0, timeout=20):
"""
This function creates a new Thread that calls an internal __retrieve_updates function.
@ -302,9 +427,9 @@ class TeleBot:
except KeyboardInterrupt:
logger.info("KeyboardInterrupt received.")
self.__stop_polling.set()
polling_thread.stop()
break
polling_thread.stop()
logger.info('Stopped polling.')
def __non_threaded_polling(self, none_stop=False, interval=0, timeout=3):
@ -341,6 +466,11 @@ class TeleBot:
def stop_polling(self):
self.__stop_polling.set()
def stop_bot(self):
self.stop_polling()
if self.worker_pool:
self.worker_pool.close()
def set_update_listener(self, listener):
self.update_listener.append(listener)
@ -351,6 +481,9 @@ class TeleBot:
def get_file(self, file_id):
return types.File.de_json(apihelper.get_file(self.token, file_id))
def get_file_url(self, file_id):
return apihelper.get_file_url(self.token, file_id)
def download_file(self, file_path):
return apihelper.download_file(self.token, file_path)
@ -478,7 +611,7 @@ class TeleBot:
def delete_message(self, chat_id, message_id):
"""
Use this method to delete message. Returns True on success.
Use this method to delete message. Returns True on success.
:param chat_id: in which chat to delete
:param message_id: which message to delete
:return: API reply.
@ -486,24 +619,25 @@ class TeleBot:
return apihelper.delete_message(self.token, chat_id, message_id)
def send_photo(self, chat_id, photo, caption=None, reply_to_message_id=None, reply_markup=None,
disable_notification=None):
parse_mode=None, disable_notification=None):
"""
Use this method to send photos.
:param disable_notification:
:param chat_id:
:param photo:
:param caption:
:param parse_mode
:param reply_to_message_id:
:param reply_markup:
:return: API reply.
"""
return types.Message.de_json(
apihelper.send_photo(self.token, chat_id, photo, caption, reply_to_message_id, reply_markup,
disable_notification))
parse_mode, disable_notification))
def send_audio(self, chat_id, audio, caption=None, duration=None, performer=None, title=None,
reply_to_message_id=None,
reply_markup=None, disable_notification=None, timeout=None):
reply_to_message_id=None, reply_markup=None, parse_mode=None, disable_notification=None,
timeout=None):
"""
Use this method to send audio files, if you want Telegram clients to display them in the music player. Your audio must be in the .mp3 format.
:param chat_id:Unique identifier for the message recipient
@ -511,16 +645,17 @@ class TeleBot:
:param duration:Duration of the audio in seconds
:param performer:Performer
:param title:Track name
:param parse_mode
:param reply_to_message_id:If the message is a reply, ID of the original message
:param reply_markup:
:return: Message
"""
return types.Message.de_json(
apihelper.send_audio(self.token, chat_id, audio, caption, duration, performer, title, reply_to_message_id,
reply_markup, disable_notification, timeout))
reply_markup, parse_mode, disable_notification, timeout))
def send_voice(self, chat_id, voice, caption=None, duration=None, reply_to_message_id=None, reply_markup=None,
disable_notification=None, timeout=None):
parse_mode=None, disable_notification=None, timeout=None):
"""
Use this method to send audio files, if you want Telegram clients to display the file as a playable voice message.
:param chat_id:Unique identifier for the message recipient.
@ -528,25 +663,28 @@ class TeleBot:
:param duration:Duration of sent audio in seconds
:param reply_to_message_id:
:param reply_markup:
:param parse_mode
:return: Message
"""
return types.Message.de_json(
apihelper.send_voice(self.token, chat_id, voice, caption, duration, reply_to_message_id, reply_markup,
disable_notification, timeout))
parse_mode, disable_notification, timeout))
def send_document(self, chat_id, data, reply_to_message_id=None, caption=None, reply_markup=None,
disable_notification=None, timeout=None):
parse_mode=None, disable_notification=None, timeout=None):
"""
Use this method to send general files.
:param chat_id:
:param data:
:param reply_to_message_id:
:param reply_markup:
:param parse_mode:
:param disable_notification:
:return: API reply.
"""
return types.Message.de_json(
apihelper.send_data(self.token, chat_id, data, 'document', reply_to_message_id, reply_markup,
disable_notification, timeout, caption=caption))
parse_mode, disable_notification, timeout, caption=caption))
def send_sticker(self, chat_id, data, reply_to_message_id=None, reply_markup=None, disable_notification=None,
timeout=None):
@ -563,20 +701,22 @@ class TeleBot:
disable_notification, timeout))
def send_video(self, chat_id, data, duration=None, caption=None, reply_to_message_id=None, reply_markup=None,
disable_notification=None, timeout=None):
parse_mode=None, supports_streaming=None, disable_notification=None, timeout=None):
"""
Use this method to send video files, Telegram clients support mp4 videos.
:param chat_id: Integer : Unique identifier for the message recipient — User or GroupChat id
:param data: InputFile or String : Video to send. You can either pass a file_id as String to resend a video that is already on the Telegram server
:param duration: Integer : Duration of sent video in seconds
:param caption: String : Video caption (may also be used when resending videos by file_id).
:param parse_mode:
:param supports_streaming:
:param reply_to_message_id:
:param reply_markup:
:return:
"""
return types.Message.de_json(
apihelper.send_video(self.token, chat_id, data, duration, caption, reply_to_message_id, reply_markup,
disable_notification, timeout))
parse_mode, supports_streaming, disable_notification, timeout))
def send_video_note(self, chat_id, data, duration=None, length=None, reply_to_message_id=None, reply_markup=None,
disable_notification=None, timeout=None):
@ -639,7 +779,7 @@ class TeleBot:
:return:
"""
return types.Message.de_json(
apihelper.edit_message_live_location(self, latitude, longitude, chat_id, message_id,
apihelper.edit_message_live_location(self.token, latitude, longitude, chat_id, message_id,
inline_message_id, reply_markup))
def stop_message_live_location(self, chat_id=None, message_id=None, inline_message_id=None, reply_markup=None):
@ -653,7 +793,7 @@ class TeleBot:
:return:
"""
return types.Message.de_json(
apihelper.stop_message_live_location(self, chat_id, message_id, inline_message_id, reply_markup))
apihelper.stop_message_live_location(self.token, chat_id, message_id, inline_message_id, reply_markup))
def send_venue(self, chat_id, latitude, longitude, title, address, foursquare_id=None, disable_notification=None,
reply_to_message_id=None, reply_markup=None):
@ -858,6 +998,12 @@ class TeleBot:
return result
return types.Message.de_json(result)
def edit_message_media(self, media, chat_id=None, message_id=None, inline_message_id=None, reply_markup=None):
result = apihelper.edit_message_media(self.token, media, chat_id, message_id, inline_message_id, reply_markup)
if type(result) == bool: # if edit inline message return is bool not Message.
return result
return types.Message.de_json(result)
def edit_message_reply_markup(self, chat_id=None, message_id=None, inline_message_id=None, reply_markup=None):
result = apihelper.edit_message_reply_markup(self.token, chat_id, message_id, inline_message_id, reply_markup)
if type(result) == bool:
@ -888,8 +1034,8 @@ class TeleBot:
def send_invoice(self, chat_id, title, description, invoice_payload, provider_token, currency, prices,
start_parameter, 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,
is_flexible=None,
disable_notification=None, reply_to_message_id=None, reply_markup=None, provider_data=None):
is_flexible=None, disable_notification=None, reply_to_message_id=None, reply_markup=None,
provider_data=None):
result = apihelper.send_invoice(self.token, chat_id, title, description, invoice_payload, provider_token,
currency, prices, start_parameter, photo_url, photo_size, photo_width,
photo_height,
@ -903,9 +1049,10 @@ class TeleBot:
def answer_pre_checkout_query(self, pre_checkout_query_id, ok, error_message=None):
return apihelper.answer_pre_checkout_query(self.token, pre_checkout_query_id, ok, error_message)
def edit_message_caption(self, caption, chat_id=None, message_id=None, inline_message_id=None, reply_markup=None):
def edit_message_caption(self, caption, chat_id=None, message_id=None, inline_message_id=None,
parse_mode=None, reply_markup=None):
result = apihelper.edit_message_caption(self.token, caption, chat_id, message_id, inline_message_id,
reply_markup)
parse_mode, reply_markup)
if type(result) == bool:
return result
return types.Message.de_json(result)
@ -961,7 +1108,6 @@ class TeleBot:
def get_sticker_set(self, name):
"""
Use this method to get a sticker set. On success, a StickerSet object is returned.
:param token:
:param name:
:return:
"""
@ -996,7 +1142,7 @@ class TeleBot:
return apihelper.create_new_sticker_set(self.token, user_id, name, title, png_sticker, emojis, contains_masks,
mask_position)
def add_sticker_to_set(self, user_id, name, png_sticker, emojis, mask_position):
def add_sticker_to_set(self, user_id, name, png_sticker, emojis, mask_position=None):
"""
Use this method to add a new sticker to a set created by the bot. Returns True on success.
:param user_id:
@ -1025,51 +1171,80 @@ class TeleBot:
"""
return apihelper.delete_sticker_from_set(self.token, sticker)
def register_for_reply(self, message, callback):
def register_for_reply(self, message, callback, *args, **kwargs):
"""
Registers a callback function to be notified when a reply to `message` arrives.
Warning: `message` must be sent with reply_markup=types.ForceReply(), otherwise TeleBot will not be able to see
the difference between a reply to `message` and an ordinary message.
Warning: In case `callback` as lambda function, saving reply handlers will not work.
:param message: The message for which we are awaiting a reply.
:param callback: The callback function to be called when a reply arrives. Must accept one `message`
parameter, which will contain the replied message.
"""
with self.message_subscribers_lock:
self.message_subscribers_messages.insert(0, message.message_id)
self.message_subscribers_callbacks.insert(0, callback)
if len(self.message_subscribers_messages) > 10000:
self.message_subscribers_messages.pop()
self.message_subscribers_callbacks.pop()
message_id = message.message_id
self.register_for_reply_by_message_id(message_id, callback, *args, **kwargs)
def _notify_message_subscribers(self, new_messages):
def register_for_reply_by_message_id(self, message_id, callback, *args, **kwargs):
"""
Registers a callback function to be notified when a reply to `message` arrives.
Warning: In case `callback` as lambda function, saving reply handlers will not work.
:param message_id: The id of the message for which we are awaiting a reply.
:param callback: The callback function to be called when a reply arrives. Must accept one `message`
parameter, which will contain the replied message.
"""
if message_id in self.reply_handlers.keys():
self.reply_handlers[message_id].append(Handler(callback, *args, **kwargs))
else:
self.reply_handlers[message_id] = [Handler(callback, *args, **kwargs)]
if self.reply_saver is not None:
self.reply_saver.start_save_timer()
def _notify_reply_handlers(self, new_messages):
for message in new_messages:
if not message.reply_to_message:
continue
if hasattr(message, "reply_to_message") and message.reply_to_message is not None:
reply_msg_id = message.reply_to_message.message_id
if reply_msg_id in self.reply_handlers.keys():
handlers = self.reply_handlers[reply_msg_id]
for handler in handlers:
self._exec_task(handler["callback"], message, *handler["args"], **handler["kwargs"])
self.reply_handlers.pop(reply_msg_id)
if self.reply_saver is not None:
self.reply_saver.start_save_timer()
reply_msg_id = message.reply_to_message.message_id
if reply_msg_id in self.message_subscribers_messages:
index = self.message_subscribers_messages.index(reply_msg_id)
self.message_subscribers_callbacks[index](message)
with self.message_subscribers_lock:
index = self.message_subscribers_messages.index(reply_msg_id)
del self.message_subscribers_messages[index]
del self.message_subscribers_callbacks[index]
def register_next_step_handler(self, message, callback):
def register_next_step_handler(self, message, callback, *args, **kwargs):
"""
Registers a callback function to be notified when new message arrives after `message`.
:param message: The message for which we want to handle new message after that in same chat.
Warning: In case `callback` as lambda function, saving next step handlers will not work.
:param message: The message for which we want to handle new message in the same chat.
:param callback: The callback function which next new message arrives.
:param args: Args to pass in callback func
:param kwargs: Args to pass in callback func
"""
chat_id = message.chat.id
if chat_id in self.pre_message_subscribers_next_step:
self.pre_message_subscribers_next_step[chat_id].append(callback)
self.register_next_step_handler_by_chat_id(chat_id, callback, *args, **kwargs)
def register_next_step_handler_by_chat_id(self, chat_id, callback, *args, **kwargs):
"""
Registers a callback function to be notified when new message arrives after `message`.
Warning: In case `callback` as lambda function, saving next step handlers will not work.
:param chat_id: The chat for which we want to handle new message.
:param callback: The callback function which next new message arrives.
:param args: Args to pass in callback func
:param kwargs: Args to pass in callback func
"""
if chat_id in self.next_step_handlers.keys():
self.next_step_handlers[chat_id].append(Handler(callback, *args, **kwargs))
else:
self.pre_message_subscribers_next_step[chat_id] = [callback]
self.next_step_handlers[chat_id] = [Handler(callback, *args, **kwargs)]
if self.next_step_saver is not None:
self.next_step_saver.start_save_timer()
def clear_step_handler(self, message):
"""
@ -1078,26 +1253,60 @@ class TeleBot:
:param message: The message for which we want to handle new message after that in same chat.
"""
chat_id = message.chat.id
self.pre_message_subscribers_next_step[chat_id] = []
self.clear_step_handler_by_chat_id(chat_id)
def _notify_message_next_handler(self, new_messages):
for message in new_messages:
def clear_step_handler_by_chat_id(self, chat_id):
"""
Clears all callback functions registered by register_next_step_handler().
:param chat_id: The chat for which we want to clear next step handlers
"""
self.next_step_handlers[chat_id] = []
if self.next_step_saver is not None:
self.next_step_saver.start_save_timer()
def clear_reply_handlers(self, message):
"""
Clears all callback functions registered by register_for_reply() and register_for_reply_by_message_id().
:param message: The message for which we want to clear reply handlers
"""
message_id = message.message_id
self.clear_reply_handlers_by_message_id(message_id)
def clear_reply_handlers_by_message_id(self, message_id):
"""
Clears all callback functions registered by register_for_reply() and register_for_reply_by_message_id().
:param message_id: The message id for which we want to clear reply handlers
"""
self.reply_handlers[message_id] = []
if self.reply_saver is not None:
self.reply_saver.start_save_timer()
def _notify_next_handlers(self, new_messages):
i = 0
while i < len(new_messages):
message = new_messages[i]
chat_id = message.chat.id
if chat_id in self.message_subscribers_next_step:
handlers = self.message_subscribers_next_step[chat_id]
for handler in handlers:
self._exec_task(handler, message)
self.message_subscribers_next_step.pop(chat_id, None)
was_poped = False
if chat_id in self.next_step_handlers.keys():
handlers = self.next_step_handlers.pop(chat_id, None)
if (handlers):
for handler in handlers:
self._exec_task(handler["callback"], message, *handler["args"], **handler["kwargs"])
new_messages.pop(i) # removing message that detects with next_step_handler
was_poped = True
if self.next_step_saver is not None:
self.next_step_saver.start_save_timer()
if (not was_poped):
i += 1
def _append_pre_next_step_handler(self):
for k in self.pre_message_subscribers_next_step.keys():
if k in self.message_subscribers_next_step:
self.message_subscribers_next_step[k].extend(self.pre_message_subscribers_next_step[k])
else:
self.message_subscribers_next_step[k] = self.pre_message_subscribers_next_step[k]
self.pre_message_subscribers_next_step = {}
def _build_handler_dict(self, handler, **filters):
@staticmethod
def _build_handler_dict(handler, **filters):
return {
'function': handler,
'filters': filters
@ -1263,7 +1472,8 @@ class TeleBot:
return True
def _test_filter(self, filter, filter_value, message):
@staticmethod
def _test_filter(filter, filter_value, message):
test_cases = {
'content_types': lambda msg: msg.content_type in filter_value,
'regexp': lambda msg: msg.content_type == 'text' and re.search(filter_value, msg.text, re.IGNORECASE),
@ -1275,9 +1485,6 @@ class TeleBot:
def _notify_command_handlers(self, handlers, new_messages):
for message in new_messages:
# if message has next step handler, dont exec command handlers
if hasattr(message, 'chat') and message.chat and (message.chat.id in self.message_subscribers_next_step):
continue
for message_handler in handlers:
if self._test_message_handler(message_handler, message):
self._exec_task(message_handler['function'], message)
@ -1288,230 +1495,260 @@ class AsyncTeleBot(TeleBot):
def __init__(self, *args, **kwargs):
TeleBot.__init__(self, *args, **kwargs)
@util.async()
@util.async_dec()
def enable_save_next_step_handlers(self, delay=120, filename="./.handler-saves/step.save", del_file_after_loading=True):
return TeleBot.enable_save_next_step_handlers(self, delay, filename, del_file_after_loading)
@util.async_dec()
def enable_save_reply_handlers(self, delay=120, filename="./.handler-saves/reply.save", del_file_after_loading=True):
return TeleBot.enable_save_reply_handlers(self, delay, filename, del_file_after_loading)
@util.async_dec()
def disable_save_next_step_handlers(self):
return TeleBot.disable_save_next_step_handlers(self)
@util.async_dec()
def disable_save_reply_handlers(self):
return TeleBot.enable_save_reply_handlers(self)
@util.async_dec()
def load_next_step_handlers(self, filename="./.handler-saves/step.save"):
return TeleBot.load_next_step_handlers(self, filename)
@util.async_dec()
def load_reply_handlers(self, filename="./.handler-saves/reply.save"):
return TeleBot.load_reply_handlers(self, filename)
@util.async_dec()
@util.async_dec()
def get_me(self):
return TeleBot.get_me(self)
@util.async()
@util.async_dec()
def get_file(self, *args):
return TeleBot.get_file(self, *args)
@util.async()
@util.async_dec()
def download_file(self, *args):
return TeleBot.download_file(self, *args)
@util.async()
@util.async_dec()
def get_user_profile_photos(self, *args, **kwargs):
return TeleBot.get_user_profile_photos(self, *args, **kwargs)
@util.async()
@util.async_dec()
def get_chat(self, *args):
return TeleBot.get_chat(self, *args)
@util.async()
@util.async_dec()
def leave_chat(self, *args):
return TeleBot.leave_chat(self, *args)
@util.async()
@util.async_dec()
def get_chat_administrators(self, *args):
return TeleBot.get_chat_administrators(self, *args)
@util.async()
@util.async_dec()
def get_chat_members_count(self, *args):
return TeleBot.get_chat_members_count(self, *args)
@util.async()
@util.async_dec()
def set_chat_sticker_set(self, *args):
return TeleBot.set_chat_sticker_set(self, *args)
@util.async()
@util.async_dec()
def delete_chat_sticker_set(self, *args):
return TeleBot.delete_chat_sticker_set(self, *args)
@util.async()
@util.async_dec()
def get_chat_member(self, *args):
return TeleBot.get_chat_member(self, *args)
@util.async()
@util.async_dec()
def send_message(self, *args, **kwargs):
return TeleBot.send_message(self, *args, **kwargs)
@util.async()
@util.async_dec()
def forward_message(self, *args, **kwargs):
return TeleBot.forward_message(self, *args, **kwargs)
@util.async()
@util.async_dec()
def delete_message(self, *args):
return TeleBot.delete_message(self, *args)
@util.async()
@util.async_dec()
def send_photo(self, *args, **kwargs):
return TeleBot.send_photo(self, *args, **kwargs)
@util.async()
@util.async_dec()
def send_audio(self, *args, **kwargs):
return TeleBot.send_audio(self, *args, **kwargs)
@util.async()
@util.async_dec()
def send_voice(self, *args, **kwargs):
return TeleBot.send_voice(self, *args, **kwargs)
@util.async()
@util.async_dec()
def send_document(self, *args, **kwargs):
return TeleBot.send_document(self, *args, **kwargs)
@util.async()
@util.async_dec()
def send_sticker(self, *args, **kwargs):
return TeleBot.send_sticker(self, *args, **kwargs)
@util.async()
@util.async_dec()
def send_video(self, *args, **kwargs):
return TeleBot.send_video(self, *args, **kwargs)
@util.async()
@util.async_dec()
def send_video_note(self, *args, **kwargs):
return TeleBot.send_video_note(self, *args, **kwargs)
@util.async()
@util.async_dec()
def send_media_group(self, *args, **kwargs):
return TeleBot.send_media_group(self, *args, **kwargs)
@util.async()
@util.async_dec()
def send_location(self, *args, **kwargs):
return TeleBot.send_location(self, *args, **kwargs)
@util.async()
@util.async_dec()
def edit_message_live_location(self, *args, **kwargs):
return TeleBot.edit_message_live_location(self, *args, **kwargs)
@util.async()
@util.async_dec()
def stop_message_live_location(self, *args, **kwargs):
return TeleBot.stop_message_live_location(self, *args, **kwargs)
@util.async()
@util.async_dec()
def send_venue(self, *args, **kwargs):
return TeleBot.send_venue(self, *args, **kwargs)
@util.async()
@util.async_dec()
def send_contact(self, *args, **kwargs):
return TeleBot.send_contact(self, *args, **kwargs)
@util.async()
@util.async_dec()
def send_chat_action(self, *args, **kwargs):
return TeleBot.send_chat_action(self, *args, **kwargs)
@util.async()
@util.async_dec()
def kick_chat_member(self, *args, **kwargs):
return TeleBot.kick_chat_member(self, *args, **kwargs)
@util.async()
@util.async_dec()
def unban_chat_member(self, *args):
return TeleBot.unban_chat_member(self, *args)
@util.async()
@util.async_dec()
def restrict_chat_member(self, *args, **kwargs):
return TeleBot.restrict_chat_member(self, *args, **kwargs)
@util.async()
@util.async_dec()
def promote_chat_member(self, *args, **kwargs):
return TeleBot.promote_chat_member(self, *args, **kwargs)
@util.async()
@util.async_dec()
def export_chat_invite_link(self, *args):
return TeleBot.export_chat_invite_link(self, *args)
@util.async()
@util.async_dec()
def set_chat_photo(self, *args):
return TeleBot.set_chat_photo(self, *args)
@util.async()
@util.async_dec()
def delete_chat_photo(self, *args):
return TeleBot.delete_chat_photo(self, *args)
@util.async()
@util.async_dec()
def set_chat_title(self, *args):
return TeleBot.set_chat_title(self, *args)
@util.async()
@util.async_dec()
def set_chat_description(self, *args):
return TeleBot.set_chat_description(self, *args)
@util.async()
@util.async_dec()
def pin_chat_message(self, *args, **kwargs):
return TeleBot.pin_chat_message(self, *args, **kwargs)
@util.async()
@util.async_dec()
def unpin_chat_message(self, *args):
return TeleBot.unpin_chat_message(self, *args)
@util.async()
@util.async_dec()
def edit_message_text(self, *args, **kwargs):
return TeleBot.edit_message_text(self, *args, **kwargs)
@util.async()
@util.async_dec()
def edit_message_media(self, *args, **kwargs):
return TeleBot.edit_message_media(self, *args, **kwargs)
@util.async_dec()
def edit_message_reply_markup(self, *args, **kwargs):
return TeleBot.edit_message_reply_markup(self, *args, **kwargs)
@util.async()
@util.async_dec()
def send_game(self, *args, **kwargs):
return TeleBot.send_game(self, *args, **kwargs)
@util.async()
@util.async_dec()
def set_game_score(self, *args, **kwargs):
return TeleBot.set_game_score(self, *args, **kwargs)
@util.async()
@util.async_dec()
def get_game_high_scores(self, *args, **kwargs):
return TeleBot.get_game_high_scores(self, *args, **kwargs)
@util.async()
@util.async_dec()
def send_invoice(self, *args, **kwargs):
return TeleBot.send_invoice(self, *args, **kwargs)
@util.async()
@util.async_dec()
def answer_shipping_query(self, *args, **kwargs):
return TeleBot.answer_shipping_query(self, *args, **kwargs)
@util.async()
@util.async_dec()
def answer_pre_checkout_query(self, *args, **kwargs):
return TeleBot.answer_pre_checkout_query(self, *args, **kwargs)
@util.async()
@util.async_dec()
def edit_message_caption(self, *args, **kwargs):
return TeleBot.edit_message_caption(self, *args, **kwargs)
@util.async()
@util.async_dec()
def answer_inline_query(self, *args, **kwargs):
return TeleBot.answer_inline_query(self, *args, **kwargs)
@util.async()
@util.async_dec()
def answer_callback_query(self, *args, **kwargs):
return TeleBot.answer_callback_query(self, *args, **kwargs)
@util.async()
@util.async_dec()
def send_sticker(self, *args, **kwargs):
return TeleBot.send_sticker(self, *args, **kwargs)
@util.async()
@util.async_dec()
def get_sticker_set(self, *args, **kwargs):
return TeleBot.get_sticker_set(self, *args, **kwargs)
@util.async()
@util.async_dec()
def upload_sticker_file(self, *args, **kwargs):
return TeleBot.upload_sticker_file(self, *args, **kwargs)
@util.async()
@util.async_dec()
def create_new_sticker_set(self, *args, **kwargs):
return TeleBot.create_new_sticker_set(self, *args, **kwargs)
@util.async()
@util.async_dec()
def add_sticker_to_set(self, *args, **kwargs):
return TeleBot.add_sticker_to_set(self, *args, **kwargs)
@util.async()
@util.async_dec()
def set_sticker_position_in_set(self, *args, **kwargs):
return TeleBot.set_sticker_position_in_set(self, *args, **kwargs)
@util.async()
@util.async_dec()
def delete_sticker_from_set(self, *args, **kwargs):
return TeleBot.delete_sticker_from_set(self, *args, **kwargs)

View File

@ -27,8 +27,8 @@ CONNECT_TIMEOUT = 3.5
READ_TIMEOUT = 9999
def _get_req_session():
return util.per_thread('req_session', lambda: requests.session())
def _get_req_session(reset=False):
return util.per_thread('req_session', lambda: requests.session(), reset)
def _make_request(token, method_name, method='get', params=None, files=None, base_url=API_URL):
@ -98,9 +98,14 @@ def get_file(token, file_id):
return _make_request(token, method_url, params={'file_id': file_id})
def get_file_url(token, file_id):
method_url = r'getFile'
return FILE_URL.format(token, get_file(token, file_id).file_path)
def download_file(token, file_path):
url = FILE_URL.format(token, file_path)
result = _get_req_session().get(url)
result = _get_req_session().get(url, proxies=proxy)
if result.status_code != 200:
msg = 'The server returned HTTP {0} {1}. Response body:\n[{2}]' \
.format(result.status_code, result.reason, result.text)
@ -236,7 +241,7 @@ def forward_message(token, chat_id, from_chat_id, message_id, disable_notificati
def send_photo(token, chat_id, photo, caption=None, reply_to_message_id=None, reply_markup=None,
disable_notification=None):
parse_mode=None, disable_notification=None):
method_url = r'sendPhoto'
payload = {'chat_id': chat_id}
files = None
@ -250,6 +255,8 @@ def send_photo(token, chat_id, photo, caption=None, reply_to_message_id=None, re
payload['reply_to_message_id'] = reply_to_message_id
if reply_markup:
payload['reply_markup'] = _convert_markup(reply_markup)
if parse_mode:
payload['parse_mode'] = parse_mode
if disable_notification:
payload['disable_notification'] = disable_notification
return _make_request(token, method_url, params=payload, files=files, method='post')
@ -257,14 +264,14 @@ def send_photo(token, chat_id, photo, caption=None, reply_to_message_id=None, re
def send_media_group(token, chat_id, media, disable_notification=None, reply_to_message_id=None):
method_url = r'sendMediaGroup'
media_json = _convert_list_json_serializable(media)
print(media_json)
media_json, files = _convert_input_media_array(media)
payload = {'chat_id': chat_id, 'media': media_json}
if disable_notification:
payload['disable_notification'] = disable_notification
if reply_to_message_id:
payload['reply_to_message_id'] = reply_to_message_id
return _make_request(token, method_url, params=payload)
return _make_request(token, method_url, params=payload, method='post' if files else 'get',
files=files if files else None)
def send_location(token, chat_id, latitude, longitude, live_period=None, reply_to_message_id=None, reply_markup=None,
@ -272,7 +279,7 @@ def send_location(token, chat_id, latitude, longitude, live_period=None, reply_t
method_url = r'sendLocation'
payload = {'chat_id': chat_id, 'latitude': latitude, 'longitude': longitude}
if live_period:
payload['live_perion'] = live_period
payload['live_period'] = live_period
if reply_to_message_id:
payload['reply_to_message_id'] = reply_to_message_id
if reply_markup:
@ -349,7 +356,7 @@ def send_chat_action(token, chat_id, action):
def send_video(token, chat_id, data, duration=None, caption=None, reply_to_message_id=None, reply_markup=None,
disable_notification=None, timeout=None):
parse_mode=None, supports_streaming=None, disable_notification=None, timeout=None):
method_url = r'sendVideo'
payload = {'chat_id': chat_id}
files = None
@ -365,6 +372,10 @@ def send_video(token, chat_id, data, duration=None, caption=None, reply_to_messa
payload['reply_to_message_id'] = reply_to_message_id
if reply_markup:
payload['reply_markup'] = _convert_markup(reply_markup)
if parse_mode:
payload['parse_mode'] = parse_mode
if supports_streaming:
payload['supports_streaming'] = supports_streaming
if disable_notification:
payload['disable_notification'] = disable_notification
if timeout:
@ -373,7 +384,7 @@ def send_video(token, chat_id, data, duration=None, caption=None, reply_to_messa
def send_voice(token, chat_id, voice, caption=None, duration=None, reply_to_message_id=None, reply_markup=None,
disable_notification=None, timeout=None):
parse_mode=None, disable_notification=None, timeout=None):
method_url = r'sendVoice'
payload = {'chat_id': chat_id}
files = None
@ -389,6 +400,8 @@ def send_voice(token, chat_id, voice, caption=None, duration=None, reply_to_mess
payload['reply_to_message_id'] = reply_to_message_id
if reply_markup:
payload['reply_markup'] = _convert_markup(reply_markup)
if parse_mode:
payload['parse_mode'] = parse_mode
if disable_notification:
payload['disable_notification'] = disable_notification
if timeout:
@ -423,7 +436,7 @@ def send_video_note(token, chat_id, data, duration=None, length=None, reply_to_m
def send_audio(token, chat_id, audio, caption=None, duration=None, performer=None, title=None, reply_to_message_id=None,
reply_markup=None, disable_notification=None, timeout=None):
reply_markup=None, parse_mode=None, disable_notification=None, timeout=None):
method_url = r'sendAudio'
payload = {'chat_id': chat_id}
files = None
@ -443,6 +456,8 @@ def send_audio(token, chat_id, audio, caption=None, duration=None, performer=Non
payload['reply_to_message_id'] = reply_to_message_id
if reply_markup:
payload['reply_markup'] = _convert_markup(reply_markup)
if parse_mode:
payload['parse_mode'] = parse_mode
if disable_notification:
payload['disable_notification'] = disable_notification
if timeout:
@ -450,8 +465,8 @@ def send_audio(token, chat_id, audio, caption=None, duration=None, performer=Non
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, disable_notification=None,
timeout=None, caption=None):
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):
method_url = get_method_by_type(data_type)
payload = {'chat_id': chat_id}
files = None
@ -463,6 +478,8 @@ def send_data(token, chat_id, data, data_type, reply_to_message_id=None, reply_m
payload['reply_to_message_id'] = reply_to_message_id
if reply_markup:
payload['reply_markup'] = _convert_markup(reply_markup)
if parse_mode and data_type == 'document':
payload['parse_mode'] = parse_mode
if disable_notification:
payload['disable_notification'] = disable_notification
if timeout:
@ -604,7 +621,8 @@ def edit_message_text(token, text, chat_id=None, message_id=None, inline_message
return _make_request(token, method_url, params=payload)
def edit_message_caption(token, caption, chat_id=None, message_id=None, inline_message_id=None, reply_markup=None):
def edit_message_caption(token, caption, chat_id=None, message_id=None, inline_message_id=None,
parse_mode=None, reply_markup=None):
method_url = r'editMessageCaption'
payload = {'caption': caption}
if chat_id:
@ -613,11 +631,28 @@ def edit_message_caption(token, caption, chat_id=None, message_id=None, inline_m
payload['message_id'] = message_id
if inline_message_id:
payload['inline_message_id'] = inline_message_id
if parse_mode:
payload['parse_mode'] = parse_mode
if reply_markup:
payload['reply_markup'] = _convert_markup(reply_markup)
return _make_request(token, method_url, params=payload)
def edit_message_media(token, media, chat_id=None, message_id=None, inline_message_id=None, reply_markup=None):
method_url = r'editMessageMedia'
media_json, file = _convert_input_media(media)
payload = {'media': media_json}
if chat_id:
payload['chat_id'] = chat_id
if message_id:
payload['message_id'] = message_id
if inline_message_id:
payload['inline_message_id'] = inline_message_id
if reply_markup:
payload['reply_markup'] = _convert_markup(reply_markup)
return _make_request(token, method_url, params=payload, files=file, method='post' if file else 'get')
def edit_message_reply_markup(token, chat_id=None, message_id=None, inline_message_id=None, reply_markup=None):
method_url = r'editMessageReplyMarkup'
payload = {}
@ -827,7 +862,7 @@ def answer_callback_query(token, callback_query_id, text=None, show_alert=None,
payload['show_alert'] = show_alert
if url:
payload['url'] = url
if cache_time:
if cache_time is not None:
payload['cache_time'] = cache_time
return _make_request(token, method_url, params=payload, method='post')
@ -836,7 +871,7 @@ def answer_inline_query(token, inline_query_id, results, cache_time=None, is_per
switch_pm_text=None, switch_pm_parameter=None):
method_url = 'answerInlineQuery'
payload = {'inline_query_id': inline_query_id, 'results': _convert_list_json_serializable(results)}
if cache_time:
if cache_time is not None:
payload['cache_time'] = cache_time
if is_personal:
payload['is_personal'] = is_personal
@ -917,10 +952,29 @@ def _convert_markup(markup):
return markup
def _convert_input_media(media):
if isinstance(media, types.InputMedia):
return media._convert_input_media()
return None, None
def _convert_input_media_array(array):
media = []
files = {}
for input_media in array:
if isinstance(input_media, types.InputMedia):
media_dict = input_media.to_dic()
if media_dict['media'].startswith('attach://'):
key = media_dict['media'].replace('attach://', '')
files[key] = input_media.media
media.append(media_dict)
return json.dumps(media), files
def _no_encode(func):
def wrapper(key, val):
if key == 'filename':
return '{0}={1}'.format(key, val)
return u'{0}={1}'.format(key, val)
else:
return func(key, val)

View File

@ -10,7 +10,7 @@ import six
from telebot import util
class JsonSerializable:
class JsonSerializable(object):
"""
Subclasses of this class are guaranteed to be able to be converted to JSON format.
All subclasses of this class must override to_json.
@ -26,7 +26,7 @@ class JsonSerializable:
raise NotImplementedError
class Dictionaryable:
class Dictionaryable(object):
"""
Subclasses of this class are guaranteed to be able to be converted to dictionary.
All subclasses of this class must override to_dic.
@ -42,7 +42,7 @@ class Dictionaryable:
raise NotImplementedError
class JsonDeserializable:
class JsonDeserializable(object):
"""
Subclasses of this class are guaranteed to be able to be created from a json-style dict or json formatted string.
All subclasses of this class must override de_json.
@ -127,7 +127,6 @@ class Update(JsonDeserializable):
def __init__(self, update_id, message, edited_message, channel_post, edited_channel_post, inline_query,
chosen_inline_result, callback_query, shipping_query, pre_checkout_query):
self.update_id = update_id
self.edited_message = edited_message
self.message = message
self.edited_message = edited_message
self.channel_post = channel_post
@ -274,6 +273,8 @@ class Message(JsonDeserializable):
opts['reply_to_message'] = Message.de_json(obj['reply_to_message'])
if 'edit_date' in obj:
opts['edit_date'] = obj.get('edit_date')
if 'media_group_id' in obj:
opts['media_group_id'] = obj.get('media_group_id')
if 'author_signature' in obj:
opts['author_signature'] = obj.get('author_signature')
if 'text' in obj:
@ -333,10 +334,13 @@ class Message(JsonDeserializable):
content_type = 'left_chat_member'
if 'new_chat_title' in obj:
opts['new_chat_title'] = obj['new_chat_title']
content_type = 'new_chat_title'
if 'new_chat_photo' in obj:
opts['new_chat_photo'] = Message.parse_photo(obj['new_chat_photo'])
content_type = 'new_chat_photo'
if 'delete_chat_photo' in obj:
opts['delete_chat_photo'] = obj['delete_chat_photo']
content_type = 'delete_chat_photo'
if 'group_chat_created' in obj:
opts['group_chat_created'] = obj['group_chat_created']
content_type = 'group_chat_created'
@ -348,8 +352,10 @@ class Message(JsonDeserializable):
content_type = 'channel_chat_created'
if 'migrate_to_chat_id' in obj:
opts['migrate_to_chat_id'] = obj['migrate_to_chat_id']
content_type = 'migrate_to_chat_id'
if 'migrate_from_chat_id' in obj:
opts['migrate_from_chat_id'] = obj['migrate_from_chat_id']
content_type = 'migrate_from_chat_id'
if 'pinned_message' in obj:
opts['pinned_message'] = Message.de_json(obj['pinned_message'])
content_type = 'pinned_message'
@ -359,7 +365,10 @@ class Message(JsonDeserializable):
if 'successful_payment' in obj:
opts['successful_payment'] = SuccessfulPayment.de_json(obj['successful_payment'])
content_type = 'successful_payment'
return cls(message_id, from_user, date, chat, content_type, opts)
if 'connected_website' in obj:
opts['connected_website'] = obj['connected_website']
content_type = 'connected_website'
return cls(message_id, from_user, date, chat, content_type, opts, json_string)
@classmethod
def parse_chat(cls, chat):
@ -382,7 +391,7 @@ class Message(JsonDeserializable):
ret.append(MessageEntity.de_json(me))
return ret
def __init__(self, message_id, from_user, date, chat, content_type, options):
def __init__(self, message_id, from_user, date, chat, content_type, options, json_string):
self.content_type = content_type
self.message_id = message_id
self.from_user = from_user
@ -393,6 +402,7 @@ class Message(JsonDeserializable):
self.forward_date = None
self.reply_to_message = None
self.edit_date = None
self.media_group_id = None
self.author_signature = None
self.text = None
self.entities = None
@ -422,9 +432,74 @@ class Message(JsonDeserializable):
self.pinned_message = None
self.invoice = None
self.successful_payment = None
self.connected_website = None
for key in options:
setattr(self, key, options[key])
self.json = json_string
def __html_text(self, text, entities):
"""
Author: @sviat9440
Message: "*Test* parse _formatting_, [url](https://example.com), [text_mention](tg://user?id=123456) and mention @username"
Example:
message.html_text
>> "<b>Test</b> parse <i>formatting</i>, <a href=\"https://example.com\">url</a>, <a href=\"tg://user?id=123456\">text_mention</a> and mention @username"
Cusom subs:
You can customize the substitutes. By default, there is no substitute for the entities: hashtag, bot_command, email. You can add or modify substitute an existing entity.
Example:
message.custom_subs = {"bold": "<strong class=\"example\">{text}</strong>", "italic": "<i class=\"example\">{text}</i>", "mention": "<a href={url}>{text}</a>"}
message.html_text
>> "<strong class=\"example\">Test</strong> parse <i class=\"example\">formatting</i>, <a href=\"https://example.com\">url</a> and <a href=\"tg://user?id=123456\">text_mention</a> and mention <a href=\"https://t.me/username\">@username</a>"
"""
if not entities:
return text
_subs = {
"bold": "<b>{text}</b>",
"italic": "<i>{text}</i>",
"pre": "<pre>{text}</pre>",
"code": "<code>{text}</code>",
"url": "<a href=\"{url}\">{text}</a>",
"text_link": "<a href=\"{url}\">{text}</a>"
}
if hasattr(self, "custom_subs"):
for type in self.custom_subs:
_subs[type] = self.custom_subs[type]
utf16_text = text.encode("utf-16-le")
html_text = ""
def func(text, type=None, url=None, user=None):
text = text.decode("utf-16-le")
if type == "text_mention":
type = "url"
url = "tg://user?id={0}".format(user.id)
elif type == "mention":
url = "https://t.me/{0}".format(text[1:])
if not type or not _subs.get(type):
return text
subs = _subs.get(type)
text = text.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
return subs.format(text=text, url=url)
offset = 0
for entity in entities:
if entity.offset > offset:
html_text += func(utf16_text[offset * 2 : entity.offset * 2])
offset = entity.offset
html_text += func(utf16_text[offset * 2 : (offset + entity.length) * 2], entity.type, entity.url, entity.user)
offset += entity.length
if offset * 2 < len(utf16_text):
html_text += func(utf16_text[offset * 2:])
return html_text
@property
def html_text(self):
return self.__html_text(self.text, self.entities)
@property
def html_caption(self):
return self.__html_text(self.caption, self.caption_entities)
class MessageEntity(JsonDeserializable):
@classmethod
@ -1116,7 +1191,7 @@ class InlineQueryResultArticle(JsonSerializable):
class InlineQueryResultPhoto(JsonSerializable):
def __init__(self, id, photo_url, thumb_url, photo_width=None, photo_height=None, title=None,
description=None, caption=None, reply_markup=None, input_message_content=None):
description=None, caption=None, parse_mode=None, reply_markup=None, input_message_content=None):
"""
Represents a link to a photo.
:param id: Unique identifier for this result, 1-64 bytes
@ -1127,6 +1202,8 @@ class InlineQueryResultPhoto(JsonSerializable):
:param title: Title for the result.
:param description: Short description of the result.
:param caption: Caption of the photo to be sent, 0-200 characters.
:param parse_mode: Send Markdown or HTML, if you want Telegram apps to show bold, italic, fixed-width text or
inline URLs in the media caption.
:param reply_markup: InlineKeyboardMarkup : Inline keyboard attached to the message
:param input_message_content: InputMessageContent : Content of the message to be sent instead of the photo
:return:
@ -1140,6 +1217,7 @@ class InlineQueryResultPhoto(JsonSerializable):
self.title = title
self.description = description
self.caption = caption
self.parse_mode = parse_mode
self.reply_markup = reply_markup
self.input_message_content = input_message_content
@ -1155,6 +1233,8 @@ class InlineQueryResultPhoto(JsonSerializable):
json_dict['description'] = self.description
if self.caption:
json_dict['caption'] = self.caption
if self.parse_mode:
json_dict['parse_mode'] = self.parse_mode
if self.reply_markup:
json_dict['reply_markup'] = self.reply_markup.to_dic()
if self.input_message_content:
@ -1211,7 +1291,7 @@ class InlineQueryResultGif(JsonSerializable):
class InlineQueryResultMpeg4Gif(JsonSerializable):
def __init__(self, id, mpeg4_url, thumb_url, mpeg4_width=None, mpeg4_height=None, title=None, caption=None,
reply_markup=None, input_message_content=None, mpeg4_duration=None):
parse_mode=None, reply_markup=None, input_message_content=None, mpeg4_duration=None):
"""
Represents a link to a video animation (H.264/MPEG-4 AVC video without sound).
:param id: Unique identifier for this result, 1-64 bytes
@ -1221,6 +1301,8 @@ class InlineQueryResultMpeg4Gif(JsonSerializable):
:param mpeg4_height: Video height
:param title: Title for the result
:param caption: Caption of the MPEG-4 file to be sent, 0-200 characters
:param parse_mode: Send Markdown or HTML, if you want Telegram apps to show bold, italic, fixed-width text
or inline URLs in the media caption.
:param reply_markup: InlineKeyboardMarkup : Inline keyboard attached to the message
:param input_message_content: InputMessageContent : Content of the message to be sent instead of the photo
:return:
@ -1233,6 +1315,7 @@ class InlineQueryResultMpeg4Gif(JsonSerializable):
self.thumb_url = thumb_url
self.title = title
self.caption = caption
self.parse_mode = parse_mode
self.reply_markup = reply_markup
self.input_message_content = input_message_content
self.mpeg4_duration = mpeg4_duration
@ -1247,6 +1330,8 @@ class InlineQueryResultMpeg4Gif(JsonSerializable):
json_dict['title'] = self.title
if self.caption:
json_dict['caption'] = self.caption
if self.parse_mode:
json_dict['parse_mode'] = self.parse_mode
if self.reply_markup:
json_dict['reply_markup'] = self.reply_markup.to_dic()
if self.input_message_content:
@ -1258,8 +1343,8 @@ class InlineQueryResultMpeg4Gif(JsonSerializable):
class InlineQueryResultVideo(JsonSerializable):
def __init__(self, id, video_url, mime_type, thumb_url, title,
caption=None, video_width=None, video_height=None, video_duration=None, description=None,
reply_markup=None, input_message_content=None):
caption=None, parse_mode=None, video_width=None, video_height=None, video_duration=None,
description=None, reply_markup=None, input_message_content=None):
"""
Represents link to a page containing an embedded video player or a video file.
:param id: Unique identifier for this result, 1-64 bytes
@ -1267,6 +1352,8 @@ class InlineQueryResultVideo(JsonSerializable):
:param mime_type: Mime type of the content of video url, “text/html” or “video/mp4”
:param thumb_url: URL of the thumbnail (jpeg only) for the video
:param title: Title for the result
:param parse_mode: Send Markdown or HTML, if you want Telegram apps to show bold, italic, fixed-width text or
inline URLs in the media caption.
:param video_width: Video width
:param video_height: Video height
:param video_duration: Video duration in seconds
@ -1283,6 +1370,7 @@ class InlineQueryResultVideo(JsonSerializable):
self.thumb_url = thumb_url
self.title = title
self.caption = caption
self.parse_mode = parse_mode
self.description = description
self.input_message_content = input_message_content
self.reply_markup = reply_markup
@ -1300,6 +1388,8 @@ class InlineQueryResultVideo(JsonSerializable):
json_dict['description'] = self.description
if self.caption:
json_dict['caption'] = self.caption
if self.parse_mode:
json_dict['parse_mode'] = self.parse_mode
if self.reply_markup:
json_dict['reply_markup'] = self.reply_markup.to_dic()
if self.input_message_content:
@ -1308,13 +1398,14 @@ class InlineQueryResultVideo(JsonSerializable):
class InlineQueryResultAudio(JsonSerializable):
def __init__(self, id, audio_url, title, caption=None, performer=None, audio_duration=None, reply_markup=None,
input_message_content=None):
def __init__(self, id, audio_url, title, caption=None, parse_mode=None, performer=None, audio_duration=None,
reply_markup=None, input_message_content=None):
self.type = 'audio'
self.id = id
self.audio_url = audio_url
self.title = title
self.caption = caption
self.parse_mode = parse_mode
self.performer = performer
self.audio_duration = audio_duration
self.reply_markup = reply_markup
@ -1324,6 +1415,8 @@ class InlineQueryResultAudio(JsonSerializable):
json_dict = {'type': self.type, 'id': self.id, 'audio_url': self.audio_url, 'title': self.title}
if self.caption:
json_dict['caption'] = self.caption
if self.parse_mode:
json_dict['parse_mode'] = self.parse_mode
if self.performer:
json_dict['performer'] = self.performer
if self.audio_duration:
@ -1336,13 +1429,14 @@ class InlineQueryResultAudio(JsonSerializable):
class InlineQueryResultVoice(JsonSerializable):
def __init__(self, id, voice_url, title, caption=None, performer=None, voice_duration=None, reply_markup=None,
input_message_content=None):
def __init__(self, id, voice_url, title, caption=None, parse_mode=None, performer=None, voice_duration=None,
reply_markup=None, input_message_content=None):
self.type = 'voice'
self.id = id
self.voice_url = voice_url
self.title = title
self.caption = caption
self.parse_mode = parse_mode
self.performer = performer
self.voice_duration = voice_duration
self.reply_markup = reply_markup
@ -1352,6 +1446,8 @@ class InlineQueryResultVoice(JsonSerializable):
json_dict = {'type': self.type, 'id': self.id, 'voice_url': self.voice_url, 'title': self.title}
if self.caption:
json_dict['caption'] = self.caption
if self.parse_mode:
json_dict['parse_mode'] = self.parse_mode
if self.performer:
json_dict['performer'] = self.performer
if self.voice_duration:
@ -1364,14 +1460,15 @@ class InlineQueryResultVoice(JsonSerializable):
class InlineQueryResultDocument(JsonSerializable):
def __init__(self, id, title, document_url, mime_type, caption=None, description=None, reply_markup=None,
input_message_content=None, thumb_url=None, thumb_width=None, thumb_height=None):
def __init__(self, id, title, document_url, mime_type, caption=None, parse_mode=None, description=None,
reply_markup=None, input_message_content=None, thumb_url=None, thumb_width=None, thumb_height=None):
self.type = 'document'
self.id = id
self.title = title
self.document_url = document_url
self.mime_type = mime_type
self.caption = caption
self.parse_mode = parse_mode
self.description = description
self.reply_markup = reply_markup
self.input_message_content = input_message_content
@ -1384,6 +1481,8 @@ class InlineQueryResultDocument(JsonSerializable):
'mime_type': self.mime_type}
if self.caption:
json_dict['caption'] = self.caption
if self.parse_mode:
json_dict['parse_mode'] = self.parse_mode
if self.description:
json_dict['description'] = self.description
if self.thumb_url:
@ -1526,8 +1625,8 @@ class BaseInlineQueryResultCached(JsonSerializable):
class InlineQueryResultCachedPhoto(BaseInlineQueryResultCached):
def __init__(self, id, photo_file_id, title=None, description=None, caption=None, reply_markup=None,
input_message_content=None):
def __init__(self, id, photo_file_id, title=None, description=None, caption=None, parse_mode=None,
reply_markup=None, input_message_content=None):
BaseInlineQueryResultCached.__init__(self)
self.type = 'photo'
self.id = id
@ -1538,10 +1637,11 @@ class InlineQueryResultCachedPhoto(BaseInlineQueryResultCached):
self.reply_markup = reply_markup
self.input_message_content = input_message_content
self.payload_dic['photo_file_id'] = photo_file_id
self.payload_dic['parse_mode'] = parse_mode
class InlineQueryResultCachedGif(BaseInlineQueryResultCached):
def __init__(self, id, gif_file_id, title=None, description=None, caption=None, reply_markup=None,
def __init__(self, id, gif_file_id, title=None, description=None, caption=None, parse_mode=None, reply_markup=None,
input_message_content=None):
BaseInlineQueryResultCached.__init__(self)
self.type = 'gif'
@ -1553,11 +1653,12 @@ class InlineQueryResultCachedGif(BaseInlineQueryResultCached):
self.reply_markup = reply_markup
self.input_message_content = input_message_content
self.payload_dic['gif_file_id'] = gif_file_id
self.payload_dic['parse_mode'] = parse_mode
class InlineQueryResultCachedMpeg4Gif(BaseInlineQueryResultCached):
def __init__(self, id, mpeg4_file_id, title=None, description=None, caption=None, reply_markup=None,
input_message_content=None):
def __init__(self, id, mpeg4_file_id, title=None, description=None, caption=None, parse_mode=None,
reply_markup=None, input_message_content=None):
BaseInlineQueryResultCached.__init__(self)
self.type = 'mpeg4_gif'
self.id = id
@ -1568,6 +1669,7 @@ class InlineQueryResultCachedMpeg4Gif(BaseInlineQueryResultCached):
self.reply_markup = reply_markup
self.input_message_content = input_message_content
self.payload_dic['mpeg4_file_id'] = mpeg4_file_id
self.payload_dic['parse_mode'] = parse_mode
class InlineQueryResultCachedSticker(BaseInlineQueryResultCached):
@ -1582,7 +1684,7 @@ class InlineQueryResultCachedSticker(BaseInlineQueryResultCached):
class InlineQueryResultCachedDocument(BaseInlineQueryResultCached):
def __init__(self, id, document_file_id, title, description=None, caption=None, reply_markup=None,
def __init__(self, id, document_file_id, title, description=None, caption=None, parse_mode=None, reply_markup=None,
input_message_content=None):
BaseInlineQueryResultCached.__init__(self)
self.type = 'document'
@ -1594,10 +1696,11 @@ class InlineQueryResultCachedDocument(BaseInlineQueryResultCached):
self.reply_markup = reply_markup
self.input_message_content = input_message_content
self.payload_dic['document_file_id'] = document_file_id
self.payload_dic['parse_mode'] = parse_mode
class InlineQueryResultCachedVideo(BaseInlineQueryResultCached):
def __init__(self, id, video_file_id, title, description=None, caption=None, reply_markup=None,
def __init__(self, id, video_file_id, title, description=None, caption=None, parse_mode=None, reply_markup=None,
input_message_content=None):
BaseInlineQueryResultCached.__init__(self)
self.type = 'video'
@ -1609,10 +1712,12 @@ class InlineQueryResultCachedVideo(BaseInlineQueryResultCached):
self.reply_markup = reply_markup
self.input_message_content = input_message_content
self.payload_dic['video_file_id'] = video_file_id
self.payload_dic['parse_mode'] = parse_mode
class InlineQueryResultCachedVoice(BaseInlineQueryResultCached):
def __init__(self, id, voice_file_id, title, caption=None, reply_markup=None, input_message_content=None):
def __init__(self, id, voice_file_id, title, caption=None, parse_mode=None, reply_markup=None,
input_message_content=None):
BaseInlineQueryResultCached.__init__(self)
self.type = 'voice'
self.id = id
@ -1622,10 +1727,11 @@ class InlineQueryResultCachedVoice(BaseInlineQueryResultCached):
self.reply_markup = reply_markup
self.input_message_content = input_message_content
self.payload_dic['voice_file_id'] = voice_file_id
self.payload_dic['parse_mode'] = parse_mode
class InlineQueryResultCachedAudio(BaseInlineQueryResultCached):
def __init__(self, id, audio_file_id, caption=None, reply_markup=None, input_message_content=None):
def __init__(self, id, audio_file_id, caption=None, parse_mode=None, reply_markup=None, input_message_content=None):
BaseInlineQueryResultCached.__init__(self)
self.type = 'audio'
self.id = id
@ -1634,6 +1740,7 @@ class InlineQueryResultCachedAudio(BaseInlineQueryResultCached):
self.reply_markup = reply_markup
self.input_message_content = input_message_content
self.payload_dic['audio_file_id'] = audio_file_id
self.payload_dic['parse_mode'] = parse_mode
# Games
@ -1809,7 +1916,7 @@ class ShippingOption(JsonSerializable):
def add_price(self, *args):
"""
Add LabeledPrice to ShippingOption
:param args: LabeledPrices
:param args: LabeledPrices
"""
for price in args:
self.prices.append(price)
@ -1966,38 +2073,84 @@ class MaskPosition(JsonDeserializable, JsonSerializable):
# InputMedia
class InputMediaPhoto(JsonSerializable):
def __init__(self, media, caption=None):
self.type = "photo"
class InputMedia(JsonSerializable):
def __init__(self, type, media, caption=None, parse_mode=None):
self.type = type
self.media = media
self.caption = caption
self.parse_mode = parse_mode
if util.is_string(self.media):
self._media_name = ''
self._media_dic = self.media
else:
self._media_name = util.generate_random_token()
self._media_dic = 'attach://{0}'.format(self._media_name)
def to_json(self):
return json.dumps(self.to_dic())
def to_dic(self):
ret = {'type': self.type, 'media': self.media}
ret = {'type': self.type, 'media': self._media_dic}
if self.caption:
ret['caption'] = self.caption
if self.parse_mode:
ret['parse_mode'] = self.parse_mode
return ret
def _convert_input_media(self):
if util.is_string(self.media):
return self.to_json(), None
return self.to_json(), {self._media_name: self.media}
class InputMediaPhoto(InputMedia):
def __init__(self, media, caption=None, parse_mode=None):
super(InputMediaPhoto, self).__init__(type="photo", media=media, caption=caption, parse_mode=parse_mode)
def to_dic(self):
ret = super(InputMediaPhoto, self).to_dic()
return ret
class InputMediaVideo(JsonSerializable):
def __init__(self, media, caption=None, width=None, height=None, duration=None):
self.type = "video"
self.media = media
self.caption = caption
class InputMediaVideo(InputMedia):
def __init__(self, media, thumb=None, caption=None, parse_mode=None, width=None, height=None, duration=None,
supports_streaming=None):
super(InputMediaVideo, self).__init__(type="video", media=media, caption=caption, parse_mode=parse_mode)
self.thumb = thumb
self.width = width
self.height = height
self.duration = duration
self.supports_streaming = supports_streaming
def to_dic(self):
ret = super(InputMediaVideo, self).to_dic()
if self.thumb:
ret['thumb'] = self.thumb
if self.width:
ret['width'] = self.width
if self.height:
ret['height'] = self.height
if self.duration:
ret['duration'] = self.duration
if self.supports_streaming:
ret['supports_streaming'] = self.supports_streaming
return ret
class InputMediaAnimation(InputMedia):
def __init__(self, media, thumb=None, caption=None, parse_mode=None, width=None, height=None, duration=None):
super(InputMediaAnimation, self).__init__(type="animation", media=media, caption=caption, parse_mode=parse_mode)
self.thumb = thumb
self.width = width
self.height = height
self.duration = duration
def to_json(self):
return json.dumps(self.to_dic())
def to_dic(self):
ret = {'type': self.type, 'media': self.media}
if self.caption:
ret['caption'] = self.caption
ret = super(InputMediaAnimation, self).to_dic()
if self.thumb:
ret['thumb'] = self.thumb
if self.width:
ret['width'] = self.width
if self.height:
@ -2005,3 +2158,36 @@ class InputMediaVideo(JsonSerializable):
if self.duration:
ret['duration'] = self.duration
return ret
class InputMediaAudio(InputMedia):
def __init__(self, media, thumb=None, caption=None, parse_mode=None, duration=None, performer=None, title=None):
super(InputMediaAudio, self).__init__(type="audio", media=media, caption=caption, parse_mode=parse_mode)
self.thumb = thumb
self.duration = duration
self.performer = performer
self.title = title
def to_dic(self):
ret = super(InputMediaAudio, self).to_dic()
if self.thumb:
ret['thumb'] = self.thumb
if self.duration:
ret['duration'] = self.duration
if self.performer:
ret['performer'] = self.performer
if self.title:
ret['title'] = self.title
return ret
class InputMediaDocument(InputMedia):
def __init__(self, media, thumb=None, caption=None, parse_mode=None):
super(InputMediaDocument, self).__init__(type="document", media=media, caption=caption, parse_mode=parse_mode)
self.thumb = thumb
def to_dic(self):
ret = super(InputMediaDocument, self).to_dic()
if self.thumb:
ret['thumb'] = self.thumb
return ret

View File

@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
import random
import string
import threading
import traceback
import re
@ -13,9 +14,9 @@ try:
import Queue
except ImportError:
import queue as Queue
import logging
from telebot import logger
logger = logging.getLogger('TeleBot')
thread_local = threading.local()
@ -142,7 +143,7 @@ class AsyncTask:
return self.result
def async():
def async_dec():
def decorator(fn):
def wrapper(*args, **kwargs):
return AsyncTask(fn, *args, **kwargs)
@ -247,10 +248,13 @@ def extract_arguments(text):
return result.group(2) if is_command(text) else None
def per_thread(key, construct_value):
try:
return getattr(thread_local, key)
except AttributeError:
def per_thread(key, construct_value, reset=False):
if reset or not hasattr(thread_local, key):
value = construct_value()
setattr(thread_local, key, value)
return value
return getattr(thread_local, key)
def generate_random_token():
return ''.join(random.sample(string.ascii_letters, 16))

View File

@ -361,6 +361,20 @@ class TestTeleBot:
new_msg = tb.edit_message_caption(caption='Edit test', chat_id=CHAT_ID, message_id=msg.message_id)
assert new_msg.caption == 'Edit test'
def test_edit_message_media(self):
file_data = open('../examples/detailed_example/kitten.jpg', 'rb')
file_data_2 = open('../examples/detailed_example/rooster.jpg', 'rb')
tb = telebot.TeleBot(TOKEN)
msg = tb.send_photo(CHAT_ID, file_data)
new_msg = tb.edit_message_media(chat_id=CHAT_ID, message_id=msg.message_id,
media=types.InputMediaPhoto(file_data_2, caption='Test editMessageMedia 0'))
assert type(new_msg) != bool
new_msg = tb.edit_message_media(chat_id=CHAT_ID, message_id=msg.message_id,
media=types.InputMediaPhoto(msg.photo[0].file_id, caption='Test editMessageMedia'))
assert type(new_msg) != bool
assert new_msg.caption == 'Test editMessageMedia'
def test_get_chat(self):
tb = telebot.TeleBot(TOKEN)
ch = tb.get_chat(GROUP_ID)
@ -391,7 +405,7 @@ class TestTeleBot:
def create_text_message(self, text):
params = {'text': text}
chat = types.User(11, False, 'test')
return types.Message(1, None, None, chat, 'text', params)
return types.Message(1, None, None, chat, 'text', params, "")
def test_is_string_unicode(self):
s1 = u'string'
@ -418,3 +432,59 @@ class TestTeleBot:
medias = [types.InputMediaPhoto(img1, "View"), types.InputMediaPhoto(img2, "Dog")]
result = tb.send_media_group(CHAT_ID, medias)
assert len(result) == 2
assert result[0].media_group_id is not None
assert result[0].media_group_id == result[1].media_group_id
def test_send_media_group_local_files(self):
photo = open('../examples/detailed_example/kitten.jpg', 'rb')
video = open('./test_data/test_video.mp4', 'rb')
tb = telebot.TeleBot(TOKEN)
medias = [types.InputMediaPhoto(photo, "View"),
types.InputMediaVideo(video)]
result = tb.send_media_group(CHAT_ID, medias)
assert len(result) == 2
assert result[0].media_group_id is not None
assert result[1].media_group_id is not None
def test_send_photo_formating_caption(self):
file_data = open('../examples/detailed_example/kitten.jpg', 'rb')
tb = telebot.TeleBot(TOKEN)
ret_msg = tb.send_photo(CHAT_ID, file_data, caption='_italic_', parse_mode='Markdown')
assert ret_msg.caption_entities[0].type == 'italic'
def test_send_video_formatting_caption(self):
file_data = open('./test_data/test_video.mp4', 'rb')
tb = telebot.TeleBot(TOKEN)
ret_msg = tb.send_video(CHAT_ID, file_data, caption='_italic_', parse_mode='Markdown')
assert ret_msg.caption_entities[0].type == 'italic'
def test_send_audio_formatting_caption(self):
file_data = open('./test_data/record.mp3', 'rb')
tb = telebot.TeleBot(TOKEN)
ret_msg = tb.send_audio(CHAT_ID, file_data, caption='<b>bold</b>', parse_mode='HTML')
assert ret_msg.caption_entities[0].type == 'bold'
def test_send_voice_formatting_caprion(self):
file_data = open('./test_data/record.ogg', 'rb')
tb = telebot.TeleBot(TOKEN)
ret_msg = tb.send_voice(CHAT_ID, file_data, caption='<b>bold</b>', parse_mode='HTML')
assert ret_msg.caption_entities[0].type == 'bold'
assert ret_msg.voice.mime_type == 'audio/ogg'
def test_send_media_group_formatting_caption(self):
tb = telebot.TeleBot(TOKEN)
img1 = 'https://i.imgur.com/CjXjcnU.png'
img2 = 'https://i.imgur.com/CjXjcnU.png'
medias = [types.InputMediaPhoto(img1, "*View*", parse_mode='Markdown'),
types.InputMediaPhoto(img2, "_Dog_", parse_mode='Markdown')]
result = tb.send_media_group(CHAT_ID, medias)
assert len(result) == 2
assert result[0].media_group_id is not None
assert result[0].caption_entities[0].type == 'bold'
assert result[1].caption_entities[0].type == 'italic'
def test_send_document_formating_caption(self):
file_data = open('../examples/detailed_example/kitten.jpg', 'rb')
tb = telebot.TeleBot(TOKEN)
ret_msg = tb.send_document(CHAT_ID, file_data, caption='_italic_', parse_mode='Markdown')
assert ret_msg.caption_entities[0].type == 'italic'