diff --git a/README.md b/README.md
index 2ea415f..fbe40e8 100644
--- a/README.md
+++ b/README.md
@@ -584,5 +584,9 @@ Get help. Discuss. Chat.
* [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)
+* [MandangoBot](https://t.me/MandangoBot) by @Alvaricias - Bot for managing Marvel Strike Force alliances (only in spanish, atm).
+
Want to have your bot listed here? Send a Telegram message to @eternnoir or @pevdh.
diff --git a/README.rst b/README.rst
deleted file mode 100644
index d776571..0000000
--- a/README.rst
+++ /dev/null
@@ -1,807 +0,0 @@
-#
-
-.. raw:: html
-
-
-
-pyTelegramBotAPI
-
-.. raw:: html
-
-
-
-A simple, but extensible Python implementation for the `Telegram Bot
-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 `__.
-
-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 `__, 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 `__ 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 |
-| | | ` |
-| | | __ |
-+--------+------+------+
-| 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 = ''
- 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 `__.
-
-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 `__
-
-.. 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 `__ 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 `__
-
-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 `__.
-
-More examples
--------------
-
-- `Echo
- Bot `__
-- `Deep
- Linking `__
-- `next\_step\_handler
- Example `__
-
-Bots using this API
--------------------
-
-- `SiteAlert bot `__
- (`source `__) by
- *ilteoood* - Monitors websites and sends a notification on changes
-- `TelegramLoggingBot `__
- by *aRandomStranger*
-- `Telegram
- LMGTFY\_bot `__ by
- *GabrielRF*
-- `Telegram
- UrlProBot `__ by
- *GabrielRF*
-- `Telegram Proxy
- Bot `__ by
- *Groosha* - A simple BITM (bot-in-the-middle) for Telegram acting as
- some kind of "proxy".
-- `Telegram Proxy Bot `__ by
- *mrgigabyte* -
- ``Credits for the original version of this bot goes to`` **Groosha**
- ``, simply added certain features which I thought were needed``.
-- `RadRetroRobot `__ by
- *Tronikart* - Multifunctional Telegram Bot RadRetroRobot.
-- `League of Legends bot `__
- (`source `__) by *i32ropie*
-- `NeoBot `__ by *neoranger*
-- `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
diff --git a/examples/deep_linking.py b/examples/deep_linking.py
index f92680f..f5ea506 100644
--- a/examples/deep_linking.py
+++ b/examples/deep_linking.py
@@ -1,3 +1,5 @@
+#!/usr/bin/python
+
# This example shows how to implement deep linking (https://core.telegram.org/bots#deep-linking)
# with the pyTelegramBotAPI.
# Note: This is not a working, production-ready sample.
@@ -31,34 +33,38 @@
# steps are not shown here. Only steps 5 to 7 are illustrated, some in pseudo-code, with this example.
import telebot
-import time
bot = telebot.TeleBot('TOKEN')
+
def extract_unique_code(text):
# Extracts the unique_code from the sent /start command.
return text.split()[1] if len(text.split()) > 1 else None
+
def in_storage(unique_code):
# (pseudo-code) Should check if a unique code exists in storage
return True
+
def get_username_from_storage(unique_code):
# (pseudo-code) Does a query to the storage, retrieving the associated username
# Should be replaced by a real database-lookup.
return "ABC" if in_storage(unique_code) else None
+
def save_chat_id(chat_id, username):
# (pseudo-code) Save the chat_id->username to storage
# Should be replaced by a real database query.
pass
+
@bot.message_handler(commands=['start'])
def send_welcome(message):
unique_code = extract_unique_code(message.text)
- if unique_code: # if the '/start' command contains a unique_code
+ if unique_code: # if the '/start' command contains a unique_code
username = get_username_from_storage(unique_code)
- if username: # if the username exists in our database
+ if username: # if the username exists in our database
save_chat_id(message.chat.id, username)
reply = "Hello {0}, how are you?".format(username)
else:
@@ -67,4 +73,5 @@ def send_welcome(message):
reply = "Please visit me via a provided URL from the website."
bot.reply_to(message, reply)
+
bot.polling()
diff --git a/examples/detailed_example/detailed_example.py b/examples/detailed_example/detailed_example.py
index 8fad9af..f481832 100644
--- a/examples/detailed_example/detailed_example.py
+++ b/examples/detailed_example/detailed_example.py
@@ -2,9 +2,10 @@
This is a detailed example using almost every command of the API
"""
+import time
+
import telebot
from telebot import types
-import time
TOKEN = ''
@@ -12,10 +13,10 @@ knownUsers = [] # todo: save these in a file,
userStep = {} # so they won't reset every time the bot restarts
commands = { # command description used in the "help" command
- 'start': 'Get used to the bot',
- 'help': 'Gives you information about the available commands',
- 'sendLongText': 'A test using the \'send_chat_action\' command',
- 'getImage': 'A test using multi-stage messages, custom keyboard, and media sending'
+ 'start' : 'Get used to the bot',
+ 'help' : 'Gives you information about the available commands',
+ 'sendLongText': 'A test using the \'send_chat_action\' command',
+ 'getImage' : 'A test using multi-stage messages, custom keyboard, and media sending'
}
imageSelect = types.ReplyKeyboardMarkup(one_time_keyboard=True) # create the image selection keyboard
@@ -33,7 +34,7 @@ def get_user_step(uid):
else:
knownUsers.append(uid)
userStep[uid] = 0
- print "New user detected, who hasn't used \"/start\" yet"
+ print("New user detected, who hasn't used \"/start\" yet")
return 0
@@ -45,7 +46,7 @@ def listener(messages):
for m in messages:
if m.content_type == 'text':
# print the sent message to the console
- print str(m.chat.first_name) + " [" + str(m.chat.id) + "]: " + m.text
+ print(str(m.chat.first_name) + " [" + str(m.chat.id) + "]: " + m.text)
bot = telebot.TeleBot(TOKEN)
@@ -128,4 +129,5 @@ def command_default(m):
# this is the standard reply to a normal message
bot.send_message(m.chat.id, "I don't understand \"" + m.text + "\"\nMaybe try the help page at /help")
+
bot.polling()
diff --git a/examples/echo_bot.py b/examples/echo_bot.py
index f88d3bf..b66eb34 100644
--- a/examples/echo_bot.py
+++ b/examples/echo_bot.py
@@ -1,3 +1,5 @@
+#!/usr/bin/python
+
# This is a simple echo bot using the decorator mechanism.
# It echoes any incoming text messages.
@@ -7,6 +9,7 @@ API_TOKEN = ''
bot = telebot.TeleBot(API_TOKEN)
+
# Handle '/start' and '/help'
@bot.message_handler(commands=['help', 'start'])
def send_welcome(message):
@@ -21,4 +24,5 @@ I am here to echo your kind words back to you. Just say anything nice and I'll s
def echo_message(message):
bot.reply_to(message, message.text)
+
bot.polling()
diff --git a/examples/inline_example.py b/examples/inline_example.py
index c97dea2..e36932d 100644
--- a/examples/inline_example.py
+++ b/examples/inline_example.py
@@ -1,8 +1,9 @@
-# This example show how to write an inline mode telegramt bot use pyTelegramBotAPI.
-import telebot
-import time
-import sys
+# This example show how to write an inline mode telegram bot use pyTelegramBotAPI.
import logging
+import sys
+import time
+
+import telebot
from telebot import types
API_TOKEN = ''
@@ -69,5 +70,5 @@ if __name__ == '__main__':
try:
main_loop()
except KeyboardInterrupt:
- print >> sys.stderr, '\nExiting by user request.\n'
+ print('\nExiting by user request.\n')
sys.exit(0)
diff --git a/examples/inline_keyboard_example.py b/examples/inline_keyboard_example.py
new file mode 100644
index 0000000..0618eee
--- /dev/null
+++ b/examples/inline_keyboard_example.py
@@ -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 = ''
+
+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)
diff --git a/examples/payments_example.py b/examples/payments_example.py
index 3e82955..d0f52d4 100644
--- a/examples/payments_example.py
+++ b/examples/payments_example.py
@@ -1,6 +1,5 @@
import telebot
-from telebot.types import LabeledPrice
-from telebot.types import ShippingOption
+from telebot.types import LabeledPrice, ShippingOption
token = '1234567890:AAAABBBBCCCCDDDDeeeeFFFFgggGHHHH'
provider_token = '1234567890:TEST:AAAABBBBCCCCDDDD' # @BotFather -> Bot Settings -> Payments
diff --git a/examples/step_example.py b/examples/step_example.py
index fd8d07d..0fc17e5 100644
--- a/examples/step_example.py
+++ b/examples/step_example.py
@@ -2,7 +2,6 @@
"""
This Example will show you how to use register_next_step handler.
"""
-import time
import telebot
from telebot import types
@@ -75,4 +74,13 @@ 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()
diff --git a/examples/telebot_bot/telebot_bot.py b/examples/telebot_bot/telebot_bot.py
index cd29276..ac6b63c 100644
--- a/examples/telebot_bot/telebot_bot.py
+++ b/examples/telebot_bot/telebot_bot.py
@@ -3,9 +3,10 @@
# and goes by the name 'TeleBot (@pyTeleBot)'. Join our group to talk to him!
# WARNING: Tested with Python 2.7
-import telebot
import os
+import telebot
+
text_messages = {
'welcome':
u'Please welcome {name}!\n\n'
@@ -33,8 +34,10 @@ if "TELEBOT_BOT_TOKEN" not in os.environ or "GROUP_CHAT_ID" not in os.environ:
bot = telebot.AsyncTeleBot(os.environ["TELEBOT_BOT_TOKEN"])
GROUP_CHAT_ID = int(os.environ["GROUP_CHAT_ID"])
+
def is_api_group(chat_id):
- return chat_id== GROUP_CHAT_ID
+ return chat_id == GROUP_CHAT_ID
+
@bot.message_handler(func=lambda m: True, content_types=['new_chat_participant'])
def on_user_joins(message):
@@ -50,6 +53,7 @@ def on_user_joins(message):
bot.reply_to(message, text_messages['welcome'].format(name=name))
+
@bot.message_handler(commands=['info', 'help'])
def on_info(message):
if not is_api_group(message.chat.id):
@@ -58,21 +62,23 @@ def on_info(message):
bot.reply_to(message, text_messages['info'])
+
@bot.message_handler(commands=["ping"])
def on_ping(message):
bot.reply_to(message, "Still alive and kicking!")
+
@bot.message_handler(commands=['start'])
def on_start(message):
if not is_api_group(message.chat.id):
bot.reply_to(message, text_messages['wrong_chat'])
return
+
def listener(messages):
for m in messages:
- print str(m)
+ print(str(m))
+
bot.set_update_listener(listener)
bot.polling()
-
-
diff --git a/examples/webhook_examples/webhook_aiohttp_echo_bot.py b/examples/webhook_examples/webhook_aiohttp_echo_bot.py
index d92cff9..bbb7c6e 100644
--- a/examples/webhook_examples/webhook_aiohttp_echo_bot.py
+++ b/examples/webhook_examples/webhook_aiohttp_echo_bot.py
@@ -11,7 +11,6 @@ from aiohttp import web
import telebot
-
API_TOKEN = ''
WEBHOOK_HOST = ''
@@ -32,7 +31,6 @@ WEBHOOK_SSL_PRIV = './webhook_pkey.pem' # Path to the ssl private key
WEBHOOK_URL_BASE = "https://{}:{}".format(WEBHOOK_HOST, WEBHOOK_PORT)
WEBHOOK_URL_PATH = "/{}/".format(API_TOKEN)
-
logger = telebot.logger
telebot.logger.setLevel(logging.INFO)
@@ -51,6 +49,7 @@ async def handle(request):
else:
return web.Response(status=403)
+
app.router.add_post('/{token}/', handle)
@@ -72,7 +71,7 @@ def echo_message(message):
bot.remove_webhook()
# Set webhook
-bot.set_webhook(url=WEBHOOK_URL_BASE+WEBHOOK_URL_PATH,
+bot.set_webhook(url=WEBHOOK_URL_BASE + WEBHOOK_URL_PATH,
certificate=open(WEBHOOK_SSL_CERT, 'r'))
# Build ssl context
diff --git a/examples/webhook_examples/webhook_cherrypy_echo_bot.py b/examples/webhook_examples/webhook_cherrypy_echo_bot.py
index d0f3da0..7b46e78 100644
--- a/examples/webhook_examples/webhook_cherrypy_echo_bot.py
+++ b/examples/webhook_examples/webhook_cherrypy_echo_bot.py
@@ -4,10 +4,11 @@
# This is a simple echo bot using decorators and webhook with CherryPy
# It echoes any incoming text messages and does not use the polling method.
-import cherrypy
-import telebot
import logging
+import cherrypy
+
+import telebot
API_TOKEN = ''
@@ -29,7 +30,6 @@ WEBHOOK_SSL_PRIV = './webhook_pkey.pem' # Path to the ssl private key
WEBHOOK_URL_BASE = "https://%s:%s" % (WEBHOOK_HOST, WEBHOOK_PORT)
WEBHOOK_URL_PATH = "/%s/" % (API_TOKEN)
-
logger = telebot.logger
telebot.logger.setLevel(logging.INFO)
@@ -70,7 +70,7 @@ def echo_message(message):
bot.remove_webhook()
# Set webhook
-bot.set_webhook(url=WEBHOOK_URL_BASE+WEBHOOK_URL_PATH,
+bot.set_webhook(url=WEBHOOK_URL_BASE + WEBHOOK_URL_PATH,
certificate=open(WEBHOOK_SSL_CERT, 'r'))
# Disable CherryPy requests log
@@ -80,9 +80,9 @@ for handler in tuple(access_log.handlers):
# Start cherrypy server
cherrypy.config.update({
- 'server.socket_host': WEBHOOK_LISTEN,
- 'server.socket_port': WEBHOOK_PORT,
- 'server.ssl_module': 'builtin',
+ 'server.socket_host' : WEBHOOK_LISTEN,
+ 'server.socket_port' : WEBHOOK_PORT,
+ 'server.ssl_module' : 'builtin',
'server.ssl_certificate': WEBHOOK_SSL_CERT,
'server.ssl_private_key': WEBHOOK_SSL_PRIV
})
diff --git a/examples/webhook_examples/webhook_cpython_echo_bot.py b/examples/webhook_examples/webhook_cpython_echo_bot.py
index 5c1dfc9..029f361 100644
--- a/examples/webhook_examples/webhook_cpython_echo_bot.py
+++ b/examples/webhook_examples/webhook_cpython_echo_bot.py
@@ -4,11 +4,17 @@
# This is a simple echo bot using decorators and webhook with BaseHTTPServer
# It echoes any incoming text messages and does not use the polling method.
-import BaseHTTPServer
-import ssl
-import telebot
-import logging
+try:
+ # Python 2
+ from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
+except ImportError:
+ # Python 3
+ from http.server import BaseHTTPRequestHandler, HTTPServer
+import logging
+import ssl
+
+import telebot
API_TOKEN = ''
@@ -30,7 +36,6 @@ WEBHOOK_SSL_PRIV = './webhook_pkey.pem' # Path to the ssl private key
WEBHOOK_URL_BASE = "https://%s:%s" % (WEBHOOK_HOST, WEBHOOK_PORT)
WEBHOOK_URL_PATH = "/%s/" % (API_TOKEN)
-
logger = telebot.logger
telebot.logger.setLevel(logging.INFO)
@@ -38,7 +43,7 @@ bot = telebot.TeleBot(API_TOKEN)
# WebhookHandler, process webhook calls
-class WebhookHandler(BaseHTTPServer.BaseHTTPRequestHandler):
+class WebhookHandler(BaseHTTPRequestHandler):
server_version = "WebhookHandler/1.0"
def do_HEAD(self):
@@ -84,12 +89,12 @@ def echo_message(message):
bot.remove_webhook()
# Set webhook
-bot.set_webhook(url=WEBHOOK_URL_BASE+WEBHOOK_URL_PATH,
+bot.set_webhook(url=WEBHOOK_URL_BASE + WEBHOOK_URL_PATH,
certificate=open(WEBHOOK_SSL_CERT, 'r'))
# Start server
-httpd = BaseHTTPServer.HTTPServer((WEBHOOK_LISTEN, WEBHOOK_PORT),
- WebhookHandler)
+httpd = HTTPServer((WEBHOOK_LISTEN, WEBHOOK_PORT),
+ WebhookHandler)
httpd.socket = ssl.wrap_socket(httpd.socket,
certfile=WEBHOOK_SSL_CERT,
diff --git a/examples/webhook_examples/webhook_flask_echo_bot.py b/examples/webhook_examples/webhook_flask_echo_bot.py
index d0327d7..daa3995 100644
--- a/examples/webhook_examples/webhook_flask_echo_bot.py
+++ b/examples/webhook_examples/webhook_flask_echo_bot.py
@@ -4,11 +4,12 @@
# This is a simple echo bot using decorators and webhook with flask
# It echoes any incoming text messages and does not use the polling method.
-import flask
-import telebot
import logging
import time
+import flask
+
+import telebot
API_TOKEN = ''
@@ -30,7 +31,6 @@ WEBHOOK_SSL_PRIV = './webhook_pkey.pem' # Path to the ssl private key
WEBHOOK_URL_BASE = "https://%s:%s" % (WEBHOOK_HOST, WEBHOOK_PORT)
WEBHOOK_URL_PATH = "/%s/" % (API_TOKEN)
-
logger = telebot.logger
telebot.logger.setLevel(logging.INFO)
@@ -77,7 +77,7 @@ bot.remove_webhook()
time.sleep(0.1)
# Set webhook
-bot.set_webhook(url=WEBHOOK_URL_BASE+WEBHOOK_URL_PATH,
+bot.set_webhook(url=WEBHOOK_URL_BASE + WEBHOOK_URL_PATH,
certificate=open(WEBHOOK_SSL_CERT, 'r'))
# Start flask server
diff --git a/examples/webhook_examples/webhook_flask_heroku_echo.py b/examples/webhook_examples/webhook_flask_heroku_echo.py
index 62d0a90..7bbf2bf 100644
--- a/examples/webhook_examples/webhook_flask_heroku_echo.py
+++ b/examples/webhook_examples/webhook_flask_heroku_echo.py
@@ -1,8 +1,9 @@
import os
-import telebot
from flask import Flask, request
+import telebot
+
TOKEN = ''
bot = telebot.TeleBot(TOKEN)
server = Flask(__name__)
diff --git a/examples/webhook_examples/webhook_tornado_echo_bot.py b/examples/webhook_examples/webhook_tornado_echo_bot.py
index 538b7b9..171d2d7 100644
--- a/examples/webhook_examples/webhook_tornado_echo_bot.py
+++ b/examples/webhook_examples/webhook_tornado_echo_bot.py
@@ -4,13 +4,15 @@
# This example shows webhook echo bot with Tornado web framework
# Documenation to Tornado: http://tornadoweb.org
-import telebot
-import tornado.web
-import tornado.ioloop
-import tornado.httpserver
-import tornado.options
import signal
+import tornado.httpserver
+import tornado.ioloop
+import tornado.options
+import tornado.web
+
+import telebot
+
API_TOKEN = ''
WEBHOOK_CERT = "./cert.pem"
WEBHOOK_PKEY = "./pkey.pem"
@@ -29,15 +31,18 @@ WEBHOOK_URL_BASE = "https://{0}:{1}/{2}".format(WEBHOOK_HOST, str(WEBHOOK_PORT),
bot = telebot.TeleBot(API_TOKEN)
+
class Root(tornado.web.RequestHandler):
def get(self):
self.write("Hi! This is webhook example!")
self.finish()
+
class webhook_serv(tornado.web.RequestHandler):
def get(self):
self.write("What are you doing here?")
self.finish()
+
def post(self):
if "Content-Length" in self.request.headers and \
"Content-Type" in self.request.headers and \
@@ -52,21 +57,26 @@ class webhook_serv(tornado.web.RequestHandler):
else:
self.write("What are you doing here?")
self.finish()
-
+
+
tornado.options.define("port", default=WEBHOOK_PORT, help="run on the given port", type=int)
is_closing = False
+
+
def signal_handler(signum, frame):
global is_closing
print("Exiting...")
is_closing = True
+
def try_exit():
global is_closing
if is_closing:
# clean up here
tornado.ioloop.IOLoop.instance().stop()
print("Exit success!")
-
+
+
# Handle '/start' and '/help'
@bot.message_handler(commands=['help', 'start'])
def send_welcome(message):
@@ -74,6 +84,7 @@ def send_welcome(message):
("Hi there, I am EchoBot.\n"
"I am here to echo your kind words back to you."))
+
bot.remove_webhook()
bot.set_webhook(url=WEBHOOK_URL_BASE,
certificate=open(WEBHOOK_CERT, 'r'))
@@ -86,9 +97,9 @@ application = tornado.web.Application([
])
http_server = tornado.httpserver.HTTPServer(application, ssl_options={
- "certfile": WEBHOOK_CERT,
- "keyfile": WEBHOOK_PKEY,
- })
+ "certfile": WEBHOOK_CERT,
+ "keyfile" : WEBHOOK_PKEY,
+})
http_server.listen(tornado.options.options.port)
tornado.ioloop.PeriodicCallback(try_exit, 100).start()
tornado.ioloop.IOLoop.instance().start()
diff --git a/setup.py b/setup.py
index 1e76d03..a52df53 100644
--- a/setup.py
+++ b/setup.py
@@ -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.6.3',
+ 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',
diff --git a/telebot/__init__.py b/telebot/__init__.py
index 061d4d3..1d4053d 100644
--- a/telebot/__init__.py
+++ b/telebot/__init__.py
@@ -1,13 +1,15 @@
# -*- coding: utf-8 -*-
from __future__ import print_function
-import threading
-import time
+import logging
+import os
+import pickle
import re
import sys
-import six
+import threading
+import time
-import logging
+import six
logger = logging.getLogger('TeleBot')
formatter = logging.Formatter(
@@ -27,6 +29,72 @@ 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:
@@ -85,6 +153,9 @@ class TeleBot:
# key: chat_id, value: handler list
self.next_step_handlers = {}
+ self.next_step_saver = None
+ self.reply_saver = None
+
self.message_handlers = []
self.edited_message_handlers = []
self.channel_post_handlers = []
@@ -99,6 +170,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)
@@ -880,6 +999,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:
@@ -984,7 +1109,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:
"""
@@ -1052,8 +1176,7 @@ class TeleBot:
"""
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`
@@ -1066,17 +1189,18 @@ class TeleBot:
"""
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 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({"callback": callback, "args": args, "kwargs": kwargs})
+ self.reply_handlers[message_id].append(Handler(callback, *args, **kwargs))
else:
- self.reply_handlers[message_id] = [{"callback": callback, "args": args, "kwargs": kwargs}]
+ 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:
@@ -1087,11 +1211,15 @@ class TeleBot:
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()
def register_next_step_handler(self, message, 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 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
@@ -1104,15 +1232,20 @@ class TeleBot:
"""
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({"callback": callback, "args": args, "kwargs": kwargs})
+ self.next_step_handlers[chat_id].append(Handler(callback, *args, **kwargs))
else:
- self.next_step_handlers[chat_id] = [{"callback": callback, "args": args, "kwargs": kwargs}]
+ 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):
"""
@@ -1131,11 +1264,14 @@ class TeleBot:
"""
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_id: The message for which we want to clear reply handlers
+ :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)
@@ -1148,6 +1284,9 @@ class TeleBot:
"""
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):
@@ -1155,21 +1294,22 @@ class TeleBot:
chat_id = message.chat.id
was_poped = False
if chat_id in self.next_step_handlers.keys():
- handlers = self.next_step_handlers[chat_id]
- if (handlers):
+ 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
- self.next_step_handlers.pop(chat_id, None)
- if (not was_poped):
+ if self.next_step_saver is not None:
+ self.next_step_saver.start_save_timer()
+ if not was_poped:
i += 1
@staticmethod
def _build_handler_dict(handler, **filters):
return {
'function': handler,
- 'filters': filters
+ 'filters' : filters
}
def message_handler(self, commands=None, regexp=None, func=None, content_types=['text'], **kwargs):
@@ -1332,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),
@@ -1354,6 +1495,30 @@ class AsyncTeleBot(TeleBot):
def __init__(self, *args, **kwargs):
TeleBot.__init__(self, *args, **kwargs)
+ @util.async_dec()
+ def enable_save_next_step_handlers(self, delay=120, filename="./.handler-saves/step.save"):
+ return TeleBot.enable_save_next_step_handlers(self, delay, filename)
+
+ @util.async_dec()
+ def enable_save_reply_handlers(self, delay=120, filename="./.handler-saves/reply.save"):
+ return TeleBot.enable_save_reply_handlers(self, delay, filename)
+
+ @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", del_file_after_loading=True):
+ return TeleBot.load_next_step_handlers(self, filename, del_file_after_loading)
+
+ @util.async_dec()
+ def load_reply_handlers(self, filename="./.handler-saves/reply.save", del_file_after_loading=True):
+ return TeleBot.load_reply_handlers(self, filename, del_file_after_loading)
+
@util.async_dec()
def get_me(self):
return TeleBot.get_me(self)
@@ -1514,6 +1679,10 @@ class AsyncTeleBot(TeleBot):
def edit_message_text(self, *args, **kwargs):
return TeleBot.edit_message_text(self, *args, **kwargs)
+ @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)
diff --git a/telebot/apihelper.py b/telebot/apihelper.py
index e919b5a..8c04530 100644
--- a/telebot/apihelper.py
+++ b/telebot/apihelper.py
@@ -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):
@@ -99,7 +99,6 @@ def get_file(token, 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)
@@ -123,6 +122,8 @@ def send_message(token, chat_id, text, disable_web_page_preview=None, reply_to_m
:param disable_web_page_preview:
:param reply_to_message_id:
:param reply_markup:
+ :param parse_mode:
+ :param disable_notification:
:return:
"""
method_url = r'sendMessage'
@@ -266,7 +267,7 @@ 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, files = _convert_input_media(media)
+ media_json, files = _convert_input_media_array(media)
payload = {'chat_id': chat_id, 'media': media_json}
if disable_notification:
payload['disable_notification'] = disable_notification
@@ -640,6 +641,21 @@ def edit_message_caption(token, caption, chat_id=None, message_id=None, inline_m
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 = {}
@@ -756,7 +772,8 @@ def send_invoice(token, chat_id, title, description, invoice_payload, provider_t
:param disable_notification: Sends the message silently. Users will receive a notification with no sound.
:param reply_to_message_id: If the message is a reply, ID of the original message
:param reply_markup: A JSON-serialized object for an inline keyboard. If empty, one 'Pay total price' button will be shown. If not empty, the first button must be a Pay button
- :return:
+ :param provider_data:
+ :return:
"""
method_url = r'sendInvoice'
payload = {'chat_id': chat_id, 'title': title, 'description': description, 'payload': invoice_payload,
@@ -939,11 +956,17 @@ def _convert_markup(markup):
return markup
-def _convert_input_media(array):
+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.JsonSerializable):
+ if isinstance(input_media, types.InputMedia):
media_dict = input_media.to_dic()
if media_dict['media'].startswith('attach://'):
key = media_dict['media'].replace('attach://', '')
diff --git a/telebot/types.py b/telebot/types.py
index 5ca1b4e..6165c94 100644
--- a/telebot/types.py
+++ b/telebot/types.py
@@ -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.
@@ -457,11 +457,11 @@ class Message(JsonDeserializable):
if not entities:
return text
_subs = {
- "bold": "{text}",
- "italic": "{text}",
- "pre": "{text}
",
- "code": "{text}
",
- "url": "{text}",
+ "bold" : "{text}",
+ "italic" : "{text}",
+ "pre" : "{text}
",
+ "code" : "{text}
",
+ "url" : "{text}",
"text_link": "{text}"
}
if hasattr(self, "custom_subs"):
@@ -469,6 +469,7 @@ class Message(JsonDeserializable):
_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":
@@ -501,6 +502,7 @@ class Message(JsonDeserializable):
def html_caption(self):
return self.__html_text(self.caption, self.caption_entities)
+
class MessageEntity(JsonDeserializable):
@classmethod
def de_json(cls, json_string):
@@ -598,29 +600,6 @@ class Document(JsonDeserializable):
self.file_size = file_size
-class Sticker(JsonDeserializable):
- @classmethod
- def de_json(cls, json_string):
- obj = cls.check_json(json_string)
- file_id = obj['file_id']
- width = obj['width']
- height = obj['height']
- thumb = None
- if 'thumb' in obj:
- thumb = PhotoSize.de_json(obj['thumb'])
- emoji = obj.get('emoji')
- file_size = obj.get('file_size')
- return cls(file_id, width, height, thumb, emoji, file_size)
-
- def __init__(self, file_id, width, height, thumb, emoji=None, file_size=None):
- self.file_id = file_id
- self.width = width
- self.height = height
- self.thumb = thumb
- self.emoji = emoji
- self.file_size = file_size
-
-
class Video(JsonDeserializable):
@classmethod
def de_json(cls, json_string):
@@ -1092,7 +1071,7 @@ class InputVenueMessageContent(Dictionaryable):
def to_dic(self):
json_dic = {'latitude': self.latitude, 'longitude': self.longitude, 'title': self.title,
- 'address': self.address}
+ 'address' : self.address}
if self.foursquare_id:
json_dic['foursquare_id'] = self.foursquare_id
return json_dic
@@ -1191,7 +1170,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
@@ -1202,6 +1181,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:
@@ -1215,6 +1196,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
@@ -1230,6 +1212,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:
@@ -1286,7 +1270,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
@@ -1296,6 +1280,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:
@@ -1308,6 +1294,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
@@ -1322,6 +1309,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:
@@ -1333,8 +1322,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
@@ -1342,6 +1331,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
@@ -1358,6 +1349,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
@@ -1375,6 +1367,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:
@@ -1383,13 +1377,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
@@ -1399,6 +1394,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:
@@ -1411,13 +1408,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
@@ -1427,6 +1425,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:
@@ -1439,14 +1439,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
@@ -1459,6 +1460,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:
@@ -1581,6 +1584,7 @@ class BaseInlineQueryResultCached(JsonSerializable):
self.caption = None
self.reply_markup = None
self.input_message_content = None
+ self.parse_mode = None
self.payload_dic = {}
def to_json(self):
@@ -1597,12 +1601,14 @@ class BaseInlineQueryResultCached(JsonSerializable):
json_dict['reply_markup'] = self.reply_markup.to_dic()
if self.input_message_content:
json_dict['input_message_content'] = self.input_message_content.to_dic()
+ if self.parse_mode:
+ json_dict['parse_mode'] = self.parse_mode
return json.dumps(json_dict)
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
@@ -1612,11 +1618,12 @@ class InlineQueryResultCachedPhoto(BaseInlineQueryResultCached):
self.caption = caption
self.reply_markup = reply_markup
self.input_message_content = input_message_content
+ self.parse_mode = parse_mode
self.payload_dic['photo_file_id'] = photo_file_id
class InlineQueryResultCachedGif(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'
@@ -1627,12 +1634,13 @@ class InlineQueryResultCachedGif(BaseInlineQueryResultCached):
self.caption = caption
self.reply_markup = reply_markup
self.input_message_content = input_message_content
+ self.parse_mode = parse_mode
self.payload_dic['gif_file_id'] = gif_file_id
class InlineQueryResultCachedMpeg4Gif(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
@@ -1642,6 +1650,7 @@ class InlineQueryResultCachedMpeg4Gif(BaseInlineQueryResultCached):
self.caption = caption
self.reply_markup = reply_markup
self.input_message_content = input_message_content
+ self.parse_mode = parse_mode
self.payload_dic['mpeg4_file_id'] = mpeg4_file_id
@@ -1657,7 +1666,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'
@@ -1668,11 +1677,12 @@ class InlineQueryResultCachedDocument(BaseInlineQueryResultCached):
self.caption = caption
self.reply_markup = reply_markup
self.input_message_content = input_message_content
+ self.parse_mode = parse_mode
self.payload_dic['document_file_id'] = document_file_id
class InlineQueryResultCachedVideo(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'
@@ -1683,11 +1693,13 @@ class InlineQueryResultCachedVideo(BaseInlineQueryResultCached):
self.caption = caption
self.reply_markup = reply_markup
self.input_message_content = input_message_content
+ self.parse_mode = parse_mode
self.payload_dic['video_file_id'] = video_file_id
class InlineQueryResultCachedVoice(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
@@ -1696,11 +1708,12 @@ class InlineQueryResultCachedVoice(BaseInlineQueryResultCached):
self.caption = caption
self.reply_markup = reply_markup
self.input_message_content = input_message_content
+ self.parse_mode = parse_mode
self.payload_dic['voice_file_id'] = voice_file_id
class InlineQueryResultCachedAudio(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
@@ -1708,6 +1721,7 @@ class InlineQueryResultCachedAudio(BaseInlineQueryResultCached):
self.caption = caption
self.reply_markup = reply_markup
self.input_message_content = input_message_content
+ self.parse_mode = parse_mode
self.payload_dic['audio_file_id'] = audio_file_id
@@ -2041,48 +2055,61 @@ class MaskPosition(JsonDeserializable, JsonSerializable):
# InputMedia
-class InputMediaPhoto(JsonSerializable):
- def __init__(self, media, caption=None, parse_mode=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': 'attach://' + util.generate_random_token()
- if not util.is_string(self.media) else 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
-class InputMediaVideo(JsonSerializable):
- def __init__(self, media, caption=None, parse_mode=None, width=None, height=None, duration=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(InputMedia):
+ def __init__(self, media, thumb=None, caption=None, parse_mode=None, width=None, height=None, duration=None,
supports_streaming=None):
- self.type = "video"
- self.media = media
- self.caption = caption
- self.parse_mode = parse_mode
+ 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_json(self):
- return json.dumps(self.to_dic())
-
def to_dic(self):
- ret = {'type': self.type, 'media': 'attach://' + util.generate_random_token()
- if not util.is_string(self.media) else self.media}
- if self.caption:
- ret['caption'] = self.caption
- if self.parse_mode:
- ret['parse_mode'] = self.parse_mode
+ ret = super(InputMediaVideo, self).to_dic()
+ if self.thumb:
+ ret['thumb'] = self.thumb
if self.width:
ret['width'] = self.width
if self.height:
@@ -2092,3 +2119,57 @@ class InputMediaVideo(JsonSerializable):
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_dic(self):
+ ret = super(InputMediaAnimation, 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
+ 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
diff --git a/telebot/util.py b/telebot/util.py
index f448d78..21826d0 100644
--- a/telebot/util.py
+++ b/telebot/util.py
@@ -1,10 +1,11 @@
# -*- coding: utf-8 -*-
import random
+import re
import string
+import sys
import threading
import traceback
-import re
-import sys
+
import six
from six import string_types
@@ -243,18 +244,17 @@ def extract_arguments(text):
:param text: String to extract the arguments from a command
:return: the arguments if `text` is a command (according to is_command), else None.
"""
- regexp = re.compile("\/\w*(@\w*)*\s*([\s\S]*)",re.IGNORECASE)
+ regexp = re.compile("/\w*(@\w*)*\s*([\s\S]*)",re.IGNORECASE)
result = regexp.match(text)
return result.group(2) if is_command(text) else None
-def per_thread(key, construct_value):
- 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():
diff --git a/tests/test_telebot.py b/tests/test_telebot.py
index 6818b5b..908b141 100644
--- a/tests/test_telebot.py
+++ b/tests/test_telebot.py
@@ -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)
@@ -388,7 +402,8 @@ class TestTeleBot:
new_msg = tb.edit_message_reply_markup(chat_id=CHAT_ID, message_id=ret_msg.message_id, reply_markup=markup)
assert new_msg.message_id
- def create_text_message(self, text):
+ @staticmethod
+ def create_text_message(text):
params = {'text': text}
chat = types.User(11, False, 'test')
return types.Message(1, None, None, chat, 'text', params, "")
diff --git a/tests/test_types.py b/tests/test_types.py
index c174d42..c229bdf 100644
--- a/tests/test_types.py
+++ b/tests/test_types.py
@@ -59,7 +59,7 @@ def test_json_Message_Sticker_without_thumb():
json_string = r'{"message_id":98,"from":{"id":10734,"first_name":"Fd","last_name":"Wd","username":"dd","is_bot":true },"chat":{"id":10734,"first_name":"Fd","type":"private","last_name":"Wd","username":"dd"},"date":1435479551,"sticker":{"width":550,"height":368,"file_id":"BQADBQADNAIAAsYifgYdGJOa6bGAsQI","file_size":30320}}'
msg = types.Message.de_json(json_string)
assert msg.sticker.height == 368
- assert msg.sticker.thumb == None
+ assert msg.sticker.thumb is None
assert msg.content_type == 'sticker'
@@ -113,6 +113,7 @@ def test_json_voice():
assert voice.duration == 0
assert voice.file_size == 10481
+
def test_json_update():
json_string = r'{"update_id":938203,"message":{"message_id":241,"from":{"is_bot":true,"id":9734,"first_name":"Fk","last_name":"Wg","username":"nir"},"chat":{"id":1111,"first_name":"Fk","type":"private","last_name":"Wg","username":"oir"},"date":1441447009,"text":"HIHI"}}'
update = types.Update.de_json(json_string)
@@ -120,6 +121,7 @@ def test_json_update():
assert update.message.message_id == 241
assert update.message.from_user.id == 9734
+
def test_json_chat():
json_string = r'{"id": -111111,"title": "Test Title","type": "group"}'
chat = types.Chat.de_json(json_string)
@@ -127,6 +129,7 @@ def test_json_chat():
assert chat.type == 'group'
assert chat.title == 'Test Title'
+
def test_InlineQueryResultCachedPhoto():
iq = types.InlineQueryResultCachedPhoto('aaa', 'Fileid')
json_str = iq.to_json()
@@ -143,6 +146,7 @@ def test_InlineQueryResultCachedPhoto_with_title():
assert 'Title' in json_str
assert 'caption' not in json_str
+
def test_InlineQueryResultCachedPhoto_with_markup():
markup = types.InlineKeyboardMarkup()
markup.add(types.InlineKeyboardButton("Google", url="http://www.google.com"))
@@ -154,4 +158,3 @@ def test_InlineQueryResultCachedPhoto_with_markup():
assert 'Title' in json_str
assert 'caption' not in json_str
assert 'reply_markup' in json_str
-