Compare commits

..

136 Commits

Author SHA1 Message Date
Jannes Höke 37271e4a02 Merge pull request #127 from python-telegram-bot/chatclass
API updates Oct and Nov
2015-12-16 16:32:19 +01:00
Jannes Höke 0e78606d37 Release v3.1 2015-12-16 16:25:14 +01:00
Jannes Höke cb0736fb3d update sphinx docs 2015-12-16 16:23:18 +01:00
Jannes Höke e0233a59a0 fix docstrings for sphinx 2015-12-16 16:18:48 +01:00
Jannes Höke 6e158042e1 API updates Oct and Nov 2015-12-16 15:31:02 +01:00
Jannes Höke d791a6ee89 Update group link 2015-12-16 02:57:33 +01:00
leandrotoledo 5a3e142358 Updating one more ref to old repo 2015-12-14 21:51:17 -02:00
leandrotoledo 37a58fe1ad Update TODO on README 2015-12-14 21:41:22 -02:00
leandrotoledo 80281cc66d Moving to python-telegram-bot organization 2015-12-14 21:36:22 -02:00
Jannes Höke fffa9d8a7c Update README.rst 2015-12-14 12:22:25 +01:00
Jannes Höke 806b71030f Update group link 2015-12-13 21:24:05 +01:00
Jannes Höke 764c5dac53 Add Logo to README 2015-12-13 21:18:59 +01:00
Jannes Höke 007dc5807a move error disables to .travis.yml 2015-12-11 18:52:23 +01:00
Jannes Höke 16534872a7 add pylintrc ignoring import-error,no-name-in-module to fix travis 2015-12-11 18:38:29 +01:00
Jannes Höke a13245dfbf change 'wb' to 'rb' in certificate open call 2015-12-09 12:59:34 +01:00
Jannes Höke 6622ea3ec0 Release version 3.0.0 2015-12-08 00:07:21 +01:00
Jannes Höke bcadfc5398 Release version 3.0.0 2015-12-07 23:49:19 +01:00
Jannes Höke 89ecb04a85 Catch URLError while getting updates 2015-12-07 23:45:21 +01:00
Jannes Höke 56ab40d94f increase time in test multiple messages 2015-12-01 21:58:39 +01:00
Jannes Höke f5ac1ae067 debug message after processing update 2015-12-01 21:45:12 +01:00
Jannes Höke 23507202e8 Merge branch 'Balduro-boteventhandler' 2015-12-01 18:08:38 +01:00
Jannes Höke 185bff7117 update example 2015-12-01 18:06:42 +01:00
Gerd Augsburg 9f07900d99 Added Balduro to AUTHORS.rst and fixed line length 2015-12-01 15:16:04 +01:00
Gerd Augsburg 809fe4b74f Updated tests for start_webhook 2015-12-01 14:48:55 +01:00
Gerd Augsburg 8a4d5c5de7 Separate start_webhook and setWebhook
I think starting and setting the webhook should be two different tasks.
Because the internal webhook and the adress you see from outside can be
quite diffrent through apache etc. It provides more flexibility.
2015-12-01 14:11:27 +01:00
Jannes Höke 1976822dd0 Add documentation for Updater and Dispatcher 2015-12-01 01:01:28 +01:00
Jannes Höke e60694a8cc update test_webhook and add test_webhook_no_ssl 2015-11-30 23:00:56 +01:00
Jannes Höke 14b4c1ac62 Allow starting webhook without SSL (issue #112) 2015-11-30 23:00:32 +01:00
Leandro Toledo d5890d403b Merge pull request #109 from leandrotoledo/boteventhandler
Indrucing "Updater" and "Dispatcher"
2015-11-30 08:22:36 -02:00
Jannes Höke ef569b68c6 update docs 2015-11-24 21:07:13 +01:00
Jannes Höke 628a4c4eac final renaming of files and flake8 2015-11-24 21:06:55 +01:00
Jannes Höke 83d2ca3aa7 assertIsNone(...) to assertTrue(None is ...) 2015-11-24 20:40:40 +01:00
Jannes Höke 29cdfe671e test removehandler methods 2015-11-24 20:34:38 +01:00
Jannes Höke f21b6046c5 test error in handler, other webhook server functionalites 2015-11-24 19:35:02 +01:00
Jannes Höke 8090658ab4 log warning 2015-11-24 19:33:41 +01:00
Jannes Höke f49f592f0d log warning 2015-11-24 19:33:03 +01:00
Jannes Höke c2853fa1ef WebhookServer: Lock serve_forever() and shutdown() 2015-11-24 15:39:00 +01:00
Jannes Höke e1f3f346bc Test for Update.idle() 2015-11-24 14:57:54 +01:00
Jannes Höke 1005ad57ce Improve signal handling 2015-11-23 17:40:39 +01:00
Jannes Höke 0b72acc7c8 handle SIGINT, SIGTERM and SIGABRT 2015-11-23 03:45:47 +01:00
Jannes Höke 8d267ed896 use logging 2015-11-22 19:20:16 +01:00
Jannes Höke 83812f3af6 rename boteventhandler.py to updater.py 2015-11-22 19:20:05 +01:00
Jannes Höke 658b5ff1c0 update tests 2015-11-22 19:16:49 +01:00
Jannes Höke 593153128c Update examples 2015-11-22 19:15:37 +01:00
Jannes Höke d91f210f7c refactor BotEventHandler to Updater, improve documentation, add idle() method 2015-11-22 19:15:17 +01:00
Jannes Höke 65177e791f raise error instead of quietly exiting on bad SSL cert 2015-11-22 16:46:17 +01:00
Jannes Höke 15501e185b renamed broadcaster.py to dispatcher.py 2015-11-22 14:52:41 +01:00
Jannes Höke 1782d0d19b refactor Broadcaster to Dispatcher 2015-11-22 14:47:38 +01:00
Jannes Höke 88fbf3b5cf remove broadcaster argument from __init__ 2015-11-22 14:27:05 +01:00
Jannes Höke c7a1d8dca3 test additional arguments 2015-11-22 14:08:33 +01:00
Jannes Höke 3e732a0736 fix multiple additional arguments 2015-11-22 14:07:20 +01:00
Jannes Höke 628e1b743a add bits of documentation 2015-11-22 13:58:40 +01:00
Jannes Höke e4de3f00b8 Add optional handler argument 'args' and documentation on handlers 2015-11-22 13:57:59 +01:00
Jannes Höke 32c021fdd5 Don't create new bot when broadcaster is passed 2015-11-22 13:30:23 +01:00
Jannes Höke a6e5a71e05 Check SSL-Certificate, if possible 2015-11-22 01:03:29 +01:00
Jannes Höke 4d87e236be Use random port for travis and py2&/py3 compability 2015-11-21 23:22:41 +01:00
Jannes Höke 29ab0556f0 test Webhook completed 2015-11-21 23:09:44 +01:00
Jannes Höke fca8aee177 improve logging 2015-11-21 23:09:19 +01:00
Jannes Höke 6a80a33aef test for error on getUpdates and began to test webhook 2015-11-21 21:22:58 +01:00
Jannes Höke 7c9928c58f Let shutdown return immediately if server is not running 2015-11-21 21:22:11 +01:00
Jannes Höke 0cd7aa92ae more logging 2015-11-21 21:21:09 +01:00
Jannes Höke 6626d4534e test @run_async 2015-11-21 19:35:24 +01:00
Jannes Höke 6b309397e8 send less messages in more time so travis can take its time 2015-11-21 16:18:19 +01:00
Jannes Höke 618df51811 flake8 2015-11-21 16:04:06 +01:00
Jannes Höke d38add1a1c really? 2015-11-21 15:50:39 +01:00
Jannes Höke 1c7e784662 add setWebhook to mock bot 2015-11-21 15:48:45 +01:00
Jannes Höke 0006294f29 update tests 2015-11-21 15:45:45 +01:00
Jannes Höke c1f194a310 change all start() to start_polling() 2015-11-21 15:31:23 +01:00
Jannes Höke cccd6e5baf Merge branch 'master' of github.com:leandrotoledo/python-telegram-bot into boteventhandler 2015-11-21 03:29:37 +01:00
Jannes Höke a0baa68cf3 Merge pull request #105 from rahiel/master
handle "Unauthorized" exceptions in echobot
2015-11-20 18:28:08 +01:00
Rahiel Kasim 62b79df0a4 handle "Unauthorized" exceptions in echobot 2015-11-20 14:41:12 +01:00
Jannes Höke 1246039872 Merge pull request #104 from mASOUDd/boteventhandler
remove trailing whitespaces
2015-11-17 16:05:41 +01:00
Masoud Naservand ed6dd76ae8 Corrected trailing whitespaces 2015-11-17 18:27:22 +03:30
Jannes Höke 70de2bd3f1 Merge pull request #102 from franciscod/patch-1
README: add link to TwitterForwarderBot
2015-11-17 09:08:38 +01:00
Francisco Demartino 8bde310ec7 README: add link to TwitterForwarderBot 2015-11-17 01:34:00 -03:00
Jannes Höke be368f7d74 add example for webhook 2015-11-17 00:30:21 +01:00
Jannes Höke 9defc41774 correct docstrings 2015-11-17 00:04:28 +01:00
Jannes Höke ee8b78aa0d pep8 and docs 2015-11-16 20:43:35 +01:00
Jannes Höke 0b8fd24771 fixed issues with python2 2015-11-16 20:35:27 +01:00
Jannes Höke 396dc6cd3c Implement webhook listener server 2015-11-16 13:05:57 +01:00
Jannes Höke ba3d174fde Change eh.start() to eh.start_polling() 2015-11-16 13:05:17 +01:00
Jannes Höke ab313488ad Usage instructions for bots 2015-11-15 20:13:03 +01:00
Jannes Höke d808462fb6 Simple bot 2015-11-15 20:02:09 +01:00
Jannes Höke 71e73c3999 logging 2015-11-15 19:21:35 +01:00
Jannes Höke a55df8b5e1 update example 2015-11-15 19:14:12 +01:00
Jannes Höke f59efe0f4b forward related update to errorhandlers 2015-11-15 19:12:42 +01:00
Jannes Höke 1b79a57673 Merge branch 'master' of github.com:leandrotoledo/python-telegram-bot into boteventhandler 2015-11-15 17:38:37 +01:00
Jannes Höke ae59a18e2d update example 2015-11-15 17:37:01 +01:00
Jannes Höke 12201f392d graceful stopping of threads, use logging, reuse of broadcaster 2015-11-15 17:36:38 +01:00
leandrotoledo 8f3fe123e3 Fix travis 2015-11-13 10:53:31 -02:00
Jannes Höke 04050ca883 Merge branch 'master' of github.com:leandrotoledo/python-telegram-bot into boteventhandler 2015-11-13 11:05:26 +01:00
Jannes Höke c3b3d51286 Merge pull request #99 from rahiel/readme
improve echobot with cleaner code and basic exception handling
2015-11-12 19:21:29 +01:00
Jannes Höke 10451509f1 fix for issue #79 2015-11-12 19:15:39 +01:00
Rahiel Kasim ff0d724f32 improve echobot with cleaner code and basic exception handling 2015-11-12 17:26:25 +01:00
Leandro Toledo c64e577049 Fixes pylint for Py2.6 2015-11-11 12:41:07 -02:00
Leandro Toledo f08dca0af6 Fixes pylint for Py2.6 2015-11-11 12:39:09 -02:00
Leandro Toledo 8c0bcbf5bb Fixes typo on .travis.yml 2015-11-11 12:28:34 -02:00
Leandro Toledo 596fc2aeda Adding OSX and lint/pep8 tests to Travis 2015-11-11 12:27:07 -02:00
Leandro Toledo 968b19e8b7 Rolling back travis settings 2015-11-11 12:07:43 -02:00
Leandro Toledo 7993328ff7 Enable travis for PRs 2015-11-11 12:04:23 -02:00
Leandro Toledo 16349ac69a Releasing v2.9.2 2015-11-11 11:31:20 -02:00
Jannes Höke 45712c52f1 Merge branch 'master' of github.com:leandrotoledo/python-telegram-bot into boteventhandler 2015-11-11 14:27:58 +01:00
Leandro Toledo 235dcd2ad2 Merge branch 'master' of https://github.com/leandrotoledo/python-telegram-bot 2015-11-11 11:25:05 -02:00
Leandro Toledo ed92d1c254 Adds requirements.txt to MANIFEST 2015-11-11 11:24:49 -02:00
Jannes Höke 0b4c23f50e improve error handling on timeouts 2015-11-11 14:06:10 +01:00
Jannes Höke 63c895c0a0 run_async moved to broadcaster and uses a thread pool now 2015-11-11 13:33:03 +01:00
Jannes Höke 3162bc60e9 cleaned up code 2015-11-11 13:32:11 +01:00
leandrotoledo 206802cf4d Minor fix on docs 2015-11-10 20:33:14 -02:00
leandrotoledo 5eb4f8e5cd Minor pep8 fix 2015-11-10 20:31:51 -02:00
Jannes Höke 0a27e3927b merge 2015-11-10 23:17:31 +01:00
Jannes Höke 401add78d3 fix big mistake 2015-11-10 23:15:37 +01:00
Jannes Höke af375eb402 improved code quality 2015-11-10 23:15:37 +01:00
Jannes Höke 2797111ced First tests for BotEventHandler and Broadcaster 2015-11-10 23:15:37 +01:00
Jannes Höke d7d4889c50 use run_async decorator in example 2015-11-10 23:15:37 +01:00
Jannes Höke bf356e124f add run_async decorator 2015-11-10 23:15:37 +01:00
Jannes Höke d7fd43ca79 Import BotEventHandler and Broadcaster 2015-11-10 23:15:37 +01:00
Jannes Höke 42e11d169e Update AUTHORS.rst 2015-11-10 23:15:37 +01:00
Jannes Höke c1fae0b5ee example bot for BotEventHandler 2015-11-10 23:15:37 +01:00
Jannes Höke ef99bab435 initial commit for BotEventHandler and Broadcaster 2015-11-10 23:15:37 +01:00
Jannes Höke 93afd3eabe remove check for network_delay 2015-11-10 23:12:20 +01:00
Jannes Höke 61dac76bee don't send network_delay to telegram servers 2015-11-10 22:40:19 +01:00
Jannes Höke 245f5abc45 further improved formatting 2015-11-10 21:32:54 +01:00
Jannes Höke 0f8f6584ce Fix for issue #81 2015-11-10 20:36:26 +01:00
Jannes Höke dd498ded00 Update to version 2.9.1 2015-11-10 20:22:58 +01:00
Jannes Höke 5d2999818b Update to version 2.9.1 2015-11-10 20:19:12 +01:00
Jannes Höke eb1f197ff4 2.9.1 release and generally improved formatting 2015-11-10 20:10:44 +01:00
Jannes Höke 4bc03ed56a Add parameter network_delay for slow connections and increase default to 2 seconds 2015-11-10 19:47:21 +01:00
Leandro Toledo 9191d333ca Adds future as requirement 2015-11-10 12:27:18 -02:00
Jannes Höke c19a84ac05 improved code quality 2015-11-06 00:24:01 +01:00
Jannes Höke 404fdc2dd7 First tests for BotEventHandler and Broadcaster 2015-11-06 00:23:34 +01:00
Jannes Höke 2ec56abe7e use run_async decorator in example 2015-11-05 16:01:25 +01:00
Jannes Höke 63a8700258 add run_async decorator 2015-11-05 16:01:08 +01:00
Jannes Höke d363185031 Import BotEventHandler and Broadcaster 2015-11-05 15:22:13 +01:00
Jannes Höke aabaaa9049 Update AUTHORS.rst 2015-11-05 14:07:28 +01:00
Jannes Höke eb2be61eac example bot for BotEventHandler 2015-11-05 13:52:57 +01:00
Jannes Höke 0688691974 initial commit for BotEventHandler and Broadcaster 2015-11-05 13:52:33 +01:00
28 changed files with 2162 additions and 255 deletions
+4 -2
View File
@@ -7,9 +7,11 @@ python:
- "pypy"
- "pypy3"
install:
- pip install coveralls
- pip install pylint flake8 coveralls
- pip install -r requirements.txt
script:
nosetests --with-coverage --cover-package telegram/
- nosetests --with-coverage --cover-package telegram/
- flake8 telegram
- 'if [[ $TRAVIS_PYTHON_VERSION != 2.6 ]]; then pylint -E telegram --disable=no-name-in-module,import-error; fi'
after_success:
coveralls
+2
View File
@@ -9,6 +9,7 @@ Contributors
The following wonderful people contributed directly or indirectly to this project:
- `Avanatiker <https://github.com/Avanatiker>`_
- `Balduro <https://github.com/Balduro>`_
- `bimmlerd <https://github.com/bimmlerd>`_
- `ErgoZ Riftbit Vaper <https://github.com/ergoz>`_
- `franciscod <https://github.com/franciscod>`_
@@ -16,6 +17,7 @@ The following wonderful people contributed directly or indirectly to this projec
- `jh0ker <https://github.com/jh0ker>`_
- `JRoot3D <https://github.com/JRoot3D>`_
- `macrojames <https://github.com/macrojames>`_
- `naveenvhegde <https://github.com/naveenvhegde>`_
- `njittam <https://github.com/njittam>`_
- `Rahiel Kasim <https://github.com/rahiel>`_
- `sooyhwang <https://github.com/sooyhwang>`_
+217 -127
View File
@@ -1,189 +1,279 @@
2015-11-10
Released 2.9
Emoji class now uses bytes_to_native_str from future 3rd party lib
Make user_from optional to work with channels channels
Raise exception if Telegram times out on long-polling
*Special thanks to @jh0ker for all hard work*
**2015-12-16**
*Released 3.1.0*
- The ``chat``-field in ``Message`` is now of type ``Chat``. (API update Oct 8 2015)
- ``Message`` now contains the optional fields ``supergroup_chat_created``, ``migrate_to_chat_id``, ``migrate_from_chat_id`` and ``channel_chat_created``. (API update Nov 2015)
**2015-12-08**
*Released 3.0.0*
- Introducing the ``Updater`` and ``Dispatcher`` classes
**2015-11-11**
*Released 2.9.2*
- Error handling on request timeouts has been improved
**2015-11-10**
*Released 2.9.1*
- Add parameter ``network_delay`` to Bot.getUpdates for slow connections
**2015-11-10**
*Released 2.9*
- Emoji class now uses ``bytes_to_native_str`` from ``future`` 3rd party lib
- Make ``user_from`` optional to work with channels
- Raise exception if Telegram times out on long-polling
*Special thanks to @jh0ker for all hard work*
2015-10-08
Released 2.8.7
Type as optional for GroupChat class
**2015-10-08**
*Released 2.8.7*
- Type as optional for ``GroupChat`` class
2015-10-08
Released 2.8.6
Adds type to User and GroupChat classes (pre-release Telegram feature)
**2015-10-08**
*Released 2.8.6*
- Adds type to ``User`` and ``GroupChat`` classes (pre-release Telegram feature)
2015-09-24
Released 2.8.5
Handles HTTP Bad Gateway (503) errors on request
Fixes regression on Audio and Document for unicode fields
**2015-09-24**
*Released 2.8.5*
- Handles HTTP Bad Gateway (503) errors on request
- Fixes regression on ``Audio`` and ``Document`` for unicode fields
2015-09-20
Released 2.8.4
getFile and File.download is now fully supported
**2015-09-20**
*Released 2.8.4*
- ``getFile`` and ``File.download`` is now fully supported
2015-09-10
Released 2.8.3
Moved Bot._requestURL to its own class (telegram.utils.request)
Much better, such wow, Telegram Objects tests
Add consistency for str properties on Telegram Objects
Better design to test if chat_id is invalid
Add ability to set custom filename on Bot.sendDocument(..,filename='')
Fix Sticker as InputFile
Send JSON requests over urlencoded post data
Markdown support for Bot.sendMessage(..., parse_mode=ParseMode.MARKDOWN)
Refactor of TelegramError class (no more handling IOError or URLError)
**2015-09-10**
*Released 2.8.3*
- Moved ``Bot._requestURL`` to its own class (``telegram.utils.request``)
- Much better, such wow, Telegram Objects tests
- Add consistency for ``str`` properties on Telegram Objects
- Better design to test if ``chat_id`` is invalid
- Add ability to set custom filename on ``Bot.sendDocument(..,filename='')``
- Fix Sticker as ``InputFile``
- Send JSON requests over urlencoded post data
- Markdown support for ``Bot.sendMessage(..., parse_mode=ParseMode.MARKDOWN)``
- Refactor of ``TelegramError`` class (no more handling ``IOError`` or ``URLError``)
2015-09-05
Released 2.8.2
Fix regression on Telegram ReplyMarkup
Add certificate to is_inputfile method
**2015-09-05**
*Released 2.8.2*
- Fix regression on Telegram ReplyMarkup
- Add certificate to ``is_inputfile`` method
2015-09-05
Released 2.8.1
Fix regression on Telegram objects with thumb properties
**2015-09-05**
*Released 2.8.1*
- Fix regression on Telegram objects with thumb properties
2015-09-04
Released 2.8
TelegramError when chat_id is empty for send* methods
setWebhook now supports sending self-signed certificate
Huge redesign of existing Telegram classes
Added support for PyPy
Added docstring for existing classes
**2015-09-04**
*Released 2.8*
- TelegramError when ``chat_id`` is empty for send* methods
- ``setWebhook`` now supports sending self-signed certificate
- Huge redesign of existing Telegram classes
- Added support for PyPy
- Added docstring for existing classes
2015-08-19
Released 2.7.1
Fixed JSON serialization for message
**2015-08-19**
*Released 2.7.1*
- Fixed JSON serialization for ``message``
2015-08-17
Released 2.7
Added support for Voice object and sendVoice method
Due backward compatibility performer or/and title will be required for sendAudio
Fixed JSON serialization when forwarded message
**2015-08-17**
*Released 2.7*
- Added support for ``Voice`` object and ``sendVoice`` method
- Due backward compatibility performer or/and title will be required for ``sendAudio``
- Fixed JSON serialization when forwarded message
2015-08-15
Released 2.6.1
Fixed parsing image header issue on < Python 2.7.3
**2015-08-15**
*Released 2.6.1*
- Fixed parsing image header issue on < Python 2.7.3
2015-08-14
Released 2.6.0
Depreciation of require_authentication and clearCredentials methods
Giving AUTHORS the proper credits for their contribution for this project
Message.date and Message.forward_date are now datetime objects
**2015-08-14**
*Released 2.6.0*
- Depreciation of ``require_authentication`` and ``clearCredentials`` methods
- Giving ``AUTHORS`` the proper credits for their contribution for this project
- ``Message.date`` and ``Message.forward_date`` are now ``datetime`` objects
2015-08-12
Released 2.5.3
telegram.Bot now supports to be unpickled
**2015-08-12**
*Released 2.5.3*
- ``telegram.Bot`` now supports to be unpickled
2015-08-11
Released 2.5.2
New changes from Telegram Bot API have been applied
telegram.Bot now supports to be pickled
Return empty str instead None when message.text is empty
**2015-08-11**
*Released 2.5.2*
- New changes from Telegram Bot API have been applied
- ``telegram.Bot`` now supports to be pickled
- Return empty ``str`` instead ``None`` when ``message.text`` is empty
2015-08-10
Released 2.5.1
Moved from GPLv2 to LGPLv3
**2015-08-10**
*Released 2.5.1*
- Moved from GPLv2 to LGPLv3
2015-08-09
Released 2.5
Fixes logging calls in API
**2015-08-09**
*Released 2.5*
- Fixes logging calls in API
2015-08-08
Released 2.4
Fixes Emoji class for Python 3
PEP8 improvements
**2015-08-08**
*Released 2.4*
- Fixes ``Emoji`` class for Python 3
- ``PEP8`` improvements
2015-08-08
Released 2.3
Fixes ForceReply class
Remove logging.basicConfig from library
**2015-08-08**
*Released 2.3*
- Fixes ``ForceReply`` class
- Remove ``logging.basicConfig`` from library
2015-07-25
Released 2.2
Allows debug=True when initializing telegram.Bot
**2015-07-25**
*Released 2.2*
- Allows ``debug=True`` when initializing ``telegram.Bot``
2015-07-20
Released 2.1
Fix to_dict for Document and Video
**2015-07-20**
*Released 2.1*
- Fix ``to_dict`` for ``Document`` and ``Video``
2015-07-19
Released 2.0
Fixes bugs
Improves __str__ over to_json()
Creates abstractclass TelegramObject
**2015-07-19**
*Released 2.0*
- Fixes bugs
- Improves ``__str__`` over ``to_json()``
- Creates abstract class ``TelegramObject``
2015-07-15
Released 1.9
Python 3 officially supported
PEP8 improvements
**2015-07-15**
*Released 1.9*
- Python 3 officially supported
- ``PEP8`` improvements
2015-07-12
Released 1.8
Fixes crash when replying an unicode text message (special thanks to JRoot3D)
**2015-07-12**
*Released 1.8*
- Fixes crash when replying an unicode text message (special thanks to JRoot3D)
2015-07-11
Released 1.7
Fixes crash when username is not defined on chat (special thanks to JRoot3D)
**2015-07-11**
*Released 1.7*
- Fixes crash when ``username`` is not defined on ``chat`` (special thanks to JRoot3D)
2015-07-10
Released 1.6
Improvements for GAE support
**2015-07-10**
*Released 1.6*
- Improvements for GAE support
2015-07-10
Released 1.5
Fixes randomly unicode issues when using InputFile
**2015-07-10**
*Released 1.5*
- Fixes randomly unicode issues when using ``InputFile``
2015-07-10
Released 1.4
requests lib is no longer required
Google App Engine (GAE) is supported
**2015-07-10**
*Released 1.4*
- ``requests`` lib is no longer required
- Google App Engine (GAE) is supported
2015-07-10
Released 1.3
Added support to setWebhook (special thanks to macrojames)
**2015-07-10**
*Released 1.3*
- Added support to ``setWebhook`` (special thanks to macrojames)
2015-07-09
Released 1.2
CustomKeyboard classes now available
Emojis available
PEP8 improvements
**2015-07-09**
*Released 1.2*
- ``CustomKeyboard`` classes now available
- Emojis available
- ``PEP8`` improvements
2015-07-08
Released 1.1
PyPi package now available
**2015-07-08**
*Released 1.1*
- PyPi package now available
2015-07-08
Released 1.0
Initial checkin of python-telegram-bot
**2015-07-08**
*Released 1.0*
- Initial checkin of python-telegram-bot
+2 -2
View File
@@ -35,5 +35,5 @@ If you have any question or concerns, feel free to reach out to me.
.. _`Code of Conduct`: https://www.python.org/psf/codeofconduct/
.. _`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
.. _CHANGES: https://github.com/leandrotoledo/python-telegram-bot/blob/master/CHANGES
.. _AUTHORS.rst: https://github.com/leandrotoledo/python-telegram-bot/blob/master/AUTHORS.rst
.. _CHANGES: https://github.com/python-telegram-bot/python-telegram-bot/blob/master/CHANGES
.. _AUTHORS.rst: https://github.com/python-telegram-bot/python-telegram-bot/blob/master/AUTHORS.rst
+1 -1
View File
@@ -1 +1 @@
include LICENSE LICENSE.lesser Makefile
include LICENSE LICENSE.lesser Makefile requirements.txt
+87 -24
View File
@@ -1,11 +1,12 @@
Python Telegram Bot
.. image:: https://github.com/python-telegram-bot/logos/blob/master/logo-text/png/ptb-logo-text_768.png?raw=true
:align: center
:target: https://github.com/python-telegram-bot/logos
:alt: python-telegram-bot Logo
A Python wrapper around the Telegram Bot API.
*Stay tuned for library updates and new releases on our* `Telegram Channel <http://telegram.me/pythontelegrambotchannel>`_.
By `Leandro Toledo <leandrotoledodesouza@gmail.com>`_
.. image:: https://img.shields.io/pypi/v/python-telegram-bot.svg
:target: https://pypi.python.org/pypi/python-telegram-bot
:alt: PyPi Package Version
@@ -22,16 +23,16 @@ By `Leandro Toledo <leandrotoledodesouza@gmail.com>`_
:target: http://www.gnu.org/licenses/lgpl-3.0.html
:alt: LGPLv3 License
.. image:: https://travis-ci.org/leandrotoledo/python-telegram-bot.svg?branch=master
:target: https://travis-ci.org/leandrotoledo/python-telegram-bot
.. image:: https://travis-ci.org/python-telegram-bot/python-telegram-bot.svg?branch=master
:target: https://travis-ci.org/python-telegram-bot/python-telegram-bot
:alt: Travis CI Status
.. image:: https://codeclimate.com/github/leandrotoledo/python-telegram-bot/badges/gpa.svg
:target: https://codeclimate.com/github/leandrotoledo/python-telegram-bot
.. image:: https://codeclimate.com/github/python-telegram-bot/python-telegram-bot/badges/gpa.svg
:target: https://codeclimate.com/github/python-telegram-bot/python-telegram-bot
:alt: Code Climate
.. image:: https://coveralls.io/repos/leandrotoledo/python-telegram-bot/badge.svg?branch=master&service=github
:target: https://coveralls.io/github/leandrotoledo/python-telegram-bot?branch=master
.. 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
=================
@@ -52,13 +53,15 @@ Table of contents
- `Getting started`_
1. `API`_
1. `The Updater class`_
2. `API`_
3. `Logging`_
2. `Logging`_
4. `Examples`_
3. `Examples`_
4. `Documentation`_
5. `Documentation`_
- `License`_
@@ -131,11 +134,11 @@ Or upgrade to the latest version::
_`Getting the code`
===================
The code is hosted at https://github.com/leandrotoledo/python-telegram-bot
The code is hosted at https://github.com/python-telegram-bot/python-telegram-bot
Check out the latest development version anonymously with::
$ git clone https://github.com/leandrotoledo/python-telegram-bot
$ git clone https://github.com/python-telegram-bot/python-telegram-bot
$ cd python-telegram-bot
Run tests:
@@ -152,10 +155,72 @@ _`Getting started`
View the last release API documentation at: https://core.telegram.org/bots/api
------
_`The Updater class`
------
The ``Updater`` class is the new way to create bots with ``python-telegram-bot``. It provides an easy-to-use interface to the ``telegram.Bot`` by caring about getting new updates 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.
As with the old method, we'll need an Access Token. 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::
>>> from telegram import Updater
>>> updater = Updater(token='token')
For quicker access to the ``Dispatcher`` used by our ``Updater``, we can introduce it locally::
>>> dispatcher = updater.dispatcher
Now, we need to define a function that should process a specific type of update::
>>> 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, so we need to register it in the dispatcher::
>>> dispatcher.addTelegramCommandHandler('start', start)
The last step is to tell the ``Updater`` to start working::
>>> 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 function and register it::
>>> def echo(bot, update):
... bot.sendMessage(chat_id=update.message.chat_id, text=update.message.text)
...
>>> dispatcher.addTelegramMessageHandler(echo)
Our bot should now reply to all messages that are not a command with a message that has the same content.
People might try to send commands to the bot that it doesn't understand, so we should get that covered as well::
>>> def unknown(bot, update):
... bot.sendMessage(chat_id=update.message.chat_id, text="Sorry, I didn't understand that command.")
...
>>> dispatcher.addUnknownTelegramCommandHandler(unknown)
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 the command in the handler function simply by adding it to the parameter list::
>>> def caps(bot, update, args):
... text_caps = ' '.join(args).upper()
... bot.sendMessage(chat_id=update.message.chat_id, text=text_caps)
...
>>> dispatcher.addTelegramCommandHandler('caps', caps)
Now it's time to stop the bot::
>>> updater.stop()
Check out more examples in the `examples folder <https://github.com/python-telegram-bot/python-telegram-bot/tree/master/examples>`_!
------
_`API`
------
Note: Using the ``Bot`` class directly is the 'old' method, but some of this is still important information, even if you're using the ``Updater`` class!
The API is exposed via the ``telegram.Bot`` class.
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#botfather>`_).
@@ -253,14 +318,16 @@ _`Examples`
Here follows some examples to help you to get your own Bot up to speed:
- `echobot <https://github.com/leandrotoledo/python-telegram-bot/blob/master/examples/echobot.py>`_ replies back messages.
- `echobot <https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/echobot.py>`_ replies back messages.
- `roboed <https://github.com/leandrotoledo/python-telegram-bot/blob/master/examples/roboed.py>`_ talks to `Robô Ed <http://www.ed.conpet.gov.br/br/converse.php>`_.
- `roboed <https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/roboed.py>`_ talks to `Robô Ed <http://www.ed.conpet.gov.br/br/converse.php>`_.
- `Simple-Echo-Telegram-Bot <https://github.com/sooyhwang/Simple-Echo-Telegram-Bot>`_ simple Python Telegram bot that echoes your input with Flask microframework, setWebhook method, and Google App Engine (optional) - by @sooyhwang.
- `DevOps Reaction Bot <https://github.com/leandrotoledo/gae-devops-reaction-telegram-bot>`_ sends latest or random posts from `DevOps Reaction <http://devopsreactions.tumblr.com/>`_. Running on `Google App Engine <https://cloud.google.com/appengine>`_ (billing has to be enabled for fully Socket API support).
- `TwitterForwarderBot <https://github.com/franciscod/telegram-twitter-forwarder-bot>`_ forwards you tweets from people that you have subscribed to.
================
_`Documentation`
================
@@ -277,14 +344,10 @@ You may copy, distribute and modify the software provided that modifications are
_`Contact`
==========
Feel free to join to our `Telegram group <https://telegram.me/joinchat/ALnA-AJQm5SEwuAzar3nvw>`_.
If you face trouble joining in the group please ping me `via Telegram <https://telegram.me/leandrotoledo>`_, I'll be glad to add you.
Feel free to join to our `Telegram group <https://telegram.me/joinchat/ALnA-AJQm5R7Km9hdCgyng>`_.
=======
_`TODO`
=======
Patches and bug reports are `welcome <https://github.com/leandrotoledo/python-telegram-bot/issues/new>`_, just please keep the style consistent with the original source.
- Add commands handler.
Patches and bug reports are `welcome <https://github.com/python-telegram-bot/python-telegram-bot/issues/new>`_, just please keep the style consistent with the original source.
+2 -2
View File
@@ -58,9 +58,9 @@ author = u'Leandro Toledo'
# built documents.
#
# The short X.Y version.
version = '2.9'
version = '3.1'
# The full version, including alpha/beta/rc tags.
release = '2.9'
release = '3.1.0'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
@@ -1,7 +1,7 @@
telegram.groupchat module
telegram.chat module
=========================
.. automodule:: telegram.groupchat
.. automodule:: telegram.chat
:members:
:undoc-members:
:show-inheritance:
+7
View File
@@ -0,0 +1,7 @@
telegram.dispatcher module
=========================
.. automodule:: telegram.dispatcher
:members:
:undoc-members:
:show-inheritance:
+3 -1
View File
@@ -9,13 +9,15 @@ Submodules
telegram.audio
telegram.base
telegram.bot
telegram.updater
telegram.dispatcher
telegram.chataction
telegram.contact
telegram.document
telegram.emoji
telegram.error
telegram.forcereply
telegram.groupchat
telegram.chat
telegram.inputfile
telegram.location
telegram.message
+7
View File
@@ -0,0 +1,7 @@
telegram.updater module
=========================
.. automodule:: telegram.updater
:members:
:undoc-members:
:show-inheritance:
+40 -26
View File
@@ -1,6 +1,7 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Simple Bot to reply Telegram messages
# Simple Bot to reply to Telegram messages
# Copyright (C) 2015 Leandro Toledo de Souza <leandrotoeldodesouza@gmail.com>
#
# This program is free software: you can redistribute it and/or modify
@@ -19,47 +20,60 @@
import logging
import telegram
from time import sleep
LAST_UPDATE_ID = None
try:
from urllib.error import URLError
except ImportError:
from urllib2 import URLError # python 2
def main():
global LAST_UPDATE_ID
# Telegram Bot Authorization Token
bot = telegram.Bot('TOKEN')
# get the first pending update_id, this is so we can skip over it in case
# we get an "Unauthorized" exception.
try:
update_id = bot.getUpdates()[0].update_id
except IndexError:
update_id = None
logging.basicConfig(
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
# Telegram Bot Authorization Token
bot = telegram.Bot('TOKEN')
# This will be our global variable to keep the latest update_id when requesting
# for updates. It starts with the latest update_id if available.
try:
LAST_UPDATE_ID = bot.getUpdates()[-1].update_id
except IndexError:
LAST_UPDATE_ID = None
while True:
echo(bot)
try:
update_id = echo(bot, update_id)
except telegram.TelegramError as e:
# These are network problems with Telegram.
if e.message in ("Bad Gateway", "Timed out"):
sleep(1)
elif e.message == "Unauthorized":
# The user has removed or blocked the bot.
update_id += 1
else:
raise e
except URLError as e:
# These are network problems on our end.
sleep(1)
def echo(bot):
global LAST_UPDATE_ID
def echo(bot, update_id):
# Request updates after the last updated_id
for update in bot.getUpdates(offset=LAST_UPDATE_ID, timeout=10):
# chat_id is required to reply any message
# 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
reply_text = update.message.text
update_id = update.update_id + 1
message = update.message.text
if (reply_text):
# Reply the message
if message:
# Reply to the message
bot.sendMessage(chat_id=chat_id,
text=reply_text)
text=message)
# Updates global offset to get the new updates
LAST_UPDATE_ID = update.update_id + 1
return update_id
if __name__ == '__main__':
+165
View File
@@ -0,0 +1,165 @@
#!/usr/bin/env python
"""
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:
Basic Echobot example, repeats messages. Reply to last chat from the command
line by typing "/reply <text>"
Type 'stop' on the command line to stop the bot.
"""
from telegram import Updater
from telegram.dispatcher import run_async
from time import sleep
import logging
import sys
root = logging.getLogger()
root.setLevel(logging.INFO)
ch = logging.StreamHandler(sys.stdout)
ch.setLevel(logging.INFO)
formatter = \
logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
ch.setFormatter(formatter)
root.addHandler(ch)
last_chat_id = 0
logger = logging.getLogger(__name__)
# Command Handlers
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))
def unknown_command(bot, update):
""" Answer in Telegram """
bot.sendMessage(update.message.chat_id, text='Command not recognized!')
@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.
"""
sleep(2) # IO-heavy operation here
bot.sendMessage(update.message.chat_id, text='Echo: %s' %
update.message.text)
def error(bot, update, error):
""" Print error to console """
logger.warn('Update %s caused error %s' % (update, error))
def cli_reply(bot, update, args):
"""
For any update of type telegram.Update or str, 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.
"""
update_queue.put('/%s' % update)
def unknown_cli_command(bot, update):
logger.warn("Command not found: %s" % update)
def main():
# Create the EventHandler and pass it your bot's token.
token = 'token'
updater = Updater(token, workers=2)
# Get the dispatcher to register handlers
dp = updater.dispatcher
dp.addTelegramCommandHandler("start", start)
dp.addTelegramCommandHandler("help", help)
dp.addUnknownTelegramCommandHandler(unknown_command)
dp.addTelegramMessageHandler(message)
dp.addTelegramRegexHandler('.*', any_message)
dp.addStringCommandHandler('reply', cli_reply)
dp.addUnknownStringCommandHandler(unknown_cli_command)
dp.addStringRegexHandler('[^/].*', cli_noncommand)
dp.addErrorHandler(error)
# Start the Bot and store the update Queue, so we can insert updates
update_queue = updater.start_polling(poll_interval=0.1, timeout=20)
'''
# Alternatively, run with webhook:
updater.bot.setWebhook(webhook_url='https://example.com/%s' % token,
certificate=open('cert.pem', 'rb'))
update_queue = updater.start_webhook('0.0.0.0',
443,
url_path=token,
cert='cert.pem',
key='key.key')
# 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:
try:
text = raw_input()
except NameError:
text = input()
# Gracefully stop the event handler
if text == 'stop':
updater.stop()
break
# else, put the text into the update queue
elif len(text) > 0:
update_queue.put(text) # Put command into queue
if __name__ == '__main__':
main()
+75
View File
@@ -0,0 +1,75 @@
#!/usr/bin/env python
"""
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.
Usage:
Basic Echobot example, repeats messages.
Type 'stop' on the command line to stop the bot.
"""
from telegram import Updater
import logging
import sys
# Enable logging
root = logging.getLogger()
root.setLevel(logging.INFO)
ch = logging.StreamHandler(sys.stdout)
ch.setLevel(logging.DEBUG)
formatter = \
logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
ch.setFormatter(formatter)
root.addHandler(ch)
logger = logging.getLogger(__name__)
# Command Handlers
def start(bot, update):
bot.sendMessage(update.message.chat_id, text='Hi!')
def help(bot, update):
bot.sendMessage(update.message.chat_id, text='Help!')
def echo(bot, update):
bot.sendMessage(update.message.chat_id, text=update.message.text)
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
# on different commands - answer in Telegram
dp.addTelegramCommandHandler("start", start)
dp.addTelegramCommandHandler("help", help)
# on noncommand i.e message - echo the message on Telegram
dp.addTelegramMessageHandler(echo)
# on error - print error to stdout
dp.addErrorHandler(error)
# Start the Bot
updater.start_polling(timeout=5)
# Run the bot until the user presses Ctrl-C or the process receives SIGINT,
# SIGTERM or SIGABRT
updater.idle()
if __name__ == '__main__':
main()
+14 -2
View File
@@ -13,17 +13,29 @@ def read(*paths):
return f.read()
def requirements():
"""Build the requirements list for this project"""
requirements_list = []
with open('requirements.txt') as requirements:
for install in requirements:
requirements_list.append(install.strip())
return requirements_list
setup(
name='python-telegram-bot',
version='2.9',
version='3.1.0',
author='Leandro Toledo',
author_email='leandrotoledodesouza@gmail.com',
license='LGPLv3',
url='https://github.com/leandrotoledo/python-telegram-bot',
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',
+10 -8
View File
@@ -19,11 +19,11 @@
"""A library that provides a Python interface to the Telegram Bot API"""
__author__ = 'leandrotoledodesouza@gmail.com'
__version__ = '2.8.7'
__version__ = '3.1.0'
from .base import TelegramObject
from .user import User
from .groupchat import GroupChat
from .chat import Chat
from .photosize import PhotoSize
from .audio import Audio
from .voice import Voice
@@ -47,10 +47,12 @@ from .parsemode import ParseMode
from .message import Message
from .update import Update
from .bot import Bot
from .dispatcher import Dispatcher
from .updater import Updater
__all__ = ['Bot', 'Emoji', 'TelegramError', 'InputFile', 'ReplyMarkup',
'ForceReply', 'ReplyKeyboardHide', 'ReplyKeyboardMarkup',
'UserProfilePhotos', 'ChatAction', 'Location', 'Contact',
'Video', 'Sticker', 'Document', 'File', 'Audio', 'PhotoSize',
'GroupChat', 'Update', 'ParseMode', 'Message', 'User',
'TelegramObject', 'NullHandler', 'Voice']
__all__ = ['Bot', 'Updater', 'Dispatcher', 'Emoji', 'TelegramError',
'InputFile', 'ReplyMarkup', 'ForceReply', 'ReplyKeyboardHide',
'ReplyKeyboardMarkup', 'UserProfilePhotos', 'ChatAction',
'Location', 'Contact', 'Video', 'Sticker', 'Document', 'File',
'Audio', 'PhotoSize', 'Chat', 'Update', 'ParseMode', 'Message',
'User', 'TelegramObject', 'NullHandler', 'Voice']
+19 -14
View File
@@ -191,8 +191,7 @@ class Bot(TelegramObject):
Args:
chat_id:
Unique identifier for the message recipient - telegram.User or
telegram.GroupChat id.
Unique identifier for the message recipient - telegram.Chat id.
parse_mode:
Send Markdown, if you want Telegram apps to show bold, italic and
inline URLs in your bot's message. For the moment, only Telegram
@@ -234,10 +233,10 @@ class Bot(TelegramObject):
Args:
chat_id:
Unique identifier for the message recipient - User or GroupChat id.
Unique identifier for the message recipient - Chat id.
from_chat_id:
Unique identifier for the chat where the original message was sent
- User or GroupChat id.
- Chat id.
message_id:
Unique message identifier.
@@ -268,7 +267,7 @@ class Bot(TelegramObject):
Args:
chat_id:
Unique identifier for the message recipient - User or GroupChat id.
Unique identifier for the message recipient - Chat id.
photo:
Photo to send. You can either pass a file_id as String to resend a
photo that is already on the Telegram servers, or upload a new
@@ -319,7 +318,7 @@ class Bot(TelegramObject):
Args:
chat_id:
Unique identifier for the message recipient - User or GroupChat id.
Unique identifier for the message recipient - Chat id.
audio:
Audio file to send. You can either pass a file_id as String to
resend an audio that is already on the Telegram servers, or upload
@@ -366,7 +365,7 @@ class Bot(TelegramObject):
Args:
chat_id:
Unique identifier for the message recipient - User or GroupChat id.
Unique identifier for the message recipient - Chat id.
document:
File to send. You can either pass a file_id as String to resend a
file that is already on the Telegram servers, or upload a new file
@@ -405,7 +404,7 @@ class Bot(TelegramObject):
Args:
chat_id:
Unique identifier for the message recipient - User or GroupChat id.
Unique identifier for the message recipient - Chat id.
sticker:
Sticker to send. You can either pass a file_id as String to resend
a sticker that is already on the Telegram servers, or upload a new
@@ -441,7 +440,7 @@ class Bot(TelegramObject):
Args:
chat_id:
Unique identifier for the message recipient - User or GroupChat id.
Unique identifier for the message recipient - Chat id.
video:
Video to send. You can either pass a file_id as String to resend a
video that is already on the Telegram servers, or upload a new
@@ -490,7 +489,7 @@ class Bot(TelegramObject):
Args:
chat_id:
Unique identifier for the message recipient - User or GroupChat id.
Unique identifier for the message recipient - Chat id.
voice:
Audio file to send. You can either pass a file_id as String to
resend an audio that is already on the Telegram servers, or upload
@@ -529,7 +528,7 @@ class Bot(TelegramObject):
Args:
chat_id:
Unique identifier for the message recipient - User or GroupChat id.
Unique identifier for the message recipient - Chat id.
latitude:
Latitude of location.
longitude:
@@ -565,7 +564,7 @@ class Bot(TelegramObject):
Args:
chat_id:
Unique identifier for the message recipient - User or GroupChat id.
Unique identifier for the message recipient - Chat id.
action:
Type of action to broadcast. Choose one, depending on what the user
is about to receive:
@@ -649,7 +648,8 @@ class Bot(TelegramObject):
def getUpdates(self,
offset=None,
limit=100,
timeout=0):
timeout=0,
network_delay=2.):
"""Use this method to receive incoming updates using long polling.
Args:
@@ -665,6 +665,11 @@ class Bot(TelegramObject):
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.
Returns:
A list of telegram.Update objects are returned.
@@ -680,7 +685,7 @@ class Bot(TelegramObject):
if timeout:
data['timeout'] = timeout
result = request.post(url, data)
result = request.post(url, data, network_delay=network_delay)
if result:
self.logger.info(
+18 -12
View File
@@ -17,22 +17,25 @@
# 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 GroupChat"""
"""This module contains a object that represents a Telegram Chat"""
from telegram import TelegramObject
class GroupChat(TelegramObject):
"""This object represents a Telegram GroupChat.
class Chat(TelegramObject):
"""This object represents a Telegram Chat.
Attributes:
id (int):
title (str):
type (str):
type (str): Can be 'private', 'group', 'supergroup' or 'channel'
title (str): Title, for channels and group chats
username (str): Username, for private chats and channels if available
first_name (str): First name of the other party in a private chat
last_name (str): Last name of the other party in a private chat
Args:
id (int):
title (str):
type (str):
**kwargs: Arbitrary keyword arguments.
Keyword Args:
@@ -41,24 +44,27 @@ class GroupChat(TelegramObject):
def __init__(self,
id,
title,
type,
**kwargs):
# Required
self.id = int(id)
self.title = title
self.type = type
# Optionals
self.type = kwargs.get('type', '')
self.title = kwargs.get('title', '')
self.username = kwargs.get('username', '')
self.first_name = kwargs.get('first_name', '')
self.last_name = kwargs.get('last_name', '')
@staticmethod
def de_json(data):
"""
Args:
data (str):
data (dict):
Returns:
telegram.GroupChat:
telegram.Chat:
"""
if not data:
return None
return GroupChat(**data)
return Chat(**data)
+600
View File
@@ -0,0 +1,600 @@
#!/usr/bin/env python
"""
This module contains the Dispatcher class.
"""
import logging
from functools import wraps
from inspect import getargspec
from threading import Thread, BoundedSemaphore, Lock
from re import match
from telegram import (TelegramError, Update, NullHandler)
H = NullHandler()
logging.getLogger(__name__).addHandler(H)
semaphore = None
running_async = 0
async_lock = Lock()
def run_async(func):
"""
Function decorator that will run the function in a new thread.
Args:
func (function): The function to run in the thread.
Returns:
function:
"""
@wraps(func)
def pooled(*args, **kwargs):
"""
A wrapper to run a thread in a thread pool
"""
global running_async, async_lock
result = func(*args, **kwargs)
semaphore.release()
with async_lock:
running_async -= 1
return result
@wraps(func)
def async_func(*args, **kwargs):
"""
A wrapper to run a function in a thread
"""
global running_async, async_lock
thread = Thread(target=pooled, args=args, kwargs=kwargs)
semaphore.acquire()
with async_lock:
running_async += 1
thread.start()
return thread
return async_func
class Dispatcher:
"""
This class dispatches all kinds of updates to its registered handlers.
A handler is a function that usually takes the following parameters
bot:
The telegram.Bot instance that received the message
update:
The update that should be handled by the handler
Error handlers take an additional parameter
error:
The TelegramError instance that was raised during processing the
update
All handlers, except error handlers, can also request more information by
appending one or more of the following arguments in their argument list for
convenience
update_queue:
The Queue instance which contains all new updates and is
processed by the Dispatcher. Be careful with this - you might
create an infinite loop.
args:
If the update is an instance str or telegram.Update, this will be
a list that contains the content of the message split on spaces,
except the first word (usually the command).
Example: '/add item1 item2 item3' -> ['item1', 'item2', 'item3']
For other updates, args will be None
Attributes:
Args:
bot (telegram.Bot): The bot object that should be passed to the
handlers update_queue (queue.Queue): The synchronized queue that will
contain the updates.
"""
def __init__(self, bot, update_queue, workers=4):
self.bot = bot
self.update_queue = update_queue
self.telegram_message_handlers = []
self.telegram_command_handlers = {}
self.telegram_regex_handlers = {}
self.string_regex_handlers = {}
self.string_command_handlers = {}
self.type_handlers = {}
self.unknown_telegram_command_handlers = []
self.unknown_string_command_handlers = []
self.error_handlers = []
self.logger = logging.getLogger(__name__)
self.running = False
global semaphore
if not semaphore:
semaphore = BoundedSemaphore(value=workers)
else:
self.logger.info("Semaphore already initialized, skipping.")
class _Stop(object):
"""
A class which objects can be passed into the update queue to stop the
thread
"""
pass
def start(self):
"""
Thread target of thread 'dispatcher'. Runs in background and processes
the update queue.
"""
self.running = True
self.logger.info('Dispatcher thread started')
while True:
update = None
try:
# Pop update from update queue.
# Blocks if no updates are available.
update = self.update_queue.get()
if type(update) is self._Stop:
break
self.processUpdate(update)
self.logger.debug('Processed Update: %s' % update)
# Dispatch any errors
except TelegramError as te:
self.logger.warn("Error was raised while processing Update.")
self.dispatchError(update, te)
self.logger.info('Dispatcher thread stopped')
def stop(self):
"""
Stops the thread
"""
if self.running:
self.running = False
self.update_queue.put(self._Stop())
def processUpdate(self, update):
"""
Processes a single update.
Args:
update (any):
"""
handled = False
# Custom type handlers
for t in self.type_handlers:
if isinstance(update, t):
self.dispatchType(update)
handled = True
# string update
if type(update) is str and update.startswith('/'):
self.dispatchStringCommand(update)
handled = True
elif type(update) is str:
self.dispatchStringRegex(update)
handled = True
# An error happened while polling
if isinstance(update, TelegramError):
self.dispatchError(None, update)
handled = True
# Telegram update (regex)
if isinstance(update, Update):
self.dispatchTelegramRegex(update)
handled = True
# Telegram update (command)
if isinstance(update, Update) \
and update.message.text.startswith('/'):
self.dispatchTelegramCommand(update)
handled = True
# Telegram update (message)
elif isinstance(update, Update):
self.dispatchTelegramMessage(update)
handled = True
# Update not recognized
if not handled:
self.dispatchError(update, TelegramError(
"Received update of unknown type %s" % type(update)))
# Add Handlers
def addTelegramMessageHandler(self, handler):
"""
Registers a message handler in the Dispatcher.
Args:
handler (function): A function that takes (Bot, Update, *args) as
arguments.
"""
self.telegram_message_handlers.append(handler)
def addTelegramCommandHandler(self, command, handler):
"""
Registers a command handler in the Dispatcher.
Args:
command (str): The command keyword that this handler should be
listening to.
handler (function): A function that takes (Bot, Update, *args) as
arguments.
"""
if command not in self.telegram_command_handlers:
self.telegram_command_handlers[command] = []
self.telegram_command_handlers[command].append(handler)
def addTelegramRegexHandler(self, matcher, handler):
"""
Registers a regex handler in the Dispatcher. If handlers will be
called if re.match(matcher, update.message.text) is True.
Args:
matcher (str/__Regex): A regex string or compiled regex object that
matches on messages that handler should be listening to
handler (function): A function that takes (Bot, Update, *args) as
arguments.
"""
if matcher not in self.telegram_regex_handlers:
self.telegram_regex_handlers[matcher] = []
self.telegram_regex_handlers[matcher].append(handler)
def addStringCommandHandler(self, command, handler):
"""
Registers a string-command handler in the Dispatcher.
Args:
command (str): The command keyword that this handler should be
listening to.
handler (function): A function that takes (Bot, str, *args) as
arguments.
"""
if command not in self.string_command_handlers:
self.string_command_handlers[command] = []
self.string_command_handlers[command].append(handler)
def addStringRegexHandler(self, matcher, handler):
"""
Registers a regex handler in the Dispatcher. If handlers will be
called if re.match(matcher, string) is True.
Args:
matcher (str/__Regex): A regex string or compiled regex object that
matches on the string input that handler should be listening to
handler (function): A function that takes (Bot, Update, *args) as
arguments.
"""
if matcher not in self.string_regex_handlers:
self.string_regex_handlers[matcher] = []
self.string_regex_handlers[matcher].append(handler)
def addUnknownTelegramCommandHandler(self, handler):
"""
Registers a command handler in the Dispatcher, that will receive all
commands that have no associated handler.
Args:
handler (function): A function that takes (Bot, Update, *args) as
arguments.
"""
self.unknown_telegram_command_handlers.append(handler)
def addUnknownStringCommandHandler(self, handler):
"""
Registers a string-command handler in the Dispatcher, that will
receive all commands that have no associated handler.
Args:
handler (function): A function that takes (Bot, str, *args) as
arguments.
"""
self.unknown_string_command_handlers.append(handler)
def addErrorHandler(self, handler):
"""
Registers an error handler in the Dispatcher.
Args:
handler (function): A function that takes (Bot, TelegramError) as
arguments.
"""
self.error_handlers.append(handler)
def addTypeHandler(self, the_type, handler):
"""
Registers a type handler in the Dispatcher. This allows you to send
any type of object into the update queue.
Args:
the_type (type): The type this handler should listen to
handler (function): A function that takes (Bot, type, *args) as
arguments.
"""
if the_type not in self.type_handlers:
self.type_handlers[the_type] = []
self.type_handlers[the_type].append(handler)
# Remove Handlers
def removeTelegramMessageHandler(self, handler):
"""
De-registers a message handler.
Args:
handler (any):
"""
if handler in self.telegram_message_handlers:
self.telegram_message_handlers.remove(handler)
def removeTelegramCommandHandler(self, command, handler):
"""
De-registers a command handler.
Args:
command (str): The command
handler (any):
"""
if command in self.telegram_command_handlers \
and handler in self.telegram_command_handlers[command]:
self.telegram_command_handlers[command].remove(handler)
def removeTelegramRegexHandler(self, matcher, handler):
"""
De-registers a regex handler.
Args:
matcher (str/__Regex): The regex matcher object or string
handler (any):
"""
if matcher in self.telegram_regex_handlers \
and handler in self.telegram_regex_handlers[matcher]:
self.telegram_regex_handlers[matcher].remove(handler)
def removeStringCommandHandler(self, command, handler):
"""
De-registers a string-command handler.
Args:
command (str): The command
handler (any):
"""
if command in self.string_command_handlers \
and handler in self.string_command_handlers[command]:
self.string_command_handlers[command].remove(handler)
def removeStringRegexHandler(self, matcher, handler):
"""
De-registers a regex handler.
Args:
matcher (str/__Regex): The regex matcher object or string
handler (any):
"""
if matcher in self.string_regex_handlers \
and handler in self.string_regex_handlers[matcher]:
self.string_regex_handlers[matcher].remove(handler)
def removeUnknownTelegramCommandHandler(self, handler):
"""
De-registers an unknown-command handler.
Args:
handler (any):
"""
if handler in self.unknown_telegram_command_handlers:
self.unknown_telegram_command_handlers.remove(handler)
def removeUnknownStringCommandHandler(self, handler):
"""
De-registers an unknown-command handler.
Args:
handler (any):
"""
if handler in self.unknown_string_command_handlers:
self.unknown_string_command_handlers.remove(handler)
def removeErrorHandler(self, handler):
"""
De-registers an error handler.
Args:
handler (any):
"""
if handler in self.error_handlers:
self.error_handlers.remove(handler)
def removeTypeHandler(self, the_type, handler):
"""
De-registers a type handler.
Args:
handler (any):
"""
if the_type in self.type_handlers \
and handler in self.type_handlers[the_type]:
self.type_handlers[the_type].remove(handler)
def dispatchTelegramCommand(self, update):
"""
Dispatches an update that contains a command.
Args:
command (str): The command keyword
update (telegram.Update): The Telegram update that contains the
command
"""
command = update.message.text.split(' ')[0][1:].split('@')[0]
if command in self.telegram_command_handlers:
self.dispatchTo(self.telegram_command_handlers[command], update)
else:
self.dispatchTo(self.unknown_telegram_command_handlers, update)
def dispatchTelegramRegex(self, update):
"""
Dispatches an update to all regex handlers that match the message
string.
Args:
command (str): The command keyword
update (telegram.Update): The Telegram update that contains the
command
"""
matching_handlers = []
for matcher in self.telegram_regex_handlers:
if match(matcher, update.message.text):
for handler in self.telegram_regex_handlers[matcher]:
matching_handlers.append(handler)
self.dispatchTo(matching_handlers, update)
def dispatchStringCommand(self, update):
"""
Dispatches a string-update that contains a command.
Args:
update (str): The string input
"""
command = update.split(' ')[0][1:]
if command in self.string_command_handlers:
self.dispatchTo(self.string_command_handlers[command], update)
else:
self.dispatchTo(self.unknown_string_command_handlers, update)
def dispatchStringRegex(self, update):
"""
Dispatches an update to all string regex handlers that match the
string.
Args:
command (str): The command keyword
update (telegram.Update): The Telegram update that contains the
command
"""
matching_handlers = []
for matcher in self.string_regex_handlers:
if match(matcher, update):
for handler in self.string_regex_handlers[matcher]:
matching_handlers.append(handler)
self.dispatchTo(matching_handlers, update)
def dispatchType(self, update):
"""
Dispatches an update of any type.
Args:
update (any): The update
"""
for t in self.type_handlers:
if isinstance(update, t):
self.dispatchTo(self.type_handlers[t], update)
else:
self.dispatchError(update, TelegramError(
"Received update of unknown type %s" % type(update)))
def dispatchTelegramMessage(self, update):
"""
Dispatches an update that contains a regular message.
Args:
update (telegram.Update): The Telegram update that contains the
message.
"""
self.dispatchTo(self.telegram_message_handlers, update)
def dispatchError(self, update, error):
"""
Dispatches an error.
Args:
update (any): The pdate that caused the error
error (telegram.TelegramError): The Telegram error that was raised.
"""
for handler in self.error_handlers:
handler(self.bot, update, error)
def dispatchTo(self, handlers, update):
"""
Dispatches an update to a list of handlers.
Args:
handlers (list): A list of handler-functions.
update (any): The update to be dispatched
"""
for handler in handlers:
self.call_handler(handler, update)
def call_handler(self, handler, update):
"""
Calls an update handler. Checks the handler for keyword arguments and
fills them, if possible.
Args:
handler (function): An update handler function
update (any): An update
"""
kwargs = {}
fargs = getargspec(handler).args
if 'update_queue' in fargs:
kwargs['update_queue'] = self.update_queue
if 'args' in fargs:
if isinstance(update, Update):
args = update.message.text.split(' ')[1:]
elif isinstance(update, str):
args = update.split(' ')[1:]
else:
args = None
kwargs['args'] = args
handler(self.bot, update, **kwargs)
+1 -1
View File
@@ -35,7 +35,7 @@ from telegram import TelegramError
DEFAULT_MIME_TYPE = 'application/octet-stream'
USER_AGENT = 'Python Telegram Bot' \
' (https://github.com/leandrotoledo/python-telegram-bot)'
' (https://github.com/python-telegram-bot/python-telegram-bot)'
class InputFile(object):
+10 -7
View File
@@ -22,7 +22,7 @@
from datetime import datetime
from time import mktime
from telegram import (Audio, Contact, Document, GroupChat, Location, PhotoSize,
from telegram import (Audio, Contact, Document, Chat, Location, PhotoSize,
Sticker, TelegramObject, User, Video, Voice)
@@ -60,7 +60,7 @@ class Message(TelegramObject):
message_id (int):
from_user (:class:`telegram.User`):
date (:class:`datetime.datetime`):
chat (:class:`telegram.User` or :class:`telegram.GroupChat`):
chat (:class:`telegram.User` or :class:`telegram.Chat`):
**kwargs: Arbitrary keyword arguments.
Keyword Args:
@@ -116,6 +116,12 @@ class Message(TelegramObject):
self.new_chat_photo = kwargs.get('new_chat_photo')
self.delete_chat_photo = bool(kwargs.get('delete_chat_photo', False))
self.group_chat_created = bool(kwargs.get('group_chat_created', False))
self.supergroup_chat_created = bool(kwargs.get(
'supergroup_chat_created', False))
self.migrate_to_chat_id = int(kwargs.get('migrate_to_chat_id', 0))
self.migrate_from_chat_id = int(kwargs.get('migrate_from_chat_id', 0))
self.channel_chat_created = bool(kwargs.get('channel_chat_created',
False))
@property
def chat_id(self):
@@ -126,7 +132,7 @@ class Message(TelegramObject):
def de_json(data):
"""
Args:
data (str):
data (dict):
Returns:
telegram.Message:
@@ -136,10 +142,7 @@ class Message(TelegramObject):
data['from_user'] = User.de_json(data.get('from'))
data['date'] = datetime.fromtimestamp(data['date'])
if 'first_name' in data.get('chat', ''):
data['chat'] = User.de_json(data.get('chat'))
elif 'title' in data.get('chat', ''):
data['chat'] = GroupChat.de_json(data.get('chat'))
data['chat'] = Chat.de_json(data.get('chat'))
data['forward_from'] = \
User.de_json(data.get('forward_from'))
data['forward_date'] = \
+261
View File
@@ -0,0 +1,261 @@
#!/usr/bin/env python
"""
This module contains the class Updater, which tries to make creating Telegram
Bots intuitive!
"""
import logging
import os
import ssl
from threading import Thread
from time import sleep
import subprocess
from signal import signal, SIGINT, SIGTERM, SIGABRT
from telegram import (Bot, TelegramError, dispatcher, Dispatcher,
NullHandler)
from telegram.utils.webhookhandler import (WebhookServer, WebhookHandler)
# Adjust for differences in Python versions
try:
from Queue import Queue
except ImportError:
from queue import Queue
try:
from urllib2 import URLError
except ImportError:
from urllib.error import URLError
H = NullHandler()
logging.getLogger(__name__).addHandler(H)
class Updater:
"""
This class, which employs the Dispatcher class, provides a frontend to
telegram.Bot to the programmer, so they can focus on coding the bot. It's
purpose is to receive the updates from Telegram and to deliver them to said
dispatcher. It also runs in a separate thread, so the user can interact
with the bot, for example on the command line. The dispatcher supports
handlers for different kinds of data: Updates from Telegram, basic text
commands and even arbitrary types.
The updater can be started as a polling service or, for production, use a
webhook to receive updates. This is achieved using the WebhookServer and
WebhookHandler classes.
Attributes:
Args:
token (str): The bots token given by the @BotFather
base_url (Optional[str]):
workers (Optional[int]): Amount of threads in the thread pool for
functions decorated with @run_async
"""
def __init__(self, token, base_url=None, workers=4):
self.bot = Bot(token, base_url)
self.update_queue = Queue()
self.dispatcher = Dispatcher(self.bot, self.update_queue,
workers=workers)
self.last_update_id = 0
self.logger = logging.getLogger(__name__)
self.running = False
self.is_idle = False
self.httpd = None
def start_polling(self, poll_interval=1.0, timeout=10, network_delay=2):
"""
Starts polling updates from Telegram.
Args:
poll_interval (Optional[float]): Time to wait between polling
updates from Telegram in seconds. Default is 1.0
timeout (Optional[float]): Passed to Bot.getUpdates
network_delay (Optional[float]): Passed to Bot.getUpdates
Returns:
Queue: The update queue that can be filled from the main thread
"""
# Create Thread objects
dispatcher_thread = Thread(target=self.dispatcher.start,
name="dispatcher")
event_handler_thread = Thread(target=self._start_polling,
name="updater",
args=(poll_interval, timeout,
network_delay))
self.running = True
# Start threads
dispatcher_thread.start()
event_handler_thread.start()
# Return the update queue so the main thread can insert updates
return self.update_queue
def start_webhook(self,
listen='127.0.0.1',
port=80,
url_path='',
cert=None,
key=None):
"""
Starts a small http server to listen for updates via webhook. If cert
and key are not provided, the webhook will be started directly on
http://listen:port/url_path, so SSL can be handled by another
application. Else, the webhook will be started on
https://listen:port/url_path
Args:
listen (Optional[str]): IP-Address to listen on
port (Optional[int]): Port the bot should be listening on
url_path (Optional[str]): Path inside url
cert (Optional[str]): Path to the SSL certificate file
key (Optional[str]): Path to the SSL key file
Returns:
Queue: The update queue that can be filled from the main thread
"""
# Create Thread objects
dispatcher_thread = Thread(target=self.dispatcher.start,
name="dispatcher")
event_handler_thread = Thread(target=self._start_webhook,
name="updater",
args=(listen, port, url_path, cert, key))
self.running = True
# Start threads
dispatcher_thread.start()
event_handler_thread.start()
# Return the update queue so the main thread can insert updates
return self.update_queue
def _start_polling(self, poll_interval, timeout, network_delay):
"""
Thread target of thread 'updater'. Runs in background, pulls
updates from Telegram and inserts them in the update queue of the
Dispatcher.
"""
current_interval = poll_interval
self.logger.info('Updater thread started')
# Remove webhook
self.bot.setWebhook(webhook_url=None)
while self.running:
try:
updates = self.bot.getUpdates(self.last_update_id,
timeout=timeout,
network_delay=network_delay)
if not self.running:
if len(updates) > 0:
self.logger.info('Updates ignored and will be pulled '
'again on restart.')
break
for update in updates:
self.update_queue.put(update)
self.last_update_id = update.update_id + 1
current_interval = poll_interval
sleep(current_interval)
except TelegramError as te:
# Put the error into the update queue and let the Dispatcher
# broadcast it
self.update_queue.put(te)
sleep(current_interval)
except URLError as e:
self.logger.error("Error while getting Updates: %s" % e)
# increase waiting times on subsequent errors up to 30secs
if current_interval < 30:
current_interval += current_interval / 2
if current_interval > 30:
current_interval = 30
self.logger.info('Updater thread stopped')
def _start_webhook(self, listen, port, url_path, cert, key):
self.logger.info('Updater thread started')
use_ssl = cert is not None and key is not None
url_path = "/%s" % url_path
# Create and start server
self.httpd = WebhookServer((listen, port), WebhookHandler,
self.update_queue, url_path)
if use_ssl:
# Check SSL-Certificate with openssl, if possible
try:
exit_code = subprocess.call(["openssl", "x509", "-text",
"-noout", "-in", cert],
stdout=open(os.devnull, 'wb'),
stderr=subprocess.STDOUT)
except OSError:
exit_code = 0
if exit_code is 0:
try:
self.httpd.socket = ssl.wrap_socket(self.httpd.socket,
certfile=cert,
keyfile=key,
server_side=True)
except ssl.SSLError as error:
raise TelegramError(str(error))
else:
raise TelegramError('SSL Certificate invalid')
self.httpd.serve_forever(poll_interval=1)
self.logger.info('Updater thread stopped')
def stop(self):
"""
Stops the polling/webhook thread and the dispatcher
"""
self.logger.info('Stopping Updater and Dispatcher...')
self.logger.debug('This might take a long time if you set a high value'
' as polling timeout.')
self.running = False
if self.httpd:
self.logger.info(
'Waiting for current webhook connection to be closed... '
'Send a Telegram message to the bot to exit immediately.')
self.httpd.shutdown()
self.httpd = None
self.logger.debug("Requesting Dispatcher to stop...")
self.dispatcher.stop()
while dispatcher.running_async > 0:
sleep(1)
self.logger.debug("Dispatcher stopped.")
def signal_handler(self, signum, frame):
self.is_idle = False
self.stop()
def idle(self, stop_signals=(SIGINT, SIGTERM, SIGABRT)):
"""
Waits for the user to press Ctrl-C and stops the updater
Args:
stop_signals: Iterable containing signals from the signal module
that should be subscribed to. Updater.stop() will be called on
receiving one of those signals. Defaults to (SIGINT, SIGTERM,
SIGABRT)
"""
for sig in stop_signals:
signal(sig, self.signal_handler)
self.is_idle = True
while self.is_idle:
sleep(1)
+17 -8
View File
@@ -20,6 +20,7 @@
"""This module contains methods to make POST and GET requests"""
import json
import socket
from ssl import SSLError
try:
@@ -67,22 +68,26 @@ def get(url):
def post(url,
data):
data,
network_delay=2.):
"""Request an URL.
Args:
url:
The web location we want to retrieve.
data:
A dict of (str, unicode) key/value pairs.
network_delay:
Additional timeout in seconds to allow the response from Telegram to
take some time.
Returns:
A JSON object.
"""
# Add one second to the timeout of urlopen to allow data to be transferred
# over the network.
# Add time to the timeout of urlopen to allow data to be transferred over
# the network.
if 'timeout' in data:
timeout = data['timeout'] + 1.
timeout = data['timeout'] + network_delay
else:
timeout = None
@@ -105,13 +110,17 @@ def post(url,
if error.getcode() == 502:
raise TelegramError('Bad Gateway')
message = _parse(error.read())
try:
message = _parse(error.read())
except ValueError:
message = 'Unknown HTTPError'
raise TelegramError(message)
except SSLError as error:
if "The read operation timed out" == error.message:
except (SSLError, socket.timeout) as error:
if "operation timed out" in str(error):
raise TelegramError("Timed out")
raise TelegramError(error.message)
raise TelegramError(str(error))
return _parse(result)
+85
View File
@@ -0,0 +1,85 @@
import logging
from telegram import Update, NullHandler
from future.utils import bytes_to_native_str as n
from threading import Lock
import json
try:
import BaseHTTPServer
except ImportError:
import http.server as BaseHTTPServer
H = NullHandler()
logging.getLogger(__name__).addHandler(H)
class WebhookServer(BaseHTTPServer.HTTPServer, object):
def __init__(self, server_address, RequestHandlerClass, update_queue,
webhook_path):
super(WebhookServer, self).__init__(server_address,
RequestHandlerClass)
self.logger = logging.getLogger(__name__)
self.update_queue = update_queue
self.webhook_path = webhook_path
self.is_running = False
self.server_lock = Lock()
self.shutdown_lock = Lock()
def serve_forever(self, poll_interval=0.5):
with self.server_lock:
self.is_running = True
self.logger.info("Webhook Server started.")
super(WebhookServer, self).serve_forever(poll_interval)
self.logger.info("Webhook Server stopped.")
def shutdown(self):
with self.shutdown_lock:
if not self.is_running:
self.logger.warn("Webhook Server already stopped.")
return
else:
super(WebhookServer, self).shutdown()
self.is_running = False
# WebhookHandler, process webhook calls
# Based on: https://github.com/eternnoir/pyTelegramBotAPI/blob/master/
# examples/webhook_examples/webhook_cpython_echo_bot.py
class WebhookHandler(BaseHTTPServer.BaseHTTPRequestHandler, object):
server_version = "WebhookHandler/1.0"
def __init__(self, request, client_address, server):
self.logger = logging.getLogger(__name__)
super(WebhookHandler, self).__init__(request, client_address, server)
def do_HEAD(self):
self.send_response(200)
self.end_headers()
def do_GET(self):
self.send_response(200)
self.end_headers()
def do_POST(self):
self.logger.debug("Webhook triggered")
if self.path == self.server.webhook_path and \
'content-type' in self.headers and \
'content-length' in self.headers and \
self.headers['content-type'] == 'application/json':
json_string = \
n(self.rfile.read(int(self.headers['content-length'])))
self.send_response(200)
self.end_headers()
self.logger.debug("Webhook received data: " + json_string)
update = Update.de_json(json.loads(json_string))
self.logger.info("Received Update with ID %d on Webhook" %
update.update_id)
self.server.update_queue.put(update)
else:
self.send_error(403)
self.end_headers()
+1 -1
View File
@@ -135,7 +135,7 @@ class BotTest(BaseTest, unittest.TestCase):
upf = self._bot.getUserProfilePhotos(user_id=self._chat_id)
self.assertTrue(self.is_json(upf.to_json()))
self.assertEqual(upf.photos[0][0].file_size, 6547)
self.assertEqual(upf.photos[0][0].file_size, 12421)
if __name__ == '__main__':
unittest.main()
+15 -15
View File
@@ -16,7 +16,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains a object that represents Tests for Telegram GroupChat"""
"""This module contains a object that represents Tests for Telegram Chat"""
import os
import unittest
@@ -27,8 +27,8 @@ import telegram
from tests.base import BaseTest
class GroupChatTest(BaseTest, unittest.TestCase):
"""This object represents Tests for Telegram GroupChat."""
class ChatTest(BaseTest, unittest.TestCase):
"""This object represents Tests for Telegram Chat."""
def setUp(self):
self.id = -28767330
@@ -42,36 +42,36 @@ class GroupChatTest(BaseTest, unittest.TestCase):
}
def test_group_chat_de_json_empty_json(self):
"""Test GroupChat.de_json() method"""
print('Testing GroupChat.de_json() - Empty JSON')
"""Test Chat.de_json() method"""
print('Testing Chat.de_json() - Empty JSON')
group_chat = telegram.GroupChat.de_json({})
group_chat = telegram.Chat.de_json({})
self.assertEqual(group_chat, None)
def test_group_chat_de_json(self):
"""Test GroupChat.de_json() method"""
print('Testing GroupChat.de_json()')
"""Test Chat.de_json() method"""
print('Testing Chat.de_json()')
group_chat = telegram.GroupChat.de_json(self.json_dict)
group_chat = telegram.Chat.de_json(self.json_dict)
self.assertEqual(group_chat.id, self.id)
self.assertEqual(group_chat.title, self.title)
self.assertEqual(group_chat.type, self.type)
def test_group_chat_to_json(self):
"""Test GroupChat.to_json() method"""
print('Testing GroupChat.to_json()')
"""Test Chat.to_json() method"""
print('Testing Chat.to_json()')
group_chat = telegram.GroupChat.de_json(self.json_dict)
group_chat = telegram.Chat.de_json(self.json_dict)
self.assertTrue(self.is_json(group_chat.to_json()))
def test_group_chat_to_dict(self):
"""Test GroupChat.to_dict() method"""
print('Testing GroupChat.to_dict()')
"""Test Chat.to_dict() method"""
print('Testing Chat.to_dict()')
group_chat = telegram.GroupChat.de_json(self.json_dict)
group_chat = telegram.Chat.de_json(self.json_dict)
self.assertTrue(self.is_dict(group_chat.to_dict()))
self.assertEqual(group_chat['id'], self.id)
+1
View File
@@ -38,6 +38,7 @@ class UpdateTest(BaseTest, unittest.TestCase):
'last_name': "S.",
'username': "leandrotoledo"},
'chat': {'id': 12173560,
'type': 'private',
'first_name': "Leandro",
'last_name': "S.",
'username': "leandrotoledo"},
+496
View File
@@ -0,0 +1,496 @@
#!/usr/bin/env python
# encoding: utf-8
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015 Leandro Toledo de Souza <leandrotoeldodesouza@gmail.com>
#
# 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 Updater, Dispatcher,
WebhookServer and WebhookHandler
"""
import logging
import unittest
import sys
import re
import os
import signal
from random import randrange
from time import sleep
from datetime import datetime
try:
from urllib2 import urlopen, Request
except ImportError:
from urllib.request import Request, urlopen
sys.path.append('.')
from telegram import Update, Message, TelegramError, User, Chat, Updater
from telegram.dispatcher import run_async
from tests.base import BaseTest
from threading import Lock, Thread
# Enable logging
root = logging.getLogger()
root.setLevel(logging.INFO)
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 UpdaterTest(BaseTest, unittest.TestCase):
"""
This object represents Tests for Updater, Dispatcher, WebhookServer and
WebhookHandler
"""
def setUp(self):
self.updater = Updater('', workers=2)
self.received_message = None
self.message_count = 0
self.lock = Lock()
def tearDown(self):
self.updater.stop()
def reset(self):
self.message_count = 0
self.received_message = None
def telegramHandlerTest(self, bot, update):
self.received_message = update.message.text
self.message_count += 1
@run_async
def asyncHandlerTest(self, bot, update):
sleep(1)
with self.lock:
self.received_message = update.message.text
self.message_count += 1
def stringHandlerTest(self, bot, update):
self.received_message = update
self.message_count += 1
def additionalArgsTest(self, bot, update, update_queue, args):
self.received_message = update
self.message_count += 1
if args[0] == 'resend':
update_queue.put('/test5 noresend')
elif args[0] == 'noresend':
pass
def errorRaisingHandlerTest(self, bot, update):
raise TelegramError(update)
def errorHandlerTest(self, bot, update, error):
self.received_message = error.message
self.message_count += 1
def test_addRemoveTelegramMessageHandler(self):
print('Testing add/removeTelegramMessageHandler')
bot = MockBot('Test')
self.updater.bot = bot
d = self.updater.dispatcher
d.addTelegramMessageHandler(
self.telegramHandlerTest)
self.updater.start_polling(0.01)
sleep(.1)
self.assertEqual(self.received_message, 'Test')
# Remove handler
d.removeTelegramMessageHandler(self.telegramHandlerTest)
self.reset()
bot.send_messages = 1
sleep(.1)
self.assertTrue(None is self.received_message)
def test_addTelegramMessageHandlerMultipleMessages(self):
print('Testing addTelegramMessageHandler and send 100 messages...')
self.updater.bot = MockBot('Multiple', 100)
self.updater.dispatcher.addTelegramMessageHandler(
self.telegramHandlerTest)
self.updater.start_polling(0.0)
sleep(2)
self.assertEqual(self.received_message, 'Multiple')
self.assertEqual(self.message_count, 100)
def test_addRemoveTelegramRegexHandler(self):
print('Testing add/removeStringRegexHandler')
bot = MockBot('Test2')
self.updater.bot = bot
d = self.updater.dispatcher
regobj = re.compile('Te.*')
self.updater.dispatcher.addTelegramRegexHandler(regobj,
self.telegramHandlerTest)
self.updater.start_polling(0.01)
sleep(.1)
self.assertEqual(self.received_message, 'Test2')
# Remove handler
d.removeTelegramRegexHandler(regobj, self.telegramHandlerTest)
self.reset()
bot.send_messages = 1
sleep(.1)
self.assertTrue(None is self.received_message)
def test_addRemoveTelegramCommandHandler(self):
print('Testing add/removeTelegramCommandHandler')
bot = MockBot('/test')
self.updater.bot = bot
d = self.updater.dispatcher
self.updater.dispatcher.addTelegramCommandHandler(
'test', self.telegramHandlerTest)
self.updater.start_polling(0.01)
sleep(.1)
self.assertEqual(self.received_message, '/test')
# Remove handler
d.removeTelegramCommandHandler('test', self.telegramHandlerTest)
self.reset()
bot.send_messages = 1
sleep(.1)
self.assertTrue(None is self.received_message)
def test_addRemoveUnknownTelegramCommandHandler(self):
print('Testing add/removeUnknownTelegramCommandHandler')
bot = MockBot('/test2')
self.updater.bot = bot
d = self.updater.dispatcher
self.updater.dispatcher.addUnknownTelegramCommandHandler(
self.telegramHandlerTest)
self.updater.start_polling(0.01)
sleep(.1)
self.assertEqual(self.received_message, '/test2')
# Remove handler
d.removeUnknownTelegramCommandHandler(self.telegramHandlerTest)
self.reset()
bot.send_messages = 1
sleep(.1)
self.assertTrue(None is self.received_message)
def test_addRemoveStringRegexHandler(self):
print('Testing add/removeStringRegexHandler')
bot = MockBot('', messages=0)
self.updater.bot = bot
d = self.updater.dispatcher
d.addStringRegexHandler('Te.*', self.stringHandlerTest)
queue = self.updater.start_polling(0.01)
queue.put('Test3')
sleep(.1)
self.assertEqual(self.received_message, 'Test3')
# Remove handler
d.removeStringRegexHandler('Te.*', self.stringHandlerTest)
self.reset()
queue.put('Test3')
sleep(.1)
self.assertTrue(None is self.received_message)
def test_addRemoveStringCommandHandler(self):
print('Testing add/removeStringCommandHandler')
bot = MockBot('', messages=0)
self.updater.bot = bot
d = self.updater.dispatcher
d.addStringCommandHandler(
'test3', self.stringHandlerTest)
queue = self.updater.start_polling(0.01)
queue.put('/test3')
sleep(.1)
self.assertEqual(self.received_message, '/test3')
# Remove handler
d.removeStringCommandHandler('test3', self.stringHandlerTest)
self.reset()
queue.put('/test3')
sleep(.1)
self.assertTrue(None is self.received_message)
def test_addRemoveUnknownStringCommandHandler(self):
print('Testing add/removeUnknownStringCommandHandler')
bot = MockBot('/test')
self.updater.bot = bot
d = self.updater.dispatcher
d.addUnknownStringCommandHandler(
self.stringHandlerTest)
queue = self.updater.start_polling(0.01)
queue.put('/test4')
sleep(.1)
self.assertEqual(self.received_message, '/test4')
# Remove handler
d.removeUnknownStringCommandHandler(self.stringHandlerTest)
self.reset()
bot.send_messages = 1
sleep(.1)
self.assertTrue(None is self.received_message)
def test_addRemoveErrorHandler(self):
print('Testing add/removeErrorHandler')
bot = MockBot('', messages=0)
self.updater.bot = bot
d = self.updater.dispatcher
d.addErrorHandler(self.errorHandlerTest)
queue = self.updater.start_polling(0.01)
error = TelegramError("Unauthorized.")
queue.put(error)
sleep(.1)
self.assertEqual(self.received_message, "Unauthorized.")
# Remove handler
d.removeErrorHandler(self.errorHandlerTest)
self.reset()
queue.put(error)
sleep(.1)
self.assertTrue(None is self.received_message)
def test_errorInHandler(self):
print('Testing error in Handler')
bot = MockBot('', messages=0)
self.updater.bot = bot
d = self.updater.dispatcher
d.addStringRegexHandler('.*',
self.errorRaisingHandlerTest)
self.updater.dispatcher.addErrorHandler(self.errorHandlerTest)
queue = self.updater.start_polling(0.01)
queue.put('Test Error 1')
sleep(.1)
self.assertEqual(self.received_message, 'Test Error 1')
def test_errorOnGetUpdates(self):
print('Testing error on getUpdates')
bot = MockBot('', raise_error=True)
self.updater.bot = bot
d = self.updater.dispatcher
d.addErrorHandler(self.errorHandlerTest)
self.updater.start_polling(0.01)
sleep(.1)
self.assertEqual(self.received_message, "Test Error 2")
def test_addRemoveTypeHandler(self):
print('Testing add/removeTypeHandler')
bot = MockBot('', messages=0)
self.updater.bot = bot
d = self.updater.dispatcher
d.addTypeHandler(dict, self.stringHandlerTest)
queue = self.updater.start_polling(0.01)
payload = {"Test": 42}
queue.put(payload)
sleep(.1)
self.assertEqual(self.received_message, payload)
# Remove handler
d.removeTypeHandler(dict, self.stringHandlerTest)
self.reset()
queue.put(payload)
sleep(.1)
self.assertTrue(None is self.received_message)
def test_runAsync(self):
print('Testing @run_async')
bot = MockBot('Test5', messages=2)
self.updater.bot = bot
d = self.updater.dispatcher
d.addTelegramMessageHandler(
self.asyncHandlerTest)
self.updater.start_polling(0.01)
sleep(1.2)
self.assertEqual(self.received_message, 'Test5')
self.assertEqual(self.message_count, 2)
def test_additionalArgs(self):
print('Testing additional arguments for handlers')
self.updater.bot = MockBot('', messages=0)
self.updater.dispatcher.addStringCommandHandler(
'test5', self.additionalArgsTest)
queue = self.updater.start_polling(0.01)
queue.put('/test5 resend')
sleep(.1)
self.assertEqual(self.received_message, '/test5 noresend')
self.assertEqual(self.message_count, 2)
def test_webhook(self):
print('Testing Webhook')
bot = MockBot('', messages=0)
self.updater.bot = bot
d = self.updater.dispatcher
d.addTelegramMessageHandler(
self.telegramHandlerTest)
# Select random port for travis
port = randrange(1024, 49152)
self.updater.start_webhook('127.0.0.1', port,
url_path='TOKEN',
cert='./tests/test_updater.py',
key='./tests/test_updater.py')
sleep(0.5)
# SSL-Wrapping will fail, so we start the server without SSL
Thread(target=self.updater.httpd.serve_forever).start()
# Now, we send an update to the server via urlopen
message = Message(1, User(1, "Tester"), datetime.now(),
Chat(1, "group", title="Test Group"))
message.text = "Webhook Test"
update = Update(1)
update.message = message
try:
payload = bytes(update.to_json(), encoding='utf-8')
except TypeError:
payload = bytes(update.to_json())
header = {
'content-type': 'application/json',
'content-length': str(len(payload))
}
r = Request('http://127.0.0.1:%d/TOKEN' % port,
data=payload,
headers=header)
urlopen(r)
sleep(1)
self.assertEqual(self.received_message, 'Webhook Test')
print("Test other webhook server functionalities...")
request = Request('http://localhost:%d/webookhandler.py' % port)
response = urlopen(request)
self.assertEqual(b'', response.read())
self.assertEqual(200, response.code)
request.get_method = lambda: 'HEAD'
response = urlopen(request)
self.assertEqual(b'', response.read())
self.assertEqual(200, response.code)
# Test multiple shutdown() calls
self.updater.httpd.shutdown()
self.updater.httpd.shutdown()
self.assertTrue(True)
def test_webhook_no_ssl(self):
print('Testing Webhook without SSL')
bot = MockBot('', messages=0)
self.updater.bot = bot
d = self.updater.dispatcher
d.addTelegramMessageHandler(
self.telegramHandlerTest)
# Select random port for travis
port = randrange(1024, 49152)
self.updater.start_webhook('127.0.0.1', port)
sleep(0.5)
# Now, we send an update to the server via urlopen
message = Message(1, User(1, "Tester 2"), datetime.now(),
Chat(1, 'group', title="Test Group 2"))
message.text = "Webhook Test 2"
update = Update(1)
update.message = message
try:
payload = bytes(update.to_json(), encoding='utf-8')
except TypeError:
payload = bytes(update.to_json())
header = {
'content-type': 'application/json',
'content-length': str(len(payload))
}
r = Request('http://127.0.0.1:%d/' % port,
data=payload,
headers=header)
urlopen(r)
sleep(1)
self.assertEqual(self.received_message, 'Webhook Test 2')
def signalsender(self):
sleep(0.5)
os.kill(os.getpid(), signal.SIGTERM)
def test_idle(self):
print('Testing idle')
self.updater.bot = MockBot('Test6', messages=0)
self.updater.start_polling(poll_interval=0.01)
Thread(target=self.signalsender).start()
self.updater.idle()
# If we get this far, idle() ran through
sleep(1)
self.updater.running = False
class MockBot:
def __init__(self, text, messages=1, raise_error=False):
self.text = text
self.send_messages = messages
self.raise_error = raise_error
self.token = "TOKEN"
pass
@staticmethod
def mockUpdate(text):
message = Message(0, None, None, None)
message.text = text
update = Update(0)
update.message = message
return update
def setWebhook(self, webhook_url=None, certificate=None):
pass
def getUpdates(self,
offset=None,
limit=100,
timeout=0,
network_delay=2.):
if self.raise_error:
raise TelegramError('Test Error 2')
elif self.send_messages >= 2:
self.send_messages -= 2
return self.mockUpdate(self.text), self.mockUpdate(self.text)
elif self.send_messages == 1:
self.send_messages -= 1
return self.mockUpdate(self.text),
else:
return []
if __name__ == '__main__':
unittest.main()