Compare commits

..

58 Commits

Author SHA1 Message Date
Jannes Höke c49058dbb4 Bump version to v5.1 2016-09-24 15:29:23 +02:00
Jannes Höke 8e80a8d273 comment out test_reply_contact 2016-09-24 14:25:31 +02:00
Jannes Höke cbe057083f fix test_send_photo_resend 2016-09-24 14:16:04 +02:00
Jacob Bom 5f6138b06b Merge pull request #409 from python-telegram-bot/entities-filter
Add entities filter
2016-09-24 14:02:01 +02:00
Jacob Bom 1b99caa2f9 Merge remote-tracking branch 'origin/master' into entities-filter 2016-09-24 13:46:55 +02:00
Jacob Bom e16c1da6b1 Change entities filter to be singular.
Also remove the faulty example completely since it should be no longer needed.
2016-09-24 13:38:56 +02:00
Jannes Höke e1242b3b4a message.py: add quote keyword argument to reply_x methods (#420) 2016-09-23 17:44:09 +02:00
Gareth Dwyer 93dde1ac1d Add install from source instructions to readme (#419) 2016-09-23 17:13:32 +02:00
Jannes Höke 05fb9d161a Link echobot2 example from tag v5.0 2016-09-23 17:13:06 +02:00
Eli Gao a91fe5f8f6 Properly split and handle arguments in CommandHandler (#414)
* Properly split and handle arguments in CommandHandler

* Update the docstring for pass_args in CommandHandler

* Properly split and handle arguments in StringCommandHandler
2016-09-20 06:38:49 +02:00
Jannes Höke 5116a77221 Class methods (#362)
* bot.py: add create_references method

* create bot reference in webhook handler, use create_references on new updates

* message.py: implement reply_text

* echobot2.py: use Message.reply_text

* fix create_references in webhook handler

* add some more instance methods

* Chat.kick_member and unban_member

* bot.py: Create bot references in outgoing messages

* add tests for everything testable

* test_updater.py: add create_references method to MockBot

* remove Bot.create_references and refactor TelegramObject.de_json to take the additional parameter bot

* List bot as named kwarg where used

* file.py: Use Bot.request property instead of Bot._request attr
2016-09-20 06:36:55 +02:00
Jannes Höke 1f597c6b4a Merge branch 'LiaungYip-master' 2016-09-20 05:07:00 +02:00
Jannes Höke 1efd330e59 ConversationHandler: Fix #373 2016-09-20 05:00:39 +02:00
Jannes Höke af3e8c6440 Merge branch 'master' of https://github.com/LiaungYip/python-telegram-bot into LiaungYip-master 2016-09-20 04:10:39 +02:00
Jacob Bom f34c09dd72 Fix image sizes in tests. 2016-09-14 19:58:30 +02:00
Jacob Bom 97bb04cd38 Faulty example was faulty. 2016-09-13 20:50:25 +02:00
Jacob Bom 7ab007d8d4 Add Filters.entities test. 2016-09-13 20:47:43 +02:00
Jacob Bom f7b497c1b4 Fix in keyword ordering
We're testing for a string in list, not the other way around :P
2016-09-13 20:45:42 +02:00
Jacob Bom 4e60008086 Add entities filter
Should ideally superseed #375.
2016-09-13 20:09:46 +02:00
Rahiel Kasim 5285f63e4a Merge pull request #388 from python-telegram-bot/emoji
deprecate telegram.Emoji
2016-09-13 19:25:58 +02:00
Jacob Bom 6647ae3c25 Add methods to parse entities in Message
Should close #400.

* Add parse_entity

* Add parse_entities

* Add MessageEntity types as constants to MessageEntity.

* Add MAX_MESSAGE_ENTITIES to constants.py
Note: the value has been found by experimentation as opposed to extracted from the api docs.

* Add tests for parse_entity and parse_entities
2016-09-07 08:49:09 +02:00
Noam Meltzer e4a132c0e4 Reusable dispatcher (#402)
* Create a Request class which maintains its own connection pool
* When creating a Bot instance a new Request instance will be created if one wasn't supplied.
* Updater is responsible for creating a Request instance if a Bot instance wasn't provided.
* Dispatcher: add method to run async functions without decorator
* Dispatcher can now run as a singleton (allowing run_async decorator to work) as it always did and as multiple instances (where run_async decorator will raise RuntimeError)
2016-09-06 16:38:07 +03:00
Rahiel Kasim ca81a75f29 Merge pull request #396 from python-telegram-bot/json
use ujson as JSON en/decoder if available
2016-08-26 11:42:28 +02:00
Rahiel Kasim da87d4ba78 fix yapf 2016-08-26 11:17:05 +02:00
Rahiel Kasim 4753d27bd5 bump yapf to 0.11.0 2016-08-26 10:55:41 +02:00
Rahiel Kasim eabfc0b06b set ujson as optional dependency, test CPython builds with ujson 2016-08-26 10:23:17 +02:00
Rahiel Kasim fcda567f8c use ujson as JSON en/decoder if available 2016-08-26 09:40:46 +02:00
Li-aung 'Lewis' Yip 1c36ff46ad Add myself to AUTHORS.rst 2016-08-24 09:37:23 +08:00
Jacob Bom ffff0938f4 Add forwarded filter (#392) 2016-08-23 16:55:50 +02:00
Li-aung 'Lewis' Yip ab2d6eb494 Fix "key not found" exception if the very first message handler in a ConversationHandler returns the state ConversationHandler.END. 2016-08-22 05:49:37 +08:00
Rahiel Kasim fe14000515 remove tests for telegram.Emoji 2016-08-21 11:58:00 +02:00
Rahiel Kasim 5d27059631 deprecate telegram.Emoji 2016-08-21 11:50:22 +02:00
Rahiel Kasim 00bba73673 drop Python 2.6 support (closes #245) (#386)
* drop Python 2.6 support (closes #245)

* fix NullHandler import

* README: explicitly mention Py3 and PyPy compatibility
2016-08-20 22:01:07 +02:00
eugenio412 e9c5ee7ad6 unset but (#383)
solved the bug that prevented the unset to work
2016-08-16 21:13:31 +02:00
MWeesenaar f2f62423ba Merge pull request #379 from bomjacob/master
Fix #376: Execfile not in python 3. Take #2
2016-08-11 14:58:24 +02:00
Mikki Weesenaar 26a0a173f4 Manual merge 2016-08-11 14:38:55 +02:00
Jacob Bom b736e1e855 Move the exec out of function, since that's a whole other scope... 2016-08-11 13:08:28 +02:00
Jacob Bom bd3fa3bb64 Fix #376: Execfile does not exist in python 3 (#377)
* Add execfile function since it's missing in python 3.

* Remove extra space.
2016-08-10 21:39:14 +02:00
Jacob Bom c252042ddf Remove extra space. 2016-08-10 21:00:01 +02:00
Jacob Bom 8475c322af Add execfile function since it's missing in python 3. 2016-08-10 20:59:11 +02:00
Ilya Strukov dd4c0f0f1d Add missing return statement in timerbot example (#368) 2016-08-07 17:59:58 +02:00
Jannes Höke 555e36ee80 tests 2016-08-06 14:47:45 +02:00
Jannes Höke 5134f71380 Merge branch 'more-regex-handlers' of https://github.com/bomjacob/python-telegram-bot into bomjacob-more-regex-handlers 2016-08-06 14:32:05 +02:00
Jacob Bom 32268597d9 Wrap long lines 2016-08-06 14:19:41 +02:00
Jacob Bom 4feb2553ff Add self to Authors.rst 2016-08-06 13:45:43 +02:00
Jacob Bom cd2f956e56 Also fix linebreak ^^ 2016-08-06 13:35:58 +02:00
Jacob Bom 18fdb5ed13 Fix weird indent. 2016-08-06 13:35:06 +02:00
Jacob Bom 8c698caa12 Add Regex handling to CallbackQueryHandler and InlineQueryHandler.
Mostly a copy-paste from RegexHandler.
Not fully tested! Also needs yapf - sorry.
2016-08-06 13:33:38 +02:00
Jannes Höke 587908457e move version string to telegram/version.py (#361) 2016-07-29 15:40:11 +00:00
Noam Meltzer 4375820863 fixup! updated issue template 2016-07-25 22:31:23 +03:00
Noam Meltzer 171c70b4c2 updated issue template
[ci skip]
2016-07-25 22:29:55 +03:00
overquota f1ee54fa73 ChatMigrated exception (#353)
* ChatMigrated exception
2016-07-25 21:50:33 +03:00
Jannes Höke 90913724ca Update README.rst 2016-07-24 02:42:55 +02:00
Jannes Höke 9d4691e50d Create README.md 2016-07-24 02:35:01 +02:00
Rahiel Kasim c4928229d6 README: link to website 2016-07-24 01:21:35 +02:00
Jannes Höke 2f2337cac1 update motto in readme 2016-07-24 01:16:57 +02:00
Jannes Höke f5c57cd6c6 new inlinekeyboard example (#355) 2016-07-20 00:15:03 +02:00
Rahiel Kasim c51c2224da README: update link to new conversationbot example 2016-07-15 12:36:37 +02:00
145 changed files with 2009 additions and 1347 deletions
+6 -6
View File
@@ -1,6 +1,10 @@
<!--
Thanks for reporting issues of python-telegram-bot!
To make it easier for us to help you please enter detailed information below.
Please note, we only support the latest version of python-telegram-bot and
master branch. Please make sure to upgrade & recreate the issue on the latest
version prior to opening an issue.
-->
### Steps to reproduce
1.
@@ -19,13 +23,9 @@ Tell us what happens instead
**Operating System:**
**Version of Python:**
**Version of Python, python-telegram-bot & dependencies:**
``$ python -V``
**Version of python-telegram-bot:**
``$ python -c 'import telegram; print(telegram.__version__)'``
``$ python -m telegram``
### Logs
Insert logs here (if necessary)
+3 -3
View File
@@ -1,15 +1,15 @@
- repo: git://github.com/pre-commit/mirrors-yapf
sha: 316b795b2f32cbe80047aff7e842b72368d5a2c1
sha: v0.11.0
hooks:
- id: yapf
files: ^(telegram|tests)/.*\.py$
- repo: git://github.com/pre-commit/pre-commit-hooks
sha: 3fa02652357ff0dbb42b5bc78c673b7bc105fcf3
sha: 18d7035de5388cc7775be57f529c154bf541aab9
hooks:
- id: flake8
files: ^telegram/.*\.py$
- repo: git://github.com/pre-commit/mirrors-pylint
sha: 4de6c8dfadef1a271a814561ce05b8bc1c446d22
sha: v1.5.5
hooks:
- id: pylint
files: ^telegram/.*\.py$
+2 -2
View File
@@ -1,6 +1,5 @@
language: python
python:
- "2.6"
- "2.7"
- "3.3"
- "3.4"
@@ -11,8 +10,9 @@ install:
- pip install coveralls
- pip install -r requirements.txt
- pip install -r requirements-dev.txt
- if [[ $TRAVIS_PYTHON_VERSION != 'pypy'* ]]; then pip install ujson; fi
script:
- nosetests -v --with-flaky --no-flaky-report --with-coverage --cover-package=telegram/
- 'if [ $TRAVIS_PYTHON_VERSION != 2.6 ] && [ $TRAVIS_PYTHON_VERSION != 3.3 ] && [ $TRAVIS_PYTHON_VERSION != pypy3 ]; then pre-commit run --all-files; fi'
- if [ $TRAVIS_PYTHON_VERSION != 3.3 ] && [ $TRAVIS_PYTHON_VERSION != pypy3 ]; then pre-commit run --all-files; fi
after_success:
coveralls
+4
View File
@@ -11,17 +11,21 @@ The following wonderful people contributed directly or indirectly to this projec
- `Avanatiker <https://github.com/Avanatiker>`_
- `Balduro <https://github.com/Balduro>`_
- `bimmlerd <https://github.com/bimmlerd>`_
- `Eli Gao <https://github.com/eligao>`_
- `ErgoZ Riftbit Vaper <https://github.com/ergoz>`_
- `franciscod <https://github.com/franciscod>`_
- `Jacob Bom <https://github.com/bomjacob>`_
- `JASON0916 <https://github.com/JASON0916>`_
- `jh0ker <https://github.com/jh0ker>`_
- `JRoot3D <https://github.com/JRoot3D>`_
- `jlmadurga <https://github.com/jlmadurga>`_
- `Li-aung Yip <https://github.com/LiaungYip>`_
- `macrojames <https://github.com/macrojames>`_
- `naveenvhegde <https://github.com/naveenvhegde>`_
- `njittam <https://github.com/njittam>`_
- `Noam Meltzer <https://github.com/tsnoam>`_
- `Oleg Shlyazhko <https://github.com/ollmer>`_
- `overquota <https://github.com/overquota>`_
- `Rahiel Kasim <https://github.com/rahiel>`_
- `Shelomentsev D <https://github.com/shelomentsevd>`_
- `sooyhwang <https://github.com/sooyhwang>`_
+17
View File
@@ -2,6 +2,23 @@
Changes
=======
**2016-09-24**
*Released 5.1*
- Drop Python 2.6 support
- Deprecate ``telegram.Emoji``
- Use ``ujson`` if available
- Add instance methods to ``Message``, ``Chat``, ``User``, ``InlineQuery`` and ``CallbackQuery``
- RegEx filtering for ``CallbackQueryHandler`` and ``InlineQueryHandler``
- New ``MessageHandler`` filters: ``forwarded`` and ``entity``
- Add ``Message.get_entity`` to correctly handle UTF-16 codepoints and ``MessageEntity`` offsets
- Fix bug in ``ConversationHandler`` when first handler ends the conversation
- Allow multiple ``Dispatcher`` instances
- Add ``ChatMigrated`` Exception
- Properly split and handle arguments in ``CommandHandler``
**2016-07-15**
*Released 5.0*
+12 -16
View File
@@ -1,9 +1,9 @@
.. image:: https://github.com/python-telegram-bot/logos/blob/master/logo-text/png/ptb-logo-text_768.png?raw=true
:align: center
:target: https://github.com/python-telegram-bot/logos
:target: https://python-telegram-bot.org
:alt: python-telegram-bot Logo
Not **just** a Python wrapper around the Telegram Bot API
We have made you a wrapper you can't refuse
*Stay tuned for library updates and new releases on our* `Telegram Channel <https://telegram.me/pythontelegrambotchannel>`_.
@@ -73,8 +73,7 @@ Introduction
This library provides a pure Python interface for the
`Telegram Bot API <https://core.telegram.org/bots/api>`_.
It works with Python versions from 2.6+ (**Note:** Support for 2.6 will be dropped at some point
this year. 2.7 will still be supported).
It's compatible with Python versions 2.7, 3.3+ and `PyPy <http://pypy.org/>`_.
It also works with `Google App Engine <https://cloud.google.com/appengine>`_.
In addition to the pure API implementation, this library features a number of high-level classes to
@@ -97,6 +96,13 @@ You can install or upgrade python-telegram-bot with:
$ pip install python-telegram-bot --upgrade
Or you can install from source with:
.. code:: shell
$ git clone https://github.com/python-telegram-bot/python-telegram-bot
$ cd python-telegram-bot
$ python setup.py install
===============
Getting started
===============
@@ -117,21 +123,11 @@ Learning by example
We believe that the best way to learn and understand this simple package is by example. So here
are some examples for you to review. Even if it's not your approach for learning, please take a
look at ``echobot2`` (below), it is de facto the base for most of the bots out there. Best of all,
look at ``echobot2``, it is de facto the base for most of the bots out there. Best of all,
the code for these examples are released to the public domain, so you can start by grabbing the
code and building on top of it.
- `echobot2 <https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/echobot2.py>`_ replies back messages.
- `inlinebot <https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/inlinebot.py>`_ basic example of an `inline bot <https://core.telegram.org/bots/inline>`_.
- `state machine bot <https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/state_machine_bot.py>`_ keeps the state for individual users, useful for multipart conversations.
- `timerbot <https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/timerbot.py>`_ uses the ``JobQueue`` to send timed messages.
- `echobot <https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/legacy/echobot.py>`_ uses only the pure API to echo messages.
Look at the examples on the `wiki <https://github.com/python-telegram-bot/python-telegram-bot/wiki/Examples>`_ to see other bots the community has built.
Visit `this page <https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/README.md>`_ to discover the official examples or look at the examples on the `wiki <https://github.com/python-telegram-bot/python-telegram-bot/wiki/Examples>`_ to see other bots the community has built.
-------
Logging
-7
View File
@@ -1,7 +0,0 @@
telegram.emoji module
=====================
.. automodule:: telegram.emoji
:members:
:undoc-members:
:show-inheritance:
+23
View File
@@ -0,0 +1,23 @@
# Examples
The examples in this folder are small bots meant to show you how a bot that is written with `python-telegram-bot` looks like. Some bots focus on one specific aspect of the Telegram Bot API while others focus on one of the mechanics of this library. Except for the `echobot.py` example, they all use the high-level framework this library provides with the `telegram.ext` submodule.
All examples are licensed under the [CC0 License](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/LICENSE.txt) and are therefore fully dedicated to the public domain. You can use them as the base for your own bots without worrying about copyrights.
### [`echobot2.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/v5.0/examples/echobot2.py)
This is probably the base for most of the bots made with `python-telegram-bot`. It simply replies to each text message with a message that contains the same text.
### [`timerbot.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/timerbot.py)
This bot uses the [`JobQueue`](https://pythonhosted.org/python-telegram-bot/telegram.ext.jobqueue.html) class to send timed messages. The user sets a timer by using `/set` command with a specific time, for example `/set 30`. The bot then sets up a job to send a message to that user after 30 seconds. The user can also cancel the timer by sending `/unset`. To learn more about the `JobQueue`, read [this wiki article](https://github.com/python-telegram-bot/python-telegram-bot/wiki/Extensions-%E2%80%93-JobQueue).
### [`conversationbot.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/conversationbot.py)
A common task for a bot is to ask information from the user. In v5.0 of this library, we introduced the [`ConversationHandler`](https://pythonhosted.org/python-telegram-bot/telegram.ext.conversationhandler.html) for that exact purpose. This example uses it to retrieve user-information in a conversation-like style.
### [`inlinekeyboard.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/inlinekeyboard.py)
This example sheds some light on inline keyboards, callback queries and message editing.
### [`inlinebot.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/inlinebot.py)
A basic example of an [inline bot](https://core.telegram.org/bots/inline). Don't forget to enable inline mode with [@BotFather](https://telegram.me/BotFather).
## Pure API
The [`echobot.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/echobot.py) example uses only the pure, "bare-metal" API wrapper.
+3 -3
View File
@@ -29,15 +29,15 @@ logger = logging.getLogger(__name__)
# Define a few command handlers. These usually take the two arguments bot and
# update. Error handlers also receive the raised TelegramError object in error.
def start(bot, update):
bot.sendMessage(update.message.chat_id, text='Hi!')
update.message.reply_text('Hi!')
def help(bot, update):
bot.sendMessage(update.message.chat_id, text='Help!')
update.message.reply_text('Help!')
def echo(bot, update):
bot.sendMessage(update.message.chat_id, text=update.message.text)
update.message.reply_text(update.message.text)
def error(bot, update, error):
+20 -79
View File
@@ -1,108 +1,49 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Basic example for a bot that awaits an answer from the user. It's built upon
# the state_machine_bot.py example
# Basic example for a bot that uses inline keyboards.
# This program is dedicated to the public domain under the CC0 license.
import logging
from telegram import Emoji, ForceReply, InlineKeyboardButton, \
InlineKeyboardMarkup
from telegram.ext import Updater, CommandHandler, MessageHandler, \
CallbackQueryHandler, Filters
from telegram import InlineKeyboardButton, InlineKeyboardMarkup
from telegram.ext import Updater, CommandHandler, CallbackQueryHandler
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
level=logging.DEBUG)
# Define the different states a chat can be in
MENU, AWAIT_CONFIRMATION, AWAIT_INPUT = range(3)
# Python 2 and 3 unicode differences
try:
YES, NO = (Emoji.THUMBS_UP_SIGN.decode('utf-8'), Emoji.THUMBS_DOWN_SIGN.decode('utf-8'))
except AttributeError:
YES, NO = (Emoji.THUMBS_UP_SIGN, Emoji.THUMBS_DOWN_SIGN)
# States are saved in a dict that maps chat_id -> state
state = dict()
# Sometimes you need to save data temporarily
context = dict()
# This dict is used to store the settings value for the chat.
# Usually, you'd use persistence for this (e.g. sqlite).
values = dict()
level=logging.INFO)
# Example handler. Will be called on the /set command and on regular messages
def set_value(bot, update):
chat_id = update.message.chat_id
user_id = update.message.from_user.id
user_state = state.get(chat_id, MENU)
def start(bot, update):
keyboard = [[InlineKeyboardButton("Option 1", callback_data='1'),
InlineKeyboardButton("Option 2", callback_data='2')],
if user_state == MENU:
state[user_id] = AWAIT_INPUT # set the state
bot.sendMessage(chat_id,
text="Please enter your settings value",
reply_markup=ForceReply())
[InlineKeyboardButton("Option 3", callback_data='3')]]
reply_markup = InlineKeyboardMarkup(keyboard)
bot.sendMessage(update.message.chat_id, text="Please choose:", reply_markup=reply_markup)
def entered_value(bot, update):
chat_id = update.message.chat_id
user_id = update.message.from_user.id
chat_state = state.get(user_id, MENU)
# Check if we are waiting for input
if chat_state == AWAIT_INPUT:
state[user_id] = AWAIT_CONFIRMATION
# Save the user id and the answer to context
context[user_id] = update.message.text
reply_markup = InlineKeyboardMarkup([[InlineKeyboardButton(YES, callback_data=YES),
InlineKeyboardButton(NO, callback_data=NO)]])
bot.sendMessage(chat_id, text="Are you sure?", reply_markup=reply_markup)
def confirm_value(bot, update):
def button(bot, update):
query = update.callback_query
chat_id = query.message.chat_id
user_id = query.from_user.id
text = query.data
user_state = state.get(user_id, MENU)
user_context = context.get(user_id, None)
# Check if we are waiting for confirmation and the right user answered
if user_state == AWAIT_CONFIRMATION:
del state[user_id]
del context[user_id]
bot.answerCallbackQuery(query.id, text="Ok!")
if text == YES:
values[user_id] = user_context
bot.editMessageText(text="Changed value to %s." % values[user_id],
chat_id=chat_id,
message_id=query.message.message_id)
else:
bot.editMessageText(text="Alright, value is still %s." %
values.get(user_id, 'not set'),
chat_id=chat_id,
message_id=query.message.message_id)
bot.editMessageText(text="Selected option: %s" % query.data,
chat_id=query.message.chat_id,
message_id=query.message.message_id)
def help(bot, update):
bot.sendMessage(update.message.chat_id, text="Use /set to test this bot.")
bot.sendMessage(update.message.chat_id, text="Use /start to test this bot.")
def error(bot, update, error):
logging.warning('Update "%s" caused error "%s"' % (update, error))
# Create the Updater and pass it your bot's token.
updater = Updater("TOKEN")
# The command
updater.dispatcher.add_handler(CommandHandler('set', set_value))
# The answer
updater.dispatcher.add_handler(MessageHandler([Filters.text], entered_value))
# The confirmation
updater.dispatcher.add_handler(CallbackQueryHandler(confirm_value))
updater.dispatcher.add_handler(CommandHandler('start', help))
updater.dispatcher.add_handler(CommandHandler('start', start))
updater.dispatcher.add_handler(CallbackQueryHandler(button))
updater.dispatcher.add_handler(CommandHandler('help', help))
updater.dispatcher.add_error_handler(error)
+3 -2
View File
@@ -47,6 +47,7 @@ def set(bot, update, args, job_queue):
due = int(args[0])
if due < 0:
bot.sendMessage(chat_id, text='Sorry we can not go back to future!')
return
# Add job to queue
job = Job(alarm, due, repeat=False, context=chat_id)
@@ -59,7 +60,7 @@ def set(bot, update, args, job_queue):
bot.sendMessage(chat_id, text='Usage: /set <seconds>')
def unset(bot, update):
def unset(bot, update, job_queue):
"""Removes the job if the user changed their mind"""
chat_id = update.message.chat_id
@@ -88,7 +89,7 @@ def main():
dp.add_handler(CommandHandler("start", start))
dp.add_handler(CommandHandler("help", start))
dp.add_handler(CommandHandler("set", set, pass_args=True, pass_job_queue=True))
dp.add_handler(CommandHandler("unset", unset))
dp.add_handler(CommandHandler("unset", unset, pass_job_queue=True))
# log all errors
dp.add_error_handler(error)
-1
View File
@@ -2,7 +2,6 @@ flake8
nose
pep257
pylint
unittest2
flaky
yapf
pre-commit
+12 -4
View File
@@ -2,7 +2,7 @@
"""The setup and build script for the python-telegram-bot library."""
import codecs
import telegram
import os
from setuptools import setup, find_packages
@@ -16,18 +16,27 @@ def requirements():
return requirements_list
with codecs.open('README.rst', 'r', 'utf-8') as fd:
fn = os.path.join('telegram', 'version.py')
with open(fn) as fh:
code = compile(fh.read(), fn, 'exec')
exec(code)
setup(name='python-telegram-bot',
version=telegram.__version__,
version=__version__,
author='Leandro Toledo',
author_email='devs@python-telegram-bot.org',
license='LGPLv3',
url='https://github.com/python-telegram-bot/python-telegram-bot',
url='https://python-telegram-bot.org/',
keywords='python telegram bot api wrapper',
description='Not just a Python wrapper around the Telegram Bot API',
long_description=fd.read(),
packages=find_packages(exclude=['tests*']),
install_requires=requirements(),
extras_require={
'json': 'ujson',
},
include_package_data=True,
classifiers=[
'Development Status :: 5 - Production/Stable',
@@ -39,7 +48,6 @@ with codecs.open('README.rst', 'r', 'utf-8') as fd:
'Topic :: Internet',
'Programming Language :: Python',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.3',
+6 -11
View File
@@ -43,7 +43,6 @@ from .forcereply import ForceReply
from .error import TelegramError
from .inputfile import InputFile
from .file import File
from .nullhandler import NullHandler
from .emoji import Emoji
from .parsemode import ParseMode
from .messageentity import MessageEntity
@@ -84,9 +83,10 @@ from .constants import (MAX_MESSAGE_LENGTH, MAX_CAPTION_LENGTH, SUPPORTED_WEBHOO
MAX_FILESIZE_DOWNLOAD, MAX_FILESIZE_UPLOAD,
MAX_MESSAGES_PER_SECOND_PER_CHAT, MAX_MESSAGES_PER_SECOND,
MAX_MESSAGES_PER_MINUTE_PER_GROUP)
from .version import __version__ # flake8: noqa
__author__ = 'devs@python-telegram-bot.org'
__version__ = '5.0.0'
__all__ = ['Audio', 'Bot', 'Chat', 'ChatMember', 'ChatAction', 'ChosenInlineResult',
'CallbackQuery', 'Contact', 'Document', 'Emoji', 'File', 'ForceReply',
'InlineKeyboardButton', 'InlineKeyboardMarkup', 'InlineQuery', 'InlineQueryResult',
@@ -100,14 +100,9 @@ __all__ = ['Audio', 'Bot', 'Chat', 'ChatMember', 'ChatAction', 'ChosenInlineResu
'InlineQueryResultVenue', 'InlineQueryResultVideo', 'InlineQueryResultVoice',
'InputContactMessageContent', 'InputFile', 'InputLocationMessageContent',
'InputMessageContent', 'InputTextMessageContent', 'InputVenueMessageContent',
'KeyboardButton', 'Location', 'Message', 'MessageEntity', 'NullHandler', 'ParseMode',
'PhotoSize', 'ReplyKeyboardHide', 'ReplyKeyboardMarkup', 'ReplyMarkup', 'Sticker',
'TelegramError', 'TelegramObject', 'Update', 'User', 'UserProfilePhotos', 'Venue',
'Video', 'Voice', 'MAX_MESSAGE_LENGTH', 'MAX_CAPTION_LENGTH', 'SUPPORTED_WEBHOOK_PORTS',
'KeyboardButton', 'Location', 'Message', 'MessageEntity', 'ParseMode', 'PhotoSize',
'ReplyKeyboardHide', 'ReplyKeyboardMarkup', 'ReplyMarkup', 'Sticker', 'TelegramError',
'TelegramObject', 'Update', 'User', 'UserProfilePhotos', 'Venue', 'Video', 'Voice',
'MAX_MESSAGE_LENGTH', 'MAX_CAPTION_LENGTH', 'SUPPORTED_WEBHOOK_PORTS',
'MAX_FILESIZE_DOWNLOAD', 'MAX_FILESIZE_UPLOAD', 'MAX_MESSAGES_PER_SECOND_PER_CHAT',
'MAX_MESSAGES_PER_SECOND', 'MAX_MESSAGES_PER_MINUTE_PER_GROUP']
if version_info < (2, 7):
from warnings import warn
warn("python-telegram-bot will stop supporting Python 2.6 in a future release. "
"Please upgrade your Python version to at least Python 2.7!")
+3 -2
View File
@@ -55,10 +55,11 @@ class Audio(TelegramObject):
self.file_size = int(kwargs.get('file_size', 0))
@staticmethod
def de_json(data):
def de_json(data, bot):
"""
Args:
data (str):
data (dict):
bot (telegram.Bot):
Returns:
telegram.Audio:
+12 -4
View File
@@ -18,7 +18,11 @@
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""Base class for Telegram Objects."""
import json
try:
import ujson as json
except ImportError:
import json
from abc import ABCMeta
@@ -34,13 +38,14 @@ class TelegramObject(object):
return self.__dict__[item]
@staticmethod
def de_json(data):
def de_json(data, bot):
"""
Args:
data (str):
data (dict):
bot (telegram.Bot):
Returns:
telegram.TelegramObject:
dict:
"""
if not data:
return None
@@ -64,6 +69,9 @@ class TelegramObject(object):
data = dict()
for key in iter(self.__dict__):
if key == 'bot':
continue
value = self.__dict__[key]
if value is not None:
if hasattr(value, 'to_dict'):
+42 -41
View File
@@ -23,11 +23,11 @@ import functools
import logging
from telegram import (User, Message, Update, Chat, ChatMember, UserProfilePhotos, File,
ReplyMarkup, TelegramObject, NullHandler)
ReplyMarkup, TelegramObject)
from telegram.error import InvalidToken
from telegram.utils import request
from telegram.utils.request import Request
logging.getLogger(__name__).addHandler(NullHandler())
logging.getLogger(__name__).addHandler(logging.NullHandler())
class Bot(TelegramObject):
@@ -44,10 +44,11 @@ class Bot(TelegramObject):
token (str): Bot's unique authentication.
base_url (Optional[str]): Telegram Bot API service URL.
base_file_url (Optional[str]): Telegram Bot API file URL.
request (Optional[Request]): Pre initialized `Request` class.
"""
def __init__(self, token, base_url=None, base_file_url=None):
def __init__(self, token, base_url=None, base_file_url=None, request=None):
self.token = self._validate_token(token)
if not base_url:
@@ -61,9 +62,13 @@ class Bot(TelegramObject):
self.base_file_url = base_file_url + self.token
self.bot = None
self._request = request or Request()
self.logger = logging.getLogger(__name__)
@property
def request(self):
return self._request
@staticmethod
def _validate_token(token):
"""a very basic validation on token"""
@@ -144,12 +149,12 @@ class Bot(TelegramObject):
else:
data['reply_markup'] = reply_markup
result = request.post(url, data, timeout=kwargs.get('timeout'))
result = self._request.post(url, data, timeout=kwargs.get('timeout'))
if result is True:
return result
return Message.de_json(result)
return Message.de_json(result, self)
return decorator
@@ -169,9 +174,9 @@ class Bot(TelegramObject):
url = '{0}/getMe'.format(self.base_url)
result = request.get(url)
result = self._request.get(url)
self.bot = User.de_json(result)
self.bot = User.de_json(result, self)
return self.bot
@@ -618,13 +623,8 @@ class Bot(TelegramObject):
@log
@message
def sendVenue(
self, chat_id,
latitude,
longitude,
title, address,
foursquare_id=None,
**kwargs):
def sendVenue(self, chat_id, latitude, longitude, title, address, foursquare_id=None,
**kwargs):
"""
Use this method to send information about a venue.
@@ -818,7 +818,7 @@ class Bot(TelegramObject):
if switch_pm_parameter:
data['switch_pm_parameter'] = switch_pm_parameter
result = request.post(url, data, timeout=kwargs.get('timeout'))
result = self._request.post(url, data, timeout=kwargs.get('timeout'))
return result
@@ -858,9 +858,9 @@ class Bot(TelegramObject):
if limit:
data['limit'] = limit
result = request.post(url, data, timeout=kwargs.get('timeout'))
result = self._request.post(url, data, timeout=kwargs.get('timeout'))
return UserProfilePhotos.de_json(result)
return UserProfilePhotos.de_json(result, self)
@log
def getFile(self, file_id, **kwargs):
@@ -889,12 +889,12 @@ class Bot(TelegramObject):
data = {'file_id': file_id}
result = request.post(url, data, timeout=kwargs.get('timeout'))
result = self._request.post(url, data, timeout=kwargs.get('timeout'))
if result.get('file_path'):
result['file_path'] = '%s/%s' % (self.base_file_url, result['file_path'])
return File.de_json(result)
return File.de_json(result, self)
@log
def kickChatMember(self, chat_id, user_id, **kwargs):
@@ -926,7 +926,7 @@ class Bot(TelegramObject):
data = {'chat_id': chat_id, 'user_id': user_id}
result = request.post(url, data, timeout=kwargs.get('timeout'))
result = self._request.post(url, data, timeout=kwargs.get('timeout'))
return result
@@ -960,7 +960,7 @@ class Bot(TelegramObject):
data = {'chat_id': chat_id, 'user_id': user_id}
result = request.post(url, data, timeout=kwargs.get('timeout'))
result = self._request.post(url, data, timeout=kwargs.get('timeout'))
return result
@@ -1004,7 +1004,7 @@ class Bot(TelegramObject):
if show_alert:
data['show_alert'] = show_alert
result = request.post(url, data, timeout=kwargs.get('timeout'))
result = self._request.post(url, data, timeout=kwargs.get('timeout'))
return result
@@ -1132,10 +1132,11 @@ class Bot(TelegramObject):
@log
@message
def editMessageReplyMarkup(
self, chat_id=None,
message_id=None, inline_message_id=None,
**kwargs):
def editMessageReplyMarkup(self,
chat_id=None,
message_id=None,
inline_message_id=None,
**kwargs):
"""Use this method to edit only the reply markup of messages sent by
the bot or via the bot (for inline bots).
@@ -1217,14 +1218,14 @@ class Bot(TelegramObject):
urlopen_timeout = timeout + network_delay
result = request.post(url, data, timeout=urlopen_timeout)
result = self._request.post(url, data, timeout=urlopen_timeout)
if result:
self.logger.debug('Getting updates: %s', [u['update_id'] for u in result])
else:
self.logger.debug('No new updates found.')
return [Update.de_json(x) for x in result]
return [Update.de_json(u, self) for u in result]
@log
def setWebhook(self, webhook_url=None, certificate=None, **kwargs):
@@ -1260,7 +1261,7 @@ class Bot(TelegramObject):
if certificate:
data['certificate'] = certificate
result = request.post(url, data, timeout=kwargs.get('timeout'))
result = self._request.post(url, data, timeout=kwargs.get('timeout'))
return result
@@ -1290,7 +1291,7 @@ class Bot(TelegramObject):
data = {'chat_id': chat_id}
result = request.post(url, data, timeout=kwargs.get('timeout'))
result = self._request.post(url, data, timeout=kwargs.get('timeout'))
return result
@@ -1322,9 +1323,9 @@ class Bot(TelegramObject):
data = {'chat_id': chat_id}
result = request.post(url, data, timeout=kwargs.get('timeout'))
result = self._request.post(url, data, timeout=kwargs.get('timeout'))
return Chat.de_json(result)
return Chat.de_json(result, self)
@log
def getChatAdministrators(self, chat_id, **kwargs):
@@ -1357,9 +1358,9 @@ class Bot(TelegramObject):
data = {'chat_id': chat_id}
result = request.post(url, data, timeout=kwargs.get('timeout'))
result = self._request.post(url, data, timeout=kwargs.get('timeout'))
return [ChatMember.de_json(x) for x in result]
return [ChatMember.de_json(x, self) for x in result]
@log
def getChatMembersCount(self, chat_id, **kwargs):
@@ -1387,7 +1388,7 @@ class Bot(TelegramObject):
data = {'chat_id': chat_id}
result = request.post(url, data, timeout=kwargs.get('timeout'))
result = self._request.post(url, data, timeout=kwargs.get('timeout'))
return result
@@ -1420,13 +1421,13 @@ class Bot(TelegramObject):
data = {'chat_id': chat_id, 'user_id': user_id}
result = request.post(url, data, timeout=kwargs.get('timeout'))
result = self._request.post(url, data, timeout=kwargs.get('timeout'))
return ChatMember.de_json(result)
return ChatMember.de_json(result, self)
@staticmethod
def de_json(data):
data = super(Bot, Bot).de_json(data)
def de_json(data, bot):
data = super(Bot, Bot).de_json(data, bot)
return Bot(**data)
+20 -5
View File
@@ -25,7 +25,7 @@ from telegram import TelegramObject, Message, User
class CallbackQuery(TelegramObject):
"""This object represents a Telegram CallbackQuery."""
def __init__(self, id, from_user, data, **kwargs):
def __init__(self, id, from_user, data, bot=None, **kwargs):
# Required
self.id = id
self.from_user = from_user
@@ -34,15 +34,26 @@ class CallbackQuery(TelegramObject):
self.message = kwargs.get('message')
self.inline_message_id = kwargs.get('inline_message_id', '')
self.bot = bot
@staticmethod
def de_json(data):
def de_json(data, bot):
"""
Args:
data (dict):
bot (telegram.Bot):
Returns:
telegram.CallbackQuery:
"""
if not data:
return None
data['from_user'] = User.de_json(data.get('from'))
data['message'] = Message.de_json(data.get('message'))
data['from_user'] = User.de_json(data.get('from'), bot)
data['message'] = Message.de_json(data.get('message'), bot)
return CallbackQuery(**data)
return CallbackQuery(bot=bot, **data)
def to_dict(self):
"""
@@ -54,3 +65,7 @@ class CallbackQuery(TelegramObject):
# Required
data['from'] = data.pop('from_user', None)
return data
def answer(self, *args, **kwargs):
"""Shortcut for ``bot.answerCallbackQuery(update.callback_query.id, *args, **kwargs)``"""
return self.bot.answerCallbackQuery(self.id, *args, **kwargs)
+35 -3
View File
@@ -40,6 +40,7 @@ class Chat(TelegramObject):
Keyword Args:
type (Optional[str]):
bot (Optional[Bot]): The Bot to use for instance methods
"""
PRIVATE = 'private'
@@ -47,7 +48,7 @@ class Chat(TelegramObject):
SUPERGROUP = 'supergroup'
CHANNEL = 'channel'
def __init__(self, id, type, **kwargs):
def __init__(self, id, type, bot=None, **kwargs):
# Required
self.id = int(id)
self.type = type
@@ -57,11 +58,14 @@ class Chat(TelegramObject):
self.first_name = kwargs.get('first_name', '')
self.last_name = kwargs.get('last_name', '')
self.bot = bot
@staticmethod
def de_json(data):
def de_json(data, bot):
"""
Args:
data (dict):
bot (telegram.Bot):
Returns:
telegram.Chat:
@@ -69,4 +73,32 @@ class Chat(TelegramObject):
if not data:
return None
return Chat(**data)
return Chat(bot=bot, **data)
def send_action(self, *args, **kwargs):
"""Shortcut for ``bot.sendChatAction(update.message.chat.id, *args, **kwargs)``"""
return self.bot.sendChatAction(self.id, *args, **kwargs)
def leave(self, *args, **kwargs):
"""Shortcut for ``bot.leaveChat(update.message.chat.id, *args, **kwargs)``"""
return self.bot.leaveChat(self.id, *args, **kwargs)
def get_administrators(self, *args, **kwargs):
"""Shortcut for ``bot.getChatAdministrators(update.message.chat.id, *args, **kwargs)``"""
return self.bot.getChatAdministrators(self.id, *args, **kwargs)
def get_members_count(self, *args, **kwargs):
"""Shortcut for ``bot.getChatMembersCount(update.message.chat.id, *args, **kwargs)``"""
return self.bot.getChatMembersCount(self.id, *args, **kwargs)
def get_member(self, *args, **kwargs):
"""Shortcut for ``bot.getChatMember(update.message.chat.id, *args, **kwargs)``"""
return self.bot.getChatMember(self.id, *args, **kwargs)
def kick_member(self, *args, **kwargs):
"""Shortcut for ``bot.kickChatMember(update.message.chat.id, *args, **kwargs)``"""
return self.bot.kickChatMember(self.id, *args, **kwargs)
def unban_member(self, *args, **kwargs):
"""Shortcut for ``bot.unbanChatMember(update.message.chat.id, *args, **kwargs)``"""
return self.bot.unbanChatMember(self.id, *args, **kwargs)
+3 -2
View File
@@ -46,10 +46,11 @@ class ChatMember(TelegramObject):
self.status = status
@staticmethod
def de_json(data):
def de_json(data, bot):
"""
Args:
data (dict):
bot (telegram.Bot):
Returns:
telegram.ChatMember:
@@ -57,6 +58,6 @@ class ChatMember(TelegramObject):
if not data:
return None
data['user'] = User.de_json(data.get('user'))
data['user'] = User.de_json(data.get('user'), bot)
return ChatMember(**data)
+4 -3
View File
@@ -57,10 +57,11 @@ class ChosenInlineResult(TelegramObject):
self.inline_message_id = inline_message_id
@staticmethod
def de_json(data):
def de_json(data, bot):
"""
Args:
data (dict):
bot (telegram.Bot):
Returns:
telegram.ChosenInlineResult:
@@ -69,9 +70,9 @@ class ChosenInlineResult(TelegramObject):
return None
# Required
data['from_user'] = User.de_json(data.pop('from'))
data['from_user'] = User.de_json(data.pop('from'), bot)
# Optionals
data['location'] = Location.de_json(data.get('location'))
data['location'] = Location.de_json(data.get('location'), bot)
return ChosenInlineResult(**data)
+7
View File
@@ -32,6 +32,12 @@ Attributes:
limit, but eventually you'll begin receiving 429 errors.
MAX_MESSAGES_PER_SECOND (int)
MAX_MESSAGES_PER_MINUTE_PER_GROUP (int)
The following constant have been found by experimentation:
Attributes:
MAX_MESSAGE_ENTITIES (int): Max number of entities that can be in a message.
(Beyond this cap telegram will simply ignore further formatting styles)
"""
MAX_MESSAGE_LENGTH = 4096
@@ -45,3 +51,4 @@ MAX_FILESIZE_UPLOAD = int(50E6) # (50MB)
MAX_MESSAGES_PER_SECOND_PER_CHAT = 1
MAX_MESSAGES_PER_SECOND = 30
MAX_MESSAGES_PER_MINUTE_PER_GROUP = 20
MAX_MESSAGE_ENTITIES = 100
+3 -2
View File
@@ -49,10 +49,11 @@ class Contact(TelegramObject):
self.user_id = int(kwargs.get('user_id', 0))
@staticmethod
def de_json(data):
def de_json(data, bot):
"""
Args:
data (str):
data (dict):
bot (telegram.Bot):
Returns:
telegram.Contact:
+5 -8
View File
@@ -1,11 +1,10 @@
import logging
from telegram import NullHandler
from future.moves.urllib.parse import quote
from future.moves.urllib.error import HTTPError, URLError
from future.moves.urllib.request import urlopen, Request
logging.getLogger(__name__).addHandler(NullHandler())
logging.getLogger(__name__).addHandler(logging.NullHandler())
class Botan(object):
@@ -29,12 +28,10 @@ class Botan(object):
return False
data = message.to_json()
try:
url = self.url_template.format(token=str(self.token),
uid=str(uid),
name=quote(event_name))
request = Request(url,
data=data.encode(),
headers={'Content-Type': 'application/json'})
url = self.url_template.format(
token=str(self.token), uid=str(uid), name=quote(event_name))
request = Request(
url, data=data.encode(), headers={'Content-Type': 'application/json'})
urlopen(request)
return True
except HTTPError as error:
+4 -3
View File
@@ -52,10 +52,11 @@ class Document(TelegramObject):
self.file_size = int(kwargs.get('file_size', 0))
@staticmethod
def de_json(data):
def de_json(data, bot):
"""
Args:
data (str):
data (dict):
bot (telegram.Bot):
Returns:
telegram.Document:
@@ -63,6 +64,6 @@ class Document(TelegramObject):
if not data:
return None
data['thumb'] = PhotoSize.de_json(data.get('thumb'))
data['thumb'] = PhotoSize.de_json(data.get('thumb'), bot)
return Document(**data)
+20 -4
View File
@@ -1,6 +1,6 @@
#!/usr/bin/env python
# flake8: noqa
# pylint: disable=C0103,R0903
# pylint: disable=C0103,R0903,E0213
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
@@ -18,14 +18,27 @@
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains a object that represents an Emoji."""
"""This module contains a object that represents an Emoji.
from future.utils import bytes_to_native_str as n
This module will be removed in the future.
"""
import warnings
from future.utils import bytes_to_native_str
class Emoji(object):
class Emoji2(object):
"""This object represents an Emoji."""
def n(b):
def e(cls):
warnings.warn("telegram.Emoji is being deprecated, please see https://git.io/v6DeB")
return bytes_to_native_str(b)
return property(e)
GRINNING_FACE_WITH_SMILING_EYES = n(b'\xF0\x9F\x98\x81')
FACE_WITH_TEARS_OF_JOY = n(b'\xF0\x9F\x98\x82')
SMILING_FACE_WITH_OPEN_MOUTH = n(b'\xF0\x9F\x98\x83')
@@ -879,3 +892,6 @@ class Emoji(object):
CLOCK_FACE_TEN_THIRTY = n(b'\xF0\x9F\x95\xA5')
CLOCK_FACE_ELEVEN_THIRTY = n(b'\xF0\x9F\x95\xA6')
CLOCK_FACE_TWELVE_THIRTY = n(b'\xF0\x9F\x95\xA7')
Emoji = Emoji2()
+14
View File
@@ -85,3 +85,17 @@ class TimedOut(NetworkError):
def __init__(self):
super(TimedOut, self).__init__('Timed out')
class ChatMigrated(TelegramError):
def __init__(self, new_chat_id):
"""
Args:
new_chat_id (int):
Returns:
"""
super(ChatMigrated, self).__init__('Chat migrated')
self.new_chat_id = new_chat_id
+45 -7
View File
@@ -18,14 +18,19 @@
# along with this program. If not, see [http://www.gnu.org/licenses/].
""" This module contains the CallbackQueryHandler class """
from .handler import Handler
import re
from future.utils import string_types
from telegram import Update
from telegram.utils.deprecate import deprecate
from .handler import Handler
class CallbackQueryHandler(Handler):
"""
Handler class to handle Telegram callback queries.
Handler class to handle Telegram callback queries. Optionally based on a regex.
Read the documentation of the ``re`` module for more information.
Args:
callback (function): A function that takes ``bot, update`` as
@@ -39,18 +44,51 @@ class CallbackQueryHandler(Handler):
``job_queue`` will be passed to the callback function. It will be a ``JobQueue``
instance created by the ``Updater`` which can be used to schedule new jobs.
Default is ``False``.
pattern (optional[str or Pattern]): Optional regex pattern. If not ``None`` ``re.match``
is used to determine if an update should be handled by this handler.
pass_groups (optional[bool]): If the callback should be passed the
result of ``re.match(pattern, data).groups()`` as a keyword
argument called ``groups``. Default is ``False``
pass_groupdict (optional[bool]): If the callback should be passed the
result of ``re.match(pattern, data).groupdict()`` as a keyword
argument called ``groupdict``. Default is ``False``
"""
def __init__(self, callback, pass_update_queue=False, pass_job_queue=False):
super(CallbackQueryHandler, self).__init__(callback,
pass_update_queue=pass_update_queue,
pass_job_queue=pass_job_queue)
def __init__(self,
callback,
pass_update_queue=False,
pass_job_queue=False,
pattern=None,
pass_groups=False,
pass_groupdict=False):
super(CallbackQueryHandler, self).__init__(
callback, pass_update_queue=pass_update_queue, pass_job_queue=pass_job_queue)
if isinstance(pattern, string_types):
pattern = re.compile(pattern)
self.pattern = pattern
self.pass_groups = pass_groups
self.pass_groupdict = pass_groupdict
def check_update(self, update):
return isinstance(update, Update) and update.callback_query
if isinstance(update, Update) and update.callback_query:
if self.pattern:
if update.callback_query.data:
match = re.match(self.pattern, update.callback_query.data)
return bool(match)
else:
return True
def handle_update(self, update, dispatcher):
optional_args = self.collect_optional_args(dispatcher)
if self.pattern:
match = re.match(self.pattern, update.callback_query.data)
if self.pass_groups:
optional_args['groups'] = match.groups()
if self.pass_groupdict:
optional_args['groupdict'] = match.groupdict()
return self.callback(dispatcher.bot, update, **optional_args)
+2 -3
View File
@@ -43,9 +43,8 @@ class ChosenInlineResultHandler(Handler):
"""
def __init__(self, callback, pass_update_queue=False, pass_job_queue=False):
super(ChosenInlineResultHandler, self).__init__(callback,
pass_update_queue=pass_update_queue,
pass_job_queue=pass_job_queue)
super(ChosenInlineResultHandler, self).__init__(
callback, pass_update_queue=pass_update_queue, pass_job_queue=pass_job_queue)
def check_update(self, update):
return isinstance(update, Update) and update.chosen_inline_result
+5 -5
View File
@@ -39,7 +39,8 @@ class CommandHandler(Handler):
pass_args (optional[bool]): If the handler should be passed the
arguments passed to the command as a keyword argument called `
``args``. It will contain a list of strings, which is the text
following the command split on spaces. Default is ``False``
following the command split on single or consecutive whitespace characters.
Default is ``False``
pass_update_queue (optional[bool]): If set to ``True``, a keyword argument called
``update_queue`` will be passed to the callback function. It will be the ``Queue``
instance used by the ``Updater`` and ``Dispatcher`` that contains new updates which can
@@ -57,9 +58,8 @@ class CommandHandler(Handler):
pass_args=False,
pass_update_queue=False,
pass_job_queue=False):
super(CommandHandler, self).__init__(callback,
pass_update_queue=pass_update_queue,
pass_job_queue=pass_job_queue)
super(CommandHandler, self).__init__(
callback, pass_update_queue=pass_update_queue, pass_job_queue=pass_job_queue)
self.command = command
self.allow_edited = allow_edited
self.pass_args = pass_args
@@ -81,7 +81,7 @@ class CommandHandler(Handler):
message = update.message or update.edited_message
if self.pass_args:
optional_args['args'] = message.text.split(' ')[1:]
optional_args['args'] = message.text.split()[1:]
return self.callback(dispatcher.bot, update, **optional_args)
+5 -2
View File
@@ -213,10 +213,13 @@ class ConversationHandler(Handler):
def update_state(self, new_state, key):
if new_state == self.END:
del self.conversations[key]
if key in self.conversations:
del self.conversations[key]
else:
pass
elif isinstance(new_state, Promise):
self.conversations[key] = (self.conversations[key], new_state)
self.conversations[key] = (self.conversations.get(key), new_state)
elif new_state is not None:
self.conversations[key] = new_state
+117 -56
View File
@@ -19,70 +19,43 @@
"""This module contains the Dispatcher class."""
import logging
import weakref
from functools import wraps
from threading import Thread, Lock, Event, current_thread
from threading import Thread, Lock, Event, current_thread, BoundedSemaphore
from time import sleep
from uuid import uuid4
from queue import Queue, Empty
from future.builtins import range
from telegram import (TelegramError, NullHandler)
from telegram.utils import request
from telegram import TelegramError
from telegram.ext.handler import Handler
from telegram.utils.deprecate import deprecate
from telegram.utils.promise import Promise
logging.getLogger(__name__).addHandler(NullHandler())
ASYNC_QUEUE = Queue()
ASYNC_THREADS = set()
logging.getLogger(__name__).addHandler(logging.NullHandler())
""":type: set[Thread]"""
ASYNC_LOCK = Lock() # guards ASYNC_THREADS
DEFAULT_GROUP = 0
def _pooled():
"""
A wrapper to run a thread in a thread pool
"""
while 1:
promise = ASYNC_QUEUE.get()
# If unpacking fails, the thread pool is being closed from Updater._join_async_threads
if not isinstance(promise, Promise):
logging.getLogger(__name__).debug("Closing run_async thread %s/%d" %
(current_thread().getName(), len(ASYNC_THREADS)))
break
try:
promise.run()
except:
logging.getLogger(__name__).exception("run_async function raised exception")
def run_async(func):
"""
Function decorator that will run the function in a new thread.
"""Function decorator that will run the function in a new thread.
Using this decorator is only possible when only a single Dispatcher exist in the system.
Args:
func (function): The function to run in the thread.
async_queue (Queue): The queue of the functions to be executed asynchronously.
Returns:
function:
"""
# TODO: handle exception in async threads
# set a threading.Event to notify caller thread
"""
@wraps(func)
def async_func(*args, **kwargs):
"""
A wrapper to run a function in a thread
"""
promise = Promise(func, args, kwargs)
ASYNC_QUEUE.put(promise)
return promise
return Dispatcher.get_instance().run_async(func, *args, **kwargs)
return async_func
@@ -100,7 +73,12 @@ class Dispatcher(object):
callbacks
workers (Optional[int]): Number of maximum concurrent worker threads for the ``@run_async``
decorator
"""
__singleton_lock = Lock()
__singleton_semaphore = BoundedSemaphore()
__singleton = None
logger = logging.getLogger(__name__)
def __init__(self, bot, update_queue, workers=4, exception_event=None, job_queue=None):
self.bot = bot
@@ -113,28 +91,92 @@ class Dispatcher(object):
""":type: list[int]"""
self.error_handlers = []
self.logger = logging.getLogger(__name__)
self.running = False
self.__stop_event = Event()
self.__exception_event = exception_event or Event()
self.__async_queue = Queue()
self.__async_threads = set()
with ASYNC_LOCK:
if not ASYNC_THREADS:
if request.is_con_pool_initialized():
raise RuntimeError('Connection Pool already initialized')
# we need a connection pool the size of:
# * for each of the workers
# * 1 for Dispatcher
# * 1 for polling Updater (even if updater is webhook, we can spare a connection)
# * 1 for JobQueue
request.CON_POOL_SIZE = workers + 3
for i in range(workers):
thread = Thread(target=_pooled, name=str(i))
ASYNC_THREADS.add(thread)
thread.start()
# For backward compatibility, we allow a "singleton" mode for the dispatcher. When there's
# only one instance of Dispatcher, it will be possible to use the `run_async` decorator.
with self.__singleton_lock:
if self.__singleton_semaphore.acquire(blocking=0):
self._set_singleton(self)
else:
self.logger.debug('Thread pool already initialized, skipping.')
self._set_singleton(None)
self._init_async_threads(uuid4(), workers)
@classmethod
def _reset_singleton(cls):
# NOTE: This method was added mainly for test_updater benefit and specifically pypy. Never
# call it in production code.
cls.__singleton_semaphore.release()
def _init_async_threads(self, base_name, workers):
base_name = '{}_'.format(base_name) if base_name else ''
for i in range(workers):
thread = Thread(target=self._pooled, name='{}{}'.format(base_name, i))
self.__async_threads.add(thread)
thread.start()
@classmethod
def _set_singleton(cls, val):
cls.logger.debug('Setting singleton dispatcher as %s', val)
cls.__singleton = weakref.ref(val) if val else None
@classmethod
def get_instance(cls):
"""Get the singleton instance of this class.
Returns:
Dispatcher
"""
if cls.__singleton is not None:
return cls.__singleton()
else:
raise RuntimeError('{} not initialized or multiple instances exist'.format(
cls.__name__))
def _pooled(self):
"""
A wrapper to run a thread in a thread pool
"""
thr_name = current_thread().getName()
while 1:
promise = self.__async_queue.get()
# If unpacking fails, the thread pool is being closed from Updater._join_async_threads
if not isinstance(promise, Promise):
self.logger.debug("Closing run_async thread %s/%d", thr_name,
len(self.__async_threads))
break
try:
promise.run()
except:
self.logger.exception("run_async function raised exception")
def run_async(self, func, *args, **kwargs):
"""Queue a function (with given args/kwargs) to be run asynchronously.
Args:
func (function): The function to run in the thread.
args (Optional[tuple]): Arguments to `func`.
kwargs (Optional[dict]): Keyword arguments to `func`.
Returns:
Promise
"""
# TODO: handle exception in async threads
# set a threading.Event to notify caller thread
promise = Promise(func, args, kwargs)
self.__async_queue.put(promise)
return promise
def start(self):
"""
@@ -183,6 +225,25 @@ class Dispatcher(object):
sleep(0.1)
self.__stop_event.clear()
# async threads must be join()ed only after the dispatcher thread was joined,
# otherwise we can still have new async threads dispatched
threads = list(self.__async_threads)
total = len(threads)
# Stop all threads in the thread pool by put()ting one non-tuple per thread
for i in range(total):
self.__async_queue.put(None)
for i, thr in enumerate(threads):
self.logger.debug('Waiting for async thread {0}/{1} to end'.format(i + 1, total))
thr.join()
self.__async_threads.remove(thr)
self.logger.debug('async thread {0}/{1} has ended'.format(i + 1, total))
@property
def has_running_threads(self):
return self.running or bool(self.__async_threads)
def process_update(self, update):
"""
Processes a single update.
+44 -7
View File
@@ -17,15 +17,19 @@
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
""" This module contains the InlineQueryHandler class """
import re
from future.utils import string_types
from .handler import Handler
from telegram import Update
from telegram.utils.deprecate import deprecate
from .handler import Handler
class InlineQueryHandler(Handler):
"""
Handler class to handle Telegram inline queries.
Handler class to handle Telegram inline queries. Optionally based on a regex. Read the
documentation of the ``re`` module for more information.
Args:
callback (function): A function that takes ``bot, update`` as
@@ -39,18 +43,51 @@ class InlineQueryHandler(Handler):
``job_queue`` will be passed to the callback function. It will be a ``JobQueue``
instance created by the ``Updater`` which can be used to schedule new jobs.
Default is ``False``.
pattern (optional[str or Pattern]): Optional regex pattern. If not ``None`` ``re.match``
is used to determine if an update should be handled by this handler.
pass_groups (optional[bool]): If the callback should be passed the
result of ``re.match(pattern, query).groups()`` as a keyword
argument called ``groups``. Default is ``False``
pass_groupdict (optional[bool]): If the callback should be passed the
result of ``re.match(pattern, query).groupdict()`` as a keyword
argument called ``groupdict``. Default is ``False``
"""
def __init__(self, callback, pass_update_queue=False, pass_job_queue=False):
super(InlineQueryHandler, self).__init__(callback,
pass_update_queue=pass_update_queue,
pass_job_queue=pass_job_queue)
def __init__(self,
callback,
pass_update_queue=False,
pass_job_queue=False,
pattern=None,
pass_groups=False,
pass_groupdict=False):
super(InlineQueryHandler, self).__init__(
callback, pass_update_queue=pass_update_queue, pass_job_queue=pass_job_queue)
if isinstance(pattern, string_types):
pattern = re.compile(pattern)
self.pattern = pattern
self.pass_groups = pass_groups
self.pass_groupdict = pass_groupdict
def check_update(self, update):
return isinstance(update, Update) and update.inline_query
if isinstance(update, Update) and update.inline_query:
if self.pattern:
if update.inline_query.query:
match = re.match(self.pattern, update.inline_query.query)
return bool(match)
else:
return True
def handle_update(self, update, dispatcher):
optional_args = self.collect_optional_args(dispatcher)
if self.pattern:
match = re.match(self.pattern, update.inline_query.query)
if self.pass_groups:
optional_args['groups'] = match.groups()
if self.pass_groupdict:
optional_args['groupdict'] = match.groupdict()
return self.callback(dispatcher.bot, update, **optional_args)
+25 -4
View File
@@ -81,6 +81,27 @@ class Filters(object):
or message.channel_chat_created or message.migrate_to_chat_id
or message.migrate_from_chat_id or message.pinned_message)
@staticmethod
def forwarded(message):
return bool(message.forward_date)
@staticmethod
def entity(entity_type):
"""Filters messages to only allow those which have a :class:`telegram.MessageEntity`
where their `type` matches `entity_type`.
Args:
entity_type: Entity type to check for. All types can be found as constants
in :class:`telegram.MessageEntity`.
Returns: function to use as filter
"""
def entities_filter(message):
return any([entity.type == entity_type for entity in message.entities])
return entities_filter
class MessageHandler(Handler):
"""
@@ -111,9 +132,8 @@ class MessageHandler(Handler):
allow_edited=False,
pass_update_queue=False,
pass_job_queue=False):
super(MessageHandler, self).__init__(callback,
pass_update_queue=pass_update_queue,
pass_job_queue=pass_job_queue)
super(MessageHandler, self).__init__(
callback, pass_update_queue=pass_update_queue, pass_job_queue=pass_job_queue)
self.filters = filters
self.allow_edited = allow_edited
@@ -138,7 +158,8 @@ class MessageHandler(Handler):
return self.callback(dispatcher.bot, update, **optional_args)
# old non-PEP8 Handler methods
# old non-PEP8 Handler methods
m = "telegram.MessageHandler."
checkUpdate = deprecate(check_update, m + "checkUpdate", m + "check_update")
handleUpdate = deprecate(handle_update, m + "handleUpdate", m + "handle_update")
+2 -3
View File
@@ -62,9 +62,8 @@ class RegexHandler(Handler):
pass_groupdict=False,
pass_update_queue=False,
pass_job_queue=False):
super(RegexHandler, self).__init__(callback,
pass_update_queue=pass_update_queue,
pass_job_queue=pass_job_queue)
super(RegexHandler, self).__init__(
callback, pass_update_queue=pass_update_queue, pass_job_queue=pass_job_queue)
if isinstance(pattern, string_types):
pattern = re.compile(pattern)
+5 -5
View File
@@ -35,7 +35,8 @@ class StringCommandHandler(Handler):
pass_args (optional[bool]): If the handler should be passed the
arguments passed to the command as a keyword argument called `
``args``. It will contain a list of strings, which is the text
following the command split on spaces. Default is ``False``
following the command split on single or consecutive whitespace characters.
Default is ``False``
pass_update_queue (optional[bool]): If set to ``True``, a keyword argument called
``update_queue`` will be passed to the callback function. It will be the ``Queue``
instance used by the ``Updater`` and ``Dispatcher`` that contains new updates which can
@@ -52,9 +53,8 @@ class StringCommandHandler(Handler):
pass_args=False,
pass_update_queue=False,
pass_job_queue=False):
super(StringCommandHandler, self).__init__(callback,
pass_update_queue=pass_update_queue,
pass_job_queue=pass_job_queue)
super(StringCommandHandler, self).__init__(
callback, pass_update_queue=pass_update_queue, pass_job_queue=pass_job_queue)
self.command = command
self.pass_args = pass_args
@@ -66,7 +66,7 @@ class StringCommandHandler(Handler):
optional_args = self.collect_optional_args(dispatcher)
if self.pass_args:
optional_args['args'] = update.split(' ')[1:]
optional_args['args'] = update.split()[1:]
return self.callback(dispatcher.bot, update, **optional_args)
+2 -3
View File
@@ -61,9 +61,8 @@ class StringRegexHandler(Handler):
pass_groupdict=False,
pass_update_queue=False,
pass_job_queue=False):
super(StringRegexHandler, self).__init__(callback,
pass_update_queue=pass_update_queue,
pass_job_queue=pass_job_queue)
super(StringRegexHandler, self).__init__(
callback, pass_update_queue=pass_update_queue, pass_job_queue=pass_job_queue)
if isinstance(pattern, string_types):
pattern = re.compile(pattern)
+3 -8
View File
@@ -44,15 +44,10 @@ class TypeHandler(Handler):
Default is ``False``.
"""
def __init__(self,
type,
callback,
strict=False,
pass_update_queue=False,
def __init__(self, type, callback, strict=False, pass_update_queue=False,
pass_job_queue=False):
super(TypeHandler, self).__init__(callback,
pass_update_queue=pass_update_queue,
pass_job_queue=pass_job_queue)
super(TypeHandler, self).__init__(
callback, pass_update_queue=pass_update_queue, pass_job_queue=pass_job_queue)
self.type = type
self.strict = strict
+39 -41
View File
@@ -28,12 +28,13 @@ import subprocess
from signal import signal, SIGINT, SIGTERM, SIGABRT
from queue import Queue
from telegram import Bot, TelegramError, NullHandler
from telegram.ext import dispatcher, Dispatcher, JobQueue
from telegram import Bot, TelegramError
from telegram.ext import Dispatcher, JobQueue
from telegram.error import Unauthorized, InvalidToken
from telegram.utils.request import Request
from telegram.utils.webhookhandler import (WebhookServer, WebhookHandler)
logging.getLogger(__name__).addHandler(NullHandler())
logging.getLogger(__name__).addHandler(logging.NullHandler())
class Updater(object):
@@ -57,13 +58,17 @@ class Updater(object):
base_url (Optional[str]):
workers (Optional[int]): Amount of threads in the thread pool for
functions decorated with @run_async
bot (Optional[Bot]):
bot (Optional[Bot]): A pre-initialized bot instance. If a pre-initizlied bot is used, it is
the user's responsibility to create it using a `Request` instance with a large enough
connection pool.
job_queue_tick_interval(Optional[float]): The interval the queue should
be checked for new tasks. Defaults to 1.0
Raises:
ValueError: If both `token` and `bot` are passed or none of them.
"""
_request = None
def __init__(self, token=None, base_url=None, workers=4, bot=None):
if (token is None) and (bot is None):
@@ -74,15 +79,23 @@ class Updater(object):
if bot is not None:
self.bot = bot
else:
self.bot = Bot(token, base_url)
# we need a connection pool the size of:
# * for each of the workers
# * 1 for Dispatcher
# * 1 for polling Updater (even if webhook is used, we can spare a connection)
# * 1 for JobQueue
# * 1 for main thread
self._request = Request(con_pool_size=workers + 4)
self.bot = Bot(token, base_url, request=self._request)
self.update_queue = Queue()
self.job_queue = JobQueue(self.bot)
self.__exception_event = Event()
self.dispatcher = Dispatcher(self.bot,
self.update_queue,
job_queue=self.job_queue,
workers=workers,
exception_event=self.__exception_event)
self.dispatcher = Dispatcher(
self.bot,
self.update_queue,
job_queue=self.job_queue,
workers=workers,
exception_event=self.__exception_event)
self.last_update_id = 0
self.logger = logging.getLogger(__name__)
self.running = False
@@ -216,9 +229,8 @@ class Updater(object):
while self.running:
try:
updates = self.bot.getUpdates(self.last_update_id,
timeout=timeout,
network_delay=network_delay)
updates = self.bot.getUpdates(
self.last_update_id, timeout=timeout, network_delay=network_delay)
except TelegramError as te:
self.logger.error("Error while getting Updates: {0}".format(te))
@@ -262,7 +274,8 @@ class Updater(object):
url_path = '/{0}'.format(url_path)
# Create and start server
self.httpd = WebhookServer((listen, port), WebhookHandler, self.update_queue, url_path)
self.httpd = WebhookServer((listen, port), WebhookHandler, self.update_queue, url_path,
self.bot)
if use_ssl:
self._check_ssl_cert(cert, key)
@@ -271,10 +284,11 @@ class Updater(object):
if not webhook_url:
webhook_url = self._gen_webhook_url(listen, port, url_path)
self._bootstrap(max_retries=bootstrap_retries,
clean=clean,
webhook_url=webhook_url,
cert=open(cert, 'rb'))
self._bootstrap(
max_retries=bootstrap_retries,
clean=clean,
webhook_url=webhook_url,
cert=open(cert, 'rb'))
elif clean:
self.logger.warning("cleaning updates is not supported if "
"SSL-termination happens elsewhere; skipping")
@@ -292,10 +306,8 @@ class Updater(object):
exit_code = 0
if exit_code is 0:
try:
self.httpd.socket = ssl.wrap_socket(self.httpd.socket,
certfile=cert,
keyfile=key,
server_side=True)
self.httpd.socket = ssl.wrap_socket(
self.httpd.socket, certfile=cert, keyfile=key, server_side=True)
except ssl.SSLError as error:
self.logger.exception('Failed to init SSL socket')
raise TelegramError(str(error))
@@ -345,7 +357,7 @@ class Updater(object):
self.job_queue.stop()
with self.__lock:
if self.running or dispatcher.ASYNC_THREADS:
if self.running or self.dispatcher.has_running_threads:
self.logger.debug('Stopping Updater and Dispatcher...')
self.running = False
@@ -353,9 +365,10 @@ class Updater(object):
self._stop_httpd()
self._stop_dispatcher()
self._join_threads()
# async threads must be join()ed only after the dispatcher thread was joined,
# otherwise we can still have new async threads dispatched
self._join_async_threads()
# Stop the Request instance only if it was created by the Updater
if self._request:
self._request.stop()
def _stop_httpd(self):
if self.httpd:
@@ -369,21 +382,6 @@ class Updater(object):
self.logger.debug('Requesting Dispatcher to stop...')
self.dispatcher.stop()
def _join_async_threads(self):
with dispatcher.ASYNC_LOCK:
threads = list(dispatcher.ASYNC_THREADS)
total = len(threads)
# Stop all threads in the thread pool by put()ting one non-tuple per thread
for i in range(total):
dispatcher.ASYNC_QUEUE.put(None)
for i, thr in enumerate(threads):
self.logger.debug('Waiting for async thread {0}/{1} to end'.format(i + 1, total))
thr.join()
dispatcher.ASYNC_THREADS.remove(thr)
self.logger.debug('async thread {0}/{1} has ended'.format(i + 1, total))
def _join_threads(self):
for thr in self.__threads:
self.logger.debug('Waiting for {0} thread to end'.format(thr.name))
+12 -6
View File
@@ -21,7 +21,6 @@
from os.path import basename
from telegram import TelegramObject
from telegram.utils.request import download as _download
class File(TelegramObject):
@@ -34,25 +33,31 @@ class File(TelegramObject):
Args:
file_id (str):
bot (telegram.Bot):
**kwargs: Arbitrary keyword arguments.
Keyword Args:
file_size (Optional[int]):
file_path (Optional[str]):
"""
def __init__(self, file_id, **kwargs):
def __init__(self, file_id, bot, **kwargs):
# Required
self.file_id = str(file_id)
# Optionals
self.file_size = int(kwargs.get('file_size', 0))
self.file_path = str(kwargs.get('file_path', ''))
self.bot = bot
@staticmethod
def de_json(data):
def de_json(data, bot):
"""
Args:
data (str):
data (dict):
bot (telegram.Bot):
Returns:
telegram.File:
@@ -60,12 +65,13 @@ class File(TelegramObject):
if not data:
return None
return File(**data)
return File(bot=bot, **data)
def download(self, custom_path=None):
"""
Args:
custom_path (str):
"""
url = self.file_path
@@ -74,4 +80,4 @@ class File(TelegramObject):
else:
filename = basename(url)
_download(url, filename)
self.bot.request.download(url, filename)
+3 -2
View File
@@ -43,10 +43,11 @@ class ForceReply(ReplyMarkup):
self.selective = bool(kwargs.get('selective', False))
@staticmethod
def de_json(data):
def de_json(data, bot):
"""
Args:
data (str):
data (dict):
bot (telegram.Bot):
Returns:
telegram.ForceReply:
+12 -4
View File
@@ -52,8 +52,16 @@ class InlineKeyboardButton(TelegramObject):
self.switch_inline_query = kwargs.get('switch_inline_query')
@staticmethod
def de_json(data):
data = super(InlineKeyboardButton, InlineKeyboardButton).de_json(data)
def de_json(data, bot):
"""
Args:
data (dict):
bot (telegram.Bot):
Returns:
telegram.InlineKeyboardButton:
"""
data = super(InlineKeyboardButton, InlineKeyboardButton).de_json(data, bot)
if not data:
return None
@@ -61,12 +69,12 @@ class InlineKeyboardButton(TelegramObject):
return InlineKeyboardButton(**data)
@staticmethod
def de_list(data):
def de_list(data, bot):
if not data:
return []
inline_keyboards = list()
for inline_keyboard in data:
inline_keyboards.append(InlineKeyboardButton.de_json(inline_keyboard))
inline_keyboards.append(InlineKeyboardButton.de_json(inline_keyboard, bot))
return inline_keyboards
+11 -3
View File
@@ -38,13 +38,21 @@ class InlineKeyboardMarkup(ReplyMarkup):
self.inline_keyboard = inline_keyboard
@staticmethod
def de_json(data):
data = super(InlineKeyboardMarkup, InlineKeyboardMarkup).de_json(data)
def de_json(data, bot):
"""
Args:
data (dict):
bot (telegram.Bot):
Returns:
telegram.InlineKeyboardMarkup:
"""
data = super(InlineKeyboardMarkup, InlineKeyboardMarkup).de_json(data, bot)
if not data:
return None
data['inline_keyboard'] = [InlineKeyboardButton.de_list(inline_keyboard)
data['inline_keyboard'] = [InlineKeyboardButton.de_list(inline_keyboard, bot)
for inline_keyboard in data['inline_keyboard']]
return InlineKeyboardMarkup(**data)
+14 -6
View File
@@ -42,9 +42,10 @@ class InlineQuery(TelegramObject):
Keyword Args:
location (optional[:class:`telegram.Location`]):
bot (Optional[Bot]): The Bot to use for instance methods
"""
def __init__(self, id, from_user, query, offset, **kwargs):
def __init__(self, id, from_user, query, offset, bot=None, **kwargs):
# Required
self.id = id
self.from_user = from_user
@@ -54,24 +55,27 @@ class InlineQuery(TelegramObject):
# Optional
self.location = kwargs.get('location')
self.bot = bot
@staticmethod
def de_json(data):
def de_json(data, bot):
"""
Args:
data (dict):
bot (telegram.Bot):
Returns:
telegram.InlineQuery:
"""
data = super(InlineQuery, InlineQuery).de_json(data)
data = super(InlineQuery, InlineQuery).de_json(data, bot)
if not data:
return None
data['from_user'] = User.de_json(data.get('from'))
data['location'] = Location.de_json(data.get('location'))
data['from_user'] = User.de_json(data.get('from'), bot)
data['location'] = Location.de_json(data.get('location'), bot)
return InlineQuery(**data)
return InlineQuery(bot=bot, **data)
def to_dict(self):
"""
@@ -84,3 +88,7 @@ class InlineQuery(TelegramObject):
data['from'] = data.pop('from_user', None)
return data
def answer(self, *args, **kwargs):
"""Shortcut for ``bot.answerInlineQuery(update.inline_query.id, *args, **kwargs)``"""
return self.bot.answerInlineQuery(self.id, *args, **kwargs)
+3 -3
View File
@@ -35,11 +35,11 @@ class InlineQueryResult(TelegramObject):
"""
def __init__(self, type, id):
def __init__(self, type, id, **kwargs):
# Required
self.type = str(type)
self.id = str(id)
@staticmethod
def de_json(data):
return super(InlineQueryResult, InlineQueryResult).de_json(data)
def de_json(data, bot):
return super(InlineQueryResult, InlineQueryResult).de_json(data, bot)
+5 -5
View File
@@ -94,11 +94,11 @@ class InlineQueryResultArticle(InlineQueryResult):
self.thumb_height = thumb_height
@staticmethod
def de_json(data):
data = super(InlineQueryResultArticle, InlineQueryResultArticle).de_json(data)
def de_json(data, bot):
data = super(InlineQueryResultArticle, InlineQueryResultArticle).de_json(data, bot)
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'))
data['input_message_content'] = InputMessageContent.de_json(data.get(
'input_message_content'))
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'), bot)
data['input_message_content'] = InputMessageContent.de_json(
data.get('input_message_content'), bot)
return InlineQueryResultArticle(**data)
+5 -5
View File
@@ -84,11 +84,11 @@ class InlineQueryResultAudio(InlineQueryResult):
self.input_message_content = input_message_content
@staticmethod
def de_json(data):
data = super(InlineQueryResultAudio, InlineQueryResultAudio).de_json(data)
def de_json(data, bot):
data = super(InlineQueryResultAudio, InlineQueryResultAudio).de_json(data, bot)
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'))
data['input_message_content'] = InputMessageContent.de_json(data.get(
'input_message_content'))
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'), bot)
data['input_message_content'] = InputMessageContent.de_json(
data.get('input_message_content'), bot)
return InlineQueryResultAudio(**data)
+5 -5
View File
@@ -65,11 +65,11 @@ class InlineQueryResultCachedAudio(InlineQueryResult):
self.input_message_content = input_message_content
@staticmethod
def de_json(data):
data = super(InlineQueryResultCachedAudio, InlineQueryResultCachedAudio).de_json(data)
def de_json(data, bot):
data = super(InlineQueryResultCachedAudio, InlineQueryResultCachedAudio).de_json(data, bot)
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'))
data['input_message_content'] = InputMessageContent.de_json(data.get(
'input_message_content'))
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'), bot)
data['input_message_content'] = InputMessageContent.de_json(
data.get('input_message_content'), bot)
return InlineQueryResultCachedAudio(**data)
+5 -5
View File
@@ -49,12 +49,12 @@ class InlineQueryResultCachedDocument(InlineQueryResult):
self.input_message_content = input_message_content
@staticmethod
def de_json(data):
def de_json(data, bot):
data = super(InlineQueryResultCachedDocument,
InlineQueryResultCachedDocument).de_json(data)
InlineQueryResultCachedDocument).de_json(data, bot)
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'))
data['input_message_content'] = InputMessageContent.de_json(data.get(
'input_message_content'))
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'), bot)
data['input_message_content'] = InputMessageContent.de_json(
data.get('input_message_content'), bot)
return InlineQueryResultCachedDocument(**data)
+5 -5
View File
@@ -47,11 +47,11 @@ class InlineQueryResultCachedGif(InlineQueryResult):
self.input_message_content = input_message_content
@staticmethod
def de_json(data):
data = super(InlineQueryResultCachedGif, InlineQueryResultCachedGif).de_json(data)
def de_json(data, bot):
data = super(InlineQueryResultCachedGif, InlineQueryResultCachedGif).de_json(data, bot)
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'))
data['input_message_content'] = InputMessageContent.de_json(data.get(
'input_message_content'))
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'), bot)
data['input_message_content'] = InputMessageContent.de_json(
data.get('input_message_content'), bot)
return InlineQueryResultCachedGif(**data)
+5 -5
View File
@@ -47,12 +47,12 @@ class InlineQueryResultCachedMpeg4Gif(InlineQueryResult):
self.input_message_content = input_message_content
@staticmethod
def de_json(data):
def de_json(data, bot):
data = super(InlineQueryResultCachedMpeg4Gif,
InlineQueryResultCachedMpeg4Gif).de_json(data)
InlineQueryResultCachedMpeg4Gif).de_json(data, bot)
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'))
data['input_message_content'] = InputMessageContent.de_json(data.get(
'input_message_content'))
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'), bot)
data['input_message_content'] = InputMessageContent.de_json(
data.get('input_message_content'), bot)
return InlineQueryResultCachedMpeg4Gif(**data)
+5 -5
View File
@@ -50,11 +50,11 @@ class InlineQueryResultCachedPhoto(InlineQueryResult):
self.input_message_content = input_message_content
@staticmethod
def de_json(data):
data = super(InlineQueryResultCachedPhoto, InlineQueryResultCachedPhoto).de_json(data)
def de_json(data, bot):
data = super(InlineQueryResultCachedPhoto, InlineQueryResultCachedPhoto).de_json(data, bot)
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'))
data['input_message_content'] = InputMessageContent.de_json(data.get(
'input_message_content'))
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'), bot)
data['input_message_content'] = InputMessageContent.de_json(
data.get('input_message_content'), bot)
return InlineQueryResultCachedPhoto(**data)
+6 -5
View File
@@ -41,11 +41,12 @@ class InlineQueryResultCachedSticker(InlineQueryResult):
self.input_message_content = input_message_content
@staticmethod
def de_json(data):
data = super(InlineQueryResultCachedSticker, InlineQueryResultCachedSticker).de_json(data)
def de_json(data, bot):
data = super(InlineQueryResultCachedSticker,
InlineQueryResultCachedSticker).de_json(data, bot)
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'))
data['input_message_content'] = InputMessageContent.de_json(data.get(
'input_message_content'))
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'), bot)
data['input_message_content'] = InputMessageContent.de_json(
data.get('input_message_content'), bot)
return InlineQueryResultCachedSticker(**data)
+5 -5
View File
@@ -49,11 +49,11 @@ class InlineQueryResultCachedVideo(InlineQueryResult):
self.input_message_content = input_message_content
@staticmethod
def de_json(data):
data = super(InlineQueryResultCachedVideo, InlineQueryResultCachedVideo).de_json(data)
def de_json(data, bot):
data = super(InlineQueryResultCachedVideo, InlineQueryResultCachedVideo).de_json(data, bot)
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'))
data['input_message_content'] = InputMessageContent.de_json(data.get(
'input_message_content'))
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'), bot)
data['input_message_content'] = InputMessageContent.de_json(
data.get('input_message_content'), bot)
return InlineQueryResultCachedVideo(**data)
+5 -5
View File
@@ -46,11 +46,11 @@ class InlineQueryResultCachedVoice(InlineQueryResult):
self.input_message_content = input_message_content
@staticmethod
def de_json(data):
data = super(InlineQueryResultCachedVoice, InlineQueryResultCachedVoice).de_json(data)
def de_json(data, bot):
data = super(InlineQueryResultCachedVoice, InlineQueryResultCachedVoice).de_json(data, bot)
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'))
data['input_message_content'] = InputMessageContent.de_json(data.get(
'input_message_content'))
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'), bot)
data['input_message_content'] = InputMessageContent.de_json(
data.get('input_message_content'), bot)
return InlineQueryResultCachedVoice(**data)
+5 -5
View File
@@ -55,11 +55,11 @@ class InlineQueryResultContact(InlineQueryResult):
self.thumb_height = thumb_height
@staticmethod
def de_json(data):
data = super(InlineQueryResultContact, InlineQueryResultContact).de_json(data)
def de_json(data, bot):
data = super(InlineQueryResultContact, InlineQueryResultContact).de_json(data, bot)
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'))
data['input_message_content'] = InputMessageContent.de_json(data.get(
'input_message_content'))
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'), bot)
data['input_message_content'] = InputMessageContent.de_json(
data.get('input_message_content'), bot)
return InlineQueryResultContact(**data)
+5 -5
View File
@@ -60,11 +60,11 @@ class InlineQueryResultDocument(InlineQueryResult):
self.thumb_height = thumb_height
@staticmethod
def de_json(data):
data = super(InlineQueryResultDocument, InlineQueryResultDocument).de_json(data)
def de_json(data, bot):
data = super(InlineQueryResultDocument, InlineQueryResultDocument).de_json(data, bot)
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'))
data['input_message_content'] = InputMessageContent.de_json(data.get(
'input_message_content'))
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'), bot)
data['input_message_content'] = InputMessageContent.de_json(
data.get('input_message_content'), bot)
return InlineQueryResultDocument(**data)
+5 -5
View File
@@ -56,11 +56,11 @@ class InlineQueryResultGif(InlineQueryResult):
self.input_message_content = input_message_content
@staticmethod
def de_json(data):
data = super(InlineQueryResultGif, InlineQueryResultGif).de_json(data)
def de_json(data, bot):
data = super(InlineQueryResultGif, InlineQueryResultGif).de_json(data, bot)
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'))
data['input_message_content'] = InputMessageContent.de_json(data.get(
'input_message_content'))
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'), bot)
data['input_message_content'] = InputMessageContent.de_json(
data.get('input_message_content'), bot)
return InlineQueryResultGif(**data)
+5 -5
View File
@@ -54,11 +54,11 @@ class InlineQueryResultLocation(InlineQueryResult):
self.thumb_height = thumb_height
@staticmethod
def de_json(data):
data = super(InlineQueryResultLocation, InlineQueryResultLocation).de_json(data)
def de_json(data, bot):
data = super(InlineQueryResultLocation, InlineQueryResultLocation).de_json(data, bot)
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'))
data['input_message_content'] = InputMessageContent.de_json(data.get(
'input_message_content'))
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'), bot)
data['input_message_content'] = InputMessageContent.de_json(
data.get('input_message_content'), bot)
return InlineQueryResultLocation(**data)
+5 -5
View File
@@ -56,11 +56,11 @@ class InlineQueryResultMpeg4Gif(InlineQueryResult):
self.input_message_content = input_message_content
@staticmethod
def de_json(data):
data = super(InlineQueryResultMpeg4Gif, InlineQueryResultMpeg4Gif).de_json(data)
def de_json(data, bot):
data = super(InlineQueryResultMpeg4Gif, InlineQueryResultMpeg4Gif).de_json(data, bot)
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'))
data['input_message_content'] = InputMessageContent.de_json(data.get(
'input_message_content'))
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'), bot)
data['input_message_content'] = InputMessageContent.de_json(
data.get('input_message_content'), bot)
return InlineQueryResultMpeg4Gif(**data)
+5 -5
View File
@@ -58,11 +58,11 @@ class InlineQueryResultPhoto(InlineQueryResult):
self.input_message_content = input_message_content
@staticmethod
def de_json(data):
data = super(InlineQueryResultPhoto, InlineQueryResultPhoto).de_json(data)
def de_json(data, bot):
data = super(InlineQueryResultPhoto, InlineQueryResultPhoto).de_json(data, bot)
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'))
data['input_message_content'] = InputMessageContent.de_json(data.get(
'input_message_content'))
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'), bot)
data['input_message_content'] = InputMessageContent.de_json(
data.get('input_message_content'), bot)
return InlineQueryResultPhoto(**data)
+5 -5
View File
@@ -60,11 +60,11 @@ class InlineQueryResultVenue(InlineQueryResult):
self.thumb_height = thumb_height
@staticmethod
def de_json(data):
data = super(InlineQueryResultVenue, InlineQueryResultVenue).de_json(data)
def de_json(data, bot):
data = super(InlineQueryResultVenue, InlineQueryResultVenue).de_json(data, bot)
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'))
data['input_message_content'] = InputMessageContent.de_json(data.get(
'input_message_content'))
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'), bot)
data['input_message_content'] = InputMessageContent.de_json(
data.get('input_message_content'), bot)
return InlineQueryResultVenue(**data)
+5 -5
View File
@@ -63,11 +63,11 @@ class InlineQueryResultVideo(InlineQueryResult):
self.input_message_content = input_message_content
@staticmethod
def de_json(data):
data = super(InlineQueryResultVideo, InlineQueryResultVideo).de_json(data)
def de_json(data, bot):
data = super(InlineQueryResultVideo, InlineQueryResultVideo).de_json(data, bot)
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'))
data['input_message_content'] = InputMessageContent.de_json(data.get(
'input_message_content'))
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'), bot)
data['input_message_content'] = InputMessageContent.de_json(
data.get('input_message_content'), bot)
return InlineQueryResultVideo(**data)
+5 -5
View File
@@ -47,11 +47,11 @@ class InlineQueryResultVoice(InlineQueryResult):
self.input_message_content = input_message_content
@staticmethod
def de_json(data):
data = super(InlineQueryResultVoice, InlineQueryResultVoice).de_json(data)
def de_json(data, bot):
data = super(InlineQueryResultVoice, InlineQueryResultVoice).de_json(data, bot)
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'))
data['input_message_content'] = InputMessageContent.de_json(data.get(
'input_message_content'))
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'), bot)
data['input_message_content'] = InputMessageContent.de_json(
data.get('input_message_content'), bot)
return InlineQueryResultVoice(**data)
+1 -1
View File
@@ -33,5 +33,5 @@ class InputContactMessageContent(InputMessageContent):
self.last_name = last_name
@staticmethod
def de_json(data):
def de_json(data, bot):
return InputContactMessageContent(**data)
+3 -3
View File
@@ -125,9 +125,9 @@ class InputFile(object):
# Add input_file to upload
form.extend([
form_boundary, 'Content-Disposition: form-data; name="%s"; filename="%s"' % (
self.input_name, self.filename
), 'Content-Type: %s' % self.mimetype, '', self.input_file_content
form_boundary, 'Content-Disposition: form-data; name="%s"; filename="%s"' %
(self.input_name,
self.filename), 'Content-Type: %s' % self.mimetype, '', self.input_file_content
])
form.append('--' + self.boundary + '--')
+1 -1
View File
@@ -31,5 +31,5 @@ class InputLocationMessageContent(InputMessageContent):
self.longitude = longitude
@staticmethod
def de_json(data):
def de_json(data, bot):
return InputLocationMessageContent(**data)
+6 -6
View File
@@ -26,33 +26,33 @@ class InputMessageContent(TelegramObject):
"""Base class for Telegram InputMessageContent Objects"""
@staticmethod
def de_json(data):
data = super(InputMessageContent, InputMessageContent).de_json(data)
def de_json(data, bot):
data = super(InputMessageContent, InputMessageContent).de_json(data, bot)
if not data:
return None
try:
from telegram import InputTextMessageContent
return InputTextMessageContent.de_json(data)
return InputTextMessageContent.de_json(data, bot)
except TypeError:
pass
try:
from telegram import InputVenueMessageContent
return InputVenueMessageContent.de_json(data)
return InputVenueMessageContent.de_json(data, bot)
except TypeError:
pass
try:
from telegram import InputLocationMessageContent
return InputLocationMessageContent.de_json(data)
return InputLocationMessageContent.de_json(data, bot)
except TypeError:
pass
try:
from telegram import InputContactMessageContent
return InputContactMessageContent.de_json(data)
return InputContactMessageContent.de_json(data, bot)
except TypeError:
pass
+1 -1
View File
@@ -33,5 +33,5 @@ class InputTextMessageContent(InputMessageContent):
self.disable_web_page_preview = disable_web_page_preview
@staticmethod
def de_json(data):
def de_json(data, bot):
return InputTextMessageContent(**data)
+1 -1
View File
@@ -35,5 +35,5 @@ class InputVenueMessageContent(InputMessageContent):
self.foursquare_id = foursquare_id
@staticmethod
def de_json(data):
def de_json(data, bot):
return InputVenueMessageContent(**data)
+3 -3
View File
@@ -43,19 +43,19 @@ class KeyboardButton(TelegramObject):
self.request_location = request_location
@staticmethod
def de_json(data):
def de_json(data, bot):
if not data:
return None
return KeyboardButton(**data)
@staticmethod
def de_list(data):
def de_list(data, bot):
if not data:
return []
keyboards = list()
for keyboard in data:
keyboards.append(KeyboardButton.de_json(keyboard))
keyboards.append(KeyboardButton.de_json(keyboard, bot))
return keyboards
+3 -2
View File
@@ -39,10 +39,11 @@ class Location(TelegramObject):
self.latitude = float(latitude)
@staticmethod
def de_json(data):
def de_json(data, bot):
"""
Args:
data (str):
data (dict):
bot (telegram.Bot):
Returns:
telegram.Location:
+241 -22
View File
@@ -19,6 +19,7 @@
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains a object that represents a Telegram Message."""
import sys
from datetime import datetime
from time import mktime
@@ -102,9 +103,10 @@ class Message(TelegramObject):
migrate_to_chat_id (Optional[int]):
migrate_from_chat_id (Optional[int]):
channel_chat_created (Optional[bool]):
bot (Optional[Bot]): The Bot to use for instance methods
"""
def __init__(self, message_id, from_user, date, chat, **kwargs):
def __init__(self, message_id, from_user, date, chat, bot=None, **kwargs):
# Required
self.message_id = int(message_id)
self.from_user = from_user
@@ -140,16 +142,19 @@ class Message(TelegramObject):
self.channel_chat_created = bool(kwargs.get('channel_chat_created', False))
self.pinned_message = kwargs.get('pinned_message')
self.bot = bot
@property
def chat_id(self):
"""int: Short for :attr:`Message.chat.id`"""
return self.chat.id
@staticmethod
def de_json(data):
def de_json(data, bot):
"""
Args:
data (dict):
bot (telegram.Bot):
Returns:
telegram.Message:
@@ -157,30 +162,30 @@ class Message(TelegramObject):
if not data:
return None
data['from_user'] = User.de_json(data.get('from'))
data['from_user'] = User.de_json(data.get('from'), bot)
data['date'] = datetime.fromtimestamp(data['date'])
data['chat'] = Chat.de_json(data.get('chat'))
data['entities'] = MessageEntity.de_list(data.get('entities'))
data['forward_from'] = User.de_json(data.get('forward_from'))
data['forward_from_chat'] = Chat.de_json(data.get('forward_from_chat'))
data['chat'] = Chat.de_json(data.get('chat'), bot)
data['entities'] = MessageEntity.de_list(data.get('entities'), bot)
data['forward_from'] = User.de_json(data.get('forward_from'), bot)
data['forward_from_chat'] = Chat.de_json(data.get('forward_from_chat'), bot)
data['forward_date'] = Message._fromtimestamp(data.get('forward_date'))
data['reply_to_message'] = Message.de_json(data.get('reply_to_message'))
data['reply_to_message'] = Message.de_json(data.get('reply_to_message'), bot)
data['edit_date'] = Message._fromtimestamp(data.get('edit_date'))
data['audio'] = Audio.de_json(data.get('audio'))
data['document'] = Document.de_json(data.get('document'))
data['photo'] = PhotoSize.de_list(data.get('photo'))
data['sticker'] = Sticker.de_json(data.get('sticker'))
data['video'] = Video.de_json(data.get('video'))
data['voice'] = Voice.de_json(data.get('voice'))
data['contact'] = Contact.de_json(data.get('contact'))
data['location'] = Location.de_json(data.get('location'))
data['venue'] = Venue.de_json(data.get('venue'))
data['new_chat_member'] = User.de_json(data.get('new_chat_member'))
data['left_chat_member'] = User.de_json(data.get('left_chat_member'))
data['new_chat_photo'] = PhotoSize.de_list(data.get('new_chat_photo'))
data['pinned_message'] = Message.de_json(data.get('pinned_message'))
data['audio'] = Audio.de_json(data.get('audio'), bot)
data['document'] = Document.de_json(data.get('document'), bot)
data['photo'] = PhotoSize.de_list(data.get('photo'), bot)
data['sticker'] = Sticker.de_json(data.get('sticker'), bot)
data['video'] = Video.de_json(data.get('video'), bot)
data['voice'] = Voice.de_json(data.get('voice'), bot)
data['contact'] = Contact.de_json(data.get('contact'), bot)
data['location'] = Location.de_json(data.get('location'), bot)
data['venue'] = Venue.de_json(data.get('venue'), bot)
data['new_chat_member'] = User.de_json(data.get('new_chat_member'), bot)
data['left_chat_member'] = User.de_json(data.get('left_chat_member'), bot)
data['new_chat_photo'] = PhotoSize.de_list(data.get('new_chat_photo'), bot)
data['pinned_message'] = Message.de_json(data.get('pinned_message'), bot)
return Message(**data)
return Message(bot=bot, **data)
def __getitem__(self, item):
if item in self.__dict__.keys():
@@ -244,3 +249,217 @@ class Message(TelegramObject):
except AttributeError:
# Python 3 (< 3.3) and Python 2
return int(mktime(dt_obj.timetuple()))
def _quote(self, kwargs):
"""Modify kwargs for replying with or without quoting"""
if 'reply_to_message_id' in kwargs:
if 'quote' in kwargs:
del kwargs['quote']
elif 'quote' in kwargs:
if kwargs['quote']:
kwargs['reply_to_message_id'] = self.message_id
del kwargs['quote']
else:
if self.chat.type != Chat.PRIVATE:
kwargs['reply_to_message_id'] = self.message_id
def reply_text(self, *args, **kwargs):
"""
Shortcut for ``bot.sendMessage(update.message.chat_id, *args, **kwargs)``
Keyword Args:
quote (Optional[bool]): If set to ``True``, the message is sent as an actual reply to
this message. If ``reply_to_message_id`` is passed in ``kwargs``, this parameter
will be ignored. Default: ``True`` in group chats and ``False`` in private chats.
"""
self._quote(kwargs)
return self.bot.sendMessage(self.chat_id, *args, **kwargs)
def reply_photo(self, *args, **kwargs):
"""
Shortcut for ``bot.sendPhoto(update.message.chat_id, *args, **kwargs)``
Keyword Args:
quote (Optional[bool]): If set to ``True``, the photo is sent as an actual reply to
this message. If ``reply_to_message_id`` is passed in ``kwargs``, this parameter
will be ignored. Default: ``True`` in group chats and ``False`` in private chats.
"""
self._quote(kwargs)
return self.bot.sendPhoto(self.chat_id, *args, **kwargs)
def reply_audio(self, *args, **kwargs):
"""
Shortcut for ``bot.sendAudio(update.message.chat_id, *args, **kwargs)``
Keyword Args:
quote (Optional[bool]): If set to ``True``, the audio is sent as an actual reply to
this message. If ``reply_to_message_id`` is passed in ``kwargs``, this parameter
will be ignored. Default: ``True`` in group chats and ``False`` in private chats.
"""
self._quote(kwargs)
return self.bot.sendAudio(self.chat_id, *args, **kwargs)
def reply_document(self, *args, **kwargs):
"""
Shortcut for ``bot.sendDocument(update.message.chat_id, *args, **kwargs)``
Keyword Args:
quote (Optional[bool]): If set to ``True``, the document is sent as an actual reply to
this message. If ``reply_to_message_id`` is passed in ``kwargs``, this parameter
will be ignored. Default: ``True`` in group chats and ``False`` in private chats.
"""
self._quote(kwargs)
return self.bot.sendDocument(self.chat_id, *args, **kwargs)
def reply_sticker(self, *args, **kwargs):
"""
Shortcut for ``bot.sendSticker(update.message.chat_id, *args, **kwargs)``
Keyword Args:
quote (Optional[bool]): If set to ``True``, the sticker is sent as an actual reply to
this message. If ``reply_to_message_id`` is passed in ``kwargs``, this parameter
will be ignored. Default: ``True`` in group chats and ``False`` in private chats.
"""
self._quote(kwargs)
return self.bot.sendSticker(self.chat_id, *args, **kwargs)
def reply_video(self, *args, **kwargs):
"""
Shortcut for ``bot.sendVideo(update.message.chat_id, *args, **kwargs)``
Keyword Args:
quote (Optional[bool]): If set to ``True``, the video is sent as an actual reply to
this message. If ``reply_to_message_id`` is passed in ``kwargs``, this parameter
will be ignored. Default: ``True`` in group chats and ``False`` in private chats.
"""
self._quote(kwargs)
return self.bot.sendVideo(self.chat_id, *args, **kwargs)
def reply_voice(self, *args, **kwargs):
"""
Shortcut for ``bot.sendVoice(update.message.chat_id, *args, **kwargs)``
Keyword Args:
quote (Optional[bool]): If set to ``True``, the voice is sent as an actual reply to
this message. If ``reply_to_message_id`` is passed in ``kwargs``, this parameter
will be ignored. Default: ``True`` in group chats and ``False`` in private chats.
"""
self._quote(kwargs)
return self.bot.sendVoice(self.chat_id, *args, **kwargs)
def reply_location(self, *args, **kwargs):
"""
Shortcut for ``bot.sendLocation(update.message.chat_id, *args, **kwargs)``
Keyword Args:
quote (Optional[bool]): If set to ``True``, the location is sent as an actual reply to
this message. If ``reply_to_message_id`` is passed in ``kwargs``, this parameter
will be ignored. Default: ``True`` in group chats and ``False`` in private chats.
"""
self._quote(kwargs)
return self.bot.sendLocation(self.chat_id, *args, **kwargs)
def reply_venue(self, *args, **kwargs):
"""
Shortcut for ``bot.sendVenue(update.message.chat_id, *args, **kwargs)``
Keyword Args:
quote (Optional[bool]): If set to ``True``, the venue is sent as an actual reply to
this message. If ``reply_to_message_id`` is passed in ``kwargs``, this parameter
will be ignored. Default: ``True`` in group chats and ``False`` in private chats.
"""
self._quote(kwargs)
return self.bot.sendVenue(self.chat_id, *args, **kwargs)
def reply_contact(self, *args, **kwargs):
"""
Shortcut for ``bot.sendContact(update.message.chat_id, *args, **kwargs)``
Keyword Args:
quote (Optional[bool]): If set to ``True``, the contact is sent as an actual reply to
this message. If ``reply_to_message_id`` is passed in ``kwargs``, this parameter
will be ignored. Default: ``True`` in group chats and ``False`` in private chats.
"""
self._quote(kwargs)
return self.bot.sendContact(self.chat_id, *args, **kwargs)
def forward(self, chat_id, disable_notification=False):
"""Shortcut for
bot.forwardMessage(chat_id=chat_id,
from_chat_id=update.message.chat_id,
disable_notification=disable_notification,
message_id=update.message.message_id)
"""
return self.bot.forwardMessage(
chat_id=chat_id,
from_chat_id=self.chat_id,
disable_notification=disable_notification,
message_id=self.message_id)
def parse_entity(self, entity):
"""
Returns the text from a given :class:`telegram.MessageEntity`.
Note:
This method is present because Telegram calculates the offset and length in
UTF-16 codepoint pairs, which some versions of Python don't handle automatically.
(That is, you can't just slice ``Message.text`` with the offset and length.)
Args:
entity (MessageEntity): The entity to extract the text from. It must be an entity that
belongs to this message.
Returns:
str: The text of the given entity
"""
# Is it a narrow build, if so we don't need to convert
if sys.maxunicode == 0xffff:
return self.text[entity.offset:entity.offset + entity.length]
else:
entity_text = self.text.encode('utf-16-le')
entity_text = entity_text[entity.offset * 2:(entity.offset + entity.length) * 2]
return entity_text.decode('utf-16-le')
def parse_entities(self, types=None):
"""
Returns a ``dict`` that maps :class:`telegram.MessageEntity` to ``str``.
It contains entities from this message filtered by their ``type`` attribute as the key, and
the text that each entity belongs to as the value of the ``dict``.
Note:
This method should always be used instead of the ``entities`` attribute, since it
calculates the correct substring from the message text based on UTF-16 codepoints.
See ``get_entity_text`` for more info.
Args:
types (Optional[list]): List of ``MessageEntity`` types as strings. If the ``type``
attribute of an entity is contained in this list, it will be returned.
Defaults to a list of all types. All types can be found as constants in
:class:`telegram.MessageEntity`.
Returns:
dict[:class:`telegram.MessageEntity`, ``str``]: A dictionary of entities mapped to the
text that belongs to them, calculated based on UTF-16 codepoints.
"""
if types is None:
types = MessageEntity.ALL_TYPES
return {entity: self.parse_entity(entity)
for entity in self.entities if entity.type in types}
+19 -5
View File
@@ -44,15 +44,15 @@ class MessageEntity(TelegramObject):
self.user = kwargs.get('user')
@staticmethod
def de_json(data):
data = super(MessageEntity, MessageEntity).de_json(data)
def de_json(data, bot):
data = super(MessageEntity, MessageEntity).de_json(data, bot)
data['user'] = User.de_json(data.get('user'))
data['user'] = User.de_json(data.get('user'), bot)
return MessageEntity(**data)
@staticmethod
def de_list(data):
def de_list(data, bot):
"""
Args:
data (list):
@@ -65,6 +65,20 @@ class MessageEntity(TelegramObject):
entities = list()
for entity in data:
entities.append(MessageEntity.de_json(entity))
entities.append(MessageEntity.de_json(entity, bot))
return entities
MENTION = 'mention'
HASHTAG = 'hashtag'
BOT_COMMAND = 'bot_command'
URL = 'url'
EMAIL = 'email'
BOLD = 'bold'
ITALIC = 'italic'
CODE = 'code'
PRE = 'pre'
TEXT_LINK = 'text_link'
TEXT_MENTION = 'text_mention'
ALL_TYPES = [MENTION, HASHTAG, BOT_COMMAND, URL, EMAIL, BOLD, ITALIC, CODE, PRE, TEXT_LINK,
TEXT_MENTION]
+6 -4
View File
@@ -55,10 +55,11 @@ class PhotoSize(TelegramObject):
and self.height == other.height and self.file_size == other.file_size)
@staticmethod
def de_json(data):
def de_json(data, bot):
"""
Args:
data (str):
data (dict):
bot (telegram.Bot):
Returns:
telegram.PhotoSize:
@@ -69,10 +70,11 @@ class PhotoSize(TelegramObject):
return PhotoSize(**data)
@staticmethod
def de_list(data):
def de_list(data, bot):
"""
Args:
data (list):
bot (telegram.Bot):
Returns:
List<telegram.PhotoSize>:
@@ -82,6 +84,6 @@ class PhotoSize(TelegramObject):
photos = list()
for photo in data:
photos.append(PhotoSize.de_json(photo))
photos.append(PhotoSize.de_json(photo, bot))
return photos
+3 -2
View File
@@ -44,10 +44,11 @@ class ReplyKeyboardHide(ReplyMarkup):
self.selective = bool(kwargs.get('selective', False))
@staticmethod
def de_json(data):
def de_json(data, bot):
"""
Args:
data (str):
data (dict):
bot(telegram.Bot):
Returns:
telegram.ReplyKeyboardHide:
+4 -3
View File
@@ -50,10 +50,11 @@ class ReplyKeyboardMarkup(ReplyMarkup):
self.selective = bool(kwargs.get('selective', False))
@staticmethod
def de_json(data):
def de_json(data, bot):
"""
Args:
data (str):
data (dict):
bot (telegram.Bot):
Returns:
telegram.ReplyKeyboardMarkup:
@@ -61,7 +62,7 @@ class ReplyKeyboardMarkup(ReplyMarkup):
if not data:
return None
data['keyboard'] = [KeyboardButton.de_list(keyboard) for keyboard in data['keyboard']]
data['keyboard'] = [KeyboardButton.de_list(keyboard, bot) for keyboard in data['keyboard']]
return ReplyKeyboardMarkup(**data)
+2 -2
View File
@@ -25,8 +25,8 @@ class ReplyMarkup(TelegramObject):
"""Base class for Telegram ReplyMarkup Objects"""
@staticmethod
def de_json(data):
data = super(ReplyMarkup, ReplyMarkup).de_json(data)
def de_json(data, bot):
data = super(ReplyMarkup, ReplyMarkup).de_json(data, bot)
if not data:
return None
+4 -3
View File
@@ -55,10 +55,11 @@ class Sticker(TelegramObject):
self.file_size = int(kwargs.get('file_size', 0))
@staticmethod
def de_json(data):
def de_json(data, bot):
"""
Args:
data (str):
data (dict):
bot (telegram.Bot):
Returns:
telegram.Sticker:
@@ -66,6 +67,6 @@ class Sticker(TelegramObject):
if not data:
return None
data['thumb'] = PhotoSize.de_json(data.get('thumb'))
data['thumb'] = PhotoSize.de_json(data.get('thumb'), bot)
return Sticker(**data)
+8 -6
View File
@@ -55,10 +55,11 @@ class Update(TelegramObject):
self.callback_query = kwargs.get('callback_query')
@staticmethod
def de_json(data):
def de_json(data, bot):
"""
Args:
data (dict):
bot (telegram.Bot):
Returns:
telegram.Update:
@@ -66,10 +67,11 @@ class Update(TelegramObject):
if not data:
return None
data['message'] = Message.de_json(data.get('message'))
data['edited_message'] = Message.de_json(data.get('edited_message'))
data['inline_query'] = InlineQuery.de_json(data.get('inline_query'))
data['chosen_inline_result'] = ChosenInlineResult.de_json(data.get('chosen_inline_result'))
data['callback_query'] = CallbackQuery.de_json(data.get('callback_query'))
data['message'] = Message.de_json(data.get('message'), bot)
data['edited_message'] = Message.de_json(data.get('edited_message'), bot)
data['inline_query'] = InlineQuery.de_json(data.get('inline_query'), bot)
data['chosen_inline_result'] = ChosenInlineResult.de_json(
data.get('chosen_inline_result'), bot)
data['callback_query'] = CallbackQuery.de_json(data.get('callback_query'), bot)
return Update(**data)
+14 -4
View File
@@ -41,9 +41,10 @@ class User(TelegramObject):
type (Optional[str]):
last_name (Optional[str]):
username (Optional[str]):
bot (Optional[Bot]): The Bot to use for instance methods
"""
def __init__(self, id, first_name, **kwargs):
def __init__(self, id, first_name, bot=None, **kwargs):
# Required
self.id = int(id)
self.first_name = first_name
@@ -52,6 +53,8 @@ class User(TelegramObject):
self.last_name = kwargs.get('last_name', '')
self.username = kwargs.get('username', '')
self.bot = bot
@property
def name(self):
"""str: """
@@ -62,10 +65,11 @@ class User(TelegramObject):
return self.first_name
@staticmethod
def de_json(data):
def de_json(data, bot):
"""
Args:
data (str):
data (dict):
bot (telegram.Bot):
Returns:
telegram.User:
@@ -73,4 +77,10 @@ class User(TelegramObject):
if not data:
return None
return User(**data)
return User(bot=bot, **data)
def get_profile_photos(self, *args, **kwargs):
"""
Shortcut for ``bot.getUserProfilePhotos(update.message.from_user.id, *args, **kwargs)``
"""
return self.bot.getUserProfilePhotos(self.id, *args, **kwargs)
+4 -3
View File
@@ -40,10 +40,11 @@ class UserProfilePhotos(TelegramObject):
self.photos = photos
@staticmethod
def de_json(data):
def de_json(data, bot):
"""
Args:
data (str):
data (dict):
bot (telegram.Bot):
Returns:
telegram.UserProfilePhotos:
@@ -51,7 +52,7 @@ class UserProfilePhotos(TelegramObject):
if not data:
return None
data['photos'] = [PhotoSize.de_list(photo) for photo in data['photos']]
data['photos'] = [PhotoSize.de_list(photo, bot) for photo in data['photos']]
return UserProfilePhotos(**data)
+159 -194
View File
@@ -18,7 +18,10 @@
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains methods to make POST and GET requests"""
import json
try:
import ujson as json
except ImportError:
import json
import os
import socket
import logging
@@ -28,225 +31,187 @@ import urllib3
from urllib3.connection import HTTPConnection
from telegram import (InputFile, TelegramError)
from telegram.error import Unauthorized, NetworkError, TimedOut, BadRequest
_CON_POOL = None
""":type: urllib3.PoolManager"""
_CON_POOL_PROXY = None
_CON_POOL_PROXY_KWARGS = {}
CON_POOL_SIZE = 1
from telegram.error import Unauthorized, NetworkError, TimedOut, BadRequest, ChatMigrated
logging.getLogger('urllib3').setLevel(logging.WARNING)
def _get_con_pool():
if _CON_POOL is not None:
return _CON_POOL
_init_con_pool()
return _CON_POOL
def _init_con_pool():
global _CON_POOL
kwargs = dict(maxsize=CON_POOL_SIZE,
cert_reqs='CERT_REQUIRED',
ca_certs=certifi.where(),
socket_options=HTTPConnection.default_socket_options + [
(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1),
])
proxy_url = _get_con_pool_proxy()
if not proxy_url:
mgr = urllib3.PoolManager(**kwargs)
else:
if _CON_POOL_PROXY_KWARGS:
kwargs.update(_CON_POOL_PROXY_KWARGS)
mgr = urllib3.proxy_from_url(proxy_url, **kwargs)
if mgr.proxy.auth:
# TODO: what about other auth types?
auth_hdrs = urllib3.make_headers(proxy_basic_auth=mgr.proxy.auth)
mgr.proxy_headers.update(auth_hdrs)
_CON_POOL = mgr
def is_con_pool_initialized():
return _CON_POOL is not None
def stop_con_pool():
global _CON_POOL
if _CON_POOL is not None:
_CON_POOL.clear()
_CON_POOL = None
def set_con_pool_proxy(url, **urllib3_kwargs):
"""Setup connection pool behind a proxy
class Request(object):
"""
Helper class for python-telegram-bot which provides methods to perform POST & GET towards
telegram servers.
Args:
url (str): The URL to the proxy server. For example: `http://127.0.0.1:3128`
urllib3_kwargs (dict): Arbitrary arguments passed as-is to `urllib3.ProxyManager`
"""
global _CON_POOL_PROXY
global _CON_POOL_PROXY_KWARGS
if is_con_pool_initialized():
raise TelegramError('conpool already initialized')
_CON_POOL_PROXY = url
_CON_POOL_PROXY_KWARGS = urllib3_kwargs
def _get_con_pool_proxy():
"""Return the user configured proxy according to the following order:
* proxy configured using `set_con_pool_proxy()`.
* proxy set in `HTTPS_PROXY` env. var.
* proxy set in `https_proxy` env. var.
* None (if no proxy is configured)
Returns:
str | None
"""
if _CON_POOL_PROXY:
return _CON_POOL_PROXY
from_env = os.environ.get('HTTPS_PROXY')
if from_env:
return from_env
from_env = os.environ.get('https_proxy')
if from_env:
return from_env
return None
def _parse(json_data):
"""Try and parse the JSON returned from Telegram.
Returns:
dict: A JSON parsed as Python dict with results - on error this dict will be empty.
"""
decoded_s = json_data.decode('utf-8')
try:
data = json.loads(decoded_s)
except ValueError:
raise TelegramError('Invalid server response')
if not data.get('ok') and data.get('description'):
return data['description']
return data['result']
def _request_wrapper(*args, **kwargs):
"""Wraps urllib3 request for handling known exceptions.
Args:
args: unnamed arguments, passed to urllib3 request.
kwargs: keyword arguments, passed tp urllib3 request.
Returns:
str: A non-parsed JSON text.
Raises:
TelegramError
proxy_url (str): The URL to the proxy server. For example: `http://127.0.0.1:3128`.
urllib3_proxy_kwargs (dict): Arbitrary arguments passed as-is to `urllib3.ProxyManager`.
This value will be ignored if proxy_url is not set.
"""
try:
resp = _get_con_pool().request(*args, **kwargs)
except urllib3.exceptions.TimeoutError as error:
raise TimedOut()
except urllib3.exceptions.HTTPError as error:
# HTTPError must come last as its the base urllib3 exception class
# TODO: do something smart here; for now just raise NetworkError
raise NetworkError('urllib3 HTTPError {0}'.format(error))
def __init__(self, con_pool_size=1, proxy_url=None, urllib3_proxy_kwargs=None):
if urllib3_proxy_kwargs is None:
urllib3_proxy_kwargs = dict()
if 200 <= resp.status <= 299:
# 200-299 range are HTTP success statuses
return resp.data
kwargs = dict(
maxsize=con_pool_size,
cert_reqs='CERT_REQUIRED',
ca_certs=certifi.where(),
socket_options=HTTPConnection.default_socket_options + [
(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1),
])
try:
message = _parse(resp.data)
except ValueError:
raise NetworkError('Unknown HTTPError {0}'.format(resp.status))
# Set a proxy according to the following order:
# * proxy defined in proxy_url (+ urllib3_proxy_kwargs)
# * proxy set in `HTTPS_PROXY` env. var.
# * proxy set in `https_proxy` env. var.
# * None (if no proxy is configured)
if resp.status in (401, 403):
raise Unauthorized()
elif resp.status == 400:
raise BadRequest(repr(message))
elif resp.status == 502:
raise NetworkError('Bad Gateway')
else:
raise NetworkError('{0} ({1})'.format(message, resp.status))
if not proxy_url:
proxy_url = os.environ.get('HTTPS_PROXY') or os.environ.get('https_proxy')
if not proxy_url:
mgr = urllib3.PoolManager(**kwargs)
else:
kwargs.update(urllib3_proxy_kwargs)
mgr = urllib3.proxy_from_url(proxy_url, **kwargs)
if mgr.proxy.auth:
# TODO: what about other auth types?
auth_hdrs = urllib3.make_headers(proxy_basic_auth=mgr.proxy.auth)
mgr.proxy_headers.update(auth_hdrs)
def get(url):
"""Request an URL.
Args:
url:
The web location we want to retrieve.
self._con_pool = mgr
Returns:
A JSON object.
def stop(self):
self._con_pool.clear()
"""
result = _request_wrapper('GET', url)
@staticmethod
def _parse(json_data):
"""Try and parse the JSON returned from Telegram.
return _parse(result)
Returns:
dict: A JSON parsed as Python dict with results - on error this dict will be empty.
"""
decoded_s = json_data.decode('utf-8')
try:
data = json.loads(decoded_s)
except ValueError:
raise TelegramError('Invalid server response')
def post(url, data, timeout=None):
"""Request an URL.
Args:
url:
The web location we want to retrieve.
data:
A dict of (str, unicode) key/value pairs.
timeout:
float. If this value is specified, use it as the definitive timeout (in
seconds) for urlopen() operations. [Optional]
if not data.get('ok'):
description = data.get('description')
parameters = data.get('parameters')
if parameters:
migrate_to_chat_id = parameters.get('migrate_to_chat_id')
if migrate_to_chat_id:
raise ChatMigrated(migrate_to_chat_id)
if description:
return description
Notes:
If neither `timeout` nor `data['timeout']` is specified. The underlying
defaults are used.
return data['result']
Returns:
A JSON object.
def _request_wrapper(self, *args, **kwargs):
"""Wraps urllib3 request for handling known exceptions.
"""
urlopen_kwargs = {}
Args:
args: unnamed arguments, passed to urllib3 request.
kwargs: keyword arguments, passed tp urllib3 request.
if timeout is not None:
urlopen_kwargs['timeout'] = timeout
Returns:
str: A non-parsed JSON text.
if InputFile.is_inputfile(data):
data = InputFile(data)
result = _request_wrapper('POST', url, body=data.to_form(), headers=data.headers)
else:
data = json.dumps(data)
result = _request_wrapper('POST',
url,
body=data.encode(),
headers={'Content-Type': 'application/json'},
**urlopen_kwargs)
Raises:
TelegramError
return _parse(result)
"""
try:
resp = self._con_pool.request(*args, **kwargs)
except urllib3.exceptions.TimeoutError:
raise TimedOut()
except urllib3.exceptions.HTTPError as error:
# HTTPError must come last as its the base urllib3 exception class
# TODO: do something smart here; for now just raise NetworkError
raise NetworkError('urllib3 HTTPError {0}'.format(error))
if 200 <= resp.status <= 299:
# 200-299 range are HTTP success statuses
return resp.data
def download(url, filename):
"""Download a file by its URL.
Args:
url:
The web location we want to retrieve.
try:
message = self._parse(resp.data)
except ValueError:
raise NetworkError('Unknown HTTPError {0}'.format(resp.status))
filename:
The filename within the path to download the file.
if resp.status in (401, 403):
raise Unauthorized()
elif resp.status == 400:
raise BadRequest(repr(message))
elif resp.status == 502:
raise NetworkError('Bad Gateway')
else:
raise NetworkError('{0} ({1})'.format(message, resp.status))
"""
buf = _request_wrapper('GET', url)
with open(filename, 'wb') as fobj:
fobj.write(buf)
def get(self, url):
"""Request an URL.
Args:
url:
The web location we want to retrieve.
Returns:
A JSON object.
"""
result = self._request_wrapper('GET', url)
return self._parse(result)
def post(self, url, data, timeout=None):
"""Request an URL.
Args:
url:
The web location we want to retrieve.
data:
A dict of (str, unicode) key/value pairs.
timeout:
float. If this value is specified, use it as the definitive timeout (in
seconds) for urlopen() operations. [Optional]
Notes:
If neither `timeout` nor `data['timeout']` is specified. The underlying
defaults are used.
Returns:
A JSON object.
"""
urlopen_kwargs = {}
if timeout is not None:
urlopen_kwargs['timeout'] = timeout
if InputFile.is_inputfile(data):
data = InputFile(data)
result = self._request_wrapper('POST', url, body=data.to_form(), headers=data.headers)
else:
data = json.dumps(data)
result = self._request_wrapper(
'POST',
url,
body=data.encode(),
headers={'Content-Type': 'application/json'},
**urlopen_kwargs)
return self._parse(result)
def download(self, url, filename):
"""Download a file by its URL.
Args:
url:
The web location we want to retrieve.
filename:
The filename within the path to download the file.
"""
buf = self._request_wrapper('GET', url)
with open(filename, 'wb') as fobj:
fobj.write(buf)
+10 -5
View File
@@ -1,15 +1,18 @@
import logging
from telegram import Update, NullHandler
from telegram import Update
from future.utils import bytes_to_native_str
from threading import Lock
import json
try:
import ujson as json
except ImportError:
import json
try:
import BaseHTTPServer
except ImportError:
import http.server as BaseHTTPServer
logging.getLogger(__name__).addHandler(NullHandler())
logging.getLogger(__name__).addHandler(logging.NullHandler())
class _InvalidPost(Exception):
@@ -21,11 +24,12 @@ class _InvalidPost(Exception):
class WebhookServer(BaseHTTPServer.HTTPServer, object):
def __init__(self, server_address, RequestHandlerClass, update_queue, webhook_path):
def __init__(self, server_address, RequestHandlerClass, update_queue, webhook_path, bot):
super(WebhookServer, self).__init__(server_address, RequestHandlerClass)
self.logger = logging.getLogger(__name__)
self.update_queue = update_queue
self.webhook_path = webhook_path
self.bot = bot
self.is_running = False
self.server_lock = Lock()
self.shutdown_lock = Lock()
@@ -82,7 +86,8 @@ class WebhookHandler(BaseHTTPServer.BaseHTTPRequestHandler, object):
self.logger.debug('Webhook received data: ' + json_string)
update = Update.de_json(json.loads(json_string))
update = Update.de_json(json.loads(json_string), self.server.bot)
self.logger.debug('Received Update with ID %d on Webhook' % update.update_id)
self.server.update_queue.put(update)
+3 -3
View File
@@ -41,12 +41,12 @@ class Venue(TelegramObject):
self.foursquare_id = foursquare_id
@staticmethod
def de_json(data):
data = super(Venue, Venue).de_json(data)
def de_json(data, bot):
data = super(Venue, Venue).de_json(data, bot)
if not data:
return None
data['location'] = Location.de_json(data.get('location'))
data['location'] = Location.de_json(data.get('location'), bot)
return Venue(**data)
@@ -16,17 +16,5 @@
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains a object that represents a logging NullHandler."""
import logging
class NullHandler(logging.Handler):
"""This object represents a logging NullHandler."""
def emit(self, record):
"""
Args:
record (str):
"""
pass
__version__ = '5.1.0'
+4 -3
View File
@@ -58,10 +58,11 @@ class Video(TelegramObject):
self.file_size = int(kwargs.get('file_size', 0))
@staticmethod
def de_json(data):
def de_json(data, bot):
"""
Args:
data (str):
data (dict):
bot (telegram.Bot):
Returns:
telegram.Video:
@@ -69,6 +70,6 @@ class Video(TelegramObject):
if not data:
return None
data['thumb'] = PhotoSize.de_json(data.get('thumb'))
data['thumb'] = PhotoSize.de_json(data.get('thumb'), bot)
return Video(**data)
+3 -2
View File
@@ -49,10 +49,11 @@ class Voice(TelegramObject):
self.file_size = int(kwargs.get('file_size', 0))
@staticmethod
def de_json(data):
def de_json(data, bot):
"""
Args:
data (str):
data (dict):
bot (telegram.Bot)
Returns:
telegram.Voice:
+2 -2
View File
@@ -36,8 +36,8 @@ class BaseTest(object):
def __init__(self, *args, **kwargs):
super(BaseTest, self).__init__(*args, **kwargs)
bot = telegram.Bot(os.environ.get('TOKEN',
'133505823:AAHZFMHno3mzVLErU5b5jJvaeG--qUyLyG0'))
bot = telegram.Bot(
os.environ.get('TOKEN', '133505823:AAHZFMHno3mzVLErU5b5jJvaeG--qUyLyG0'))
chat_id = os.environ.get('CHAT_ID', '12173560')
self._group_id = os.environ.get('GROUP_ID', '-49740850')
+45 -31
View File
@@ -70,13 +70,14 @@ class AudioTest(BaseTest, unittest.TestCase):
@flaky(3, 1)
@timeout(10)
def test_send_audio_all_args(self):
message = self._bot.sendAudio(self._chat_id,
self.audio_file,
duration=self.duration,
performer=self.performer,
title=self.title,
mime_type=self.mime_type,
file_size=self.file_size)
message = self._bot.sendAudio(
self._chat_id,
self.audio_file,
duration=self.duration,
performer=self.performer,
title=self.title,
mime_type=self.mime_type,
file_size=self.file_size)
audio = message.audio
@@ -91,11 +92,12 @@ class AudioTest(BaseTest, unittest.TestCase):
@flaky(3, 1)
@timeout(10)
def test_send_audio_mp3_file(self):
message = self._bot.sendAudio(chat_id=self._chat_id,
audio=self.audio_file,
duration=self.duration,
performer=self.performer,
title=self.title)
message = self._bot.sendAudio(
chat_id=self._chat_id,
audio=self.audio_file,
duration=self.duration,
performer=self.performer,
title=self.title)
audio = message.audio
@@ -110,12 +112,13 @@ class AudioTest(BaseTest, unittest.TestCase):
@flaky(3, 1)
@timeout(10)
def test_send_audio_mp3_file_custom_filename(self):
message = self._bot.sendAudio(chat_id=self._chat_id,
audio=self.audio_file,
duration=self.duration,
performer=self.performer,
title=self.title,
filename='telegram_custom.mp3')
message = self._bot.sendAudio(
chat_id=self._chat_id,
audio=self.audio_file,
duration=self.duration,
performer=self.performer,
title=self.title,
filename='telegram_custom.mp3')
audio = message.audio
@@ -130,11 +133,12 @@ class AudioTest(BaseTest, unittest.TestCase):
@flaky(3, 1)
@timeout(10)
def test_send_audio_mp3_url_file(self):
message = self._bot.sendAudio(chat_id=self._chat_id,
audio=self.audio_file_url,
duration=self.duration,
performer=self.performer,
title=self.title)
message = self._bot.sendAudio(
chat_id=self._chat_id,
audio=self.audio_file_url,
duration=self.duration,
performer=self.performer,
title=self.title)
audio = message.audio
@@ -149,11 +153,12 @@ class AudioTest(BaseTest, unittest.TestCase):
@flaky(3, 1)
@timeout(10)
def test_send_audio_resend(self):
message = self._bot.sendAudio(chat_id=self._chat_id,
audio=self.audio_file_id,
duration=self.duration,
performer=self.performer,
title=self.title)
message = self._bot.sendAudio(
chat_id=self._chat_id,
audio=self.audio_file_id,
duration=self.duration,
performer=self.performer,
title=self.title)
audio = message.audio
@@ -164,7 +169,7 @@ class AudioTest(BaseTest, unittest.TestCase):
self.assertEqual(audio.mime_type, self.mime_type)
def test_audio_de_json(self):
audio = telegram.Audio.de_json(self.json_dict)
audio = telegram.Audio.de_json(self.json_dict, self._bot)
self.assertEqual(audio.file_id, self.audio_file_id)
self.assertEqual(audio.duration, self.duration)
@@ -174,12 +179,12 @@ class AudioTest(BaseTest, unittest.TestCase):
self.assertEqual(audio.file_size, self.file_size)
def test_audio_to_json(self):
audio = telegram.Audio.de_json(self.json_dict)
audio = telegram.Audio.de_json(self.json_dict, self._bot)
self.assertTrue(self.is_json(audio.to_json()))
def test_audio_to_dict(self):
audio = telegram.Audio.de_json(self.json_dict)
audio = telegram.Audio.de_json(self.json_dict, self._bot)
self.assertTrue(self.is_dict(audio.to_dict()))
self.assertEqual(audio['file_id'], self.audio_file_id)
@@ -225,6 +230,15 @@ class AudioTest(BaseTest, unittest.TestCase):
TypeError,
lambda: self._bot.sendAudio(chat_id=self._chat_id, **json_dict))
@flaky(3, 1)
@timeout(10)
def test_reply_audio(self):
"""Test for Message.reply_audio"""
message = self._bot.sendMessage(self._chat_id, '.')
message = message.reply_audio(self.audio_file)
self.assertNotEqual(message.audio.file_id, '')
if __name__ == '__main__':
unittest.main()
+28 -32
View File
@@ -23,14 +23,10 @@ import io
import re
from datetime import datetime
import sys
import unittest
from flaky import flaky
if sys.version_info[0:2] == (2, 6):
import unittest2 as unittest
else:
import unittest
sys.path.append('.')
import telegram
@@ -51,8 +47,8 @@ class BotTest(BaseTest, unittest.TestCase):
@flaky(3, 1)
@timeout(10)
def testSendMessage(self):
message = self._bot.sendMessage(chat_id=self._chat_id,
text='Моё судно на воздушной подушке полно угрей')
message = self._bot.sendMessage(
chat_id=self._chat_id, text='Моё судно на воздушной подушке полно угрей')
self.assertTrue(self.is_json(message.to_json()))
self.assertEqual(message.text, u'Моё судно на воздушной подушке полно угрей')
@@ -61,9 +57,10 @@ class BotTest(BaseTest, unittest.TestCase):
@flaky(3, 1)
@timeout(10)
def testSilentSendMessage(self):
message = self._bot.sendMessage(chat_id=self._chat_id,
text='Моё судно на воздушной подушке полно угрей',
disable_notification=True)
message = self._bot.sendMessage(
chat_id=self._chat_id,
text='Моё судно на воздушной подушке полно угрей',
disable_notification=True)
self.assertTrue(self.is_json(message.to_json()))
self.assertEqual(message.text, u'Моё судно на воздушной подушке полно угрей')
@@ -81,9 +78,8 @@ class BotTest(BaseTest, unittest.TestCase):
@flaky(3, 1)
@timeout(10)
def testForwardMessage(self):
message = self._bot.forwardMessage(chat_id=self._chat_id,
from_chat_id=self._chat_id,
message_id=2398)
message = self._bot.forwardMessage(
chat_id=self._chat_id, from_chat_id=self._chat_id, message_id=2398)
self.assertTrue(self.is_json(message.to_json()))
self.assertEqual(message.text, 'teste')
@@ -93,9 +89,10 @@ class BotTest(BaseTest, unittest.TestCase):
@flaky(3, 1)
@timeout(10)
def testSendPhoto(self):
message = self._bot.sendPhoto(photo=open('tests/data/telegram.png', 'rb'),
caption='testSendPhoto',
chat_id=self._chat_id)
message = self._bot.sendPhoto(
photo=open('tests/data/telegram.png', 'rb'),
caption='testSendPhoto',
chat_id=self._chat_id)
self.assertTrue(self.is_json(message.to_json()))
self.assertEqual(message.photo[0].file_size, 1451)
@@ -104,10 +101,11 @@ class BotTest(BaseTest, unittest.TestCase):
@flaky(3, 1)
@timeout(10)
def testSilentSendPhoto(self):
message = self._bot.sendPhoto(photo=open('tests/data/telegram.png', 'rb'),
caption='testSendPhoto',
chat_id=self._chat_id,
disable_notification=True)
message = self._bot.sendPhoto(
photo=open('tests/data/telegram.png', 'rb'),
caption='testSendPhoto',
chat_id=self._chat_id,
disable_notification=True)
self.assertTrue(self.is_json(message.to_json()))
self.assertEqual(message.photo[0].file_size, 1451)
@@ -117,8 +115,7 @@ class BotTest(BaseTest, unittest.TestCase):
@timeout(10)
def testResendPhoto(self):
message = self._bot.sendPhoto(
photo='AgADAQAD1y0yGx8j9Qf8f_m3CKeS6Iy95y8ABI1ggfVJ4-UvwJcAAgI',
chat_id=self._chat_id)
photo='AgADAQAD1y0yGx8j9Qf8f_m3CKeS6Iy95y8ABI1ggfVJ4-UvwJcAAgI', chat_id=self._chat_id)
self.assertTrue(self.is_json(message.to_json()))
self.assertEqual(message.photo[0].file_id,
@@ -128,31 +125,28 @@ class BotTest(BaseTest, unittest.TestCase):
@timeout(10)
def testSendJPGURLPhoto(self):
message = self._bot.sendPhoto(
photo='http://dummyimage.com/600x400/000/fff.jpg&text=telegram',
chat_id=self._chat_id)
photo='http://dummyimage.com/600x400/000/fff.jpg&text=telegram', chat_id=self._chat_id)
self.assertTrue(self.is_json(message.to_json()))
self.assertEqual(message.photo[0].file_size, 822)
self.assertEqual(message.photo[0].file_size, 813)
@flaky(3, 1)
@timeout(10)
def testSendPNGURLPhoto(self):
message = self._bot.sendPhoto(
photo='http://dummyimage.com/600x400/000/fff.png&text=telegram',
chat_id=self._chat_id)
photo='http://dummyimage.com/600x400/000/fff.png&text=telegram', chat_id=self._chat_id)
self.assertTrue(self.is_json(message.to_json()))
self.assertEqual(message.photo[0].file_size, 685)
self.assertEqual(message.photo[0].file_size, 670)
@flaky(3, 1)
@timeout(10)
def testSendGIFURLPhoto(self):
message = self._bot.sendPhoto(
photo='http://dummyimage.com/600x400/000/fff.gif&text=telegram',
chat_id=self._chat_id)
photo='http://dummyimage.com/600x400/000/fff.gif&text=telegram', chat_id=self._chat_id)
self.assertTrue(self.is_json(message.to_json()))
self.assertEqual(message.photo[0].file_size, 685)
self.assertEqual(message.photo[0].file_size, 670)
@flaky(3, 1)
@timeout(10)
@@ -204,7 +198,9 @@ class BotTest(BaseTest, unittest.TestCase):
def testInvalidSrvResp(self):
with self.assertRaisesRegexp(telegram.TelegramError, 'Invalid server response'):
# bypass the valid token check
bot = telegram.Bot.__new__(telegram.Bot)
newbot_cls = type(
'NoTokenValidateBot', (telegram.Bot,), dict(_validate_token=lambda x, y: None))
bot = newbot_cls('0xdeadbeef')
bot.base_url = 'https://api.telegram.org/bot{0}'.format('12')
bot.getMe()
+18 -4
View File
@@ -20,6 +20,9 @@
import unittest
import sys
from flaky import flaky
sys.path.append('.')
import telegram
@@ -37,30 +40,41 @@ class ChatTest(BaseTest, unittest.TestCase):
self.json_dict = {'id': self.id, 'title': self.title, 'type': self.type}
def test_group_chat_de_json_empty_json(self):
group_chat = telegram.Chat.de_json({})
group_chat = telegram.Chat.de_json({}, self._bot)
self.assertEqual(group_chat, None)
def test_group_chat_de_json(self):
group_chat = telegram.Chat.de_json(self.json_dict)
group_chat = telegram.Chat.de_json(self.json_dict, self._bot)
self.assertEqual(group_chat.id, self.id)
self.assertEqual(group_chat.title, self.title)
self.assertEqual(group_chat.type, self.type)
def test_group_chat_to_json(self):
group_chat = telegram.Chat.de_json(self.json_dict)
group_chat = telegram.Chat.de_json(self.json_dict, self._bot)
self.assertTrue(self.is_json(group_chat.to_json()))
def test_group_chat_to_dict(self):
group_chat = telegram.Chat.de_json(self.json_dict)
group_chat = telegram.Chat.de_json(self.json_dict, self._bot)
self.assertTrue(self.is_dict(group_chat.to_dict()))
self.assertEqual(group_chat['id'], self.id)
self.assertEqual(group_chat['title'], self.title)
self.assertEqual(group_chat['type'], self.type)
@flaky(3, 1)
def test_send_action(self):
"""Test for Chat.send_action"""
self.json_dict['id'] = self._chat_id
group_chat = telegram.Chat.de_json(self.json_dict, self._bot)
group_chat.bot = self._bot
result = group_chat.send_action(telegram.ChatAction.TYPING)
self.assertTrue(result)
if __name__ == '__main__':
unittest.main()
+4 -8
View File
@@ -20,11 +20,7 @@
ChosenInlineResult"""
import sys
if sys.version_info[0:2] == (2, 6):
import unittest2 as unittest
else:
import unittest
import unittest
sys.path.append('.')
@@ -49,19 +45,19 @@ class ChosenInlineResultTest(BaseTest, unittest.TestCase):
}
def test_choseninlineresult_de_json(self):
result = telegram.ChosenInlineResult.de_json(self.json_dict)
result = telegram.ChosenInlineResult.de_json(self.json_dict, self._bot)
self.assertEqual(result.result_id, self.result_id)
self.assertDictEqual(result.from_user.to_dict(), self.from_user.to_dict())
self.assertEqual(result.query, self.query)
def test_choseninlineresult_to_json(self):
result = telegram.ChosenInlineResult.de_json(self.json_dict)
result = telegram.ChosenInlineResult.de_json(self.json_dict, self._bot)
self.assertTrue(self.is_json(result.to_json()))
def test_choseninlineresult_to_dict(self):
result = telegram.ChosenInlineResult.de_json(self.json_dict).to_dict()
result = telegram.ChosenInlineResult.de_json(self.json_dict, self._bot).to_dict()
self.assertTrue(self.is_dict(result))
self.assertEqual(result['result_id'], self.result_id)
+13 -15
View File
@@ -17,14 +17,10 @@
"""Test the Telegram constants."""
import sys
import unittest
from flaky import flaky
if sys.version_info[0:2] == (2, 6):
import unittest2 as unittest
else:
import unittest
sys.path.append('.')
import telegram
@@ -37,12 +33,12 @@ class ConstantsTest(BaseTest, unittest.TestCase):
@flaky(3, 1)
@timeout(10)
def testMaxMessageLength(self):
self._bot.sendMessage(chat_id=self._chat_id,
text='a' * telegram.constants.MAX_MESSAGE_LENGTH)
self._bot.sendMessage(
chat_id=self._chat_id, text='a' * telegram.constants.MAX_MESSAGE_LENGTH)
try:
self._bot.sendMessage(chat_id=self._chat_id,
text='a' * (telegram.constants.MAX_MESSAGE_LENGTH + 1))
self._bot.sendMessage(
chat_id=self._chat_id, text='a' * (telegram.constants.MAX_MESSAGE_LENGTH + 1))
except BadRequest as e:
err = str(e)
@@ -51,14 +47,16 @@ class ConstantsTest(BaseTest, unittest.TestCase):
@flaky(3, 1)
@timeout(10)
def testMaxCaptionLength(self):
self._bot.sendPhoto(photo=open('tests/data/telegram.png', 'rb'),
caption='a' * telegram.constants.MAX_CAPTION_LENGTH,
chat_id=self._chat_id)
self._bot.sendPhoto(
photo=open('tests/data/telegram.png', 'rb'),
caption='a' * telegram.constants.MAX_CAPTION_LENGTH,
chat_id=self._chat_id)
try:
self._bot.sendPhoto(photo=open('tests/data/telegram.png', 'rb'),
caption='a' * (telegram.constants.MAX_CAPTION_LENGTH + 1),
chat_id=self._chat_id)
self._bot.sendPhoto(
photo=open('tests/data/telegram.png', 'rb'),
caption='a' * (telegram.constants.MAX_CAPTION_LENGTH + 1),
chat_id=self._chat_id)
except BadRequest as e:
err = str(e)
+17 -3
View File
@@ -20,6 +20,9 @@
import unittest
import sys
from flaky import flaky
sys.path.append('.')
import telegram
@@ -43,7 +46,7 @@ class ContactTest(BaseTest, unittest.TestCase):
}
def test_contact_de_json(self):
contact = telegram.Contact.de_json(self.json_dict)
contact = telegram.Contact.de_json(self.json_dict, self._bot)
self.assertEqual(contact.phone_number, self.phone_number)
self.assertEqual(contact.first_name, self.first_name)
@@ -51,12 +54,12 @@ class ContactTest(BaseTest, unittest.TestCase):
self.assertEqual(contact.user_id, self.user_id)
def test_contact_to_json(self):
contact = telegram.Contact.de_json(self.json_dict)
contact = telegram.Contact.de_json(self.json_dict, self._bot)
self.assertTrue(self.is_json(contact.to_json()))
def test_contact_to_dict(self):
contact = telegram.Contact.de_json(self.json_dict)
contact = telegram.Contact.de_json(self.json_dict, self._bot)
self.assertTrue(self.is_dict(contact.to_dict()))
self.assertEqual(contact['phone_number'], self.phone_number)
@@ -65,5 +68,16 @@ class ContactTest(BaseTest, unittest.TestCase):
self.assertEqual(contact['user_id'], self.user_id)
''' Commented out, because it would cause "Too Many Requests (429)" errors.
@flaky(3, 1)
def test_reply_contact(self):
"""Test for Message.reply_contact"""
message = self._bot.sendMessage(self._chat_id, '.')
message = message.reply_contact(self.phone_number, self.first_name)
self.assertEqual(message.contact.phone_number, self.phone_number)
self.assertEqual(message.contact.first_name, self.first_name)
'''
if __name__ == '__main__':
unittest.main()
+60 -13
View File
@@ -22,13 +22,9 @@ This module contains a object that represents Tests for ConversationHandler
"""
import logging
import sys
import unittest
from time import sleep
if sys.version_info[0:2] == (2, 6):
import unittest2 as unittest
else:
import unittest
try:
# python2
from urllib2 import urlopen, Request, HTTPError
@@ -40,8 +36,7 @@ except ImportError:
sys.path.append('.')
from telegram import Update, Message, TelegramError, User, Chat, Bot
from telegram.utils.request import stop_con_pool
from telegram.ext import *
from telegram.ext import Updater, ConversationHandler, CommandHandler
from tests.base import BaseTest
from tests.test_updater import MockBot
@@ -65,10 +60,10 @@ class ConversationHandlerTest(BaseTest, unittest.TestCase):
# At first we're thirsty. Then we brew coffee, we drink it
# and then we can start coding!
END, THIRSTY, BREWING, DRINKING, CODING = range(-1, 4)
_updater = None
# Test related
def setUp(self):
self.updater = None
self.current_state = dict()
self.entry_points = [CommandHandler('start', self.start)]
self.states = {self.THIRSTY: [CommandHandler('brew', self.brew),
@@ -82,14 +77,22 @@ class ConversationHandlerTest(BaseTest, unittest.TestCase):
self.fallbacks = [CommandHandler('eat', self.start)]
def _setup_updater(self, *args, **kwargs):
stop_con_pool()
bot = MockBot(*args, **kwargs)
self.updater = Updater(workers=2, bot=bot)
def tearDown(self):
if self.updater is not None:
self.updater.stop()
stop_con_pool()
@property
def updater(self):
return self._updater
@updater.setter
def updater(self, val):
if self._updater:
self._updater.stop()
self._updater = val
def reset(self):
self.current_state = dict()
@@ -106,6 +109,9 @@ class ConversationHandlerTest(BaseTest, unittest.TestCase):
def start(self, bot, update):
return self._set_state(update, self.THIRSTY)
def start_end(self, bot, update):
return self._set_state(update, self.END)
def brew(self, bot, update):
return self._set_state(update, self.BREWING)
@@ -122,9 +128,8 @@ class ConversationHandlerTest(BaseTest, unittest.TestCase):
user = User(first_name="Misses Test", id=123)
second_user = User(first_name="Mister Test", id=124)
handler = ConversationHandler(entry_points=self.entry_points,
states=self.states,
fallbacks=self.fallbacks)
handler = ConversationHandler(
entry_points=self.entry_points, states=self.states, fallbacks=self.fallbacks)
d.add_handler(handler)
queue = self.updater.start_polling(0.01)
@@ -159,6 +164,48 @@ class ConversationHandlerTest(BaseTest, unittest.TestCase):
sleep(.1)
self.assertRaises(KeyError, self._get_state, user_id=second_user.id)
def test_endOnFirstMessage(self):
self._setup_updater('', messages=0)
d = self.updater.dispatcher
user = User(first_name="Misses Test", id=123)
handler = ConversationHandler(
entry_points=[CommandHandler('start', self.start_end)], states={}, fallbacks=[])
d.add_handler(handler)
queue = self.updater.start_polling(0.01)
# User starts the state machine and immediately ends it.
message = Message(0, user, None, None, text="/start")
queue.put(Update(update_id=0, message=message))
sleep(.1)
self.assertEquals(len(handler.conversations), 0)
def test_endOnFirstMessageAsync(self):
self._setup_updater('', messages=0)
d = self.updater.dispatcher
user = User(first_name="Misses Test", id=123)
start_end_async = (lambda bot, update: d.run_async(self.start_end, bot, update))
handler = ConversationHandler(
entry_points=[CommandHandler('start', start_end_async)], states={}, fallbacks=[])
d.add_handler(handler)
queue = self.updater.start_polling(0.01)
# User starts the state machine with an async function that immediately ends the
# conversation. Async results are resolved when the users state is queried next time.
message = Message(0, user, None, None, text="/start")
queue.put(Update(update_id=0, message=message))
sleep(.1)
# Assert that the Promise has been accepted as the new state
self.assertEquals(len(handler.conversations), 1)
message = Message(0, user, None, None, text="resolve promise pls")
queue.put(Update(update_id=0, message=message))
sleep(.1)
# Assert that the Promise has been resolved and the conversation ended.
self.assertEquals(len(handler.conversations), 0)
if __name__ == '__main__':
unittest.main()
+14 -6
View File
@@ -70,9 +70,8 @@ class DocumentTest(BaseTest, unittest.TestCase):
@flaky(3, 1)
@timeout(10)
def test_send_document_png_file_with_custom_file_name(self):
message = self._bot.sendDocument(self._chat_id,
self.document_file,
filename='telegram_custom.png')
message = self._bot.sendDocument(
self._chat_id, self.document_file, filename='telegram_custom.png')
document = message.document
@@ -110,7 +109,7 @@ class DocumentTest(BaseTest, unittest.TestCase):
self.assertEqual(document.mime_type, self.mime_type)
def test_document_de_json(self):
document = telegram.Document.de_json(self.json_dict)
document = telegram.Document.de_json(self.json_dict, self._bot)
self.assertEqual(document.file_id, self.document_file_id)
self.assertTrue(isinstance(document.thumb, telegram.PhotoSize))
@@ -119,12 +118,12 @@ class DocumentTest(BaseTest, unittest.TestCase):
self.assertEqual(document.file_size, self.file_size)
def test_document_to_json(self):
document = telegram.Document.de_json(self.json_dict)
document = telegram.Document.de_json(self.json_dict, self._bot)
self.assertTrue(self.is_json(document.to_json()))
def test_document_to_dict(self):
document = telegram.Document.de_json(self.json_dict)
document = telegram.Document.de_json(self.json_dict, self._bot)
self.assertTrue(self.is_dict(document.to_dict()))
self.assertEqual(document['file_id'], self.document_file_id)
@@ -168,6 +167,15 @@ class DocumentTest(BaseTest, unittest.TestCase):
lambda: self._bot.sendDocument(chat_id=self._chat_id,
**json_dict))
@flaky(3, 1)
@timeout(10)
def test_reply_document(self):
"""Test for Message.reply_document"""
message = self._bot.sendMessage(self._chat_id, '.')
message = message.reply_document(self.document_file)
self.assertNotEqual(message.document.file_id, '')
if __name__ == '__main__':
unittest.main()
-40
View File
@@ -1,40 +0,0 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains a object that represents Tests for Telegram Emoji"""
import unittest
import sys
sys.path.append('.')
import telegram
from telegram.emoji import Emoji
from tests.base import BaseTest
class EmojiTest(BaseTest, unittest.TestCase):
"""This object represents Tests for Telegram Emoji."""
def test_emoji(self):
for attr in dir(Emoji):
if attr[0] != '_': # TODO: dirty way to filter out functions
self.assertTrue(type(getattr(Emoji, attr)) is str)
if __name__ == '__main__':
unittest.main()
+3 -3
View File
@@ -101,19 +101,19 @@ class FileTest(BaseTest, unittest.TestCase):
self.assertTrue(os.path.isfile('telegram.ogg'))
def test_file_de_json(self):
newFile = telegram.File.de_json(self.json_dict)
newFile = telegram.File.de_json(self.json_dict, self._bot)
self.assertEqual(newFile.file_id, self.json_dict['file_id'])
self.assertEqual(newFile.file_path, self.json_dict['file_path'])
self.assertEqual(newFile.file_size, self.json_dict['file_size'])
def test_file_to_json(self):
newFile = telegram.File.de_json(self.json_dict)
newFile = telegram.File.de_json(self.json_dict, self._bot)
self.assertTrue(self.is_json(newFile.to_json()))
def test_file_to_dict(self):
newFile = telegram.File.de_json(self.json_dict)
newFile = telegram.File.de_json(self.json_dict, self._bot)
self.assertTrue(self.is_dict(newFile.to_dict()))
self.assertEqual(newFile['file_id'], self.json_dict['file_id'])
+17 -1
View File
@@ -23,10 +23,11 @@ This module contains a object that represents Tests for MessageHandler.Filters
import sys
import unittest
from datetime import datetime
import functools
sys.path.append('.')
from telegram import Message, User, Chat
from telegram import Message, User, Chat, MessageEntity
from telegram.ext import Filters
from tests.base import BaseTest
@@ -150,6 +151,21 @@ class FiltersTest(BaseTest, unittest.TestCase):
self.assertTrue(Filters.status_update(self.message))
self.message.pinned_message = None
def test_entities_filter(self):
e = functools.partial(MessageEntity, offset=0, length=0)
self.message.entities = [e(MessageEntity.MENTION)]
self.assertTrue(Filters.entity(MessageEntity.MENTION)(self.message))
self.message.entities = []
self.assertFalse(Filters.entity(MessageEntity.MENTION)(self.message))
self.message.entities = [e(MessageEntity.BOLD)]
self.assertFalse(Filters.entity(MessageEntity.MENTION)(self.message))
self.message.entities = [e(MessageEntity.BOLD), e(MessageEntity.MENTION)]
self.assertTrue(Filters.entity(MessageEntity.MENTION)(self.message))
if __name__ == '__main__':
unittest.main()

Some files were not shown because too many files have changed in this diff Show More