mirror of
https://github.com/python-telegram-bot/python-telegram-bot.git
synced 2026-06-19 15:45:13 +00:00
Compare commits
99 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 834bf192b9 | |||
| d5486433e5 | |||
| c9ec436d68 | |||
| ad3eec2af8 | |||
| e3fe1d2632 | |||
| 52bd3daa39 | |||
| b4e8209f2c | |||
| f5f95ef8c9 | |||
| 04a871aff5 | |||
| 81a755a7d8 | |||
| 6016aca0ba | |||
| 7c908db901 | |||
| d192b385ea | |||
| f0b2028e3f | |||
| f443003408 | |||
| afc36a235b | |||
| b76337de87 | |||
| 6afee6e0bd | |||
| 27e57bbf58 | |||
| 4990d664bb | |||
| b3e42c3e20 | |||
| c2cce40299 | |||
| a2ed7b26f1 | |||
| 89a3dc8372 | |||
| 9fd298a393 | |||
| ecbc268781 | |||
| c7c21c94ea | |||
| 57efde5e0f | |||
| 31073101a3 | |||
| 1e0ebe89f3 | |||
| 35872d7a8b | |||
| f65b6911ea | |||
| 02af1ea803 | |||
| c4a8ee5175 | |||
| e0539d5992 | |||
| 738e3213a7 | |||
| b41f7e3e79 | |||
| caf72ca490 | |||
| 7635bc0eec | |||
| 703bece155 | |||
| 949f4a4fbd | |||
| 05522e4321 | |||
| 4f101a79bb | |||
| 5b91194cc7 | |||
| 494a7ec1e4 | |||
| fc05d3a626 | |||
| bc77c845ea | |||
| a814e9de6b | |||
| d37b6d6735 | |||
| e479c7f25e | |||
| a30411c9fa | |||
| 881d1d0e25 | |||
| cb6ddfded5 | |||
| bda0244ed8 | |||
| 9338f93d24 | |||
| e10fa66286 | |||
| deb9de0ba0 | |||
| 94fd6851ab | |||
| 897f9615f0 | |||
| 86676d59f1 | |||
| f0b91ecf46 | |||
| bbbc622517 | |||
| 1f5601dae2 | |||
| 3608c2bbe5 | |||
| c28763c5be | |||
| dd8b6219b9 | |||
| 78f9bdcac9 | |||
| da95341d5b | |||
| 98be6abc11 | |||
| b08d41d0ff | |||
| de2d732135 | |||
| 1ff348adbb | |||
| 6b457bada5 | |||
| 74283bd414 | |||
| 41f6591ac6 | |||
| 6d08e1bc7f | |||
| 073d7949dc | |||
| dd91ce1f39 | |||
| 57759d8e6d | |||
| 574fc8cddf | |||
| b040568b07 | |||
| 3076dfc086 | |||
| f8a9722573 | |||
| 527daaf93d | |||
| 986add59ab | |||
| bc62a1813a | |||
| d40f0a8309 | |||
| 783f9c375c | |||
| 406303d6bb | |||
| 2534e0df9b | |||
| e7f4a07b7a | |||
| bb165b6acf | |||
| 20067ff178 | |||
| 41daccce07 | |||
| 786216305c | |||
| b3142d2974 | |||
| 8278779a23 | |||
| 3aedd78e29 | |||
| 6b90ac9f1c |
+65
-50
@@ -1,34 +1,41 @@
|
||||
How To Contribute
|
||||
=================
|
||||
===================
|
||||
|
||||
Every open source project lives from the generous help by contributors that sacrifice their time and ``python-telegram-bot`` is no different. To make participation as pleasant as possible, this project adheres to the `Code of Conduct`_ by the Python Software Foundation.
|
||||
|
||||
Setting things up
|
||||
-----------------
|
||||
-------------------
|
||||
|
||||
1. Fork the ``python-telegram-bot`` repository to your GitHub account.
|
||||
|
||||
2. Clone your forked repository of ``python-telegram-bot`` to your computer:
|
||||
|
||||
``$ git clone https://github.com/<your username>/python-telegram-bot``
|
||||
.. code-block:: bash
|
||||
|
||||
``$ cd python-telegram-bot``
|
||||
$ git clone https://github.com/<your username>/python-telegram-bot
|
||||
$ cd python-telegram-bot
|
||||
|
||||
3. Add a track to the original repository:
|
||||
|
||||
``$ git remote add upstream https://github.com/python-telegram-bot/python-telegram-bot``
|
||||
.. code-block:: bash
|
||||
|
||||
$ git remote add upstream https://github.com/python-telegram-bot/python-telegram-bot
|
||||
|
||||
4. Install dependencies:
|
||||
|
||||
``$ pip install -r requirements.txt -r requirements-dev.txt``
|
||||
.. code-block:: bash
|
||||
|
||||
$ sudo pip install -r requirements.txt -r requirements-dev.txt
|
||||
|
||||
|
||||
5. Install pre-commit hooks:
|
||||
|
||||
``$ pre-commit install``
|
||||
.. code-block:: bash
|
||||
|
||||
$ pre-commit install
|
||||
|
||||
Finding something to do
|
||||
-----------------------
|
||||
###################
|
||||
|
||||
If you already know what you'd like to work on, you can skip this section.
|
||||
|
||||
@@ -37,7 +44,7 @@ If you have an idea for something to do, first check if it's already been filed
|
||||
Another great way to start contributing is by writing tests. Tests are really important because they help prevent developers from accidentally breaking existing code, allowing them to build cool things faster. If you're interested in helping out, let the development team know by posting to the `developers' mailing list`_, and we'll help you get started.
|
||||
|
||||
Instructions for making a code change
|
||||
-------------------------------------
|
||||
####################
|
||||
|
||||
The central development branch is ``master``, which should be clean and ready for release at any time. In general, all changes should be done as feature branches based off of ``master``.
|
||||
|
||||
@@ -47,13 +54,12 @@ Here's how to make a one-off code change.
|
||||
|
||||
2. **Create a new branch with this name, starting from** ``master``. In other words, run:
|
||||
|
||||
``$ git fetch upstream``
|
||||
.. code-block:: bash
|
||||
|
||||
``$ git checkout master``
|
||||
|
||||
``$ git merge upstream/master``
|
||||
|
||||
``$ git checkout -b your-branch-name``
|
||||
$ git fetch upstream
|
||||
$ git checkout master
|
||||
$ git merge upstream/master
|
||||
$ git checkout -b your-branch-name
|
||||
|
||||
3. **Make a commit to your feature branch**. Each commit should be self-contained and have a descriptive commit message that helps other developers understand why the changes were made.
|
||||
|
||||
@@ -75,19 +81,27 @@ Here's how to make a one-off code change.
|
||||
|
||||
- Before making a commit ensure that all automated tests still pass:
|
||||
|
||||
``$ make test``
|
||||
.. code-block::
|
||||
|
||||
$ make test
|
||||
|
||||
- To actually make the commit (this will trigger tests for yapf, lint and pep8 automatically):
|
||||
|
||||
``$ git add your-file-changed.py``
|
||||
.. code-block:: bash
|
||||
|
||||
- yapf may change code formatting, make sure to re-add them to your commit.
|
||||
$ git add your-file-changed.py
|
||||
|
||||
``$ git commit -a -m "your-commit-message-here"``
|
||||
- yapf may change code formatting, make sure to re-add them to your commit.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ git commit -a -m "your-commit-message-here"
|
||||
|
||||
- Finally, push it to your GitHub fork, run:
|
||||
|
||||
``$ git push origin your-branch-name``
|
||||
.. code-block:: bash
|
||||
|
||||
$ git push origin your-branch-name
|
||||
|
||||
4. **When your feature is ready to merge, create a pull request.**
|
||||
|
||||
@@ -107,66 +121,67 @@ Here's how to make a one-off code change.
|
||||
|
||||
- Resolve any merge conflicts that arise. To resolve conflicts between 'your-branch-name' (in your fork) and 'master' (in the ``python-telegram-bot`` repository), run:
|
||||
|
||||
``$ git checkout your-branch-name``
|
||||
.. code-block:: bash
|
||||
|
||||
``$ git fetch upstream``
|
||||
|
||||
``$ git merge upstream/master``
|
||||
|
||||
``$ ...[fix the conflicts]...``
|
||||
|
||||
``$ ...[make sure the tests pass before committing]...``
|
||||
|
||||
``$ git commit -a``
|
||||
|
||||
``$ git push origin your-branch-name``
|
||||
$ git checkout your-branch-name
|
||||
$ git fetch upstream
|
||||
$ git merge upstream/master
|
||||
$ ...[fix the conflicts]...
|
||||
$ ...[make sure the tests pass before committing]...
|
||||
$ git commit -a
|
||||
$ git push origin your-branch-name
|
||||
|
||||
- At the end, the reviewer will merge the pull request.
|
||||
|
||||
6. **Tidy up!** Delete the feature branch from both your local clone and the GitHub repository:
|
||||
|
||||
``$ git branch -D your-branch-name``
|
||||
.. code-block:: bash
|
||||
|
||||
``$ git push origin --delete your-branch-name``
|
||||
$ git branch -D your-branch-name
|
||||
$ git push origin --delete your-branch-name
|
||||
|
||||
7. **Celebrate.** Congratulations, you have contributed to ``python-telegram-bot``!
|
||||
|
||||
Style commandments
|
||||
==================
|
||||
---------------------
|
||||
|
||||
Specific commandments
|
||||
---------------------
|
||||
#####################
|
||||
|
||||
- Avoid using "double quotes" where you can reasonably use 'single quotes'.
|
||||
|
||||
AssertEqual argument order
|
||||
--------------------------
|
||||
######################
|
||||
|
||||
assertEqual method's arguments should be in ('actual', 'expected') order.
|
||||
- assertEqual method's arguments should be in ('actual', 'expected') order.
|
||||
|
||||
Properly calling callables
|
||||
--------------------------
|
||||
#######################
|
||||
|
||||
Methods, functions and classes can specify optional parameters (with default
|
||||
values) using Python's keyword arg syntax. When providing a value to such a
|
||||
callable we prefer that the call also uses keyword arg syntax. For example::
|
||||
callable we prefer that the call also uses keyword arg syntax. For example:
|
||||
|
||||
# GOOD
|
||||
f(0, optional=True)
|
||||
.. code-block:: python
|
||||
|
||||
# BAD
|
||||
f(0, True)
|
||||
# GOOD
|
||||
f(0, optional=True)
|
||||
|
||||
# BAD
|
||||
f(0, True)
|
||||
|
||||
This gives us the flexibility to re-order arguments and more importantly
|
||||
to add new required arguments. It's also more explicit and easier to read.
|
||||
|
||||
Properly defining optional arguments
|
||||
------------------------------------
|
||||
########################
|
||||
|
||||
It's always good to not initialize optional arguments at class creation,
|
||||
instead use ``**kwargs`` to get them. It's well known Telegram API can
|
||||
change without notice, in that case if a new argument is added it won't
|
||||
break the API classes. For example::
|
||||
It's always good to not initialize optional arguments at class creation,
|
||||
instead use ``**kwargs`` to get them. It's well known Telegram API can
|
||||
change without notice, in that case if a new argument is added it won't
|
||||
break the API classes. For example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# GOOD
|
||||
def __init__(self, id, name, **kwargs):
|
||||
@@ -183,4 +198,4 @@ break the API classes. For example::
|
||||
.. _`PEP 8 Style Guide`: https://www.python.org/dev/peps/pep-0008/
|
||||
.. _`Google Python Style Guide`: https://google-styleguide.googlecode.com/svn/trunk/pyguide.html
|
||||
.. _`Google Python Style Docstrings`: http://sphinx-doc.org/latest/ext/example_google.html
|
||||
.. _AUTHORS.rst: https://github.com/python-telegram-bot/python-telegram-bot/blob/master/AUTHORS.rst
|
||||
.. _AUTHORS.rst: ../AUTHORS.rst
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
- id: yapf
|
||||
files: ^(telegram|tests)/.*\.py$
|
||||
- repo: git://github.com/pre-commit/pre-commit-hooks
|
||||
sha: adbb569fe9a64ad9bce3b53a77f1bc39ef31f682
|
||||
sha: 3fa02652357ff0dbb42b5bc78c673b7bc105fcf3
|
||||
hooks:
|
||||
- id: flake8
|
||||
files: ^telegram/.*\.py$
|
||||
|
||||
@@ -25,6 +25,7 @@ The following wonderful people contributed directly or indirectly to this projec
|
||||
- `Rahiel Kasim <https://github.com/rahiel>`_
|
||||
- `Shelomentsev D <https://github.com/shelomentsevd>`_
|
||||
- `sooyhwang <https://github.com/sooyhwang>`_
|
||||
- `Valentijn <https://github.com/Faalentijn>`_
|
||||
- `wjt <https://github.com/wjt>`_
|
||||
|
||||
Please add yourself here alphabetically when you submit your first pull request.
|
||||
|
||||
+51
-1
@@ -1,3 +1,53 @@
|
||||
=======
|
||||
Changes
|
||||
=======
|
||||
|
||||
**2016-07-15**
|
||||
|
||||
*Released 5.0*
|
||||
|
||||
- Rework ``JobQueue``
|
||||
- Introduce ``ConversationHandler``
|
||||
|
||||
**2016-07-12**
|
||||
|
||||
*Released 4.3.4*
|
||||
|
||||
- Fix proxy support with ``urllib3`` when proxy requires auth
|
||||
|
||||
**2016-07-08**
|
||||
|
||||
*Released 4.3.3*
|
||||
|
||||
- Fix proxy support with ``urllib3``
|
||||
|
||||
**2016-07-04**
|
||||
|
||||
*Released 4.3.2*
|
||||
|
||||
- Fix: Use ``timeout`` parameter in all API methods
|
||||
|
||||
**2016-06-29**
|
||||
|
||||
*Released 4.3.1*
|
||||
|
||||
- Update wrong requirement: ``urllib3>=1.10``
|
||||
|
||||
**2016-06-28**
|
||||
|
||||
*Released 4.3*
|
||||
|
||||
- Use ``urllib3.PoolManager`` for connection re-use
|
||||
- Rewrite ``run_async`` decorator to re-use threads
|
||||
- New requirements: ``urllib3`` and ``certifi``
|
||||
|
||||
**2016-06-10**
|
||||
|
||||
*Released 4.2.1*
|
||||
|
||||
- Fix ``CallbackQuery.to_dict()`` bug (thanks to @jlmadurga)
|
||||
- Fix ``editMessageText`` exception when receiving a ``CallbackQuery``
|
||||
|
||||
**2016-05-28**
|
||||
|
||||
*Released 4.2*
|
||||
@@ -45,7 +95,7 @@
|
||||
|
||||
- Implement Bot API 2.0
|
||||
- Almost complete recode of ``Dispatcher``
|
||||
- Please read the `Transistion Guide to 4.0 <https://github.com/python-telegram-bot/python-telegram-bot/wiki/Transistion-guide-to-Version-4.0>`_
|
||||
- Please read the `Transition Guide to 4.0 <https://github.com/python-telegram-bot/python-telegram-bot/wiki/Transition-guide-to-Version-4.0>`_
|
||||
- **Changes from 4.0rc1**
|
||||
- The syntax of filters for ``MessageHandler`` (upper/lower cases)
|
||||
- Handler groups are now identified by ``int`` only, and ordered
|
||||
|
||||
+33
-39
@@ -67,9 +67,9 @@ Table of contents
|
||||
|
||||
- `License`_
|
||||
|
||||
===============
|
||||
_`Introduction`
|
||||
===============
|
||||
============
|
||||
Introduction
|
||||
============
|
||||
|
||||
This library provides a pure Python interface for the
|
||||
`Telegram Bot API <https://core.telegram.org/bots/api>`_.
|
||||
@@ -81,15 +81,15 @@ In addition to the pure API implementation, this library features a number of hi
|
||||
make the development of bots easy and straightforward. These classes are contained in the
|
||||
``telegram.ext`` submodule.
|
||||
|
||||
=======================
|
||||
_`Telegram API support`
|
||||
=======================
|
||||
====================
|
||||
Telegram API support
|
||||
====================
|
||||
|
||||
As of **28. May 2016**, all types and methods of the Telegram Bot API are supported.
|
||||
|
||||
=============
|
||||
_`Installing`
|
||||
=============
|
||||
==========
|
||||
Installing
|
||||
==========
|
||||
|
||||
You can install or upgrade python-telegram-bot with:
|
||||
|
||||
@@ -97,9 +97,9 @@ You can install or upgrade python-telegram-bot with:
|
||||
|
||||
$ pip install python-telegram-bot --upgrade
|
||||
|
||||
==================
|
||||
_`Getting started`
|
||||
==================
|
||||
===============
|
||||
Getting started
|
||||
===============
|
||||
|
||||
Our Wiki contains a lot of resources to get you started with ``python-telegram-bot``:
|
||||
|
||||
@@ -111,9 +111,9 @@ Other references:
|
||||
- `Telegram API documentation <https://core.telegram.org/bots/api>`_
|
||||
- `python-telegram-bot documentation <https://pythonhosted.org/python-telegram-bot/>`_
|
||||
|
||||
----------------------
|
||||
_`Learning by example`
|
||||
----------------------
|
||||
-------------------
|
||||
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
|
||||
@@ -129,19 +129,13 @@ code and building on top of it.
|
||||
|
||||
- `timerbot <https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/timerbot.py>`_ uses the ``JobQueue`` to send timed messages.
|
||||
|
||||
- `clibot <https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/clibot.py>`_ has a command line interface.
|
||||
|
||||
Examples using only the pure API:
|
||||
|
||||
- `echobot <https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/legacy/echobot.py>`_ replies back messages.
|
||||
|
||||
- `roboed <https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/legacy/roboed.py>`_ talks to `Robô Ed <http://www.ed.conpet.gov.br/br/converse.php>`_.
|
||||
- `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.
|
||||
|
||||
----------
|
||||
_`Logging`
|
||||
----------
|
||||
-------
|
||||
Logging
|
||||
-------
|
||||
|
||||
This library uses the ``logging`` module. To set up logging to standard output, put:
|
||||
|
||||
@@ -167,35 +161,35 @@ If you want DEBUG logs instead:
|
||||
logger.setLevel(logging.DEBUG)
|
||||
|
||||
|
||||
================
|
||||
_`Documentation`
|
||||
================
|
||||
=============
|
||||
Documentation
|
||||
=============
|
||||
|
||||
``python-telegram-bot``'s documentation lives at `pythonhosted.org <https://pythonhosted.org/python-telegram-bot/>`_.
|
||||
|
||||
===============
|
||||
_`Getting help`
|
||||
===============
|
||||
============
|
||||
Getting help
|
||||
============
|
||||
|
||||
You can get help in several ways:
|
||||
|
||||
1. We have a vibrant community of developers helping each other in our `Telegram group <https://telegram.me/pythontelegrambotgroup>`_. Join us!
|
||||
|
||||
2. Our `Wiki <https://github.com/python-telegram-bot/python-telegram-bot/wiki/>`_ offers a growing amount of resources.
|
||||
2. Our `Wiki pages <https://github.com/python-telegram-bot/python-telegram-bot/wiki/>`_ offer a growing amount of resources.
|
||||
|
||||
3. You can ask for help on Stack Overflow using the `python-telegram-bot tag <https://stackoverflow.com/questions/tagged/python-telegram-bot>`_.
|
||||
|
||||
4. As last resort, the developers are ready to help you with `serious issues <https://github.com/python-telegram-bot/python-telegram-bot/issues/new>`_.
|
||||
|
||||
|
||||
===============
|
||||
_`Contributing`
|
||||
===============
|
||||
============
|
||||
Contributing
|
||||
============
|
||||
|
||||
Contributions of all sizes are welcome. Please review our `contribution guidelines <https://github.com/python-telegram-bot/python-telegram-bot/blob/master/.github/CONTRIBUTING.rst>`_ to get started. You can also help by `reporting bugs <https://github.com/python-telegram-bot/python-telegram-bot/issues/new>`_.
|
||||
|
||||
==========
|
||||
_`License`
|
||||
==========
|
||||
=======
|
||||
License
|
||||
=======
|
||||
|
||||
You may copy, distribute and modify the software provided that modifications are described and licensed for free under `LGPL-3 <https://www.gnu.org/licenses/lgpl-3.0.html>`_. Derivatives works (including modifications or anything statically linked to the library) can only be redistributed under `LGPL-3 <https://www.gnu.org/licenses/lgpl-3.0.html>`_, but applications that use the library don't have to be.
|
||||
You may copy, distribute and modify the software provided that modifications are described and licensed for free under `LGPL-3 <https://www.gnu.org/licenses/lgpl-3.0.html>`_. Derivatives works (including modifications or anything statically linked to the library) can only be redistributed under LGPL-3, but applications that use the library don't have to be.
|
||||
|
||||
+1
-1
@@ -189,4 +189,4 @@ xml:
|
||||
pseudoxml:
|
||||
$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
|
||||
@echo
|
||||
@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
|
||||
@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
|
||||
@@ -7,6 +7,7 @@ Welcome to Python Telegram Bot's documentation!
|
||||
===============================================
|
||||
|
||||
Contents:
|
||||
telegram
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
telegram.constants module
|
||||
=========================
|
||||
|
||||
.. automodule:: telegram.constants
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
@@ -1,7 +1,7 @@
|
||||
telegram.ext.handler module
|
||||
===========================
|
||||
telegram.ext.callbackqueryhandler module
|
||||
========================================
|
||||
|
||||
.. automodule:: telegram.ext.handler
|
||||
.. automodule:: telegram.ext.callbackqueryhandler
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
telegram.ext.conversationhandler module
|
||||
=======================================
|
||||
|
||||
.. automodule:: telegram.ext.conversationhandler
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
@@ -11,6 +11,7 @@ Submodules
|
||||
telegram.ext.jobqueue
|
||||
telegram.ext.handler
|
||||
telegram.ext.choseninlineresulthandler
|
||||
telegram.ext.conversationhandler
|
||||
telegram.ext.commandhandler
|
||||
telegram.ext.inlinequeryhandler
|
||||
telegram.ext.messagehandler
|
||||
|
||||
@@ -1,164 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Example Bot to show some of the functionality of the library
|
||||
# This program is dedicated to the public domain under the CC0 license.
|
||||
"""
|
||||
This Bot uses the Updater class to handle the bot.
|
||||
|
||||
First, a few handler functions are defined. Then, those functions are passed to
|
||||
the Dispatcher and registered at their respective places.
|
||||
Then, the bot is started and the CLI-Loop is entered, where all text inputs are
|
||||
inserted into the update queue for the bot to handle.
|
||||
|
||||
Usage:
|
||||
Repeats messages with a delay.
|
||||
Reply to last chat from the command line by typing "/reply <text>"
|
||||
Type 'stop' on the command line to stop the bot.
|
||||
"""
|
||||
|
||||
from telegram.ext import Updater, StringCommandHandler, StringRegexHandler, \
|
||||
MessageHandler, CommandHandler, RegexHandler, Filters
|
||||
from telegram.ext.dispatcher import run_async
|
||||
from time import sleep
|
||||
import logging
|
||||
|
||||
from future.builtins import input
|
||||
|
||||
# Enable Logging
|
||||
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
level=logging.INFO)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# We use this var to save the last chat id, so we can reply to it
|
||||
last_chat_id = 0
|
||||
|
||||
|
||||
# Define a few (command) handler callback functions. These usually take the
|
||||
# two arguments bot and update. Error handlers also receive the raised
|
||||
# TelegramError object in error.
|
||||
def start(bot, update):
|
||||
""" Answer in Telegram """
|
||||
bot.sendMessage(update.message.chat_id, text='Hi!')
|
||||
|
||||
|
||||
def help(bot, update):
|
||||
""" Answer in Telegram """
|
||||
bot.sendMessage(update.message.chat_id, text='Help!')
|
||||
|
||||
|
||||
def any_message(bot, update):
|
||||
""" Print to console """
|
||||
|
||||
# Save last chat_id to use in reply handler
|
||||
global last_chat_id
|
||||
last_chat_id = update.message.chat_id
|
||||
|
||||
logger.info("New message\nFrom: %s\nchat_id: %d\nText: %s" %
|
||||
(update.message.from_user, update.message.chat_id, update.message.text))
|
||||
|
||||
|
||||
@run_async
|
||||
def message(bot, update):
|
||||
"""
|
||||
Example for an asynchronous handler. It's not guaranteed that replies will
|
||||
be in order when using @run_async. Also, you have to include **kwargs in
|
||||
your parameter list. The kwargs contain all optional parameters that are
|
||||
"""
|
||||
|
||||
sleep(2) # IO-heavy operation here
|
||||
bot.sendMessage(update.message.chat_id, text='Echo: %s' % update.message.text)
|
||||
|
||||
|
||||
# These handlers are for updates of type str. We use them to react to inputs
|
||||
# on the command line interface
|
||||
def cli_reply(bot, update, args):
|
||||
"""
|
||||
For any update of type telegram.Update or str that contains a command, you
|
||||
can get the argument list by appending args to the function parameters.
|
||||
Here, we reply to the last active chat with the text after the command.
|
||||
"""
|
||||
if last_chat_id is not 0:
|
||||
bot.sendMessage(chat_id=last_chat_id, text=' '.join(args))
|
||||
|
||||
|
||||
def cli_noncommand(bot, update, update_queue):
|
||||
"""
|
||||
You can also get the update queue as an argument in any handler by
|
||||
appending it to the argument list. Be careful with this though.
|
||||
Here, we put the input string back into the queue, but as a command.
|
||||
|
||||
To learn more about those optional handler parameters, read the
|
||||
documentation of the Handler classes.
|
||||
"""
|
||||
update_queue.put('/%s' % update)
|
||||
|
||||
|
||||
def error(bot, update, error):
|
||||
""" Print error to console """
|
||||
logger.warn('Update %s caused error %s' % (update, error))
|
||||
|
||||
|
||||
def main():
|
||||
# Create the EventHandler and pass it your bot's token.
|
||||
token = 'TOKEN'
|
||||
updater = Updater(token, workers=10)
|
||||
|
||||
# Get the dispatcher to register handlers
|
||||
dp = updater.dispatcher
|
||||
|
||||
# This is how we add handlers for Telegram messages
|
||||
dp.add_handler(CommandHandler("start", start))
|
||||
dp.add_handler(CommandHandler("help", help))
|
||||
# Message handlers only receive updates that don't contain commands
|
||||
dp.add_handler(MessageHandler([Filters.text], message))
|
||||
# Regex handlers will receive all updates on which their regex matches,
|
||||
# but we have to add it in a separate group, since in one group,
|
||||
# only one handler will be executed
|
||||
dp.add_handler(RegexHandler('.*', any_message), group=1)
|
||||
|
||||
# String handlers work pretty much the same. Note that we have to tell
|
||||
# the handler to pass the args or update_queue parameter
|
||||
dp.add_handler(StringCommandHandler('reply', cli_reply, pass_args=True))
|
||||
dp.add_handler(StringRegexHandler('[^/].*', cli_noncommand, pass_update_queue=True))
|
||||
|
||||
# All TelegramErrors are caught for you and delivered to the error
|
||||
# handler(s). Other types of Errors are not caught.
|
||||
dp.add_error_handler(error)
|
||||
|
||||
# Start the Bot and store the update Queue, so we can insert updates
|
||||
update_queue = updater.start_polling(timeout=10)
|
||||
'''
|
||||
# Alternatively, run with webhook:
|
||||
|
||||
update_queue = updater.start_webhook('0.0.0.0',
|
||||
443,
|
||||
url_path=token,
|
||||
cert='cert.pem',
|
||||
key='key.key',
|
||||
webhook_url='https://example.com/%s'
|
||||
% token)
|
||||
|
||||
# Or, if SSL is handled by a reverse proxy, the webhook URL is already set
|
||||
# and the reverse proxy is configured to deliver directly to port 6000:
|
||||
|
||||
update_queue = updater.start_webhook('0.0.0.0', 6000)
|
||||
'''
|
||||
|
||||
# Start CLI-Loop
|
||||
while True:
|
||||
text = input()
|
||||
|
||||
# Gracefully stop the event handler
|
||||
if text == 'stop':
|
||||
updater.stop()
|
||||
break
|
||||
|
||||
# else, put the text into the update queue to be handled by our handlers
|
||||
elif len(text) > 0:
|
||||
update_queue.put(text)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,160 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Simple Bot to reply to Telegram messages
|
||||
# This program is dedicated to the public domain under the CC0 license.
|
||||
"""
|
||||
This Bot uses the Updater class to handle the bot.
|
||||
|
||||
First, a few callback functions are defined. Then, those functions are passed to
|
||||
the Dispatcher and registered at their respective places.
|
||||
Then, the bot is started and runs until we press Ctrl-C on the command line.
|
||||
|
||||
Usage:
|
||||
Example of a bot-user conversation using ConversationHandler.
|
||||
Send /start to initiate the conversation.
|
||||
Press Ctrl-C on the command line or send a signal to the process to stop the
|
||||
bot.
|
||||
"""
|
||||
|
||||
from telegram import (ReplyKeyboardMarkup)
|
||||
from telegram.ext import (Updater, CommandHandler, MessageHandler, Filters, RegexHandler,
|
||||
ConversationHandler)
|
||||
|
||||
import logging
|
||||
|
||||
# Enable logging
|
||||
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
level=logging.INFO)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
GENDER, PHOTO, LOCATION, BIO = range(4)
|
||||
|
||||
|
||||
def start(bot, update):
|
||||
reply_keyboard = [['Boy', 'Girl', 'Other']]
|
||||
|
||||
bot.sendMessage(update.message.chat_id,
|
||||
text='Hi! My name is Professor Bot. I will hold a conversation with you. '
|
||||
'Send /cancel to stop talking to me.\n\n'
|
||||
'Are you a boy or a girl?',
|
||||
reply_markup=ReplyKeyboardMarkup(reply_keyboard, one_time_keyboard=True))
|
||||
|
||||
return GENDER
|
||||
|
||||
|
||||
def gender(bot, update):
|
||||
user = update.message.from_user
|
||||
logger.info("Gender of %s: %s" % (user.first_name, update.message.text))
|
||||
bot.sendMessage(update.message.chat_id,
|
||||
text='I see! Please send me a photo of yourself, '
|
||||
'so I know what you look like, or send /skip if you don\'t want to.')
|
||||
|
||||
return PHOTO
|
||||
|
||||
|
||||
def photo(bot, update):
|
||||
user = update.message.from_user
|
||||
photo_file = bot.getFile(update.message.photo[-1].file_id)
|
||||
photo_file.download('user_photo.jpg')
|
||||
logger.info("Photo of %s: %s" % (user.first_name, 'user_photo.jpg'))
|
||||
bot.sendMessage(update.message.chat_id, text='Gorgeous! Now, send me your location please, '
|
||||
'or send /skip if you don\'t want to.')
|
||||
|
||||
return LOCATION
|
||||
|
||||
|
||||
def skip_photo(bot, update):
|
||||
user = update.message.from_user
|
||||
logger.info("User %s did not send a photo." % user.first_name)
|
||||
bot.sendMessage(update.message.chat_id, text='I bet you look great! Now, send me your '
|
||||
'location please, or send /skip.')
|
||||
|
||||
return LOCATION
|
||||
|
||||
|
||||
def location(bot, update):
|
||||
user = update.message.from_user
|
||||
user_location = update.message.location
|
||||
logger.info("Location of %s: %f / %f"
|
||||
% (user.first_name, user_location.latitude, user_location.longitude))
|
||||
bot.sendMessage(update.message.chat_id, text='Maybe I can visit you sometime! '
|
||||
'At last, tell me something about yourself.')
|
||||
|
||||
return BIO
|
||||
|
||||
|
||||
def skip_location(bot, update):
|
||||
user = update.message.from_user
|
||||
logger.info("User %s did not send a location." % user.first_name)
|
||||
bot.sendMessage(update.message.chat_id, text='You seem a bit paranoid! '
|
||||
'At last, tell me something about yourself.')
|
||||
|
||||
return BIO
|
||||
|
||||
|
||||
def bio(bot, update):
|
||||
user = update.message.from_user
|
||||
logger.info("Bio of %s: %s" % (user.first_name, update.message.text))
|
||||
bot.sendMessage(update.message.chat_id,
|
||||
text='Thank you! I hope we can talk again some day.')
|
||||
|
||||
return ConversationHandler.END
|
||||
|
||||
|
||||
def cancel(bot, update):
|
||||
user = update.message.from_user
|
||||
logger.info("User %s canceled the conversation." % user.first_name)
|
||||
bot.sendMessage(update.message.chat_id,
|
||||
text='Bye! I hope we can talk again some day.')
|
||||
|
||||
return ConversationHandler.END
|
||||
|
||||
|
||||
def error(bot, update, error):
|
||||
logger.warn('Update "%s" caused error "%s"' % (update, error))
|
||||
|
||||
|
||||
def main():
|
||||
# Create the EventHandler and pass it your bot's token.
|
||||
updater = Updater("TOKEN")
|
||||
|
||||
# Get the dispatcher to register handlers
|
||||
dp = updater.dispatcher
|
||||
|
||||
# Add conversation handler with the states GENDER, PHOTO, LOCATION and BIO
|
||||
conv_handler = ConversationHandler(
|
||||
entry_points=[CommandHandler('start', start)],
|
||||
|
||||
states={
|
||||
GENDER: [RegexHandler('^(Boy|Girl|Other)$', gender)],
|
||||
|
||||
PHOTO: [MessageHandler([Filters.photo], photo),
|
||||
CommandHandler('skip', skip_photo)],
|
||||
|
||||
LOCATION: [MessageHandler([Filters.location], location),
|
||||
CommandHandler('skip', skip_location)],
|
||||
|
||||
BIO: [MessageHandler([Filters.text], bio)]
|
||||
},
|
||||
|
||||
fallbacks=[CommandHandler('cancel', cancel)]
|
||||
)
|
||||
|
||||
dp.add_handler(conv_handler)
|
||||
|
||||
# log all errors
|
||||
dp.add_error_handler(error)
|
||||
|
||||
# Start the Bot
|
||||
updater.start_polling()
|
||||
|
||||
# Run the bot until the you presses Ctrl-C or the process receives SIGINT,
|
||||
# SIGTERM or SIGABRT. This should be used most of the time, since
|
||||
# start_polling() is non-blocking and will stop the bot gracefully.
|
||||
updater.idle()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -1,16 +1,19 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Simple Bot to reply to Telegram messages
|
||||
# Simple Bot to reply to Telegram messages. This is built on the API wrapper, see
|
||||
# echobot2.py to see the same example built on the telegram.ext bot framework.
|
||||
# This program is dedicated to the public domain under the CC0 license.
|
||||
|
||||
import logging
|
||||
import telegram
|
||||
from telegram.error import NetworkError, Unauthorized
|
||||
from time import sleep
|
||||
|
||||
|
||||
update_id = None
|
||||
|
||||
def main():
|
||||
global update_id
|
||||
# Telegram Bot Authorization Token
|
||||
bot = telegram.Bot('TOKEN')
|
||||
|
||||
@@ -25,7 +28,7 @@ def main():
|
||||
|
||||
while True:
|
||||
try:
|
||||
update_id = echo(bot, update_id)
|
||||
echo(bot)
|
||||
except NetworkError:
|
||||
sleep(1)
|
||||
except Unauthorized:
|
||||
@@ -33,20 +36,17 @@ def main():
|
||||
update_id += 1
|
||||
|
||||
|
||||
def echo(bot, update_id):
|
||||
|
||||
def echo(bot):
|
||||
global update_id
|
||||
# Request updates after the last update_id
|
||||
for update in bot.getUpdates(offset=update_id, timeout=10):
|
||||
# chat_id is required to reply to any message
|
||||
chat_id = update.message.chat_id
|
||||
update_id = update.update_id + 1
|
||||
message = update.message.text
|
||||
|
||||
if message:
|
||||
if update.message: # your bot can receive updates without messages
|
||||
# Reply to the message
|
||||
bot.sendMessage(chat_id=chat_id, text=message)
|
||||
|
||||
return update_id
|
||||
bot.sendMessage(chat_id=chat_id, text=update.message.text)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
@@ -1,38 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Robô Ed Telegram Bot
|
||||
# This program is dedicated to the public domain under the CC0 license.
|
||||
|
||||
import logging
|
||||
import telegram
|
||||
import urllib
|
||||
|
||||
|
||||
def main():
|
||||
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
bot = telegram.Bot('TOKEN') # Telegram Bot Authorization Token
|
||||
|
||||
LAST_UPDATE_ID = bot.getUpdates()[-1].update_id # Get lastest update
|
||||
|
||||
while True:
|
||||
for update in bot.getUpdates(offset=LAST_UPDATE_ID, timeout=10):
|
||||
text = update.message.text
|
||||
chat_id = update.message.chat.id
|
||||
update_id = update.update_id
|
||||
|
||||
if text:
|
||||
roboed = ed(text) # Ask something to Robô Ed
|
||||
bot.sendMessage(chat_id=chat_id, text=roboed)
|
||||
LAST_UPDATE_ID = update_id + 1
|
||||
|
||||
|
||||
def ed(text):
|
||||
url = 'http://www.ed.conpet.gov.br/mod_perl/bot_gateway.cgi?server=0.0.0.0%3A8085&charset_post=utf-8&charset=utf-8&pure=1&js=0&tst=1&msg=' + text
|
||||
data = urllib.urlopen(url).read()
|
||||
|
||||
return data.strip()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -1,100 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Basic example for a bot that awaits an answer from the user
|
||||
# This program is dedicated to the public domain under the CC0 license.
|
||||
|
||||
import logging
|
||||
from telegram import Emoji, ForceReply, ReplyKeyboardMarkup, KeyboardButton
|
||||
from telegram.ext import Updater, CommandHandler, MessageHandler, Filters
|
||||
|
||||
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
level=logging.INFO)
|
||||
|
||||
# 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()
|
||||
|
||||
|
||||
# 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
|
||||
text = update.message.text
|
||||
chat_state = state.get(chat_id, MENU)
|
||||
chat_context = context.get(chat_id, None)
|
||||
|
||||
# Since the handler will also be called on messages, we need to check if
|
||||
# the message is actually a command
|
||||
if chat_state == MENU and text[0] == '/':
|
||||
state[chat_id] = AWAIT_INPUT # set the state
|
||||
context[chat_id] = user_id # save the user id to context
|
||||
bot.sendMessage(chat_id,
|
||||
text="Please enter your settings value or send "
|
||||
"/cancel to abort",
|
||||
reply_markup=ForceReply())
|
||||
|
||||
# If we are waiting for input and the right user answered
|
||||
elif chat_state == AWAIT_INPUT and chat_context == user_id:
|
||||
state[chat_id] = AWAIT_CONFIRMATION
|
||||
|
||||
# Save the user id and the answer to context
|
||||
context[chat_id] = (user_id, update.message.text)
|
||||
reply_markup = ReplyKeyboardMarkup(
|
||||
[[KeyboardButton(YES), KeyboardButton(NO)]],
|
||||
one_time_keyboard=True)
|
||||
bot.sendMessage(chat_id, text="Are you sure?", reply_markup=reply_markup)
|
||||
|
||||
# If we are waiting for confirmation and the right user answered
|
||||
elif chat_state == AWAIT_CONFIRMATION and chat_context[0] == user_id:
|
||||
del state[chat_id]
|
||||
del context[chat_id]
|
||||
if text == YES:
|
||||
values[chat_id] = chat_context[1]
|
||||
bot.sendMessage(chat_id, text="Changed value to %s." % values[chat_id])
|
||||
else:
|
||||
bot.sendMessage(chat_id,
|
||||
text="Value not changed: %s." % values.get(chat_id, '<not set>'))
|
||||
|
||||
|
||||
# Handler for the /cancel command.
|
||||
# Sets the state back to MENU and clears the context
|
||||
def cancel(bot, update):
|
||||
chat_id = update.message.chat_id
|
||||
del state[chat_id]
|
||||
del context[chat_id]
|
||||
|
||||
|
||||
def help(bot, update):
|
||||
bot.sendMessage(update.message.chat_id, text="Use /set to test this bot.")
|
||||
|
||||
# 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 and confirmation
|
||||
updater.dispatcher.add_handler(MessageHandler([Filters.text], set_value))
|
||||
updater.dispatcher.add_handler(CommandHandler('cancel', cancel))
|
||||
updater.dispatcher.add_handler(CommandHandler('start', help))
|
||||
updater.dispatcher.add_handler(CommandHandler('help', help))
|
||||
|
||||
# Start the Bot
|
||||
updater.start_polling()
|
||||
|
||||
# Run the bot until the user presses Ctrl-C or the process receives SIGINT,
|
||||
# SIGTERM or SIGABRT
|
||||
updater.idle()
|
||||
+32
-17
@@ -17,15 +17,15 @@ Press Ctrl-C on the command line or send a signal to the process to stop the
|
||||
bot.
|
||||
"""
|
||||
|
||||
from telegram.ext import Updater, CommandHandler
|
||||
from telegram.ext import Updater, CommandHandler, Job
|
||||
import logging
|
||||
|
||||
# Enable logging
|
||||
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
level=logging.INFO)
|
||||
level=logging.DEBUG)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
job_queue = None
|
||||
timers = dict()
|
||||
|
||||
|
||||
# Define a few command handlers. These usually take the two arguments bot and
|
||||
@@ -34,8 +34,13 @@ def start(bot, update):
|
||||
bot.sendMessage(update.message.chat_id, text='Hi! Use /set <seconds> to ' 'set a timer')
|
||||
|
||||
|
||||
def set(bot, update, args):
|
||||
""" Adds a job to the queue """
|
||||
def alarm(bot, job):
|
||||
"""Function to send the alarm message"""
|
||||
bot.sendMessage(job.context, text='Beep!')
|
||||
|
||||
|
||||
def set(bot, update, args, job_queue):
|
||||
"""Adds a job to the queue"""
|
||||
chat_id = update.message.chat_id
|
||||
try:
|
||||
# args[0] should contain the time for the timer in seconds
|
||||
@@ -43,29 +48,38 @@ def set(bot, update, args):
|
||||
if due < 0:
|
||||
bot.sendMessage(chat_id, text='Sorry we can not go back to future!')
|
||||
|
||||
def alarm(bot):
|
||||
""" Inner function to send the alarm message """
|
||||
bot.sendMessage(chat_id, text='Beep!')
|
||||
|
||||
# Add job to queue
|
||||
job_queue.put(alarm, due, repeat=False)
|
||||
job = Job(alarm, due, repeat=False, context=chat_id)
|
||||
timers[chat_id] = job
|
||||
job_queue.put(job)
|
||||
|
||||
bot.sendMessage(chat_id, text='Timer successfully set!')
|
||||
|
||||
except IndexError:
|
||||
bot.sendMessage(chat_id, text='Usage: /set <seconds>')
|
||||
except ValueError:
|
||||
except (IndexError, ValueError):
|
||||
bot.sendMessage(chat_id, text='Usage: /set <seconds>')
|
||||
|
||||
|
||||
def unset(bot, update):
|
||||
"""Removes the job if the user changed their mind"""
|
||||
chat_id = update.message.chat_id
|
||||
|
||||
if chat_id not in timers:
|
||||
bot.sendMessage(chat_id, text='You have no active timer')
|
||||
return
|
||||
|
||||
job = timers[chat_id]
|
||||
job.schedule_removal()
|
||||
del timers[chat_id]
|
||||
|
||||
bot.sendMessage(chat_id, text='Timer successfully unset!')
|
||||
|
||||
|
||||
def error(bot, update, error):
|
||||
logger.warn('Update "%s" caused error "%s"' % (update, error))
|
||||
|
||||
|
||||
def main():
|
||||
global job_queue
|
||||
|
||||
updater = Updater("TOKEN")
|
||||
job_queue = updater.job_queue
|
||||
|
||||
# Get the dispatcher to register handlers
|
||||
dp = updater.dispatcher
|
||||
@@ -73,7 +87,8 @@ def main():
|
||||
# on different commands - answer in Telegram
|
||||
dp.add_handler(CommandHandler("start", start))
|
||||
dp.add_handler(CommandHandler("help", start))
|
||||
dp.add_handler(CommandHandler("set", set, pass_args=True))
|
||||
dp.add_handler(CommandHandler("set", set, pass_args=True, pass_job_queue=True))
|
||||
dp.add_handler(CommandHandler("unset", unset))
|
||||
|
||||
# log all errors
|
||||
dp.add_error_handler(error)
|
||||
|
||||
+3
-1
@@ -1 +1,3 @@
|
||||
future
|
||||
future>=0.15.2
|
||||
urllib3>=1.10
|
||||
certifi
|
||||
|
||||
@@ -80,9 +80,13 @@ from .inputvenuemessagecontent import InputVenueMessageContent
|
||||
from .inputcontactmessagecontent import InputContactMessageContent
|
||||
from .update import Update
|
||||
from .bot import Bot
|
||||
from .constants import (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)
|
||||
|
||||
__author__ = 'devs@python-telegram-bot.org'
|
||||
__version__ = '4.2.0'
|
||||
__version__ = '5.0.0'
|
||||
__all__ = ['Audio', 'Bot', 'Chat', 'ChatMember', 'ChatAction', 'ChosenInlineResult',
|
||||
'CallbackQuery', 'Contact', 'Document', 'Emoji', 'File', 'ForceReply',
|
||||
'InlineKeyboardButton', 'InlineKeyboardMarkup', 'InlineQuery', 'InlineQueryResult',
|
||||
@@ -99,7 +103,9 @@ __all__ = ['Audio', 'Bot', 'Chat', 'ChatMember', 'ChatAction', 'ChosenInlineResu
|
||||
'KeyboardButton', 'Location', 'Message', 'MessageEntity', 'NullHandler', 'ParseMode',
|
||||
'PhotoSize', 'ReplyKeyboardHide', 'ReplyKeyboardMarkup', 'ReplyMarkup', 'Sticker',
|
||||
'TelegramError', 'TelegramObject', 'Update', 'User', 'UserProfilePhotos', 'Venue',
|
||||
'Video', 'Voice']
|
||||
'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
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import sys
|
||||
|
||||
import urllib3
|
||||
import certifi
|
||||
import future
|
||||
|
||||
from . import __version__ as telegram_ver
|
||||
|
||||
|
||||
def print_ver_info():
|
||||
print('python-telegram-bot {0}'.format(telegram_ver))
|
||||
print('urllib3 {0}'.format(urllib3.__version__))
|
||||
print('certifi {0}'.format(certifi.__version__))
|
||||
print('future {0}'.format(future.__version__))
|
||||
print('Python {0}'.format(sys.version.replace('\n', ' ')))
|
||||
|
||||
# main
|
||||
print_ver_info()
|
||||
+6
-9
@@ -1009,6 +1009,7 @@ class Bot(TelegramObject):
|
||||
return result
|
||||
|
||||
@log
|
||||
@message
|
||||
def editMessageText(self,
|
||||
text,
|
||||
chat_id=None,
|
||||
@@ -1016,7 +1017,6 @@ class Bot(TelegramObject):
|
||||
inline_message_id=None,
|
||||
parse_mode=None,
|
||||
disable_web_page_preview=None,
|
||||
reply_markup=None,
|
||||
**kwargs):
|
||||
"""Use this method to edit text messages sent by the bot or via the bot
|
||||
(for inline bots).
|
||||
@@ -1043,6 +1043,10 @@ class Bot(TelegramObject):
|
||||
A JSON-serialized object for an inline keyboard.
|
||||
|
||||
Keyword Args:
|
||||
reply_markup (Optional[:class:`telegram.ReplyMarkup`]): Additional
|
||||
interface options. A JSON-serialized object for an inline
|
||||
keyboard, custom reply keyboard, instructions to hide reply
|
||||
keyboard or to force a reply from the user.
|
||||
timeout (Optional[float]): If this value is specified, use it as
|
||||
the definitive timeout (in seconds) for urlopen() operations.
|
||||
|
||||
@@ -1070,15 +1074,8 @@ class Bot(TelegramObject):
|
||||
data['parse_mode'] = parse_mode
|
||||
if disable_web_page_preview:
|
||||
data['disable_web_page_preview'] = disable_web_page_preview
|
||||
if reply_markup:
|
||||
if isinstance(reply_markup, ReplyMarkup):
|
||||
data['reply_markup'] = reply_markup.to_json()
|
||||
else:
|
||||
data['reply_markup'] = reply_markup
|
||||
|
||||
result = request.post(url, data, timeout=kwargs.get('timeout'))
|
||||
|
||||
return Message.de_json(result)
|
||||
return url, data
|
||||
|
||||
@log
|
||||
@message
|
||||
|
||||
@@ -43,3 +43,14 @@ class CallbackQuery(TelegramObject):
|
||||
data['message'] = Message.de_json(data.get('message'))
|
||||
|
||||
return CallbackQuery(**data)
|
||||
|
||||
def to_dict(self):
|
||||
"""
|
||||
Returns:
|
||||
dict:
|
||||
"""
|
||||
data = super(CallbackQuery, self).to_dict()
|
||||
|
||||
# Required
|
||||
data['from'] = data.pop('from_user', None)
|
||||
return data
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
# python-telegram-bot - a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2015-2016
|
||||
# by the python-telegram-bot contributors <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser 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 Lesser Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser Public License
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
"""Constants in the Telegram network.
|
||||
|
||||
Attributes:
|
||||
MAX_MESSAGE_LENGTH (int): from
|
||||
https://core.telegram.org/method/messages.sendMessage#return-errors
|
||||
MAX_CAPTION_LENGTH (int): from https://core.telegram.org/bots/api#sendphoto
|
||||
|
||||
The following constants were extracted from the
|
||||
`Telegram Bots FAQ <https://core.telegram.org/bots/faq>`_.
|
||||
|
||||
Attributes:
|
||||
SUPPORTED_WEBHOOK_PORTS (List[int])
|
||||
MAX_FILESIZE_DOWNLOAD (int): In bytes.
|
||||
MAX_FILESIZE_UPLOAD (int): Official limit, the actual limit can be a bit higher.
|
||||
MAX_MESSAGES_PER_SECOND_PER_CHAT (int): Telegram may allow short bursts that go over this
|
||||
limit, but eventually you'll begin receiving 429 errors.
|
||||
MAX_MESSAGES_PER_SECOND (int)
|
||||
MAX_MESSAGES_PER_MINUTE_PER_GROUP (int)
|
||||
"""
|
||||
|
||||
MAX_MESSAGE_LENGTH = 4096
|
||||
MAX_CAPTION_LENGTH = 200
|
||||
|
||||
# constants above this line are tested
|
||||
|
||||
SUPPORTED_WEBHOOK_PORTS = [443, 80, 88, 8443]
|
||||
MAX_FILESIZE_DOWNLOAD = int(20E6) # (20MB)
|
||||
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
|
||||
@@ -19,7 +19,7 @@
|
||||
"""Extensions over the Telegram Bot API to facilitate bot making"""
|
||||
|
||||
from .dispatcher import Dispatcher
|
||||
from .jobqueue import JobQueue
|
||||
from .jobqueue import JobQueue, Job
|
||||
from .updater import Updater
|
||||
from .callbackqueryhandler import CallbackQueryHandler
|
||||
from .choseninlineresulthandler import ChosenInlineResultHandler
|
||||
@@ -31,8 +31,9 @@ from .regexhandler import RegexHandler
|
||||
from .stringcommandhandler import StringCommandHandler
|
||||
from .stringregexhandler import StringRegexHandler
|
||||
from .typehandler import TypeHandler
|
||||
from .conversationhandler import ConversationHandler
|
||||
|
||||
__all__ = ('Dispatcher', 'JobQueue', 'Updater', 'CallbackQueryHandler',
|
||||
__all__ = ('Dispatcher', 'JobQueue', 'Job', 'Updater', 'CallbackQueryHandler',
|
||||
'ChosenInlineResultHandler', 'CommandHandler', 'Handler', 'InlineQueryHandler',
|
||||
'MessageHandler', 'Filters', 'RegexHandler', 'StringCommandHandler',
|
||||
'StringRegexHandler', 'TypeHandler')
|
||||
'StringRegexHandler', 'TypeHandler', 'ConversationHandler')
|
||||
|
||||
@@ -31,13 +31,20 @@ class CallbackQueryHandler(Handler):
|
||||
callback (function): A function that takes ``bot, update`` as
|
||||
positional arguments. It will be called when the ``check_update``
|
||||
has determined that an update should be processed by this handler.
|
||||
pass_update_queue (optional[bool]): If the handler should be passed the
|
||||
update queue as a keyword argument called ``update_queue``. It can
|
||||
be used to insert updates. 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
|
||||
be used to insert updates. Default is ``False``.
|
||||
pass_job_queue (optional[bool]): If set to ``True``, a keyword argument called
|
||||
``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``.
|
||||
"""
|
||||
|
||||
def __init__(self, callback, pass_update_queue=False):
|
||||
super(CallbackQueryHandler, self).__init__(callback, pass_update_queue)
|
||||
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 check_update(self, update):
|
||||
return isinstance(update, Update) and update.callback_query
|
||||
@@ -45,7 +52,7 @@ class CallbackQueryHandler(Handler):
|
||||
def handle_update(self, update, dispatcher):
|
||||
optional_args = self.collect_optional_args(dispatcher)
|
||||
|
||||
self.callback(dispatcher.bot, update, **optional_args)
|
||||
return self.callback(dispatcher.bot, update, **optional_args)
|
||||
|
||||
# old non-PEP8 Handler methods
|
||||
m = "telegram.CallbackQueryHandler."
|
||||
|
||||
@@ -32,13 +32,20 @@ class ChosenInlineResultHandler(Handler):
|
||||
callback (function): A function that takes ``bot, update`` as
|
||||
positional arguments. It will be called when the ``check_update``
|
||||
has determined that an update should be processed by this handler.
|
||||
pass_update_queue (optional[bool]): If the handler should be passed the
|
||||
update queue as a keyword argument called ``update_queue``. It can
|
||||
be used to insert updates. 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
|
||||
be used to insert updates. Default is ``False``.
|
||||
pass_job_queue (optional[bool]): If set to ``True``, a keyword argument called
|
||||
``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``.
|
||||
"""
|
||||
|
||||
def __init__(self, callback, pass_update_queue=False):
|
||||
super(ChosenInlineResultHandler, self).__init__(callback, pass_update_queue)
|
||||
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)
|
||||
|
||||
def check_update(self, update):
|
||||
return isinstance(update, Update) and update.chosen_inline_result
|
||||
@@ -46,7 +53,7 @@ class ChosenInlineResultHandler(Handler):
|
||||
def handle_update(self, update, dispatcher):
|
||||
optional_args = self.collect_optional_args(dispatcher)
|
||||
|
||||
self.callback(dispatcher.bot, update, **optional_args)
|
||||
return self.callback(dispatcher.bot, update, **optional_args)
|
||||
|
||||
# old non-PEP8 Handler methods
|
||||
m = "telegram.ChosenInlineResultHandler."
|
||||
|
||||
@@ -40,9 +40,14 @@ class CommandHandler(Handler):
|
||||
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``
|
||||
pass_update_queue (optional[bool]): If the handler should be passed the
|
||||
update queue as a keyword argument called ``update_queue``. It can
|
||||
be used to insert updates. 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
|
||||
be used to insert updates. Default is ``False``.
|
||||
pass_job_queue (optional[bool]): If set to ``True``, a keyword argument called
|
||||
``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``.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
@@ -50,8 +55,11 @@ class CommandHandler(Handler):
|
||||
callback,
|
||||
allow_edited=False,
|
||||
pass_args=False,
|
||||
pass_update_queue=False):
|
||||
super(CommandHandler, self).__init__(callback, pass_update_queue)
|
||||
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)
|
||||
self.command = command
|
||||
self.allow_edited = allow_edited
|
||||
self.pass_args = pass_args
|
||||
@@ -75,7 +83,7 @@ class CommandHandler(Handler):
|
||||
if self.pass_args:
|
||||
optional_args['args'] = message.text.split(' ')[1:]
|
||||
|
||||
self.callback(dispatcher.bot, update, **optional_args)
|
||||
return self.callback(dispatcher.bot, update, **optional_args)
|
||||
|
||||
# old non-PEP8 Handler methods
|
||||
m = "telegram.CommandHandler."
|
||||
|
||||
@@ -0,0 +1,222 @@
|
||||
#!/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 Lesser 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 Lesser Public License for more details.
|
||||
#
|
||||
# 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 ConversationHandler """
|
||||
|
||||
import logging
|
||||
|
||||
from telegram import Update
|
||||
from telegram.ext import Handler
|
||||
from telegram.utils.promise import Promise
|
||||
|
||||
|
||||
class ConversationHandler(Handler):
|
||||
"""
|
||||
A handler to hold a conversation with a user by managing four collections of other handlers.
|
||||
|
||||
The first collection, a ``list`` named ``entry_points``, is used to initiate the conversation,
|
||||
for example with a ``CommandHandler`` or ``RegexHandler``.
|
||||
|
||||
The second collection, a ``dict`` named ``states``, contains the different conversation steps
|
||||
and one or more associated handlers that should be used if the user sends a message when the
|
||||
conversation with them is currently in that state. You will probably use mostly
|
||||
``MessageHandler`` and ``RegexHandler`` here.
|
||||
|
||||
The third collection, a ``list`` named ``fallbacks``, is used if the user is currently in a
|
||||
conversation but the state has either no associated handler or the handler that is associated
|
||||
to the state is inappropriate for the update, for example if the update contains a command, but
|
||||
a regular text message is expected. You could use this for a ``/cancel`` command or to let the
|
||||
user know their message was not recognized.
|
||||
|
||||
The fourth, optional collection of handlers, a ``list`` named ``timed_out_behavior`` is used if
|
||||
the wait for ``run_async`` takes longer than defined in ``run_async_timeout``. For example,
|
||||
you can let the user know that they should wait for a bit before they can continue.
|
||||
|
||||
To change the state of conversation, the callback function of a handler must return the new
|
||||
state after responding to the user. If it does not return anything (returning ``None`` by
|
||||
default), the state will not change. To end the conversation, the callback function must
|
||||
return ``CallbackHandler.END`` or ``-1``.
|
||||
|
||||
Args:
|
||||
entry_points (list): A list of ``Handler`` objects that can trigger the start of the
|
||||
conversation. The first handler which ``check_update`` method returns ``True`` will be
|
||||
used. If all return ``False``, the update is not handled.
|
||||
states (dict): A ``dict[object: list[Handler]]`` that defines the different states of
|
||||
conversation a user can be in and one or more associated ``Handler`` objects that
|
||||
should be used in that state. The first handler which ``check_update`` method returns
|
||||
``True`` will be used.
|
||||
fallbacks (list): A list of handlers that might be used if the user is in a conversation,
|
||||
but every handler for their current state returned ``False`` on ``check_update``.
|
||||
The first handler which ``check_update`` method returns ``True`` will be used. If all
|
||||
return ``False``, the update is not handled.
|
||||
allow_reentry (Optional[bool]): If set to ``True``, a user that is currently in a
|
||||
conversation can restart the conversation by triggering one of the entry points.
|
||||
run_async_timeout (Optional[float]): If the previous handler for this user was running
|
||||
asynchronously using the ``run_async`` decorator, it might not be finished when the
|
||||
next message arrives. This timeout defines how long the conversation handler should
|
||||
wait for the next state to be computed. The default is ``None`` which means it will
|
||||
wait indefinitely.
|
||||
timed_out_behavior (Optional[list]): A list of handlers that might be used if
|
||||
the wait for ``run_async`` timed out. The first handler which ``check_update`` method
|
||||
returns ``True`` will be used. If all return ``False``, the update is not handled.
|
||||
|
||||
"""
|
||||
|
||||
END = -1
|
||||
|
||||
def __init__(self,
|
||||
entry_points,
|
||||
states,
|
||||
fallbacks,
|
||||
allow_reentry=False,
|
||||
run_async_timeout=None,
|
||||
timed_out_behavior=None):
|
||||
|
||||
self.entry_points = entry_points
|
||||
""":type: list[telegram.ext.Handler]"""
|
||||
|
||||
self.states = states
|
||||
""":type: dict[str: telegram.ext.Handler]"""
|
||||
|
||||
self.fallbacks = fallbacks
|
||||
""":type: list[telegram.ext.Handler]"""
|
||||
|
||||
self.allow_reentry = allow_reentry
|
||||
self.run_async_timeout = run_async_timeout
|
||||
|
||||
self.timed_out_behavior = timed_out_behavior
|
||||
""":type: list[telegram.ext.Handler]"""
|
||||
|
||||
self.conversations = dict()
|
||||
""":type: dict[(int, int): str]"""
|
||||
|
||||
self.current_conversation = None
|
||||
self.current_handler = None
|
||||
|
||||
self.logger = logging.getLogger(__name__)
|
||||
|
||||
def check_update(self, update):
|
||||
|
||||
if not isinstance(update, Update):
|
||||
return False
|
||||
|
||||
user = None
|
||||
chat = None
|
||||
|
||||
if update.message:
|
||||
user = update.message.from_user
|
||||
chat = update.message.chat
|
||||
|
||||
elif update.edited_message:
|
||||
user = update.edited_message.from_user
|
||||
chat = update.edited_message.chat
|
||||
|
||||
elif update.inline_query:
|
||||
user = update.inline_query.from_user
|
||||
|
||||
elif update.chosen_inline_result:
|
||||
user = update.chosen_inline_result.from_user
|
||||
|
||||
elif update.callback_query:
|
||||
user = update.callback_query.from_user
|
||||
chat = update.callback_query.message.chat if update.callback_query.message else None
|
||||
|
||||
else:
|
||||
return False
|
||||
|
||||
key = (chat.id, user.id) if chat else (None, user.id)
|
||||
state = self.conversations.get(key)
|
||||
|
||||
# Resolve promises
|
||||
if isinstance(state, tuple) and len(state) is 2 and isinstance(state[1], Promise):
|
||||
self.logger.debug('waiting for promise...')
|
||||
|
||||
old_state, new_state = state
|
||||
new_state.result(timeout=self.run_async_timeout)
|
||||
|
||||
if new_state.done.is_set():
|
||||
self.update_state(new_state.result(), key)
|
||||
state = self.conversations.get(key)
|
||||
|
||||
else:
|
||||
for candidate in (self.timed_out_behavior or []):
|
||||
if candidate.check_update(update):
|
||||
# Save the current user and the selected handler for handle_update
|
||||
self.current_conversation = key
|
||||
self.current_handler = candidate
|
||||
|
||||
return True
|
||||
|
||||
else:
|
||||
return False
|
||||
|
||||
self.logger.debug('selecting conversation %s with state %s' % (str(key), str(state)))
|
||||
|
||||
handler = None
|
||||
|
||||
# Search entry points for a match
|
||||
if state is None or self.allow_reentry:
|
||||
for entry_point in self.entry_points:
|
||||
if entry_point.check_update(update):
|
||||
handler = entry_point
|
||||
break
|
||||
|
||||
else:
|
||||
if state is None:
|
||||
return False
|
||||
|
||||
# Get the handler list for current state, if we didn't find one yet and we're still here
|
||||
if state is not None and not handler:
|
||||
handlers = self.states.get(state)
|
||||
|
||||
for candidate in (handlers or []):
|
||||
if candidate.check_update(update):
|
||||
handler = candidate
|
||||
break
|
||||
|
||||
# Find a fallback handler if all other handlers fail
|
||||
else:
|
||||
for fallback in self.fallbacks:
|
||||
if fallback.check_update(update):
|
||||
handler = fallback
|
||||
break
|
||||
|
||||
else:
|
||||
return False
|
||||
|
||||
# Save the current user and the selected handler for handle_update
|
||||
self.current_conversation = key
|
||||
self.current_handler = handler
|
||||
|
||||
return True
|
||||
|
||||
def handle_update(self, update, dispatcher):
|
||||
|
||||
new_state = self.current_handler.handle_update(update, dispatcher)
|
||||
|
||||
self.update_state(new_state, self.current_conversation)
|
||||
|
||||
def update_state(self, new_state, key):
|
||||
if new_state == self.END:
|
||||
del self.conversations[key]
|
||||
|
||||
elif isinstance(new_state, Promise):
|
||||
self.conversations[key] = (self.conversations[key], new_state)
|
||||
|
||||
elif new_state is not None:
|
||||
self.conversations[key] = new_state
|
||||
+61
-38
@@ -20,24 +20,47 @@
|
||||
|
||||
import logging
|
||||
from functools import wraps
|
||||
from threading import Thread, BoundedSemaphore, Lock, Event, current_thread
|
||||
from threading import Thread, Lock, Event, current_thread
|
||||
from time import sleep
|
||||
from queue import Queue, Empty
|
||||
|
||||
from queue import Empty
|
||||
from future.builtins import range
|
||||
|
||||
from telegram import (TelegramError, NullHandler)
|
||||
from telegram.utils import request
|
||||
from telegram.ext.handler import Handler
|
||||
from telegram.utils.deprecate import deprecate
|
||||
from telegram.utils.promise import Promise
|
||||
|
||||
logging.getLogger(__name__).addHandler(NullHandler())
|
||||
|
||||
semaphore = None
|
||||
async_threads = set()
|
||||
ASYNC_QUEUE = Queue()
|
||||
ASYNC_THREADS = set()
|
||||
""":type: set[Thread]"""
|
||||
async_lock = Lock()
|
||||
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.
|
||||
@@ -53,30 +76,13 @@ def run_async(func):
|
||||
# set a threading.Event to notify caller thread
|
||||
|
||||
@wraps(func)
|
||||
def pooled(*pargs, **kwargs):
|
||||
"""
|
||||
A wrapper to run a thread in a thread pool
|
||||
"""
|
||||
try:
|
||||
result = func(*pargs, **kwargs)
|
||||
finally:
|
||||
semaphore.release()
|
||||
|
||||
with async_lock:
|
||||
async_threads.remove(current_thread())
|
||||
return result
|
||||
|
||||
@wraps(func)
|
||||
def async_func(*pargs, **kwargs):
|
||||
def async_func(*args, **kwargs):
|
||||
"""
|
||||
A wrapper to run a function in a thread
|
||||
"""
|
||||
thread = Thread(target=pooled, args=pargs, kwargs=kwargs)
|
||||
semaphore.acquire()
|
||||
with async_lock:
|
||||
async_threads.add(thread)
|
||||
thread.start()
|
||||
return thread
|
||||
promise = Promise(func, args, kwargs)
|
||||
ASYNC_QUEUE.put(promise)
|
||||
return promise
|
||||
|
||||
return async_func
|
||||
|
||||
@@ -90,11 +96,16 @@ class Dispatcher(object):
|
||||
handlers
|
||||
update_queue (Queue): The synchronized queue that will contain the
|
||||
updates.
|
||||
job_queue (Optional[telegram.ext.JobQueue]): The ``JobQueue`` instance to pass onto handler
|
||||
callbacks
|
||||
workers (Optional[int]): Number of maximum concurrent worker threads for the ``@run_async``
|
||||
decorator
|
||||
"""
|
||||
|
||||
def __init__(self, bot, update_queue, workers=4, exception_event=None):
|
||||
def __init__(self, bot, update_queue, workers=4, exception_event=None, job_queue=None):
|
||||
self.bot = bot
|
||||
self.update_queue = update_queue
|
||||
self.job_queue = job_queue
|
||||
|
||||
self.handlers = {}
|
||||
""":type: dict[int, list[Handler]"""
|
||||
@@ -107,11 +118,23 @@ class Dispatcher(object):
|
||||
self.__stop_event = Event()
|
||||
self.__exception_event = exception_event or Event()
|
||||
|
||||
global semaphore
|
||||
if not semaphore:
|
||||
semaphore = BoundedSemaphore(value=workers)
|
||||
else:
|
||||
self.logger.debug('Semaphore already initialized, skipping.')
|
||||
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()
|
||||
else:
|
||||
self.logger.debug('Thread pool already initialized, skipping.')
|
||||
|
||||
def start(self):
|
||||
"""
|
||||
@@ -131,7 +154,7 @@ class Dispatcher(object):
|
||||
self.running = True
|
||||
self.logger.debug('Dispatcher started')
|
||||
|
||||
while True:
|
||||
while 1:
|
||||
try:
|
||||
# Pop update from update queue.
|
||||
update = self.update_queue.get(True, 1)
|
||||
@@ -145,7 +168,7 @@ class Dispatcher(object):
|
||||
continue
|
||||
|
||||
self.logger.debug('Processing Update: %s' % update)
|
||||
self.processUpdate(update)
|
||||
self.process_update(update)
|
||||
|
||||
self.running = False
|
||||
self.logger.debug('Dispatcher thread stopped')
|
||||
@@ -160,7 +183,7 @@ class Dispatcher(object):
|
||||
sleep(0.1)
|
||||
self.__stop_event.clear()
|
||||
|
||||
def processUpdate(self, update):
|
||||
def process_update(self, update):
|
||||
"""
|
||||
Processes a single update.
|
||||
|
||||
@@ -170,7 +193,7 @@ class Dispatcher(object):
|
||||
|
||||
# An error happened while polling
|
||||
if isinstance(update, TelegramError):
|
||||
self.dispatchError(None, update)
|
||||
self.dispatch_error(None, update)
|
||||
|
||||
else:
|
||||
for group in self.groups:
|
||||
@@ -185,7 +208,7 @@ class Dispatcher(object):
|
||||
'Update.')
|
||||
|
||||
try:
|
||||
self.dispatchError(update, te)
|
||||
self.dispatch_error(update, te)
|
||||
except Exception:
|
||||
self.logger.exception('An uncaught error was raised while '
|
||||
'handling the error')
|
||||
@@ -271,7 +294,7 @@ class Dispatcher(object):
|
||||
if callback in self.error_handlers:
|
||||
self.error_handlers.remove(callback)
|
||||
|
||||
def dispatchError(self, update, error):
|
||||
def dispatch_error(self, update, error):
|
||||
"""
|
||||
Dispatches an error.
|
||||
|
||||
|
||||
+16
-6
@@ -31,14 +31,20 @@ class Handler(object):
|
||||
callback (function): A function that takes ``bot, update`` as
|
||||
positional arguments. It will be called when the ``check_update``
|
||||
has determined that an update should be processed by this handler.
|
||||
pass_update_queue (optional[bool]): If the callback should be passed
|
||||
the update queue as a keyword argument called ``update_queue``. It
|
||||
can be used to insert updates. 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
|
||||
be used to insert updates. Default is ``False``.
|
||||
pass_job_queue (optional[bool]): If set to ``True``, a keyword argument called
|
||||
``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``.
|
||||
"""
|
||||
|
||||
def __init__(self, callback, pass_update_queue=False):
|
||||
def __init__(self, callback, pass_update_queue=False, pass_job_queue=False):
|
||||
self.callback = callback
|
||||
self.pass_update_queue = pass_update_queue
|
||||
self.pass_job_queue = pass_job_queue
|
||||
|
||||
def check_update(self, update):
|
||||
"""
|
||||
@@ -57,11 +63,13 @@ class Handler(object):
|
||||
"""
|
||||
This method is called if it was determined that an update should indeed
|
||||
be handled by this instance. It should also be overridden, but in most
|
||||
cases call self.callback(dispatcher.bot, update), possibly along with
|
||||
optional arguments.
|
||||
cases call ``self.callback(dispatcher.bot, update)``, possibly along with
|
||||
optional arguments. To work with the ``ConversationHandler``, this method should return the
|
||||
value returned from ``self.callback``
|
||||
|
||||
Args:
|
||||
update (object): The update to be handled
|
||||
dispatcher (Dispatcher): The dispatcher to collect optional args
|
||||
|
||||
"""
|
||||
raise NotImplementedError
|
||||
@@ -77,6 +85,8 @@ class Handler(object):
|
||||
optional_args = dict()
|
||||
if self.pass_update_queue:
|
||||
optional_args['update_queue'] = dispatcher.update_queue
|
||||
if self.pass_job_queue:
|
||||
optional_args['job_queue'] = dispatcher.job_queue
|
||||
|
||||
return optional_args
|
||||
|
||||
|
||||
@@ -31,13 +31,20 @@ class InlineQueryHandler(Handler):
|
||||
callback (function): A function that takes ``bot, update`` as
|
||||
positional arguments. It will be called when the ``check_update``
|
||||
has determined that an update should be processed by this handler.
|
||||
pass_update_queue (optional[bool]): If the handler should be passed the
|
||||
update queue as a keyword argument called ``update_queue``. It can
|
||||
be used to insert updates. 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
|
||||
be used to insert updates. Default is ``False``.
|
||||
pass_job_queue (optional[bool]): If set to ``True``, a keyword argument called
|
||||
``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``.
|
||||
"""
|
||||
|
||||
def __init__(self, callback, pass_update_queue=False):
|
||||
super(InlineQueryHandler, self).__init__(callback, pass_update_queue)
|
||||
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 check_update(self, update):
|
||||
return isinstance(update, Update) and update.inline_query
|
||||
@@ -45,7 +52,7 @@ class InlineQueryHandler(Handler):
|
||||
def handle_update(self, update, dispatcher):
|
||||
optional_args = self.collect_optional_args(dispatcher)
|
||||
|
||||
self.callback(dispatcher.bot, update, **optional_args)
|
||||
return self.callback(dispatcher.bot, update, **optional_args)
|
||||
|
||||
# old non-PEP8 Handler methods
|
||||
m = "telegram.InlineQueryHandler."
|
||||
|
||||
+181
-80
@@ -16,139 +16,240 @@
|
||||
#
|
||||
# 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 class JobQueue."""
|
||||
"""This module contains the classes JobQueue and Job."""
|
||||
|
||||
import logging
|
||||
import time
|
||||
from threading import Thread, Lock
|
||||
from queue import PriorityQueue
|
||||
from threading import Thread, Lock, Event
|
||||
from queue import PriorityQueue, Empty
|
||||
|
||||
|
||||
class JobQueue(object):
|
||||
"""
|
||||
This class allows you to periodically perform tasks with the bot.
|
||||
"""This class allows you to periodically perform tasks with the bot.
|
||||
|
||||
Attributes:
|
||||
tick_interval (float):
|
||||
queue (PriorityQueue):
|
||||
bot (Bot):
|
||||
running (bool):
|
||||
prevent_autostart (Optional[bool]): If ``True``, the job queue will not be started
|
||||
automatically. Defaults to ``False``
|
||||
|
||||
Args:
|
||||
bot (Bot): The bot instance that should be passed to the jobs
|
||||
tick_interval (Optional[float]): The interval this queue should check
|
||||
the newest task in seconds. Defaults to 1.0
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, bot, tick_interval=1.0):
|
||||
self.tick_interval = tick_interval
|
||||
def __init__(self, bot, prevent_autostart=False):
|
||||
self.queue = PriorityQueue()
|
||||
self.bot = bot
|
||||
self.logger = logging.getLogger(__name__)
|
||||
self.__lock = Lock()
|
||||
self.running = False
|
||||
self.logger = logging.getLogger(self.__class__.__name__)
|
||||
self.__start_lock = Lock()
|
||||
self.__next_peek_lock = Lock() # to protect self._next_peek & self.__tick
|
||||
self.__tick = Event()
|
||||
self.__thread = None
|
||||
""":type: Thread"""
|
||||
self._next_peek = None
|
||||
""":type: float"""
|
||||
self._running = False
|
||||
|
||||
def put(self, run, interval, repeat=True, next_t=None, prevent_autostart=False):
|
||||
"""
|
||||
Queue a new job. If the JobQueue is not running, it will be started.
|
||||
if not prevent_autostart:
|
||||
self.logger.debug('Auto-starting %s', self.__class__.__name__)
|
||||
self.start()
|
||||
|
||||
def put(self, job, next_t=None):
|
||||
"""Queue a new job. If the JobQueue is not running, it will be started.
|
||||
|
||||
Args:
|
||||
run (function): A function that takes the parameter `bot`
|
||||
interval (float): The interval in seconds in which `run` should be
|
||||
executed
|
||||
repeat (Optional[bool]): If `False`, job will only be executed once
|
||||
next_t (Optional[float]): Time in seconds in which run should be
|
||||
executed first. Defaults to `interval`
|
||||
prevent_autostart (Optional[bool]): If `True`, the job queue will
|
||||
not be started automatically if it is not running.
|
||||
"""
|
||||
name = run.__name__
|
||||
job (Job): The ``Job`` instance representing the new job
|
||||
next_t (Optional[float]): Time in seconds in which the job should be executed first.
|
||||
Defaults to ``job.interval``
|
||||
|
||||
job = JobQueue.Job()
|
||||
job.run = run
|
||||
job.interval = interval
|
||||
job.name = name
|
||||
job.repeat = repeat
|
||||
"""
|
||||
job.job_queue = self
|
||||
|
||||
if next_t is None:
|
||||
next_t = interval
|
||||
next_t = job.interval
|
||||
|
||||
next_t += time.time()
|
||||
now = time.time()
|
||||
next_t += now
|
||||
|
||||
self.logger.debug('Putting a %s with t=%f' % (job.name, next_t))
|
||||
self.logger.debug('Putting job %s with t=%f', job.name, next_t)
|
||||
self.queue.put((next_t, job))
|
||||
|
||||
if not self.running and not prevent_autostart:
|
||||
self.logger.debug('Auto-starting JobQueue')
|
||||
self.start()
|
||||
# Wake up the loop if this job should be executed next
|
||||
self._set_next_peek(next_t)
|
||||
|
||||
def _set_next_peek(self, t):
|
||||
"""
|
||||
Set next peek if not defined or `t` is before next peek.
|
||||
In case the next peek was set, also trigger the `self.__tick` event.
|
||||
|
||||
"""
|
||||
with self.__next_peek_lock:
|
||||
if not self._next_peek or self._next_peek > t:
|
||||
self._next_peek = t
|
||||
self.__tick.set()
|
||||
|
||||
def tick(self):
|
||||
"""
|
||||
Run all jobs that are due and re-enqueue them with their interval
|
||||
Run all jobs that are due and re-enqueue them with their interval.
|
||||
|
||||
"""
|
||||
now = time.time()
|
||||
|
||||
self.logger.debug('Ticking jobs with t=%f' % now)
|
||||
while not self.queue.empty():
|
||||
t, j = self.queue.queue[0]
|
||||
self.logger.debug('Peeked at %s with t=%f' % (j.name, t))
|
||||
self.logger.debug('Ticking jobs with t=%f', now)
|
||||
|
||||
if t < now:
|
||||
self.queue.get()
|
||||
self.logger.debug('Running job %s' % j.name)
|
||||
try:
|
||||
j.run(self.bot)
|
||||
except:
|
||||
self.logger.exception('An uncaught error was raised while '
|
||||
'executing job %s' % j.name)
|
||||
if j.repeat:
|
||||
self.put(j.run, j.interval)
|
||||
while True:
|
||||
try:
|
||||
t, job = self.queue.get(False)
|
||||
except Empty:
|
||||
break
|
||||
|
||||
self.logger.debug('Peeked at %s with t=%f', job.name, t)
|
||||
|
||||
if t > now:
|
||||
# we can get here in two conditions:
|
||||
# 1. At the second or later pass of the while loop, after we've already processed
|
||||
# the job(s) we were supposed to at this time.
|
||||
# 2. At the first iteration of the loop only if `self.put()` had triggered
|
||||
# `self.__tick` because `self._next_peek` wasn't set
|
||||
self.logger.debug("Next task isn't due yet. Finished!")
|
||||
self.queue.put((t, job))
|
||||
self._set_next_peek(t)
|
||||
break
|
||||
|
||||
if job._remove.is_set():
|
||||
self.logger.debug('Removing job %s', job.name)
|
||||
continue
|
||||
|
||||
self.logger.debug('Next task isn\'t due yet. Finished!')
|
||||
break
|
||||
if job.enabled:
|
||||
self.logger.debug('Running job %s', job.name)
|
||||
|
||||
try:
|
||||
job.run(self.bot)
|
||||
|
||||
except:
|
||||
self.logger.exception('An uncaught error was raised while executing job %s',
|
||||
job.name)
|
||||
|
||||
else:
|
||||
self.logger.debug('Skipping disabled job %s', job.name)
|
||||
|
||||
if job.repeat:
|
||||
self.put(job)
|
||||
|
||||
def start(self):
|
||||
"""
|
||||
Starts the job_queue thread.
|
||||
|
||||
"""
|
||||
self.__lock.acquire()
|
||||
if not self.running:
|
||||
self.running = True
|
||||
self.__lock.release()
|
||||
job_queue_thread = Thread(target=self._start, name="job_queue")
|
||||
job_queue_thread.start()
|
||||
self.logger.debug('Job Queue thread started')
|
||||
self.__start_lock.acquire()
|
||||
|
||||
if not self._running:
|
||||
self._running = True
|
||||
self.__start_lock.release()
|
||||
self.__thread = Thread(target=self._main_loop, name="job_queue")
|
||||
self.__thread.start()
|
||||
self.logger.debug('%s thread started', self.__class__.__name__)
|
||||
|
||||
else:
|
||||
self.__lock.release()
|
||||
self.__start_lock.release()
|
||||
|
||||
def _start(self):
|
||||
def _main_loop(self):
|
||||
"""
|
||||
Thread target of thread 'job_queue'. Runs in background and performs
|
||||
ticks on the job queue.
|
||||
Thread target of thread ``job_queue``. Runs in background and performs ticks on the job
|
||||
queue.
|
||||
|
||||
"""
|
||||
while self.running:
|
||||
while self._running:
|
||||
# self._next_peek may be (re)scheduled during self.tick() or self.put()
|
||||
with self.__next_peek_lock:
|
||||
tmout = self._next_peek and self._next_peek - time.time()
|
||||
self._next_peek = None
|
||||
self.__tick.clear()
|
||||
|
||||
self.__tick.wait(tmout)
|
||||
|
||||
# If we were woken up by self.stop(), just bail out
|
||||
if not self._running:
|
||||
break
|
||||
|
||||
self.tick()
|
||||
time.sleep(self.tick_interval)
|
||||
|
||||
self.logger.debug('Job Queue thread stopped')
|
||||
self.logger.debug('%s thread stopped', self.__class__.__name__)
|
||||
|
||||
def stop(self):
|
||||
"""
|
||||
Stops the thread
|
||||
"""
|
||||
with self.__lock:
|
||||
self.running = False
|
||||
with self.__start_lock:
|
||||
self._running = False
|
||||
|
||||
class Job(object):
|
||||
""" Inner class that represents a job """
|
||||
interval = None
|
||||
name = None
|
||||
repeat = None
|
||||
self.__tick.set()
|
||||
if self.__thread is not None:
|
||||
self.__thread.join()
|
||||
|
||||
def run(self):
|
||||
pass
|
||||
def jobs(self):
|
||||
"""Returns a tuple of all jobs that are currently in the ``JobQueue``"""
|
||||
return tuple(job[1] for job in self.queue.queue if job)
|
||||
|
||||
def __lt__(self, other):
|
||||
return False
|
||||
|
||||
class Job(object):
|
||||
"""This class encapsulates a Job
|
||||
|
||||
Attributes:
|
||||
callback (function):
|
||||
interval (float):
|
||||
repeat (bool):
|
||||
name (str):
|
||||
enabled (bool): Boolean property that decides if this job is currently active
|
||||
|
||||
Args:
|
||||
callback (function): The callback function that should be executed by the Job. It should
|
||||
take two parameters ``bot`` and ``job``, where ``job`` is the ``Job`` instance. It
|
||||
can be used to terminate the job or modify its interval.
|
||||
interval (float): The interval in which this job should execute its callback function in
|
||||
seconds.
|
||||
repeat (Optional[bool]): If this job should be periodically execute its callback function
|
||||
(``True``) or only once (``False``). Defaults to ``True``
|
||||
context (Optional[object]): Additional data needed for the callback function. Can be
|
||||
accessed through ``job.context`` in the callback. Defaults to ``None``
|
||||
|
||||
"""
|
||||
job_queue = None
|
||||
|
||||
def __init__(self, callback, interval, repeat=True, context=None):
|
||||
self.callback = callback
|
||||
self.interval = interval
|
||||
self.repeat = repeat
|
||||
self.context = context
|
||||
|
||||
self.name = callback.__name__
|
||||
self._remove = Event()
|
||||
self._enabled = Event()
|
||||
self._enabled.set()
|
||||
|
||||
def run(self, bot):
|
||||
"""Executes the callback function"""
|
||||
self.callback(bot, self)
|
||||
|
||||
def schedule_removal(self):
|
||||
"""
|
||||
Schedules this job for removal from the ``JobQueue``. It will be removed without executing
|
||||
its callback function again.
|
||||
|
||||
"""
|
||||
self._remove.set()
|
||||
|
||||
def is_enabled(self):
|
||||
return self._enabled.is_set()
|
||||
|
||||
def set_enabled(self, status):
|
||||
if status:
|
||||
self._enabled.set()
|
||||
else:
|
||||
self._enabled.clear()
|
||||
|
||||
enabled = property(is_enabled, set_enabled)
|
||||
|
||||
def __lt__(self, other):
|
||||
return False
|
||||
|
||||
@@ -105,8 +105,15 @@ class MessageHandler(Handler):
|
||||
be used to insert updates. Default is ``False``
|
||||
"""
|
||||
|
||||
def __init__(self, filters, callback, allow_edited=False, pass_update_queue=False):
|
||||
super(MessageHandler, self).__init__(callback, pass_update_queue)
|
||||
def __init__(self,
|
||||
filters,
|
||||
callback,
|
||||
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)
|
||||
self.filters = filters
|
||||
self.allow_edited = allow_edited
|
||||
|
||||
@@ -129,7 +136,7 @@ class MessageHandler(Handler):
|
||||
def handle_update(self, update, dispatcher):
|
||||
optional_args = self.collect_optional_args(dispatcher)
|
||||
|
||||
self.callback(dispatcher.bot, update, **optional_args)
|
||||
return self.callback(dispatcher.bot, update, **optional_args)
|
||||
|
||||
# old non-PEP8 Handler methods
|
||||
m = "telegram.MessageHandler."
|
||||
|
||||
@@ -45,9 +45,14 @@ class RegexHandler(Handler):
|
||||
pass_groupdict (optional[bool]): If the callback should be passed the
|
||||
result of ``re.match(pattern, text).groupdict()`` as a keyword
|
||||
argument called ``groupdict``. Default is ``False``
|
||||
pass_update_queue (optional[bool]): If the handler should be passed the
|
||||
update queue as a keyword argument called ``update_queue``. It can
|
||||
be used to insert updates. 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
|
||||
be used to insert updates. Default is ``False``.
|
||||
pass_job_queue (optional[bool]): If set to ``True``, a keyword argument called
|
||||
``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``.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
@@ -55,8 +60,11 @@ class RegexHandler(Handler):
|
||||
callback,
|
||||
pass_groups=False,
|
||||
pass_groupdict=False,
|
||||
pass_update_queue=False):
|
||||
super(RegexHandler, self).__init__(callback, pass_update_queue)
|
||||
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)
|
||||
|
||||
if isinstance(pattern, string_types):
|
||||
pattern = re.compile(pattern)
|
||||
@@ -81,7 +89,7 @@ class RegexHandler(Handler):
|
||||
if self.pass_groupdict:
|
||||
optional_args['groupdict'] = match.groupdict()
|
||||
|
||||
self.callback(dispatcher.bot, update, **optional_args)
|
||||
return self.callback(dispatcher.bot, update, **optional_args)
|
||||
|
||||
# old non-PEP8 Handler methods
|
||||
m = "telegram.RegexHandler."
|
||||
|
||||
@@ -36,13 +36,25 @@ class StringCommandHandler(Handler):
|
||||
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``
|
||||
pass_update_queue (optional[bool]): If the handler should be passed the
|
||||
update queue as a keyword argument called ``update_queue``. It can
|
||||
be used to insert updates. 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
|
||||
be used to insert updates. Default is ``False``.
|
||||
pass_job_queue (optional[bool]): If set to ``True``, a keyword argument called
|
||||
``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``.
|
||||
"""
|
||||
|
||||
def __init__(self, command, callback, pass_args=False, pass_update_queue=False):
|
||||
super(StringCommandHandler, self).__init__(callback, pass_update_queue)
|
||||
def __init__(self,
|
||||
command,
|
||||
callback,
|
||||
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)
|
||||
self.command = command
|
||||
self.pass_args = pass_args
|
||||
|
||||
@@ -56,7 +68,7 @@ class StringCommandHandler(Handler):
|
||||
if self.pass_args:
|
||||
optional_args['args'] = update.split(' ')[1:]
|
||||
|
||||
self.callback(dispatcher.bot, update, **optional_args)
|
||||
return self.callback(dispatcher.bot, update, **optional_args)
|
||||
|
||||
# old non-PEP8 Handler methods
|
||||
m = "telegram.StringCommandHandler."
|
||||
|
||||
@@ -44,9 +44,14 @@ class StringRegexHandler(Handler):
|
||||
pass_groupdict (optional[bool]): If the callback should be passed the
|
||||
result of ``re.match(pattern, update).groupdict()`` as a keyword
|
||||
argument called ``groupdict``. Default is ``False``
|
||||
pass_update_queue (optional[bool]): If the handler should be passed the
|
||||
update queue as a keyword argument called ``update_queue``. It can
|
||||
be used to insert updates. 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
|
||||
be used to insert updates. Default is ``False``.
|
||||
pass_job_queue (optional[bool]): If set to ``True``, a keyword argument called
|
||||
``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``.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
@@ -54,8 +59,11 @@ class StringRegexHandler(Handler):
|
||||
callback,
|
||||
pass_groups=False,
|
||||
pass_groupdict=False,
|
||||
pass_update_queue=False):
|
||||
super(StringRegexHandler, self).__init__(callback, pass_update_queue)
|
||||
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)
|
||||
|
||||
if isinstance(pattern, string_types):
|
||||
pattern = re.compile(pattern)
|
||||
@@ -76,7 +84,7 @@ class StringRegexHandler(Handler):
|
||||
if self.pass_groupdict:
|
||||
optional_args['groupdict'] = match.groupdict()
|
||||
|
||||
self.callback(dispatcher.bot, update, **optional_args)
|
||||
return self.callback(dispatcher.bot, update, **optional_args)
|
||||
|
||||
# old non-PEP8 Handler methods
|
||||
m = "telegram.StringRegexHandler."
|
||||
|
||||
@@ -34,13 +34,25 @@ class TypeHandler(Handler):
|
||||
has determined that an update should be processed by this handler.
|
||||
strict (optional[bool]): Use ``type`` instead of ``isinstance``.
|
||||
Default is ``False``
|
||||
pass_update_queue (optional[bool]): If the handler should be passed the
|
||||
update queue as a keyword argument called ``update_queue``. It can
|
||||
be used to insert updates. 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
|
||||
be used to insert updates. Default is ``False``.
|
||||
pass_job_queue (optional[bool]): If set to ``True``, a keyword argument called
|
||||
``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``.
|
||||
"""
|
||||
|
||||
def __init__(self, type, callback, strict=False, pass_update_queue=False):
|
||||
super(TypeHandler, self).__init__(callback, pass_update_queue)
|
||||
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)
|
||||
self.type = type
|
||||
self.strict = strict
|
||||
|
||||
@@ -53,7 +65,7 @@ class TypeHandler(Handler):
|
||||
def handle_update(self, update, dispatcher):
|
||||
optional_args = self.collect_optional_args(dispatcher)
|
||||
|
||||
self.callback(dispatcher.bot, update, **optional_args)
|
||||
return self.callback(dispatcher.bot, update, **optional_args)
|
||||
|
||||
# old non-PEP8 Handler methods
|
||||
m = "telegram.TypeHandler."
|
||||
|
||||
+24
-20
@@ -65,12 +65,7 @@ class Updater(object):
|
||||
ValueError: If both `token` and `bot` are passed or none of them.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
token=None,
|
||||
base_url=None,
|
||||
workers=4,
|
||||
bot=None,
|
||||
job_queue_tick_interval=1.0):
|
||||
def __init__(self, token=None, base_url=None, workers=4, bot=None):
|
||||
if (token is None) and (bot is None):
|
||||
raise ValueError('`token` or `bot` must be passed')
|
||||
if (token is not None) and (bot is not None):
|
||||
@@ -81,9 +76,13 @@ class Updater(object):
|
||||
else:
|
||||
self.bot = Bot(token, base_url)
|
||||
self.update_queue = Queue()
|
||||
self.job_queue = JobQueue(self.bot, job_queue_tick_interval)
|
||||
self.job_queue = JobQueue(self.bot)
|
||||
self.__exception_event = Event()
|
||||
self.dispatcher = Dispatcher(self.bot, self.update_queue, workers, 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
|
||||
@@ -309,7 +308,7 @@ class Updater(object):
|
||||
|
||||
def _bootstrap(self, max_retries, clean, webhook_url, cert=None):
|
||||
retries = 0
|
||||
while True:
|
||||
while 1:
|
||||
|
||||
try:
|
||||
if clean:
|
||||
@@ -346,7 +345,7 @@ class Updater(object):
|
||||
|
||||
self.job_queue.stop()
|
||||
with self.__lock:
|
||||
if self.running:
|
||||
if self.running or dispatcher.ASYNC_THREADS:
|
||||
self.logger.debug('Stopping Updater and Dispatcher...')
|
||||
|
||||
self.running = False
|
||||
@@ -354,9 +353,8 @@ 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
|
||||
# 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()
|
||||
|
||||
def _stop_httpd(self):
|
||||
@@ -372,13 +370,19 @@ class Updater(object):
|
||||
self.dispatcher.stop()
|
||||
|
||||
def _join_async_threads(self):
|
||||
with dispatcher.async_lock:
|
||||
threads = list(dispatcher.async_threads)
|
||||
total = len(threads)
|
||||
for i, thr in enumerate(threads):
|
||||
self.logger.debug('Waiting for async thread {0}/{1} to end'.format(i, total))
|
||||
thr.join()
|
||||
self.logger.debug('async thread {0}/{1} has ended'.format(i, total))
|
||||
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:
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
#!/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 Lesser 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 Lesser Public License for more details.
|
||||
#
|
||||
# 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 Promise class """
|
||||
|
||||
from threading import Event
|
||||
|
||||
|
||||
class Promise(object):
|
||||
"""A simple Promise implementation for the run_async decorator"""
|
||||
|
||||
def __init__(self, pooled_function, args, kwargs):
|
||||
self.pooled_function = pooled_function
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
self.done = Event()
|
||||
self._result = None
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
self._result = self.pooled_function(*self.args, **self.kwargs)
|
||||
|
||||
except:
|
||||
raise
|
||||
|
||||
finally:
|
||||
self.done.set()
|
||||
|
||||
def result(self, timeout=None):
|
||||
self.done.wait(timeout=timeout)
|
||||
return self._result
|
||||
+143
-56
@@ -18,29 +18,116 @@
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
"""This module contains methods to make POST and GET requests"""
|
||||
|
||||
import functools
|
||||
import json
|
||||
import os
|
||||
import socket
|
||||
from ssl import SSLError
|
||||
import logging
|
||||
|
||||
from future.moves.http.client import HTTPException
|
||||
from future.moves.urllib.error import HTTPError, URLError
|
||||
from future.moves.urllib.request import urlopen, urlretrieve, Request
|
||||
import certifi
|
||||
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
|
||||
|
||||
def _parse(json_data):
|
||||
"""Try and parse the JSON returned from Telegram and return an empty
|
||||
dictionary if there is any error.
|
||||
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
|
||||
|
||||
Args:
|
||||
url:
|
||||
urllib.urlopen object
|
||||
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:
|
||||
A JSON parsed as Python dict with results.
|
||||
dict: A JSON parsed as Python dict with results - on error this dict will be empty.
|
||||
|
||||
"""
|
||||
decoded_s = json_data.decode('utf-8')
|
||||
try:
|
||||
@@ -54,53 +141,49 @@ def _parse(json_data):
|
||||
return data['result']
|
||||
|
||||
|
||||
def _try_except_req(func):
|
||||
"""Decorator for requests to handle known exceptions"""
|
||||
def _request_wrapper(*args, **kwargs):
|
||||
"""Wraps urllib3 request for handling known exceptions.
|
||||
|
||||
@functools.wraps(func)
|
||||
def decorator(*args, **kwargs):
|
||||
try:
|
||||
return func(*args, **kwargs)
|
||||
Args:
|
||||
args: unnamed arguments, passed to urllib3 request.
|
||||
kwargs: keyword arguments, passed tp urllib3 request.
|
||||
|
||||
except HTTPError as error:
|
||||
# `HTTPError` inherits from `URLError` so `HTTPError` handling must
|
||||
# come first.
|
||||
errcode = error.getcode()
|
||||
Returns:
|
||||
str: A non-parsed JSON text.
|
||||
|
||||
try:
|
||||
message = _parse(error.read())
|
||||
Raises:
|
||||
TelegramError
|
||||
|
||||
if errcode in (401, 403):
|
||||
raise Unauthorized()
|
||||
elif errcode == 400:
|
||||
raise BadRequest(message)
|
||||
elif errcode == 502:
|
||||
raise NetworkError('Bad Gateway')
|
||||
except ValueError:
|
||||
message = 'Unknown HTTPError {0}'.format(error.getcode())
|
||||
"""
|
||||
|
||||
raise NetworkError('{0} ({1})'.format(message, errcode))
|
||||
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))
|
||||
|
||||
except URLError as error:
|
||||
raise NetworkError('URLError: {0}'.format(error.reason))
|
||||
if 200 <= resp.status <= 299:
|
||||
# 200-299 range are HTTP success statuses
|
||||
return resp.data
|
||||
|
||||
except (SSLError, socket.timeout) as error:
|
||||
err_s = str(error)
|
||||
if 'operation timed out' in err_s:
|
||||
raise TimedOut()
|
||||
try:
|
||||
message = _parse(resp.data)
|
||||
except ValueError:
|
||||
raise NetworkError('Unknown HTTPError {0}'.format(resp.status))
|
||||
|
||||
raise NetworkError(err_s)
|
||||
|
||||
except HTTPException as error:
|
||||
raise NetworkError('HTTPException: {0!r}'.format(error))
|
||||
|
||||
except socket.error as error:
|
||||
raise NetworkError('socket.error: {0!r}'.format(error))
|
||||
|
||||
return decorator
|
||||
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))
|
||||
|
||||
|
||||
@_try_except_req
|
||||
def get(url):
|
||||
"""Request an URL.
|
||||
Args:
|
||||
@@ -109,13 +192,13 @@ def get(url):
|
||||
|
||||
Returns:
|
||||
A JSON object.
|
||||
|
||||
"""
|
||||
result = urlopen(url).read()
|
||||
result = _request_wrapper('GET', url)
|
||||
|
||||
return _parse(result)
|
||||
|
||||
|
||||
@_try_except_req
|
||||
def post(url, data, timeout=None):
|
||||
"""Request an URL.
|
||||
Args:
|
||||
@@ -142,16 +225,18 @@ def post(url, data, timeout=None):
|
||||
|
||||
if InputFile.is_inputfile(data):
|
||||
data = InputFile(data)
|
||||
request = Request(url, data=data.to_form(), headers=data.headers)
|
||||
result = _request_wrapper('POST', url, body=data.to_form(), headers=data.headers)
|
||||
else:
|
||||
data = json.dumps(data)
|
||||
request = Request(url, data=data.encode(), headers={'Content-Type': 'application/json'})
|
||||
result = _request_wrapper('POST',
|
||||
url,
|
||||
body=data.encode(),
|
||||
headers={'Content-Type': 'application/json'},
|
||||
**urlopen_kwargs)
|
||||
|
||||
result = urlopen(request, **urlopen_kwargs).read()
|
||||
return _parse(result)
|
||||
|
||||
|
||||
@_try_except_req
|
||||
def download(url, filename):
|
||||
"""Download a file by its URL.
|
||||
Args:
|
||||
@@ -160,6 +245,8 @@ def download(url, filename):
|
||||
|
||||
filename:
|
||||
The filename within the path to download the file.
|
||||
"""
|
||||
|
||||
urlretrieve(url, filename)
|
||||
"""
|
||||
buf = _request_wrapper('GET', url)
|
||||
with open(filename, 'wb') as fobj:
|
||||
fobj.write(buf)
|
||||
|
||||
@@ -102,3 +102,19 @@ class WebhookHandler(BaseHTTPServer.BaseHTTPRequestHandler, object):
|
||||
if clen < 0:
|
||||
raise _InvalidPost(403)
|
||||
return clen
|
||||
|
||||
def log_message(self, format, *args):
|
||||
"""Log an arbitrary message.
|
||||
|
||||
This is used by all other logging functions.
|
||||
|
||||
It overrides ``BaseHTTPRequestHandler.log_message``, which logs to ``sys.stderr``.
|
||||
|
||||
The first argument, FORMAT, is a format string for the message to be logged. If the format
|
||||
string contains any % escapes requiring parameters, they should be specified as subsequent
|
||||
arguments (it's just like printf!).
|
||||
|
||||
The client ip is prefixed to every message.
|
||||
"""
|
||||
|
||||
self.logger.debug("%s - - %s" % (self.address_string(), format % args))
|
||||
|
||||
+4
-2
@@ -20,6 +20,7 @@
|
||||
"""This module contains a object that represents Tests for Telegram Bot"""
|
||||
|
||||
import io
|
||||
import re
|
||||
from datetime import datetime
|
||||
import sys
|
||||
|
||||
@@ -211,10 +212,11 @@ class BotTest(BaseTest, unittest.TestCase):
|
||||
@flaky(3, 1)
|
||||
@timeout(10)
|
||||
def testLeaveChat(self):
|
||||
with self.assertRaisesRegexp(telegram.error.BadRequest, 'Chat not found'):
|
||||
regex = re.compile('chat not found', re.IGNORECASE)
|
||||
with self.assertRaisesRegexp(telegram.error.BadRequest, regex):
|
||||
chat = self._bot.leaveChat(-123456)
|
||||
|
||||
with self.assertRaisesRegexp(telegram.error.NetworkError, 'Chat not found'):
|
||||
with self.assertRaisesRegexp(telegram.error.NetworkError, regex):
|
||||
chat = self._bot.leaveChat(-123456)
|
||||
|
||||
@flaky(3, 1)
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
# python-telegram-bot - a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2015-2016
|
||||
# by the python-telegram-bot contributors <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser 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 Lesser Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser Public License
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
"""Test the Telegram constants."""
|
||||
|
||||
import sys
|
||||
|
||||
from flaky import flaky
|
||||
|
||||
if sys.version_info[0:2] == (2, 6):
|
||||
import unittest2 as unittest
|
||||
else:
|
||||
import unittest
|
||||
|
||||
sys.path.append('.')
|
||||
|
||||
import telegram
|
||||
from telegram.error import BadRequest
|
||||
from tests.base import BaseTest, timeout
|
||||
|
||||
|
||||
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)
|
||||
|
||||
try:
|
||||
self._bot.sendMessage(chat_id=self._chat_id,
|
||||
text='a' * (telegram.constants.MAX_MESSAGE_LENGTH + 1))
|
||||
except BadRequest as e:
|
||||
err = str(e)
|
||||
|
||||
self.assertTrue("too long" in err) # BadRequest: 'Message is too long'
|
||||
|
||||
@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)
|
||||
|
||||
try:
|
||||
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)
|
||||
|
||||
self.assertTrue("TOO_LONG" in err) # BadRequest: 'MEDIA_CAPTION_TOO_LONG'
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -0,0 +1,164 @@
|
||||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
#
|
||||
# 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 ConversationHandler
|
||||
"""
|
||||
import logging
|
||||
import sys
|
||||
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
|
||||
except ImportError:
|
||||
# python3
|
||||
from urllib.request import Request, urlopen
|
||||
from urllib.error import HTTPError
|
||||
|
||||
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 tests.base import BaseTest
|
||||
from tests.test_updater import MockBot
|
||||
|
||||
# Enable logging
|
||||
root = logging.getLogger()
|
||||
root.setLevel(logging.DEBUG)
|
||||
|
||||
ch = logging.StreamHandler(sys.stdout)
|
||||
ch.setLevel(logging.WARN)
|
||||
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s ' '- %(message)s')
|
||||
ch.setFormatter(formatter)
|
||||
root.addHandler(ch)
|
||||
|
||||
|
||||
class ConversationHandlerTest(BaseTest, unittest.TestCase):
|
||||
"""
|
||||
This object represents the tests for the conversation handler.
|
||||
"""
|
||||
|
||||
# State definitions
|
||||
# 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)
|
||||
|
||||
# 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),
|
||||
CommandHandler('wait', self.start)],
|
||||
self.BREWING: [CommandHandler('pourCoffee', self.drink)],
|
||||
self.DRINKING: [CommandHandler('startCoding', self.code),
|
||||
CommandHandler('drinkMore', self.drink)],
|
||||
self.CODING: [CommandHandler('keepCoding', self.code),
|
||||
CommandHandler('gettingThirsty', self.start),
|
||||
CommandHandler('drinkMore', self.drink)],}
|
||||
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()
|
||||
|
||||
def reset(self):
|
||||
self.current_state = dict()
|
||||
|
||||
# State handlers
|
||||
def _set_state(self, update, state):
|
||||
self.current_state[update.message.from_user.id] = state
|
||||
return state
|
||||
|
||||
def _get_state(self, user_id):
|
||||
return self.current_state[user_id]
|
||||
|
||||
# Actions
|
||||
def start(self, bot, update):
|
||||
return self._set_state(update, self.THIRSTY)
|
||||
|
||||
def brew(self, bot, update):
|
||||
return self._set_state(update, self.BREWING)
|
||||
|
||||
def drink(self, bot, update):
|
||||
return self._set_state(update, self.DRINKING)
|
||||
|
||||
def code(self, bot, update):
|
||||
return self._set_state(update, self.CODING)
|
||||
|
||||
# Tests
|
||||
def test_addConversationHandler(self):
|
||||
self._setup_updater('', messages=0)
|
||||
d = self.updater.dispatcher
|
||||
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)
|
||||
d.add_handler(handler)
|
||||
queue = self.updater.start_polling(0.01)
|
||||
|
||||
# User one, starts the state machine.
|
||||
message = Message(0, user, None, None, text="/start")
|
||||
queue.put(Update(update_id=0, message=message))
|
||||
sleep(.1)
|
||||
self.assertTrue(self.current_state[user.id] == self.THIRSTY)
|
||||
|
||||
# The user is thirsty and wants to brew coffee.
|
||||
message = Message(0, user, None, None, text="/brew")
|
||||
queue.put(Update(update_id=0, message=message))
|
||||
sleep(.1)
|
||||
self.assertTrue(self.current_state[user.id] == self.BREWING)
|
||||
|
||||
# Lets see if an invalid command makes sure, no state is changed.
|
||||
message = Message(0, user, None, None, text="/nothing")
|
||||
queue.put(Update(update_id=0, message=message))
|
||||
sleep(.1)
|
||||
self.assertTrue(self.current_state[user.id] == self.BREWING)
|
||||
|
||||
# Lets see if the state machine still works by pouring coffee.
|
||||
message = Message(0, user, None, None, text="/pourCoffee")
|
||||
queue.put(Update(update_id=0, message=message))
|
||||
sleep(.1)
|
||||
self.assertTrue(self.current_state[user.id] == self.DRINKING)
|
||||
|
||||
# Let's now verify that for another user, who did not start yet,
|
||||
# the state has not been changed.
|
||||
message = Message(0, second_user, None, None, text="/brew")
|
||||
queue.put(Update(update_id=0, message=message))
|
||||
sleep(.1)
|
||||
self.assertRaises(KeyError, self._get_state, user_id=second_user.id)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
+81
-17
@@ -31,7 +31,8 @@ else:
|
||||
|
||||
sys.path.append('.')
|
||||
|
||||
from telegram.ext import JobQueue, Updater
|
||||
from telegram.utils.request import stop_con_pool
|
||||
from telegram.ext import JobQueue, Job, Updater
|
||||
from tests.base import BaseTest
|
||||
|
||||
# Enable logging
|
||||
@@ -52,53 +53,116 @@ class JobQueueTest(BaseTest, unittest.TestCase):
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.jq = JobQueue("Bot", tick_interval=0.005)
|
||||
self.jq = JobQueue("Bot")
|
||||
self.result = 0
|
||||
|
||||
def tearDown(self):
|
||||
if self.jq is not None:
|
||||
self.jq.stop()
|
||||
stop_con_pool()
|
||||
|
||||
def job1(self, bot):
|
||||
def job1(self, bot, job):
|
||||
self.result += 1
|
||||
|
||||
def job2(self, bot):
|
||||
def job2(self, bot, job):
|
||||
raise Exception("Test Error")
|
||||
|
||||
def job3(self, bot, job):
|
||||
self.result += 1
|
||||
job.schedule_removal()
|
||||
|
||||
def job4(self, bot, job):
|
||||
self.result += job.context
|
||||
|
||||
def test_basic(self):
|
||||
self.jq.put(self.job1, 0.1)
|
||||
self.jq.put(Job(self.job1, 0.1))
|
||||
sleep(1.5)
|
||||
self.assertGreaterEqual(self.result, 10)
|
||||
|
||||
def test_job_with_context(self):
|
||||
self.jq.put(Job(self.job4, 0.1, context=5))
|
||||
sleep(1.5)
|
||||
self.assertGreaterEqual(self.result, 50)
|
||||
|
||||
def test_noRepeat(self):
|
||||
self.jq.put(self.job1, 0.1, repeat=False)
|
||||
self.jq.put(Job(self.job1, 0.1, repeat=False))
|
||||
sleep(0.5)
|
||||
self.assertEqual(1, self.result)
|
||||
|
||||
def test_nextT(self):
|
||||
self.jq.put(self.job1, 0.1, next_t=0.5)
|
||||
self.jq.put(Job(self.job1, 0.1), next_t=0.5)
|
||||
sleep(0.45)
|
||||
self.assertEqual(0, self.result)
|
||||
sleep(0.1)
|
||||
self.assertEqual(1, self.result)
|
||||
|
||||
def test_multiple(self):
|
||||
self.jq.put(self.job1, 0.1, repeat=False)
|
||||
self.jq.put(self.job1, 0.2, repeat=False)
|
||||
self.jq.put(self.job1, 0.4)
|
||||
self.jq.put(Job(self.job1, 0.1, repeat=False))
|
||||
self.jq.put(Job(self.job1, 0.2, repeat=False))
|
||||
self.jq.put(Job(self.job1, 0.4))
|
||||
sleep(1)
|
||||
self.assertEqual(4, self.result)
|
||||
|
||||
def test_error(self):
|
||||
self.jq.put(self.job2, 0.1)
|
||||
self.jq.put(self.job1, 0.2)
|
||||
self.jq.start()
|
||||
sleep(0.4)
|
||||
def test_disabled(self):
|
||||
j0 = Job(self.job1, 0.1)
|
||||
j1 = Job(self.job1, 0.2)
|
||||
|
||||
self.jq.put(j0)
|
||||
self.jq.put(Job(self.job1, 0.4))
|
||||
self.jq.put(j1)
|
||||
|
||||
j0.enabled = False
|
||||
j1.enabled = False
|
||||
|
||||
sleep(1)
|
||||
self.assertEqual(2, self.result)
|
||||
|
||||
def test_schedule_removal(self):
|
||||
j0 = Job(self.job1, 0.1)
|
||||
j1 = Job(self.job1, 0.2)
|
||||
|
||||
self.jq.put(j0)
|
||||
self.jq.put(Job(self.job1, 0.4))
|
||||
self.jq.put(j1)
|
||||
|
||||
j0.schedule_removal()
|
||||
j1.schedule_removal()
|
||||
|
||||
sleep(1)
|
||||
self.assertEqual(2, self.result)
|
||||
|
||||
def test_schedule_removal_from_within(self):
|
||||
self.jq.put(Job(self.job1, 0.4))
|
||||
self.jq.put(Job(self.job3, 0.2))
|
||||
|
||||
sleep(1)
|
||||
self.assertEqual(3, self.result)
|
||||
|
||||
def test_longer_first(self):
|
||||
self.jq.put(Job(self.job1, 0.2, repeat=False))
|
||||
self.jq.put(Job(self.job1, 0.1, repeat=False))
|
||||
sleep(0.15)
|
||||
self.assertEqual(1, self.result)
|
||||
|
||||
def test_error(self):
|
||||
self.jq.put(Job(self.job2, 0.1))
|
||||
self.jq.put(Job(self.job1, 0.2))
|
||||
self.jq.start()
|
||||
sleep(0.5)
|
||||
self.assertEqual(2, self.result)
|
||||
|
||||
def test_jobs_tuple(self):
|
||||
self.jq.stop()
|
||||
jobs = tuple(Job(self.job1, t) for t in range(5, 25))
|
||||
|
||||
for job in jobs:
|
||||
self.jq.put(job)
|
||||
|
||||
self.assertTupleEqual(jobs, self.jq.jobs())
|
||||
|
||||
def test_inUpdater(self):
|
||||
u = Updater(bot="MockBot", job_queue_tick_interval=0.005)
|
||||
u.job_queue.put(self.job1, 0.5)
|
||||
u = Updater(bot="MockBot")
|
||||
u.job_queue.put(Job(self.job1, 0.5))
|
||||
sleep(0.75)
|
||||
self.assertEqual(1, self.result)
|
||||
u.stop()
|
||||
|
||||
+2
-3
@@ -35,15 +35,14 @@ class PhotoTest(BaseTest, unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.photo_file = open('tests/data/telegram.jpg', 'rb')
|
||||
self.photo_file_id = 'AgADAQADvb8xGx8j9QcpZDKxYoFK3bfX1i8ABFX_dgMWoKDuQugAAgI'
|
||||
self.photo_file_id = 'AgADAQADgEsyGx8j9QfmDMmwkPBrFcKRzy8ABHW8ul9nW7FoNHYBAAEC'
|
||||
self.photo_file_url = 'https://raw.githubusercontent.com/python-telegram-bot/python-telegram-bot/master/tests/data/telegram.jpg'
|
||||
self.width = 300
|
||||
self.height = 300
|
||||
self.thumb = {
|
||||
'width': 90,
|
||||
'height': 90,
|
||||
'file_id':
|
||||
'AgADAQADvb8xGx8j9QcpZDKxYoFK3bfX1i8ABBxRLXFhLnhIQ-gAAgI',
|
||||
'file_id': 'AgADAQADgEsyGx8j9QfmDMmwkPBrFcKRzy8ABD64nkFkjujeNXYBAAEC',
|
||||
'file_size': 1478
|
||||
}
|
||||
self.file_size = 10209
|
||||
|
||||
+46
-34
@@ -48,6 +48,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.dispatcher import run_async
|
||||
from telegram.error import Unauthorized, InvalidToken
|
||||
@@ -71,6 +72,11 @@ class UpdaterTest(BaseTest, unittest.TestCase):
|
||||
WebhookHandler
|
||||
"""
|
||||
|
||||
updater = None
|
||||
received_message = None
|
||||
message_count = None
|
||||
lock = None
|
||||
|
||||
def setUp(self):
|
||||
self.updater = None
|
||||
self.received_message = None
|
||||
@@ -78,12 +84,14 @@ class UpdaterTest(BaseTest, unittest.TestCase):
|
||||
self.lock = Lock()
|
||||
|
||||
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()
|
||||
|
||||
def reset(self):
|
||||
self.message_count = 0
|
||||
@@ -120,9 +128,12 @@ class UpdaterTest(BaseTest, unittest.TestCase):
|
||||
self.received_message = (groups, groupdict)
|
||||
self.message_count += 1
|
||||
|
||||
def additionalArgsTest(self, bot, update, update_queue, args):
|
||||
def additionalArgsTest(self, bot, update, update_queue, job_queue, args):
|
||||
job_queue.put(Job(lambda bot, job: job.schedule_removal(), 0.1))
|
||||
|
||||
self.received_message = update
|
||||
self.message_count += 1
|
||||
|
||||
if args[0] == 'resend':
|
||||
update_queue.put('/test5 noresend')
|
||||
elif args[0] == 'noresend':
|
||||
@@ -148,13 +159,13 @@ class UpdaterTest(BaseTest, unittest.TestCase):
|
||||
d = self.updater.dispatcher
|
||||
from telegram.ext import Filters
|
||||
handler = MessageHandler([Filters.text], self.telegramHandlerTest)
|
||||
d.addHandler(handler)
|
||||
d.add_handler(handler)
|
||||
self.updater.start_polling(0.01)
|
||||
sleep(.1)
|
||||
self.assertEqual(self.received_message, 'Test')
|
||||
|
||||
# Remove handler
|
||||
d.removeHandler(handler)
|
||||
d.remove_handler(handler)
|
||||
self.reset()
|
||||
|
||||
self.updater.bot.send_messages = 1
|
||||
@@ -185,7 +196,7 @@ class UpdaterTest(BaseTest, unittest.TestCase):
|
||||
|
||||
def test_addTelegramMessageHandlerMultipleMessages(self):
|
||||
self._setup_updater('Multiple', 100)
|
||||
self.updater.dispatcher.addHandler(MessageHandler([], self.telegramHandlerTest))
|
||||
self.updater.dispatcher.add_handler(MessageHandler([], self.telegramHandlerTest))
|
||||
self.updater.start_polling(0.0)
|
||||
sleep(2)
|
||||
self.assertEqual(self.received_message, 'Multiple')
|
||||
@@ -196,13 +207,13 @@ class UpdaterTest(BaseTest, unittest.TestCase):
|
||||
d = self.updater.dispatcher
|
||||
regobj = re.compile('Te.*')
|
||||
handler = RegexHandler(regobj, self.telegramHandlerTest)
|
||||
self.updater.dispatcher.addHandler(handler)
|
||||
self.updater.dispatcher.add_handler(handler)
|
||||
self.updater.start_polling(0.01)
|
||||
sleep(.1)
|
||||
self.assertEqual(self.received_message, 'Test2')
|
||||
|
||||
# Remove handler
|
||||
d.removeHandler(handler)
|
||||
d.remove_handler(handler)
|
||||
self.reset()
|
||||
|
||||
self.updater.bot.send_messages = 1
|
||||
@@ -213,13 +224,13 @@ class UpdaterTest(BaseTest, unittest.TestCase):
|
||||
self._setup_updater('/test')
|
||||
d = self.updater.dispatcher
|
||||
handler = CommandHandler('test', self.telegramHandlerTest)
|
||||
self.updater.dispatcher.addHandler(handler)
|
||||
self.updater.dispatcher.add_handler(handler)
|
||||
self.updater.start_polling(0.01)
|
||||
sleep(.1)
|
||||
self.assertEqual(self.received_message, '/test')
|
||||
|
||||
# Remove handler
|
||||
d.removeHandler(handler)
|
||||
d.remove_handler(handler)
|
||||
self.reset()
|
||||
|
||||
self.updater.bot.send_messages = 1
|
||||
@@ -249,14 +260,14 @@ class UpdaterTest(BaseTest, unittest.TestCase):
|
||||
self._setup_updater('', messages=0)
|
||||
d = self.updater.dispatcher
|
||||
handler = StringRegexHandler('Te.*', self.stringHandlerTest)
|
||||
d.addHandler(handler)
|
||||
d.add_handler(handler)
|
||||
queue = self.updater.start_polling(0.01)
|
||||
queue.put('Test3')
|
||||
sleep(.1)
|
||||
self.assertEqual(self.received_message, 'Test3')
|
||||
|
||||
# Remove handler
|
||||
d.removeHandler(handler)
|
||||
d.remove_handler(handler)
|
||||
self.reset()
|
||||
|
||||
queue.put('Test3')
|
||||
@@ -267,7 +278,7 @@ class UpdaterTest(BaseTest, unittest.TestCase):
|
||||
self._setup_updater('', messages=0)
|
||||
d = self.updater.dispatcher
|
||||
handler = StringCommandHandler('test3', self.stringHandlerTest)
|
||||
d.addHandler(handler)
|
||||
d.add_handler(handler)
|
||||
|
||||
queue = self.updater.start_polling(0.01)
|
||||
queue.put('/test3')
|
||||
@@ -275,7 +286,7 @@ class UpdaterTest(BaseTest, unittest.TestCase):
|
||||
self.assertEqual(self.received_message, '/test3')
|
||||
|
||||
# Remove handler
|
||||
d.removeHandler(handler)
|
||||
d.remove_handler(handler)
|
||||
self.reset()
|
||||
|
||||
queue.put('/test3')
|
||||
@@ -285,7 +296,7 @@ class UpdaterTest(BaseTest, unittest.TestCase):
|
||||
def test_addRemoveErrorHandler(self):
|
||||
self._setup_updater('', messages=0)
|
||||
d = self.updater.dispatcher
|
||||
d.addErrorHandler(self.errorHandlerTest)
|
||||
d.add_error_handler(self.errorHandlerTest)
|
||||
queue = self.updater.start_polling(0.01)
|
||||
error = TelegramError("Unauthorized.")
|
||||
queue.put(error)
|
||||
@@ -293,7 +304,7 @@ class UpdaterTest(BaseTest, unittest.TestCase):
|
||||
self.assertEqual(self.received_message, "Unauthorized.")
|
||||
|
||||
# Remove handler
|
||||
d.removeErrorHandler(self.errorHandlerTest)
|
||||
d.remove_error_handler(self.errorHandlerTest)
|
||||
self.reset()
|
||||
|
||||
queue.put(error)
|
||||
@@ -304,8 +315,8 @@ class UpdaterTest(BaseTest, unittest.TestCase):
|
||||
self._setup_updater('', messages=0)
|
||||
d = self.updater.dispatcher
|
||||
handler = StringRegexHandler('.*', self.errorRaisingHandlerTest)
|
||||
d.addHandler(handler)
|
||||
self.updater.dispatcher.addErrorHandler(self.errorHandlerTest)
|
||||
d.add_handler(handler)
|
||||
self.updater.dispatcher.add_error_handler(self.errorHandlerTest)
|
||||
queue = self.updater.start_polling(0.01)
|
||||
|
||||
queue.put('Test Error 1')
|
||||
@@ -316,7 +327,7 @@ class UpdaterTest(BaseTest, unittest.TestCase):
|
||||
self._setup_updater('')
|
||||
d = self.updater.dispatcher
|
||||
handler = MessageHandler([], self.telegramHandlerTest)
|
||||
d.addHandler(handler)
|
||||
d.add_handler(handler)
|
||||
self.updater.start_polling(0.01, clean=True)
|
||||
sleep(.1)
|
||||
self.assertEqual(self.message_count, 0)
|
||||
@@ -325,7 +336,7 @@ class UpdaterTest(BaseTest, unittest.TestCase):
|
||||
def test_errorOnGetUpdates(self):
|
||||
self._setup_updater('', raise_error=True)
|
||||
d = self.updater.dispatcher
|
||||
d.addErrorHandler(self.errorHandlerTest)
|
||||
d.add_error_handler(self.errorHandlerTest)
|
||||
self.updater.start_polling(0.01)
|
||||
sleep(.1)
|
||||
self.assertEqual(self.received_message, "Test Error 2")
|
||||
@@ -334,7 +345,7 @@ class UpdaterTest(BaseTest, unittest.TestCase):
|
||||
self._setup_updater('', messages=0)
|
||||
d = self.updater.dispatcher
|
||||
handler = TypeHandler(dict, self.stringHandlerTest)
|
||||
d.addHandler(handler)
|
||||
d.add_handler(handler)
|
||||
queue = self.updater.start_polling(0.01)
|
||||
payload = {"Test": 42}
|
||||
queue.put(payload)
|
||||
@@ -342,7 +353,7 @@ class UpdaterTest(BaseTest, unittest.TestCase):
|
||||
self.assertEqual(self.received_message, payload)
|
||||
|
||||
# Remove handler
|
||||
d.removeHandler(handler)
|
||||
d.remove_handler(handler)
|
||||
self.reset()
|
||||
|
||||
queue.put(payload)
|
||||
@@ -354,8 +365,8 @@ class UpdaterTest(BaseTest, unittest.TestCase):
|
||||
d = self.updater.dispatcher
|
||||
handler = InlineQueryHandler(self.telegramInlineHandlerTest)
|
||||
handler2 = ChosenInlineResultHandler(self.telegramInlineHandlerTest)
|
||||
d.addHandler(handler)
|
||||
d.addHandler(handler2)
|
||||
d.add_handler(handler)
|
||||
d.add_handler(handler2)
|
||||
queue = self.updater.start_polling(0.01)
|
||||
update = Update(update_id=0, inline_query="testquery")
|
||||
update2 = Update(update_id=0, chosen_inline_result="testresult")
|
||||
@@ -368,8 +379,8 @@ class UpdaterTest(BaseTest, unittest.TestCase):
|
||||
self.assertEqual(self.received_message[1], "testresult")
|
||||
|
||||
# Remove handler
|
||||
d.removeHandler(handler)
|
||||
d.removeHandler(handler2)
|
||||
d.remove_handler(handler)
|
||||
d.remove_handler(handler2)
|
||||
self.reset()
|
||||
|
||||
queue.put(update)
|
||||
@@ -380,7 +391,7 @@ class UpdaterTest(BaseTest, unittest.TestCase):
|
||||
self._setup_updater('', messages=0)
|
||||
d = self.updater.dispatcher
|
||||
handler = CallbackQueryHandler(self.telegramCallbackHandlerTest)
|
||||
d.addHandler(handler)
|
||||
d.add_handler(handler)
|
||||
queue = self.updater.start_polling(0.01)
|
||||
update = Update(update_id=0, callback_query="testcallback")
|
||||
queue.put(update)
|
||||
@@ -388,7 +399,7 @@ class UpdaterTest(BaseTest, unittest.TestCase):
|
||||
self.assertEqual(self.received_message, "testcallback")
|
||||
|
||||
# Remove handler
|
||||
d.removeHandler(handler)
|
||||
d.remove_handler(handler)
|
||||
self.reset()
|
||||
|
||||
queue.put(update)
|
||||
@@ -399,7 +410,7 @@ class UpdaterTest(BaseTest, unittest.TestCase):
|
||||
self._setup_updater('Test5', messages=2)
|
||||
d = self.updater.dispatcher
|
||||
handler = MessageHandler([], self.asyncHandlerTest)
|
||||
d.addHandler(handler)
|
||||
d.add_handler(handler)
|
||||
self.updater.start_polling(0.01)
|
||||
sleep(1.2)
|
||||
self.assertEqual(self.received_message, 'Test5')
|
||||
@@ -410,8 +421,9 @@ class UpdaterTest(BaseTest, unittest.TestCase):
|
||||
handler = StringCommandHandler('test5',
|
||||
self.additionalArgsTest,
|
||||
pass_update_queue=True,
|
||||
pass_job_queue=True,
|
||||
pass_args=True)
|
||||
self.updater.dispatcher.addHandler(handler)
|
||||
self.updater.dispatcher.add_handler(handler)
|
||||
|
||||
queue = self.updater.start_polling(0.01)
|
||||
queue.put('/test5 resend')
|
||||
@@ -426,7 +438,7 @@ class UpdaterTest(BaseTest, unittest.TestCase):
|
||||
self.regexGroupHandlerTest,
|
||||
pass_groupdict=True,
|
||||
pass_groups=True)
|
||||
d.addHandler(handler)
|
||||
d.add_handler(handler)
|
||||
queue = self.updater.start_polling(0.01)
|
||||
queue.put('This is a test message for regex group matching.')
|
||||
sleep(.1)
|
||||
@@ -437,7 +449,7 @@ class UpdaterTest(BaseTest, unittest.TestCase):
|
||||
self._setup_updater('Test6', messages=2)
|
||||
d = self.updater.dispatcher
|
||||
handler = MessageHandler([], self.asyncAdditionalHandlerTest, pass_update_queue=True)
|
||||
d.addHandler(handler)
|
||||
d.add_handler(handler)
|
||||
self.updater.start_polling(0.01)
|
||||
sleep(1.2)
|
||||
self.assertEqual(self.received_message, 'Test6')
|
||||
@@ -447,7 +459,7 @@ class UpdaterTest(BaseTest, unittest.TestCase):
|
||||
self._setup_updater('', messages=0)
|
||||
d = self.updater.dispatcher
|
||||
handler = MessageHandler([], self.telegramHandlerTest)
|
||||
d.addHandler(handler)
|
||||
d.add_handler(handler)
|
||||
|
||||
ip = '127.0.0.1'
|
||||
port = randrange(1024, 49152) # Select random port for travis
|
||||
@@ -497,7 +509,7 @@ class UpdaterTest(BaseTest, unittest.TestCase):
|
||||
self._setup_updater('', messages=0)
|
||||
d = self.updater.dispatcher
|
||||
handler = MessageHandler([], self.telegramHandlerTest)
|
||||
d.addHandler(handler)
|
||||
d.add_handler(handler)
|
||||
|
||||
ip = '127.0.0.1'
|
||||
port = randrange(1024, 49152) # Select random port for travis
|
||||
@@ -639,8 +651,8 @@ class UpdaterTest(BaseTest, unittest.TestCase):
|
||||
self.assertFalse(self.updater.running)
|
||||
|
||||
def test_createBot(self):
|
||||
updater = Updater('123:abcd')
|
||||
self.assertIsNotNone(updater.bot)
|
||||
self.updater = Updater('123:abcd')
|
||||
self.assertIsNotNone(self.updater.bot)
|
||||
|
||||
def test_mutualExclusiveTokenBot(self):
|
||||
bot = Bot('123:zyxw')
|
||||
|
||||
Reference in New Issue
Block a user