Compare commits

..

154 Commits

Author SHA1 Message Date
Jannes Höke 834bf192b9 Bump version to v5.0.0 2016-07-15 01:48:11 +02:00
Jannes Höke d5486433e5 use job context for timerbot example 2016-07-15 01:46:27 +02:00
Jannes Höke c9ec436d68 remove old state machine example 2016-07-15 01:45:43 +02:00
Jannes Höke ad3eec2af8 ConversationHandler (#331)
* initial commit for conversationhandler and example

* implement simple Promise for run_async/conversationhandler

* refactor Promise._done to done

* add handling for timed out Promises

* correctly handle promises with None results

* fix handling tuple states

* update comments on example

* Added a first test on the ConversationHandler.

* Fixed a small typo.

* Yapf'd.

* add sphinx doc for conversation handler

* fix title for callbackqueryhandler sphinx docs
2016-07-15 01:30:54 +02:00
leandrotoledo e3fe1d2632 Merge branch 'master' of https://github.com/python-telegram-bot/python-telegram-bot 2016-07-14 17:19:13 -03:00
leandrotoledo 52bd3daa39 Bumping version of pre-commit [ci skip] 2016-07-14 17:18:29 -03:00
Leandro Toledo b4e8209f2c Update CONTRIBUTING.rst
Adding a bullet on "assertEqual method's arguments should be in ('actual', 'expected') order"
2016-07-14 17:16:25 -03:00
Valentijn f5f95ef8c9 Documentation (#350)
* Small fixes to documentation and add myself to AUTHORS.

* Rework CONTRIBUTIONS.rst

Use code-blocks instead of literals, change headings for portability and use a relative link to AUTHORS instead of linking to a specific copy.
2016-07-14 16:56:05 -03:00
Rahiel Kasim 04a871aff5 introduce constants module (#342) 2016-07-14 21:48:31 +02:00
Jannes Höke 81a755a7d8 Merge branch 'urllib3_fix_proxy_auth' 2016-07-13 15:09:23 +02:00
Noam Meltzer 6016aca0ba Bump version to v4.3.4 2016-07-12 23:34:49 +03:00
Noam Meltzer 7c908db901 urllib3: can now connect through proxies which require auth
fixes #343
2016-07-12 23:31:38 +03:00
Noam Meltzer d192b385ea dispatcher: add comment to describe the reason for conpool size 2016-07-12 21:58:27 +03:00
Jannes Höke f0b2028e3f Merge pull request #344 from python-telegram-bot/silence-webhook-logging
Move webhook handler logs to logging at DEBUG level
2016-07-12 14:35:14 +02:00
Mikki Weesenaar f443003408 Small change in the documentation. 2016-07-12 13:45:37 +02:00
Jannes Höke afc36a235b move webhook handler logs to logging at DEBUG level 2016-07-11 23:44:40 +02:00
Noam Meltzer b76337de87 __main__.py: assist with creating issues on github
usage:
python -m telegram

and copy/paste the output
2016-07-09 14:40:53 +03:00
Noam Meltzer 6afee6e0bd Merge pull request #340 from python-telegram-bot/v4.3.x
urllib3: now supports proxy
2016-07-08 23:53:56 +03:00
Jannes Höke 27e57bbf58 Bump version to v4.3.3 2016-07-08 22:13:46 +02:00
Noam Meltzer 4990d664bb Merge pull request #339 from python-telegram-bot/urllib3_fix_proxy
urllib3: now supports proxy
2016-07-08 23:00:02 +03:00
Noam Meltzer b3e42c3e20 urllib3: now supports proxy
fixes #336
2016-07-08 22:33:37 +03:00
Jannes Höke c2cce40299 Merge branch 'use-timeout' 2016-07-04 21:56:26 +02:00
Jannes Höke a2ed7b26f1 Bump version to v4.3.2 2016-07-04 21:52:00 +02:00
Jannes Höke 89a3dc8372 use urlopen timeout 2016-07-04 21:40:31 +02:00
Jannes Höke 9fd298a393 Merge pull request #307 from python-telegram-bot/jobqueue-rework
Make job queue API similar to the dispatcher, add new functionality
2016-06-29 16:20:43 +02:00
Jannes Höke ecbc268781 Bump version to v4.3.1 2016-06-29 15:53:52 +02:00
Jannes Höke c7c21c94ea update requirement: urllib3>=1.10 2016-06-29 15:53:10 +02:00
Jannes Höke 57efde5e0f Bump version to v4.3 2016-06-28 13:35:42 +02:00
Jannes Höke 31073101a3 yapf 2016-06-24 19:22:49 +02:00
Noam Meltzer 1e0ebe89f3 JobQueue: minimize the amount of places changing self.__tick state
- start the jobqueue (by default) during __init__() instead of during
   put()
 - protect self._next_peek and self.__tick with a Lock
 - rename self._start() to self._main_loop()
 - stop() is now blocking until the event loop thread exits
2016-06-24 19:35:54 +03:00
Noam Meltzer 35872d7a8b test_jobqueue: fix test_jobs_tuple()
this test was based on timing and assumed that the JobQueue did not have
time to start processing the queue before checking the assert.
what we really should do is make sure JobQueue does not process anything
2016-06-24 19:13:40 +03:00
Noam Meltzer f65b6911ea JobQueue: use class name for the logger name 2016-06-24 19:13:40 +03:00
Noam Meltzer 02af1ea803 jobqueue: cosmetic fixes 2016-06-24 19:13:40 +03:00
Jannes Höke c4a8ee5175 Merge branch 'master' into jobqueue-rework
Conflicts:
	tests/test_jobqueue.py
2016-06-20 05:32:15 +02:00
Noam Meltzer e0539d5992 Merge pull request #327 from python-telegram-bot/urllib3
Urllib3
2016-06-20 06:30:25 +03:00
Jannes Höke 738e3213a7 Merge branch 'master' into jobqueue-rework 2016-06-20 00:49:01 +02:00
leandrotoledo b41f7e3e79 Code style with latest yapf 2016-06-19 17:50:02 -04:00
Jannes Höke caf72ca490 Merge branch 'urllib3' of github.com:python-telegram-bot/python-telegram-bot into urllib3 2016-06-19 23:46:53 +02:00
Jannes Höke 7635bc0eec comments, lock thread pool, while 1 and snake_case everywhere 2016-06-19 23:46:34 +02:00
Jannes Höke 703bece155 set loglevel of urllib3 to WARNING by default 2016-06-19 23:40:34 +02:00
Jannes Höke 949f4a4fbd update requirements: minimum versions of urllib3 and future 2016-06-19 23:39:00 +02:00
leandrotoledo 05522e4321 Merge remote-tracking branch 'origin/master' into urllib3 2016-06-19 17:38:19 -04:00
leandrotoledo 4f101a79bb Update travis yapf [ci skip] 2016-06-19 17:08:12 -04:00
Noam Meltzer 5b91194cc7 new yapf version, new cosmetic fixes 2016-06-18 20:05:10 +03:00
Noam Meltzer 494a7ec1e4 ypaf fixes 2016-06-18 19:57:11 +03:00
Noam Meltzer fc05d3a626 switch back to PoolManager
telegram servers might send a reponse with HTTP 302 (redirect) to
another hostname. in such case HTTPSConnectionPool will fail to do the
job
2016-06-18 19:50:18 +03:00
Noam Meltzer bc77c845ea test_updater: make sure that conpool is stopped before setting updater
even for the first unitest, it might come after another unitests from
another file which had already init the conpool.
2016-06-18 09:53:08 +03:00
Noam Meltzer a814e9de6b make sure to stop conpool between sensitive unitests 2016-06-18 00:50:44 +03:00
Noam Meltzer d37b6d6735 make sure to stop Updater after the test_createBot is over 2016-06-18 00:01:36 +03:00
Noam Meltzer e479c7f25e type hinting (cosmetic fix) 2016-06-17 23:59:32 +03:00
Noam Meltzer a30411c9fa make sure to remove the stopped dispatcher threads from ASYNC_THREADS 2016-06-17 23:58:22 +03:00
Noam Meltzer 881d1d0e25 fix/hack Updater.stop() not working on extreme cases
during test_bootstrap_retries_fail() there is an exception raised (by
design): TelegramError('test')

For a reason I haven't managed to pinpoint the above exception in its
precise timing caused the Updater to be left in a state which is
'self.running == False', but the dispatcher threads already initialized.
This patch identifies this extreme case and makes sure to go over the
stop procedure.
2016-06-17 23:53:18 +03:00
Noam Meltzer cb6ddfded5 Merge remote-tracking branch 'origin/master' into urllib3 2016-06-17 17:54:04 +03:00
Noam Meltzer bda0244ed8 updater: fix print in log 2016-06-17 16:52:25 +03:00
Rahiel Kasim 9338f93d24 Merge pull request #325 from python-telegram-bot/examples
more robust echobot, let roboed go
2016-06-12 17:08:15 +02:00
Rahiel Kasim e10fa66286 echobot: simplify handling messageless updates 2016-06-12 17:06:03 +02:00
Rahiel Kasim deb9de0ba0 README: remove roboed, rename example 2016-06-12 16:58:18 +02:00
Rahiel Kasim 94fd6851ab more robust echobot, let roboed go 2016-06-12 15:30:56 +02:00
leandrotoledo 897f9615f0 Bump version to v4.2.1 2016-06-10 09:44:17 -04:00
Leandro Toledo 86676d59f1 Merge pull request #321 from python-telegram-bot/editMessageText-decorator
Adds @message decorator to editMessageText #320
2016-06-04 05:19:27 -04:00
leandrotoledo f0b91ecf46 Fix travis 2016-06-03 13:44:24 -04:00
leandrotoledo bbbc622517 Adds @message decorator to editMessageText #320 2016-06-03 13:28:29 -04:00
Noam Meltzer 1f5601dae2 fix SyntaxWarning 2016-06-01 22:38:08 +03:00
Noam Meltzer 3608c2bbe5 dispatcher: if connection pool is already initialized raise exception
this will better protect the user from wrong usage
2016-06-01 22:30:34 +03:00
Noam Meltzer c28763c5be dispatcher: cosmetic fix 2016-06-01 22:30:33 +03:00
Noam Meltzer dd8b6219b9 dispatcher: a little performance improvment 2016-06-01 22:30:33 +03:00
Noam Meltzer 78f9bdcac9 dispatcher: pep8 style fix
globals are supposed to be upper case
2016-06-01 22:30:09 +03:00
Jannes Höke da95341d5b Update README.rst 2016-06-01 00:05:09 +02:00
Jannes Höke 98be6abc11 Remove clibot.py example 2016-05-31 21:07:47 +02:00
Jannes Höke b08d41d0ff formatting 2016-05-31 15:35:40 +02:00
Jannes Höke de2d732135 Merge branch 'master' into jobqueue-rework
Conflicts:
	README.rst
	telegram/ext/commandhandler.py
	telegram/ext/messagehandler.py
2016-05-31 15:34:36 +02:00
Jannes Höke 1ff348adbb issue warning if connection pool was initialized before Dispatcher 2016-05-31 13:47:43 +02:00
Jannes Höke 6b457bada5 use keepalive for connection pool 2016-05-31 13:45:43 +02:00
Jannes Höke 74283bd414 use HTTPSConnectionPool instead of PoolManager 2016-05-30 17:12:50 +02:00
Jannes Höke 41f6591ac6 more sensible logging 2016-05-30 17:12:27 +02:00
Leandro Toledo 6d08e1bc7f Merge pull request #317 from jlmadurga/issue/316
Fix callbackquery to_dict
2016-05-30 11:35:44 -03:00
Juan Madurga 073d7949dc fix callbackquery to_dict 2016-05-30 15:59:45 +02:00
Jannes Höke dd91ce1f39 use single queue for thread pool, initialize connection pool with n+3 2016-05-30 13:09:23 +02:00
Jannes Höke 57759d8e6d [drunk] use actual thread pool and queue new functions into the pool instead of starting new threads every time 2016-05-30 03:16:33 +02:00
Noam Meltzer 574fc8cddf urllib3: validate https certificate 2016-05-30 01:05:19 +03:00
Noam Meltzer b040568b07 test_bot: fix for urllib3 compatibility 2016-05-30 01:05:19 +03:00
Noam Meltzer 3076dfc086 use urllib3 instead of urllib(2) 2016-05-30 01:05:19 +03:00
Jannes Höke f8a9722573 remove duplicate target names 2016-05-29 12:35:52 +02:00
Leandro Toledo 527daaf93d Merge pull request #315 from jlmadurga/typo/changes
Fix typo in Transition to 4.0 link
2016-05-29 06:50:57 -03:00
Juan Madurga 986add59ab fix typo in Transition to 4.0 link 2016-05-29 11:37:35 +02:00
Jannes Höke bc62a1813a fix rst according to collective.checkdocs 2016-05-28 23:36:51 +02:00
Jannes Höke 41432f5b02 bump version to v4.2.0 2016-05-28 22:49:15 +02:00
Jannes Höke f737ab780a Merge pull request #314 from python-telegram-bot/docs-restructuring
Move large parts from README to Wiki
2016-05-28 22:44:17 +02:00
Rahiel Kasim e0e8f6b085 README: update docs badge link, https some links 2016-05-28 22:40:18 +02:00
Jannes Höke 17abf0274e Fix formatting 2016-05-28 21:52:03 +02:00
Jannes Höke 8fab7ad302 move stuff to wiki, other stuff 2016-05-28 21:44:10 +02:00
Rahiel Kasim f31bd91673 Merge pull request #305 from python-telegram-bot/move-botan
move botan from utils to contrib
2016-05-28 21:32:07 +02:00
Leandro Toledo 32ac617e2e Merge pull request #312 from python-telegram-bot/timeout_take2
set default network_delay to 5 seconds
2016-05-28 15:14:54 -03:00
Noam Meltzer 7e7acdeb23 set default network_delay to 5 seconds
fixes #309
2016-05-28 19:34:16 +03:00
Leandro Toledo 792ad62fe8 Merge pull request #308 from python-telegram-bot/bot2.1
New methods for Bot 2.1 API
2016-05-28 12:14:01 -03:00
Jannes Höke 25bcfa9b35 add constants for Chat.type and ChatMember.status 2016-05-28 16:51:44 +02:00
Jannes Höke ff00e211d7 include in warning that Py2.7 will still be supported 2016-05-28 16:44:39 +02:00
Jannes Höke d40f0a8309 update update_queue and job_queue docstrings on all handlers 2016-05-28 16:04:19 +02:00
Jannes Höke 783f9c375c move job_queue kwarg to end 2016-05-28 14:21:39 +02:00
Jannes Höke 406303d6bb refactor: running -> _running, next_peek -> _next_peek 2016-05-28 13:48:30 +02:00
Jannes Höke 2534e0df9b allow jobs to be ran outside of jobqueue 2016-05-28 13:41:23 +02:00
Rahiel Kasim b06983a94a let python 2 find the contrib module 2016-05-28 09:27:17 +02:00
Rahiel Kasim 2f408ab767 generate docs for botan 2016-05-28 09:18:36 +02:00
Rahiel Kasim c8497424b7 move botan to contrib 2016-05-28 09:12:10 +02:00
Leandro Toledo 80fbe98b44 Reflecting tests for Filters change 2016-05-27 21:14:55 -03:00
Leandro Toledo 22e9326610 Merge remote-tracking branch 'origin/master' into bot2.1 2016-05-27 21:10:24 -03:00
Jannes Höke a0bb5730c6 add allow_edited parameter to MessageHandler and CommandHandler 2016-05-27 11:07:06 +02:00
Jannes Höke 748cc3a35f Add isitmaintained.com issue resolving time badge 2016-05-26 23:30:29 +02:00
Leandro Toledo 9a13de4a96 Merge remote-tracking branch 'origin/master' into bot2.1
Conflicts:
	telegram/bot.py
	tests/test_bot.py
2016-05-26 16:15:50 -03:00
Noam Meltzer 561f1c3f02 bot: validate token does not contain white spaces (#306)
in addition move validation code from validate.py into bot.py and delete
the former file
2016-05-26 22:09:14 +03:00
Leandro Toledo 3907e64966 Adds telegram.utils.botan back using deprecate 2016-05-26 14:13:27 -03:00
Rahiel Kasim 1abbca3324 bot.py: fix snake_case alias 2016-05-26 15:32:02 +02:00
Jannes Höke e7f4a07b7a update timerbot example with pass_job_queue 2016-05-26 14:48:50 +02:00
Jannes Höke bb165b6acf add pass_job_queue parameter to all handler classes 2016-05-26 14:39:11 +02:00
Jannes Höke 20067ff178 add test for context parameter 2016-05-26 14:07:44 +02:00
Jannes Höke 41daccce07 minor comments and formatting 2016-05-26 14:02:52 +02:00
Jannes Höke 786216305c Add context parameter in Job class #281 2016-05-26 13:55:56 +02:00
Leandro Toledo 86571bc75d addHandler to add_handler 2016-05-26 01:55:51 -03:00
Leandro Toledo 663fa0013d merge master 2016-05-25 22:09:18 -03:00
Leandro Toledo 37c7af2e14 Add docstrings #302 2016-05-25 21:41:12 -03:00
Leandro Toledo e70625772c Keeps backwards compatibility to BadRequest #302 2016-05-25 21:24:29 -03:00
Leandro Toledo 1e398821a0 Introducing telegram.error.BadRequest and testLeaveChat testcase #302 2016-05-25 21:15:17 -03:00
Jannes Höke b3142d2974 yapf 2016-05-25 23:57:29 +02:00
Jannes Höke 8278779a23 Merge branch 'jobqueue-rework' of github.com:python-telegram-bot/python-telegram-bot into jobqueue-rework 2016-05-25 23:37:10 +02:00
Jannes Höke 3aedd78e29 make job queue API similar to the dispatcher, add new functionality 2016-05-25 23:36:41 +02:00
Jannes Höke 6b90ac9f1c make job queue API similar to the dispatcher, add new functionality 2016-05-25 22:51:13 +02:00
Rahiel Kasim 386b91f708 refactor testing if user is bot 2016-05-25 09:41:48 +02:00
Rahiel Kasim 76b9a7d328 add tests for Bot.getChat* 2016-05-25 01:31:10 +02:00
Leandro Toledo d90b0f495d End the madness when bumping versions [ci skip] 2016-05-24 19:43:24 -03:00
Rahiel Kasim c4d5eff9f3 move botan from utils to ext 2016-05-24 23:40:09 +02:00
Rahiel Kasim f45ecee820 Merge pull request #304 from TiagoDanin/FixReadme
README Update url for contribution guidelines
2016-05-24 23:09:13 +02:00
Tiago Danin c340585f33 README Update url for contribution guidelines 2016-05-24 15:49:43 -05:00
Leandro Toledo 75490ac757 Fix travis 2016-05-23 22:13:38 -03:00
Leandro Toledo 046e69b1c1 Commenting test for token with newline 2016-05-23 22:02:15 -03:00
Leandro Toledo 7e0be09c58 Merge remote-tracking branch 'origin/master' into bot2.1 2016-05-23 21:56:01 -03:00
Leandro Toledo 18f3f43026 Add issue template #298 2016-05-23 21:54:36 -03:00
Leandro Toledo 0b2fd120d8 Due kwargs I had to change the factory class in favor of InputLocationMessageContent #302 2016-05-23 21:09:07 -03:00
Leandro Toledo 408959e91c Removes examples from tests 2016-05-23 20:44:47 -03:00
Leandro Toledo ab2f6e13c9 Add kwargs to API calls #302 2016-05-23 20:43:17 -03:00
Leandro Toledo 108e4264fc Add user to MessageEntity #302 2016-05-23 20:31:36 -03:00
Leandro Toledo 68b5562c49 Add edit_date to Message #302 2016-05-23 20:28:36 -03:00
Leandro Toledo e50a3622e1 Add edited_message to Update #302 2016-05-23 20:24:43 -03:00
Leandro Toledo d7e226ec0f Add new Bot methods and ChatMember class #302 2016-05-23 20:22:31 -03:00
Leandro Toledo 7c84516d2b Merge branch 'master' of github.com:python-telegram-bot/python-telegram-bot 2016-05-23 19:44:34 -03:00
Leandro Toledo 53a91be21f Update CONTRIBUTING.rst 2016-05-23 19:05:56 -03:00
Leandro Toledo 2471eaa778 Adding some style commandments 2016-05-23 19:05:04 -03:00
leandrotoledo 6bfdff8892 Update examples to column width to 99 [ci skip] 2016-05-23 17:45:01 -03:00
leandrotoledo 2389b07382 Updates pre-commit hooks to check examples folder [ci skip] 2016-05-23 17:23:57 -03:00
leandrotoledo c7db9a96cd Set split_before_logical_operator to True 2016-05-23 17:19:35 -03:00
Leandro Toledo af89cbecf3 Add test for unstripped tokens [ci skip] 2016-05-22 18:22:22 -03:00
leandrotoledo b987c8937c Hooks for travis 2016-05-22 13:26:57 -03:00
leandrotoledo 5a0696b181 Replace individual checks to pre-commit run --all-files in Travis 2016-05-22 13:12:05 -03:00
leandrotoledo eb303903ef Fix travis 2016-05-22 12:55:48 -03:00
leandrotoledo a00f409992 Updates pre-commit hooks 2016-05-22 12:31:03 -03:00
81 changed files with 2585 additions and 1495 deletions
+201 -140
View File
@@ -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.
- Dont 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.
- Dont 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
+31
View File
@@ -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.
-->
### 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 -V``
**Version of python-telegram-bot:**
``$ python -c 'import telegram; print(telegram.__version__)'``
### Logs
Insert logs here (if necessary)
+9 -8
View File
@@ -1,17 +1,18 @@
- repo: git://github.com/pre-commit/mirrors-yapf
sha: 'v0.7.1'
sha: 316b795b2f32cbe80047aff7e842b72368d5a2c1
hooks:
- id: yapf
args: ['-i']
files: ^(telegram|tests)/.*\.py$
- repo: git://github.com/pre-commit/pre-commit-hooks
sha: 'v0.5.0'
sha: 3fa02652357ff0dbb42b5bc78c673b7bc105fcf3
hooks:
- id: flake8
args: ['telegram']
files: ^telegram/.*\.py$
- repo: git://github.com/pre-commit/mirrors-pylint
sha: 'v1.5.5'
sha: 4de6c8dfadef1a271a814561ce05b8bc1c446d22
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
+1 -3
View File
@@ -13,8 +13,6 @@ install:
- pip install -r requirements-dev.txt
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 != 2.6 ] && [ $TRAVIS_PYTHON_VERSION != 3.3 ] && [ $TRAVIS_PYTHON_VERSION != pypy3 ]; then pre-commit run --all-files; fi'
after_success:
coveralls
+1
View File
@@ -25,6 +25,7 @@ The following wonderful people contributed directly or indirectly to this projec
- `Rahiel Kasim <https://github.com/rahiel>`_
- `Shelomentsev D <https://github.com/shelomentsevd>`_
- `sooyhwang <https://github.com/sooyhwang>`_
- `Valentijn <https://github.com/Faalentijn>`_
- `wjt <https://github.com/wjt>`_
Please add yourself here alphabetically when you submit your first pull request.
+59 -1
View File
@@ -1,3 +1,61 @@
=======
Changes
=======
**2016-07-15**
*Released 5.0*
- Rework ``JobQueue``
- Introduce ``ConversationHandler``
**2016-07-12**
*Released 4.3.4*
- Fix proxy support with ``urllib3`` when proxy requires auth
**2016-07-08**
*Released 4.3.3*
- Fix proxy support with ``urllib3``
**2016-07-04**
*Released 4.3.2*
- Fix: Use ``timeout`` parameter in all API methods
**2016-06-29**
*Released 4.3.1*
- Update wrong requirement: ``urllib3>=1.10``
**2016-06-28**
*Released 4.3*
- Use ``urllib3.PoolManager`` for connection re-use
- Rewrite ``run_async`` decorator to re-use threads
- New requirements: ``urllib3`` and ``certifi``
**2016-06-10**
*Released 4.2.1*
- Fix ``CallbackQuery.to_dict()`` bug (thanks to @jlmadurga)
- Fix ``editMessageText`` exception when receiving a ``CallbackQuery``
**2016-05-28**
*Released 4.2*
- Implement Bot API 2.1
- Move ``botan`` module to ``telegram.contrib``
- New exception type: ``BadRequest``
**2016-05-22**
*Released 4.1.2*
@@ -37,7 +95,7 @@
- Implement Bot API 2.0
- Almost complete recode of ``Dispatcher``
- Please read the `Transistion Guide to 4.0 <https://github.com/python-telegram-bot/python-telegram-bot/wiki/Transistion-guide-to-Version-4.0>`_
- Please read the `Transition Guide to 4.0 <https://github.com/python-telegram-bot/python-telegram-bot/wiki/Transition-guide-to-Version-4.0>`_
- **Changes from 4.0rc1**
- The syntax of filters for ``MessageHandler`` (upper/lower cases)
- Handler groups are now identified by ``int`` only, and ordered
+89 -368
View File
@@ -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
@@ -53,12 +57,6 @@ Table of contents
#. `Learning by example`_
#. `API`_
#. `Extensions`_
#. `JobQueue`_
#. `Logging`_
#. `Documentation`_
@@ -69,46 +67,29 @@ 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 works with Python versions from 2.6+ (**Note:** Support for 2.6 will be dropped at some point
this year. 2.7 will still be supported).
It 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:
@@ -116,13 +97,47 @@ You can install or upgrade python-telegram-bot with:
$ pip install python-telegram-bot --upgrade
==================
_`Getting started`
==================
===============
Getting started
===============
View the last release API documentation at: https://core.telegram.org/bots/api
Our Wiki contains a lot of resources to get you started with ``python-telegram-bot``:
This library uses the `logging` module. To set up logging to standard output, put:
- `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>`_
Other references:
- `Telegram API documentation <https://core.telegram.org/bots/api>`_
- `python-telegram-bot documentation <https://pythonhosted.org/python-telegram-bot/>`_
-------------------
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.
- `echobot2 <https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/echobot2.py>`_ replies back messages.
- `inlinebot <https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/inlinebot.py>`_ basic example of an `inline bot <https://core.telegram.org/bots/inline>`_.
- `state machine bot <https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/state_machine_bot.py>`_ keeps the state for individual users, useful for multipart conversations.
- `timerbot <https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/timerbot.py>`_ uses the ``JobQueue`` to send timed messages.
- `echobot <https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/legacy/echobot.py>`_ uses only the pure API to echo messages.
Look at the examples on the `wiki <https://github.com/python-telegram-bot/python-telegram-bot/wiki/Examples>`_ to see other bots the community has built.
-------
Logging
-------
This library uses the ``logging`` module. To set up logging to standard output, put:
.. code:: python
@@ -132,343 +147,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 ``MessageHandler`` with a ``command`` filter 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.")
...
>>> unknown_handler = MessageHandler([Filters.command], 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
View File
@@ -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
View File
@@ -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.2'
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'),
]
+1
View File
@@ -7,6 +7,7 @@ Welcome to Python Telegram Bot's documentation!
===============================================
Contents:
telegram
.. toctree::
:maxdepth: 2
+7
View File
@@ -0,0 +1,7 @@
telegram.constants module
=========================
.. automodule:: telegram.constants
:members:
:undoc-members:
:show-inheritance:
+7
View File
@@ -0,0 +1,7 @@
telegram.contrib.botan module
=============================
.. automodule:: telegram.contrib.botan
: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:
+1
View File
@@ -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
-170
View File
@@ -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()
+160
View File
@@ -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 -4
View File
@@ -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__)
@@ -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
View File
@@ -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()
@@ -11,8 +11,7 @@ from telegram import Emoji, ForceReply, InlineKeyboardButton, \
from telegram.ext import Updater, CommandHandler, MessageHandler, \
CallbackQueryHandler, Filters
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - '
'%(message)s',
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
level=logging.DEBUG)
# Define the different states a chat can be in
@@ -20,8 +19,7 @@ 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'))
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)
@@ -58,11 +56,9 @@ def entered_value(bot, update):
# 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)
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):
@@ -82,14 +78,12 @@ def confirm_value(bot, update):
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)
message_id=query.message.message_id)
else:
bot.editMessageText(text="Alright, value is still %s."
% values.get(user_id, 'not set'),
bot.editMessageText(text="Alright, value is still %s." %
values.get(user_id, 'not set'),
chat_id=chat_id,
message_id=
query.message.message_id)
message_id=query.message.message_id)
def help(bot, update):
@@ -103,7 +97,7 @@ def error(bot, update, error):
updater = Updater("TOKEN")
# The command
updater.dispatcher.addHandler(CommandHandler('set', set_value))
updater.dispatcher.add_handler(CommandHandler('set', set_value))
# The answer
updater.dispatcher.add_handler(MessageHandler([Filters.text], entered_value))
# The confirmation
-38
View File
@@ -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()
-106
View File
@@ -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()
+36 -22
View File
@@ -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,69 @@ 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!')
# Add job to queue
job_queue.put(alarm, due, repeat=False)
job = Job(alarm, due, repeat=False, context=chat_id)
timers[chat_id] = job
job_queue.put(job)
bot.sendMessage(chat_id, text='Timer successfully set!')
except IndexError:
bot.sendMessage(chat_id, text='Usage: /set <seconds>')
except ValueError:
except (IndexError, ValueError):
bot.sendMessage(chat_id, text='Usage: /set <seconds>')
def unset(bot, update):
"""Removes the job if the user changed their mind"""
chat_id = update.message.chat_id
if chat_id not in timers:
bot.sendMessage(chat_id, text='You have no active timer')
return
job = timers[chat_id]
job.schedule_removal()
del timers[chat_id]
bot.sendMessage(chat_id, text='Timer successfully unset!')
def error(bot, update, error):
logger.warn('Update "%s" caused error "%s"' % (update, error))
def main():
global job_queue
updater = Updater("TOKEN")
job_queue = updater.job_queue
# Get the dispatcher to register handlers
dp = updater.dispatcher
@@ -75,7 +87,8 @@ def main():
# on different commands - answer in Telegram
dp.add_handler(CommandHandler("start", start))
dp.add_handler(CommandHandler("help", start))
dp.add_handler(CommandHandler("set", set, pass_args=True))
dp.add_handler(CommandHandler("set", set, pass_args=True, pass_job_queue=True))
dp.add_handler(CommandHandler("unset", unset))
# log all errors
dp.add_error_handler(error)
@@ -88,5 +101,6 @@ def main():
# start_polling() is non-blocking and will stop the bot gracefully.
updater.idle()
if __name__ == '__main__':
main()
+3 -1
View File
@@ -1 +1,3 @@
future
future>=0.15.2
urllib3>=1.10
certifi
+2
View File
@@ -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
+33 -39
View File
@@ -1,17 +1,11 @@
#!/usr/bin/env python
'''The setup and build script for the python-telegram-bot library.'''
import os
"""The setup and build script for the python-telegram-bot library."""
import codecs
import telegram
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 = []
@@ -22,33 +16,33 @@ def requirements():
return requirements_list
setup(name='python-telegram-bot',
version='4.1.2',
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:
setup(name='python-telegram-bot',
version=telegram.__version__,
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='Not just a Python wrapper around the Telegram Bot API',
long_description=fd.read(),
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',
],)
+26 -18
View File
@@ -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
@@ -79,27 +80,34 @@ from .inputvenuemessagecontent import InputVenueMessageContent
from .inputcontactmessagecontent import InputContactMessageContent
from .update import Update
from .bot import Bot
from .constants import (MAX_MESSAGE_LENGTH, MAX_CAPTION_LENGTH, SUPPORTED_WEBHOOK_PORTS,
MAX_FILESIZE_DOWNLOAD, MAX_FILESIZE_UPLOAD,
MAX_MESSAGES_PER_SECOND_PER_CHAT, MAX_MESSAGES_PER_SECOND,
MAX_MESSAGES_PER_MINUTE_PER_GROUP)
__author__ = 'devs@python-telegram-bot.org'
__version__ = '4.1.2'
__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']
__version__ = '5.0.0'
__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', 'NullHandler', 'ParseMode',
'PhotoSize', 'ReplyKeyboardHide', 'ReplyKeyboardMarkup', 'ReplyMarkup', 'Sticker',
'TelegramError', 'TelegramObject', 'Update', 'User', 'UserProfilePhotos', 'Venue',
'Video', 'Voice', 'MAX_MESSAGE_LENGTH', 'MAX_CAPTION_LENGTH', 'SUPPORTED_WEBHOOK_PORTS',
'MAX_FILESIZE_DOWNLOAD', 'MAX_FILESIZE_UPLOAD', 'MAX_MESSAGES_PER_SECOND_PER_CHAT',
'MAX_MESSAGES_PER_SECOND', 'MAX_MESSAGES_PER_MINUTE_PER_GROUP']
if version_info < (2, 7):
from warnings import warn
warn("python-telegram-bot will stop supporting Python 2.6 in a future release. "
"Please upgrade your Python!")
"Please upgrade your Python version to at least Python 2.7!")
+18
View File
@@ -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()
+205 -35
View File
@@ -19,13 +19,13 @@
# 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 import (User, Message, Update, Chat, ChatMember, UserProfilePhotos, File,
ReplyMarkup, TelegramObject, NullHandler)
from telegram.error import InvalidToken
from telegram.utils import request
from telegram.utils.validate import validate_token
logging.getLogger(__name__).addHandler(NullHandler())
@@ -48,7 +48,7 @@ class Bot(TelegramObject):
"""
def __init__(self, token, base_url=None, base_file_url=None):
self.token = validate_token(token)
self.token = self._validate_token(token)
if not base_url:
self.base_url = 'https://api.telegram.org/bot{0}'.format(self.token)
@@ -64,6 +64,18 @@ class Bot(TelegramObject):
self.logger = logging.getLogger(__name__)
@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)
@@ -142,7 +154,7 @@ class Bot(TelegramObject):
return decorator
@log
def getMe(self):
def getMe(self, **kwargs):
"""A simple method for testing your bot's auth token.
Returns:
@@ -997,6 +1009,7 @@ class Bot(TelegramObject):
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
@@ -1170,31 +1179,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`
@@ -1259,6 +1264,166 @@ class Bot(TelegramObject):
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 = 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 = request.post(url, data, timeout=kwargs.get('timeout'))
return Chat.de_json(result)
@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 = request.post(url, data, timeout=kwargs.get('timeout'))
return [ChatMember.de_json(x) 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 = 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 = request.post(url, data, timeout=kwargs.get('timeout'))
return ChatMember.de_json(result)
@staticmethod
def de_json(data):
data = super(Bot, Bot).de_json(data)
@@ -1302,3 +1467,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
+11
View File
@@ -43,3 +43,14 @@ class CallbackQuery(TelegramObject):
data['message'] = Message.de_json(data.get('message'))
return CallbackQuery(**data)
def to_dict(self):
"""
Returns:
dict:
"""
data = super(CallbackQuery, self).to_dict()
# Required
data['from'] = data.pop('from_user', None)
return data
+5
View File
@@ -42,6 +42,11 @@ class Chat(TelegramObject):
type (Optional[str]):
"""
PRIVATE = 'private'
GROUP = 'group'
SUPERGROUP = 'supergroup'
CHANNEL = 'channel'
def __init__(self, id, type, **kwargs):
# Required
self.id = int(id)
+62
View File
@@ -0,0 +1,62 @@
#!/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):
"""
Args:
data (dict):
Returns:
telegram.ChatMember:
"""
if not data:
return None
data['user'] = User.de_json(data.get('user'))
return ChatMember(**data)
+7 -1
View File
@@ -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
+47
View File
@@ -0,0 +1,47 @@
# python-telegram-bot - a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# by the python-telegram-bot contributors <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""Constants in the Telegram network.
Attributes:
MAX_MESSAGE_LENGTH (int): from
https://core.telegram.org/method/messages.sendMessage#return-errors
MAX_CAPTION_LENGTH (int): from https://core.telegram.org/bots/api#sendphoto
The following constants were extracted from the
`Telegram Bots FAQ <https://core.telegram.org/bots/faq>`_.
Attributes:
SUPPORTED_WEBHOOK_PORTS (List[int])
MAX_FILESIZE_DOWNLOAD (int): In bytes.
MAX_FILESIZE_UPLOAD (int): Official limit, the actual limit can be a bit higher.
MAX_MESSAGES_PER_SECOND_PER_CHAT (int): Telegram may allow short bursts that go over this
limit, but eventually you'll begin receiving 429 errors.
MAX_MESSAGES_PER_SECOND (int)
MAX_MESSAGES_PER_MINUTE_PER_GROUP (int)
"""
MAX_MESSAGE_LENGTH = 4096
MAX_CAPTION_LENGTH = 200
# constants above this line are tested
SUPPORTED_WEBHOOK_PORTS = [443, 80, 88, 8443]
MAX_FILESIZE_DOWNLOAD = int(20E6) # (20MB)
MAX_FILESIZE_UPLOAD = int(50E6) # (50MB)
MAX_MESSAGES_PER_SECOND_PER_CHAT = 1
MAX_MESSAGES_PER_SECOND = 30
MAX_MESSAGES_PER_MINUTE_PER_GROUP = 20
View File
+46
View File
@@ -0,0 +1,46 @@
import logging
from telegram import NullHandler
from future.moves.urllib.parse import quote
from future.moves.urllib.error import HTTPError, URLError
from future.moves.urllib.request import urlopen, Request
logging.getLogger(__name__).addHandler(NullHandler())
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
+5
View File
@@ -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,6 +77,10 @@ class NetworkError(TelegramError):
pass
class BadRequest(NetworkError):
pass
class TimedOut(NetworkError):
def __init__(self):
+4 -3
View File
@@ -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')
+13 -6
View File
@@ -31,13 +31,20 @@ class CallbackQueryHandler(Handler):
callback (function): A function that takes ``bot, update`` as
positional arguments. It will be called when the ``check_update``
has determined that an update should be processed by this handler.
pass_update_queue (optional[bool]): If the handler should be passed the
update queue as a keyword argument called ``update_queue``. It can
be used to insert updates. Default is ``False``
pass_update_queue (optional[bool]): If set to ``True``, a keyword argument called
``update_queue`` will be passed to the callback function. It will be the ``Queue``
instance used by the ``Updater`` and ``Dispatcher`` that contains new updates which can
be used to insert updates. Default is ``False``.
pass_job_queue (optional[bool]): If set to ``True``, a keyword argument called
``job_queue`` will be passed to the callback function. It will be a ``JobQueue``
instance created by the ``Updater`` which can be used to schedule new jobs.
Default is ``False``.
"""
def __init__(self, callback, pass_update_queue=False):
super(CallbackQueryHandler, self).__init__(callback, pass_update_queue)
def __init__(self, callback, pass_update_queue=False, pass_job_queue=False):
super(CallbackQueryHandler, self).__init__(callback,
pass_update_queue=pass_update_queue,
pass_job_queue=pass_job_queue)
def check_update(self, update):
return isinstance(update, Update) and update.callback_query
@@ -45,7 +52,7 @@ class CallbackQueryHandler(Handler):
def handle_update(self, update, dispatcher):
optional_args = self.collect_optional_args(dispatcher)
self.callback(dispatcher.bot, update, **optional_args)
return self.callback(dispatcher.bot, update, **optional_args)
# old non-PEP8 Handler methods
m = "telegram.CallbackQueryHandler."
+13 -6
View File
@@ -32,13 +32,20 @@ class ChosenInlineResultHandler(Handler):
callback (function): A function that takes ``bot, update`` as
positional arguments. It will be called when the ``check_update``
has determined that an update should be processed by this handler.
pass_update_queue (optional[bool]): If the handler should be passed the
update queue as a keyword argument called ``update_queue``. It can
be used to insert updates. Default is ``False``
pass_update_queue (optional[bool]): If set to ``True``, a keyword argument called
``update_queue`` will be passed to the callback function. It will be the ``Queue``
instance used by the ``Updater`` and ``Dispatcher`` that contains new updates which can
be used to insert updates. Default is ``False``.
pass_job_queue (optional[bool]): If set to ``True``, a keyword argument called
``job_queue`` will be passed to the callback function. It will be a ``JobQueue``
instance created by the ``Updater`` which can be used to schedule new jobs.
Default is ``False``.
"""
def __init__(self, callback, pass_update_queue=False):
super(ChosenInlineResultHandler, self).__init__(callback, pass_update_queue)
def __init__(self, callback, pass_update_queue=False, pass_job_queue=False):
super(ChosenInlineResultHandler, self).__init__(callback,
pass_update_queue=pass_update_queue,
pass_job_queue=pass_job_queue)
def check_update(self, update):
return isinstance(update, Update) and update.chosen_inline_result
@@ -46,7 +53,7 @@ class ChosenInlineResultHandler(Handler):
def handle_update(self, update, dispatcher):
optional_args = self.collect_optional_args(dispatcher)
self.callback(dispatcher.bot, update, **optional_args)
return self.callback(dispatcher.bot, update, **optional_args)
# old non-PEP8 Handler methods
m = "telegram.ChosenInlineResultHandler."
+35 -11
View File
@@ -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``
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."
+222
View File
@@ -0,0 +1,222 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
""" This module contains the ConversationHandler """
import logging
from telegram import Update
from telegram.ext import Handler
from telegram.utils.promise import Promise
class ConversationHandler(Handler):
"""
A handler to hold a conversation with a user by managing four collections of other handlers.
The first collection, a ``list`` named ``entry_points``, is used to initiate the conversation,
for example with a ``CommandHandler`` or ``RegexHandler``.
The second collection, a ``dict`` named ``states``, contains the different conversation steps
and one or more associated handlers that should be used if the user sends a message when the
conversation with them is currently in that state. You will probably use mostly
``MessageHandler`` and ``RegexHandler`` here.
The third collection, a ``list`` named ``fallbacks``, is used if the user is currently in a
conversation but the state has either no associated handler or the handler that is associated
to the state is inappropriate for the update, for example if the update contains a command, but
a regular text message is expected. You could use this for a ``/cancel`` command or to let the
user know their message was not recognized.
The fourth, optional collection of handlers, a ``list`` named ``timed_out_behavior`` is used if
the wait for ``run_async`` takes longer than defined in ``run_async_timeout``. For example,
you can let the user know that they should wait for a bit before they can continue.
To change the state of conversation, the callback function of a handler must return the new
state after responding to the user. If it does not return anything (returning ``None`` by
default), the state will not change. To end the conversation, the callback function must
return ``CallbackHandler.END`` or ``-1``.
Args:
entry_points (list): A list of ``Handler`` objects that can trigger the start of the
conversation. The first handler which ``check_update`` method returns ``True`` will be
used. If all return ``False``, the update is not handled.
states (dict): A ``dict[object: list[Handler]]`` that defines the different states of
conversation a user can be in and one or more associated ``Handler`` objects that
should be used in that state. The first handler which ``check_update`` method returns
``True`` will be used.
fallbacks (list): A list of handlers that might be used if the user is in a conversation,
but every handler for their current state returned ``False`` on ``check_update``.
The first handler which ``check_update`` method returns ``True`` will be used. If all
return ``False``, the update is not handled.
allow_reentry (Optional[bool]): If set to ``True``, a user that is currently in a
conversation can restart the conversation by triggering one of the entry points.
run_async_timeout (Optional[float]): If the previous handler for this user was running
asynchronously using the ``run_async`` decorator, it might not be finished when the
next message arrives. This timeout defines how long the conversation handler should
wait for the next state to be computed. The default is ``None`` which means it will
wait indefinitely.
timed_out_behavior (Optional[list]): A list of handlers that might be used if
the wait for ``run_async`` timed out. The first handler which ``check_update`` method
returns ``True`` will be used. If all return ``False``, the update is not handled.
"""
END = -1
def __init__(self,
entry_points,
states,
fallbacks,
allow_reentry=False,
run_async_timeout=None,
timed_out_behavior=None):
self.entry_points = entry_points
""":type: list[telegram.ext.Handler]"""
self.states = states
""":type: dict[str: telegram.ext.Handler]"""
self.fallbacks = fallbacks
""":type: list[telegram.ext.Handler]"""
self.allow_reentry = allow_reentry
self.run_async_timeout = run_async_timeout
self.timed_out_behavior = timed_out_behavior
""":type: list[telegram.ext.Handler]"""
self.conversations = dict()
""":type: dict[(int, int): str]"""
self.current_conversation = None
self.current_handler = None
self.logger = logging.getLogger(__name__)
def check_update(self, update):
if not isinstance(update, Update):
return False
user = None
chat = None
if update.message:
user = update.message.from_user
chat = update.message.chat
elif update.edited_message:
user = update.edited_message.from_user
chat = update.edited_message.chat
elif update.inline_query:
user = update.inline_query.from_user
elif update.chosen_inline_result:
user = update.chosen_inline_result.from_user
elif update.callback_query:
user = update.callback_query.from_user
chat = update.callback_query.message.chat if update.callback_query.message else None
else:
return False
key = (chat.id, user.id) if chat else (None, user.id)
state = self.conversations.get(key)
# Resolve promises
if isinstance(state, tuple) and len(state) is 2 and isinstance(state[1], Promise):
self.logger.debug('waiting for promise...')
old_state, new_state = state
new_state.result(timeout=self.run_async_timeout)
if new_state.done.is_set():
self.update_state(new_state.result(), key)
state = self.conversations.get(key)
else:
for candidate in (self.timed_out_behavior or []):
if candidate.check_update(update):
# Save the current user and the selected handler for handle_update
self.current_conversation = key
self.current_handler = candidate
return True
else:
return False
self.logger.debug('selecting conversation %s with state %s' % (str(key), str(state)))
handler = None
# Search entry points for a match
if state is None or self.allow_reentry:
for entry_point in self.entry_points:
if entry_point.check_update(update):
handler = entry_point
break
else:
if state is None:
return False
# Get the handler list for current state, if we didn't find one yet and we're still here
if state is not None and not handler:
handlers = self.states.get(state)
for candidate in (handlers or []):
if candidate.check_update(update):
handler = candidate
break
# Find a fallback handler if all other handlers fail
else:
for fallback in self.fallbacks:
if fallback.check_update(update):
handler = fallback
break
else:
return False
# Save the current user and the selected handler for handle_update
self.current_conversation = key
self.current_handler = handler
return True
def handle_update(self, update, dispatcher):
new_state = self.current_handler.handle_update(update, dispatcher)
self.update_state(new_state, self.current_conversation)
def update_state(self, new_state, key):
if new_state == self.END:
del self.conversations[key]
elif isinstance(new_state, Promise):
self.conversations[key] = (self.conversations[key], new_state)
elif new_state is not None:
self.conversations[key] = new_state
+61 -38
View File
@@ -20,24 +20,47 @@
import logging
from functools import wraps
from threading import Thread, BoundedSemaphore, Lock, Event, current_thread
from threading import Thread, Lock, Event, current_thread
from time import sleep
from queue import Queue, Empty
from queue import Empty
from future.builtins import range
from telegram import (TelegramError, NullHandler)
from telegram.utils import request
from telegram.ext.handler import Handler
from telegram.utils.deprecate import deprecate
from telegram.utils.promise import Promise
logging.getLogger(__name__).addHandler(NullHandler())
semaphore = None
async_threads = set()
ASYNC_QUEUE = Queue()
ASYNC_THREADS = set()
""":type: set[Thread]"""
async_lock = Lock()
ASYNC_LOCK = Lock() # guards ASYNC_THREADS
DEFAULT_GROUP = 0
def _pooled():
"""
A wrapper to run a thread in a thread pool
"""
while 1:
promise = ASYNC_QUEUE.get()
# If unpacking fails, the thread pool is being closed from Updater._join_async_threads
if not isinstance(promise, Promise):
logging.getLogger(__name__).debug("Closing run_async thread %s/%d" %
(current_thread().getName(), len(ASYNC_THREADS)))
break
try:
promise.run()
except:
logging.getLogger(__name__).exception("run_async function raised exception")
def run_async(func):
"""
Function decorator that will run the function in a new thread.
@@ -53,30 +76,13 @@ def run_async(func):
# set a threading.Event to notify caller thread
@wraps(func)
def pooled(*pargs, **kwargs):
"""
A wrapper to run a thread in a thread pool
"""
try:
result = func(*pargs, **kwargs)
finally:
semaphore.release()
with async_lock:
async_threads.remove(current_thread())
return result
@wraps(func)
def async_func(*pargs, **kwargs):
def async_func(*args, **kwargs):
"""
A wrapper to run a function in a thread
"""
thread = Thread(target=pooled, args=pargs, kwargs=kwargs)
semaphore.acquire()
with async_lock:
async_threads.add(thread)
thread.start()
return thread
promise = Promise(func, args, kwargs)
ASYNC_QUEUE.put(promise)
return promise
return async_func
@@ -90,11 +96,16 @@ class Dispatcher(object):
handlers
update_queue (Queue): The synchronized queue that will contain the
updates.
job_queue (Optional[telegram.ext.JobQueue]): The ``JobQueue`` instance to pass onto handler
callbacks
workers (Optional[int]): Number of maximum concurrent worker threads for the ``@run_async``
decorator
"""
def __init__(self, bot, update_queue, workers=4, exception_event=None):
def __init__(self, bot, update_queue, workers=4, exception_event=None, job_queue=None):
self.bot = bot
self.update_queue = update_queue
self.job_queue = job_queue
self.handlers = {}
""":type: dict[int, list[Handler]"""
@@ -107,11 +118,23 @@ class Dispatcher(object):
self.__stop_event = Event()
self.__exception_event = exception_event or Event()
global semaphore
if not semaphore:
semaphore = BoundedSemaphore(value=workers)
else:
self.logger.debug('Semaphore already initialized, skipping.')
with ASYNC_LOCK:
if not ASYNC_THREADS:
if request.is_con_pool_initialized():
raise RuntimeError('Connection Pool already initialized')
# we need a connection pool the size of:
# * for each of the workers
# * 1 for Dispatcher
# * 1 for polling Updater (even if updater is webhook, we can spare a connection)
# * 1 for JobQueue
request.CON_POOL_SIZE = workers + 3
for i in range(workers):
thread = Thread(target=_pooled, name=str(i))
ASYNC_THREADS.add(thread)
thread.start()
else:
self.logger.debug('Thread pool already initialized, skipping.')
def start(self):
"""
@@ -131,7 +154,7 @@ class Dispatcher(object):
self.running = True
self.logger.debug('Dispatcher started')
while True:
while 1:
try:
# Pop update from update queue.
update = self.update_queue.get(True, 1)
@@ -145,7 +168,7 @@ class Dispatcher(object):
continue
self.logger.debug('Processing Update: %s' % update)
self.processUpdate(update)
self.process_update(update)
self.running = False
self.logger.debug('Dispatcher thread stopped')
@@ -160,7 +183,7 @@ class Dispatcher(object):
sleep(0.1)
self.__stop_event.clear()
def processUpdate(self, update):
def process_update(self, update):
"""
Processes a single update.
@@ -170,7 +193,7 @@ class Dispatcher(object):
# An error happened while polling
if isinstance(update, TelegramError):
self.dispatchError(None, update)
self.dispatch_error(None, update)
else:
for group in self.groups:
@@ -185,7 +208,7 @@ class Dispatcher(object):
'Update.')
try:
self.dispatchError(update, te)
self.dispatch_error(update, te)
except Exception:
self.logger.exception('An uncaught error was raised while '
'handling the error')
@@ -271,7 +294,7 @@ class Dispatcher(object):
if callback in self.error_handlers:
self.error_handlers.remove(callback)
def dispatchError(self, update, error):
def dispatch_error(self, update, error):
"""
Dispatches an error.
+16 -6
View File
@@ -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
+13 -6
View File
@@ -31,13 +31,20 @@ class InlineQueryHandler(Handler):
callback (function): A function that takes ``bot, update`` as
positional arguments. It will be called when the ``check_update``
has determined that an update should be processed by this handler.
pass_update_queue (optional[bool]): If the handler should be passed the
update queue as a keyword argument called ``update_queue``. It can
be used to insert updates. Default is ``False``
pass_update_queue (optional[bool]): If set to ``True``, a keyword argument called
``update_queue`` will be passed to the callback function. It will be the ``Queue``
instance used by the ``Updater`` and ``Dispatcher`` that contains new updates which can
be used to insert updates. Default is ``False``.
pass_job_queue (optional[bool]): If set to ``True``, a keyword argument called
``job_queue`` will be passed to the callback function. It will be a ``JobQueue``
instance created by the ``Updater`` which can be used to schedule new jobs.
Default is ``False``.
"""
def __init__(self, callback, pass_update_queue=False):
super(InlineQueryHandler, self).__init__(callback, pass_update_queue)
def __init__(self, callback, pass_update_queue=False, pass_job_queue=False):
super(InlineQueryHandler, self).__init__(callback,
pass_update_queue=pass_update_queue,
pass_job_queue=pass_job_queue)
def check_update(self, update):
return isinstance(update, Update) and update.inline_query
@@ -45,7 +52,7 @@ class InlineQueryHandler(Handler):
def handle_update(self, update, dispatcher):
optional_args = self.collect_optional_args(dispatcher)
self.callback(dispatcher.bot, update, **optional_args)
return self.callback(dispatcher.bot, update, **optional_args)
# old non-PEP8 Handler methods
m = "telegram.InlineQueryHandler."
+181 -80
View File
@@ -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
+49 -37
View File
@@ -30,60 +30,56 @@ 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)
class MessageHandler(Handler):
@@ -102,29 +98,45 @@ 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
m = "telegram.MessageHandler."
+14 -6
View File
@@ -45,9 +45,14 @@ class RegexHandler(Handler):
pass_groupdict (optional[bool]): If the callback should be passed the
result of ``re.match(pattern, text).groupdict()`` as a keyword
argument called ``groupdict``. Default is ``False``
pass_update_queue (optional[bool]): If the handler should be passed the
update queue as a keyword argument called ``update_queue``. It can
be used to insert updates. Default is ``False``
pass_update_queue (optional[bool]): If set to ``True``, a keyword argument called
``update_queue`` will be passed to the callback function. It will be the ``Queue``
instance used by the ``Updater`` and ``Dispatcher`` that contains new updates which can
be used to insert updates. Default is ``False``.
pass_job_queue (optional[bool]): If set to ``True``, a keyword argument called
``job_queue`` will be passed to the callback function. It will be a ``JobQueue``
instance created by the ``Updater`` which can be used to schedule new jobs.
Default is ``False``.
"""
def __init__(self,
@@ -55,8 +60,11 @@ class RegexHandler(Handler):
callback,
pass_groups=False,
pass_groupdict=False,
pass_update_queue=False):
super(RegexHandler, self).__init__(callback, pass_update_queue)
pass_update_queue=False,
pass_job_queue=False):
super(RegexHandler, self).__init__(callback,
pass_update_queue=pass_update_queue,
pass_job_queue=pass_job_queue)
if isinstance(pattern, string_types):
pattern = re.compile(pattern)
@@ -81,7 +89,7 @@ class RegexHandler(Handler):
if self.pass_groupdict:
optional_args['groupdict'] = match.groupdict()
self.callback(dispatcher.bot, update, **optional_args)
return self.callback(dispatcher.bot, update, **optional_args)
# old non-PEP8 Handler methods
m = "telegram.RegexHandler."
+20 -8
View File
@@ -36,19 +36,31 @@ class StringCommandHandler(Handler):
arguments passed to the command as a keyword argument called `
``args``. It will contain a list of strings, which is the text
following the command split on spaces. Default is ``False``
pass_update_queue (optional[bool]): If the handler should be passed the
update queue as a keyword argument called ``update_queue``. It can
be used to insert updates. Default is ``False``
pass_update_queue (optional[bool]): If set to ``True``, a keyword argument called
``update_queue`` will be passed to the callback function. It will be the ``Queue``
instance used by the ``Updater`` and ``Dispatcher`` that contains new updates which can
be used to insert updates. Default is ``False``.
pass_job_queue (optional[bool]): If set to ``True``, a keyword argument called
``job_queue`` will be passed to the callback function. It will be a ``JobQueue``
instance created by the ``Updater`` which can be used to schedule new jobs.
Default is ``False``.
"""
def __init__(self, command, callback, pass_args=False, pass_update_queue=False):
super(StringCommandHandler, self).__init__(callback, pass_update_queue)
def __init__(self,
command,
callback,
pass_args=False,
pass_update_queue=False,
pass_job_queue=False):
super(StringCommandHandler, self).__init__(callback,
pass_update_queue=pass_update_queue,
pass_job_queue=pass_job_queue)
self.command = command
self.pass_args = pass_args
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)
@@ -56,7 +68,7 @@ class StringCommandHandler(Handler):
if self.pass_args:
optional_args['args'] = update.split(' ')[1:]
self.callback(dispatcher.bot, update, **optional_args)
return self.callback(dispatcher.bot, update, **optional_args)
# old non-PEP8 Handler methods
m = "telegram.StringCommandHandler."
+14 -6
View File
@@ -44,9 +44,14 @@ class StringRegexHandler(Handler):
pass_groupdict (optional[bool]): If the callback should be passed the
result of ``re.match(pattern, update).groupdict()`` as a keyword
argument called ``groupdict``. Default is ``False``
pass_update_queue (optional[bool]): If the handler should be passed the
update queue as a keyword argument called ``update_queue``. It can
be used to insert updates. Default is ``False``
pass_update_queue (optional[bool]): If set to ``True``, a keyword argument called
``update_queue`` will be passed to the callback function. It will be the ``Queue``
instance used by the ``Updater`` and ``Dispatcher`` that contains new updates which can
be used to insert updates. Default is ``False``.
pass_job_queue (optional[bool]): If set to ``True``, a keyword argument called
``job_queue`` will be passed to the callback function. It will be a ``JobQueue``
instance created by the ``Updater`` which can be used to schedule new jobs.
Default is ``False``.
"""
def __init__(self,
@@ -54,8 +59,11 @@ class StringRegexHandler(Handler):
callback,
pass_groups=False,
pass_groupdict=False,
pass_update_queue=False):
super(StringRegexHandler, self).__init__(callback, pass_update_queue)
pass_update_queue=False,
pass_job_queue=False):
super(StringRegexHandler, self).__init__(callback,
pass_update_queue=pass_update_queue,
pass_job_queue=pass_job_queue)
if isinstance(pattern, string_types):
pattern = re.compile(pattern)
@@ -76,7 +84,7 @@ class StringRegexHandler(Handler):
if self.pass_groupdict:
optional_args['groupdict'] = match.groupdict()
self.callback(dispatcher.bot, update, **optional_args)
return self.callback(dispatcher.bot, update, **optional_args)
# old non-PEP8 Handler methods
m = "telegram.StringRegexHandler."
+18 -6
View File
@@ -34,13 +34,25 @@ class TypeHandler(Handler):
has determined that an update should be processed by this handler.
strict (optional[bool]): Use ``type`` instead of ``isinstance``.
Default is ``False``
pass_update_queue (optional[bool]): If the handler should be passed the
update queue as a keyword argument called ``update_queue``. It can
be used to insert updates. Default is ``False``
pass_update_queue (optional[bool]): If set to ``True``, a keyword argument called
``update_queue`` will be passed to the callback function. It will be the ``Queue``
instance used by the ``Updater`` and ``Dispatcher`` that contains new updates which can
be used to insert updates. Default is ``False``.
pass_job_queue (optional[bool]): If set to ``True``, a keyword argument called
``job_queue`` will be passed to the callback function. It will be a ``JobQueue``
instance created by the ``Updater`` which can be used to schedule new jobs.
Default is ``False``.
"""
def __init__(self, type, callback, strict=False, pass_update_queue=False):
super(TypeHandler, self).__init__(callback, pass_update_queue)
def __init__(self,
type,
callback,
strict=False,
pass_update_queue=False,
pass_job_queue=False):
super(TypeHandler, self).__init__(callback,
pass_update_queue=pass_update_queue,
pass_job_queue=pass_job_queue)
self.type = type
self.strict = strict
@@ -53,7 +65,7 @@ class TypeHandler(Handler):
def handle_update(self, update, dispatcher):
optional_args = self.collect_optional_args(dispatcher)
self.callback(dispatcher.bot, update, **optional_args)
return self.callback(dispatcher.bot, update, **optional_args)
# old non-PEP8 Handler methods
m = "telegram.TypeHandler."
+37 -30
View File
@@ -65,12 +65,7 @@ class Updater(object):
ValueError: If both `token` and `bot` are passed or none of them.
"""
def __init__(self,
token=None,
base_url=None,
workers=4,
bot=None,
job_queue_tick_interval=1.0):
def __init__(self, token=None, base_url=None, workers=4, bot=None):
if (token is None) and (bot is None):
raise ValueError('`token` or `bot` must be passed')
if (token is not None) and (bot is not None):
@@ -81,9 +76,13 @@ class Updater(object):
else:
self.bot = Bot(token, base_url)
self.update_queue = Queue()
self.job_queue = JobQueue(self.bot, job_queue_tick_interval)
self.job_queue = JobQueue(self.bot)
self.__exception_event = Event()
self.dispatcher = Dispatcher(self.bot, self.update_queue, workers, self.__exception_event)
self.dispatcher = Dispatcher(self.bot,
self.update_queue,
job_queue=self.job_queue,
workers=workers,
exception_event=self.__exception_event)
self.last_update_id = 0
self.logger = logging.getLogger(__name__)
self.running = False
@@ -112,22 +111,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)
@@ -306,7 +308,7 @@ class Updater(object):
def _bootstrap(self, max_retries, clean, webhook_url, cert=None):
retries = 0
while True:
while 1:
try:
if clean:
@@ -318,8 +320,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 +345,7 @@ class Updater(object):
self.job_queue.stop()
with self.__lock:
if self.running:
if self.running or dispatcher.ASYNC_THREADS:
self.logger.debug('Stopping Updater and Dispatcher...')
self.running = False
@@ -351,9 +353,8 @@ class Updater(object):
self._stop_httpd()
self._stop_dispatcher()
self._join_threads()
# async threads must be join()ed only after the dispatcher
# thread was joined, otherwise we can still have new async
# threads dispatched
# async threads must be join()ed only after the dispatcher thread was joined,
# otherwise we can still have new async threads dispatched
self._join_async_threads()
def _stop_httpd(self):
@@ -369,13 +370,19 @@ class Updater(object):
self.dispatcher.stop()
def _join_async_threads(self):
with dispatcher.async_lock:
threads = list(dispatcher.async_threads)
total = len(threads)
for i, thr in enumerate(threads):
self.logger.debug('Waiting for async thread {0}/{1} to end'.format(i, total))
thr.join()
self.logger.debug('async thread {0}/{1} has ended'.format(i, total))
with dispatcher.ASYNC_LOCK:
threads = list(dispatcher.ASYNC_THREADS)
total = len(threads)
# Stop all threads in the thread pool by put()ting one non-tuple per thread
for i in range(total):
dispatcher.ASYNC_QUEUE.put(None)
for i, thr in enumerate(threads):
self.logger.debug('Waiting for async thread {0}/{1} to end'.format(i + 1, total))
thr.join()
dispatcher.ASYNC_THREADS.remove(thr)
self.logger.debug('async thread {0}/{1} has ended'.format(i + 1, total))
def _join_threads(self):
for thr in self.__threads:
+1 -1
View File
@@ -33,7 +33,7 @@ class InlineKeyboardMarkup(ReplyMarkup):
"""
def __init__(self, inline_keyboard):
def __init__(self, inline_keyboard, **kwargs):
# Required
self.inline_keyboard = inline_keyboard
+1 -1
View File
@@ -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
+1 -1
View File
@@ -25,7 +25,7 @@ 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
+4 -4
View File
@@ -39,14 +39,14 @@ class InputMessageContent(TelegramObject):
pass
try:
from telegram import InputLocationMessageContent
return InputLocationMessageContent.de_json(data)
from telegram import InputVenueMessageContent
return InputVenueMessageContent.de_json(data)
except TypeError:
pass
try:
from telegram import InputVenueMessageContent
return InputVenueMessageContent.de_json(data)
from telegram import InputLocationMessageContent
return InputLocationMessageContent.de_json(data)
except TypeError:
pass
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -33,7 +33,7 @@ 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)
+6
View File
@@ -40,6 +40,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 +81,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`]):
@@ -113,6 +115,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')
@@ -162,6 +165,7 @@ class Message(TelegramObject):
data['forward_from_chat'] = Chat.de_json(data.get('forward_from_chat'))
data['forward_date'] = Message._fromtimestamp(data.get('forward_date'))
data['reply_to_message'] = Message.de_json(data.get('reply_to_message'))
data['edit_date'] = Message._fromtimestamp(data.get('edit_date'))
data['audio'] = Audio.de_json(data.get('audio'))
data['document'] = Document.de_json(data.get('document'))
data['photo'] = PhotoSize.de_list(data.get('photo'))
@@ -197,6 +201,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:
+5 -1
View File
@@ -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,6 +31,7 @@ class MessageEntity(TelegramObject):
offset (int):
length (int):
url (Optional[str]):
user (Optional[:class:`telegram.User`]):
"""
def __init__(self, type, offset, length, **kwargs):
@@ -40,11 +41,14 @@ class MessageEntity(TelegramObject):
self.length = length
# Optionals
self.url = kwargs.get('url')
self.user = kwargs.get('user')
@staticmethod
def de_json(data):
data = super(MessageEntity, MessageEntity).de_json(data)
data['user'] = User.de_json(data.get('user'))
return MessageEntity(**data)
@staticmethod
+2 -2
View File
@@ -51,8 +51,8 @@ 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):
+4
View File
@@ -27,6 +27,7 @@ class Update(TelegramObject):
Attributes:
update_id (int):
message (:class:`telegram.Message`):
edited_message (:class:`telegram.Message`):
inline_query (:class:`telegram.InlineQuery`):
chosen_inline_result (:class:`telegram.ChosenInlineResult`):
callback_query (:class:`telegram.CallbackQuery`):
@@ -37,6 +38,7 @@ class Update(TelegramObject):
Keyword Args:
message (Optional[:class:`telegram.Message`]):
edited_message (Optional[:class:`telegram.Message`]):
inline_query (Optional[:class:`telegram.InlineQuery`]):
chosen_inline_result (Optional[:class:`telegram.ChosenInlineResult`])
callback_query (Optional[:class:`telegram.CallbackQuery`]):
@@ -47,6 +49,7 @@ class Update(TelegramObject):
self.update_id = int(update_id)
# Optionals
self.message = kwargs.get('message')
self.edited_message = kwargs.get('edited_message')
self.inline_query = kwargs.get('inline_query')
self.chosen_inline_result = kwargs.get('chosen_inline_result')
self.callback_query = kwargs.get('callback_query')
@@ -64,6 +67,7 @@ class Update(TelegramObject):
return None
data['message'] = Message.de_json(data.get('message'))
data['edited_message'] = Message.de_json(data.get('edited_message'))
data['inline_query'] = InlineQuery.de_json(data.get('inline_query'))
data['chosen_inline_result'] = ChosenInlineResult.de_json(data.get('chosen_inline_result'))
data['callback_query'] = CallbackQuery.de_json(data.get('callback_query'))
+1 -1
View File
@@ -34,7 +34,7 @@ class UserProfilePhotos(TelegramObject):
photos (List[List[:class:`telegram.PhotoSize`]]):
"""
def __init__(self, total_count, photos):
def __init__(self, total_count, photos, **kwargs):
# Required
self.total_count = int(total_count)
self.photos = photos
+3 -51
View File
@@ -1,52 +1,4 @@
#!/usr/bin/env python
from .deprecate import deprecate
from telegram.contrib.botan import Botan as Bo
import logging
from telegram import NullHandler
try:
from urllib.request import urlopen, Request
from urllib.parse import quote
from urllib.error import URLError, HTTPError
except ImportError:
from urllib2 import urlopen, Request
from urllib import quote
from urllib2 import URLError, HTTPError
logging.getLogger(__name__).addHandler(NullHandler())
class Botan(object):
"""This class helps to send incoming events in 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
Botan = deprecate(Bo, 'telegram.utils.botan', 'telegram.contrib.botan')
@@ -16,14 +16,31 @@
#
# 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 functions to validate function arguments"""
""" This module contains the Promise class """
from telegram.error import InvalidToken
from threading import Event
def validate_token(token):
"""a very basic validation on token"""
left, sep, _right = token.partition(':')
if (not sep) or (not left.isdigit()) or (len(left) < 3):
raise InvalidToken()
return token
class Promise(object):
"""A simple Promise implementation for the run_async decorator"""
def __init__(self, pooled_function, args, kwargs):
self.pooled_function = pooled_function
self.args = args
self.kwargs = kwargs
self.done = Event()
self._result = None
def run(self):
try:
self._result = self.pooled_function(*self.args, **self.kwargs)
except:
raise
finally:
self.done.set()
def result(self, timeout=None):
self.done.wait(timeout=timeout)
return self._result
+145 -56
View File
@@ -18,29 +18,116 @@
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains methods to make POST and GET requests"""
import functools
import json
import os
import socket
from ssl import SSLError
import logging
from future.moves.http.client import HTTPException
from future.moves.urllib.error import HTTPError, URLError
from future.moves.urllib.request import urlopen, urlretrieve, Request
import certifi
import urllib3
from urllib3.connection import HTTPConnection
from telegram import (InputFile, TelegramError)
from telegram.error import Unauthorized, NetworkError, TimedOut
from telegram.error import Unauthorized, NetworkError, TimedOut, BadRequest
_CON_POOL = None
""":type: urllib3.PoolManager"""
_CON_POOL_PROXY = None
_CON_POOL_PROXY_KWARGS = {}
CON_POOL_SIZE = 1
logging.getLogger('urllib3').setLevel(logging.WARNING)
def _get_con_pool():
if _CON_POOL is not None:
return _CON_POOL
_init_con_pool()
return _CON_POOL
def _init_con_pool():
global _CON_POOL
kwargs = dict(maxsize=CON_POOL_SIZE,
cert_reqs='CERT_REQUIRED',
ca_certs=certifi.where(),
socket_options=HTTPConnection.default_socket_options + [
(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1),
])
proxy_url = _get_con_pool_proxy()
if not proxy_url:
mgr = urllib3.PoolManager(**kwargs)
else:
if _CON_POOL_PROXY_KWARGS:
kwargs.update(_CON_POOL_PROXY_KWARGS)
mgr = urllib3.proxy_from_url(proxy_url, **kwargs)
if mgr.proxy.auth:
# TODO: what about other auth types?
auth_hdrs = urllib3.make_headers(proxy_basic_auth=mgr.proxy.auth)
mgr.proxy_headers.update(auth_hdrs)
_CON_POOL = mgr
def is_con_pool_initialized():
return _CON_POOL is not None
def stop_con_pool():
global _CON_POOL
if _CON_POOL is not None:
_CON_POOL.clear()
_CON_POOL = None
def set_con_pool_proxy(url, **urllib3_kwargs):
"""Setup connection pool behind a proxy
Args:
url (str): The URL to the proxy server. For example: `http://127.0.0.1:3128`
urllib3_kwargs (dict): Arbitrary arguments passed as-is to `urllib3.ProxyManager`
"""
global _CON_POOL_PROXY
global _CON_POOL_PROXY_KWARGS
if is_con_pool_initialized():
raise TelegramError('conpool already initialized')
_CON_POOL_PROXY = url
_CON_POOL_PROXY_KWARGS = urllib3_kwargs
def _get_con_pool_proxy():
"""Return the user configured proxy according to the following order:
* proxy configured using `set_con_pool_proxy()`.
* proxy set in `HTTPS_PROXY` env. var.
* proxy set in `https_proxy` env. var.
* None (if no proxy is configured)
Returns:
str | None
"""
if _CON_POOL_PROXY:
return _CON_POOL_PROXY
from_env = os.environ.get('HTTPS_PROXY')
if from_env:
return from_env
from_env = os.environ.get('https_proxy')
if from_env:
return from_env
return None
def _parse(json_data):
"""Try and parse the JSON returned from Telegram and return an empty
dictionary if there is any error.
Args:
url:
urllib.urlopen object
"""Try and parse the JSON returned from Telegram.
Returns:
A JSON parsed as Python dict with results.
dict: A JSON parsed as Python dict with results - on error this dict will be empty.
"""
decoded_s = json_data.decode('utf-8')
try:
@@ -54,51 +141,49 @@ def _parse(json_data):
return data['result']
def _try_except_req(func):
"""Decorator for requests to handle known exceptions"""
def _request_wrapper(*args, **kwargs):
"""Wraps urllib3 request for handling known exceptions.
@functools.wraps(func)
def decorator(*args, **kwargs):
try:
return func(*args, **kwargs)
Args:
args: unnamed arguments, passed to urllib3 request.
kwargs: keyword arguments, passed tp urllib3 request.
except HTTPError as error:
# `HTTPError` inherits from `URLError` so `HTTPError` handling must
# come first.
errcode = error.getcode()
Returns:
str: A non-parsed JSON text.
if errcode in (401, 403):
raise Unauthorized()
elif errcode == 502:
raise NetworkError('Bad Gateway')
Raises:
TelegramError
try:
message = _parse(error.read())
except ValueError:
message = 'Unknown HTTPError {0}'.format(error.getcode())
"""
raise NetworkError('{0} ({1})'.format(message, errcode))
try:
resp = _get_con_pool().request(*args, **kwargs)
except urllib3.exceptions.TimeoutError as error:
raise TimedOut()
except urllib3.exceptions.HTTPError as error:
# HTTPError must come last as its the base urllib3 exception class
# TODO: do something smart here; for now just raise NetworkError
raise NetworkError('urllib3 HTTPError {0}'.format(error))
except URLError as error:
raise NetworkError('URLError: {0}'.format(error.reason))
if 200 <= resp.status <= 299:
# 200-299 range are HTTP success statuses
return resp.data
except (SSLError, socket.timeout) as error:
err_s = str(error)
if 'operation timed out' in err_s:
raise TimedOut()
try:
message = _parse(resp.data)
except ValueError:
raise NetworkError('Unknown HTTPError {0}'.format(resp.status))
raise NetworkError(err_s)
except HTTPException as error:
raise NetworkError('HTTPException: {0!r}'.format(error))
except socket.error as error:
raise NetworkError('socket.error: {0!r}'.format(error))
return decorator
if resp.status in (401, 403):
raise Unauthorized()
elif resp.status == 400:
raise BadRequest(repr(message))
elif resp.status == 502:
raise NetworkError('Bad Gateway')
else:
raise NetworkError('{0} ({1})'.format(message, resp.status))
@_try_except_req
def get(url):
"""Request an URL.
Args:
@@ -107,13 +192,13 @@ def get(url):
Returns:
A JSON object.
"""
result = urlopen(url).read()
result = _request_wrapper('GET', url)
return _parse(result)
@_try_except_req
def post(url, data, timeout=None):
"""Request an URL.
Args:
@@ -140,16 +225,18 @@ def post(url, data, timeout=None):
if InputFile.is_inputfile(data):
data = InputFile(data)
request = Request(url, data=data.to_form(), headers=data.headers)
result = _request_wrapper('POST', url, body=data.to_form(), headers=data.headers)
else:
data = json.dumps(data)
request = Request(url, data=data.encode(), headers={'Content-Type': 'application/json'})
result = _request_wrapper('POST',
url,
body=data.encode(),
headers={'Content-Type': 'application/json'},
**urlopen_kwargs)
result = urlopen(request, **urlopen_kwargs).read()
return _parse(result)
@_try_except_req
def download(url, filename):
"""Download a file by its URL.
Args:
@@ -158,6 +245,8 @@ def download(url, filename):
filename:
The filename within the path to download the file.
"""
urlretrieve(url, filename)
"""
buf = _request_wrapper('GET', url)
with open(filename, 'wb') as fobj:
fobj.write(buf)
+16
View File
@@ -102,3 +102,19 @@ class WebhookHandler(BaseHTTPServer.BaseHTTPRequestHandler, object):
if clen < 0:
raise _InvalidPost(403)
return clen
def log_message(self, format, *args):
"""Log an arbitrary message.
This is used by all other logging functions.
It overrides ``BaseHTTPRequestHandler.log_message``, which logs to ``sys.stderr``.
The first argument, FORMAT, is a format string for the message to be logged. If the format
string contains any % escapes requiring parameters, they should be specified as subsequent
arguments (it's just like printf!).
The client ip is prefixed to every message.
"""
self.logger.debug("%s - - %s" % (self.address_string(), format % args))
+1 -1
View File
@@ -32,7 +32,7 @@ class Venue(TelegramObject):
foursquare_id (Optional[str]):
"""
def __init__(self, location, title, address, foursquare_id=None):
def __init__(self, location, title, address, foursquare_id=None, **kwargs):
# Required
self.location = location
self.title = title
+2
View File
@@ -40,6 +40,8 @@ class BaseTest(object):
'133505823:AAHZFMHno3mzVLErU5b5jJvaeG--qUyLyG0'))
chat_id = os.environ.get('CHAT_ID', '12173560')
self._group_id = os.environ.get('GROUP_ID', '-49740850')
self._channel_id = os.environ.get('CHANNEL_ID', '@pythontelegrambottests')
self._bot = bot
self._chat_id = chat_id
+70 -8
View File
@@ -20,6 +20,7 @@
"""This module contains a object that represents Tests for Telegram Bot"""
import io
import re
from datetime import datetime
import sys
@@ -45,11 +46,7 @@ class BotTest(BaseTest, unittest.TestCase):
bot = self._bot.getMe()
self.assertTrue(self.is_json(bot.to_json()))
self.assertEqual(bot.id, 133505823)
self.assertEqual(bot.first_name, 'PythonTelegramBot')
self.assertEqual(bot.last_name, '')
self.assertEqual(bot.username, 'PythonTelegramBot')
self.assertEqual(bot.name, '@PythonTelegramBot')
self._testUserEqualsBot(bot)
@flaky(3, 1)
@timeout(10)
@@ -75,7 +72,7 @@ class BotTest(BaseTest, unittest.TestCase):
@flaky(3, 1)
@timeout(10)
def testGetUpdates(self):
updates = self._bot.getUpdates()
updates = self._bot.getUpdates(timeout=1)
if updates:
self.assertTrue(self.is_json(updates[0].to_json()))
@@ -120,12 +117,12 @@ class BotTest(BaseTest, unittest.TestCase):
@timeout(10)
def testResendPhoto(self):
message = self._bot.sendPhoto(
photo='AgADAQADyKcxGx8j9Qdp6d-gpUsw4Gja1i8ABEVJsVqQk8LfJ3wAAgI',
photo='AgADAQAD1y0yGx8j9Qf8f_m3CKeS6Iy95y8ABI1ggfVJ4-UvwJcAAgI',
chat_id=self._chat_id)
self.assertTrue(self.is_json(message.to_json()))
self.assertEqual(message.photo[0].file_id,
'AgADAQADyKcxGx8j9Qdp6d-gpUsw4Gja1i8ABEVJsVqQk8LfJ3wAAgI')
'AgADAQAD1y0yGx8j9Qf8f_m3CKeS6Iy95y8ABI1ggfVJ4-UvwJcAAgI')
@flaky(3, 1)
@timeout(10)
@@ -192,6 +189,13 @@ class BotTest(BaseTest, unittest.TestCase):
def testInvalidToken3(self):
self._test_invalid_token('12:')
def testInvalidToken4(self):
# white spaces are invalid
self._test_invalid_token('1234:abcd1234\n')
self._test_invalid_token(' 1234:abcd1234')
self._test_invalid_token(' 1234:abcd1234\r')
self._test_invalid_token('1234:abcd 1234')
def testUnauthToken(self):
with self.assertRaisesRegexp(telegram.error.Unauthorized, 'Unauthorized'):
bot = telegram.Bot('1234:abcd1234')
@@ -205,6 +209,64 @@ class BotTest(BaseTest, unittest.TestCase):
bot.getMe()
@flaky(3, 1)
@timeout(10)
def testLeaveChat(self):
regex = re.compile('chat not found', re.IGNORECASE)
with self.assertRaisesRegexp(telegram.error.BadRequest, regex):
chat = self._bot.leaveChat(-123456)
with self.assertRaisesRegexp(telegram.error.NetworkError, regex):
chat = self._bot.leaveChat(-123456)
@flaky(3, 1)
@timeout(10)
def testGetChat(self):
chat = self._bot.getChat(self._group_id)
self.assertTrue(self.is_json(chat.to_json()))
self.assertEqual(chat.type, "group")
self.assertEqual(chat.title, ">>> telegram.Bot() - Developers")
self.assertEqual(chat.id, int(self._group_id))
@flaky(3, 1)
@timeout(10)
def testGetChatAdministrators(self):
admins = self._bot.getChatAdministrators(self._channel_id)
self.assertTrue(isinstance(admins, list))
self.assertTrue(self.is_json(admins[0].to_json()))
for a in admins:
self.assertTrue(a.status in ("administrator", "creator"))
bot = [a.user for a in admins if a.user.id == 133505823][0]
self._testUserEqualsBot(bot)
@flaky(3, 1)
@timeout(10)
def testGetChatMembersCount(self):
count = self._bot.getChatMembersCount(self._channel_id)
self.assertTrue(isinstance(count, int))
self.assertTrue(count > 3)
@flaky(3, 1)
@timeout(10)
def testGetChatMember(self):
chat_member = self._bot.getChatMember(self._channel_id, 133505823)
bot = chat_member.user
self.assertTrue(self.is_json(chat_member.to_json()))
self.assertEqual(chat_member.status, "administrator")
self._testUserEqualsBot(bot)
def _testUserEqualsBot(self, user):
"""Tests if user is our trusty @PythonTelegramBot."""
self.assertEqual(user.id, 133505823)
self.assertEqual(user.first_name, 'PythonTelegramBot')
self.assertEqual(user.last_name, '')
self.assertEqual(user.username, 'PythonTelegramBot')
self.assertEqual(user.name, '@PythonTelegramBot')
if __name__ == '__main__':
unittest.main()
+2 -2
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python
"""This module contains a object that represents Tests for Botan analytics integration"""
"""This module contains an object that represents Tests for Botan analytics integration"""
import sys
import unittest
@@ -9,7 +9,7 @@ from flaky import flaky
sys.path.append('.')
from telegram.utils.botan import Botan
from telegram.contrib.botan import Botan
from tests.base import BaseTest
+69
View File
@@ -0,0 +1,69 @@
# python-telegram-bot - a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# by the python-telegram-bot contributors <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""Test the Telegram constants."""
import sys
from flaky import flaky
if sys.version_info[0:2] == (2, 6):
import unittest2 as unittest
else:
import unittest
sys.path.append('.')
import telegram
from telegram.error import BadRequest
from tests.base import BaseTest, timeout
class ConstantsTest(BaseTest, unittest.TestCase):
@flaky(3, 1)
@timeout(10)
def testMaxMessageLength(self):
self._bot.sendMessage(chat_id=self._chat_id,
text='a' * telegram.constants.MAX_MESSAGE_LENGTH)
try:
self._bot.sendMessage(chat_id=self._chat_id,
text='a' * (telegram.constants.MAX_MESSAGE_LENGTH + 1))
except BadRequest as e:
err = str(e)
self.assertTrue("too long" in err) # BadRequest: 'Message is too long'
@flaky(3, 1)
@timeout(10)
def testMaxCaptionLength(self):
self._bot.sendPhoto(photo=open('tests/data/telegram.png', 'rb'),
caption='a' * telegram.constants.MAX_CAPTION_LENGTH,
chat_id=self._chat_id)
try:
self._bot.sendPhoto(photo=open('tests/data/telegram.png', 'rb'),
caption='a' * (telegram.constants.MAX_CAPTION_LENGTH + 1),
chat_id=self._chat_id)
except BadRequest as e:
err = str(e)
self.assertTrue("TOO_LONG" in err) # BadRequest: 'MEDIA_CAPTION_TOO_LONG'
if __name__ == '__main__':
unittest.main()
+164
View File
@@ -0,0 +1,164 @@
#!/usr/bin/env python
# encoding: utf-8
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""
This module contains a object that represents Tests for ConversationHandler
"""
import logging
import sys
from time import sleep
if sys.version_info[0:2] == (2, 6):
import unittest2 as unittest
else:
import unittest
try:
# python2
from urllib2 import urlopen, Request, HTTPError
except ImportError:
# python3
from urllib.request import Request, urlopen
from urllib.error import HTTPError
sys.path.append('.')
from telegram import Update, Message, TelegramError, User, Chat, Bot
from telegram.utils.request import stop_con_pool
from telegram.ext import *
from tests.base import BaseTest
from tests.test_updater import MockBot
# Enable logging
root = logging.getLogger()
root.setLevel(logging.DEBUG)
ch = logging.StreamHandler(sys.stdout)
ch.setLevel(logging.WARN)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s ' '- %(message)s')
ch.setFormatter(formatter)
root.addHandler(ch)
class ConversationHandlerTest(BaseTest, unittest.TestCase):
"""
This object represents the tests for the conversation handler.
"""
# State definitions
# At first we're thirsty. Then we brew coffee, we drink it
# and then we can start coding!
END, THIRSTY, BREWING, DRINKING, CODING = range(-1, 4)
# Test related
def setUp(self):
self.updater = None
self.current_state = dict()
self.entry_points = [CommandHandler('start', self.start)]
self.states = {self.THIRSTY: [CommandHandler('brew', self.brew),
CommandHandler('wait', self.start)],
self.BREWING: [CommandHandler('pourCoffee', self.drink)],
self.DRINKING: [CommandHandler('startCoding', self.code),
CommandHandler('drinkMore', self.drink)],
self.CODING: [CommandHandler('keepCoding', self.code),
CommandHandler('gettingThirsty', self.start),
CommandHandler('drinkMore', self.drink)],}
self.fallbacks = [CommandHandler('eat', self.start)]
def _setup_updater(self, *args, **kwargs):
stop_con_pool()
bot = MockBot(*args, **kwargs)
self.updater = Updater(workers=2, bot=bot)
def tearDown(self):
if self.updater is not None:
self.updater.stop()
stop_con_pool()
def reset(self):
self.current_state = dict()
# State handlers
def _set_state(self, update, state):
self.current_state[update.message.from_user.id] = state
return state
def _get_state(self, user_id):
return self.current_state[user_id]
# Actions
def start(self, bot, update):
return self._set_state(update, self.THIRSTY)
def brew(self, bot, update):
return self._set_state(update, self.BREWING)
def drink(self, bot, update):
return self._set_state(update, self.DRINKING)
def code(self, bot, update):
return self._set_state(update, self.CODING)
# Tests
def test_addConversationHandler(self):
self._setup_updater('', messages=0)
d = self.updater.dispatcher
user = User(first_name="Misses Test", id=123)
second_user = User(first_name="Mister Test", id=124)
handler = ConversationHandler(entry_points=self.entry_points,
states=self.states,
fallbacks=self.fallbacks)
d.add_handler(handler)
queue = self.updater.start_polling(0.01)
# User one, starts the state machine.
message = Message(0, user, None, None, text="/start")
queue.put(Update(update_id=0, message=message))
sleep(.1)
self.assertTrue(self.current_state[user.id] == self.THIRSTY)
# The user is thirsty and wants to brew coffee.
message = Message(0, user, None, None, text="/brew")
queue.put(Update(update_id=0, message=message))
sleep(.1)
self.assertTrue(self.current_state[user.id] == self.BREWING)
# Lets see if an invalid command makes sure, no state is changed.
message = Message(0, user, None, None, text="/nothing")
queue.put(Update(update_id=0, message=message))
sleep(.1)
self.assertTrue(self.current_state[user.id] == self.BREWING)
# Lets see if the state machine still works by pouring coffee.
message = Message(0, user, None, None, text="/pourCoffee")
queue.put(Update(update_id=0, message=message))
sleep(.1)
self.assertTrue(self.current_state[user.id] == self.DRINKING)
# Let's now verify that for another user, who did not start yet,
# the state has not been changed.
message = Message(0, second_user, None, None, text="/brew")
queue.put(Update(update_id=0, message=message))
sleep(.1)
self.assertRaises(KeyError, self._get_state, user_id=second_user.id)
if __name__ == '__main__':
unittest.main()
+35 -36
View File
@@ -26,7 +26,7 @@ from datetime import datetime
sys.path.append('.')
from telegram import Update, Message, User, Chat
from telegram import Message, User, Chat
from telegram.ext import Filters
from tests.base import BaseTest
@@ -36,119 +36,118 @@ class FiltersTest(BaseTest, unittest.TestCase):
def setUp(self):
self.message = Message(0, User(0, "Testuser"), datetime.now(), Chat(0, 'private'))
self.update = Update(0, message=self.message)
def test_filters_text(self):
self.message.text = 'test'
self.assertTrue(Filters.text(self.update))
self.assertTrue(Filters.text(self.message))
self.message.text = '/test'
self.assertFalse(Filters.text(self.update))
self.assertFalse(Filters.text(self.message))
def test_filters_command(self):
self.message.text = 'test'
self.assertFalse(Filters.command(self.update))
self.assertFalse(Filters.command(self.message))
self.message.text = '/test'
self.assertTrue(Filters.command(self.update))
self.assertTrue(Filters.command(self.message))
def test_filters_audio(self):
self.message.audio = 'test'
self.assertTrue(Filters.audio(self.update))
self.assertTrue(Filters.audio(self.message))
self.message.audio = None
self.assertFalse(Filters.audio(self.update))
self.assertFalse(Filters.audio(self.message))
def test_filters_document(self):
self.message.document = 'test'
self.assertTrue(Filters.document(self.update))
self.assertTrue(Filters.document(self.message))
self.message.document = None
self.assertFalse(Filters.document(self.update))
self.assertFalse(Filters.document(self.message))
def test_filters_photo(self):
self.message.photo = 'test'
self.assertTrue(Filters.photo(self.update))
self.assertTrue(Filters.photo(self.message))
self.message.photo = None
self.assertFalse(Filters.photo(self.update))
self.assertFalse(Filters.photo(self.message))
def test_filters_sticker(self):
self.message.sticker = 'test'
self.assertTrue(Filters.sticker(self.update))
self.assertTrue(Filters.sticker(self.message))
self.message.sticker = None
self.assertFalse(Filters.sticker(self.update))
self.assertFalse(Filters.sticker(self.message))
def test_filters_video(self):
self.message.video = 'test'
self.assertTrue(Filters.video(self.update))
self.assertTrue(Filters.video(self.message))
self.message.video = None
self.assertFalse(Filters.video(self.update))
self.assertFalse(Filters.video(self.message))
def test_filters_voice(self):
self.message.voice = 'test'
self.assertTrue(Filters.voice(self.update))
self.assertTrue(Filters.voice(self.message))
self.message.voice = None
self.assertFalse(Filters.voice(self.update))
self.assertFalse(Filters.voice(self.message))
def test_filters_contact(self):
self.message.contact = 'test'
self.assertTrue(Filters.contact(self.update))
self.assertTrue(Filters.contact(self.message))
self.message.contact = None
self.assertFalse(Filters.contact(self.update))
self.assertFalse(Filters.contact(self.message))
def test_filters_location(self):
self.message.location = 'test'
self.assertTrue(Filters.location(self.update))
self.assertTrue(Filters.location(self.message))
self.message.location = None
self.assertFalse(Filters.location(self.update))
self.assertFalse(Filters.location(self.message))
def test_filters_venue(self):
self.message.venue = 'test'
self.assertTrue(Filters.venue(self.update))
self.assertTrue(Filters.venue(self.message))
self.message.venue = None
self.assertFalse(Filters.venue(self.update))
self.assertFalse(Filters.venue(self.message))
def test_filters_status_update(self):
self.assertFalse(Filters.status_update(self.update))
self.assertFalse(Filters.status_update(self.message))
self.message.new_chat_member = 'test'
self.assertTrue(Filters.status_update(self.update))
self.assertTrue(Filters.status_update(self.message))
self.message.new_chat_member = None
self.message.left_chat_member = 'test'
self.assertTrue(Filters.status_update(self.update))
self.assertTrue(Filters.status_update(self.message))
self.message.left_chat_member = None
self.message.new_chat_title = 'test'
self.assertTrue(Filters.status_update(self.update))
self.assertTrue(Filters.status_update(self.message))
self.message.new_chat_title = ''
self.message.new_chat_photo = 'test'
self.assertTrue(Filters.status_update(self.update))
self.assertTrue(Filters.status_update(self.message))
self.message.new_chat_photo = None
self.message.delete_chat_photo = True
self.assertTrue(Filters.status_update(self.update))
self.assertTrue(Filters.status_update(self.message))
self.message.delete_chat_photo = False
self.message.group_chat_created = True
self.assertTrue(Filters.status_update(self.update))
self.assertTrue(Filters.status_update(self.message))
self.message.group_chat_created = False
self.message.supergroup_chat_created = True
self.assertTrue(Filters.status_update(self.update))
self.assertTrue(Filters.status_update(self.message))
self.message.supergroup_chat_created = False
self.message.migrate_to_chat_id = 100
self.assertTrue(Filters.status_update(self.update))
self.assertTrue(Filters.status_update(self.message))
self.message.migrate_to_chat_id = 0
self.message.migrate_from_chat_id = 100
self.assertTrue(Filters.status_update(self.update))
self.assertTrue(Filters.status_update(self.message))
self.message.migrate_from_chat_id = 0
self.message.channel_chat_created = True
self.assertTrue(Filters.status_update(self.update))
self.assertTrue(Filters.status_update(self.message))
self.message.channel_chat_created = False
self.message.pinned_message = 'test'
self.assertTrue(Filters.status_update(self.update))
self.assertTrue(Filters.status_update(self.message))
self.message.pinned_message = None
+4 -4
View File
@@ -60,8 +60,8 @@ class InlineKeyboardMarkupTest(BaseTest, unittest.TestCase):
inline_keyboard_markup = telegram.InlineKeyboardMarkup.de_json(self.json_dict)
self.assertTrue(isinstance(inline_keyboard_markup.inline_keyboard, list))
self.assertTrue(isinstance(inline_keyboard_markup.inline_keyboard[0][
0], telegram.InlineKeyboardButton))
self.assertTrue(isinstance(inline_keyboard_markup.inline_keyboard[0][0],
telegram.InlineKeyboardButton))
def test_inline_keyboard_markup_to_json(self):
inline_keyboard_markup = telegram.InlineKeyboardMarkup.de_json(self.json_dict)
@@ -72,8 +72,8 @@ class InlineKeyboardMarkupTest(BaseTest, unittest.TestCase):
inline_keyboard_markup = telegram.InlineKeyboardMarkup.de_json(self.json_dict)
self.assertTrue(isinstance(inline_keyboard_markup.inline_keyboard, list))
self.assertTrue(isinstance(inline_keyboard_markup.inline_keyboard[0][
0], telegram.InlineKeyboardButton))
self.assertTrue(isinstance(inline_keyboard_markup.inline_keyboard[0][0],
telegram.InlineKeyboardButton))
if __name__ == '__main__':
+81 -17
View File
@@ -31,7 +31,8 @@ else:
sys.path.append('.')
from telegram.ext import JobQueue, Updater
from telegram.utils.request import stop_con_pool
from telegram.ext import JobQueue, Job, Updater
from tests.base import BaseTest
# Enable logging
@@ -52,53 +53,116 @@ class JobQueueTest(BaseTest, unittest.TestCase):
"""
def setUp(self):
self.jq = JobQueue("Bot", tick_interval=0.005)
self.jq = JobQueue("Bot")
self.result = 0
def tearDown(self):
if self.jq is not None:
self.jq.stop()
stop_con_pool()
def job1(self, bot):
def job1(self, bot, job):
self.result += 1
def job2(self, bot):
def job2(self, bot, job):
raise Exception("Test Error")
def job3(self, bot, job):
self.result += 1
job.schedule_removal()
def job4(self, bot, job):
self.result += job.context
def test_basic(self):
self.jq.put(self.job1, 0.1)
self.jq.put(Job(self.job1, 0.1))
sleep(1.5)
self.assertGreaterEqual(self.result, 10)
def test_job_with_context(self):
self.jq.put(Job(self.job4, 0.1, context=5))
sleep(1.5)
self.assertGreaterEqual(self.result, 50)
def test_noRepeat(self):
self.jq.put(self.job1, 0.1, repeat=False)
self.jq.put(Job(self.job1, 0.1, repeat=False))
sleep(0.5)
self.assertEqual(1, self.result)
def test_nextT(self):
self.jq.put(self.job1, 0.1, next_t=0.5)
self.jq.put(Job(self.job1, 0.1), next_t=0.5)
sleep(0.45)
self.assertEqual(0, self.result)
sleep(0.1)
self.assertEqual(1, self.result)
def test_multiple(self):
self.jq.put(self.job1, 0.1, repeat=False)
self.jq.put(self.job1, 0.2, repeat=False)
self.jq.put(self.job1, 0.4)
self.jq.put(Job(self.job1, 0.1, repeat=False))
self.jq.put(Job(self.job1, 0.2, repeat=False))
self.jq.put(Job(self.job1, 0.4))
sleep(1)
self.assertEqual(4, self.result)
def test_error(self):
self.jq.put(self.job2, 0.1)
self.jq.put(self.job1, 0.2)
self.jq.start()
sleep(0.4)
def test_disabled(self):
j0 = Job(self.job1, 0.1)
j1 = Job(self.job1, 0.2)
self.jq.put(j0)
self.jq.put(Job(self.job1, 0.4))
self.jq.put(j1)
j0.enabled = False
j1.enabled = False
sleep(1)
self.assertEqual(2, self.result)
def test_schedule_removal(self):
j0 = Job(self.job1, 0.1)
j1 = Job(self.job1, 0.2)
self.jq.put(j0)
self.jq.put(Job(self.job1, 0.4))
self.jq.put(j1)
j0.schedule_removal()
j1.schedule_removal()
sleep(1)
self.assertEqual(2, self.result)
def test_schedule_removal_from_within(self):
self.jq.put(Job(self.job1, 0.4))
self.jq.put(Job(self.job3, 0.2))
sleep(1)
self.assertEqual(3, self.result)
def test_longer_first(self):
self.jq.put(Job(self.job1, 0.2, repeat=False))
self.jq.put(Job(self.job1, 0.1, repeat=False))
sleep(0.15)
self.assertEqual(1, self.result)
def test_error(self):
self.jq.put(Job(self.job2, 0.1))
self.jq.put(Job(self.job1, 0.2))
self.jq.start()
sleep(0.5)
self.assertEqual(2, self.result)
def test_jobs_tuple(self):
self.jq.stop()
jobs = tuple(Job(self.job1, t) for t in range(5, 25))
for job in jobs:
self.jq.put(job)
self.assertTupleEqual(jobs, self.jq.jobs())
def test_inUpdater(self):
u = Updater(bot="MockBot", job_queue_tick_interval=0.005)
u.job_queue.put(self.job1, 0.5)
u = Updater(bot="MockBot")
u.job_queue.put(Job(self.job1, 0.5))
sleep(0.75)
self.assertEqual(1, self.result)
u.stop()
+2 -3
View File
@@ -35,15 +35,14 @@ class PhotoTest(BaseTest, unittest.TestCase):
def setUp(self):
self.photo_file = open('tests/data/telegram.jpg', 'rb')
self.photo_file_id = 'AgADAQADvb8xGx8j9QcpZDKxYoFK3bfX1i8ABFX_dgMWoKDuQugAAgI'
self.photo_file_id = 'AgADAQADgEsyGx8j9QfmDMmwkPBrFcKRzy8ABHW8ul9nW7FoNHYBAAEC'
self.photo_file_url = 'https://raw.githubusercontent.com/python-telegram-bot/python-telegram-bot/master/tests/data/telegram.jpg'
self.width = 300
self.height = 300
self.thumb = {
'width': 90,
'height': 90,
'file_id':
'AgADAQADvb8xGx8j9QcpZDKxYoFK3bfX1i8ABBxRLXFhLnhIQ-gAAgI',
'file_id': 'AgADAQADgEsyGx8j9QfmDMmwkPBrFcKRzy8ABD64nkFkjujeNXYBAAEC',
'file_size': 1478
}
self.file_size = 10209
+98 -35
View File
@@ -48,6 +48,7 @@ except ImportError:
sys.path.append('.')
from telegram import Update, Message, TelegramError, User, Chat, Bot
from telegram.utils.request import stop_con_pool
from telegram.ext import *
from telegram.ext.dispatcher import run_async
from telegram.error import Unauthorized, InvalidToken
@@ -71,6 +72,11 @@ class UpdaterTest(BaseTest, unittest.TestCase):
WebhookHandler
"""
updater = None
received_message = None
message_count = None
lock = None
def setUp(self):
self.updater = None
self.received_message = None
@@ -78,12 +84,14 @@ class UpdaterTest(BaseTest, unittest.TestCase):
self.lock = Lock()
def _setup_updater(self, *args, **kwargs):
stop_con_pool()
bot = MockBot(*args, **kwargs)
self.updater = Updater(workers=2, bot=bot)
def tearDown(self):
if self.updater is not None:
self.updater.stop()
stop_con_pool()
def reset(self):
self.message_count = 0
@@ -93,6 +101,10 @@ class UpdaterTest(BaseTest, unittest.TestCase):
self.received_message = update.message.text
self.message_count += 1
def telegramHandlerEditedTest(self, bot, update):
self.received_message = update.edited_message.text
self.message_count += 1
def telegramInlineHandlerTest(self, bot, update):
self.received_message = (update.inline_query, update.chosen_inline_result)
self.message_count += 1
@@ -116,9 +128,12 @@ class UpdaterTest(BaseTest, unittest.TestCase):
self.received_message = (groups, groupdict)
self.message_count += 1
def additionalArgsTest(self, bot, update, update_queue, args):
def additionalArgsTest(self, bot, update, update_queue, job_queue, args):
job_queue.put(Job(lambda bot, job: job.schedule_removal(), 0.1))
self.received_message = update
self.message_count += 1
if args[0] == 'resend':
update_queue.put('/test5 noresend')
elif args[0] == 'noresend':
@@ -144,6 +159,24 @@ class UpdaterTest(BaseTest, unittest.TestCase):
d = self.updater.dispatcher
from telegram.ext import Filters
handler = MessageHandler([Filters.text], self.telegramHandlerTest)
d.add_handler(handler)
self.updater.start_polling(0.01)
sleep(.1)
self.assertEqual(self.received_message, 'Test')
# Remove handler
d.remove_handler(handler)
self.reset()
self.updater.bot.send_messages = 1
sleep(.1)
self.assertTrue(None is self.received_message)
def test_editedMessageHandler(self):
self._setup_updater('Test', edited=True)
d = self.updater.dispatcher
from telegram.ext import Filters
handler = MessageHandler([Filters.text], self.telegramHandlerEditedTest, allow_edited=True)
d.addHandler(handler)
self.updater.start_polling(0.01)
sleep(.1)
@@ -151,6 +184,10 @@ class UpdaterTest(BaseTest, unittest.TestCase):
# Remove handler
d.removeHandler(handler)
handler = MessageHandler([Filters.text],
self.telegramHandlerEditedTest,
allow_edited=False)
d.addHandler(handler)
self.reset()
self.updater.bot.send_messages = 1
@@ -159,7 +196,7 @@ class UpdaterTest(BaseTest, unittest.TestCase):
def test_addTelegramMessageHandlerMultipleMessages(self):
self._setup_updater('Multiple', 100)
self.updater.dispatcher.addHandler(MessageHandler([], self.telegramHandlerTest))
self.updater.dispatcher.add_handler(MessageHandler([], self.telegramHandlerTest))
self.updater.start_polling(0.0)
sleep(2)
self.assertEqual(self.received_message, 'Multiple')
@@ -170,13 +207,13 @@ class UpdaterTest(BaseTest, unittest.TestCase):
d = self.updater.dispatcher
regobj = re.compile('Te.*')
handler = RegexHandler(regobj, self.telegramHandlerTest)
self.updater.dispatcher.addHandler(handler)
self.updater.dispatcher.add_handler(handler)
self.updater.start_polling(0.01)
sleep(.1)
self.assertEqual(self.received_message, 'Test2')
# Remove handler
d.removeHandler(handler)
d.remove_handler(handler)
self.reset()
self.updater.bot.send_messages = 1
@@ -187,13 +224,32 @@ class UpdaterTest(BaseTest, unittest.TestCase):
self._setup_updater('/test')
d = self.updater.dispatcher
handler = CommandHandler('test', self.telegramHandlerTest)
self.updater.dispatcher.addHandler(handler)
self.updater.dispatcher.add_handler(handler)
self.updater.start_polling(0.01)
sleep(.1)
self.assertEqual(self.received_message, '/test')
# Remove handler
d.remove_handler(handler)
self.reset()
self.updater.bot.send_messages = 1
sleep(.1)
self.assertTrue(None is self.received_message)
def test_editedCommandHandler(self):
self._setup_updater('/test', edited=True)
d = self.updater.dispatcher
handler = CommandHandler('test', self.telegramHandlerEditedTest, allow_edited=True)
d.addHandler(handler)
self.updater.start_polling(0.01)
sleep(.1)
self.assertEqual(self.received_message, '/test')
# Remove handler
d.removeHandler(handler)
handler = CommandHandler('test', self.telegramHandlerEditedTest, allow_edited=False)
d.addHandler(handler)
self.reset()
self.updater.bot.send_messages = 1
@@ -204,14 +260,14 @@ class UpdaterTest(BaseTest, unittest.TestCase):
self._setup_updater('', messages=0)
d = self.updater.dispatcher
handler = StringRegexHandler('Te.*', self.stringHandlerTest)
d.addHandler(handler)
d.add_handler(handler)
queue = self.updater.start_polling(0.01)
queue.put('Test3')
sleep(.1)
self.assertEqual(self.received_message, 'Test3')
# Remove handler
d.removeHandler(handler)
d.remove_handler(handler)
self.reset()
queue.put('Test3')
@@ -222,7 +278,7 @@ class UpdaterTest(BaseTest, unittest.TestCase):
self._setup_updater('', messages=0)
d = self.updater.dispatcher
handler = StringCommandHandler('test3', self.stringHandlerTest)
d.addHandler(handler)
d.add_handler(handler)
queue = self.updater.start_polling(0.01)
queue.put('/test3')
@@ -230,7 +286,7 @@ class UpdaterTest(BaseTest, unittest.TestCase):
self.assertEqual(self.received_message, '/test3')
# Remove handler
d.removeHandler(handler)
d.remove_handler(handler)
self.reset()
queue.put('/test3')
@@ -240,7 +296,7 @@ class UpdaterTest(BaseTest, unittest.TestCase):
def test_addRemoveErrorHandler(self):
self._setup_updater('', messages=0)
d = self.updater.dispatcher
d.addErrorHandler(self.errorHandlerTest)
d.add_error_handler(self.errorHandlerTest)
queue = self.updater.start_polling(0.01)
error = TelegramError("Unauthorized.")
queue.put(error)
@@ -248,7 +304,7 @@ class UpdaterTest(BaseTest, unittest.TestCase):
self.assertEqual(self.received_message, "Unauthorized.")
# Remove handler
d.removeErrorHandler(self.errorHandlerTest)
d.remove_error_handler(self.errorHandlerTest)
self.reset()
queue.put(error)
@@ -259,8 +315,8 @@ class UpdaterTest(BaseTest, unittest.TestCase):
self._setup_updater('', messages=0)
d = self.updater.dispatcher
handler = StringRegexHandler('.*', self.errorRaisingHandlerTest)
d.addHandler(handler)
self.updater.dispatcher.addErrorHandler(self.errorHandlerTest)
d.add_handler(handler)
self.updater.dispatcher.add_error_handler(self.errorHandlerTest)
queue = self.updater.start_polling(0.01)
queue.put('Test Error 1')
@@ -271,7 +327,7 @@ class UpdaterTest(BaseTest, unittest.TestCase):
self._setup_updater('')
d = self.updater.dispatcher
handler = MessageHandler([], self.telegramHandlerTest)
d.addHandler(handler)
d.add_handler(handler)
self.updater.start_polling(0.01, clean=True)
sleep(.1)
self.assertEqual(self.message_count, 0)
@@ -280,7 +336,7 @@ class UpdaterTest(BaseTest, unittest.TestCase):
def test_errorOnGetUpdates(self):
self._setup_updater('', raise_error=True)
d = self.updater.dispatcher
d.addErrorHandler(self.errorHandlerTest)
d.add_error_handler(self.errorHandlerTest)
self.updater.start_polling(0.01)
sleep(.1)
self.assertEqual(self.received_message, "Test Error 2")
@@ -289,7 +345,7 @@ class UpdaterTest(BaseTest, unittest.TestCase):
self._setup_updater('', messages=0)
d = self.updater.dispatcher
handler = TypeHandler(dict, self.stringHandlerTest)
d.addHandler(handler)
d.add_handler(handler)
queue = self.updater.start_polling(0.01)
payload = {"Test": 42}
queue.put(payload)
@@ -297,7 +353,7 @@ class UpdaterTest(BaseTest, unittest.TestCase):
self.assertEqual(self.received_message, payload)
# Remove handler
d.removeHandler(handler)
d.remove_handler(handler)
self.reset()
queue.put(payload)
@@ -309,8 +365,8 @@ class UpdaterTest(BaseTest, unittest.TestCase):
d = self.updater.dispatcher
handler = InlineQueryHandler(self.telegramInlineHandlerTest)
handler2 = ChosenInlineResultHandler(self.telegramInlineHandlerTest)
d.addHandler(handler)
d.addHandler(handler2)
d.add_handler(handler)
d.add_handler(handler2)
queue = self.updater.start_polling(0.01)
update = Update(update_id=0, inline_query="testquery")
update2 = Update(update_id=0, chosen_inline_result="testresult")
@@ -323,8 +379,8 @@ class UpdaterTest(BaseTest, unittest.TestCase):
self.assertEqual(self.received_message[1], "testresult")
# Remove handler
d.removeHandler(handler)
d.removeHandler(handler2)
d.remove_handler(handler)
d.remove_handler(handler2)
self.reset()
queue.put(update)
@@ -335,7 +391,7 @@ class UpdaterTest(BaseTest, unittest.TestCase):
self._setup_updater('', messages=0)
d = self.updater.dispatcher
handler = CallbackQueryHandler(self.telegramCallbackHandlerTest)
d.addHandler(handler)
d.add_handler(handler)
queue = self.updater.start_polling(0.01)
update = Update(update_id=0, callback_query="testcallback")
queue.put(update)
@@ -343,7 +399,7 @@ class UpdaterTest(BaseTest, unittest.TestCase):
self.assertEqual(self.received_message, "testcallback")
# Remove handler
d.removeHandler(handler)
d.remove_handler(handler)
self.reset()
queue.put(update)
@@ -354,7 +410,7 @@ class UpdaterTest(BaseTest, unittest.TestCase):
self._setup_updater('Test5', messages=2)
d = self.updater.dispatcher
handler = MessageHandler([], self.asyncHandlerTest)
d.addHandler(handler)
d.add_handler(handler)
self.updater.start_polling(0.01)
sleep(1.2)
self.assertEqual(self.received_message, 'Test5')
@@ -365,8 +421,9 @@ class UpdaterTest(BaseTest, unittest.TestCase):
handler = StringCommandHandler('test5',
self.additionalArgsTest,
pass_update_queue=True,
pass_job_queue=True,
pass_args=True)
self.updater.dispatcher.addHandler(handler)
self.updater.dispatcher.add_handler(handler)
queue = self.updater.start_polling(0.01)
queue.put('/test5 resend')
@@ -381,7 +438,7 @@ class UpdaterTest(BaseTest, unittest.TestCase):
self.regexGroupHandlerTest,
pass_groupdict=True,
pass_groups=True)
d.addHandler(handler)
d.add_handler(handler)
queue = self.updater.start_polling(0.01)
queue.put('This is a test message for regex group matching.')
sleep(.1)
@@ -392,7 +449,7 @@ class UpdaterTest(BaseTest, unittest.TestCase):
self._setup_updater('Test6', messages=2)
d = self.updater.dispatcher
handler = MessageHandler([], self.asyncAdditionalHandlerTest, pass_update_queue=True)
d.addHandler(handler)
d.add_handler(handler)
self.updater.start_polling(0.01)
sleep(1.2)
self.assertEqual(self.received_message, 'Test6')
@@ -402,7 +459,7 @@ class UpdaterTest(BaseTest, unittest.TestCase):
self._setup_updater('', messages=0)
d = self.updater.dispatcher
handler = MessageHandler([], self.telegramHandlerTest)
d.addHandler(handler)
d.add_handler(handler)
ip = '127.0.0.1'
port = randrange(1024, 49152) # Select random port for travis
@@ -452,7 +509,7 @@ class UpdaterTest(BaseTest, unittest.TestCase):
self._setup_updater('', messages=0)
d = self.updater.dispatcher
handler = MessageHandler([], self.telegramHandlerTest)
d.addHandler(handler)
d.add_handler(handler)
ip = '127.0.0.1'
port = randrange(1024, 49152) # Select random port for travis
@@ -594,8 +651,8 @@ class UpdaterTest(BaseTest, unittest.TestCase):
self.assertFalse(self.updater.running)
def test_createBot(self):
updater = Updater('123:abcd')
self.assertIsNotNone(updater.bot)
self.updater = Updater('123:abcd')
self.assertIsNotNone(self.updater.bot)
def test_mutualExclusiveTokenBot(self):
bot = Bot('123:zyxw')
@@ -612,7 +669,8 @@ class MockBot(object):
messages=1,
raise_error=False,
bootstrap_retries=None,
bootstrap_err=TelegramError('test')):
bootstrap_err=TelegramError('test'),
edited=False):
self.text = text
self.send_messages = messages
self.raise_error = raise_error
@@ -620,13 +678,18 @@ class MockBot(object):
self.bootstrap_retries = bootstrap_retries
self.bootstrap_attempts = 0
self.bootstrap_err = bootstrap_err
self.edited = edited
@staticmethod
def mockUpdate(text):
def mockUpdate(self, text):
message = Message(0, None, None, None)
message.text = text
update = Update(0)
update.message = message
if self.edited:
update.edited_message = message
else:
update.message = message
return update
def setWebhook(self, webhook_url=None, certificate=None):