mirror of
https://github.com/python-telegram-bot/python-telegram-bot.git
synced 2026-06-20 08:05:27 +00:00
Compare commits
221 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c49058dbb4 | |||
| 8e80a8d273 | |||
| cbe057083f | |||
| 5f6138b06b | |||
| 1b99caa2f9 | |||
| e16c1da6b1 | |||
| e1242b3b4a | |||
| 93dde1ac1d | |||
| 05fb9d161a | |||
| a91fe5f8f6 | |||
| 5116a77221 | |||
| 1f597c6b4a | |||
| 1efd330e59 | |||
| af3e8c6440 | |||
| f34c09dd72 | |||
| 97bb04cd38 | |||
| 7ab007d8d4 | |||
| f7b497c1b4 | |||
| 4e60008086 | |||
| 5285f63e4a | |||
| 6647ae3c25 | |||
| e4a132c0e4 | |||
| ca81a75f29 | |||
| da87d4ba78 | |||
| 4753d27bd5 | |||
| eabfc0b06b | |||
| fcda567f8c | |||
| 1c36ff46ad | |||
| ffff0938f4 | |||
| ab2d6eb494 | |||
| fe14000515 | |||
| 5d27059631 | |||
| 00bba73673 | |||
| e9c5ee7ad6 | |||
| f2f62423ba | |||
| 26a0a173f4 | |||
| b736e1e855 | |||
| bd3fa3bb64 | |||
| c252042ddf | |||
| 8475c322af | |||
| dd4c0f0f1d | |||
| 555e36ee80 | |||
| 5134f71380 | |||
| 32268597d9 | |||
| 4feb2553ff | |||
| cd2f956e56 | |||
| 18fdb5ed13 | |||
| 8c698caa12 | |||
| 587908457e | |||
| 4375820863 | |||
| 171c70b4c2 | |||
| f1ee54fa73 | |||
| 90913724ca | |||
| 9d4691e50d | |||
| c4928229d6 | |||
| 2f2337cac1 | |||
| f5c57cd6c6 | |||
| c51c2224da | |||
| 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 | |||
| 41432f5b02 | |||
| f737ab780a | |||
| e0e8f6b085 | |||
| 17abf0274e | |||
| 8fab7ad302 | |||
| f31bd91673 | |||
| 32ac617e2e | |||
| 7e7acdeb23 | |||
| 792ad62fe8 | |||
| 25bcfa9b35 | |||
| ff00e211d7 | |||
| d40f0a8309 | |||
| 783f9c375c | |||
| 406303d6bb | |||
| 2534e0df9b | |||
| b06983a94a | |||
| 2f408ab767 | |||
| c8497424b7 | |||
| 80fbe98b44 | |||
| 22e9326610 | |||
| a0bb5730c6 | |||
| 748cc3a35f | |||
| 9a13de4a96 | |||
| 561f1c3f02 | |||
| 3907e64966 | |||
| 1abbca3324 | |||
| e7f4a07b7a | |||
| bb165b6acf | |||
| 20067ff178 | |||
| 41daccce07 | |||
| 786216305c | |||
| 86571bc75d | |||
| 663fa0013d | |||
| 37c7af2e14 | |||
| e70625772c | |||
| 1e398821a0 | |||
| b3142d2974 | |||
| 8278779a23 | |||
| 3aedd78e29 | |||
| 6b90ac9f1c | |||
| 386b91f708 | |||
| 76b9a7d328 | |||
| d90b0f495d | |||
| c4d5eff9f3 | |||
| f45ecee820 | |||
| c340585f33 | |||
| 75490ac757 | |||
| 046e69b1c1 | |||
| 7e0be09c58 | |||
| 18f3f43026 | |||
| 0b2fd120d8 | |||
| 408959e91c | |||
| ab2f6e13c9 | |||
| 108e4264fc | |||
| 68b5562c49 | |||
| e50a3622e1 | |||
| d7e226ec0f | |||
| 7c84516d2b | |||
| 53a91be21f | |||
| 2471eaa778 | |||
| 6bfdff8892 | |||
| 2389b07382 | |||
| c7db9a96cd | |||
| af89cbecf3 | |||
| b987c8937c | |||
| 5a0696b181 | |||
| eb303903ef | |||
| a00f409992 | |||
| dc27ff41ef | |||
| 68ec73afb6 | |||
| 0ace0aa016 | |||
| f5847be8ca | |||
| ba26a8ba5d | |||
| d028d4edd0 | |||
| 4d770843cc | |||
| 53de38f6c9 | |||
| be105a2d4a |
@@ -1,140 +1,201 @@
|
||||
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``
|
||||
|
||||
``$ 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``
|
||||
|
||||
4. Install dependencies:
|
||||
|
||||
``$ pip install -r requirements.txt -r requirements-dev.txt``
|
||||
|
||||
|
||||
5. Install pre-commit hooks:
|
||||
|
||||
``$ pre-commit install``
|
||||
|
||||
Finding something to do
|
||||
-----------------------
|
||||
|
||||
If you already know what you'd like to work on, you can skip this section.
|
||||
|
||||
If you have an idea for something to do, first check if it's already been filed on the `issue tracker`_. If so, add a comment to the issue saying you'd like to work on it, and we'll help you get started! Otherwise, please file a new issue and assign yourself to it.
|
||||
|
||||
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``.
|
||||
|
||||
Here's how to make a one-off code change.
|
||||
|
||||
1. **Choose a descriptive branch name.** It should be lowercase, hyphen-separated, and a noun describing the change (so, ``fuzzy-rules``, but not ``implement-fuzzy-rules``). Also, it shouldn't start with ``hotfix`` or ``release``.
|
||||
|
||||
2. **Create a new branch with this name, starting from** ``master``. In other words, run:
|
||||
|
||||
``$ 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.
|
||||
|
||||
- You can refer to relevant issues in the commit message by writing, e.g., "#105".
|
||||
|
||||
- Your code should adhere to the `PEP 8 Style Guide`_, with the exception that we have a maximum line length of 99.
|
||||
|
||||
- For consistency, please conform to `Google Python Style Guide`_ and `Google Python Style Docstrings`_. In addition, code should be formatted consistently with other code around it.
|
||||
|
||||
- The following exceptions to the above (Google's) style guides applies:
|
||||
|
||||
- Documenting types of global variables and complex types of class members can be done using the Sphinx docstring convention.
|
||||
|
||||
- Please ensure that the code you write is well-tested.
|
||||
|
||||
- Don’t break backward compatibility.
|
||||
|
||||
- Add yourself to the AUTHORS.rst_ file in an alphabetical fashion.
|
||||
|
||||
- Before making a commit ensure that all automated tests still pass:
|
||||
|
||||
``$ make test``
|
||||
|
||||
- To actually make the commit (this will trigger tests for yapf, lint and pep8 automatically):
|
||||
|
||||
``$ git add your-file-changed.py``
|
||||
|
||||
- yapf may change code formatting, make sure to re-add them to your commit.
|
||||
|
||||
``$ git commit -a -m "your-commit-message-here"``
|
||||
|
||||
- Finally, push it to your GitHub fork, run:
|
||||
|
||||
``$ git push origin your-branch-name``
|
||||
|
||||
4. **When your feature is ready to merge, create a pull request.**
|
||||
|
||||
- Go to your fork on GitHub, select your branch from the dropdown menu, and click "New pull request".
|
||||
|
||||
- Add a descriptive comment explaining the purpose of the branch (e.g. "Add the new API feature to create inline bot queries."). This will tell the reviewer what the purpose of the branch is.
|
||||
|
||||
- Click "Create pull request". An admin will assign a reviewer to your commit.
|
||||
|
||||
5. **Address review comments until all reviewers give LGTM ('looks good to me').**
|
||||
|
||||
- When your reviewer has reviewed the code, you'll get an email. You'll need to respond in two ways:
|
||||
|
||||
- Make a new commit addressing the comments you agree with, and push it to the same branch. Ideally, the commit message would explain what the commit does (e.g. "Fix lint error"), but if there are lots of disparate review comments, it's fine to refer to the original commit message and add something like "(address review comments)".
|
||||
|
||||
- In addition, please reply to each comment. Each reply should be either "Done" or a response explaining why the corresponding suggestion wasn't implemented. All comments must be resolved before LGTM can be given.
|
||||
|
||||
- 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``
|
||||
|
||||
``$ 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``
|
||||
|
||||
``$ git push origin --delete your-branch-name``
|
||||
|
||||
7. **Celebrate.** Congratulations, you have contributed to ``python-telegram-bot``!
|
||||
|
||||
.. _`Code of Conduct`: https://www.python.org/psf/codeofconduct/
|
||||
.. _`issue tracker`: https://github.com/python-telegram-bot/python-telegram-bot/issues
|
||||
.. _`developers' mailing list`: mailto:devs@python-telegram-bot.org
|
||||
.. _`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
|
||||
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:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ git clone https://github.com/<your username>/python-telegram-bot
|
||||
$ cd python-telegram-bot
|
||||
|
||||
3. Add a track to the original repository:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ git remote add upstream https://github.com/python-telegram-bot/python-telegram-bot
|
||||
|
||||
4. Install dependencies:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ sudo pip install -r requirements.txt -r requirements-dev.txt
|
||||
|
||||
|
||||
5. Install pre-commit hooks:
|
||||
|
||||
.. 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.
|
||||
|
||||
If you have an idea for something to do, first check if it's already been filed on the `issue tracker`_. If so, add a comment to the issue saying you'd like to work on it, and we'll help you get started! Otherwise, please file a new issue and assign yourself to it.
|
||||
|
||||
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``.
|
||||
|
||||
Here's how to make a one-off code change.
|
||||
|
||||
1. **Choose a descriptive branch name.** It should be lowercase, hyphen-separated, and a noun describing the change (so, ``fuzzy-rules``, but not ``implement-fuzzy-rules``). Also, it shouldn't start with ``hotfix`` or ``release``.
|
||||
|
||||
2. **Create a new branch with this name, starting from** ``master``. In other words, run:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ 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.
|
||||
|
||||
- You can refer to relevant issues in the commit message by writing, e.g., "#105".
|
||||
|
||||
- Your code should adhere to the `PEP 8 Style Guide`_, with the exception that we have a maximum line length of 99.
|
||||
|
||||
- For consistency, please conform to `Google Python Style Guide`_ and `Google Python Style Docstrings`_. In addition, code should be formatted consistently with other code around it.
|
||||
|
||||
- The following exceptions to the above (Google's) style guides applies:
|
||||
|
||||
- Documenting types of global variables and complex types of class members can be done using the Sphinx docstring convention.
|
||||
|
||||
- Please ensure that the code you write is well-tested.
|
||||
|
||||
- Don’t break backward compatibility.
|
||||
|
||||
- Add yourself to the AUTHORS.rst_ file in an alphabetical fashion.
|
||||
|
||||
- Before making a commit ensure that all automated tests still pass:
|
||||
|
||||
.. code-block::
|
||||
|
||||
$ make test
|
||||
|
||||
- To actually make the commit (this will trigger tests for yapf, lint and pep8 automatically):
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ git add your-file-changed.py
|
||||
|
||||
- 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:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ git push origin your-branch-name
|
||||
|
||||
4. **When your feature is ready to merge, create a pull request.**
|
||||
|
||||
- Go to your fork on GitHub, select your branch from the dropdown menu, and click "New pull request".
|
||||
|
||||
- Add a descriptive comment explaining the purpose of the branch (e.g. "Add the new API feature to create inline bot queries."). This will tell the reviewer what the purpose of the branch is.
|
||||
|
||||
- Click "Create pull request". An admin will assign a reviewer to your commit.
|
||||
|
||||
5. **Address review comments until all reviewers give LGTM ('looks good to me').**
|
||||
|
||||
- When your reviewer has reviewed the code, you'll get an email. You'll need to respond in two ways:
|
||||
|
||||
- Make a new commit addressing the comments you agree with, and push it to the same branch. Ideally, the commit message would explain what the commit does (e.g. "Fix lint error"), but if there are lots of disparate review comments, it's fine to refer to the original commit message and add something like "(address review comments)".
|
||||
|
||||
- In addition, please reply to each comment. Each reply should be either "Done" or a response explaining why the corresponding suggestion wasn't implemented. All comments must be resolved before LGTM can be given.
|
||||
|
||||
- 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:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ 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:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ 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.
|
||||
|
||||
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:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# 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:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# GOOD
|
||||
def __init__(self, id, name, **kwargs):
|
||||
self.last_name = kwargs.get('last_name', '')
|
||||
|
||||
# BAD
|
||||
def __init__(self, id, name, last_name=''):
|
||||
self.last_name = last_name
|
||||
|
||||
|
||||
.. _`Code of Conduct`: https://www.python.org/psf/codeofconduct/
|
||||
.. _`issue tracker`: https://github.com/python-telegram-bot/python-telegram-bot/issues
|
||||
.. _`developers' mailing list`: mailto:devs@python-telegram-bot.org
|
||||
.. _`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: ../AUTHORS.rst
|
||||
@@ -0,0 +1,31 @@
|
||||
<!--
|
||||
Thanks for reporting issues of python-telegram-bot!
|
||||
To make it easier for us to help you please enter detailed information below.
|
||||
|
||||
Please note, we only support the latest version of python-telegram-bot and
|
||||
master branch. Please make sure to upgrade & recreate the issue on the latest
|
||||
version prior to opening an issue.
|
||||
-->
|
||||
### Steps to reproduce
|
||||
1.
|
||||
|
||||
2.
|
||||
|
||||
3.
|
||||
|
||||
### Expected behaviour
|
||||
Tell us what should happen
|
||||
|
||||
### Actual behaviour
|
||||
Tell us what happens instead
|
||||
|
||||
### Configuration
|
||||
**Operating System:**
|
||||
|
||||
|
||||
**Version of Python, python-telegram-bot & dependencies:**
|
||||
|
||||
``$ python -m telegram``
|
||||
|
||||
### Logs
|
||||
Insert logs here (if necessary)
|
||||
@@ -1,17 +1,18 @@
|
||||
- repo: git://github.com/pre-commit/mirrors-yapf
|
||||
sha: 'v0.7.1'
|
||||
sha: v0.11.0
|
||||
hooks:
|
||||
- id: yapf
|
||||
args: ['-i']
|
||||
|
||||
files: ^(telegram|tests)/.*\.py$
|
||||
- repo: git://github.com/pre-commit/pre-commit-hooks
|
||||
sha: 'v0.5.0'
|
||||
sha: 18d7035de5388cc7775be57f529c154bf541aab9
|
||||
hooks:
|
||||
- id: flake8
|
||||
args: ['telegram']
|
||||
|
||||
files: ^telegram/.*\.py$
|
||||
- repo: git://github.com/pre-commit/mirrors-pylint
|
||||
sha: 'v1.5.5'
|
||||
sha: v1.5.5
|
||||
hooks:
|
||||
- id: pylint
|
||||
args: ['--errors-only', '--disable=no-name-in-module,import-error', 'telegram']
|
||||
files: ^telegram/.*\.py$
|
||||
args:
|
||||
- --errors-only
|
||||
- --disable=no-name-in-module,import-error
|
||||
|
||||
+2
-4
@@ -1,6 +1,5 @@
|
||||
language: python
|
||||
python:
|
||||
- "2.6"
|
||||
- "2.7"
|
||||
- "3.3"
|
||||
- "3.4"
|
||||
@@ -11,10 +10,9 @@ install:
|
||||
- pip install coveralls
|
||||
- pip install -r requirements.txt
|
||||
- pip install -r requirements-dev.txt
|
||||
- if [[ $TRAVIS_PYTHON_VERSION != 'pypy'* ]]; then pip install ujson; fi
|
||||
script:
|
||||
- nosetests -v --with-flaky --no-flaky-report --with-coverage --cover-package=telegram/
|
||||
- 'if [ $TRAVIS_PYTHON_VERSION != 2.6 ] && [ $TRAVIS_PYTHON_VERSION != 3.3 ] && [ $TRAVIS_PYTHON_VERSION != pypy3 ]; then yapf -r telegram; fi'
|
||||
- flake8 telegram
|
||||
- 'if [ $TRAVIS_PYTHON_VERSION != 2.6 ]; then pylint -E telegram --disable=no-name-in-module,import-error; fi'
|
||||
- if [ $TRAVIS_PYTHON_VERSION != 3.3 ] && [ $TRAVIS_PYTHON_VERSION != pypy3 ]; then pre-commit run --all-files; fi
|
||||
after_success:
|
||||
coveralls
|
||||
|
||||
@@ -11,20 +11,25 @@ The following wonderful people contributed directly or indirectly to this projec
|
||||
- `Avanatiker <https://github.com/Avanatiker>`_
|
||||
- `Balduro <https://github.com/Balduro>`_
|
||||
- `bimmlerd <https://github.com/bimmlerd>`_
|
||||
- `Eli Gao <https://github.com/eligao>`_
|
||||
- `ErgoZ Riftbit Vaper <https://github.com/ergoz>`_
|
||||
- `franciscod <https://github.com/franciscod>`_
|
||||
- `Jacob Bom <https://github.com/bomjacob>`_
|
||||
- `JASON0916 <https://github.com/JASON0916>`_
|
||||
- `jh0ker <https://github.com/jh0ker>`_
|
||||
- `JRoot3D <https://github.com/JRoot3D>`_
|
||||
- `jlmadurga <https://github.com/jlmadurga>`_
|
||||
- `Li-aung Yip <https://github.com/LiaungYip>`_
|
||||
- `macrojames <https://github.com/macrojames>`_
|
||||
- `naveenvhegde <https://github.com/naveenvhegde>`_
|
||||
- `njittam <https://github.com/njittam>`_
|
||||
- `Noam Meltzer <https://github.com/tsnoam>`_
|
||||
- `Oleg Shlyazhko <https://github.com/ollmer>`_
|
||||
- `overquota <https://github.com/overquota>`_
|
||||
- `Rahiel Kasim <https://github.com/rahiel>`_
|
||||
- `Shelomentsev D <https://github.com/shelomentsevd>`_
|
||||
- `sooyhwang <https://github.com/sooyhwang>`_
|
||||
- `Valentijn <https://github.com/Faalentijn>`_
|
||||
- `wjt <https://github.com/wjt>`_
|
||||
|
||||
Please add yourself here alphabetically when you submit your first pull request.
|
||||
|
||||
+96
-1
@@ -1,3 +1,98 @@
|
||||
=======
|
||||
Changes
|
||||
=======
|
||||
|
||||
**2016-09-24**
|
||||
|
||||
*Released 5.1*
|
||||
|
||||
- Drop Python 2.6 support
|
||||
- Deprecate ``telegram.Emoji``
|
||||
|
||||
- Use ``ujson`` if available
|
||||
- Add instance methods to ``Message``, ``Chat``, ``User``, ``InlineQuery`` and ``CallbackQuery``
|
||||
- RegEx filtering for ``CallbackQueryHandler`` and ``InlineQueryHandler``
|
||||
- New ``MessageHandler`` filters: ``forwarded`` and ``entity``
|
||||
- Add ``Message.get_entity`` to correctly handle UTF-16 codepoints and ``MessageEntity`` offsets
|
||||
- Fix bug in ``ConversationHandler`` when first handler ends the conversation
|
||||
- Allow multiple ``Dispatcher`` instances
|
||||
- Add ``ChatMigrated`` Exception
|
||||
- Properly split and handle arguments in ``CommandHandler``
|
||||
|
||||
**2016-07-15**
|
||||
|
||||
*Released 5.0*
|
||||
|
||||
- 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*
|
||||
|
||||
- Implement Bot API 2.1
|
||||
- Move ``botan`` module to ``telegram.contrib``
|
||||
- New exception type: ``BadRequest``
|
||||
|
||||
**2016-05-22**
|
||||
|
||||
*Released 4.1.2*
|
||||
|
||||
- Fix ``MessageEntity`` decoding with Bot API 2.1 changes
|
||||
|
||||
**2016-05-16**
|
||||
|
||||
*Released 4.1.1*
|
||||
|
||||
- Fix deprecation warning in ``Dispatcher``
|
||||
|
||||
**2016-05-15**
|
||||
|
||||
*Released 4.1*
|
||||
|
||||
- Implement API changes from May 6, 2016
|
||||
- Fix bug when ``start_polling`` with ``clean=True``
|
||||
- Methods now have snake_case equivalent, for example ``telegram.Bot.send_message`` is the same as ``telegram.Bot.sendMessage``
|
||||
|
||||
**2016-05-01**
|
||||
|
||||
*Released 4.0.3*
|
||||
@@ -17,7 +112,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
|
||||
|
||||
+75
-392
@@ -1,9 +1,9 @@
|
||||
.. image:: https://github.com/python-telegram-bot/logos/blob/master/logo-text/png/ptb-logo-text_768.png?raw=true
|
||||
:align: center
|
||||
:target: https://github.com/python-telegram-bot/logos
|
||||
:target: https://python-telegram-bot.org
|
||||
:alt: python-telegram-bot Logo
|
||||
|
||||
Not **just** a Python wrapper around the Telegram Bot API
|
||||
We have made you a wrapper you can't refuse
|
||||
|
||||
*Stay tuned for library updates and new releases on our* `Telegram Channel <https://telegram.me/pythontelegrambotchannel>`_.
|
||||
|
||||
@@ -15,8 +15,8 @@ Not **just** a Python wrapper around the Telegram Bot API
|
||||
:target: https://pypi.python.org/pypi/python-telegram-bot
|
||||
:alt: Supported python versions
|
||||
|
||||
.. image:: https://readthedocs.org/projects/python-telegram-bot/badge/?version=latest
|
||||
:target: https://readthedocs.org/projects/python-telegram-bot/?badge=latest
|
||||
.. image:: https://img.shields.io/badge/docs-latest-af1a97.svg
|
||||
:target: https://pythonhosted.org/python-telegram-bot/
|
||||
:alt: Documentation Status
|
||||
|
||||
.. image:: https://img.shields.io/pypi/l/python-telegram-bot.svg
|
||||
@@ -34,6 +34,10 @@ Not **just** a Python wrapper around the Telegram Bot API
|
||||
.. image:: https://coveralls.io/repos/python-telegram-bot/python-telegram-bot/badge.svg?branch=master&service=github
|
||||
:target: https://coveralls.io/github/python-telegram-bot/python-telegram-bot?branch=master
|
||||
:alt: Coveralls
|
||||
|
||||
.. image:: http://isitmaintained.com/badge/resolution/python-telegram-bot/python-telegram-bot.svg
|
||||
:target: http://isitmaintained.com/project/python-telegram-bot/python-telegram-bot
|
||||
:alt: Average time to resolve an issue
|
||||
|
||||
.. image:: https://img.shields.io/badge/Telegram-Group-blue.svg
|
||||
:target: https://telegram.me/pythontelegrambotgroup
|
||||
@@ -49,18 +53,10 @@ Table of contents
|
||||
|
||||
- `Installing`_
|
||||
|
||||
- `Getting the code`_
|
||||
|
||||
- `Getting started`_
|
||||
|
||||
#. `Learning by example`_
|
||||
|
||||
#. `API`_
|
||||
|
||||
#. `Extensions`_
|
||||
|
||||
#. `JobQueue`_
|
||||
|
||||
#. `Logging`_
|
||||
|
||||
#. `Documentation`_
|
||||
@@ -71,46 +67,28 @@ Table of contents
|
||||
|
||||
- `License`_
|
||||
|
||||
===============
|
||||
_`Introduction`
|
||||
===============
|
||||
============
|
||||
Introduction
|
||||
============
|
||||
|
||||
This library provides a pure Python interface for the `Telegram Bot API <https://core.telegram.org/bots/api>`_. It works with Python versions from 2.6+. It also works with `Google App Engine <https://cloud.google.com/appengine>`_.
|
||||
This library provides a pure Python interface for the
|
||||
`Telegram Bot API <https://core.telegram.org/bots/api>`_.
|
||||
It's compatible with Python versions 2.7, 3.3+ and `PyPy <http://pypy.org/>`_.
|
||||
It also works with `Google App Engine <https://cloud.google.com/appengine>`_.
|
||||
|
||||
=======================
|
||||
_`Telegram API support`
|
||||
=======================
|
||||
In addition to the pure API implementation, this library features a number of high-level classes to
|
||||
make the development of bots easy and straightforward. These classes are contained in the
|
||||
``telegram.ext`` submodule.
|
||||
|
||||
========================= ============
|
||||
Telegram Bot API Method *Supported?*
|
||||
========================= ============
|
||||
getMe Yes
|
||||
sendMessage Yes
|
||||
forwardMessage Yes
|
||||
sendPhoto Yes
|
||||
sendAudio Yes
|
||||
sendDocument Yes
|
||||
sendSticker Yes
|
||||
sendVideo Yes
|
||||
sendVoice Yes
|
||||
sendLocation Yes
|
||||
sendChatAction Yes
|
||||
getUpdates Yes
|
||||
getUserProfilePhotos Yes
|
||||
getFile Yes
|
||||
setWebhook Yes
|
||||
answerInlineQuery Yes
|
||||
kickChatMember Yes
|
||||
unbanChatMember Yes
|
||||
answerCallbackQuery Yes
|
||||
editMessageText Yes
|
||||
editMessageCaption Yes
|
||||
editMessageReplyMarkup Yes
|
||||
========================= ============
|
||||
====================
|
||||
Telegram API support
|
||||
====================
|
||||
|
||||
=============
|
||||
_`Installing`
|
||||
=============
|
||||
As of **28. May 2016**, all types and methods of the Telegram Bot API are supported.
|
||||
|
||||
==========
|
||||
Installing
|
||||
==========
|
||||
|
||||
You can install or upgrade python-telegram-bot with:
|
||||
|
||||
@@ -118,44 +96,44 @@ You can install or upgrade python-telegram-bot with:
|
||||
|
||||
$ pip install python-telegram-bot --upgrade
|
||||
|
||||
===================
|
||||
_`Getting the code`
|
||||
===================
|
||||
|
||||
The code is hosted at https://github.com/python-telegram-bot/python-telegram-bot
|
||||
|
||||
Check out the latest development version anonymously with:
|
||||
Or you can install from source with:
|
||||
|
||||
.. code:: shell
|
||||
|
||||
$ git clone https://github.com/python-telegram-bot/python-telegram-bot
|
||||
$ cd python-telegram-bot
|
||||
$ python setup.py install
|
||||
===============
|
||||
Getting started
|
||||
===============
|
||||
|
||||
Install dependencies:
|
||||
Our Wiki contains a lot of resources to get you started with ``python-telegram-bot``:
|
||||
|
||||
.. code:: shell
|
||||
- `Introduction to the API <https://github.com/python-telegram-bot/python-telegram-bot/wiki/Introduction-to-the-API>`_
|
||||
- Tutorial: `Your first Bot <https://github.com/python-telegram-bot/python-telegram-bot/wiki/Extensions-%E2%80%93-Your-first-Bot>`_
|
||||
|
||||
$ pip install -r requirements.txt -r requirements-dev.txt
|
||||
Other references:
|
||||
|
||||
Run tests:
|
||||
- `Telegram API documentation <https://core.telegram.org/bots/api>`_
|
||||
- `python-telegram-bot documentation <https://pythonhosted.org/python-telegram-bot/>`_
|
||||
|
||||
.. code:: shell
|
||||
-------------------
|
||||
Learning by example
|
||||
-------------------
|
||||
|
||||
$ make test
|
||||
We believe that the best way to learn and understand this simple package is by example. So here
|
||||
are some examples for you to review. Even if it's not your approach for learning, please take a
|
||||
look at ``echobot2``, it is de facto the base for most of the bots out there. Best of all,
|
||||
the code for these examples are released to the public domain, so you can start by grabbing the
|
||||
code and building on top of it.
|
||||
|
||||
To see other available options, run:
|
||||
Visit `this page <https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/README.md>`_ to discover the official examples or look at the examples on the `wiki <https://github.com/python-telegram-bot/python-telegram-bot/wiki/Examples>`_ to see other bots the community has built.
|
||||
|
||||
.. code:: shell
|
||||
-------
|
||||
Logging
|
||||
-------
|
||||
|
||||
$ make help
|
||||
|
||||
==================
|
||||
_`Getting started`
|
||||
==================
|
||||
|
||||
View the last release API documentation at: https://core.telegram.org/bots/api
|
||||
|
||||
This library uses the `logging` module. To set up logging to standard output, put:
|
||||
This library uses the ``logging`` module. To set up logging to standard output, put:
|
||||
|
||||
.. code:: python
|
||||
|
||||
@@ -165,344 +143,49 @@ This library uses the `logging` module. To set up logging to standard output, pu
|
||||
|
||||
at the beginning of your script.
|
||||
|
||||
**Note:** The ``telegram.ext`` module will catch errors that would cause the bot to crash. All these are logged to the ``logging`` module, so it's recommended to use this if you are looking for error causes.
|
||||
|
||||
----------------------
|
||||
_`Learning by example`
|
||||
----------------------
|
||||
|
||||
We believe that the best way to learn and understand this simple package is by example. So here are some examples for you to review. Even if it's not your approach for learning, please take a look at ``echobot2`` (below), it is de facto the base for most of the bots out there. Best of all, the code for these examples are released to the public domain, so you can start by grabbing the code and building on top of it.
|
||||
|
||||
- `clibot <https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/clibot.py>`_ has a command line interface.
|
||||
|
||||
- `echobot2 <https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/echobot2.py>`_ replies back messages.
|
||||
|
||||
- `inlinebot <https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/inlinebot.py>`_ basic example of an `inline bot <https://core.telegram.org/bots/inline>`_
|
||||
|
||||
- `state machine bot <https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/state_machine_bot.py>`_ keeps the state for individual users, useful for multipart conversations
|
||||
|
||||
- `timerbot <https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/timerbot.py>`_ uses the ``JobQueue`` to send timed messages.
|
||||
|
||||
Examples using only the 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>`_.
|
||||
|
||||
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.
|
||||
|
||||
------
|
||||
_`API`
|
||||
------
|
||||
|
||||
Note: Using the ``Bot`` class directly is the 'old' method, we have an easier way to make bots described in the next section. All of this is however still important information, even if you're using the ``telegram.ext`` submodule!
|
||||
|
||||
The API is exposed via the ``telegram.Bot`` class. The methods have names as described in the official `Telegram Bot API <https://core.telegram.org/bots/api>`_, but equivalent snake_case methods are available for `PEP8 <https://www.python.org/dev/peps/pep-0008/>`_ enthusiasts. So for example `telegram.Bot.send_message` is the same as `telegram.Bot.sendMessage`.
|
||||
|
||||
To generate an Access Token you have to talk to `BotFather <https://telegram.me/botfather>`_ and follow a few simple steps (described `here <https://core.telegram.org/bots#6-botfather>`_).
|
||||
|
||||
For full details see the `Bots: An introduction for developers <https://core.telegram.org/bots>`_.
|
||||
|
||||
To create an instance of the ``telegram.Bot``:
|
||||
You can also use logs in your application by calling ``logging.getLogger()`` and setting the log level you want:
|
||||
|
||||
.. code:: python
|
||||
|
||||
>>> import telegram
|
||||
>>> bot = telegram.Bot(token='token')
|
||||
|
||||
To see if your credentials are successful:
|
||||
|
||||
.. code:: python
|
||||
|
||||
>>> print(bot.getMe())
|
||||
{"first_name": "Toledo's Palace Bot", "username": "ToledosPalaceBot"}
|
||||
|
||||
Bots can't initiate conversations with users. A user must either add them to a group or send them a message first. People can use ``telegram.me/<bot_username>`` links or username search to find your bot.
|
||||
|
||||
To fetch text messages sent to your Bot:
|
||||
|
||||
.. code:: python
|
||||
|
||||
>>> updates = bot.getUpdates()
|
||||
>>> print([u.message.text for u in updates])
|
||||
|
||||
To fetch images sent to your Bot:
|
||||
|
||||
.. code:: python
|
||||
|
||||
>>> updates = bot.getUpdates()
|
||||
>>> print([u.message.photo for u in updates if u.message.photo])
|
||||
|
||||
To reply messages you'll always need the ``chat_id``:
|
||||
|
||||
.. code:: python
|
||||
|
||||
>>> chat_id = bot.getUpdates()[-1].message.chat_id
|
||||
|
||||
To post a text message:
|
||||
|
||||
.. code:: python
|
||||
|
||||
>>> bot.sendMessage(chat_id=chat_id, text="I'm sorry Dave I'm afraid I can't do that.")
|
||||
|
||||
To post a text message with markdown:
|
||||
|
||||
.. code:: python
|
||||
|
||||
>>> bot.sendMessage(chat_id=chat_id, text="*bold* _italic_ [link](http://google.com).", parse_mode=telegram.ParseMode.MARKDOWN)
|
||||
|
||||
To post a text message with Html style:
|
||||
|
||||
.. code:: python
|
||||
|
||||
>>> bot.sendMessage(chat_id=chat_id, text='<b>bold</b> <i>italic</i> <a href="http://google.com">link</a>.', parse_mode=telegram.ParseMode.HTML)
|
||||
|
||||
To post an Emoji (special thanks to `Tim Whitlock <http://apps.timwhitlock.info/emoji/tables/unicode>`_):
|
||||
|
||||
.. code:: python
|
||||
|
||||
>>> bot.sendMessage(chat_id=chat_id, text=telegram.Emoji.PILE_OF_POO)
|
||||
|
||||
To post an image file via URL:
|
||||
|
||||
.. code:: python
|
||||
|
||||
>>> bot.sendPhoto(chat_id=chat_id, photo='https://telegram.org/img/t_logo.png')
|
||||
|
||||
To post an image file from disk:
|
||||
|
||||
.. code:: python
|
||||
|
||||
>>> bot.sendPhoto(chat_id=chat_id, photo=open('tests/test.png', 'rb'))
|
||||
|
||||
To post a voice file from disk:
|
||||
|
||||
.. code:: python
|
||||
|
||||
>>> bot.sendVoice(chat_id=chat_id, voice=open('tests/telegram.ogg', 'rb'))
|
||||
|
||||
To tell the user that something is happening on bot's side:
|
||||
|
||||
.. code:: python
|
||||
|
||||
>>> bot.sendChatAction(chat_id=chat_id, action=telegram.ChatAction.TYPING)
|
||||
|
||||
To create `Custom Keyboards <https://core.telegram.org/bots#keyboards>`_:
|
||||
|
||||
.. code:: python
|
||||
|
||||
>>> custom_keyboard = [[ telegram.KeyboardButton(telegram.Emoji.THUMBS_UP_SIGN),
|
||||
... telegram.KeyboardButton(telegram.Emoji.THUMBS_DOWN_SIGN) ]]
|
||||
>>> reply_markup = telegram.ReplyKeyboardMarkup(custom_keyboard)
|
||||
>>> bot.sendMessage(chat_id=chat_id, text="Stay here, I'll be back.", reply_markup=reply_markup)
|
||||
|
||||
To hide `Custom Keyboards <https://core.telegram.org/bots#keyboards>`_:
|
||||
|
||||
.. code:: python
|
||||
|
||||
>>> reply_markup = telegram.ReplyKeyboardHide()
|
||||
>>> bot.sendMessage(chat_id=chat_id, text="I'm back.", reply_markup=reply_markup)
|
||||
|
||||
To download a file (you will need its ``file_id``):
|
||||
|
||||
.. code:: python
|
||||
|
||||
>>> file_id = message.voice.file_id
|
||||
>>> newFile = bot.getFile(file_id)
|
||||
>>> newFile.download('voice.ogg')
|
||||
|
||||
There are many more API methods, to read the full API documentation:
|
||||
|
||||
.. code:: shell
|
||||
|
||||
$ pydoc telegram.Bot
|
||||
|
||||
-------------
|
||||
_`Extensions`
|
||||
-------------
|
||||
|
||||
The ``telegram.ext`` submodule is built on top of the bare-metal API. It provides an easy-to-use interface to the ``telegram.Bot`` by caring about getting new updates with the ``Updater`` class from telegram and forwarding them to the ``Dispatcher`` class. We can register handler functions in the ``Dispatcher`` to make our bot react to Telegram commands, messages and even arbitrary updates.
|
||||
|
||||
We'll need an Access Token. **Note:** If you have done this in the previous step, you can use that one. To generate an Access Token, we have to talk to `BotFather <https://telegram.me/botfather>`_ and follow a few simple steps (described `here <https://core.telegram.org/bots#botfather>`_).
|
||||
|
||||
First, we create an ``Updater`` object:
|
||||
|
||||
.. code:: python
|
||||
|
||||
>>> from telegram.ext import Updater
|
||||
>>> updater = Updater(token='token')
|
||||
|
||||
For quicker access to the ``Dispatcher`` used by our ``Updater``, we can introduce it locally:
|
||||
|
||||
.. code:: python
|
||||
|
||||
>>> dispatcher = updater.dispatcher
|
||||
|
||||
Now, we need to define a function that should process a specific type of update:
|
||||
|
||||
.. code:: python
|
||||
|
||||
>>> def start(bot, update):
|
||||
... bot.sendMessage(chat_id=update.message.chat_id, text="I'm a bot, please talk to me!")
|
||||
|
||||
We want this function to be called on a Telegram message that contains the ``/start`` command. To do that, we have to use a ``CommandHandler`` object and register it in the dispatcher:
|
||||
|
||||
.. code:: python
|
||||
|
||||
>>> from telegram.ext import CommandHandler
|
||||
>>> start_handler = CommandHandler('start', start)
|
||||
>>> dispatcher.add_handler(start_handler)
|
||||
|
||||
The last step is to tell the ``Updater`` to start working:
|
||||
|
||||
.. code:: python
|
||||
|
||||
>>> updater.start_polling()
|
||||
|
||||
Our bot is now up and running (go ahead and try it)! It's not doing anything yet, besides answering to the ``/start`` command. Let's add another handler that listens for regular messages. We're using the `MessageHandler` here to echo to all text messages:
|
||||
|
||||
.. code:: python
|
||||
|
||||
>>> def echo(bot, update):
|
||||
... bot.sendMessage(chat_id=update.message.chat_id, text=update.message.text)
|
||||
...
|
||||
>>> from telegram.ext import MessageHandler, Filters
|
||||
>>> echo_handler = MessageHandler([Filters.text], echo)
|
||||
>>> dispatcher.add_handler(echo_handler)
|
||||
|
||||
Our bot should now reply to all text messages that are not a command with a message that has the same content.
|
||||
|
||||
Let's add some functionality to our bot. We want to add the ``/caps`` command, that will take some text as parameter and return it in all caps. We can get the arguments that were passed to a command in the handler function:
|
||||
|
||||
.. code:: python
|
||||
|
||||
>>> def caps(bot, update, args):
|
||||
... text_caps = ' '.join(args).upper()
|
||||
... bot.sendMessage(chat_id=update.message.chat_id, text=text_caps)
|
||||
...
|
||||
>>> caps_handler = CommandHandler('caps', caps, pass_args=True)
|
||||
>>> dispatcher.add_handler(caps_handler)
|
||||
|
||||
To enable our bot to respond to inline queries, we can add the following (you will also have to talk to BotFather):
|
||||
|
||||
.. code:: python
|
||||
|
||||
>>> from telegram import InlineQueryResultArticle
|
||||
>>> def inline_caps(bot, update):
|
||||
... query = bot.update.inline_query.query
|
||||
... results = list()
|
||||
... results.append(InlineQueryResultArticle(query.upper(), 'Caps', query.upper()))
|
||||
... bot.answerInlineQuery(update.inline_query.id, results)
|
||||
...
|
||||
>>> from telegram.ext import InlineQueryHandler
|
||||
>>> inline_caps_handler = InlineQueryHandler(inline_caps)
|
||||
>>> dispatcher.add_handler(inline_caps_handler)
|
||||
|
||||
People might try to send commands to the bot that it doesn't understand, so we can use a ``RegexHandler`` to recognize all commands that were not recognized by the previous handlers. **Note:** This handler has to be added last, else it will be triggered before the ``CommandHandlers`` had a chance to look at the update:
|
||||
|
||||
.. code:: python
|
||||
|
||||
>>> def unknown(bot, update):
|
||||
... bot.sendMessage(chat_id=update.message.chat_id, text="Sorry, I didn't understand that command.")
|
||||
...
|
||||
>>> from telegram.ext import RegexHandler
|
||||
>>> unknown_handler = RegexHandler(r'/.*', unknown)
|
||||
>>> dispatcher.add_handler(unknown_handler)
|
||||
|
||||
If you're done playing around, stop the bot with this:
|
||||
|
||||
.. code:: python
|
||||
|
||||
>>> updater.stop()
|
||||
|
||||
Check out more examples in the `examples folder <https://github.com/python-telegram-bot/python-telegram-bot/tree/master/examples>`_!
|
||||
|
||||
-----------
|
||||
_`JobQueue`
|
||||
-----------
|
||||
|
||||
The ``JobQueue`` allows you to perform tasks with a delay or even periodically. The ``Updater`` will create one for you:
|
||||
|
||||
.. code:: python
|
||||
|
||||
>>> from telegram.ext import Updater
|
||||
>>> u = Updater('TOKEN')
|
||||
>>> j = u.job_queue
|
||||
|
||||
The job queue uses functions for tasks, so we define one and add it to the queue. Usually, when the first job is added to the queue, it wil start automatically. We can prevent this by setting ``prevent_autostart=True``:
|
||||
|
||||
.. code:: python
|
||||
|
||||
>>> def job1(bot):
|
||||
... bot.sendMessage(chat_id='@examplechannel', text='One message every minute')
|
||||
>>> j.put(job1, 60, next_t=0, prevent_autostart=True)
|
||||
|
||||
You can also have a job that will not be executed repeatedly:
|
||||
|
||||
.. code:: python
|
||||
|
||||
>>> def job2(bot):
|
||||
... bot.sendMessage(chat_id='@examplechannel', text='A single message with 30s delay')
|
||||
>>> j.put(job2, 30, repeat=False)
|
||||
|
||||
Now, because we didn't prevent the auto start this time, the queue will start ticking. It runs in a seperate thread, so it is non-blocking. When we stop the Updater, the related queue will be stopped as well:
|
||||
|
||||
.. code:: python
|
||||
|
||||
>>> u.stop()
|
||||
|
||||
We can also stop the job queue by itself:
|
||||
|
||||
.. code:: python
|
||||
|
||||
>>> j.stop()
|
||||
|
||||
----------
|
||||
_`Logging`
|
||||
----------
|
||||
|
||||
You can get logs in your main application by calling `logging` and setting the log level you want:
|
||||
|
||||
.. code:: python
|
||||
|
||||
>>> import logging
|
||||
>>> logger = logging.getLogger()
|
||||
>>> logger.setLevel(logging.INFO)
|
||||
logger = logging.getLogger()
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
If you want DEBUG logs instead:
|
||||
|
||||
.. code:: python
|
||||
|
||||
>>> logger.setLevel(logging.DEBUG)
|
||||
logger.setLevel(logging.DEBUG)
|
||||
|
||||
|
||||
================
|
||||
_`Documentation`
|
||||
================
|
||||
=============
|
||||
Documentation
|
||||
=============
|
||||
|
||||
``python-telegram-bot``'s documentation lives at `Read the Docs <https://python-telegram-bot.readthedocs.org/en/latest/>`_.
|
||||
``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. You can ask for help on Stack Overflow using the `python-telegram-bot tag <https://stackoverflow.com/questions/tagged/python-telegram-bot>`_.
|
||||
|
||||
3. As last resort, the developers are ready to help you with `serious issues <https://github.com/python-telegram-bot/python-telegram-bot/issues/new>`_.
|
||||
|
||||
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/CONTRIBUTING.rst>`_ to get started. You can also help by `reporting bugs <https://github.com/python-telegram-bot/python-telegram-bot/issues/new>`_.
|
||||
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."
|
||||
+4
-3
@@ -15,6 +15,7 @@
|
||||
import sys
|
||||
import os
|
||||
import shlex
|
||||
import telegram
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
@@ -58,9 +59,9 @@ author = u'Leandro Toledo'
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '4.1'
|
||||
version = telegram.__version__[:3]
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = '4.1'
|
||||
release = telegram.__version__
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
@@ -270,7 +271,7 @@ man_pages = [
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
(master_doc, 'PythonTelegramBot', u'Python Telegram Bot Documentation',
|
||||
author, 'PythonTelegramBot', 'One line description of project.',
|
||||
author, 'PythonTelegramBot', 'Not just a Python wrapper around the Telegram Bot API',
|
||||
'Miscellaneous'),
|
||||
]
|
||||
|
||||
|
||||
@@ -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:
|
||||
@@ -0,0 +1,7 @@
|
||||
telegram.contrib.botan module
|
||||
=============================
|
||||
|
||||
.. automodule:: telegram.contrib.botan
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
@@ -1,7 +0,0 @@
|
||||
telegram.emoji module
|
||||
=====================
|
||||
|
||||
.. automodule:: telegram.emoji
|
||||
: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
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
# Examples
|
||||
|
||||
The examples in this folder are small bots meant to show you how a bot that is written with `python-telegram-bot` looks like. Some bots focus on one specific aspect of the Telegram Bot API while others focus on one of the mechanics of this library. Except for the `echobot.py` example, they all use the high-level framework this library provides with the `telegram.ext` submodule.
|
||||
|
||||
All examples are licensed under the [CC0 License](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/LICENSE.txt) and are therefore fully dedicated to the public domain. You can use them as the base for your own bots without worrying about copyrights.
|
||||
|
||||
### [`echobot2.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/v5.0/examples/echobot2.py)
|
||||
This is probably the base for most of the bots made with `python-telegram-bot`. It simply replies to each text message with a message that contains the same text.
|
||||
|
||||
### [`timerbot.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/timerbot.py)
|
||||
This bot uses the [`JobQueue`](https://pythonhosted.org/python-telegram-bot/telegram.ext.jobqueue.html) class to send timed messages. The user sets a timer by using `/set` command with a specific time, for example `/set 30`. The bot then sets up a job to send a message to that user after 30 seconds. The user can also cancel the timer by sending `/unset`. To learn more about the `JobQueue`, read [this wiki article](https://github.com/python-telegram-bot/python-telegram-bot/wiki/Extensions-%E2%80%93-JobQueue).
|
||||
|
||||
### [`conversationbot.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/conversationbot.py)
|
||||
A common task for a bot is to ask information from the user. In v5.0 of this library, we introduced the [`ConversationHandler`](https://pythonhosted.org/python-telegram-bot/telegram.ext.conversationhandler.html) for that exact purpose. This example uses it to retrieve user-information in a conversation-like style.
|
||||
|
||||
### [`inlinekeyboard.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/inlinekeyboard.py)
|
||||
This example sheds some light on inline keyboards, callback queries and message editing.
|
||||
|
||||
### [`inlinebot.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/inlinebot.py)
|
||||
A basic example of an [inline bot](https://core.telegram.org/bots/inline). Don't forget to enable inline mode with [@BotFather](https://telegram.me/BotFather).
|
||||
|
||||
## Pure API
|
||||
The [`echobot.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/echobot.py) example uses only the pure, "bare-metal" API wrapper.
|
||||
@@ -1,170 +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')
|
||||
|
||||
@@ -21,12 +24,11 @@ def main():
|
||||
except IndexError:
|
||||
update_id = None
|
||||
|
||||
logging.basicConfig(
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
|
||||
while True:
|
||||
try:
|
||||
update_id = echo(bot, update_id)
|
||||
echo(bot)
|
||||
except NetworkError:
|
||||
sleep(1)
|
||||
except Unauthorized:
|
||||
@@ -34,21 +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__':
|
||||
@@ -3,7 +3,6 @@
|
||||
#
|
||||
# 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.
|
||||
|
||||
@@ -21,9 +20,8 @@ from telegram.ext import Updater, CommandHandler, MessageHandler, Filters
|
||||
import logging
|
||||
|
||||
# Enable logging
|
||||
logging.basicConfig(
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
level=logging.INFO)
|
||||
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
level=logging.INFO)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -31,15 +29,15 @@ logger = logging.getLogger(__name__)
|
||||
# Define a few command handlers. These usually take the two arguments bot and
|
||||
# update. Error handlers also receive the raised TelegramError object in error.
|
||||
def start(bot, update):
|
||||
bot.sendMessage(update.message.chat_id, text='Hi!')
|
||||
update.message.reply_text('Hi!')
|
||||
|
||||
|
||||
def help(bot, update):
|
||||
bot.sendMessage(update.message.chat_id, text='Help!')
|
||||
update.message.reply_text('Help!')
|
||||
|
||||
|
||||
def echo(bot, update):
|
||||
bot.sendMessage(update.message.chat_id, text=update.message.text)
|
||||
update.message.reply_text(update.message.text)
|
||||
|
||||
|
||||
def error(bot, update, error):
|
||||
@@ -71,5 +69,6 @@ def main():
|
||||
# start_polling() is non-blocking and will stop the bot gracefully.
|
||||
updater.idle()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
+17
-20
@@ -3,7 +3,6 @@
|
||||
#
|
||||
# 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.
|
||||
|
||||
@@ -26,9 +25,8 @@ from telegram.ext import Updater, InlineQueryHandler, CommandHandler
|
||||
import logging
|
||||
|
||||
# Enable logging
|
||||
logging.basicConfig(
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
level=logging.INFO)
|
||||
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
level=logging.INFO)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -53,24 +51,22 @@ def inlinequery(bot, update):
|
||||
query = update.inline_query.query
|
||||
results = list()
|
||||
|
||||
results.append(InlineQueryResultArticle(
|
||||
id=uuid4(),
|
||||
title="Caps",
|
||||
input_message_content=InputTextMessageContent(query.upper())))
|
||||
results.append(InlineQueryResultArticle(id=uuid4(),
|
||||
title="Caps",
|
||||
input_message_content=InputTextMessageContent(
|
||||
query.upper())))
|
||||
|
||||
results.append(InlineQueryResultArticle(
|
||||
id=uuid4(),
|
||||
title="Bold",
|
||||
input_message_content=InputTextMessageContent(
|
||||
"*%s*" % escape_markdown(query),
|
||||
parse_mode=ParseMode.MARKDOWN)))
|
||||
results.append(InlineQueryResultArticle(id=uuid4(),
|
||||
title="Bold",
|
||||
input_message_content=InputTextMessageContent(
|
||||
"*%s*" % escape_markdown(query),
|
||||
parse_mode=ParseMode.MARKDOWN)))
|
||||
|
||||
results.append(InlineQueryResultArticle(
|
||||
id=uuid4(),
|
||||
title="Italic",
|
||||
input_message_content=InputTextMessageContent(
|
||||
"_%s_" % escape_markdown(query),
|
||||
parse_mode=ParseMode.MARKDOWN)))
|
||||
results.append(InlineQueryResultArticle(id=uuid4(),
|
||||
title="Italic",
|
||||
input_message_content=InputTextMessageContent(
|
||||
"_%s_" % escape_markdown(query),
|
||||
parse_mode=ParseMode.MARKDOWN)))
|
||||
|
||||
bot.answerInlineQuery(update.inline_query.id, results=results)
|
||||
|
||||
@@ -104,5 +100,6 @@ def main():
|
||||
# start_polling() is non-blocking and will stop the bot gracefully.
|
||||
updater.idle()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Basic example for a bot that uses inline keyboards.
|
||||
# This program is dedicated to the public domain under the CC0 license.
|
||||
|
||||
import logging
|
||||
from telegram import InlineKeyboardButton, InlineKeyboardMarkup
|
||||
from telegram.ext import Updater, CommandHandler, CallbackQueryHandler
|
||||
|
||||
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
level=logging.INFO)
|
||||
|
||||
|
||||
def start(bot, update):
|
||||
keyboard = [[InlineKeyboardButton("Option 1", callback_data='1'),
|
||||
InlineKeyboardButton("Option 2", callback_data='2')],
|
||||
|
||||
[InlineKeyboardButton("Option 3", callback_data='3')]]
|
||||
|
||||
reply_markup = InlineKeyboardMarkup(keyboard)
|
||||
|
||||
bot.sendMessage(update.message.chat_id, text="Please choose:", reply_markup=reply_markup)
|
||||
|
||||
|
||||
def button(bot, update):
|
||||
query = update.callback_query
|
||||
|
||||
bot.editMessageText(text="Selected option: %s" % query.data,
|
||||
chat_id=query.message.chat_id,
|
||||
message_id=query.message.message_id)
|
||||
|
||||
|
||||
def help(bot, update):
|
||||
bot.sendMessage(update.message.chat_id, text="Use /start to test this bot.")
|
||||
|
||||
|
||||
def error(bot, update, error):
|
||||
logging.warning('Update "%s" caused error "%s"' % (update, error))
|
||||
|
||||
|
||||
# Create the Updater and pass it your bot's token.
|
||||
updater = Updater("TOKEN")
|
||||
|
||||
updater.dispatcher.add_handler(CommandHandler('start', start))
|
||||
updater.dispatcher.add_handler(CallbackQueryHandler(button))
|
||||
updater.dispatcher.add_handler(CommandHandler('help', help))
|
||||
updater.dispatcher.add_error_handler(error)
|
||||
|
||||
# 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()
|
||||
@@ -1,120 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Basic example for a bot that awaits an answer from the user. It's built upon
|
||||
# the state_machine_bot.py example
|
||||
# This program is dedicated to the public domain under the CC0 license.
|
||||
|
||||
import logging
|
||||
from telegram import Emoji, ForceReply, InlineKeyboardButton, \
|
||||
InlineKeyboardMarkup
|
||||
from telegram.ext import Updater, CommandHandler, MessageHandler, \
|
||||
CallbackQueryHandler, Filters
|
||||
|
||||
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - '
|
||||
'%(message)s',
|
||||
level=logging.DEBUG)
|
||||
|
||||
# Define the different states a chat can be in
|
||||
MENU, AWAIT_CONFIRMATION, AWAIT_INPUT = range(3)
|
||||
|
||||
# Python 2 and 3 unicode differences
|
||||
try:
|
||||
YES, NO = (Emoji.THUMBS_UP_SIGN.decode('utf-8'),
|
||||
Emoji.THUMBS_DOWN_SIGN.decode('utf-8'))
|
||||
except AttributeError:
|
||||
YES, NO = (Emoji.THUMBS_UP_SIGN, Emoji.THUMBS_DOWN_SIGN)
|
||||
|
||||
# States are saved in a dict that maps chat_id -> state
|
||||
state = dict()
|
||||
# Sometimes you need to save data temporarily
|
||||
context = dict()
|
||||
# This dict is used to store the settings value for the chat.
|
||||
# Usually, you'd use persistence for this (e.g. sqlite).
|
||||
values = dict()
|
||||
|
||||
|
||||
# Example handler. Will be called on the /set command and on regular messages
|
||||
def set_value(bot, update):
|
||||
chat_id = update.message.chat_id
|
||||
user_id = update.message.from_user.id
|
||||
user_state = state.get(chat_id, MENU)
|
||||
|
||||
if user_state == MENU:
|
||||
state[user_id] = AWAIT_INPUT # set the state
|
||||
bot.sendMessage(chat_id,
|
||||
text="Please enter your settings value",
|
||||
reply_markup=ForceReply())
|
||||
|
||||
|
||||
def entered_value(bot, update):
|
||||
chat_id = update.message.chat_id
|
||||
user_id = update.message.from_user.id
|
||||
chat_state = state.get(user_id, MENU)
|
||||
|
||||
# Check if we are waiting for input
|
||||
if chat_state == AWAIT_INPUT:
|
||||
state[user_id] = AWAIT_CONFIRMATION
|
||||
|
||||
# Save the user id and the answer to context
|
||||
context[user_id] = update.message.text
|
||||
reply_markup = InlineKeyboardMarkup(
|
||||
[[InlineKeyboardButton(YES, callback_data=YES),
|
||||
InlineKeyboardButton(NO, callback_data=NO)]])
|
||||
bot.sendMessage(chat_id, text="Are you sure?",
|
||||
reply_markup=reply_markup)
|
||||
|
||||
|
||||
def confirm_value(bot, update):
|
||||
query = update.callback_query
|
||||
chat_id = query.message.chat_id
|
||||
user_id = query.from_user.id
|
||||
text = query.data
|
||||
user_state = state.get(user_id, MENU)
|
||||
user_context = context.get(user_id, None)
|
||||
|
||||
# Check if we are waiting for confirmation and the right user answered
|
||||
if user_state == AWAIT_CONFIRMATION:
|
||||
del state[user_id]
|
||||
del context[user_id]
|
||||
bot.answerCallbackQuery(query.id, text="Ok!")
|
||||
if text == YES:
|
||||
values[user_id] = user_context
|
||||
bot.editMessageText(text="Changed value to %s." % values[user_id],
|
||||
chat_id=chat_id,
|
||||
message_id=
|
||||
query.message.message_id)
|
||||
else:
|
||||
bot.editMessageText(text="Alright, value is still %s."
|
||||
% values.get(user_id, 'not set'),
|
||||
chat_id=chat_id,
|
||||
message_id=
|
||||
query.message.message_id)
|
||||
|
||||
|
||||
def help(bot, update):
|
||||
bot.sendMessage(update.message.chat_id, text="Use /set to test this bot.")
|
||||
|
||||
|
||||
def error(bot, update, error):
|
||||
logging.warning('Update "%s" caused error "%s"' % (update, error))
|
||||
|
||||
# Create the Updater and pass it your bot's token.
|
||||
updater = Updater("TOKEN")
|
||||
|
||||
# The command
|
||||
updater.dispatcher.addHandler(CommandHandler('set', set_value))
|
||||
# The answer
|
||||
updater.dispatcher.add_handler(MessageHandler([Filters.text], entered_value))
|
||||
# The confirmation
|
||||
updater.dispatcher.add_handler(CallbackQueryHandler(confirm_value))
|
||||
updater.dispatcher.add_handler(CommandHandler('start', help))
|
||||
updater.dispatcher.add_handler(CommandHandler('help', help))
|
||||
updater.dispatcher.add_error_handler(error)
|
||||
|
||||
# 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()
|
||||
@@ -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,106 +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()
|
||||
+37
-22
@@ -3,7 +3,6 @@
|
||||
#
|
||||
# Simple Bot to send timed Telegram messages
|
||||
# This program is dedicated to the public domain under the CC0 license.
|
||||
|
||||
"""
|
||||
This Bot uses the Updater class to handle the bot and the JobQueue to send
|
||||
timed messages.
|
||||
@@ -18,56 +17,70 @@ 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)
|
||||
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
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
|
||||
# update. Error handlers also receive the raised TelegramError object in error.
|
||||
def start(bot, update):
|
||||
bot.sendMessage(update.message.chat_id, text='Hi! Use /set <seconds> to '
|
||||
'set a timer')
|
||||
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
|
||||
due = int(args[0])
|
||||
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!')
|
||||
bot.sendMessage(chat_id, text='Sorry we can not go back to future!')
|
||||
return
|
||||
|
||||
# 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, job_queue):
|
||||
"""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
|
||||
@@ -75,7 +88,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, pass_job_queue=True))
|
||||
|
||||
# log all errors
|
||||
dp.add_error_handler(error)
|
||||
@@ -88,5 +102,6 @@ def main():
|
||||
# start_polling() is non-blocking and will stop the bot gracefully.
|
||||
updater.idle()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
@@ -2,7 +2,6 @@ flake8
|
||||
nose
|
||||
pep257
|
||||
pylint
|
||||
unittest2
|
||||
flaky
|
||||
yapf
|
||||
pre-commit
|
||||
|
||||
+3
-1
@@ -1 +1,3 @@
|
||||
future
|
||||
future>=0.15.2
|
||||
urllib3>=1.10
|
||||
certifi
|
||||
|
||||
@@ -11,7 +11,9 @@ upload-dir = docs/build/html
|
||||
|
||||
[flake8]
|
||||
max-line-length = 99
|
||||
ignore = W503
|
||||
|
||||
[yapf]
|
||||
based_on_style = google
|
||||
split_before_logical_operator = True
|
||||
column_limit = 99
|
||||
|
||||
@@ -1,17 +1,11 @@
|
||||
#!/usr/bin/env python
|
||||
'''The setup and build script for the python-telegram-bot library.'''
|
||||
"""The setup and build script for the python-telegram-bot library."""
|
||||
|
||||
import codecs
|
||||
import os
|
||||
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
|
||||
def read(*paths):
|
||||
"""Build a file path from *paths* and return the contents."""
|
||||
with open(os.path.join(*paths), 'r') as f:
|
||||
return f.read()
|
||||
|
||||
|
||||
def requirements():
|
||||
"""Build the requirements list for this project"""
|
||||
requirements_list = []
|
||||
@@ -23,32 +17,40 @@ def requirements():
|
||||
return requirements_list
|
||||
|
||||
|
||||
setup(name='python-telegram-bot',
|
||||
version='4.1',
|
||||
author='Leandro Toledo',
|
||||
author_email='devs@python-telegram-bot.org',
|
||||
license='LGPLv3',
|
||||
url='https://github.com/python-telegram-bot/python-telegram-bot',
|
||||
keywords='python telegram bot api wrapper',
|
||||
description='A Python wrapper around the Telegram Bot API',
|
||||
long_description=(read('README.rst')),
|
||||
packages=find_packages(exclude=['tests*']),
|
||||
install_requires=requirements(),
|
||||
include_package_data=True,
|
||||
classifiers=[
|
||||
'Development Status :: 5 - Production/Stable',
|
||||
'Intended Audience :: Developers',
|
||||
'License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)',
|
||||
'Operating System :: OS Independent',
|
||||
'Topic :: Software Development :: Libraries :: Python Modules',
|
||||
'Topic :: Communications :: Chat',
|
||||
'Topic :: Internet',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 2',
|
||||
'Programming Language :: Python :: 2.6',
|
||||
'Programming Language :: Python :: 2.7',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.3',
|
||||
'Programming Language :: Python :: 3.4',
|
||||
'Programming Language :: Python :: 3.5',
|
||||
],)
|
||||
with codecs.open('README.rst', 'r', 'utf-8') as fd:
|
||||
fn = os.path.join('telegram', 'version.py')
|
||||
with open(fn) as fh:
|
||||
code = compile(fh.read(), fn, 'exec')
|
||||
exec(code)
|
||||
|
||||
setup(name='python-telegram-bot',
|
||||
version=__version__,
|
||||
author='Leandro Toledo',
|
||||
author_email='devs@python-telegram-bot.org',
|
||||
license='LGPLv3',
|
||||
url='https://python-telegram-bot.org/',
|
||||
keywords='python telegram bot api wrapper',
|
||||
description='Not just a Python wrapper around the Telegram Bot API',
|
||||
long_description=fd.read(),
|
||||
packages=find_packages(exclude=['tests*']),
|
||||
install_requires=requirements(),
|
||||
extras_require={
|
||||
'json': 'ujson',
|
||||
},
|
||||
include_package_data=True,
|
||||
classifiers=[
|
||||
'Development Status :: 5 - Production/Stable',
|
||||
'Intended Audience :: Developers',
|
||||
'License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)',
|
||||
'Operating System :: OS Independent',
|
||||
'Topic :: Software Development :: Libraries :: Python Modules',
|
||||
'Topic :: Communications :: Chat',
|
||||
'Topic :: Internet',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 2',
|
||||
'Programming Language :: Python :: 2.7',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.3',
|
||||
'Programming Language :: Python :: 3.4',
|
||||
'Programming Language :: Python :: 3.5',
|
||||
],)
|
||||
|
||||
+25
-22
@@ -23,6 +23,7 @@ from sys import version_info
|
||||
from .base import TelegramObject
|
||||
from .user import User
|
||||
from .chat import Chat
|
||||
from .chatmember import ChatMember
|
||||
from .photosize import PhotoSize
|
||||
from .audio import Audio
|
||||
from .voice import Voice
|
||||
@@ -42,7 +43,6 @@ from .forcereply import ForceReply
|
||||
from .error import TelegramError
|
||||
from .inputfile import InputFile
|
||||
from .file import File
|
||||
from .nullhandler import NullHandler
|
||||
from .emoji import Emoji
|
||||
from .parsemode import ParseMode
|
||||
from .messageentity import MessageEntity
|
||||
@@ -79,27 +79,30 @@ 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)
|
||||
from .version import __version__ # flake8: noqa
|
||||
|
||||
__author__ = 'devs@python-telegram-bot.org'
|
||||
__version__ = '4.1'
|
||||
__all__ = ['Audio', 'Bot', 'Chat', 'ChatAction', 'ChosenInlineResult', 'CallbackQuery', 'Contact',
|
||||
'Document', 'Emoji', 'File', 'ForceReply', 'InlineKeyboardButton',
|
||||
'InlineKeyboardMarkup', 'InlineQuery', 'InlineQueryResult', 'InlineQueryResult',
|
||||
'InlineQueryResultArticle', 'InlineQueryResultAudio', 'InlineQueryResultCachedAudio',
|
||||
'InlineQueryResultCachedDocument', 'InlineQueryResultCachedGif',
|
||||
'InlineQueryResultCachedMpeg4Gif', 'InlineQueryResultCachedPhoto',
|
||||
'InlineQueryResultCachedSticker', 'InlineQueryResultCachedVideo',
|
||||
'InlineQueryResultCachedVoice', 'InlineQueryResultContact', 'InlineQueryResultDocument',
|
||||
'InlineQueryResultGif', 'InlineQueryResultLocation', 'InlineQueryResultMpeg4Gif',
|
||||
'InlineQueryResultPhoto', 'InlineQueryResultVenue', 'InlineQueryResultVideo',
|
||||
'InlineQueryResultVoice', 'InputContactMessageContent', 'InputFile',
|
||||
'InputLocationMessageContent', 'InputMessageContent', 'InputTextMessageContent',
|
||||
'InputVenueMessageContent', 'KeyboardButton', 'Location', 'Message', 'MessageEntity',
|
||||
'NullHandler', 'ParseMode', 'PhotoSize', 'ReplyKeyboardHide', 'ReplyKeyboardMarkup',
|
||||
'ReplyMarkup', 'Sticker', 'TelegramError', 'TelegramObject', 'Update', 'User',
|
||||
'UserProfilePhotos', 'Venue', 'Video', 'Voice']
|
||||
|
||||
if version_info < (2, 7):
|
||||
from warnings import warn
|
||||
warn("python-telegram-bot will stop supporting Python 2.6 in a future release. "
|
||||
"Please upgrade your Python!")
|
||||
__all__ = ['Audio', 'Bot', 'Chat', 'ChatMember', 'ChatAction', 'ChosenInlineResult',
|
||||
'CallbackQuery', 'Contact', 'Document', 'Emoji', 'File', 'ForceReply',
|
||||
'InlineKeyboardButton', 'InlineKeyboardMarkup', 'InlineQuery', 'InlineQueryResult',
|
||||
'InlineQueryResult', 'InlineQueryResultArticle', 'InlineQueryResultAudio',
|
||||
'InlineQueryResultCachedAudio', 'InlineQueryResultCachedDocument',
|
||||
'InlineQueryResultCachedGif', 'InlineQueryResultCachedMpeg4Gif',
|
||||
'InlineQueryResultCachedPhoto', 'InlineQueryResultCachedSticker',
|
||||
'InlineQueryResultCachedVideo', 'InlineQueryResultCachedVoice',
|
||||
'InlineQueryResultContact', 'InlineQueryResultDocument', 'InlineQueryResultGif',
|
||||
'InlineQueryResultLocation', 'InlineQueryResultMpeg4Gif', 'InlineQueryResultPhoto',
|
||||
'InlineQueryResultVenue', 'InlineQueryResultVideo', 'InlineQueryResultVoice',
|
||||
'InputContactMessageContent', 'InputFile', 'InputLocationMessageContent',
|
||||
'InputMessageContent', 'InputTextMessageContent', 'InputVenueMessageContent',
|
||||
'KeyboardButton', 'Location', 'Message', 'MessageEntity', 'ParseMode', 'PhotoSize',
|
||||
'ReplyKeyboardHide', 'ReplyKeyboardMarkup', 'ReplyMarkup', 'Sticker', 'TelegramError',
|
||||
'TelegramObject', 'Update', 'User', 'UserProfilePhotos', 'Venue', 'Video', 'Voice',
|
||||
'MAX_MESSAGE_LENGTH', 'MAX_CAPTION_LENGTH', 'SUPPORTED_WEBHOOK_PORTS',
|
||||
'MAX_FILESIZE_DOWNLOAD', 'MAX_FILESIZE_UPLOAD', 'MAX_MESSAGES_PER_SECOND_PER_CHAT',
|
||||
'MAX_MESSAGES_PER_SECOND', 'MAX_MESSAGES_PER_MINUTE_PER_GROUP']
|
||||
|
||||
@@ -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()
|
||||
+3
-2
@@ -55,10 +55,11 @@ class Audio(TelegramObject):
|
||||
self.file_size = int(kwargs.get('file_size', 0))
|
||||
|
||||
@staticmethod
|
||||
def de_json(data):
|
||||
def de_json(data, bot):
|
||||
"""
|
||||
Args:
|
||||
data (str):
|
||||
data (dict):
|
||||
bot (telegram.Bot):
|
||||
|
||||
Returns:
|
||||
telegram.Audio:
|
||||
|
||||
+12
-4
@@ -18,7 +18,11 @@
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
"""Base class for Telegram Objects."""
|
||||
|
||||
import json
|
||||
try:
|
||||
import ujson as json
|
||||
except ImportError:
|
||||
import json
|
||||
|
||||
from abc import ABCMeta
|
||||
|
||||
|
||||
@@ -34,13 +38,14 @@ class TelegramObject(object):
|
||||
return self.__dict__[item]
|
||||
|
||||
@staticmethod
|
||||
def de_json(data):
|
||||
def de_json(data, bot):
|
||||
"""
|
||||
Args:
|
||||
data (str):
|
||||
data (dict):
|
||||
bot (telegram.Bot):
|
||||
|
||||
Returns:
|
||||
telegram.TelegramObject:
|
||||
dict:
|
||||
"""
|
||||
if not data:
|
||||
return None
|
||||
@@ -64,6 +69,9 @@ class TelegramObject(object):
|
||||
data = dict()
|
||||
|
||||
for key in iter(self.__dict__):
|
||||
if key == 'bot':
|
||||
continue
|
||||
|
||||
value = self.__dict__[key]
|
||||
if value is not None:
|
||||
if hasattr(value, 'to_dict'):
|
||||
|
||||
+238
-67
@@ -19,15 +19,15 @@
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
"""This module contains a object that represents a Telegram Bot."""
|
||||
|
||||
import logging
|
||||
import functools
|
||||
import logging
|
||||
|
||||
from telegram import (User, Message, Update, UserProfilePhotos, File, ReplyMarkup, TelegramObject,
|
||||
NullHandler)
|
||||
from telegram.utils import request
|
||||
from telegram.utils.validate import validate_token
|
||||
from telegram import (User, Message, Update, Chat, ChatMember, UserProfilePhotos, File,
|
||||
ReplyMarkup, TelegramObject)
|
||||
from telegram.error import InvalidToken
|
||||
from telegram.utils.request import Request
|
||||
|
||||
logging.getLogger(__name__).addHandler(NullHandler())
|
||||
logging.getLogger(__name__).addHandler(logging.NullHandler())
|
||||
|
||||
|
||||
class Bot(TelegramObject):
|
||||
@@ -44,11 +44,12 @@ class Bot(TelegramObject):
|
||||
token (str): Bot's unique authentication.
|
||||
base_url (Optional[str]): Telegram Bot API service URL.
|
||||
base_file_url (Optional[str]): Telegram Bot API file URL.
|
||||
request (Optional[Request]): Pre initialized `Request` class.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, token, base_url=None, base_file_url=None):
|
||||
self.token = validate_token(token)
|
||||
def __init__(self, token, base_url=None, base_file_url=None, request=None):
|
||||
self.token = self._validate_token(token)
|
||||
|
||||
if not base_url:
|
||||
self.base_url = 'https://api.telegram.org/bot{0}'.format(self.token)
|
||||
@@ -61,9 +62,25 @@ class Bot(TelegramObject):
|
||||
self.base_file_url = base_file_url + self.token
|
||||
|
||||
self.bot = None
|
||||
|
||||
self._request = request or Request()
|
||||
self.logger = logging.getLogger(__name__)
|
||||
|
||||
@property
|
||||
def request(self):
|
||||
return self._request
|
||||
|
||||
@staticmethod
|
||||
def _validate_token(token):
|
||||
"""a very basic validation on token"""
|
||||
if any(x.isspace() for x in token):
|
||||
raise InvalidToken()
|
||||
|
||||
left, sep, _right = token.partition(':')
|
||||
if (not sep) or (not left.isdigit()) or (len(left) < 3):
|
||||
raise InvalidToken()
|
||||
|
||||
return token
|
||||
|
||||
def info(func):
|
||||
|
||||
@functools.wraps(func)
|
||||
@@ -132,17 +149,17 @@ class Bot(TelegramObject):
|
||||
else:
|
||||
data['reply_markup'] = reply_markup
|
||||
|
||||
result = request.post(url, data, timeout=kwargs.get('timeout'))
|
||||
result = self._request.post(url, data, timeout=kwargs.get('timeout'))
|
||||
|
||||
if result is True:
|
||||
return result
|
||||
|
||||
return Message.de_json(result)
|
||||
return Message.de_json(result, self)
|
||||
|
||||
return decorator
|
||||
|
||||
@log
|
||||
def getMe(self):
|
||||
def getMe(self, **kwargs):
|
||||
"""A simple method for testing your bot's auth token.
|
||||
|
||||
Returns:
|
||||
@@ -157,9 +174,9 @@ class Bot(TelegramObject):
|
||||
|
||||
url = '{0}/getMe'.format(self.base_url)
|
||||
|
||||
result = request.get(url)
|
||||
result = self._request.get(url)
|
||||
|
||||
self.bot = User.de_json(result)
|
||||
self.bot = User.de_json(result, self)
|
||||
|
||||
return self.bot
|
||||
|
||||
@@ -606,13 +623,8 @@ class Bot(TelegramObject):
|
||||
|
||||
@log
|
||||
@message
|
||||
def sendVenue(
|
||||
self, chat_id,
|
||||
latitude,
|
||||
longitude,
|
||||
title, address,
|
||||
foursquare_id=None,
|
||||
**kwargs):
|
||||
def sendVenue(self, chat_id, latitude, longitude, title, address, foursquare_id=None,
|
||||
**kwargs):
|
||||
"""
|
||||
Use this method to send information about a venue.
|
||||
|
||||
@@ -806,7 +818,7 @@ class Bot(TelegramObject):
|
||||
if switch_pm_parameter:
|
||||
data['switch_pm_parameter'] = switch_pm_parameter
|
||||
|
||||
result = request.post(url, data, timeout=kwargs.get('timeout'))
|
||||
result = self._request.post(url, data, timeout=kwargs.get('timeout'))
|
||||
|
||||
return result
|
||||
|
||||
@@ -846,9 +858,9 @@ class Bot(TelegramObject):
|
||||
if limit:
|
||||
data['limit'] = limit
|
||||
|
||||
result = request.post(url, data, timeout=kwargs.get('timeout'))
|
||||
result = self._request.post(url, data, timeout=kwargs.get('timeout'))
|
||||
|
||||
return UserProfilePhotos.de_json(result)
|
||||
return UserProfilePhotos.de_json(result, self)
|
||||
|
||||
@log
|
||||
def getFile(self, file_id, **kwargs):
|
||||
@@ -877,12 +889,12 @@ class Bot(TelegramObject):
|
||||
|
||||
data = {'file_id': file_id}
|
||||
|
||||
result = request.post(url, data, timeout=kwargs.get('timeout'))
|
||||
result = self._request.post(url, data, timeout=kwargs.get('timeout'))
|
||||
|
||||
if result.get('file_path'):
|
||||
result['file_path'] = '%s/%s' % (self.base_file_url, result['file_path'])
|
||||
|
||||
return File.de_json(result)
|
||||
return File.de_json(result, self)
|
||||
|
||||
@log
|
||||
def kickChatMember(self, chat_id, user_id, **kwargs):
|
||||
@@ -914,7 +926,7 @@ class Bot(TelegramObject):
|
||||
|
||||
data = {'chat_id': chat_id, 'user_id': user_id}
|
||||
|
||||
result = request.post(url, data, timeout=kwargs.get('timeout'))
|
||||
result = self._request.post(url, data, timeout=kwargs.get('timeout'))
|
||||
|
||||
return result
|
||||
|
||||
@@ -948,7 +960,7 @@ class Bot(TelegramObject):
|
||||
|
||||
data = {'chat_id': chat_id, 'user_id': user_id}
|
||||
|
||||
result = request.post(url, data, timeout=kwargs.get('timeout'))
|
||||
result = self._request.post(url, data, timeout=kwargs.get('timeout'))
|
||||
|
||||
return result
|
||||
|
||||
@@ -992,11 +1004,12 @@ class Bot(TelegramObject):
|
||||
if show_alert:
|
||||
data['show_alert'] = show_alert
|
||||
|
||||
result = request.post(url, data, timeout=kwargs.get('timeout'))
|
||||
result = self._request.post(url, data, timeout=kwargs.get('timeout'))
|
||||
|
||||
return result
|
||||
|
||||
@log
|
||||
@message
|
||||
def editMessageText(self,
|
||||
text,
|
||||
chat_id=None,
|
||||
@@ -1004,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).
|
||||
@@ -1031,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.
|
||||
|
||||
@@ -1058,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
|
||||
@@ -1123,10 +1132,11 @@ class Bot(TelegramObject):
|
||||
|
||||
@log
|
||||
@message
|
||||
def editMessageReplyMarkup(
|
||||
self, chat_id=None,
|
||||
message_id=None, inline_message_id=None,
|
||||
**kwargs):
|
||||
def editMessageReplyMarkup(self,
|
||||
chat_id=None,
|
||||
message_id=None,
|
||||
inline_message_id=None,
|
||||
**kwargs):
|
||||
"""Use this method to edit only the reply markup of messages sent by
|
||||
the bot or via the bot (for inline bots).
|
||||
|
||||
@@ -1170,31 +1180,27 @@ class Bot(TelegramObject):
|
||||
return url, data
|
||||
|
||||
@log
|
||||
def getUpdates(self, offset=None, limit=100, timeout=0, network_delay=.2):
|
||||
def getUpdates(self, offset=None, limit=100, timeout=0, network_delay=5., **kwargs):
|
||||
"""Use this method to receive incoming updates using long polling.
|
||||
|
||||
Args:
|
||||
offset:
|
||||
Identifier of the first update to be returned. Must be greater by
|
||||
one than the highest among the identifiers of previously received
|
||||
updates. By default, updates starting with the earliest unconfirmed
|
||||
update are returned. An update is considered confirmed as soon as
|
||||
getUpdates is called with an offset higher than its update_id.
|
||||
limit:
|
||||
Limits the number of updates to be retrieved. Values between 1-100
|
||||
are accepted. Defaults to 100.
|
||||
timeout:
|
||||
Timeout in seconds for long polling. Defaults to 0, i.e. usual
|
||||
short polling.
|
||||
network_delay:
|
||||
Additional timeout in seconds to allow the response from Telegram
|
||||
to take some time when using long polling. Defaults to 2, which
|
||||
should be enough for most connections. Increase it if it takes very
|
||||
long for data to be transmitted from and to the Telegram servers.
|
||||
offset (Optional[int]):
|
||||
Identifier of the first update to be returned. Must be greater by one than the highest
|
||||
among the identifiers of previously received updates. By default, updates starting with
|
||||
the earliest unconfirmed update are returned. An update is considered confirmed as soon
|
||||
as getUpdates is called with an offset higher than its update_id.
|
||||
limit (Optional[int]):
|
||||
Limits the number of updates to be retrieved. Values between 1-100 are accepted.
|
||||
Defaults to 100.
|
||||
timeout (Optional[int]):
|
||||
Timeout in seconds for long polling. Defaults to 0, i.e. usual short polling.
|
||||
network_delay (Optional[float]):
|
||||
Additional timeout in seconds to allow the response from Telegram servers. This should
|
||||
cover network latency around the globe, SSL handshake and slowness of the Telegram
|
||||
servers (which unfortunately happens a lot recently - 2016-05-28). Defaults to 5.
|
||||
|
||||
Returns:
|
||||
list[:class:`telegram.Update`]: A list of :class:`telegram.Update`
|
||||
objects are returned.
|
||||
list[:class:`telegram.Update`]
|
||||
|
||||
Raises:
|
||||
:class:`telegram.TelegramError`
|
||||
@@ -1212,14 +1218,14 @@ class Bot(TelegramObject):
|
||||
|
||||
urlopen_timeout = timeout + network_delay
|
||||
|
||||
result = request.post(url, data, timeout=urlopen_timeout)
|
||||
result = self._request.post(url, data, timeout=urlopen_timeout)
|
||||
|
||||
if result:
|
||||
self.logger.debug('Getting updates: %s', [u['update_id'] for u in result])
|
||||
else:
|
||||
self.logger.debug('No new updates found.')
|
||||
|
||||
return [Update.de_json(x) for x in result]
|
||||
return [Update.de_json(u, self) for u in result]
|
||||
|
||||
@log
|
||||
def setWebhook(self, webhook_url=None, certificate=None, **kwargs):
|
||||
@@ -1255,13 +1261,173 @@ class Bot(TelegramObject):
|
||||
if certificate:
|
||||
data['certificate'] = certificate
|
||||
|
||||
result = request.post(url, data, timeout=kwargs.get('timeout'))
|
||||
result = self._request.post(url, data, timeout=kwargs.get('timeout'))
|
||||
|
||||
return result
|
||||
|
||||
@log
|
||||
def leaveChat(self, chat_id, **kwargs):
|
||||
"""Use this method for your bot to leave a group, supergroup or
|
||||
channel.
|
||||
|
||||
Args:
|
||||
chat_id:
|
||||
Unique identifier for the target chat or username of the target
|
||||
channel (in the format @channelusername).
|
||||
|
||||
Keyword Args:
|
||||
timeout (Optional[float]): If this value is specified, use it as
|
||||
the definitive timeout (in seconds) for urlopen() operations.
|
||||
|
||||
Returns:
|
||||
bool: On success, `True` is returned.
|
||||
|
||||
Raises:
|
||||
:class:`telegram.TelegramError`
|
||||
|
||||
"""
|
||||
|
||||
url = '{0}/leaveChat'.format(self.base_url)
|
||||
|
||||
data = {'chat_id': chat_id}
|
||||
|
||||
result = self._request.post(url, data, timeout=kwargs.get('timeout'))
|
||||
|
||||
return result
|
||||
|
||||
@log
|
||||
def getChat(self, chat_id, **kwargs):
|
||||
"""Use this method to get up to date information about the chat
|
||||
(current name of the user for one-on-one conversations, current
|
||||
username of a user, group or channel, etc.).
|
||||
|
||||
Args:
|
||||
chat_id:
|
||||
Unique identifier for the target chat or username of the target
|
||||
channel (in the format @channelusername).
|
||||
|
||||
Keyword Args:
|
||||
timeout (Optional[float]): If this value is specified, use it as
|
||||
the definitive timeout (in seconds) for urlopen() operations.
|
||||
|
||||
Returns:
|
||||
:class:`telegram.Chat`: On success, :class:`telegram.Chat` is
|
||||
returned.
|
||||
|
||||
Raises:
|
||||
:class:`telegram.TelegramError`
|
||||
|
||||
"""
|
||||
|
||||
url = '{0}/getChat'.format(self.base_url)
|
||||
|
||||
data = {'chat_id': chat_id}
|
||||
|
||||
result = self._request.post(url, data, timeout=kwargs.get('timeout'))
|
||||
|
||||
return Chat.de_json(result, self)
|
||||
|
||||
@log
|
||||
def getChatAdministrators(self, chat_id, **kwargs):
|
||||
"""Use this method to get a list of administrators in a chat. On
|
||||
success, returns an Array of ChatMember objects that contains
|
||||
information about all chat administrators except other bots. If the
|
||||
chat is a group or a supergroup and no administrators were appointed,
|
||||
only the creator will be returned.
|
||||
|
||||
Args:
|
||||
chat_id:
|
||||
Unique identifier for the target chat or username of the target
|
||||
channel (in the format @channelusername).
|
||||
|
||||
|
||||
Keyword Args:
|
||||
timeout (Optional[float]): If this value is specified, use it as
|
||||
the definitive timeout (in seconds) for urlopen() operations.
|
||||
|
||||
Returns:
|
||||
list[:class:`telegram.ChatMember`]: On success, a list of
|
||||
:class:`telegram.ChatMember` objects are returned.
|
||||
|
||||
Raises:
|
||||
:class:`telegram.TelegramError`
|
||||
|
||||
"""
|
||||
|
||||
url = '{0}/getChatAdministrators'.format(self.base_url)
|
||||
|
||||
data = {'chat_id': chat_id}
|
||||
|
||||
result = self._request.post(url, data, timeout=kwargs.get('timeout'))
|
||||
|
||||
return [ChatMember.de_json(x, self) for x in result]
|
||||
|
||||
@log
|
||||
def getChatMembersCount(self, chat_id, **kwargs):
|
||||
"""Use this method to get the number of members in a chat.
|
||||
|
||||
Args:
|
||||
chat_id:
|
||||
Unique identifier for the target chat or username of the target
|
||||
channel (in the format @channelusername).
|
||||
|
||||
|
||||
Keyword Args:
|
||||
timeout (Optional[float]): If this value is specified, use it as
|
||||
the definitive timeout (in seconds) for urlopen() operations.
|
||||
|
||||
Returns:
|
||||
int: On success, an `int` is returned.
|
||||
|
||||
Raises:
|
||||
:class:`telegram.TelegramError`
|
||||
|
||||
"""
|
||||
|
||||
url = '{0}/getChatMembersCount'.format(self.base_url)
|
||||
|
||||
data = {'chat_id': chat_id}
|
||||
|
||||
result = self._request.post(url, data, timeout=kwargs.get('timeout'))
|
||||
|
||||
return result
|
||||
|
||||
@log
|
||||
def getChatMember(self, chat_id, user_id, **kwargs):
|
||||
"""Use this method to get information about a member of a chat.
|
||||
|
||||
Args:
|
||||
chat_id:
|
||||
Unique identifier for the target chat or username of the target
|
||||
channel (in the format @channelusername).
|
||||
user_id:
|
||||
Unique identifier of the target user.
|
||||
|
||||
|
||||
Keyword Args:
|
||||
timeout (Optional[float]): If this value is specified, use it as
|
||||
the definitive timeout (in seconds) for urlopen() operations.
|
||||
|
||||
Returns:
|
||||
:class:`telegram.ChatMember`: On success,
|
||||
:class:`telegram.ChatMember` is returned.
|
||||
|
||||
Raises:
|
||||
:class:`telegram.TelegramError`
|
||||
|
||||
"""
|
||||
|
||||
url = '{0}/getChatMember'.format(self.base_url)
|
||||
|
||||
data = {'chat_id': chat_id, 'user_id': user_id}
|
||||
|
||||
result = self._request.post(url, data, timeout=kwargs.get('timeout'))
|
||||
|
||||
return ChatMember.de_json(result, self)
|
||||
|
||||
@staticmethod
|
||||
def de_json(data):
|
||||
data = super(Bot, Bot).de_json(data)
|
||||
def de_json(data, bot):
|
||||
data = super(Bot, Bot).de_json(data, bot)
|
||||
|
||||
return Bot(**data)
|
||||
|
||||
@@ -1302,3 +1468,8 @@ class Bot(TelegramObject):
|
||||
edit_message_reply_markup = editMessageReplyMarkup
|
||||
get_updates = getUpdates
|
||||
set_webhook = setWebhook
|
||||
leave_chat = leaveChat
|
||||
get_chat = getChat
|
||||
get_chat_administrators = getChatAdministrators
|
||||
get_chat_member = getChatMember
|
||||
get_chat_members_count = getChatMembersCount
|
||||
|
||||
@@ -25,7 +25,7 @@ from telegram import TelegramObject, Message, User
|
||||
class CallbackQuery(TelegramObject):
|
||||
"""This object represents a Telegram CallbackQuery."""
|
||||
|
||||
def __init__(self, id, from_user, data, **kwargs):
|
||||
def __init__(self, id, from_user, data, bot=None, **kwargs):
|
||||
# Required
|
||||
self.id = id
|
||||
self.from_user = from_user
|
||||
@@ -34,12 +34,38 @@ class CallbackQuery(TelegramObject):
|
||||
self.message = kwargs.get('message')
|
||||
self.inline_message_id = kwargs.get('inline_message_id', '')
|
||||
|
||||
self.bot = bot
|
||||
|
||||
@staticmethod
|
||||
def de_json(data):
|
||||
def de_json(data, bot):
|
||||
"""
|
||||
Args:
|
||||
data (dict):
|
||||
bot (telegram.Bot):
|
||||
|
||||
Returns:
|
||||
telegram.CallbackQuery:
|
||||
"""
|
||||
|
||||
if not data:
|
||||
return None
|
||||
|
||||
data['from_user'] = User.de_json(data.get('from'))
|
||||
data['message'] = Message.de_json(data.get('message'))
|
||||
data['from_user'] = User.de_json(data.get('from'), bot)
|
||||
data['message'] = Message.de_json(data.get('message'), bot)
|
||||
|
||||
return CallbackQuery(**data)
|
||||
return CallbackQuery(bot=bot, **data)
|
||||
|
||||
def to_dict(self):
|
||||
"""
|
||||
Returns:
|
||||
dict:
|
||||
"""
|
||||
data = super(CallbackQuery, self).to_dict()
|
||||
|
||||
# Required
|
||||
data['from'] = data.pop('from_user', None)
|
||||
return data
|
||||
|
||||
def answer(self, *args, **kwargs):
|
||||
"""Shortcut for ``bot.answerCallbackQuery(update.callback_query.id, *args, **kwargs)``"""
|
||||
return self.bot.answerCallbackQuery(self.id, *args, **kwargs)
|
||||
|
||||
+40
-3
@@ -40,9 +40,15 @@ class Chat(TelegramObject):
|
||||
|
||||
Keyword Args:
|
||||
type (Optional[str]):
|
||||
bot (Optional[Bot]): The Bot to use for instance methods
|
||||
"""
|
||||
|
||||
def __init__(self, id, type, **kwargs):
|
||||
PRIVATE = 'private'
|
||||
GROUP = 'group'
|
||||
SUPERGROUP = 'supergroup'
|
||||
CHANNEL = 'channel'
|
||||
|
||||
def __init__(self, id, type, bot=None, **kwargs):
|
||||
# Required
|
||||
self.id = int(id)
|
||||
self.type = type
|
||||
@@ -52,11 +58,14 @@ class Chat(TelegramObject):
|
||||
self.first_name = kwargs.get('first_name', '')
|
||||
self.last_name = kwargs.get('last_name', '')
|
||||
|
||||
self.bot = bot
|
||||
|
||||
@staticmethod
|
||||
def de_json(data):
|
||||
def de_json(data, bot):
|
||||
"""
|
||||
Args:
|
||||
data (dict):
|
||||
bot (telegram.Bot):
|
||||
|
||||
Returns:
|
||||
telegram.Chat:
|
||||
@@ -64,4 +73,32 @@ class Chat(TelegramObject):
|
||||
if not data:
|
||||
return None
|
||||
|
||||
return Chat(**data)
|
||||
return Chat(bot=bot, **data)
|
||||
|
||||
def send_action(self, *args, **kwargs):
|
||||
"""Shortcut for ``bot.sendChatAction(update.message.chat.id, *args, **kwargs)``"""
|
||||
return self.bot.sendChatAction(self.id, *args, **kwargs)
|
||||
|
||||
def leave(self, *args, **kwargs):
|
||||
"""Shortcut for ``bot.leaveChat(update.message.chat.id, *args, **kwargs)``"""
|
||||
return self.bot.leaveChat(self.id, *args, **kwargs)
|
||||
|
||||
def get_administrators(self, *args, **kwargs):
|
||||
"""Shortcut for ``bot.getChatAdministrators(update.message.chat.id, *args, **kwargs)``"""
|
||||
return self.bot.getChatAdministrators(self.id, *args, **kwargs)
|
||||
|
||||
def get_members_count(self, *args, **kwargs):
|
||||
"""Shortcut for ``bot.getChatMembersCount(update.message.chat.id, *args, **kwargs)``"""
|
||||
return self.bot.getChatMembersCount(self.id, *args, **kwargs)
|
||||
|
||||
def get_member(self, *args, **kwargs):
|
||||
"""Shortcut for ``bot.getChatMember(update.message.chat.id, *args, **kwargs)``"""
|
||||
return self.bot.getChatMember(self.id, *args, **kwargs)
|
||||
|
||||
def kick_member(self, *args, **kwargs):
|
||||
"""Shortcut for ``bot.kickChatMember(update.message.chat.id, *args, **kwargs)``"""
|
||||
return self.bot.kickChatMember(self.id, *args, **kwargs)
|
||||
|
||||
def unban_member(self, *args, **kwargs):
|
||||
"""Shortcut for ``bot.unbanChatMember(update.message.chat.id, *args, **kwargs)``"""
|
||||
return self.bot.unbanChatMember(self.id, *args, **kwargs)
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
#!/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 a object that represents a Telegram ChatMember."""
|
||||
|
||||
from telegram import User, TelegramObject
|
||||
|
||||
|
||||
class ChatMember(TelegramObject):
|
||||
"""This object represents a Telegram ChatMember.
|
||||
|
||||
Attributes:
|
||||
user (:class:`telegram.User`): Information about the user.
|
||||
status (str): The member's status in the chat. Can be 'creator', 'administrator', 'member',
|
||||
'left' or 'kicked'.
|
||||
|
||||
Args:
|
||||
user (:class:`telegram.User`):
|
||||
status (str):
|
||||
"""
|
||||
|
||||
CREATOR = 'creator'
|
||||
ADMINISTRATOR = 'administrator'
|
||||
MEMBER = 'member'
|
||||
LEFT = 'left'
|
||||
KICKED = 'kicked'
|
||||
|
||||
def __init__(self, user, status, **kwargs):
|
||||
# Required
|
||||
self.user = user
|
||||
self.status = status
|
||||
|
||||
@staticmethod
|
||||
def de_json(data, bot):
|
||||
"""
|
||||
Args:
|
||||
data (dict):
|
||||
bot (telegram.Bot):
|
||||
|
||||
Returns:
|
||||
telegram.ChatMember:
|
||||
"""
|
||||
if not data:
|
||||
return None
|
||||
|
||||
data['user'] = User.de_json(data.get('user'), bot)
|
||||
|
||||
return ChatMember(**data)
|
||||
@@ -41,7 +41,13 @@ class ChosenInlineResult(TelegramObject):
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, result_id, from_user, query, location=None, inline_message_id=None):
|
||||
def __init__(self,
|
||||
result_id,
|
||||
from_user,
|
||||
query,
|
||||
location=None,
|
||||
inline_message_id=None,
|
||||
**kwargs):
|
||||
# Required
|
||||
self.result_id = result_id
|
||||
self.from_user = from_user
|
||||
@@ -51,10 +57,11 @@ class ChosenInlineResult(TelegramObject):
|
||||
self.inline_message_id = inline_message_id
|
||||
|
||||
@staticmethod
|
||||
def de_json(data):
|
||||
def de_json(data, bot):
|
||||
"""
|
||||
Args:
|
||||
data (dict):
|
||||
bot (telegram.Bot):
|
||||
|
||||
Returns:
|
||||
telegram.ChosenInlineResult:
|
||||
@@ -63,9 +70,9 @@ class ChosenInlineResult(TelegramObject):
|
||||
return None
|
||||
|
||||
# Required
|
||||
data['from_user'] = User.de_json(data.pop('from'))
|
||||
data['from_user'] = User.de_json(data.pop('from'), bot)
|
||||
# Optionals
|
||||
data['location'] = Location.de_json(data.get('location'))
|
||||
data['location'] = Location.de_json(data.get('location'), bot)
|
||||
|
||||
return ChosenInlineResult(**data)
|
||||
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
# 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)
|
||||
|
||||
The following constant have been found by experimentation:
|
||||
|
||||
Attributes:
|
||||
MAX_MESSAGE_ENTITIES (int): Max number of entities that can be in a message.
|
||||
(Beyond this cap telegram will simply ignore further formatting styles)
|
||||
"""
|
||||
|
||||
MAX_MESSAGE_LENGTH = 4096
|
||||
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
|
||||
MAX_MESSAGE_ENTITIES = 100
|
||||
+3
-2
@@ -49,10 +49,11 @@ class Contact(TelegramObject):
|
||||
self.user_id = int(kwargs.get('user_id', 0))
|
||||
|
||||
@staticmethod
|
||||
def de_json(data):
|
||||
def de_json(data, bot):
|
||||
"""
|
||||
Args:
|
||||
data (str):
|
||||
data (dict):
|
||||
bot (telegram.Bot):
|
||||
|
||||
Returns:
|
||||
telegram.Contact:
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import logging
|
||||
|
||||
from future.moves.urllib.parse import quote
|
||||
from future.moves.urllib.error import HTTPError, URLError
|
||||
from future.moves.urllib.request import urlopen, Request
|
||||
|
||||
logging.getLogger(__name__).addHandler(logging.NullHandler())
|
||||
|
||||
|
||||
class Botan(object):
|
||||
"""This class helps to send incoming events to your botan analytics account.
|
||||
See more: https://github.com/botanio/sdk#botan-sdk
|
||||
"""
|
||||
|
||||
token = ''
|
||||
url_template = 'https://api.botan.io/track?token={token}' \
|
||||
'&uid={uid}&name={name}&src=python-telegram-bot'
|
||||
|
||||
def __init__(self, token):
|
||||
self.token = token
|
||||
self.logger = logging.getLogger(__name__)
|
||||
|
||||
def track(self, message, event_name='event'):
|
||||
try:
|
||||
uid = message.chat_id
|
||||
except AttributeError:
|
||||
self.logger.warn('No chat_id in message')
|
||||
return False
|
||||
data = message.to_json()
|
||||
try:
|
||||
url = self.url_template.format(
|
||||
token=str(self.token), uid=str(uid), name=quote(event_name))
|
||||
request = Request(
|
||||
url, data=data.encode(), headers={'Content-Type': 'application/json'})
|
||||
urlopen(request)
|
||||
return True
|
||||
except HTTPError as error:
|
||||
self.logger.warn('Botan track error ' + str(error.code) + ':' + error.read().decode(
|
||||
'utf-8'))
|
||||
return False
|
||||
except URLError as error:
|
||||
self.logger.warn('Botan track error ' + str(error.reason))
|
||||
return False
|
||||
@@ -52,10 +52,11 @@ class Document(TelegramObject):
|
||||
self.file_size = int(kwargs.get('file_size', 0))
|
||||
|
||||
@staticmethod
|
||||
def de_json(data):
|
||||
def de_json(data, bot):
|
||||
"""
|
||||
Args:
|
||||
data (str):
|
||||
data (dict):
|
||||
bot (telegram.Bot):
|
||||
|
||||
Returns:
|
||||
telegram.Document:
|
||||
@@ -63,6 +64,6 @@ class Document(TelegramObject):
|
||||
if not data:
|
||||
return None
|
||||
|
||||
data['thumb'] = PhotoSize.de_json(data.get('thumb'))
|
||||
data['thumb'] = PhotoSize.de_json(data.get('thumb'), bot)
|
||||
|
||||
return Document(**data)
|
||||
|
||||
+20
-4
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
# flake8: noqa
|
||||
# pylint: disable=C0103,R0903
|
||||
# pylint: disable=C0103,R0903,E0213
|
||||
#
|
||||
# A library that provides a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2015-2016
|
||||
@@ -18,14 +18,27 @@
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser Public License
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
"""This module contains a object that represents an Emoji."""
|
||||
"""This module contains a object that represents an Emoji.
|
||||
|
||||
from future.utils import bytes_to_native_str as n
|
||||
This module will be removed in the future.
|
||||
"""
|
||||
|
||||
import warnings
|
||||
|
||||
from future.utils import bytes_to_native_str
|
||||
|
||||
|
||||
class Emoji(object):
|
||||
class Emoji2(object):
|
||||
"""This object represents an Emoji."""
|
||||
|
||||
def n(b):
|
||||
|
||||
def e(cls):
|
||||
warnings.warn("telegram.Emoji is being deprecated, please see https://git.io/v6DeB")
|
||||
return bytes_to_native_str(b)
|
||||
|
||||
return property(e)
|
||||
|
||||
GRINNING_FACE_WITH_SMILING_EYES = n(b'\xF0\x9F\x98\x81')
|
||||
FACE_WITH_TEARS_OF_JOY = n(b'\xF0\x9F\x98\x82')
|
||||
SMILING_FACE_WITH_OPEN_MOUTH = n(b'\xF0\x9F\x98\x83')
|
||||
@@ -879,3 +892,6 @@ class Emoji(object):
|
||||
CLOCK_FACE_TEN_THIRTY = n(b'\xF0\x9F\x95\xA5')
|
||||
CLOCK_FACE_ELEVEN_THIRTY = n(b'\xF0\x9F\x95\xA6')
|
||||
CLOCK_FACE_TWELVE_THIRTY = n(b'\xF0\x9F\x95\xA7')
|
||||
|
||||
|
||||
Emoji = Emoji2()
|
||||
|
||||
@@ -51,6 +51,7 @@ class TelegramError(Exception):
|
||||
|
||||
msg = _lstrip_str(message, 'Error: ')
|
||||
msg = _lstrip_str(msg, '[Error]: ')
|
||||
msg = _lstrip_str(msg, 'Bad Request: ')
|
||||
if msg != message:
|
||||
# api_error - capitalize the msg...
|
||||
msg = msg.capitalize()
|
||||
@@ -76,7 +77,25 @@ class NetworkError(TelegramError):
|
||||
pass
|
||||
|
||||
|
||||
class BadRequest(NetworkError):
|
||||
pass
|
||||
|
||||
|
||||
class TimedOut(NetworkError):
|
||||
|
||||
def __init__(self):
|
||||
super(TimedOut, self).__init__('Timed out')
|
||||
|
||||
|
||||
class ChatMigrated(TelegramError):
|
||||
|
||||
def __init__(self, new_chat_id):
|
||||
"""
|
||||
Args:
|
||||
new_chat_id (int):
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
super(ChatMigrated, self).__init__('Chat migrated')
|
||||
self.new_chat_id = new_chat_id
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -18,34 +18,79 @@
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
""" This module contains the CallbackQueryHandler class """
|
||||
|
||||
from .handler import Handler
|
||||
import re
|
||||
|
||||
from future.utils import string_types
|
||||
|
||||
from telegram import Update
|
||||
from telegram.utils.deprecate import deprecate
|
||||
from .handler import Handler
|
||||
|
||||
|
||||
class CallbackQueryHandler(Handler):
|
||||
"""
|
||||
Handler class to handle Telegram callback queries.
|
||||
Handler class to handle Telegram callback queries. Optionally based on a regex.
|
||||
Read the documentation of the ``re`` module for more information.
|
||||
|
||||
Args:
|
||||
callback (function): A function that takes ``bot, update`` as
|
||||
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``.
|
||||
pattern (optional[str or Pattern]): Optional regex pattern. If not ``None`` ``re.match``
|
||||
is used to determine if an update should be handled by this handler.
|
||||
pass_groups (optional[bool]): If the callback should be passed the
|
||||
result of ``re.match(pattern, data).groups()`` as a keyword
|
||||
argument called ``groups``. Default is ``False``
|
||||
pass_groupdict (optional[bool]): If the callback should be passed the
|
||||
result of ``re.match(pattern, data).groupdict()`` as a keyword
|
||||
argument called ``groupdict``. Default is ``False``
|
||||
"""
|
||||
|
||||
def __init__(self, callback, pass_update_queue=False):
|
||||
super(CallbackQueryHandler, self).__init__(callback, pass_update_queue)
|
||||
def __init__(self,
|
||||
callback,
|
||||
pass_update_queue=False,
|
||||
pass_job_queue=False,
|
||||
pattern=None,
|
||||
pass_groups=False,
|
||||
pass_groupdict=False):
|
||||
super(CallbackQueryHandler, self).__init__(
|
||||
callback, pass_update_queue=pass_update_queue, pass_job_queue=pass_job_queue)
|
||||
|
||||
if isinstance(pattern, string_types):
|
||||
pattern = re.compile(pattern)
|
||||
|
||||
self.pattern = pattern
|
||||
self.pass_groups = pass_groups
|
||||
self.pass_groupdict = pass_groupdict
|
||||
|
||||
def check_update(self, update):
|
||||
return isinstance(update, Update) and update.callback_query
|
||||
if isinstance(update, Update) and update.callback_query:
|
||||
if self.pattern:
|
||||
if update.callback_query.data:
|
||||
match = re.match(self.pattern, update.callback_query.data)
|
||||
return bool(match)
|
||||
else:
|
||||
return True
|
||||
|
||||
def handle_update(self, update, dispatcher):
|
||||
optional_args = self.collect_optional_args(dispatcher)
|
||||
if self.pattern:
|
||||
match = re.match(self.pattern, update.callback_query.data)
|
||||
|
||||
self.callback(dispatcher.bot, update, **optional_args)
|
||||
if self.pass_groups:
|
||||
optional_args['groups'] = match.groups()
|
||||
if self.pass_groupdict:
|
||||
optional_args['groupdict'] = match.groupdict()
|
||||
|
||||
return self.callback(dispatcher.bot, update, **optional_args)
|
||||
|
||||
# old non-PEP8 Handler methods
|
||||
m = "telegram.CallbackQueryHandler."
|
||||
|
||||
@@ -32,13 +32,19 @@ 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 +52,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."
|
||||
|
||||
@@ -34,32 +34,56 @@ class CommandHandler(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.
|
||||
allow_edited (Optional[bool]): If the handler should also accept edited messages.
|
||||
Default is ``False``
|
||||
pass_args (optional[bool]): If the handler should be passed the
|
||||
arguments passed to the command as a keyword argument called `
|
||||
``args``. It will contain a list of strings, which is the text
|
||||
following the command split on spaces. Default is ``False``
|
||||
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``
|
||||
following the command split on single or consecutive whitespace characters.
|
||||
Default is ``False``
|
||||
pass_update_queue (optional[bool]): If set to ``True``, a keyword argument called
|
||||
``update_queue`` will be passed to the callback function. It will be the ``Queue``
|
||||
instance used by the ``Updater`` and ``Dispatcher`` that contains new updates which can
|
||||
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(CommandHandler, self).__init__(callback, pass_update_queue)
|
||||
def __init__(self,
|
||||
command,
|
||||
callback,
|
||||
allow_edited=False,
|
||||
pass_args=False,
|
||||
pass_update_queue=False,
|
||||
pass_job_queue=False):
|
||||
super(CommandHandler, self).__init__(
|
||||
callback, pass_update_queue=pass_update_queue, pass_job_queue=pass_job_queue)
|
||||
self.command = command
|
||||
self.allow_edited = allow_edited
|
||||
self.pass_args = pass_args
|
||||
|
||||
def check_update(self, update):
|
||||
return (isinstance(update, Update) and update.message and update.message.text and
|
||||
update.message.text.startswith('/') and
|
||||
update.message.text[1:].split(' ')[0].split('@')[0] == self.command)
|
||||
if (isinstance(update, Update)
|
||||
and (update.message or update.edited_message and self.allow_edited)):
|
||||
message = update.message or update.edited_message
|
||||
|
||||
return (message.text and message.text.startswith('/')
|
||||
and message.text[1:].split(' ')[0].split('@')[0] == self.command)
|
||||
|
||||
else:
|
||||
return False
|
||||
|
||||
def handle_update(self, update, dispatcher):
|
||||
optional_args = self.collect_optional_args(dispatcher)
|
||||
|
||||
if self.pass_args:
|
||||
optional_args['args'] = update.message.text.split(' ')[1:]
|
||||
message = update.message or update.edited_message
|
||||
|
||||
self.callback(dispatcher.bot, update, **optional_args)
|
||||
if self.pass_args:
|
||||
optional_args['args'] = message.text.split()[1:]
|
||||
|
||||
return self.callback(dispatcher.bot, update, **optional_args)
|
||||
|
||||
# old non-PEP8 Handler methods
|
||||
m = "telegram.CommandHandler."
|
||||
|
||||
@@ -0,0 +1,225 @@
|
||||
#!/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:
|
||||
if key in self.conversations:
|
||||
del self.conversations[key]
|
||||
else:
|
||||
pass
|
||||
|
||||
elif isinstance(new_state, Promise):
|
||||
self.conversations[key] = (self.conversations.get(key), new_state)
|
||||
|
||||
elif new_state is not None:
|
||||
self.conversations[key] = new_state
|
||||
+136
-52
@@ -19,64 +19,43 @@
|
||||
"""This module contains the Dispatcher class."""
|
||||
|
||||
import logging
|
||||
import weakref
|
||||
from functools import wraps
|
||||
from threading import Thread, BoundedSemaphore, Lock, Event, current_thread
|
||||
from threading import Thread, Lock, Event, current_thread, BoundedSemaphore
|
||||
from time import sleep
|
||||
from uuid import uuid4
|
||||
|
||||
from queue import Empty
|
||||
from queue import Queue, Empty
|
||||
|
||||
from telegram import (TelegramError, NullHandler)
|
||||
from future.builtins import range
|
||||
|
||||
from telegram import TelegramError
|
||||
from telegram.ext.handler import Handler
|
||||
from telegram.utils.deprecate import deprecate
|
||||
from telegram.utils.promise import Promise
|
||||
|
||||
logging.getLogger(__name__).addHandler(NullHandler())
|
||||
|
||||
semaphore = None
|
||||
async_threads = set()
|
||||
logging.getLogger(__name__).addHandler(logging.NullHandler())
|
||||
""":type: set[Thread]"""
|
||||
async_lock = Lock()
|
||||
DEFAULT_GROUP = 0
|
||||
|
||||
|
||||
def run_async(func):
|
||||
"""
|
||||
Function decorator that will run the function in a new thread.
|
||||
"""Function decorator that will run the function in a new thread.
|
||||
|
||||
Using this decorator is only possible when only a single Dispatcher exist in the system.
|
||||
|
||||
Args:
|
||||
func (function): The function to run in the thread.
|
||||
async_queue (Queue): The queue of the functions to be executed asynchronously.
|
||||
|
||||
Returns:
|
||||
function:
|
||||
|
||||
"""
|
||||
|
||||
# TODO: handle exception in async threads
|
||||
# set a threading.Event to notify caller thread
|
||||
|
||||
@wraps(func)
|
||||
def 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):
|
||||
"""
|
||||
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
|
||||
def async_func(*args, **kwargs):
|
||||
return Dispatcher.get_instance().run_async(func, *args, **kwargs)
|
||||
|
||||
return async_func
|
||||
|
||||
@@ -90,11 +69,21 @@ 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):
|
||||
"""
|
||||
__singleton_lock = Lock()
|
||||
__singleton_semaphore = BoundedSemaphore()
|
||||
__singleton = None
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def __init__(self, bot, update_queue, workers=4, exception_event=None, job_queue=None):
|
||||
self.bot = bot
|
||||
self.update_queue = update_queue
|
||||
self.job_queue = job_queue
|
||||
|
||||
self.handlers = {}
|
||||
""":type: dict[int, list[Handler]"""
|
||||
@@ -102,16 +91,92 @@ class Dispatcher(object):
|
||||
""":type: list[int]"""
|
||||
self.error_handlers = []
|
||||
|
||||
self.logger = logging.getLogger(__name__)
|
||||
self.running = False
|
||||
self.__stop_event = Event()
|
||||
self.__exception_event = exception_event or Event()
|
||||
self.__async_queue = Queue()
|
||||
self.__async_threads = set()
|
||||
|
||||
global semaphore
|
||||
if not semaphore:
|
||||
semaphore = BoundedSemaphore(value=workers)
|
||||
# For backward compatibility, we allow a "singleton" mode for the dispatcher. When there's
|
||||
# only one instance of Dispatcher, it will be possible to use the `run_async` decorator.
|
||||
with self.__singleton_lock:
|
||||
if self.__singleton_semaphore.acquire(blocking=0):
|
||||
self._set_singleton(self)
|
||||
else:
|
||||
self._set_singleton(None)
|
||||
|
||||
self._init_async_threads(uuid4(), workers)
|
||||
|
||||
@classmethod
|
||||
def _reset_singleton(cls):
|
||||
# NOTE: This method was added mainly for test_updater benefit and specifically pypy. Never
|
||||
# call it in production code.
|
||||
cls.__singleton_semaphore.release()
|
||||
|
||||
def _init_async_threads(self, base_name, workers):
|
||||
base_name = '{}_'.format(base_name) if base_name else ''
|
||||
|
||||
for i in range(workers):
|
||||
thread = Thread(target=self._pooled, name='{}{}'.format(base_name, i))
|
||||
self.__async_threads.add(thread)
|
||||
thread.start()
|
||||
|
||||
@classmethod
|
||||
def _set_singleton(cls, val):
|
||||
cls.logger.debug('Setting singleton dispatcher as %s', val)
|
||||
cls.__singleton = weakref.ref(val) if val else None
|
||||
|
||||
@classmethod
|
||||
def get_instance(cls):
|
||||
"""Get the singleton instance of this class.
|
||||
|
||||
Returns:
|
||||
Dispatcher
|
||||
|
||||
"""
|
||||
if cls.__singleton is not None:
|
||||
return cls.__singleton()
|
||||
else:
|
||||
self.logger.debug('Semaphore already initialized, skipping.')
|
||||
raise RuntimeError('{} not initialized or multiple instances exist'.format(
|
||||
cls.__name__))
|
||||
|
||||
def _pooled(self):
|
||||
"""
|
||||
A wrapper to run a thread in a thread pool
|
||||
"""
|
||||
thr_name = current_thread().getName()
|
||||
while 1:
|
||||
promise = self.__async_queue.get()
|
||||
|
||||
# If unpacking fails, the thread pool is being closed from Updater._join_async_threads
|
||||
if not isinstance(promise, Promise):
|
||||
self.logger.debug("Closing run_async thread %s/%d", thr_name,
|
||||
len(self.__async_threads))
|
||||
break
|
||||
|
||||
try:
|
||||
promise.run()
|
||||
|
||||
except:
|
||||
self.logger.exception("run_async function raised exception")
|
||||
|
||||
def run_async(self, func, *args, **kwargs):
|
||||
"""Queue a function (with given args/kwargs) to be run asynchronously.
|
||||
|
||||
Args:
|
||||
func (function): The function to run in the thread.
|
||||
args (Optional[tuple]): Arguments to `func`.
|
||||
kwargs (Optional[dict]): Keyword arguments to `func`.
|
||||
|
||||
Returns:
|
||||
Promise
|
||||
|
||||
"""
|
||||
# TODO: handle exception in async threads
|
||||
# set a threading.Event to notify caller thread
|
||||
promise = Promise(func, args, kwargs)
|
||||
self.__async_queue.put(promise)
|
||||
return promise
|
||||
|
||||
def start(self):
|
||||
"""
|
||||
@@ -131,7 +196,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 +210,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 +225,26 @@ class Dispatcher(object):
|
||||
sleep(0.1)
|
||||
self.__stop_event.clear()
|
||||
|
||||
def processUpdate(self, update):
|
||||
# async threads must be join()ed only after the dispatcher thread was joined,
|
||||
# otherwise we can still have new async threads dispatched
|
||||
threads = list(self.__async_threads)
|
||||
total = len(threads)
|
||||
|
||||
# Stop all threads in the thread pool by put()ting one non-tuple per thread
|
||||
for i in range(total):
|
||||
self.__async_queue.put(None)
|
||||
|
||||
for i, thr in enumerate(threads):
|
||||
self.logger.debug('Waiting for async thread {0}/{1} to end'.format(i + 1, total))
|
||||
thr.join()
|
||||
self.__async_threads.remove(thr)
|
||||
self.logger.debug('async thread {0}/{1} has ended'.format(i + 1, total))
|
||||
|
||||
@property
|
||||
def has_running_threads(self):
|
||||
return self.running or bool(self.__async_threads)
|
||||
|
||||
def process_update(self, update):
|
||||
"""
|
||||
Processes a single update.
|
||||
|
||||
@@ -170,14 +254,14 @@ 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:
|
||||
for handler in self.handlers[group]:
|
||||
try:
|
||||
if handler.checkUpdate(update):
|
||||
handler.handleUpdate(update, self)
|
||||
if handler.check_update(update):
|
||||
handler.handle_update(update, self)
|
||||
break
|
||||
# Dispatch any errors
|
||||
except TelegramError as te:
|
||||
@@ -185,7 +269,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 +355,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
|
||||
|
||||
|
||||
@@ -17,35 +17,79 @@
|
||||
# You should have received a copy of the GNU Lesser Public License
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
""" This module contains the InlineQueryHandler class """
|
||||
import re
|
||||
|
||||
from future.utils import string_types
|
||||
|
||||
from .handler import Handler
|
||||
from telegram import Update
|
||||
from telegram.utils.deprecate import deprecate
|
||||
from .handler import Handler
|
||||
|
||||
|
||||
class InlineQueryHandler(Handler):
|
||||
"""
|
||||
Handler class to handle Telegram inline queries.
|
||||
Handler class to handle Telegram inline queries. Optionally based on a regex. Read the
|
||||
documentation of the ``re`` module for more information.
|
||||
|
||||
Args:
|
||||
callback (function): A function that takes ``bot, update`` as
|
||||
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``.
|
||||
pattern (optional[str or Pattern]): Optional regex pattern. If not ``None`` ``re.match``
|
||||
is used to determine if an update should be handled by this handler.
|
||||
pass_groups (optional[bool]): If the callback should be passed the
|
||||
result of ``re.match(pattern, query).groups()`` as a keyword
|
||||
argument called ``groups``. Default is ``False``
|
||||
pass_groupdict (optional[bool]): If the callback should be passed the
|
||||
result of ``re.match(pattern, query).groupdict()`` as a keyword
|
||||
argument called ``groupdict``. Default is ``False``
|
||||
"""
|
||||
|
||||
def __init__(self, callback, pass_update_queue=False):
|
||||
super(InlineQueryHandler, self).__init__(callback, pass_update_queue)
|
||||
def __init__(self,
|
||||
callback,
|
||||
pass_update_queue=False,
|
||||
pass_job_queue=False,
|
||||
pattern=None,
|
||||
pass_groups=False,
|
||||
pass_groupdict=False):
|
||||
super(InlineQueryHandler, self).__init__(
|
||||
callback, pass_update_queue=pass_update_queue, pass_job_queue=pass_job_queue)
|
||||
|
||||
if isinstance(pattern, string_types):
|
||||
pattern = re.compile(pattern)
|
||||
|
||||
self.pattern = pattern
|
||||
self.pass_groups = pass_groups
|
||||
self.pass_groupdict = pass_groupdict
|
||||
|
||||
def check_update(self, update):
|
||||
return isinstance(update, Update) and update.inline_query
|
||||
if isinstance(update, Update) and update.inline_query:
|
||||
if self.pattern:
|
||||
if update.inline_query.query:
|
||||
match = re.match(self.pattern, update.inline_query.query)
|
||||
return bool(match)
|
||||
else:
|
||||
return True
|
||||
|
||||
def handle_update(self, update, dispatcher):
|
||||
optional_args = self.collect_optional_args(dispatcher)
|
||||
if self.pattern:
|
||||
match = re.match(self.pattern, update.inline_query.query)
|
||||
|
||||
self.callback(dispatcher.bot, update, **optional_args)
|
||||
if self.pass_groups:
|
||||
optional_args['groups'] = match.groups()
|
||||
if self.pass_groupdict:
|
||||
optional_args['groupdict'] = match.groupdict()
|
||||
|
||||
return self.callback(dispatcher.bot, update, **optional_args)
|
||||
|
||||
# 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
|
||||
|
||||
@@ -30,60 +30,77 @@ class Filters(object):
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def text(update):
|
||||
return update.message.text and not update.message.text.startswith('/')
|
||||
def text(message):
|
||||
return message.text and not message.text.startswith('/')
|
||||
|
||||
@staticmethod
|
||||
def command(update):
|
||||
return update.message.text and update.message.text.startswith('/')
|
||||
def command(message):
|
||||
return message.text and message.text.startswith('/')
|
||||
|
||||
@staticmethod
|
||||
def audio(update):
|
||||
return bool(update.message.audio)
|
||||
def audio(message):
|
||||
return bool(message.audio)
|
||||
|
||||
@staticmethod
|
||||
def document(update):
|
||||
return bool(update.message.document)
|
||||
def document(message):
|
||||
return bool(message.document)
|
||||
|
||||
@staticmethod
|
||||
def photo(update):
|
||||
return bool(update.message.photo)
|
||||
def photo(message):
|
||||
return bool(message.photo)
|
||||
|
||||
@staticmethod
|
||||
def sticker(update):
|
||||
return bool(update.message.sticker)
|
||||
def sticker(message):
|
||||
return bool(message.sticker)
|
||||
|
||||
@staticmethod
|
||||
def video(update):
|
||||
return bool(update.message.video)
|
||||
def video(message):
|
||||
return bool(message.video)
|
||||
|
||||
@staticmethod
|
||||
def voice(update):
|
||||
return bool(update.message.voice)
|
||||
def voice(message):
|
||||
return bool(message.voice)
|
||||
|
||||
@staticmethod
|
||||
def contact(update):
|
||||
return bool(update.message.contact)
|
||||
def contact(message):
|
||||
return bool(message.contact)
|
||||
|
||||
@staticmethod
|
||||
def location(update):
|
||||
return bool(update.message.location)
|
||||
def location(message):
|
||||
return bool(message.location)
|
||||
|
||||
@staticmethod
|
||||
def venue(update):
|
||||
return bool(update.message.venue)
|
||||
def venue(message):
|
||||
return bool(message.venue)
|
||||
|
||||
@staticmethod
|
||||
def status_update(update):
|
||||
# yapf: disable
|
||||
# https://github.com/google/yapf/issues/252
|
||||
return bool(update.message.new_chat_member or update.message.left_chat_member or
|
||||
update.message.new_chat_title or update.message.new_chat_photo or
|
||||
update.message.delete_chat_photo or update.message.group_chat_created or
|
||||
update.message.supergroup_chat_created or
|
||||
update.message.channel_chat_created or update.message.migrate_to_chat_id or
|
||||
update.message.migrate_from_chat_id or update.message.pinned_message)
|
||||
# yapf: enable
|
||||
def status_update(message):
|
||||
return bool(message.new_chat_member or message.left_chat_member or message.new_chat_title
|
||||
or message.new_chat_photo or message.delete_chat_photo
|
||||
or message.group_chat_created or message.supergroup_chat_created
|
||||
or message.channel_chat_created or message.migrate_to_chat_id
|
||||
or message.migrate_from_chat_id or message.pinned_message)
|
||||
|
||||
@staticmethod
|
||||
def forwarded(message):
|
||||
return bool(message.forward_date)
|
||||
|
||||
@staticmethod
|
||||
def entity(entity_type):
|
||||
"""Filters messages to only allow those which have a :class:`telegram.MessageEntity`
|
||||
where their `type` matches `entity_type`.
|
||||
|
||||
Args:
|
||||
entity_type: Entity type to check for. All types can be found as constants
|
||||
in :class:`telegram.MessageEntity`.
|
||||
|
||||
Returns: function to use as filter
|
||||
"""
|
||||
|
||||
def entities_filter(message):
|
||||
return any([entity.type == entity_type for entity in message.entities])
|
||||
|
||||
return entities_filter
|
||||
|
||||
|
||||
class MessageHandler(Handler):
|
||||
@@ -102,31 +119,47 @@ class MessageHandler(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.
|
||||
allow_edited (Optional[bool]): If the handler should also accept edited messages.
|
||||
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``
|
||||
"""
|
||||
|
||||
def __init__(self, filters, callback, 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
|
||||
|
||||
def check_update(self, update):
|
||||
if isinstance(update, Update) and update.message:
|
||||
if (isinstance(update, Update)
|
||||
and (update.message or update.edited_message and self.allow_edited)):
|
||||
|
||||
if not self.filters:
|
||||
res = True
|
||||
|
||||
else:
|
||||
res = any(func(update) for func in self.filters)
|
||||
message = update.message or update.edited_message
|
||||
res = any(func(message) for func in self.filters)
|
||||
|
||||
else:
|
||||
res = False
|
||||
|
||||
return res
|
||||
|
||||
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
|
||||
|
||||
# old non-PEP8 Handler methods
|
||||
m = "telegram.MessageHandler."
|
||||
checkUpdate = deprecate(check_update, m + "checkUpdate", m + "check_update")
|
||||
handleUpdate = deprecate(handle_update, m + "handleUpdate", m + "handle_update")
|
||||
|
||||
@@ -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,10 @@ 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 +88,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."
|
||||
|
||||
@@ -35,28 +35,40 @@ class StringCommandHandler(Handler):
|
||||
pass_args (optional[bool]): If the handler should be passed the
|
||||
arguments passed to the command as a keyword argument called `
|
||||
``args``. It will contain a list of strings, which is the text
|
||||
following the command split on spaces. Default is ``False``
|
||||
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``
|
||||
following the command split on single or consecutive whitespace characters.
|
||||
Default is ``False``
|
||||
pass_update_queue (optional[bool]): If set to ``True``, a keyword argument called
|
||||
``update_queue`` will be passed to the callback function. It will be the ``Queue``
|
||||
instance used by the ``Updater`` and ``Dispatcher`` that contains new updates which can
|
||||
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
|
||||
|
||||
def check_update(self, update):
|
||||
return (isinstance(update, str) and update.startswith('/') and
|
||||
update[1:].split(' ')[0] == self.command)
|
||||
return (isinstance(update, str) and update.startswith('/')
|
||||
and update[1:].split(' ')[0] == self.command)
|
||||
|
||||
def handle_update(self, update, dispatcher):
|
||||
optional_args = self.collect_optional_args(dispatcher)
|
||||
|
||||
if self.pass_args:
|
||||
optional_args['args'] = update.split(' ')[1:]
|
||||
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,10 @@ 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 +83,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,20 @@ 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 +60,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."
|
||||
|
||||
+56
-51
@@ -28,12 +28,13 @@ import subprocess
|
||||
from signal import signal, SIGINT, SIGTERM, SIGABRT
|
||||
from queue import Queue
|
||||
|
||||
from telegram import Bot, TelegramError, NullHandler
|
||||
from telegram.ext import dispatcher, Dispatcher, JobQueue
|
||||
from telegram import Bot, TelegramError
|
||||
from telegram.ext import Dispatcher, JobQueue
|
||||
from telegram.error import Unauthorized, InvalidToken
|
||||
from telegram.utils.request import Request
|
||||
from telegram.utils.webhookhandler import (WebhookServer, WebhookHandler)
|
||||
|
||||
logging.getLogger(__name__).addHandler(NullHandler())
|
||||
logging.getLogger(__name__).addHandler(logging.NullHandler())
|
||||
|
||||
|
||||
class Updater(object):
|
||||
@@ -57,20 +58,19 @@ class Updater(object):
|
||||
base_url (Optional[str]):
|
||||
workers (Optional[int]): Amount of threads in the thread pool for
|
||||
functions decorated with @run_async
|
||||
bot (Optional[Bot]):
|
||||
bot (Optional[Bot]): A pre-initialized bot instance. If a pre-initizlied bot is used, it is
|
||||
the user's responsibility to create it using a `Request` instance with a large enough
|
||||
connection pool.
|
||||
job_queue_tick_interval(Optional[float]): The interval the queue should
|
||||
be checked for new tasks. Defaults to 1.0
|
||||
|
||||
Raises:
|
||||
ValueError: If both `token` and `bot` are passed or none of them.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
token=None,
|
||||
base_url=None,
|
||||
workers=4,
|
||||
bot=None,
|
||||
job_queue_tick_interval=1.0):
|
||||
"""
|
||||
_request = None
|
||||
|
||||
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):
|
||||
@@ -79,11 +79,23 @@ class Updater(object):
|
||||
if bot is not None:
|
||||
self.bot = bot
|
||||
else:
|
||||
self.bot = Bot(token, base_url)
|
||||
# we need a connection pool the size of:
|
||||
# * for each of the workers
|
||||
# * 1 for Dispatcher
|
||||
# * 1 for polling Updater (even if webhook is used, we can spare a connection)
|
||||
# * 1 for JobQueue
|
||||
# * 1 for main thread
|
||||
self._request = Request(con_pool_size=workers + 4)
|
||||
self.bot = Bot(token, base_url, request=self._request)
|
||||
self.update_queue = Queue()
|
||||
self.job_queue = JobQueue(self.bot, 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
|
||||
@@ -112,22 +124,25 @@ class Updater(object):
|
||||
def start_polling(self,
|
||||
poll_interval=0.0,
|
||||
timeout=10,
|
||||
network_delay=2,
|
||||
network_delay=5.,
|
||||
clean=False,
|
||||
bootstrap_retries=0):
|
||||
"""
|
||||
Starts polling updates from Telegram.
|
||||
|
||||
Args:
|
||||
poll_interval (Optional[float]): Time to wait between polling
|
||||
updates from Telegram in seconds. Default is 0.0
|
||||
poll_interval (Optional[float]): Time to wait between polling updates from Telegram in
|
||||
seconds. Default is 0.0
|
||||
|
||||
timeout (Optional[float]): Passed to Bot.getUpdates
|
||||
|
||||
network_delay (Optional[float]): Passed to Bot.getUpdates
|
||||
clean (Optional[bool]): Whether to clean any pending updates on
|
||||
Telegram servers before actually starting to poll. Default is
|
||||
False.
|
||||
bootstrap_retries (Optional[int[): Whether the bootstrapping phase
|
||||
of the `Updater` will retry on failures on the Telegram server.
|
||||
|
||||
clean (Optional[bool]): Whether to clean any pending updates on Telegram servers before
|
||||
actually starting to poll. Default is False.
|
||||
|
||||
bootstrap_retries (Optional[int]): Whether the bootstrapping phase of the `Updater`
|
||||
will retry on failures on the Telegram server.
|
||||
|
||||
| < 0 - retry indefinitely
|
||||
| 0 - no retries (default)
|
||||
@@ -214,9 +229,8 @@ class Updater(object):
|
||||
|
||||
while self.running:
|
||||
try:
|
||||
updates = self.bot.getUpdates(self.last_update_id,
|
||||
timeout=timeout,
|
||||
network_delay=network_delay)
|
||||
updates = self.bot.getUpdates(
|
||||
self.last_update_id, timeout=timeout, network_delay=network_delay)
|
||||
except TelegramError as te:
|
||||
self.logger.error("Error while getting Updates: {0}".format(te))
|
||||
|
||||
@@ -260,7 +274,8 @@ class Updater(object):
|
||||
url_path = '/{0}'.format(url_path)
|
||||
|
||||
# Create and start server
|
||||
self.httpd = WebhookServer((listen, port), WebhookHandler, self.update_queue, url_path)
|
||||
self.httpd = WebhookServer((listen, port), WebhookHandler, self.update_queue, url_path,
|
||||
self.bot)
|
||||
|
||||
if use_ssl:
|
||||
self._check_ssl_cert(cert, key)
|
||||
@@ -269,10 +284,11 @@ class Updater(object):
|
||||
if not webhook_url:
|
||||
webhook_url = self._gen_webhook_url(listen, port, url_path)
|
||||
|
||||
self._bootstrap(max_retries=bootstrap_retries,
|
||||
clean=clean,
|
||||
webhook_url=webhook_url,
|
||||
cert=open(cert, 'rb'))
|
||||
self._bootstrap(
|
||||
max_retries=bootstrap_retries,
|
||||
clean=clean,
|
||||
webhook_url=webhook_url,
|
||||
cert=open(cert, 'rb'))
|
||||
elif clean:
|
||||
self.logger.warning("cleaning updates is not supported if "
|
||||
"SSL-termination happens elsewhere; skipping")
|
||||
@@ -290,10 +306,8 @@ class Updater(object):
|
||||
exit_code = 0
|
||||
if exit_code is 0:
|
||||
try:
|
||||
self.httpd.socket = ssl.wrap_socket(self.httpd.socket,
|
||||
certfile=cert,
|
||||
keyfile=key,
|
||||
server_side=True)
|
||||
self.httpd.socket = ssl.wrap_socket(
|
||||
self.httpd.socket, certfile=cert, keyfile=key, server_side=True)
|
||||
except ssl.SSLError as error:
|
||||
self.logger.exception('Failed to init SSL socket')
|
||||
raise TelegramError(str(error))
|
||||
@@ -306,7 +320,7 @@ class Updater(object):
|
||||
|
||||
def _bootstrap(self, max_retries, clean, webhook_url, cert=None):
|
||||
retries = 0
|
||||
while True:
|
||||
while 1:
|
||||
|
||||
try:
|
||||
if clean:
|
||||
@@ -318,8 +332,8 @@ class Updater(object):
|
||||
except (Unauthorized, InvalidToken):
|
||||
raise
|
||||
except TelegramError:
|
||||
msg = 'error in bootstrap phase; try={0} max_retries={1}'\
|
||||
.format(retries, max_retries)
|
||||
msg = 'error in bootstrap phase; try={0} max_retries={1}'.format(retries,
|
||||
max_retries)
|
||||
if max_retries < 0 or retries < max_retries:
|
||||
self.logger.warning(msg)
|
||||
retries += 1
|
||||
@@ -343,7 +357,7 @@ class Updater(object):
|
||||
|
||||
self.job_queue.stop()
|
||||
with self.__lock:
|
||||
if self.running:
|
||||
if self.running or self.dispatcher.has_running_threads:
|
||||
self.logger.debug('Stopping Updater and Dispatcher...')
|
||||
|
||||
self.running = False
|
||||
@@ -351,10 +365,10 @@ class Updater(object):
|
||||
self._stop_httpd()
|
||||
self._stop_dispatcher()
|
||||
self._join_threads()
|
||||
# async threads must be join()ed only after the dispatcher
|
||||
# thread was joined, otherwise we can still have new async
|
||||
# threads dispatched
|
||||
self._join_async_threads()
|
||||
|
||||
# Stop the Request instance only if it was created by the Updater
|
||||
if self._request:
|
||||
self._request.stop()
|
||||
|
||||
def _stop_httpd(self):
|
||||
if self.httpd:
|
||||
@@ -368,15 +382,6 @@ class Updater(object):
|
||||
self.logger.debug('Requesting Dispatcher to stop...')
|
||||
self.dispatcher.stop()
|
||||
|
||||
def _join_async_threads(self):
|
||||
with dispatcher.async_lock:
|
||||
threads = list(dispatcher.async_threads)
|
||||
total = len(threads)
|
||||
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))
|
||||
|
||||
def _join_threads(self):
|
||||
for thr in self.__threads:
|
||||
self.logger.debug('Waiting for {0} thread to end'.format(thr.name))
|
||||
|
||||
+12
-6
@@ -21,7 +21,6 @@
|
||||
from os.path import basename
|
||||
|
||||
from telegram import TelegramObject
|
||||
from telegram.utils.request import download as _download
|
||||
|
||||
|
||||
class File(TelegramObject):
|
||||
@@ -34,25 +33,31 @@ class File(TelegramObject):
|
||||
|
||||
Args:
|
||||
file_id (str):
|
||||
bot (telegram.Bot):
|
||||
**kwargs: Arbitrary keyword arguments.
|
||||
|
||||
Keyword Args:
|
||||
file_size (Optional[int]):
|
||||
file_path (Optional[str]):
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, file_id, **kwargs):
|
||||
def __init__(self, file_id, bot, **kwargs):
|
||||
# Required
|
||||
self.file_id = str(file_id)
|
||||
|
||||
# Optionals
|
||||
self.file_size = int(kwargs.get('file_size', 0))
|
||||
self.file_path = str(kwargs.get('file_path', ''))
|
||||
|
||||
self.bot = bot
|
||||
|
||||
@staticmethod
|
||||
def de_json(data):
|
||||
def de_json(data, bot):
|
||||
"""
|
||||
Args:
|
||||
data (str):
|
||||
data (dict):
|
||||
bot (telegram.Bot):
|
||||
|
||||
Returns:
|
||||
telegram.File:
|
||||
@@ -60,12 +65,13 @@ class File(TelegramObject):
|
||||
if not data:
|
||||
return None
|
||||
|
||||
return File(**data)
|
||||
return File(bot=bot, **data)
|
||||
|
||||
def download(self, custom_path=None):
|
||||
"""
|
||||
Args:
|
||||
custom_path (str):
|
||||
|
||||
"""
|
||||
url = self.file_path
|
||||
|
||||
@@ -74,4 +80,4 @@ class File(TelegramObject):
|
||||
else:
|
||||
filename = basename(url)
|
||||
|
||||
_download(url, filename)
|
||||
self.bot.request.download(url, filename)
|
||||
|
||||
@@ -43,10 +43,11 @@ class ForceReply(ReplyMarkup):
|
||||
self.selective = bool(kwargs.get('selective', False))
|
||||
|
||||
@staticmethod
|
||||
def de_json(data):
|
||||
def de_json(data, bot):
|
||||
"""
|
||||
Args:
|
||||
data (str):
|
||||
data (dict):
|
||||
bot (telegram.Bot):
|
||||
|
||||
Returns:
|
||||
telegram.ForceReply:
|
||||
|
||||
@@ -52,8 +52,16 @@ class InlineKeyboardButton(TelegramObject):
|
||||
self.switch_inline_query = kwargs.get('switch_inline_query')
|
||||
|
||||
@staticmethod
|
||||
def de_json(data):
|
||||
data = super(InlineKeyboardButton, InlineKeyboardButton).de_json(data)
|
||||
def de_json(data, bot):
|
||||
"""
|
||||
Args:
|
||||
data (dict):
|
||||
bot (telegram.Bot):
|
||||
|
||||
Returns:
|
||||
telegram.InlineKeyboardButton:
|
||||
"""
|
||||
data = super(InlineKeyboardButton, InlineKeyboardButton).de_json(data, bot)
|
||||
|
||||
if not data:
|
||||
return None
|
||||
@@ -61,12 +69,12 @@ class InlineKeyboardButton(TelegramObject):
|
||||
return InlineKeyboardButton(**data)
|
||||
|
||||
@staticmethod
|
||||
def de_list(data):
|
||||
def de_list(data, bot):
|
||||
if not data:
|
||||
return []
|
||||
|
||||
inline_keyboards = list()
|
||||
for inline_keyboard in data:
|
||||
inline_keyboards.append(InlineKeyboardButton.de_json(inline_keyboard))
|
||||
inline_keyboards.append(InlineKeyboardButton.de_json(inline_keyboard, bot))
|
||||
|
||||
return inline_keyboards
|
||||
|
||||
@@ -33,18 +33,26 @@ class InlineKeyboardMarkup(ReplyMarkup):
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, inline_keyboard):
|
||||
def __init__(self, inline_keyboard, **kwargs):
|
||||
# Required
|
||||
self.inline_keyboard = inline_keyboard
|
||||
|
||||
@staticmethod
|
||||
def de_json(data):
|
||||
data = super(InlineKeyboardMarkup, InlineKeyboardMarkup).de_json(data)
|
||||
def de_json(data, bot):
|
||||
"""
|
||||
Args:
|
||||
data (dict):
|
||||
bot (telegram.Bot):
|
||||
|
||||
Returns:
|
||||
telegram.InlineKeyboardMarkup:
|
||||
"""
|
||||
data = super(InlineKeyboardMarkup, InlineKeyboardMarkup).de_json(data, bot)
|
||||
|
||||
if not data:
|
||||
return None
|
||||
|
||||
data['inline_keyboard'] = [InlineKeyboardButton.de_list(inline_keyboard)
|
||||
data['inline_keyboard'] = [InlineKeyboardButton.de_list(inline_keyboard, bot)
|
||||
for inline_keyboard in data['inline_keyboard']]
|
||||
|
||||
return InlineKeyboardMarkup(**data)
|
||||
|
||||
+14
-6
@@ -42,9 +42,10 @@ class InlineQuery(TelegramObject):
|
||||
|
||||
Keyword Args:
|
||||
location (optional[:class:`telegram.Location`]):
|
||||
bot (Optional[Bot]): The Bot to use for instance methods
|
||||
"""
|
||||
|
||||
def __init__(self, id, from_user, query, offset, **kwargs):
|
||||
def __init__(self, id, from_user, query, offset, bot=None, **kwargs):
|
||||
# Required
|
||||
self.id = id
|
||||
self.from_user = from_user
|
||||
@@ -54,24 +55,27 @@ class InlineQuery(TelegramObject):
|
||||
# Optional
|
||||
self.location = kwargs.get('location')
|
||||
|
||||
self.bot = bot
|
||||
|
||||
@staticmethod
|
||||
def de_json(data):
|
||||
def de_json(data, bot):
|
||||
"""
|
||||
Args:
|
||||
data (dict):
|
||||
bot (telegram.Bot):
|
||||
|
||||
Returns:
|
||||
telegram.InlineQuery:
|
||||
"""
|
||||
data = super(InlineQuery, InlineQuery).de_json(data)
|
||||
data = super(InlineQuery, InlineQuery).de_json(data, bot)
|
||||
|
||||
if not data:
|
||||
return None
|
||||
|
||||
data['from_user'] = User.de_json(data.get('from'))
|
||||
data['location'] = Location.de_json(data.get('location'))
|
||||
data['from_user'] = User.de_json(data.get('from'), bot)
|
||||
data['location'] = Location.de_json(data.get('location'), bot)
|
||||
|
||||
return InlineQuery(**data)
|
||||
return InlineQuery(bot=bot, **data)
|
||||
|
||||
def to_dict(self):
|
||||
"""
|
||||
@@ -84,3 +88,7 @@ class InlineQuery(TelegramObject):
|
||||
data['from'] = data.pop('from_user', None)
|
||||
|
||||
return data
|
||||
|
||||
def answer(self, *args, **kwargs):
|
||||
"""Shortcut for ``bot.answerInlineQuery(update.inline_query.id, *args, **kwargs)``"""
|
||||
return self.bot.answerInlineQuery(self.id, *args, **kwargs)
|
||||
|
||||
@@ -35,11 +35,11 @@ class InlineQueryResult(TelegramObject):
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, type, id):
|
||||
def __init__(self, type, id, **kwargs):
|
||||
# Required
|
||||
self.type = str(type)
|
||||
self.id = str(id)
|
||||
|
||||
@staticmethod
|
||||
def de_json(data):
|
||||
return super(InlineQueryResult, InlineQueryResult).de_json(data)
|
||||
def de_json(data, bot):
|
||||
return super(InlineQueryResult, InlineQueryResult).de_json(data, bot)
|
||||
|
||||
@@ -94,11 +94,11 @@ class InlineQueryResultArticle(InlineQueryResult):
|
||||
self.thumb_height = thumb_height
|
||||
|
||||
@staticmethod
|
||||
def de_json(data):
|
||||
data = super(InlineQueryResultArticle, InlineQueryResultArticle).de_json(data)
|
||||
def de_json(data, bot):
|
||||
data = super(InlineQueryResultArticle, InlineQueryResultArticle).de_json(data, bot)
|
||||
|
||||
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'))
|
||||
data['input_message_content'] = InputMessageContent.de_json(data.get(
|
||||
'input_message_content'))
|
||||
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'), bot)
|
||||
data['input_message_content'] = InputMessageContent.de_json(
|
||||
data.get('input_message_content'), bot)
|
||||
|
||||
return InlineQueryResultArticle(**data)
|
||||
|
||||
@@ -84,11 +84,11 @@ class InlineQueryResultAudio(InlineQueryResult):
|
||||
self.input_message_content = input_message_content
|
||||
|
||||
@staticmethod
|
||||
def de_json(data):
|
||||
data = super(InlineQueryResultAudio, InlineQueryResultAudio).de_json(data)
|
||||
def de_json(data, bot):
|
||||
data = super(InlineQueryResultAudio, InlineQueryResultAudio).de_json(data, bot)
|
||||
|
||||
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'))
|
||||
data['input_message_content'] = InputMessageContent.de_json(data.get(
|
||||
'input_message_content'))
|
||||
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'), bot)
|
||||
data['input_message_content'] = InputMessageContent.de_json(
|
||||
data.get('input_message_content'), bot)
|
||||
|
||||
return InlineQueryResultAudio(**data)
|
||||
|
||||
@@ -65,11 +65,11 @@ class InlineQueryResultCachedAudio(InlineQueryResult):
|
||||
self.input_message_content = input_message_content
|
||||
|
||||
@staticmethod
|
||||
def de_json(data):
|
||||
data = super(InlineQueryResultCachedAudio, InlineQueryResultCachedAudio).de_json(data)
|
||||
def de_json(data, bot):
|
||||
data = super(InlineQueryResultCachedAudio, InlineQueryResultCachedAudio).de_json(data, bot)
|
||||
|
||||
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'))
|
||||
data['input_message_content'] = InputMessageContent.de_json(data.get(
|
||||
'input_message_content'))
|
||||
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'), bot)
|
||||
data['input_message_content'] = InputMessageContent.de_json(
|
||||
data.get('input_message_content'), bot)
|
||||
|
||||
return InlineQueryResultCachedAudio(**data)
|
||||
|
||||
@@ -49,12 +49,12 @@ class InlineQueryResultCachedDocument(InlineQueryResult):
|
||||
self.input_message_content = input_message_content
|
||||
|
||||
@staticmethod
|
||||
def de_json(data):
|
||||
def de_json(data, bot):
|
||||
data = super(InlineQueryResultCachedDocument,
|
||||
InlineQueryResultCachedDocument).de_json(data)
|
||||
InlineQueryResultCachedDocument).de_json(data, bot)
|
||||
|
||||
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'))
|
||||
data['input_message_content'] = InputMessageContent.de_json(data.get(
|
||||
'input_message_content'))
|
||||
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'), bot)
|
||||
data['input_message_content'] = InputMessageContent.de_json(
|
||||
data.get('input_message_content'), bot)
|
||||
|
||||
return InlineQueryResultCachedDocument(**data)
|
||||
|
||||
@@ -47,11 +47,11 @@ class InlineQueryResultCachedGif(InlineQueryResult):
|
||||
self.input_message_content = input_message_content
|
||||
|
||||
@staticmethod
|
||||
def de_json(data):
|
||||
data = super(InlineQueryResultCachedGif, InlineQueryResultCachedGif).de_json(data)
|
||||
def de_json(data, bot):
|
||||
data = super(InlineQueryResultCachedGif, InlineQueryResultCachedGif).de_json(data, bot)
|
||||
|
||||
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'))
|
||||
data['input_message_content'] = InputMessageContent.de_json(data.get(
|
||||
'input_message_content'))
|
||||
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'), bot)
|
||||
data['input_message_content'] = InputMessageContent.de_json(
|
||||
data.get('input_message_content'), bot)
|
||||
|
||||
return InlineQueryResultCachedGif(**data)
|
||||
|
||||
@@ -47,12 +47,12 @@ class InlineQueryResultCachedMpeg4Gif(InlineQueryResult):
|
||||
self.input_message_content = input_message_content
|
||||
|
||||
@staticmethod
|
||||
def de_json(data):
|
||||
def de_json(data, bot):
|
||||
data = super(InlineQueryResultCachedMpeg4Gif,
|
||||
InlineQueryResultCachedMpeg4Gif).de_json(data)
|
||||
InlineQueryResultCachedMpeg4Gif).de_json(data, bot)
|
||||
|
||||
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'))
|
||||
data['input_message_content'] = InputMessageContent.de_json(data.get(
|
||||
'input_message_content'))
|
||||
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'), bot)
|
||||
data['input_message_content'] = InputMessageContent.de_json(
|
||||
data.get('input_message_content'), bot)
|
||||
|
||||
return InlineQueryResultCachedMpeg4Gif(**data)
|
||||
|
||||
@@ -50,11 +50,11 @@ class InlineQueryResultCachedPhoto(InlineQueryResult):
|
||||
self.input_message_content = input_message_content
|
||||
|
||||
@staticmethod
|
||||
def de_json(data):
|
||||
data = super(InlineQueryResultCachedPhoto, InlineQueryResultCachedPhoto).de_json(data)
|
||||
def de_json(data, bot):
|
||||
data = super(InlineQueryResultCachedPhoto, InlineQueryResultCachedPhoto).de_json(data, bot)
|
||||
|
||||
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'))
|
||||
data['input_message_content'] = InputMessageContent.de_json(data.get(
|
||||
'input_message_content'))
|
||||
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'), bot)
|
||||
data['input_message_content'] = InputMessageContent.de_json(
|
||||
data.get('input_message_content'), bot)
|
||||
|
||||
return InlineQueryResultCachedPhoto(**data)
|
||||
|
||||
@@ -41,11 +41,12 @@ class InlineQueryResultCachedSticker(InlineQueryResult):
|
||||
self.input_message_content = input_message_content
|
||||
|
||||
@staticmethod
|
||||
def de_json(data):
|
||||
data = super(InlineQueryResultCachedSticker, InlineQueryResultCachedSticker).de_json(data)
|
||||
def de_json(data, bot):
|
||||
data = super(InlineQueryResultCachedSticker,
|
||||
InlineQueryResultCachedSticker).de_json(data, bot)
|
||||
|
||||
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'))
|
||||
data['input_message_content'] = InputMessageContent.de_json(data.get(
|
||||
'input_message_content'))
|
||||
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'), bot)
|
||||
data['input_message_content'] = InputMessageContent.de_json(
|
||||
data.get('input_message_content'), bot)
|
||||
|
||||
return InlineQueryResultCachedSticker(**data)
|
||||
|
||||
@@ -49,11 +49,11 @@ class InlineQueryResultCachedVideo(InlineQueryResult):
|
||||
self.input_message_content = input_message_content
|
||||
|
||||
@staticmethod
|
||||
def de_json(data):
|
||||
data = super(InlineQueryResultCachedVideo, InlineQueryResultCachedVideo).de_json(data)
|
||||
def de_json(data, bot):
|
||||
data = super(InlineQueryResultCachedVideo, InlineQueryResultCachedVideo).de_json(data, bot)
|
||||
|
||||
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'))
|
||||
data['input_message_content'] = InputMessageContent.de_json(data.get(
|
||||
'input_message_content'))
|
||||
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'), bot)
|
||||
data['input_message_content'] = InputMessageContent.de_json(
|
||||
data.get('input_message_content'), bot)
|
||||
|
||||
return InlineQueryResultCachedVideo(**data)
|
||||
|
||||
@@ -46,11 +46,11 @@ class InlineQueryResultCachedVoice(InlineQueryResult):
|
||||
self.input_message_content = input_message_content
|
||||
|
||||
@staticmethod
|
||||
def de_json(data):
|
||||
data = super(InlineQueryResultCachedVoice, InlineQueryResultCachedVoice).de_json(data)
|
||||
def de_json(data, bot):
|
||||
data = super(InlineQueryResultCachedVoice, InlineQueryResultCachedVoice).de_json(data, bot)
|
||||
|
||||
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'))
|
||||
data['input_message_content'] = InputMessageContent.de_json(data.get(
|
||||
'input_message_content'))
|
||||
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'), bot)
|
||||
data['input_message_content'] = InputMessageContent.de_json(
|
||||
data.get('input_message_content'), bot)
|
||||
|
||||
return InlineQueryResultCachedVoice(**data)
|
||||
|
||||
@@ -55,11 +55,11 @@ class InlineQueryResultContact(InlineQueryResult):
|
||||
self.thumb_height = thumb_height
|
||||
|
||||
@staticmethod
|
||||
def de_json(data):
|
||||
data = super(InlineQueryResultContact, InlineQueryResultContact).de_json(data)
|
||||
def de_json(data, bot):
|
||||
data = super(InlineQueryResultContact, InlineQueryResultContact).de_json(data, bot)
|
||||
|
||||
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'))
|
||||
data['input_message_content'] = InputMessageContent.de_json(data.get(
|
||||
'input_message_content'))
|
||||
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'), bot)
|
||||
data['input_message_content'] = InputMessageContent.de_json(
|
||||
data.get('input_message_content'), bot)
|
||||
|
||||
return InlineQueryResultContact(**data)
|
||||
|
||||
@@ -60,11 +60,11 @@ class InlineQueryResultDocument(InlineQueryResult):
|
||||
self.thumb_height = thumb_height
|
||||
|
||||
@staticmethod
|
||||
def de_json(data):
|
||||
data = super(InlineQueryResultDocument, InlineQueryResultDocument).de_json(data)
|
||||
def de_json(data, bot):
|
||||
data = super(InlineQueryResultDocument, InlineQueryResultDocument).de_json(data, bot)
|
||||
|
||||
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'))
|
||||
data['input_message_content'] = InputMessageContent.de_json(data.get(
|
||||
'input_message_content'))
|
||||
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'), bot)
|
||||
data['input_message_content'] = InputMessageContent.de_json(
|
||||
data.get('input_message_content'), bot)
|
||||
|
||||
return InlineQueryResultDocument(**data)
|
||||
|
||||
@@ -56,11 +56,11 @@ class InlineQueryResultGif(InlineQueryResult):
|
||||
self.input_message_content = input_message_content
|
||||
|
||||
@staticmethod
|
||||
def de_json(data):
|
||||
data = super(InlineQueryResultGif, InlineQueryResultGif).de_json(data)
|
||||
def de_json(data, bot):
|
||||
data = super(InlineQueryResultGif, InlineQueryResultGif).de_json(data, bot)
|
||||
|
||||
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'))
|
||||
data['input_message_content'] = InputMessageContent.de_json(data.get(
|
||||
'input_message_content'))
|
||||
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'), bot)
|
||||
data['input_message_content'] = InputMessageContent.de_json(
|
||||
data.get('input_message_content'), bot)
|
||||
|
||||
return InlineQueryResultGif(**data)
|
||||
|
||||
@@ -54,11 +54,11 @@ class InlineQueryResultLocation(InlineQueryResult):
|
||||
self.thumb_height = thumb_height
|
||||
|
||||
@staticmethod
|
||||
def de_json(data):
|
||||
data = super(InlineQueryResultLocation, InlineQueryResultLocation).de_json(data)
|
||||
def de_json(data, bot):
|
||||
data = super(InlineQueryResultLocation, InlineQueryResultLocation).de_json(data, bot)
|
||||
|
||||
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'))
|
||||
data['input_message_content'] = InputMessageContent.de_json(data.get(
|
||||
'input_message_content'))
|
||||
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'), bot)
|
||||
data['input_message_content'] = InputMessageContent.de_json(
|
||||
data.get('input_message_content'), bot)
|
||||
|
||||
return InlineQueryResultLocation(**data)
|
||||
|
||||
@@ -56,11 +56,11 @@ class InlineQueryResultMpeg4Gif(InlineQueryResult):
|
||||
self.input_message_content = input_message_content
|
||||
|
||||
@staticmethod
|
||||
def de_json(data):
|
||||
data = super(InlineQueryResultMpeg4Gif, InlineQueryResultMpeg4Gif).de_json(data)
|
||||
def de_json(data, bot):
|
||||
data = super(InlineQueryResultMpeg4Gif, InlineQueryResultMpeg4Gif).de_json(data, bot)
|
||||
|
||||
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'))
|
||||
data['input_message_content'] = InputMessageContent.de_json(data.get(
|
||||
'input_message_content'))
|
||||
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'), bot)
|
||||
data['input_message_content'] = InputMessageContent.de_json(
|
||||
data.get('input_message_content'), bot)
|
||||
|
||||
return InlineQueryResultMpeg4Gif(**data)
|
||||
|
||||
@@ -58,11 +58,11 @@ class InlineQueryResultPhoto(InlineQueryResult):
|
||||
self.input_message_content = input_message_content
|
||||
|
||||
@staticmethod
|
||||
def de_json(data):
|
||||
data = super(InlineQueryResultPhoto, InlineQueryResultPhoto).de_json(data)
|
||||
def de_json(data, bot):
|
||||
data = super(InlineQueryResultPhoto, InlineQueryResultPhoto).de_json(data, bot)
|
||||
|
||||
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'))
|
||||
data['input_message_content'] = InputMessageContent.de_json(data.get(
|
||||
'input_message_content'))
|
||||
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'), bot)
|
||||
data['input_message_content'] = InputMessageContent.de_json(
|
||||
data.get('input_message_content'), bot)
|
||||
|
||||
return InlineQueryResultPhoto(**data)
|
||||
|
||||
@@ -60,11 +60,11 @@ class InlineQueryResultVenue(InlineQueryResult):
|
||||
self.thumb_height = thumb_height
|
||||
|
||||
@staticmethod
|
||||
def de_json(data):
|
||||
data = super(InlineQueryResultVenue, InlineQueryResultVenue).de_json(data)
|
||||
def de_json(data, bot):
|
||||
data = super(InlineQueryResultVenue, InlineQueryResultVenue).de_json(data, bot)
|
||||
|
||||
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'))
|
||||
data['input_message_content'] = InputMessageContent.de_json(data.get(
|
||||
'input_message_content'))
|
||||
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'), bot)
|
||||
data['input_message_content'] = InputMessageContent.de_json(
|
||||
data.get('input_message_content'), bot)
|
||||
|
||||
return InlineQueryResultVenue(**data)
|
||||
|
||||
@@ -63,11 +63,11 @@ class InlineQueryResultVideo(InlineQueryResult):
|
||||
self.input_message_content = input_message_content
|
||||
|
||||
@staticmethod
|
||||
def de_json(data):
|
||||
data = super(InlineQueryResultVideo, InlineQueryResultVideo).de_json(data)
|
||||
def de_json(data, bot):
|
||||
data = super(InlineQueryResultVideo, InlineQueryResultVideo).de_json(data, bot)
|
||||
|
||||
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'))
|
||||
data['input_message_content'] = InputMessageContent.de_json(data.get(
|
||||
'input_message_content'))
|
||||
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'), bot)
|
||||
data['input_message_content'] = InputMessageContent.de_json(
|
||||
data.get('input_message_content'), bot)
|
||||
|
||||
return InlineQueryResultVideo(**data)
|
||||
|
||||
@@ -47,11 +47,11 @@ class InlineQueryResultVoice(InlineQueryResult):
|
||||
self.input_message_content = input_message_content
|
||||
|
||||
@staticmethod
|
||||
def de_json(data):
|
||||
data = super(InlineQueryResultVoice, InlineQueryResultVoice).de_json(data)
|
||||
def de_json(data, bot):
|
||||
data = super(InlineQueryResultVoice, InlineQueryResultVoice).de_json(data, bot)
|
||||
|
||||
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'))
|
||||
data['input_message_content'] = InputMessageContent.de_json(data.get(
|
||||
'input_message_content'))
|
||||
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'), bot)
|
||||
data['input_message_content'] = InputMessageContent.de_json(
|
||||
data.get('input_message_content'), bot)
|
||||
|
||||
return InlineQueryResultVoice(**data)
|
||||
|
||||
@@ -25,7 +25,7 @@ from telegram import InputMessageContent
|
||||
class InputContactMessageContent(InputMessageContent):
|
||||
"""Base class for Telegram InputContactMessageContent Objects"""
|
||||
|
||||
def __init__(self, phone_number, first_name, last_name=None):
|
||||
def __init__(self, phone_number, first_name, last_name=None, **kwargs):
|
||||
# Required
|
||||
self.phone_number = phone_number
|
||||
self.first_name = first_name
|
||||
@@ -33,5 +33,5 @@ class InputContactMessageContent(InputMessageContent):
|
||||
self.last_name = last_name
|
||||
|
||||
@staticmethod
|
||||
def de_json(data):
|
||||
def de_json(data, bot):
|
||||
return InputContactMessageContent(**data)
|
||||
|
||||
@@ -125,9 +125,9 @@ class InputFile(object):
|
||||
|
||||
# Add input_file to upload
|
||||
form.extend([
|
||||
form_boundary, 'Content-Disposition: form-data; name="%s"; filename="%s"' % (
|
||||
self.input_name, self.filename
|
||||
), 'Content-Type: %s' % self.mimetype, '', self.input_file_content
|
||||
form_boundary, 'Content-Disposition: form-data; name="%s"; filename="%s"' %
|
||||
(self.input_name,
|
||||
self.filename), 'Content-Type: %s' % self.mimetype, '', self.input_file_content
|
||||
])
|
||||
|
||||
form.append('--' + self.boundary + '--')
|
||||
|
||||
@@ -25,11 +25,11 @@ from telegram import InputMessageContent
|
||||
class InputLocationMessageContent(InputMessageContent):
|
||||
"""Base class for Telegram InputLocationMessageContent Objects"""
|
||||
|
||||
def __init__(self, latitude, longitude):
|
||||
def __init__(self, latitude, longitude, **kwargs):
|
||||
# Required
|
||||
self.latitude = latitude
|
||||
self.longitude = longitude
|
||||
|
||||
@staticmethod
|
||||
def de_json(data):
|
||||
def de_json(data, bot):
|
||||
return InputLocationMessageContent(**data)
|
||||
|
||||
@@ -26,33 +26,33 @@ class InputMessageContent(TelegramObject):
|
||||
"""Base class for Telegram InputMessageContent Objects"""
|
||||
|
||||
@staticmethod
|
||||
def de_json(data):
|
||||
data = super(InputMessageContent, InputMessageContent).de_json(data)
|
||||
def de_json(data, bot):
|
||||
data = super(InputMessageContent, InputMessageContent).de_json(data, bot)
|
||||
|
||||
if not data:
|
||||
return None
|
||||
|
||||
try:
|
||||
from telegram import InputTextMessageContent
|
||||
return InputTextMessageContent.de_json(data)
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
try:
|
||||
from telegram import InputLocationMessageContent
|
||||
return InputLocationMessageContent.de_json(data)
|
||||
return InputTextMessageContent.de_json(data, bot)
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
try:
|
||||
from telegram import InputVenueMessageContent
|
||||
return InputVenueMessageContent.de_json(data)
|
||||
return InputVenueMessageContent.de_json(data, bot)
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
try:
|
||||
from telegram import InputLocationMessageContent
|
||||
return InputLocationMessageContent.de_json(data, bot)
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
try:
|
||||
from telegram import InputContactMessageContent
|
||||
return InputContactMessageContent.de_json(data)
|
||||
return InputContactMessageContent.de_json(data, bot)
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ from telegram import InputMessageContent
|
||||
class InputTextMessageContent(InputMessageContent):
|
||||
"""Base class for Telegram InputTextMessageContent Objects"""
|
||||
|
||||
def __init__(self, message_text, parse_mode=None, disable_web_page_preview=None):
|
||||
def __init__(self, message_text, parse_mode=None, disable_web_page_preview=None, **kwargs):
|
||||
# Required
|
||||
self.message_text = message_text
|
||||
# Optionals
|
||||
@@ -33,5 +33,5 @@ class InputTextMessageContent(InputMessageContent):
|
||||
self.disable_web_page_preview = disable_web_page_preview
|
||||
|
||||
@staticmethod
|
||||
def de_json(data):
|
||||
def de_json(data, bot):
|
||||
return InputTextMessageContent(**data)
|
||||
|
||||
@@ -25,7 +25,7 @@ from telegram import InputMessageContent
|
||||
class InputVenueMessageContent(InputMessageContent):
|
||||
"""Base class for Telegram InputVenueMessageContent Objects"""
|
||||
|
||||
def __init__(self, latitude, longitude, title, address, foursquare_id=None):
|
||||
def __init__(self, latitude, longitude, title, address, foursquare_id=None, **kwargs):
|
||||
# Required
|
||||
self.latitude = latitude
|
||||
self.longitude = longitude
|
||||
@@ -35,5 +35,5 @@ class InputVenueMessageContent(InputMessageContent):
|
||||
self.foursquare_id = foursquare_id
|
||||
|
||||
@staticmethod
|
||||
def de_json(data):
|
||||
def de_json(data, bot):
|
||||
return InputVenueMessageContent(**data)
|
||||
|
||||
@@ -33,7 +33,7 @@ class KeyboardButton(TelegramObject):
|
||||
request_contact (Optional[bool]):
|
||||
"""
|
||||
|
||||
def __init__(self, text, request_contact=None, request_location=None):
|
||||
def __init__(self, text, request_contact=None, request_location=None, **kwargs):
|
||||
# Required
|
||||
self.text = text
|
||||
# Optionals
|
||||
@@ -43,19 +43,19 @@ class KeyboardButton(TelegramObject):
|
||||
self.request_location = request_location
|
||||
|
||||
@staticmethod
|
||||
def de_json(data):
|
||||
def de_json(data, bot):
|
||||
if not data:
|
||||
return None
|
||||
|
||||
return KeyboardButton(**data)
|
||||
|
||||
@staticmethod
|
||||
def de_list(data):
|
||||
def de_list(data, bot):
|
||||
if not data:
|
||||
return []
|
||||
|
||||
keyboards = list()
|
||||
for keyboard in data:
|
||||
keyboards.append(KeyboardButton.de_json(keyboard))
|
||||
keyboards.append(KeyboardButton.de_json(keyboard, bot))
|
||||
|
||||
return keyboards
|
||||
|
||||
@@ -33,16 +33,17 @@ class Location(TelegramObject):
|
||||
latitude (float):
|
||||
"""
|
||||
|
||||
def __init__(self, longitude, latitude):
|
||||
def __init__(self, longitude, latitude, **kwargs):
|
||||
# Required
|
||||
self.longitude = float(longitude)
|
||||
self.latitude = float(latitude)
|
||||
|
||||
@staticmethod
|
||||
def de_json(data):
|
||||
def de_json(data, bot):
|
||||
"""
|
||||
Args:
|
||||
data (str):
|
||||
data (dict):
|
||||
bot (telegram.Bot):
|
||||
|
||||
Returns:
|
||||
telegram.Location:
|
||||
|
||||
+247
-22
@@ -19,6 +19,7 @@
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
"""This module contains a object that represents a Telegram Message."""
|
||||
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from time import mktime
|
||||
|
||||
@@ -40,6 +41,7 @@ class Message(TelegramObject):
|
||||
forward_from_chat (:class:`telegram.Chat`):
|
||||
forward_date (:class:`datetime.datetime`):
|
||||
reply_to_message (:class:`telegram.Message`):
|
||||
edit_date (:class:`datetime.datetime`):
|
||||
text (str):
|
||||
audio (:class:`telegram.Audio`):
|
||||
document (:class:`telegram.Document`):
|
||||
@@ -80,6 +82,7 @@ class Message(TelegramObject):
|
||||
forward_from_chat (:class:`telegram.Chat`):
|
||||
forward_date (Optional[:class:`datetime.datetime`]):
|
||||
reply_to_message (Optional[:class:`telegram.Message`]):
|
||||
edit_date (Optional[:class:`datetime.datetime`]):
|
||||
text (Optional[str]):
|
||||
audio (Optional[:class:`telegram.Audio`]):
|
||||
document (Optional[:class:`telegram.Document`]):
|
||||
@@ -100,9 +103,10 @@ class Message(TelegramObject):
|
||||
migrate_to_chat_id (Optional[int]):
|
||||
migrate_from_chat_id (Optional[int]):
|
||||
channel_chat_created (Optional[bool]):
|
||||
bot (Optional[Bot]): The Bot to use for instance methods
|
||||
"""
|
||||
|
||||
def __init__(self, message_id, from_user, date, chat, **kwargs):
|
||||
def __init__(self, message_id, from_user, date, chat, bot=None, **kwargs):
|
||||
# Required
|
||||
self.message_id = int(message_id)
|
||||
self.from_user = from_user
|
||||
@@ -113,6 +117,7 @@ class Message(TelegramObject):
|
||||
self.forward_from_chat = kwargs.get('forward_from_chat')
|
||||
self.forward_date = kwargs.get('forward_date')
|
||||
self.reply_to_message = kwargs.get('reply_to_message')
|
||||
self.edit_date = kwargs.get('edit_date')
|
||||
self.text = kwargs.get('text', '')
|
||||
self.entities = kwargs.get('entities', list())
|
||||
self.audio = kwargs.get('audio')
|
||||
@@ -137,16 +142,19 @@ class Message(TelegramObject):
|
||||
self.channel_chat_created = bool(kwargs.get('channel_chat_created', False))
|
||||
self.pinned_message = kwargs.get('pinned_message')
|
||||
|
||||
self.bot = bot
|
||||
|
||||
@property
|
||||
def chat_id(self):
|
||||
"""int: Short for :attr:`Message.chat.id`"""
|
||||
return self.chat.id
|
||||
|
||||
@staticmethod
|
||||
def de_json(data):
|
||||
def de_json(data, bot):
|
||||
"""
|
||||
Args:
|
||||
data (dict):
|
||||
bot (telegram.Bot):
|
||||
|
||||
Returns:
|
||||
telegram.Message:
|
||||
@@ -154,29 +162,30 @@ class Message(TelegramObject):
|
||||
if not data:
|
||||
return None
|
||||
|
||||
data['from_user'] = User.de_json(data.get('from'))
|
||||
data['from_user'] = User.de_json(data.get('from'), bot)
|
||||
data['date'] = datetime.fromtimestamp(data['date'])
|
||||
data['chat'] = Chat.de_json(data.get('chat'))
|
||||
data['entities'] = MessageEntity.de_list(data.get('entities'))
|
||||
data['forward_from'] = User.de_json(data.get('forward_from'))
|
||||
data['forward_from_chat'] = Chat.de_json(data.get('forward_from_chat'))
|
||||
data['chat'] = Chat.de_json(data.get('chat'), bot)
|
||||
data['entities'] = MessageEntity.de_list(data.get('entities'), bot)
|
||||
data['forward_from'] = User.de_json(data.get('forward_from'), bot)
|
||||
data['forward_from_chat'] = Chat.de_json(data.get('forward_from_chat'), bot)
|
||||
data['forward_date'] = Message._fromtimestamp(data.get('forward_date'))
|
||||
data['reply_to_message'] = Message.de_json(data.get('reply_to_message'))
|
||||
data['audio'] = Audio.de_json(data.get('audio'))
|
||||
data['document'] = Document.de_json(data.get('document'))
|
||||
data['photo'] = PhotoSize.de_list(data.get('photo'))
|
||||
data['sticker'] = Sticker.de_json(data.get('sticker'))
|
||||
data['video'] = Video.de_json(data.get('video'))
|
||||
data['voice'] = Voice.de_json(data.get('voice'))
|
||||
data['contact'] = Contact.de_json(data.get('contact'))
|
||||
data['location'] = Location.de_json(data.get('location'))
|
||||
data['venue'] = Venue.de_json(data.get('venue'))
|
||||
data['new_chat_member'] = User.de_json(data.get('new_chat_member'))
|
||||
data['left_chat_member'] = User.de_json(data.get('left_chat_member'))
|
||||
data['new_chat_photo'] = PhotoSize.de_list(data.get('new_chat_photo'))
|
||||
data['pinned_message'] = Message.de_json(data.get('pinned_message'))
|
||||
data['reply_to_message'] = Message.de_json(data.get('reply_to_message'), bot)
|
||||
data['edit_date'] = Message._fromtimestamp(data.get('edit_date'))
|
||||
data['audio'] = Audio.de_json(data.get('audio'), bot)
|
||||
data['document'] = Document.de_json(data.get('document'), bot)
|
||||
data['photo'] = PhotoSize.de_list(data.get('photo'), bot)
|
||||
data['sticker'] = Sticker.de_json(data.get('sticker'), bot)
|
||||
data['video'] = Video.de_json(data.get('video'), bot)
|
||||
data['voice'] = Voice.de_json(data.get('voice'), bot)
|
||||
data['contact'] = Contact.de_json(data.get('contact'), bot)
|
||||
data['location'] = Location.de_json(data.get('location'), bot)
|
||||
data['venue'] = Venue.de_json(data.get('venue'), bot)
|
||||
data['new_chat_member'] = User.de_json(data.get('new_chat_member'), bot)
|
||||
data['left_chat_member'] = User.de_json(data.get('left_chat_member'), bot)
|
||||
data['new_chat_photo'] = PhotoSize.de_list(data.get('new_chat_photo'), bot)
|
||||
data['pinned_message'] = Message.de_json(data.get('pinned_message'), bot)
|
||||
|
||||
return Message(**data)
|
||||
return Message(bot=bot, **data)
|
||||
|
||||
def __getitem__(self, item):
|
||||
if item in self.__dict__.keys():
|
||||
@@ -197,6 +206,8 @@ class Message(TelegramObject):
|
||||
# Optionals
|
||||
if self.forward_date:
|
||||
data['forward_date'] = self._totimestamp(self.forward_date)
|
||||
if self.edit_date:
|
||||
data['edit_date'] = self._totimestamp(self.edit_date)
|
||||
if self.photo:
|
||||
data['photo'] = [p.to_dict() for p in self.photo]
|
||||
if self.entities:
|
||||
@@ -238,3 +249,217 @@ class Message(TelegramObject):
|
||||
except AttributeError:
|
||||
# Python 3 (< 3.3) and Python 2
|
||||
return int(mktime(dt_obj.timetuple()))
|
||||
|
||||
def _quote(self, kwargs):
|
||||
"""Modify kwargs for replying with or without quoting"""
|
||||
|
||||
if 'reply_to_message_id' in kwargs:
|
||||
if 'quote' in kwargs:
|
||||
del kwargs['quote']
|
||||
|
||||
elif 'quote' in kwargs:
|
||||
if kwargs['quote']:
|
||||
kwargs['reply_to_message_id'] = self.message_id
|
||||
|
||||
del kwargs['quote']
|
||||
|
||||
else:
|
||||
if self.chat.type != Chat.PRIVATE:
|
||||
kwargs['reply_to_message_id'] = self.message_id
|
||||
|
||||
def reply_text(self, *args, **kwargs):
|
||||
"""
|
||||
Shortcut for ``bot.sendMessage(update.message.chat_id, *args, **kwargs)``
|
||||
|
||||
Keyword Args:
|
||||
quote (Optional[bool]): If set to ``True``, the message is sent as an actual reply to
|
||||
this message. If ``reply_to_message_id`` is passed in ``kwargs``, this parameter
|
||||
will be ignored. Default: ``True`` in group chats and ``False`` in private chats.
|
||||
"""
|
||||
|
||||
self._quote(kwargs)
|
||||
return self.bot.sendMessage(self.chat_id, *args, **kwargs)
|
||||
|
||||
def reply_photo(self, *args, **kwargs):
|
||||
"""
|
||||
Shortcut for ``bot.sendPhoto(update.message.chat_id, *args, **kwargs)``
|
||||
|
||||
Keyword Args:
|
||||
quote (Optional[bool]): If set to ``True``, the photo is sent as an actual reply to
|
||||
this message. If ``reply_to_message_id`` is passed in ``kwargs``, this parameter
|
||||
will be ignored. Default: ``True`` in group chats and ``False`` in private chats.
|
||||
"""
|
||||
|
||||
self._quote(kwargs)
|
||||
return self.bot.sendPhoto(self.chat_id, *args, **kwargs)
|
||||
|
||||
def reply_audio(self, *args, **kwargs):
|
||||
"""
|
||||
Shortcut for ``bot.sendAudio(update.message.chat_id, *args, **kwargs)``
|
||||
|
||||
Keyword Args:
|
||||
quote (Optional[bool]): If set to ``True``, the audio is sent as an actual reply to
|
||||
this message. If ``reply_to_message_id`` is passed in ``kwargs``, this parameter
|
||||
will be ignored. Default: ``True`` in group chats and ``False`` in private chats.
|
||||
"""
|
||||
|
||||
self._quote(kwargs)
|
||||
return self.bot.sendAudio(self.chat_id, *args, **kwargs)
|
||||
|
||||
def reply_document(self, *args, **kwargs):
|
||||
"""
|
||||
Shortcut for ``bot.sendDocument(update.message.chat_id, *args, **kwargs)``
|
||||
|
||||
Keyword Args:
|
||||
quote (Optional[bool]): If set to ``True``, the document is sent as an actual reply to
|
||||
this message. If ``reply_to_message_id`` is passed in ``kwargs``, this parameter
|
||||
will be ignored. Default: ``True`` in group chats and ``False`` in private chats.
|
||||
"""
|
||||
|
||||
self._quote(kwargs)
|
||||
return self.bot.sendDocument(self.chat_id, *args, **kwargs)
|
||||
|
||||
def reply_sticker(self, *args, **kwargs):
|
||||
"""
|
||||
Shortcut for ``bot.sendSticker(update.message.chat_id, *args, **kwargs)``
|
||||
|
||||
Keyword Args:
|
||||
quote (Optional[bool]): If set to ``True``, the sticker is sent as an actual reply to
|
||||
this message. If ``reply_to_message_id`` is passed in ``kwargs``, this parameter
|
||||
will be ignored. Default: ``True`` in group chats and ``False`` in private chats.
|
||||
"""
|
||||
|
||||
self._quote(kwargs)
|
||||
return self.bot.sendSticker(self.chat_id, *args, **kwargs)
|
||||
|
||||
def reply_video(self, *args, **kwargs):
|
||||
"""
|
||||
Shortcut for ``bot.sendVideo(update.message.chat_id, *args, **kwargs)``
|
||||
|
||||
Keyword Args:
|
||||
quote (Optional[bool]): If set to ``True``, the video is sent as an actual reply to
|
||||
this message. If ``reply_to_message_id`` is passed in ``kwargs``, this parameter
|
||||
will be ignored. Default: ``True`` in group chats and ``False`` in private chats.
|
||||
"""
|
||||
|
||||
self._quote(kwargs)
|
||||
return self.bot.sendVideo(self.chat_id, *args, **kwargs)
|
||||
|
||||
def reply_voice(self, *args, **kwargs):
|
||||
"""
|
||||
Shortcut for ``bot.sendVoice(update.message.chat_id, *args, **kwargs)``
|
||||
|
||||
Keyword Args:
|
||||
quote (Optional[bool]): If set to ``True``, the voice is sent as an actual reply to
|
||||
this message. If ``reply_to_message_id`` is passed in ``kwargs``, this parameter
|
||||
will be ignored. Default: ``True`` in group chats and ``False`` in private chats.
|
||||
"""
|
||||
|
||||
self._quote(kwargs)
|
||||
return self.bot.sendVoice(self.chat_id, *args, **kwargs)
|
||||
|
||||
def reply_location(self, *args, **kwargs):
|
||||
"""
|
||||
Shortcut for ``bot.sendLocation(update.message.chat_id, *args, **kwargs)``
|
||||
|
||||
Keyword Args:
|
||||
quote (Optional[bool]): If set to ``True``, the location is sent as an actual reply to
|
||||
this message. If ``reply_to_message_id`` is passed in ``kwargs``, this parameter
|
||||
will be ignored. Default: ``True`` in group chats and ``False`` in private chats.
|
||||
"""
|
||||
|
||||
self._quote(kwargs)
|
||||
return self.bot.sendLocation(self.chat_id, *args, **kwargs)
|
||||
|
||||
def reply_venue(self, *args, **kwargs):
|
||||
"""
|
||||
Shortcut for ``bot.sendVenue(update.message.chat_id, *args, **kwargs)``
|
||||
|
||||
Keyword Args:
|
||||
quote (Optional[bool]): If set to ``True``, the venue is sent as an actual reply to
|
||||
this message. If ``reply_to_message_id`` is passed in ``kwargs``, this parameter
|
||||
will be ignored. Default: ``True`` in group chats and ``False`` in private chats.
|
||||
"""
|
||||
|
||||
self._quote(kwargs)
|
||||
return self.bot.sendVenue(self.chat_id, *args, **kwargs)
|
||||
|
||||
def reply_contact(self, *args, **kwargs):
|
||||
"""
|
||||
Shortcut for ``bot.sendContact(update.message.chat_id, *args, **kwargs)``
|
||||
|
||||
Keyword Args:
|
||||
quote (Optional[bool]): If set to ``True``, the contact is sent as an actual reply to
|
||||
this message. If ``reply_to_message_id`` is passed in ``kwargs``, this parameter
|
||||
will be ignored. Default: ``True`` in group chats and ``False`` in private chats.
|
||||
"""
|
||||
|
||||
self._quote(kwargs)
|
||||
return self.bot.sendContact(self.chat_id, *args, **kwargs)
|
||||
|
||||
def forward(self, chat_id, disable_notification=False):
|
||||
"""Shortcut for
|
||||
|
||||
bot.forwardMessage(chat_id=chat_id,
|
||||
from_chat_id=update.message.chat_id,
|
||||
disable_notification=disable_notification,
|
||||
message_id=update.message.message_id)
|
||||
|
||||
"""
|
||||
return self.bot.forwardMessage(
|
||||
chat_id=chat_id,
|
||||
from_chat_id=self.chat_id,
|
||||
disable_notification=disable_notification,
|
||||
message_id=self.message_id)
|
||||
|
||||
def parse_entity(self, entity):
|
||||
"""
|
||||
Returns the text from a given :class:`telegram.MessageEntity`.
|
||||
|
||||
Note:
|
||||
This method is present because Telegram calculates the offset and length in
|
||||
UTF-16 codepoint pairs, which some versions of Python don't handle automatically.
|
||||
(That is, you can't just slice ``Message.text`` with the offset and length.)
|
||||
|
||||
Args:
|
||||
entity (MessageEntity): The entity to extract the text from. It must be an entity that
|
||||
belongs to this message.
|
||||
|
||||
Returns:
|
||||
str: The text of the given entity
|
||||
"""
|
||||
# Is it a narrow build, if so we don't need to convert
|
||||
if sys.maxunicode == 0xffff:
|
||||
return self.text[entity.offset:entity.offset + entity.length]
|
||||
else:
|
||||
entity_text = self.text.encode('utf-16-le')
|
||||
entity_text = entity_text[entity.offset * 2:(entity.offset + entity.length) * 2]
|
||||
|
||||
return entity_text.decode('utf-16-le')
|
||||
|
||||
def parse_entities(self, types=None):
|
||||
"""
|
||||
Returns a ``dict`` that maps :class:`telegram.MessageEntity` to ``str``.
|
||||
It contains entities from this message filtered by their ``type`` attribute as the key, and
|
||||
the text that each entity belongs to as the value of the ``dict``.
|
||||
|
||||
Note:
|
||||
This method should always be used instead of the ``entities`` attribute, since it
|
||||
calculates the correct substring from the message text based on UTF-16 codepoints.
|
||||
See ``get_entity_text`` for more info.
|
||||
|
||||
Args:
|
||||
types (Optional[list]): List of ``MessageEntity`` types as strings. If the ``type``
|
||||
attribute of an entity is contained in this list, it will be returned.
|
||||
Defaults to a list of all types. All types can be found as constants in
|
||||
:class:`telegram.MessageEntity`.
|
||||
|
||||
Returns:
|
||||
dict[:class:`telegram.MessageEntity`, ``str``]: A dictionary of entities mapped to the
|
||||
text that belongs to them, calculated based on UTF-16 codepoints.
|
||||
"""
|
||||
if types is None:
|
||||
types = MessageEntity.ALL_TYPES
|
||||
|
||||
return {entity: self.parse_entity(entity)
|
||||
for entity in self.entities if entity.type in types}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
"""This module contains a object that represents a Telegram MessageEntity."""
|
||||
|
||||
from telegram import TelegramObject
|
||||
from telegram import User, TelegramObject
|
||||
|
||||
|
||||
class MessageEntity(TelegramObject):
|
||||
@@ -31,24 +31,28 @@ class MessageEntity(TelegramObject):
|
||||
offset (int):
|
||||
length (int):
|
||||
url (Optional[str]):
|
||||
user (Optional[:class:`telegram.User`]):
|
||||
"""
|
||||
|
||||
def __init__(self, type, offset, length, url=None):
|
||||
def __init__(self, type, offset, length, **kwargs):
|
||||
# Required
|
||||
self.type = type
|
||||
self.offset = offset
|
||||
self.length = length
|
||||
# Optionals
|
||||
self.url = url
|
||||
self.url = kwargs.get('url')
|
||||
self.user = kwargs.get('user')
|
||||
|
||||
@staticmethod
|
||||
def de_json(data):
|
||||
data = super(MessageEntity, MessageEntity).de_json(data)
|
||||
def de_json(data, bot):
|
||||
data = super(MessageEntity, MessageEntity).de_json(data, bot)
|
||||
|
||||
data['user'] = User.de_json(data.get('user'), bot)
|
||||
|
||||
return MessageEntity(**data)
|
||||
|
||||
@staticmethod
|
||||
def de_list(data):
|
||||
def de_list(data, bot):
|
||||
"""
|
||||
Args:
|
||||
data (list):
|
||||
@@ -61,6 +65,20 @@ class MessageEntity(TelegramObject):
|
||||
|
||||
entities = list()
|
||||
for entity in data:
|
||||
entities.append(MessageEntity.de_json(entity))
|
||||
entities.append(MessageEntity.de_json(entity, bot))
|
||||
|
||||
return entities
|
||||
|
||||
MENTION = 'mention'
|
||||
HASHTAG = 'hashtag'
|
||||
BOT_COMMAND = 'bot_command'
|
||||
URL = 'url'
|
||||
EMAIL = 'email'
|
||||
BOLD = 'bold'
|
||||
ITALIC = 'italic'
|
||||
CODE = 'code'
|
||||
PRE = 'pre'
|
||||
TEXT_LINK = 'text_link'
|
||||
TEXT_MENTION = 'text_mention'
|
||||
ALL_TYPES = [MENTION, HASHTAG, BOT_COMMAND, URL, EMAIL, BOLD, ITALIC, CODE, PRE, TEXT_LINK,
|
||||
TEXT_MENTION]
|
||||
|
||||
@@ -51,14 +51,15 @@ class PhotoSize(TelegramObject):
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, self.__class__):
|
||||
return False
|
||||
return (self.file_id == other.file_id and self.width == other.width and
|
||||
self.height == other.height and self.file_size == other.file_size)
|
||||
return (self.file_id == other.file_id and self.width == other.width
|
||||
and self.height == other.height and self.file_size == other.file_size)
|
||||
|
||||
@staticmethod
|
||||
def de_json(data):
|
||||
def de_json(data, bot):
|
||||
"""
|
||||
Args:
|
||||
data (str):
|
||||
data (dict):
|
||||
bot (telegram.Bot):
|
||||
|
||||
Returns:
|
||||
telegram.PhotoSize:
|
||||
@@ -69,10 +70,11 @@ class PhotoSize(TelegramObject):
|
||||
return PhotoSize(**data)
|
||||
|
||||
@staticmethod
|
||||
def de_list(data):
|
||||
def de_list(data, bot):
|
||||
"""
|
||||
Args:
|
||||
data (list):
|
||||
bot (telegram.Bot):
|
||||
|
||||
Returns:
|
||||
List<telegram.PhotoSize>:
|
||||
@@ -82,6 +84,6 @@ class PhotoSize(TelegramObject):
|
||||
|
||||
photos = list()
|
||||
for photo in data:
|
||||
photos.append(PhotoSize.de_json(photo))
|
||||
photos.append(PhotoSize.de_json(photo, bot))
|
||||
|
||||
return photos
|
||||
|
||||
@@ -44,10 +44,11 @@ class ReplyKeyboardHide(ReplyMarkup):
|
||||
self.selective = bool(kwargs.get('selective', False))
|
||||
|
||||
@staticmethod
|
||||
def de_json(data):
|
||||
def de_json(data, bot):
|
||||
"""
|
||||
Args:
|
||||
data (str):
|
||||
data (dict):
|
||||
bot(telegram.Bot):
|
||||
|
||||
Returns:
|
||||
telegram.ReplyKeyboardHide:
|
||||
|
||||
@@ -50,10 +50,11 @@ class ReplyKeyboardMarkup(ReplyMarkup):
|
||||
self.selective = bool(kwargs.get('selective', False))
|
||||
|
||||
@staticmethod
|
||||
def de_json(data):
|
||||
def de_json(data, bot):
|
||||
"""
|
||||
Args:
|
||||
data (str):
|
||||
data (dict):
|
||||
bot (telegram.Bot):
|
||||
|
||||
Returns:
|
||||
telegram.ReplyKeyboardMarkup:
|
||||
@@ -61,7 +62,7 @@ class ReplyKeyboardMarkup(ReplyMarkup):
|
||||
if not data:
|
||||
return None
|
||||
|
||||
data['keyboard'] = [KeyboardButton.de_list(keyboard) for keyboard in data['keyboard']]
|
||||
data['keyboard'] = [KeyboardButton.de_list(keyboard, bot) for keyboard in data['keyboard']]
|
||||
|
||||
return ReplyKeyboardMarkup(**data)
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user