Compare commits

...

29 Commits

Author SHA1 Message Date
Hinrich Mahler 15268acb27 Bump to v12.8 2020-06-22 20:20:00 +02:00
Poolitzer 927502e588 API 4.9 (#1980)
* Add Basketball Dice

Added Basketball Dice Variation

* Update dice.py

* Update dice.py

* Update telegram/dice.py

Co-authored-by: Bibo-Joshi <hinrich.mahler@freenet.de>

* Update bot.py

* Update filters.py

* Update test_filters.py

* Fixed whitespaces

* Update dice.py

* Fix line length

* adding dice values to docstring

* adding via_bot to message and thumb_mime_type to iqresults

* feat: updating docs

* feat: improving message attribute test

* Fix flake8

Co-authored-by: Jannik <32801117+code1mountain@users.noreply.github.com>
Co-authored-by: Bibo-Joshi <hinrich.mahler@freenet.de>
2020-06-22 20:09:52 +02:00
Yan 0af5cc2db8 Don't override builtin method help() in examples (#1997)
* Do not override builtin method help()

* Rename inlinebot and inlinekeyboard /help function to not conflict with builtin
2020-06-16 17:07:05 +02:00
Bibo-Joshi 6005861f46 Stabilize CI (#2000) 2020-06-15 18:45:38 +02:00
Nils K 8406889179 Remove Python 2 Support (#1715)
* Remove usages of python-future lib

* Remove python2 datetime.timezone replacement

* Remove python2 workaround in InputFile.__init__

* Remove import of str necessary for python2

* Remove urllib2 import necessary for python2

* Remove a mention of python 2 in doc

* Remove python 2 from travis config file

* Remove python 2 from appveyor config

* Remove python2 from debian build rules

* Remove unnecessarry aliasing of time.perf_counter

* Remove python 2 from github workflow

* Remove mention of python 2 in descriptions/readme

* Remove version check for queue import

* Remove version checks in tests

* Adjust docs to correctly mention supported version

* Fix indentation

* Remove unused 'sys' imports

* Fix indentation

* Remove references to mq.curtime in tests

* Replace super calls by argumentsless version

* Remove future dependency

* Fix error in de_json declaration

* Use python3 metaclass syntax

* Use implicit inheriting from object

* Remove accidentally committed .vscode folder

* Use nameless f-string and raw string

* Fix regex string literal syntax

* Remove old style classes

* Run pyupgrade

* Fix leftover from automatic merge

* Fix lint errors

* Update telegram/files/sticker.py

Co-authored-by: Bibo-Joshi <hinrich.mahler@freenet.de>
2020-06-15 18:20:51 +02:00
n5y a4e78f6183 Add standalone example on error handlers (#1983)
* Remove error handlers from examples

Most examples use the same error handler, that error handler logs
update.to_dict but doesn't log error traceback. Hiding error traceback
is quite bad, removing the error handler entirely causes PTB to use
default error logging which does include error traceback.

* adding error handling example

* Change error handler example

Including:
- Change the telegram message to include usual python error message.
- HTML-escape the strings used to build the telegram message.
- Capitalize comments and add more empty lines to hopefully unify the
  style with other examples, at least a bit.
- Reorder imports.

* Add an error-rising command to the error handler example

* Slightly change example error handler docstring and comments

* Make telegram message sent by the error handler example more readable

* Rename error_handler.py to errorhandlerbot.py and add a start command

* Change error handler example to work without developer chat id

* Revert "Change error handler example to work without developer chat id"

This reverts commit c4efea6f

* Make bot token a module level constant in the error handler example

Otherwise the example will require two edits 40 lines apart to run.

* Show chat id in start command of the error handler example

The example requires you to set developer chat id, this change will
make things easier for users that don't know how to see their chat id.

* Add errorhandlerbot.py to the examples folder readme

Co-authored-by: poolitzer <25934244+poolitzer@users.noreply.github.com>
Co-authored-by: Bibo-Joshi <hinrich.mahler@freenet.de>
2020-06-12 18:50:12 +02:00
Bibo-Joshi 8c6cb44a85 Update examples Readme (#1995) 2020-06-12 13:58:20 +02:00
n5y ac7cc7fe5e Ignore private attributes in TelegramObject.to_dict() (#1989) 2020-06-10 22:21:25 +02:00
Bibo-Joshi a42b68933c Add User.send_poll (#1968) 2020-05-27 21:59:49 +02:00
ѕнιναм c2d91c752f Typo-Fix (#1962) 2020-05-22 15:32:03 +02:00
Bibo-Joshi 5057825586 Doc fixes (#1940)
* Update notes on editing messages

* Update thumb and InputMedia* doc strings

* Fix attribute docstring for Updater.user_sig_handler

* Improve rendering for CCs attributes

* fix doc str for InputMedia*.media attribute

* Minor fix
2020-05-15 15:59:41 +02:00
Bibo-Joshi 613175b2c4 Allow updating ids/usernames of Filters.{user, chat} (#1757)
* Make Filters.user attrs mutable

* Add test_filters_user_empty_args

* Add test_filters_user_empty_args

* fix locks

* Make codecov happy

* Make user_ids and usernames sets

* Correct doc string

* Address review

* Review Vol. II

* Apply suggestions from code review

Co-authored-by: Noam Meltzer <tsnoam@gmail.com>

* Review Vol III.

* propery setter is now only a wrapper to a private method + more cleanups

pylint complained on some extra stuff, so cleaned them as well

* Review Vol. IV

* Review Vol. V

* Apply changes to Filters.chat

Co-authored-by: Noam Meltzer <tsnoam@gmail.com>
2020-05-10 12:15:11 +02:00
Bibo-Joshi 1330696259 Improve readability of nested conversation example (#1943) 2020-05-04 16:59:51 +02:00
n5y 5898e1fe7a Remove NullHandlers (#1913) 2020-05-03 10:28:03 +02:00
Bibo-Joshi b6dec118c1 Update contribution guide and stale bot (#1937)
* * Remove developers Mail from contrib guide
* stricter settings for stale bot

* Remove link, too ...

* Link to TG Group instead
2020-05-02 22:24:12 +02:00
Hinrich Mahler 186fd1b418 Bump version to v12.7 2020-05-02 12:14:38 +02:00
Hinrich Mahler 284786fdb8 Fix doc string of run_monthly 2020-05-02 12:14:01 +02:00
Bibo-Joshi c7c56ad24e Api 4.8 (#1917)
* API 4.8

* Elaborate docs

* Address review

* Fix Message.to_json/dict() test

* More coverage

* Update telegram/bot.py

Co-authored-by: Noam Meltzer <tsnoam@gmail.com>

Co-authored-by: Noam Meltzer <tsnoam@gmail.com>
2020-05-02 11:56:52 +02:00
D David Livingston ae17ce977e Add JobQueue.run_monthly() (#1705)
* added monthly job

* removed fold argument

* addressed pr comments

* addressed pr comments

* made changes from pr review

* updated comments

* clean up code

* Update .pre-commit-config.yaml

* Minor cleanup

* Update according to #1685, minor robustness changes

Co-authored-by: Hinrich Mahler <hinrich.mahler@freenet.de>
2020-05-02 08:59:50 +02:00
Bibo-Joshi 7e231183c4 Add tzinfo kwarg to from_timestamp() (#1621)
* Add tz kwarg to from_timestamp()

* Correct handling of tzinfo=None

* Small Improvements

* None-tz yields naive dto

* Remove legacey compatibility of UTC stuff

* Update telegram/utils/helpers.py

Co-authored-by: Noam Meltzer <tsnoam@gmail.com>
2020-05-01 22:55:13 +02:00
Bibo-Joshi 8427346a0d Add supegroup for each test bot (#1919) 2020-05-01 21:29:18 +03:00
Bibo-Joshi 632b989d90 Use @abstractmethod instead of raising NotImplementedError (#1905) 2020-05-01 21:27:34 +03:00
Bibo-Joshi 76567ba635 Stabilize CI (#1931) 2020-05-01 13:27:46 +02:00
Bibo-Joshi 2bd3f2a65a Render Notes correctly (#1914)
* Renders Notes in JobQueues docs correctly

* Notes: -> Note:
2020-04-25 12:34:13 +02:00
Bibo-Joshi 26a5006bf1 Update question template (#1910)
* Update formulation in question template

* grammar
2020-04-20 18:11:48 +02:00
Andrej730 110e2df443 Job.next_t (#1685)
* next_t property is added to Job class

Added new property to Job class - next_t, it will show the datetime when the job will be executed next time.
The property is updated during JobQueue._put method, right after job is added to queue.
Related to #1676

* Fixed newline and trailing whitespace

* Fixed PR issues, added test

1. Added setter for next_t - now JobQueue doesn't access protected Job._next_t.
2. Fixed Job class docstring.
3. Added test for next_t property.
4. Set next_t to None for run_once jobs that already ran.

* Fixed Flake8 issues

* Added next_t setter for datetime, added test

1. next_t setter now can accept datetime type.
2. added test for setting datetime to next_t and added some asserts that check tests results.
3. Also noticed Job.days setter raises ValueError when it's more appropriate to raise TypeError.

* Fixed test_warnings, added Number type to next_t setter

1. Changed type of error raised by interval setter from ValueError to TypeError..
2. Fixed test_warning after changing type of errors in Job.days and Job.interval.
3. Added Number type to next_t setter - now it can accept int too.

* Python 2 compatibility for test_job_next_t_property

Added _UTC and _UtcOffsetTimezone for python 2 compatibility

* Fixed PR issues

1. Replaced "datetime.replace tzinfo" with "datetime.astimezone"
2. Moved testing next_t setter to separate test.
3. Changed test_job_next_t_setter so it now uses non UTC timezone.

* Defining tzinfo from run_once, run_repeating

1. Added option to define Job.tzinfo from run_once (by when.tzinfo) and run_repeating (first.tzinfo)
2. Added test to check that tzinfo is always passed correctly.

* address review

Co-authored-by: Hinrich Mahler <hinrich.mahler@freenet.de>
2020-04-18 15:08:16 +02:00
Bibo-Joshi 57546795c5 Notes on Filters.text accepting command messages (#1902) 2020-04-18 12:16:14 +02:00
Hinrich Mahler 314f87ec44 Bump version to v12.6.1 2020-04-11 09:53:29 +02:00
Bibo-Joshi 4bbcd51ef5 Fix serialization of reply_markups (#1889) 2020-04-11 09:44:40 +02:00
225 changed files with 2347 additions and 1073 deletions
+3 -3
View File
@@ -41,9 +41,9 @@ If you already know what you'd like to work on, you can skip this section.
If you have an idea for something to do, first check if it's already been filed on the `issue tracker`_. If so, add a comment to the issue saying you'd like to work on it, and we'll help you get started! Otherwise, please file a new issue and assign yourself to it.
Another great way to start contributing is by writing tests. Tests are really important because they help prevent developers from accidentally breaking existing code, allowing them to build cool things faster. If you're interested in helping out, let the development team know by posting to the `developers' mailing list`_, and we'll help you get started.
Another great way to start contributing is by writing tests. Tests are really important because they help prevent developers from accidentally breaking existing code, allowing them to build cool things faster. If you're interested in helping out, let the development team know by posting to the `Telegram group`_ (use `@admins` to mention the maintainers), and we'll help you get started.
That being said, we want to mention that we are very hesistant about adding new requirements to our projects. If you intend to do this, please state this in an issue and get a verification from one of the maintainers.
That being said, we want to mention that we are very hesitant about adding new requirements to our projects. If you intend to do this, please state this in an issue and get a verification from one of the maintainers.
Instructions for making a code change
#####################################
@@ -245,7 +245,7 @@ break the API classes. For example:
.. _`Code of Conduct`: https://www.python.org/psf/codeofconduct/
.. _`issue tracker`: https://github.com/python-telegram-bot/python-telegram-bot/issues
.. _`developers' mailing list`: mailto:devs@python-telegram-bot.org
.. _`Telegram group`: https://telegram.me/pythontelegrambotgroup
.. _`PEP 8 Style Guide`: https://www.python.org/dev/peps/pep-0008/
.. _`sphinx`: http://sphinx-doc.org
.. _`Google Python Style Guide`: http://google.github.io/styleguide/pyguide.html
+1 -1
View File
@@ -10,7 +10,7 @@ assignees: ''
<!--
Hey there, you have a question? We are happy to answer. Please make sure no similar question was opened already.
The following template is a suggestion how you can report an issue you run into whilst using our library. If you just want to ask a question, feel free to delete everything; just make sure you have a describing title :)
To make it easier for us to help you, please try to follow the template below as closely as possible.
Please mind that there is also a users' Telegram group at https://t.me/pythontelegrambotgroup for questions about the library. Questions asked there might be answered quicker than here. In case you are unable to join our group due to Telegram restrictions, you can use our IRC channel at https://webchat.freenode.net/?channels=##python-telegram-bot to participate in the group.
-->
+1 -1
View File
@@ -1,5 +1,5 @@
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 5
daysUntilStale: 3
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 2
# Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled)
+1 -1
View File
@@ -51,7 +51,7 @@ jobs:
exit ${global_exit}
env:
JOB_INDEX: ${{ strategy.job-index }}
BOTS: W3sidG9rZW4iOiAiNjk2MTg4NzMyOkFBR1Z3RUtmSEhsTmpzY3hFRE5LQXdraEdzdFpfa28xbUMwIiwgInBheW1lbnRfcHJvdmlkZXJfdG9rZW4iOiAiMjg0Njg1MDYzOlRFU1Q6WldGaU1UUmxNbVF5TnpNeSIsICJib3RfbmFtZSI6ICJQVEIgdGVzdHMgb24gVHJhdmlzIHVzaW5nIENQeXRob24gMi43IiwgImJvdF91c2VybmFtZSI6ICJAcHRiX3RyYXZpc19jcHl0aG9uXzI3X2JvdCJ9LCB7InRva2VuIjogIjY3MTQ2ODg4NjpBQUdQR2ZjaVJJQlVORmU4MjR1SVZkcTdKZTNfWW5BVE5HdyIsICJwYXltZW50X3Byb3ZpZGVyX3Rva2VuIjogIjI4NDY4NTA2MzpURVNUOlpHWXdPVGxrTXpNeE4yWTIiLCAiYm90X25hbWUiOiAiUFRCIHRlc3RzIG9uIFRyYXZpcyB1c2luZyBDUHl0aG9uIDMuNCIsICJib3RfdXNlcm5hbWUiOiAiQHB0Yl90cmF2aXNfY3B5dGhvbl8zNF9ib3QifSwgeyJ0b2tlbiI6ICI2MjkzMjY1Mzg6QUFGUnJaSnJCN29CM211ekdzR0pYVXZHRTVDUXpNNUNVNG8iLCAicGF5bWVudF9wcm92aWRlcl90b2tlbiI6ICIyODQ2ODUwNjM6VEVTVDpNbU01WVdKaFl6a3hNMlUxIiwgImJvdF9uYW1lIjogIlBUQiB0ZXN0cyBvbiBUcmF2aXMgdXNpbmcgQ1B5dGhvbiAzLjUiLCAiYm90X3VzZXJuYW1lIjogIkBwdGJfdHJhdmlzX2NweXRob25fMzVfYm90In0sIHsidG9rZW4iOiAiNjQwMjA4OTQzOkFBRmhCalFwOXFtM1JUeFN6VXBZekJRakNsZS1Kano1aGNrIiwgInBheW1lbnRfcHJvdmlkZXJfdG9rZW4iOiAiMjg0Njg1MDYzOlRFU1Q6WXpoa1pUZzFOamMxWXpWbCIsICJib3RfbmFtZSI6ICJQVEIgdGVzdHMgb24gVHJhdmlzIHVzaW5nIENQeXRob24gMy42IiwgImJvdF91c2VybmFtZSI6ICJAcHRiX3RyYXZpc19jcHl0aG9uXzM2X2JvdCJ9LCB7InRva2VuIjogIjY5NTEwNDA4ODpBQUhmenlsSU9qU0lJUy1lT25JMjB5MkUyMEhvZEhzZnotMCIsICJwYXltZW50X3Byb3ZpZGVyX3Rva2VuIjogIjI4NDY4NTA2MzpURVNUOk9HUTFNRGd3WmpJd1pqRmwiLCAiYm90X25hbWUiOiAiUFRCIHRlc3RzIG9uIFRyYXZpcyB1c2luZyBDUHl0aG9uIDMuNyIsICJib3RfdXNlcm5hbWUiOiAiQHB0Yl90cmF2aXNfY3B5dGhvbl8zN19ib3QifSwgeyJ0b2tlbiI6ICI2OTE0MjM1NTQ6QUFGOFdrakNaYm5IcVBfaTZHaFRZaXJGRWxackdhWU9oWDAiLCAicGF5bWVudF9wcm92aWRlcl90b2tlbiI6ICIyODQ2ODUwNjM6VEVTVDpZamM1TlRoaU1tUXlNV1ZoIiwgImJvdF9uYW1lIjogIlBUQiB0ZXN0cyBvbiBUcmF2aXMgdXNpbmcgUHlQeSAyLjciLCAiYm90X3VzZXJuYW1lIjogIkBwdGJfdHJhdmlzX3B5cHlfMjdfYm90In0sIHsidG9rZW4iOiAiNjg0MzM5OTg0OkFBRk1nRUVqcDAxcjVyQjAwN3lDZFZOc2c4QWxOc2FVLWNjIiwgInBheW1lbnRfcHJvdmlkZXJfdG9rZW4iOiAiMjg0Njg1MDYzOlRFU1Q6TVRBek1UWTNNR1V5TmpnMCIsICJib3RfbmFtZSI6ICJQVEIgdGVzdHMgb24gVHJhdmlzIHVzaW5nIFB5UHkgMy41IiwgImJvdF91c2VybmFtZSI6ICJAcHRiX3RyYXZpc19weXB5XzM1X2JvdCJ9LCB7InRva2VuIjogIjY5MDA5MTM0NzpBQUZMbVI1cEFCNVljcGVfbU9oN3pNNEpGQk9oMHozVDBUbyIsICJwYXltZW50X3Byb3ZpZGVyX3Rva2VuIjogIjI4NDY4NTA2MzpURVNUOlpEaGxOekU1TURrd1lXSmkiLCAiYm90X25hbWUiOiAiUFRCIHRlc3RzIG9uIEFwcFZleW9yIHVzaW5nIENQeXRob24gMy40IiwgImJvdF91c2VybmFtZSI6ICJAcHRiX2FwcHZleW9yX2NweXRob25fMzRfYm90In0sIHsidG9rZW4iOiAiNjk0MzA4MDUyOkFBRUIyX3NvbkNrNTVMWTlCRzlBTy1IOGp4aVBTNTVvb0JBIiwgInBheW1lbnRfcHJvdmlkZXJfdG9rZW4iOiAiMjg0Njg1MDYzOlRFU1Q6WW1aaVlXWm1NakpoWkdNeSIsICJib3RfbmFtZSI6ICJQVEIgdGVzdHMgb24gQXBwVmV5b3IgdXNpbmcgQ1B5dGhvbiAyLjciLCAiYm90X3VzZXJuYW1lIjogIkBwdGJfYXBwdmV5b3JfY3B5dGhvbl8yN19ib3QifSwgeyJ0b2tlbiI6ICIxMDU1Mzk3NDcxOkFBRzE4bkJfUzJXQXd1SjNnN29oS0JWZ1hYY2VNbklPeVNjIiwgInBheW1lbnRfcHJvdmlkZXJfdG9rZW4iOiAiMjg0Njg1MDYzOlRFU1Q6TmpBd056QXpZalZpTkdOayIsICJuYW1lIjogIlBUQiB0ZXN0cyBbMF0iLCAidXNlcm5hbWUiOiAicHRiXzBfYm90In0sIHsidG9rZW4iOiAiMTA0NzMyNjc3MTpBQUY4bk90ODFGcFg4bGJidno4VWV3UVF2UmZUYkZmQnZ1SSIsICJwYXltZW50X3Byb3ZpZGVyX3Rva2VuIjogIjI4NDY4NTA2MzpURVNUOllUVTFOVEk0WkdSallqbGkiLCAibmFtZSI6ICJQVEIgdGVzdHMgWzFdIiwgInVzZXJuYW1lIjogInB0Yl8xX2JvdCJ9LCB7InRva2VuIjogIjk3MTk5Mjc0NTpBQUdPa09hVzBOSGpnSXY1LTlqUWJPajR2R3FkaFNGLVV1cyIsICJwYXltZW50X3Byb3ZpZGVyX3Rva2VuIjogIjI4NDY4NTA2MzpURVNUOk5XWmtNV1ZoWWpsallqVTUiLCAibmFtZSI6ICJQVEIgdGVzdHMgWzJdIiwgInVzZXJuYW1lIjogInB0Yl8yX2JvdCJ9XQ==
BOTS: W3sidG9rZW4iOiAiNjk2MTg4NzMyOkFBR1Z3RUtmSEhsTmpzY3hFRE5LQXdraEdzdFpfa28xbUMwIiwgInBheW1lbnRfcHJvdmlkZXJfdG9rZW4iOiAiMjg0Njg1MDYzOlRFU1Q6WldGaU1UUmxNbVF5TnpNeSIsICJib3RfbmFtZSI6ICJQVEIgdGVzdHMgb24gVHJhdmlzIHVzaW5nIENQeXRob24gMi43IiwgInN1cGVyX2dyb3VwX2lkIjogIi0xMDAxMzkwOTgzOTk3IiwgImJvdF91c2VybmFtZSI6ICJAcHRiX3RyYXZpc19jcHl0aG9uXzI3X2JvdCJ9LCB7InRva2VuIjogIjY3MTQ2ODg4NjpBQUdQR2ZjaVJJQlVORmU4MjR1SVZkcTdKZTNfWW5BVE5HdyIsICJwYXltZW50X3Byb3ZpZGVyX3Rva2VuIjogIjI4NDY4NTA2MzpURVNUOlpHWXdPVGxrTXpNeE4yWTIiLCAiYm90X25hbWUiOiAiUFRCIHRlc3RzIG9uIFRyYXZpcyB1c2luZyBDUHl0aG9uIDMuNCIsICJzdXBlcl9ncm91cF9pZCI6ICItMTAwMTQ0NjAyMjUyMiIsICJib3RfdXNlcm5hbWUiOiAiQHB0Yl90cmF2aXNfY3B5dGhvbl8zNF9ib3QifSwgeyJ0b2tlbiI6ICI2MjkzMjY1Mzg6QUFGUnJaSnJCN29CM211ekdzR0pYVXZHRTVDUXpNNUNVNG8iLCAicGF5bWVudF9wcm92aWRlcl90b2tlbiI6ICIyODQ2ODUwNjM6VEVTVDpNbU01WVdKaFl6a3hNMlUxIiwgImJvdF9uYW1lIjogIlBUQiB0ZXN0cyBvbiBUcmF2aXMgdXNpbmcgQ1B5dGhvbiAzLjUiLCAic3VwZXJfZ3JvdXBfaWQiOiAiLTEwMDE0OTY5MTc3NTAiLCAiYm90X3VzZXJuYW1lIjogIkBwdGJfdHJhdmlzX2NweXRob25fMzVfYm90In0sIHsidG9rZW4iOiAiNjQwMjA4OTQzOkFBRmhCalFwOXFtM1JUeFN6VXBZekJRakNsZS1Kano1aGNrIiwgInBheW1lbnRfcHJvdmlkZXJfdG9rZW4iOiAiMjg0Njg1MDYzOlRFU1Q6WXpoa1pUZzFOamMxWXpWbCIsICJib3RfbmFtZSI6ICJQVEIgdGVzdHMgb24gVHJhdmlzIHVzaW5nIENQeXRob24gMy42IiwgInN1cGVyX2dyb3VwX2lkIjogIi0xMDAxMzMzODcxNDYxIiwgImJvdF91c2VybmFtZSI6ICJAcHRiX3RyYXZpc19jcHl0aG9uXzM2X2JvdCJ9LCB7InRva2VuIjogIjY5NTEwNDA4ODpBQUhmenlsSU9qU0lJUy1lT25JMjB5MkUyMEhvZEhzZnotMCIsICJwYXltZW50X3Byb3ZpZGVyX3Rva2VuIjogIjI4NDY4NTA2MzpURVNUOk9HUTFNRGd3WmpJd1pqRmwiLCAiYm90X25hbWUiOiAiUFRCIHRlc3RzIG9uIFRyYXZpcyB1c2luZyBDUHl0aG9uIDMuNyIsICJzdXBlcl9ncm91cF9pZCI6ICItMTAwMTQ3ODI5MzcxNCIsICJib3RfdXNlcm5hbWUiOiAiQHB0Yl90cmF2aXNfY3B5dGhvbl8zN19ib3QifSwgeyJ0b2tlbiI6ICI2OTE0MjM1NTQ6QUFGOFdrakNaYm5IcVBfaTZHaFRZaXJGRWxackdhWU9oWDAiLCAicGF5bWVudF9wcm92aWRlcl90b2tlbiI6ICIyODQ2ODUwNjM6VEVTVDpZamM1TlRoaU1tUXlNV1ZoIiwgImJvdF9uYW1lIjogIlBUQiB0ZXN0cyBvbiBUcmF2aXMgdXNpbmcgUHlQeSAyLjciLCAic3VwZXJfZ3JvdXBfaWQiOiAiLTEwMDEzNjM5MzI1NzMiLCAiYm90X3VzZXJuYW1lIjogIkBwdGJfdHJhdmlzX3B5cHlfMjdfYm90In0sIHsidG9rZW4iOiAiNjg0MzM5OTg0OkFBRk1nRUVqcDAxcjVyQjAwN3lDZFZOc2c4QWxOc2FVLWNjIiwgInBheW1lbnRfcHJvdmlkZXJfdG9rZW4iOiAiMjg0Njg1MDYzOlRFU1Q6TVRBek1UWTNNR1V5TmpnMCIsICJib3RfbmFtZSI6ICJQVEIgdGVzdHMgb24gVHJhdmlzIHVzaW5nIFB5UHkgMy41IiwgInN1cGVyX2dyb3VwX2lkIjogIi0xMDAxNDA3ODM2NjA1IiwgImJvdF91c2VybmFtZSI6ICJAcHRiX3RyYXZpc19weXB5XzM1X2JvdCJ9LCB7InRva2VuIjogIjY5MDA5MTM0NzpBQUZMbVI1cEFCNVljcGVfbU9oN3pNNEpGQk9oMHozVDBUbyIsICJwYXltZW50X3Byb3ZpZGVyX3Rva2VuIjogIjI4NDY4NTA2MzpURVNUOlpEaGxOekU1TURrd1lXSmkiLCAiYm90X25hbWUiOiAiUFRCIHRlc3RzIG9uIEFwcFZleW9yIHVzaW5nIENQeXRob24gMy40IiwgInN1cGVyX2dyb3VwX2lkIjogIi0xMDAxMjc5NjAwMDI2IiwgImJvdF91c2VybmFtZSI6ICJAcHRiX2FwcHZleW9yX2NweXRob25fMzRfYm90In0sIHsidG9rZW4iOiAiNjk0MzA4MDUyOkFBRUIyX3NvbkNrNTVMWTlCRzlBTy1IOGp4aVBTNTVvb0JBIiwgInBheW1lbnRfcHJvdmlkZXJfdG9rZW4iOiAiMjg0Njg1MDYzOlRFU1Q6WW1aaVlXWm1NakpoWkdNeSIsICJib3RfbmFtZSI6ICJQVEIgdGVzdHMgb24gQXBwVmV5b3IgdXNpbmcgQ1B5dGhvbiAyLjciLCAic3VwZXJfZ3JvdXBfaWQiOiAiLTEwMDEyOTMwNzkxNjUiLCAiYm90X3VzZXJuYW1lIjogIkBwdGJfYXBwdmV5b3JfY3B5dGhvbl8yN19ib3QifSwgeyJ0b2tlbiI6ICIxMDU1Mzk3NDcxOkFBRzE4bkJfUzJXQXd1SjNnN29oS0JWZ1hYY2VNbklPeVNjIiwgInBheW1lbnRfcHJvdmlkZXJfdG9rZW4iOiAiMjg0Njg1MDYzOlRFU1Q6TmpBd056QXpZalZpTkdOayIsICJuYW1lIjogIlBUQiB0ZXN0cyBbMF0iLCAic3VwZXJfZ3JvdXBfaWQiOiAiLTEwMDExODU1MDk2MzYiLCAidXNlcm5hbWUiOiAicHRiXzBfYm90In0sIHsidG9rZW4iOiAiMTA0NzMyNjc3MTpBQUY4bk90ODFGcFg4bGJidno4VWV3UVF2UmZUYkZmQnZ1SSIsICJwYXltZW50X3Byb3ZpZGVyX3Rva2VuIjogIjI4NDY4NTA2MzpURVNUOllUVTFOVEk0WkdSallqbGkiLCAibmFtZSI6ICJQVEIgdGVzdHMgWzFdIiwgInN1cGVyX2dyb3VwX2lkIjogIi0xMDAxNDg0Nzk3NjEyIiwgInVzZXJuYW1lIjogInB0Yl8xX2JvdCJ9LCB7InRva2VuIjogIjk3MTk5Mjc0NTpBQUdPa09hVzBOSGpnSXY1LTlqUWJPajR2R3FkaFNGLVV1cyIsICJwYXltZW50X3Byb3ZpZGVyX3Rva2VuIjogIjI4NDY4NTA2MzpURVNUOk5XWmtNV1ZoWWpsallqVTUiLCAibmFtZSI6ICJQVEIgdGVzdHMgWzJdIiwgInN1cGVyX2dyb3VwX2lkIjogIi0xMDAxNDAyMjU1MDcwIiwgInVzZXJuYW1lIjogInB0Yl8yX2JvdCJ9XQ==
TEST_BUILD: ${{ matrix.test-build }}
TEST_PRE_COMMIT: ${{ matrix.test-pre-commit }}
shell: bash --noprofile --norc {0}
+2
View File
@@ -18,6 +18,7 @@ The following wonderful people contributed directly or indirectly to this projec
- `Alateas <https://github.com/alateas>`_
- `Ales Dokshanin <https://github.com/alesdokshanin>`_
- `Ambro17 <https://github.com/Ambro17>`_
- `Andrej Zhilenkov <https://github.com/Andrej730>`_
- `Anton Tagunov <https://github.com/anton-tagunov>`_
- `Avanatiker <https://github.com/Avanatiker>`_
- `Balduro <https://github.com/Balduro>`_
@@ -26,6 +27,7 @@ The following wonderful people contributed directly or indirectly to this projec
- `d-qoi <https://github.com/d-qoi>`_
- `daimajia <https://github.com/daimajia>`_
- `Daniel Reed <https://github.com/nmlorg>`_
- `D David Livingston <https://github.com/daviddl9>`_
- `Eana Hufwe <https://github.com/blueset>`_
- `Ehsan Online <https://github.com/ehsanonline>`_
- `Eli Gao <https://github.com/eligao>`_
+76
View File
@@ -2,6 +2,82 @@
Changelog
=========
Version 12.8
============
*Released 2020-06-22*
**Major Changes:**
- Remove Python 2 support (`#1715`_)
- Bot API 4.9 support (`#1980`_)
- IDs/Usernames of ``Filters.user`` and ``Filters.chat`` can now be updated (`#1757`_)
**Minor changes, CI improvements, doc fixes or bug fixes:**
- Update contribution guide and stale bot (`#1937`_)
- Remove ``NullHandlers`` (`#1913`_)
- Improve and expand examples (`#1943`_, `#1995`_, `#1983`_, `#1997`_)
- Doc fixes (`#1940`_, `#1962`_)
- Add ``User.send_poll()`` shortcut (`#1968`_)
- Ignore private attributes en ``TelegramObject.to_dict()`` (`#1989`_)
- Stabilize CI (`#2000`_)
.. _`#1937`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1937
.. _`#1913`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1913
.. _`#1943`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1943
.. _`#1757`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1757
.. _`#1940`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1940
.. _`#1962`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1962
.. _`#1968`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1968
.. _`#1989`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1989
.. _`#1995`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1995
.. _`#1983`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1983
.. _`#1715`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1715
.. _`#2000`: https://github.com/python-telegram-bot/python-telegram-bot/pull/2000
.. _`#1997`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1997
.. _`#1980`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1980
Version 12.7
============
*Released 2020-05-02*
**Major Changes:**
- Bot API 4.8 support. **Note:** The ``Dice`` object now has a second positional argument ``emoji``. This is relevant, if you instantiate ``Dice`` objects manually. (`#1917`_)
**New Features:**
- New method ``run_mothly`` for the ``JobQueue`` (`#1705`_)
- ``Job.next_t`` now gives the datetime of the jobs next execution (`#1685`_)
**Minor changes, CI improvements, doc fixes or bug fixes:**
- Added ``tzinfo`` argument to ``helpers.from_timestamp`` (`#1621`_)
- Stabalize CI (`#1919`_, `#1931`_)
- Use ABCs ``@abstractmethod`` instead of raising ``NotImplementedError`` for ``Handler``, ``BasePersistence`` and ``BaseFilter`` (`#1905`_)
- Doc fixes (`#1914`_, `#1902`_, `#1910`_)
.. _`#1902`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1902
.. _`#1685`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1685
.. _`#1910`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1910
.. _`#1914`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1914
.. _`#1931`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1931
.. _`#1905`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1905
.. _`#1919`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1919
.. _`#1621`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1621
.. _`#1705`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1705
.. _`#1917`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1917
Version 12.6.1
==============
*Released 2020-04-11*
**Bug fixes:**
- Fix serialization of ``reply_markup`` in media messages (`#1889`_)
.. _`#1889`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1889
Version 12.6
============
*Released 2020-04-10*
+1 -1
View File
@@ -93,7 +93,7 @@ make the development of bots easy and straightforward. These classes are contain
Telegram API support
====================
All types and methods of the Telegram Bot API **4.7** are supported.
All types and methods of the Telegram Bot API **4.8** are supported.
==========
Installing
+1 -1
View File
@@ -15,7 +15,7 @@ Depends: ${python3:Depends}, ${misc:Depends}
Description: We have made you a wrapper you can't refuse!
The Python Telegram bot (Python 3)
This library provides a pure Python interface for the Telegram Bot API.
It's compatible with Python versions 2.7, 3.3+ and PyPy.
It's compatible with Python versions 3.5+ and PyPy.
.
In addition to the pure API implementation, this library features
a number of high-level
+1 -1
View File
@@ -6,7 +6,7 @@
export PYBUILD_NAME=telegram
%:
DEB_BUILD_OPTIONS=nocheck dh $@ --with python2,python3 --buildsystem=pybuild
DEB_BUILD_OPTIONS=nocheck dh $@ --with python3 --buildsystem=pybuild
# If you need to rebuild the Sphinx documentation
+2 -2
View File
@@ -58,9 +58,9 @@ author = u'Leandro Toledo'
# built documents.
#
# The short X.Y version.
version = '12.6' # telegram.__version__[:3]
version = '12.8' # telegram.__version__[:3]
# The full version, including alpha/beta/rc tags.
release = '12.6' # telegram.__version__
release = '12.8' # telegram.__version__
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
+14 -2
View File
@@ -19,20 +19,32 @@ A more complex example of a bot that uses the `ConversationHandler`. It is also
### [`nestedconversationbot.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/nestedconversationbot.py)
A even more complex example of a bot that uses the nested `ConversationHandler`s. While it's certainly not that complex that you couldn't built it without nested `ConversationHanldler`s, it gives a good impression on how to work with them. Of course, there is a [fancy state diagram](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/nestedconversationbot.png) for this example, too!
### [`persistentconversationbot.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/persistentconversationbot.py)
A basic example of a bot store conversation state and user_data over multiple restarts.
### [`inlinekeyboard.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/inlinekeyboard.py)
This example sheds some light on inline keyboards, callback queries and message editing.
### [`inlinekeyboard2.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/inlinekeyboard2.py)
A more complex example about inline keyboards, callback queries and message editing. This example showcases how an interactive menu could be build using inline keyboards.
### [`deeplinking.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/deeplinking.py)
A basic example on how to use deeplinking with inline keyboards.
### [`inlinebot.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/inlinebot.py)
A basic example of an [inline bot](https://core.telegram.org/bots/inline). Don't forget to enable inline mode with [@BotFather](https://telegram.me/BotFather).
### [`pollbot.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/pollbot.py)
This example sheds some light on polls, poll answers and the corresponding handlers.
### [`passportbot.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/passportbot.py)
A basic example of a bot that can accept passports. Use in combination with [`passportbot.html`](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/passportbot.html). Don't forget to enable and configure payments with [@BotFather](https://telegram.me/BotFather). Check out this [guide](https://git.io/fAvYd) on Telegram passports in PTB.
### [`paymentbot.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/paymentbot.py)
A basic example of a bot that can accept payments. Don't forget to enable and configure payments with [@BotFather](https://telegram.me/BotFather).
### [`persistentconversationbot.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/persistentconversationbot.py)
A basic example of a bot store conversation state and user_data over multiple restarts.
### [`errorhandlerbot.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/errorhandlerbot.py)
A basic example on how to set up a costum error handler.
## Pure API
The [`echobot.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/echobot.py) example uses only the pure, "bare-metal" API wrapper.
-8
View File
@@ -108,11 +108,6 @@ def cancel(update, context):
return ConversationHandler.END
def error(update, context):
"""Log Errors caused by Updates."""
logger.warning('Update "%s" caused error "%s"', update, context.error)
def main():
# Create the Updater and pass it your bot's token.
# Make sure to set use_context=True to use the new context based callbacks
@@ -143,9 +138,6 @@ def main():
dp.add_handler(conv_handler)
# log all errors
dp.add_error_handler(error)
# Start the Bot
updater.start_polling()
-8
View File
@@ -96,11 +96,6 @@ def done(update, context):
return ConversationHandler.END
def error(update, context):
"""Log Errors caused by Updates."""
logger.warning('Update "%s" caused error "%s"', update, context.error)
def main():
# Create the Updater and pass it your bot's token.
# Make sure to set use_context=True to use the new context based callbacks
@@ -135,9 +130,6 @@ def main():
dp.add_handler(conv_handler)
# log all errors
dp.add_error_handler(error)
# Start the Bot
updater.start_polling()
+2 -10
View File
@@ -61,7 +61,7 @@ def deep_linked_level_2(update, context):
bot = context.bot
url = helpers.create_deep_linked_url(bot.get_me().username, USING_ENTITIES)
text = "You can also mask the deep-linked URLs as links: " \
"[▶️ CLICK HERE]({0}).".format(url)
"[▶️ CLICK HERE]({}).".format(url)
update.message.reply_text(text, parse_mode=ParseMode.MARKDOWN, disable_web_page_preview=True)
@@ -69,12 +69,7 @@ def deep_linked_level_3(update, context):
"""Reached through the USING_ENTITIES payload"""
payload = context.args
update.message.reply_text("Congratulations! This is as deep as it gets 👏🏻\n\n"
"The payload was: {0}".format(payload))
def error(update, context):
"""Log Errors caused by Updates."""
logger.warning('Update "%s" caused error "%s"', update, context.error)
"The payload was: {}".format(payload))
def main():
@@ -103,9 +98,6 @@ def main():
# Make sure the deep-linking handlers occur *before* the normal /start handler.
dp.add_handler(CommandHandler("start", start))
# log all errors
dp.add_error_handler(error)
# Start the Bot
updater.start_polling()
+2 -10
View File
@@ -33,7 +33,7 @@ def start(update, context):
update.message.reply_text('Hi!')
def help(update, context):
def help_command(update, context):
"""Send a message when the command /help is issued."""
update.message.reply_text('Help!')
@@ -43,11 +43,6 @@ def echo(update, context):
update.message.reply_text(update.message.text)
def error(update, context):
"""Log Errors caused by Updates."""
logger.warning('Update "%s" caused error "%s"', update, context.error)
def main():
"""Start the bot."""
# Create the Updater and pass it your bot's token.
@@ -60,14 +55,11 @@ def main():
# on different commands - answer in Telegram
dp.add_handler(CommandHandler("start", start))
dp.add_handler(CommandHandler("help", help))
dp.add_handler(CommandHandler("help", help_command))
# on noncommand i.e message - echo the message on Telegram
dp.add_handler(MessageHandler(Filters.text, echo))
# log all errors
dp.add_error_handler(error)
# Start the Bot
updater.start_polling()
+95
View File
@@ -0,0 +1,95 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# This program is dedicated to the public domain under the CC0 license.
"""
This is a very simple example on how one could implement a custom error handler
"""
import html
import json
import logging
import traceback
from telegram import Update, ParseMode
from telegram.ext import Updater, CallbackContext, CommandHandler
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
level=logging.INFO)
logger = logging.getLogger(__name__)
# The token you got from @botfather when you created the bot
BOT_TOKEN = "TOKEN"
# This can be your own ID, or one for a developer group/channel.
# You can use the /start command of this bot to see your chat id.
DEVELOPER_CHAT_ID = 123456789
def error_handler(update: Update, context: CallbackContext):
"""Log the error and send a telegram message to notify the developer."""
# Log the error before we do anything else, so we can see it even if something breaks.
logger.error(msg="Exception while handling an update:", exc_info=context.error)
# traceback.format_exception returns the usual python message about an exception, but as a
# list of strings rather than a single string, so we have to join them together.
tb_list = traceback.format_exception(None, context.error, context.error.__traceback__)
tb = ''.join(tb_list)
# Build the message with some markup and additional information about what happened.
# You might need to add some logic to deal with messages longer than the 4096 character limit.
message = (
'An exception was raised while handling an update\n'
'<pre>update = {}</pre>\n\n'
'<pre>context.chat_data = {}</pre>\n\n'
'<pre>context.user_data = {}</pre>\n\n'
'<pre>{}</pre>'
).format(
html.escape(json.dumps(update.to_dict(), indent=2, ensure_ascii=False)),
html.escape(str(context.chat_data)),
html.escape(str(context.user_data)),
html.escape(tb)
)
# Finally, send the message
context.bot.send_message(chat_id=DEVELOPER_CHAT_ID, text=message, parse_mode=ParseMode.HTML)
def bad_command(update: Update, context: CallbackContext):
"""Raise an error to trigger the error handler."""
context.bot.wrong_method_name()
def start(update: Update, context: CallbackContext):
update.effective_message.reply_html('Use /bad_command to cause an error.\n'
'Your chat id is <code>{}</code>.'
.format(update.effective_chat.id))
def main():
# Create the Updater and pass it your bot's token.
# Make sure to set use_context=True to use the new context based callbacks
# Post version 12 this will no longer be necessary
updater = Updater(BOT_TOKEN, use_context=True)
# Get the dispatcher to register handlers
dp = updater.dispatcher
# Register the commands...
dp.add_handler(CommandHandler('start', start))
dp.add_handler(CommandHandler('bad_command', bad_command))
# ...and the error handler
dp.add_error_handler(error_handler)
# 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()
+2 -10
View File
@@ -34,7 +34,7 @@ def start(update, context):
update.message.reply_text('Hi!')
def help(update, context):
def help_command(update, context):
"""Send a message when the command /help is issued."""
update.message.reply_text('Help!')
@@ -64,11 +64,6 @@ def inlinequery(update, context):
update.inline_query.answer(results)
def error(update, context):
"""Log Errors caused by Updates."""
logger.warning('Update "%s" caused error "%s"', update, context.error)
def main():
# Create the Updater and pass it your bot's token.
# Make sure to set use_context=True to use the new context based callbacks
@@ -80,14 +75,11 @@ def main():
# on different commands - answer in Telegram
dp.add_handler(CommandHandler("start", start))
dp.add_handler(CommandHandler("help", help))
dp.add_handler(CommandHandler("help", help_command))
# on noncommand i.e message - echo the message on Telegram
dp.add_handler(InlineQueryHandler(inlinequery))
# log all errors
dp.add_error_handler(error)
# Start the Bot
updater.start_polling()
+2 -8
View File
@@ -36,15 +36,10 @@ def button(update, context):
query.edit_message_text(text="Selected option: {}".format(query.data))
def help(update, context):
def help_command(update, context):
update.message.reply_text("Use /start to test this bot.")
def error(update, context):
"""Log Errors caused by Updates."""
logger.warning('Update "%s" caused error "%s"', update, context.error)
def main():
# Create the Updater and pass it your bot's token.
# Make sure to set use_context=True to use the new context based callbacks
@@ -53,8 +48,7 @@ def main():
updater.dispatcher.add_handler(CommandHandler('start', start))
updater.dispatcher.add_handler(CallbackQueryHandler(button))
updater.dispatcher.add_handler(CommandHandler('help', help))
updater.dispatcher.add_error_handler(error)
updater.dispatcher.add_handler(CommandHandler('help', help_command))
# Start the Bot
updater.start_polling()
-8
View File
@@ -149,11 +149,6 @@ def end(update, context):
return ConversationHandler.END
def error(update, context):
"""Log Errors caused by Updates."""
logger.warning('Update "%s" caused error "%s"', update, context.error)
def main():
# Create the Updater and pass it your bot's token.
updater = Updater("TOKEN", use_context=True)
@@ -184,9 +179,6 @@ def main():
# updates
dp.add_handler(conv_handler)
# log all errors
dp.add_error_handler(error)
# Start the Bot
updater.start_polling()
+16 -24
View File
@@ -100,14 +100,14 @@ def show_data(update, context):
text = ''
if level == SELF:
for person in user_data[level]:
text += '\nName: {0}, Age: {1}'.format(person.get(NAME, '-'), person.get(AGE, '-'))
text += '\nName: {}, Age: {}'.format(person.get(NAME, '-'), person.get(AGE, '-'))
else:
male, female = _name_switcher(level)
for person in user_data[level]:
gender = female if person[GENDER] == FEMALE else male
text += '\n{0}: Name: {1}, Age: {2}'.format(gender, person.get(NAME, '-'),
person.get(AGE, '-'))
text += '\n{}: Name: {}, Age: {}'.format(gender, person.get(NAME, '-'),
person.get(AGE, '-'))
return text
ud = context.user_data
@@ -267,12 +267,6 @@ def stop_nested(update, context):
return STOPPING
# Error handler
def error(update, context):
"""Log Errors caused by Updates."""
logger.warning('Update "%s" caused error "%s"', update, context.error)
def main():
# Create the Updater and pass it your bot's token.
# Make sure to set use_context=True to use the new context based callbacks
@@ -313,8 +307,8 @@ def main():
states={
SELECTING_LEVEL: [CallbackQueryHandler(select_gender,
pattern='^{0}$|^{1}$'.format(str(PARENTS),
str(CHILDREN)))],
pattern='^{}$|^{}$'.format(str(PARENTS),
str(CHILDREN)))],
SELECTING_GENDER: [description_conv]
},
@@ -335,32 +329,30 @@ def main():
)
# Set up top level ConversationHandler (selecting action)
# Because the states of the third level conversation map to the ones of the econd level
# conversation, we need to make sure the top level conversation can also handle them
selection_handlers = [
add_member_conv,
CallbackQueryHandler(show_data, pattern='^' + str(SHOWING) + '$'),
CallbackQueryHandler(adding_self, pattern='^' + str(ADDING_SELF) + '$'),
CallbackQueryHandler(end, pattern='^' + str(END) + '$'),
]
conv_handler = ConversationHandler(
entry_points=[CommandHandler('start', start)],
states={
SHOWING: [CallbackQueryHandler(start, pattern='^' + str(END) + '$')],
SELECTING_ACTION: [
add_member_conv,
CallbackQueryHandler(show_data, pattern='^' + str(SHOWING) + '$'),
CallbackQueryHandler(adding_self, pattern='^' + str(ADDING_SELF) + '$'),
CallbackQueryHandler(end, pattern='^' + str(END) + '$'),
],
SELECTING_ACTION: selection_handlers,
SELECTING_LEVEL: selection_handlers,
DESCRIBING_SELF: [description_conv],
STOPPING: [CommandHandler('start', start)],
},
fallbacks=[CommandHandler('stop', stop)],
)
# Because the states of the third level conversation map to the ones of the
# second level conversation, we need to be a bit hacky about that:
conv_handler.states[SELECTING_LEVEL] = conv_handler.states[SELECTING_ACTION]
conv_handler.states[STOPPING] = conv_handler.entry_points
dp.add_handler(conv_handler)
# log all errors
dp.add_error_handler(error)
# Start the Bot
updater.start_polling()
-8
View File
@@ -77,11 +77,6 @@ def msg(update, context):
actual_file.download()
def error(update, context):
"""Log Errors caused by Updates."""
logger.warning('Update "%s" caused error "%s"', update, context.error)
def main():
"""Start the bot."""
# Create the Updater and pass it your token and private key
@@ -93,9 +88,6 @@ def main():
# On messages that include passport data call msg
dp.add_handler(MessageHandler(Filters.passport_data, msg))
# log all errors
dp.add_error_handler(error)
# Start the Bot
updater.start_polling()
-8
View File
@@ -19,11 +19,6 @@ logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s
logger = logging.getLogger(__name__)
def error(update, context):
"""Log Errors caused by Updates."""
logger.warning('Update "%s" caused error "%s"', update, context.error)
def start_callback(update, context):
msg = "Use /shipping to get an invoice for shipping-payment, "
msg += "or /noshipping for an invoice without shipping."
@@ -134,9 +129,6 @@ def main():
# 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()
-7
View File
@@ -107,11 +107,6 @@ def done(update, context):
return ConversationHandler.END
def error(update, context):
"""Log Errors caused by Updates."""
logger.warning('Update "%s" caused error "%s"', update, context.error)
def main():
# Create the Updater and pass it your bot's token.
pp = PicklePersistence(filename='conversationbot')
@@ -149,8 +144,6 @@ def main():
show_data_handler = CommandHandler('show_data', show_data)
dp.add_handler(show_data_handler)
# log all errors
dp.add_error_handler(error)
# Start the Bot
updater.start_polling()
-8
View File
@@ -77,11 +77,6 @@ def unset(update, context):
update.message.reply_text('Timer successfully unset!')
def error(update, context):
"""Log Errors caused by Updates."""
logger.warning('Update "%s" caused error "%s"', update, context.error)
def main():
"""Run bot."""
# Create the Updater and pass it your bot's token.
@@ -101,9 +96,6 @@ def main():
pass_chat_data=True))
dp.add_handler(CommandHandler("unset", unset, pass_chat_data=True))
# log all errors
dp.add_error_handler(error)
# Start the Bot
updater.start_polling()
-1
View File
@@ -1,4 +1,3 @@
future>=0.16.0
certifi
tornado>=5.1
cryptography
+4 -6
View File
@@ -20,7 +20,6 @@ import sys
import subprocess
import certifi
import future
from . import __version__ as telegram_ver
@@ -37,11 +36,10 @@ def _git_revision():
def print_ver_info():
git_revision = _git_revision()
print('python-telegram-bot {0}'.format(telegram_ver) + (' ({0})'.format(git_revision)
if git_revision else ''))
print('certifi {0}'.format(certifi.__version__))
print('future {0}'.format(future.__version__))
print('Python {0}'.format(sys.version.replace('\n', ' ')))
print('python-telegram-bot {}'.format(telegram_ver) + (' ({})'.format(git_revision)
if git_revision else ''))
print('certifi {}'.format(certifi.__version__))
print('Python {}'.format(sys.version.replace('\n', ' ')))
def main():
+4 -12
View File
@@ -23,13 +23,10 @@ try:
except ImportError:
import json
from abc import ABCMeta
class TelegramObject(object):
class TelegramObject:
"""Base class for most telegram objects."""
__metaclass__ = ABCMeta
_id_attrs = ()
def __str__(self):
@@ -60,12 +57,7 @@ class TelegramObject(object):
data = dict()
for key in iter(self.__dict__):
if key in ('bot',
'_id_attrs',
'_credentials',
'_decrypted_credentials',
'_decrypted_data',
'_decrypted_secret'):
if key == 'bot' or key.startswith('_'):
continue
value = self.__dict__[key]
@@ -82,9 +74,9 @@ class TelegramObject(object):
def __eq__(self, other):
if isinstance(other, self.__class__):
return self._id_attrs == other._id_attrs
return super(TelegramObject, self).__eq__(other) # pylint: disable=no-member
return super().__eq__(other) # pylint: disable=no-member
def __hash__(self):
if self._id_attrs:
return hash((self.__class__, self._id_attrs)) # pylint: disable=no-member
return super(TelegramObject, self).__hash__()
return super().__hash__()
+144 -95
View File
@@ -34,7 +34,6 @@ from datetime import datetime
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
from future.utils import string_types
from telegram import (User, Message, Update, Chat, ChatMember, UserProfilePhotos, File,
ReplyMarkup, TelegramObject, WebhookInfo, GameHighScore, StickerSet,
@@ -44,8 +43,6 @@ from telegram.error import InvalidToken, TelegramError
from telegram.utils.helpers import to_timestamp, DEFAULT_NONE
from telegram.utils.request import Request
logging.getLogger(__name__).addHandler(logging.NullHandler())
def info(func):
@functools.wraps(func)
@@ -96,7 +93,7 @@ class Bot(TelegramObject):
defaults = kwargs.get('defaults')
# Make an instance of the class
instance = super(Bot, cls).__new__(cls)
instance = super().__new__(cls)
if not defaults:
return instance
@@ -163,7 +160,9 @@ class Bot(TelegramObject):
if reply_markup is not None:
if isinstance(reply_markup, ReplyMarkup):
data['reply_markup'] = reply_markup.to_dict()
# We need to_json() instead of to_dict() here, because reply_markups may be
# attached to media messages, which aren't json dumped by utils.request
data['reply_markup'] = reply_markup.to_json()
else:
data['reply_markup'] = reply_markup
@@ -266,7 +265,7 @@ class Bot(TelegramObject):
def name(self):
""":obj:`str`: Bot's @username."""
return '@{0}'.format(self.username)
return '@{}'.format(self.username)
@log
def get_me(self, timeout=None, **kwargs):
@@ -285,7 +284,7 @@ class Bot(TelegramObject):
:class:`telegram.TelegramError`
"""
url = '{0}/getMe'.format(self.base_url)
url = '{}/getMe'.format(self.base_url)
result = self._request.get(url, timeout=timeout)
@@ -335,7 +334,7 @@ class Bot(TelegramObject):
:class:`telegram.TelegramError`
"""
url = '{0}/sendMessage'.format(self.base_url)
url = '{}/sendMessage'.format(self.base_url)
data = {'chat_id': chat_id, 'text': text}
@@ -380,7 +379,7 @@ class Bot(TelegramObject):
:class:`telegram.TelegramError`
"""
url = '{0}/deleteMessage'.format(self.base_url)
url = '{}/deleteMessage'.format(self.base_url)
data = {'chat_id': chat_id, 'message_id': message_id}
@@ -418,7 +417,7 @@ class Bot(TelegramObject):
:class:`telegram.TelegramError`
"""
url = '{0}/forwardMessage'.format(self.base_url)
url = '{}/forwardMessage'.format(self.base_url)
data = {}
@@ -479,7 +478,7 @@ class Bot(TelegramObject):
:class:`telegram.TelegramError`
"""
url = '{0}/sendPhoto'.format(self.base_url)
url = '{}/sendPhoto'.format(self.base_url)
if isinstance(photo, PhotoSize):
photo = photo.file_id
@@ -549,9 +548,10 @@ class Bot(TelegramObject):
JSON-serialized object for an inline keyboard, custom reply keyboard, instructions
to remove reply keyboard or to force a reply from the user.
thumb (`filelike object`, optional): Thumbnail of the file sent; can be ignored if
thumbnail generation for the file is supported server-side. The thumbnail should
be in JPEG format and less than 200 kB in size. A thumbnail's width and height
should not exceed 320. Ignored if the file is not is passed as a string or file_id.
thumbnail generation for the file is supported server-side. The thumbnail should be
in JPEG format and less than 200 kB in size. A thumbnail's width and height should
not exceed 320. Ignored if the file is not uploaded using multipart/form-data.
Thumbnails can't be reused and can be only uploaded as a new file.
timeout (:obj:`int` | :obj:`float`, optional): Send file timeout (default: 20 seconds).
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
@@ -562,7 +562,7 @@ class Bot(TelegramObject):
:class:`telegram.TelegramError`
"""
url = '{0}/sendAudio'.format(self.base_url)
url = '{}/sendAudio'.format(self.base_url)
if isinstance(audio, Audio):
audio = audio.file_id
@@ -636,9 +636,10 @@ class Bot(TelegramObject):
JSON-serialized object for an inline keyboard, custom reply keyboard, instructions
to remove reply keyboard or to force a reply from the user.
thumb (`filelike object`, optional): Thumbnail of the file sent; can be ignored if
thumbnail generation for the file is supported server-side. The thumbnail should
be in JPEG format and less than 200 kB in size. A thumbnail's width and height
should not exceed 320. Ignored if the file is not passed as a string or file_id.
thumbnail generation for the file is supported server-side. The thumbnail should be
in JPEG format and less than 200 kB in size. A thumbnail's width and height should
not exceed 320. Ignored if the file is not uploaded using multipart/form-data.
Thumbnails can't be reused and can be only uploaded as a new file.
timeout (:obj:`int` | :obj:`float`, optional): Send file timeout (default: 20 seconds).
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
@@ -649,7 +650,7 @@ class Bot(TelegramObject):
:class:`telegram.TelegramError`
"""
url = '{0}/sendDocument'.format(self.base_url)
url = '{}/sendDocument'.format(self.base_url)
if isinstance(document, Document):
document = document.file_id
@@ -712,7 +713,7 @@ class Bot(TelegramObject):
:class:`telegram.TelegramError`
"""
url = '{0}/sendSticker'.format(self.base_url)
url = '{}/sendSticker'.format(self.base_url)
if isinstance(sticker, Sticker):
sticker = sticker.file_id
@@ -778,9 +779,10 @@ class Bot(TelegramObject):
JSON-serialized object for an inline keyboard, custom reply keyboard, instructions
to remove reply keyboard or to force a reply from the user.
thumb (`filelike object`, optional): Thumbnail of the file sent; can be ignored if
thumbnail generation for the file is supported server-side. The thumbnail should
be in JPEG format and less than 200 kB in size. A thumbnails width and height
should not exceed 320. Ignored if the file is not is passed as a string or file_id.
thumbnail generation for the file is supported server-side. The thumbnail should be
in JPEG format and less than 200 kB in size. A thumbnail's width and height should
not exceed 320. Ignored if the file is not uploaded using multipart/form-data.
Thumbnails can't be reused and can be only uploaded as a new file.
timeout (:obj:`int` | :obj:`float`, optional): Send file timeout (default: 20 seconds).
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
@@ -791,7 +793,7 @@ class Bot(TelegramObject):
:class:`telegram.TelegramError`
"""
url = '{0}/sendVideo'.format(self.base_url)
url = '{}/sendVideo'.format(self.base_url)
if isinstance(video, Video):
video = video.file_id
@@ -860,9 +862,10 @@ class Bot(TelegramObject):
JSON-serialized object for an inline keyboard, custom reply keyboard,
instructions to remove reply keyboard or to force a reply from the user.
thumb (`filelike object`, optional): Thumbnail of the file sent; can be ignored if
thumbnail generation for the file is supported server-side. The thumbnail should
be in JPEG format and less than 200 kB in size. A thumbnails width and height
should not exceed 320. Ignored if the file is not is passed as a string or file_id.
thumbnail generation for the file is supported server-side. The thumbnail should be
in JPEG format and less than 200 kB in size. A thumbnail's width and height should
not exceed 320. Ignored if the file is not uploaded using multipart/form-data.
Thumbnails can't be reused and can be only uploaded as a new file.
timeout (:obj:`int` | :obj:`float`, optional): Send file timeout (default: 20 seconds).
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
@@ -873,7 +876,7 @@ class Bot(TelegramObject):
:class:`telegram.TelegramError`
"""
url = '{0}/sendVideoNote'.format(self.base_url)
url = '{}/sendVideoNote'.format(self.base_url)
if isinstance(video_note, VideoNote):
video_note = video_note.file_id
@@ -927,9 +930,10 @@ class Bot(TelegramObject):
width (:obj:`int`, optional): Animation width.
height (:obj:`int`, optional): Animation height.
thumb (`filelike object`, optional): Thumbnail of the file sent; can be ignored if
thumbnail generation for the file is supported server-side. The thumbnail should
be in JPEG format and less than 200 kB in size. A thumbnails width and height
should not exceed 320. Ignored if the file is not is passed as a string or file_id.
thumbnail generation for the file is supported server-side. The thumbnail should be
in JPEG format and less than 200 kB in size. A thumbnail's width and height should
not exceed 320. Ignored if the file is not uploaded using multipart/form-data.
Thumbnails can't be reused and can be only uploaded as a new file.
caption (:obj:`str`, optional): Animation caption (may also be used when resending
animations by file_id), 0-1024 characters after entities parsing.
parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to
@@ -952,7 +956,7 @@ class Bot(TelegramObject):
:class:`telegram.TelegramError`
"""
url = '{0}/sendAnimation'.format(self.base_url)
url = '{}/sendAnimation'.format(self.base_url)
if isinstance(animation, Animation):
animation = animation.file_id
@@ -1033,7 +1037,7 @@ class Bot(TelegramObject):
:class:`telegram.TelegramError`
"""
url = '{0}/sendVoice'.format(self.base_url)
url = '{}/sendVoice'.format(self.base_url)
if isinstance(voice, Voice):
voice = voice.file_id
@@ -1082,7 +1086,7 @@ class Bot(TelegramObject):
:class:`telegram.TelegramError`
"""
url = '{0}/sendMediaGroup'.format(self.base_url)
url = '{}/sendMediaGroup'.format(self.base_url)
data = {'chat_id': chat_id, 'media': media}
@@ -1150,7 +1154,7 @@ class Bot(TelegramObject):
:class:`telegram.TelegramError`
"""
url = '{0}/sendLocation'.format(self.base_url)
url = '{}/sendLocation'.format(self.base_url)
if not ((latitude is not None and longitude is not None) or location):
raise ValueError("Either location or latitude and longitude must be passed as"
@@ -1213,7 +1217,7 @@ class Bot(TelegramObject):
edited Message is returned, otherwise ``True`` is returned.
"""
url = '{0}/editMessageLiveLocation'.format(self.base_url)
url = '{}/editMessageLiveLocation'.format(self.base_url)
if not (all([latitude, longitude]) or location):
raise ValueError("Either location or latitude and longitude must be passed as"
@@ -1267,7 +1271,7 @@ class Bot(TelegramObject):
edited Message is returned, otherwise ``True`` is returned.
"""
url = '{0}/stopMessageLiveLocation'.format(self.base_url)
url = '{}/stopMessageLiveLocation'.format(self.base_url)
data = {}
@@ -1333,7 +1337,7 @@ class Bot(TelegramObject):
:class:`telegram.TelegramError`
"""
url = '{0}/sendVenue'.format(self.base_url)
url = '{}/sendVenue'.format(self.base_url)
if not (venue or all([latitude, longitude, address, title])):
raise ValueError("Either venue or latitude, longitude, address and title must be"
@@ -1411,7 +1415,7 @@ class Bot(TelegramObject):
:class:`telegram.TelegramError`
"""
url = '{0}/sendContact'.format(self.base_url)
url = '{}/sendContact'.format(self.base_url)
if (not contact) and (not all([phone_number, first_name])):
raise ValueError("Either contact or phone_number and first_name must be passed as"
@@ -1469,7 +1473,7 @@ class Bot(TelegramObject):
:class:`telegram.TelegramError`
"""
url = '{0}/sendGame'.format(self.base_url)
url = '{}/sendGame'.format(self.base_url)
data = {'chat_id': chat_id, 'game_short_name': game_short_name}
@@ -1503,7 +1507,7 @@ class Bot(TelegramObject):
:class:`telegram.TelegramError`
"""
url = '{0}/sendChatAction'.format(self.base_url)
url = '{}/sendChatAction'.format(self.base_url)
data = {'chat_id': chat_id, 'action': action}
data.update(kwargs)
@@ -1567,7 +1571,7 @@ class Bot(TelegramObject):
:class:`telegram.TelegramError`
"""
url = '{0}/answerInlineQuery'.format(self.base_url)
url = '{}/answerInlineQuery'.format(self.base_url)
for res in results:
if res._has_parse_mode and res.parse_mode == DEFAULT_NONE:
@@ -1633,7 +1637,7 @@ class Bot(TelegramObject):
:class:`telegram.TelegramError`
"""
url = '{0}/getUserProfilePhotos'.format(self.base_url)
url = '{}/getUserProfilePhotos'.format(self.base_url)
data = {'user_id': user_id}
@@ -1681,7 +1685,7 @@ class Bot(TelegramObject):
:class:`telegram.TelegramError`
"""
url = '{0}/getFile'.format(self.base_url)
url = '{}/getFile'.format(self.base_url)
try:
file_id = file_id.file_id
@@ -1694,7 +1698,7 @@ class Bot(TelegramObject):
result = self._request.post(url, data, timeout=timeout)
if result.get('file_path'):
result['file_path'] = '%s/%s' % (self.base_file_url, result['file_path'])
result['file_path'] = '{}/{}'.format(self.base_file_url, result['file_path'])
return File.de_json(result, self)
@@ -1725,7 +1729,7 @@ class Bot(TelegramObject):
:class:`telegram.TelegramError`
"""
url = '{0}/kickChatMember'.format(self.base_url)
url = '{}/kickChatMember'.format(self.base_url)
data = {'chat_id': chat_id, 'user_id': user_id}
data.update(kwargs)
@@ -1762,7 +1766,7 @@ class Bot(TelegramObject):
:class:`telegram.TelegramError`
"""
url = '{0}/unbanChatMember'.format(self.base_url)
url = '{}/unbanChatMember'.format(self.base_url)
data = {'chat_id': chat_id, 'user_id': user_id}
data.update(kwargs)
@@ -1814,7 +1818,7 @@ class Bot(TelegramObject):
:class:`telegram.TelegramError`
"""
url_ = '{0}/answerCallbackQuery'.format(self.base_url)
url_ = '{}/answerCallbackQuery'.format(self.base_url)
data = {'callback_query_id': callback_query_id}
@@ -1876,7 +1880,7 @@ class Bot(TelegramObject):
:class:`telegram.TelegramError`
"""
url = '{0}/editMessageText'.format(self.base_url)
url = '{}/editMessageText'.format(self.base_url)
data = {'text': text}
@@ -1940,7 +1944,7 @@ class Bot(TelegramObject):
'edit_message_caption: Both chat_id and message_id are required when '
'inline_message_id is not specified')
url = '{0}/editMessageCaption'.format(self.base_url)
url = '{}/editMessageCaption'.format(self.base_url)
data = {}
@@ -2002,7 +2006,7 @@ class Bot(TelegramObject):
'edit_message_media: Both chat_id and message_id are required when '
'inline_message_id is not specified')
url = '{0}/editMessageMedia'.format(self.base_url)
url = '{}/editMessageMedia'.format(self.base_url)
data = {'media': media}
@@ -2055,7 +2059,7 @@ class Bot(TelegramObject):
'edit_message_reply_markup: Both chat_id and message_id are required when '
'inline_message_id is not specified')
url = '{0}/editMessageReplyMarkup'.format(self.base_url)
url = '{}/editMessageReplyMarkup'.format(self.base_url)
data = {}
@@ -2101,7 +2105,7 @@ class Bot(TelegramObject):
updates may be received for a short period of time.
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
Notes:
Note:
1. This method will not work if an outgoing webhook is set up.
2. In order to avoid getting duplicate updates, recalculate offset after each
server response.
@@ -2114,7 +2118,7 @@ class Bot(TelegramObject):
:class:`telegram.TelegramError`
"""
url = '{0}/getUpdates'.format(self.base_url)
url = '{}/getUpdates'.format(self.base_url)
data = {'timeout': timeout}
@@ -2208,7 +2212,7 @@ class Bot(TelegramObject):
.. _`guide to Webhooks`: https://core.telegram.org/bots/webhooks
"""
url_ = '{0}/setWebhook'.format(self.base_url)
url_ = '{}/setWebhook'.format(self.base_url)
# Backwards-compatibility: 'url' used to be named 'webhook_url'
if 'webhook_url' in kwargs: # pragma: no cover
@@ -2258,7 +2262,7 @@ class Bot(TelegramObject):
:class:`telegram.TelegramError`
"""
url = '{0}/deleteWebhook'.format(self.base_url)
url = '{}/deleteWebhook'.format(self.base_url)
data = kwargs
@@ -2285,7 +2289,7 @@ class Bot(TelegramObject):
:class:`telegram.TelegramError`
"""
url = '{0}/leaveChat'.format(self.base_url)
url = '{}/leaveChat'.format(self.base_url)
data = {'chat_id': chat_id}
data.update(kwargs)
@@ -2315,7 +2319,7 @@ class Bot(TelegramObject):
:class:`telegram.TelegramError`
"""
url = '{0}/getChat'.format(self.base_url)
url = '{}/getChat'.format(self.base_url)
data = {'chat_id': chat_id}
data.update(kwargs)
@@ -2350,7 +2354,7 @@ class Bot(TelegramObject):
:class:`telegram.TelegramError`
"""
url = '{0}/getChatAdministrators'.format(self.base_url)
url = '{}/getChatAdministrators'.format(self.base_url)
data = {'chat_id': chat_id}
data.update(kwargs)
@@ -2378,7 +2382,7 @@ class Bot(TelegramObject):
:class:`telegram.TelegramError`
"""
url = '{0}/getChatMembersCount'.format(self.base_url)
url = '{}/getChatMembersCount'.format(self.base_url)
data = {'chat_id': chat_id}
data.update(kwargs)
@@ -2407,7 +2411,7 @@ class Bot(TelegramObject):
:class:`telegram.TelegramError`
"""
url = '{0}/getChatMember'.format(self.base_url)
url = '{}/getChatMember'.format(self.base_url)
data = {'chat_id': chat_id, 'user_id': user_id}
data.update(kwargs)
@@ -2438,7 +2442,7 @@ class Bot(TelegramObject):
:obj:`bool`: On success, ``True`` is returned.
"""
url = '{0}/setChatStickerSet'.format(self.base_url)
url = '{}/setChatStickerSet'.format(self.base_url)
data = {'chat_id': chat_id, 'sticker_set_name': sticker_set_name}
@@ -2465,7 +2469,7 @@ class Bot(TelegramObject):
:obj:`bool`: On success, ``True`` is returned.
"""
url = '{0}/deleteChatStickerSet'.format(self.base_url)
url = '{}/deleteChatStickerSet'.format(self.base_url)
data = {'chat_id': chat_id}
@@ -2488,7 +2492,7 @@ class Bot(TelegramObject):
:class:`telegram.WebhookInfo`
"""
url = '{0}/getWebhookInfo'.format(self.base_url)
url = '{}/getWebhookInfo'.format(self.base_url)
data = kwargs
@@ -2537,7 +2541,7 @@ class Bot(TelegramObject):
current score in the chat and force is False.
"""
url = '{0}/setGameScore'.format(self.base_url)
url = '{}/setGameScore'.format(self.base_url)
data = {'user_id': user_id, 'score': score}
@@ -2586,7 +2590,7 @@ class Bot(TelegramObject):
:class:`telegram.TelegramError`
"""
url = '{0}/getGameHighScores'.format(self.base_url)
url = '{}/getGameHighScores'.format(self.base_url)
data = {'user_id': user_id}
@@ -2687,7 +2691,7 @@ class Bot(TelegramObject):
:class:`telegram.TelegramError`
"""
url = '{0}/sendInvoice'.format(self.base_url)
url = '{}/sendInvoice'.format(self.base_url)
data = {
'chat_id': chat_id,
@@ -2700,7 +2704,7 @@ class Bot(TelegramObject):
'prices': [p.to_dict() for p in prices]
}
if provider_data is not None:
if isinstance(provider_data, string_types):
if isinstance(provider_data, str):
data['provider_data'] = provider_data
else:
data['provider_data'] = json.dumps(provider_data)
@@ -2779,7 +2783,7 @@ class Bot(TelegramObject):
'answerShippingQuery: If ok is False, error_message '
'should not be empty and there should not be shipping_options')
url_ = '{0}/answerShippingQuery'.format(self.base_url)
url_ = '{}/answerShippingQuery'.format(self.base_url)
data = {'shipping_query_id': shipping_query_id, 'ok': ok}
@@ -2834,7 +2838,7 @@ class Bot(TelegramObject):
'not be error_message; if ok is False, error_message '
'should not be empty')
url_ = '{0}/answerPreCheckoutQuery'.format(self.base_url)
url_ = '{}/answerPreCheckoutQuery'.format(self.base_url)
data = {'pre_checkout_query_id': pre_checkout_query_id, 'ok': ok}
@@ -2879,7 +2883,7 @@ class Bot(TelegramObject):
Raises:
:class:`telegram.TelegramError`
"""
url = '{0}/restrictChatMember'.format(self.base_url)
url = '{}/restrictChatMember'.format(self.base_url)
data = {'chat_id': chat_id, 'user_id': user_id, 'permissions': permissions.to_dict()}
@@ -2938,7 +2942,7 @@ class Bot(TelegramObject):
:class:`telegram.TelegramError`
"""
url = '{0}/promoteChatMember'.format(self.base_url)
url = '{}/promoteChatMember'.format(self.base_url)
data = {'chat_id': chat_id, 'user_id': user_id}
@@ -2987,7 +2991,7 @@ class Bot(TelegramObject):
:class:`telegram.TelegramError`
"""
url = '{0}/setChatPermissions'.format(self.base_url)
url = '{}/setChatPermissions'.format(self.base_url)
data = {'chat_id': chat_id, 'permissions': permissions.to_dict()}
data.update(kwargs)
@@ -3025,7 +3029,7 @@ class Bot(TelegramObject):
:class:`telegram.TelegramError`
"""
url = '{0}/setChatAdministratorCustomTitle'.format(self.base_url)
url = '{}/setChatAdministratorCustomTitle'.format(self.base_url)
data = {'chat_id': chat_id, 'user_id': user_id, 'custom_title': custom_title}
data.update(kwargs)
@@ -3056,7 +3060,7 @@ class Bot(TelegramObject):
:class:`telegram.TelegramError`
"""
url = '{0}/exportChatInviteLink'.format(self.base_url)
url = '{}/exportChatInviteLink'.format(self.base_url)
data = {'chat_id': chat_id}
data.update(kwargs)
@@ -3088,7 +3092,7 @@ class Bot(TelegramObject):
:class:`telegram.TelegramError`
"""
url = '{0}/setChatPhoto'.format(self.base_url)
url = '{}/setChatPhoto'.format(self.base_url)
if InputFile.is_file(photo):
photo = InputFile(photo)
@@ -3122,7 +3126,7 @@ class Bot(TelegramObject):
:class:`telegram.TelegramError`
"""
url = '{0}/deleteChatPhoto'.format(self.base_url)
url = '{}/deleteChatPhoto'.format(self.base_url)
data = {'chat_id': chat_id}
data.update(kwargs)
@@ -3154,7 +3158,7 @@ class Bot(TelegramObject):
:class:`telegram.TelegramError`
"""
url = '{0}/setChatTitle'.format(self.base_url)
url = '{}/setChatTitle'.format(self.base_url)
data = {'chat_id': chat_id, 'title': title}
data.update(kwargs)
@@ -3186,7 +3190,7 @@ class Bot(TelegramObject):
:class:`telegram.TelegramError`
"""
url = '{0}/setChatDescription'.format(self.base_url)
url = '{}/setChatDescription'.format(self.base_url)
data = {'chat_id': chat_id, 'description': description}
data.update(kwargs)
@@ -3223,7 +3227,7 @@ class Bot(TelegramObject):
:class:`telegram.TelegramError`
"""
url = '{0}/pinChatMessage'.format(self.base_url)
url = '{}/pinChatMessage'.format(self.base_url)
data = {'chat_id': chat_id, 'message_id': message_id}
@@ -3258,7 +3262,7 @@ class Bot(TelegramObject):
:class:`telegram.TelegramError`
"""
url = '{0}/unpinChatMessage'.format(self.base_url)
url = '{}/unpinChatMessage'.format(self.base_url)
data = {'chat_id': chat_id}
data.update(kwargs)
@@ -3285,7 +3289,7 @@ class Bot(TelegramObject):
:class:`telegram.TelegramError`
"""
url = '{0}/getStickerSet'.format(self.base_url)
url = '{}/getStickerSet'.format(self.base_url)
data = {'name': name}
data.update(kwargs)
@@ -3322,7 +3326,7 @@ class Bot(TelegramObject):
:class:`telegram.TelegramError`
"""
url = '{0}/uploadStickerFile'.format(self.base_url)
url = '{}/uploadStickerFile'.format(self.base_url)
if InputFile.is_file(png_sticker):
png_sticker = InputFile(png_sticker)
@@ -3387,7 +3391,7 @@ class Bot(TelegramObject):
:class:`telegram.TelegramError`
"""
url = '{0}/createNewStickerSet'.format(self.base_url)
url = '{}/createNewStickerSet'.format(self.base_url)
if InputFile.is_file(png_sticker):
png_sticker = InputFile(png_sticker)
@@ -3404,6 +3408,8 @@ class Bot(TelegramObject):
if contains_masks is not None:
data['contains_masks'] = contains_masks
if mask_position is not None:
# We need to_json() instead of to_dict() here, because we're sending a media
# message here, which isn't json dumped by utils.request
data['mask_position'] = mask_position.to_json()
data.update(kwargs)
@@ -3457,7 +3463,7 @@ class Bot(TelegramObject):
:class:`telegram.TelegramError`
"""
url = '{0}/addStickerToSet'.format(self.base_url)
url = '{}/addStickerToSet'.format(self.base_url)
if InputFile.is_file(png_sticker):
png_sticker = InputFile(png_sticker)
@@ -3472,6 +3478,8 @@ class Bot(TelegramObject):
if tgs_sticker is not None:
data['tgs_sticker'] = tgs_sticker
if mask_position is not None:
# We need to_json() instead of to_dict() here, because we're sending a media
# message here, which isn't json dumped by utils.request
data['mask_position'] = mask_position.to_json()
data.update(kwargs)
@@ -3498,7 +3506,7 @@ class Bot(TelegramObject):
:class:`telegram.TelegramError`
"""
url = '{0}/setStickerPositionInSet'.format(self.base_url)
url = '{}/setStickerPositionInSet'.format(self.base_url)
data = {'sticker': sticker, 'position': position}
data.update(kwargs)
@@ -3525,7 +3533,7 @@ class Bot(TelegramObject):
:class:`telegram.TelegramError`
"""
url = '{0}/deleteStickerFromSet'.format(self.base_url)
url = '{}/deleteStickerFromSet'.format(self.base_url)
data = {'sticker': sticker}
data.update(kwargs)
@@ -3604,7 +3612,7 @@ class Bot(TelegramObject):
:class:`telegram.TelegramError`
"""
url_ = '{0}/setPassportDataErrors'.format(self.base_url)
url_ = '{}/setPassportDataErrors'.format(self.base_url)
data = {'user_id': user_id, 'errors': [error.to_dict() for error in errors]}
data.update(kwargs)
@@ -3627,6 +3635,10 @@ class Bot(TelegramObject):
reply_to_message_id=None,
reply_markup=None,
timeout=None,
explanation=None,
explanation_parse_mode=DEFAULT_NONE,
open_period=None,
close_date=None,
**kwargs):
"""
Use this method to send a native poll.
@@ -3644,6 +3656,18 @@ class Bot(TelegramObject):
answers, ignored for polls in quiz mode, defaults to False.
correct_option_id (:obj:`int`, optional): 0-based identifier of the correct answer
option, required for polls in quiz mode.
explanation (:obj:`str`, optional): Text that is shown when a user chooses an incorrect
answer or taps on the lamp icon in a quiz-style poll, 0-200 characters with at most
2 line feeds after entities parsing.
explanation_parse_mode (:obj:`str`, optional): Mode for parsing entities in the
explanation. See the constants in :class:`telegram.ParseMode` for the available
modes.
open_period (:obj:`int`, optional): Amount of time in seconds the poll will be active
after creation, 5-600. Can't be used together with :attr:`close_date`.
close_date (:obj:`int` | :obj:`datetime.datetime`, optional): Point in time (Unix
timestamp) when the poll will be automatically closed. Must be at least 5 and no
more than 600 seconds in the future. Can't be used together with
:attr:`open_period`.
is_closed (:obj:`bool`, optional): Pass True, if the poll needs to be immediately
closed. This can be useful for poll preview.
disable_notification (:obj:`bool`, optional): Sends the message silently. Users will
@@ -3665,7 +3689,7 @@ class Bot(TelegramObject):
:class:`telegram.TelegramError`
"""
url = '{0}/sendPoll'.format(self.base_url)
url = '{}/sendPoll'.format(self.base_url)
data = {
'chat_id': chat_id,
@@ -3673,6 +3697,12 @@ class Bot(TelegramObject):
'options': options
}
if explanation_parse_mode == DEFAULT_NONE:
if self.defaults:
explanation_parse_mode = self.defaults.parse_mode
else:
explanation_parse_mode = None
if not is_anonymous:
data['is_anonymous'] = is_anonymous
if type:
@@ -3683,6 +3713,16 @@ class Bot(TelegramObject):
data['correct_option_id'] = correct_option_id
if is_closed:
data['is_closed'] = is_closed
if explanation:
data['explanation'] = explanation
if explanation_parse_mode:
data['explanation_parse_mode'] = explanation_parse_mode
if open_period:
data['open_period'] = open_period
if close_date:
if isinstance(close_date, datetime):
close_date = to_timestamp(close_date)
data['close_date'] = close_date
return self._message(url, data, timeout=timeout, disable_notification=disable_notification,
reply_to_message_id=reply_to_message_id, reply_markup=reply_markup,
@@ -3717,7 +3757,7 @@ class Bot(TelegramObject):
:class:`telegram.TelegramError`
"""
url = '{0}/stopPoll'.format(self.base_url)
url = '{}/stopPoll'.format(self.base_url)
data = {
'chat_id': chat_id,
@@ -3726,7 +3766,9 @@ class Bot(TelegramObject):
if reply_markup:
if isinstance(reply_markup, ReplyMarkup):
data['reply_markup'] = reply_markup.to_dict()
# We need to_json() instead of to_dict() here, because reply_markups may be
# attached to media messages, which aren't json dumped by utils.request
data['reply_markup'] = reply_markup.to_json()
else:
data['reply_markup'] = reply_markup
@@ -3741,13 +3783,17 @@ class Bot(TelegramObject):
reply_to_message_id=None,
reply_markup=None,
timeout=None,
emoji=None,
**kwargs):
"""
Use this method to send a dice, which will have a random value from 1 to 6. On success, the
Use this method to send an animated emoji, which will have a random value. On success, the
sent Message is returned.
Args:
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target private chat.
emoji (:obj:`str`, optional): Emoji on which the dice throw animation is based.
Currently, must be one of “🎲”, “🎯” or “🏀”. Dice can have values 1-6 for “🎲” and
“🎯”, and values 1-5 for “🏀” . Defaults to “🎲”
disable_notification (:obj:`bool`, optional): Sends the message silently. Users will
receive a notification with no sound.
reply_to_message_id (:obj:`int`, optional): If the message is a reply, ID of the
@@ -3767,12 +3813,15 @@ class Bot(TelegramObject):
:class:`telegram.TelegramError`
"""
url = '{0}/sendDice'.format(self.base_url)
url = '{}/sendDice'.format(self.base_url)
data = {
'chat_id': chat_id,
}
if emoji:
data['emoji'] = emoji
return self._message(url, data, timeout=timeout, disable_notification=disable_notification,
reply_to_message_id=reply_to_message_id, reply_markup=reply_markup,
**kwargs)
@@ -3795,7 +3844,7 @@ class Bot(TelegramObject):
:class:`telegram.TelegramError`
"""
url = '{0}/getMyCommands'.format(self.base_url)
url = '{}/getMyCommands'.format(self.base_url)
result = self._request.get(url, timeout=timeout)
@@ -3824,7 +3873,7 @@ class Bot(TelegramObject):
:class:`telegram.TelegramError`
"""
url = '{0}/setMyCommands'.format(self.base_url)
url = '{}/setMyCommands'.format(self.base_url)
cmds = [c if isinstance(c, BotCommand) else BotCommand(c[0], c[1]) for c in commands]
+1 -1
View File
@@ -99,7 +99,7 @@ class CallbackQuery(TelegramObject):
if not data:
return None
data = super(CallbackQuery, cls).de_json(data, bot)
data = super().de_json(data, bot)
data['from_user'] = User.de_json(data.get('from'), bot)
message = data.get('message')
+1 -1
View File
@@ -20,7 +20,7 @@
"""This module contains an object that represents a Telegram ChatAction."""
class ChatAction(object):
class ChatAction:
"""Helper class to provide constants for different chatactions."""
FIND_LOCATION = 'find_location'
+2 -2
View File
@@ -150,7 +150,7 @@ class ChatMember(TelegramObject):
if not data:
return None
data = super(ChatMember, cls).de_json(data, bot)
data = super().de_json(data, bot)
data['user'] = User.de_json(data.get('user'), bot)
data['until_date'] = from_timestamp(data.get('until_date', None))
@@ -158,7 +158,7 @@ class ChatMember(TelegramObject):
return cls(**data)
def to_dict(self):
data = super(ChatMember, self).to_dict()
data = super().to_dict()
data['until_date'] = to_timestamp(self.until_date)
+1 -1
View File
@@ -72,7 +72,7 @@ class ChosenInlineResult(TelegramObject):
if not data:
return None
data = super(ChosenInlineResult, cls).de_json(data, bot)
data = super().de_json(data, bot)
# Required
data['from_user'] = User.de_json(data.pop('from'), bot)
# Optionals
+27 -4
View File
@@ -23,17 +23,30 @@ from telegram import TelegramObject
class Dice(TelegramObject):
"""
This object represents a dice with random value from 1 to 6. (The singular form of "dice" is
"die". However, PTB mimics the Telegram API, which uses the term "dice".)
This object represents an animated emoji with a random value for currently supported base
emoji. (The singular form of "dice" is "die". However, PTB mimics the Telegram API, which uses
the term "dice".)
Note:
If :attr:`emoji` is "🎯", a value of 6 currently represents a bullseye, while a value of 1
indicates that the dartboard was missed. However, this behaviour is undocumented and might
be changed by Telegram.
If :attr:`emoji` is "🏀", a value of 4 or 5 currently score a basket, while a value of 1 to
3 indicates that the basket was missed. However, this behaviour is undocumented and might
be changed by Telegram.
Attributes:
value (:obj:`int`): Value of the dice.
emoji (:obj:`str`): Emoji on which the dice throw animation is based.
Args:
value (:obj:`int`): Value of the dice, 1-6.
value (:obj:`int`): Value of the dice. 1-6 for dice and darts, 1-5 for basketball.
emoji (:obj:`str`): Emoji on which the dice throw animation is based.
"""
def __init__(self, value, **kwargs):
def __init__(self, value, emoji, **kwargs):
self.value = value
self.emoji = emoji
@classmethod
def de_json(cls, data, bot):
@@ -41,3 +54,13 @@ class Dice(TelegramObject):
return None
return cls(**data)
DICE = '🎲'
""":obj:`str`: '🎲'"""
DARTS = '🎯'
""":obj:`str`: '🎯'"""
BASKETBALL = '🏀'
""":obj:`str`: '🏀'"""
ALL_EMOJI = [DICE, DARTS, BASKETBALL]
"""List[:obj:`str`]: List of all supported base emoji. Currently :attr:`DICE`,
:attr:`DARTS` and :attr:`BASKETBALL`."""
+6 -8
View File
@@ -38,7 +38,7 @@ def _lstrip_str(in_s, lstr):
class TelegramError(Exception):
def __init__(self, message):
super(TelegramError, self).__init__()
super().__init__()
msg = _lstrip_str(message, 'Error: ')
msg = _lstrip_str(msg, '[Error]: ')
@@ -58,7 +58,7 @@ class Unauthorized(TelegramError):
class InvalidToken(TelegramError):
def __init__(self):
super(InvalidToken, self).__init__('Invalid token')
super().__init__('Invalid token')
class NetworkError(TelegramError):
@@ -71,7 +71,7 @@ class BadRequest(NetworkError):
class TimedOut(NetworkError):
def __init__(self):
super(TimedOut, self).__init__('Timed out')
super().__init__('Timed out')
class ChatMigrated(TelegramError):
@@ -82,8 +82,7 @@ class ChatMigrated(TelegramError):
"""
def __init__(self, new_chat_id):
super(ChatMigrated,
self).__init__('Group migrated to supergroup. New chat id: {}'.format(new_chat_id))
super().__init__('Group migrated to supergroup. New chat id: {}'.format(new_chat_id))
self.new_chat_id = new_chat_id
@@ -95,8 +94,7 @@ class RetryAfter(TelegramError):
"""
def __init__(self, retry_after):
super(RetryAfter,
self).__init__('Flood control exceeded. Retry in {} seconds'.format(retry_after))
super().__init__('Flood control exceeded. Retry in {} seconds'.format(retry_after))
self.retry_after = float(retry_after)
@@ -110,4 +108,4 @@ class Conflict(TelegramError):
"""
def __init__(self, msg):
super(Conflict, self).__init__(msg)
super().__init__(msg)
+11 -9
View File
@@ -18,8 +18,10 @@
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains the BasePersistence class."""
from abc import ABC, abstractmethod
class BasePersistence(object):
class BasePersistence(ABC):
"""Interface class for adding persistence to your bot.
Subclass this object for different implementations of a persistent bot.
@@ -57,6 +59,7 @@ class BasePersistence(object):
self.store_chat_data = store_chat_data
self.store_bot_data = store_bot_data
@abstractmethod
def get_user_data(self):
""""Will be called by :class:`telegram.ext.Dispatcher` upon creation with a
persistence object. It should return the user_data if stored, or an empty
@@ -65,8 +68,8 @@ class BasePersistence(object):
Returns:
:obj:`defaultdict`: The restored user data.
"""
raise NotImplementedError
@abstractmethod
def get_chat_data(self):
""""Will be called by :class:`telegram.ext.Dispatcher` upon creation with a
persistence object. It should return the chat_data if stored, or an empty
@@ -75,8 +78,8 @@ class BasePersistence(object):
Returns:
:obj:`defaultdict`: The restored chat data.
"""
raise NotImplementedError
@abstractmethod
def get_bot_data(self):
""""Will be called by :class:`telegram.ext.Dispatcher` upon creation with a
persistence object. It should return the bot_data if stored, or an empty
@@ -85,8 +88,8 @@ class BasePersistence(object):
Returns:
:obj:`defaultdict`: The restored bot data.
"""
raise NotImplementedError
@abstractmethod
def get_conversations(self, name):
""""Will be called by :class:`telegram.ext.Dispatcher` when a
:class:`telegram.ext.ConversationHandler` is added if
@@ -99,8 +102,8 @@ class BasePersistence(object):
Returns:
:obj:`dict`: The restored conversations for the handler.
"""
raise NotImplementedError
@abstractmethod
def update_conversation(self, name, key, new_state):
"""Will be called when a :attr:`telegram.ext.ConversationHandler.update_state`
is called. this allows the storeage of the new state in the persistence.
@@ -110,8 +113,8 @@ class BasePersistence(object):
key (:obj:`tuple`): The key the state is changed for.
new_state (:obj:`tuple` | :obj:`any`): The new state for the given key.
"""
raise NotImplementedError
@abstractmethod
def update_user_data(self, user_id, data):
"""Will be called by the :class:`telegram.ext.Dispatcher` after a handler has
handled an update.
@@ -120,8 +123,8 @@ class BasePersistence(object):
user_id (:obj:`int`): The user the data might have been changed for.
data (:obj:`dict`): The :attr:`telegram.ext.dispatcher.user_data` [user_id].
"""
raise NotImplementedError
@abstractmethod
def update_chat_data(self, chat_id, data):
"""Will be called by the :class:`telegram.ext.Dispatcher` after a handler has
handled an update.
@@ -130,8 +133,8 @@ class BasePersistence(object):
chat_id (:obj:`int`): The chat the data might have been changed for.
data (:obj:`dict`): The :attr:`telegram.ext.dispatcher.chat_data` [chat_id].
"""
raise NotImplementedError
@abstractmethod
def update_bot_data(self, data):
"""Will be called by the :class:`telegram.ext.Dispatcher` after a handler has
handled an update.
@@ -139,7 +142,6 @@ class BasePersistence(object):
Args:
data (:obj:`dict`): The :attr:`telegram.ext.dispatcher.bot_data` .
"""
raise NotImplementedError
def flush(self):
"""Will be called by :class:`telegram.ext.Updater` upon receiving a stop signal. Gives the
+7 -7
View File
@@ -21,7 +21,7 @@
from telegram import Update
class CallbackContext(object):
class CallbackContext:
"""
This is a context object passed to the callback called by :class:`telegram.ext.Handler`
or by the :class:`telegram.ext.Dispatcher` in an error handler added by
@@ -43,9 +43,9 @@ class CallbackContext(object):
that you think you added will not be present.
Attributes:
bot_data (:obj:`dict`, optional): A dict that can be used to keep any data in. For each
bot_data (:obj:`dict`): Optional. A dict that can be used to keep any data in. For each
update it will be the same ``dict``.
chat_data (:obj:`dict`, optional): A dict that can be used to keep any data in. For each
chat_data (:obj:`dict`): Optional. A dict that can be used to keep any data in. For each
update from the same chat id it will be the same ``dict``.
Warning:
@@ -54,18 +54,18 @@ class CallbackContext(object):
<https://github.com/python-telegram-bot/python-telegram-bot/wiki/
Storing-user--and-chat-related-data#chat-migration>`_.
user_data (:obj:`dict`, optional): A dict that can be used to keep any data in. For each
user_data (:obj:`dict`): Optional. A dict that can be used to keep any data in. For each
update from the same user it will be the same ``dict``.
matches (List[:obj:`re match object`], optional): If the associated update originated from
matches (List[:obj:`re match object`]): Optional. If the associated update originated from
a regex-supported handler or had a :class:`Filters.regex`, this will contain a list of
match objects for every pattern where ``re.search(pattern, string)`` returned a match.
Note that filters short circuit, so combined regex filters will not always
be evaluated.
args (List[:obj:`str`], optional): Arguments passed to a command if the associated update
args (List[:obj:`str`]): Optional. Arguments passed to a command if the associated update
is handled by :class:`telegram.ext.CommandHandler`, :class:`telegram.ext.PrefixHandler`
or :class:`telegram.ext.StringCommandHandler`. It contains a list of the words in the
text after the command, using any whitespace string as a delimiter.
error (:class:`telegram.TelegramError`, optional): The Telegram error that was raised.
error (:class:`telegram.TelegramError`): Optional. The Telegram error that was raised.
Only present when passed to a error handler registered with
:attr:`telegram.ext.Dispatcher.add_error_handler`.
job (:class:`telegram.ext.Job`): The job that that originated this callback.
+3 -7
View File
@@ -20,8 +20,6 @@
import re
from future.utils import string_types
from telegram import Update
from .handler import Handler
@@ -105,14 +103,14 @@ class CallbackQueryHandler(Handler):
pass_groupdict=False,
pass_user_data=False,
pass_chat_data=False):
super(CallbackQueryHandler, self).__init__(
super().__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)
if isinstance(pattern, string_types):
if isinstance(pattern, str):
pattern = re.compile(pattern)
self.pattern = pattern
@@ -139,9 +137,7 @@ class CallbackQueryHandler(Handler):
return True
def collect_optional_args(self, dispatcher, update=None, check_result=None):
optional_args = super(CallbackQueryHandler, self).collect_optional_args(dispatcher,
update,
check_result)
optional_args = super().collect_optional_args(dispatcher, update, check_result)
if self.pattern:
if self.pass_groups:
optional_args['groups'] = check_result.groups()
+6 -8
View File
@@ -20,8 +20,6 @@
import re
import warnings
from future.utils import string_types
from telegram.ext import Filters
from telegram.utils.deprecate import TelegramDeprecationWarning
@@ -125,14 +123,14 @@ class CommandHandler(Handler):
pass_job_queue=False,
pass_user_data=False,
pass_chat_data=False):
super(CommandHandler, self).__init__(
super().__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)
if isinstance(command, string_types):
if isinstance(command, str):
self.command = [command.lower()]
else:
self.command = [x.lower() for x in command]
@@ -184,7 +182,7 @@ class CommandHandler(Handler):
return False
def collect_optional_args(self, dispatcher, update=None, check_result=None):
optional_args = super(CommandHandler, self).collect_optional_args(dispatcher, update)
optional_args = super().collect_optional_args(dispatcher, update)
if self.pass_args:
optional_args['args'] = check_result[0]
return optional_args
@@ -306,7 +304,7 @@ class PrefixHandler(CommandHandler):
self._command = list()
self._commands = list()
super(PrefixHandler, self).__init__(
super().__init__(
'nocommand', callback, filters=filters, allow_edited=None, pass_args=pass_args,
pass_update_queue=pass_update_queue,
pass_job_queue=pass_job_queue,
@@ -323,7 +321,7 @@ class PrefixHandler(CommandHandler):
@prefix.setter
def prefix(self, prefix):
if isinstance(prefix, string_types):
if isinstance(prefix, str):
self._prefix = [prefix.lower()]
else:
self._prefix = prefix
@@ -335,7 +333,7 @@ class PrefixHandler(CommandHandler):
@command.setter
def command(self, command):
if isinstance(command, string_types):
if isinstance(command, str):
self._command = [command.lower()]
else:
self._command = command
+2 -2
View File
@@ -28,7 +28,7 @@ from telegram.ext import (Handler, CallbackQueryHandler, InlineQueryHandler,
from telegram.utils.promise import Promise
class _ConversationTimeoutContext(object):
class _ConversationTimeoutContext:
def __init__(self, conversation_key, update, dispatcher, callback_context):
self.conversation_key = conversation_key
self.update = update
@@ -404,7 +404,7 @@ class ConversationHandler(Handler):
return key, handler, check
return None
self.logger.debug('selecting conversation %s with state %s' % (str(key), str(state)))
self.logger.debug('selecting conversation {} with state {}'.format(str(key), str(state)))
handler = None
+3 -3
View File
@@ -66,9 +66,9 @@ class DictPersistence(BasePersistence):
chat_data_json='',
bot_data_json='',
conversations_json=''):
super(DictPersistence, self).__init__(store_user_data=store_user_data,
store_chat_data=store_chat_data,
store_bot_data=store_bot_data)
super().__init__(store_user_data=store_user_data,
store_chat_data=store_chat_data,
store_bot_data=store_bot_data)
self._user_data = None
self._chat_data = None
self._bot_data = None
+4 -7
View File
@@ -29,8 +29,6 @@ from collections import defaultdict
from queue import Queue, Empty
from future.builtins import range
from telegram import TelegramError, Update
from telegram.ext.handler import Handler
from telegram.ext.callbackcontext import CallbackContext
@@ -38,7 +36,6 @@ from telegram.utils.deprecate import TelegramDeprecationWarning
from telegram.utils.promise import Promise
from telegram.ext import BasePersistence
logging.getLogger(__name__).addHandler(logging.NullHandler())
DEFAULT_GROUP = 0
@@ -67,7 +64,7 @@ class DispatcherHandlerStop(Exception):
pass
class Dispatcher(object):
class Dispatcher:
"""This class dispatches all kinds of updates to its registered handlers.
Attributes:
@@ -305,10 +302,10 @@ class Dispatcher(object):
self.__async_queue.put(None)
for i, thr in enumerate(threads):
self.logger.debug('Waiting for async thread {0}/{1} to end'.format(i + 1, total))
self.logger.debug('Waiting for async thread {}/{} to end'.format(i + 1, total))
thr.join()
self.__async_threads.remove(thr)
self.logger.debug('async thread {0}/{1} has ended'.format(i + 1, total))
self.logger.debug('async thread {}/{} has ended'.format(i + 1, total))
@property
def has_running_threads(self):
@@ -392,7 +389,7 @@ class Dispatcher(object):
from .conversationhandler import ConversationHandler
if not isinstance(handler, Handler):
raise TypeError('handler is not an instance of {0}'.format(Handler.__name__))
raise TypeError('handler is not an instance of {}'.format(Handler.__name__))
if not isinstance(group, int):
raise TypeError('group is not int')
if isinstance(handler, ConversationHandler) and handler.persistent:
+361 -74
View File
@@ -20,14 +20,15 @@
import re
from future.utils import string_types
from abc import ABC, abstractmethod
from threading import Lock
from telegram import Chat, Update, MessageEntity
__all__ = ['Filters', 'BaseFilter', 'InvertedFilter', 'MergedFilter']
class BaseFilter(object):
class BaseFilter(ABC):
"""Base class for all Message Filters.
Subclassing from this class filters to be combined using bitwise operators:
@@ -103,6 +104,7 @@ class BaseFilter(object):
self.name = self.__class__.__name__
return self.name
@abstractmethod
def filter(self, update):
"""This method must be overwritten.
@@ -118,8 +120,6 @@ class BaseFilter(object):
"""
raise NotImplementedError
class InvertedFilter(BaseFilter):
"""Represents a filter that has been inverted.
@@ -215,7 +215,39 @@ class MergedFilter(BaseFilter):
self.and_filter or self.or_filter)
class Filters(object):
class _DiceEmoji(BaseFilter):
def __init__(self, emoji=None, name=None):
self.name = 'Filters.dice.{}'.format(name) if name else 'Filters.dice'
self.emoji = emoji
class _DiceValues(BaseFilter):
def __init__(self, values, name, emoji=None):
self.values = [values] if isinstance(values, int) else values
self.emoji = emoji
self.name = '{}({})'.format(name, values)
def filter(self, message):
if bool(message.dice and message.dice.value in self.values):
if self.emoji:
return message.dice.emoji == self.emoji
return True
def __call__(self, update):
if isinstance(update, Update):
return self.filter(update.effective_message)
else:
return self._DiceValues(update, self.name, emoji=self.emoji)
def filter(self, message):
if bool(message.dice):
if self.emoji:
return message.dice.emoji == self.emoji
return True
class Filters:
"""Predefined filters for use as the `filter` argument of :class:`telegram.ext.MessageHandler`.
Examples:
@@ -273,8 +305,11 @@ class Filters(object):
MessageHandler(Filters.text(buttons), callback_method)
Note:
Dice messages don't have text. If you want to filter either text or dice messages, use
``Filters.text | Filters.dice``.
* Dice messages don't have text. If you want to filter either text or dice messages, use
``Filters.text | Filters.dice``.
* Messages containing a command are accepted by this filter. Use
``Filters.text & (~Filters.command)``, if you want to filter only text messages without
commands.
Args:
update (List[:obj:`str`] | Tuple[:obj:`str`], optional): Which messages to allow. Only
@@ -350,6 +385,9 @@ class Filters(object):
MessageHandler(Filters.command, command_at_start_callback)
MessageHandler(Filters.command(False), command_anywhere_callback)
Note:
``Filters.text`` also accepts messages containing a command.
Args:
update (:obj:`bool`, optional): Whether to only allow messages that `start` with a bot
command. Defaults to ``True``.
@@ -387,7 +425,7 @@ class Filters(object):
data_filter = True
def __init__(self, pattern):
if isinstance(pattern, string_types):
if isinstance(pattern, str):
pattern = re.compile(pattern)
self.pattern = pattern
self.name = 'Filters.regex({})'.format(self.pattern)
@@ -854,38 +892,166 @@ officedocument.wordprocessingml.document")``-
Examples:
``MessageHandler(Filters.user(1234), callback_method)``
Warning:
:attr:`user_ids` will give a *copy* of the saved user ids as :class:`frozenset`. This
is to ensure thread safety. To add/remove a user, you should use :meth:`add_usernames`,
:meth:`add_user_ids`, :meth:`remove_usernames` and :meth:`remove_user_ids`. Only update
the entire set by ``filter.user_ids/usernames = new_set``, if you are entirely sure
that it is not causing race conditions, as this will complete replace the current set
of allowed users.
Attributes:
user_ids(set(:obj:`int`), optional): Which user ID(s) to allow through.
usernames(set(:obj:`str`), optional): Which username(s) (without leading '@') to allow
through.
allow_empty(:obj:`bool`, optional): Whether updates should be processed, if no user
is specified in :attr:`user_ids` and :attr:`usernames`.
Args:
user_id(:obj:`int` | List[:obj:`int`], optional): Which user ID(s) to allow through.
username(:obj:`str` | List[:obj:`str`], optional): Which username(s) to allow through.
If username starts with '@' symbol, it will be ignored.
user_id(:obj:`int` | List[:obj:`int`], optional): Which user ID(s) to allow
through.
username(:obj:`str` | List[:obj:`str`], optional): Which username(s) to allow
through. Leading '@'s in usernames will be discarded.
allow_empty(:obj:`bool`, optional): Whether updates should be processed, if no user
is specified in :attr:`user_ids` and :attr:`usernames`. Defaults to :obj:`False`
Raises:
ValueError: If chat_id and username are both present, or neither is.
RuntimeError: If user_id and username are both present.
"""
def __init__(self, user_id=None, username=None):
if not (bool(user_id) ^ bool(username)):
raise ValueError('One and only one of user_id or username must be used')
if user_id is not None and isinstance(user_id, int):
self.user_ids = [user_id]
else:
self.user_ids = user_id
def __init__(self, user_id=None, username=None, allow_empty=False):
self.allow_empty = allow_empty
self.__lock = Lock()
self._user_ids = set()
self._usernames = set()
self._set_user_ids(user_id)
self._set_usernames(username)
@staticmethod
def _parse_user_id(user_id):
if user_id is None:
return set()
if isinstance(user_id, int):
return {user_id}
return set(user_id)
@staticmethod
def _parse_username(username):
if username is None:
self.usernames = username
elif isinstance(username, string_types):
self.usernames = [username.replace('@', '')]
else:
self.usernames = [user.replace('@', '') for user in username]
return set()
if isinstance(username, str):
return {username[1:] if username.startswith('@') else username}
return {user[1:] if user.startswith('@') else user for user in username}
def _set_user_ids(self, user_id):
with self.__lock:
if user_id and self._usernames:
raise RuntimeError("Can't set user_id in conjunction with (already set) "
"usernames.")
self._user_ids = self._parse_user_id(user_id)
def _set_usernames(self, username):
with self.__lock:
if username and self._user_ids:
raise RuntimeError("Can't set username in conjunction with (already set) "
"user_ids.")
self._usernames = self._parse_username(username)
@property
def user_ids(self):
with self.__lock:
return frozenset(self._user_ids)
@user_ids.setter
def user_ids(self, user_id):
self._set_user_ids(user_id)
@property
def usernames(self):
with self.__lock:
return frozenset(self._usernames)
@usernames.setter
def usernames(self, username):
self._set_usernames(username)
def add_usernames(self, username):
"""
Add one or more users to the allowed usernames.
Args:
username(:obj:`str` | List[:obj:`str`], optional): Which username(s) to allow
through. Leading '@'s in usernames will be discarded.
"""
with self.__lock:
if self._user_ids:
raise RuntimeError("Can't set username in conjunction with (already set) "
"user_ids.")
username = self._parse_username(username)
self._usernames |= username
def add_user_ids(self, user_id):
"""
Add one or more users to the allowed user ids.
Args:
user_id(:obj:`int` | List[:obj:`int`], optional): Which user ID(s) to allow
through.
"""
with self.__lock:
if self._usernames:
raise RuntimeError("Can't set user_id in conjunction with (already set) "
"usernames.")
user_id = self._parse_user_id(user_id)
self._user_ids |= user_id
def remove_usernames(self, username):
"""
Remove one or more users from allowed usernames.
Args:
username(:obj:`str` | List[:obj:`str`], optional): Which username(s) to disallow
through. Leading '@'s in usernames will be discarded.
"""
with self.__lock:
if self._user_ids:
raise RuntimeError("Can't set username in conjunction with (already set) "
"user_ids.")
username = self._parse_username(username)
self._usernames -= username
def remove_user_ids(self, user_id):
"""
Remove one or more users from allowed user ids.
Args:
user_id(:obj:`int` | List[:obj:`int`], optional): Which user ID(s) to disallow
through.
"""
with self.__lock:
if self._usernames:
raise RuntimeError("Can't set user_id in conjunction with (already set) "
"usernames.")
user_id = self._parse_user_id(user_id)
self._user_ids -= user_id
def filter(self, message):
"""""" # remove method from docs
if self.user_ids is not None:
return bool(message.from_user and message.from_user.id in self.user_ids)
else:
# self.usernames is not None
return bool(message.from_user and message.from_user.username
if message.from_user:
if self.user_ids:
return message.from_user.id in self.user_ids
if self.usernames:
return (message.from_user.username
and message.from_user.username in self.usernames)
return self.allow_empty
return False
class chat(BaseFilter):
"""Filters messages to allow only those which are from specified chat ID.
@@ -893,37 +1059,166 @@ officedocument.wordprocessingml.document")``-
Examples:
``MessageHandler(Filters.chat(-1234), callback_method)``
Warning:
:attr:`chat_ids` will give a *copy* of the saved chat ids as :class:`frozenset`. This
is to ensure thread safety. To add/remove a chat, you should use :meth:`add_usernames`,
:meth:`add_chat_ids`, :meth:`remove_usernames` and :meth:`remove_chat_ids`. Only update
the entire set by ``filter.chat_ids/usernames = new_set``, if you are entirely sure
that it is not causing race conditions, as this will complete replace the current set
of allowed chats.
Attributes:
chat_ids(set(:obj:`int`), optional): Which chat ID(s) to allow through.
usernames(set(:obj:`str`), optional): Which username(s) (without leading '@') to allow
through.
allow_empty(:obj:`bool`, optional): Whether updates should be processed, if no chat
is specified in :attr:`chat_ids` and :attr:`usernames`.
Args:
chat_id(:obj:`int` | List[:obj:`int`], optional): Which chat ID(s) to allow through.
username(:obj:`str` | List[:obj:`str`], optional): Which username(s) to allow through.
If username start swith '@' symbol, it will be ignored.
chat_id(:obj:`int` | List[:obj:`int`], optional): Which chat ID(s) to allow
through.
username(:obj:`str` | List[:obj:`str`], optional): Which username(s) to allow
through. Leading '@'s in usernames will be discarded.
allow_empty(:obj:`bool`, optional): Whether updates should be processed, if no chat
is specified in :attr:`chat_ids` and :attr:`usernames`. Defaults to :obj:`False`
Raises:
ValueError: If chat_id and username are both present, or neither is.
RuntimeError: If chat_id and username are both present.
"""
def __init__(self, chat_id=None, username=None):
if not (bool(chat_id) ^ bool(username)):
raise ValueError('One and only one of chat_id or username must be used')
if chat_id is not None and isinstance(chat_id, int):
self.chat_ids = [chat_id]
else:
self.chat_ids = chat_id
def __init__(self, chat_id=None, username=None, allow_empty=False):
self.allow_empty = allow_empty
self.__lock = Lock()
self._chat_ids = set()
self._usernames = set()
self._set_chat_ids(chat_id)
self._set_usernames(username)
@staticmethod
def _parse_chat_id(chat_id):
if chat_id is None:
return set()
if isinstance(chat_id, int):
return {chat_id}
return set(chat_id)
@staticmethod
def _parse_username(username):
if username is None:
self.usernames = username
elif isinstance(username, string_types):
self.usernames = [username.replace('@', '')]
else:
self.usernames = [chat.replace('@', '') for chat in username]
return set()
if isinstance(username, str):
return {username[1:] if username.startswith('@') else username}
return {chat[1:] if chat.startswith('@') else chat for chat in username}
def _set_chat_ids(self, chat_id):
with self.__lock:
if chat_id and self._usernames:
raise RuntimeError("Can't set chat_id in conjunction with (already set) "
"usernames.")
self._chat_ids = self._parse_chat_id(chat_id)
def _set_usernames(self, username):
with self.__lock:
if username and self._chat_ids:
raise RuntimeError("Can't set username in conjunction with (already set) "
"chat_ids.")
self._usernames = self._parse_username(username)
@property
def chat_ids(self):
with self.__lock:
return frozenset(self._chat_ids)
@chat_ids.setter
def chat_ids(self, chat_id):
self._set_chat_ids(chat_id)
@property
def usernames(self):
with self.__lock:
return frozenset(self._usernames)
@usernames.setter
def usernames(self, username):
self._set_usernames(username)
def add_usernames(self, username):
"""
Add one or more chats to the allowed usernames.
Args:
username(:obj:`str` | List[:obj:`str`], optional): Which username(s) to allow
through. Leading '@'s in usernames will be discarded.
"""
with self.__lock:
if self._chat_ids:
raise RuntimeError("Can't set username in conjunction with (already set) "
"chat_ids.")
username = self._parse_username(username)
self._usernames |= username
def add_chat_ids(self, chat_id):
"""
Add one or more chats to the allowed chat ids.
Args:
chat_id(:obj:`int` | List[:obj:`int`], optional): Which chat ID(s) to allow
through.
"""
with self.__lock:
if self._usernames:
raise RuntimeError("Can't set chat_id in conjunction with (already set) "
"usernames.")
chat_id = self._parse_chat_id(chat_id)
self._chat_ids |= chat_id
def remove_usernames(self, username):
"""
Remove one or more chats from allowed usernames.
Args:
username(:obj:`str` | List[:obj:`str`], optional): Which username(s) to disallow
through. Leading '@'s in usernames will be discarded.
"""
with self.__lock:
if self._chat_ids:
raise RuntimeError("Can't set username in conjunction with (already set) "
"chat_ids.")
username = self._parse_username(username)
self._usernames -= username
def remove_chat_ids(self, chat_id):
"""
Remove one or more chats from allowed chat ids.
Args:
chat_id(:obj:`int` | List[:obj:`int`], optional): Which chat ID(s) to disallow
through.
"""
with self.__lock:
if self._usernames:
raise RuntimeError("Can't set chat_id in conjunction with (already set) "
"usernames.")
chat_id = self._parse_chat_id(chat_id)
self._chat_ids -= chat_id
def filter(self, message):
"""""" # remove method from docs
if self.chat_ids is not None:
return bool(message.chat_id in self.chat_ids)
else:
# self.usernames is not None
return bool(message.chat.username and message.chat.username in self.usernames)
if message.chat:
if self.chat_ids:
return message.chat.id in self.chat_ids
if self.usernames:
return (message.chat.username
and message.chat.username in self.usernames)
return self.allow_empty
return False
class _Invoice(BaseFilter):
name = 'Filters.invoice'
@@ -961,26 +1256,10 @@ officedocument.wordprocessingml.document")``-
poll = _Poll()
"""Messages that contain a :class:`telegram.Poll`."""
class _Dice(BaseFilter):
name = 'Filters.dice'
class _DiceValues(BaseFilter):
def __init__(self, values):
self.values = [values] if isinstance(values, int) else values
self.name = 'Filters.dice({})'.format(values)
def filter(self, message):
return bool(message.dice and message.dice.value in self.values)
def __call__(self, update):
if isinstance(update, Update):
return self.filter(update.effective_message)
else:
return self._DiceValues(update)
def filter(self, message):
return bool(message.dice)
class _Dice(_DiceEmoji):
dice = _DiceEmoji('🎲', 'dice')
darts = _DiceEmoji('🎯', 'darts')
basketball = _DiceEmoji('🏀', 'basketball')
dice = _Dice()
"""Dice Messages. If an integer or a list of integers is passed, it filters messages to only
@@ -998,9 +1277,17 @@ officedocument.wordprocessingml.document")``-
update (:obj:`int` | List[:obj:`int`], optional): Which values to allow. If not
specified, will allow any dice message.
Note:
Dice messages don't have text. If you want to filter either text or dice messages, use
``Filters.text | Filters.dice``.
Note:
Dice messages don't have text. If you want to filter either text or dice messages, use
``Filters.text | Filters.dice``.
Attributes:
dice: Dice messages with the emoji 🎲. Passing a list of integers is supported just as for
:attr:`Filters.dice`.
darts: Dice messages with the emoji 🎯. Passing a list of integers is supported just as for
:attr:`Filters.dice`.
basketball: Dice messages with the emoji 🏀. Passing a list of integers is supported just
as for :attr:`Filters.dice`.
"""
class language(BaseFilter):
@@ -1021,7 +1308,7 @@ officedocument.wordprocessingml.document")``-
"""
def __init__(self, lang):
if isinstance(lang, string_types):
if isinstance(lang, str):
self.lang = [lang]
else:
self.lang = lang
+4 -2
View File
@@ -18,8 +18,10 @@
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains the base class for handlers as used by the Dispatcher."""
from abc import ABC, abstractmethod
class Handler(object):
class Handler(ABC):
"""The base class for all update handlers. Create custom handlers by inheriting from it.
Attributes:
@@ -82,6 +84,7 @@ class Handler(object):
self.pass_user_data = pass_user_data
self.pass_chat_data = pass_chat_data
@abstractmethod
def check_update(self, update):
"""
This method is called to determine if an update should be handled by
@@ -96,7 +99,6 @@ class Handler(object):
when the update gets handled.
"""
raise NotImplementedError
def handle_update(self, update, dispatcher, check_result, context=None):
"""
+4 -6
View File
@@ -19,9 +19,8 @@
""" This module contains the InlineQueryHandler class """
import re
from future.utils import string_types
from telegram import Update
from .handler import Handler
@@ -104,14 +103,14 @@ class InlineQueryHandler(Handler):
pass_groupdict=False,
pass_user_data=False,
pass_chat_data=False):
super(InlineQueryHandler, self).__init__(
super().__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)
if isinstance(pattern, string_types):
if isinstance(pattern, str):
pattern = re.compile(pattern)
self.pattern = pattern
@@ -140,8 +139,7 @@ class InlineQueryHandler(Handler):
return True
def collect_optional_args(self, dispatcher, update=None, check_result=None):
optional_args = super(InlineQueryHandler, self).collect_optional_args(dispatcher,
update, check_result)
optional_args = super().collect_optional_args(dispatcher, update, check_result)
if self.pattern:
if self.pass_groups:
optional_args['groups'] = check_result.groups()
+181 -15
View File
@@ -18,6 +18,7 @@
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains the classes JobQueue and Job."""
import calendar
import datetime
import logging
import time
@@ -29,15 +30,15 @@ from threading import Thread, Lock, Event
from telegram.ext.callbackcontext import CallbackContext
from telegram.utils.deprecate import TelegramDeprecationWarning
from telegram.utils.helpers import to_float_timestamp, _UTC
from telegram.utils.helpers import to_float_timestamp
class Days(object):
class Days:
MON, TUE, WED, THU, FRI, SAT, SUN = range(7)
EVERY_DAY = tuple(range(7))
class JobQueue(object):
class JobQueue:
"""This class allows you to periodically perform tasks with the bot.
Attributes:
@@ -53,7 +54,7 @@ class JobQueue(object):
warnings.warn("Passing bot to jobqueue is deprecated. Please use set_dispatcher "
"instead!", TelegramDeprecationWarning, stacklevel=2)
class MockDispatcher(object):
class MockDispatcher:
def __init__(self):
self.bot = bot
self.use_context = False
@@ -104,6 +105,7 @@ class JobQueue(object):
# enqueue:
self.logger.debug('Putting job %s with t=%s', job.name, time_spec)
self._queue.put((next_t, job))
job._set_next_t(next_t)
# Wake up the loop if this job should be executed next
self._set_next_peek(next_t)
@@ -135,6 +137,9 @@ class JobQueue(object):
job should run. This could be either today or, if the time has already passed,
tomorrow. If the timezone (``time.tzinfo``) is ``None``, UTC will be assumed.
If ``when`` is :obj:`datetime.datetime` or :obj:`datetime.time` type
then ``when.tzinfo`` will define ``Job.tzinfo``. Otherwise UTC will be assumed.
context (:obj:`object`, optional): Additional data needed for the callback function.
Can be accessed through ``job.context`` in the callback. Defaults to ``None``.
name (:obj:`str`, optional): The name of the new job. Defaults to
@@ -145,7 +150,14 @@ class JobQueue(object):
queue.
"""
job = Job(callback, repeat=False, context=context, name=name, job_queue=self)
tzinfo = when.tzinfo if isinstance(when, (datetime.datetime, datetime.time)) else None
job = Job(callback,
repeat=False,
context=context,
name=name,
job_queue=self,
tzinfo=tzinfo)
self._put(job, time_spec=when)
return job
@@ -179,6 +191,9 @@ class JobQueue(object):
job should run. This could be either today or, if the time has already passed,
tomorrow. If the timezone (``time.tzinfo``) is ``None``, UTC will be assumed.
If ``first`` is :obj:`datetime.datetime` or :obj:`datetime.time` type
then ``first.tzinfo`` will define ``Job.tzinfo``. Otherwise UTC will be assumed.
Defaults to ``interval``
context (:obj:`object`, optional): Additional data needed for the callback function.
Can be accessed through ``job.context`` in the callback. Defaults to ``None``.
@@ -189,21 +204,131 @@ class JobQueue(object):
:class:`telegram.ext.Job`: The new ``Job`` instance that has been added to the job
queue.
Notes:
Note:
`interval` is always respected "as-is". That means that if DST changes during that
interval, the job might not run at the time one would expect. It is always recommended
to pin servers to UTC time, then time related behaviour can always be expected.
"""
tzinfo = first.tzinfo if isinstance(first, (datetime.datetime, datetime.time)) else None
job = Job(callback,
interval=interval,
repeat=True,
context=context,
name=name,
job_queue=self)
job_queue=self,
tzinfo=tzinfo)
self._put(job, time_spec=first)
return job
def run_monthly(self, callback, when, day, context=None, name=None, day_is_strict=True):
"""Creates a new ``Job`` that runs on a monthly basis and adds it to the queue.
Args:
callback (:obj:`callable`): The callback function that should be executed by the new
job. Callback signature for context based API:
``def callback(CallbackContext)``
``context.job`` is the :class:`telegram.ext.Job` instance. It can be used to access
its ``job.context`` or change it to a repeating job.
when (:obj:`datetime.time`): Time of day at which the job should run. If the timezone
(``when.tzinfo``) is ``None``, UTC will be assumed. This will also implicitly
define ``Job.tzinfo``.
day (:obj:`int`): Defines the day of the month whereby the job would run. It should
be within the range of 1 and 31, inclusive.
context (:obj:`object`, optional): Additional data needed for the callback function.
Can be accessed through ``job.context`` in the callback. Defaults to ``None``.
name (:obj:`str`, optional): The name of the new job. Defaults to
``callback.__name__``.
day_is_strict (:obj:`bool`, optional): If ``False`` and day > month.days, will pick
the last day in the month. Defaults to ``True``.
Returns:
:class:`telegram.ext.Job`: The new ``Job`` instance that has been added to the job
queue.
"""
tzinfo = when.tzinfo if isinstance(when, (datetime.datetime, datetime.time)) else None
if 1 <= day <= 31:
next_dt = self._get_next_month_date(day, day_is_strict, when, allow_now=True)
job = Job(callback, repeat=False, context=context, name=name, job_queue=self,
is_monthly=True, day_is_strict=day_is_strict, tzinfo=tzinfo)
self._put(job, time_spec=next_dt)
return job
else:
raise ValueError("The elements of the 'day' argument should be from 1 up to"
" and including 31")
def _get_next_month_date(self, day, day_is_strict, when, allow_now=False):
"""This method returns the date that the next monthly job should be scheduled.
Args:
day (:obj:`int`): The day of the month the job should run.
day_is_strict (:obj:`bool`):
Specification as to whether the specified day of job should be strictly
respected. If day_is_strict is ``True`` it ignores months whereby the
specified date does not exist (e.g February 31st). If it set to ``False``,
it returns the last valid date of the month instead. For example,
if the user runs a job on the 31st of every month, and sets
the day_is_strict variable to ``False``, April, for example,
the job would run on April 30th.
when (:obj:`datetime.time`): Time of day at which the job should run. If the
timezone (``time.tzinfo``) is ``None``, UTC will be assumed.
allow_now (:obj:`bool`): Whether executing the job right now is a feasible options.
For stability reasons, this defaults to :obj:`False`, but it needs to be :obj:`True`
on initializing a job.
"""
dt = datetime.datetime.now(tz=when.tzinfo or datetime.timezone.utc)
dt_time = dt.time().replace(tzinfo=when.tzinfo)
days_in_current_month = calendar.monthrange(dt.year, dt.month)[1]
days_till_months_end = days_in_current_month - dt.day
if days_in_current_month < day:
# if the day does not exist in the current month (e.g Feb 31st)
if day_is_strict is False:
# set day as last day of month instead
next_dt = dt + datetime.timedelta(days=days_till_months_end)
else:
# else set as day in subsequent month. Subsequent month is
# guaranteed to have the date, if current month does not have the date.
next_dt = dt + datetime.timedelta(days=days_till_months_end + day)
else:
# if the day exists in the current month
if dt.day < day:
# day is upcoming
next_dt = dt + datetime.timedelta(day - dt.day)
elif dt.day > day or (dt.day == day and ((not allow_now and dt_time >= when)
or (allow_now and dt_time > when))):
# run next month if day has already passed
next_year = dt.year + 1 if dt.month == 12 else dt.year
next_month = 1 if dt.month == 12 else dt.month + 1
days_in_next_month = calendar.monthrange(next_year, next_month)[1]
next_month_has_date = days_in_next_month >= day
if next_month_has_date:
next_dt = dt + datetime.timedelta(days=days_till_months_end + day)
elif day_is_strict:
# schedule the subsequent month if day is strict
next_dt = dt + datetime.timedelta(
days=days_till_months_end + days_in_next_month + day)
else:
# schedule in the next month last date if day is not strict
next_dt = dt + datetime.timedelta(days=days_till_months_end
+ days_in_next_month)
else:
# day is today but time has not yet come
next_dt = dt
# Set the correct time
next_dt = next_dt.replace(hour=when.hour, minute=when.minute, second=when.second,
microsecond=when.microsecond)
# fold is new in Py3.6
if hasattr(next_dt, 'fold'):
next_dt = next_dt.replace(fold=when.fold)
return next_dt
def run_daily(self, callback, time, days=Days.EVERY_DAY, context=None, name=None):
"""Creates a new ``Job`` that runs on a daily basis and adds it to the queue.
@@ -217,6 +342,7 @@ class JobQueue(object):
its ``job.context`` or change it to a repeating job.
time (:obj:`datetime.time`): Time of day at which the job should run. If the timezone
(``time.tzinfo``) is ``None``, UTC will be assumed.
``time.tzinfo`` will implicitly define ``Job.tzinfo``.
days (Tuple[:obj:`int`], optional): Defines on which days of the week the job should
run. Defaults to ``EVERY_DAY``
context (:obj:`object`, optional): Additional data needed for the callback function.
@@ -228,7 +354,7 @@ class JobQueue(object):
:class:`telegram.ext.Job`: The new ``Job`` instance that has been added to the job
queue.
Notes:
Note:
Daily is just an alias for "24 Hours". That means that if DST changes during that
interval, the job might not run at the time one would expect. It is always recommended
to pin servers to UTC time, then time related behaviour can always be expected.
@@ -300,7 +426,13 @@ class JobQueue(object):
if job.repeat and not job.removed:
self._put(job, previous_t=t)
elif job.is_monthly and not job.removed:
dt = datetime.datetime.now(tz=job.tzinfo)
dt_time = dt.time().replace(tzinfo=job.tzinfo)
self._put(job, time_spec=self._get_next_month_date(dt.day, job.day_is_strict,
dt_time))
else:
job._set_next_t(None)
self.logger.debug('Dropping non-repeating or removed job %s', job.name)
def start(self):
@@ -360,13 +492,15 @@ class JobQueue(object):
return tuple(job[1] for job in self._queue.queue if job and job[1].name == name)
class Job(object):
class Job:
"""This class encapsulates a Job.
Attributes:
callback (:obj:`callable`): The callback function that should be executed by the new job.
context (:obj:`object`): Optional. Additional data needed for the callback function.
name (:obj:`str`): Optional. The name of the new job.
is_monthly (:obj: `bool`): Optional. Indicates whether it is a monthly job.
day_is_strict (:obj: `bool`): Optional. Indicates whether the monthly jobs day is strict.
Args:
callback (:obj:`callable`): The callback function that should be executed by the new job.
@@ -393,6 +527,11 @@ class Job(object):
tzinfo (:obj:`datetime.tzinfo`, optional): timezone associated to this job. Used when
checking the day of the week to determine whether a job should run (only relevant when
``days is not Days.EVERY_DAY``). Defaults to UTC.
is_monthly (:obj:`bool`, optional): If this job is supposed to be a monthly scheduled job.
Defaults to ``False``.
day_is_strict (:obj:`bool`, optional): If ``False`` and day > month.days, will pick the
last day in the month. Defaults to ``True``. Only relevant when ``is_monthly`` is
``True``.
"""
def __init__(self,
@@ -403,7 +542,9 @@ class Job(object):
days=Days.EVERY_DAY,
name=None,
job_queue=None,
tzinfo=None):
tzinfo=None,
is_monthly=False,
day_is_strict=True):
self.callback = callback
self.context = context
@@ -412,11 +553,14 @@ class Job(object):
self._repeat = None
self._interval = None
self.interval = interval
self._next_t = None
self.repeat = repeat
self.is_monthly = is_monthly
self.day_is_strict = day_is_strict
self._days = None
self.days = days
self.tzinfo = tzinfo or _UTC
self.tzinfo = tzinfo or datetime.timezone.utc
self._job_queue = weakref.proxy(job_queue) if job_queue is not None else None
@@ -438,6 +582,7 @@ class Job(object):
"""
self._remove.set()
self._next_t = None
@property
def removed(self):
@@ -471,8 +616,8 @@ class Job(object):
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'")
raise TypeError("The 'interval' must be of type 'datetime.timedelta',"
" 'int' or 'float'")
self._interval = interval
@@ -485,6 +630,27 @@ class Job(object):
else:
return interval
@property
def next_t(self):
"""
:obj:`datetime.datetime`: Datetime for the next job execution.
Datetime is localized according to :attr:`tzinfo`.
If job is removed or already ran it equals to ``None``.
"""
return datetime.datetime.fromtimestamp(self._next_t, self.tzinfo) if self._next_t else None
def _set_next_t(self, next_t):
if isinstance(next_t, datetime.datetime):
# Set timezone to UTC in case datetime is in local timezone.
next_t = next_t.astimezone(datetime.timezone.utc)
next_t = to_float_timestamp(next_t)
elif not (isinstance(next_t, Number) or next_t is None):
raise TypeError("The 'next_t' argument should be one of the following types: "
"'float', 'int', 'datetime.datetime' or 'NoneType'")
self._next_t = next_t
@property
def repeat(self):
""":obj:`bool`: Optional. If this job should periodically execute its callback function."""
@@ -504,10 +670,10 @@ class Job(object):
@days.setter
def days(self, days):
if not isinstance(days, tuple):
raise ValueError("The 'days' argument should be of type 'tuple'")
raise TypeError("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'")
raise TypeError("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 "
+1 -1
View File
@@ -117,7 +117,7 @@ class MessageHandler(Handler):
channel_post_updates=None,
edited_updates=None):
super(MessageHandler, self).__init__(
super().__init__(
callback,
pass_update_queue=pass_update_queue,
pass_job_queue=pass_job_queue,
+8 -23
View File
@@ -23,24 +23,9 @@
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
import queue as q
class DelayQueueError(RuntimeError):
@@ -95,11 +80,11 @@ class DelayQueue(threading.Thread):
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)
name = '{}-{}'.format(self.__class__.__name__, self.__class__._instcnt)
super().__init__(name=name)
self.daemon = False
if autostart: # immediately start processing
super(DelayQueue, self).start()
super().start()
def run(self):
"""
@@ -114,7 +99,7 @@ class DelayQueue(threading.Thread):
if self.__exit_req:
return # shutdown thread
# delay routine
now = curtime()
now = time.perf_counter()
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
@@ -146,7 +131,7 @@ class DelayQueue(threading.Thread):
self.__exit_req = True # gently request
self._queue.put(None) # put something to unfreeze if frozen
super(DelayQueue, self).join(timeout=timeout)
super().join(timeout=timeout)
@staticmethod
def _default_exception_handler(exc):
@@ -180,7 +165,7 @@ class DelayQueue(threading.Thread):
# 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):
class MessageQueue:
"""
Implements callback processing with proper delays to avoid hitting Telegram's message limits.
Contains two ``DelayQueue``, for group and for all messages, interconnected in delay chain.
@@ -249,7 +234,7 @@ class MessageQueue(object):
``DelayQueue`` (if set to ``False``), resulting in needed delays to avoid
hitting specified limits. Defaults to ``False``.
Notes:
Note:
Method is designed to accept ``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
+3 -3
View File
@@ -66,9 +66,9 @@ class PicklePersistence(BasePersistence):
store_bot_data=True,
single_file=True,
on_flush=False):
super(PicklePersistence, self).__init__(store_user_data=store_user_data,
store_chat_data=store_chat_data,
store_bot_data=store_bot_data)
super().__init__(store_user_data=store_user_data,
store_chat_data=store_chat_data,
store_bot_data=store_bot_data)
self.filename = filename
self.single_file = single_file
self.on_flush = on_flush
+10 -11
View File
@@ -110,21 +110,20 @@ class RegexHandler(MessageHandler):
warnings.warn('RegexHandler is deprecated. See https://git.io/fxJuV for more info',
TelegramDeprecationWarning,
stacklevel=2)
super(RegexHandler, self).__init__(Filters.regex(pattern),
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,
message_updates=message_updates,
channel_post_updates=channel_post_updates,
edited_updates=edited_updates)
super().__init__(Filters.regex(pattern),
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,
message_updates=message_updates,
channel_post_updates=channel_post_updates,
edited_updates=edited_updates)
self.pass_groups = pass_groups
self.pass_groupdict = pass_groupdict
def collect_optional_args(self, dispatcher, update=None, check_result=None):
optional_args = super(RegexHandler, self).collect_optional_args(dispatcher, update,
check_result)
optional_args = super().collect_optional_args(dispatcher, update, check_result)
if self.pass_groups:
optional_args['groups'] = check_result['matches'][0].groups()
if self.pass_groupdict:
+3 -7
View File
@@ -18,8 +18,6 @@
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains the StringCommandHandler class."""
from future.utils import string_types
from .handler import Handler
@@ -73,7 +71,7 @@ class StringCommandHandler(Handler):
pass_args=False,
pass_update_queue=False,
pass_job_queue=False):
super(StringCommandHandler, self).__init__(
super().__init__(
callback,
pass_update_queue=pass_update_queue,
pass_job_queue=pass_job_queue)
@@ -90,15 +88,13 @@ class StringCommandHandler(Handler):
:obj:`bool`
"""
if isinstance(update, string_types) and update.startswith('/'):
if isinstance(update, str) and update.startswith('/'):
args = update[1:].split(' ')
if args[0] == self.command:
return args[1:]
def collect_optional_args(self, dispatcher, update=None, check_result=None):
optional_args = super(StringCommandHandler, self).collect_optional_args(dispatcher,
update,
check_result)
optional_args = super().collect_optional_args(dispatcher, update, check_result)
if self.pass_args:
optional_args['args'] = check_result
return optional_args
+4 -7
View File
@@ -20,8 +20,6 @@
import re
from future.utils import string_types
from .handler import Handler
@@ -85,12 +83,12 @@ class StringRegexHandler(Handler):
pass_groupdict=False,
pass_update_queue=False,
pass_job_queue=False):
super(StringRegexHandler, self).__init__(
super().__init__(
callback,
pass_update_queue=pass_update_queue,
pass_job_queue=pass_job_queue)
if isinstance(pattern, string_types):
if isinstance(pattern, str):
pattern = re.compile(pattern)
self.pattern = pattern
@@ -107,14 +105,13 @@ class StringRegexHandler(Handler):
:obj:`bool`
"""
if isinstance(update, string_types):
if isinstance(update, str):
match = re.match(self.pattern, update)
if match:
return match
def collect_optional_args(self, dispatcher, update=None, check_result=None):
optional_args = super(StringRegexHandler, self).collect_optional_args(dispatcher,
update, check_result)
optional_args = super().collect_optional_args(dispatcher, update, check_result)
if self.pattern:
if self.pass_groups:
optional_args['groups'] = check_result.groups()
+1 -1
View File
@@ -65,7 +65,7 @@ class TypeHandler(Handler):
strict=False,
pass_update_queue=False,
pass_job_queue=False):
super(TypeHandler, self).__init__(
super().__init__(
callback,
pass_update_queue=pass_update_queue,
pass_job_queue=pass_job_queue)
+9 -10
View File
@@ -32,10 +32,8 @@ from telegram.utils.helpers import get_signal_name
from telegram.utils.request import Request
from telegram.utils.webhookhandler import (WebhookServer, WebhookAppClass)
logging.getLogger(__name__).addHandler(logging.NullHandler())
class Updater(object):
class Updater:
"""
This class, which employs the :class:`telegram.ext.Dispatcher`, provides a frontend to
:class:`telegram.Bot` to the programmer, so they can focus on coding the bot. Its purpose is to
@@ -49,7 +47,8 @@ class Updater(object):
Attributes:
bot (:class:`telegram.Bot`): The bot used with this Updater.
user_sig_handler (:obj:`signal`): signals the updater will respond to.
user_sig_handler (:obj:`function`): Optional. Function to be called when a signal is
received.
update_queue (:obj:`Queue`): Queue for the updates.
job_queue (:class:`telegram.ext.JobQueue`): Jobqueue for the updater.
dispatcher (:class:`telegram.ext.Dispatcher`): Dispatcher that handles the updates and
@@ -57,7 +56,7 @@ class Updater(object):
running (:obj:`bool`): Indicates if the updater is running.
persistence (:class:`telegram.ext.BasePersistence`): Optional. The persistence class to
store data that should be persistent over restarts.
use_context (:obj:`bool`, optional): ``True`` if using context based callbacks.
use_context (:obj:`bool`): Optional. ``True`` if using context based callbacks.
Args:
token (:obj:`str`, optional): The bot's token given by the @BotFather.
@@ -211,14 +210,14 @@ class Updater(object):
def _thread_wrapper(self, target, *args, **kwargs):
thr_name = current_thread().name
self.logger.debug('{0} - started'.format(thr_name))
self.logger.debug('{} - started'.format(thr_name))
try:
target(*args, **kwargs)
except Exception:
self.__exception_event.set()
self.logger.exception('unhandled exception in %s', thr_name)
raise
self.logger.debug('{0} - ended'.format(thr_name))
self.logger.debug('{} - ended'.format(thr_name))
def start_polling(self,
poll_interval=0.0,
@@ -415,7 +414,7 @@ class Updater(object):
self.logger.debug('Updater thread started (webhook)')
use_ssl = cert is not None and key is not None
if not url_path.startswith('/'):
url_path = '/{0}'.format(url_path)
url_path = '/{}'.format(url_path)
# Create Tornado app instance
app = WebhookAppClass(url_path, self.bot, self.update_queue,
@@ -544,9 +543,9 @@ class Updater(object):
def _join_threads(self):
for thr in self.__threads:
self.logger.debug('Waiting for {0} thread to end'.format(thr.name))
self.logger.debug('Waiting for {} thread to end'.format(thr.name))
thr.join()
self.logger.debug('{0} thread has ended'.format(thr.name))
self.logger.debug('{} thread has ended'.format(thr.name))
self.__threads = []
def signal_handler(self, signum, frame):
+1 -1
View File
@@ -88,7 +88,7 @@ class Animation(TelegramObject):
if not data:
return None
data = super(Animation, cls).de_json(data, bot)
data = super().de_json(data, bot)
data['thumb'] = PhotoSize.de_json(data.get('thumb'), bot)
+1 -1
View File
@@ -76,7 +76,7 @@ class Document(TelegramObject):
if not data:
return None
data = super(Document, cls).de_json(data, bot)
data = super().de_json(data, bot)
data['thumb'] = PhotoSize.de_json(data.get('thumb'), bot)
+1 -1
View File
@@ -21,7 +21,7 @@ from base64 import b64decode
from os.path import basename
import os
from future.backports.urllib import parse as urllib_parse
import urllib.parse as urllib_parse
from telegram import TelegramObject
from telegram.passport.credentials import decrypt
+2 -6
View File
@@ -29,7 +29,7 @@ from telegram import TelegramError
DEFAULT_MIME_TYPE = 'application/octet-stream'
class InputFile(object):
class InputFile:
"""This object represents a Telegram InputFile.
Attributes:
@@ -55,11 +55,7 @@ class InputFile(object):
if filename:
self.filename = filename
elif (hasattr(obj, 'name')
and not isinstance(obj.name, int) # py3
and obj.name != '<fdopen>'): # py2
# on py2.7, pylint fails to understand this properly
# pylint: disable=E1101
elif (hasattr(obj, 'name') and not isinstance(obj.name, int)):
self.filename = os.path.basename(obj.name)
try:
+59 -97
View File
@@ -38,33 +38,25 @@ class InputMediaAnimation(InputMedia):
Attributes:
type (:obj:`str`): ``animation``.
media (:obj:`str` | `filelike object` | :class:`telegram.Animation`): Animation to
send. Pass a file_id as String to send an animation that exists on the Telegram
servers (recommended), pass an HTTP URL as a String for Telegram to get an
animation from the Internet, or upload a new animation using multipart/form-data.
Lastly you can pass an existing :class:`telegram.Animation` object to send.
thumb (`filelike object`): Optional. Thumbnail of the
file sent. The thumbnail should be in JPEG format and less than 200 kB in size.
A thumbnail's width and height should not exceed 320. Ignored if the file is not
is passed as a string or file_id.
caption (:obj:`str`): Optional. Caption of the animation to be sent, 0-1024 characters
after entities parsing.
parse_mode (:obj:`str`): Optional. Send Markdown or HTML, if you want Telegram apps to show
bold, italic, fixed-width text or inline URLs in the media caption. See the constants
in :class:`telegram.ParseMode` for the available modes.
media (:obj:`str` | :class:`telegram.InputFile`): Animation to send.
caption (:obj:`str`): Optional. Caption of the document to be sent.
parse_mode (:obj:`str`): Optional. The parse mode to use for text formatting.
thumb (:class:`telegram.InputFile`): Optional. Thumbnail of the file to send.
width (:obj:`int`): Optional. Animation width.
height (:obj:`int`): Optional. Animation height.
duration (:obj:`int`): Optional. Animation duration.
Args:
media (:obj:`str`): File to send. Pass a file_id to send a file that exists on the Telegram
servers (recommended), pass an HTTP URL for Telegram to get a file from the Internet.
Lastly you can pass an existing :class:`telegram.Animation` object to send.
thumb (`filelike object`, optional): Thumbnail of the
file sent. The thumbnail should be in JPEG format and less than 200 kB in size.
A thumbnail's width and height should not exceed 320. Ignored if the file is not
is passed as a string or file_id.
media (:obj:`str` | `filelike object` | :class:`telegram.Animation`): File to send. Pass a
file_id to send a file that exists on the Telegram servers (recommended), pass an HTTP
URL for Telegram to get a file from the Internet. Lastly you can pass an existing
:class:`telegram.Animation` object to send.
thumb (`filelike object`, optional): Thumbnail of the file sent; can be ignored if
thumbnail generation for the file is supported server-side. The thumbnail should be
in JPEG format and less than 200 kB in size. A thumbnail's width and height should
not exceed 320. Ignored if the file is not uploaded using multipart/form-data.
Thumbnails can't be reused and can be only uploaded as a new file.
caption (:obj:`str`, optional): Caption of the animation to be sent, 0-1024 characters
after entities parsing.
parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show
@@ -121,21 +113,15 @@ class InputMediaPhoto(InputMedia):
Attributes:
type (:obj:`str`): ``photo``.
media (:obj:`str` | `filelike object` | :class:`telegram.PhotoSize`): Photo to send.
Pass a file_id as String to send a photo that exists on the Telegram servers
(recommended), pass an HTTP URL as a String for Telegram to get a photo from the
Internet, or upload a new photo using multipart/form-data. Lastly you can pass
an existing :class:`telegram.PhotoSize` object to send.
caption (:obj:`str`): Optional. Caption of the photo to be sent, 0-1024 characters after
entities parsing.
parse_mode (:obj:`str`): Optional. Send Markdown or HTML, if you want Telegram apps to show
bold, italic, fixed-width text or inline URLs in the media caption. See the constants
in :class:`telegram.ParseMode` for the available modes.
media (:obj:`str` | :class:`telegram.InputFile`): Photo to send.
caption (:obj:`str`): Optional. Caption of the document to be sent.
parse_mode (:obj:`str`): Optional. The parse mode to use for text formatting.
Args:
media (:obj:`str`): File to send. Pass a file_id to send a file that exists on the
Telegram servers (recommended), pass an HTTP URL for Telegram to get a file from the
Internet. Lastly you can pass an existing :class:`telegram.PhotoSize` object to send.
media (:obj:`str` | `filelike object` | :class:`telegram.PhotoSize`): File to send. Pass a
file_id to send a file that exists on the Telegram servers (recommended), pass an HTTP
URL for Telegram to get a file from the Internet. Lastly you can pass an existing
:class:`telegram.PhotoSize` object to send.
caption (:obj:`str`, optional ): Caption of the photo to be sent, 0-1024 characters after
entities parsing.
parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show
@@ -163,30 +149,21 @@ class InputMediaVideo(InputMedia):
Attributes:
type (:obj:`str`): ``video``.
media (:obj:`str` | `filelike object` | :class:`telegram.Video`): Video file to send.
Pass a file_id as String to send an video file that exists on the Telegram servers
(recommended), pass an HTTP URL as a String for Telegram to get an video file from
the Internet, or upload a new one using multipart/form-data. Lastly you can pass
an existing :class:`telegram.Video` object to send.
caption (:obj:`str`): Optional. Caption of the video to be sent, 0-1024 characters after
entities parsing.
parse_mode (:obj:`str`): Optional. Send Markdown or HTML, if you want Telegram apps to show
bold, italic, fixed-width text or inline URLs in the media caption. See the constants
in :class:`telegram.ParseMode` for the available modes.
media (:obj:`str` | :class:`telegram.InputFile`): Video file to send.
caption (:obj:`str`): Optional. Caption of the document to be sent.
parse_mode (:obj:`str`): Optional. The parse mode to use for text formatting.
width (:obj:`int`): Optional. Video width.
height (:obj:`int`): Optional. Video height.
duration (:obj:`int`): Optional. Video duration.
supports_streaming (:obj:`bool`): Optional. Pass True, if the uploaded video is suitable
for streaming.
thumb (`filelike object`): Optional. Thumbnail of the
file sent. The thumbnail should be in JPEG format and less than 200 kB in size.
A thumbnail's width and height should not exceed 320. Ignored if the file is not
is passed as a string or file_id.
thumb (:class:`telegram.InputFile`): Optional. Thumbnail of the file to send.
Args:
media (:obj:`str`): File to send. Pass a file_id to send a file that exists on the Telegram
servers (recommended), pass an HTTP URL for Telegram to get a file from the Internet.
Lastly you can pass an existing :class:`telegram.Video` object to send.
media (:obj:`str` | `filelike object` | :class:`telegram.Video`): File to send. Pass a
file_id to send a file that exists on the Telegram servers (recommended), pass an HTTP
URL for Telegram to get a file from the Internet. Lastly you can pass an existing
:class:`telegram.Video` object to send.
caption (:obj:`str`, optional): Caption of the video to be sent, 0-1024 characters after
entities parsing.
parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show
@@ -197,10 +174,11 @@ class InputMediaVideo(InputMedia):
duration (:obj:`int`, optional): Video duration.
supports_streaming (:obj:`bool`, optional): Pass True, if the uploaded video is suitable
for streaming.
thumb (`filelike object`, optional): Thumbnail of the
file sent. The thumbnail should be in JPEG format and less than 200 kB in size.
A thumbnail's width and height should not exceed 320. Ignored if the file is not
is passed as a string or file_id.
thumb (`filelike object`, optional): Thumbnail of the file sent; can be ignored if
thumbnail generation for the file is supported server-side. The thumbnail should be
in JPEG format and less than 200 kB in size. A thumbnail's width and height should
not exceed 320. Ignored if the file is not uploaded using multipart/form-data.
Thumbnails can't be reused and can be only uploaded as a new file.
Note:
When using a :class:`telegram.Video` for the :attr:`media` attribute. It will take the
@@ -245,29 +223,20 @@ class InputMediaAudio(InputMedia):
Attributes:
type (:obj:`str`): ``audio``.
media (:obj:`str` | `filelike object` | :class:`telegram.Audio`): Audio file to send.
Pass a file_id as String to send an audio file that exists on the Telegram servers
(recommended), pass an HTTP URL as a String for Telegram to get an audio file from
the Internet, or upload a new one using multipart/form-data. Lastly you can pass
an existing :class:`telegram.Audio` object to send.
caption (:obj:`str`): Optional. Caption of the audio to be sent, 0-1024 characters after
entities parsing.
parse_mode (:obj:`str`): Optional. Send Markdown or HTML, if you want Telegram apps to show
bold, italic, fixed-width text or inline URLs in the media caption. See the constants
in :class:`telegram.ParseMode` for the available modes.
media (:obj:`str` | :class:`telegram.InputFile`): Audio file to send.
caption (:obj:`str`): Optional. Caption of the document to be sent.
parse_mode (:obj:`str`): Optional. The parse mode to use for text formatting.
duration (:obj:`int`): Duration of the audio in seconds.
performer (:obj:`str`): Optional. Performer of the audio as defined by sender or by audio
tags.
title (:obj:`str`): Optional. Title of the audio as defined by sender or by audio tags.
thumb (`filelike object`): Optional. Thumbnail of the
file sent. The thumbnail should be in JPEG format and less than 200 kB in size.
A thumbnail's width and height should not exceed 320. Ignored if the file is not
is passed as a string or file_id.
thumb (:class:`telegram.InputFile`): Optional. Thumbnail of the file to send.
Args:
media (:obj:`str`): File to send. Pass a file_id to send a file that exists on the Telegram
servers (recommended), pass an HTTP URL for Telegram to get a file from the Internet.
Lastly you can pass an existing :class:`telegram.Document` object to send.
media (:obj:`str` | `filelike object` | :class:`telegram.Audio`): File to send. Pass a
file_id to send a file that exists on the Telegram servers (recommended), pass an HTTP
URL for Telegram to get a file from the Internet. Lastly you can pass an existing
:class:`telegram.Document` object to send.
caption (:obj:`str`, optional): Caption of the audio to be sent, 0-1024 characters after
entities parsing.
parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show
@@ -277,10 +246,11 @@ class InputMediaAudio(InputMedia):
performer (:obj:`str`, optional): Performer of the audio as defined by sender or by audio
tags.
title (:obj:`str`, optional): Title of the audio as defined by sender or by audio tags.
thumb (`filelike object`, optional): Thumbnail of the
file sent. The thumbnail should be in JPEG format and less than 200 kB in size.
A thumbnail's width and height should not exceed 320. Ignored if the file is not
is passed as a string or file_id.
thumb (`filelike object`, optional): Thumbnail of the file sent; can be ignored if
thumbnail generation for the file is supported server-side. The thumbnail should be
in JPEG format and less than 200 kB in size. A thumbnail's width and height should
not exceed 320. Ignored if the file is not uploaded using multipart/form-data.
Thumbnails can't be reused and can be only uploaded as a new file.
Note:
When using a :class:`telegram.Audio` for the :attr:`media` attribute. It will take the
@@ -323,34 +293,26 @@ class InputMediaDocument(InputMedia):
Attributes:
type (:obj:`str`): ``document``.
media (:obj:`str` | `filelike object` | :class:`telegram.Document`): File to send.
Pass a file_id as String to send a file that exists on the Telegram servers
(recommended), pass an HTTP URL as a String for Telegram to get a file from the
Internet, or upload a new one using multipart/form-data. Lastly you can pass
an existing :class:`telegram.Document` object to send.
caption (:obj:`str`): Optional. Caption of the document to be sent, 0-1024 characters after
entities parsing.
parse_mode (:obj:`str`): Optional. Send Markdown or HTML, if you want Telegram apps to show
bold, italic, fixed-width text or inline URLs in the media caption. See the constants
in :class:`telegram.ParseMode` for the available modes.
thumb (`filelike object`): Optional. Thumbnail of the
file sent. The thumbnail should be in JPEG format and less than 200 kB in size.
A thumbnail's width and height should not exceed 320. Ignored if the file is not
is passed as a string or file_id.
media (:obj:`str` | :class:`telegram.InputFile`): File to send.
caption (:obj:`str`): Optional. Caption of the document to be sent.
parse_mode (:obj:`str`): Optional. The parse mode to use for text formatting.
thumb (:class:`telegram.InputFile`): Optional. Thumbnail of the file to send.
Args:
media (:obj:`str`): File to send. Pass a file_id to send a file that exists on the Telegram
servers (recommended), pass an HTTP URL for Telegram to get a file from the Internet.
Lastly you can pass an existing :class:`telegram.Document` object to send.
media (:obj:`str` | `filelike object` | :class:`telegram.Document`): File to send. Pass a
file_id to send a file that exists on the Telegram servers (recommended), pass an HTTP
URL for Telegram to get a file from the Internet. Lastly you can pass an existing
:class:`telegram.Document` object to send.
caption (:obj:`str`, optional): Caption of the document to be sent, 0-1024 characters after
entities parsing.
parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show
bold, italic, fixed-width text or inline URLs in the media caption. See the constants
in :class:`telegram.ParseMode` for the available modes.
thumb (`filelike object`, optional): Thumbnail of the
file sent. The thumbnail should be in JPEG format and less than 200 kB in size.
A thumbnail's width and height should not exceed 320. Ignored if the file is not
is passed as a string or file_id.
thumb (`filelike object`, optional): Thumbnail of the file sent; can be ignored if
thumbnail generation for the file is supported server-side. The thumbnail should be
in JPEG format and less than 200 kB in size. A thumbnail's width and height should
not exceed 320. Ignored if the file is not uploaded using multipart/form-data.
Thumbnails can't be reused and can be only uploaded as a new file.
"""
def __init__(self, media, thumb=None, caption=None, parse_mode=DEFAULT_NONE):
+7 -7
View File
@@ -96,7 +96,7 @@ class Sticker(TelegramObject):
if not data:
return None
data = super(Sticker, cls).de_json(data, bot)
data = super().de_json(data, bot)
data['thumb'] = PhotoSize.de_json(data.get('thumb'), bot)
data['mask_position'] = MaskPosition.de_json(data.get('mask_position'), bot)
@@ -164,20 +164,20 @@ class StickerSet(TelegramObject):
self._id_attrs = (self.name,)
@staticmethod
def de_json(data, bot):
@classmethod
def de_json(cls, data, bot):
if not data:
return None
data = super(StickerSet, StickerSet).de_json(data, bot)
data = super().de_json(data, bot)
data['thumb'] = PhotoSize.de_json(data.get('thumb'), bot)
data['stickers'] = Sticker.de_list(data.get('stickers'), bot)
return StickerSet(bot=bot, **data)
return cls(bot=bot, **data)
def to_dict(self):
data = super(StickerSet, self).to_dict()
data = super().to_dict()
data['stickers'] = [s.to_dict() for s in data.get('stickers')]
@@ -195,7 +195,7 @@ class MaskPosition(TelegramObject):
size, from top to bottom.
scale (:obj:`float`): Mask scaling coefficient. For example, 2.0 means double size.
Notes:
Note:
:attr:`type` should be one of the following: `forehead`, `eyes`, `mouth` or `chin`. You can
use the classconstants for those.
+1 -1
View File
@@ -57,7 +57,7 @@ class Venue(TelegramObject):
@classmethod
def de_json(cls, data, bot):
data = super(Venue, cls).de_json(data, bot)
data = super().de_json(data, bot)
if not data:
return None
+1 -1
View File
@@ -83,7 +83,7 @@ class Video(TelegramObject):
if not data:
return None
data = super(Video, cls).de_json(data, bot)
data = super().de_json(data, bot)
data['thumb'] = PhotoSize.de_json(data.get('thumb'), bot)
+1 -1
View File
@@ -75,7 +75,7 @@ class VideoNote(TelegramObject):
if not data:
return None
data = super(VideoNote, cls).de_json(data, bot)
data = super().de_json(data, bot)
data['thumb'] = PhotoSize.de_json(data.get('thumb'), bot)
+1 -1
View File
@@ -71,7 +71,7 @@ class Voice(TelegramObject):
if not data:
return None
data = super(Voice, cls).de_json(data, bot)
data = super().de_json(data, bot)
return cls(bot=bot, **data)
+2 -2
View File
@@ -77,7 +77,7 @@ class Game(TelegramObject):
if not data:
return None
data = super(Game, cls).de_json(data, bot)
data = super().de_json(data, bot)
data['photo'] = PhotoSize.de_list(data.get('photo'), bot)
data['text_entities'] = MessageEntity.de_list(data.get('text_entities'), bot)
@@ -86,7 +86,7 @@ class Game(TelegramObject):
return cls(**data)
def to_dict(self):
data = super(Game, self).to_dict()
data = super().to_dict()
data['photo'] = [p.to_dict() for p in self.photo]
if self.text_entities:
+1 -1
View File
@@ -46,7 +46,7 @@ class GameHighScore(TelegramObject):
if not data:
return None
data = super(GameHighScore, cls).de_json(data, bot)
data = super().de_json(data, bot)
data['user'] = User.de_json(data.get('user'), bot)
+1 -1
View File
@@ -41,7 +41,7 @@ class InlineKeyboardMarkup(ReplyMarkup):
self.inline_keyboard = inline_keyboard
def to_dict(self):
data = super(InlineKeyboardMarkup, self).to_dict()
data = super().to_dict()
data['inline_keyboard'] = []
for inline_keyboard in self.inline_keyboard:
+1 -1
View File
@@ -65,7 +65,7 @@ class InlineQuery(TelegramObject):
@classmethod
def de_json(cls, data, bot):
data = super(InlineQuery, cls).de_json(data, bot)
data = super().de_json(data, bot)
if not data:
return None
+1 -1
View File
@@ -72,7 +72,7 @@ class InlineQueryResultArticle(InlineQueryResult):
**kwargs):
# Required
super(InlineQueryResultArticle, self).__init__('article', id)
super().__init__('article', id)
self.title = title
self.input_message_content = input_message_content
+1 -1
View File
@@ -75,7 +75,7 @@ class InlineQueryResultAudio(InlineQueryResult):
**kwargs):
# Required
super(InlineQueryResultAudio, self).__init__('audio', id)
super().__init__('audio', id)
self.audio_url = audio_url
self.title = title
@@ -65,7 +65,7 @@ class InlineQueryResultCachedAudio(InlineQueryResult):
parse_mode=DEFAULT_NONE,
**kwargs):
# Required
super(InlineQueryResultCachedAudio, self).__init__('audio', id)
super().__init__('audio', id)
self.audio_file_id = audio_file_id
# Optionals
@@ -73,7 +73,7 @@ class InlineQueryResultCachedDocument(InlineQueryResult):
parse_mode=DEFAULT_NONE,
**kwargs):
# Required
super(InlineQueryResultCachedDocument, self).__init__('document', id)
super().__init__('document', id)
self.title = title
self.document_file_id = document_file_id
@@ -71,7 +71,7 @@ class InlineQueryResultCachedGif(InlineQueryResult):
parse_mode=DEFAULT_NONE,
**kwargs):
# Required
super(InlineQueryResultCachedGif, self).__init__('gif', id)
super().__init__('gif', id)
self.gif_file_id = gif_file_id
# Optionals
@@ -71,7 +71,7 @@ class InlineQueryResultCachedMpeg4Gif(InlineQueryResult):
parse_mode=DEFAULT_NONE,
**kwargs):
# Required
super(InlineQueryResultCachedMpeg4Gif, self).__init__('mpeg4_gif', id)
super().__init__('mpeg4_gif', id)
self.mpeg4_file_id = mpeg4_file_id
# Optionals
@@ -74,7 +74,7 @@ class InlineQueryResultCachedPhoto(InlineQueryResult):
parse_mode=DEFAULT_NONE,
**kwargs):
# Required
super(InlineQueryResultCachedPhoto, self).__init__('photo', id)
super().__init__('photo', id)
self.photo_file_id = photo_file_id
# Optionals
@@ -54,7 +54,7 @@ class InlineQueryResultCachedSticker(InlineQueryResult):
input_message_content=None,
**kwargs):
# Required
super(InlineQueryResultCachedSticker, self).__init__('sticker', id)
super().__init__('sticker', id)
self.sticker_file_id = sticker_file_id
# Optionals
@@ -74,7 +74,7 @@ class InlineQueryResultCachedVideo(InlineQueryResult):
parse_mode=DEFAULT_NONE,
**kwargs):
# Required
super(InlineQueryResultCachedVideo, self).__init__('video', id)
super().__init__('video', id)
self.video_file_id = video_file_id
self.title = title
@@ -68,7 +68,7 @@ class InlineQueryResultCachedVoice(InlineQueryResult):
parse_mode=DEFAULT_NONE,
**kwargs):
# Required
super(InlineQueryResultCachedVoice, self).__init__('voice', id)
super().__init__('voice', id)
self.voice_file_id = voice_file_id
self.title = title
+1 -1
View File
@@ -74,7 +74,7 @@ class InlineQueryResultContact(InlineQueryResult):
vcard=None,
**kwargs):
# Required
super(InlineQueryResultContact, self).__init__('contact', id)
super().__init__('contact', id)
self.phone_number = phone_number
self.first_name = first_name
+1 -1
View File
@@ -88,7 +88,7 @@ class InlineQueryResultDocument(InlineQueryResult):
parse_mode=DEFAULT_NONE,
**kwargs):
# Required
super(InlineQueryResultDocument, self).__init__('document', id)
super().__init__('document', id)
self.document_url = document_url
self.title = title
self.mime_type = mime_type
+1 -1
View File
@@ -42,7 +42,7 @@ class InlineQueryResultGame(InlineQueryResult):
def __init__(self, id, game_short_name, reply_markup=None, **kwargs):
# Required
super(InlineQueryResultGame, self).__init__('game', id)
super().__init__('game', id)
self.id = id
self.game_short_name = game_short_name
+6 -1
View File
@@ -36,6 +36,7 @@ class InlineQueryResultGif(InlineQueryResult):
gif_height (:obj:`int`): Optional. Height of the GIF.
gif_duration (:obj:`int`): Optional. Duration of the GIF.
thumb_url (:obj:`str`): URL of the static thumbnail for the result (jpeg or gif).
thumb_mime_type (:obj:`str`): Optional. MIME type of the thumbnail.
title (:obj:`str`): Optional. Title for the result.
caption (:obj:`str`): Optional. Caption of the GIF file to be sent, 0-1024 characters
after entities parsing.
@@ -54,6 +55,8 @@ class InlineQueryResultGif(InlineQueryResult):
gif_height (:obj:`int`, optional): Height of the GIF.
gif_duration (:obj:`int`, optional): Duration of the GIF
thumb_url (:obj:`str`): URL of the static thumbnail for the result (jpeg or gif).
thumb_mime_type (:obj:`str`): Optional. MIME type of the thumbnail, must be one of
image/jpeg, image/gif, or video/mp4. Defaults to image/jpeg.
title (:obj:`str`, optional): Title for the result.
caption (:obj:`str`, optional): Caption of the GIF file to be sent, 0-1024 characters
after entities parsing.
@@ -80,10 +83,11 @@ class InlineQueryResultGif(InlineQueryResult):
input_message_content=None,
gif_duration=None,
parse_mode=DEFAULT_NONE,
thumb_mime_type=None,
**kwargs):
# Required
super(InlineQueryResultGif, self).__init__('gif', id)
super().__init__('gif', id)
self.gif_url = gif_url
self.thumb_url = thumb_url
@@ -96,3 +100,4 @@ class InlineQueryResultGif(InlineQueryResult):
self.parse_mode = parse_mode
self.reply_markup = reply_markup
self.input_message_content = input_message_content
self.thumb_mime_type = thumb_mime_type
+1 -1
View File
@@ -74,7 +74,7 @@ class InlineQueryResultLocation(InlineQueryResult):
thumb_height=None,
**kwargs):
# Required
super(InlineQueryResultLocation, self).__init__('location', id)
super().__init__('location', id)
self.latitude = latitude
self.longitude = longitude
self.title = title
+6 -1
View File
@@ -37,6 +37,7 @@ class InlineQueryResultMpeg4Gif(InlineQueryResult):
mpeg4_height (:obj:`int`): Optional. Video height.
mpeg4_duration (:obj:`int`): Optional. Video duration.
thumb_url (:obj:`str`): URL of the static thumbnail (jpeg or gif) for the result.
thumb_mime_type (:obj:`str`): Optional. MIME type of the thumbnail.
title (:obj:`str`): Optional. Title for the result.
caption (:obj:`str`): Optional. Caption of the MPEG-4 file to be sent, 0-1024 characters
after entities parsing.
@@ -55,6 +56,8 @@ class InlineQueryResultMpeg4Gif(InlineQueryResult):
mpeg4_height (:obj:`int`, optional): Video height.
mpeg4_duration (:obj:`int`, optional): Video duration.
thumb_url (:obj:`str`): URL of the static thumbnail (jpeg or gif) for the result.
thumb_mime_type (:obj:`str`): Optional. MIME type of the thumbnail, must be one of
image/jpeg, image/gif, or video/mp4. Defaults to image/jpeg.
title (:obj:`str`, optional): Title for the result.
caption (:obj:`str`, optional): Caption of the MPEG-4 file to be sent, 0-1024 characters
after entities parsing.
@@ -81,10 +84,11 @@ class InlineQueryResultMpeg4Gif(InlineQueryResult):
input_message_content=None,
mpeg4_duration=None,
parse_mode=DEFAULT_NONE,
thumb_mime_type=None,
**kwargs):
# Required
super(InlineQueryResultMpeg4Gif, self).__init__('mpeg4_gif', id)
super().__init__('mpeg4_gif', id)
self.mpeg4_url = mpeg4_url
self.thumb_url = thumb_url
@@ -97,3 +101,4 @@ class InlineQueryResultMpeg4Gif(InlineQueryResult):
self.parse_mode = parse_mode
self.reply_markup = reply_markup
self.input_message_content = input_message_content
self.thumb_mime_type = thumb_mime_type
+1 -1
View File
@@ -84,7 +84,7 @@ class InlineQueryResultPhoto(InlineQueryResult):
parse_mode=DEFAULT_NONE,
**kwargs):
# Required
super(InlineQueryResultPhoto, self).__init__('photo', id)
super().__init__('photo', id)
self.photo_url = photo_url
self.thumb_url = thumb_url
+1 -1
View File
@@ -83,7 +83,7 @@ class InlineQueryResultVenue(InlineQueryResult):
**kwargs):
# Required
super(InlineQueryResultVenue, self).__init__('venue', id)
super().__init__('venue', id)
self.latitude = latitude
self.longitude = longitude
self.title = title
+1 -1
View File
@@ -97,7 +97,7 @@ class InlineQueryResultVideo(InlineQueryResult):
**kwargs):
# Required
super(InlineQueryResultVideo, self).__init__('video', id)
super().__init__('video', id)
self.video_url = video_url
self.mime_type = mime_type
self.thumb_url = thumb_url
+1 -1
View File
@@ -73,7 +73,7 @@ class InlineQueryResultVoice(InlineQueryResult):
**kwargs):
# Required
super(InlineQueryResultVoice, self).__init__('voice', id)
super().__init__('voice', id)
self.voice_url = voice_url
self.title = title
+28 -23
View File
@@ -107,6 +107,7 @@ class Message(TelegramObject):
poll (:class:`telegram.Poll`): Optional. Message is a native poll,
information about the poll.
dice (:class:`telegram.Dice`): Optional. Message is a dice.
via_bot (:class:`telegram.User`): Optional. Bot through which the message was sent.
reply_markup (:class:`telegram.InlineKeyboardMarkup`): Optional. Inline keyboard attached
to the message.
bot (:class:`telegram.Bot`): Optional. The Bot to use for instance methods.
@@ -216,6 +217,7 @@ class Message(TelegramObject):
poll (:class:`telegram.Poll`, optional): Message is a native poll,
information about the poll.
dice (:class:`telegram.Dice`, optional): Message is a dice with random value from 1 to 6.
via_bot (:class:`telegram.User`, optional): Message was sent through an inline bot.
reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): Inline keyboard attached
to the message. login_url buttons are represented as ordinary url buttons.
default_quote (:obj:`bool`, optional): Default setting for the `quote` parameter of the
@@ -285,6 +287,7 @@ class Message(TelegramObject):
bot=None,
default_quote=None,
dice=None,
via_bot=None,
**kwargs):
# Required
self.message_id = int(message_id)
@@ -335,6 +338,7 @@ class Message(TelegramObject):
self.passport_data = passport_data
self.poll = poll
self.dice = dice
self.via_bot = via_bot
self.reply_markup = reply_markup
self.bot = bot
self.default_quote = default_quote
@@ -364,7 +368,7 @@ class Message(TelegramObject):
if not data:
return None
data = super(Message, cls).de_json(data, bot)
data = super().de_json(data, bot)
data['from_user'] = User.de_json(data.get('from'), bot)
data['date'] = from_timestamp(data['date'])
@@ -409,6 +413,7 @@ class Message(TelegramObject):
data['passport_data'] = PassportData.de_json(data.get('passport_data'), bot)
data['poll'] = Poll.de_json(data.get('poll'), bot)
data['dice'] = Dice.de_json(data.get('dice'), bot)
data['via_bot'] = User.de_json(data.get('via_bot'), bot)
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'), bot)
return cls(bot=bot, **data)
@@ -452,7 +457,7 @@ class Message(TelegramObject):
return self.chat.id
def to_dict(self):
data = super(Message, self).to_dict()
data = super().to_dict()
# Required
data['date'] = to_timestamp(self.date)
@@ -859,9 +864,9 @@ class Message(TelegramObject):
**kwargs)
Note:
You can only edit messages that the bot sent itself,
therefore this method can only be used on the
return value of the ``bot.send_*`` family of methods.
You can only edit messages that the bot sent itself (i.e. of the ``bot.send_*`` family
of methods) or channel posts, if the bot is an admin in that channel. However, this
behaviour is undocumented and might be changed by Telegram.
Returns:
:class:`telegram.Message`: On success, instance representing the edited message.
@@ -879,9 +884,9 @@ class Message(TelegramObject):
**kwargs)
Note:
You can only edit messages that the bot sent itself,
therefore this method can only be used on the
return value of the ``bot.send_*`` family of methods.
You can only edit messages that the bot sent itself (i.e. of the ``bot.send_*`` family
of methods) or channel posts, if the bot is an admin in that channel. However, this
behaviour is undocumented and might be changed by Telegram.
Returns:
:class:`telegram.Message`: On success, instance representing the edited message.
@@ -893,21 +898,21 @@ class Message(TelegramObject):
def edit_media(self, media, *args, **kwargs):
"""Shortcut for::
bot.edit_message_media(chat_id=message.chat_id,
message_id=message.message_id,
*args,
**kwargs)
bot.edit_message_media(chat_id=message.chat_id,
message_id=message.message_id,
*args,
**kwargs)
Note:
You can only edit messages that the bot sent itself,
therefore this method can only be used on the
return value of the ``bot.send_*`` family of methods.
Note:
You can only edit messages that the bot sent itself (i.e. of the ``bot.send_*`` family
of methods) or channel posts, if the bot is an admin in that channel. However, this
behaviour is undocumented and might be changed by Telegram.
Returns:
:class:`telegram.Message`: On success, instance representing the edited
message.
Returns:
:class:`telegram.Message`: On success, instance representing the edited
message.
"""
"""
return self.bot.edit_message_media(
chat_id=self.chat_id, message_id=self.message_id, media=media, *args, **kwargs)
@@ -920,9 +925,9 @@ class Message(TelegramObject):
**kwargs)
Note:
You can only edit messages that the bot sent itself,
therefore this method can only be used on the
return value of the ``bot.send_*`` family of methods.
You can only edit messages that the bot sent itself (i.e. of the ``bot.send_*`` family
of methods) or channel posts, if the bot is an admin in that channel. However, this
behaviour is undocumented and might be changed by Telegram.
Returns:
:class:`telegram.Message`: On success, instance representing the edited message.
+1 -1
View File
@@ -65,7 +65,7 @@ class MessageEntity(TelegramObject):
@classmethod
def de_json(cls, data, bot):
data = super(MessageEntity, cls).de_json(data, bot)
data = super().de_json(data, bot)
if not data:
return None
+1 -1
View File
@@ -20,7 +20,7 @@
"""This module contains an object that represents a Telegram Message Parse Modes."""
class ParseMode(object):
class ParseMode:
"""This object represents a Telegram Message Parse Modes."""
MARKDOWN = 'Markdown'
+8 -10
View File
@@ -28,7 +28,6 @@ from cryptography.hazmat.primitives.ciphers import Cipher
from cryptography.hazmat.primitives.ciphers.algorithms import AES
from cryptography.hazmat.primitives.ciphers.modes import CBC
from cryptography.hazmat.primitives.hashes import SHA512, SHA256, Hash, SHA1
from future.utils import bord
from telegram import TelegramObject, TelegramError
@@ -39,8 +38,7 @@ class TelegramDecryptionError(TelegramError):
"""
def __init__(self, message):
super(TelegramDecryptionError, self).__init__("TelegramDecryptionError: "
"{}".format(message))
super().__init__("TelegramDecryptionError: {}".format(message))
def decrypt(secret, hash, data):
@@ -83,7 +81,7 @@ def decrypt(secret, hash, data):
# Raise a error that is caught inside telegram.PassportData and transformed into a warning
raise TelegramDecryptionError("Hashes are not equal! {} != {}".format(data_hash, hash))
# Return data without padding
return data[bord(data[0]):]
return data[data[0]:]
def decrypt_json(secret, hash, data):
@@ -134,7 +132,7 @@ class EncryptedCredentials(TelegramObject):
if not data:
return None
data = super(EncryptedCredentials, cls).de_json(data, bot)
data = super().de_json(data, bot)
return cls(bot=bot, **data)
@@ -347,7 +345,7 @@ class SecureValue(TelegramObject):
return cls(bot=bot, **data)
def to_dict(self):
data = super(SecureValue, self).to_dict()
data = super().to_dict()
data['files'] = [p.to_dict() for p in self.files]
data['translation'] = [p.to_dict() for p in self.translation]
@@ -402,10 +400,10 @@ class DataCredentials(_CredentialsBase):
"""
def __init__(self, data_hash, secret, **kwargs):
super(DataCredentials, self).__init__(data_hash, secret, **kwargs)
super().__init__(data_hash, secret, **kwargs)
def to_dict(self):
data = super(DataCredentials, self).to_dict()
data = super().to_dict()
del data['file_hash']
del data['hash']
@@ -428,10 +426,10 @@ class FileCredentials(_CredentialsBase):
"""
def __init__(self, file_hash, secret, **kwargs):
super(FileCredentials, self).__init__(file_hash, secret, **kwargs)
super().__init__(file_hash, secret, **kwargs)
def to_dict(self):
data = super(FileCredentials, self).to_dict()
data = super().to_dict()
del data['data_hash']
del data['hash']
@@ -19,8 +19,8 @@
"""This module contains an object that represents a Telegram EncryptedPassportElement."""
from base64 import b64decode
from telegram import (TelegramObject, PassportFile, PersonalDetails, IdDocumentData,
ResidentialAddress)
from telegram import (IdDocumentData, PassportFile, PersonalDetails,
ResidentialAddress, TelegramObject)
from telegram.passport.credentials import decrypt_json
@@ -138,7 +138,7 @@ class EncryptedPassportElement(TelegramObject):
if not data:
return None
data = super(EncryptedPassportElement, cls).de_json(data, bot)
data = super().de_json(data, bot)
data['files'] = PassportFile.de_list(data.get('files'), bot) or None
data['front_side'] = PassportFile.de_json(data.get('front_side'), bot)
@@ -153,7 +153,7 @@ class EncryptedPassportElement(TelegramObject):
if not data:
return None
data = super(EncryptedPassportElement, cls).de_json(data, bot)
data = super().de_json(data, bot)
if data['type'] not in ('phone_number', 'email'):
secure_data = getattr(credentials.secure_data, data['type'])
@@ -197,7 +197,7 @@ class EncryptedPassportElement(TelegramObject):
return encrypted_passport_elements
def to_dict(self):
data = super(EncryptedPassportElement, self).to_dict()
data = super().to_dict()
if self.files:
data['files'] = [p.to_dict() for p in self.files]
+2 -2
View File
@@ -58,7 +58,7 @@ class PassportData(TelegramObject):
if not data:
return None
data = super(PassportData, cls).de_json(data, bot)
data = super().de_json(data, bot)
data['data'] = EncryptedPassportElement.de_list(data.get('data'), bot)
data['credentials'] = EncryptedCredentials.de_json(data.get('credentials'), bot)
@@ -66,7 +66,7 @@ class PassportData(TelegramObject):
return cls(bot=bot, **data)
def to_dict(self):
data = super(PassportData, self).to_dict()
data = super().to_dict()
data['data'] = [e.to_dict() for e in self.data]
+9 -11
View File
@@ -76,7 +76,7 @@ class PassportElementErrorDataField(PassportElementError):
message,
**kwargs):
# Required
super(PassportElementErrorDataField, self).__init__('data', type, message)
super().__init__('data', type, message)
self.field_name = field_name
self.data_hash = data_hash
@@ -111,7 +111,7 @@ class PassportElementErrorFile(PassportElementError):
message,
**kwargs):
# Required
super(PassportElementErrorFile, self).__init__('file', type, message)
super().__init__('file', type, message)
self.file_hash = file_hash
self._id_attrs = (self.source, self.type, self.file_hash, self.message)
@@ -145,7 +145,7 @@ class PassportElementErrorFiles(PassportElementError):
message,
**kwargs):
# Required
super(PassportElementErrorFiles, self).__init__('files', type, message)
super().__init__('files', type, message)
self.file_hashes = file_hashes
self._id_attrs = ((self.source, self.type, self.message)
@@ -180,7 +180,7 @@ class PassportElementErrorFrontSide(PassportElementError):
message,
**kwargs):
# Required
super(PassportElementErrorFrontSide, self).__init__('front_side', type, message)
super().__init__('front_side', type, message)
self.file_hash = file_hash
self._id_attrs = (self.source, self.type, self.file_hash, self.message)
@@ -214,7 +214,7 @@ class PassportElementErrorReverseSide(PassportElementError):
message,
**kwargs):
# Required
super(PassportElementErrorReverseSide, self).__init__('reverse_side', type, message)
super().__init__('reverse_side', type, message)
self.file_hash = file_hash
self._id_attrs = (self.source, self.type, self.file_hash, self.message)
@@ -246,7 +246,7 @@ class PassportElementErrorSelfie(PassportElementError):
message,
**kwargs):
# Required
super(PassportElementErrorSelfie, self).__init__('selfie', type, message)
super().__init__('selfie', type, message)
self.file_hash = file_hash
self._id_attrs = (self.source, self.type, self.file_hash, self.message)
@@ -282,8 +282,7 @@ class PassportElementErrorTranslationFile(PassportElementError):
message,
**kwargs):
# Required
super(PassportElementErrorTranslationFile, self).__init__('translation_file',
type, message)
super().__init__('translation_file', type, message)
self.file_hash = file_hash
self._id_attrs = (self.source, self.type, self.file_hash, self.message)
@@ -319,8 +318,7 @@ class PassportElementErrorTranslationFiles(PassportElementError):
message,
**kwargs):
# Required
super(PassportElementErrorTranslationFiles, self).__init__('translation_files',
type, message)
super().__init__('translation_files', type, message)
self.file_hashes = file_hashes
self._id_attrs = ((self.source, self.type, self.message)
@@ -351,7 +349,7 @@ class PassportElementErrorUnspecified(PassportElementError):
message,
**kwargs):
# Required
super(PassportElementErrorUnspecified, self).__init__('unspecified', type, message)
super().__init__('unspecified', type, message)
self.element_hash = element_hash
self._id_attrs = (self.source, self.type, self.element_hash, self.message)
+2 -2
View File
@@ -71,7 +71,7 @@ class PassportFile(TelegramObject):
if not data:
return None
data = super(PassportFile, cls).de_json(data, bot)
data = super().de_json(data, bot)
return cls(bot=bot, **data)
@@ -80,7 +80,7 @@ class PassportFile(TelegramObject):
if not data:
return None
data = super(PassportFile, cls).de_json(data, bot)
data = super().de_json(data, bot)
data['credentials'] = credentials
+1 -1
View File
@@ -50,7 +50,7 @@ class OrderInfo(TelegramObject):
if not data:
return cls()
data = super(OrderInfo, cls).de_json(data, bot)
data = super().de_json(data, bot)
data['shipping_address'] = ShippingAddress.de_json(data.get('shipping_address'), bot)
+1 -1
View File
@@ -82,7 +82,7 @@ class PreCheckoutQuery(TelegramObject):
if not data:
return None
data = super(PreCheckoutQuery, cls).de_json(data, bot)
data = super().de_json(data, bot)
data['from_user'] = User.de_json(data.pop('from'), bot)
data['order_info'] = OrderInfo.de_json(data.get('order_info'), bot)
+1 -1
View File
@@ -45,7 +45,7 @@ class ShippingOption(TelegramObject):
self._id_attrs = (self.id,)
def to_dict(self):
data = super(ShippingOption, self).to_dict()
data = super().to_dict()
data['prices'] = [p.to_dict() for p in self.prices]
+1 -1
View File
@@ -59,7 +59,7 @@ class ShippingQuery(TelegramObject):
if not data:
return None
data = super(ShippingQuery, cls).de_json(data, bot)
data = super().de_json(data, bot)
data['from_user'] = User.de_json(data.pop('from'), bot)
data['shipping_address'] = ShippingAddress.de_json(data.get('shipping_address'), bot)
+1 -1
View File
@@ -74,7 +74,7 @@ class SuccessfulPayment(TelegramObject):
if not data:
return None
data = super(SuccessfulPayment, cls).de_json(data, bot)
data = super().de_json(data, bot)
data['order_info'] = OrderInfo.de_json(data.get('order_info'), bot)
return cls(**data)

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