Compare commits

...

259 Commits

Author SHA1 Message Date
Hinrich Mahler 38a33581b1 Bump version to v12.6 2020-04-10 23:52:08 +02:00
Hinrich Mahler fe821c08e6 Doc Fixes 2020-04-10 23:43:58 +02:00
Harshil 0a9f4bfbdd Doc fixes (#1884)
* Bot.py doc fixes

All docs obtained from official Bot API docs

* made flake8 happy

* address review

Also improved consistency of `returns:` in docs

Co-authored-by: Hinrich Mahler <hinrich.mahler@freenet.de>
2020-04-10 20:05:01 +02:00
Bibo-Joshi c4364c7166 GitHub Actions: Use checkout@v2 (#1887) 2020-04-10 19:57:52 +02:00
Bibo-Joshi d63e710784 API 4.7 (#1858)
* Pure API changes

* Address review

* set Bot.commands on successfull call of set_my_commands

* Get started on tests

* More tests!

* More Coverage!

* Reset changes in utils.request

* Filters.dice, Filters.dice.text

* more coverage

* Address review

* Address review

* Test stop_poll with reply_markup

* Test stop_poll also without reply_markup

* Rephrase note on 'dice'

* Fix grammar in note on Filters.dice

* update api version readme

* address review
2020-04-10 19:22:45 +02:00
Bibo-Joshi f379f54d5a Tweak persistence handling (#1827)
* Unify persistence updates in dispatcher

* Ensure user/chat_data is not None when updating it

* Update persistence after job runs

* Increase coverage
2020-04-10 13:23:13 +02:00
Bibo-Joshi bdf0cb91f3 Pass last valid context to TIMEOUT handlers (#1826) 2020-04-10 13:18:43 +02:00
Bibo-Joshi 3101ea8432 Favor concrete types over "Iterable" (#1882)
* Use concrete types instead of 'iterable'

* Fix overlooked docstring

* address review
2020-04-08 22:49:01 +02:00
Harshil beb8ba3db0 Doc Fixes (#1874)
* doc fixes

* Update AUTHORS.rst

* More doc fixes

All docs were obtained from official Bot API docs.

* Shortened line length

Did this so it passes codacy check

* Revert id docstring changes

* typo

Co-authored-by: Hinrich Mahler <hinrich.mahler@freenet.de>
2020-04-07 17:25:17 +02:00
Bibo-Joshi f0b1aeb6fd Customize issue template chooser (#1880)
* Customize issue template chooser

* Improve wording
2020-04-07 15:45:17 +02:00
Bibo-Joshi d65558888e Add note on UTC to run_{repeating, once} (#1854) 2020-03-31 00:05:08 +02:00
Bibo-Joshi 61a66a32c8 Add tests for empty string as switch_inline_query(_current_chat) (#1635) 2020-03-31 00:03:45 +02:00
Hinrich Mahler 392d4e1a9c Bump version to v12.5.1 2020-03-30 18:25:53 +02:00
Andrej730 9cb34af65a Fix UTC as default tzinfo for Jobs (#1696)
1. Made sure that default tzinfo in JobQueue is UTC #1693.
2. Added test that checks that all methods by default set job.tzinfo as UTC.
2020-03-30 18:10:27 +02:00
Bibo-Joshi e9cb6675ca PrefixHandlers command and prefix editable (#1636)
* Rename internal list of PrefixHandler

* Make PFH.prefix and .command setable attributes

* Improve coverage
2020-03-30 17:49:50 +02:00
Bibo-Joshi 982f6707e1 Make ConversationHandler attributes immutable (#1756)
* Make ConversationHandler attributes immutable

* Add forgotten name property to test_immutable
2020-03-30 17:37:37 +02:00
Rys Artem d55d981e22 Reorder tests to make them more stable (#1835) 2020-03-30 17:06:24 +02:00
Iulian Onofrei f20953f7a9 Fix docs wording (#1855) 2020-03-30 00:32:06 +03:00
Bibo-Joshi e18220be10 Add docs for PollHandler and PollAnswerHandler (#1853) 2020-03-29 11:24:44 +02:00
Hinrich Mahler 90729c21d7 Bump version to v12.5 2020-03-29 10:01:11 +02:00
Poolitzer 55e3ecf9f8 API 4.6 (#1723)
* First take on 4.6 support

* improved docs

* Minor doc formattings

* added poll and poll_answer to filters

* added tests, fixed mentioned issues

* added poll_answer + poll filter tests

* Update docs according to official API docs

* introducing pollhandler and pollanswerhandler

* First take on 4.6 support

* improved docs

* Minor doc formattings

* added poll and poll_answer to filters

* added tests, fixed mentioned issues

* added poll_answer + poll filter tests

* Update docs according to official API docs

* introducing pollhandler and pollanswerhandler

* correct_option_id validated with None

when trying to send a poll with correct option id 0 it was failing. Now None check is done so that even when 0 is passed it is assigned.

* improving example

* improving code

* adding poll filter example to the pollbot.py

* Update Readme

* simplify pollbot.py and add some comments

* add tests for Poll(Answer)Handler

* We just want Filters.poll, not Filters.update.poll

* Make test_official fail again

* Handle ME.language in M._parse_*

Co-authored-by: Hinrich Mahler <hinrich.mahler@freenet.de>
Co-authored-by: Sharun Kumar <715417+sharunkumar@users.noreply.github.com>
2020-03-29 09:52:30 +02:00
Bibo-Joshi 8d2c7af1f3 API 4.5 (#1508)
* Allow for nested MessageEntities in Message._parse_markdown/html, adjust tests

* remove testing relict

* Use MessageEntitys new equality check (#1465)

* Remove unused variable

* Update to custom_title feature and slow_mode_delay option

Changes:

 - custom_title for ChatMember
 - new method setChatAdministratorCustomTitle for Bot
 - new slow_mode_delay for Chat

Update due to new API future `custom_title` from API 4.5 (https://core.telegram.org/bots/api#december-31-2019)

* Minor typo fix

* Comply with Flake8

* Add new MessageEntities and MarkdownV2

* Added file_unique_id attrs from API 4.5 and updated tests for it

* Fixed test and checked using flake8

* Fixed ChatPhoto documentation

* Fix Flake8

* Add setChatAdminCstmTitle to Bot

* Rename MDV2 methods

* Change files id attrs to unique id

* correct id_attrs for chat_photo

* Revert "temporarily skip tests failing b/c missing api 4.5 (#1738)"

This reverts commit 7cde6ca268.

* Fix text_markdown_v2 for monospace and text_links

* closing remarks from pieter

* Minor fix in escape_markdown, improve tests for it

* Fix offset bug in Message._parse_*

* Add test_chatphoto.py

* remove debug print from test_message.py

* try making codecov happy

* Update readme

* all hail codecov

* Improve Link handling for MarkdownV1 and adjust tests. Closes #1654

* Dont use beginning of pre-entity as language in _parse_markdown

* Remove debug print

* Dummy commit to try fix codecov

Co-authored-by: Hoi Dmytro <dmytro.hoi@gmail.com>
Co-authored-by: Dmytro Hoi <code@dmytrohoi.com>
Co-authored-by: poolitzer <25934244+poolitzer@users.noreply.github.com>
2020-03-28 16:37:26 +01:00
Pietu R e86ae25a62 Update CallbackQuery docstrings (#1818)
* mark chat_instance as required

* change ordering and add bot
2020-03-28 15:52:37 +01:00
Bibo-Joshi 2d3357bfeb Ignore Message.default_quote in test_official (#1848) 2020-03-28 14:32:16 +01:00
Bibo-Joshi b6f4783fd3 Revert accitendtal change in vendored urllib3 (#1775) 2020-03-28 12:15:51 +01:00
Bibo-Joshi f94ea9acbb Answer CQs and use edit_message_text in examples (#1721) 2020-03-28 12:07:23 +01:00
Aleksey 157652cfdf Fixe typo in edit_message_media (#1779) 2020-03-28 12:01:06 +01:00
Bibo-Joshi 104d0127aa Doc Fixes (#1778)
* Update docs according to official docs

* Add note to Updater according to #1772

* Add note on ChatPermissions

* Fix rendering for arg type of conversation_timeout
2020-03-28 11:49:47 +01:00
Bibo-Joshi e0b22e60b4 Update label in question template (#1840) 2020-03-20 23:19:32 +02:00
Bibo-Joshi 16613d7ce0 Don't comment when labeling issue as stale (#1829) 2020-03-12 10:14:00 +02:00
Bibo-Joshi eac7f02211 Add Py3.8 to docs (#1824) 2020-03-11 23:36:34 +02:00
Ak4zh 28ded6718e Add link property to Bot (#1770)
* added link property to bot

link property was available in User and Chat objects but not in Bot which was inconsistent.

* added 'link' property to Bot object

Bot will always have username so it does not require hasattr check

* add tests

Co-authored-by: Hinrich Mahler <hinrich.mahler@freenet.de>
2020-03-09 23:17:05 +02:00
Noam Meltzer 13a641b3d7 Python 3.8 support (#1614)
* github workflow: Add python-3.8

* workaround for tornado issue on win with py>=3.8

* add workaround to webhookhandler

* Try making codecov and codacy happy

* Fix stupid mistake

Co-authored-by: Hinrich Mahler <hinrich.mahler@freenet.de>
2020-03-09 23:13:16 +02:00
Bibo-Joshi 27cccc7734 Add stale bot (#1820)
* add stale.yml for stale bot

* move stale.yml to the right directory
2020-03-08 23:40:22 +02:00
Noam Meltzer f7ec7a7c4c Use temporary directories in test_persistence (#1808) 2020-03-04 23:58:19 +02:00
Bibo-Joshi 8d6970ab02 Remove references to travis and appveyor (#1791) 2020-02-23 23:04:56 +02:00
Bibo-Joshi 1dc67dcbda Remove builtin names where possible (#1792) 2020-02-23 23:03:58 +02:00
Poolitzer 14f712b3c4 Update pre-commit config file (#1787) 2020-02-21 07:43:48 +02:00
Poolitzer 0fb0fbb93f Remove legacy CI files (#1783) 2020-02-20 17:33:03 +02:00
Pieter Schutz 72ecc696cb Bump to v12.4.2 2020-02-10 11:39:26 +01:00
Bibo-Joshi a447760411 Make sure PP can read files that dont have bot_data (#1760)
* Make sure PP can read files that dont have bot_data

* Improve workaround
2020-02-08 19:24:35 +02:00
Bibo-Joshi 6da529c46b Pass correct parse_mode to InlineResults if bot.defaults is None (#1763)
* Pass correct parse_mode to InlineResults if bot.defaults is None

* Add tests for inlinequeryresults with (default)parse_mode

* enhance tests
2020-02-08 18:52:22 +02:00
Noam Meltzer bd1b2fb6c1 Bump version to v12.4.1 2020-02-08 15:06:05 +02:00
Bibo-Joshi f9a8cd924c Make Filters.command only accept MessageEntity commands (#1744)
* Make Filters.command only accept MessageEntitie commands

* Add option to filters.command to allow cmds anywhere in the message

* Make codecov happy, also retroactive for #1631
2020-02-08 14:54:21 +02:00
Bibo-Joshi 7b3b278c7c Bump version to v12.4 2020-02-08 10:01:41 +02:00
Bibo-Joshi cf3635d408 make test_webhook_invalid_posts flaky (#1758) 2020-02-07 18:32:38 +02:00
Bibo-Joshi 84cfc6f7fa Rename Test suite … (#1750)
* Rename Test suite

* Actually change the badge in readme

* fix download without path arguments (#1591)

* fix download without path arguments

* fix download without path arguments

* solved downloading a file without file_path or custom_path

* if no file_path, download as file_id

* Add test case

* Elaborate doc string

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

* Add default values (#1490)

* added parse_mode parameter in Updater and in Bot class to set a default parse mode for bot

* DefaultValue

* Add default parse_mode everywhere

* Renome to default_parse_mode

* Test def parse_mode for send_*, edit_message_*

* Remove duplicate code in Input* classes

* Improve handling of def_p_m for Bot methods

* Remove unneeded deletion of kwargs

* Make @log preserve signature, add bots with defaults to tests

* Fix Codacy

* Fix Travis Error

* Add default_disable_notification

* Add default_disable_web_page_preview

* Add default_disable_timeout

* add default_disable_web_page_preview for InputTextMessageContent

* add default_quote

* Try fixing test_pin_and_unpin

* Just run 3.5 tests for paranoia

* add tests for Defaults

* Revert "Just run 3.5 tests for paranoia"

This reverts commit 1baa91a3a1.

* Tidy up parameters, move Defaults to ext

* Stage new files, because im an idiot

* remove debug prints

* change equality checks for DEFAULT_NONE

* Some last changes

* fix S&R error so that tests actually run

Co-authored-by: Ak4zh <agwl.akash@gmail.com>
Co-authored-by: Eldinnie <Eldinnie@users.noreply.github.com>

* Skip test relying on ordered dicts for 3.5 (#1752)

* Rename Test suite

* Actually change the badge in readme

Co-authored-by: Gabriel Simonetto <42247511+GabrielSimonetto@users.noreply.github.com>
Co-authored-by: Ak4zh <agwl.akash@gmail.com>
Co-authored-by: Eldinnie <Eldinnie@users.noreply.github.com>
2020-02-06 11:38:21 +01:00
Bibo-Joshi bf06fa2c18 Skip test relying on ordered dicts for 3.5 (#1752) 2020-02-06 11:28:27 +01:00
Bibo-Joshi 960c7a0c70 Add default values (#1490)
* added parse_mode parameter in Updater and in Bot class to set a default parse mode for bot

* DefaultValue

* Add default parse_mode everywhere

* Renome to default_parse_mode

* Test def parse_mode for send_*, edit_message_*

* Remove duplicate code in Input* classes

* Improve handling of def_p_m for Bot methods

* Remove unneeded deletion of kwargs

* Make @log preserve signature, add bots with defaults to tests

* Fix Codacy

* Fix Travis Error

* Add default_disable_notification

* Add default_disable_web_page_preview

* Add default_disable_timeout

* add default_disable_web_page_preview for InputTextMessageContent

* add default_quote

* Try fixing test_pin_and_unpin

* Just run 3.5 tests for paranoia

* add tests for Defaults

* Revert "Just run 3.5 tests for paranoia"

This reverts commit 1baa91a3a1.

* Tidy up parameters, move Defaults to ext

* Stage new files, because im an idiot

* remove debug prints

* change equality checks for DEFAULT_NONE

* Some last changes

* fix S&R error so that tests actually run

Co-authored-by: Ak4zh <agwl.akash@gmail.com>
Co-authored-by: Eldinnie <Eldinnie@users.noreply.github.com>
2020-02-06 11:22:56 +01:00
Gabriel Simonetto bacabbe767 fix download without path arguments (#1591)
* fix download without path arguments

* fix download without path arguments

* solved downloading a file without file_path or custom_path

* if no file_path, download as file_id

* Add test case

* Elaborate doc string

Co-authored-by: Bibo-Joshi <hinrich.mahler@freenet.de>
2020-02-06 11:21:21 +01:00
Eldinnie d8dcdeea75 fix test_pin_and_unpin_message (#1749)
Make it flaky and timeout
2020-02-02 23:39:08 +01:00
isinstance 818475bd93 Fix Server response could not be decoded using UTF-8 (#1623)
Co-authored-by: Noam Meltzer <tsnoam@gmail.com>
2020-02-03 00:12:27 +02:00
Bibo-Joshi f97ac90af7 skip test_json on TestDictPersistence on py3.5 (#1748) 2020-02-03 00:10:48 +02:00
billchenchina 90eeb40ae8 Add base_file_url support for telegram.ext.Updater (#1679)
Co-authored-by: Noam Meltzer <tsnoam@gmail.com>
2020-02-02 23:47:02 +02:00
Bibo-Joshi 6d9d11b8bd Handler persistence for nested ConversationHandlers (#1711)
* Handler persistence for nested ConversationHandlers

* Add tests for persistence w/ nested CHs
2020-02-02 22:31:56 +01:00
Bibo-Joshi f6b663f175 bot_data as global memory (V12 version of #1322) (#1325)
* Update AUTHORS.rst

* Update AUTHORS.rst

* Add bot_data to CallbackContext as global memory

* Minor fixes in docstrings

* Incorp. req. changes, Flake8 Fixes

* Persist before stop

* Fix CI errors

* Implement #1342 for bot_data

* Add check pickle_persistence_only_bot similar to #1462

* Fix test_persistence

* Try dispatching error before logging it

* Fix test

Co-authored-by: Eldinnie <Eldinnie@users.noreply.github.com>
2020-02-02 22:20:31 +01:00
Noam Meltzer 43bfebb150 Update copyright date to 2020 (#1746) 2020-02-02 23:08:54 +02:00
Jelle Besseling 743e2fce07 Add --with-upstream-urllib3 option to remove vendored version (#1725)
Co-authored-by: Noam Meltzer <tsnoam@gmail.com>
2020-02-02 22:56:25 +02:00
Shreyas Thirumalai 4c2a3d07ce Remove duplicate entries from docs. (#1739)
Fixes #1726
2020-02-02 20:35:52 +01:00
Poolitzer 423794f473 fixing message.link (#1741)
* fixing message.link

* improving if clause
2020-02-02 20:27:53 +01:00
Bibo-Joshi a397e6c3c6 Doc fixes (#1642)
* Add missing DispatcherHandlerStop to docs

* Forgot to stage new file ...

* update docstring for bot.edit_message_*

* make job callback docs context based

* Flake8

* Fix doc strings of message.reply_

* Update thumb size docs

* Add missing DispatcherHandlerStop to docs

* Forgot to stage new file ...

* update docstring for bot.edit_message_*

* make job callback docs context based

* Flake8

* Fix doc strings of message.reply_

* Update thumb size docs

* Update docs on InlineQuery.query length

* Minor doc updates

* change module to class in to_float_timestamp doc string
2020-02-02 20:20:54 +01:00
Bibo-Joshi 7cde6ca268 temporarily skip tests failing b/c missing api 4.5 (#1738) 2020-01-30 20:57:16 +02:00
Bibo-Joshi 408062dd43 Add note on how to run test_official to contrib guide (#1740) 2020-01-29 22:43:57 +02:00
Poolitzer d96d233dc5 dropping 2.7, 3.3, 3.4 support from setup.py and README.rst (#1734)
Fixes #1720
2020-01-26 23:47:52 +02:00
Bibo-Joshi 3eb2cef600 Make Filters.text accept leading slash (#1680)
Fixes #1678
2020-01-26 23:19:38 +02:00
Poolitzer 0b87f4b274 contibuting guide: warning about adding requirements (#1718) 2020-01-26 23:15:42 +02:00
Bibo-Joshi 0df526d390 jobqueue: Log datetimes correctly (minor change) (#1714) 2020-01-26 23:08:33 +02:00
Bibo-Joshi cb9af36937 Fix None check in JobQueue._put() (#1707)
fixes #1701
2020-01-26 23:07:17 +02:00
Poolitzer fbb7e0e645 No more unitests for py2.7 (#1731)
Still not removing any py2.7 specific code, but no reason to waste precious CPU and unitest time on py2.7.
2020-01-26 23:01:29 +02:00
Eana Hufwe d9d65cc2ac Add poll messages support to filters (#1673) 2020-01-26 22:57:48 +02:00
Bibo-Joshi 62f514f068 CallbackContext: Expose dispatcher as a property (#1684)
Co-authored-by: Poolitzer <25934244+Poolitzer@users.noreply.github.com>
2020-01-26 22:55:00 +02:00
David Auer 08bbeca8ec Fix typo in example text (#1703) 2020-01-26 22:35:34 +02:00
Mayur Newase 38b9f4b9bc Dispatcher.__init__: Remove double assignmed to self.job_queue (#1698)
Co-authored-by: mayur741 <mayur@wipadika.com>
2020-01-26 22:34:25 +02:00
Viktor Oreshkin 3d59b2f581 Docstring fix: thumb limits were changed with Bot API 4.2 (#1669) 2020-01-26 22:30:26 +02:00
rizlas e3c8466e41 Rename enocde_conversations_to_json() -> enocde_conversations_to_json() (#1661)
Fixes #1660
2020-01-26 22:24:00 +02:00
compSciKai 1d92f52c6a Minor documentation fix (#1647) 2020-01-26 22:19:59 +02:00
Bibo-Joshi 33280a7fe0 Add missing name for Filters.update classes (#1632) 2020-01-26 22:18:29 +02:00
Poolitzer 4b5ba15d31 both google python style links have been moved (#1624)
I choose https://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_google.html, based on https://web.archive.org/web/20160322212330/http://www.sphinx-doc.org/en/stable/ext/example_google.html
and http://google.github.io/styleguide/pyguide.html based on https://web.archive.org/web/20160304111857/https://google.github.io/styleguide/pyguide.html
2020-01-26 22:16:19 +02:00
Poolitzer d2466f1e6e CI: Fix running on master after push + official can fail (#1716)
allowing official to fail is only temporary until we'll add the latest bot api support
2020-01-26 22:07:24 +02:00
Bibo-Joshi 883c6b5901 Add dispatcher argument to Updater (#1484) 2020-01-26 21:59:47 +02:00
Noam Meltzer 1e7f4fae6f Bump version to v12.3.0 2020-01-11 21:21:46 +02:00
Noam Meltzer dae5ab47a0 CHANGES.rst: Update towards v12.3.0 release 2020-01-11 21:08:03 +02:00
Bibo-Joshi a9d9b1d750 Add #1653 to changelog 2020-01-11 20:57:58 +02:00
Hinrich Mahler 90496f70a5 Prepare for v12.3.0 2020-01-11 20:57:58 +02:00
tobiaswicker 940b42e048 ConversationHandler: Fix wrong signature call for timeout handlers
Fixes #1652

Co-authored-by: Eldinnie <Eldinnie@users.noreply.github.com>
2020-01-11 20:51:31 +02:00
Poolitzer a582515766 README.rst: replace travis and appveyor with github (#1645) 2020-01-11 20:47:21 +02:00
Eldinnie 3d42df3366 Fix documentation about return values in message (#1656)
* Fix documentation about return values in message

* flake8 satisfaction
2019-12-16 14:22:11 +01:00
Poolitzer 2c67a9833b add private /c links to message.links object (#1619)
* add private /c links to message.links object

* fixing ids for basic groups

* fixing ids for non basic chats and the test

* Improve tests for Message.link

* Simplify id_to_link
2019-11-29 13:50:44 +01:00
Bibo-Joshi 5e8a961669 Refactor msg_in (#1631)
* Revert "Add msg_in filter (new) (#1570)"

This reverts commit 34bdbc632a.

* Refactor msg_in, add Filters.caption on the fly

* Update docstrings

* Fix copy-paste typo
2019-11-29 13:09:44 +01:00
Bibo-Joshi a5ba64becb Remove checks for None on assignement for opt args (#1600) 2019-11-23 18:05:03 +02:00
Noam Meltzer 2a3169a22f Fix deprecation warning with Python 3.8 triggered by vendored urllib3 (#1618)
Fixes #1586
2019-11-16 14:37:15 +02:00
Poolitzer 894d8281ab github workflow: add a cron job (#1615) 2019-11-16 00:19:47 +02:00
Noam Meltzer 2fdf48023b github workflow: give pre-commit its own job (#1612) 2019-11-15 23:35:44 +02:00
Paolo Lammens 4e717a172b Fix UTC/local inconsistencies for naive datetimes (#1506) 2019-11-15 22:51:22 +02:00
Noam Meltzer 10c9ec2313 workflow: run test-official in its own job (#1607) 2019-11-09 22:38:23 +02:00
Noam Meltzer 096a7c3593 Allow PRs to test (#1606) 2019-11-09 22:13:02 +02:00
Noam Meltzer e9d9f01bd4 Fix windows actions (#1605)
* Stop testing with ujson

* Fix timing issue with test_delete_message

* ignore pip deprecation warning. hopefully it will fix windows+py2.7

* telegram replies to deleteing old messages are not consistent
2019-11-09 20:33:51 +02:00
Jasmin Bom 8b4b22cc89 Implement Github Actions CI (#1556)
* Add test workflow

* Attempt github grouping

* Improve bot info fetching

- Add support for b64+json encoded github secret with all the vars
- Add bot_name and bot_username since it's needed for a proper get_me test

* Improve test workflow a lot

- Add coverage
- Install ujson
- test_official only run on in single job
- Pass bot info to pytest

* Improve github grouping by having shorter titles

* Run pytest with coverage

* Improve coverage report

* Proper exitcode behaviour for pytest

* Proper test official handling

* Proper error handling

* Skip jobqueue tests on windows

* run coverage tests even if nocoverage ones fail

* Skip messagequeue tests on windows

* Clean up to satisfy flake8

* Run meta tests
2019-10-27 14:28:33 +01:00
Bibo-Joshi b294c92bad question template: Add info about users' group (#1577) 2019-10-27 14:33:30 +02:00
Poolitzer 264de2b7c1 Github actions - notify maintainers about changed examples (#1555) 2019-10-27 01:42:47 +03:00
Julian Ste ac64027580 Fixed comments in examples (#1566) 2019-10-27 01:15:09 +03:00
Bibo-Joshi 34bdbc632a Add msg_in filter (new) (#1570)
Closes #1144
2019-10-27 01:12:54 +03:00
Bibo-Joshi bbcff96804 Doc fixes (#1572)
Fixes #1576
2019-10-27 01:04:48 +03:00
Bibo-Joshi 93449443b2 Add *args, **kwargs to Message.forward() (#1574) 2019-10-27 01:00:27 +03:00
Poolitzer 8cdb20a85a updating example to V12 (#1579) 2019-10-27 00:51:29 +03:00
Jannes Höke 6fddb49af5 📝 Update section "Getting help" 2019-10-22 00:04:31 +02:00
Jannes Höke b0aef0c718 🔀 Update issue templates (#1569)
* 📝 Update issue templates

* 👌 Update question template

* 🔥 Delete old issue template
2019-10-21 23:51:58 +02:00
Iulian Onofrei 88eccc6608 Add MAX_PHOTOSIZE_UPLOAD size limit constant (#1560)
* Add MAX_PHOTOSIZE_UPLOAD size limit constant

* Add the other source of the constants' values
2019-10-17 22:24:44 +02:00
Lorenzo Rossi 3d8771bbdf 🔀 Add mutex protection on ConversationHandler (#1533)
* Add mutex protection on ConversationHandler

* Remove timeout job before child update

* Make locks private

* Add conversation timeout conflict test
2019-10-17 00:03:53 +02:00
Iulian Onofrei 7152b5aaf9 Fix CONTRIBUTING.rst not to install requirements as root (#1558) 2019-10-16 21:50:38 +03:00
Noam Meltzer 98147fce32 Bump version to v12.2.0 2019-10-14 21:12:13 +03:00
Poolitzer e54e9f2347 moving flake config to one file (#1546) 2019-10-14 11:10:51 +03:00
Poolitzer 3545139dd7 adding to_dict test (#1544)
fixes #1541
2019-10-12 23:40:42 +03:00
Poolitzer d0c27e2d46 adding 3.8 to travis and appveyor (#1543)
Fixes #1542
2019-10-12 16:12:41 +03:00
Paolo Lammens 3318239cf6 tests: refactor `test_commandhandler.py` (#1408)
- Improved usage of fixtures
    - Replaced fixtures for directly callable factories where
    multiple mock objects were needed in the same test function
    - Extracted fixtures where possible (in place of literals or
    global constants)
  - Moved some fixtures to ``conftest.py`` to be used by other
  modules
  - Made a common base class for both ``TestCommandHandler`` and
  ``TestPrefixHandler``, extracting common methods, patterns and
  signatures
    - The extracted patterns in test methods have been named with
    leading ``_test``
  - Extracted other repeatedly used test utilities into functions
  (e.g. ``is_match``) and methods (e.g. ``make_default_handler``)
2019-10-12 16:11:09 +03:00
Bibo-Joshi aadb6df271 Nested ConversationHandlers (#1512)
Fixes #405
2019-10-11 22:59:36 +03:00
Noah Evans 2cc9aac7dc Fix Bot.to_dict to use proper first_name (#1525)
Fixes #1519
2019-10-11 22:37:28 +03:00
Trainer Jono 1d007b1b60 Fix typos in examples (#1537) 2019-10-11 21:10:21 +03:00
Noam Meltzer 3257148d13 travis.yaml: TEST_OFFICIAL removed from allowed_failures
it was originally added to allowed_failures until we completed API 4.4
support
2019-10-11 21:02:32 +03:00
Poolitzer 805a798b50 Fix CI failures due to non-backward compat attrs depndency (#1540) 2019-10-11 21:01:25 +03:00
Noam Meltzer e60a42010b Bump version to v12.1.1 2019-09-17 11:30:52 +03:00
Noam Meltzer ae88129f0f Revert accidental change to the git revision of the urllib3 submodule (#1517)
Fixes #1516
2019-09-17 11:25:54 +03:00
Noam Meltzer 3812251dac small fix for documentation to compile 2019-09-13 22:31:02 +03:00
Noam Meltzer e1193425ca Bump version to v12.1.0 2019-09-13 22:29:06 +03:00
Joscha Götzer ccf5e6c692 Implemented Tools for deep linking (#1049) 2019-09-13 22:09:05 +03:00
zeshuaro 32dd415fb8 Add instance methods to Animation and ChatPhoto (#1489)
get_file, get_small_file, get_big_file
2019-09-13 22:07:56 +03:00
Bibo-Joshi f13aeaa2a1 Doc fixes (#1499) 2019-09-13 21:49:04 +03:00
Bibo-Joshi 4cd07361d1 Inline Keyboard example (#1502)
Supersedes. aka. fixes #884
2019-09-13 21:43:03 +03:00
Paolo Lammens b38a1840b2 docs: Add info on documentation to CONTRIBUTING.rst (#1500)
I didn't find this information elsewhere in the repository;
I thought it might be useful for new contributors.
2019-09-09 10:01:04 +02:00
Bibo-Joshi fba3cc90d9 API 4.4 small code cleanup (#1510) 2019-09-07 12:42:26 +03:00
Bibo-Joshi 965ad17af8 API 4.4 (#1464) 2019-09-06 22:41:43 +03:00
Poolitzer d5399de99b raise attribute errors when someone tries to assign values to chat/us… (#1495)
* raise attribute errors when someone tries to assign values to chat/user_data (closes #1402)

* fix test

* something something not switching entirely and using a messy patch and no not anymore and argh
2019-09-05 22:48:28 +02:00
Bibo-Joshi 280306d1e9 Remove note about V12b in examples (#1503) 2019-09-05 13:51:26 +02:00
Noam Meltzer c84e21d8eb Bump version to v<VERSION> 2019-08-29 21:33:51 +03:00
Noam Meltzer 738e5a0784 V12 changelog (#1480)
* python3.4 is no longer supported

* Prepare CHANGES.RST & README.rst for v12.0.0 release

* CHANGES.rst: small fix

* Add Bibo-Joshi to Credits

* update the changelog - in anticipation for release

* remove line about api 4.4

* fix spelling

* move too long descriptions to wiki
2019-08-29 19:30:29 +02:00
Poolitzer ec6dc7fa10 fix typo in PicklePersistence (#1488)
* fix single typo

* fix unitests for picle singe_file -> single_file
2019-08-29 19:09:38 +02:00
Noam Meltzer b71196dad3 Run test_official in a seperate travis job (#1491) 2019-08-28 22:58:58 +03:00
Bibo-Joshi 425912da4a Use UTC in from_timestamp (breaking change) (#1485) 2019-08-28 22:06:41 +03:00
Poolitzer 2c92c356b8 Error handlers now handle all errors (#1483)
* python3.4 is no longer supported

* Prepare CHANGES.RST & README.rst for v12.0.0 release

* CHANGES.rst: small fix

* Add Bibo-Joshi to Credits

* improving error_handler

* fixing affected tests
2019-08-27 09:09:02 +02:00
Noam Meltzer f379a34ccd Run test_official only on py3.7 2019-08-24 01:13:40 +03:00
Noam Meltzer 4328eaefb2 Fix test_photo.py (#1479)
Telegram had changed their server behaviour again
2019-08-24 01:10:30 +03:00
Noam Meltzer 79dc6edf25 Merge remote-tracking branch 'origin/V12' 2019-08-24 01:04:10 +03:00
Bibo-Joshi c7e9281068 Make MessageEntity objects comparable (#1465) 2019-08-24 00:54:04 +03:00
Eldinnie edad6e8b53 Add a prefix to threads (#1358)
* Add a prefix to worker thread

This adds a prefix of `Bot:<id>:worker:` to the name of the worker threads.
Fixes #1332

* Also prefix other threads

* Fix test

* Fix test and remove helper method.
2019-08-23 22:57:08 +02:00
zeshuaro 7eb7c30741 updated inputmedia docstring (#1436) 2019-08-23 22:03:43 +02:00
Eldinnie 3ae14dda80 Remove deprecated regexhandler from examples (#1426) 2019-08-23 22:32:12 +03:00
Eldinnie ac60d057a5 All api 4.2 and 4.3 changes (#1418) 2019-08-23 22:20:41 +03:00
Marchello00 e492d5b97b Fix send_location() - latitude may be 0 (#1437)
Fixes #1435
2019-08-23 22:13:29 +03:00
René Filip 3afb0ae6c3 Improve timer_bot.py example (#1440)
Fixes #1439
2019-08-23 22:09:46 +03:00
Ales Dokshanin 179cf14bd8 #1410 Call task_done() on update queue after update processing finished (#1428) 2019-08-23 21:54:07 +03:00
Bibo-Joshi a1eabc0cae Fix for #1452 (#1462)
Dispatcher force updating persistence object's chat data attribute
2019-08-23 21:52:14 +03:00
Bibo-Joshi 5e90231f4e Make updates persist even on DispatcherHandlerStop (#1463) 2019-08-23 21:38:29 +03:00
Zhuoyun Wei 2cb6377dab examples/conversationbot2.py: Use context.error in error() (#1474) 2019-08-23 18:24:19 +03:00
Bibo-Joshi 316d046628 Indent return statement in parse*entities (#1473) 2019-08-23 18:20:07 +03:00
Marco Marinello 5e0e4c01ff Prepare debian packetization (#1476) 2019-08-23 17:47:00 +03:00
Bibo-Joshi 24546bda67 Make all categories lower case to avoid KeyErrors (#1475) 2019-08-18 18:39:29 +03:00
Pieter Schutz d70577b9cf remove 3.4 from Appveyor 2019-06-05 22:52:26 +02:00
Pieter Schutz 3fc57479f3 Merge branch '4.3' into RC1 2019-06-05 22:19:48 +02:00
Pieter Schutz e11efa2e5b Add bot api 4.3
Add LoginUrl to InlineKeyboardButton

Add reply_markup to message
2019-06-05 22:15:32 +02:00
Pieter Schutz d84551134b Merge branch 'api4.2-poll' into RC1 2019-06-05 22:11:44 +02:00
Pieter Schutz cebd2d6a86 test poll de_json 2019-06-05 22:11:28 +02:00
Pieter Schutz 725c21b88d API 4.2 changes
actually works now

Add tests

Fix flake8 issues.

Add poll argument to Update.

Fix pre-commit config
Finalizing 4.2

No notify on pin
2019-06-05 15:00:07 +02:00
Noam Meltzer 9d005d5124 jobqueue: Add docstring note about intervals and DST (#1397)
Fixes #1381
2019-04-27 20:10:56 +02:00
Jason Rhinelander 2cde878d1e Fix webhook listen (#1383)
The `listen` argument wasn't being passed through to Tornado; this fixes
it.

Fixes #1382
2019-04-15 10:28:41 +03:00
Jasmin Bom 984bea16d1 Fix trailing whitespace (flake8 error) 2019-04-05 20:03:44 +02:00
Jasmin Bom 474ff8ae41 Add missing message.text check in PrefixHandler check_update (#1375)
* Add missing message.text check in PrefixHandler check_update

* Remove message length check
2019-04-05 12:59:50 +02:00
Loo Zheng Yuan 2ed4cbd26d Fix #1366: _trigger_timeout() missing 1 required positional argument: 'job' (#1367)
* Fix #1366: _trigger_timeout() missing 1 required positional argument: 'job'

* Add comments
2019-04-05 12:59:32 +02:00
Jasmin Bom 7944805627 Fix bot tests
- telegram how throws an error if you try to reply to nonexistent msg
- telegram also changed the message when you try to delete old msg
2019-04-01 13:05:45 +02:00
Jasmin Bom b5891a6a61 Making merged filters short-circuit (#1350)
* Making merged filters short-circuit

* Add notes to docs about short-circuiting
2019-03-14 09:03:21 +01:00
Jasmin Bom df813c46e1 Pin pre-commit pylint to v2.3.0
Fixes issue seen at https://travis-ci.org/python-telegram-bot/python-telegram-bot/jobs/499853384#L702
2019-02-28 22:02:08 +01:00
Eldinnie 36a74da4f4 fix setting bot on ShippingQuery (#1355)
* fix setting bot on ShippingQuery

* Fix test to test de_json
2019-02-23 21:33:03 +01:00
Eldinnie 0c10c537f7 fix typo in master
[CI skip]
2019-02-23 17:39:04 +01:00
Jasmin Bom 26ce9bb343 Documentation fixes (#1348)
* Clarify InlineKeyboardButton callback-data docstring

Fixes #1267

* Improve documentation slightly

- Prettier changelog
- Link to examples on frontpage
- Link to wiki on frontpage
- Put telegram.ext in own sidebar thingy

* Improve ext.Filters documentation

Superseeds #1089

- Remove `:obj:Filter` as it's pretty obvious that it's a filter since it's in this module. It also made the html output about 1/3 times longer to scroll through.
- Add an __all__ to reorder so Filter is at the top instead of BaseFilter
- Add a proper docstring for document that documents the attributes (this is the #1089 part)
- Also fix a couple of grammar errors.

* Fix indentation in telegram.rst

* Add `git describe --long --tags` to the `python -m telegram` output

* Use admonition for examples to make them *pop* better

* Remove automodule telegram

Sphinx was whining a lot and it doesn't actually seem to be needed in any way?

* Fix Filters.document docstring per CR

Co-Authored-By: jsmnbom <jsmnbom@gmail.com>

* Fix spelling error in docs index

Co-Authored-By: jsmnbom <jsmnbom@gmail.com>

* Setting needs_sphinx to 1.7.9

so we don't have more issues with incongruent versions

* Fixed last warnings

* Make pre-commit happy
2019-02-18 20:04:52 +01:00
Jasmin Bom 39d686b1a1 Tiny spelling fix in CHANGES.rst [ci skip] 2019-02-14 12:31:00 +01:00
Kirill Vasin 60f2044bbd Entry returns None ends conversation (#1270)
* Fix unresolvable promises

* added async test and description

* added test_none_on_first_message for conv_handler

* Small change in ConversationHandler docstring

* Fix test to work with new commandhandler
2019-02-14 12:29:58 +01:00
Jasmin Bom dda7ca18cd Update CHANGES.rst
Also include #1270 even though not merged yet, but it should be very soon :)
2019-02-14 12:03:20 +01:00
Jasmin Bom 07c51d236b Fix spelling error in example notice 2019-02-14 11:53:09 +01:00
Jasmin Bom 9f1eccf569 Merge branch 'master' into V12 2019-02-14 11:52:31 +01:00
Eldinnie cd7c642f49 Add WAITING state and behavior (#1344)
* Add WAITING state and behavior

* Remove `run_async_timeout` and `timed_out_behavior` arguments
* replace with `WAITING` constant and behavior from states
* never wait for promise to resolve (will hang up entire update queue
* see #1250 for discussion

* Fixing pytest version to 4.2.0

Pytest 4.2.1 has a weird bug on top level collect in 4.2.1 Fixing version to 4.2.0
2019-02-14 11:00:21 +01:00
Bibo-Joshi f7abb21323 Adjust persistence on exit behaviour (#1312)
* Adjust persistence of exit behaviour

* Fix binary operators in on_flush

* Fix docstring

* Add test
2019-02-13 23:30:29 +01:00
Eldinnie 7e2dbdd4b3 Fix #1297 (#1342)
* Fix #1297

This makes a deepcopy of the user_data and chat_data dict as suggested by @Bibo-Joshi

* Fix dictpersistence aswel.
2019-02-13 23:28:48 +01:00
Jasmin Bom b64698e4b6 Use warnings.warn for conversationhandler warnings. (#1343)
* FIXED: ConversationHandler errors were logged to root logger

* Use warnings.warn instead of self.logger.warning.
2019-02-13 23:28:23 +01:00
Ambro d0936f76ad Only one warning for multiple CallbackqueryHandler's on ConversationHandler (#1319) 2019-02-13 22:08:49 +01:00
Jasmin Bom da342af7ed Small flake8 fixes 2019-02-13 16:04:48 +01:00
Jasmin Bom 446c54cf8d Bump to version 12.0.0b1 2019-02-13 13:41:04 +01:00
Jasmin Bom f5bfe2f29c Update example comments and docstrings to note the V12 beta 2019-02-13 13:38:07 +01:00
Eldinnie b02b68880f Make dispatcher use one context per update (#1283)
* Make dispatcher use one context per update

It gives user the option to `overload` context with their own properties in a lower group handler if they like

* Improve callbackcontext & run_async docstring

- Add note about how you can add custom attributes to context.
- Add warnings about how run_async and custom attributes should not be used together.

* Small documentation improvements. [ci skip]
2019-02-13 12:18:37 +01:00
Eldinnie 2c5eade4f0 Update Filters, CommandHandler and MessageHandler (#1221)
* update_filter attribute on filters

Makes it possible to have filters work on an update instead of message, while keeping behavior for current filters

* add update_type filter

* Messagehandler rework

- remove allow_edited (deprecated for a while)
- set deprecated defaults to None
- Raise deprecation warning when they're used
- add sensible defaults for filters.
- rework tests

* Commandhandler rework

* Remove deprecation test from new handler

* Some tweaks per CR

- rename update_types -> updates
- added some clarification to docstrings

* run webhook set test only on 3.6 on appveyor

* update_filter attribute on filters

Makes it possible to have filters work on an update instead of message, while keeping behavior for current filters

* add update_type filter

* Messagehandler rework

- remove allow_edited (deprecated for a while)
- set deprecated defaults to None
- Raise deprecation warning when they're used
- add sensible defaults for filters.
- rework tests

* Commandhandler rework

* Remove deprecation test from new handler

* Some tweaks per CR

- rename update_types -> updates
- added some clarification to docstrings

* run webhook set test only on 3.6 on appveyor

* Changes per CR

* Update travis to build v12

* small doc update

* try to make ci build version branches

* doc for BaseFilter

* Modify regexfilter and mergedfilter

Now returns a list of match objects for every regexfilter

* Change callbackcontext (+ docs)

* integrate in CommandHandler and PrefixHandler

* integrate in MessageHandler

* cbqhandler, iqhandler and srhandler

* make regexhandler a shell over MessageHandler

And raise deprecationWarning on creation

* clean up code and add some comments

* Rework based on internal group feedback

- use data_filter instead of regex_filter on BaseFilter
- have these filters return a dict that is then updated onto CallbackContext instead of using a list is before
- Add a .match property on CallbackContext that returns .matches[0] or None

* Fix and add test for callbackcontext.match

* Lots of documentation fixes and improvements [ci skip]
2019-02-13 12:07:25 +01:00
Jasmin Bom 950ec35970 Remove message decorator to fix default timeouts. (#1156)
* Remove message decorator to fix default timeouts.

* Make message wrapper method private.

* Make tests pass

* Fix callbackquery shortcuts

Closes #1180

* Fix callbackquery shortcut tests

* Fix merge

* Address CR

* Add missing default timeout=20 for some bot file uploads

* Fix wrong return value in convhandler

Probably stems from a combination of bad merge plus quickly merged hacktoberfest PR.
2019-02-13 11:37:13 +01:00
Jasmin Bom d33e1d9913 Small flake8 fix 2019-02-09 18:45:34 +01:00
Jasmin Bom 2e203e41e4 Merge branch 'master' into V12 2019-02-09 18:45:00 +01:00
Jasmin Bom e54a3188ce Revert "Fix bug: unable to save jobs with timezone aware dates (#1308)"
This reverts commit 23fe991b

See https://github.com/python-telegram-bot/python-telegram-bot/pull/1308 for more details.

NOTE: Keeping Ambro17 in AUTHORS.rst as I'm pretty sure they've contributed more since then :)
2019-02-09 18:44:04 +01:00
Jasmin Bom 710f43a23a travis: Use xenial dist for pypy
Cryptography started complaning about old openssl - hopefully xenial has a newer version
2019-02-09 18:26:17 +01:00
Jasmin Bom 27b757df32 Reorder some stickset tests to hopefully make them pass 2019-02-09 18:21:12 +01:00
Jasmin Bom 66e43c5932 Fix a bunch of flake8 W504 errors 2019-02-08 20:55:40 +01:00
Jasmin Bom 7fcbfc19f5 Mark location sending test as xfail as it seems to fail randomly 2019-02-08 20:22:44 +01:00
Jasmin Bom a60c07f549 Update test animation size
Telegram must've again chagned internal stuff
2019-02-08 20:21:24 +01:00
Jasmin Bom 1b52e6148e Merge branch 'master' into V12 2019-02-08 12:49:28 +01:00
Ambro 487bce18dd Improve regex filter docstring and avoid compiling compiled regex (#1314)
* Improve regex docstring and add test case

* Add Ambro17 as contributor

* rename regex filter

* Fix sphinx documentation for Filters.regex

* Add spacing to render Note with blue background
2019-02-08 11:12:49 +01:00
Gregory Petukhov 5c45e469d5 Fix #1335: Message.MESSAGE_TYPES does not contain "left_chat_member" (#1336) 2019-02-08 11:02:54 +01:00
Jasmin Bom 25e5449e97 Fix TestDispatcher::test_error_handler using pytest >= 4.0 2019-01-30 20:56:15 +01:00
Jasmin Bom a8bade4d73 Fix a bunch of tests
Looks like it's once again time for: Telegram changed some weird internal stuff and how thumbnails take up either 4 times as much space or half as much space. Oh and they also seemingly randomized the width and height of said thumbnails....
2019-01-30 20:40:53 +01:00
Jasmin Bom e08afe7fb2 Fix flake8 errors that only show in CI?? 2019-01-30 20:20:35 +01:00
Jasmin Bom 9817310788 Reflow docstrings in replykeyboardmarkup to satisfy flake8 2019-01-30 19:50:33 +01:00
Jasmin Bom ed33c4a7a9 Merge remote-tracking branch 'origin/master' 2019-01-30 19:45:37 +01:00
Jasmin Bom 1ee53e9e17 Updating the pylint that pre-commit uses
Fixes #1321
2019-01-30 19:42:57 +01:00
Gregory Petukhov 3e8d71582d Fix #1328: custom timeout argument does not work (#1330)
* Fix #1328: custom timeout argument does not work

* Remove unused import
2019-01-30 19:38:15 +01:00
Tanuj c03160c07f Add convenience classmethods for InlineKeyboardMarkup (fixes #1186) (#1260)
* Add convenience classmethods for InlineKeyboardMarkup (#1186)

* Switch to row and column methods

* Also add convenience classmethods for ReplyKeyboardMarkup

* Add some simple tests
2019-01-04 21:04:45 +01:00
Ambro 23fe991b85 Fix bug: unable to save jobs with timezone aware dates (#1308)
* Fix bug on jobs with timezone aware dates

* Add Ambro17 as colaborator
2019-01-04 20:29:07 +01:00
Bibo-Joshi 7eeb670a59 Fix check for effective chat/user in persistence (#1303) 2018-12-05 00:12:43 +01:00
Bibo-Joshi f23298a13b Fix typos in telegram/bot.py (#1305) 2018-12-04 15:06:48 +01:00
Konstantin Zemlyak 92f407bfb3 Update setup.py (#1306) 2018-12-04 15:06:22 +01:00
Steve Sandke 0cf0cccbc5 Fix description for JobQueue's run_daily and run_repeating methods. (#1299) 2018-11-22 13:03:58 +01:00
Jasmin Bom 378784f55e Fix persistence with non telegram.Update updates (#1271)
* Allow persistence with no telegram.Update updates

For use with TypeHandler

* Add test
2018-11-09 11:44:20 +01:00
Pieter Schutz 384173115f Merge branch 'master' into V12 2018-11-01 11:45:51 +01:00
Pieter Schutz ea5b301b59 fix pre-commit hooks 2018-11-01 11:08:09 +01:00
Pieter Schutz 9b66681ee4 fix pre-commit hooks 2018-11-01 11:08:09 +01:00
Pieter Schutz 92e7427689 Merge remote-tracking branch 'origin/master' 2018-11-01 10:53:46 +01:00
Pieter Schutz f82ceee777 Merge remote-tracking branch 'origin/master' 2018-11-01 10:53:46 +01:00
Pieter Schutz d2e2fe9ccc fix last flake8 errors 2018-11-01 10:52:36 +01:00
Pieter Schutz 76a72e9742 fix last flake8 errors 2018-11-01 10:52:36 +01:00
Noam Meltzer cf69a234d4 fix accidental commit to submodule config 2018-11-01 11:25:52 +02:00
Noam Meltzer eaf6dc2b88 fix accidental commit to submodule config 2018-11-01 11:25:52 +02:00
Noam Meltzer 9596343efd pep8 fixes 2018-11-01 11:18:07 +02:00
Noam Meltzer 30cc0f8cf9 pep8 fixes 2018-11-01 11:18:07 +02:00
Pieter Schutz f252436cd4 Merge remote-tracking branch 'origin/master' into V12 2018-11-01 09:32:55 +01:00
Jasmin Bom c9630ee8c5 Add Conflict error (HTTP error code 409) (#1154)
* Add conflicting bot id to conflict error message.

* Add test and comment to conflict error

* Don't extract bot id in Conflict exception per PR comments
2018-10-29 22:08:52 +01:00
Eldinnie 9d99660ba9 Change MAX_CAPTION_LENGTH to 1024 (#1262)
* update MAX_CAPTION_LENGTH

Telegram silently changed the max length for captions to 1024 chars.

* Update test_constants.py

* change docstrings to reflect new length

* remove message

* clear message and proper match
2018-10-16 19:51:57 +02:00
Jasmin Bom eca0ccf6b3 Update tests to support new test bots 2018-10-14 12:15:27 +02:00
Pavel Shakhov 9ece7fdb1c Mistake in MessageQueue.__call__'s docstring (#1249) 2018-10-08 15:36:59 +02:00
Evan Haberecht 4861d1a20d Removed unneccessary else and replaced with comment (#1247)
* Resolved issue #1163: Removed unneccessary else and replaced with comment

* Added myself to AUTHORS.rst
2018-10-08 08:18:33 +02:00
simonvorobjev dbf364e168 Allowed to use Handlers on conversation timeout (#1217)
* handler for ConversationHandler.END (timeout one) #1136

* review fixes

* review fixes

* review fixes

* review fixes

* docs and tests

* fixing stuff

* Fix problem

* fix conftest

* now it should work

* Add ConversationTimeoutContext

As discussed in the developers group. Use a class as the jobs context over using a dict.

* less verbosity
2018-10-04 08:58:40 +02:00
dbxnr d6d0dec6e0 remove extra else clause (#1239)
Fix #1236
2018-10-02 11:29:30 +02:00
Eldinnie 0bbca65a95 add sleep to bailout error test (#1241)
Hopefully it won;t hit the race condition so often on appveyor
2018-10-02 09:00:38 +02:00
Holden Oullette 1d715f0d36 Removed empty quotes (#1237) 2018-10-01 23:29:46 +02:00
Eldinnie f6f8667d6c add codacy badge to readme (#1232) 2018-10-01 22:16:48 +02:00
Pieter Schutz 8731365911 Merge branch 'master' into V12 2018-10-01 21:12:21 +02:00
Jasmin Bom d9ae4be2b3 Add 3.7 to travis and make pypy allowed_failures (#1215)
* Add 3.7 to travis build matrix using workaround

See https://github.com/travis-ci/travis-ci/issues/9815 for workaround discussion

* Add 3.7 to pypi classifiers

* Format build matrix differently

* Try adding pypy6.0.0 to travis build matrix

* Add py3.7 to appveyor

* Try pypy 5.10.1 instead

6.0.0 isn't on travis yet: https://github.com/travis-ci/travis-ci/issues/9542

* pypy2-5.10.0 isn't on travis yet either...

* allow failures on travis pypy
2018-10-01 20:50:44 +02:00
Pieter Schutz b5a3d7852a try to make ci build version branches 2018-10-01 11:48:10 +02:00
Pieter Schutz 3d8ab23d66 try to make ci build version branches 2018-10-01 11:46:48 +02:00
Pieter Schutz 6c36316aed Change yamls to build v12 on travis an appveyor 2018-10-01 10:25:06 +02:00
Jasmin Bom 4c66ba3a8d Allow filenames without dots in them when sending files (#1228)
* Fix issue 1227 by allowing filenames without dots in them

* Touch new test data file

* Satisfy flake8
2018-09-30 12:07:17 +02:00
reablaz c714a177d1 Update data.py to be compatible with example (#1213)
* Update data.py to be compatible with example

for now, if you process personal_info with example code, then you got an error if there is no set option to get native fist and last name.

setting default value will allow to process personal_info without native name/surname transation

* fixing line length

i hope i understood right this. sorry for delay, just starting using github!
2018-09-26 15:46:25 +02:00
Ehsan 3829666a53 add text mention for message parse (#1206)
* add text mention for message parse

* add author

* Update .gitignore
2018-09-25 20:07:55 +02:00
Pieter Schutz 9c1b493f37 Make persistence example context-based 2018-09-21 13:57:44 +02:00
Pieter Schutz af2d716129 Fix test_persistence
* To work with new CommandHandler
* To make tests context-based
2018-09-21 13:57:36 +02:00
Jasmin Bom 5252a493cb Pass check result into handle_update
Missed during merge
2018-09-21 09:20:35 +02:00
Jasmin Bom e75615cbf6 Revert "Revert "CommandHandler overhaul and PrefixHandler added (#1114)""
This reverts commit 9e2357b
2018-09-21 08:57:43 +02:00
Jasmin Bom cf95027308 Revert "Revert "Context based callbacks (#1100)""
This reverts commit f8a17cd
2018-09-21 08:57:01 +02:00
Eldinnie 439790375e Persistence (#1017)
* BasePersistence

* basic construct

* Keep working

* Continue work

Add tests for Basepersistence

* Finish up BasePersistence and implementation

* PickelPersistence and start tests

* Finishing up

* Oops, left in some typings

* Compatibilty issues regarding py2 solved

For Py2 compatibility

* increasing coverage

* Small changes due to CR

* All persistence tests in one file

* add DictPersistence

* Last changes per CR

* forgot change

* changes per CR

* call update_* only with relevant data

As discussed with @jsmnbom

* Add conversationbot Example

* should not have committed API-key
2018-09-20 22:50:40 +02:00
Marcelo G. de Andrade b9f56ca479 fixes comment on examples/conversationbot2.py (#1216) 2018-09-12 21:27:35 +02:00
Jasmin Bom e247fa7c2c Fix uploading files with unicode filenames (#1214)
* Patch urllib3RequestField to make it *not* support RFC2231

* Add new required test data file

* Fix on py2

* Remove weird legacy code from inputfile

Not needed anymore, and also makes it not work... so that was not great
2018-09-10 21:08:05 +02:00
Kirill Vasin b8c288ff4a Make ConversationHandler and run_async work together properly (#1176)
* #1175

* fix issue with timeout

* exception in promise resolve is treated as None result

* removed useless variables
2018-09-10 20:05:45 +02:00
Jasmin Bom bbe633e571 Remove GAE support message
We still kinda support it, but not more so than other providers.
2018-09-10 15:58:25 +02:00
Kirill Vasin f2b06728e9 Replace http.server with Tornado (#1191)
Fixes #1189
2018-09-08 23:25:48 +03:00
Jasmin Bom b2fb4264a3 Small test fixes to make them more stable on travis CI 2018-09-07 16:07:27 +02:00
Noam Meltzer 19591c955a Allow SOCKSConnection to parse username and password from URL (#1211) 2018-09-07 15:21:01 +02:00
Jasmin Bom 259a1faedc Releasing v11.1 2018-09-01 17:27:35 +02:00
Jasmin Bom 09bdb88822 Bot api 4.1 (#1198)
* Fix passport decryption failing at random times

Sometimes a decrypted secret was being treated as b64 and therefore got decoded even further. Fix by decoding b64 right before call to decrypt so we have better control of when not to do it

* Bot api 4.1

Telegram passport 1.1
Added support for middle names.
Added support for translations for documents
Add errors for translations for documents
Added support for requesting names in the language of the user's country of residence
Replaced the payload parameter with the new parameter nonce

NOTE: Scope stuff is NOT implemented, as we wanna STRONGLY encourage users to use the telegram provided SDKs anyway (and not generate telegram auth links in their bot, but rather on a server)

* Minor fixes

* Add hash to EncryptedPassportElement

For use with PassportElementErrorUnspecified apparently
2018-09-01 16:58:08 +02:00
312 changed files with 17458 additions and 4648 deletions
+34 -3
View File
@@ -25,7 +25,7 @@ Setting things up
.. code-block:: bash
$ sudo pip install -r requirements.txt -r requirements-dev.txt
$ pip install -r requirements.txt -r requirements-dev.txt
5. Install pre-commit hooks:
@@ -43,6 +43,8 @@ If you have an idea for something to do, first check if it's already been filed
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.
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.
Instructions for making a code change
#####################################
@@ -66,6 +68,26 @@ Here's how to make a one-off code change.
- You can refer to relevant issues in the commit message by writing, e.g., "#105".
- Your code should adhere to the `PEP 8 Style Guide`_, with the exception that we have a maximum line length of 99.
- Document your code. This project uses `sphinx`_ to generate static HTML docs. To build them, first make sure you have the required dependencies:
.. code-block:: bash
$ pip install -r docs/requirements-docs.txt
then run the following from the PTB root directory:
.. code-block:: bash
$ make -C docs html
or, if you don't have ``make`` available (e.g. on Windows):
.. code-block:: bash
$ sphinx-build docs/source docs/build/html
Once the process terminates, you can view the built documentation by opening ``docs/build/html/index.html`` with a browser.
- For consistency, please conform to `Google Python Style Guide`_ and `Google Python Style Docstrings`_. In addition, code should be formatted consistently with other code around it.
@@ -91,6 +113,14 @@ Here's how to make a one-off code change.
$ pytest -v
To run ``test_official`` (particularly useful if you made API changes), run
.. code-block::
$ export TEST_OFFICIAL=true
prior to running the tests.
- To actually make the commit (this will trigger tests for yapf, lint and pep8 automatically):
.. code-block:: bash
@@ -217,6 +247,7 @@ break the API classes. For example:
.. _`issue tracker`: https://github.com/python-telegram-bot/python-telegram-bot/issues
.. _`developers' mailing list`: mailto:devs@python-telegram-bot.org
.. _`PEP 8 Style Guide`: https://www.python.org/dev/peps/pep-0008/
.. _`Google Python Style Guide`: https://google-styleguide.googlecode.com/svn/trunk/pyguide.html
.. _`Google Python Style Docstrings`: http://sphinx-doc.org/latest/ext/example_google.html
.. _`sphinx`: http://sphinx-doc.org
.. _`Google Python Style Guide`: http://google.github.io/styleguide/pyguide.html
.. _`Google Python Style Docstrings`: https://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_google.html
.. _AUTHORS.rst: ../AUTHORS.rst
@@ -1,9 +1,16 @@
---
name: Bug report
about: Create a report to help us improve
title: "[BUG]"
labels: 'bug :bug:'
assignees: ''
---
<!--
Thanks for reporting issues of python-telegram-bot!
Use this template to notify us if you found a bug, or if you want to request a new feature.
If you're looking for help with programming your bot using our library, feel free to ask your
questions in out telegram group at: https://t.me/pythontelegrambotgroup
Use this template to notify us if you found a bug.
To make it easier for us to help you please enter detailed information below.
+8
View File
@@ -0,0 +1,8 @@
blank_issues_enabled: false
contact_links:
- name: Telegram Group
url: https://telegram.me/pythontelegrambotgroup
about: Questions asked on the group usually get answered faster.
- name: IRC Channel
url: https://webchat.freenode.net/?channels=##python-telegram-bot
about: In case you are unable to join our group due to Telegram restrictions, you can use our IRC channel
+24
View File
@@ -0,0 +1,24 @@
---
name: Feature request
about: Suggest an idea for this project
title: "[FEATURE]"
labels: enhancement
assignees: ''
---
#### Is your feature request related to a problem? Please describe.
A clear and concise description of what the problem is.
Ex. *I want to do X, but there is no way to do it.*
#### Describe the solution you'd like
A clear and concise description of what you want to happen.
Ex. *I think it would be nice if you would add feature Y so it will make it easier.*
#### Describe alternatives you've considered
A clear and concise description of any alternative solutions or features you've considered.
Ex. *I considered Z, but that didn't work because...*
#### Additional context
Add any other context or screenshots about the feature request here.
Ex. *Here's a photo of my cat!*
+29
View File
@@ -0,0 +1,29 @@
---
name: Question
about: Get help with errors or general questions
title: "[QUESTION]"
labels: question
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 :)
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.
-->
### Issue I am facing
Please describe the issue here in as much detail as possible
### Traceback to the issue
```
put it here
```
### Related part of your code
```python
put it here
```
+14
View File
@@ -0,0 +1,14 @@
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 5
# 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)
onlyLabels: question
# Label to use when marking an issue as stale
staleLabel: stale
# Comment to post when marking as stale. Set to `false` to disable
markComment: false
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: >
This issue has been automatically closed due to inactivity. Feel free to comment in order to reopen
or ask again in our Telegram support group at https://t.me/pythontelegrambotgroup.
+14
View File
@@ -0,0 +1,14 @@
name: Warning maintainers
on:
pull_request:
paths: examples/**
jobs:
job:
runs-on: ubuntu-latest
name: about example change
steps:
- name: running the check
uses: Poolitzer/notifier-action@master
with:
notify-message: Hey there. Relax, I am just a little warning for the maintainers to release directly after merging your PR, otherwise we have broken examples and people might get confused :)
repo-token: ${{ secrets.GITHUB_TOKEN }}
+119
View File
@@ -0,0 +1,119 @@
name: GitHub Actions
on:
pull_request:
branches:
- master
schedule:
- cron: 7 3 * * *
push:
branches:
- master
jobs:
pytest:
name: pytest
runs-on: ${{matrix.os}}
strategy:
matrix:
python-version: [3.5, 3.6, 3.7, 3.8]
os: [ubuntu-latest, windows-latest]
include:
- os: ubuntu-latest
python-version: 3.7
test-build: True
- os: windows-latest
python-version: 3.7
test-build: True
fail-fast: False
steps:
- uses: actions/checkout@v2
- name: Initialize vendored libs
run:
git submodule update --init --recursive
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v1
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -W ignore -m pip install --upgrade pip
python -W ignore -m pip install -U codecov pytest-cov
python -W ignore -m pip install -r requirements.txt
python -W ignore -m pip install -r requirements-dev.txt
- name: Test with pytest
run: |
pytest -v -m nocoverage
nocov_exit=$?
pytest -v -m "not nocoverage" --cov
cov_exit=$?
global_exit=$(( nocov_exit > cov_exit ? nocov_exit : cov_exit ))
exit ${global_exit}
env:
JOB_INDEX: ${{ strategy.job-index }}
BOTS: W3sidG9rZW4iOiAiNjk2MTg4NzMyOkFBR1Z3RUtmSEhsTmpzY3hFRE5LQXdraEdzdFpfa28xbUMwIiwgInBheW1lbnRfcHJvdmlkZXJfdG9rZW4iOiAiMjg0Njg1MDYzOlRFU1Q6WldGaU1UUmxNbVF5TnpNeSIsICJib3RfbmFtZSI6ICJQVEIgdGVzdHMgb24gVHJhdmlzIHVzaW5nIENQeXRob24gMi43IiwgImJvdF91c2VybmFtZSI6ICJAcHRiX3RyYXZpc19jcHl0aG9uXzI3X2JvdCJ9LCB7InRva2VuIjogIjY3MTQ2ODg4NjpBQUdQR2ZjaVJJQlVORmU4MjR1SVZkcTdKZTNfWW5BVE5HdyIsICJwYXltZW50X3Byb3ZpZGVyX3Rva2VuIjogIjI4NDY4NTA2MzpURVNUOlpHWXdPVGxrTXpNeE4yWTIiLCAiYm90X25hbWUiOiAiUFRCIHRlc3RzIG9uIFRyYXZpcyB1c2luZyBDUHl0aG9uIDMuNCIsICJib3RfdXNlcm5hbWUiOiAiQHB0Yl90cmF2aXNfY3B5dGhvbl8zNF9ib3QifSwgeyJ0b2tlbiI6ICI2MjkzMjY1Mzg6QUFGUnJaSnJCN29CM211ekdzR0pYVXZHRTVDUXpNNUNVNG8iLCAicGF5bWVudF9wcm92aWRlcl90b2tlbiI6ICIyODQ2ODUwNjM6VEVTVDpNbU01WVdKaFl6a3hNMlUxIiwgImJvdF9uYW1lIjogIlBUQiB0ZXN0cyBvbiBUcmF2aXMgdXNpbmcgQ1B5dGhvbiAzLjUiLCAiYm90X3VzZXJuYW1lIjogIkBwdGJfdHJhdmlzX2NweXRob25fMzVfYm90In0sIHsidG9rZW4iOiAiNjQwMjA4OTQzOkFBRmhCalFwOXFtM1JUeFN6VXBZekJRakNsZS1Kano1aGNrIiwgInBheW1lbnRfcHJvdmlkZXJfdG9rZW4iOiAiMjg0Njg1MDYzOlRFU1Q6WXpoa1pUZzFOamMxWXpWbCIsICJib3RfbmFtZSI6ICJQVEIgdGVzdHMgb24gVHJhdmlzIHVzaW5nIENQeXRob24gMy42IiwgImJvdF91c2VybmFtZSI6ICJAcHRiX3RyYXZpc19jcHl0aG9uXzM2X2JvdCJ9LCB7InRva2VuIjogIjY5NTEwNDA4ODpBQUhmenlsSU9qU0lJUy1lT25JMjB5MkUyMEhvZEhzZnotMCIsICJwYXltZW50X3Byb3ZpZGVyX3Rva2VuIjogIjI4NDY4NTA2MzpURVNUOk9HUTFNRGd3WmpJd1pqRmwiLCAiYm90X25hbWUiOiAiUFRCIHRlc3RzIG9uIFRyYXZpcyB1c2luZyBDUHl0aG9uIDMuNyIsICJib3RfdXNlcm5hbWUiOiAiQHB0Yl90cmF2aXNfY3B5dGhvbl8zN19ib3QifSwgeyJ0b2tlbiI6ICI2OTE0MjM1NTQ6QUFGOFdrakNaYm5IcVBfaTZHaFRZaXJGRWxackdhWU9oWDAiLCAicGF5bWVudF9wcm92aWRlcl90b2tlbiI6ICIyODQ2ODUwNjM6VEVTVDpZamM1TlRoaU1tUXlNV1ZoIiwgImJvdF9uYW1lIjogIlBUQiB0ZXN0cyBvbiBUcmF2aXMgdXNpbmcgUHlQeSAyLjciLCAiYm90X3VzZXJuYW1lIjogIkBwdGJfdHJhdmlzX3B5cHlfMjdfYm90In0sIHsidG9rZW4iOiAiNjg0MzM5OTg0OkFBRk1nRUVqcDAxcjVyQjAwN3lDZFZOc2c4QWxOc2FVLWNjIiwgInBheW1lbnRfcHJvdmlkZXJfdG9rZW4iOiAiMjg0Njg1MDYzOlRFU1Q6TVRBek1UWTNNR1V5TmpnMCIsICJib3RfbmFtZSI6ICJQVEIgdGVzdHMgb24gVHJhdmlzIHVzaW5nIFB5UHkgMy41IiwgImJvdF91c2VybmFtZSI6ICJAcHRiX3RyYXZpc19weXB5XzM1X2JvdCJ9LCB7InRva2VuIjogIjY5MDA5MTM0NzpBQUZMbVI1cEFCNVljcGVfbU9oN3pNNEpGQk9oMHozVDBUbyIsICJwYXltZW50X3Byb3ZpZGVyX3Rva2VuIjogIjI4NDY4NTA2MzpURVNUOlpEaGxOekU1TURrd1lXSmkiLCAiYm90X25hbWUiOiAiUFRCIHRlc3RzIG9uIEFwcFZleW9yIHVzaW5nIENQeXRob24gMy40IiwgImJvdF91c2VybmFtZSI6ICJAcHRiX2FwcHZleW9yX2NweXRob25fMzRfYm90In0sIHsidG9rZW4iOiAiNjk0MzA4MDUyOkFBRUIyX3NvbkNrNTVMWTlCRzlBTy1IOGp4aVBTNTVvb0JBIiwgInBheW1lbnRfcHJvdmlkZXJfdG9rZW4iOiAiMjg0Njg1MDYzOlRFU1Q6WW1aaVlXWm1NakpoWkdNeSIsICJib3RfbmFtZSI6ICJQVEIgdGVzdHMgb24gQXBwVmV5b3IgdXNpbmcgQ1B5dGhvbiAyLjciLCAiYm90X3VzZXJuYW1lIjogIkBwdGJfYXBwdmV5b3JfY3B5dGhvbl8yN19ib3QifSwgeyJ0b2tlbiI6ICIxMDU1Mzk3NDcxOkFBRzE4bkJfUzJXQXd1SjNnN29oS0JWZ1hYY2VNbklPeVNjIiwgInBheW1lbnRfcHJvdmlkZXJfdG9rZW4iOiAiMjg0Njg1MDYzOlRFU1Q6TmpBd056QXpZalZpTkdOayIsICJuYW1lIjogIlBUQiB0ZXN0cyBbMF0iLCAidXNlcm5hbWUiOiAicHRiXzBfYm90In0sIHsidG9rZW4iOiAiMTA0NzMyNjc3MTpBQUY4bk90ODFGcFg4bGJidno4VWV3UVF2UmZUYkZmQnZ1SSIsICJwYXltZW50X3Byb3ZpZGVyX3Rva2VuIjogIjI4NDY4NTA2MzpURVNUOllUVTFOVEk0WkdSallqbGkiLCAibmFtZSI6ICJQVEIgdGVzdHMgWzFdIiwgInVzZXJuYW1lIjogInB0Yl8xX2JvdCJ9LCB7InRva2VuIjogIjk3MTk5Mjc0NTpBQUdPa09hVzBOSGpnSXY1LTlqUWJPajR2R3FkaFNGLVV1cyIsICJwYXltZW50X3Byb3ZpZGVyX3Rva2VuIjogIjI4NDY4NTA2MzpURVNUOk5XWmtNV1ZoWWpsallqVTUiLCAibmFtZSI6ICJQVEIgdGVzdHMgWzJdIiwgInVzZXJuYW1lIjogInB0Yl8yX2JvdCJ9XQ==
TEST_BUILD: ${{ matrix.test-build }}
TEST_PRE_COMMIT: ${{ matrix.test-pre-commit }}
shell: bash --noprofile --norc {0}
- name: Submit coverage
run: |
if [ "$CODECOV_TOKEN" != "" ]; then
codecov -F github -t $CODECOV_TOKEN --name "${{ matrix.os }}-${{ matrix.python-version }}"
fi
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
shell: bash
test_official:
name: test-official
runs-on: ${{matrix.os}}
strategy:
matrix:
python-version: [3.7]
os: [ubuntu-latest]
fail-fast: False
steps:
- uses: actions/checkout@v2
- name: Initialize vendored libs
run:
git submodule update --init --recursive
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v1
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -W ignore -m pip install --upgrade pip
python -W ignore -m pip install -r requirements.txt
python -W ignore -m pip install -r requirements-dev.txt
- name: Compare to official api
run: |
pytest -v tests/test_official.py
exit $?
env:
TEST_OFFICIAL: "true"
shell: bash --noprofile --norc {0}
test_pre_commit:
name: test-pre-commit
runs-on: ${{matrix.os}}
strategy:
matrix:
python-version: [3.7]
os: [ubuntu-latest]
fail-fast: False
steps:
- uses: actions/checkout@v2
- name: Initialize vendored libs
run:
git submodule update --init --recursive
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v1
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -W ignore -m pip install --upgrade pip
python -W ignore -m pip install -r requirements.txt
python -W ignore -m pip install -r requirements-dev.txt
- name: Run pre-commit tests
run: pre-commit run --all-files
+6
View File
@@ -23,6 +23,11 @@ var/
.installed.cfg
*.egg
.env
.pybuild
debian/tmp
debian/python3-telegram
debian/python3-telegram-doc
debian/.debhelper
# PyInstaller
# Usually these files are written by a python script from a template
@@ -64,6 +69,7 @@ target/
*.sublime*
# unitests files
game.gif
telegram.mp3
telegram.mp4
telegram2.mp4
+5 -5
View File
@@ -1,17 +1,17 @@
repos:
- repo: git://github.com/python-telegram-bot/mirrors-yapf
sha: master
rev: 5769e088ef6e0a0d1eb63bd6d0c1fe9f3606d6c8
hooks:
- id: yapf
files: ^(telegram|tests)/.*\.py$
args:
- --diff
- repo: git://github.com/pre-commit/pre-commit-hooks
sha: v1.2.0
- repo: https://gitlab.com/pycqa/flake8
rev: 3.7.1
hooks:
- id: flake8
exclude: ^(setup.py|docs/source/conf.py)$
- repo: git://github.com/pre-commit/mirrors-pylint
sha: v1.7.1
rev: 9d8dcbc2b86c796275680f239c1e90dcd50bd398
hooks:
- id: pylint
files: ^telegram/.*\.py$
-39
View File
@@ -1,39 +0,0 @@
language: python
python:
- "2.7"
- "3.4"
- "3.5"
- "3.6"
- "3.7-dev"
- "pypy-5.7.1"
dist: trusty
sudo: false
branches:
only:
- master
cache:
directories:
- $HOME/.cache/pip
- $HOME/.pre-commit
before_cache:
- rm -f $HOME/.cache/pip/log/debug.log
- rm -f $HOME/.pre-commit/pre-commit.log
install:
- pip install -U codecov pytest-cov
- echo $TRAVIS_PYTHON_VERSION
- if [[ $TRAVIS_PYTHON_VERSION == '3.7'* ]]; then pip install -U git+https://github.com/yaml/pyyaml.git; fi
- pip install -U -r requirements.txt
- pip install -U -r requirements-dev.txt
- if [[ $TRAVIS_PYTHON_VERSION != 'pypy'* ]]; then pip install ujson; fi
script:
- pytest -v -m nocoverage
- pytest -v -m "not nocoverage" --cov
after_success:
- coverage combine
- codecov -F Travis
+18 -1
View File
@@ -4,7 +4,7 @@ Credits
``python-telegram-bot`` was originally created by
`Leandro Toledo <https://github.com/leandrotoledo>`_ and is now maintained by
`Jannes Höke <https://github.com/jh0ker>`_ (`@jh0ker <https://t.me/jh0ker>`_ on Telegram),
`Noam Meltzer <https://github.com/tsnoam>`_, `Pieter Schutz <https://github.com/eldinnie>`_ and `Jasmin Bom <https://github.com/jsmnbom>`_.
`Noam Meltzer <https://github.com/tsnoam>`_, `Pieter Schutz <https://github.com/eldinnie>`_, `Jasmin Bom <https://github.com/jsmnbom>`_ and `Hinrich Mahler <https://github.com/Bibo-Joshi>`_.
We're vendoring urllib3 as part of ``python-telegram-bot`` which is distributed under the MIT
license. For more info, full credits & license terms, the sources can be found here:
@@ -16,20 +16,28 @@ Contributors
The following wonderful people contributed directly or indirectly to this project:
- `Alateas <https://github.com/alateas>`_
- `Ales Dokshanin <https://github.com/alesdokshanin>`_
- `Ambro17 <https://github.com/Ambro17>`_
- `Anton Tagunov <https://github.com/anton-tagunov>`_
- `Avanatiker <https://github.com/Avanatiker>`_
- `Balduro <https://github.com/Balduro>`_
- `Bibo-Joshi <https://github.com/Bibo-Joshi>`_
- `bimmlerd <https://github.com/bimmlerd>`_
- `d-qoi <https://github.com/d-qoi>`_
- `daimajia <https://github.com/daimajia>`_
- `Daniel Reed <https://github.com/nmlorg>`_
- `Eana Hufwe <https://github.com/blueset>`_
- `Ehsan Online <https://github.com/ehsanonline>`_
- `Eli Gao <https://github.com/eligao>`_
- `Emilio Molinari <https://github.com/xates>`_
- `ErgoZ Riftbit Vaper <https://github.com/ergoz>`_
- `Eugene Lisitsky <https://github.com/lisitsky>`_
- `Eugenio Panadero <https://github.com/azogue>`_
- `Evan Haberecht <https://github.com/habereet>`_
- `evgfilim1 <https://github.com/evgfilim1>`_
- `franciscod <https://github.com/franciscod>`_
- `gamgi <https://github.com/gamgi>`_
- `Harshil <https://github.com/harshil21>`_
- `Hugo Damer <https://github.com/HakimusGIT>`_
- `ihoru <https://github.com/ihoru>`_
- `Jasmin Bom <https://github.com/jsmnbom>`_
@@ -42,8 +50,10 @@ The following wonderful people contributed directly or indirectly to this projec
- `Joscha Götzer <https://github.com/Rostgnom>`_
- `jossalgon <https://github.com/jossalgon>`_
- `JRoot3D <https://github.com/JRoot3D>`_
- `Kirill Vasin <https://github.com/vasinkd>`_
- `Kjwon15 <https://github.com/kjwon15>`_
- `Li-aung Yip <https://github.com/LiaungYip>`_
- `Loo Zheng Yuan <https://github.com/loozhengyuan>`_
- `macrojames <https://github.com/macrojames>`_
- `Michael Elovskikh <https://github.com/wronglink>`_
- `Mischa Krüger <https://github.com/Makman2>`_
@@ -58,16 +68,23 @@ The following wonderful people contributed directly or indirectly to this projec
- `Patrick Hofmann <https://github.com/PH89>`_
- `Paul Larsen <https://github.com/PaulSonOfLars>`_
- `Pieter Schutz <https://github.com/eldinnie>`_
- `Poolitzer <https://github.com/Poolitzer>`_
- `Rahiel Kasim <https://github.com/rahiel>`_
- `Riko Naka <https://github.com/rikonaka>`_
- `Rizlas <https://github.com/rizlas>`_
- `Sahil Sharma <https://github.com/sahilsharma811>`_
- `Sascha <https://github.com/saschalalala>`_
- `Shelomentsev D <https://github.com/shelomentsevd>`_
- `Simon Schürrle <https://github.com/SitiSchu>`_
- `sooyhwang <https://github.com/sooyhwang>`_
- `syntx <https://github.com/syntx>`_
- `thodnev <https://github.com/thodnev>`_
- `Trainer Jono <https://github.com/Tr-Jono>`_
- `Valentijn <https://github.com/Faalentijn>`_
- `voider1 <https://github.com/voider1>`_
- `Vorobjev Simon <https://github.com/simonvorobjev>`_
- `Wagner Macedo <https://github.com/wagnerluis1982>`_
- `wjt <https://github.com/wjt>`_
- `zeshuaro <https://github.com/zeshuaro>`_
Please add yourself here alphabetically when you submit your first pull request.
+506 -25
View File
@@ -1,9 +1,477 @@
=======
Changes
=======
=========
Changelog
=========
**2018-08-29**
*Released 11.0.0*
Version 12.6
============
*Released 2020-04-10*
**Major Changes:**
- Bot API 4.7 support. **Note:** In ``Bot.create_new_sticker_set`` and ``Bot.add_sticker_to_set``, the order of the parameters had be changed, as the ``png_sticker`` parameter is now optional. (`#1858`_)
**Minor changes, CI improvements or bug fixes:**
- Add tests for ``swtich_inline_query(_current_chat)`` with empty string (`#1635`_)
- Doc fixes (`#1854`_, `#1874`_, `#1884`_)
- Update issue templates (`#1880`_)
- Favor concrete types over "Iterable" (`#1882`_)
- Pass last valid ``CallbackContext`` to ``TIMEOUT`` handlers of ``ConversationHandler`` (`#1826`_)
- Tweak handling of persistence and update persistence after job calls (`#1827`_)
- Use checkout@v2 for GitHub actions (`#1887`_)
.. _`#1858`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1858
.. _`#1635`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1635
.. _`#1854`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1854
.. _`#1874`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1874
.. _`#1884`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1884
.. _`#1880`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1880
.. _`#1882`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1882
.. _`#1826`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1826
.. _`#1827`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1827
.. _`#1887`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1887
Version 12.5.1
==============
*Released 2020-03-30*
**Minor changes, doc fixes or bug fixes:**
- Add missing docs for `PollHandler` and `PollAnswerHandler` (`#1853`_)
- Fix wording in `Filters` docs (`#1855`_)
- Reorder tests to make them more stable (`#1835`_)
- Make `ConversationHandler` attributes immutable (`#1756`_)
- Make `PrefixHandler` attributes `command` and `prefix` editable (`#1636`_)
- Fix UTC as default `tzinfo` for `Job` (`#1696`_)
.. _`#1853`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1853
.. _`#1855`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1855
.. _`#1835`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1835
.. _`#1756`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1756
.. _`#1636`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1636
.. _`#1696`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1696
Version 12.5
============
*Released 2020-03-29*
**New Features:**
- `Bot.link` gives the `t.me` link of the bot (`#1770`_)
**Major Changes:**
- Bot API 4.5 and 4.6 support. (`#1508`_, `#1723`_)
**Minor changes, CI improvements or bug fixes:**
- Remove legacy CI files (`#1783`_, `#1791`_)
- Update pre-commit config file (`#1787`_)
- Remove builtin names (`#1792`_)
- CI improvements (`#1808`_, `#1848`_)
- Support Python 3.8 (`#1614`_, `#1824`_)
- Use stale bot for auto closing stale issues (`#1820`_, `#1829`_, `#1840`_)
- Doc fixes (`#1778`_, `#1818`_)
- Fix typo in `edit_message_media` (`#1779`_)
- In examples, answer CallbackQueries and use `edit_message_text` shortcut (`#1721`_)
- Revert accidental change in vendored urllib3 (`#1775`_)
.. _`#1783`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1783
.. _`#1787`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1787
.. _`#1792`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1792
.. _`#1791`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1791
.. _`#1808`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1808
.. _`#1614`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1614
.. _`#1770`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1770
.. _`#1824`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1824
.. _`#1820`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1820
.. _`#1829`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1829
.. _`#1840`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1840
.. _`#1778`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1778
.. _`#1779`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1779
.. _`#1721`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1721
.. _`#1775`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1775
.. _`#1848`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1848
.. _`#1818`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1818
.. _`#1508`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1508
.. _`#1723`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1723
Version 12.4.2
==============
*Released 2020-02-10*
**Bug Fixes**
- Pass correct parse_mode to InlineResults if bot.defaults is None (`#1763`_)
- Make sure PP can read files that dont have bot_data (`#1760`_)
.. _`#1763`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1763
.. _`#1760`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1760
Version 12.4.1
==============
*Released 2020-02-08*
This is a quick release for `#1744`_ which was accidently left out of v12.4.0 though mentioned in the
release notes.
Version 12.4.0
==============
*Released 2020-02-08*
**New features:**
- Set default values for arguments appearing repeatedly. We also have a `wiki page for the new defaults`_. (`#1490`_)
- Store data in ``CallbackContext.bot_data`` to access it in every callback. Also persists. (`#1325`_)
- ``Filters.poll`` allows only messages containing a poll (`#1673`_)
**Major changes:**
- ``Filters.text`` now accepts messages that start with a slash, because ``CommandHandler`` checks for ``MessageEntity.BOT_COMMAND`` since v12. This might lead to your MessageHandlers receiving more updates than before (`#1680`_).
- ``Filters.command`` new checks for ``MessageEntity.BOT_COMMAND`` instead of just a leading slash. Also by ``Filters.command(False)`` you can now filters for messages containing a command `anywhere` in the text (`#1744`_).
**Minor changes, CI improvements or bug fixes:**
- Add ``disptacher`` argument to ``Updater`` to allow passing a customized ``Dispatcher`` (`#1484`_)
- Add missing names for ``Filters`` (`#1632`_)
- Documentation fixes (`#1624`_, `#1647`_, `#1669`_, `#1703`_, `#1718`_, `#1734`_, `#1740`_, `#1642`_, `#1739`_, `#1746`_)
- CI improvements (`#1716`_, `#1731`_, `#1738`_, `#1748`_, `#1749`_, `#1750`_, `#1752`_)
- Fix spelling issue for ``encode_conversations_to_json`` (`#1661`_)
- Remove double assignement of ``Dispatcher.job_queue`` (`#1698`_)
- Expose dispatcher as property for ``CallbackContext`` (`#1684`_)
- Fix ``None`` check in ``JobQueue._put()`` (`#1707`_)
- Log datetimes correctly in ``JobQueue`` (`#1714`_)
- Fix false ``Message.link`` creation for private groups (`#1741`_)
- Add option ``--with-upstream-urllib3`` to `setup.py` to allow using non-vendored version (`#1725`_)
- Fix persistence for nested ``ConversationHandlers`` (`#1679`_)
- Improve handling of non-decodable server responses (`#1623`_)
- Fix download for files without ``file_path`` (`#1591`_)
- test_webhook_invalid_posts is now considered flaky and retried on failure (`#1758`_)
.. _`wiki page for the new defaults`: https://github.com/python-telegram-bot/python-telegram-bot/wiki/Adding-defaults-to-your-bot
.. _`#1744`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1744
.. _`#1752`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1752
.. _`#1750`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1750
.. _`#1591`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1591
.. _`#1490`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1490
.. _`#1749`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1749
.. _`#1623`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1623
.. _`#1748`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1748
.. _`#1679`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1679
.. _`#1711`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1711
.. _`#1325`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1325
.. _`#1746`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1746
.. _`#1725`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1725
.. _`#1739`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1739
.. _`#1741`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1741
.. _`#1642`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1642
.. _`#1738`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1738
.. _`#1740`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1740
.. _`#1734`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1734
.. _`#1680`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1680
.. _`#1718`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1718
.. _`#1714`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1714
.. _`#1707`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1707
.. _`#1731`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1731
.. _`#1673`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1673
.. _`#1684`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1684
.. _`#1703`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1703
.. _`#1698`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1698
.. _`#1669`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1669
.. _`#1661`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1661
.. _`#1647`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1647
.. _`#1632`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1632
.. _`#1624`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1624
.. _`#1716`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1716
.. _`#1484`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1484
.. _`#1758`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1484
Version 12.3.0
==============
*Released 2020-01-11*
**New features:**
- `Filters.caption` allows only messages with caption (`#1631`_).
- Filter for exact messages/captions with new capability of `Filters.text` and `Filters.caption`. Especially useful in combination with ReplyKeyboardMarkup. (`#1631`_).
**Major changes:**
- Fix inconsistent handling of naive datetimes (`#1506`_).
**Minor changes, CI improvements or bug fixes:**
- Documentation fixes (`#1558`_, `#1569`_, `#1579`_, `#1572`_, `#1566`_, `#1577`_, `#1656`_).
- Add mutex protection on `ConversationHandler` (`#1533`_).
- Add `MAX_PHOTOSIZE_UPLOAD` constant (`#1560`_).
- Add args and kwargs to `Message.forward()` (`#1574`_).
- Transfer to GitHub Actions CI (`#1555`_, `#1556`_, `#1605`_, `#1606`_, `#1607`_, `#1612`_, `#1615`_, `#1645`_).
- Fix deprecation warning with Py3.8 by vendored urllib3 (`#1618`_).
- Simplify assignements for optional arguments (`#1600`_)
- Allow private groups for `Message.link` (`#1619`_).
- Fix wrong signature call for `ConversationHandler.TIMEOUT` handlers (`#1653`_).
.. _`#1631`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1631
.. _`#1506`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1506
.. _`#1558`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1558
.. _`#1569`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1569
.. _`#1579`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1579
.. _`#1572`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1572
.. _`#1566`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1566
.. _`#1577`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1577
.. _`#1533`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1533
.. _`#1560`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1560
.. _`#1574`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1574
.. _`#1555`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1555
.. _`#1556`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1556
.. _`#1605`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1605
.. _`#1606`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1606
.. _`#1607`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1607
.. _`#1612`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1612
.. _`#1615`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1615
.. _`#1618`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1618
.. _`#1600`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1600
.. _`#1619`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1619
.. _`#1653`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1653
.. _`#1656`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1656
.. _`#1645`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1645
Version 12.2.0
==============
*Released 2019-10-14*
**New features:**
- Nested ConversationHandlers (`#1512`_).
**Minor changes, CI improvments or bug fixes:**
- Fix CI failures due to non-backward compat attrs depndency (`#1540`_).
- travis.yaml: TEST_OFFICIAL removed from allowed_failures.
- Fix typos in examples (`#1537`_).
- Fix Bot.to_dict to use proper first_name (`#1525`_).
- Refactor ``test_commandhandler.py`` (`#1408`_).
- Add Python 3.8 (RC version) to Travis testing matrix (`#1543`_).
- test_bot.py: Add to_dict test (`#1544`_).
- Flake config moved into setup.cfg (`#1546`_).
.. _`#1512`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1512
.. _`#1540`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1540
.. _`#1537`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1537
.. _`#1525`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1525
.. _`#1408`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1408
.. _`#1543`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1543
.. _`#1544`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1544
.. _`#1546`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1546
Version 12.1.1
==============
*Released 2019-09-18*
**Hot fix release**
Fixed regression in the vendored urllib3 (`#1517`_).
.. _`#1517`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1517
Version 12.1.0
================
*Released 2019-09-13*
**Major changes:**
- Bot API 4.4 support (`#1464`_, `#1510`_)
- Add `get_file` method to `Animation` & `ChatPhoto`. Add, `get_small_file` & `get_big_file`
methods to `ChatPhoto` (`#1489`_)
- Tools for deep linking (`#1049`_)
**Minor changes and/or bug fixes:**
- Documentation fixes (`#1500`_, `#1499`_)
- Improved examples (`#1502`_)
.. _`#1464`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1464
.. _`#1502`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1502
.. _`#1499`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1499
.. _`#1500`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1500
.. _`#1049`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1049
.. _`#1489`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1489
.. _`#1510`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1510
Version 12.0.0
================
*Released 2019-08-29*
Well... This felt like decades. But here we are with a new release.
Expect minor releases soon (mainly complete Bot API 4.4 support)
**Major and/or breaking changes:**
- Context based callbacks
- Persistence
- PrefixHandler added (Handler overhaul)
- Deprecation of RegexHandler and edited_messages, channel_post, etc. arguments (Filter overhaul)
- Various ConversationHandler changes and fixes
- Bot API 4.1, 4.2, 4.3 support
- Python 3.4 is no longer supported
- Error Handler now handles all types of exceptions (`#1485`_)
- Return UTC from from_timestamp() (`#1485`_)
**See the wiki page at https://git.io/fxJuV for a detailed guide on how to migrate from version 11 to version 12.**
Context based callbacks (`#1100`_)
----------------------------------
- Use of ``pass_`` in handlers is deprecated.
- Instead use ``use_context=True`` on ``Updater`` or ``Dispatcher`` and change callback from (bot, update, others...) to (update, context).
- This also applies to error handlers ``Dispatcher.add_error_handler`` and JobQueue jobs (change (bot, job) to (context) here).
- For users with custom handlers subclassing Handler, this is mostly backwards compatible, but to use the new context based callbacks you need to implement the new collect_additional_context method.
- Passing bot to ``JobQueue.__init__`` is deprecated. Use JobQueue.set_dispatcher with a dispatcher instead.
- Dispatcher makes sure to use a single `CallbackContext` for a entire update. This means that if an update is handled by multiple handlers (by using the group argument), you can add custom arguments to the `CallbackContext` in a lower group handler and use it in higher group handler. NOTE: Never use with @run_async, see docs for more info. (`#1283`_)
- If you have custom handlers they will need to be updated to support the changes in this release.
- Update all examples to use context based callbacks.
Persistence (`#1017`_)
----------------------
- Added PicklePersistence and DictPersistence for adding persistence to your bots.
- BasePersistence can be subclassed for all your persistence needs.
- Add a new example that shows a persistent ConversationHandler bot
Handler overhaul (`#1114`_)
---------------------------
- CommandHandler now only triggers on actual commands as defined by telegram servers (everything that the clients mark as a tabable link).
- PrefixHandler can be used if you need to trigger on prefixes (like all messages starting with a "/" (old CommandHandler behaviour) or even custom prefixes like "#" or "!").
Filter overhaul (`#1221`_)
--------------------------
- RegexHandler is deprecated and should be replaced with a MessageHandler with a regex filter.
- Use update filters to filter update types instead of arguments (message_updates, channel_post_updates and edited_updates) on the handlers.
- Completely remove allow_edited argument - it has been deprecated for a while.
- data_filters now exist which allows filters that return data into the callback function. This is how the regex filter is implemented.
- All this means that it no longer possible to use a list of filters in a handler. Use bitwise operators instead!
ConversationHandler
-------------------
- Remove ``run_async_timeout`` and ``timed_out_behavior`` arguments (`#1344`_)
- Replace with ``WAITING`` constant and behavior from states (`#1344`_)
- Only emit one warning for multiple CallbackQueryHandlers in a ConversationHandler (`#1319`_)
- Use warnings.warn for ConversationHandler warnings (`#1343`_)
- Fix unresolvable promises (`#1270`_)
Bug fixes & improvements
------------------------
- Handlers should be faster due to deduped logic.
- Avoid compiling compiled regex in regex filter. (`#1314`_)
- Add missing ``left_chat_member`` to Message.MESSAGE_TYPES (`#1336`_)
- Make custom timeouts actually work properly (`#1330`_)
- Add convenience classmethods (from_button, from_row and from_column) to InlineKeyboardMarkup
- Small typo fix in setup.py (`#1306`_)
- Add Conflict error (HTTP error code 409) (`#1154`_)
- Change MAX_CAPTION_LENGTH to 1024 (`#1262`_)
- Remove some unnecessary clauses (`#1247`_, `#1239`_)
- Allow filenames without dots in them when sending files (`#1228`_)
- Fix uploading files with unicode filenames (`#1214`_)
- Replace http.server with Tornado (`#1191`_)
- Allow SOCKSConnection to parse username and password from URL (`#1211`_)
- Fix for arguments in passport/data.py (`#1213`_)
- Improve message entity parsing by adding text_mention (`#1206`_)
- Documentation fixes (`#1348`_, `#1397`_, `#1436`_)
- Merged filters short-circuit (`#1350`_)
- Fix webhook listen with tornado (`#1383`_)
- Call task_done() on update queue after update processing finished (`#1428`_)
- Fix send_location() - latitude may be 0 (`#1437`_)
- Make MessageEntity objects comparable (`#1465`_)
- Add prefix to thread names (`#1358`_)
Buf fixes since v12.0.0b1
-------------------------
- Fix setting bot on ShippingQuery (`#1355`_)
- Fix _trigger_timeout() missing 1 required positional argument: 'job' (`#1367`_)
- Add missing message.text check in PrefixHandler check_update (`#1375`_)
- Make updates persist even on DispatcherHandlerStop (`#1463`_)
- Dispatcher force updating persistence object's chat data attribute(`#1462`_)
.. _`#1100`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1100
.. _`#1283`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1283
.. _`#1017`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1017
.. _`#1325`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1325
.. _`#1301`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1301
.. _`#1312`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1312
.. _`#1324`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1324
.. _`#1114`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1114
.. _`#1221`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1221
.. _`#1314`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1314
.. _`#1336`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1336
.. _`#1330`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1330
.. _`#1306`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1306
.. _`#1154`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1154
.. _`#1262`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1262
.. _`#1247`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1247
.. _`#1239`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1239
.. _`#1228`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1228
.. _`#1214`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1214
.. _`#1191`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1191
.. _`#1211`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1211
.. _`#1213`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1213
.. _`#1206`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1206
.. _`#1344`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1344
.. _`#1319`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1319
.. _`#1343`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1343
.. _`#1270`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1270
.. _`#1348`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1348
.. _`#1350`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1350
.. _`#1383`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1383
.. _`#1397`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1397
.. _`#1428`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1428
.. _`#1436`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1436
.. _`#1437`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1437
.. _`#1465`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1465
.. _`#1358`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1358
.. _`#1355`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1355
.. _`#1367`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1367
.. _`#1375`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1375
.. _`#1463`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1463
.. _`#1462`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1462
.. _`#1483`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1483
.. _`#1485`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1485
Internal improvements
---------------------
- Finally fix our CI builds mostly (too many commits and PRs to list)
- Use multiple bots for CI to improve testing times significantly.
- Allow pypy to fail in CI.
- Remove the last CamelCase CheckUpdate methods from the handlers we missed earlier.
- test_official is now executed in a different job
Version 11.1.0
==============
*Released 2018-09-01*
Fixes and updates for Telegram Passport: (`#1198`_)
- Fix passport decryption failing at random times
- Added support for middle names.
- Added support for translations for documents
- Add errors for translations for documents
- Added support for requesting names in the language of the user's country of residence
- Replaced the payload parameter with the new parameter nonce
- Add hash to EncryptedPassportElement
.. _`#1198`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1198
Version 11.0.0
==============
*Released 2018-08-29*
Fully support Bot API version 4.0!
(also some bugfixes :))
@@ -58,8 +526,9 @@ Non Bot API 4.0 changes:
.. _`#1184`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1184
.. _`our telegram passport wiki page`: https://git.io/fAvYd
**2018-05-02**
*Released 10.1.0*
Version 10.1.0
==============
*Released 2018-05-02*
Fixes changing previous behaviour:
@@ -85,8 +554,9 @@ Fixes:
.. _`#1096`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1096
.. _`#1099`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1099
**2018-04-17**
*Released 10.0.2*
Version 10.0.2
==============
*Released 2018-04-17*
Important fix:
@@ -117,8 +587,9 @@ Fixes:
.. _`#1076`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1076
.. _`#1071`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1071
**2018-03-05**
*Released 10.0.1*
Version 10.0.1
==============
*Released 2018-03-05*
Fixes:
@@ -128,8 +599,9 @@ Fixes:
.. _`#1032`: https://github.com/python-telegram-bot/python-telegram-bot/pull/826
.. _`#912`: https://github.com/python-telegram-bot/python-telegram-bot/pull/826
**2018-03-02**
*Released 10.0.0*
Version 10.0.0
==============
*Released 2018-03-02*
Non backward compatabile changes and changed defaults
@@ -194,8 +666,9 @@ Changes
.. _`#1019`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1019
.. _`#1020`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1020
**2017-12-08**
*Released 9.0.0*
Version 9.0.0
=============
*Released 2017-12-08*
Breaking changes (possibly)
@@ -220,15 +693,17 @@ Changes
.. _`#694`: https://github.com/python-telegram-bot/python-telegram-bot/pull/694
.. _`#870`: https://github.com/python-telegram-bot/python-telegram-bot/pull/870
**2017-10-15**
*Released 8.1.1*
Version 8.1.1
=============
*Released 2017-10-15*
- Fix Commandhandler crashing on single character messages (PR `#873`_).
.. _`#873`: https://github.com/python-telegram-bot/python-telegram-bot/pull/871
**2017-10-14**
*Released 8.1.0*
Version 8.1.0
=============
*Released 2017-10-14*
New features
- Support Bot API 3.4 (PR `#865`_).
@@ -245,8 +720,9 @@ Changes
.. _`#865`: https://github.com/python-telegram-bot/python-telegram-bot/pull/865
.. _`#869`: https://github.com/python-telegram-bot/python-telegram-bot/pull/869
**2017-09-01**
*Released 8.0.0*
Version 8.0.0
=============
*Released 2017-09-01*
New features
@@ -287,14 +763,16 @@ Changes
.. _`#793`: https://github.com/python-telegram-bot/python-telegram-bot/pull/793
.. _`#810`: https://github.com/python-telegram-bot/python-telegram-bot/pull/810
**2017-07-28**
*Released 7.0.1*
Version 7.0.1
===============
*Released 2017-07-28*
- Fix TypeError exception in RegexHandler (PR #751).
- Small documentation fix (PR #749).
**2017-07-25**
*Released 7.0.0*
Version 7.0.0
=============
*Released 2017-07-25*
- Fully support Bot API 3.2.
- New filters for handling messages from specific chat/user id (PR #677).
@@ -312,6 +790,9 @@ Changes
- Improved documentation.
- Improved unitests.
Pre-version 7.0
===============
**2017-06-18**
*Released 6.1.0*
+16 -17
View File
@@ -29,14 +29,9 @@ We have a vibrant community of developers helping each other in our `Telegram gr
:target: https://www.gnu.org/licenses/lgpl-3.0.html
:alt: LGPLv3 License
.. image:: https://travis-ci.org/python-telegram-bot/python-telegram-bot.svg?branch=master
:target: https://travis-ci.org/python-telegram-bot/python-telegram-bot
:alt: Travis CI Status
.. image:: https://img.shields.io/appveyor/ci/python-telegram-bot/python-telegram-bot/master.svg?logo=appveyor
:target: https://ci.appveyor.com/project/python-telegram-bot/python-telegram-bot
:alt: AppVeyor CI Status
.. image:: https://github.com/python-telegram-bot/python-telegram-bot/workflows/GitHub%20Actions/badge.svg?event=schedule
:target: https://github.com/python-telegram-bot/python-telegram-bot/
:alt: Github Actions workflow
.. image:: https://codecov.io/gh/python-telegram-bot/python-telegram-bot/branch/master/graph/badge.svg
:target: https://codecov.io/gh/python-telegram-bot/python-telegram-bot
@@ -46,6 +41,10 @@ We have a vibrant community of developers helping each other in our `Telegram gr
:target: http://isitmaintained.com/project/python-telegram-bot/python-telegram-bot
:alt: Median time to resolve an issue
.. image:: https://api.codacy.com/project/badge/Grade/99d901eaa09b44b4819aec05c330c968
:target: https://www.codacy.com/app/python-telegram-bot/python-telegram-bot?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=python-telegram-bot/python-telegram-bot&amp;utm_campaign=Badge_Grade
:alt: Code quality
.. image:: https://img.shields.io/badge/Telegram-Group-blue.svg
:target: https://telegram.me/pythontelegrambotgroup
:alt: Telegram Group
@@ -84,8 +83,7 @@ Introduction
This library provides a pure Python interface for the
`Telegram Bot API <https://core.telegram.org/bots/api>`_.
It's compatible with Python versions 2.7, 3.3+ and `PyPy <http://pypy.org/>`_.
It also works with `Google App Engine <https://cloud.google.com/appengine>`_.
It's compatible with Python versions 3.5+ and `PyPy <http://pypy.org/>`_.
In addition to the pure API implementation, this library features a number of high-level classes to
make the development of bots easy and straightforward. These classes are contained in the
@@ -95,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.0** are supported.
All types and methods of the Telegram Bot API **4.7** are supported.
==========
Installing
@@ -139,9 +137,9 @@ Other references:
Learning by example
-------------------
We believe that the best way to learn and understand this simple package is by example. So here
are some examples for you to review. Even if it's not your approach for learning, please take a
look at ``echobot2``, it is de facto the base for most of the bots out there. Best of all,
We believe that the best way to learn this package is by example. Here
are some examples for you to review. Even if it is not your approach for learning, please take a
look at ``echobot2``, it is the de facto base for most of the bots out there. Best of all,
the code for these examples are released to the public domain, so you can start by grabbing the
code and building on top of it.
@@ -189,11 +187,12 @@ You can get help in several ways:
1. We have a vibrant community of developers helping each other in our `Telegram group <https://telegram.me/pythontelegrambotgroup>`_. Join us!
2. Our `Wiki pages <https://github.com/python-telegram-bot/python-telegram-bot/wiki/>`_ offer a growing amount of resources.
2. Report bugs, request new features or ask questions by `creating an issue <https://github.com/python-telegram-bot/python-telegram-bot/issues/new/choose>`_.
3. You can ask for help on Stack Overflow using the `python-telegram-bot tag <https://stackoverflow.com/questions/tagged/python-telegram-bot>`_.
3. Our `Wiki pages <https://github.com/python-telegram-bot/python-telegram-bot/wiki/>`_ offer a growing amount of resources.
4. You can even ask for help on Stack Overflow using the `python-telegram-bot tag <https://stackoverflow.com/questions/tagged/python-telegram-bot>`_.
4. As last resort, the developers are ready to help you with `serious issues <https://github.com/python-telegram-bot/python-telegram-bot/issues/new>`_.
============
-38
View File
@@ -1,38 +0,0 @@
environment:
matrix:
# For Python versions available on Appveyor, see
# http://www.appveyor.com/docs/installed-software#python
# The list here is complete (excluding Python 2.6, which
# isn't covered by this document) at the time of writing.
- PYTHON: "C:\\Python27"
- PYTHON: "C:\\Python34"
- PYTHON: "C:\\Python35"
- PYTHON: "C:\\Python36"
branches:
only:
- master
skip_branch_with_pr: true
max_jobs: 1
install:
- "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%"
- "git submodule update --init --recursive"
# Check that we have the expected version and architecture for Python
- "python --version"
# We need wheel installed to build wheels
- "pip install -U codecov pytest-cov"
- "pip install -r requirements.txt"
- "pip install -r requirements-dev.txt"
build: off
test_script:
- "pytest -m \"not nocoverage\" --cov --cov-report xml:coverage.xml"
after_test:
- "codecov -f coverage.xml -F Appveyor"
+7
View File
@@ -0,0 +1,7 @@
#!/bin/bash
cp -R contrib/debian .
debuild -us -uc
debian/rules clean
rm -rf debian
+6
View File
@@ -0,0 +1,6 @@
telegram (12.0.0b1) unstable; urgency=medium
* Debian packaging;
* Initial Release.
-- Marco Marinello <me@marcomarinello.it> Thu, 22 Aug 2019 20:36:47 +0200
+1
View File
@@ -0,0 +1 @@
11
+25
View File
@@ -0,0 +1,25 @@
Source: telegram
Section: utils
Priority: optional
Maintainer: Marco Marinello <me@marcomarinello.it>
Build-Depends: debhelper (>= 11), dh-python, python3-all, python3-setuptools
Standards-Version: 4.1.3
Homepage: https://python-telegram-bot.org
X-Python-Version: >= 3.2
Vcs-Browser: https://github.com/python-telegram-bot/python-telegram-bot
Vcs-Git: https://github.com/python-telegram-bot/python-telegram-bot.git
Package: python3-telegram-bot
Architecture: any
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.
.
In addition to the pure API implementation, this library features
a number of high-level
classes to make the development of bots easy and straightforward.
These classes are contained in the telegram.ext submodule.
.
This package installs the library for Python 3.
+30
View File
@@ -0,0 +1,30 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: telegram
Source: https://github.com/python-telegram-bot/python-telegram-bot
Files: *
Copyright: 2019 Leandro Toledo
2019 see AUTHORS file
License: LGPLv3
Files: debian/*
Copyright: 2019 Marco Marinello <me@marcomarinello.it>
License: GPL-3.0+
License: GPL-3.0+
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
.
This package is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
.
On Debian systems, the complete text of the GNU General
Public License version 3 can be found in "/usr/share/common-licenses/GPL-3".
+1
View File
@@ -0,0 +1 @@
AUTHORS.rst /usr/share/doc/python3-telegram-bot
+18
View File
@@ -0,0 +1,18 @@
#!/usr/bin/make -f
# See debhelper(7) (uncomment to enable)
# output every command that modifies files on the build system.
#export DH_VERBOSE = 1
export PYBUILD_NAME=telegram
%:
DEB_BUILD_OPTIONS=nocheck dh $@ --with python2,python3 --buildsystem=pybuild
# If you need to rebuild the Sphinx documentation
# Add spinxdoc to the dh --with line
#override_dh_auto_build:
# dh_auto_build
# PYTHONPATH=. http_proxy='127.0.0.1:9' sphinx-build -N -bhtml docs/ build/html # HTML generator
# PYTHONPATH=. http_proxy='127.0.0.1:9' sphinx-build -N -bman docs/ build/man # Manpage generator
+1
View File
@@ -0,0 +1 @@
3.0 (native)
+1
View File
@@ -0,0 +1 @@
extend-diff-ignore = "^[^/]*[.]egg-info/"
+1 -1
View File
@@ -1,3 +1,3 @@
sphinx>=1.5.4
sphinx>=1.7.9
sphinx_rtd_theme
sphinx-pypi-upload
+1
View File
@@ -0,0 +1 @@
.. include:: ../../CHANGES.rst
+8 -4
View File
@@ -24,7 +24,7 @@ sys.path.insert(0, os.path.abspath('../..'))
# -- General configuration ------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
needs_sphinx = '1.5.4' # fixes issues with autodoc-skip-member and napoleon
needs_sphinx = '1.7.9'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
@@ -50,7 +50,7 @@ master_doc = 'index'
# General information about the project.
project = u'Python Telegram Bot'
copyright = u'2015-2018, Leandro Toledo'
copyright = u'2015-2020, Leandro Toledo'
author = u'Leandro Toledo'
# The version info for the project you're documenting, acts as replacement for
@@ -58,9 +58,9 @@ author = u'Leandro Toledo'
# built documents.
#
# The short X.Y version.
version = '11.0' # telegram.__version__[:3]
version = '12.6' # telegram.__version__[:3]
# The full version, including alpha/beta/rc tags.
release = '11.0.0' # telegram.__version__
release = '12.6' # telegram.__version__
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
@@ -289,6 +289,10 @@ texinfo_documents = [
# If true, do not generate a @detailmenu in the "Top" node's menu.
#texinfo_no_detailmenu = False
# Napoleon stuff
napoleon_use_admonition_for_examples = True
# -- script stuff --------------------------------------------------------
+20 -9
View File
@@ -6,8 +6,23 @@
Welcome to Python Telegram Bot's documentation!
===============================================
Below you can find the documentation for the python-telegram-bot library. except for the .ext package most of the
objects in the package reflect the types as defined by the `telegram bot api <https://core.telegram.org/bots/api>`_.
Guides and tutorials
====================
If you're just starting out with the library, we recommend following our `"Your first Bot" <https://github.com/python-telegram-bot/python-telegram-bot/wiki/Extensions-%E2%80%93-Your-first-Bot>`_ tutorial that you can find on our `wiki <https://github.com/python-telegram-bot/python-telegram-bot/wiki>`_.
On our wiki you will also find guides like how to use handlers, webhooks, emoji, proxies and much more.
Examples
========
A great way to learn is by looking at examples. Ours can be found at our `github in the examples folder <https://github.com/python-telegram-bot/python-telegram-bot/tree/master/examples>`_.
Reference
=========
Below you can find a reference of all the classes and methods in python-telegram-bot.
Apart from the `telegram.ext` package the objects should reflect the types defined in the `official telegram bot api documentation <https://core.telegram.org/bots/api>`_.
.. toctree::
telegram
@@ -15,13 +30,9 @@ objects in the package reflect the types as defined by the `telegram bot api <ht
Changelog
---------
.. include:: ../../CHANGES.rst
.. toctree::
:maxdepth: 2
Indices and tables
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`
changelog
+6
View File
@@ -0,0 +1,6 @@
telegram.BotCommand
===================
.. autoclass:: telegram.BotCommand
:members:
:show-inheritance:
+6
View File
@@ -0,0 +1,6 @@
telegram.ChatPermissions
========================
.. autoclass:: telegram.ChatPermissions
:members:
:show-inheritance:
+6
View File
@@ -0,0 +1,6 @@
telegram.Dice
=============
.. autoclass:: telegram.Dice
:members:
:show-inheritance:
@@ -0,0 +1,6 @@
telegram.ext.BasePersistence
============================
.. autoclass:: telegram.ext.BasePersistence
:members:
:show-inheritance:
@@ -0,0 +1,5 @@
telegram.ext.CallbackContext
============================
.. autoclass:: telegram.ext.CallbackContext
:members:
+6
View File
@@ -0,0 +1,6 @@
telegram.ext.Defaults
=====================
.. autoclass:: telegram.ext.Defaults
:members:
:show-inheritance:
@@ -0,0 +1,6 @@
telegram.ext.DictPersistence
============================
.. autoclass:: telegram.ext.DictPersistence
:members:
:show-inheritance:
@@ -0,0 +1,6 @@
telegram.ext.DispatcherHandlerStop
==================================
.. autoclass:: telegram.ext.DispatcherHandlerStop
:members:
:show-inheritance:
@@ -0,0 +1,6 @@
telegram.ext.PicklePersistence
==============================
.. autoclass:: telegram.ext.PicklePersistence
:members:
:show-inheritance:
@@ -0,0 +1,6 @@
telegram.ext.PollAnswerHandler
==============================
.. autoclass:: telegram.ext.PollAnswerHandler
:members:
:show-inheritance:
+6
View File
@@ -0,0 +1,6 @@
telegram.ext.PollHandler
========================
.. autoclass:: telegram.ext.PollHandler
:members:
:show-inheritance:
@@ -0,0 +1,6 @@
telegram.ext.PrefixHandler
===========================
.. autoclass:: telegram.ext.PrefixHandler
:members:
:show-inheritance:
+15
View File
@@ -5,11 +5,14 @@ telegram.ext package
telegram.ext.updater
telegram.ext.dispatcher
telegram.ext.dispatcherhandlerstop
telegram.ext.filters
telegram.ext.job
telegram.ext.jobqueue
telegram.ext.messagequeue
telegram.ext.delayqueue
telegram.ext.callbackcontext
telegram.ext.defaults
Handlers
--------
@@ -23,9 +26,21 @@ Handlers
telegram.ext.commandhandler
telegram.ext.inlinequeryhandler
telegram.ext.messagehandler
telegram.ext.pollanswerhandler
telegram.ext.pollhandler
telegram.ext.precheckoutqueryhandler
telegram.ext.prefixhandler
telegram.ext.regexhandler
telegram.ext.shippingqueryhandler
telegram.ext.stringcommandhandler
telegram.ext.stringregexhandler
telegram.ext.typehandler
Persistence
-----------
.. toctree::
telegram.ext.basepersistence
telegram.ext.picklepersistence
telegram.ext.dictpersistence
@@ -0,0 +1,6 @@
telegram.KeyboardButtonPollType
===============================
.. autoclass:: telegram.KeyboardButtonPollType
:members:
:show-inheritance:
+6
View File
@@ -0,0 +1,6 @@
telegram.LoginUrl
=================
.. autoclass:: telegram.LoginUrl
:members:
:show-inheritance:
+6
View File
@@ -0,0 +1,6 @@
telegram.Poll
=============
.. autoclass:: telegram.Poll
:members:
:show-inheritance:
+6
View File
@@ -0,0 +1,6 @@
telegram.PollAnswer
===================
.. autoclass:: telegram.PollAnswer
:members:
:show-inheritance:
+6
View File
@@ -0,0 +1,6 @@
telegram.PollOption
===================
.. autoclass:: telegram.PollOption
:members:
:show-inheritance:
+95 -94
View File
@@ -1,117 +1,126 @@
.. include:: telegram.ext.rst
telegram package
================
.. toctree::
telegram.ext
telegram.utils
telegram.animation
telegram.audio
telegram.bot
telegram.callbackquery
telegram.chat
telegram.chataction
telegram.chatmember
telegram.chatphoto
telegram.constants
telegram.contact
telegram.document
telegram.error
telegram.file
telegram.forcereply
telegram.inlinekeyboardbutton
telegram.inlinekeyboardmarkup
telegram.inputfile
telegram.inputmedia
telegram.inputmediaanimation
telegram.inputmediaaudio
telegram.inputmediadocument
telegram.inputmediaphoto
telegram.inputmediavideo
telegram.keyboardbutton
telegram.location
telegram.message
telegram.messageentity
telegram.parsemode
telegram.photosize
telegram.replykeyboardremove
telegram.replykeyboardmarkup
telegram.replymarkup
telegram.telegramobject
telegram.update
telegram.user
telegram.userprofilephotos
telegram.venue
telegram.video
telegram.videonote
telegram.voice
telegram.webhookinfo
telegram.animation
telegram.audio
telegram.bot
telegram.botcommand
telegram.callbackquery
telegram.chat
telegram.chataction
telegram.chatmember
telegram.chatpermissions
telegram.chatphoto
telegram.constants
telegram.contact
telegram.dice
telegram.document
telegram.error
telegram.file
telegram.forcereply
telegram.inlinekeyboardbutton
telegram.inlinekeyboardmarkup
telegram.inputfile
telegram.inputmedia
telegram.inputmediaanimation
telegram.inputmediaaudio
telegram.inputmediadocument
telegram.inputmediaphoto
telegram.inputmediavideo
telegram.keyboardbutton
telegram.keyboardbuttonpolltype
telegram.location
telegram.loginurl
telegram.message
telegram.messageentity
telegram.parsemode
telegram.photosize
telegram.poll
telegram.pollanswer
telegram.polloption
telegram.replykeyboardremove
telegram.replykeyboardmarkup
telegram.replymarkup
telegram.telegramobject
telegram.update
telegram.user
telegram.userprofilephotos
telegram.venue
telegram.video
telegram.videonote
telegram.voice
telegram.webhookinfo
Stickers
--------
.. toctree::
telegram.sticker
telegram.stickerset
telegram.maskposition
telegram.sticker
telegram.stickerset
telegram.maskposition
Inline Mode
-----------
.. toctree::
telegram.inlinequery
telegram.inlinequeryresult
telegram.inlinequeryresultarticle
telegram.inlinequeryresultaudio
telegram.inlinequeryresultcachedaudio
telegram.inlinequeryresultcacheddocument
telegram.inlinequeryresultcachedgif
telegram.inlinequeryresultcachedmpeg4gif
telegram.inlinequeryresultcachedphoto
telegram.inlinequeryresultcachedsticker
telegram.inlinequeryresultcachedvideo
telegram.inlinequeryresultcachedvoice
telegram.inlinequeryresultcontact
telegram.inlinequeryresultdocument
telegram.inlinequeryresultgame
telegram.inlinequeryresultgif
telegram.inlinequeryresultlocation
telegram.inlinequeryresultmpeg4gif
telegram.inlinequeryresultphoto
telegram.inlinequeryresultvenue
telegram.inlinequeryresultvideo
telegram.inlinequeryresultvoice
telegram.inputmessagecontent
telegram.inputtextmessagecontent
telegram.inputlocationmessagecontent
telegram.inputvenuemessagecontent
telegram.inputcontactmessagecontent
telegram.choseninlineresult
telegram.inlinequery
telegram.inlinequeryresult
telegram.inlinequeryresultarticle
telegram.inlinequeryresultaudio
telegram.inlinequeryresultcachedaudio
telegram.inlinequeryresultcacheddocument
telegram.inlinequeryresultcachedgif
telegram.inlinequeryresultcachedmpeg4gif
telegram.inlinequeryresultcachedphoto
telegram.inlinequeryresultcachedsticker
telegram.inlinequeryresultcachedvideo
telegram.inlinequeryresultcachedvoice
telegram.inlinequeryresultcontact
telegram.inlinequeryresultdocument
telegram.inlinequeryresultgame
telegram.inlinequeryresultgif
telegram.inlinequeryresultlocation
telegram.inlinequeryresultmpeg4gif
telegram.inlinequeryresultphoto
telegram.inlinequeryresultvenue
telegram.inlinequeryresultvideo
telegram.inlinequeryresultvoice
telegram.inputmessagecontent
telegram.inputtextmessagecontent
telegram.inputlocationmessagecontent
telegram.inputvenuemessagecontent
telegram.inputcontactmessagecontent
telegram.choseninlineresult
Payments
--------
.. toctree::
telegram.labeledprice
telegram.invoice
telegram.shippingaddress
telegram.orderinfo
telegram.shippingoption
telegram.successfulpayment
telegram.shippingquery
telegram.precheckoutquery
telegram.labeledprice
telegram.invoice
telegram.shippingaddress
telegram.orderinfo
telegram.shippingoption
telegram.successfulpayment
telegram.shippingquery
telegram.precheckoutquery
Games
-----
.. toctree::
telegram.game
telegram.callbackgame
telegram.gamehighscore
telegram.game
telegram.callbackgame
telegram.gamehighscore
Passport
--------
@@ -137,12 +146,4 @@ Passport
telegram.encryptedpassportelement
telegram.encryptedcredentials
Module contents
---------------
.. automodule:: telegram
:members:
:undoc-members:
:show-inheritance:
:noindex:
.. include:: telegram.utils.rst
+11 -2
View File
@@ -1,6 +1,6 @@
# Examples
In this folder there are small examples to show what a bot written with `python-telegram-bot` looks like. Some bots focus on one specific aspect of the Telegram Bot API while others focus on one of the mechanics of this library. Except for the [`echobot.py`](#pure-api) example, they all use the high-level framework this library provides with the [`telegram.ext`](https://python-telegram-bot.readthedocs.io/en/latest/telegram.ext.html) submodule.
In this folder are small examples to show what a bot written with `python-telegram-bot` looks like. Some bots focus on one specific aspect of the Telegram Bot API while others focus on one of the mechanics of this library. Except for the [`echobot.py`](#pure-api) example, they all use the high-level framework this library provides with the [`telegram.ext`](https://python-telegram-bot.readthedocs.io/en/latest/telegram.ext.html) submodule.
All examples are licensed under the [CC0 License](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/LICENSE.txt) and are therefore fully dedicated to the public domain. You can use them as the base for your own bots without worrying about copyrights.
@@ -16,14 +16,23 @@ A common task for a bot is to ask information from the user. In v5.0 of this lib
### [`conversationbot2.py`](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/conversationbot2.py)
A more complex example of a bot that uses the `ConversationHandler`. It is also more confusing. Good thing there is a [fancy state diagram](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/conversationbot2.png) for this one, too!
### [`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!
### [`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.
### [`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).
### [`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.
## 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.
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.
+21 -22
View File
@@ -1,11 +1,8 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Simple Bot to reply to Telegram messages
# This program is dedicated to the public domain under the CC0 license.
"""
This Bot uses the Updater class to handle the bot.
"""
First, a few callback functions are defined. Then, those functions are passed to
the Dispatcher and registered at their respective places.
Then, the bot is started and runs until we press Ctrl-C on the command line.
@@ -17,12 +14,12 @@ Press Ctrl-C on the command line or send a signal to the process to stop the
bot.
"""
from telegram import (ReplyKeyboardMarkup, ReplyKeyboardRemove)
from telegram.ext import (Updater, CommandHandler, MessageHandler, Filters, RegexHandler,
ConversationHandler)
import logging
from telegram import (ReplyKeyboardMarkup, ReplyKeyboardRemove)
from telegram.ext import (Updater, CommandHandler, MessageHandler, Filters,
ConversationHandler)
# Enable logging
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
level=logging.INFO)
@@ -32,7 +29,7 @@ logger = logging.getLogger(__name__)
GENDER, PHOTO, LOCATION, BIO = range(4)
def start(bot, update):
def start(update, context):
reply_keyboard = [['Boy', 'Girl', 'Other']]
update.message.reply_text(
@@ -44,7 +41,7 @@ def start(bot, update):
return GENDER
def gender(bot, update):
def gender(update, context):
user = update.message.from_user
logger.info("Gender of %s: %s", user.first_name, update.message.text)
update.message.reply_text('I see! Please send me a photo of yourself, '
@@ -54,9 +51,9 @@ def gender(bot, update):
return PHOTO
def photo(bot, update):
def photo(update, context):
user = update.message.from_user
photo_file = bot.get_file(update.message.photo[-1].file_id)
photo_file = update.message.photo[-1].get_file()
photo_file.download('user_photo.jpg')
logger.info("Photo of %s: %s", user.first_name, 'user_photo.jpg')
update.message.reply_text('Gorgeous! Now, send me your location please, '
@@ -65,7 +62,7 @@ def photo(bot, update):
return LOCATION
def skip_photo(bot, update):
def skip_photo(update, context):
user = update.message.from_user
logger.info("User %s did not send a photo.", user.first_name)
update.message.reply_text('I bet you look great! Now, send me your location please, '
@@ -74,7 +71,7 @@ def skip_photo(bot, update):
return LOCATION
def location(bot, update):
def location(update, context):
user = update.message.from_user
user_location = update.message.location
logger.info("Location of %s: %f / %f", user.first_name, user_location.latitude,
@@ -85,7 +82,7 @@ def location(bot, update):
return BIO
def skip_location(bot, update):
def skip_location(update, context):
user = update.message.from_user
logger.info("User %s did not send a location.", user.first_name)
update.message.reply_text('You seem a bit paranoid! '
@@ -94,7 +91,7 @@ def skip_location(bot, update):
return BIO
def bio(bot, update):
def bio(update, context):
user = update.message.from_user
logger.info("Bio of %s: %s", user.first_name, update.message.text)
update.message.reply_text('Thank you! I hope we can talk again some day.')
@@ -102,7 +99,7 @@ def bio(bot, update):
return ConversationHandler.END
def cancel(bot, update):
def cancel(update, context):
user = update.message.from_user
logger.info("User %s canceled the conversation.", user.first_name)
update.message.reply_text('Bye! I hope we can talk again some day.',
@@ -111,14 +108,16 @@ def cancel(bot, update):
return ConversationHandler.END
def error(bot, update, error):
def error(update, context):
"""Log Errors caused by Updates."""
logger.warning('Update "%s" caused error "%s"', update, error)
logger.warning('Update "%s" caused error "%s"', update, context.error)
def main():
# Create the EventHandler and pass it your bot's token.
updater = Updater("TOKEN")
# 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("TOKEN", use_context=True)
# Get the dispatcher to register handlers
dp = updater.dispatcher
@@ -128,7 +127,7 @@ def main():
entry_points=[CommandHandler('start', start)],
states={
GENDER: [RegexHandler('^(Boy|Girl|Other)$', gender)],
GENDER: [MessageHandler(Filters.regex('^(Boy|Girl|Other)$'), gender)],
PHOTO: [MessageHandler(Filters.photo, photo),
CommandHandler('skip', skip_photo)],
+29 -31
View File
@@ -1,11 +1,8 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Simple Bot to reply to Telegram messages
# This program is dedicated to the public domain under the CC0 license.
"""
This Bot uses the Updater class to handle the bot.
"""
First, a few callback functions are defined. Then, those functions are passed to
the Dispatcher and registered at their respective places.
Then, the bot is started and runs until we press Ctrl-C on the command line.
@@ -17,12 +14,12 @@ Press Ctrl-C on the command line or send a signal to the process to stop the
bot.
"""
from telegram import ReplyKeyboardMarkup
from telegram.ext import (Updater, CommandHandler, MessageHandler, Filters, RegexHandler,
ConversationHandler)
import logging
from telegram import ReplyKeyboardMarkup
from telegram.ext import (Updater, CommandHandler, MessageHandler, Filters,
ConversationHandler)
# Enable logging
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
level=logging.INFO)
@@ -46,7 +43,7 @@ def facts_to_str(user_data):
return "\n".join(facts).join(['\n', '\n'])
def start(bot, update):
def start(update, context):
update.message.reply_text(
"Hi! My name is Doctor Botter. I will hold a more complex conversation with you. "
"Why don't you tell me something about yourself?",
@@ -55,37 +52,39 @@ def start(bot, update):
return CHOOSING
def regular_choice(bot, update, user_data):
def regular_choice(update, context):
text = update.message.text
user_data['choice'] = text
context.user_data['choice'] = text
update.message.reply_text(
'Your {}? Yes, I would love to hear about that!'.format(text.lower()))
return TYPING_REPLY
def custom_choice(bot, update):
def custom_choice(update, context):
update.message.reply_text('Alright, please send me the category first, '
'for example "Most impressive skill"')
return TYPING_CHOICE
def received_information(bot, update, user_data):
def received_information(update, context):
user_data = context.user_data
text = update.message.text
category = user_data['choice']
user_data[category] = text
del user_data['choice']
update.message.reply_text("Neat! Just so you know, this is what you already told me:"
"{}"
"You can tell me more, or change your opinion on something.".format(
facts_to_str(user_data)), reply_markup=markup)
"{} You can tell me more, or change your opinion"
" on something.".format(facts_to_str(user_data)),
reply_markup=markup)
return CHOOSING
def done(bot, update, user_data):
def done(update, context):
user_data = context.user_data
if 'choice' in user_data:
del user_data['choice']
@@ -97,42 +96,41 @@ def done(bot, update, user_data):
return ConversationHandler.END
def error(bot, update, error):
def error(update, context):
"""Log Errors caused by Updates."""
logger.warning('Update "%s" caused error "%s"', update, error)
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")
# 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("TOKEN", use_context=True)
# Get the dispatcher to register handlers
dp = updater.dispatcher
# Add conversation handler with the states GENDER, PHOTO, LOCATION and BIO
# Add conversation handler with the states CHOOSING, TYPING_CHOICE and TYPING_REPLY
conv_handler = ConversationHandler(
entry_points=[CommandHandler('start', start)],
states={
CHOOSING: [RegexHandler('^(Age|Favourite colour|Number of siblings)$',
regular_choice,
pass_user_data=True),
RegexHandler('^Something else...$',
custom_choice),
CHOOSING: [MessageHandler(Filters.regex('^(Age|Favourite colour|Number of siblings)$'),
regular_choice),
MessageHandler(Filters.regex('^Something else...$'),
custom_choice)
],
TYPING_CHOICE: [MessageHandler(Filters.text,
regular_choice,
pass_user_data=True),
regular_choice)
],
TYPING_REPLY: [MessageHandler(Filters.text,
received_information,
pass_user_data=True),
received_information),
],
},
fallbacks=[RegexHandler('^Done$', done, pass_user_data=True)]
fallbacks=[MessageHandler(Filters.regex('^Done$'), done)]
)
dp.add_handler(conv_handler)
+119
View File
@@ -0,0 +1,119 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""Bot that explains Telegram's "Deep Linking Parameters" functionality.
This program is dedicated to the public domain under the CC0 license.
This Bot uses the Updater class to handle the bot.
First, a few handler functions are defined. Then, those functions are passed to
the Dispatcher and registered at their respective places.
Then, the bot is started and runs until we press Ctrl-C on the command line.
Usage:
Deep Linking example. Send /start to get the link.
Press Ctrl-C on the command line or send a signal to the process to stop the
bot.
"""
import logging
from telegram import ParseMode, InlineKeyboardMarkup, InlineKeyboardButton
from telegram.ext import Updater, CommandHandler, Filters
# Enable logging
from telegram.utils import helpers
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
level=logging.INFO)
logger = logging.getLogger(__name__)
# Define constants that will allow us to reuse the deep-linking parameters.
CHECK_THIS_OUT = 'check-this-out'
USING_ENTITIES = 'using-entities-here'
SO_COOL = 'so-cool'
def start(update, context):
"""Send a deep-linked URL when the command /start is issued."""
bot = context.bot
url = helpers.create_deep_linked_url(bot.get_me().username, CHECK_THIS_OUT, group=True)
text = "Feel free to tell your friends about it:\n\n" + url
update.message.reply_text(text)
def deep_linked_level_1(update, context):
"""Reached through the CHECK_THIS_OUT payload"""
bot = context.bot
url = helpers.create_deep_linked_url(bot.get_me().username, SO_COOL)
text = "Awesome, you just accessed hidden functionality! " \
" Now let's get back to the private chat."
keyboard = InlineKeyboardMarkup.from_button(
InlineKeyboardButton(text='Continue here!', url=url)
)
update.message.reply_text(text, reply_markup=keyboard)
def deep_linked_level_2(update, context):
"""Reached through the SO_COOL payload"""
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)
update.message.reply_text(text, parse_mode=ParseMode.MARKDOWN, disable_web_page_preview=True)
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)
def main():
"""Start the bot."""
# Create the Updater and pass it your bot's token.
updater = Updater("TOKEN", use_context=True)
# Get the dispatcher to register handlers
dp = updater.dispatcher
# More info on what deep linking actually is (read this first if it's unclear to you):
# https://core.telegram.org/bots#deep-linking
# Register a deep-linking handler
dp.add_handler(CommandHandler("start", deep_linked_level_1, Filters.regex(CHECK_THIS_OUT)))
# This one works with a textual link instead of an URL
dp.add_handler(CommandHandler("start", deep_linked_level_2, Filters.regex(SO_COOL)))
# We can also pass on the deep-linking payload
dp.add_handler(CommandHandler("start",
deep_linked_level_3,
Filters.regex(USING_ENTITIES),
pass_args=True))
# 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()
# 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()
+16 -15
View File
@@ -1,11 +1,9 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# This program is dedicated to the public domain under the CC0 license.
"""Simple Bot to reply to Telegram messages.
This program is dedicated to the public domain under the CC0 license.
This Bot uses the Updater class to handle the bot.
"""
Simple Bot to reply to Telegram messages.
First, a few handler functions are defined. Then, those functions are passed to
the Dispatcher and registered at their respective places.
@@ -17,9 +15,10 @@ Press Ctrl-C on the command line or send a signal to the process to stop the
bot.
"""
from telegram.ext import Updater, CommandHandler, MessageHandler, Filters
import logging
from telegram.ext import Updater, CommandHandler, MessageHandler, Filters
# Enable logging
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
level=logging.INFO)
@@ -27,32 +26,34 @@ logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s
logger = logging.getLogger(__name__)
# Define a few command handlers. These usually take the two arguments bot and
# update. Error handlers also receive the raised TelegramError object in error.
def start(bot, update):
# Define a few command handlers. These usually take the two arguments update and
# context. Error handlers also receive the raised TelegramError object in error.
def start(update, context):
"""Send a message when the command /start is issued."""
update.message.reply_text('Hi!')
def help(bot, update):
def help(update, context):
"""Send a message when the command /help is issued."""
update.message.reply_text('Help!')
def echo(bot, update):
def echo(update, context):
"""Echo the user message."""
update.message.reply_text(update.message.text)
def error(bot, update, error):
def error(update, context):
"""Log Errors caused by Updates."""
logger.warning('Update "%s" caused error "%s"', update, error)
logger.warning('Update "%s" caused error "%s"', update, context.error)
def main():
"""Start the bot."""
# Create the EventHandler and pass it your bot's token.
updater = Updater("TOKEN")
# 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("TOKEN", use_context=True)
# Get the dispatcher to register handlers
dp = updater.dispatcher
+14 -17
View File
@@ -1,12 +1,8 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# This program is dedicated to the public domain under the CC0 license.
"""Simple Bot to reply to Telegram messages.
This program is dedicated to the public domain under the CC0 license.
This Bot uses the Updater class to handle the bot.
"""
First, a few handler functions are defined. Then, those functions are passed to
the Dispatcher and registered at their respective places.
Then, the bot is started and runs until we press Ctrl-C on the command line.
@@ -16,14 +12,13 @@ Basic inline bot example. Applies different text transformations.
Press Ctrl-C on the command line or send a signal to the process to stop the
bot.
"""
import logging
from uuid import uuid4
from telegram.utils.helpers import escape_markdown
from telegram import InlineQueryResultArticle, ParseMode, \
InputTextMessageContent
from telegram.ext import Updater, InlineQueryHandler, CommandHandler
import logging
from telegram.utils.helpers import escape_markdown
# Enable logging
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
@@ -32,19 +27,19 @@ logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s
logger = logging.getLogger(__name__)
# Define a few command handlers. These usually take the two arguments bot and
# update. Error handlers also receive the raised TelegramError object in error.
def start(bot, update):
# Define a few command handlers. These usually take the two arguments update and
# context. Error handlers also receive the raised TelegramError object in error.
def start(update, context):
"""Send a message when the command /start is issued."""
update.message.reply_text('Hi!')
def help(bot, update):
def help(update, context):
"""Send a message when the command /help is issued."""
update.message.reply_text('Help!')
def inlinequery(bot, update):
def inlinequery(update, context):
"""Handle the inline query."""
query = update.inline_query.query
results = [
@@ -69,14 +64,16 @@ def inlinequery(bot, update):
update.inline_query.answer(results)
def error(bot, update, error):
def error(update, context):
"""Log Errors caused by Updates."""
logger.warning('Update "%s" caused error "%s"', update, error)
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")
# 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("TOKEN", use_context=True)
# Get the dispatcher to register handlers
dp = updater.dispatcher
+17 -11
View File
@@ -1,10 +1,12 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""Basic example for a bot that uses inline keyboards.
# This program is dedicated to the public domain under the CC0 license.
"""
Basic example for a bot that uses inline keyboards.
"""
import logging
from telegram import InlineKeyboardButton, InlineKeyboardMarkup
from telegram.ext import Updater, CommandHandler, CallbackQueryHandler
@@ -13,7 +15,7 @@ logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s
logger = logging.getLogger(__name__)
def start(bot, update):
def start(update, context):
keyboard = [[InlineKeyboardButton("Option 1", callback_data='1'),
InlineKeyboardButton("Option 2", callback_data='2')],
@@ -24,26 +26,30 @@ def start(bot, update):
update.message.reply_text('Please choose:', reply_markup=reply_markup)
def button(bot, update):
def button(update, context):
query = update.callback_query
bot.edit_message_text(text="Selected option: {}".format(query.data),
chat_id=query.message.chat_id,
message_id=query.message.message_id)
# CallbackQueries need to be answered, even if no notification to the user is needed
# Some clients may have trouble otherwise. See https://core.telegram.org/bots/api#callbackquery
query.answer()
query.edit_message_text(text="Selected option: {}".format(query.data))
def help(bot, update):
def help(update, context):
update.message.reply_text("Use /start to test this bot.")
def error(bot, update, error):
def error(update, context):
"""Log Errors caused by Updates."""
logger.warning('Update "%s" caused error "%s"', update, error)
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")
# 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("TOKEN", use_context=True)
updater.dispatcher.add_handler(CommandHandler('start', start))
updater.dispatcher.add_handler(CallbackQueryHandler(button))
+200
View File
@@ -0,0 +1,200 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""Simple inline keyboard bot with multiple CallbackQueryHandlers.
This Bot uses the Updater class to handle the bot.
First, a few callback functions are defined as callback query handler. Then, those functions are
passed to the Dispatcher and registered at their respective places.
Then, the bot is started and runs until we press Ctrl-C on the command line.
Usage:
Example of a bot that uses inline keyboard that has multiple CallbackQueryHandlers arranged in a
ConversationHandler.
Send /start to initiate the conversation.
Press Ctrl-C on the command line to stop the bot.
"""
from telegram import InlineKeyboardButton, InlineKeyboardMarkup
from telegram.ext import Updater, CommandHandler, CallbackQueryHandler, ConversationHandler
import logging
# Enable logging
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
level=logging.INFO)
logger = logging.getLogger(__name__)
# Stages
FIRST, SECOND = range(2)
# Callback data
ONE, TWO, THREE, FOUR = range(4)
def start(update, context):
"""Send message on `/start`."""
# Get user that sent /start and log his name
user = update.message.from_user
logger.info("User %s started the conversation.", user.first_name)
# Build InlineKeyboard where each button has a displayed text
# and a string as callback_data
# The keyboard is a list of button rows, where each row is in turn
# a list (hence `[[...]]`).
keyboard = [
[InlineKeyboardButton("1", callback_data=str(ONE)),
InlineKeyboardButton("2", callback_data=str(TWO))]
]
reply_markup = InlineKeyboardMarkup(keyboard)
# Send message with text and appended InlineKeyboard
update.message.reply_text(
"Start handler, Choose a route",
reply_markup=reply_markup
)
# Tell ConversationHandler that we're in state `FIRST` now
return FIRST
def start_over(update, context):
"""Prompt same text & keyboard as `start` does but not as new message"""
# Get CallbackQuery from Update
query = update.callback_query
# CallbackQueries need to be answered, even if no notification to the user is needed
# Some clients may have trouble otherwise. See https://core.telegram.org/bots/api#callbackquery
query.answer()
keyboard = [
[InlineKeyboardButton("1", callback_data=str(ONE)),
InlineKeyboardButton("2", callback_data=str(TWO))]
]
reply_markup = InlineKeyboardMarkup(keyboard)
# Instead of sending a new message, edit the message that
# originated the CallbackQuery. This gives the feeling of an
# interactive menu.
query.edit_message_text(
text="Start handler, Choose a route",
reply_markup=reply_markup
)
return FIRST
def one(update, context):
"""Show new choice of buttons"""
query = update.callback_query
query.answer()
keyboard = [
[InlineKeyboardButton("3", callback_data=str(THREE)),
InlineKeyboardButton("4", callback_data=str(FOUR))]
]
reply_markup = InlineKeyboardMarkup(keyboard)
query.edit_message_text(
text="First CallbackQueryHandler, Choose a route",
reply_markup=reply_markup
)
return FIRST
def two(update, context):
"""Show new choice of buttons"""
query = update.callback_query
query.answer()
keyboard = [
[InlineKeyboardButton("1", callback_data=str(ONE)),
InlineKeyboardButton("3", callback_data=str(THREE))]
]
reply_markup = InlineKeyboardMarkup(keyboard)
query.edit_message_text(
text="Second CallbackQueryHandler, Choose a route",
reply_markup=reply_markup
)
return FIRST
def three(update, context):
"""Show new choice of buttons"""
query = update.callback_query
query.answer()
keyboard = [
[InlineKeyboardButton("Yes, let's do it again!", callback_data=str(ONE)),
InlineKeyboardButton("Nah, I've had enough ...", callback_data=str(TWO))]
]
reply_markup = InlineKeyboardMarkup(keyboard)
query.edit_message_text(
text="Third CallbackQueryHandler. Do want to start over?",
reply_markup=reply_markup
)
# Transfer to conversation state `SECOND`
return SECOND
def four(update, context):
"""Show new choice of buttons"""
query = update.callback_query
query.answer()
keyboard = [
[InlineKeyboardButton("2", callback_data=str(TWO)),
InlineKeyboardButton("4", callback_data=str(FOUR))]
]
reply_markup = InlineKeyboardMarkup(keyboard)
query.edit_message_text(
text="Fourth CallbackQueryHandler, Choose a route",
reply_markup=reply_markup
)
return FIRST
def end(update, context):
"""Returns `ConversationHandler.END`, which tells the
ConversationHandler that the conversation is over"""
query = update.callback_query
query.answer()
query.edit_message_text(
text="See you next time!"
)
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)
# Get the dispatcher to register handlers
dp = updater.dispatcher
# Setup conversation handler with the states FIRST and SECOND
# Use the pattern parameter to pass CallbackQueries with specific
# data pattern to the corresponding handlers.
# ^ means "start of line/string"
# $ means "end of line/string"
# So ^ABC$ will only allow 'ABC'
conv_handler = ConversationHandler(
entry_points=[CommandHandler('start', start)],
states={
FIRST: [CallbackQueryHandler(one, pattern='^' + str(ONE) + '$'),
CallbackQueryHandler(two, pattern='^' + str(TWO) + '$'),
CallbackQueryHandler(three, pattern='^' + str(THREE) + '$'),
CallbackQueryHandler(four, pattern='^' + str(FOUR) + '$')],
SECOND: [CallbackQueryHandler(start_over, pattern='^' + str(ONE) + '$'),
CallbackQueryHandler(end, pattern='^' + str(TWO) + '$')]
},
fallbacks=[CommandHandler('start', start)]
)
# Add ConversationHandler to dispatcher that will be used for handling
# updates
dp.add_handler(conv_handler)
# log all errors
dp.add_error_handler(error)
# Start the Bot
updater.start_polling()
# Run the bot until you press Ctrl-C or the process receives SIGINT,
# SIGTERM or SIGABRT. This should be used most of the time, since
# start_polling() is non-blocking and will stop the bot gracefully.
updater.idle()
if __name__ == '__main__':
main()
Binary file not shown.

After

Width:  |  Height:  |  Size: 492 KiB

+374
View File
@@ -0,0 +1,374 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# This program is dedicated to the public domain under the CC0 license.
"""
First, a few callback functions are defined. Then, those functions are passed to
the Dispatcher and registered at their respective places.
Then, the bot is started and runs until we press Ctrl-C on the command line.
Usage:
Example of a bot-user conversation using nested ConversationHandlers.
Send /start to initiate the conversation.
Press Ctrl-C on the command line or send a signal to the process to stop the
bot.
"""
import logging
from telegram import (InlineKeyboardMarkup, InlineKeyboardButton)
from telegram.ext import (Updater, CommandHandler, MessageHandler, Filters,
ConversationHandler, CallbackQueryHandler)
# Enable logging
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
level=logging.INFO)
logger = logging.getLogger(__name__)
# State definitions for top level conversation
SELECTING_ACTION, ADDING_MEMBER, ADDING_SELF, DESCRIBING_SELF = map(chr, range(4))
# State definitions for second level conversation
SELECTING_LEVEL, SELECTING_GENDER = map(chr, range(4, 6))
# State definitions for descriptions conversation
SELECTING_FEATURE, TYPING = map(chr, range(6, 8))
# Meta states
STOPPING, SHOWING = map(chr, range(8, 10))
# Shortcut for ConversationHandler.END
END = ConversationHandler.END
# Different constants for this example
(PARENTS, CHILDREN, SELF, GENDER, MALE, FEMALE, AGE, NAME, START_OVER, FEATURES,
CURRENT_FEATURE, CURRENT_LEVEL) = map(chr, range(10, 22))
# Helper
def _name_switcher(level):
if level == PARENTS:
return ('Father', 'Mother')
elif level == CHILDREN:
return ('Brother', 'Sister')
# Top level conversation callbacks
def start(update, context):
"""Select an action: Adding parent/child or show data."""
text = 'You may add a familiy member, yourself show the gathered data or end the ' \
'conversation. To abort, simply type /stop.'
buttons = [[
InlineKeyboardButton(text='Add family member', callback_data=str(ADDING_MEMBER)),
InlineKeyboardButton(text='Add yourself', callback_data=str(ADDING_SELF))
], [
InlineKeyboardButton(text='Show data', callback_data=str(SHOWING)),
InlineKeyboardButton(text='Done', callback_data=str(END))
]]
keyboard = InlineKeyboardMarkup(buttons)
# If we're starting over we don't need do send a new message
if context.user_data.get(START_OVER):
update.callback_query.answer()
update.callback_query.edit_message_text(text=text, reply_markup=keyboard)
else:
update.message.reply_text('Hi, I\'m FamiliyBot and here to help you gather information'
'about your family.')
update.message.reply_text(text=text, reply_markup=keyboard)
context.user_data[START_OVER] = False
return SELECTING_ACTION
def adding_self(update, context):
"""Add information about youself."""
context.user_data[CURRENT_LEVEL] = SELF
text = 'Okay, please tell me about yourself.'
button = InlineKeyboardButton(text='Add info', callback_data=str(MALE))
keyboard = InlineKeyboardMarkup.from_button(button)
update.callback_query.answer()
update.callback_query.edit_message_text(text=text, reply_markup=keyboard)
return DESCRIBING_SELF
def show_data(update, context):
"""Pretty print gathered data."""
def prettyprint(user_data, level):
people = user_data.get(level)
if not people:
return '\nNo information yet.'
text = ''
if level == SELF:
for person in user_data[level]:
text += '\nName: {0}, Age: {1}'.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, '-'))
return text
ud = context.user_data
text = 'Yourself:' + prettyprint(ud, SELF)
text += '\n\nParents:' + prettyprint(ud, PARENTS)
text += '\n\nChildren:' + prettyprint(ud, CHILDREN)
buttons = [[
InlineKeyboardButton(text='Back', callback_data=str(END))
]]
keyboard = InlineKeyboardMarkup(buttons)
update.callback_query.answer()
update.callback_query.edit_message_text(text=text, reply_markup=keyboard)
ud[START_OVER] = True
return SHOWING
def stop(update, context):
"""End Conversation by command."""
update.message.reply_text('Okay, bye.')
return END
def end(update, context):
"""End conversation from InlineKeyboardButton."""
update.callback_query.answer()
text = 'See you around!'
update.callback_query.edit_message_text(text=text)
return END
# Second level conversation callbacks
def select_level(update, context):
"""Choose to add a parent or a child."""
text = 'You may add a parent or a child. Also you can show the gathered data or go back.'
buttons = [[
InlineKeyboardButton(text='Add parent', callback_data=str(PARENTS)),
InlineKeyboardButton(text='Add child', callback_data=str(CHILDREN))
], [
InlineKeyboardButton(text='Show data', callback_data=str(SHOWING)),
InlineKeyboardButton(text='Back', callback_data=str(END))
]]
keyboard = InlineKeyboardMarkup(buttons)
update.callback_query.answer()
update.callback_query.edit_message_text(text=text, reply_markup=keyboard)
return SELECTING_LEVEL
def select_gender(update, context):
"""Choose to add mother or father."""
level = update.callback_query.data
context.user_data[CURRENT_LEVEL] = level
text = 'Please choose, whom to add.'
male, female = _name_switcher(level)
buttons = [[
InlineKeyboardButton(text='Add ' + male, callback_data=str(MALE)),
InlineKeyboardButton(text='Add ' + female, callback_data=str(FEMALE))
], [
InlineKeyboardButton(text='Show data', callback_data=str(SHOWING)),
InlineKeyboardButton(text='Back', callback_data=str(END))
]]
keyboard = InlineKeyboardMarkup(buttons)
update.callback_query.answer()
update.callback_query.edit_message_text(text=text, reply_markup=keyboard)
return SELECTING_GENDER
def end_second_level(update, context):
"""Return to top level conversation."""
context.user_data[START_OVER] = True
start(update, context)
return END
# Third level callbacks
def select_feature(update, context):
"""Select a feature to update for the person."""
buttons = [[
InlineKeyboardButton(text='Name', callback_data=str(NAME)),
InlineKeyboardButton(text='Age', callback_data=str(AGE)),
InlineKeyboardButton(text='Done', callback_data=str(END)),
]]
keyboard = InlineKeyboardMarkup(buttons)
# If we collect features for a new person, clear the cache and save the gender
if not context.user_data.get(START_OVER):
context.user_data[FEATURES] = {GENDER: update.callback_query.data}
text = 'Please select a feature to update.'
update.callback_query.answer()
update.callback_query.edit_message_text(text=text, reply_markup=keyboard)
# But after we do that, we need to send a new message
else:
text = 'Got it! Please select a feature to update.'
update.message.reply_text(text=text, reply_markup=keyboard)
context.user_data[START_OVER] = False
return SELECTING_FEATURE
def ask_for_input(update, context):
"""Prompt user to input data for selected feature."""
context.user_data[CURRENT_FEATURE] = update.callback_query.data
text = 'Okay, tell me.'
update.callback_query.answer()
update.callback_query.edit_message_text(text=text)
return TYPING
def save_input(update, context):
"""Save input for feature and return to feature selection."""
ud = context.user_data
ud[FEATURES][ud[CURRENT_FEATURE]] = update.message.text
ud[START_OVER] = True
return select_feature(update, context)
def end_describing(update, context):
"""End gathering of features and return to parent conversation."""
ud = context.user_data
level = ud[CURRENT_LEVEL]
if not ud.get(level):
ud[level] = []
ud[level].append(ud[FEATURES])
# Print upper level menu
if level == SELF:
ud[START_OVER] = True
start(update, context)
else:
select_level(update, context)
return END
def stop_nested(update, context):
"""Completely end conversation from within nested conversation."""
update.message.reply_text('Okay, bye.')
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
# Post version 12 this will no longer be necessary
updater = Updater("TOKEN", use_context=True)
# Get the dispatcher to register handlers
dp = updater.dispatcher
# Set up third level ConversationHandler (collecting features)
description_conv = ConversationHandler(
entry_points=[CallbackQueryHandler(select_feature,
pattern='^' + str(MALE) + '$|^' + str(FEMALE) + '$')],
states={
SELECTING_FEATURE: [CallbackQueryHandler(ask_for_input,
pattern='^(?!' + str(END) + ').*$')],
TYPING: [MessageHandler(Filters.text, save_input)],
},
fallbacks=[
CallbackQueryHandler(end_describing, pattern='^' + str(END) + '$'),
CommandHandler('stop', stop_nested)
],
map_to_parent={
# Return to second level menu
END: SELECTING_LEVEL,
# End conversation alltogether
STOPPING: STOPPING,
}
)
# Set up second level ConversationHandler (adding a person)
add_member_conv = ConversationHandler(
entry_points=[CallbackQueryHandler(select_level,
pattern='^' + str(ADDING_MEMBER) + '$')],
states={
SELECTING_LEVEL: [CallbackQueryHandler(select_gender,
pattern='^{0}$|^{1}$'.format(str(PARENTS),
str(CHILDREN)))],
SELECTING_GENDER: [description_conv]
},
fallbacks=[
CallbackQueryHandler(show_data, pattern='^' + str(SHOWING) + '$'),
CallbackQueryHandler(end_second_level, pattern='^' + str(END) + '$'),
CommandHandler('stop', stop_nested)
],
map_to_parent={
# After showing data return to top level menu
SHOWING: SHOWING,
# Return to top level menu
END: SELECTING_ACTION,
# End conversation alltogether
STOPPING: END,
}
)
# Set up top level ConversationHandler (selecting action)
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) + '$'),
],
DESCRIBING_SELF: [description_conv],
},
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()
# 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()
+19 -9
View File
@@ -1,9 +1,10 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Simple Bot to print/download all incoming passport data
# This program is dedicated to the public domain under the CC0 license.
"""
Simple Bot to print/download all incoming passport data
See https://telegram.org/blog/passport for info about what telegram passport is.
See https://git.io/fAvYd for how to use Telegram Passport properly with python-telegram-bot.
@@ -20,13 +21,13 @@ logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s
logger = logging.getLogger(__name__)
def msg(bot, update):
def msg(update, context):
# If we received any passport data
passport_data = update.message.passport_data
if passport_data:
# If our payload doesn't match what we think, this Update did not originate from us
# Ideally you would randomize the payload on the server
if passport_data.decrypted_credentials.payload != 'thisisatest':
# If our nonce doesn't match what we think, this Update did not originate from us
# Ideally you would randomize the nonce on the server
if passport_data.decrypted_credentials.nonce != 'thisisatest':
return
# Print the decrypted credential data
@@ -39,7 +40,7 @@ def msg(bot, update):
elif data.type == 'email':
print('Email: ', data.email)
if data.type in ('personal_details', 'passport', 'driver_license', 'identity_card',
'identity_passport', 'address'):
'internal_passport', 'address'):
print(data.type, data.data)
if data.type in ('utility_bill', 'bank_statement', 'rental_agreement',
'passport_registration', 'temporary_registration'):
@@ -65,11 +66,20 @@ def msg(bot, update):
file = data.selfie.get_file()
print(data.type, file)
file.download()
if data.type in ('passport', 'driver_license', 'identity_card',
'internal_passport', 'utility_bill', 'bank_statement',
'rental_agreement', 'passport_registration',
'temporary_registration'):
print(data.type, len(data.translation), 'translation')
for file in data.translation:
actual_file = file.get_file()
print(actual_file)
actual_file.download()
def error(bot, update, error):
def error(update, context):
"""Log Errors caused by Updates."""
logger.warning('Update "%s" caused error "%s"', update, error)
logger.warning('Update "%s" caused error "%s"', update, context.error)
def main():
+31 -31
View File
@@ -1,15 +1,16 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
"""Basic example for a bot that can receive payment from user.
# This program is dedicated to the public domain under the CC0 license.
This program is dedicated to the public domain under the CC0 license.
"""
Basic example for a bot that can receive payment from user.
"""
import logging
from telegram import (LabeledPrice, ShippingOption)
from telegram.ext import (Updater, CommandHandler, MessageHandler,
Filters, PreCheckoutQueryHandler, ShippingQueryHandler)
import logging
# Enable logging
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
@@ -18,18 +19,18 @@ logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s
logger = logging.getLogger(__name__)
def error(bot, update, error):
def error(update, context):
"""Log Errors caused by Updates."""
logger.warning('Update "%s" caused error "%s"', update, error)
logger.warning('Update "%s" caused error "%s"', update, context.error)
def start_callback(bot, update):
def start_callback(update, context):
msg = "Use /shipping to get an invoice for shipping-payment, "
msg += "or /noshipping for an invoice without shipping."
update.message.reply_text(msg)
def start_with_shipping_callback(bot, update):
def start_with_shipping_callback(update, context):
chat_id = update.message.chat_id
title = "Payment Example"
description = "Payment Example using python-telegram-bot"
@@ -41,19 +42,19 @@ def start_with_shipping_callback(bot, update):
currency = "USD"
# price in dollars
price = 1
# price * 100 so as to include 2 d.p.
# price * 100 so as to include 2 decimal points
# check https://core.telegram.org/bots/payments#supported-currencies for more details
prices = [LabeledPrice("Test", price * 100)]
# optionally pass need_name=True, need_phone_number=True,
# need_email=True, need_shipping_address=True, is_flexible=True
bot.sendInvoice(chat_id, title, description, payload,
provider_token, start_parameter, currency, prices,
need_name=True, need_phone_number=True,
need_email=True, need_shipping_address=True, is_flexible=True)
context.bot.send_invoice(chat_id, title, description, payload,
provider_token, start_parameter, currency, prices,
need_name=True, need_phone_number=True,
need_email=True, need_shipping_address=True, is_flexible=True)
def start_without_shipping_callback(bot, update):
def start_without_shipping_callback(update, context):
chat_id = update.message.chat_id
title = "Payment Example"
description = "Payment Example using python-telegram-bot"
@@ -65,22 +66,21 @@ def start_without_shipping_callback(bot, update):
currency = "USD"
# price in dollars
price = 1
# price * 100 so as to include 2 d.p.
# price * 100 so as to include 2 decimal points
prices = [LabeledPrice("Test", price * 100)]
# optionally pass need_name=True, need_phone_number=True,
# need_email=True, need_shipping_address=True, is_flexible=True
bot.sendInvoice(chat_id, title, description, payload,
provider_token, start_parameter, currency, prices)
context.bot.send_invoice(chat_id, title, description, payload,
provider_token, start_parameter, currency, prices)
def shipping_callback(bot, update):
def shipping_callback(update, context):
query = update.shipping_query
# check the payload, is this from your bot?
if query.invoice_payload != 'Custom-Payload':
# answer False pre_checkout_query
bot.answer_shipping_query(shipping_query_id=query.id, ok=False,
error_message="Something went wrong...")
query.answer(ok=False, error_message="Something went wrong...")
return
else:
options = list()
@@ -89,31 +89,31 @@ def shipping_callback(bot, update):
# an array of LabeledPrice objects
price_list = [LabeledPrice('B1', 150), LabeledPrice('B2', 200)]
options.append(ShippingOption('2', 'Shipping Option B', price_list))
bot.answer_shipping_query(shipping_query_id=query.id, ok=True,
shipping_options=options)
query.answer(ok=True, shipping_options=options)
# after (optional) shipping, it's the pre-checkout
def precheckout_callback(bot, update):
def precheckout_callback(update, context):
query = update.pre_checkout_query
# check the payload, is this from your bot?
if query.invoice_payload != 'Custom-Payload':
# answer False pre_checkout_query
bot.answer_pre_checkout_query(pre_checkout_query_id=query.id, ok=False,
error_message="Something went wrong...")
query.answer(ok=False, error_message="Something went wrong...")
else:
bot.answer_pre_checkout_query(pre_checkout_query_id=query.id, ok=True)
query.answer(ok=True)
# finally, after contacting to the payment provider...
def successful_payment_callback(bot, update):
# do something after successful receive of payment?
# finally, after contacting the payment provider...
def successful_payment_callback(update, context):
# do something after successfully receiving payment?
update.message.reply_text("Thank you for your payment!")
def main():
# Create the EventHandler and pass it your bot's token.
updater = Updater(token="BOT_TOKEN")
# 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("TOKEN", use_context=True)
# Get the dispatcher to register handlers
dp = updater.dispatcher
+165
View File
@@ -0,0 +1,165 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# This program is dedicated to the public domain under the CC0 license.
"""
First, a few callback functions are defined. Then, those functions are passed to
the Dispatcher and registered at their respective places.
Then, the bot is started and runs until we press Ctrl-C on the command line.
Usage:
Example of a bot-user conversation using ConversationHandler.
Send /start to initiate the conversation.
Press Ctrl-C on the command line or send a signal to the process to stop the
bot.
"""
from telegram import ReplyKeyboardMarkup
from telegram.ext import (Updater, CommandHandler, MessageHandler, Filters,
ConversationHandler, PicklePersistence)
import logging
# Enable logging
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
level=logging.INFO)
logger = logging.getLogger(__name__)
CHOOSING, TYPING_REPLY, TYPING_CHOICE = range(3)
reply_keyboard = [['Age', 'Favourite colour'],
['Number of siblings', 'Something else...'],
['Done']]
markup = ReplyKeyboardMarkup(reply_keyboard, one_time_keyboard=True)
def facts_to_str(user_data):
facts = list()
for key, value in user_data.items():
facts.append('{} - {}'.format(key, value))
return "\n".join(facts).join(['\n', '\n'])
def start(update, context):
reply_text = "Hi! My name is Doctor Botter."
if context.user_data:
reply_text += " You already told me your {}. Why don't you tell me something more " \
"about yourself? Or change anything I " \
"already know.".format(", ".join(context.user_data.keys()))
else:
reply_text += " I will hold a more complex conversation with you. Why don't you tell me " \
"something about yourself?"
update.message.reply_text(reply_text, reply_markup=markup)
return CHOOSING
def regular_choice(update, context):
text = update.message.text.lower()
context.user_data['choice'] = text
if context.user_data.get(text):
reply_text = 'Your {}, I already know the following ' \
'about that: {}'.format(text, context.user_data[text])
else:
reply_text = 'Your {}? Yes, I would love to hear about that!'.format(text)
update.message.reply_text(reply_text)
return TYPING_REPLY
def custom_choice(update, context):
update.message.reply_text('Alright, please send me the category first, '
'for example "Most impressive skill"')
return TYPING_CHOICE
def received_information(update, context):
text = update.message.text
category = context.user_data['choice']
context.user_data[category] = text.lower()
del context.user_data['choice']
update.message.reply_text("Neat! Just so you know, this is what you already told me:"
"{}"
"You can tell me more, or change your opinion on "
"something.".format(facts_to_str(context.user_data)),
reply_markup=markup)
return CHOOSING
def show_data(update, context):
update.message.reply_text("This is what you already told me:"
"{}".format(facts_to_str(context.user_data)))
def done(update, context):
if 'choice' in context.user_data:
del context.user_data['choice']
update.message.reply_text("I learned these facts about you:"
"{}"
"Until next time!".format(facts_to_str(context.user_data)))
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')
updater = Updater("TOKEN", persistence=pp, use_context=True)
# Get the dispatcher to register handlers
dp = updater.dispatcher
# Add conversation handler with the states CHOOSING, TYPING_CHOICE and TYPING_REPLY
conv_handler = ConversationHandler(
entry_points=[CommandHandler('start', start)],
states={
CHOOSING: [MessageHandler(Filters.regex('^(Age|Favourite colour|Number of siblings)$'),
regular_choice),
MessageHandler(Filters.regex('^Something else...$'),
custom_choice),
],
TYPING_CHOICE: [MessageHandler(Filters.text,
regular_choice),
],
TYPING_REPLY: [MessageHandler(Filters.text,
received_information),
],
},
fallbacks=[MessageHandler(Filters.regex('^Done$'), done)],
name="my_conversation",
persistent=True
)
dp.add_handler(conv_handler)
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()
# 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()
+147
View File
@@ -0,0 +1,147 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# This program is dedicated to the public domain under the CC0 license.
"""
Basic example for a bot that works with polls. Only 3 people are allowed to interact with each
poll/quiz the bot generates. The preview command generates a closed poll/quiz, excatly like the
one the user sends the bot
"""
import logging
from telegram import (Poll, ParseMode, KeyboardButton, KeyboardButtonPollType,
ReplyKeyboardMarkup, ReplyKeyboardRemove)
from telegram.ext import (Updater, CommandHandler, PollAnswerHandler, PollHandler, MessageHandler,
Filters)
from telegram.utils.helpers import mention_html
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
level=logging.INFO)
logger = logging.getLogger(__name__)
def start(update, context):
"""Inform user about what this bot can do"""
update.message.reply_text('Please select /poll to get a Poll, /quiz to get a Quiz or /preview'
' to generate a preview for your poll')
def poll(update, context):
"""Sends a predefined poll"""
questions = ["Good", "Really good", "Fantastic", "Great"]
message = context.bot.send_poll(update.effective_user.id, "How are you?", questions,
is_anonymous=False, allows_multiple_answers=True)
# Save some info about the poll the bot_data for later use in receive_poll_answer
payload = {message.poll.id: {"questions": questions, "message_id": message.message_id,
"chat_id": update.effective_chat.id, "answers": 0}}
context.bot_data.update(payload)
def receive_poll_answer(update, context):
"""Summarize a users poll vote"""
answer = update.poll_answer
poll_id = answer.poll_id
try:
questions = context.bot_data[poll_id]["questions"]
# this means this poll answer update is from an old poll, we can't do our answering then
except KeyError:
return
selected_options = answer.option_ids
answer_string = ""
for question_id in selected_options:
if question_id != selected_options[-1]:
answer_string += questions[question_id] + " and "
else:
answer_string += questions[question_id]
user_mention = mention_html(update.effective_user.id, update.effective_user.full_name)
context.bot.send_message(context.bot_data[poll_id]["chat_id"],
"{} feels {}!".format(user_mention, answer_string),
parse_mode=ParseMode.HTML)
context.bot_data[poll_id]["answers"] += 1
# Close poll after three participants voted
if context.bot_data[poll_id]["answers"] == 3:
context.bot.stop_poll(context.bot_data[poll_id]["chat_id"],
context.bot_data[poll_id]["message_id"])
def quiz(update, context):
"""Send a predefined poll"""
questions = ["1", "2", "4", "20"]
message = update.effective_message.reply_poll("How many eggs do you need for a cake?",
questions, type=Poll.QUIZ, correct_option_id=2)
# Save some info about the poll the bot_data for later use in receive_quiz_answer
payload = {message.poll.id: {"chat_id": update.effective_chat.id,
"message_id": message.message_id}}
context.bot_data.update(payload)
def receive_quiz_answer(update, context):
"""Close quiz after three participants took it"""
# the bot can receive closed poll updates we don't care about
if update.poll.is_closed:
return
if update.poll.total_voter_count == 3:
try:
quiz_data = context.bot_data[update.poll.id]
# this means this poll answer update is from an old poll, we can't stop it then
except KeyError:
return
context.bot.stop_poll(quiz_data["chat_id"], quiz_data["message_id"])
def preview(update, context):
"""Ask user to create a poll and display a preview of it"""
# using this without a type lets the user chooses what he wants (quiz or poll)
button = [[KeyboardButton("Press me!", request_poll=KeyboardButtonPollType())]]
message = "Press the button to let the bot generate a preview for your poll"
# using one_time_keyboard to hide the keyboard
update.effective_message.reply_text(message,
reply_markup=ReplyKeyboardMarkup(button,
one_time_keyboard=True))
def receive_poll(update, context):
"""On receiving polls, reply to it by a closed poll copying the received poll"""
actual_poll = update.effective_message.poll
# Only need to set the question and options, since all other parameters don't matter for
# a closed poll
update.effective_message.reply_poll(
question=actual_poll.question,
options=[o.text for o in actual_poll.options],
# with is_closed true, the poll/quiz is immediately closed
is_closed=True,
reply_markup=ReplyKeyboardRemove()
)
def help_handler(update, context):
"""Display a help message"""
update.message.reply_text("Use /quiz, /poll or /preview to test this "
"bot.")
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("TOKEN", use_context=True)
dp = updater.dispatcher
dp.add_handler(CommandHandler('start', start))
dp.add_handler(CommandHandler('poll', poll))
dp.add_handler(PollAnswerHandler(receive_poll_answer))
dp.add_handler(CommandHandler('quiz', quiz))
dp.add_handler(PollHandler(receive_quiz_answer))
dp.add_handler(CommandHandler('preview', preview))
dp.add_handler(MessageHandler(Filters.poll, receive_poll))
dp.add_handler(CommandHandler('help', help_handler))
# Start the Bot
updater.start_polling()
# Run the bot until the user presses Ctrl-C or the process receives SIGINT,
# SIGTERM or SIGABRT
updater.idle()
if __name__ == '__main__':
main()
+29 -22
View File
@@ -1,11 +1,10 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""Simple Bot to send timed Telegram messages.
# This program is dedicated to the public domain under the CC0 license.
"""
Simple Bot to send timed Telegram messages.
This Bot uses the Updater class to handle the bot and the JobQueue to send
timed messages.
@@ -19,9 +18,10 @@ Press Ctrl-C on the command line or send a signal to the process to stop the
bot.
"""
from telegram.ext import Updater, CommandHandler
import logging
from telegram.ext import Updater, CommandHandler
# Enable logging
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
level=logging.INFO)
@@ -29,30 +29,34 @@ logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s
logger = logging.getLogger(__name__)
# Define a few command handlers. These usually take the two arguments bot and
# update. Error handlers also receive the raised TelegramError object in error.
def start(bot, update):
# Define a few command handlers. These usually take the two arguments update and
# context. Error handlers also receive the raised TelegramError object in error.
def start(update, context):
update.message.reply_text('Hi! Use /set <seconds> to set a timer')
def alarm(bot, job):
def alarm(context):
"""Send the alarm message."""
bot.send_message(job.context, text='Beep!')
job = context.job
context.bot.send_message(job.context, text='Beep!')
def set_timer(bot, update, args, job_queue, chat_data):
def set_timer(update, context):
"""Add a job to the queue."""
chat_id = update.message.chat_id
try:
# args[0] should contain the time for the timer in seconds
due = int(args[0])
due = int(context.args[0])
if due < 0:
update.message.reply_text('Sorry we can not go back to future!')
return
# Add job to queue
job = job_queue.run_once(alarm, due, context=chat_id)
chat_data['job'] = job
# Add job to queue and stop current one if there is a timer already
if 'job' in context.chat_data:
old_job = context.chat_data['job']
old_job.schedule_removal()
new_job = context.job_queue.run_once(alarm, due, context=chat_id)
context.chat_data['job'] = new_job
update.message.reply_text('Timer successfully set!')
@@ -60,27 +64,30 @@ def set_timer(bot, update, args, job_queue, chat_data):
update.message.reply_text('Usage: /set <seconds>')
def unset(bot, update, chat_data):
def unset(update, context):
"""Remove the job if the user changed their mind."""
if 'job' not in chat_data:
if 'job' not in context.chat_data:
update.message.reply_text('You have no active timer')
return
job = chat_data['job']
job = context.chat_data['job']
job.schedule_removal()
del chat_data['job']
del context.chat_data['job']
update.message.reply_text('Timer successfully unset!')
def error(bot, update, error):
def error(update, context):
"""Log Errors caused by Updates."""
logger.warning('Update "%s" caused error "%s"', update, error)
logger.warning('Update "%s" caused error "%s"', update, context.error)
def main():
"""Run bot."""
updater = Updater("TOKEN")
# 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("TOKEN", use_context=True)
# Get the dispatcher to register handlers
dp = updater.dispatcher
+2 -1
View File
@@ -5,6 +5,7 @@ flaky
yapf
pre-commit
beautifulsoup4
pytest
pytest==4.2.0
pytest-timeout
wheel
attrs==19.1.0
+2
View File
@@ -1,3 +1,5 @@
future>=0.16.0
certifi
tornado>=5.1
cryptography
decorator>=4.4.0
+3 -1
View File
@@ -14,7 +14,8 @@ upload-dir = docs/build/html
[flake8]
max-line-length = 99
ignore = W503
ignore = W503, W605
exclude = setup.py, docs/source/conf.py
[yapf]
based_on_style = google
@@ -27,6 +28,7 @@ addopts = --no-success-flaky-report -rsxX
filterwarnings =
error
ignore::DeprecationWarning
ignore::telegram.utils.deprecate.TelegramDeprecationWarning
[coverage:run]
branch = True
+14 -5
View File
@@ -3,6 +3,8 @@
import codecs
import os
import sys
from setuptools import setup, find_packages
@@ -18,6 +20,14 @@ def requirements():
packages = find_packages(exclude=['tests*'])
requirements = requirements()
# Allow for a package install to not use the vendored urllib3
UPSTREAM_URLLIB3_FLAG = '--with-upstream-urllib3'
if UPSTREAM_URLLIB3_FLAG in sys.argv:
sys.argv.remove(UPSTREAM_URLLIB3_FLAG)
requirements.append('urllib3 >= 1.19.1')
packages = [x for x in packages if not x.startswith('telegram.vendor.ptb_urllib3')]
with codecs.open('README.rst', 'r', 'utf-8') as fd:
fn = os.path.join('telegram', 'version.py')
@@ -35,7 +45,7 @@ with codecs.open('README.rst', 'r', 'utf-8') as fd:
description="We have made you a wrapper you can't refuse",
long_description=fd.read(),
packages=packages,
install_requires=requirements(),
install_requires=requirements,
extras_require={
'json': 'ujson',
'socks': 'PySocks'
@@ -50,10 +60,9 @@ with codecs.open('README.rst', 'r', 'utf-8') as fd:
'Topic :: Communications :: Chat',
'Topic :: Internet',
'Programming Language :: Python',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6'
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
],)
+21 -9
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2018
# Copyright (C) 2015-2020
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
@@ -19,10 +19,12 @@
"""A library that provides a Python interface to the Telegram Bot API"""
from .base import TelegramObject
from .botcommand import BotCommand
from .user import User
from .files.chatphoto import ChatPhoto
from .chat import Chat
from .chatmember import ChatMember
from .chatpermissions import ChatPermissions
from .files.photosize import PhotoSize
from .files.audio import Audio
from .files.voice import Voice
@@ -35,8 +37,10 @@ from .files.location import Location
from .files.venue import Venue
from .files.videonote import VideoNote
from .chataction import ChatAction
from .dice import Dice
from .userprofilephotos import UserProfilePhotos
from .keyboardbutton import KeyboardButton
from .keyboardbuttonpolltype import KeyboardButtonPollType
from .replymarkup import ReplyMarkup
from .replykeyboardmarkup import ReplyKeyboardMarkup
from .replykeyboardremove import ReplyKeyboardRemove
@@ -47,6 +51,8 @@ from .files.file import File
from .parsemode import ParseMode
from .messageentity import MessageEntity
from .games.game import Game
from .poll import Poll, PollOption, PollAnswer
from .loginurl import LoginUrl
from .games.callbackgame import CallbackGame
from .payment.shippingaddress import ShippingAddress
from .payment.orderinfo import OrderInfo
@@ -57,11 +63,11 @@ from .passport.passportfile import PassportFile
from .passport.data import IdDocumentData, PersonalDetails, ResidentialAddress
from .passport.encryptedpassportelement import EncryptedPassportElement
from .passport.passportdata import PassportData
from .inline.inlinekeyboardbutton import InlineKeyboardButton
from .inline.inlinekeyboardmarkup import InlineKeyboardMarkup
from .message import Message
from .callbackquery import CallbackQuery
from .choseninlineresult import ChosenInlineResult
from .inline.inlinekeyboardbutton import InlineKeyboardButton
from .inline.inlinekeyboardmarkup import InlineKeyboardMarkup
from .inline.inputmessagecontent import InputMessageContent
from .inline.inlinequery import InlineQuery
from .inline.inlinequeryresult import InlineQueryResult
@@ -109,19 +115,22 @@ from .passport.passportelementerrors import (PassportElementError,
PassportElementErrorFiles,
PassportElementErrorFrontSide,
PassportElementErrorReverseSide,
PassportElementErrorSelfie)
PassportElementErrorSelfie,
PassportElementErrorTranslationFile,
PassportElementErrorTranslationFiles,
PassportElementErrorUnspecified)
from .passport.credentials import (Credentials,
DataCredentials,
SecureData,
FileCredentials,
TelegramDecryptionError)
from .version import __version__ # flake8: noqa
from .version import __version__ # noqa: F401
__author__ = 'devs@python-telegram-bot.org'
__all__ = [
'Audio', 'Bot', 'Chat', 'ChatMember', 'ChatAction', 'ChosenInlineResult', 'CallbackQuery',
'Contact', 'Document', 'File', 'ForceReply', 'InlineKeyboardButton',
'Audio', 'Bot', 'Chat', 'ChatMember', 'ChatPermissions', 'ChatAction', 'ChosenInlineResult',
'CallbackQuery', 'Contact', 'Document', 'File', 'ForceReply', 'InlineKeyboardButton',
'InlineKeyboardMarkup', 'InlineQuery', 'InlineQueryResult', 'InlineQueryResult',
'InlineQueryResultArticle', 'InlineQueryResultAudio', 'InlineQueryResultCachedAudio',
'InlineQueryResultCachedDocument', 'InlineQueryResultCachedGif',
@@ -132,7 +141,7 @@ __all__ = [
'InlineQueryResultPhoto', 'InlineQueryResultVenue', 'InlineQueryResultVideo',
'InlineQueryResultVoice', 'InlineQueryResultGame', 'InputContactMessageContent', 'InputFile',
'InputLocationMessageContent', 'InputMessageContent', 'InputTextMessageContent',
'InputVenueMessageContent', 'KeyboardButton', 'Location', 'EncryptedCredentials',
'InputVenueMessageContent', 'Location', 'EncryptedCredentials',
'PassportFile', 'EncryptedPassportElement', 'PassportData', 'Message', 'MessageEntity',
'ParseMode', 'PhotoSize', 'ReplyKeyboardRemove', 'ReplyKeyboardMarkup', 'ReplyMarkup',
'Sticker', 'TelegramError', 'TelegramObject', 'Update', 'User', 'UserProfilePhotos', 'Venue',
@@ -148,5 +157,8 @@ __all__ = [
'Credentials', 'DataCredentials', 'SecureData', 'FileCredentials', 'IdDocumentData',
'PersonalDetails', 'ResidentialAddress', 'InputMediaVideo', 'InputMediaAnimation',
'InputMediaAudio', 'InputMediaDocument', 'TelegramDecryptionError',
'PassportElementErrorSelfie'
'PassportElementErrorSelfie', 'PassportElementErrorTranslationFile',
'PassportElementErrorTranslationFiles', 'PassportElementErrorUnspecified', 'Poll',
'PollOption', 'PollAnswer', 'LoginUrl', 'KeyboardButton', 'KeyboardButtonPollType', 'Dice',
'BotCommand'
]
+15 -2
View File
@@ -1,7 +1,7 @@
# !/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2018
# Copyright (C) 2015-2020
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
@@ -17,15 +17,28 @@
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
import sys
import subprocess
import certifi
import future
from . import __version__ as telegram_ver
def _git_revision():
try:
output = subprocess.check_output(["git", "describe", "--long", "--tags"],
stderr=subprocess.STDOUT)
except (subprocess.SubprocessError, OSError):
return None
return output.decode().strip()
def print_ver_info():
print('python-telegram-bot {0}'.format(telegram_ver))
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', ' ')))
+1 -1
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2018
# Copyright (C) 2015-2020
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
+878 -338
View File
File diff suppressed because it is too large Load Diff
+46
View File
@@ -0,0 +1,46 @@
#!/usr/bin/env python
# pylint: disable=R0903
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2020
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains an object that represents a Telegram Bot Command."""
from telegram import TelegramObject
class BotCommand(TelegramObject):
"""
This object represents a bot command.
Attributes:
command (:obj:`str`): Text of the command.
description (:obj:`str`): Description of the command.
Args:
command (:obj:`str`): Text of the command, 1-32 characters. Can contain only lowercase
English letters, digits and underscores.
description (:obj:`str`): Description of the command, 3-256 characters.
"""
def __init__(self, command, description, **kwargs):
self.command = command
self.description = description
@classmethod
def de_json(cls, data, bot):
if not data:
return None
return cls(**data)
+42 -29
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2018
# Copyright (C) 2015-2020
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
@@ -36,30 +36,31 @@ class CallbackQuery(TelegramObject):
Attributes:
id (:obj:`str`): Unique identifier for this query.
from_user (:class:`telegram.User`): Sender.
chat_instance (:obj:`str`): Global identifier, uniquely corresponding to the chat to which
the message with the callback button was sent.
message (:class:`telegram.Message`): Optional. Message with the callback button that
originated the query.
data (:obj:`str`): Optional. Data associated with the callback button.
inline_message_id (:obj:`str`): Optional. Identifier of the message sent via the bot in
inline mode, that originated the query.
chat_instance (:obj:`str`): Optional. Global identifier, uniquely corresponding to the chat
to which the message with the callback button was sent.
data (:obj:`str`): Optional. Data associated with the callback button.
game_short_name (:obj:`str`): Optional. Short name of a Game to be returned.
bot (:class:`telegram.Bot`, optional): The Bot to use for instance methods.
Args:
id (:obj:`str`): Unique identifier for this query.
from_user (:class:`telegram.User`): Sender.
chat_instance (:obj:`str`): Global identifier, uniquely corresponding to the chat to which
the message with the callback button was sent. Useful for high scores in games.
message (:class:`telegram.Message`, optional): Message with the callback button that
originated the query. Note that message content and message date will not be available
if the message is too old.
inline_message_id (:obj:`str`, optional): Identifier of the message sent via the bot in
inline mode, that originated the query.
chat_instance (:obj:`str`, optional): Global identifier, uniquely corresponding to the chat
to which the message with the callback button was sent. Useful for high scores in
games.
data (:obj:`str`, optional): Data associated with the callback button. Be aware that a bad
client can send arbitrary data in this field.
inline_message_id (:obj:`str`, optional): Identifier of the message sent via the bot in
inline mode, that originated the query.
game_short_name (:obj:`str`, optional): Short name of a Game to be returned, serves as
the unique identifier for the game
bot (:class:`telegram.Bot`, optional): The Bot to use for instance methods.
Note:
After the user presses an inline button, Telegram clients will display a progress bar
@@ -101,7 +102,10 @@ class CallbackQuery(TelegramObject):
data = super(CallbackQuery, cls).de_json(data, bot)
data['from_user'] = User.de_json(data.get('from'), bot)
data['message'] = Message.de_json(data.get('message'), bot)
message = data.get('message')
if message:
message['default_quote'] = data.get('default_quote')
data['message'] = Message.de_json(message, bot)
return cls(bot=bot, **data)
@@ -116,16 +120,16 @@ class CallbackQuery(TelegramObject):
"""
return self.bot.answerCallbackQuery(self.id, *args, **kwargs)
def edit_message_text(self, *args, **kwargs):
def edit_message_text(self, text, *args, **kwargs):
"""Shortcut for either::
bot.edit_message_text(chat_id=update.callback_query.message.chat_id,
bot.edit_message_text(text, chat_id=update.callback_query.message.chat_id,
message_id=update.callback_query.message.message_id,
*args, **kwargs)
or::
bot.edit_message_text(inline_message_id=update.callback_query.inline_message_id,
bot.edit_message_text(text, inline_message_id=update.callback_query.inline_message_id,
*args, **kwargs)
Returns:
@@ -134,22 +138,24 @@ class CallbackQuery(TelegramObject):
"""
if self.inline_message_id:
return self.bot.edit_message_text(
inline_message_id=self.inline_message_id, *args, **kwargs)
return self.bot.edit_message_text(text, inline_message_id=self.inline_message_id,
*args, **kwargs)
else:
return self.bot.edit_message_text(
chat_id=self.message.chat_id, message_id=self.message.message_id, *args, **kwargs)
return self.bot.edit_message_text(text, chat_id=self.message.chat_id,
message_id=self.message.message_id, *args, **kwargs)
def edit_message_caption(self, *args, **kwargs):
def edit_message_caption(self, caption, *args, **kwargs):
"""Shortcut for either::
bot.edit_message_caption(chat_id=update.callback_query.message.chat_id,
bot.edit_message_caption(caption=caption,
chat_id=update.callback_query.message.chat_id,
message_id=update.callback_query.message.message_id,
*args, **kwargs)
or::
bot.edit_message_caption(inline_message_id=update.callback_query.inline_message_id,
bot.edit_message_caption(caption=caption
inline_message_id=update.callback_query.inline_message_id,
*args, **kwargs)
Returns:
@@ -158,22 +164,26 @@ class CallbackQuery(TelegramObject):
"""
if self.inline_message_id:
return self.bot.edit_message_caption(
inline_message_id=self.inline_message_id, *args, **kwargs)
return self.bot.edit_message_caption(caption=caption,
inline_message_id=self.inline_message_id,
*args, **kwargs)
else:
return self.bot.edit_message_caption(
chat_id=self.message.chat_id, message_id=self.message.message_id, *args, **kwargs)
return self.bot.edit_message_caption(caption=caption, chat_id=self.message.chat_id,
message_id=self.message.message_id,
*args, **kwargs)
def edit_message_reply_markup(self, *args, **kwargs):
def edit_message_reply_markup(self, reply_markup, *args, **kwargs):
"""Shortcut for either::
bot.edit_message_replyMarkup(chat_id=update.callback_query.message.chat_id,
message_id=update.callback_query.message.message_id,
reply_markup=reply_markup,
*args, **kwargs)
or::
bot.edit_message_reply_markup(inline_message_id=update.callback_query.inline_message_id,
reply_markup=reply_markup,
*args, **kwargs)
Returns:
@@ -182,8 +192,11 @@ class CallbackQuery(TelegramObject):
"""
if self.inline_message_id:
return self.bot.edit_message_reply_markup(
inline_message_id=self.inline_message_id, *args, **kwargs)
return self.bot.edit_message_reply_markup(reply_markup=reply_markup,
inline_message_id=self.inline_message_id,
*args, **kwargs)
else:
return self.bot.edit_message_reply_markup(
chat_id=self.message.chat_id, message_id=self.message.message_id, *args, **kwargs)
return self.bot.edit_message_reply_markup(reply_markup=reply_markup,
chat_id=self.message.chat_id,
message_id=self.message.message_id,
*args, **kwargs)
+58 -9
View File
@@ -2,7 +2,7 @@
# pylint: disable=C0103,W0622
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2018
# Copyright (C) 2015-2020
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
@@ -20,6 +20,7 @@
"""This module contains an object that represents a Telegram Chat."""
from telegram import TelegramObject, ChatPhoto
from .chatpermissions import ChatPermissions
class Chat(TelegramObject):
@@ -32,12 +33,15 @@ class Chat(TelegramObject):
username (:obj:`str`): Optional. Username.
first_name (:obj:`str`): Optional. First name of the other party in a private chat.
last_name (:obj:`str`): Optional. Last name of the other party in a private chat.
all_members_are_administrators (:obj:`bool`): Optional.
photo (:class:`telegram.ChatPhoto`): Optional. Chat photo.
description (:obj:`str`): Optional. Description, for supergroups and channel chats.
description (:obj:`str`): Optional. Description, for groups, supergroups and channel chats.
invite_link (:obj:`str`): Optional. Chat invite link, for supergroups and channel chats.
pinned_message (:class:`telegram.Message`): Optional. Pinned message, for supergroups.
Returned only in get_chat.
permissions (:class:`telegram.ChatPermission`): Optional. Default chat member permissions,
for groups and supergroups. Returned only in getChat.
slow_mode_delay (:obj:`int`): Optional. For supergroups, the minimum allowed delay between
consecutive messages sent by each unpriviledged user. Returned only in getChat.
sticker_set_name (:obj:`str`): Optional. For supergroups, name of Group sticker set.
can_set_sticker_set (:obj:`bool`): Optional. ``True``, if the bot can change group the
sticker set.
@@ -54,15 +58,17 @@ class Chat(TelegramObject):
available.
first_name(:obj:`str`, optional): First name of the other party in a private chat.
last_name(:obj:`str`, optional): Last name of the other party in a private chat.
all_members_are_administrators (:obj:`bool`, optional): True if a group has `All Members
Are Admins` enabled.
photo (:class:`telegram.ChatPhoto`, optional): Chat photo. Returned only in getChat.
description (:obj:`str`, optional): Description, for supergroups and channel chats.
description (:obj:`str`, optional): Description, for groups, supergroups and channel chats.
Returned only in get_chat.
invite_link (:obj:`str`, optional): Chat invite link, for supergroups and channel chats.
Returned only in get_chat.
pinned_message (:class:`telegram.Message`, optional): Pinned message, for supergroups.
Returned only in get_chat.
permissions (:class:`telegram.ChatPermission`): Optional. Default chat member permissions,
for groups and supergroups. Returned only in getChat.
slow_mode_delay (:obj:`int`, optional): For supergroups, the minimum allowed delay between
consecutive messages sent by each unpriviledged user. Returned only in getChat.
bot (:class:`telegram.Bot`, optional): The Bot to use for instance methods.
sticker_set_name (:obj:`str`, optional): For supergroups, name of Group sticker set.
Returned only in get_chat.
@@ -88,14 +94,15 @@ class Chat(TelegramObject):
username=None,
first_name=None,
last_name=None,
all_members_are_administrators=None,
bot=None,
photo=None,
description=None,
invite_link=None,
pinned_message=None,
permissions=None,
sticker_set_name=None,
can_set_sticker_set=None,
slow_mode_delay=None,
**kwargs):
# Required
self.id = int(id)
@@ -105,11 +112,14 @@ class Chat(TelegramObject):
self.username = username
self.first_name = first_name
self.last_name = last_name
self.all_members_are_administrators = all_members_are_administrators
# TODO: Remove (also from tests), when Telegram drops this completely
self.all_members_are_administrators = kwargs.get('all_members_are_administrators')
self.photo = photo
self.description = description
self.invite_link = invite_link
self.pinned_message = pinned_message
self.permissions = permissions
self.slow_mode_delay = slow_mode_delay
self.sticker_set_name = sticker_set_name
self.can_set_sticker_set = can_set_sticker_set
@@ -131,7 +141,11 @@ class Chat(TelegramObject):
data['photo'] = ChatPhoto.de_json(data.get('photo'), bot)
from telegram import Message
data['pinned_message'] = Message.de_json(data.get('pinned_message'), bot)
pinned_message = data.get('pinned_message')
if pinned_message:
pinned_message['default_quote'] = data.get('default_quote')
data['pinned_message'] = Message.de_json(pinned_message, bot)
data['permissions'] = ChatPermissions.de_json(data.get('permissions'), bot)
return cls(bot=bot, **data)
@@ -221,6 +235,28 @@ class Chat(TelegramObject):
"""
return self.bot.unban_chat_member(self.id, *args, **kwargs)
def set_permissions(self, *args, **kwargs):
"""Shortcut for::
bot.set_chat_permissions(update.message.chat.id, *args, **kwargs)
Returns:
:obj:`bool`: If the action was sent successfully.
"""
return self.bot.set_chat_permissions(self.id, *args, **kwargs)
def set_administrator_custom_title(self, *args, **kwargs):
"""Shortcut for::
bot.set_chat_administrator_custom_title(update.message.chat.id, *args, **kwargs)
Returns:
:obj:`bool`: If the action was sent successfully.
"""
return self.bot.set_chat_administrator_custom_title(self.id, *args, **kwargs)
def send_message(self, *args, **kwargs):
"""Shortcut for::
@@ -337,3 +373,16 @@ class Chat(TelegramObject):
"""
return self.bot.send_voice(self.id, *args, **kwargs)
def send_poll(self, *args, **kwargs):
"""Shortcut for::
bot.send_poll(Chat.id, *args, **kwargs)
Where Chat is the current instance.
Returns:
:class:`telegram.Message`: On success, instance representing the message posted.
"""
return self.bot.send_poll(self.id, *args, **kwargs)
+1 -1
View File
@@ -2,7 +2,7 @@
# pylint: disable=R0903
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2018
# Copyright (C) 2015-2020
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
+27 -14
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2018
# Copyright (C) 2015-2020
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
@@ -28,28 +28,32 @@ class ChatMember(TelegramObject):
Attributes:
user (:class:`telegram.User`): Information about the user.
status (:obj:`str`): The member's status in the chat.
custom_title (:obj:`str`): Optional. Custom title for owner and administrators.
until_date (:class:`datetime.datetime`): Optional. Date when restrictions will be lifted
for this user.
can_be_edited (:obj:`bool`): Optional. If the bot is allowed to edit administrator
privileges of that user.
can_change_info (:obj:`bool`): Optional. If the administrator can change the chat title,
photo and other settings.
can_change_info (:obj:`bool`): Optional. If the user can change the chat title, photo and
other settings.
can_post_messages (:obj:`bool`): Optional. If the administrator can post in the channel.
can_edit_messages (:obj:`bool`): Optional. If the administrator can edit messages of other
users.
can_delete_messages (:obj:`bool`): Optional. If the administrator can delete messages of
other users.
can_invite_users (:obj:`bool`): Optional. If the administrator can invite new users to the
chat.
can_invite_users (:obj:`bool`): Optional. If the user can invite new users to the chat.
can_restrict_members (:obj:`bool`): Optional. If the administrator can restrict, ban or
unban chat members.
can_pin_messages (:obj:`bool`): Optional. If the administrator can pin messages.
can_pin_messages (:obj:`bool`): Optional. If the user can pin messages.
can_promote_members (:obj:`bool`): Optional. If the administrator can add new
administrators.
is_member (:obj:`bool`): Optional. Restricted only. True, if the user is a member of the
chat at the moment of the request.
can_send_messages (:obj:`bool`): Optional. If the user can send text messages, contacts,
locations and venues.
can_send_media_messages (:obj:`bool`): Optional. If the user can send media messages,
implies can_send_messages.
can_send_polls (:obj:`bool`): Optional. True, if the user is allowed to
send polls.
can_send_other_messages (:obj:`bool`): Optional. If the user can send animations, games,
stickers and use inline bots, implies can_send_media_messages.
can_add_web_page_previews (:obj:`bool`): Optional. If user may add web page previews to his
@@ -59,33 +63,39 @@ class ChatMember(TelegramObject):
user (:class:`telegram.User`): Information about the user.
status (:obj:`str`): The member's status in the chat. Can be 'creator', 'administrator',
'member', 'restricted', 'left' or 'kicked'.
custom_title (:obj:`str`, optional): Owner and administrators only.
Custom title for this user.
until_date (:class:`datetime.datetime`, optional): Restricted and kicked only. Date when
restrictions will be lifted for this user.
can_be_edited (:obj:`bool`, optional): Administrators only. True, if the bot is allowed to
edit administrator privileges of that user.
can_change_info (:obj:`bool`, optional): Administrators only. True, if the administrator
can change the chat title, photo and other settings.
can_change_info (:obj:`bool`, optional): Administrators and restricted only. True, if the
user can change the chat title, photo and other settings.
can_post_messages (:obj:`bool`, optional): Administrators only. True, if the administrator
can post in the channel, channels only.
can_edit_messages (:obj:`bool`, optional): Administrators only. True, if the administrator
can edit messages of other users, channels only.
can_delete_messages (:obj:`bool`, optional): Administrators only. True, if the
administrator can delete messages of other user.
can_invite_users (:obj:`bool`, optional): Administrators only. True, if the administrator
can invite new users to the chat.
can_invite_users (:obj:`bool`, optional): Administrators and restricted only. True, if the
user can invite new users to the chat.
can_restrict_members (:obj:`bool`, optional): Administrators only. True, if the
administrator can restrict, ban or unban chat members.
can_pin_messages (:obj:`bool`, optional): Administrators only. True, if the administrator
can pin messages, supergroups only.
can_pin_messages (:obj:`bool`, optional): Administrators and restricted only. True, if the
user can pin messages, supergroups only.
can_promote_members (:obj:`bool`, optional): Administrators only. True, if the
administrator can add new administrators with a subset of his own privileges or demote
administrators that he has promoted, directly or indirectly (promoted by administrators
that were appointed by the user).
is_member (:obj:`bool`, optional): Restricted only. True, if the user is a member of the
chat at the moment of the request.
can_send_messages (:obj:`bool`, optional): Restricted only. True, if the user can send text
messages, contacts, locations and venues.
can_send_media_messages (:obj:`bool`, optional): Restricted only. True, if the user can
send audios, documents, photos, videos, video notes and voice notes, implies
can_send_messages.
can_send_polls (:obj:`bool`, optional): Restricted only. True, if the user is allowed to
send polls.
can_send_other_messages (:obj:`bool`, optional): Restricted only. True, if the user can
send animations, games, stickers and use inline bots, implies can_send_media_messages.
can_add_web_page_previews (:obj:`bool`, optional): Restricted only. True, if user may add
@@ -110,11 +120,12 @@ class ChatMember(TelegramObject):
can_delete_messages=None, can_invite_users=None,
can_restrict_members=None, can_pin_messages=None,
can_promote_members=None, can_send_messages=None,
can_send_media_messages=None, can_send_other_messages=None,
can_add_web_page_previews=None, **kwargs):
can_send_media_messages=None, can_send_polls=None, can_send_other_messages=None,
can_add_web_page_previews=None, is_member=None, custom_title=None, **kwargs):
# Required
self.user = user
self.status = status
self.custom_title = custom_title
self.until_date = until_date
self.can_be_edited = can_be_edited
self.can_change_info = can_change_info
@@ -127,8 +138,10 @@ class ChatMember(TelegramObject):
self.can_promote_members = can_promote_members
self.can_send_messages = can_send_messages
self.can_send_media_messages = can_send_media_messages
self.can_send_polls = can_send_polls
self.can_send_other_messages = can_send_other_messages
self.can_add_web_page_previews = can_add_web_page_previews
self.is_member = is_member
self._id_attrs = (self.user, self.status)
+92
View File
@@ -0,0 +1,92 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2020
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains an object that represents a Telegram ChatPermission."""
from telegram import TelegramObject
class ChatPermissions(TelegramObject):
"""Describes actions that a non-administrator user is allowed to take in a chat.
Note:
Though not stated explicitly in the offical docs, Telegram changes not only the permissions
that are set, but also sets all the others to :obj:`False`. However, since not documented,
this behaviour may change unbeknown to PTB.
Attributes:
can_send_messages (:obj:`bool`): Optional. True, if the user is allowed to send text
messages, contacts, locations and venues.
can_send_media_messages (:obj:`bool`): Optional. True, if the user is allowed to send
audios, documents, photos, videos, video notes and voice notes, implies
:attr:`can_send_messages`.
can_send_polls (:obj:`bool`): Optional. True, if the user is allowed to send polls, implies
:attr:`can_send_messages`.
can_send_other_messages (:obj:`bool`): Optional. True, if the user is allowed to send
animations, games, stickers and use inline bots, implies
:attr:`can_send_media_messages`.
can_add_web_page_previews (:obj:`bool`): Optional. True, if the user is allowed to add web
page previews to their messages, implies :attr:`can_send_media_messages`.
can_change_info (:obj:`bool`): Optional. True, if the user is allowed to change the chat
title, photo and other settings. Ignored in public supergroups.
can_invite_users (:obj:`bool`): Optional. True, if the user is allowed to invite new users
to the chat.
can_pin_messages (:obj:`bool`): Optional. True, if the user is allowed to pin messages.
Ignored in public supergroups.
Args:
can_send_messages (:obj:`bool`, optional): True, if the user is allowed to send text
messages, contacts, locations and venues.
can_send_media_messages (:obj:`bool`, optional): True, if the user is allowed to send
audios, documents, photos, videos, video notes and voice notes, implies
:attr:`can_send_messages`.
can_send_polls (:obj:`bool`, optional): True, if the user is allowed to send polls, implies
:attr:`can_send_messages`.
can_send_other_messages (:obj:`bool`, optional): True, if the user is allowed to send
animations, games, stickers and use inline bots, implies
:attr:`can_send_media_messages`.
can_add_web_page_previews (:obj:`bool`, optional): True, if the user is allowed to add web
page previews to their messages, implies :attr:`can_send_media_messages`.
can_change_info (:obj:`bool`, optional): True, if the user is allowed to change the chat
title, photo and other settings. Ignored in public supergroups.
can_invite_users (:obj:`bool`, optional): True, if the user is allowed to invite new users
to the chat.
can_pin_messages (:obj:`bool`, optional): True, if the user is allowed to pin messages.
Ignored in public supergroups.
"""
def __init__(self, can_send_messages=None, can_send_media_messages=None, can_send_polls=None,
can_send_other_messages=None, can_add_web_page_previews=None,
can_change_info=None, can_invite_users=None, can_pin_messages=None, **kwargs):
# Required
self.can_send_messages = can_send_messages
self.can_send_media_messages = can_send_media_messages
self.can_send_polls = can_send_polls
self.can_send_other_messages = can_send_other_messages
self.can_add_web_page_previews = can_add_web_page_previews
self.can_change_info = can_change_info
self.can_invite_users = can_invite_users
self.can_pin_messages = can_pin_messages
@classmethod
def de_json(cls, data, bot):
if not data:
return None
return cls(**data)
+1 -1
View File
@@ -2,7 +2,7 @@
# pylint: disable=R0902,R0912,R0913
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2018
# Copyright (C) 2015-2020
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
+7 -4
View File
@@ -1,5 +1,5 @@
# python-telegram-bot - a Python interface to the Telegram Bot API
# Copyright (C) 2015-2018
# Copyright (C) 2015-2020
# by the python-telegram-bot contributors <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
@@ -17,14 +17,16 @@
"""Constants in the Telegram network.
The following constants were extracted from the
`Telegram Bots FAQ <https://core.telegram.org/bots/faq>`_.
`Telegram Bots FAQ <https://core.telegram.org/bots/faq>`_ and
`Telegram Bots API <https://core.telegram.org/bots/api>`_.
Attributes:
MAX_MESSAGE_LENGTH (:obj:`int`): 4096
MAX_CAPTION_LENGTH (:obj:`int`): 200
MAX_CAPTION_LENGTH (:obj:`int`): 1024
SUPPORTED_WEBHOOK_PORTS (List[:obj:`int`]): [443, 80, 88, 8443]
MAX_FILESIZE_DOWNLOAD (:obj:`int`): In bytes (20MB)
MAX_FILESIZE_UPLOAD (:obj:`int`): In bytes (50MB)
MAX_PHOTOSIZE_UPLOAD (:obj:`int`): In bytes (10MB)
MAX_MESSAGES_PER_SECOND_PER_CHAT (:obj:`int`): `1`. Telegram may allow short bursts that go
over this limit, but eventually you'll begin receiving 429 errors.
MAX_MESSAGES_PER_SECOND (:obj:`int`): 30
@@ -40,13 +42,14 @@ Attributes:
"""
MAX_MESSAGE_LENGTH = 4096
MAX_CAPTION_LENGTH = 200
MAX_CAPTION_LENGTH = 1024
# constants above this line are tested
SUPPORTED_WEBHOOK_PORTS = [443, 80, 88, 8443]
MAX_FILESIZE_DOWNLOAD = int(20E6) # (20MB)
MAX_FILESIZE_UPLOAD = int(50E6) # (50MB)
MAX_PHOTOSIZE_UPLOAD = int(10E6) # (10MB)
MAX_MESSAGES_PER_SECOND_PER_CHAT = 1
MAX_MESSAGES_PER_SECOND = 30
MAX_MESSAGES_PER_MINUTE_PER_GROUP = 20
+43
View File
@@ -0,0 +1,43 @@
#!/usr/bin/env python
# pylint: disable=R0903
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2020
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains an object that represents a Telegram Dice."""
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".)
Attributes:
value (:obj:`int`): Value of the dice.
Args:
value (:obj:`int`): Value of the dice, 1-6.
"""
def __init__(self, value, **kwargs):
self.value = value
@classmethod
def de_json(cls, data, bot):
if not data:
return None
return cls(**data)
+14 -3
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2018
# Copyright (C) 2015-2020
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
@@ -57,7 +57,6 @@ class Unauthorized(TelegramError):
class InvalidToken(TelegramError):
def __init__(self):
super(InvalidToken, self).__init__('Invalid token')
@@ -71,7 +70,6 @@ class BadRequest(NetworkError):
class TimedOut(NetworkError):
def __init__(self):
super(TimedOut, self).__init__('Timed out')
@@ -100,3 +98,16 @@ class RetryAfter(TelegramError):
super(RetryAfter,
self).__init__('Flood control exceeded. Retry in {} seconds'.format(retry_after))
self.retry_after = float(retry_after)
class Conflict(TelegramError):
"""
Raised when a long poll or webhook conflicts with another one.
Args:
msg (:obj:`str`): The message from telegrams server.
"""
def __init__(self, msg):
super(Conflict, self).__init__(msg)
+14 -5
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2018
# Copyright (C) 2015-2020
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
@@ -18,16 +18,20 @@
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""Extensions over the Telegram Bot API to facilitate bot making"""
from .basepersistence import BasePersistence
from .picklepersistence import PicklePersistence
from .dictpersistence import DictPersistence
from .handler import Handler
from .callbackcontext import CallbackContext
from .dispatcher import Dispatcher, DispatcherHandlerStop, run_async
from .jobqueue import JobQueue, Job
from .updater import Updater
from .callbackqueryhandler import CallbackQueryHandler
from .choseninlineresulthandler import ChosenInlineResultHandler
from .commandhandler import CommandHandler
from .handler import Handler
from .inlinequeryhandler import InlineQueryHandler
from .messagehandler import MessageHandler
from .filters import BaseFilter, Filters
from .messagehandler import MessageHandler
from .commandhandler import CommandHandler, PrefixHandler
from .regexhandler import RegexHandler
from .stringcommandhandler import StringCommandHandler
from .stringregexhandler import StringRegexHandler
@@ -37,10 +41,15 @@ from .precheckoutqueryhandler import PreCheckoutQueryHandler
from .shippingqueryhandler import ShippingQueryHandler
from .messagequeue import MessageQueue
from .messagequeue import DelayQueue
from .pollanswerhandler import PollAnswerHandler
from .pollhandler import PollHandler
from .defaults import Defaults
__all__ = ('Dispatcher', 'JobQueue', 'Job', 'Updater', 'CallbackQueryHandler',
'ChosenInlineResultHandler', 'CommandHandler', 'Handler', 'InlineQueryHandler',
'MessageHandler', 'BaseFilter', 'Filters', 'RegexHandler', 'StringCommandHandler',
'StringRegexHandler', 'TypeHandler', 'ConversationHandler',
'PreCheckoutQueryHandler', 'ShippingQueryHandler', 'MessageQueue', 'DelayQueue',
'DispatcherHandlerStop', 'run_async')
'DispatcherHandlerStop', 'run_async', 'CallbackContext', 'BasePersistence',
'PicklePersistence', 'DictPersistence', 'PrefixHandler', 'PollAnswerHandler',
'PollHandler', 'Defaults')
+149
View File
@@ -0,0 +1,149 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2020
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains the BasePersistence class."""
class BasePersistence(object):
"""Interface class for adding persistence to your bot.
Subclass this object for different implementations of a persistent bot.
All relevant methods must be overwritten. This means:
* If :attr:`store_bot_data` is ``True`` you must overwrite :meth:`get_bot_data` and
:meth:`update_bot_data`.
* If :attr:`store_chat_data` is ``True`` you must overwrite :meth:`get_chat_data` and
:meth:`update_chat_data`.
* If :attr:`store_user_data` is ``True`` you must overwrite :meth:`get_user_data` and
:meth:`update_user_data`.
* If you want to store conversation data with :class:`telegram.ext.ConversationHandler`, you
must overwrite :meth:`get_conversations` and :meth:`update_conversation`.
* :meth:`flush` will be called when the bot is shutdown.
Attributes:
store_user_data (:obj:`bool`): Optional, Whether user_data should be saved by this
persistence class.
store_chat_data (:obj:`bool`): Optional. Whether chat_data should be saved by this
persistence class.
store_bot_data (:obj:`bool`): Optional. Whether bot_data should be saved by this
persistence class.
Args:
store_user_data (:obj:`bool`, optional): Whether user_data should be saved by this
persistence class. Default is ``True``.
store_chat_data (:obj:`bool`, optional): Whether chat_data should be saved by this
persistence class. Default is ``True`` .
store_bot_data (:obj:`bool`, optional): Whether bot_data should be saved by this
persistence class. Default is ``True`` .
"""
def __init__(self, store_user_data=True, store_chat_data=True, store_bot_data=True):
self.store_user_data = store_user_data
self.store_chat_data = store_chat_data
self.store_bot_data = store_bot_data
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
``defaultdict(dict)``.
Returns:
:obj:`defaultdict`: The restored user data.
"""
raise NotImplementedError
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
``defaultdict(dict)``.
Returns:
:obj:`defaultdict`: The restored chat data.
"""
raise NotImplementedError
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
``dict``.
Returns:
:obj:`defaultdict`: The restored bot data.
"""
raise NotImplementedError
def get_conversations(self, name):
""""Will be called by :class:`telegram.ext.Dispatcher` when a
:class:`telegram.ext.ConversationHandler` is added if
:attr:`telegram.ext.ConversationHandler.persistent` is ``True``.
It should return the conversations for the handler with `name` or an empty ``dict``
Args:
name (:obj:`str`): The handlers name.
Returns:
:obj:`dict`: The restored conversations for the handler.
"""
raise NotImplementedError
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.
Args:
name (:obj:`str`): The handlers name.
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
def update_user_data(self, user_id, data):
"""Will be called by the :class:`telegram.ext.Dispatcher` after a handler has
handled an update.
Args:
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
def update_chat_data(self, chat_id, data):
"""Will be called by the :class:`telegram.ext.Dispatcher` after a handler has
handled an update.
Args:
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
def update_bot_data(self, data):
"""Will be called by the :class:`telegram.ext.Dispatcher` after a handler has
handled an update.
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
persistence a chance to finish up saving or close a database connection gracefully. If this
is not of any importance just pass will be sufficient.
"""
pass
+189
View File
@@ -0,0 +1,189 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2020
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains the CallbackContext class."""
from telegram import Update
class CallbackContext(object):
"""
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
:attr:`telegram.ext.Dispatcher.add_error_handler` or to the callback of a
:class:`telegram.ext.Job`.
Note:
:class:`telegram.ext.Dispatcher` will create a single context for an entire update. This
means that if you got 2 handlers in different groups and they both get called, they will
get passed the same `CallbackContext` object (of course with proper attributes like
`.matches` differing). This allows you to add custom attributes in a lower handler group
callback, and then subsequently access those attributes in a higher handler group callback.
Note that the attributes on `CallbackContext` might change in the future, so make sure to
use a fairly unique name for the attributes.
Warning:
Do not combine custom attributes and @run_async. Due to how @run_async works, it will
almost certainly execute the callbacks for an update out of order, and the attributes
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
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
update from the same chat id it will be the same ``dict``.
Warning:
When a group chat migrates to a supergroup, its chat id will change and the
``chat_data`` needs to be transferred. For details see our `wiki page
<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
update from the same user it will be the same ``dict``.
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
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.
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.
Only present when passed to the callback of :class:`telegram.ext.Job`.
"""
def __init__(self, dispatcher):
"""
Args:
dispatcher (:class:`telegram.ext.Dispatcher`):
"""
if not dispatcher.use_context:
raise ValueError('CallbackContext should not be used with a non context aware '
'dispatcher!')
self._dispatcher = dispatcher
self._bot_data = dispatcher.bot_data
self._chat_data = None
self._user_data = None
self.args = None
self.matches = None
self.error = None
self.job = None
@property
def dispatcher(self):
""":class:`telegram.ext.Dispatcher`: The dispatcher associated with this context."""
return self._dispatcher
@property
def bot_data(self):
return self._bot_data
@bot_data.setter
def bot_data(self, value):
raise AttributeError("You can not assign a new value to bot_data, see "
"https://git.io/fjxKe")
@property
def chat_data(self):
return self._chat_data
@chat_data.setter
def chat_data(self, value):
raise AttributeError("You can not assign a new value to chat_data, see "
"https://git.io/fjxKe")
@property
def user_data(self):
return self._user_data
@user_data.setter
def user_data(self, value):
raise AttributeError("You can not assign a new value to user_data, see "
"https://git.io/fjxKe")
@classmethod
def from_error(cls, update, error, dispatcher):
self = cls.from_update(update, dispatcher)
self.error = error
return self
@classmethod
def from_update(cls, update, dispatcher):
self = cls(dispatcher)
if update is not None and isinstance(update, Update):
chat = update.effective_chat
user = update.effective_user
if chat:
self._chat_data = dispatcher.chat_data[chat.id]
if user:
self._user_data = dispatcher.user_data[user.id]
return self
@classmethod
def from_job(cls, job, dispatcher):
self = cls(dispatcher)
self.job = job
return self
def update(self, data):
self.__dict__.update(data)
@property
def bot(self):
""":class:`telegram.Bot`: The bot associated with this context."""
return self._dispatcher.bot
@property
def job_queue(self):
"""
:class:`telegram.ext.JobQueue`: The ``JobQueue`` used by the
:class:`telegram.ext.Dispatcher` and (usually) the :class:`telegram.ext.Updater`
associated with this context.
"""
return self._dispatcher.job_queue
@property
def update_queue(self):
"""
:class:`queue.Queue`: The ``Queue`` instance used by the
:class:`telegram.ext.Dispatcher` and (usually) the :class:`telegram.ext.Updater`
associated with this context.
"""
return self._dispatcher.update_queue
@property
def match(self):
"""
`Regex match type`: The first match from :attr:`matches`.
Useful if you are only filtering using a single regex filter.
Returns `None` if :attr:`matches` is empty.
"""
try:
return self.matches[0] # pylint: disable=unsubscriptable-object
except (IndexError, TypeError):
return None
+36 -25
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2018
# Copyright (C) 2015-2020
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
@@ -33,19 +33,19 @@ class CallbackQueryHandler(Handler):
Attributes:
callback (:obj:`callable`): The callback function for this handler.
pass_update_queue (:obj:`bool`): Optional. Determines whether ``update_queue`` will be
pass_update_queue (:obj:`bool`): Determines whether ``update_queue`` will be
passed to the callback function.
pass_job_queue (:obj:`bool`): Optional. Determines whether ``job_queue`` will be passed to
pass_job_queue (:obj:`bool`): Determines whether ``job_queue`` will be passed to
the callback function.
pattern (:obj:`str` | `Pattern`): Optional. Regex pattern to test
:attr:`telegram.CallbackQuery.data` against.
pass_groups (:obj:`bool`): Optional. Determines whether ``groups`` will be passed to the
pass_groups (:obj:`bool`): Determines whether ``groups`` will be passed to the
callback function.
pass_groupdict (:obj:`bool`): Optional. Determines whether ``groupdict``. will be passed to
pass_groupdict (:obj:`bool`): Determines whether ``groupdict``. will be passed to
the callback function.
pass_user_data (:obj:`bool`): Optional. Determines whether ``user_data`` will be passed to
pass_user_data (:obj:`bool`): Determines whether ``user_data`` will be passed to
the callback function.
pass_chat_data (:obj:`bool`): Optional. Determines whether ``chat_data`` will be passed to
pass_chat_data (:obj:`bool`): Determines whether ``chat_data`` will be passed to
the callback function.
Note:
@@ -54,31 +54,45 @@ class CallbackQueryHandler(Handler):
either the user or the chat that the update was sent in. For each update from the same user
or in the same chat, it will be the same ``dict``.
Note that this is DEPRECATED, and you should use context based callbacks. See
https://git.io/fxJuV for more info.
Args:
callback (:obj:`callable`): A function that takes ``bot, update`` as positional arguments.
It will be called when the :attr:`check_update` has determined that an update should be
processed by this handler.
callback (:obj:`callable`): The callback function for this handler. Will be called when
:attr:`check_update` has determined that an update should be processed by this handler.
Callback signature for context based API:
``def callback(update: Update, context: CallbackContext)``
The return value of the callback is usually ignored except for the special case of
:class:`telegram.ext.ConversationHandler`.
pass_update_queue (:obj:`bool`, optional): If set to ``True``, a keyword argument called
``update_queue`` will be passed to the callback function. It will be the ``Queue``
instance used by the :class:`telegram.ext.Updater` and :class:`telegram.ext.Dispatcher`
that contains new updates which can be used to insert updates. Default is ``False``.
DEPRECATED: Please switch to context based callbacks.
pass_job_queue (:obj:`bool`, optional): If set to ``True``, a keyword argument called
``job_queue`` will be passed to the callback function. It will be a
:class:`telegram.ext.JobQueue` instance created by the :class:`telegram.ext.Updater`
which can be used to schedule new jobs. Default is ``False``.
DEPRECATED: Please switch to context based callbacks.
pattern (:obj:`str` | `Pattern`, optional): Regex pattern. If not ``None``, ``re.match``
is used on :attr:`telegram.CallbackQuery.data` to determine if an update should be
handled by this handler.
pass_groups (:obj:`bool`, optional): If the callback should be passed the result of
``re.match(pattern, data).groups()`` as a keyword argument called ``groups``.
Default is ``False``
DEPRECATED: Please switch to context based callbacks.
pass_groupdict (:obj:`bool`, optional): If the callback should be passed the result of
``re.match(pattern, data).groupdict()`` as a keyword argument called ``groupdict``.
Default is ``False``
DEPRECATED: Please switch to context based callbacks.
pass_user_data (:obj:`bool`, optional): If set to ``True``, a keyword argument called
``user_data`` will be passed to the callback function. Default is ``False``.
DEPRECATED: Please switch to context based callbacks.
pass_chat_data (:obj:`bool`, optional): If set to ``True``, a keyword argument called
``chat_data`` will be passed to the callback function. Default is ``False``.
DEPRECATED: Please switch to context based callbacks.
"""
@@ -119,25 +133,22 @@ class CallbackQueryHandler(Handler):
if self.pattern:
if update.callback_query.data:
match = re.match(self.pattern, update.callback_query.data)
return bool(match)
if match:
return match
else:
return True
def handle_update(self, update, dispatcher):
"""Send the update to the :attr:`callback`.
Args:
update (:class:`telegram.Update`): Incoming telegram update.
dispatcher (:class:`telegram.ext.Dispatcher`): Dispatcher that originated the Update.
"""
optional_args = self.collect_optional_args(dispatcher, update)
def collect_optional_args(self, dispatcher, update=None, check_result=None):
optional_args = super(CallbackQueryHandler, self).collect_optional_args(dispatcher,
update,
check_result)
if self.pattern:
match = re.match(self.pattern, update.callback_query.data)
if self.pass_groups:
optional_args['groups'] = match.groups()
optional_args['groups'] = check_result.groups()
if self.pass_groupdict:
optional_args['groupdict'] = match.groupdict()
optional_args['groupdict'] = check_result.groupdict()
return optional_args
return self.callback(dispatcher.bot, update, **optional_args)
def collect_additional_context(self, context, update, dispatcher, check_result):
if self.pattern:
context.matches = [check_result]
+21 -40
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2018
# Copyright (C) 2015-2020
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
@@ -18,9 +18,8 @@
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains the ChosenInlineResultHandler class."""
from .handler import Handler
from telegram import Update
from telegram.utils.deprecate import deprecate
from .handler import Handler
class ChosenInlineResultHandler(Handler):
@@ -28,13 +27,13 @@ class ChosenInlineResultHandler(Handler):
Attributes:
callback (:obj:`callable`): The callback function for this handler.
pass_update_queue (:obj:`bool`): Optional. Determines whether ``update_queue`` will be
pass_update_queue (:obj:`bool`): Determines whether ``update_queue`` will be
passed to the callback function.
pass_job_queue (:obj:`bool`): Optional. Determines whether ``job_queue`` will be passed to
pass_job_queue (:obj:`bool`): Determines whether ``job_queue`` will be passed to
the callback function.
pass_user_data (:obj:`bool`): Optional. Determines whether ``user_data`` will be passed to
pass_user_data (:obj:`bool`): Determines whether ``user_data`` will be passed to
the callback function.
pass_chat_data (:obj:`bool`): Optional. Determines whether ``chat_data`` will be passed to
pass_chat_data (:obj:`bool`): Determines whether ``chat_data`` will be passed to
the callback function.
Note:
@@ -43,38 +42,37 @@ class ChosenInlineResultHandler(Handler):
either the user or the chat that the update was sent in. For each update from the same user
or in the same chat, it will be the same ``dict``.
Note that this is DEPRECATED, and you should use context based callbacks. See
https://git.io/fxJuV for more info.
Args:
callback (:obj:`callable`): A function that takes ``bot, update`` as positional arguments.
It will be called when the :attr:`check_update` has determined that an update should be
processed by this handler.
callback (:obj:`callable`): The callback function for this handler. Will be called when
:attr:`check_update` has determined that an update should be processed by this handler.
Callback signature for context based API:
``def callback(update: Update, context: CallbackContext)``
The return value of the callback is usually ignored except for the special case of
:class:`telegram.ext.ConversationHandler`.
pass_update_queue (:obj:`bool`, optional): If set to ``True``, a keyword argument called
``update_queue`` will be passed to the callback function. It will be the ``Queue``
instance used by the :class:`telegram.ext.Updater` and :class:`telegram.ext.Dispatcher`
that contains new updates which can be used to insert updates. Default is ``False``.
DEPRECATED: Please switch to context based callbacks.
pass_job_queue (:obj:`bool`, optional): If set to ``True``, a keyword argument called
``job_queue`` will be passed to the callback function. It will be a
:class:`telegram.ext.JobQueue` instance created by the :class:`telegram.ext.Updater`
which can be used to schedule new jobs. Default is ``False``.
DEPRECATED: Please switch to context based callbacks.
pass_user_data (:obj:`bool`, optional): If set to ``True``, a keyword argument called
``user_data`` will be passed to the callback function. Default is ``False``.
DEPRECATED: Please switch to context based callbacks.
pass_chat_data (:obj:`bool`, optional): If set to ``True``, a keyword argument called
``chat_data`` will be passed to the callback function. Default is ``False``.
DEPRECATED: Please switch to context based callbacks.
"""
def __init__(self,
callback,
pass_update_queue=False,
pass_job_queue=False,
pass_user_data=False,
pass_chat_data=False):
super(ChosenInlineResultHandler, self).__init__(
callback,
pass_update_queue=pass_update_queue,
pass_job_queue=pass_job_queue,
pass_user_data=pass_user_data,
pass_chat_data=pass_chat_data)
def check_update(self, update):
"""Determines whether an update should be passed to this handlers :attr:`callback`.
@@ -86,20 +84,3 @@ class ChosenInlineResultHandler(Handler):
"""
return isinstance(update, Update) and update.chosen_inline_result
def handle_update(self, update, dispatcher):
"""Send the update to the :attr:`callback`.
Args:
update (:class:`telegram.Update`): Incoming telegram update.
dispatcher (:class:`telegram.ext.Dispatcher`): Dispatcher that originated the Update.
"""
optional_args = self.collect_optional_args(dispatcher, update)
return self.callback(dispatcher.bot, update, **optional_args)
# old non-PEP8 Handler methods
m = "telegram.ChosenInlineResultHandler."
checkUpdate = deprecate(check_update, m + "checkUpdate", m + "check_update")
handleUpdate = deprecate(handle_update, m + "handleUpdate", m + "handle_update")
+255 -55
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2018
# Copyright (C) 2015-2020
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
@@ -16,38 +16,48 @@
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains the CommandHandler class."""
"""This module contains the CommandHandler and PrefixHandler classes."""
import re
import warnings
from future.utils import string_types
from telegram.ext import Filters
from telegram.utils.deprecate import TelegramDeprecationWarning
from telegram import Update, MessageEntity
from .handler import Handler
from telegram import Update
class CommandHandler(Handler):
"""Handler class to handle Telegram commands.
Commands are Telegram messages that start with ``/``, optionally followed by an ``@`` and the
bot's name and/or some additional text.
bot's name and/or some additional text. The handler will add a ``list`` to the
:class:`CallbackContext` named :attr:`CallbackContext.args`. It will contain a list of strings,
which is the text following the command split on single or consecutive whitespace characters.
By default the handler listens to messages as well as edited messages. To change this behavior
use ``~Filters.update.edited_message`` in the filter argument.
Attributes:
command (:obj:`str` | List[:obj:`str`]): The command or list of commands this handler
should listen for.
should listen for. Limitations are the same as described here
https://core.telegram.org/bots#commands
callback (:obj:`callable`): The callback function for this handler.
filters (:class:`telegram.ext.BaseFilter`): Optional. Only allow updates with these
Filters.
allow_edited (:obj:`bool`): Optional. Determines Whether the handler should also accept
allow_edited (:obj:`bool`): Determines Whether the handler should also accept
edited messages.
pass_args (:obj:`bool`): Optional. Determines whether the handler should be passed
pass_args (:obj:`bool`): Determines whether the handler should be passed
``args``.
pass_update_queue (:obj:`bool`): Optional. Determines whether ``update_queue`` will be
pass_update_queue (:obj:`bool`): Determines whether ``update_queue`` will be
passed to the callback function.
pass_job_queue (:obj:`bool`): Optional. Determines whether ``job_queue`` will be passed to
pass_job_queue (:obj:`bool`): Determines whether ``job_queue`` will be passed to
the callback function.
pass_user_data (:obj:`bool`): Optional. Determines whether ``user_data`` will be passed to
pass_user_data (:obj:`bool`): Determines whether ``user_data`` will be passed to
the callback function.
pass_chat_data (:obj:`bool`): Optional. Determines whether ``chat_data`` will be passed to
pass_chat_data (:obj:`bool`): Determines whether ``chat_data`` will be passed to
the callback function.
Note:
@@ -56,42 +66,60 @@ class CommandHandler(Handler):
either the user or the chat that the update was sent in. For each update from the same user
or in the same chat, it will be the same ``dict``.
Note that this is DEPRECATED, and you should use context based callbacks. See
https://git.io/fxJuV for more info.
Args:
command (:obj:`str` | List[:obj:`str`]): The command or list of commands this handler
should listen for.
callback (:obj:`callable`): A function that takes ``bot, update`` as positional arguments.
It will be called when the :attr:`check_update` has determined that an update should be
processed by this handler.
should listen for. Limitations are the same as described here
https://core.telegram.org/bots#commands
callback (:obj:`callable`): The callback function for this handler. Will be called when
:attr:`check_update` has determined that an update should be processed by this handler.
Callback signature for context based API:
``def callback(update: Update, context: CallbackContext)``
The return value of the callback is usually ignored except for the special case of
:class:`telegram.ext.ConversationHandler`.
filters (:class:`telegram.ext.BaseFilter`, optional): A filter inheriting from
:class:`telegram.ext.filters.BaseFilter`. Standard filters can be found in
:class:`telegram.ext.filters.Filters`. Filters can be combined using bitwise
operators (& for and, | for or, ~ for not).
allow_edited (:obj:`bool`, optional): Determines whether the handler should also accept
edited messages. Default is ``False``.
DEPRECATED: Edited is allowed by default. To change this behavior use
``~Filters.update.edited_message``.
pass_args (:obj:`bool`, optional): Determines whether the handler should be passed the
arguments passed to the command as a keyword argument called ``args``. It will contain
a list of strings, which is the text following the command split on single or
consecutive whitespace characters. Default is ``False``
DEPRECATED: Please switch to context based callbacks.
pass_update_queue (:obj:`bool`, optional): If set to ``True``, a keyword argument called
``update_queue`` will be passed to the callback function. It will be the ``Queue``
instance used by the :class:`telegram.ext.Updater` and :class:`telegram.ext.Dispatcher`
that contains new updates which can be used to insert updates. Default is ``False``.
DEPRECATED: Please switch to context based callbacks.
pass_job_queue (:obj:`bool`, optional): If set to ``True``, a keyword argument called
``job_queue`` will be passed to the callback function. It will be a
:class:`telegram.ext.JobQueue` instance created by the :class:`telegram.ext.Updater`
which can be used to schedule new jobs. Default is ``False``.
DEPRECATED: Please switch to context based callbacks.
pass_user_data (:obj:`bool`, optional): If set to ``True``, a keyword argument called
``user_data`` will be passed to the callback function. Default is ``False``.
DEPRECATED: Please switch to context based callbacks.
pass_chat_data (:obj:`bool`, optional): If set to ``True``, a keyword argument called
``chat_data`` will be passed to the callback function. Default is ``False``.
DEPRECATED: Please switch to context based callbacks.
Raises:
ValueError - when command is too long or has illegal chars.
"""
def __init__(self,
command,
callback,
filters=None,
allow_edited=False,
allow_edited=None,
pass_args=False,
pass_update_queue=False,
pass_job_queue=False,
@@ -108,16 +136,22 @@ class CommandHandler(Handler):
self.command = [command.lower()]
else:
self.command = [x.lower() for x in command]
self.filters = filters
self.allow_edited = allow_edited
self.pass_args = pass_args
for comm in self.command:
if not re.match(r'^[\da-z_]{1,32}$', comm):
raise ValueError('Command is not a valid bot command')
# We put this up here instead of with the rest of checking code
# in check_update since we don't wanna spam a ton
if isinstance(self.filters, list):
warnings.warn('Using a list of filters in MessageHandler is getting '
'deprecated, please use bitwise operators (& and |) '
'instead. More info: https://git.io/vPTbc.')
if filters:
self.filters = Filters.update.messages & filters
else:
self.filters = Filters.update.messages
if allow_edited is not None:
warnings.warn('allow_edited is deprecated. See https://git.io/fxJuV for more info',
TelegramDeprecationWarning,
stacklevel=2)
if not allow_edited:
self.filters &= ~Filters.update.edited_message
self.pass_args = pass_args
def check_update(self, update):
"""Determines whether an update should be passed to this handlers :attr:`callback`.
@@ -126,48 +160,214 @@ class CommandHandler(Handler):
update (:class:`telegram.Update`): Incoming telegram update.
Returns:
:obj:`bool`
:obj:`list`: The list of args for the handler
"""
if (isinstance(update, Update)
and (update.message or update.edited_message and self.allow_edited)):
message = update.message or update.edited_message
if isinstance(update, Update) and update.effective_message:
message = update.effective_message
if message.text and message.text.startswith('/') and len(message.text) > 1:
first_word = message.text_html.split(None, 1)[0]
if len(first_word) > 1 and first_word.startswith('/'):
command = first_word[1:].split('@')
command.append(
message.bot.username) # in case the command was sent without a username
if (message.entities and message.entities[0].type == MessageEntity.BOT_COMMAND
and message.entities[0].offset == 0):
command = message.text[1:message.entities[0].length]
args = message.text.split()[1:]
command = command.split('@')
command.append(message.bot.username)
if not (command[0].lower() in self.command
and command[1].lower() == message.bot.username.lower()):
return False
if not (command[0].lower() in self.command
and command[1].lower() == message.bot.username.lower()):
return None
if self.filters is None:
res = True
elif isinstance(self.filters, list):
res = any(func(message) for func in self.filters)
else:
res = self.filters(message)
filter_result = self.filters(update)
if filter_result:
return args, filter_result
else:
return False
return res
def collect_optional_args(self, dispatcher, update=None, check_result=None):
optional_args = super(CommandHandler, self).collect_optional_args(dispatcher, update)
if self.pass_args:
optional_args['args'] = check_result[0]
return optional_args
return False
def collect_additional_context(self, context, update, dispatcher, check_result):
context.args = check_result[0]
if isinstance(check_result[1], dict):
context.update(check_result[1])
def handle_update(self, update, dispatcher):
"""Send the update to the :attr:`callback`.
class PrefixHandler(CommandHandler):
"""Handler class to handle custom prefix commands
This is a intermediate handler between :class:`MessageHandler` and :class:`CommandHandler`.
It supports configurable commands with the same options as CommandHandler. It will respond to
every combination of :attr:`prefix` and :attr:`command`. It will add a ``list`` to the
:class:`CallbackContext` named :attr:`CallbackContext.args`. It will contain a list of strings,
which is the text following the command split on single or consecutive whitespace characters.
Examples::
Single prefix and command:
PrefixHandler('!', 'test', callback) will respond to '!test'.
Multiple prefixes, single command:
PrefixHandler(['!', '#'], 'test', callback) will respond to '!test' and
'#test'.
Miltiple prefixes and commands:
PrefixHandler(['!', '#'], ['test', 'help`], callback) will respond to '!test',
'#test', '!help' and '#help'.
By default the handler listens to messages as well as edited messages. To change this behavior
use ~``Filters.update.edited_message``.
Attributes:
prefix (:obj:`str` | List[:obj:`str`]): The prefix(es) that will precede :attr:`command`.
command (:obj:`str` | List[:obj:`str`]): The command or list of commands this handler
should listen for.
callback (:obj:`callable`): The callback function for this handler.
filters (:class:`telegram.ext.BaseFilter`): Optional. Only allow updates with these
Filters.
pass_args (:obj:`bool`): Determines whether the handler should be passed
``args``.
pass_update_queue (:obj:`bool`): Determines whether ``update_queue`` will be
passed to the callback function.
pass_job_queue (:obj:`bool`): Determines whether ``job_queue`` will be passed to
the callback function.
pass_user_data (:obj:`bool`): Determines whether ``user_data`` will be passed to
the callback function.
pass_chat_data (:obj:`bool`): Determines whether ``chat_data`` will be passed to
the callback function.
Note:
:attr:`pass_user_data` and :attr:`pass_chat_data` determine whether a ``dict`` you
can use to keep any data in will be sent to the :attr:`callback` function. Related to
either the user or the chat that the update was sent in. For each update from the same user
or in the same chat, it will be the same ``dict``.
Note that this is DEPRECATED, and you should use context based callbacks. See
https://git.io/fxJuV for more info.
Args:
prefix (:obj:`str` | List[:obj:`str`]): The prefix(es) that will precede :attr:`command`.
command (:obj:`str` | List[:obj:`str`]): The command or list of commands this handler
should listen for.
callback (:obj:`callable`): The callback function for this handler. Will be called when
:attr:`check_update` has determined that an update should be processed by this handler.
Callback signature for context based API:
``def callback(update: Update, context: CallbackContext)``
The return value of the callback is usually ignored except for the special case of
:class:`telegram.ext.ConversationHandler`.
filters (:class:`telegram.ext.BaseFilter`, optional): A filter inheriting from
:class:`telegram.ext.filters.BaseFilter`. Standard filters can be found in
:class:`telegram.ext.filters.Filters`. Filters can be combined using bitwise
operators (& for and, | for or, ~ for not).
pass_args (:obj:`bool`, optional): Determines whether the handler should be passed the
arguments passed to the command as a keyword argument called ``args``. It will contain
a list of strings, which is the text following the command split on single or
consecutive whitespace characters. Default is ``False``
DEPRECATED: Please switch to context based callbacks.
pass_update_queue (:obj:`bool`, optional): If set to ``True``, a keyword argument called
``update_queue`` will be passed to the callback function. It will be the ``Queue``
instance used by the :class:`telegram.ext.Updater` and :class:`telegram.ext.Dispatcher`
that contains new updates which can be used to insert updates. Default is ``False``.
DEPRECATED: Please switch to context based callbacks.
pass_job_queue (:obj:`bool`, optional): If set to ``True``, a keyword argument called
``job_queue`` will be passed to the callback function. It will be a
:class:`telegram.ext.JobQueue` instance created by the :class:`telegram.ext.Updater`
which can be used to schedule new jobs. Default is ``False``.
DEPRECATED: Please switch to context based callbacks.
pass_user_data (:obj:`bool`, optional): If set to ``True``, a keyword argument called
``user_data`` will be passed to the callback function. Default is ``False``.
DEPRECATED: Please switch to context based callbacks.
pass_chat_data (:obj:`bool`, optional): If set to ``True``, a keyword argument called
``chat_data`` will be passed to the callback function. Default is ``False``.
DEPRECATED: Please switch to context based callbacks.
"""
def __init__(self,
prefix,
command,
callback,
filters=None,
pass_args=False,
pass_update_queue=False,
pass_job_queue=False,
pass_user_data=False,
pass_chat_data=False):
self._prefix = list()
self._command = list()
self._commands = list()
super(PrefixHandler, self).__init__(
'nocommand', callback, filters=filters, allow_edited=None, pass_args=pass_args,
pass_update_queue=pass_update_queue,
pass_job_queue=pass_job_queue,
pass_user_data=pass_user_data,
pass_chat_data=pass_chat_data)
self.prefix = prefix
self.command = command
self._build_commands()
@property
def prefix(self):
return self._prefix
@prefix.setter
def prefix(self, prefix):
if isinstance(prefix, string_types):
self._prefix = [prefix.lower()]
else:
self._prefix = prefix
self._build_commands()
@property
def command(self):
return self._command
@command.setter
def command(self, command):
if isinstance(command, string_types):
self._command = [command.lower()]
else:
self._command = command
self._build_commands()
def _build_commands(self):
self._commands = [x.lower() + y.lower() for x in self.prefix for y in self.command]
def check_update(self, update):
"""Determines whether an update should be passed to this handlers :attr:`callback`.
Args:
update (:class:`telegram.Update`): Incoming telegram update.
dispatcher (:class:`telegram.ext.Dispatcher`): Dispatcher that originated the Update.
Returns:
:obj:`list`: The list of args for the handler
"""
optional_args = self.collect_optional_args(dispatcher, update)
if isinstance(update, Update) and update.effective_message:
message = update.effective_message
message = update.message or update.edited_message
if message.text:
text_list = message.text.split()
if text_list[0].lower() not in self._commands:
return None
filter_result = self.filters(update)
if filter_result:
return text_list[1:], filter_result
else:
return False
if self.pass_args:
optional_args['args'] = message.text.split()[1:]
return self.callback(dispatcher.bot, update, **optional_args)
def collect_additional_context(self, context, update, dispatcher, check_result):
context.args = check_result[0]
if isinstance(check_result[1], dict):
context.update(check_result[1])
+314 -121
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2018
# Copyright (C) 2015-2020
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
@@ -19,18 +19,27 @@
"""This module contains the ConversationHandler."""
import logging
import warnings
from threading import Lock
from telegram import Update
from telegram.ext import (Handler, CallbackQueryHandler, InlineQueryHandler,
ChosenInlineResultHandler)
ChosenInlineResultHandler, CallbackContext)
from telegram.utils.promise import Promise
class _ConversationTimeoutContext(object):
def __init__(self, conversation_key, update, dispatcher, callback_context):
self.conversation_key = conversation_key
self.update = update
self.dispatcher = dispatcher
self.callback_context = callback_context
class ConversationHandler(Handler):
"""
A handler to hold a conversation with a single user by managing four collections of other
handlers. Note that neither posts in Telegram Channels, nor group interactions with multiple
users are managed by instances of this class.
handlers.
The first collection, a ``list`` named :attr:`entry_points`, is used to initiate the
conversation, for example with a :class:`telegram.ext.CommandHandler` or
@@ -38,8 +47,10 @@ class ConversationHandler(Handler):
The second collection, a ``dict`` named :attr:`states`, contains the different conversation
steps and one or more associated handlers that should be used if the user sends a message when
the conversation with them is currently in that state. You will probably use mostly
:class:`telegram.ext.MessageHandler` and :class:`telegram.ext.RegexHandler` here.
the conversation with them is currently in that state. Here you can also define a state for
:attr:`TIMEOUT` to define the behavior when :attr:`conversation_timeout` is exceeded, and a
state for :attr:`WAITING` to define behavior when a new update is received while the previous
``@run_async`` decorated handler is not finished.
The third collection, a ``list`` named :attr:`fallbacks`, is used if the user is currently in a
conversation but the state has either no associated handler or the handler that is associated
@@ -47,15 +58,26 @@ class ConversationHandler(Handler):
a regular text message is expected. You could use this for a ``/cancel`` command or to let the
user know their message was not recognized.
The fourth, optional collection of handlers, a ``list`` named :attr:`timed_out_behavior` is
used if the wait for ``run_async`` takes longer than defined in :attr:`run_async_timeout`.
For example, you can let the user know that they should wait for a bit before they can
continue.
To change the state of conversation, the callback function of a handler must return the new
state after responding to the user. If it does not return anything (returning ``None`` by
default), the state will not change. To end the conversation, the callback function must
return :attr:`END` or ``-1``.
default), the state will not change. If an entry point callback function returns None,
the conversation ends immediately after the execution of this callback function.
To end the conversation, the callback function must return :attr:`END` or ``-1``. To
handle the conversation timeout, use handler :attr:`TIMEOUT` or ``-2``.
Note:
In each of the described collections of handlers, a handler may in turn be a
:class:`ConversationHandler`. In that case, the nested :class:`ConversationHandler` should
have the attribute :attr:`map_to_parent` which allows to return to the parent conversation
at specified states within the nested conversation.
Note that the keys in :attr:`map_to_parent` must not appear as keys in :attr:`states`
attribute or else the latter will be ignored. You may map :attr:`END` to one of the parents
states to continue the parent conversation after this has ended or even map a state to
:attr:`END` to end the *parent* conversation from within the nested one. For an example on
nested :class:`ConversationHandler` s, see our `examples`_.
.. _`examples`: https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples
Attributes:
entry_points (List[:class:`telegram.ext.Handler`]): A list of ``Handler`` objects that can
@@ -66,19 +88,25 @@ class ConversationHandler(Handler):
fallbacks (List[:class:`telegram.ext.Handler`]): A list of handlers that might be used if
the user is in a conversation, but every handler for their current state returned
``False`` on :attr:`check_update`.
allow_reentry (:obj:`bool`): Optional. Determines if a user can restart a conversation with
allow_reentry (:obj:`bool`): Determines if a user can restart a conversation with
an entry point.
run_async_timeout (:obj:`float`): Optional. The time-out for ``run_async`` decorated
Handlers.
timed_out_behavior (List[:class:`telegram.ext.Handler`]): Optional. A list of handlers that
might be used if the wait for ``run_async`` timed out.
per_chat (:obj:`bool`): Optional. If the conversationkey should contain the Chat's ID.
per_user (:obj:`bool`): Optional. If the conversationkey should contain the User's ID.
per_message (:obj:`bool`): Optional. If the conversationkey should contain the Message's
per_chat (:obj:`bool`): If the conversationkey should contain the Chat's ID.
per_user (:obj:`bool`): If the conversationkey should contain the User's ID.
per_message (:obj:`bool`): If the conversationkey should contain the Message's
ID.
conversation_timeout (:obj:`float`|:obj:`datetime.timedelta`): Optional. When this handler
is inactive more than this timeout (in seconds), it will be automatically ended. If
this value is 0 (default), there will be no timeout.
conversation_timeout (:obj:`float` | :obj:`datetime.timedelta`): Optional. When this
handler is inactive more than this timeout (in seconds), it will be automatically
ended. If this value is 0 (default), there will be no timeout. When it's triggered, the
last received update and the corresponding ``context`` will be handled by ALL the
handler's who's `check_update` method returns True that are in the state
:attr:`ConversationHandler.TIMEOUT`.
name (:obj:`str`): Optional. The name for this conversationhandler. Required for
persistence
persistent (:obj:`bool`): Optional. If the conversations dict for this handler should be
saved. Name is required and persistence has to be set in :class:`telegram.ext.Updater`
map_to_parent (Dict[:obj:`object`, :obj:`object`]): Optional. A :obj:`dict` that can be
used to instruct a nested conversationhandler to transition into a mapped state on
its parent conversationhandler in place of a specified nested state.
Args:
entry_points (List[:class:`telegram.ext.Handler`]): A list of ``Handler`` objects that can
@@ -95,24 +123,25 @@ class ConversationHandler(Handler):
returns ``True`` will be used. If all return ``False``, the update is not handled.
allow_reentry (:obj:`bool`, optional): If set to ``True``, a user that is currently in a
conversation can restart the conversation by triggering one of the entry points.
run_async_timeout (:obj:`float`, optional): If the previous handler for this user was
running asynchronously using the ``run_async`` decorator, it might not be finished when
the next message arrives. This timeout defines how long the conversation handler should
wait for the next state to be computed. The default is ``None`` which means it will
wait indefinitely.
timed_out_behavior (List[:class:`telegram.ext.Handler`], optional): A list of handlers that
might be used if the wait for ``run_async`` timed out. The first handler which
:attr:`check_update` method returns ``True`` will be used. If all return ``False``,
the update is not handled.
per_chat (:obj:`bool`, optional): If the conversationkey should contain the Chat's ID.
Default is ``True``.
per_user (:obj:`bool`, optional): If the conversationkey should contain the User's ID.
Default is ``True``.
per_message (:obj:`bool`, optional): If the conversationkey should contain the Message's
ID. Default is ``False``.
conversation_timeout (:obj:`float`|:obj:`datetime.timedelta`, optional): When this handler
is inactive more than this timeout (in seconds), it will be automatically ended. If
this value is 0 or None (default), there will be no timeout.
conversation_timeout (:obj:`float` | :obj:`datetime.timedelta`, optional): When this
handler is inactive more than this timeout (in seconds), it will be automatically
ended. If this value is 0 or None (default), there will be no timeout. The last
received update and the corresponding ``context`` will be handled by ALL the handler's
who's `check_update` method returns True that are in the state
:attr:`ConversationHandler.TIMEOUT`.
name (:obj:`str`, optional): The name for this conversationhandler. Required for
persistence
persistent (:obj:`bool`, optional): If the conversations dict for this handler should be
saved. Name is required and persistence has to be set in :class:`telegram.ext.Updater`
map_to_parent (Dict[:obj:`object`, :obj:`object`], optional): A :obj:`dict` that can be
used to instruct a nested conversationhandler to transition into a mapped state on
its parent conversationhandler in place of a specified nested state.
Raises:
ValueError
@@ -120,35 +149,47 @@ class ConversationHandler(Handler):
"""
END = -1
""":obj:`int`: Used as a constant to return when a conversation is ended."""
TIMEOUT = -2
""":obj:`int`: Used as a constant to handle state when a conversation is timed out."""
WAITING = -3
""":obj:`int`: Used as a constant to handle state when a conversation is still waiting on the
previous ``@run_sync`` decorated running handler to finish."""
def __init__(self,
entry_points,
states,
fallbacks,
allow_reentry=False,
run_async_timeout=None,
timed_out_behavior=None,
per_chat=True,
per_user=True,
per_message=False,
conversation_timeout=None):
conversation_timeout=None,
name=None,
persistent=False,
map_to_parent=None):
self.entry_points = entry_points
self.states = states
self.fallbacks = fallbacks
self._entry_points = entry_points
self._states = states
self._fallbacks = fallbacks
self.allow_reentry = allow_reentry
self.run_async_timeout = run_async_timeout
self.timed_out_behavior = timed_out_behavior
self.per_user = per_user
self.per_chat = per_chat
self.per_message = per_message
self.conversation_timeout = conversation_timeout
self._allow_reentry = allow_reentry
self._per_user = per_user
self._per_chat = per_chat
self._per_message = per_message
self._conversation_timeout = conversation_timeout
self._name = name
if persistent and not self.name:
raise ValueError("Conversations can't be persistent when handler is unnamed.")
self.persistent = persistent
self._persistence = None
""":obj:`telegram.ext.BasePersistance`: The persistence used to store conversations.
Set by dispatcher"""
self._map_to_parent = map_to_parent
self.timeout_jobs = dict()
self.conversations = dict()
self.current_conversation = None
self.current_handler = None
self._timeout_jobs_lock = Lock()
self._conversations = dict()
self._conversations_lock = Lock()
self.logger = logging.getLogger(__name__)
@@ -156,8 +197,8 @@ class ConversationHandler(Handler):
raise ValueError("'per_user', 'per_chat' and 'per_message' can't all be 'False'")
if self.per_message and not self.per_chat:
logging.warning("If 'per_message=True' is used, 'per_chat=True' should also be used, "
"since message IDs are not globally unique.")
warnings.warn("If 'per_message=True' is used, 'per_chat=True' should also be used, "
"since message IDs are not globally unique.")
all_handlers = list()
all_handlers.extend(entry_points)
@@ -169,20 +210,130 @@ class ConversationHandler(Handler):
if self.per_message:
for handler in all_handlers:
if not isinstance(handler, CallbackQueryHandler):
logging.warning("If 'per_message=True', all entry points and state handlers"
" must be 'CallbackQueryHandler', since no other handlers "
"have a message context.")
warnings.warn("If 'per_message=True', all entry points and state handlers"
" must be 'CallbackQueryHandler', since no other handlers "
"have a message context.")
break
else:
for handler in all_handlers:
if isinstance(handler, CallbackQueryHandler):
logging.warning("If 'per_message=False', 'CallbackQueryHandler' will not be "
"tracked for every message.")
warnings.warn("If 'per_message=False', 'CallbackQueryHandler' will not be "
"tracked for every message.")
break
if self.per_chat:
for handler in all_handlers:
if isinstance(handler, (InlineQueryHandler, ChosenInlineResultHandler)):
logging.warning("If 'per_chat=True', 'InlineQueryHandler' can not be used, "
"since inline queries have no chat context.")
warnings.warn("If 'per_chat=True', 'InlineQueryHandler' can not be used, "
"since inline queries have no chat context.")
break
@property
def entry_points(self):
return self._entry_points
@entry_points.setter
def entry_points(self, value):
raise ValueError('You can not assign a new value to entry_points after initialization.')
@property
def states(self):
return self._states
@states.setter
def states(self, value):
raise ValueError('You can not assign a new value to states after initialization.')
@property
def fallbacks(self):
return self._fallbacks
@fallbacks.setter
def fallbacks(self, value):
raise ValueError('You can not assign a new value to fallbacks after initialization.')
@property
def allow_reentry(self):
return self._allow_reentry
@allow_reentry.setter
def allow_reentry(self, value):
raise ValueError('You can not assign a new value to allow_reentry after initialization.')
@property
def per_user(self):
return self._per_user
@per_user.setter
def per_user(self, value):
raise ValueError('You can not assign a new value to per_user after initialization.')
@property
def per_chat(self):
return self._per_chat
@per_chat.setter
def per_chat(self, value):
raise ValueError('You can not assign a new value to per_chat after initialization.')
@property
def per_message(self):
return self._per_message
@per_message.setter
def per_message(self, value):
raise ValueError('You can not assign a new value to per_message after initialization.')
@property
def conversation_timeout(self):
return self._conversation_timeout
@conversation_timeout.setter
def conversation_timeout(self, value):
raise ValueError('You can not assign a new value to conversation_timeout after '
'initialization.')
@property
def name(self):
return self._name
@name.setter
def name(self, value):
raise ValueError('You can not assign a new value to name after initialization.')
@property
def map_to_parent(self):
return self._map_to_parent
@map_to_parent.setter
def map_to_parent(self, value):
raise ValueError('You can not assign a new value to map_to_parent after initialization.')
@property
def persistence(self):
return self._persistence
@persistence.setter
def persistence(self, persistence):
self._persistence = persistence
# Set persistence for nested conversations
for handlers in self.states.values():
for handler in handlers:
if isinstance(handler, ConversationHandler):
handler.persistence = self.persistence
@property
def conversations(self):
return self._conversations
@conversations.setter
def conversations(self, value):
self._conversations = value
# Set conversations for nested conversations
for handlers in self.states.values():
for handler in handlers:
if isinstance(handler, ConversationHandler):
handler.conversations = self.persistence.get_conversations(handler.name)
def _get_key(self, update):
chat = update.effective_chat
@@ -215,44 +366,43 @@ class ConversationHandler(Handler):
"""
# Ignore messages in channels
if (not isinstance(update, Update) or
update.channel_post or
self.per_chat and not update.effective_chat or
self.per_message and not update.callback_query or
update.callback_query and self.per_chat and not update.callback_query.message):
return False
if (not isinstance(update, Update)
or update.channel_post
or self.per_chat and not update.effective_chat
or self.per_message and not update.callback_query
or update.callback_query and self.per_chat and not update.callback_query.message):
return None
key = self._get_key(update)
state = self.conversations.get(key)
with self._conversations_lock:
state = self.conversations.get(key)
# Resolve promises
if isinstance(state, tuple) and len(state) is 2 and isinstance(state[1], Promise):
if isinstance(state, tuple) and len(state) == 2 and isinstance(state[1], Promise):
self.logger.debug('waiting for promise...')
old_state, new_state = state
error = False
try:
res = new_state.result(timeout=self.run_async_timeout)
except Exception as exc:
self.logger.exception("Promise function raised exception")
self.logger.exception("{}".format(exc))
error = True
if not error and new_state.done.is_set():
self.update_state(res, key)
state = self.conversations.get(key)
if new_state.done.wait(0):
try:
res = new_state.result(0)
res = res if res is not None else old_state
except Exception as exc:
self.logger.exception("Promise function raised exception")
self.logger.exception("{}".format(exc))
res = old_state
finally:
if res is None and old_state is None:
res = self.END
self.update_state(res, key)
with self._conversations_lock:
state = self.conversations.get(key)
else:
for candidate in (self.timed_out_behavior or []):
if candidate.check_update(update):
# Save the current user and the selected handler for handle_update
self.current_conversation = key
self.current_handler = candidate
return True
else:
return False
handlers = self.states.get(self.WAITING, [])
for handler in handlers:
check = handler.check_update(update)
if check is not None and check is not False:
return key, handler, check
return None
self.logger.debug('selecting conversation %s with state %s' % (str(key), str(state)))
@@ -261,73 +411,116 @@ class ConversationHandler(Handler):
# Search entry points for a match
if state is None or self.allow_reentry:
for entry_point in self.entry_points:
if entry_point.check_update(update):
check = entry_point.check_update(update)
if check is not None and check is not False:
handler = entry_point
break
else:
if state is None:
return False
return None
# Get the handler list for current state, if we didn't find one yet and we're still here
if state is not None and not handler:
handlers = self.states.get(state)
for candidate in (handlers or []):
if candidate.check_update(update):
check = candidate.check_update(update)
if check is not None and check is not False:
handler = candidate
break
# Find a fallback handler if all other handlers fail
else:
for fallback in self.fallbacks:
if fallback.check_update(update):
check = fallback.check_update(update)
if check is not None and check is not False:
handler = fallback
break
else:
return False
return None
# Save the current user and the selected handler for handle_update
self.current_conversation = key
self.current_handler = handler
return key, handler, check
return True
def handle_update(self, update, dispatcher):
def handle_update(self, update, dispatcher, check_result, context=None):
"""Send the update to the callback for the current state and Handler
Args:
check_result: The result from check_update. For this handler it's a tuple of key,
handler, and the handler's check result.
update (:class:`telegram.Update`): Incoming telegram update.
dispatcher (:class:`telegram.ext.Dispatcher`): Dispatcher that originated the Update.
"""
new_state = self.current_handler.handle_update(update, dispatcher)
timeout_job = self.timeout_jobs.pop(self.current_conversation, None)
conversation_key, handler, check_result = check_result
if timeout_job is not None:
timeout_job.schedule_removal()
if self.conversation_timeout and new_state != self.END:
self.timeout_jobs[self.current_conversation] = dispatcher.job_queue.run_once(
self._trigger_timeout, self.conversation_timeout,
context=self.current_conversation
)
with self._timeout_jobs_lock:
# Remove the old timeout job (if present)
timeout_job = self.timeout_jobs.pop(conversation_key, None)
self.update_state(new_state, self.current_conversation)
if timeout_job is not None:
timeout_job.schedule_removal()
new_state = handler.handle_update(update, dispatcher, check_result, context)
with self._timeout_jobs_lock:
if self.conversation_timeout and new_state != self.END:
# Add the new timeout job
self.timeout_jobs[conversation_key] = dispatcher.job_queue.run_once(
self._trigger_timeout, self.conversation_timeout,
context=_ConversationTimeoutContext(conversation_key, update,
dispatcher, context))
if isinstance(self.map_to_parent, dict) and new_state in self.map_to_parent:
self.update_state(self.END, conversation_key)
return self.map_to_parent.get(new_state)
else:
self.update_state(new_state, conversation_key)
def update_state(self, new_state, key):
if new_state == self.END:
if key in self.conversations:
del self.conversations[key]
else:
pass
with self._conversations_lock:
if key in self.conversations:
# If there is no key in conversations, nothing is done.
del self.conversations[key]
if self.persistent:
self.persistence.update_conversation(self.name, key, None)
elif isinstance(new_state, Promise):
self.conversations[key] = (self.conversations.get(key), new_state)
with self._conversations_lock:
self.conversations[key] = (self.conversations.get(key), new_state)
if self.persistent:
self.persistence.update_conversation(self.name, key,
(self.conversations.get(key), new_state))
elif new_state is not None:
self.conversations[key] = new_state
with self._conversations_lock:
self.conversations[key] = new_state
if self.persistent:
self.persistence.update_conversation(self.name, key, new_state)
def _trigger_timeout(self, bot, job):
del self.timeout_jobs[job.context]
self.update_state(self.END, job.context)
def _trigger_timeout(self, context, job=None):
self.logger.debug('conversation timeout was triggered!')
# Backward compatibility with bots that do not use CallbackContext
callback_context = None
if isinstance(context, CallbackContext):
job = context.job
context = job.context
callback_context = context.callback_context
with self._timeout_jobs_lock:
found_job = self.timeout_jobs[context.conversation_key]
if found_job is not job:
# The timeout has been canceled in handle_update
return
del self.timeout_jobs[context.conversation_key]
handlers = self.states.get(self.TIMEOUT, [])
for handler in handlers:
check = handler.check_update(context.update)
if check is not None and check is not False:
handler.handle_update(context.update, context.dispatcher, check, callback_context)
self.update_state(self.END, context.conversation_key)
+127
View File
@@ -0,0 +1,127 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2020
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains the class Defaults, which allows to pass default values to Updater."""
from telegram.utils.helpers import DEFAULT_NONE
class Defaults:
"""Convenience Class to gather all parameters with a (user defined) default value
Attributes:
parse_mode (:obj:`str`): Optional. Send Markdown or HTML, if you want Telegram apps to show
bold, italic, fixed-width toxt or URLs in your bot's message.
disable_notification (:obj:`bool`): Optional. Sends the message silently. Users will
receive a notification with no sound.
disable_web_page_preview (:obj:`bool`): Optional. Disables link previews for links in this
message.
timeout (:obj:`int` | :obj:`float`): Optional. If this value is specified, use it as the
read timeout from the server (instead of the one specified during creation of the
connection pool).
quote (:obj:`bool`): Optional. If set to ``True``, the reply is sent as an actual reply to
the message. If ``reply_to_message_id`` is passed in ``kwargs``, this parameter will
be ignored. Default: ``True`` in group chats and ``False`` in private chats.
Parameters:
parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show
bold, italic, fixed-width toxt or URLs in your bot's message.
disable_notification (:obj:`bool`, optional): Sends the message silently. Users will
receive a notification with no sound.
disable_web_page_preview (:obj:`bool`, optional): Disables link previews for links in this
message.
timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as the
read timeout from the server (instead of the one specified during creation of the
connection pool).
quote (:obj:`bool`, opitonal): If set to ``True``, the reply is sent as an actual reply to
the message. If ``reply_to_message_id`` is passed in ``kwargs``, this parameter will
be ignored. Default: ``True`` in group chats and ``False`` in private chats.
"""
def __init__(self,
parse_mode=None,
disable_notification=None,
disable_web_page_preview=None,
# Timeout needs special treatment, since the bot methods have two different
# default values for timeout (None and 20s)
timeout=DEFAULT_NONE,
quote=None):
self._parse_mode = parse_mode
self._disable_notification = disable_notification
self._disable_web_page_preview = disable_web_page_preview
self._timeout = timeout
self._quote = quote
@property
def parse_mode(self):
return self._parse_mode
@parse_mode.setter
def parse_mode(self, value):
raise AttributeError("You can not assign a new value to defaults after because it would "
"not have any effect.")
@property
def disable_notification(self):
return self._disable_notification
@disable_notification.setter
def disable_notification(self, value):
raise AttributeError("You can not assign a new value to defaults after because it would "
"not have any effect.")
@property
def disable_web_page_preview(self):
return self._disable_web_page_preview
@disable_web_page_preview.setter
def disable_web_page_preview(self, value):
raise AttributeError("You can not assign a new value to defaults after because it would "
"not have any effect.")
@property
def timeout(self):
return self._timeout
@timeout.setter
def timeout(self, value):
raise AttributeError("You can not assign a new value to defaults after because it would "
"not have any effect.")
@property
def quote(self):
return self._quote
@quote.setter
def quote(self, value):
raise AttributeError("You can not assign a new value to defaults after because it would "
"not have any effect.")
def __hash__(self):
return hash((self._parse_mode,
self._disable_notification,
self._disable_web_page_preview,
self._timeout,
self._quote))
def __eq__(self, other):
if isinstance(other, Defaults):
return self.__dict__ == other.__dict__
return False
def __ne__(self, other):
return not self == other
+259
View File
@@ -0,0 +1,259 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2020
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains the DictPersistence class."""
from copy import deepcopy
from telegram.utils.helpers import decode_user_chat_data_from_json,\
decode_conversations_from_json, encode_conversations_to_json
try:
import ujson as json
except ImportError:
import json
from collections import defaultdict
from telegram.ext import BasePersistence
class DictPersistence(BasePersistence):
"""Using python's dicts and json for making your bot persistent.
Attributes:
store_user_data (:obj:`bool`): Whether user_data should be saved by this
persistence class.
store_chat_data (:obj:`bool`): Whether chat_data should be saved by this
persistence class.
store_bot_data (:obj:`bool`): Whether bot_data should be saved by this
persistence class.
Args:
store_user_data (:obj:`bool`, optional): Whether user_data should be saved by this
persistence class. Default is ``True``.
store_chat_data (:obj:`bool`, optional): Whether user_data should be saved by this
persistence class. Default is ``True``.
store_bot_data (:obj:`bool`, optional): Whether bot_data should be saved by this
persistence class. Default is ``True`` .
user_data_json (:obj:`str`, optional): Json string that will be used to reconstruct
user_data on creating this persistence. Default is ``""``.
chat_data_json (:obj:`str`, optional): Json string that will be used to reconstruct
chat_data on creating this persistence. Default is ``""``.
bot_data_json (:obj:`str`, optional): Json string that will be used to reconstruct
bot_data on creating this persistence. Default is ``""``.
conversations_json (:obj:`str`, optional): Json string that will be used to reconstruct
conversation on creating this persistence. Default is ``""``.
"""
def __init__(self,
store_user_data=True,
store_chat_data=True,
store_bot_data=True,
user_data_json='',
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)
self._user_data = None
self._chat_data = None
self._bot_data = None
self._conversations = None
self._user_data_json = None
self._chat_data_json = None
self._bot_data_json = None
self._conversations_json = None
if user_data_json:
try:
self._user_data = decode_user_chat_data_from_json(user_data_json)
self._user_data_json = user_data_json
except (ValueError, AttributeError):
raise TypeError("Unable to deserialize user_data_json. Not valid JSON")
if chat_data_json:
try:
self._chat_data = decode_user_chat_data_from_json(chat_data_json)
self._chat_data_json = chat_data_json
except (ValueError, AttributeError):
raise TypeError("Unable to deserialize chat_data_json. Not valid JSON")
if bot_data_json:
try:
self._bot_data = json.loads(bot_data_json)
self._bot_data_json = bot_data_json
except (ValueError, AttributeError):
raise TypeError("Unable to deserialize bot_data_json. Not valid JSON")
if not isinstance(self._bot_data, dict):
raise TypeError("bot_data_json must be serialized dict")
if conversations_json:
try:
self._conversations = decode_conversations_from_json(conversations_json)
self._conversations_json = conversations_json
except (ValueError, AttributeError):
raise TypeError("Unable to deserialize conversations_json. Not valid JSON")
@property
def user_data(self):
""":obj:`dict`: The user_data as a dict"""
return self._user_data
@property
def user_data_json(self):
""":obj:`str`: The user_data serialized as a JSON-string."""
if self._user_data_json:
return self._user_data_json
else:
return json.dumps(self.user_data)
@property
def chat_data(self):
""":obj:`dict`: The chat_data as a dict"""
return self._chat_data
@property
def chat_data_json(self):
""":obj:`str`: The chat_data serialized as a JSON-string."""
if self._chat_data_json:
return self._chat_data_json
else:
return json.dumps(self.chat_data)
@property
def bot_data(self):
""":obj:`dict`: The bot_data as a dict"""
return self._bot_data
@property
def bot_data_json(self):
""":obj:`str`: The bot_data serialized as a JSON-string."""
if self._bot_data_json:
return self._bot_data_json
else:
return json.dumps(self.bot_data)
@property
def conversations(self):
""":obj:`dict`: The conversations as a dict"""
return self._conversations
@property
def conversations_json(self):
""":obj:`str`: The conversations serialized as a JSON-string."""
if self._conversations_json:
return self._conversations_json
else:
return encode_conversations_to_json(self.conversations)
def get_user_data(self):
"""Returns the user_data created from the ``user_data_json`` or an empty defaultdict.
Returns:
:obj:`defaultdict`: The restored user data.
"""
if self.user_data:
pass
else:
self._user_data = defaultdict(dict)
return deepcopy(self.user_data)
def get_chat_data(self):
"""Returns the chat_data created from the ``chat_data_json`` or an empty defaultdict.
Returns:
:obj:`defaultdict`: The restored user data.
"""
if self.chat_data:
pass
else:
self._chat_data = defaultdict(dict)
return deepcopy(self.chat_data)
def get_bot_data(self):
"""Returns the bot_data created from the ``bot_data_json`` or an empty dict.
Returns:
:obj:`defaultdict`: The restored user data.
"""
if self.bot_data:
pass
else:
self._bot_data = {}
return deepcopy(self.bot_data)
def get_conversations(self, name):
"""Returns the conversations created from the ``conversations_json`` or an empty
defaultdict.
Returns:
:obj:`defaultdict`: The restored user data.
"""
if self.conversations:
pass
else:
self._conversations = {}
return self.conversations.get(name, {}).copy()
def update_conversation(self, name, key, new_state):
"""Will update the conversations for the given handler.
Args:
name (:obj:`str`): The handlers name.
key (:obj:`tuple`): The key the state is changed for.
new_state (:obj:`tuple` | :obj:`any`): The new state for the given key.
"""
if self._conversations.setdefault(name, {}).get(key) == new_state:
return
self._conversations[name][key] = new_state
self._conversations_json = None
def update_user_data(self, user_id, data):
"""Will update the user_data (if changed).
Args:
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].
"""
if self._user_data is None:
self._user_data = defaultdict(dict)
if self._user_data.get(user_id) == data:
return
self._user_data[user_id] = data
self._user_data_json = None
def update_chat_data(self, chat_id, data):
"""Will update the chat_data (if changed).
Args:
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].
"""
if self._chat_data is None:
self._chat_data = defaultdict(dict)
if self._chat_data.get(chat_id) == data:
return
self._chat_data[chat_id] = data
self._chat_data_json = None
def update_bot_data(self, data):
"""Will update the bot_data (if changed).
Args:
data (:obj:`dict`): The :attr:`telegram.ext.dispatcher.bot_data`.
"""
if self._bot_data == data:
return
self._bot_data = data.copy()
self._bot_data_json = None
+171 -28
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2018
# Copyright (C) 2015-2020
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
@@ -19,6 +19,7 @@
"""This module contains the Dispatcher class."""
import logging
import warnings
import weakref
from functools import wraps
from threading import Thread, Lock, Event, current_thread, BoundedSemaphore
@@ -30,24 +31,30 @@ from queue import Queue, Empty
from future.builtins import range
from telegram import TelegramError
from telegram import TelegramError, Update
from telegram.ext.handler import Handler
from telegram.ext.callbackcontext import CallbackContext
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
def run_async(func):
"""Function decorator that will run the function in a new thread.
"""
Function decorator that will run the function in a new thread.
Will run :attr:`telegram.ext.Dispatcher.run_async`.
Using this decorator is only possible when only a single Dispatcher exist in the system.
Note: Use this decorator to run handlers asynchronously.
Warning:
If you're using @run_async you cannot rely on adding custom attributes to
:class:`telegram.ext.CallbackContext`. See its docs for more info.
"""
@wraps(func)
def async_func(*args, **kwargs):
return Dispatcher.get_instance().run_async(func, *args, **kwargs)
@@ -70,6 +77,11 @@ class Dispatcher(object):
instance to pass onto handler callbacks.
workers (:obj:`int`): Number of maximum concurrent worker threads for the ``@run_async``
decorator.
user_data (:obj:`defaultdict`): A dictionary handlers can use to store data for the user.
chat_data (:obj:`defaultdict`): A dictionary handlers can use to store data for the chat.
bot_data (:obj:`dict`): A dictionary handlers can use to store data for the bot.
persistence (:class:`telegram.ext.BasePersistence`): Optional. The persistence class to
store data that should be persistent over restarts
Args:
bot (:class:`telegram.Bot`): The bot object that should be passed to the handlers.
@@ -78,6 +90,11 @@ class Dispatcher(object):
instance to pass onto handler callbacks.
workers (:obj:`int`, optional): Number of maximum concurrent worker threads for the
``@run_async`` decorator. defaults to 4.
persistence (:class:`telegram.ext.BasePersistence`, optional): The persistence class to
store data that should be persistent over restarts
use_context (:obj:`bool`, optional): If set to ``True`` Use the context based callback API.
During the deprecation period of the old API the default is ``False``. **New users**:
set this to ``True``.
"""
@@ -86,16 +103,46 @@ class Dispatcher(object):
__singleton = None
logger = logging.getLogger(__name__)
def __init__(self, bot, update_queue, workers=4, exception_event=None, job_queue=None):
def __init__(self,
bot,
update_queue,
workers=4,
exception_event=None,
job_queue=None,
persistence=None,
use_context=False):
self.bot = bot
self.update_queue = update_queue
self.job_queue = job_queue
self.workers = workers
self.use_context = use_context
if not use_context:
warnings.warn('Old Handler API is deprecated - see https://git.io/fxJuV for details',
TelegramDeprecationWarning, stacklevel=3)
self.user_data = defaultdict(dict)
""":obj:`dict`: A dictionary handlers can use to store data for the user."""
self.chat_data = defaultdict(dict)
""":obj:`dict`: A dictionary handlers can use to store data for the chat."""
self.bot_data = {}
if persistence:
if not isinstance(persistence, BasePersistence):
raise TypeError("persistence should be based on telegram.ext.BasePersistence")
self.persistence = persistence
if self.persistence.store_user_data:
self.user_data = self.persistence.get_user_data()
if not isinstance(self.user_data, defaultdict):
raise ValueError("user_data must be of type defaultdict")
if self.persistence.store_chat_data:
self.chat_data = self.persistence.get_chat_data()
if not isinstance(self.chat_data, defaultdict):
raise ValueError("chat_data must be of type defaultdict")
if self.persistence.store_bot_data:
self.bot_data = self.persistence.get_bot_data()
if not isinstance(self.bot_data, dict):
raise ValueError("bot_data must be of type dict")
else:
self.persistence = None
self.handlers = {}
"""Dict[:obj:`int`, List[:class:`telegram.ext.Handler`]]: Holds the handlers per group."""
self.groups = []
@@ -118,11 +165,16 @@ class Dispatcher(object):
else:
self._set_singleton(None)
@property
def exception_event(self):
return self.__exception_event
def _init_async_threads(self, base_name, workers):
base_name = '{}_'.format(base_name) if base_name else ''
for i in range(workers):
thread = Thread(target=self._pooled, name='{}{}'.format(base_name, i))
thread = Thread(target=self._pooled, name='Bot:{}:worker:{}{}'.format(self.bot.id,
base_name, i))
self.__async_threads.add(thread)
thread.start()
@@ -168,6 +220,10 @@ class Dispatcher(object):
def run_async(self, func, *args, **kwargs):
"""Queue a function (with given args/kwargs) to be run asynchronously.
Warning:
If you're using @run_async you cannot rely on adding custom attributes to
:class:`telegram.ext.CallbackContext`. See its docs for more info.
Args:
func (:obj:`callable`): The function to run in the thread.
*args (:obj:`tuple`, optional): Arguments to `func`.
@@ -226,6 +282,7 @@ class Dispatcher(object):
self.logger.debug('Processing Update: %s' % update)
self.process_update(update)
self.update_queue.task_done()
self.running = False
self.logger.debug('Dispatcher thread stopped')
@@ -265,6 +322,7 @@ class Dispatcher(object):
The update to process.
"""
# An error happened while polling
if isinstance(update, TelegramError):
try:
@@ -273,37 +331,43 @@ class Dispatcher(object):
self.logger.exception('An uncaught error was raised while handling the error')
return
context = None
for group in self.groups:
try:
for handler in (x for x in self.handlers[group] if x.check_update(update)):
handler.handle_update(update, self)
break
for handler in self.handlers[group]:
check = handler.check_update(update)
if check is not None and check is not False:
if not context and self.use_context:
context = CallbackContext.from_update(update, self)
handler.handle_update(update, self, check, context)
self.update_persistence(update=update)
break
# Stop processing with any other handler.
except DispatcherHandlerStop:
self.logger.debug('Stopping further handlers due to DispatcherHandlerStop')
self.update_persistence(update=update)
break
# Dispatch any error.
except TelegramError as te:
self.logger.warning('A TelegramError was raised while processing the Update')
except Exception as e:
try:
self.dispatch_error(update, te)
self.dispatch_error(update, e)
except DispatcherHandlerStop:
self.logger.debug('Error handler stopped further handlers')
break
# Errors should not stop the thread.
except Exception:
self.logger.exception('An uncaught error was raised while handling the error')
# Errors should not stop the thread.
except Exception:
self.logger.exception('An uncaught error was raised while processing the update')
self.logger.exception('An error was raised while processing the update and an '
'uncaught error was raised while handling the error '
'with an error_handler')
def add_handler(self, handler, group=DEFAULT_GROUP):
"""Register a handler.
TL;DR: Order and priority counts. 0 or 1 handlers per group will be used.
TL;DR: Order and priority counts. 0 or 1 handlers per group will be used. End handling of
update with :class:`telegram.ext.DispatcherHandlerStop`.
A handler must be an instance of a subclass of :class:`telegram.ext.Handler`. All handlers
are organized in groups with a numeric value. The default group is 0. All groups will be
@@ -324,11 +388,20 @@ class Dispatcher(object):
group (:obj:`int`, optional): The group identifier. Default is 0.
"""
# Unfortunately due to circular imports this has to be here
from .conversationhandler import ConversationHandler
if not isinstance(handler, Handler):
raise TypeError('handler is not an instance of {0}'.format(Handler.__name__))
if not isinstance(group, int):
raise TypeError('group is not int')
if isinstance(handler, ConversationHandler) and handler.persistent:
if not self.persistence:
raise ValueError(
"Conversationhandler {} can not be persistent if dispatcher has no "
"persistence".format(handler.name))
handler.persistence = self.persistence
handler.conversations = self.persistence.get_conversations(handler.name)
if group not in self.handlers:
self.handlers[group] = list()
@@ -351,13 +424,80 @@ class Dispatcher(object):
del self.handlers[group]
self.groups.remove(group)
def add_error_handler(self, callback):
"""Registers an error handler in the Dispatcher.
def update_persistence(self, update=None):
"""Update :attr:`user_data`, :attr:`chat_data` and :attr:`bot_data` in :attr:`persistence`.
Args:
callback (:obj:`callable`): A function that takes ``Bot, Update, TelegramError`` as
arguments.
update (:class:`telegram.Update`, optional): The update to process. If passed, only the
corresponding ``user_data`` and ``chat_data`` will be updated.
"""
if self.persistence:
chat_ids = self.chat_data.keys()
user_ids = self.user_data.keys()
if isinstance(update, Update):
if update.effective_chat:
chat_ids = [update.effective_chat.id]
else:
chat_ids = []
if update.effective_user:
user_ids = [update.effective_user.id]
else:
user_ids = []
if self.persistence.store_bot_data:
try:
self.persistence.update_bot_data(self.bot_data)
except Exception as e:
try:
self.dispatch_error(update, e)
except Exception:
message = 'Saving bot data raised an error and an ' \
'uncaught error was raised while handling ' \
'the error with an error_handler'
self.logger.exception(message)
if self.persistence.store_chat_data:
for chat_id in chat_ids:
try:
self.persistence.update_chat_data(chat_id, self.chat_data[chat_id])
except Exception as e:
try:
self.dispatch_error(update, e)
except Exception:
message = 'Saving chat data raised an error and an ' \
'uncaught error was raised while handling ' \
'the error with an error_handler'
self.logger.exception(message)
if self.persistence.store_user_data:
for user_id in user_ids:
try:
self.persistence.update_user_data(user_id, self.user_data[user_id])
except Exception as e:
try:
self.dispatch_error(update, e)
except Exception:
message = 'Saving user data raised an error and an ' \
'uncaught error was raised while handling ' \
'the error with an error_handler'
self.logger.exception(message)
def add_error_handler(self, callback):
"""Registers an error handler in the Dispatcher. This handler will receive every error
which happens in your bot.
Warning: The errors handled within these handlers won't show up in the logger, so you
need to make sure that you reraise the error.
Args:
callback (:obj:`callable`): The callback function for this error handler. Will be
called when an error is raised. Callback signature for context based API:
``def callback(update: Update, context: CallbackContext)``
The error that happened will be present in context.error.
Note:
See https://git.io/fxJuV for more info about switching to context based API.
"""
self.error_handlers.append(callback)
@@ -376,12 +516,15 @@ class Dispatcher(object):
Args:
update (:obj:`str` | :class:`telegram.Update` | None): The update that caused the error
error (:class:`telegram.TelegramError`): The Telegram error that was raised.
error (:obj:`Exception`): The error that was raised.
"""
if self.error_handlers:
for callback in self.error_handlers:
callback(self.bot, update, error)
if self.use_context:
callback(update, CallbackContext.from_error(update, error, self))
else:
callback(self.bot, update, error)
else:
self.logger.exception(
+471 -80
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2018
# Copyright (C) 2015-2020
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
@@ -19,9 +19,13 @@
"""This module contains the Filters for use with the MessageHandler class."""
import re
from telegram import Chat
from future.utils import string_types
from telegram import Chat, Update, MessageEntity
__all__ = ['Filters', 'BaseFilter', 'InvertedFilter', 'MergedFilter']
class BaseFilter(object):
"""Base class for all Message Filters.
@@ -45,6 +49,16 @@ class BaseFilter(object):
>>> (Filters.text & (Filters.entity(URL) | Filters.entity(TEXT_LINK)))
>>> Filters.text & (~ Filters.forwarded)
Note:
Filters use the same short circuiting logic as python's `and`, `or` and `not`.
This means that for example:
>>> Filters.regex(r'(a?x)') | Filters.regex(r'(b?x)')
With a message.text of `x`, will only ever return the matches for the first filter,
since the second one is never evaluated.
If you want to create your own filters create a class inheriting from this class and implement
a `filter` method that returns a boolean: `True` if the message should be handled, `False`
otherwise. Note that the filters work only as class instances, not actual class objects
@@ -56,13 +70,23 @@ class BaseFilter(object):
Attributes:
name (:obj:`str`): Name for this filter. Defaults to the type of filter.
update_filter (:obj:`bool`): Whether this filter should work on update. If ``False`` it
will run the filter on :attr:`update.effective_message``. Default is ``False``.
data_filter (:obj:`bool`): Whether this filter is a data filter. A data filter should
return a dict with lists. The dict will be merged with
:class:`telegram.ext.CallbackContext`'s internal dict in most cases
(depends on the handler).
"""
name = None
update_filter = False
data_filter = False
def __call__(self, message):
return self.filter(message)
def __call__(self, update):
if self.update_filter:
return self.filter(update)
else:
return self.filter(update.effective_message)
def __and__(self, other):
return MergedFilter(self, and_filter=other)
@@ -79,14 +103,18 @@ class BaseFilter(object):
self.name = self.__class__.__name__
return self.name
def filter(self, message):
def filter(self, update):
"""This method must be overwritten.
Note:
If :attr:`update_filter` is false then the first argument is `message` and of
type :class:`telegram.Message`.
Args:
message (:class:`telegram.Message`): The message that is tested.
update (:class:`telegram.Update`): The update that is tested.
Returns:
:obj:`bool`
:obj:`dict` or :obj:`bool`
"""
@@ -100,12 +128,13 @@ class InvertedFilter(BaseFilter):
f: The filter to invert.
"""
update_filter = True
def __init__(self, f):
self.f = f
def filter(self, message):
return not self.f(message)
def filter(self, update):
return not bool(self.f(update))
def __repr__(self):
return "<inverted {}>".format(self.f)
@@ -120,17 +149,66 @@ class MergedFilter(BaseFilter):
or_filter: Optional filter to "or" with base_filter. Mutually exclusive with and_filter.
"""
update_filter = True
def __init__(self, base_filter, and_filter=None, or_filter=None):
self.base_filter = base_filter
if self.base_filter.data_filter:
self.data_filter = True
self.and_filter = and_filter
if (self.and_filter
and not isinstance(self.and_filter, bool)
and self.and_filter.data_filter):
self.data_filter = True
self.or_filter = or_filter
if (self.or_filter
and not isinstance(self.and_filter, bool)
and self.or_filter.data_filter):
self.data_filter = True
def filter(self, message):
def _merge(self, base_output, comp_output):
base = base_output if isinstance(base_output, dict) else {}
comp = comp_output if isinstance(comp_output, dict) else {}
for k in comp.keys():
# Make sure comp values are lists
comp_value = comp[k] if isinstance(comp[k], list) else []
try:
# If base is a list then merge
if isinstance(base[k], list):
base[k] += comp_value
else:
base[k] = [base[k]] + comp_value
except KeyError:
base[k] = comp_value
return base
def filter(self, update):
base_output = self.base_filter(update)
# We need to check if the filters are data filters and if so return the merged data.
# If it's not a data filter or an or_filter but no matches return bool
if self.and_filter:
return self.base_filter(message) and self.and_filter(message)
# And filter needs to short circuit if base is falsey
if base_output:
comp_output = self.and_filter(update)
if comp_output:
if self.data_filter:
merged = self._merge(base_output, comp_output)
if merged:
return merged
return True
elif self.or_filter:
return self.base_filter(message) or self.or_filter(message)
# Or filter needs to short circuit if base is truthey
if base_output:
if self.data_filter:
return base_output
return True
else:
comp_output = self.or_filter(update)
if comp_output:
if self.data_filter:
return comp_output
return True
return False
def __repr__(self):
return "<{} {} {}>".format(self.base_filter, "and" if self.and_filter else "or",
@@ -153,54 +231,174 @@ class Filters(object):
return True
all = _All()
""":obj:`Filter`: All Messages."""
"""All Messages."""
class _Text(BaseFilter):
name = 'Filters.text'
class _TextStrings(BaseFilter):
def __init__(self, strings):
self.strings = strings
self.name = 'Filters.text({})'.format(strings)
def filter(self, message):
if message.text:
return message.text in self.strings
return False
def __call__(self, update):
if isinstance(update, Update):
return self.filter(update.effective_message)
else:
return self._TextStrings(update)
def filter(self, message):
return bool(message.text and not message.text.startswith('/'))
return bool(message.text)
text = _Text()
""":obj:`Filter`: Text Messages."""
"""Text Messages. If a list of strings is passed, it filters messages to only allow those
whose text is appearing in the given list.
Examples:
To allow any text message, simply use
``MessageHandler(Filters.text, callback_method)``.
A simple usecase for passing a list is to allow only messages that were send by a
custom :class:`telegram.ReplyKeyboardMarkup`::
buttons = ['Start', 'Settings', 'Back']
markup = ReplyKeyboardMarkup.from_column(buttons)
...
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``.
Args:
update (List[:obj:`str`] | Tuple[:obj:`str`], optional): Which messages to allow. Only
exact matches are allowed. If not specified, will allow any text message.
"""
class _Caption(BaseFilter):
name = 'Filters.caption'
class _CaptionStrings(BaseFilter):
def __init__(self, strings):
self.strings = strings
self.name = 'Filters.caption({})'.format(strings)
def filter(self, message):
if message.caption:
return message.caption in self.strings
return False
def __call__(self, update):
if isinstance(update, Update):
return self.filter(update.effective_message)
else:
return self._CaptionStrings(update)
def filter(self, message):
return bool(message.caption)
caption = _Caption()
"""Messages with a caption. If a list of strings is passed, it filters messages to only
allow those whose caption is appearing in the given list.
Examples:
``MessageHandler(Filters.caption, callback_method)``
Args:
update (List[:obj:`str`] | Tuple[:obj:`str`], optional): Which captions to allow. Only
exact matches are allowed. If not specified, will allow any message with a caption.
"""
class _Command(BaseFilter):
name = 'Filters.command'
class _CommandOnlyStart(BaseFilter):
def __init__(self, only_start):
self.only_start = only_start
self.name = 'Filters.command({})'.format(only_start)
def filter(self, message):
return (message.entities
and any([e.type == MessageEntity.BOT_COMMAND for e in message.entities]))
def __call__(self, update):
if isinstance(update, Update):
return self.filter(update.effective_message)
else:
return self._CommandOnlyStart(update)
def filter(self, message):
return bool(message.text and message.text.startswith('/'))
return (message.entities and message.entities[0].type == MessageEntity.BOT_COMMAND
and message.entities[0].offset == 0)
command = _Command()
""":obj:`Filter`: Messages starting with ``/``."""
"""
Messages with a :attr:`telegram.MessageEntity.BOT_COMMAND`. By default only allows
messages `starting` with a bot command. Pass ``False`` to also allow messages that contain a
bot command `anywhere` in the text.
Examples::
MessageHandler(Filters.command, command_at_start_callback)
MessageHandler(Filters.command(False), command_anywhere_callback)
Args:
update (:obj:`bool`, optional): Whether to only allow messages that `start` with a bot
command. Defaults to ``True``.
"""
class regex(BaseFilter):
"""
Filters updates by searching for an occurence of ``pattern`` in the message text.
Filters updates by searching for an occurrence of ``pattern`` in the message text.
The ``re.search`` function is used to determine whether an update should be filtered.
Refer to the documentation of the ``re`` module for more information.
Note: Does not allow passing groups or a groupdict like the ``RegexHandler`` yet,
but this will probably be implemented in a future update, gradually phasing out the
RegexHandler (see https://github.com/python-telegram-bot/python-telegram-bot/issues/835).
To get the groups and groupdict matched, see :attr:`telegram.ext.CallbackContext.matches`.
Examples:
Example ``CommandHandler("start", deep_linked_callback, Filters.regex('parameter'))``
Use ``MessageHandler(Filters.regex(r'help'), callback)`` to capture all messages that
contain the word help. You can also use
``MessageHandler(Filters.regex(re.compile(r'help', re.IGNORECASE), callback)`` if
you want your pattern to be case insensitive. This approach is recommended
if you need to specify flags on your pattern.
Note:
Filters use the same short circuiting logic as python's `and`, `or` and `not`.
This means that for example:
>>> Filters.regex(r'(a?x)') | Filters.regex(r'(b?x)')
With a message.text of `x`, will only ever return the matches for the first filter,
since the second one is never evaluated.
Args:
pattern (:obj:`str` | :obj:`Pattern`): The regex pattern.
"""
data_filter = True
def __init__(self, pattern):
self.pattern = re.compile(pattern)
if isinstance(pattern, string_types):
pattern = re.compile(pattern)
self.pattern = pattern
self.name = 'Filters.regex({})'.format(self.pattern)
# TODO: Once the callback revamp (#1026) is done, the regex filter should be able to pass
# the matched groups and groupdict to the context object.
def filter(self, message):
"""""" # remove method from docs
if message.text:
return bool(self.pattern.search(message.text))
return False
match = self.pattern.search(message.text)
if match:
return {'matches': [match]}
return {}
class _Reply(BaseFilter):
name = 'Filters.reply'
@@ -209,7 +407,7 @@ class Filters(object):
return bool(message.reply_to_message)
reply = _Reply()
""":obj:`Filter`: Messages that are a reply to another message."""
"""Messages that are a reply to another message."""
class _Audio(BaseFilter):
name = 'Filters.audio'
@@ -218,7 +416,7 @@ class Filters(object):
return bool(message.audio)
audio = _Audio()
""":obj:`Filter`: Messages that contain :class:`telegram.Audio`."""
"""Messages that contain :class:`telegram.Audio`."""
class _Document(BaseFilter):
name = 'Filters.document'
@@ -232,8 +430,8 @@ class Filters(object):
The user can manipulate the mime-type of a message and
send media with wrong types that don't fit to this handler.
Examples:
Filters.documents.category('audio/') returnes `True` for all types
Example:
Filters.documents.category('audio/') returns `True` for all types
of audio sent as file, for example 'audio/mpeg' or 'audio/x-wav'
"""
@@ -246,6 +444,7 @@ class Filters(object):
self.name = "Filters.document.category('{}')".format(self.category)
def filter(self, message):
"""""" # remove method from docs
if message.document:
return message.document.mime_type.startswith(self.category)
@@ -264,8 +463,8 @@ class Filters(object):
The user can manipulate the mime-type of a message and
send media with wrong types that don't fit to this handler.
Examples:
Filters.documents.mime_type('audio/mpeg') filters all audio in mp3 format.
Example:
``Filters.documents.mime_type('audio/mpeg')`` filters all audio in mp3 format.
"""
def __init__(self, mimetype):
@@ -277,6 +476,7 @@ class Filters(object):
self.name = "Filters.document.mime_type('{}')".format(self.mimetype)
def filter(self, message):
"""""" # remove method from docs
if message.document:
return message.document.mime_type == self.mimetype
@@ -300,7 +500,59 @@ class Filters(object):
return bool(message.document)
document = _Document()
""":obj:`Filter`: Messages that contain :class:`telegram.Document`."""
"""
Subset for messages containing a document/file.
Examples:
Use these filters like: ``Filters.document.mp3``,
``Filters.document.mime_type("text/plain")`` etc. Or use just
``Filters.document`` for all document messages.
Attributes:
category: This Filter filters documents by their category in the mime-type attribute
Note:
This Filter only filters by the mime_type of the document,
it doesn't check the validity of the document.
The user can manipulate the mime-type of a message and
send media with wrong types that don't fit to this handler.
Example:
``Filters.documents.category('audio/')`` filters all types
of audio sent as file, for example 'audio/mpeg' or 'audio/x-wav'
application: Same as ``Filters.document.category("application")``.
audio: Same as ``Filters.document.category("audio")``.
image: Same as ``Filters.document.category("image")``.
video: Same as ``Filters.document.category("video")``.
text: Same as ``Filters.document.category("text")``.
mime_type: This Filter filters documents by their mime-type attribute
Note:
This Filter only filters by the mime_type of the document,
it doesn't check the validity of document.
The user can manipulate the mime-type of a message and
send media with wrong types that don't fit to this handler.
Example:
``Filters.documents.mime_type('audio/mpeg')`` filters all audio in mp3 format.
apk: Same as ``Filters.document.mime_type("application/vnd.android.package-archive")``-
doc: Same as ``Filters.document.mime_type("application/msword")``-
docx: Same as ``Filters.document.mime_type("application/vnd.openxmlformats-\
officedocument.wordprocessingml.document")``-
exe: Same as ``Filters.document.mime_type("application/x-ms-dos-executable")``-
gif: Same as ``Filters.document.mime_type("video/mp4")``-
jpg: Same as ``Filters.document.mime_type("image/jpeg")``-
mp3: Same as ``Filters.document.mime_type("audio/mpeg")``-
pdf: Same as ``Filters.document.mime_type("application/pdf")``-
py: Same as ``Filters.document.mime_type("text/x-python")``-
svg: Same as ``Filters.document.mime_type("image/svg+xml")``-
txt: Same as ``Filters.document.mime_type("text/plain")``-
targz: Same as ``Filters.document.mime_type("application/x-compressed-tar")``-
wav: Same as ``Filters.document.mime_type("audio/x-wav")``-
xml: Same as ``Filters.document.mime_type("application/xml")``-
zip: Same as ``Filters.document.mime_type("application/zip")``-
"""
class _Animation(BaseFilter):
name = 'Filters.animation'
@@ -309,7 +561,7 @@ class Filters(object):
return bool(message.animation)
animation = _Animation()
""":obj:`Filter`: Messages that contain :class:`telegram.Animation`."""
"""Messages that contain :class:`telegram.Animation`."""
class _Photo(BaseFilter):
name = 'Filters.photo'
@@ -318,7 +570,7 @@ class Filters(object):
return bool(message.photo)
photo = _Photo()
""":obj:`Filter`: Messages that contain :class:`telegram.PhotoSize`."""
"""Messages that contain :class:`telegram.PhotoSize`."""
class _Sticker(BaseFilter):
name = 'Filters.sticker'
@@ -327,7 +579,7 @@ class Filters(object):
return bool(message.sticker)
sticker = _Sticker()
""":obj:`Filter`: Messages that contain :class:`telegram.Sticker`."""
"""Messages that contain :class:`telegram.Sticker`."""
class _Video(BaseFilter):
name = 'Filters.video'
@@ -336,7 +588,7 @@ class Filters(object):
return bool(message.video)
video = _Video()
""":obj:`Filter`: Messages that contain :class:`telegram.Video`."""
"""Messages that contain :class:`telegram.Video`."""
class _Voice(BaseFilter):
name = 'Filters.voice'
@@ -345,7 +597,7 @@ class Filters(object):
return bool(message.voice)
voice = _Voice()
""":obj:`Filter`: Messages that contain :class:`telegram.Voice`."""
"""Messages that contain :class:`telegram.Voice`."""
class _VideoNote(BaseFilter):
name = 'Filters.video_note'
@@ -354,7 +606,7 @@ class Filters(object):
return bool(message.video_note)
video_note = _VideoNote()
""":obj:`Filter`: Messages that contain :class:`telegram.VideoNote`."""
"""Messages that contain :class:`telegram.VideoNote`."""
class _Contact(BaseFilter):
name = 'Filters.contact'
@@ -363,7 +615,7 @@ class Filters(object):
return bool(message.contact)
contact = _Contact()
""":obj:`Filter`: Messages that contain :class:`telegram.Contact`."""
"""Messages that contain :class:`telegram.Contact`."""
class _Location(BaseFilter):
name = 'Filters.location'
@@ -372,7 +624,7 @@ class Filters(object):
return bool(message.location)
location = _Location()
""":obj:`Filter`: Messages that contain :class:`telegram.Location`."""
"""Messages that contain :class:`telegram.Location`."""
class _Venue(BaseFilter):
name = 'Filters.venue'
@@ -381,7 +633,7 @@ class Filters(object):
return bool(message.venue)
venue = _Venue()
""":obj:`Filter`: Messages that contain :class:`telegram.Venue`."""
"""Messages that contain :class:`telegram.Venue`."""
class _StatusUpdate(BaseFilter):
"""Subset for messages containing a status update.
@@ -391,6 +643,7 @@ class Filters(object):
``Filters.status_update`` for all status update messages.
"""
update_filter = True
class _NewChatMembers(BaseFilter):
name = 'Filters.status_update.new_chat_members'
@@ -399,7 +652,7 @@ class Filters(object):
return bool(message.new_chat_members)
new_chat_members = _NewChatMembers()
""":obj:`Filter`: Messages that contain :attr:`telegram.Message.new_chat_members`."""
"""Messages that contain :attr:`telegram.Message.new_chat_members`."""
class _LeftChatMember(BaseFilter):
name = 'Filters.status_update.left_chat_member'
@@ -408,7 +661,7 @@ class Filters(object):
return bool(message.left_chat_member)
left_chat_member = _LeftChatMember()
""":obj:`Filter`: Messages that contain :attr:`telegram.Message.left_chat_member`."""
"""Messages that contain :attr:`telegram.Message.left_chat_member`."""
class _NewChatTitle(BaseFilter):
name = 'Filters.status_update.new_chat_title'
@@ -417,7 +670,7 @@ class Filters(object):
return bool(message.new_chat_title)
new_chat_title = _NewChatTitle()
""":obj:`Filter`: Messages that contain :attr:`telegram.Message.new_chat_title`."""
"""Messages that contain :attr:`telegram.Message.new_chat_title`."""
class _NewChatPhoto(BaseFilter):
name = 'Filters.status_update.new_chat_photo'
@@ -426,7 +679,7 @@ class Filters(object):
return bool(message.new_chat_photo)
new_chat_photo = _NewChatPhoto()
""":obj:`Filter`: Messages that contain :attr:`telegram.Message.new_chat_photo`."""
"""Messages that contain :attr:`telegram.Message.new_chat_photo`."""
class _DeleteChatPhoto(BaseFilter):
name = 'Filters.status_update.delete_chat_photo'
@@ -435,17 +688,17 @@ class Filters(object):
return bool(message.delete_chat_photo)
delete_chat_photo = _DeleteChatPhoto()
""":obj:`Filter`: Messages that contain :attr:`telegram.Message.delete_chat_photo`."""
"""Messages that contain :attr:`telegram.Message.delete_chat_photo`."""
class _ChatCreated(BaseFilter):
name = 'Filters.status_update.chat_created'
def filter(self, message):
return bool(message.group_chat_created or message.supergroup_chat_created or
message.channel_chat_created)
return bool(message.group_chat_created or message.supergroup_chat_created
or message.channel_chat_created)
chat_created = _ChatCreated()
""":obj:`Filter`: Messages that contain :attr:`telegram.Message.group_chat_created`,
"""Messages that contain :attr:`telegram.Message.group_chat_created`,
:attr: `telegram.Message.supergroup_chat_created` or
:attr: `telegram.Message.channel_chat_created`."""
@@ -456,7 +709,7 @@ class Filters(object):
return bool(message.migrate_from_chat_id or message.migrate_to_chat_id)
migrate = _Migrate()
""":obj:`Filter`: Messages that contain :attr:`telegram.Message.migrate_from_chat_id` or
"""Messages that contain :attr:`telegram.Message.migrate_from_chat_id` or
:attr: `telegram.Message.migrate_to_chat_id`."""
class _PinnedMessage(BaseFilter):
@@ -466,7 +719,7 @@ class Filters(object):
return bool(message.pinned_message)
pinned_message = _PinnedMessage()
""":obj:`Filter`: Messages that contain :attr:`telegram.Message.pinned_message`."""
"""Messages that contain :attr:`telegram.Message.pinned_message`."""
class _ConnectedWebsite(BaseFilter):
name = 'Filters.status_update.connected_website'
@@ -475,16 +728,16 @@ class Filters(object):
return bool(message.connected_website)
connected_website = _ConnectedWebsite()
""":obj:`Filter`: Messages that contain :attr:`telegram.Message.connected_website`."""
"""Messages that contain :attr:`telegram.Message.connected_website`."""
name = 'Filters.status_update'
def filter(self, message):
return bool(self.new_chat_members(message) or self.left_chat_member(message) or
self.new_chat_title(message) or self.new_chat_photo(message) or
self.delete_chat_photo(message) or self.chat_created(message) or
self.migrate(message) or self.pinned_message(message) or
self.connected_website(message))
return bool(self.new_chat_members(message) or self.left_chat_member(message)
or self.new_chat_title(message) or self.new_chat_photo(message)
or self.delete_chat_photo(message) or self.chat_created(message)
or self.migrate(message) or self.pinned_message(message)
or self.connected_website(message))
status_update = _StatusUpdate()
"""Subset for messages containing a status update.
@@ -494,24 +747,24 @@ class Filters(object):
``Filters.status_update`` for all status update messages.
Attributes:
chat_created (:obj:`Filter`): Messages that contain
chat_created: Messages that contain
:attr:`telegram.Message.group_chat_created`,
:attr:`telegram.Message.supergroup_chat_created` or
:attr:`telegram.Message.channel_chat_created`.
delete_chat_photo (:obj:`Filter`): Messages that contain
delete_chat_photo: Messages that contain
:attr:`telegram.Message.delete_chat_photo`.
left_chat_member (:obj:`Filter`): Messages that contain
left_chat_member: Messages that contain
:attr:`telegram.Message.left_chat_member`.
migrate (:obj:`Filter`): Messages that contain
migrate: Messages that contain
:attr:`telegram.Message.migrate_from_chat_id` or
:attr: `telegram.Message.migrate_from_chat_id`.
new_chat_members (:obj:`Filter`): Messages that contain
new_chat_members: Messages that contain
:attr:`telegram.Message.new_chat_members`.
new_chat_photo (:obj:`Filter`): Messages that contain
new_chat_photo: Messages that contain
:attr:`telegram.Message.new_chat_photo`.
new_chat_title (:obj:`Filter`): Messages that contain
new_chat_title: Messages that contain
:attr:`telegram.Message.new_chat_title`.
pinned_message (:obj:`Filter`): Messages that contain
pinned_message: Messages that contain
:attr:`telegram.Message.pinned_message`.
"""
@@ -522,7 +775,7 @@ class Filters(object):
return bool(message.forward_date)
forwarded = _Forwarded()
""":obj:`Filter`: Messages that are forwarded."""
"""Messages that are forwarded."""
class _Game(BaseFilter):
name = 'Filters.game'
@@ -531,7 +784,7 @@ class Filters(object):
return bool(message.game)
game = _Game()
""":obj:`Filter`: Messages that contain :class:`telegram.Game`."""
"""Messages that contain :class:`telegram.Game`."""
class entity(BaseFilter):
"""
@@ -552,6 +805,7 @@ class Filters(object):
self.name = 'Filters.entity({})'.format(self.entity_type)
def filter(self, message):
"""""" # remove method from docs
return any(entity.type == self.entity_type for entity in message.entities)
class caption_entity(BaseFilter):
@@ -573,6 +827,7 @@ class Filters(object):
self.name = 'Filters.caption_entity({})'.format(self.entity_type)
def filter(self, message):
"""""" # remove method from docs
return any(entity.type == self.entity_type for entity in message.caption_entities)
class _Private(BaseFilter):
@@ -582,7 +837,7 @@ class Filters(object):
return message.chat.type == Chat.PRIVATE
private = _Private()
""":obj:`Filter`: Messages sent in a private chat."""
"""Messages sent in a private chat."""
class _Group(BaseFilter):
name = 'Filters.group'
@@ -591,7 +846,7 @@ class Filters(object):
return message.chat.type in [Chat.GROUP, Chat.SUPERGROUP]
group = _Group()
""":obj:`Filter`: Messages sent in a group chat."""
"""Messages sent in a group chat."""
class user(BaseFilter):
"""Filters messages to allow only those which are from specified user ID.
@@ -624,12 +879,13 @@ class Filters(object):
self.usernames = [user.replace('@', '') for user in username]
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 and
message.from_user.username in self.usernames)
return bool(message.from_user and message.from_user.username
and message.from_user.username in self.usernames)
class chat(BaseFilter):
"""Filters messages to allow only those which are from specified chat ID.
@@ -662,6 +918,7 @@ class Filters(object):
self.usernames = [chat.replace('@', '') for chat in username]
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:
@@ -675,7 +932,7 @@ class Filters(object):
return bool(message.invoice)
invoice = _Invoice()
""":obj:`Filter`: Messages that contain :class:`telegram.Invoice`."""
"""Messages that contain :class:`telegram.Invoice`."""
class _SuccessfulPayment(BaseFilter):
name = 'Filters.successful_payment'
@@ -684,7 +941,7 @@ class Filters(object):
return bool(message.successful_payment)
successful_payment = _SuccessfulPayment()
""":obj:`Filter`: Messages that confirm a :class:`telegram.SuccessfulPayment`."""
"""Messages that confirm a :class:`telegram.SuccessfulPayment`."""
class _PassportData(BaseFilter):
name = 'Filters.passport_data'
@@ -693,13 +950,65 @@ class Filters(object):
return bool(message.passport_data)
passport_data = _PassportData()
""":obj:`Filter`: Messages that contain a :class:`telegram.PassportData`"""
"""Messages that contain a :class:`telegram.PassportData`"""
class _Poll(BaseFilter):
name = 'Filters.poll'
def filter(self, message):
return bool(message.poll)
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)
dice = _Dice()
"""Dice Messages. If an integer or a list of integers is passed, it filters messages to only
allow those whose dice value is appearing in the given list.
Examples:
To allow any dice message, simply use
``MessageHandler(Filters.dice, callback_method)``.
To allow only dice with value 6, use
``MessageHandler(Filters.dice(6), callback_method)``.
To allow only dice with value 5 `or` 6, use
``MessageHandler(Filters.dice([5, 6]), callback_method)``.
Args:
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``.
"""
class language(BaseFilter):
"""Filters messages to only allow those which are from users with a certain language code.
Note: According to telegrams documentation, every single user does not have the
`language_code` attribute.
Note:
According to official telegram api documentation, not every single user has the
`language_code` attribute. Do not count on this filter working on all users.
Examples:
``MessageHandler(Filters.language("en"), callback_method)``
@@ -719,5 +1028,87 @@ class Filters(object):
self.name = 'Filters.language({})'.format(self.lang)
def filter(self, message):
"""""" # remove method from docs
return message.from_user.language_code and any(
[message.from_user.language_code.startswith(x) for x in self.lang])
class _UpdateType(BaseFilter):
update_filter = True
name = 'Filters.update'
class _Message(BaseFilter):
name = 'Filters.update.message'
update_filter = True
def filter(self, update):
return update.message is not None
message = _Message()
class _EditedMessage(BaseFilter):
name = 'Filters.update.edited_message'
update_filter = True
def filter(self, update):
return update.edited_message is not None
edited_message = _EditedMessage()
class _Messages(BaseFilter):
name = 'Filters.update.messages'
update_filter = True
def filter(self, update):
return update.message is not None or update.edited_message is not None
messages = _Messages()
class _ChannelPost(BaseFilter):
name = 'Filters.update.channel_post'
update_filter = True
def filter(self, update):
return update.channel_post is not None
channel_post = _ChannelPost()
class _EditedChannelPost(BaseFilter):
update_filter = True
name = 'Filters.update.edited_channel_post'
def filter(self, update):
return update.edited_channel_post is not None
edited_channel_post = _EditedChannelPost()
class _ChannelPosts(BaseFilter):
update_filter = True
name = 'Filters.update.channel_posts'
def filter(self, update):
return update.channel_post is not None or update.edited_channel_post is not None
channel_posts = _ChannelPosts()
def filter(self, update):
return self.messages(update) or self.channel_posts(update)
update = _UpdateType()
"""Subset for filtering the type of update.
Examples:
Use these filters like: ``Filters.update.message`` or
``Filters.update.channel_posts`` etc. Or use just ``Filters.update`` for all
types.
Attributes:
message: Updates with :attr:`telegram.Update.message`
edited_message: Updates with :attr:`telegram.Update.edited_message`
messages: Updates with either :attr:`telegram.Update.message` or
:attr:`telegram.Update.edited_message`
channel_post: Updates with :attr:`telegram.Update.channel_post`
edited_channel_post: Updates with
:attr:`telegram.Update.edited_channel_post`
channel_posts: Updates with either :attr:`telegram.Update.channel_post` or
:attr:`telegram.Update.edited_channel_post`
"""
+62 -26
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2018
# Copyright (C) 2015-2020
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
@@ -24,13 +24,13 @@ class Handler(object):
Attributes:
callback (:obj:`callable`): The callback function for this handler.
pass_update_queue (:obj:`bool`): Optional. Determines whether ``update_queue`` will be
pass_update_queue (:obj:`bool`): Determines whether ``update_queue`` will be
passed to the callback function.
pass_job_queue (:obj:`bool`): Optional. Determines whether ``job_queue`` will be passed to
pass_job_queue (:obj:`bool`): Determines whether ``job_queue`` will be passed to
the callback function.
pass_user_data (:obj:`bool`): Optional. Determines whether ``user_data`` will be passed to
pass_user_data (:obj:`bool`): Determines whether ``user_data`` will be passed to
the callback function.
pass_chat_data (:obj:`bool`): Optional. Determines whether ``chat_data`` will be passed to
pass_chat_data (:obj:`bool`): Determines whether ``chat_data`` will be passed to
the callback function.
Note:
@@ -39,22 +39,34 @@ class Handler(object):
either the user or the chat that the update was sent in. For each update from the same user
or in the same chat, it will be the same ``dict``.
Note that this is DEPRECATED, and you should use context based callbacks. See
https://git.io/fxJuV for more info.
Args:
callback (:obj:`callable`): A function that takes ``bot, update`` as positional arguments.
It will be called when the :attr:`check_update` has determined that an update should be
processed by this handler.
callback (:obj:`callable`): The callback function for this handler. Will be called when
:attr:`check_update` has determined that an update should be processed by this handler.
Callback signature for context based API:
``def callback(update: Update, context: CallbackContext)``
The return value of the callback is usually ignored except for the special case of
:class:`telegram.ext.ConversationHandler`.
pass_update_queue (:obj:`bool`, optional): If set to ``True``, a keyword argument called
``update_queue`` will be passed to the callback function. It will be the ``Queue``
instance used by the :class:`telegram.ext.Updater` and :class:`telegram.ext.Dispatcher`
that contains new updates which can be used to insert updates. Default is ``False``.
DEPRECATED: Please switch to context based callbacks.
pass_job_queue (:obj:`bool`, optional): If set to ``True``, a keyword argument called
``job_queue`` will be passed to the callback function. It will be a
:class:`telegram.ext.JobQueue` instance created by the :class:`telegram.ext.Updater`
which can be used to schedule new jobs. Default is ``False``.
DEPRECATED: Please switch to context based callbacks.
pass_user_data (:obj:`bool`, optional): If set to ``True``, a keyword argument called
``user_data`` will be passed to the callback function. Default is ``False``.
DEPRECATED: Please switch to context based callbacks.
pass_chat_data (:obj:`bool`, optional): If set to ``True``, a keyword argument called
``chat_data`` will be passed to the callback function. Default is ``False``.
DEPRECATED: Please switch to context based callbacks.
"""
@@ -79,31 +91,58 @@ class Handler(object):
update (:obj:`str` | :class:`telegram.Update`): The update to be tested.
Returns:
:obj:`bool`
Either ``None`` or ``False`` if the update should not be handled. Otherwise an object
that will be passed to :attr:`handle_update` and :attr:`collect_additional_context`
when the update gets handled.
"""
raise NotImplementedError
def handle_update(self, update, dispatcher):
def handle_update(self, update, dispatcher, check_result, context=None):
"""
This method is called if it was determined that an update should indeed
be handled by this instance. It should also be overridden, but in most
cases call ``self.callback(dispatcher.bot, update)``, possibly along with
optional arguments. To work with the ``ConversationHandler``, this method should return the
value returned from ``self.callback``
be handled by this instance. Calls :attr:`self.callback` along with its respectful
arguments. To work with the :class:`telegram.ext.ConversationHandler`, this method
returns the value returned from ``self.callback``.
Note that it can be overridden if needed by the subclassing handler.
Args:
update (:obj:`str` | :class:`telegram.Update`): The update to be handled.
dispatcher (:class:`telegram.ext.Dispatcher`): The dispatcher to collect optional args.
dispatcher (:class:`telegram.ext.Dispatcher`): The calling dispatcher.
check_result: The result from :attr:`check_update`.
"""
raise NotImplementedError
if context:
self.collect_additional_context(context, update, dispatcher, check_result)
return self.callback(update, context)
else:
optional_args = self.collect_optional_args(dispatcher, update, check_result)
return self.callback(dispatcher.bot, update, **optional_args)
def collect_optional_args(self, dispatcher, update=None):
"""Prepares the optional arguments that are the same for all types of handlers.
def collect_additional_context(self, context, update, dispatcher, check_result):
"""Prepares additional arguments for the context. Override if needed.
Args:
context (:class:`telegram.ext.CallbackContext`): The context object.
update (:class:`telegram.Update`): The update to gather chat/user id from.
dispatcher (:class:`telegram.ext.Dispatcher`): The calling dispatcher.
check_result: The result (return value) from :attr:`check_update`.
"""
pass
def collect_optional_args(self, dispatcher, update=None, check_result=None):
"""
Prepares the optional arguments. If the handler has additional optional args,
it should subclass this method, but remember to call this super method.
DEPRECATED: This method is being replaced by new context based callbacks. Please see
https://git.io/fxJuV for more info.
Args:
dispatcher (:class:`telegram.ext.Dispatcher`): The dispatcher.
update (:class:`telegram.Update`): The update to gather chat/user id from.
check_result: The result from check_update
"""
optional_args = dict()
@@ -112,14 +151,11 @@ class Handler(object):
optional_args['update_queue'] = dispatcher.update_queue
if self.pass_job_queue:
optional_args['job_queue'] = dispatcher.job_queue
if self.pass_user_data or self.pass_chat_data:
chat = update.effective_chat
if self.pass_user_data:
user = update.effective_user
if self.pass_user_data:
optional_args['user_data'] = dispatcher.user_data[user.id if user else None]
if self.pass_chat_data:
optional_args['chat_data'] = dispatcher.chat_data[chat.id if chat else None]
optional_args['user_data'] = dispatcher.user_data[user.id if user else None]
if self.pass_chat_data:
chat = update.effective_chat
optional_args['chat_data'] = dispatcher.chat_data[chat.id if chat else None]
return optional_args
+37 -32
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2018
# Copyright (C) 2015-2020
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
@@ -22,7 +22,6 @@ import re
from future.utils import string_types
from telegram import Update
from telegram.utils.deprecate import deprecate
from .handler import Handler
@@ -33,19 +32,19 @@ class InlineQueryHandler(Handler):
Attributes:
callback (:obj:`callable`): The callback function for this handler.
pass_update_queue (:obj:`bool`): Optional. Determines whether ``update_queue`` will be
pass_update_queue (:obj:`bool`): Determines whether ``update_queue`` will be
passed to the callback function.
pass_job_queue (:obj:`bool`): Optional. Determines whether ``job_queue`` will be passed to
pass_job_queue (:obj:`bool`): Determines whether ``job_queue`` will be passed to
the callback function.
pattern (:obj:`str` | :obj:`Pattern`): Optional. Regex pattern to test
:attr:`telegram.InlineQuery.query` against.
pass_groups (:obj:`bool`): Optional. Determines whether ``groups`` will be passed to the
pass_groups (:obj:`bool`): Determines whether ``groups`` will be passed to the
callback function.
pass_groupdict (:obj:`bool`): Optional. Determines whether ``groupdict``. will be passed to
pass_groupdict (:obj:`bool`): Determines whether ``groupdict``. will be passed to
the callback function.
pass_user_data (:obj:`bool`): Optional. Determines whether ``user_data`` will be passed to
pass_user_data (:obj:`bool`): Determines whether ``user_data`` will be passed to
the callback function.
pass_chat_data (:obj:`bool`): Optional. Determines whether ``chat_data`` will be passed to
pass_chat_data (:obj:`bool`): Determines whether ``chat_data`` will be passed to
the callback function.
Note:
@@ -54,31 +53,46 @@ class InlineQueryHandler(Handler):
either the user or the chat that the update was sent in. For each update from the same user
or in the same chat, it will be the same ``dict``.
Note that this is DEPRECATED, and you should use context based callbacks. See
https://git.io/fxJuV for more info.
Args:
callback (:obj:`callable`): A function that takes ``bot, update`` as positional arguments.
It will be called when the :attr:`check_update` has determined that an update should be
processed by this handler.
callback (:obj:`callable`): The callback function for this handler. Will be called when
:attr:`check_update` has determined that an update should be processed by this handler.
Callback signature for context based API:
``def callback(update: Update, context: CallbackContext)``
The return value of the callback is usually ignored except for the special case of
:class:`telegram.ext.ConversationHandler`.
pass_update_queue (:obj:`bool`, optional): If set to ``True``, a keyword argument called
``update_queue`` will be passed to the callback function. It will be the ``Queue``
instance used by the :class:`telegram.ext.Updater` and :class:`telegram.ext.Dispatcher`
that contains new updates which can be used to insert updates. Default is ``False``.
DEPRECATED: Please switch to context based callbacks.
pass_job_queue (:obj:`bool`, optional): If set to ``True``, a keyword argument called
``job_queue`` will be passed to the callback function. It will be a
:class:`telegram.ext.JobQueue` instance created by the :class:`telegram.ext.Updater`
which can be used to schedule new jobs. Default is ``False``.
DEPRECATED: Please switch to context based callbacks.
pattern (:obj:`str` | :obj:`Pattern`, optional): Regex pattern. If not ``None``,
``re.match`` is used on :attr:`telegram.InlineQuery.query` to determine if an update
should be handled by this handler.
pass_groups (:obj:`bool`, optional): If the callback should be passed the result of
``re.match(pattern, data).groups()`` as a keyword argument called ``groups``.
Default is ``False``
DEPRECATED: Please switch to context based callbacks.
pass_groupdict (:obj:`bool`, optional): If the callback should be passed the result of
``re.match(pattern, data).groupdict()`` as a keyword argument called ``groupdict``.
Default is ``False``
DEPRECATED: Please switch to context based callbacks.
pass_user_data (:obj:`bool`, optional): If set to ``True``, a keyword argument called
``user_data`` will be passed to the callback function. Default is ``False``.
DEPRECATED: Please switch to context based callbacks.
pass_chat_data (:obj:`bool`, optional): If set to ``True``, a keyword argument called
``chat_data`` will be passed to the callback function. Default is ``False``.
DEPRECATED: Please switch to context based callbacks.
"""
def __init__(self,
@@ -113,37 +127,28 @@ class InlineQueryHandler(Handler):
Returns:
:obj:`bool`
"""
if isinstance(update, Update) and update.inline_query:
if self.pattern:
if update.inline_query.query:
match = re.match(self.pattern, update.inline_query.query)
return bool(match)
if match:
return match
else:
return True
def handle_update(self, update, dispatcher):
"""
Send the update to the :attr:`callback`.
Args:
update (:class:`telegram.Update`): Incoming telegram update.
dispatcher (:class:`telegram.ext.Dispatcher`): Dispatcher that originated the Update.
"""
optional_args = self.collect_optional_args(dispatcher, update)
def collect_optional_args(self, dispatcher, update=None, check_result=None):
optional_args = super(InlineQueryHandler, self).collect_optional_args(dispatcher,
update, check_result)
if self.pattern:
match = re.match(self.pattern, update.inline_query.query)
if self.pass_groups:
optional_args['groups'] = match.groups()
optional_args['groups'] = check_result.groups()
if self.pass_groupdict:
optional_args['groupdict'] = match.groupdict()
optional_args['groupdict'] = check_result.groupdict()
return optional_args
return self.callback(dispatcher.bot, update, **optional_args)
# old non-PEP8 Handler methods
m = "telegram.InlineQueryHandler."
checkUpdate = deprecate(check_update, m + "checkUpdate", m + "check_update")
handleUpdate = deprecate(handle_update, m + "handleUpdate", m + "handle_update")
def collect_additional_context(self, context, update, dispatcher, check_result):
if self.pattern:
context.matches = [check_result]
+122 -62
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2018
# Copyright (C) 2015-2020
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
@@ -18,13 +18,18 @@
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains the classes JobQueue and Job."""
import datetime
import logging
import time
import datetime
import warnings
import weakref
from numbers import Number
from threading import Thread, Lock, Event
from queue import PriorityQueue, Empty
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
class Days(object):
@@ -37,16 +42,25 @@ class JobQueue(object):
Attributes:
_queue (:obj:`PriorityQueue`): The queue that holds the Jobs.
bot (:class:`telegram.Bot`): Bot that's send to the handlers.
Args:
bot (:class:`telegram.Bot`): The bot instance that should be passed to the jobs.
DEPRECATED: Use :attr:`set_dispatcher` instead.
"""
def __init__(self, bot):
def __init__(self, bot=None):
self._queue = PriorityQueue()
self.bot = bot
if bot:
warnings.warn("Passing bot to jobqueue is deprecated. Please use set_dispatcher "
"instead!", TelegramDeprecationWarning, stacklevel=2)
class MockDispatcher(object):
def __init__(self):
self.bot = bot
self.use_context = False
self._dispatcher = MockDispatcher()
else:
self._dispatcher = None
self.logger = logging.getLogger(self.__class__.__name__)
self.__start_lock = Lock()
self.__next_peek_lock = Lock() # to protect self._next_peek & self.__tick
@@ -55,30 +69,40 @@ class JobQueue(object):
self._next_peek = None
self._running = False
def _put(self, job, next_t=None, last_t=None):
if next_t is None:
next_t = job.interval
if next_t is None:
raise ValueError('next_t is None')
def set_dispatcher(self, dispatcher):
"""Set the dispatcher to be used by this JobQueue. Use this instead of passing a
:class:`telegram.Bot` to the JobQueue, which is deprecated.
if isinstance(next_t, datetime.datetime):
next_t = (next_t - datetime.datetime.now()).total_seconds()
Args:
dispatcher (:class:`telegram.ext.Dispatcher`): The dispatcher.
elif isinstance(next_t, datetime.time):
next_datetime = datetime.datetime.combine(datetime.date.today(), next_t)
"""
self._dispatcher = dispatcher
if datetime.datetime.now().time() > next_t:
next_datetime += datetime.timedelta(days=1)
def _put(self, job, time_spec=None, previous_t=None):
"""
Enqueues the job, scheduling its next run at the correct time.
next_t = (next_datetime - datetime.datetime.now()).total_seconds()
Args:
job (telegram.ext.Job): job to enqueue
time_spec (optional):
Specification of the time for which the job should be scheduled. The precise
semantics of this parameter depend on its type (see
:func:`telegram.ext.JobQueue.run_repeating` for details).
Defaults to now + ``job.interval``.
previous_t (optional):
Time at which the job last ran (``None`` if it hasn't run yet).
elif isinstance(next_t, datetime.timedelta):
next_t = next_t.total_seconds()
next_t += last_t or time.time()
self.logger.debug('Putting job %s with t=%f', job.name, next_t)
"""
# get time at which to run:
if time_spec is None:
time_spec = job.interval
if time_spec is None:
raise ValueError("no time specification given for scheduling non-repeating job")
next_t = to_float_timestamp(time_spec, reference_timestamp=previous_t)
# enqueue:
self.logger.debug('Putting job %s with t=%s', job.name, time_spec)
self._queue.put((next_t, job))
# Wake up the loop if this job should be executed next
@@ -89,9 +113,12 @@ class JobQueue(object):
Args:
callback (:obj:`callable`): The callback function that should be executed by the new
job. It should take ``bot, job`` as parameters, where ``job`` is the
:class:`telegram.ext.Job` instance. It can be used to access it's
``job.context`` or change it to a repeating job.
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:`int` | :obj:`float` | :obj:`datetime.timedelta` | \
:obj:`datetime.datetime` | :obj:`datetime.time`):
Time in or at which the job should run. This parameter will be interpreted
@@ -102,10 +129,11 @@ class JobQueue(object):
* :obj:`datetime.timedelta` will be interpreted as "time from now" in which the
job should run.
* :obj:`datetime.datetime` will be interpreted as a specific date and time at
which the job should run.
which the job should run. If the timezone (``datetime.tzinfo``) is ``None``, UTC
will be assumed.
* :obj:`datetime.time` will be interpreted as a specific time of day at which the
job should run. This could be either today or, if the time has already passed,
tomorrow.
tomorrow. If the timezone (``time.tzinfo``) is ``None``, 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``.
@@ -118,17 +146,20 @@ class JobQueue(object):
"""
job = Job(callback, repeat=False, context=context, name=name, job_queue=self)
self._put(job, next_t=when)
self._put(job, time_spec=when)
return job
def run_repeating(self, callback, interval, first=None, context=None, name=None):
"""Creates a new ``Job`` that runs once and adds it to the queue.
"""Creates a new ``Job`` that runs at specified intervals and adds it to the queue.
Args:
callback (:obj:`callable`): The callback function that should be executed by the new
job. It should take ``bot, job`` as parameters, where ``job`` is the
:class:`telegram.ext.Job` instance. It can be used to access it's
``Job.context`` or change it to a repeating job.
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.
interval (:obj:`int` | :obj:`float` | :obj:`datetime.timedelta`): The interval in which
the job will run. If it is an :obj:`int` or a :obj:`float`, it will be interpreted
as seconds.
@@ -142,10 +173,11 @@ class JobQueue(object):
* :obj:`datetime.timedelta` will be interpreted as "time from now" in which the
job should run.
* :obj:`datetime.datetime` will be interpreted as a specific date and time at
which the job should run.
which the job should run. If the timezone (``datetime.tzinfo``) is ``None``, UTC
will be assumed.
* :obj:`datetime.time` will be interpreted as a specific time of day at which the
job should run. This could be either today or, if the time has already passed,
tomorrow.
tomorrow. If the timezone (``time.tzinfo``) is ``None``, UTC will be assumed.
Defaults to ``interval``
context (:obj:`object`, optional): Additional data needed for the callback function.
@@ -157,6 +189,11 @@ class JobQueue(object):
:class:`telegram.ext.Job`: The new ``Job`` instance that has been added to the job
queue.
Notes:
`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.
"""
job = Job(callback,
interval=interval,
@@ -164,18 +201,22 @@ class JobQueue(object):
context=context,
name=name,
job_queue=self)
self._put(job, next_t=first)
self._put(job, time_spec=first)
return job
def run_daily(self, callback, time, days=Days.EVERY_DAY, context=None, name=None):
"""Creates a new ``Job`` that runs once and adds it to the queue.
"""Creates a new ``Job`` that runs on a daily basis and adds it to the queue.
Args:
callback (:obj:`callable`): The callback function that should be executed by the new
job. It should take ``bot, job`` as parameters, where ``job`` is the
:class:`telegram.ext.Job` instance. It can be used to access it's ``Job.context``
or change it to a repeating job.
time (:obj:`datetime.time`): Time of day at which the job should run.
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.
time (:obj:`datetime.time`): Time of day at which the job should run. If the timezone
(``time.tzinfo``) is ``None``, UTC will be assumed.
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.
@@ -187,15 +228,21 @@ class JobQueue(object):
:class:`telegram.ext.Job`: The new ``Job`` instance that has been added to the job
queue.
Notes:
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.
"""
job = Job(callback,
interval=datetime.timedelta(days=1),
repeat=True,
days=days,
tzinfo=time.tzinfo,
context=context,
name=name,
job_queue=self)
self._put(job, next_t=time)
self._put(job, time_spec=time)
return job
def _set_next_peek(self, t):
@@ -239,10 +286,11 @@ class JobQueue(object):
if job.enabled:
try:
current_week_day = datetime.datetime.now().weekday()
if any(day == current_week_day for day in job.days):
current_week_day = datetime.datetime.now(job.tzinfo).date().weekday()
if current_week_day in job.days:
self.logger.debug('Running job %s', job.name)
job.run(self.bot)
job.run(self._dispatcher)
self._dispatcher.update_persistence()
except Exception:
self.logger.exception('An uncaught error was raised while executing job %s',
@@ -251,7 +299,7 @@ class JobQueue(object):
self.logger.debug('Skipping disabled job %s', job.name)
if job.repeat and not job.removed:
self._put(job, last_t=t)
self._put(job, previous_t=t)
else:
self.logger.debug('Dropping non-repeating or removed job %s', job.name)
@@ -262,7 +310,8 @@ class JobQueue(object):
if not self._running:
self._running = True
self.__start_lock.release()
self.__thread = Thread(target=self._main_loop, name="job_queue")
self.__thread = Thread(target=self._main_loop,
name="Bot:{}:job_queue".format(self._dispatcher.bot.id))
self.__thread.start()
self.logger.debug('%s thread started', self.__class__.__name__)
else:
@@ -321,13 +370,17 @@ class Job(object):
Args:
callback (:obj:`callable`): The callback function that should be executed by the new job.
It should take ``bot, job`` as parameters, where ``job`` is the
:class:`telegram.ext.Job` instance. It can be used to access it's :attr:`context`
or change it to a repeating job.
interval (:obj:`int` | :obj:`float` | :obj:`datetime.timedelta`, optional): The interval in
which the job will run. If it is an :obj:`int` or a :obj:`float`, it will be
interpreted as seconds. If you don't set this value, you must set :attr:`repeat` to
``False`` and specify :attr:`next_t` when you put the job into the job queue.
Callback signature for context based API:
``def callback(CallbackContext)``
a ``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.
interval (:obj:`int` | :obj:`float` | :obj:`datetime.timedelta`, optional): The time
interval between executions of the job. If it is an :obj:`int` or a :obj:`float`,
it will be interpreted as seconds. If you don't set this value, you must set
:attr:`repeat` to ``False`` and specify :attr:`time_spec` when you put the job into
the job queue.
repeat (:obj:`bool`, optional): If this job should be periodically execute its callback
function (``True``) or only once (``False``). Defaults to ``True``.
context (:obj:`object`, optional): Additional data needed for the callback function. Can be
@@ -337,7 +390,9 @@ class Job(object):
Defaults to ``Days.EVERY_DAY``
job_queue (:class:`telegram.ext.JobQueue`, optional): The ``JobQueue`` this job belongs to.
Only optional for backward compatibility with ``JobQueue.put()``.
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.
"""
def __init__(self,
@@ -347,19 +402,21 @@ class Job(object):
context=None,
days=Days.EVERY_DAY,
name=None,
job_queue=None):
job_queue=None,
tzinfo=None):
self.callback = callback
self.context = context
self.name = name or callback.__name__
self._repeat = repeat
self._repeat = None
self._interval = None
self.interval = interval
self.repeat = repeat
self._days = None
self.days = days
self.tzinfo = tzinfo or _UTC
self._job_queue = weakref.proxy(job_queue) if job_queue is not None else None
@@ -367,9 +424,12 @@ class Job(object):
self._enabled = Event()
self._enabled.set()
def run(self, bot):
def run(self, dispatcher):
"""Executes the callback function."""
self.callback(bot, self)
if dispatcher.use_context:
self.callback(CallbackContext.from_job(self, dispatcher))
else:
self.callback(dispatcher.bot, self)
def schedule_removal(self):
"""
+75 -72
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2018
# Copyright (C) 2015-2020
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
@@ -20,7 +20,10 @@
"""This module contains the MessageHandler class."""
import warnings
from telegram.utils.deprecate import TelegramDeprecationWarning
from telegram import Update
from telegram.ext import Filters
from .handler import Handler
@@ -31,22 +34,20 @@ class MessageHandler(Handler):
filters (:obj:`Filter`): Only allow updates with these Filters. See
:mod:`telegram.ext.filters` for a full list of all available filters.
callback (:obj:`callable`): The callback function for this handler.
pass_update_queue (:obj:`bool`): Optional. Determines whether ``update_queue`` will be
pass_update_queue (:obj:`bool`): Determines whether ``update_queue`` will be
passed to the callback function.
pass_job_queue (:obj:`bool`): Optional. Determines whether ``job_queue`` will be passed to
pass_job_queue (:obj:`bool`): Determines whether ``job_queue`` will be passed to
the callback function.
pass_user_data (:obj:`bool`): Optional. Determines whether ``user_data`` will be passed to
pass_user_data (:obj:`bool`): Determines whether ``user_data`` will be passed to
the callback function.
pass_chat_data (:obj:`bool`): Optional. Determines whether ``chat_data`` will be passed to
pass_chat_data (:obj:`bool`): Determines whether ``chat_data`` will be passed to
the callback function.
message_updates (:obj:`bool`): Optional. Should "normal" message updates be handled?
Default is ``True``.
channel_post_updates (:obj:`bool`): Optional. Should channel posts updates be handled?
Default is ``True``.
edited_updates (:obj:`bool`): Optional. Should "edited" message updates be handled?
Default is ``False``.
allow_edited (:obj:`bool`): Optional. If the handler should also accept edited messages.
Default is ``False`` - Deprecated. use edited_updates instead.
message_updates (:obj:`bool`): Should "normal" message updates be handled?
Default is ``None``.
channel_post_updates (:obj:`bool`): Should channel posts updates be handled?
Default is ``None``.
edited_updates (:obj:`bool`): Should "edited" message updates be handled?
Default is ``None``.
Note:
:attr:`pass_user_data` and :attr:`pass_chat_data` determine whether a ``dict`` you
@@ -54,34 +55,51 @@ class MessageHandler(Handler):
either the user or the chat that the update was sent in. For each update from the same user
or in the same chat, it will be the same ``dict``.
Note that this is DEPRECATED, and you should use context based callbacks. See
https://git.io/fxJuV for more info.
Args:
filters (:class:`telegram.ext.BaseFilter`, optional): A filter inheriting from
:class:`telegram.ext.filters.BaseFilter`. Standard filters can be found in
:class:`telegram.ext.filters.Filters`. Filters can be combined using bitwise
operators (& for and, | for or, ~ for not).
callback (:obj:`callable`): A function that takes ``bot, update`` as positional arguments.
It will be called when the :attr:`check_update` has determined that an update should be
processed by this handler.
operators (& for and, | for or, ~ for not). Default is
:attr:`telegram.ext.filters.Filters.update`. This defaults to all message_type updates
being: ``message``, ``edited_message``, ``channel_post`` and ``edited_channel_post``.
If you don't want or need any of those pass ``~Filters.update.*`` in the filter
argument.
callback (:obj:`callable`): The callback function for this handler. Will be called when
:attr:`check_update` has determined that an update should be processed by this handler.
Callback signature for context based API:
``def callback(update: Update, context: CallbackContext)``
The return value of the callback is usually ignored except for the special case of
:class:`telegram.ext.ConversationHandler`.
pass_update_queue (:obj:`bool`, optional): If set to ``True``, a keyword argument called
``update_queue`` will be passed to the callback function. It will be the ``Queue``
instance used by the :class:`telegram.ext.Updater` and :class:`telegram.ext.Dispatcher`
that contains new updates which can be used to insert updates. Default is ``False``.
DEPRECATED: Please switch to context based callbacks.
pass_job_queue (:obj:`bool`, optional): If set to ``True``, a keyword argument called
``job_queue`` will be passed to the callback function. It will be a
:class:`telegram.ext.JobQueue` instance created by the :class:`telegram.ext.Updater`
which can be used to schedule new jobs. Default is ``False``.
DEPRECATED: Please switch to context based callbacks.
pass_user_data (:obj:`bool`, optional): If set to ``True``, a keyword argument called
``user_data`` will be passed to the callback function. Default is ``False``.
DEPRECATED: Please switch to context based callbacks.
pass_chat_data (:obj:`bool`, optional): If set to ``True``, a keyword argument called
``chat_data`` will be passed to the callback function. Default is ``False``.
DEPRECATED: Please switch to context based callbacks.
message_updates (:obj:`bool`, optional): Should "normal" message updates be handled?
Default is ``True``.
Default is ``None``.
DEPRECATED: Please switch to filters for update filtering.
channel_post_updates (:obj:`bool`, optional): Should channel posts updates be handled?
Default is ``True``.
Default is ``None``.
DEPRECATED: Please switch to filters for update filtering.
edited_updates (:obj:`bool`, optional): Should "edited" message updates be handled? Default
is ``False``.
allow_edited (:obj:`bool`, optional): If the handler should also accept edited messages.
Default is ``False`` - Deprecated. use edited_updates instead.
is ``None``.
DEPRECATED: Please switch to filters for update filtering.
Raises:
ValueError
@@ -91,20 +109,13 @@ class MessageHandler(Handler):
def __init__(self,
filters,
callback,
allow_edited=False,
pass_update_queue=False,
pass_job_queue=False,
pass_user_data=False,
pass_chat_data=False,
message_updates=True,
channel_post_updates=True,
edited_updates=False):
if not message_updates and not channel_post_updates and not edited_updates:
raise ValueError(
'message_updates, channel_post_updates and edited_updates are all False')
if allow_edited:
warnings.warn('allow_edited is getting deprecated, please use edited_updates instead')
edited_updates = allow_edited
message_updates=None,
channel_post_updates=None,
edited_updates=None):
super(MessageHandler, self).__init__(
callback,
@@ -112,22 +123,36 @@ class MessageHandler(Handler):
pass_job_queue=pass_job_queue,
pass_user_data=pass_user_data,
pass_chat_data=pass_chat_data)
if message_updates is False and channel_post_updates is False and edited_updates is False:
raise ValueError(
'message_updates, channel_post_updates and edited_updates are all False')
self.filters = filters
self.message_updates = message_updates
self.channel_post_updates = channel_post_updates
self.edited_updates = edited_updates
if self.filters is not None:
self.filters &= Filters.update
else:
self.filters = Filters.update
if message_updates is not None:
warnings.warn('message_updates is deprecated. See https://git.io/fxJuV for more info',
TelegramDeprecationWarning,
stacklevel=2)
if message_updates is False:
self.filters &= ~Filters.update.message
# We put this up here instead of with the rest of checking code
# in check_update since we don't wanna spam a ton
if isinstance(self.filters, list):
warnings.warn('Using a list of filters in MessageHandler is getting '
'deprecated, please use bitwise operators (& and |) '
'instead. More info: https://git.io/vPTbc.')
if channel_post_updates is not None:
warnings.warn('channel_post_updates is deprecated. See https://git.io/fxJuV '
'for more info',
TelegramDeprecationWarning,
stacklevel=2)
if channel_post_updates is False:
self.filters &= ~Filters.update.channel_post
def _is_allowed_update(self, update):
return any([self.message_updates and update.message,
self.edited_updates and (update.edited_message or update.edited_channel_post),
self.channel_post_updates and update.channel_post])
if edited_updates is not None:
warnings.warn('edited_updates is deprecated. See https://git.io/fxJuV for more info',
TelegramDeprecationWarning,
stacklevel=2)
if edited_updates is False:
self.filters &= ~(Filters.update.edited_message
| Filters.update.edited_channel_post)
def check_update(self, update):
"""Determines whether an update should be passed to this handlers :attr:`callback`.
@@ -139,31 +164,9 @@ class MessageHandler(Handler):
:obj:`bool`
"""
if isinstance(update, Update) and self._is_allowed_update(update):
if isinstance(update, Update) and update.effective_message:
return self.filters(update)
if not self.filters:
res = True
else:
message = update.effective_message
if isinstance(self.filters, list):
res = any(func(message) for func in self.filters)
else:
res = self.filters(message)
else:
res = False
return res
def handle_update(self, update, dispatcher):
"""Send the update to the :attr:`callback`.
Args:
update (:class:`telegram.Update`): Incoming telegram update.
dispatcher (:class:`telegram.ext.Dispatcher`): Dispatcher that originated the Update.
"""
optional_args = self.collect_optional_args(dispatcher, update)
return self.callback(dispatcher.bot, update, **optional_args)
def collect_additional_context(self, context, update, dispatcher, check_result):
if isinstance(check_result, dict):
context.update(check_result)
+2 -2
View File
@@ -4,7 +4,7 @@
# Tymofii A. Khodniev (thodnev) <thodnev@mail.ru>
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2018
# Copyright (C) 2015-2020
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
@@ -247,7 +247,7 @@ class MessageQueue(object):
is_group_msg (:obj:`bool`, optional): Defines whether ``promise`` would be processed in
group*+*all* ``DelayQueue``s (if set to ``True``), or only through *all*
``DelayQueue`` (if set to ``False``), resulting in needed delays to avoid
hitting specified limits. Defaults to ``True``.
hitting specified limits. Defaults to ``False``.
Notes:
Method is designed to accept ``telegram.utils.promise.Promise`` as ``promise``
+290
View File
@@ -0,0 +1,290 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2020
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains the PicklePersistence class."""
import pickle
from collections import defaultdict
from copy import deepcopy
from telegram.ext import BasePersistence
class PicklePersistence(BasePersistence):
"""Using python's builtin pickle for making you bot persistent.
Attributes:
filename (:obj:`str`): The filename for storing the pickle files. When :attr:`single_file`
is false this will be used as a prefix.
store_user_data (:obj:`bool`): Optional. Whether user_data should be saved by this
persistence class.
store_chat_data (:obj:`bool`): Optional. Whether user_data should be saved by this
persistence class.
store_bot_data (:obj:`bool`): Optional. Whether bot_data should be saved by this
persistence class.
single_file (:obj:`bool`): Optional. When ``False`` will store 3 sperate files of
`filename_user_data`, `filename_chat_data` and `filename_conversations`. Default is
``True``.
on_flush (:obj:`bool`, optional): When ``True`` will only save to file when :meth:`flush`
is called and keep data in memory until that happens. When ``False`` will store data
on any transaction *and* on call fo :meth:`flush`. Default is ``False``.
Args:
filename (:obj:`str`): The filename for storing the pickle files. When :attr:`single_file`
is false this will be used as a prefix.
store_user_data (:obj:`bool`, optional): Whether user_data should be saved by this
persistence class. Default is ``True``.
store_chat_data (:obj:`bool`, optional): Whether user_data should be saved by this
persistence class. Default is ``True``.
store_bot_data (:obj:`bool`, optional): Whether bot_data should be saved by this
persistence class. Default is ``True`` .
single_file (:obj:`bool`, optional): When ``False`` will store 3 sperate files of
`filename_user_data`, `filename_chat_data` and `filename_conversations`. Default is
``True``.
on_flush (:obj:`bool`, optional): When ``True`` will only save to file when :meth:`flush`
is called and keep data in memory until that happens. When ``False`` will store data
on any transaction *and* on call fo :meth:`flush`. Default is ``False``.
"""
def __init__(self, filename,
store_user_data=True,
store_chat_data=True,
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)
self.filename = filename
self.single_file = single_file
self.on_flush = on_flush
self.user_data = None
self.chat_data = None
self.bot_data = None
self.conversations = None
def load_singlefile(self):
try:
filename = self.filename
with open(self.filename, "rb") as f:
data = pickle.load(f)
self.user_data = defaultdict(dict, data['user_data'])
self.chat_data = defaultdict(dict, data['chat_data'])
# For backwards compatibility with files not containing bot data
self.bot_data = data.get('bot_data', {})
self.conversations = data['conversations']
except IOError:
self.conversations = {}
self.user_data = defaultdict(dict)
self.chat_data = defaultdict(dict)
self.bot_data = {}
except pickle.UnpicklingError:
raise TypeError("File {} does not contain valid pickle data".format(filename))
except Exception:
raise TypeError("Something went wrong unpickling {}".format(filename))
def load_file(self, filename):
try:
with open(filename, "rb") as f:
return pickle.load(f)
except IOError:
return None
except pickle.UnpicklingError:
raise TypeError("File {} does not contain valid pickle data".format(filename))
except Exception:
raise TypeError("Something went wrong unpickling {}".format(filename))
def dump_singlefile(self):
with open(self.filename, "wb") as f:
data = {'conversations': self.conversations, 'user_data': self.user_data,
'chat_data': self.chat_data, 'bot_data': self.bot_data}
pickle.dump(data, f)
def dump_file(self, filename, data):
with open(filename, "wb") as f:
pickle.dump(data, f)
def get_user_data(self):
"""Returns the user_data from the pickle file if it exsists or an empty defaultdict.
Returns:
:obj:`defaultdict`: The restored user data.
"""
if self.user_data:
pass
elif not self.single_file:
filename = "{}_user_data".format(self.filename)
data = self.load_file(filename)
if not data:
data = defaultdict(dict)
else:
data = defaultdict(dict, data)
self.user_data = data
else:
self.load_singlefile()
return deepcopy(self.user_data)
def get_chat_data(self):
"""Returns the chat_data from the pickle file if it exsists or an empty defaultdict.
Returns:
:obj:`defaultdict`: The restored chat data.
"""
if self.chat_data:
pass
elif not self.single_file:
filename = "{}_chat_data".format(self.filename)
data = self.load_file(filename)
if not data:
data = defaultdict(dict)
else:
data = defaultdict(dict, data)
self.chat_data = data
else:
self.load_singlefile()
return deepcopy(self.chat_data)
def get_bot_data(self):
"""Returns the bot_data from the pickle file if it exsists or an empty dict.
Returns:
:obj:`defaultdict`: The restored bot data.
"""
if self.bot_data:
pass
elif not self.single_file:
filename = "{}_bot_data".format(self.filename)
data = self.load_file(filename)
if not data:
data = {}
self.bot_data = data
else:
self.load_singlefile()
return deepcopy(self.bot_data)
def get_conversations(self, name):
"""Returns the conversations from the pickle file if it exsists or an empty defaultdict.
Args:
name (:obj:`str`): The handlers name.
Returns:
:obj:`dict`: The restored conversations for the handler.
"""
if self.conversations:
pass
elif not self.single_file:
filename = "{}_conversations".format(self.filename)
data = self.load_file(filename)
if not data:
data = {name: {}}
self.conversations = data
else:
self.load_singlefile()
return self.conversations.get(name, {}).copy()
def update_conversation(self, name, key, new_state):
"""Will update the conversations for the given handler and depending on :attr:`on_flush`
save the pickle file.
Args:
name (:obj:`str`): The handlers name.
key (:obj:`tuple`): The key the state is changed for.
new_state (:obj:`tuple` | :obj:`any`): The new state for the given key.
"""
if self.conversations.setdefault(name, {}).get(key) == new_state:
return
self.conversations[name][key] = new_state
if not self.on_flush:
if not self.single_file:
filename = "{}_conversations".format(self.filename)
self.dump_file(filename, self.conversations)
else:
self.dump_singlefile()
def update_user_data(self, user_id, data):
"""Will update the user_data (if changed) and depending on :attr:`on_flush` save the
pickle file.
Args:
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].
"""
if self.user_data is None:
self.user_data = defaultdict(dict)
if self.user_data.get(user_id) == data:
return
self.user_data[user_id] = data
if not self.on_flush:
if not self.single_file:
filename = "{}_user_data".format(self.filename)
self.dump_file(filename, self.user_data)
else:
self.dump_singlefile()
def update_chat_data(self, chat_id, data):
"""Will update the chat_data (if changed) and depending on :attr:`on_flush` save the
pickle file.
Args:
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].
"""
if self.chat_data is None:
self.chat_data = defaultdict(dict)
if self.chat_data.get(chat_id) == data:
return
self.chat_data[chat_id] = data
if not self.on_flush:
if not self.single_file:
filename = "{}_chat_data".format(self.filename)
self.dump_file(filename, self.chat_data)
else:
self.dump_singlefile()
def update_bot_data(self, data):
"""Will update the bot_data (if changed) and depending on :attr:`on_flush` save the
pickle file.
Args:
data (:obj:`dict`): The :attr:`telegram.ext.dispatcher.bot_data`.
"""
if self.bot_data == data:
return
self.bot_data = data.copy()
if not self.on_flush:
if not self.single_file:
filename = "{}_bot_data".format(self.filename)
self.dump_file(filename, self.bot_data)
else:
self.dump_singlefile()
def flush(self):
""" Will save all data in memory to pickle file(s).
"""
if self.single_file:
if self.user_data or self.chat_data or self.conversations:
self.dump_singlefile()
else:
if self.user_data:
self.dump_file("{}_user_data".format(self.filename), self.user_data)
if self.chat_data:
self.dump_file("{}_chat_data".format(self.filename), self.chat_data)
if self.bot_data:
self.dump_file("{}_bot_data".format(self.filename), self.bot_data)
if self.conversations:
self.dump_file("{}_conversations".format(self.filename), self.conversations)
+85
View File
@@ -0,0 +1,85 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2019
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
from telegram import Update
from .handler import Handler
class PollAnswerHandler(Handler):
"""Handler class to handle Telegram updates that contain a poll answer.
Attributes:
callback (:obj:`callable`): The callback function for this handler.
pass_update_queue (:obj:`bool`): Determines whether ``update_queue`` will be
passed to the callback function.
pass_job_queue (:obj:`bool`): Determines whether ``job_queue`` will be passed to
the callback function.
pass_user_data (:obj:`bool`): Determines whether ``user_data`` will be passed to
the callback function.
pass_chat_data (:obj:`bool`): Determines whether ``chat_data`` will be passed to
the callback function.
Note:
:attr:`pass_user_data` and :attr:`pass_chat_data` determine whether a ``dict`` you
can use to keep any data in will be sent to the :attr:`callback` function. Related to
either the user or the chat that the update was sent in. For each update from the same user
or in the same chat, it will be the same ``dict``.
Note that this is DEPRECATED, and you should use context based callbacks. See
https://git.io/fxJuV for more info.
Args:
callback (:obj:`callable`): The callback function for this handler. Will be called when
:attr:`check_update` has determined that an update should be processed by this handler.
Callback signature for context based API:
``def callback(update: Update, context: CallbackContext)``
The return value of the callback is usually ignored except for the special case of
:class:`telegram.ext.ConversationHandler`.
pass_update_queue (:obj:`bool`, optional): If set to ``True``, a keyword argument called
``update_queue`` will be passed to the callback function. It will be the ``Queue``
instance used by the :class:`telegram.ext.Updater` and :class:`telegram.ext.Dispatcher`
that contains new updates which can be used to insert updates. Default is ``False``.
DEPRECATED: Please switch to context based callbacks.
pass_job_queue (:obj:`bool`, optional): If set to ``True``, a keyword argument called
``job_queue`` will be passed to the callback function. It will be a
:class:`telegram.ext.JobQueue` instance created by the :class:`telegram.ext.Updater`
which can be used to schedule new jobs. Default is ``False``.
DEPRECATED: Please switch to context based callbacks.
pass_user_data (:obj:`bool`, optional): If set to ``True``, a keyword argument called
``user_data`` will be passed to the callback function. Default is ``False``.
DEPRECATED: Please switch to context based callbacks.
pass_chat_data (:obj:`bool`, optional): If set to ``True``, a keyword argument called
``chat_data`` will be passed to the callback function. Default is ``False``.
DEPRECATED: Please switch to context based callbacks.
"""
def check_update(self, update):
"""Determines whether an update should be passed to this handlers :attr:`callback`.
Args:
update (:class:`telegram.Update`): Incoming telegram update.
Returns:
:obj:`bool`
"""
return isinstance(update, Update) and update.poll_answer
+85
View File
@@ -0,0 +1,85 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2019
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
from telegram import Update
from .handler import Handler
class PollHandler(Handler):
"""Handler class to handle Telegram updates that contain a poll.
Attributes:
callback (:obj:`callable`): The callback function for this handler.
pass_update_queue (:obj:`bool`): Determines whether ``update_queue`` will be
passed to the callback function.
pass_job_queue (:obj:`bool`): Determines whether ``job_queue`` will be passed to
the callback function.
pass_user_data (:obj:`bool`): Determines whether ``user_data`` will be passed to
the callback function.
pass_chat_data (:obj:`bool`): Determines whether ``chat_data`` will be passed to
the callback function.
Note:
:attr:`pass_user_data` and :attr:`pass_chat_data` determine whether a ``dict`` you
can use to keep any data in will be sent to the :attr:`callback` function. Related to
either the user or the chat that the update was sent in. For each update from the same user
or in the same chat, it will be the same ``dict``.
Note that this is DEPRECATED, and you should use context based callbacks. See
https://git.io/fxJuV for more info.
Args:
callback (:obj:`callable`): The callback function for this handler. Will be called when
:attr:`check_update` has determined that an update should be processed by this handler.
Callback signature for context based API:
``def callback(update: Update, context: CallbackContext)``
The return value of the callback is usually ignored except for the special case of
:class:`telegram.ext.ConversationHandler`.
pass_update_queue (:obj:`bool`, optional): If set to ``True``, a keyword argument called
``update_queue`` will be passed to the callback function. It will be the ``Queue``
instance used by the :class:`telegram.ext.Updater` and :class:`telegram.ext.Dispatcher`
that contains new updates which can be used to insert updates. Default is ``False``.
DEPRECATED: Please switch to context based callbacks.
pass_job_queue (:obj:`bool`, optional): If set to ``True``, a keyword argument called
``job_queue`` will be passed to the callback function. It will be a
:class:`telegram.ext.JobQueue` instance created by the :class:`telegram.ext.Updater`
which can be used to schedule new jobs. Default is ``False``.
DEPRECATED: Please switch to context based callbacks.
pass_user_data (:obj:`bool`, optional): If set to ``True``, a keyword argument called
``user_data`` will be passed to the callback function. Default is ``False``.
DEPRECATED: Please switch to context based callbacks.
pass_chat_data (:obj:`bool`, optional): If set to ``True``, a keyword argument called
``chat_data`` will be passed to the callback function. Default is ``False``.
DEPRECATED: Please switch to context based callbacks.
"""
def check_update(self, update):
"""Determines whether an update should be passed to this handlers :attr:`callback`.
Args:
update (:class:`telegram.Update`): Incoming telegram update.
Returns:
:obj:`bool`
"""
return isinstance(update, Update) and update.poll
+20 -32
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2018
# Copyright (C) 2015-2020
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
@@ -27,13 +27,13 @@ class PreCheckoutQueryHandler(Handler):
Attributes:
callback (:obj:`callable`): The callback function for this handler.
pass_update_queue (:obj:`bool`): Optional. Determines whether ``update_queue`` will be
pass_update_queue (:obj:`bool`): Determines whether ``update_queue`` will be
passed to the callback function.
pass_job_queue (:obj:`bool`): Optional. Determines whether ``job_queue`` will be passed to
pass_job_queue (:obj:`bool`): Determines whether ``job_queue`` will be passed to
the callback function.
pass_user_data (:obj:`bool`): Optional. Determines whether ``user_data`` will be passed to
pass_user_data (:obj:`bool`): Determines whether ``user_data`` will be passed to
the callback function.
pass_chat_data (:obj:`bool`): Optional. Determines whether ``chat_data`` will be passed to
pass_chat_data (:obj:`bool`): Determines whether ``chat_data`` will be passed to
the callback function.
Note:
@@ -42,38 +42,37 @@ class PreCheckoutQueryHandler(Handler):
either the user or the chat that the update was sent in. For each update from the same user
or in the same chat, it will be the same ``dict``.
Note that this is DEPRECATED, and you should use context based callbacks. See
https://git.io/fxJuV for more info.
Args:
callback (:obj:`callable`): A function that takes ``bot, update`` as positional arguments.
It will be called when the :attr:`check_update` has determined that an update should be
processed by this handler.
callback (:obj:`callable`): The callback function for this handler. Will be called when
:attr:`check_update` has determined that an update should be processed by this handler.
Callback signature for context based API:
``def callback(update: Update, context: CallbackContext)``
The return value of the callback is usually ignored except for the special case of
:class:`telegram.ext.ConversationHandler`.
pass_update_queue (:obj:`bool`, optional): If set to ``True``, a keyword argument called
``update_queue`` will be passed to the callback function. It will be the ``Queue``
DEPRECATED: Please switch to context based callbacks.
instance used by the :class:`telegram.ext.Updater` and :class:`telegram.ext.Dispatcher`
that contains new updates which can be used to insert updates. Default is ``False``.
pass_job_queue (:obj:`bool`, optional): If set to ``True``, a keyword argument called
``job_queue`` will be passed to the callback function. It will be a
:class:`telegram.ext.JobQueue` instance created by the :class:`telegram.ext.Updater`
which can be used to schedule new jobs. Default is ``False``.
DEPRECATED: Please switch to context based callbacks.
pass_user_data (:obj:`bool`, optional): If set to ``True``, a keyword argument called
``user_data`` will be passed to the callback function. Default is ``False``.
DEPRECATED: Please switch to context based callbacks.
pass_chat_data (:obj:`bool`, optional): If set to ``True``, a keyword argument called
``chat_data`` will be passed to the callback function. Default is ``False``.
DEPRECATED: Please switch to context based callbacks.
"""
def __init__(self,
callback,
pass_update_queue=False,
pass_job_queue=False,
pass_user_data=False,
pass_chat_data=False):
super(PreCheckoutQueryHandler, self).__init__(
callback,
pass_update_queue=pass_update_queue,
pass_job_queue=pass_job_queue,
pass_user_data=pass_user_data,
pass_chat_data=pass_chat_data)
def check_update(self, update):
"""Determines whether an update should be passed to this handlers :attr:`callback`.
@@ -85,14 +84,3 @@ class PreCheckoutQueryHandler(Handler):
"""
return isinstance(update, Update) and update.pre_checkout_query
def handle_update(self, update, dispatcher):
"""Send the update to the :attr:`callback`.
Args:
update (:class:`telegram.Update`): Incoming telegram update.
dispatcher (:class:`telegram.ext.Dispatcher`): Dispatcher that originated the Update.
"""
optional_args = self.collect_optional_args(dispatcher, update)
return self.callback(dispatcher.bot, update, **optional_args)

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