Compare commits

...

109 Commits

Author SHA1 Message Date
Jannes Höke 6479e15578 Bump version to v6.0.0 2017-05-19 21:49:01 +02:00
Jannes Höke 5dd3a660e3 forgot some escaping 2017-05-19 20:57:52 +02:00
Eldinnie e2a651afc8 Allow edited as seperate input (#608)
* Allow edited as seperate input

In short made it possible to tune messagehandler more to your wishes. and choose exactly what updates to receive. messages, edited_message or channel_post or a combination.

- Added the edited_updates argument to MessageHandler
- Added DepricationWarning when using allow_edited
- replaced _is_allowed_message and _is_allowed_channel_post with _is_allowed_update
- Modified tests to reflect new way

* oops

Spelled deprecation wrong
made an error in the _is_allowed_update.

* Python 2 does not have assertWarns.

* remove unneeded statements
2017-05-19 20:26:10 +02:00
Evgen 33512ffd2e Improved CommandHandler (#613)
* Improved CommandHandler

Now you can pass list of commands instead of one command

* Added tests for list of commands

* Return backward compatibility

Renamed `commands` to `command` in CommandHandler

* Added test for a command not in the list

* Fixed py2 unicode command support in `CommandHandler`
2017-05-19 20:21:37 +02:00
Evgen c2c5452829 Updated docstring in deleteMessage methods (#618)
* Removed "undocumented" notice

* Updated docstring according to Bot API documentation

* Removed extra line
2017-05-19 20:21:20 +02:00
Jannes Höke 9aa5522694 sanitize html and markdown in Message.text_html and text_markdown (#621)
* sanitize html and markdown in Message.text_html and text_markdown

* add import for escape_html
2017-05-19 19:11:40 +02:00
Noam Meltzer 9720f59d7e Fix docs (#614)
* Spelling fixes in `README.rst`

Fixes #609 and #610

* Fixed and added some type hints in docstrings
2017-05-17 22:46:06 +03:00
evgfilim1 578627feca Fixed and added some type hints in docstrings 2017-05-17 21:41:08 +05:00
evgfilim1 257b8321f8 Spelling fixes in README.rst
Fixes #609 and #610
2017-05-16 22:59:02 +05:00
Eldinnie 4f2ea5b0f1 Update readme to reflect changed install procedure (#610)
Fixes #609
2017-05-16 10:32:36 +03:00
Noam Meltzer 9a2d5e0410 Update copyright notice to include 2017 - missing files from another PR 2017-05-15 00:37:40 +03:00
Jacob Bom 1b7f83625c Update copyright notice to include 2017
Not strictly needed, but it helps show that the project is being actively
developed which I find important.
2017-05-15 00:36:49 +03:00
Jacob Bom da01601ff9 Add equality rich comparision operators to telegram objects (#604)
fixes #587
2017-05-15 00:29:31 +03:00
Evgen 752b64769d Fix for #601 (#603) 2017-05-12 17:42:02 +02:00
Evgen f3b75d95c4 Implemented undocumented 'deleteMessage' method (#602)
* Implemented `Bot.delete_message()` and `Message.delete()` methods

* Added tests for `Bot.delete_message()` and `Messsage.delete()`

* Added myself to `AUTHORS.rst`

* Using `assertRaisesRegexp` instead of `assertRaisesRegex` in tests

Now tests can run on Python 2.7 without raising 'AttributeError'

* Fix docstring

Added a warning to the docstring describing that this method is undocumented and not guaranteed to work
2017-05-12 17:40:57 +02:00
Eldinnie 36b98b0ab4 fix mimetype errors in video tests (#600) 2017-05-12 17:39:28 +02:00
Eldinnie 05b85d4334 Skip test_idle() and test_UserSignal on Windows (#599) 2017-05-12 17:39:14 +02:00
Eldinnie f27807552f refactor self.id -> self._id to let pycharms testrunner run (#598) 2017-05-12 17:38:51 +02:00
Eldinnie f3aca42e69 pass errormessage to Unauthorized (#597) 2017-05-12 17:38:36 +02:00
Noam Meltzer ed49bdb19c Merge pull request #592 from python-telegram-bot/snake_case
prefer snake_case for telegram.Bot methods
2017-05-08 00:30:05 +03:00
Rahiel Kasim 9f3afa5fa2 prefer snake_case for telegram.Bot methods 2017-05-07 16:09:58 +02:00
Joscha Götzer cdf36a20b7 Fixed deprecation warning (#586) 2017-04-29 19:56:27 +02:00
manorom c5598b96bc Fix Bug #571 (second try) (#574)
* Fix Bug #571
ConversationHandler will not process CallbackQuery if per_chat=True and
the CallbackQuery has no message attached to it (as is the case with
buttons on inline results)

* Adds test case for CallbackQuery without Chat
2017-04-29 15:51:48 +02:00
Noam Meltzer ca4351079f Merge pull request #542 from python-telegram-bot/urllib3-vendor-beta
Vendor (embed) urllib3 with our package.
2017-04-29 16:38:44 +03:00
Noam Meltzer 34059c951d Don't run coveralls on the vendor subdir 2017-04-29 15:52:31 +03:00
Jannes Höke c7dbdce3dc Allow CallbackQueryHandler in ConversationHandler with per_mess… (#561)
* 🐛 Allow CallbackQueryHandler in ConversationHandler with per_message=False

but show a warning #556

*  warning logs instead of ValueErrors

 #556
2017-04-29 15:15:17 +03:00
Noam Meltzer fe5ae8ed84 request.py: Fix warning on stderr irrelevant for most users
Attempting to import urllib3.contrib.socks may fail if PySocks is not
installed. Most users won't care for that.
Only import that module if the user requested to use a socks proxy.
2017-04-29 14:42:36 +03:00
Noam Meltzer a6b28b022a Merge remote-tracking branch 'origin/master' into urllib3-vendor-beta 2017-04-29 14:09:45 +03:00
Noam Meltzer 284f16b87b Merge pull request #583 from whipermr5/gae-urllib3-support
Compatibility with GAE via urllib3.contrib package
2017-04-29 12:29:51 +03:00
John Yong 785245a57e Add myself to authors list 2017-04-27 17:00:31 +08:00
John Yong 1d905d567c GAE support via urllib3.contrib.appengine module #334 2017-04-27 16:59:35 +08:00
Jannes Höke 4541476143 enforce urllib3==1.20 (#580)
* 🚑 enforce urllib3==1.20

 #579

* 🚑 Bump version to v5.3.1

 #579
2017-04-25 17:26:54 +02:00
Dickson Tan 11a3de67ea Remove deprecated code #408 part 1 (#564)
* Remove non-pep8 dispatcher methods

* callbackqueryhandler: Remove non-pep8 compliant methods

* commandhandler: Remove non-pep8 compliant methods

* handler: Remove non-pep8 compliant methods

* messagehandler: Remove non-pep8 compliant methods

* stringcommandhandler: Remove non-pep8 compliant methods

* stringregexhandler: Remove non-pep8 compliant methods

* test_updater: fix usage of deprecated methods

* dispatcher: fix accidental misalignment of comments by pre-commit hook

* Add myself to list of contributors
2017-04-25 10:39:02 +02:00
Eldinnie 90bf26c09b Issue 566 (#577)
* stripping token of whitespaces before starting bot

* Line feed

* - Case insensitivity for commandhandler
- Ignore pylint case on windows.
2017-04-25 10:37:06 +02:00
Eldinnie b5b09884b1 Group filters (#575)
* stripping token of whitespaces before starting bot

* Line feed

* show exception that's caused (fixes flake8 failing)

* Add private/group filters

Add filters:
 - Filters.private (for messages in private chats)
 - Filters.group   (for messages in group chats)

* use constants
2017-04-23 23:22:05 +02:00
Rahiel Kasim 35132271af contributing: how to run tests without make; AUTHORS: fix formatting (#567) 2017-04-17 14:49:42 +03:00
Iulian Onofrei 44d7bad11c Update constants.py (#553)
Add maximum inline query results constant
2017-03-29 17:31:26 +02:00
Jacob Bom ad5f009ce7 Add the ability to invert (not) filters (#552)
* Add InvertedFilter and use it from __invert__

* Add docstrings and __str__ for inverting filters

* Tests for inverted filters
2017-03-28 18:38:44 +02:00
Jannes Höke 8fe6e13ff2 Update handlers (#532)
* 🔨 Refactor `Update.extract_` methods to `Update.effective_` properties
 #507

*  Update RegexHandler to work with edited messages and channel posts

* 🔨 messagehandler.py: refactor channel_posts_updates -> channel_post_updates

* 🔨 handler.py: use effective_ properties
2017-03-26 14:36:45 +02:00
Jannes Höke ff39e2436e Refactor Update.extract_ methods to Update.effective_ properties (#531)
* 🔨 Refactor `Update.extract_` methods to `Update.effective_` properties
 #507

* 🔨 handler.py: use effective_ properties
2017-03-26 14:36:34 +02:00
Alex Hirschfeld 5b14b134dc Added user defined function for updater's signalHandler (#512)
* Added user defined function for updater's signalHandler

* Added test_userSignal to test_updater

* Added test_userSignal to test_updater
Fixing paren
2017-03-26 14:36:13 +02:00
Jannes Höke 5897affa07 add missing allowed_updates to start_webhook (#549)
* 🐛 add missing allowed_updates to start_webhook
 #548

* 🔨 fix webhook-related tests
 #548
2017-03-25 12:46:37 +01:00
Jannes Höke 9982f3c908 🐛 fix message date parsing for messages without a timestamp (#550)
#546
2017-03-25 12:15:37 +01:00
thodnev 22142e7cbd Introduce MessageQueue (#537)
* Introduce MessageQueue

* minor documentation and terminology fixes according to the review

* minor documentation and terminology fixes according to the review

* minor documentation and terminology fixes according to the review

* pep8 fix
2017-03-15 17:35:33 +01:00
Noam Meltzer 4c8d1c9a5e Fix Bot.sendDocument 2017-03-11 23:55:26 +02:00
Noam Meltzer bef6651da0 travis: when running nosetests run only the telegram tests (not urllib3) 2017-03-11 23:50:30 +02:00
Noam Meltzer 8c2ee0b439 Remove urllib3 from requirements.txt 2017-03-11 23:50:30 +02:00
Noam Meltzer bfb99a688a AUTHORS.rst: Add self to maintainers and add license info about urllib3 2017-03-11 23:50:30 +02:00
Noam Meltzer d239f4ee62 setup.py: vendor the urllib3 package 2017-03-11 23:50:30 +02:00
Noam Meltzer 2b930c221d Sane default for sending files timeout 2017-03-11 16:23:43 +02:00
Jannes Höke 2a1d40bd28 🚧 manipulate sys.path to prefer vendor urllib3
#533
2017-03-09 23:29:15 +01:00
Jannes Höke 1d4464ddbd add git submodule to vendor urllib3
#533
2017-03-09 23:16:58 +01:00
Jannes Höke 85b9236641 Update AUTHORS.rst 2017-02-28 15:55:38 +01:00
Jose Luis Salazar Gonzalez e78d11a99b Add filters to commandHandler (#536)
* Add filters to commandHandler

* Add commandHandler tests with filters

* Add myself to authors
2017-02-28 15:44:55 +01:00
Jannes Höke cc73469dab Issue 502 (#530)
* conversationhandler.py: add per_chat, per_user and per_message

* test_conversationhandler.py: test case per_user=False

* test_conversationhandler.py: add test for callbackqueryhandlers

* ✏️ Fix accidental typo in logging format
2017-02-27 17:52:58 +01:00
Jannes Höke 853d823964 🚨 promise.py: remove pylint warning
#529
2017-02-27 16:23:04 +01:00
Jannes Höke 69bfb85298 Fix tests (missed one)
#494
2017-02-27 15:35:57 +01:00
Jannes Höke f267646828 Fix tests
#494
2017-02-27 15:23:48 +01:00
Jannes Höke 6b7144bbab 🚨 fix yapf 2017-02-27 14:53:28 +01:00
Jannes Höke f8b13440c1 🔀 Merge thodnev/ptb into ptb/promises-with-exceptions
#529
2017-02-27 14:45:12 +01:00
Jannes Höke a1ade408b0 🔀 Merge master into dec04
#483
2017-02-27 14:39:18 +01:00
Jannes Höke 2954ca2bad 🔀 Merge master into fix-460
#494
2017-02-27 14:33:58 +01:00
Eldinnie 34ebb7fe5a Issue 520 (#521)
* stripping token of whitespaces before starting bot

* Line feed

* fixed chat parameter all_members_are_administrators
2017-02-27 14:27:06 +01:00
Eldinnie 78094b796d Fix commandhandler (#515)
* stripping token of whitespaces before starting bot

* Line feed

* CommandHandler checks if message is for this bot

* CommandHandler checks if message is for this bot
- Added tests

* Fixed tests in test_conversationHandler to work with new commandhandler structure

* type in conversationhandler test
2017-02-27 14:26:53 +01:00
thodnev fb378775a4 Changed promises to handle exceptions 2017-02-26 23:27:03 +02:00
代码家 264b9bd08c add myself to authors (#527) 2017-02-26 13:57:50 +01:00
Noam Meltzer d714da4b36 Merge pull request #500 from python-telegram-bot/bug470
Fix bad formatting of BadRequest exception message

Fixes #470
2017-02-25 22:40:49 +02:00
代码家 e39afad321 Add support for Socks5 proxy. (#518) 2017-02-25 20:47:56 +02:00
Rahiel Kasim 0507378509 fix typo's (#523) and comment out failing sticker test 2017-02-18 16:03:50 +01:00
Rahiel Kasim 659ac52d92 fix tests (telegram cache file_id's) 2017-02-05 23:10:48 +01:00
Rahiel Kasim 924c241680 declare support for Python 3.6 2017-02-05 20:40:16 +01:00
Joscha Götzer ac59f2f37c Added methods to generate the original markdown/html string from entities contained in Message (#507)
* Added methods to generate the original markdown/html formatted string from the entities contained in an update

* Added

* Moved the html/markdown parsing methods to `Message`

* Moved extract_* methods from helpers to the appropriate location

* Refactored text_markdown and text_html

* Refactored text_markdown and text_html for efficiency

* Fixed method call in conversationhandler

* Fixed method call in handler

* Fixed `make test` command on windows systems

* Improved method documentation
2017-01-20 20:13:58 +01:00
Jannes Höke 86c8ebbfb7 fix tests 2017-01-16 00:28:47 +01:00
Jannes Höke cd38bdbed5 bot.py: add delete_webhook alias 2017-01-16 00:28:26 +01:00
Jannes Höke 975d193441 Merge branch 'master' into dec04 2017-01-16 00:15:09 +01:00
Jannes Höke 728ffa432d updater.py: add allowed_updates parameter to start_polling 2017-01-16 00:07:46 +01:00
Jannes Höke 8ac66698b5 test_bot.py: unit test setWebhook and deleteWebhook 2017-01-16 00:01:32 +01:00
Jannes Höke c2c93f5d51 webhookinfo.py: add new parameters 2017-01-16 00:00:54 +01:00
Jannes Höke fc9f36d4db use deleteWebhook in Updater._bootstrap 2017-01-16 00:00:27 +01:00
Jacob Bom e69e99ce23 Make everything default to None
This effectively removes most type checking from all optional variables... I'm not really sure that's what we want...
2017-01-11 19:41:39 +01:00
Jacob Bom 27b2fd64b7 More IDE friendliness 2017-01-09 19:29:05 +01:00
Jacob Bom 4dee785fba Last of the classes I think 2017-01-09 19:16:28 +01:00
Noam Meltzer 4c7cc3a05b Fix test_set_game_score3 - telegram changed something in their responses 2017-01-07 23:37:23 +02:00
Noam Meltzer 1bb5dd224b Fix bad formatting of BadRequest exception message
fixes #470
2017-01-07 23:09:06 +02:00
Noam Meltzer bd96771a7a Fix typo 2017-01-07 23:05:58 +02:00
Noam Meltzer 191e442e59 test_jobqueue.py: Fix time delta calculations on several tests 2017-01-07 20:13:08 +02:00
Noam Meltzer 58dddfd9c3 Control the read timeout from telegram servers
refs #495
refs #364
2017-01-07 20:13:08 +02:00
Noam Meltzer 68a7d9fa1b Make sure to hint Telegram servers that we reuse connections
refs #495
refs #364
2017-01-07 20:13:08 +02:00
Jacob Bom 38f2064639 Tricky empty string in __init__ 2016-12-30 14:03:49 +01:00
Jacob Bom 651119fd69 Fix instances of empty strings in __init__ in example contributing code 2016-12-30 13:58:57 +01:00
Jacob Bom 232a0b0286 Fix instances of empty strings in __init__
Should partially fix #460
2016-12-30 13:57:59 +01:00
Anton Tagunov 48bcc3129a set timeout for post message with file object (#486) 2016-12-29 12:01:58 +02:00
Joscha Götzer 6a01164897 ConversationHandler breaks when bot is also used in Channels (#487)
* ConversationHandler now ignores channel posts
2016-12-25 22:36:01 +02:00
Noam Meltzer 7f6b017ce2 Merge pull request #484 from python-telegram-bot/jobqueue-absolute-simple
JobQueue: Simpler API for adding jobs
2016-12-21 00:04:01 +02:00
Jannes Höke 09cb33f52d rename Job.is_removed to removed 2016-12-20 22:37:36 +01:00
Noam Meltzer 423251f66c Change Job.is_removed to be a property instead of a method 2016-12-20 00:14:03 +02:00
Noam Meltzer ed1785981d disable test_send_contact 2016-12-20 00:12:57 +02:00
Hugo Hakim Damer a8fecc527d Prevented modifications to the request object's original data (#454)
fixes #357
2016-12-20 00:07:35 +02:00
Yan c3984e1bf1 Download changed (#459)
* DownBytes added

* File.downbyte changed

* Changed file.download();Remove downbyte()

* Fixed typo

* add docstring, make custom_path and out mutually exclusive, rename downbytes to retrieve

* remove trailing whitespace

* run pre-commit hooks
2016-12-18 03:05:00 +01:00
Noam Meltzer 93bf21a0a4 jobqueue.py: stability improvments
- Job.job_queue is now weakref.proxy reducing the risk of cyclic
   pointers preventing Job object from being deleted.
 - JobQueue._put(): raise if both next_t and job.interval are None
 - Don't put repeating job back to queue if user had disabled it was
   disabled during the time of execution.
 - New method: Job.is_removed() - promising a consistent API (instead of
   access to private member Job._remove)
 - Documentation fixes.
2016-12-15 00:17:57 +02:00
Jannes Höke cbf93e1046 switch to run_x naming scheme 2016-12-14 18:01:44 +01:00
Jannes Höke a1a8628c75 bot.py: fix argument order, setWebhook: make 'url' and 'webhook_url' mutually exclusive 2016-12-14 17:38:06 +01:00
Jannes Höke d5ce32c672 removed Job.run_immediately and related code 2016-12-14 17:15:52 +01:00
Jannes Höke 09ddc1b1a8 DAAAANGER ZOOOONE 2016-12-14 16:27:45 +01:00
Jannes Höke c7cd379016 jobqueue.py: move the check for job.interval types into Job.__init__ 2016-12-14 06:30:18 +01:00
Jannes Höke 84f3bc0c79 README.rst: update api support date 2016-12-14 00:51:43 +01:00
Jannes Höke 92dc9b81ce bot.py: implement changes of december 04 (bot api 2.3.1) 2016-12-14 00:50:34 +01:00
Jannes Höke 8ead72e3ef jobqueue: add support for specifying next_t in datetime.datetime or datetime.time 2016-12-13 23:38:13 +01:00
Jannes Höke a37add39f4 [ci skip] update timerbot.py to use chat_data 2016-12-13 21:57:37 +01:00
171 changed files with 3878 additions and 1073 deletions
+4 -1
View File
@@ -1,5 +1,8 @@
[run]
source = telegram
omit = telegram/vendor/*
[report]
omit = tests/
omit =
tests/
telegram/vendor/*
+8 -2
View File
@@ -85,6 +85,12 @@ Here's how to make a one-off code change.
$ make test
If you don't have ``make``, do:
.. code-block::
$ nosetests -v
- To actually make the commit (this will trigger tests for yapf, lint and pep8 automatically):
.. code-block:: bash
@@ -184,11 +190,11 @@ break the API classes. For example:
.. code-block:: python
# GOOD
def __init__(self, id, name, last_name='', **kwargs):
def __init__(self, id, name, last_name=None, **kwargs):
self.last_name = last_name
# BAD
def __init__(self, id, name, last_name=''):
def __init__(self, id, name, last_name=None):
self.last_name = last_name
+4
View File
@@ -0,0 +1,4 @@
[submodule "telegram/vendor/urllib3"]
path = telegram/vendor/urllib3
url = https://github.com/python-telegram-bot/urllib3.git
branch = ptb
+2 -1
View File
@@ -4,6 +4,7 @@ python:
- "3.3"
- "3.4"
- "3.5"
- "3.6"
- "pypy"
- "pypy3"
branches:
@@ -15,7 +16,7 @@ install:
- pip install -r requirements-dev.txt
- if [[ $TRAVIS_PYTHON_VERSION != 'pypy'* ]]; then pip install ujson; fi
script:
- nosetests -v --with-flaky --no-flaky-report --with-coverage --cover-package=telegram/
- nosetests -v --with-flaky --no-flaky-report --with-coverage --cover-package=telegram/ tests
- if [[ $TRAVIS_PYTHON_VERSION == 3.5 ]]; then pre-commit run --all-files; fi
after_success:
coveralls
+19 -1
View File
@@ -1,7 +1,14 @@
Credits
=======
``python-telegram-bot`` is written and maintained by `Leandro Toledo <https://github.com/leandrotoledo>`_.
``python-telegram-bot`` was originally created by
`Leandro Toledo <https://github.com/leandrotoledo>`_ and is now maintained by
`Jannes Höke <https://github.com/jh0ker>`_ (`@jh0ker <https://t.me/jh0ker>`_ on Telegram) and
`Noam Meltzer <https://github.com/tsnoam>`_.
We're vendoring urllib3 as part of ``python-telegram-bot`` which is distributed under the MIT
license. For more info, full credits & license terms, the sources can be found here:
`https://github.com/python-telegram-bot/urllib3`.
Contributors
------------
@@ -9,15 +16,22 @@ Contributors
The following wonderful people contributed directly or indirectly to this project:
- `Avanatiker <https://github.com/Avanatiker>`_
- `Anton Tagunov <https://github.com/anton-tagunov>`_
- `Balduro <https://github.com/Balduro>`_
- `bimmlerd <https://github.com/bimmlerd>`_
- `d-qoi <https://github.com/d-qoi>`_
- `daimajia <https://github.com/daimajia>`_
- `Eli Gao <https://github.com/eligao>`_
- `ErgoZ Riftbit Vaper <https://github.com/ergoz>`_
- `Eugene Lisitsky <https://github.com/lisitsky>`_
- `evgfilim1 <https://github.com/evgfilim1>`_
- `franciscod <https://github.com/franciscod>`_
- `Hugo Damer <https://github.com/HakimusGIT>`_
- `Jacob Bom <https://github.com/bomjacob>`_
- `JASON0916 <https://github.com/JASON0916>`_
- `jh0ker <https://github.com/jh0ker>`_
- `John Yong <https://github.com/whipermr5>`_
- `jossalgon <https://github.com/jossalgon>`_
- `JRoot3D <https://github.com/JRoot3D>`_
- `jlmadurga <https://github.com/jlmadurga>`_
- `Kjwon15 <https://github.com/kjwon15>`_
@@ -25,14 +39,18 @@ The following wonderful people contributed directly or indirectly to this projec
- `macrojames <https://github.com/macrojames>`_
- `Michael Elovskikh <https://github.com/wronglink>`_
- `naveenvhegde <https://github.com/naveenvhegde>`_
- `neurrone <https://github.com/neurrone>`_
- `njittam <https://github.com/njittam>`_
- `Noam Meltzer <https://github.com/tsnoam>`_
- `Oleg Shlyazhko <https://github.com/ollmer>`_
- `overquota <https://github.com/overquota>`_
- `Patrick Hofmann <https://github.com/PH89>`_
- `Pieter Schutz <https://github.com/eldinnie>`_
- `Rahiel Kasim <https://github.com/rahiel>`_
- `Joscha Götzer <https://github.com/Rostgnom>`_
- `Shelomentsev D <https://github.com/shelomentsevd>`_
- `sooyhwang <https://github.com/sooyhwang>`_
- `thodnev <https://github.com/thodnev>`_
- `Valentijn <https://github.com/Faalentijn>`_
- `voider1 <https://github.com/voider1>`_
- `wjt <https://github.com/wjt>`_
+27
View File
@@ -2,6 +2,33 @@
Changes
=======
**2017-05-19**
*Released 6.0.0*
- Add support for Bot API 2.3.1
- Add support for ``deleteMessage`` API method
- New, simpler API for ``JobQueue`` - https://github.com/python-telegram-bot/python-telegram-bot/pull/484
- Download files into file-like objects - https://github.com/python-telegram-bot/python-telegram-bot/pull/459
- Use vendor ``urllib3`` to address issues with timeouts
- The default timeout for messages is now 5 seconds. For sending media, the default timeout is now 20 seconds.
- String attributes that are not set are now ``None`` by default, instead of empty strings
- Add ``text_markdown`` and ``text_html`` properties to ``Message`` - https://github.com/python-telegram-bot/python-telegram-bot/pull/507
- Add support for Socks5 proxy - https://github.com/python-telegram-bot/python-telegram-bot/pull/518
- Add support for filters in ``CommandHandler`` - https://github.com/python-telegram-bot/python-telegram-bot/pull/536
- Add the ability to invert (not) filters - https://github.com/python-telegram-bot/python-telegram-bot/pull/552
- Add ``Filters.group`` and ``Filters.private``
- Compatibility with GAE via ``urllib3.contrib`` package - https://github.com/python-telegram-bot/python-telegram-bot/pull/583
- Add equality rich comparision operators to telegram objects - https://github.com/python-telegram-bot/python-telegram-bot/pull/604
- Several bugfixes and other improvements
- Remove some deprecated code
**2017-04-17**
*Released 5.3.1*
- Hotfix release due to bug introduced by urllib3 version 1.21
**2016-12-11**
*Released 5.3*
+8 -2
View File
@@ -84,7 +84,7 @@ make the development of bots easy and straightforward. These classes are contain
Telegram API support
====================
As of **3. Oct 2016**, all types and methods of the Telegram Bot API are supported.
As of **4. Dec 2016**, all types and methods of the Telegram Bot API are supported.
==========
Installing
@@ -100,9 +100,15 @@ Or you can install from source with:
.. code:: shell
$ git clone https://github.com/python-telegram-bot/python-telegram-bot
$ git clone https://github.com/python-telegram-bot/python-telegram-bot --recursive
$ cd python-telegram-bot
$ python setup.py install
In case you have a previously cloned local repository already, you should initialize the added urllib3 submodule before installing with:
.. code:: shell
$ git submodule update --init --recursive
===============
Getting started
+3 -3
View File
@@ -51,7 +51,7 @@ master_doc = 'index'
# General information about the project.
project = u'Python Telegram Bot'
copyright = u'2015-2016, Leandro Toledo'
copyright = u'2015-2017, Leandro Toledo'
author = u'Leandro Toledo'
# The version info for the project you're documenting, acts as replacement for
@@ -59,9 +59,9 @@ author = u'Leandro Toledo'
# built documents.
#
# The short X.Y version.
version = '5.3' # telegram.__version__[:3]
version = '6.0' # telegram.__version__[:3]
# The full version, including alpha/beta/rc tags.
release = '5.3.0' # telegram.__version__
release = '6.0.0' # telegram.__version__
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
@@ -0,0 +1,7 @@
telegram.ext.messagequeue module
================================
.. automodule:: telegram.ext.messagequeue
:members:
:undoc-members:
:show-inheritance:
+1
View File
@@ -16,6 +16,7 @@ Submodules
telegram.ext.commandhandler
telegram.ext.inlinequeryhandler
telegram.ext.messagehandler
telegram.ext.messagequeue
telegram.ext.filters
telegram.ext.regexhandler
telegram.ext.stringcommandhandler
+2 -2
View File
@@ -56,7 +56,7 @@ def gender(bot, update):
def photo(bot, update):
user = update.message.from_user
photo_file = bot.getFile(update.message.photo[-1].file_id)
photo_file = bot.get_file(update.message.photo[-1].file_id)
photo_file.download('user_photo.jpg')
logger.info("Photo of %s: %s" % (user.first_name, 'user_photo.jpg'))
update.message.reply_text('Gorgeous! Now, send me your location please, '
@@ -149,7 +149,7 @@ def main():
# Start the Bot
updater.start_polling()
# Run the bot until the you presses Ctrl-C or the process receives SIGINT,
# Run the bot until you press 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()
+1 -1
View File
@@ -142,7 +142,7 @@ def main():
# Start the Bot
updater.start_polling()
# Run the bot until the you presses Ctrl-C or the process receives SIGINT,
# Run the bot until you press 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()
+2 -4
View File
@@ -20,7 +20,7 @@ def main():
# 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
update_id = bot.get_updates()[0].update_id
except IndexError:
update_id = None
@@ -39,9 +39,7 @@ def main():
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
for update in bot.get_updates(offset=update_id, timeout=10):
update_id = update.update_id + 1
if update.message: # your bot can receive updates without messages
+1 -1
View File
@@ -64,7 +64,7 @@ def main():
# Start the Bot
updater.start_polling()
# Run the bot until the you presses Ctrl-C or the process receives SIGINT,
# Run the bot until you press 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()
+1 -1
View File
@@ -72,7 +72,7 @@ def inlinequery(bot, update):
def error(bot, update, error):
logger.warn('Update "%s" caused error "%s"' % (update, error))
logger.warning('Update "%s" caused error "%s"' % (update, error))
def main():
+3 -3
View File
@@ -26,9 +26,9 @@ def start(bot, update):
def button(bot, update):
query = update.callback_query
bot.editMessageText(text="Selected option: %s" % query.data,
chat_id=query.message.chat_id,
message_id=query.message.message_id)
bot.edit_message_text(text="Selected option: %s" % query.data,
chat_id=query.message.chat_id,
message_id=query.message.message_id)
def help(bot, update):
+18 -18
View File
@@ -22,10 +22,9 @@ import logging
# Enable logging
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
level=logging.DEBUG)
level=logging.INFO)
logger = logging.getLogger(__name__)
timers = dict()
# Define a few command handlers. These usually take the two arguments bot and
@@ -36,10 +35,10 @@ def start(bot, update):
def alarm(bot, job):
"""Function to send the alarm message"""
bot.sendMessage(job.context, text='Beep!')
bot.send_message(job.context, text='Beep!')
def set(bot, update, args, job_queue):
def set(bot, update, args, job_queue, chat_data):
"""Adds a job to the queue"""
chat_id = update.message.chat_id
try:
@@ -50,9 +49,8 @@ def set(bot, update, args, job_queue):
return
# Add job to queue
job = Job(alarm, due, repeat=False, context=chat_id)
timers[chat_id] = job
job_queue.put(job)
job = job_queue.run_once(alarm, due, context=chat_id)
chat_data['job'] = job
update.message.reply_text('Timer successfully set!')
@@ -60,23 +58,22 @@ def set(bot, update, args, job_queue):
update.message.reply_text('Usage: /set <seconds>')
def unset(bot, update):
def unset(bot, update, chat_data):
"""Removes the job if the user changed their mind"""
chat_id = update.message.chat_id
if chat_id not in timers:
if 'job' not in chat_data:
update.message.reply_text('You have no active timer')
return
job = timers[chat_id]
job = chat_data['job']
job.schedule_removal()
del timers[chat_id]
del chat_data['job']
update.message.reply_text('Timer successfully unset!')
def error(bot, update, error):
logger.warn('Update "%s" caused error "%s"' % (update, error))
logger.warning('Update "%s" caused error "%s"' % (update, error))
def main():
@@ -88,8 +85,11 @@ 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, pass_job_queue=True))
dp.add_handler(CommandHandler("unset", unset))
dp.add_handler(CommandHandler("set", set,
pass_args=True,
pass_job_queue=True,
pass_chat_data=True))
dp.add_handler(CommandHandler("unset", unset, pass_chat_data=True))
# log all errors
dp.add_error_handler(error)
@@ -97,9 +97,9 @@ def main():
# Start the Bot
updater.start_polling()
# Block 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.
# Block until you press 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()
-1
View File
@@ -1,3 +1,2 @@
future>=0.15.2
urllib3>=1.10
certifi
+10 -1
View File
@@ -17,6 +17,13 @@ def requirements():
return requirements_list
packages = find_packages(exclude=['tests*'])
packages.extend(['telegram.vendor.urllib3.urllib3',
'telegram.vendor.urllib3.urllib3.packages', 'telegram.vendor.urllib3.urllib3.packages.ssl_match_hostname',
'telegram.vendor.urllib3.urllib3.packages.backports', 'telegram.vendor.urllib3.urllib3.contrib',
'telegram.vendor.urllib3.urllib3.util',
])
with codecs.open('README.rst', 'r', 'utf-8') as fd:
fn = os.path.join('telegram', 'version.py')
with open(fn) as fh:
@@ -32,10 +39,11 @@ with codecs.open('README.rst', 'r', 'utf-8') as fd:
keywords='python telegram bot api wrapper',
description="We have made you a wrapper you can't refuse",
long_description=fd.read(),
packages=find_packages(exclude=['tests*']),
packages=packages,
install_requires=requirements(),
extras_require={
'json': 'ujson',
'socks': 'PySocks'
},
include_package_data=True,
classifiers=[
@@ -53,4 +61,5 @@ with codecs.open('README.rst', 'r', 'utf-8') as fd:
'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6'
],)
+5 -1
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Copyright (C) 2015-2017
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
@@ -19,6 +19,10 @@
"""A library that provides a Python interface to the Telegram Bot API"""
from sys import version_info
import sys
import os
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'vendor', 'urllib3'))
from .base import TelegramObject
from .user import User
+16 -6
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Copyright (C) 2015-2017
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
@@ -35,12 +35,20 @@ class Animation(TelegramObject):
"""
def __init__(self, file_id, **kwargs):
def __init__(self,
file_id,
thumb=None,
file_name=None,
mime_type=None,
file_size=None,
**kwargs):
self.file_id = file_id
self.thumb = kwargs.get('thumb')
self.file_name = kwargs.get('file_name')
self.mime_type = kwargs.get('mime_type')
self.file_size = kwargs.get('file_size')
self.thumb = thumb
self.file_name = file_name
self.mime_type = mime_type
self.file_size = file_size
self._id_attrs = (self.file_id,)
@staticmethod
def de_json(data, bot):
@@ -55,6 +63,8 @@ class Animation(TelegramObject):
if not data:
return None
data = super(Animation, Animation).de_json(data, bot)
data['thumb'] = PhotoSize.de_json(data.get('thumb'), bot)
return Animation(**data)
+9 -7
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Copyright (C) 2015-2017
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
@@ -46,10 +46,10 @@ class Audio(TelegramObject):
def __init__(self,
file_id,
duration,
performer='',
title='',
mime_type='',
file_size=0,
performer=None,
title=None,
mime_type=None,
file_size=None,
**kwargs):
# Required
self.file_id = str(file_id)
@@ -57,8 +57,10 @@ class Audio(TelegramObject):
# Optionals
self.performer = performer
self.title = title
self.mime_type = str(mime_type)
self.file_size = int(file_size)
self.mime_type = mime_type
self.file_size = file_size
self._id_attrs = (self.file_id,)
@staticmethod
def de_json(data, bot):
+13 -3
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Copyright (C) 2015-2017
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
@@ -28,8 +28,8 @@ from abc import ABCMeta
class TelegramObject(object):
"""Base class for most telegram objects."""
__metaclass__ = ABCMeta
_id_attrs = ()
def __str__(self):
return str(self.to_dict())
@@ -69,7 +69,7 @@ class TelegramObject(object):
data = dict()
for key in iter(self.__dict__):
if key == 'bot':
if key in ('bot', '_id_attrs'):
continue
value = self.__dict__[key]
@@ -80,3 +80,13 @@ class TelegramObject(object):
data[key] = value
return data
def __eq__(self, other):
if isinstance(other, self.__class__):
return self._id_attrs == other._id_attrs
return super(TelegramObject, self).__eq__(other)
def __hash__(self):
if self._id_attrs:
return hash((self.__class__, self._id_attrs))
return super(TelegramObject, self).__hash__()
+610 -378
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Copyright (C) 2015-2017
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
+3 -1
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Copyright (C) 2015-2017
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
@@ -60,6 +60,8 @@ class CallbackQuery(TelegramObject):
if not data:
return None
data = super(CallbackQuery, CallbackQuery).de_json(data, bot)
data['from_user'] = User.de_json(data.get('from'), bot)
data['message'] = Message.de_json(data.get('message'), bot)
+9 -8
View File
@@ -2,7 +2,7 @@
# pylint: disable=C0103,W0622
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Copyright (C) 2015-2017
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
@@ -32,7 +32,7 @@ class Chat(TelegramObject):
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
all_members_are_admins (bool): True if a group has 'All Members Are Admins' enabled.
all_members_are_administrators (bool): True if group has 'All Members Are Administrators'
Args:
id (int):
@@ -53,11 +53,11 @@ class Chat(TelegramObject):
def __init__(self,
id,
type,
title='',
username='',
first_name='',
last_name='',
all_members_are_admins=False,
title=None,
username=None,
first_name=None,
last_name=None,
all_members_are_administrators=None,
bot=None,
**kwargs):
# Required
@@ -68,9 +68,10 @@ class Chat(TelegramObject):
self.username = username
self.first_name = first_name
self.last_name = last_name
self.all_members_are_admins = all_members_are_admins
self.all_members_are_administrators = all_members_are_administrators
self.bot = bot
self._id_attrs = (self.id,)
@staticmethod
def de_json(data, bot):
+1 -1
View File
@@ -2,7 +2,7 @@
# pylint: disable=R0903
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Copyright (C) 2015-2017
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
+5 -1
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Copyright (C) 2015-2017
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
@@ -46,6 +46,8 @@ class ChatMember(TelegramObject):
self.user = user
self.status = status
self._id_attrs = (self.user, self.status)
@staticmethod
def de_json(data, bot):
"""
@@ -59,6 +61,8 @@ class ChatMember(TelegramObject):
if not data:
return None
data = super(ChatMember, ChatMember).de_json(data, bot)
data['user'] = User.de_json(data.get('user'), bot)
return ChatMember(**data)
+6 -2
View File
@@ -2,7 +2,8 @@
# pylint: disable=R0902,R0912,R0913
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015 Leandro Toledo de Souza <devs@python-telegram-bot.org>
# Copyright (C) 2015-2017
# 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
@@ -61,6 +62,8 @@ class ChosenInlineResult(TelegramObject):
self.location = location
self.inline_message_id = inline_message_id
self._id_attrs = (self.result_id,)
@staticmethod
def de_json(data, bot):
"""
@@ -74,7 +77,8 @@ class ChosenInlineResult(TelegramObject):
if not data:
return None
# Required
data = super(ChosenInlineResult, ChosenInlineResult).de_json(data, bot)
# Required
data['from_user'] = User.de_json(data.pop('from'), bot)
# Optionals
data['location'] = Location.de_json(data.get('location'), bot)
+3 -1
View File
@@ -1,5 +1,5 @@
# python-telegram-bot - a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Copyright (C) 2015-2017
# by the python-telegram-bot contributors <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
@@ -32,6 +32,7 @@ Attributes:
limit, but eventually you'll begin receiving 429 errors.
MAX_MESSAGES_PER_SECOND (int)
MAX_MESSAGES_PER_MINUTE_PER_GROUP (int)
MAX_INLINE_QUERY_RESULTS (int)
The following constant have been found by experimentation:
@@ -52,3 +53,4 @@ MAX_MESSAGES_PER_SECOND_PER_CHAT = 1
MAX_MESSAGES_PER_SECOND = 30
MAX_MESSAGES_PER_MINUTE_PER_GROUP = 20
MAX_MESSAGE_ENTITIES = 100
MAX_INLINE_QUERY_RESULTS = 50
+5 -3
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Copyright (C) 2015-2017
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
@@ -39,13 +39,15 @@ class Contact(TelegramObject):
"""
def __init__(self, phone_number, first_name, last_name='', user_id=0, **kwargs):
def __init__(self, phone_number, first_name, last_name=None, user_id=None, **kwargs):
# Required
self.phone_number = str(phone_number)
self.first_name = first_name
# Optionals
self.last_name = last_name
self.user_id = int(user_id)
self.user_id = user_id
self._id_attrs = (self.phone_number,)
@staticmethod
def de_json(data, bot):
+16 -4
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Copyright (C) 2015-2017
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
@@ -41,14 +41,24 @@ class Document(TelegramObject):
"""
def __init__(self, file_id, thumb=None, file_name='', mime_type='', file_size=0, **kwargs):
_id_keys = ('file_id',)
def __init__(self,
file_id,
thumb=None,
file_name=None,
mime_type=None,
file_size=None,
**kwargs):
# Required
self.file_id = str(file_id)
# Optionals
self.thumb = thumb
self.file_name = file_name
self.mime_type = str(mime_type)
self.file_size = int(file_size)
self.mime_type = mime_type
self.file_size = file_size
self._id_attrs = (self.file_id,)
@staticmethod
def de_json(data, bot):
@@ -63,6 +73,8 @@ class Document(TelegramObject):
if not data:
return None
data = super(Document, Document).de_json(data, bot)
data['thumb'] = PhotoSize.de_json(data.get('thumb'), bot)
return Document(**data)
+1 -1
View File
@@ -3,7 +3,7 @@
# pylint: disable=C0103,R0903,E0213
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Copyright (C) 2015-2017
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
+2 -4
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Copyright (C) 2015-2017
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
@@ -62,9 +62,7 @@ class TelegramError(Exception):
class Unauthorized(TelegramError):
def __init__(self):
super(Unauthorized, self).__init__('Unauthorized')
pass
class InvalidToken(TelegramError):
+1 -1
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Copyright (C) 2015-2017
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
+1 -7
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Copyright (C) 2015-2017
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
@@ -23,7 +23,6 @@ import re
from future.utils import string_types
from telegram import Update
from telegram.utils.deprecate import deprecate
from .handler import Handler
@@ -105,8 +104,3 @@ class CallbackQueryHandler(Handler):
optional_args['groupdict'] = match.groupdict()
return self.callback(dispatcher.bot, update, **optional_args)
# old non-PEP8 Handler methods
m = "telegram.CallbackQueryHandler."
checkUpdate = deprecate(check_update, m + "checkUpdate", m + "check_update")
handleUpdate = deprecate(handle_update, m + "handleUpdate", m + "handle_update")
+1 -1
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Copyright (C) 2015-2017
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
+42 -11
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Copyright (C) 2015-2017
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
@@ -17,10 +17,10 @@
# 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 CommandHandler class """
import warnings
from .handler import Handler
from telegram import Update
from telegram.utils.deprecate import deprecate
class CommandHandler(Handler):
@@ -30,10 +30,15 @@ class CommandHandler(Handler):
name and/or some additional text.
Args:
command (str): The name of the command this handler should listen for.
command (str|list): The name of the command or list of command this handler should
listen for.
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.
filters (telegram.ext.BaseFilter): A filter inheriting from
:class:`telegram.ext.filters.BaseFilter`. Standard filters can be found in
:class:`telegram.ext.filters.Filters`. Filters can be combined using bitwise
operators (& for and, | for or).
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
@@ -62,6 +67,7 @@ class CommandHandler(Handler):
def __init__(self,
command,
callback,
filters=None,
allow_edited=False,
pass_args=False,
pass_update_queue=False,
@@ -74,17 +80,47 @@ class CommandHandler(Handler):
pass_job_queue=pass_job_queue,
pass_user_data=pass_user_data,
pass_chat_data=pass_chat_data)
self.command = command
try:
_str = basestring # Python 2
except NameError:
_str = str # Python 3
if isinstance(command, _str):
self.command = [command]
else:
self.command = command
self.filters = filters
self.allow_edited = allow_edited
self.pass_args = pass_args
# We put this up here instead of with the rest of checking code
# in check_update since we don't wanna spam a ton
if isinstance(self.filters, list):
warnings.warn('Using a list of filters in MessageHandler is getting '
'deprecated, please use bitwise operators (& and |) '
'instead. More info: https://git.io/vPTbc.')
def check_update(self, update):
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)
if message.text:
command = message.text[1:].split(' ')[0].split('@')
command.append(
message.bot.username) # in case the command was send without a username
if self.filters is None:
res = True
elif isinstance(self.filters, list):
res = any(func(message) for func in self.filters)
else:
res = self.filters(message)
return res and (message.text.startswith('/') and command[0] in self.command
and command[1].lower() == message.bot.username.lower())
else:
return False
else:
return False
@@ -98,8 +134,3 @@ class CommandHandler(Handler):
optional_args['args'] = message.text.split()[1:]
return self.callback(dispatcher.bot, update, **optional_args)
# old non-PEP8 Handler methods
m = "telegram.CommandHandler."
checkUpdate = deprecate(check_update, m + "checkUpdate", m + "check_update")
handleUpdate = deprecate(handle_update, m + "handleUpdate", m + "handle_update")
+79 -13
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Copyright (C) 2015-2017
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
@@ -21,14 +21,16 @@
import logging
from telegram import Update
from telegram.ext import Handler
from telegram.utils.helpers import extract_chat_and_user
from telegram.ext import (Handler, CallbackQueryHandler, InlineQueryHandler,
ChosenInlineResultHandler)
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.
A handler to hold a conversation with a single user by managing four collections of other
handlers. Note that neither posts in Telegram Channels, nor group interactions with multiple
users are managed by instances of this class.
The first collection, a ``list`` named ``entry_points``, is used to initiate the conversation,
for example with a ``CommandHandler`` or ``RegexHandler``.
@@ -86,7 +88,10 @@ class ConversationHandler(Handler):
fallbacks,
allow_reentry=False,
run_async_timeout=None,
timed_out_behavior=None):
timed_out_behavior=None,
per_chat=True,
per_user=True,
per_message=False):
self.entry_points = entry_points
""":type: list[telegram.ext.Handler]"""
@@ -104,21 +109,76 @@ class ConversationHandler(Handler):
""":type: list[telegram.ext.Handler]"""
self.conversations = dict()
""":type: dict[(int, int): str]"""
self.per_user = per_user
self.per_chat = per_chat
self.per_message = per_message
""":type: dict[tuple: object]"""
self.current_conversation = None
self.current_handler = None
self.logger = logging.getLogger(__name__)
if not any((self.per_user, self.per_chat, self.per_message)):
raise ValueError("'per_user', 'per_chat' and 'per_message' can't all be 'False'")
if self.per_message and not self.per_chat:
logging.warning("If 'per_message=True' is used, 'per_chat=True' should also be used, "
"since message IDs are not globally unique.")
all_handlers = list()
all_handlers.extend(entry_points)
all_handlers.extend(fallbacks)
for state_handlers in states.values():
all_handlers.extend(state_handlers)
if self.per_message:
for handler in all_handlers:
if not isinstance(handler, CallbackQueryHandler):
logging.warning("If 'per_message=True', all entry points and state handlers"
" must be 'CallbackQueryHandler', since no other handlers "
"have a message context.")
else:
for handler in all_handlers:
if isinstance(handler, CallbackQueryHandler):
logging.warning("If 'per_message=False', 'CallbackQueryHandler' will not be "
"tracked for every message.")
if self.per_chat:
for handler in all_handlers:
if isinstance(handler, (InlineQueryHandler, ChosenInlineResultHandler)):
logging.warning("If 'per_chat=True', 'InlineQueryHandler' can not be used, "
"since inline queries have no chat context.")
def _get_key(self, update):
chat = update.effective_chat
user = update.effective_user
key = list()
if self.per_chat:
key.append(chat.id)
if self.per_user:
key.append(user.id)
if self.per_message:
key.append(update.callback_query.inline_message_id
or update.callback_query.message.message_id)
return tuple(key)
def check_update(self, update):
if not isinstance(update, Update):
# Ignore messages in channels
if (not isinstance(update, Update) or update.channel_post or self.per_chat
and (update.inline_query or update.chosen_inline_result) or self.per_message
and not update.callback_query or update.callback_query and self.per_chat
and not update.callback_query.message):
return False
chat, user = extract_chat_and_user(update)
key = (chat.id, user.id) if chat else (None, user.id)
key = self._get_key(update)
state = self.conversations.get(key)
# Resolve promises
@@ -126,10 +186,16 @@ class ConversationHandler(Handler):
self.logger.debug('waiting for promise...')
old_state, new_state = state
new_state.result(timeout=self.run_async_timeout)
error = False
try:
res = new_state.result(timeout=self.run_async_timeout)
except Exception as exc:
self.logger.exception("Promise function raised exception")
self.logger.exception("{}".format(exc))
error = True
if new_state.done.is_set():
self.update_state(new_state.result(), key)
if not error and new_state.done.is_set():
self.update_state(res, key)
state = self.conversations.get(key)
else:
+2 -15
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Copyright (C) 2015-2017
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
@@ -32,7 +32,6 @@ from future.builtins import range
from telegram import TelegramError
from telegram.ext.handler import Handler
from telegram.utils.deprecate import deprecate
from telegram.utils.promise import Promise
logging.getLogger(__name__).addHandler(logging.NullHandler())
@@ -159,11 +158,7 @@ class Dispatcher(object):
len(self.__async_threads))
break
try:
promise.run()
except:
self.logger.exception("run_async function raised exception")
promise.run()
def run_async(self, func, *args, **kwargs):
"""Queue a function (with given args/kwargs) to be run asynchronously.
@@ -372,11 +367,3 @@ class Dispatcher(object):
for callback in self.error_handlers:
callback(self.bot, update, error)
# old non-PEP8 Dispatcher methods
m = "telegram.dispatcher."
addHandler = deprecate(add_handler, m + "AddHandler", m + "add_handler")
removeHandler = deprecate(remove_handler, m + "removeHandler", m + "remove_handler")
addErrorHandler = deprecate(add_error_handler, m + "addErrorHandler", m + "add_error_handler")
removeErrorHandler = deprecate(remove_error_handler, m + "removeErrorHandler",
m + "remove_error_handler")
+43 -1
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Copyright (C) 2015-2017
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
@@ -17,6 +17,7 @@
# 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 Filters for use with the MessageHandler class """
from telegram import Chat
class BaseFilter(object):
@@ -32,9 +33,14 @@ class BaseFilter(object):
>>> (Filters.audio | Filters.video)
Not:
>>> ~ Filters.command
Also works with more than two filters:
>>> (Filters.text & (Filters.entity(URL) | Filters.entity(TEXT_LINK)))
>>> Filters.text & (~ Filters.forwarded)
If you want to create your own filters create a class inheriting from this class and implement
a `filter` method that returns a boolean: `True` if the message should be handled, `False`
@@ -51,10 +57,32 @@ class BaseFilter(object):
def __or__(self, other):
return MergedFilter(self, or_filter=other)
def __invert__(self):
return InvertedFilter(self)
def filter(self, message):
raise NotImplementedError
class InvertedFilter(BaseFilter):
"""Represents a filter that has been inverted.
Args:
f: The filter to invert
"""
def __init__(self, f):
self.f = f
def filter(self, message):
return not self.f(message)
def __str__(self):
return "<telegram.ext.filters.InvertedFilter inverting {}>".format(self.f)
__repr__ = __str__
class MergedFilter(BaseFilter):
"""Represents a filter consisting of two other filters.
@@ -221,3 +249,17 @@ class Filters(object):
def filter(self, message):
return any([entity.type == self.entity_type for entity in message.entities])
class _Private(BaseFilter):
def filter(self, message):
return message.chat.type == Chat.PRIVATE
private = _Private()
class _Group(BaseFilter):
def filter(self, message):
return message.chat.type in [Chat.GROUP, Chat.SUPERGROUP]
group = _Group()
+4 -13
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Copyright (C) 2015-2017
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
@@ -19,9 +19,6 @@
""" This module contains the base class for handlers as used by the
Dispatcher """
from telegram.utils.deprecate import deprecate
from telegram.utils.helpers import extract_chat_and_user
class Handler(object):
"""
@@ -105,19 +102,13 @@ class Handler(object):
if self.pass_job_queue:
optional_args['job_queue'] = dispatcher.job_queue
if self.pass_user_data or self.pass_chat_data:
chat, user = extract_chat_and_user(update)
chat = update.effective_chat
user = update.effective_user
if self.pass_user_data:
optional_args['user_data'] = dispatcher.user_data[user.id]
optional_args['user_data'] = dispatcher.user_data[user.id if user else None]
if self.pass_chat_data:
optional_args['chat_data'] = dispatcher.chat_data[chat.id if chat else None]
return optional_args
# old non-PEP8 Handler methods
m = "telegram.Handler."
checkUpdate = deprecate(check_update, m + "checkUpdate", m + "check_update")
handleUpdate = deprecate(handle_update, m + "handleUpdate", m + "handle_update")
collectOptionalArgs = deprecate(collect_optional_args, m + "collectOptionalArgs",
m + "collect_optional_args")
+1 -1
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Copyright (C) 2015-2017
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
+275 -53
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Copyright (C) 2015-2017
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
@@ -22,6 +22,7 @@ import logging
import time
import warnings
import datetime
import weakref
from numbers import Number
from threading import Thread, Lock, Event
from queue import PriorityQueue, Empty
@@ -68,40 +69,182 @@ class JobQueue(object):
Args:
job (telegram.ext.Job): The ``Job`` instance representing the new job
next_t (Optional[int, float, datetime.timedelta]): Time in which the job
should be executed first. Defaults to ``job.interval``. ``int`` and ``float``
will be interpreted as seconds.
next_t (Optional[int, float, datetime.timedelta, datetime.datetime, datetime.time]):
Time in or at which the job should run for the first time. This parameter will be
interpreted depending on its type.
``int`` or ``float`` will be interpreted as "seconds from now" in which the job
should run.
``datetime.timedelta`` will be interpreted as "time from now" in which the job
should run.
``datetime.datetime`` will be interpreted as a specific date and time at which the
job should run.
``datetime.time`` will be interpreted as a specific time at which the job should
run. This could be either today or, if the time has already passed, tomorrow.
"""
warnings.warn("'JobQueue.put' is being deprecated, use 'JobQueue.run_once', "
"'JobQueue.run_daily' or 'JobQueue.run_repeating' instead")
if job.job_queue is None:
job.job_queue = self
self._put(job, next_t=next_t)
def _put(self, job, next_t=None, last_t=None):
"""Queue a new job.
Args:
job (telegram.ext.Job): The ``Job`` instance representing the new job
next_t (Optional[int, float, datetime.timedelta, datetime.datetime, datetime.time]):
Time in or at which the job should run for the first time. This parameter will be
interpreted depending on its type.
* ``int`` or ``float`` will be interpreted as "seconds from now" in which the job
should run.
* ``datetime.timedelta`` will be interpreted as "time from now" in which the job
should run.
* ``datetime.datetime`` will be interpreted as a specific date and time at which
the job should run.
* ``datetime.time`` will be interpreted as a specific time of day at which the job
should run. This could be either today or, if the time has already passed,
tomorrow.
last_t (Optional[float]): Timestamp of the time when ``job`` was scheduled for in the
last ``put`` call. If provided, it will be used to calculate the next timestamp
more accurately by accounting for the execution time of the job (and possibly
others). If None, `now` will be assumed.
"""
job.job_queue = self
if next_t is None:
interval = job.interval
next_t = job.interval
if next_t is None:
raise ValueError('next_t is None')
if isinstance(next_t, datetime.datetime):
next_t = (next_t - datetime.datetime.now()).total_seconds()
elif isinstance(next_t, datetime.time):
next_datetime = datetime.datetime.combine(datetime.date.today(), next_t)
if datetime.datetime.now().time() > next_t:
next_datetime += datetime.timedelta(days=1)
next_t = (next_datetime - datetime.datetime.now()).total_seconds()
if isinstance(interval, Number):
next_t = interval
elif isinstance(interval, datetime.timedelta):
next_t = interval.total_seconds()
else:
raise ValueError("The interval argument should be of type datetime.timedelta,"
" int or float")
elif isinstance(next_t, datetime.timedelta):
next_t = next_t.total_second()
next_t = next_t.total_seconds()
now = time.time()
next_t += now
next_t += last_t or time.time()
self.logger.debug('Putting job %s with t=%f', job.name, next_t)
self.queue.put((next_t, job))
# Wake up the loop if this job should be executed next
self._set_next_peek(next_t)
def run_once(self, callback, when, context=None, name=None):
"""Creates a new ``Job`` that runs once and adds it to the queue.
Args:
callback (function): The callback function that should be executed by the new job. It
should take two parameters ``bot`` and ``job``, where ``job`` is the ``Job``
instance. It can be used to access it's ``context`` or change it to a repeating
job.
when (int, float, datetime.timedelta, datetime.datetime, datetime.time):
Time in or at which the job should run. This parameter will be interpreted
depending on its type.
* ``int`` or ``float`` will be interpreted as "seconds from now" in which the job
should run.
* ``datetime.timedelta`` will be interpreted as "time from now" in which the job
should run.
* ``datetime.datetime`` will be interpreted as a specific date and time at which
the job should run.
* ``datetime.time`` will be interpreted as a specific time of day at which the job
should run. This could be either today or, if the time has already passed,
tomorrow.
context (Optional[object]): Additional data needed for the callback function. Can be
accessed through ``job.context`` in the callback. Defaults to ``None``
name (Optional[str]): The name of the new job. Defaults to ``callback.__name__``
Returns:
Job: The new ``Job`` instance that has been added to the job queue.
"""
job = Job(callback, repeat=False, context=context, name=name, job_queue=self)
self._put(job, next_t=when)
return job
def run_repeating(self, callback, interval, first=None, context=None, name=None):
"""Creates a new ``Job`` that runs once and adds it to the queue.
Args:
callback (function): The callback function that should be executed by the new job. It
should take two parameters ``bot`` and ``job``, where ``job`` is the ``Job``
instance. It can be used to access it's ``context``, terminate the job or change
its interval.
interval (int, float, datetime.timedelta): The interval in which the job will run.
If it is an ``int`` or a ``float``, it will be interpreted as seconds.
first (int, float, datetime.timedelta, datetime.datetime, datetime.time):
* ``int`` or ``float`` will be interpreted as "seconds from now" in which the job
should run.
* ``datetime.timedelta`` will be interpreted as "time from now" in which the job
should run.
* ``datetime.datetime`` will be interpreted as a specific date and time at which
the job should run.
* ``datetime.time`` will be interpreted as a specific time of day at which the job
should run. This could be either today or, if the time has already passed,
tomorrow.
Defaults to ``interval``
context (Optional[object]): Additional data needed for the callback function. Can be
accessed through ``job.context`` in the callback. Defaults to ``None``
name (Optional[str]): The name of the new job. Defaults to ``callback.__name__``
Returns:
Job: The new ``Job`` instance that has been added to the job queue.
"""
job = Job(callback,
interval=interval,
repeat=True,
context=context,
name=name,
job_queue=self)
self._put(job, next_t=first)
return job
def run_daily(self, callback, time, days=Days.EVERY_DAY, context=None, name=None):
"""Creates a new ``Job`` that runs once and adds it to the queue.
Args:
callback (function): The callback function that should be executed by the new job. It
should take two parameters ``bot`` and ``job``, where ``job`` is the ``Job``
instance. It can be used to access it's ``context`` or terminate the job.
time (datetime.time): Time of day at which the job should run.
days (Optional[tuple[int]]): Defines on which days of the week the job should run.
Defaults to ``Days.EVERY_DAY``
context (Optional[object]): Additional data needed for the callback function. Can be
accessed through ``job.context`` in the callback. Defaults to ``None``
name (Optional[str]): The name of the new job. Defaults to ``callback.__name__``
Returns:
Job: The new ``Job`` instance that has been added to the job queue.
"""
job = Job(callback,
interval=datetime.timedelta(days=1),
repeat=True,
days=days,
context=context,
name=name,
job_queue=self)
self._put(job, next_t=time)
return job
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:
@@ -126,9 +269,9 @@ class JobQueue(object):
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.
# 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!")
@@ -136,7 +279,7 @@ class JobQueue(object):
self._set_next_peek(t)
break
if job._remove.is_set():
if job.removed:
self.logger.debug('Removing job %s', job.name)
continue
@@ -146,15 +289,17 @@ class JobQueue(object):
if any(day == current_week_day for day in job.days):
self.logger.debug('Running job %s', job.name)
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)
if job.repeat and not job.removed:
self._put(job, last_t=t)
else:
self.logger.debug('Dropping non-repeating or removed job %s', job.name)
def start(self):
"""
@@ -169,7 +314,6 @@ class JobQueue(object):
self.__thread = Thread(target=self._main_loop, name="job_queue")
self.__thread.start()
self.logger.debug('%s thread started', self.__class__.__name__)
else:
self.__start_lock.release()
@@ -182,7 +326,7 @@ class JobQueue(object):
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()
tmout = self._next_peek - time.time() if self._next_peek else None
self._next_peek = None
self.__tick.clear()
@@ -216,46 +360,58 @@ class Job(object):
"""This class encapsulates a Job
Attributes:
callback (function):
interval (float):
days: (tuple)
repeat (bool):
name (str):
callback (function): The function that the job executes when it's due
interval (int, float, datetime.timedelta): The interval in which the job runs
days (tuple[int]): A tuple of ``int`` values that determine on which days of the week the
job runs
repeat (bool): If the job runs periodically or only once
name (str): The name of this job
job_queue (JobQueue): The ``JobQueue`` this job belongs to
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 ([int, float, datetime.timedelta]): The interval in which the job will execute its
callback function. ``int`` and ``float`` will be interpreted as seconds.
interval (Optional[int, float, datetime.timedelta]): The interval in which the job will
execute its callback function. ``int`` and ``float`` will be interpreted as seconds.
If you don't set this value, you must set ``repeat=False`` and specify ``next_t`` when
you put the job into the job queue.
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``
days (Tuple): Defines on which days the job should be ran.
days (Optional[tuple[int]]): Defines on which days of the week the job should run.
Defaults to ``Days.EVERY_DAY``
name (Optional[str]): The name of this job. Defaults to ``callback.__name__``
job_queue (Optional[class:`telegram.ext.JobQueue`]): The ``JobQueue`` this job belongs to.
Only optional for backward compatibility with ``JobQueue.put()``.
"""
job_queue = None
def __init__(self, callback, interval, repeat=True, context=None, days=Days.EVERY_DAY):
def __init__(self,
callback,
interval=None,
repeat=True,
context=None,
days=Days.EVERY_DAY,
name=None,
job_queue=None):
self.callback = callback
self.context = context
self.name = name or callback.__name__
self._repeat = repeat
self._interval = None
self.interval = interval
self.repeat = repeat
self.context = context
if not isinstance(days, tuple):
raise ValueError("The 'days argument should be of type 'tuple'")
if not all(isinstance(day, int) for day in days):
raise ValueError("The elements of the 'days' argument should be of type 'int'")
if not all(day >= 0 and day <= 6 for day in days):
raise ValueError("The elements of the 'days' argument should be from 0 up to and "
"including 6")
self._days = None
self.days = days
self.name = callback.__name__
self._job_queue = weakref.proxy(job_queue) if job_queue is not None else None
self._remove = Event()
self._enabled = Event()
self._enabled.set()
@@ -268,20 +424,86 @@ class Job(object):
"""
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):
@property
def removed(self):
return self._remove.is_set()
@property
def enabled(self):
return self._enabled.is_set()
def set_enabled(self, status):
@enabled.setter
def enabled(self, status):
if status:
self._enabled.set()
else:
self._enabled.clear()
enabled = property(is_enabled, set_enabled)
@property
def interval(self):
return self._interval
@interval.setter
def interval(self, interval):
if interval is None and self.repeat:
raise ValueError("The 'interval' can not be 'None' when 'repeat' is set to 'True'")
if not (interval is None or isinstance(interval, (Number, datetime.timedelta))):
raise ValueError("The 'interval' must be of type 'datetime.timedelta',"
" 'int' or 'float'")
self._interval = interval
@property
def interval_seconds(self):
if isinstance(self.interval, datetime.timedelta):
return self.interval.total_seconds()
else:
return self.interval
@property
def repeat(self):
return self._repeat
@repeat.setter
def repeat(self, repeat):
if self.interval is None and repeat:
raise ValueError("'repeat' can not be set to 'True' when no 'interval' is set")
self._repeat = repeat
@property
def days(self):
return self._days
@days.setter
def days(self, days):
if not isinstance(days, tuple):
raise ValueError("The 'days' argument should be of type 'tuple'")
if not all(isinstance(day, int) for day in days):
raise ValueError("The elements of the 'days' argument should be of type 'int'")
if not all(0 <= day <= 6 for day in days):
raise ValueError("The elements of the 'days' argument should be from 0 up to and "
"including 6")
self._days = days
@property
def job_queue(self):
""" :rtype: JobQueue """
return self._job_queue
@job_queue.setter
def job_queue(self, job_queue):
# Property setter for backward compatibility with JobQueue.put()
if not self._job_queue:
self._job_queue = weakref.proxy(job_queue)
else:
raise RuntimeError("The 'job_queue' attribute can only be set once.")
def __lt__(self, other):
return False
+22 -26
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Copyright (C) 2015-2017
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
@@ -21,7 +21,6 @@ import warnings
from .handler import Handler
from telegram import Update
from telegram.utils.deprecate import deprecate
class MessageHandler(Handler):
@@ -38,8 +37,6 @@ 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``
@@ -53,8 +50,12 @@ class MessageHandler(Handler):
For each update in the same chat, it will be the same ``dict``. Default is ``False``.
message_updates (Optional[bool]): Should "normal" message updates be handled? Default is
``True``.
channel_posts_updates (Optional[bool]): Should channel posts updates be handled? Default is
allow_edited (Optional[bool]): If the handler should also accept edited messages.
Default is ``False`` - Deprecated. use edited updates instead.
channel_post_updates (Optional[bool]): Should channel posts updates be handled? Default is
``True``.
edited_updates (Optional[bool]): Should "edited" message updates be handled? Default is
``False``.
"""
@@ -67,9 +68,14 @@ class MessageHandler(Handler):
pass_user_data=False,
pass_chat_data=False,
message_updates=True,
channel_posts_updates=True):
if not message_updates and not channel_posts_updates:
raise ValueError('Both message_updates & channel_post_updates are False')
channel_post_updates=True,
edited_updates=False):
if not message_updates and not channel_post_updates and not edited_updates:
raise ValueError(
'message_updates, channel_post_updates and edited_updates are all False')
if allow_edited:
warnings.warn('allow_edited is getting deprecated, please use edited_updates instead')
edited_updates = allow_edited
super(MessageHandler, self).__init__(
callback,
@@ -78,9 +84,9 @@ class MessageHandler(Handler):
pass_user_data=pass_user_data,
pass_chat_data=pass_chat_data)
self.filters = filters
self.allow_edited = allow_edited
self.message_updates = message_updates
self.channel_posts_updates = channel_posts_updates
self.channel_post_updates = channel_post_updates
self.edited_updates = edited_updates
# We put this up here instead of with the rest of checking code
# in check_update since we don't wanna spam a ton
@@ -89,24 +95,19 @@ class MessageHandler(Handler):
'deprecated, please use bitwise operators (& and |) '
'instead. More info: https://git.io/vPTbc.')
def _is_allowed_message(self, update):
return (self.message_updates
and (update.message or (update.edited_message and self.allow_edited)))
def _is_allowed_channel_post(self, update):
return (self.channel_posts_updates
and (update.channel_post or (update.edited_channel_post and self.allow_edited)))
def _is_allowed_update(self, update):
return any([(self.message_updates and update.message),
(self.edited_updates and update.edited_message),
(self.channel_post_updates and update.channel_post)])
def check_update(self, update):
if (isinstance(update, Update)
and (self._is_allowed_message(update) or self._is_allowed_channel_post(update))):
if isinstance(update, Update) and self._is_allowed_update(update):
if not self.filters:
res = True
else:
message = (update.message or update.edited_message or update.channel_post
or update.edited_channel_post)
message = update.effective_message
if isinstance(self.filters, list):
res = any(func(message) for func in self.filters)
else:
@@ -121,8 +122,3 @@ class MessageHandler(Handler):
optional_args = self.collect_optional_args(dispatcher, update)
return self.callback(dispatcher.bot, update, **optional_args)
# old non-PEP8 Handler methods
m = "telegram.MessageHandler."
checkUpdate = deprecate(check_update, m + "checkUpdate", m + "check_update")
handleUpdate = deprecate(handle_update, m + "handleUpdate", m + "handle_update")
+330
View File
@@ -0,0 +1,330 @@
#!/usr/bin/env python
#
# Module author:
# Tymofii A. Khodniev (thodnev) <thodnev@mail.ru>
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2017
# 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/]
'''A throughput-limiting message processor for Telegram bots'''
from telegram.utils import promise
import functools
import sys
import time
import threading
if sys.version_info.major > 2:
import queue as q
else:
import Queue as q
# We need to count < 1s intervals, so the most accurate timer is needed
# Starting from Python 3.3 we have time.perf_counter which is the clock
# with the highest resolution available to the system, so let's use it there.
# In Python 2.7, there's no perf_counter yet, so fallback on what we have:
# on Windows, the best available is time.clock while time.time is on
# another platforms (M. Lutz, "Learning Python," 4ed, p.630-634)
if sys.version_info.major == 3 and sys.version_info.minor >= 3:
curtime = time.perf_counter # pylint: disable=E1101
else:
curtime = time.clock if sys.platform[:3] == 'win' else time.time
class DelayQueueError(RuntimeError):
'''Indicates processing errors'''
pass
class DelayQueue(threading.Thread):
'''Processes callbacks from queue with specified throughput limits.
Creates a separate thread to process callbacks with delays.
Args:
queue (:obj:`queue.Queue`, optional): used to pass callbacks to
thread.
Creates `queue.Queue` implicitly if not provided.
burst_limit (:obj:`int`, optional): number of maximum callbacks to
process per time-window defined by `time_limit_ms`.
Defaults to 30.
time_limit_ms (:obj:`int`, optional): defines width of time-window
used when each processing limit is calculated.
Defaults to 1000.
exc_route (:obj:`callable`, optional): a callable, accepting 1
positional argument; used to route exceptions from processor
thread to main thread; is called on `Exception` subclass
exceptions.
If not provided, exceptions are routed through dummy handler,
which re-raises them.
autostart (:obj:`bool`, optional): if True, processor is started
immediately after object's creation; if False, should be
started manually by `start` method.
Defaults to True.
name (:obj:`str`, optional): thread's name.
Defaults to ``'DelayQueue-N'``, where N is sequential
number of object created.
'''
_instcnt = 0 # instance counter
def __init__(self,
queue=None,
burst_limit=30,
time_limit_ms=1000,
exc_route=None,
autostart=True,
name=None):
self._queue = queue if queue is not None else q.Queue()
self.burst_limit = burst_limit
self.time_limit = time_limit_ms / 1000
self.exc_route = (exc_route if exc_route is not None else self._default_exception_handler)
self.__exit_req = False # flag to gently exit thread
self.__class__._instcnt += 1
if name is None:
name = '%s-%s' % (self.__class__.__name__, self.__class__._instcnt)
super(DelayQueue, self).__init__(name=name)
self.daemon = False
if autostart: # immediately start processing
super(DelayQueue, self).start()
def run(self):
'''Do not use the method except for unthreaded testing purposes,
the method normally is automatically called by `start` method.
'''
times = [] # used to store each callable processing time
while True:
item = self._queue.get()
if self.__exit_req:
return # shutdown thread
# delay routine
now = curtime()
t_delta = now - self.time_limit # calculate early to improve perf.
if times and t_delta > times[-1]:
# if last call was before the limit time-window
# used to impr. perf. in long-interval calls case
times = [now]
else:
# collect last in current limit time-window
times = [t for t in times if t >= t_delta]
times.append(now)
if len(times) >= self.burst_limit: # if throughput limit was hit
time.sleep(times[1] - t_delta)
# finally process one
try:
func, args, kwargs = item
func(*args, **kwargs)
except Exception as exc: # re-route any exceptions
self.exc_route(exc) # to prevent thread exit
def stop(self, timeout=None):
'''Used to gently stop processor and shutdown its thread.
Args:
timeout (:obj:`float`): indicates maximum time to wait for
processor to stop and its thread to exit.
If timeout exceeds and processor has not stopped, method
silently returns. `is_alive` could be used afterwards
to check the actual status. If `timeout` set to None, blocks
until processor is shut down.
Defaults to None.
Returns:
None
'''
self.__exit_req = True # gently request
self._queue.put(None) # put something to unfreeze if frozen
super(DelayQueue, self).join(timeout=timeout)
@staticmethod
def _default_exception_handler(exc):
'''Dummy exception handler which re-raises exception in thread.
Could be possibly overwritten by subclasses.
'''
raise exc
def __call__(self, func, *args, **kwargs):
'''Used to process callbacks in throughput-limiting thread
through queue.
Args:
func (:obj:`callable`): the actual function (or any callable) that
is processed through queue.
*args: variable-length `func` arguments.
**kwargs: arbitrary keyword-arguments to `func`.
Returns:
None
'''
if not self.is_alive() or self.__exit_req:
raise DelayQueueError('Could not process callback in stopped thread')
self._queue.put((func, args, kwargs))
# The most straightforward way to implement this is to use 2 sequenital delay
# queues, like on classic delay chain schematics in electronics.
# So, message path is:
# msg --> group delay if group msg, else no delay --> normal msg delay --> out
# This way OS threading scheduler cares of timings accuracy.
# (see time.time, time.clock, time.perf_counter, time.sleep @ docs.python.org)
class MessageQueue(object):
'''Implements callback processing with proper delays to avoid hitting
Telegram's message limits.
Contains two `DelayQueue`s, for group and for all messages, interconnected
in delay chain. Callables are processed through *group* `DelayQueue`, then
through *all* `DelayQueue` for group-type messages. For non-group messages,
only the *all* `DelayQueue` is used.
Args:
all_burst_limit (:obj:`int`, optional): numer of maximum *all-type*
callbacks to process per time-window defined by
`all_time_limit_ms`.
Defaults to 30.
all_time_limit_ms (:obj:`int`, optional): defines width of *all-type*
time-window used when each processing limit is calculated.
Defaults to 1000 ms.
group_burst_limit (:obj:`int`, optional): numer of maximum *group-type*
callbacks to process per time-window defined by
`group_time_limit_ms`.
Defaults to 20.
group_time_limit_ms (:obj:`int`, optional): defines width of
*group-type* time-window used when each processing limit is
calculated.
Defaults to 60000 ms.
exc_route (:obj:`callable`, optional): a callable, accepting one
positional argument; used to route exceptions from processor
threads to main thread; is called on `Exception` subclass
exceptions.
If not provided, exceptions are routed through dummy handler,
which re-raises them.
autostart (:obj:`bool`, optional): if True, processors are started
immediately after object's creation; if False, should be
started manually by `start` method.
Defaults to True.
Attributes:
_all_delayq (:obj:`telegram.ext.messagequeue.DelayQueue`): actual
`DelayQueue` used for *all-type* callback processing
_group_delayq (:obj:`telegram.ext.messagequeue.DelayQueue`): actual
`DelayQueue` used for *group-type* callback processing
'''
def __init__(self,
all_burst_limit=30,
all_time_limit_ms=1000,
group_burst_limit=20,
group_time_limit_ms=60000,
exc_route=None,
autostart=True):
# create accoring delay queues, use composition
self._all_delayq = DelayQueue(
burst_limit=all_burst_limit,
time_limit_ms=all_time_limit_ms,
exc_route=exc_route,
autostart=autostart)
self._group_delayq = DelayQueue(
burst_limit=group_burst_limit,
time_limit_ms=group_time_limit_ms,
exc_route=exc_route,
autostart=autostart)
def start(self):
'''Method is used to manually start the `MessageQueue` processing
Returns:
None
'''
self._all_delayq.start()
self._group_delayq.start()
def stop(self, timeout=None):
self._group_delayq.stop(timeout=timeout)
self._all_delayq.stop(timeout=timeout)
stop.__doc__ = DelayQueue.stop.__doc__ or '' # reuse docsting if any
def __call__(self, promise, is_group_msg=False):
'''Processes callables in troughput-limiting queues to avoid
hitting limits (specified with \*_burst_limit and *\_time_limit_ms).
Args:
promise (:obj:`callable`): mainly the
:obj:`telegram.utils.promise.Promise` (see Notes for other
callables), that is processed in delay queues
is_group_msg (:obj:`bool`, optional): defines whether `promise`
would be processed in *group*+*all* `DelayQueue`s
(if set to ``True``), or only through *all* `DelayQueue`
(if set to ``False``), resulting in needed delays to avoid
hitting specified limits.
Defaults to ``True``.
Notes:
Method is designed to accept :obj:`telegram.utils.promise.Promise`
as `promise` argument, but other callables could be used too.
For example, lambdas or simple functions could be used to wrap
original func to be called with needed args.
In that case, be sure that either wrapper func does not raise
outside exceptions or the proper `exc_route` handler is provided.
Returns:
:obj:`callable` used as `promise` argument.
'''
if not is_group_msg: # ignore middle group delay
self._all_delayq(promise)
else: # use middle group delay
self._group_delayq(self._all_delayq, promise)
return promise
def queuedmessage(method):
'''A decorator to be used with `telegram.bot.Bot` send* methods.
Note:
As it probably wouldn't be a good idea to make this decorator a
property, it had been coded as decorator function, so it implies that
**first positional argument to wrapped MUST be self**\.
The next object attributes are used by decorator:
Attributes:
self._is_messages_queued_default (:obj:`bool`): Value to provide
class-defaults to `queued` kwarg if not provided during wrapped
method call.
self._msg_queue (:obj:`telegram.ext.messagequeue.MessageQueue`):
The actual `MessageQueue` used to delay outbond messages according
to specified time-limits.
Wrapped method starts accepting the next kwargs:
Args:
queued (:obj:`bool`, optional): if set to ``True``, the `MessageQueue`
is used to process output messages.
Defaults to `self._is_queued_out`.
isgroup (:obj:`bool`, optional): if set to ``True``, the message is
meant to be group-type (as there's no obvious way to determine its
type in other way at the moment). Group-type messages could have
additional processing delay according to limits set in
`self._out_queue`.
Defaults to ``False``.
Returns:
Either :obj:`telegram.utils.promise.Promise` in case call is queued,
or original method's return value if it's not.
'''
@functools.wraps(method)
def wrapped(self, *args, **kwargs):
queued = kwargs.pop('queued', self._is_messages_queued_default)
isgroup = kwargs.pop('isgroup', False)
if queued:
prom = promise.Promise(method, args, kwargs)
return self._msg_queue(prom, isgroup)
return method(self, *args, **kwargs)
return wrapped
+21 -5
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Copyright (C) 2015-2017
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
@@ -71,7 +71,10 @@ class RegexHandler(Handler):
pass_update_queue=False,
pass_job_queue=False,
pass_user_data=False,
pass_chat_data=False):
pass_chat_data=False,
allow_edited=False,
message_updates=True,
channel_post_updates=False):
super(RegexHandler, self).__init__(
callback,
pass_update_queue=pass_update_queue,
@@ -85,17 +88,30 @@ class RegexHandler(Handler):
self.pattern = pattern
self.pass_groups = pass_groups
self.pass_groupdict = pass_groupdict
self.allow_edited = allow_edited
self.message_updates = message_updates
self.channel_post_updates = channel_post_updates
def _is_allowed_message(self, update):
return (self.message_updates
and (update.message or (update.edited_message and self.allow_edited)))
def _is_allowed_channel_post(self, update):
return (self.channel_post_updates
and (update.channel_post or (update.edited_channel_post and self.allow_edited)))
def check_update(self, update):
if isinstance(update, Update) and update.message and update.message.text:
match = re.match(self.pattern, update.message.text)
if (isinstance(update, Update)
and (self._is_allowed_message(update) or self._is_allowed_channel_post(update))
and update.effective_message.text):
match = re.match(self.pattern, update.effective_message.text)
return bool(match)
else:
return False
def handle_update(self, update, dispatcher):
optional_args = self.collect_optional_args(dispatcher, update)
match = re.match(self.pattern, update.message.text)
match = re.match(self.pattern, update.effective_message.text)
if self.pass_groups:
optional_args['groups'] = match.groups()
+1 -7
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Copyright (C) 2015-2017
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
@@ -19,7 +19,6 @@
""" This module contains the StringCommandHandler class """
from .handler import Handler
from telegram.utils.deprecate import deprecate
class StringCommandHandler(Handler):
@@ -69,8 +68,3 @@ class StringCommandHandler(Handler):
optional_args['args'] = update.split()[1:]
return self.callback(dispatcher.bot, update, **optional_args)
# old non-PEP8 Handler methods
m = "telegram.StringCommandHandler."
checkUpdate = deprecate(check_update, m + "checkUpdate", m + "check_update")
handleUpdate = deprecate(handle_update, m + "handleUpdate", m + "handle_update")
+1 -7
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Copyright (C) 2015-2017
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
@@ -23,7 +23,6 @@ import re
from future.utils import string_types
from .handler import Handler
from telegram.utils.deprecate import deprecate
class StringRegexHandler(Handler):
@@ -84,8 +83,3 @@ class StringRegexHandler(Handler):
optional_args['groupdict'] = match.groupdict()
return self.callback(dispatcher.bot, update, **optional_args)
# old non-PEP8 Handler methods
m = "telegram.StringRegexHandler."
checkUpdate = deprecate(check_update, m + "checkUpdate", m + "check_update")
handleUpdate = deprecate(handle_update, m + "handleUpdate", m + "handle_update")
+1 -1
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Copyright (C) 2015-2017
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
+61 -19
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Copyright (C) 2015-2017
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
@@ -22,6 +22,7 @@ Telegram bots intuitive."""
import logging
import os
import ssl
import warnings
from threading import Thread, Lock, current_thread, Event
from time import sleep
import subprocess
@@ -61,6 +62,11 @@ class Updater(object):
bot (Optional[Bot]): A pre-initialized bot instance. If a pre-initizlied bot is used, it is
the user's responsibility to create it using a `Request` instance with a large enough
connection pool.
user_sig_handler (Optional[function]): Takes ``signum, frame`` as positional arguments.
This will be called when a signal is received, defaults are (SIGINT, SIGTERM, SIGABRT)
setable with Updater.idle(stop_signals=(signals))
request_kwargs (Optional[dict]): Keyword args to control the creation of a request object
(ignored if `bot` argument is used).
Raises:
ValueError: If both `token` and `bot` are passed or none of them.
@@ -68,7 +74,14 @@ class Updater(object):
"""
_request = None
def __init__(self, token=None, base_url=None, workers=4, bot=None):
def __init__(self,
token=None,
base_url=None,
workers=4,
bot=None,
user_sig_handler=None,
request_kwargs=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):
@@ -83,8 +96,13 @@ class Updater(object):
# * 1 for polling Updater (even if webhook is used, we can spare a connection)
# * 1 for JobQueue
# * 1 for main thread
self._request = Request(con_pool_size=workers + 4)
if request_kwargs is None:
request_kwargs = {}
if 'con_pool_size' not in request_kwargs:
request_kwargs['con_pool_size'] = workers + 4
self._request = Request(**request_kwargs)
self.bot = Bot(token, base_url, request=self._request)
self.user_sig_handler = user_sig_handler
self.update_queue = Queue()
self.job_queue = JobQueue(self.bot)
self.__exception_event = Event()
@@ -122,9 +140,11 @@ class Updater(object):
def start_polling(self,
poll_interval=0.0,
timeout=10,
network_delay=5.,
network_delay=None,
clean=False,
bootstrap_retries=0):
bootstrap_retries=0,
read_latency=2.,
allowed_updates=None):
"""
Starts polling updates from Telegram.
@@ -134,7 +154,8 @@ class Updater(object):
timeout (Optional[float]): Passed to Bot.getUpdates
network_delay (Optional[float]): Passed to Bot.getUpdates
network_delay: Deprecated. Will be honoured as `read_latency` for a while but will be
removed in the future.
clean (Optional[bool]): Whether to clean any pending updates on Telegram servers before
actually starting to poll. Default is False.
@@ -143,13 +164,24 @@ class Updater(object):
will retry on failures on the Telegram server.
| < 0 - retry indefinitely
| 0 - no retries (default)
| 0 - no retries (default)
| > 0 - retry up to X times
allowed_updates (Optional[list[str]]): Passed to Bot.getUpdates
read_latency (Optional[float|int]): Grace time in seconds for receiving the reply from
server. Will be added to the `timeout` value and used as the read timeout from
server (Default: 2).
Returns:
Queue: The update queue that can be filled from the main thread
"""
if network_delay is not None:
warnings.warn('network_delay is deprecated, use read_latency instead')
read_latency = network_delay
with self.__lock:
if not self.running:
self.running = True
@@ -158,7 +190,7 @@ class Updater(object):
self.job_queue.start()
self._init_thread(self.dispatcher.start, "dispatcher")
self._init_thread(self._start_polling, "updater", poll_interval, timeout,
network_delay, bootstrap_retries, clean)
read_latency, bootstrap_retries, clean, allowed_updates)
# Return the update queue so the main thread can insert updates
return self.update_queue
@@ -171,7 +203,8 @@ class Updater(object):
key=None,
clean=False,
bootstrap_retries=0,
webhook_url=None):
webhook_url=None,
allowed_updates=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
@@ -194,9 +227,10 @@ class Updater(object):
| < 0 - retry indefinitely
| 0 - no retries (default)
| > 0 - retry up to X times
webhook_url (Optional[str]): Explicitly specifiy the webhook url.
webhook_url (Optional[str]): Explicitly specify the webhook url.
Useful behind NAT, reverse proxy, etc. Default is derived from
`listen`, `port` & `url_path`.
allowed_updates (Optional[list[str]]): Passed to Bot.setWebhook
Returns:
Queue: The update queue that can be filled from the main thread
@@ -210,12 +244,13 @@ class Updater(object):
self.job_queue.start()
self._init_thread(self.dispatcher.start, "dispatcher"),
self._init_thread(self._start_webhook, "updater", listen, port, url_path, cert,
key, bootstrap_retries, clean, webhook_url)
key, bootstrap_retries, clean, webhook_url, allowed_updates)
# Return the update queue so the main thread can insert updates
return self.update_queue
def _start_polling(self, poll_interval, timeout, network_delay, bootstrap_retries, clean):
def _start_polling(self, poll_interval, timeout, read_latency, bootstrap_retries, clean,
allowed_updates):
"""
Thread target of thread 'updater'. Runs in background, pulls
updates from Telegram and inserts them in the update queue of the
@@ -225,12 +260,15 @@ class Updater(object):
cur_interval = poll_interval
self.logger.debug('Updater thread started')
self._bootstrap(bootstrap_retries, clean=clean, webhook_url='')
self._bootstrap(bootstrap_retries, clean=clean, webhook_url='', allowed_updates=None)
while self.running:
try:
updates = self.bot.getUpdates(
self.last_update_id, timeout=timeout, network_delay=network_delay)
self.last_update_id,
timeout=timeout,
read_latency=read_latency,
allowed_updates=allowed_updates)
except RetryAfter as e:
self.logger.info(str(e))
cur_interval = 0.5 + e.retry_after
@@ -270,7 +308,7 @@ class Updater(object):
return current_interval
def _start_webhook(self, listen, port, url_path, cert, key, bootstrap_retries, clean,
webhook_url):
webhook_url, allowed_updates):
self.logger.debug('Updater thread started')
use_ssl = cert is not None and key is not None
if not url_path.startswith('/'):
@@ -291,7 +329,8 @@ class Updater(object):
max_retries=bootstrap_retries,
clean=clean,
webhook_url=webhook_url,
cert=open(cert, 'rb'))
cert=open(cert, 'rb'),
allowed_updates=allowed_updates)
elif clean:
self.logger.warning("cleaning updates is not supported if "
"SSL-termination happens elsewhere; skipping")
@@ -321,18 +360,19 @@ class Updater(object):
def _gen_webhook_url(listen, port, url_path):
return 'https://{listen}:{port}{path}'.format(listen=listen, port=port, path=url_path)
def _bootstrap(self, max_retries, clean, webhook_url, cert=None):
def _bootstrap(self, max_retries, clean, webhook_url, allowed_updates, cert=None):
retries = 0
while 1:
try:
if clean:
# Disable webhook for cleaning
self.bot.setWebhook(webhook_url='')
self.bot.deleteWebhook()
self._clean_updates()
sleep(1)
self.bot.setWebhook(webhook_url=webhook_url, certificate=cert)
self.bot.setWebhook(
url=webhook_url, certificate=cert, allowed_updates=allowed_updates)
except (Unauthorized, InvalidToken):
raise
except TelegramError:
@@ -397,6 +437,8 @@ class Updater(object):
self.is_idle = False
if self.running:
self.stop()
if self.user_sig_handler:
self.user_sig_handler(signum, frame)
else:
self.logger.warning('Exiting immediately!')
import os
+36 -11
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Copyright (C) 2015-2017
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
@@ -40,16 +40,19 @@ class File(TelegramObject):
"""
def __init__(self, file_id, bot, file_size=0, file_path='', **kwargs):
def __init__(self, file_id, bot, file_size=None, file_path=None, **kwargs):
# Required
self.file_id = str(file_id)
# Optionals
self.file_size = int(file_size)
self.file_path = str(file_path)
self.file_size = file_size
if file_path:
self.file_path = str(file_path)
self.bot = bot
self._id_attrs = (self.file_id,)
@staticmethod
def de_json(data, bot):
"""
@@ -65,17 +68,39 @@ class File(TelegramObject):
return File(bot=bot, **data)
def download(self, custom_path=None):
def download(self, custom_path=None, out=None, timeout=None):
"""
Download this file. By default, the file is saved in the current working directory with its
original filename as reported by Telegram. If a ``custom_path`` is supplied, it will be
saved to that path instead. If ``out`` is defined, the file contents will be saved to that
object using the ``out.write`` method. ``custom_path`` and ``out`` are mutually exclusive.
Args:
custom_path (str):
custom_path (Optional[str]): Custom path.
out (Optional[object]): A file-like object. Must be opened in binary mode, if
applicable.
timeout (Optional[int|float]): If this value is specified, use it as the read timeout
from the server (instead of the one specified during creation of the connection
pool).
Raises:
ValueError: If both ``custom_path`` and ``out`` are passed.
"""
if custom_path is not None and out is not None:
raise ValueError('custom_path and out are mutually exclusive')
url = self.file_path
if custom_path:
filename = custom_path
else:
filename = basename(url)
if out:
buf = self.bot.request.retrieve(url)
out.write(buf)
self.bot.request.download(url, filename)
else:
if custom_path:
filename = custom_path
else:
filename = basename(url)
self.bot.request.download(url, filename, timeout=timeout)
+1 -1
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Copyright (C) 2015-2017
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
+4 -2
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Copyright (C) 2015-2017
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
@@ -48,7 +48,7 @@ class Game(TelegramObject):
title,
description,
photo,
text='',
text=None,
text_entities=None,
animation=None,
**kwargs):
@@ -73,6 +73,8 @@ class Game(TelegramObject):
if not data:
return None
data = super(Game, Game).de_json(data, bot)
data['photo'] = PhotoSize.de_list(data.get('photo'), bot)
data['text_entities'] = MessageEntity.de_list(data.get('text_entities'), bot)
data['animation'] = Animation.de_json(data.get('animation'), bot)
+3 -1
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Copyright (C) 2015-2017
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
@@ -49,6 +49,8 @@ class GameHighScore(TelegramObject):
if not data:
return None
data = super(GameHighScore, GameHighScore).de_json(data, bot)
data['user'] = User.de_json(data.get('user'), bot)
return GameHighScore(**data)
+11 -4
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Copyright (C) 2015-2017
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
@@ -51,7 +51,14 @@ class InlineKeyboardButton(TelegramObject):
"""
def __init__(self, text, url=None, callback_data=None, switch_inline_query=None, **kwargs):
def __init__(self,
text,
url=None,
callback_data=None,
switch_inline_query=None,
switch_inline_query_current_chat=None,
callback_game=None,
**kwargs):
# Required
self.text = text
@@ -59,8 +66,8 @@ class InlineKeyboardButton(TelegramObject):
self.url = url
self.callback_data = callback_data
self.switch_inline_query = switch_inline_query
self.switch_inline_query_current_chat = kwargs.get('switch_inline_query_current_chat')
self.callback_game = kwargs.get('callback_game')
self.switch_inline_query_current_chat = switch_inline_query_current_chat
self.callback_game = callback_game
@staticmethod
def de_json(data, bot):
+1 -1
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Copyright (C) 2015-2017
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
+3 -1
View File
@@ -2,7 +2,8 @@
# pylint: disable=R0902,R0912,R0913
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015 Leandro Toledo de Souza <devs@python-telegram-bot.org>
# Copyright (C) 2015-2017
# 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
@@ -55,6 +56,7 @@ class InlineQuery(TelegramObject):
self.location = location
self.bot = bot
self._id_attrs = (self.id,)
@staticmethod
def de_json(data, bot):
+3 -1
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Copyright (C) 2015-2017
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
@@ -41,6 +41,8 @@ class InlineQueryResult(TelegramObject):
self.type = str(type)
self.id = str(id)
self._id_attrs = (self.id,)
@staticmethod
def de_json(data, bot):
return super(InlineQueryResult, InlineQueryResult).de_json(data, bot)
+1 -1
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Copyright (C) 2015-2017
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
+1 -1
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Copyright (C) 2015-2017
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
+1 -1
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Copyright (C) 2015-2017
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
+1 -1
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Copyright (C) 2015-2017
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
+1 -1
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Copyright (C) 2015-2017
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
+1 -1
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Copyright (C) 2015-2017
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
+1 -1
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Copyright (C) 2015-2017
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
+1 -1
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Copyright (C) 2015-2017
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
+1 -1
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Copyright (C) 2015-2017
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
+1 -1
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Copyright (C) 2015-2017
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
+1 -1
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Copyright (C) 2015-2017
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
+1 -1
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Copyright (C) 2015-2017
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
+1 -1
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Copyright (C) 2015-2017
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
+1 -1
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Copyright (C) 2015-2017
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
+1 -1
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Copyright (C) 2015-2017
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
+1 -1
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Copyright (C) 2015-2017
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
+1 -1
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Copyright (C) 2015-2017
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
+1 -1
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Copyright (C) 2015-2017
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
+1 -1
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Copyright (C) 2015-2017
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
+1 -1
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Copyright (C) 2015-2017
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
+1 -1
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Copyright (C) 2015-2017
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
+1 -1
View File
@@ -2,7 +2,7 @@
# pylint: disable=W0622,E0611
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Copyright (C) 2015-2017
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
+1 -1
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Copyright (C) 2015-2017
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
+1 -1
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Copyright (C) 2015-2017
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
+1 -1
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Copyright (C) 2015-2017
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
+1 -1
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Copyright (C) 2015-2017
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
+3 -5
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Copyright (C) 2015-2017
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
@@ -37,10 +37,8 @@ class KeyboardButton(TelegramObject):
# Required
self.text = text
# Optionals
if request_contact:
self.request_contact = request_contact
if request_location:
self.request_location = request_location
self.request_contact = request_contact
self.request_location = request_location
@staticmethod
def de_json(data, bot):
+3 -1
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Copyright (C) 2015-2017
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
@@ -38,6 +38,8 @@ class Location(TelegramObject):
self.longitude = float(longitude)
self.latitude = float(latitude)
self._id_attrs = (self.longitude, self.latitude)
@staticmethod
def de_json(data, bot):
"""
+109 -10
View File
@@ -2,7 +2,7 @@
# pylint: disable=R0902,R0912,R0913
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Copyright (C) 2015-2017
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
@@ -18,13 +18,13 @@
# 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 an object that represents a Telegram Message."""
import sys
from datetime import datetime
from time import mktime
from telegram import (Audio, Contact, Document, Chat, Location, PhotoSize, Sticker, TelegramObject,
User, Video, Voice, Venue, MessageEntity, Game)
from telegram.utils.helpers import escape_html, escape_markdown
class Message(TelegramObject):
@@ -117,7 +117,7 @@ class Message(TelegramObject):
forward_date=None,
reply_to_message=None,
edit_date=None,
text='',
text=None,
entities=None,
audio=None,
document=None,
@@ -125,19 +125,19 @@ class Message(TelegramObject):
sticker=None,
video=None,
voice=None,
caption='',
caption=None,
contact=None,
location=None,
venue=None,
new_chat_member=None,
left_chat_member=None,
new_chat_title='',
new_chat_title=None,
new_chat_photo=None,
delete_chat_photo=False,
group_chat_created=False,
supergroup_chat_created=False,
migrate_to_chat_id=0,
migrate_from_chat_id=0,
migrate_to_chat_id=None,
migrate_from_chat_id=None,
channel_chat_created=False,
pinned_message=None,
forward_from_message_id=None,
@@ -174,14 +174,16 @@ class Message(TelegramObject):
self.delete_chat_photo = bool(delete_chat_photo)
self.group_chat_created = bool(group_chat_created)
self.supergroup_chat_created = bool(supergroup_chat_created)
self.migrate_to_chat_id = int(migrate_to_chat_id)
self.migrate_from_chat_id = int(migrate_from_chat_id)
self.migrate_to_chat_id = migrate_to_chat_id
self.migrate_from_chat_id = migrate_from_chat_id
self.channel_chat_created = bool(channel_chat_created)
self.pinned_message = pinned_message
self.forward_from_message_id = forward_from_message_id
self.bot = bot
self._id_attrs = (self.message_id,)
@property
def chat_id(self):
"""int: Short for :attr:`Message.chat.id`"""
@@ -200,8 +202,10 @@ class Message(TelegramObject):
if not data:
return None
data = super(Message, Message).de_json(data, bot)
data['from_user'] = User.de_json(data.get('from'), bot)
data['date'] = datetime.fromtimestamp(data['date'])
data['date'] = Message._fromtimestamp(data['date'])
data['chat'] = Chat.de_json(data.get('chat'), bot)
data['entities'] = MessageEntity.de_list(data.get('entities'), bot)
data['forward_from'] = User.de_json(data.get('forward_from'), bot)
@@ -539,6 +543,21 @@ class Message(TelegramObject):
return self.bot.edit_message_reply_markup(
chat_id=self.chat_id, message_id=self.message_id, *args, **kwargs)
def delete(self, *args, **kwargs):
"""
Shortcut for
>>> bot.delete_message(chat_id=message.chat_id,
... message_id=message.message_id,
... *args, **kwargs)
Returns:
bool: On success, `True` is returned.
"""
return self.bot.delete_message(
chat_id=self.chat_id, message_id=self.message_id, *args, **kwargs)
def parse_entity(self, entity):
"""
Returns the text from a given :class:`telegram.MessageEntity`.
@@ -584,6 +603,7 @@ class Message(TelegramObject):
Returns:
dict[:class:`telegram.MessageEntity`, ``str``]: A dictionary of entities mapped to the
text that belongs to them, calculated based on UTF-16 codepoints.
"""
if types is None:
types = MessageEntity.ALL_TYPES
@@ -592,3 +612,82 @@ class Message(TelegramObject):
entity: self.parse_entity(entity)
for entity in self.entities if entity.type in types
}
@property
def text_html(self):
"""
Creates an html-formatted string from the markup entities found in the message
(uses ``parse_entities``).
Use this if you want to retrieve the original string sent by the bot, as opposed to the
plain text with corresponding markup entities.
Returns:
str
"""
entities = self.parse_entities()
message_text = self.text
markdown_text = ''
last_offset = 0
for entity, text in sorted(entities.items(), key=(lambda item: item[0].offset)):
text = escape_html(text)
if entity.type == MessageEntity.TEXT_LINK:
insert = '<a href="{}">{}</a>'.format(entity.url, text)
elif entity.type == MessageEntity.BOLD:
insert = '<b>' + text + '</b>'
elif entity.type == MessageEntity.ITALIC:
insert = '<i>' + text + '</i>'
elif entity.type == MessageEntity.CODE:
insert = '<code>' + text + '</code>'
elif entity.type == MessageEntity.PRE:
insert = '<pre>' + text + '</pre>'
else:
insert = text
markdown_text += escape_html(message_text[last_offset:entity.offset]) + insert
last_offset = entity.offset + entity.length
markdown_text += escape_html(message_text[last_offset:])
return markdown_text
@property
def text_markdown(self):
"""
Creates a markdown-formatted string from the markup entities found in the message
(uses ``parse_entities``).
Use this if you want to retrieve the original string sent by the bot, as opposed to the
plain text with corresponding markup entities.
Returns:
str
"""
entities = self.parse_entities()
message_text = self.text
markdown_text = ''
last_offset = 0
for entity, text in sorted(entities.items(), key=(lambda item: item[0].offset)):
text = escape_markdown(text)
if entity.type == MessageEntity.TEXT_LINK:
insert = '[{}]({})'.format(text, entity.url)
elif entity.type == MessageEntity.BOLD:
insert = '*' + text + '*'
elif entity.type == MessageEntity.ITALIC:
insert = '_' + text + '_'
elif entity.type == MessageEntity.CODE:
insert = '`' + text + '`'
elif entity.type == MessageEntity.PRE:
insert = '```' + text + '```'
else:
insert = text
markdown_text += escape_markdown(message_text[last_offset:entity.offset]) + insert
last_offset = entity.offset + entity.length
markdown_text += escape_markdown(message_text[last_offset:])
return markdown_text
+1 -1
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Copyright (C) 2015-2017
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
+1 -1
View File
@@ -2,7 +2,7 @@
# pylint: disable=R0903
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Copyright (C) 2015-2017
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
+4 -8
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Copyright (C) 2015-2017
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
@@ -40,19 +40,15 @@ class PhotoSize(TelegramObject):
file_size (Optional[int]):
"""
def __init__(self, file_id, width, height, file_size=0, **kwargs):
def __init__(self, file_id, width, height, file_size=None, **kwargs):
# Required
self.file_id = str(file_id)
self.width = int(width)
self.height = int(height)
# Optionals
self.file_size = int(file_size)
self.file_size = file_size
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)
self._id_attrs = (self.file_id,)
@staticmethod
def de_json(data, bot):
+3 -1
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Copyright (C) 2015-2017
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
@@ -67,6 +67,8 @@ class ReplyKeyboardMarkup(ReplyMarkup):
if not data:
return None
data = super(ReplyKeyboardMarkup, ReplyKeyboardMarkup).de_json(data, bot)
data['keyboard'] = [KeyboardButton.de_list(keyboard, bot) for keyboard in data['keyboard']]
return ReplyKeyboardMarkup(**data)
+1 -1
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Copyright (C) 2015-2017
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
+1 -1
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Copyright (C) 2015-2017
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
+7 -3
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Copyright (C) 2015-2017
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
@@ -44,7 +44,7 @@ class Sticker(TelegramObject):
file_size (Optional[int]):
"""
def __init__(self, file_id, width, height, thumb=None, emoji='', file_size=0, **kwargs):
def __init__(self, file_id, width, height, thumb=None, emoji=None, file_size=None, **kwargs):
# Required
self.file_id = str(file_id)
self.width = int(width)
@@ -52,7 +52,9 @@ class Sticker(TelegramObject):
# Optionals
self.thumb = thumb
self.emoji = emoji
self.file_size = int(file_size)
self.file_size = file_size
self._id_attrs = (self.file_id,)
@staticmethod
def de_json(data, bot):
@@ -67,6 +69,8 @@ class Sticker(TelegramObject):
if not data:
return None
data = super(Sticker, Sticker).de_json(data, bot)
data['thumb'] = PhotoSize.de_json(data.get('thumb'), bot)
return Sticker(**data)
+100 -1
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Copyright (C) 2015-2017
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
@@ -73,6 +73,12 @@ class Update(TelegramObject):
self.channel_post = channel_post
self.edited_channel_post = edited_channel_post
self._effective_user = None
self._effective_chat = None
self._effective_message = None
self._id_attrs = (self.update_id,)
@staticmethod
def de_json(data, bot):
"""
@@ -86,6 +92,8 @@ class Update(TelegramObject):
if not data:
return None
data = super(Update, Update).de_json(data, bot)
data['message'] = Message.de_json(data.get('message'), bot)
data['edited_message'] = Message.de_json(data.get('edited_message'), bot)
data['inline_query'] = InlineQuery.de_json(data.get('inline_query'), bot)
@@ -96,3 +104,94 @@ class Update(TelegramObject):
data['edited_channel_post'] = Message.de_json(data.get('edited_channel_post'), bot)
return Update(**data)
@property
def effective_user(self):
"""
A property that contains the ``User`` that sent this update, no matter what kind of update
this is. Will be ``None`` for channel posts.
"""
if self._effective_user:
return self._effective_user
user = None
if self.message:
user = self.message.from_user
elif self.edited_message:
user = self.edited_message.from_user
elif self.inline_query:
user = self.inline_query.from_user
elif self.chosen_inline_result:
user = self.chosen_inline_result.from_user
elif self.callback_query:
user = self.callback_query.from_user
self._effective_user = user
return user
@property
def effective_chat(self):
"""
A property that contains the ``Chat`` that this update was sent in, no matter what kind of
update this is. Will be ``None`` for inline queries and chosen inline results.
"""
if self._effective_chat:
return self._effective_chat
chat = None
if self.message:
chat = self.message.chat
elif self.edited_message:
chat = self.edited_message.chat
elif self.callback_query and self.callback_query.message:
chat = self.callback_query.message.chat
elif self.channel_post:
chat = self.channel_post.chat
elif self.edited_channel_post:
chat = self.edited_channel_post.chat
self._effective_chat = chat
return chat
@property
def effective_message(self):
"""
A property that contains the ``Message`` included in this update, no matter what kind
of update this is. Will be ``None`` for inline queries, chosen inline results and callback
queries from inline messages.
"""
if self._effective_message:
return self._effective_message
message = None
if self.message:
message = self.message
elif self.edited_message:
message = self.edited_message
elif self.callback_query:
message = self.callback_query.message
elif self.channel_post:
message = self.channel_post
elif self.edited_channel_post:
message = self.edited_channel_post
self._effective_message = message
return message
+13 -2
View File
@@ -2,7 +2,7 @@
# pylint: disable=C0103,W0622
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Copyright (C) 2015-2017
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
@@ -44,7 +44,14 @@ class User(TelegramObject):
bot (Optional[Bot]): The Bot to use for instance methods
"""
def __init__(self, id, first_name, type='', last_name='', username='', bot=None, **kwargs):
def __init__(self,
id,
first_name,
type=None,
last_name=None,
username=None,
bot=None,
**kwargs):
# Required
self.id = int(id)
self.first_name = first_name
@@ -55,6 +62,8 @@ class User(TelegramObject):
self.bot = bot
self._id_attrs = (self.id,)
@property
def name(self):
"""str: """
@@ -77,6 +86,8 @@ class User(TelegramObject):
if not data:
return None
data = super(User, User).de_json(data, bot)
return User(bot=bot, **data)
def get_profile_photos(self, *args, **kwargs):
+3 -1
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Copyright (C) 2015-2017
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
@@ -52,6 +52,8 @@ class UserProfilePhotos(TelegramObject):
if not data:
return None
data = super(UserProfilePhotos, UserProfilePhotos).de_json(data, bot)
data['photos'] = [PhotoSize.de_list(photo, bot) for photo in data['photos']]
return UserProfilePhotos(**data)
+1 -1
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Copyright (C) 2015-2017
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify

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