Compare commits

...

224 Commits

Author SHA1 Message Date
Jannes Höke d5583190b8 Bump version to v6.1.0 2017-06-18 12:35:16 +02:00
Jacob Bom faddb92395 Clean up Bot code a bit (#673)
* Clean up Bot code a bit

- Move decorators to module. It really wasn't clear how decorators inside classes work, and why they didn't have a self parameter, but still wasn't static. This also makes them effectively private without having to underscore them, which I think we should have done long time ago atm. Note that this might break backwards compatibility slightly (only if people are daft enough to have used the decorators themselves)
- Don't call _message_wrapper directly. Ever. Instead always use the message decorator, since it's what it's there for. Closes #627
- Don't use the message decorator if the method isn't supposed to return a message. The decorator could handle values like True (which is often the return value), but to someone reading the code, it seems like it's a message returning method even when it wasn't.
- Always document timeout and **kwargs
- Log all methods

* Add test to make sure timeout propagates properly despite decorators
2017-06-18 12:14:24 +02:00
saschalalala 9b5e014a0a Simplification of boolean checks (#662)
* Simplification of boolean checks

* Cast ok to bool for Telegram API json encoding
2017-06-18 12:09:32 +02:00
Jannes Höke 845312da59 Merge branch 'master' of github.com:python-telegram-bot/python-telegram-bot 2017-06-16 04:16:45 +02:00
saschalalala 3863b4f371 Rename shortcut functions to snake_case (#661)
* Rename shortcut functions to snake_case

* More function renaming

* Example function rewrite

* Add myself to authors.rst

* More function renaming

* Rename mockbot test functions

* Break comment line for flake max line length
2017-06-14 00:07:03 +02:00
Eldinnie 25912dca6e add --recursive in contributing guide (#664) 2017-06-14 00:02:16 +02:00
alateas 6877886f7d Change hard-coded 20. to timeout parameter (#663)
* Change hard-coded 20. to timeout paramter

Passing timeout parameter to _message_wrapper in send_audio instead of hard-coded 20 seconds

* add myself to contributors
2017-06-14 00:01:38 +02:00
Noam Meltzer d16d38530c Merge pull request #658 from jeffffc/paymentbot_example_and_fixes
Add PaymentBot Example, Bugfixes on payment methods
2017-06-11 00:18:20 +03:00
Jeff c7ed87d475 add telegram doc link comment 2017-06-11 04:30:21 +08:00
Jeff a68e0ad044 bugfixes on error in answer_shipping_query and answer_precheckout_query 2017-06-11 03:45:48 +08:00
Jeff ad603bd2f9 add new payment example 2017-06-11 03:43:38 +08:00
Jannes Höke 179fc141bb Merge branch 'master' of github.com:python-telegram-bot/python-telegram-bot 2017-06-10 21:24:28 +02:00
Noam Meltzer da8a3cee44 Merge pull request #631 from jeffffc/paymenthandlers
Add both handlers for queries from new Payment API
2017-06-09 18:23:29 +03:00
Eldin bc3669fa4b make commandhandler case insensitive 2017-06-09 17:45:15 +03:00
Eldin 7def2c53e1 fix tests for pycharm windows
Refractored self.id in new tests to self._id (as I did before) so testrunner in pycharm can run under Windows.
2017-06-09 17:44:43 +03:00
Noam Meltzer 09230e6e84 Merge pull request #649 from python-telegram-bot/fix-docs
Fix some documentation stuff
[ci skip]
2017-06-09 13:14:03 +03:00
Eldin 011e321583 Update gitignore with new video file 2017-06-09 12:27:10 +03:00
Eldin 0afb38f45c Fix video_note tests
Added a video_note to send.
2017-06-09 12:27:10 +03:00
Jeff 4247dc0e21 tiny changes upon PR review 2017-06-08 09:47:19 +08:00
Jacob Bom 896392d01e Merge branch 'master' into fix-docs 2017-06-07 13:12:32 +02:00
Jacob Bom 61c5d6ff08 Remove more warnings 2017-06-07 13:07:45 +02:00
Jacob Bom aa1c4e0e02 Fix most of "more than one target found for cross-reference" warnings 2017-06-07 12:53:02 +02:00
Jacob Bom 92183a5bbc Some objects don't have a module 2017-06-07 12:52:36 +02:00
Jacob Bom 87b096c7f9 Only document functions/methods that are 100% lowercase + use favicon
In bot.py we did stuff like sendAudio = send_audio, which made sphinx document both, which led to clutter in our docs.
2017-06-07 12:40:09 +02:00
Jacob Bom 3329d15691 Add orange favicon
Do we need a blue one too?
2017-06-07 12:37:29 +02:00
Noam Meltzer 0fb00c4c8b When Promise running throws an uncaught exception - log it 2017-06-03 16:34:03 +03:00
Noam Meltzer 2680740316 Merge pull request #641 from azogue/fixes
fix setting default mimetype of inputfile
2017-06-03 00:22:32 +03:00
Jeff b6ba66ba8e Fix typo in filter test 2017-06-03 02:05:26 +08:00
Jeff eaf765dcbc Remove duplicated/useless codes, added Filters.invoice test 2017-06-03 01:48:30 +08:00
Jeff f735a37828 Merge from master and resolve conflicts
Merge from master and resolve conflicts
2017-06-03 01:19:29 +08:00
Jeff e9d08c6f7a Merge branch 'master' into paymenthandlers-new
# Conflicts:
#	telegram/bot.py
#	telegram/message.py
#	telegram/precheckoutquery.py
#	telegram/shippingquery.py
2017-06-03 01:09:17 +08:00
Noam Meltzer c0e3453a62 Travis: add test to make sure setup.py doesn't break
Refs #635
2017-06-02 11:51:31 +03:00
Noam Meltzer 90e9e32632 Merge pull request #622 from python-telegram-bot/payment
Payment stuff

refs #617
2017-06-02 11:14:18 +03:00
azogue 1e3e8973ba added myself to authors 2017-06-02 08:25:39 +02:00
Noam Meltzer 0ed997cc53 Merge remote-tracking branch 'origin/master' into payment 2017-06-01 22:07:14 +03:00
Jacob Bom 760737345d Fix tests for video_note
It would seem that length is no longer incorrectly required so that's good
However they stopped sending the file_size when you send via a file_id which is a bit odd
2017-06-01 22:06:34 +03:00
Noam Meltzer 137ffe2e73 Merge remote-tracking branch 'origin/master' into payment 2017-06-01 22:01:04 +03:00
Noam Meltzer c4b78673a3 payment: cr fixes 2017-06-01 21:52:47 +03:00
Noam Meltzer ee1cf6b9b0 test_set_webhook_get_webhook_info: Retry more
Unitests fail too much on this one. Lets retry more, maybe we'll stop
seeing this.
[ci skip]
2017-06-01 21:25:00 +03:00
Noam Meltzer c15371e8c0 Merge pull request #642 from evgfilim1/new-filters
New filters
2017-06-01 21:21:22 +03:00
evgfilim1 f66076b06c Returned old tests 2017-06-01 16:37:39 +05:00
evgfilim1 5b1252ac2d Perfomance optimizations
Using `or` instead of `|`
2017-06-01 16:36:32 +05:00
Rahiel Kasim 647059735c CHANGES: document introduction of telegram.constants (#643) 2017-06-01 11:33:36 +02:00
evgfilim1 d0f6860bac Fixed tests to use new filters 2017-06-01 07:10:26 +05:00
evgfilim1 d98b18d1e3 Added new status_update filters 2017-06-01 07:09:30 +05:00
Jannes Höke 9d6c394b02 Bump version to v6.0.3 2017-05-29 22:55:15 +02:00
Jannes Höke 1951d6fdcd Bump version to v6.0.2 2017-05-29 21:24:12 +02:00
azogue 84b59c8cb2 unittest sending non standard jpg photo with bytesio stream 2017-05-29 09:38:02 +02:00
azogue b46bdf5304 fix setting default mimetype of inputfile 2017-05-29 08:30:29 +02:00
Noam Meltzer cd24bb4ba5 payment: Small fixes
- Semantic fixes bot.py (if XXX is not None: ...).
 - Documentation fixes (arguments which are optional).
2017-05-26 20:02:18 +03:00
Noam Meltzer ae33d33580 Merge pull request #625 from python-telegram-bot/videonote
Add VideoNote
2017-05-26 19:34:13 +03:00
Noam Meltzer 706f79fa99 Fix setup.py with urllib3 vendoring
fixes #635
2017-05-26 19:24:54 +03:00
Noam Meltzer a5bfc52326 videonote: Semantic fix according to CR 2017-05-26 18:36:21 +03:00
Noam Meltzer 858684ab64 Merge pull request #628 from python-telegram-bot/may18minor
May 18 minor changes
2017-05-26 18:23:31 +03:00
Jacob Bom f4c6197c5f Update filters to new_chat_members instead of new_chat_member 2017-05-25 14:45:47 +02:00
Jacob Bom a7044562af Fix stuff as per @tsnoam's comments 2017-05-25 14:16:53 +02:00
Jacob Bom 27a4fa8dba Move *_duration attributes as to keep backwardscompat 2017-05-25 14:05:08 +02:00
Jacob Bom a41ee3b040 Actually deprecate new_chat_member to help users switch 2017-05-25 14:02:41 +02:00
Jacob Bom 8499dcc33c Better deprecation warnings
Actually shows where in the users code the error happened, not just where the warning came from in our internal code
2017-05-25 13:53:35 +02:00
Noam Meltzer ff897ce9ef Avoid confusion with user's urllib3 by renaming vendored urllib3 2017-05-24 13:14:33 +03:00
Jeff f314915954 fix typo again 2017-05-23 16:26:04 +08:00
Jeff 1bf0078573 add missing "need_email" in sendInvoice, fix pep8/flake 2017-05-23 16:21:45 +08:00
Jeff 1210e4ef04 Finalize Payment API
bugfixes
added payment-related handlers
2017-05-23 02:19:23 +08:00
Jeff 05ed693fb6 fix authors typo 2017-05-22 18:11:19 +00:00
Jeff 18822d1396 Merge branch 'fix-paymenthandlers' into paymenthandlers-final 2017-05-22 17:47:43 +00:00
Jeff 73ac259721 fix typo... 2017-05-23 00:13:30 +08:00
Jeff 96171112bf fix crucial typo in filters 2017-05-22 23:42:09 +08:00
Jeff 27238c52e2 add successful_payment filter under message 2017-05-22 23:39:28 +08:00
Jeff 43f4128b31 fix answer* methods not sending api requests 2017-05-22 22:55:19 +08:00
Jeff 1142953108 add checks for answer* methods 2017-05-22 22:47:35 +08:00
Jeff 1e250f2415 add kwargs to answer* methods 2017-05-22 21:37:15 +08:00
Jeff 8f2f29c7d4 fix typo, add bot and kwargs to class init 2017-05-22 21:32:42 +08:00
Jeff 3767d26fc8 Add both handlers for queries from new Payment API (#630)
* add handlers for new payment API

* fix typo

* fix docstring mistakes

* added missing 'from_user'
2017-05-22 14:07:53 +02:00
Jeff a78f72c086 added missing 'from_user' 2017-05-22 20:04:03 +08:00
Jeff cc03649f1c fix docstring mistakes 2017-05-22 19:59:13 +08:00
Jeff ee057e76ef fix typo 2017-05-22 19:42:48 +08:00
Jeff 2c05b03fba add handlers for new payment API 2017-05-22 19:20:26 +08:00
Jacob Bom 630b63ec10 Add payment filters 2017-05-22 12:13:00 +02:00
Jannes Höke 23b37c9ea4 mention beta version in README 2017-05-21 22:10:08 +02:00
Jacob Bom 01430a24a5 Import order fix 2017-05-21 15:51:01 +02:00
Jacob Bom acda19b7e7 Merge branch 'may18minor' into beta 2017-05-21 15:45:40 +02:00
Jacob Bom 48fa3d975b Merge branch 'videonote' into beta
# Conflicts:
#	telegram/__init__.py
#	telegram/message.py
2017-05-21 15:45:20 +02:00
Jacob Bom d220ff4e38 Merge branch 'payment' into beta 2017-05-21 15:42:24 +02:00
Jacob Bom 5a15d1b5d6 Add mpeg4_duration and gif_duration to inline gif and mpeg4gif 2017-05-21 14:38:12 +02:00
Jacob Bom ad347b5c02 new_chat_member -> new_chat_members
Keep old for now... we can remove it in the future when telegram stops parsing it along.
Also: TODO: write proper Message tests
2017-05-21 14:32:36 +02:00
Jannes Höke 017fdeaae8 run yapf with --diff 2017-05-21 14:29:24 +02:00
Jannes Höke ba6c4fd517 Bump version to v6.0.1 2017-05-21 14:25:40 +02:00
Jacob Bom 99ecac5649 assertRaisesRegex doesn't exist on py2 (also fuck yapf)
This deprecation is totally gonna come back and bite us when the regexp
gets removed completely in never version´
2017-05-21 14:10:55 +02:00
Jannes Höke 1c4c228cf1 add support for User.language_code (#624)
* add support for User.language_code

* Add language filter

Useful is you wanna do something like restrict your shop to a single or a few locales or something like that.
2017-05-21 14:00:53 +02:00
Jacob Bom 2e89e21261 Fix text_markdown and text_html (#623)
* Fix text_markdown and text_html

* Missed a few narrow build checks

* Added tests for emoji-first strings and emojis in url
2017-05-21 14:00:07 +02:00
Jacob Bom 7583fa9d65 Tests for video notes 2017-05-21 13:51:58 +02:00
Jacob Bom 2dd5290ec2 Add reply_video_note in line with other reply_* 2017-05-21 13:51:27 +02:00
Jacob Bom 41299244b7 Allow InputFile to handle video notes 2017-05-21 13:50:52 +02:00
Jacob Bom 0e2bcf28a6 Export VideoNote 2017-05-21 13:50:15 +02:00
Jacob Bom 2746ab77e5 Add sendVideoNote to Bot 2017-05-20 19:35:55 +02:00
Jacob Bom ae39c902ed Add video_note to Message 2017-05-20 19:30:07 +02:00
Jacob Bom 65929a0813 VideoNote chatactions 2017-05-20 19:26:26 +02:00
Jacob Bom 4426eb0c61 Add VideoNote 2017-05-20 19:25:24 +02:00
Jacob Bom d4f2f2e077 Getting instead of popping
I really think we should be popping and I'm unable to see why it makes our tests fail, but for now.. rolling back.
2017-05-20 19:02:01 +02:00
Jacob Bom 73d75b964a Remove extra space from payment provider token 2017-05-20 18:55:21 +02:00
Jacob Bom 45897048aa Add invoice test and there's no EUD currency, whoops 2017-05-20 18:51:31 +02:00
Jacob Bom 76db279624 We have to send and array... that's new... 2017-05-20 18:46:39 +02:00
Jacob Bom a97da5cb20 Sooo apparently python 2 doesn't like ‘ (\xe2) 2017-05-20 17:36:10 +02:00
Jacob Bom 9f6ec125b9 Basic tests for payment stuff 2017-05-20 17:31:46 +02:00
Jacob Bom 5f96c507b9 Payment ids should be unique... probably 2017-05-20 17:31:46 +02:00
Jacob Bom 402e1381bb OrderInfo can be completely empty
In which case it should still be an OrderInfo not None
2017-05-20 17:31:46 +02:00
Jacob Bom b276b06b31 Pop instead of get if our name doesn't match telegrams (user/from_user) 2017-05-20 17:31:46 +02:00
Jacob Bom b69ecc3015 Fix import order 2017-05-20 17:31:46 +02:00
Jacob Bom 77f06888cb MessageEntity != Sticker :P 2017-05-20 17:31:46 +02:00
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
Jacob Bom 834c1ab3c5 Merge branch 'master' into payment 2017-05-19 20:25:46 +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
Jacob Bom c78d697b0e Add invoice and successful_payment to Message 2017-05-19 20:06:58 +02:00
Jacob Bom 2929c76248 Fix docstring for Message 2017-05-19 20:04:24 +02:00
Jacob Bom 034bcf0895 Add pay inlinekeyboardbutton type 2017-05-19 19:49:31 +02:00
Jacob Bom 8be57198b4 New payment bot methods
sendInvoice, answerShippingQuery, and answerPreCheckoutQuery
2017-05-19 19:46:42 +02:00
Jacob Bom 8e62b02ff6 Add new payment classes
Invoice, LabeledPrice, OrderInfo, PreCheckoutQuery, ShippingAddress, ShippingOption, ShippingQuery and SuccessfulPayment
2017-05-19 19:41:06 +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
198 changed files with 6687 additions and 1297 deletions
+4 -1
View File
@@ -1,5 +1,8 @@
[run]
source = telegram
omit = telegram/vendor/*
[report]
omit = tests/
omit =
tests/
telegram/vendor/*
+9 -3
View File
@@ -12,7 +12,7 @@ Setting things up
.. code-block:: bash
$ git clone https://github.com/<your username>/python-telegram-bot
$ git clone https://github.com/<your username>/python-telegram-bot --recursive
$ cd python-telegram-bot
3. Add a track to the original repository:
@@ -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
+1
View File
@@ -65,6 +65,7 @@ target/
# unitests files
telegram.mp3
telegram.mp4
telegram2.mp4
telegram.ogg
telegram.png
telegram.webp
+4
View File
@@ -0,0 +1,4 @@
[submodule "telegram/vendor/urllib3"]
path = telegram/vendor/ptb_urllib3
url = https://github.com/python-telegram-bot/urllib3.git
branch = ptb
+2
View File
@@ -3,6 +3,8 @@
hooks:
- id: yapf
files: ^(telegram|tests)/.*\.py$
args:
- --diff
- repo: git://github.com/pre-commit/pre-commit-hooks
sha: 18d7035de5388cc7775be57f529c154bf541aab9
hooks:
+3 -1
View File
@@ -4,6 +4,7 @@ python:
- "3.3"
- "3.4"
- "3.5"
- "3.6"
- "pypy"
- "pypy3"
branches:
@@ -15,7 +16,8 @@ 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
- python ./setup.py bdist_dumb
after_success:
coveralls
+23 -1
View File
@@ -1,23 +1,40 @@
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
------------
The following wonderful people contributed directly or indirectly to this project:
- `Alateas <https://github.com/alateas>`_
- `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>`_
- `Eugenio Panadero <https://github.com/azogue>`_
- `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>`_
- `jeffffc <https://github.com/jeffffc>`_
- `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 +42,19 @@ 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>`_
- `Sascha <https://github.com/saschalalala>`_
- `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>`_
+55
View File
@@ -2,6 +2,60 @@
Changes
=======
**2017-06-18**
*Released 6.1.0*
- Fully support Bot API 3.0
- Add more fine-grained filters for status updates
- Bug fixes and other improvements
**2017-05-29**
*Released 6.0.3*
- Faulty PyPI release
**2017-05-29**
*Released 6.0.2*
- Avoid confusion with user's ``urllib3`` by renaming vendored ``urllib3`` to ``ptb_urllib3``
**2017-05-19**
*Released 6.0.1*
- Add support for ``User.language_code``
- Fix ``Message.text_html`` and ``Message.text_markdown`` for messages with emoji
**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*
@@ -46,6 +100,7 @@ Changes
- Rework ``JobQueue``
- Introduce ``ConversationHandler``
- Introduce ``telegram.constants`` - https://github.com/python-telegram-bot/python-telegram-bot/pull/342
**2016-07-12**
+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 **18. June 2017**, all types and methods of the Telegram Bot API 3.0 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
+23 -6
View File
@@ -11,7 +11,6 @@
#
# All configuration values have a default; values that are commented out
# serve to show the default.
import sys
import os
import shlex
@@ -25,7 +24,7 @@ sys.path.insert(0, os.path.abspath('../..'))
# -- General configuration ------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#needs_sphinx = '1.0'
needs_sphinx = '1.5.4' # fixes issues with autodoc-skip-member and napoleon
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
@@ -51,7 +50,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 +58,9 @@ author = u'Leandro Toledo'
# built documents.
#
# The short X.Y version.
version = '5.3' # telegram.__version__[:3]
version = '6.1' # telegram.__version__[:3]
# The full version, including alpha/beta/rc tags.
release = '5.3.0' # telegram.__version__
release = '6.1.0' # telegram.__version__
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
@@ -136,7 +135,7 @@ html_logo = 'ptb-logo-orange.png'
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#html_favicon = None
html_favicon = 'ptb-logo-orange.ico'
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
@@ -289,3 +288,21 @@ texinfo_documents = [
# If true, do not generate a @detailmenu in the "Top" node's menu.
#texinfo_no_detailmenu = False
# -- script stuff --------------------------------------------------------
import inspect
def autodoc_skip_member(app, what, name, obj, skip, options):
try:
if inspect.getmodule(obj).__name__.startswith('telegram') and inspect.isfunction(obj):
if name.lower() != name:
return True
except AttributeError:
pass
# Return None so napoleon can handle it
def setup(app):
app.connect('autodoc-skip-member', autodoc_skip_member)
Binary file not shown.

After

Width:  |  Height:  |  Size: 361 KiB

@@ -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):
+146
View File
@@ -0,0 +1,146 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Basic example for a bot that can receive payment from user.
# This program is dedicated to the public domain under the CC0 license.
from telegram import (LabeledPrice, ShippingOption)
from telegram.ext import (Updater, CommandHandler, MessageHandler,
Filters, PreCheckoutQueryHandler, ShippingQueryHandler)
import logging
# Enable logging
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO)
logger = logging.getLogger(__name__)
def error(bot, update, error):
logger.warn('Update "%s" caused error "%s"' % (update, error))
def start_callback(bot, update):
msg = "Use /shipping to get an invoice for shipping-payment, "
msg += "or /noshipping for an invoice without shipping."
update.message.reply_text(msg)
def start_with_shipping_callback(bot, update):
chat_id = update.message.chat_id
title = "Payment Example"
description = "Payment Example using python-telegram-bot"
# select a payload just for you to recognize its the donation from your bot
payload = "Custom-Payload"
# get your provider_token at @botfather, see https://core.telegram.org/bots/payments#getting-a-token
provider_token = "PROVIDER_TOKEN"
start_parameter = "test-payment"
currency = "USD"
# price in dollars
price = 1
# price * 100 so as to include 2 d.p.
# check https://core.telegram.org/bots/payments#supported-currencies for more details
prices = [LabeledPrice("Test", price * 100)]
# optionally pass need_name=True, need_phone_number=True,
# need_email=True, need_shipping_address=True, is_flexible=True
bot.sendInvoice(chat_id, title, description, payload,
provider_token, start_parameter, currency, prices,
need_name=True, need_phone_number=True,
need_email=True, need_shipping_address=True, is_flexible=True)
def start_without_shipping_callback(bot, update):
chat_id = update.message.chat_id
title = "Payment Example"
description = "Payment Example using python-telegram-bot"
# select a payload just for you to recognize its the donation from your bot
payload = "Custom-Payload"
# get your provider_token at @botfather, see https://core.telegram.org/bots/payments#getting-a-token
provider_token = "PROVIDER_TOKEN"
start_parameter = "test-payment"
currency = "USD"
# price in dollars
price = 1
# price * 100 so as to include 2 d.p.
prices = [LabeledPrice("Test", price * 100)]
# optionally pass need_name=True, need_phone_number=True,
# need_email=True, need_shipping_address=True, is_flexible=True
bot.sendInvoice(chat_id, title, description, payload,
provider_token, start_parameter, currency, prices)
def shipping_callback(bot, update):
query = update.shipping_query
# check the payload, is this from your bot?
if query.invoice_payload != 'Custom-Payload':
# answer False pre_checkout_query
bot.answer_shipping_query(shipping_query_id=query.id, ok=False,
error_message="Something went wrong...")
return
else:
options = list()
# a single LabeledPrice
options.append(ShippingOption('1', 'Shipping Option A', [LabeledPrice('A', 100)]))
# an array of LabeledPrice objects
price_list = [LabeledPrice('B1', 150), LabeledPrice('B2', 200)]
options.append(ShippingOption('2', 'Shipping Option B', price_list))
bot.answer_shipping_query(shipping_query_id=query.id, ok=True,
shipping_options=options)
# after (optional) shipping, it's the pre-checkout
def precheckout_callback(bot, update):
query = update.pre_checkout_query
# check the payload, is this from your bot?
if query.invoice_payload != 'Custom-Payload':
# answer False pre_checkout_query
bot.answer_pre_checkout_query(pre_checkout_query_id=query.id, ok=False,
error_message="Something went wrong...")
else:
bot.answer_pre_checkout_query(pre_checkout_query_id=query.id, ok=True)
# finally, after contacting to the payment provider...
def successful_payment_callback(bot, update):
# do something after successful receive of payment?
update.message.reply_text("Thank you for your payment!")
def main():
# Create the EventHandler and pass it your bot's token.
updater = Updater(token="BOT_TOKEN")
# Get the dispatcher to register handlers
dp = updater.dispatcher
# simple start function
dp.add_handler(CommandHandler("start", start_callback))
# Add command handler to start the payment invoice
dp.add_handler(CommandHandler("shipping", start_with_shipping_callback))
dp.add_handler(CommandHandler("noshipping", start_without_shipping_callback))
# Optional handler if your product requires shipping
dp.add_handler(ShippingQueryHandler(shipping_callback))
# Pre-checkout handler to final check
dp.add_handler(PreCheckoutQueryHandler(precheckout_callback))
# Success! Notify your user!
dp.add_handler(MessageHandler(Filters.successful_payment, successful_payment_callback))
# log all errors
dp.add_error_handler(error)
# Start the Bot
updater.start_polling()
# 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()
if __name__ == '__main__':
main()
+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
+5 -1
View File
@@ -17,6 +17,8 @@ def requirements():
return requirements_list
packages = find_packages(exclude=['tests*'])
with codecs.open('README.rst', 'r', 'utf-8') as fd:
fn = os.path.join('telegram', 'version.py')
with open(fn) as fh:
@@ -32,10 +34,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 +56,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'
],)
+14 -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
@@ -19,6 +19,8 @@
"""A library that provides a Python interface to the Telegram Bot API"""
from sys import version_info
import sys
import os
from .base import TelegramObject
from .user import User
@@ -48,6 +50,10 @@ from .parsemode import ParseMode
from .messageentity import MessageEntity
from .animation import Animation
from .game import Game
from .shippingaddress import ShippingAddress
from .orderinfo import OrderInfo
from .successfulpayment import SuccessfulPayment
from .invoice import Invoice
from .message import Message
from .inputmessagecontent import InputMessageContent
from .callbackquery import CallbackQuery
@@ -80,8 +86,13 @@ from .inputtextmessagecontent import InputTextMessageContent
from .inputlocationmessagecontent import InputLocationMessageContent
from .inputvenuemessagecontent import InputVenueMessageContent
from .inputcontactmessagecontent import InputContactMessageContent
from .labeledprice import LabeledPrice
from .shippingoption import ShippingOption
from .precheckoutquery import PreCheckoutQuery
from .shippingquery import ShippingQuery
from .webhookinfo import WebhookInfo
from .gamehighscore import GameHighScore
from .videonote import VideoNote
from .update import Update
from .bot import Bot
from .constants import (MAX_MESSAGE_LENGTH, MAX_CAPTION_LENGTH, SUPPORTED_WEBHOOK_PORTS,
@@ -111,5 +122,6 @@ __all__ = [
'Video', 'Voice', 'MAX_MESSAGE_LENGTH', 'MAX_CAPTION_LENGTH', 'SUPPORTED_WEBHOOK_PORTS',
'MAX_FILESIZE_DOWNLOAD', 'MAX_FILESIZE_UPLOAD', 'MAX_MESSAGES_PER_SECOND_PER_CHAT',
'MAX_MESSAGES_PER_SECOND', 'MAX_MESSAGES_PER_MINUTE_PER_GROUP', 'WebhookInfo', 'Animation',
'Game', 'GameHighScore'
'Game', 'GameHighScore', 'VideoNote', 'LabeledPrice', 'SuccessfulPayment', 'ShippingOption',
'ShippingAddress', 'PreCheckoutQuery', 'OrderInfo', 'Invoice', 'ShippingQuery'
]
+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__()
+869 -406
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)
+24 -23
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):
@@ -41,7 +41,7 @@ class Chat(TelegramObject):
username(Optional[str]):
first_name(Optional[str]):
last_name(Optional[str]):
bot (Optional[Bot]): The Bot to use for instance methods
bot (Optional[telegram.Bot]): The Bot to use for instance methods
**kwargs (dict): Arbitrary keyword arguments.
"""
@@ -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):
@@ -88,29 +89,29 @@ class Chat(TelegramObject):
return Chat(bot=bot, **data)
def send_action(self, *args, **kwargs):
"""Shortcut for ``bot.sendChatAction(update.message.chat.id, *args, **kwargs)``"""
return self.bot.sendChatAction(self.id, *args, **kwargs)
"""Shortcut for ``bot.send_chat_action(update.message.chat.id, *args, **kwargs)``"""
return self.bot.send_chat_action(self.id, *args, **kwargs)
def leave(self, *args, **kwargs):
"""Shortcut for ``bot.leaveChat(update.message.chat.id, *args, **kwargs)``"""
return self.bot.leaveChat(self.id, *args, **kwargs)
"""Shortcut for ``bot.leave_chat(update.message.chat.id, *args, **kwargs)``"""
return self.bot.leave_chat(self.id, *args, **kwargs)
def get_administrators(self, *args, **kwargs):
"""Shortcut for ``bot.getChatAdministrators(update.message.chat.id, *args, **kwargs)``"""
return self.bot.getChatAdministrators(self.id, *args, **kwargs)
"""Shortcut for ``bot.get_chat_administrators(update.message.chat.id, *args, **kwargs)``"""
return self.bot.get_chat_administrators(self.id, *args, **kwargs)
def get_members_count(self, *args, **kwargs):
"""Shortcut for ``bot.getChatMembersCount(update.message.chat.id, *args, **kwargs)``"""
return self.bot.getChatMembersCount(self.id, *args, **kwargs)
"""Shortcut for ``bot.get_chat_members_count(update.message.chat.id, *args, **kwargs)``"""
return self.bot.get_chat_members_count(self.id, *args, **kwargs)
def get_member(self, *args, **kwargs):
"""Shortcut for ``bot.getChatMember(update.message.chat.id, *args, **kwargs)``"""
return self.bot.getChatMember(self.id, *args, **kwargs)
"""Shortcut for ``bot.get_chat_member(update.message.chat.id, *args, **kwargs)``"""
return self.bot.get_chat_member(self.id, *args, **kwargs)
def kick_member(self, *args, **kwargs):
"""Shortcut for ``bot.kickChatMember(update.message.chat.id, *args, **kwargs)``"""
return self.bot.kickChatMember(self.id, *args, **kwargs)
"""Shortcut for ``bot.kick_chat_member(update.message.chat.id, *args, **kwargs)``"""
return self.bot.kick_chat_member(self.id, *args, **kwargs)
def unban_member(self, *args, **kwargs):
"""Shortcut for ``bot.unbanChatMember(update.message.chat.id, *args, **kwargs)``"""
return self.bot.unbanChatMember(self.id, *args, **kwargs)
"""Shortcut for ``bot.unban_chat_member(update.message.chat.id, *args, **kwargs)``"""
return self.bot.unban_chat_member(self.id, *args, **kwargs)
+3 -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
@@ -31,3 +31,5 @@ class ChatAction(object):
UPLOAD_AUDIO = 'upload_audio'
UPLOAD_DOCUMENT = 'upload_document'
FIND_LOCATION = 'find_location'
RECORD_VIDEO_NOTE = 'record_video_note'
UPLOAD_VIDEO_NOTE = 'upload_video_note'
+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):
+5 -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
@@ -33,8 +33,11 @@ from .stringcommandhandler import StringCommandHandler
from .stringregexhandler import StringRegexHandler
from .typehandler import TypeHandler
from .conversationhandler import ConversationHandler
from .precheckoutqueryhandler import PreCheckoutQueryHandler
from .shippingqueryhandler import ShippingQueryHandler
__all__ = ('Dispatcher', 'JobQueue', 'Job', 'Updater', 'CallbackQueryHandler',
'ChosenInlineResultHandler', 'CommandHandler', 'Handler', 'InlineQueryHandler',
'MessageHandler', 'BaseFilter', 'Filters', 'RegexHandler', 'StringCommandHandler',
'StringRegexHandler', 'TypeHandler', 'ConversationHandler')
'StringRegexHandler', 'TypeHandler', 'ConversationHandler',
'PreCheckoutQueryHandler', 'ShippingQueryHandler')
+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.lower()]
else:
self.command = [x.lower() for x in 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].lower() 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")
+144 -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
@@ -17,6 +17,12 @@
# 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
try:
str_type = base_string
except NameError:
str_type = str
class BaseFilter(object):
@@ -32,9 +38,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 +62,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.
@@ -181,13 +214,68 @@ class Filters(object):
class _StatusUpdate(BaseFilter):
class _NewChatMembers(BaseFilter):
def filter(self, message):
return bool(message.new_chat_members)
new_chat_members = _NewChatMembers()
class _LeftChatMember(BaseFilter):
def filter(self, message):
return bool(message.left_chat_member)
left_chat_member = _LeftChatMember()
class _NewChatTitle(BaseFilter):
def filter(self, message):
return bool(message.new_chat_title)
new_chat_title = _NewChatTitle()
class _NewChatPhoto(BaseFilter):
def filter(self, message):
return bool(message.new_chat_photo)
new_chat_photo = _NewChatPhoto()
class _DeleteChatPhoto(BaseFilter):
def filter(self, message):
return bool(message.delete_chat_photo)
delete_chat_photo = _DeleteChatPhoto()
class _ChatCreated(BaseFilter):
def filter(self, message):
return bool(message.group_chat_created or message.supergroup_chat_created or
message.channel_chat_created)
chat_created = _ChatCreated()
class _Migrate(BaseFilter):
def filter(self, message):
return bool(message.migrate_from_chat_id or message.migrate_to_chat_id)
migrate = _Migrate()
class _PinnedMessage(BaseFilter):
def filter(self, message):
return bool(message.pinned_message)
pinned_message = _PinnedMessage()
def filter(self, message):
return bool(message.new_chat_member or message.left_chat_member
or message.new_chat_title or message.new_chat_photo
or message.delete_chat_photo or message.group_chat_created
or message.supergroup_chat_created or message.channel_chat_created
or message.migrate_to_chat_id or message.migrate_from_chat_id
or message.pinned_message)
return bool(self.new_chat_members(message) or self.left_chat_member(message) or
self.new_chat_title(message) or self.new_chat_photo(message) or
self.delete_chat_photo(message) or self.chat_created(message) or
self.migrate(message) or self.pinned_message(message))
status_update = _StatusUpdate()
@@ -221,3 +309,52 @@ 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()
class _Invoice(BaseFilter):
def filter(self, message):
return bool(message.invoice)
invoice = _Invoice()
class _SuccessfulPayment(BaseFilter):
def filter(self, message):
return bool(message.successful_payment)
successful_payment = _SuccessfulPayment()
class language(BaseFilter):
"""
Filters messages to only allow those which are from users with a certain language code.
Note that according to telegrams documentation, every single user does not have the
language_code attribute.
Args:
lang (str|list): Which language code(s) to allow through. This will be matched using
.startswith meaning that 'en' will match both 'en_US' and 'en_GB'
"""
def __init__(self, lang):
if isinstance(lang, str_type):
self.lang = [lang]
else:
self.lang = lang
def filter(self, message):
return message.from_user.language_code and any(
[message.from_user.language_code.startswith(x) for x in self.lang])
+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
+278 -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,185 @@ 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:
telegram.ext.jobqueue.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:
telegram.ext.jobqueue.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:
telegram.ext.jobqueue.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 +272,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 +282,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 +292,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 +317,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 +329,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 +363,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 (telegram.ext.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 +427,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
+69
View File
@@ -0,0 +1,69 @@
#!/usr/bin/env python
#
# 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/].
""" This module contains the PreCheckoutQueryHandler class """
from telegram import Update
from .handler import Handler
class PreCheckoutQueryHandler(Handler):
"""
Handler class to handle Telegram PreCheckout callback queries.
Args:
callback (function): A function that takes ``bot, update`` as
positional arguments. It will be called when the ``check_update``
has determined that an update should be processed by this handler.
pass_update_queue (optional[bool]): If set to ``True``, a keyword argument called
``update_queue`` will be passed to the callback function. It will be the ``Queue``
instance used by the ``Updater`` and ``Dispatcher`` that contains new updates which can
be used to insert updates. Default is ``False``.
pass_job_queue (optional[bool]): If set to ``True``, a keyword argument called
``job_queue`` will be passed to the callback function. It will be a ``JobQueue``
instance created by the ``Updater`` which can be used to schedule new jobs.
Default is ``False``.
pass_user_data (optional[bool]): If set to ``True``, a keyword argument called
``user_data`` will be passed to the callback function. It will be a ``dict`` you
can use to keep any data related to the user that sent the update. For each update of
the same user, it will be the same ``dict``. Default is ``False``.
pass_chat_data (optional[bool]): If set to ``True``, a keyword argument called
``chat_data`` will be passed to the callback function. It will be a ``dict`` you
can use to keep any data related to the chat that the update was sent in.
For each update in the same chat, it will be the same ``dict``. Default is ``False``.
"""
def __init__(self,
callback,
pass_update_queue=False,
pass_job_queue=False,
pass_user_data=False,
pass_chat_data=False):
super(PreCheckoutQueryHandler, self).__init__(
callback,
pass_update_queue=pass_update_queue,
pass_job_queue=pass_job_queue,
pass_user_data=pass_user_data,
pass_chat_data=pass_chat_data)
def check_update(self, update):
return isinstance(update, Update) and update.pre_checkout_query
def handle_update(self, update, dispatcher):
optional_args = self.collect_optional_args(dispatcher, update)
return self.callback(dispatcher.bot, update, **optional_args)
+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()
+69
View File
@@ -0,0 +1,69 @@
#!/usr/bin/env python
#
# 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/].
""" This module contains the ShippingQueryHandler class """
from telegram import Update
from .handler import Handler
class ShippingQueryHandler(Handler):
"""
Handler class to handle Telegram shipping callback queries.
Args:
callback (function): A function that takes ``bot, update`` as
positional arguments. It will be called when the ``check_update``
has determined that an update should be processed by this handler.
pass_update_queue (optional[bool]): If set to ``True``, a keyword argument called
``update_queue`` will be passed to the callback function. It will be the ``Queue``
instance used by the ``Updater`` and ``Dispatcher`` that contains new updates which can
be used to insert updates. Default is ``False``.
pass_job_queue (optional[bool]): If set to ``True``, a keyword argument called
``job_queue`` will be passed to the callback function. It will be a ``JobQueue``
instance created by the ``Updater`` which can be used to schedule new jobs.
Default is ``False``.
pass_user_data (optional[bool]): If set to ``True``, a keyword argument called
``user_data`` will be passed to the callback function. It will be a ``dict`` you
can use to keep any data related to the user that sent the update. For each update of
the same user, it will be the same ``dict``. Default is ``False``.
pass_chat_data (optional[bool]): If set to ``True``, a keyword argument called
``chat_data`` will be passed to the callback function. It will be a ``dict`` you
can use to keep any data related to the chat that the update was sent in.
For each update in the same chat, it will be the same ``dict``. Default is ``False``.
"""
def __init__(self,
callback,
pass_update_queue=False,
pass_job_queue=False,
pass_user_data=False,
pass_chat_data=False):
super(ShippingQueryHandler, self).__init__(
callback,
pass_update_queue=pass_update_queue,
pass_job_queue=pass_job_queue,
pass_user_data=pass_user_data,
pass_chat_data=pass_chat_data)
def check_update(self, update):
return isinstance(update, Update) and update.shipping_query
def handle_update(self, update, dispatcher):
optional_args = self.collect_optional_args(dispatcher, update)
return self.callback(dispatcher.bot, update, **optional_args)
+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")
+2 -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
@@ -27,7 +27,7 @@ class TypeHandler(Handler):
Handler class to handle updates of custom types.
Args:
type (type): The ``type`` of updates this handler should process, as
type (class): The ``type`` of updates this handler should process, as
determined by ``isinstance``
callback (function): A function that takes ``bot, update`` as
positional arguments. It will be called when the ``check_update``
+67 -25
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
@@ -58,9 +59,14 @@ class Updater(object):
base_url (Optional[str]):
workers (Optional[int]): Amount of threads in the thread pool for
functions decorated with @run_async
bot (Optional[Bot]): 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.
bot (Optional[telegram.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)
updates = self.bot.get_updates(
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.delete_webhook()
self._clean_updates()
sleep(1)
self.bot.setWebhook(webhook_url=webhook_url, certificate=cert)
self.bot.set_webhook(
url=webhook_url, certificate=cert, allowed_updates=allowed_updates)
except (Unauthorized, InvalidToken):
raise
except TelegramError:
@@ -350,9 +390,9 @@ class Updater(object):
def _clean_updates(self):
self.logger.debug('Cleaning updates from Telegram server')
updates = self.bot.getUpdates()
updates = self.bot.get_updates()
while updates:
updates = self.bot.getUpdates(updates[-1].update_id + 1)
updates = self.bot.get_updates(updates[-1].update_id + 1)
def stop(self):
"""
@@ -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)
+17 -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
@@ -46,12 +46,23 @@ class InlineKeyboardButton(TelegramObject):
the bot's username and the specified inline query in the current chat's input field.
Can be empty, in which case only the bot's username will be inserted.
callback_game (Optional[:class:`telegram.CallbackGame`]): Description of the game that will
be launched when the user presses the button.
be launched when the user presses the button. NOTE: This type of button must always be
the first button in the first row.
pay (Optional[bool]): Specify True, to send a Pay button. NOTE: This type of button must
always be the first button in the first row.
**kwargs (dict): Arbitrary keyword arguments.
"""
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,
pay=None,
**kwargs):
# Required
self.text = text
@@ -59,8 +70,9 @@ 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
self.pay = pay
@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
+6 -4
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
@@ -39,7 +40,7 @@ class InlineQuery(TelegramObject):
query (str):
offset (str):
location (optional[:class:`telegram.Location`]):
bot (Optional[Bot]): The Bot to use for instance methods
bot (Optional[telegram.Bot]): The Bot to use for instance methods
**kwargs (dict): Arbitrary keyword arguments.
"""
@@ -55,6 +56,7 @@ class InlineQuery(TelegramObject):
self.location = location
self.bot = bot
self._id_attrs = (self.id,)
@staticmethod
def de_json(data, bot):
@@ -89,5 +91,5 @@ class InlineQuery(TelegramObject):
return data
def answer(self, *args, **kwargs):
"""Shortcut for ``bot.answerInlineQuery(update.inline_query.id, *args, **kwargs)``"""
return self.bot.answerInlineQuery(self.id, *args, **kwargs)
"""Shortcut for ``bot.answer_inline_query(update.inline_query.id, *args, **kwargs)``"""
return self.bot.answer_inline_query(self.id, *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
@@ -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
+6 -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
@@ -32,6 +32,7 @@ class InlineQueryResultGif(InlineQueryResult):
thumb_url (str): URL of the static thumbnail for the result (jpeg or gif).
gif_width (Optional[int]): Width of the GIF.
gif_height (Optional[int]): Height of the GIF.
gif_duration (Optional[int]): Duration of the GIF.
title (Optional[str]): Title for the result.
caption (Optional[str]): Caption of the GIF file to be sent, 0-200 characters.
reply_markup (Optional[:class:`telegram.InlineKeyboardMarkup`]): Inline keyboard attached
@@ -45,6 +46,7 @@ class InlineQueryResultGif(InlineQueryResult):
thumb_url (str):
gif_width (Optional[int]):
gif_height (Optional[int]):
gif_duration (Optional[int]):
title (Optional[str]):
caption (Optional[str]):
reply_markup (Optional[:class:`telegram.InlineKeyboardMarkup`]):
@@ -63,6 +65,7 @@ class InlineQueryResultGif(InlineQueryResult):
caption=None,
reply_markup=None,
input_message_content=None,
gif_duration=None,
**kwargs):
# Required
@@ -75,6 +78,8 @@ class InlineQueryResultGif(InlineQueryResult):
self.gif_width = gif_width
if gif_height:
self.gif_height = gif_height
if gif_duration:
self.gif_duration = gif_duration
if title:
self.title = title
if caption:
+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
+6 -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
@@ -32,6 +32,7 @@ class InlineQueryResultMpeg4Gif(InlineQueryResult):
thumb_url (str): URL of the static thumbnail (jpeg or gif) for the result.
mpeg4_width (Optional[int]): Video width.
mpeg4_height (Optional[int]): Video height.
mpeg4_duration (Optional[int]): Video duration
title (Optional[str]): Title for the result.
caption (Optional[str]): Caption of the MPEG-4 file to be sent, 0-200 characters.
reply_markup (Optional[:class:`telegram.InlineKeyboardMarkup`]): Inline keyboard attached
@@ -44,6 +45,7 @@ class InlineQueryResultMpeg4Gif(InlineQueryResult):
thumb_url (str): URL of the static thumbnail (jpeg or gif) for the result.
mpeg4_width (Optional[int]): Video width.
mpeg4_height (Optional[int]): Video height.
mpeg4_duration (Optional[int]): Video duration
title (Optional[str]): Title for the result.
caption (Optional[str]): Caption of the MPEG-4 file to be sent, 0-200 characters.
reply_markup (Optional[:class:`telegram.InlineKeyboardMarkup`]): Inline keyboard attached
@@ -64,6 +66,7 @@ class InlineQueryResultMpeg4Gif(InlineQueryResult):
caption=None,
reply_markup=None,
input_message_content=None,
mpeg4_duration=None,
**kwargs):
# Required
@@ -76,6 +79,8 @@ class InlineQueryResultMpeg4Gif(InlineQueryResult):
self.mpeg4_width = mpeg4_width
if mpeg4_height:
self.mpeg4_height = mpeg4_height
if mpeg4_duration:
self.mpeg4_duration = mpeg4_duration
if title:
self.title = title
if caption:
+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
+17 -28
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
@@ -35,7 +35,8 @@ from telegram import TelegramError
DEFAULT_MIME_TYPE = 'application/octet-stream'
USER_AGENT = 'Python Telegram Bot (https://github.com/python-telegram-bot/python-telegram-bot)'
FILE_TYPES = ('audio', 'document', 'photo', 'sticker', 'video', 'voice', 'certificate')
FILE_TYPES = ('audio', 'document', 'photo', 'sticker', 'video', 'voice', 'certificate',
'video_note')
class InputFile(object):
@@ -45,27 +46,11 @@ class InputFile(object):
self.data = data
self.boundary = choose_boundary()
if 'audio' in data:
self.input_name = 'audio'
self.input_file = data.pop('audio')
elif 'document' in data:
self.input_name = 'document'
self.input_file = data.pop('document')
elif 'photo' in data:
self.input_name = 'photo'
self.input_file = data.pop('photo')
elif 'sticker' in data:
self.input_name = 'sticker'
self.input_file = data.pop('sticker')
elif 'video' in data:
self.input_name = 'video'
self.input_file = data.pop('video')
elif 'voice' in data:
self.input_name = 'voice'
self.input_file = data.pop('voice')
elif 'certificate' in data:
self.input_name = 'certificate'
self.input_file = data.pop('certificate')
for t in FILE_TYPES:
if t in data:
self.input_name = t
self.input_file = data.pop(t)
break
else:
raise TelegramError('Unknown inputfile type')
@@ -84,13 +69,17 @@ class InputFile(object):
if not self.filename or '.' not in self.filename:
self.filename = self.mimetype.replace('/', '.')
except TelegramError:
self.mimetype = mimetypes.guess_type(self.filename)[0] or DEFAULT_MIME_TYPE
if self.filename:
self.mimetype = mimetypes.guess_type(
self.filename)[0] or DEFAULT_MIME_TYPE
else:
self.mimetype = DEFAULT_MIME_TYPE
@property
def headers(self):
"""
Returns:
str:
str
"""
return {'User-agent': USER_AGENT, 'Content-type': self.content_type}
@@ -98,14 +87,14 @@ class InputFile(object):
def content_type(self):
"""
Returns:
str:
str
"""
return 'multipart/form-data; boundary=%s' % self.boundary
def to_form(self):
"""
Returns:
str:
str
"""
form = []
form_boundary = '--' + self.boundary
@@ -133,7 +122,7 @@ class InputFile(object):
def _parse(form):
"""
Returns:
str:
str
"""
if sys.version_info > (3,):
# on Python 3 form needs to be byte encoded
+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
+58
View File
@@ -0,0 +1,58 @@
#!/usr/bin/env python
#
# 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/].
"""This module contains an object that represents a Telegram Invoice."""
from telegram import TelegramObject
class Invoice(TelegramObject):
"""This object contains basic information about an invoice.
Attributes:
title (str): Product name
description (str): Product description
start_parameter (str): Unique bot deep-linking parameter that can
be used to generate this invoice
currency (str): Three-letter ISO 4217 currency code
total_amount (int): Total price in the smallest units of the currency (integer)
**kwargs (dict): Arbitrary keyword arguments.
"""
def __init__(self, title, description, start_parameter, currency, total_amount, **kwargs):
self.title = title
self.description = description
self.start_parameter = start_parameter
self.currency = currency
self.total_amount = total_amount
@staticmethod
def de_json(data, bot):
"""
Args:
data (dict):
bot (telegram.Bot):
Returns:
telegram.Invoice:
"""
if not data:
return None
return Invoice(**data)
+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):
+70
View File
@@ -0,0 +1,70 @@
#!/usr/bin/env python
#
# 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/].
"""This module contains an object that represents a Telegram LabeledPrice."""
from telegram import TelegramObject
class LabeledPrice(TelegramObject):
"""This object represents a portion of the price for goods or services.
Attributes:
label (str): Portion label
amount (int): Price of the product in the smallest units of the currency (integer)
**kwargs (dict): Arbitrary keyword arguments.
"""
def __init__(self, label, amount, **kwargs):
self.label = label
self.amount = amount
@staticmethod
def de_json(data, bot):
"""
Args:
data (dict):
bot (telegram.Bot):
Returns:
telegram.LabeledPrice:
"""
if not data:
return None
return LabeledPrice(**data)
@staticmethod
def de_list(data, bot):
"""
Args:
data (list):
bot (telegram.Bot):
Returns:
List<telegram.PhotoSize>:
"""
if not data:
return []
labeled_prices = list()
for labeled_price in data:
labeled_prices.append(LabeledPrice.de_json(labeled_price, bot))
return labeled_prices
+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):
"""
+259 -101
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,15 @@
# 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)
User, Video, Voice, Venue, MessageEntity, Game, Invoice, SuccessfulPayment)
from telegram.utils.deprecate import warn_deprecate_obj
from telegram.utils.helpers import escape_html, escape_markdown
from telegram.videonote import VideoNote
class Message(TelegramObject):
@@ -34,36 +36,69 @@ class Message(TelegramObject):
* In Python `from` is a reserved word, use `from_user` instead.
Attributes:
message_id (int):
from_user (:class:`telegram.User`):
date (:class:`datetime.datetime`):
forward_from (:class:`telegram.User`):
forward_from_chat (:class:`telegram.Chat`):
forward_from_message_id (int):
forward_date (:class:`datetime.datetime`):
reply_to_message (:class:`telegram.Message`):
edit_date (:class:`datetime.datetime`):
text (str):
audio (:class:`telegram.Audio`):
document (:class:`telegram.Document`):
game (:class:`telegram.Game`):
photo (List[:class:`telegram.PhotoSize`]):
sticker (:class:`telegram.Sticker`):
video (:class:`telegram.Video`):
voice (:class:`telegram.Voice`):
caption (str):
contact (:class:`telegram.Contact`):
location (:class:`telegram.Location`):
new_chat_member (:class:`telegram.User`):
left_chat_member (:class:`telegram.User`):
new_chat_title (str):
new_chat_photo (List[:class:`telegram.PhotoSize`]):
delete_chat_photo (bool):
group_chat_created (bool):
supergroup_chat_created (bool):
migrate_to_chat_id (int):
migrate_from_chat_id (int):
channel_chat_created (bool):
message_id (int): Unique message identifier inside this chat
from_user (:class:`telegram.User`): Sender, can be empty for messages sent to channels
date (:class:`datetime.datetime`): Date the message was sent in Unix time
chat (:class:`telegram.Chat`): Conversation the message belongs to
forward_from (:class:`telegram.User`): For forwarded messages, sender of the original
message
forward_from_chat (:class:`telegram.Chat`): For messages forwarded from a channel,
information about the original channel
forward_from_message_id (int): For forwarded channel posts, identifier of the original
message in the channel
forward_date (:class:`datetime.datetime`): For forwarded messages, date the original
message was sent in Unix time
reply_to_message (:class:`telegram.Message`): For replies, the original message. Note
that the Message object in this field will not contain further reply_to_message
fields even if it itself is a reply.
edit_date (:class:`datetime.datetime`): Date the message was last edited in Unix time
text (str): For text messages, the actual UTF-8 text of the message, 0-4096 characters.
entities (List[:class:`telegram.MessageEntity`]): For text messages, special entities
like usernames, URLs, bot commands, etc. that appear in the text. See
parse_entity and parse_entities methods for how to use properly
video_note (:class:`telegram.VideoNote`): Message is a video note, information about the
video message
audio (:class:`telegram.Audio`): Message is an audio file, information about the file
document (:class:`telegram.Document`): Message is a general file, information about the
file
game (:class:`telegram.Game`):Message is a game, information about the game
photo (List[:class:`telegram.PhotoSize`]): Message is a photo, available sizes of the photo
sticker (:class:`telegram.Sticker`): Message is a sticker, information about the sticker
video (:class:`telegram.Video`): Message is a video, information about the video
voice (:class:`telegram.Voice`): Message is a voice message, information about the file
caption (str): Caption for the document, photo or video, 0-200 characters
contact (:class:`telegram.Contact`): Message is a shared contact, information about the
contact
location (:class:`telegram.Location`): Message is a shared location, information about the
location
new_chat_member (:class:`telegram.User`): A new member was added to the group,
information about them (this member may be the bot itself)
left_chat_member (:class:`telegram.User`): A member was removed from the group,
information about them (this member may be the bot itself)
new_chat_title (str): A chat title was changed to this value
new_chat_photo (List[:class:`telegram.PhotoSize`]): A chat photo was change to this value
delete_chat_photo (bool): Service message: the chat photo was deleted
group_chat_created (bool): Service message: the group has been created
supergroup_chat_created (bool): Service message: the supergroup has been created. This
field can't be received in a message coming through updates, because bot can't be a
member of a supergroup when it is created. It can only be found in reply_to_message
if someone replies to a very first message in a directly created supergroup.
migrate_to_chat_id (int): The group has been migrated to a supergroup with the specified
identifier.
migrate_from_chat_id (int): The supergroup has been migrated from a group with the
specified identifier.
channel_chat_created (bool): Service message: the channel has been created. This field
can't be received in a message coming through updates, because bot can't be a member
of a channel when it is created. It can only be found in reply_to_message if someone
replies to a very first message in a channel.
pinned_message (:class:`telegram.message`): Specified message was pinned. Note that the
Message object in this field will not contain further reply_to_message fields even if
it is itself a reply.
invoice (:class:`telegram.Invoice`): Message is an invoice for a payment, information
about the invoice.
successful_payment (:class:`telegram.SuccessfulPayment`): Message is a service message
about a successful payment, information about the payment.
bot (Optional[telegram.Bot]): The Bot to use for instance methods
Deprecated: 4.0
new_chat_participant (:class:`telegram.User`): Use `new_chat_member`
@@ -71,40 +106,6 @@ class Message(TelegramObject):
left_chat_participant (:class:`telegram.User`): Use `left_chat_member`
instead.
Args:
message_id (int):
from_user (:class:`telegram.User`):
date (:class:`datetime.datetime`):
chat (:class:`telegram.Chat`):
forward_from (Optional[:class:`telegram.User`]):
forward_from_chat (Optional[:class:`telegram.Chat`]):
forward_from_message_id (Optional[int]):
forward_date (Optional[:class:`datetime.datetime`]):
reply_to_message (Optional[:class:`telegram.Message`]):
edit_date (Optional[:class:`datetime.datetime`]):
text (Optional[str]):
audio (Optional[:class:`telegram.Audio`]):
document (Optional[:class:`telegram.Document`]):
game (Optional[:class:`telegram.Game`]):
photo (Optional[List[:class:`telegram.PhotoSize`]]):
sticker (Optional[:class:`telegram.Sticker`]):
video (Optional[:class:`telegram.Video`]):
voice (Optional[:class:`telegram.Voice`]):
caption (Optional[str]):
contact (Optional[:class:`telegram.Contact`]):
location (Optional[:class:`telegram.Location`]):
new_chat_member (Optional[:class:`telegram.User`]):
left_chat_member (Optional[:class:`telegram.User`]):
new_chat_title (Optional[str]):
new_chat_photo (Optional[List[:class:`telegram.PhotoSize`]):
delete_chat_photo (Optional[bool]):
group_chat_created (Optional[bool]):
supergroup_chat_created (Optional[bool]):
migrate_to_chat_id (Optional[int]):
migrate_from_chat_id (Optional[int]):
channel_chat_created (Optional[bool]):
bot (Optional[Bot]): The Bot to use for instance methods
"""
def __init__(self,
@@ -117,7 +118,7 @@ class Message(TelegramObject):
forward_date=None,
reply_to_message=None,
edit_date=None,
text='',
text=None,
entities=None,
audio=None,
document=None,
@@ -125,23 +126,27 @@ class Message(TelegramObject):
sticker=None,
video=None,
voice=None,
caption='',
caption=None,
contact=None,
location=None,
venue=None,
new_chat_member=None,
new_chat_members=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,
invoice=None,
successful_payment=None,
bot=None,
video_note=None,
**kwargs):
# Required
self.message_id = int(message_id)
@@ -163,25 +168,31 @@ class Message(TelegramObject):
self.sticker = sticker
self.video = video
self.voice = voice
self.video_note = video_note
self.caption = caption
self.contact = contact
self.location = location
self.venue = venue
self.new_chat_member = new_chat_member
self._new_chat_member = new_chat_member
self.new_chat_members = new_chat_members
self.left_chat_member = left_chat_member
self.new_chat_title = new_chat_title
self.new_chat_photo = new_chat_photo
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.invoice = invoice
self.successful_payment = successful_payment
self.bot = bot
self._id_attrs = (self.message_id,)
@property
def chat_id(self):
"""int: Short for :attr:`Message.chat.id`"""
@@ -200,8 +211,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)
@@ -216,13 +229,17 @@ class Message(TelegramObject):
data['sticker'] = Sticker.de_json(data.get('sticker'), bot)
data['video'] = Video.de_json(data.get('video'), bot)
data['voice'] = Voice.de_json(data.get('voice'), bot)
data['video_note'] = VideoNote.de_json(data.get('video_note'), bot)
data['contact'] = Contact.de_json(data.get('contact'), bot)
data['location'] = Location.de_json(data.get('location'), bot)
data['venue'] = Venue.de_json(data.get('venue'), bot)
data['new_chat_member'] = User.de_json(data.get('new_chat_member'), bot)
data['new_chat_members'] = User.de_list(data.get('new_chat_members'), bot)
data['left_chat_member'] = User.de_json(data.get('left_chat_member'), bot)
data['new_chat_photo'] = PhotoSize.de_list(data.get('new_chat_photo'), bot)
data['pinned_message'] = Message.de_json(data.get('pinned_message'), bot)
data['invoice'] = Invoice.de_json(data.get('invoice'), bot)
data['successful_payment'] = SuccessfulPayment.de_json(data.get('successful_payment'), bot)
return Message(bot=bot, **data)
@@ -253,6 +270,9 @@ class Message(TelegramObject):
data['entities'] = [e.to_dict() for e in self.entities]
if self.new_chat_photo:
data['new_chat_photo'] = [p.to_dict() for p in self.new_chat_photo]
data['new_chat_member'] = data.pop('_new_chat_member', None)
if self.new_chat_members:
data['new_chat_members'] = [u.to_dict() for u in self.new_chat_members]
return data
@@ -308,7 +328,7 @@ class Message(TelegramObject):
def reply_text(self, *args, **kwargs):
"""
Shortcut for ``bot.sendMessage(update.message.chat_id, *args, **kwargs)``
Shortcut for ``bot.send_message(update.message.chat_id, *args, **kwargs)``
Keyword Args:
quote (Optional[bool]): If set to ``True``, the message is sent as an actual reply to
@@ -317,11 +337,11 @@ class Message(TelegramObject):
"""
self._quote(kwargs)
return self.bot.sendMessage(self.chat_id, *args, **kwargs)
return self.bot.send_message(self.chat_id, *args, **kwargs)
def reply_photo(self, *args, **kwargs):
"""
Shortcut for ``bot.sendPhoto(update.message.chat_id, *args, **kwargs)``
Shortcut for ``bot.send_photo(update.message.chat_id, *args, **kwargs)``
Keyword Args:
quote (Optional[bool]): If set to ``True``, the photo is sent as an actual reply to
@@ -334,11 +354,11 @@ class Message(TelegramObject):
"""
self._quote(kwargs)
return self.bot.sendPhoto(self.chat_id, *args, **kwargs)
return self.bot.send_photo(self.chat_id, *args, **kwargs)
def reply_audio(self, *args, **kwargs):
"""
Shortcut for ``bot.sendAudio(update.message.chat_id, *args, **kwargs)``
Shortcut for ``bot.send_audio(update.message.chat_id, *args, **kwargs)``
Keyword Args:
quote (Optional[bool]): If set to ``True``, the audio is sent as an actual reply to
@@ -351,11 +371,11 @@ class Message(TelegramObject):
"""
self._quote(kwargs)
return self.bot.sendAudio(self.chat_id, *args, **kwargs)
return self.bot.send_audio(self.chat_id, *args, **kwargs)
def reply_document(self, *args, **kwargs):
"""
Shortcut for ``bot.sendDocument(update.message.chat_id, *args, **kwargs)``
Shortcut for ``bot.send_document(update.message.chat_id, *args, **kwargs)``
Keyword Args:
quote (Optional[bool]): If set to ``True``, the document is sent as an actual reply to
@@ -368,11 +388,11 @@ class Message(TelegramObject):
"""
self._quote(kwargs)
return self.bot.sendDocument(self.chat_id, *args, **kwargs)
return self.bot.send_document(self.chat_id, *args, **kwargs)
def reply_sticker(self, *args, **kwargs):
"""
Shortcut for ``bot.sendSticker(update.message.chat_id, *args, **kwargs)``
Shortcut for ``bot.send_sticker(update.message.chat_id, *args, **kwargs)``
Keyword Args:
quote (Optional[bool]): If set to ``True``, the sticker is sent as an actual reply to
@@ -385,11 +405,11 @@ class Message(TelegramObject):
"""
self._quote(kwargs)
return self.bot.sendSticker(self.chat_id, *args, **kwargs)
return self.bot.send_sticker(self.chat_id, *args, **kwargs)
def reply_video(self, *args, **kwargs):
"""
Shortcut for ``bot.sendVideo(update.message.chat_id, *args, **kwargs)``
Shortcut for ``bot.send_video(update.message.chat_id, *args, **kwargs)``
Keyword Args:
quote (Optional[bool]): If set to ``True``, the video is sent as an actual reply to
@@ -402,11 +422,28 @@ class Message(TelegramObject):
"""
self._quote(kwargs)
return self.bot.sendVideo(self.chat_id, *args, **kwargs)
return self.bot.send_video(self.chat_id, *args, **kwargs)
def reply_video_note(self, *args, **kwargs):
"""
Shortcut for ``bot.send_video_note(update.message.chat_id, *args, **kwargs)``
Keyword Args:
quote (Optional[bool]): If set to ``True``, the video is sent as an actual reply to
this message. If ``reply_to_message_id`` is passed in ``kwargs``, this parameter
will be ignored. Default: ``True`` in group chats and ``False`` in private chats.
Returns:
:class:`telegram.Message`: On success, instance representing the message posted.
"""
self._quote(kwargs)
return self.bot.send_video_note(self.chat_id, *args, **kwargs)
def reply_voice(self, *args, **kwargs):
"""
Shortcut for ``bot.sendVoice(update.message.chat_id, *args, **kwargs)``
Shortcut for ``bot.send_voice(update.message.chat_id, *args, **kwargs)``
Keyword Args:
quote (Optional[bool]): If set to ``True``, the voice is sent as an actual reply to
@@ -419,11 +456,11 @@ class Message(TelegramObject):
"""
self._quote(kwargs)
return self.bot.sendVoice(self.chat_id, *args, **kwargs)
return self.bot.send_voice(self.chat_id, *args, **kwargs)
def reply_location(self, *args, **kwargs):
"""
Shortcut for ``bot.sendLocation(update.message.chat_id, *args, **kwargs)``
Shortcut for ``bot.send_location(update.message.chat_id, *args, **kwargs)``
Keyword Args:
quote (Optional[bool]): If set to ``True``, the location is sent as an actual reply to
@@ -436,11 +473,11 @@ class Message(TelegramObject):
"""
self._quote(kwargs)
return self.bot.sendLocation(self.chat_id, *args, **kwargs)
return self.bot.send_location(self.chat_id, *args, **kwargs)
def reply_venue(self, *args, **kwargs):
"""
Shortcut for ``bot.sendVenue(update.message.chat_id, *args, **kwargs)``
Shortcut for ``bot.send_venue(update.message.chat_id, *args, **kwargs)``
Keyword Args:
quote (Optional[bool]): If set to ``True``, the venue is sent as an actual reply to
@@ -453,11 +490,11 @@ class Message(TelegramObject):
"""
self._quote(kwargs)
return self.bot.sendVenue(self.chat_id, *args, **kwargs)
return self.bot.send_venue(self.chat_id, *args, **kwargs)
def reply_contact(self, *args, **kwargs):
"""
Shortcut for ``bot.sendContact(update.message.chat_id, *args, **kwargs)``
Shortcut for ``bot.send_contact(update.message.chat_id, *args, **kwargs)``
Keyword Args:
quote (Optional[bool]): If set to ``True``, the contact is sent as an actual reply to
@@ -470,12 +507,12 @@ class Message(TelegramObject):
"""
self._quote(kwargs)
return self.bot.sendContact(self.chat_id, *args, **kwargs)
return self.bot.send_contact(self.chat_id, *args, **kwargs)
def forward(self, chat_id, disable_notification=False):
"""Shortcut for
>>> bot.forwardMessage(chat_id=chat_id,
>>> bot.forward_message(chat_id=chat_id,
... from_chat_id=update.message.chat_id,
... disable_notification=disable_notification,
... message_id=update.message.message_id)
@@ -484,7 +521,7 @@ class Message(TelegramObject):
:class:`telegram.Message`: On success, instance representing the message forwarded.
"""
return self.bot.forwardMessage(
return self.bot.forward_message(
chat_id=chat_id,
from_chat_id=self.chat_id,
disable_notification=disable_notification,
@@ -494,7 +531,7 @@ class Message(TelegramObject):
"""
Shortcut for
>>> bot.editMessageText(chat_id=message.chat_id,
>>> bot.edit_message_text(chat_id=message.chat_id,
... message_id=message.message_id,
... *args, **kwargs)
@@ -511,7 +548,7 @@ class Message(TelegramObject):
"""
Shortcut for
>>> bot.editMessageCaption(chat_id=message.chat_id,
>>> bot.edit_message_caption(chat_id=message.chat_id,
... message_id=message.message_id,
... *args, **kwargs)
@@ -527,7 +564,7 @@ class Message(TelegramObject):
"""
Shortcut for
>>> bot.editReplyMarkup(chat_id=message.chat_id,
>>> bot.edit_message_reply_markup(chat_id=message.chat_id,
... message_id=message.message_id,
... *args, **kwargs)
@@ -539,6 +576,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 +636,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 +645,108 @@ 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
if not sys.maxunicode == 0xffff:
message_text = message_text.encode('utf-16-le')
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
if sys.maxunicode == 0xffff:
markdown_text += escape_html(message_text[last_offset:entity.offset]) + insert
else:
markdown_text += escape_html(message_text[last_offset * 2:entity.offset * 2]
.decode('utf-16-le')) + insert
last_offset = entity.offset + entity.length
if sys.maxunicode == 0xffff:
markdown_text += escape_html(message_text[last_offset:])
else:
markdown_text += escape_html(message_text[last_offset * 2:].decode('utf-16-le'))
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
if not sys.maxunicode == 0xffff:
message_text = message_text.encode('utf-16-le')
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
if sys.maxunicode == 0xffff:
markdown_text += escape_markdown(message_text[last_offset:entity.offset]) + insert
else:
markdown_text += escape_markdown(message_text[last_offset * 2:entity.offset * 2]
.decode('utf-16-le')) + insert
last_offset = entity.offset + entity.length
if sys.maxunicode == 0xffff:
markdown_text += escape_markdown(message_text[last_offset:])
else:
markdown_text += escape_markdown(message_text[last_offset * 2:].decode('utf-16-le'))
return markdown_text
@property
def new_chat_member(self):
warn_deprecate_obj('new_chat_member', 'new_chat_members')
return self._new_chat_member
+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
+59
View File
@@ -0,0 +1,59 @@
#!/usr/bin/env python
#
# 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/].
"""This module contains an object that represents a Telegram OrderInfo."""
from telegram import TelegramObject, ShippingAddress
class OrderInfo(TelegramObject):
"""This object represents information about an order.
Attributes:
name (Optional[str]): User name
phone_number (Optional[str]): User's phone number
email (Optional[str]): User email
shipping_address (Optional[:class:`telegram.ShippingAddress`]): User shipping address
**kwargs (dict): Arbitrary keyword arguments.
"""
def __init__(self, name=None, phone_number=None, email=None, shipping_address=None, **kwargs):
self.name = name
self.phone_number = phone_number
self.email = email
self.shipping_address = shipping_address
@staticmethod
def de_json(data, bot):
"""
Args:
data (dict):
bot (telegram.Bot):
Returns:
telegram.OrderInfo:
"""
if not data:
return OrderInfo()
data = super(OrderInfo, OrderInfo).de_json(data, bot)
data['shipping_address'] = ShippingAddress.de_json(data.get('shipping_address'), bot)
return OrderInfo(**data)
+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

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